diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml new file mode 100644 index 0000000000..91dce35155 --- /dev/null +++ b/.config/cypress-devcontainer.yml @@ -0,0 +1,211 @@ +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# Misskey configuration +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +# ┌─────┐ +#───┘ URL └───────────────────────────────────────────────────── + +# Final accessible URL seen by a user. +url: 'http://misskey.local' + +# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE +# URL SETTINGS AFTER THAT! + +# ┌───────────────────────┐ +#───┘ Port and TLS settings └─────────────────────────────────── + +# +# Misskey requires a reverse proxy to support HTTPS connections. +# +# +----- https://example.tld/ ------------+ +# +------+ |+-------------+ +----------------+| +# | User | ---> || Proxy (443) | ---> | Misskey (3000) || +# +------+ |+-------------+ +----------------+| +# +---------------------------------------+ +# +# You need to set up a reverse proxy. (e.g. nginx) +# An encrypted connection with HTTPS is highly recommended +# because tokens may be transferred in GET requests. + +# The port that your Misskey server should listen on. +port: 61812 + +# ┌──────────────────────────┐ +#───┘ PostgreSQL configuration └──────────────────────────────── + +db: + host: db + port: 5432 + + # Database name + db: misskey + + # Auth + user: postgres + pass: postgres + + # Whether disable Caching queries + #disableCache: true + + # Extra Connection options + #extra: + # ssl: true + +dbReplications: false + +# You can configure any number of replicas here +#dbSlaves: +# - +# host: +# port: +# db: +# user: +# pass: +# - +# host: +# port: +# db: +# user: +# pass: + +# ┌─────────────────────┐ +#───┘ Redis configuration └───────────────────────────────────── + +redis: + host: redis + port: 6379 + #family: 0 # 0=Both, 4=IPv4, 6=IPv6 + #pass: example-pass + #prefix: example-prefix + #db: 1 + +#redisForPubsub: +# host: redis +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 + +#redisForJobQueue: +# host: redis +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 + +#redisForTimelines: +# host: redis +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 + +#redisForReactions: +# host: redis +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 + +# ┌───────────────────────────┐ +#───┘ MeiliSearch configuration └───────────────────────────── + +#meilisearch: +# host: meilisearch +# port: 7700 +# apiKey: '' +# ssl: true +# index: '' + +# ┌───────────────┐ +#───┘ ID generation └─────────────────────────────────────────── + +# You can select the ID generation method. +# You don't usually need to change this setting, but you can +# change it according to your preferences. + +# Available methods: +# aid ... Short, Millisecond accuracy +# aidx ... Millisecond accuracy +# meid ... Similar to ObjectID, Millisecond accuracy +# ulid ... Millisecond accuracy +# objectid ... This is left for backward compatibility + +# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE +# ID SETTINGS AFTER THAT! + +id: 'aidx' + +# ┌────────────────┐ +#───┘ Error tracking └────────────────────────────────────────── + +# Sentry is available for error tracking. +# See the Sentry documentation for more details on options. + +#sentryForBackend: +# enableNodeProfiling: true +# options: +# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' + +#sentryForFrontend: +# options: +# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' + +# ┌─────────────────────┐ +#───┘ Other configuration └───────────────────────────────────── + +# Whether disable HSTS +#disableHsts: true + +# Number of worker processes +#clusterLimit: 1 + +# Job concurrency per worker +# deliverJobConcurrency: 128 +# inboxJobConcurrency: 16 + +# Job rate limiter +# deliverJobPerSec: 128 +# inboxJobPerSec: 32 + +# Job attempts +# deliverJobMaxAttempts: 12 +# inboxJobMaxAttempts: 8 + +# IP address family used for outgoing request (ipv4, ipv6 or dual) +#outgoingAddressFamily: ipv4 + +# Proxy for HTTP/HTTPS +#proxy: http://127.0.0.1:3128 + +proxyBypassHosts: + - api.deepl.com + - api-free.deepl.com + - www.recaptcha.net + - hcaptcha.com + - challenges.cloudflare.com + +# Proxy for SMTP/SMTPS +#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT +#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4 +#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5 + +# Media Proxy +#mediaProxy: https://example.com/proxy + +# Proxy remote files (default: true) +proxyRemoteFiles: true + +# Sign to ActivityPub GET request (default: true) +signToActivityPubGet: true + +allowedPrivateNetworks: [ + '127.0.0.1/32' +] + +# Upload or download file size limits (bytes) +#maxFileSize: 262144000 diff --git a/.config/docker_example.yml b/.config/docker_example.yml index d347882d1a..3f8e5734ce 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -106,6 +106,14 @@ redis: # #prefix: example-prefix # #db: 1 +#redisForReactions: +# host: redis +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 + # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/.config/example.yml b/.config/example.yml index b11cbd1373..7080159117 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -172,6 +172,16 @@ redis: # # You can specify more ioredis options... # #username: example-username +#redisForReactions: +# host: localhost +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 +# # You can specify more ioredis options... +# #username: example-username + # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml index beefcfd0a2..3eb4fc2879 100644 --- a/.devcontainer/devcontainer.yml +++ b/.devcontainer/devcontainer.yml @@ -103,6 +103,14 @@ redis: # #prefix: example-prefix # #db: 1 +#redisForReactions: +# host: redis +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 + # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/.devcontainer/init.sh b/.devcontainer/init.sh index 55fb1e6fa6..e02a533c15 100755 --- a/.devcontainer/init.sh +++ b/.devcontainer/init.sh @@ -3,6 +3,8 @@ set -xe sudo chown node node_modules +sudo apt-get update +sudo apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6 xauth xvfb git config --global --add safe.directory /workspace git submodule update --init corepack install @@ -12,3 +14,4 @@ pnpm install --frozen-lockfile cp .devcontainer/devcontainer.yml .config/default.yml pnpm build pnpm migrate +pnpm exec cypress install diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml index e7db18316c..8380a3bb23 100644 --- a/.github/workflows/api-misskey-js.yml +++ b/.github/workflows/api-misskey-js.yml @@ -21,7 +21,7 @@ jobs: - run: corepack enable - name: Setup Node.js - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.0.4 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/changelog-check.yml b/.github/workflows/changelog-check.yml index d4e99f966e..44cc1a04f2 100644 --- a/.github/workflows/changelog-check.yml +++ b/.github/workflows/changelog-check.yml @@ -14,7 +14,7 @@ jobs: - name: Checkout head uses: actions/checkout@v4.1.1 - name: Setup Node.js - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.0.4 with: node-version-file: '.node-version' diff --git a/.github/workflows/check-misskey-js-autogen.yml b/.github/workflows/check-misskey-js-autogen.yml index 3a2a2d5f8d..5afd7d2714 100644 --- a/.github/workflows/check-misskey-js-autogen.yml +++ b/.github/workflows/check-misskey-js-autogen.yml @@ -28,7 +28,7 @@ jobs: - name: setup node id: setup-node - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.0.4 with: node-version-file: '.node-version' cache: pnpm diff --git a/.github/workflows/check-spdx-license-id.yml b/.github/workflows/check-spdx-license-id.yml index 6cd8bf60d5..05582008b5 100644 --- a/.github/workflows/check-spdx-license-id.yml +++ b/.github/workflows/check-spdx-license-id.yml @@ -48,12 +48,16 @@ jobs: "packages/backend/migration" "packages/backend/src" "packages/backend/test" + "packages/frontend-shared/@types" + "packages/frontend-shared/js" "packages/frontend/.storybook" "packages/frontend/@types" "packages/frontend/lib" "packages/frontend/public" "packages/frontend/src" "packages/frontend/test" + "packages/frontend-embed/@types" + "packages/frontend-embed/src" "packages/misskey-bubble-game/src" "packages/misskey-reversi/src" "packages/sw/src" diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml index 81e8134fb7..1bcaa0d9c4 100644 --- a/.github/workflows/get-api-diff.yml +++ b/.github/workflows/get-api-diff.yml @@ -33,7 +33,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.0.4 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 222a14d28d..07d9af12f7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,16 +8,24 @@ on: paths: - packages/backend/** - packages/frontend/** + - packages/frontend-shared/** + - packages/frontend-embed/** - packages/sw/** - packages/misskey-js/** + - packages/misskey-bubble-game/** + - packages/misskey-reversi/** - packages/shared/eslint.config.js - .github/workflows/lint.yml pull_request: paths: - packages/backend/** - packages/frontend/** + - packages/frontend-shared/** + - packages/frontend-embed/** - packages/sw/** - packages/misskey-js/** + - packages/misskey-bubble-game/** + - packages/misskey-reversi/** - packages/shared/eslint.config.js - .github/workflows/lint.yml jobs: @@ -29,7 +37,7 @@ jobs: fetch-depth: 0 submodules: true - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4.0.3 + - uses: actions/setup-node@v4.0.4 with: node-version-file: '.node-version' cache: 'pnpm' @@ -45,8 +53,12 @@ jobs: workspace: - backend - frontend + - frontend-shared + - frontend-embed - sw - misskey-js + - misskey-bubble-game + - misskey-reversi env: eslint-cache-version: v1 eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }} @@ -56,7 +68,7 @@ jobs: fetch-depth: 0 submodules: true - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4.0.3 + - uses: actions/setup-node@v4.0.4 with: node-version-file: '.node-version' cache: 'pnpm' @@ -86,7 +98,7 @@ jobs: fetch-depth: 0 submodules: true - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4.0.3 + - uses: actions/setup-node@v4.0.4 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/locale.yml b/.github/workflows/locale.yml index 95251bfe31..6bc8860a11 100644 --- a/.github/workflows/locale.yml +++ b/.github/workflows/locale.yml @@ -19,7 +19,7 @@ jobs: fetch-depth: 0 submodules: true - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4.0.3 + - uses: actions/setup-node@v4.0.4 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/on-release-created.yml b/.github/workflows/on-release-created.yml index 8dd9ed2513..ffaf7bc038 100644 --- a/.github/workflows/on-release-created.yml +++ b/.github/workflows/on-release-created.yml @@ -26,7 +26,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.0.4 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/report-api-diff.yml b/.github/workflows/report-api-diff.yml index df9cc279e8..1170f898ce 100644 --- a/.github/workflows/report-api-diff.yml +++ b/.github/workflows/report-api-diff.yml @@ -70,18 +70,25 @@ jobs: - id: out-diff name: Build diff Comment run: | - cat <<- EOF > ./output.md - このPRによるapi.jsonの差分 - <details> - <summary>差分はこちら</summary> - - \`\`\`diff - $(cat ./api.json.diff) - \`\`\` - </details> - - [Get diff files from Workflow Page](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}) - EOF + HEADER="このPRによるapi.jsonの差分" + FOOTER="[Get diff files from Workflow Page](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})" + DIFF_BYTES="$(stat ./api.json.diff -c '%s' | tr -d '\n')" + + echo "$HEADER" > ./output.md + + if (( "$DIFF_BYTES" <= 1 )); then + echo '差分はありません。' >> ./output.md + else + echo '<details>' >> ./output.md + echo '<summary>差分はこちら</summary>' >> ./output.md + echo >> ./output.md + echo '```diff' >> ./output.md + cat ./api.json.diff >> ./output.md + echo '```' >> ./output.md + echo '</details>' >> .output.md + fi + + echo "$FOOTER" >> ./output.md - uses: thollander/actions-comment-pull-request@v2 with: pr_number: ${{ steps.load-pr-num.outputs.pr-number }} diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index bea93f1456..c02f38ee0b 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -41,7 +41,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js 20.x - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.0.4 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index 026550025c..d95d6676f9 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -46,7 +46,7 @@ jobs: - name: Install FFmpeg uses: FedericoCarboni/setup-ffmpeg@v3 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.0.4 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -93,7 +93,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.0.4 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index fcaef52969..c68e1a8ef1 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -35,7 +35,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.0.4 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -90,7 +90,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.0.4 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml index 9ad71919df..63e81f8c92 100644 --- a/.github/workflows/test-misskey-js.yml +++ b/.github/workflows/test-misskey-js.yml @@ -31,7 +31,7 @@ jobs: - run: corepack enable - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.0.4 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml index 8ad8a64766..0abc09c5a6 100644 --- a/.github/workflows/test-production.yml +++ b/.github/workflows/test-production.yml @@ -25,7 +25,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.0.4 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/validate-api-json.yml b/.github/workflows/validate-api-json.yml index 06e987f27e..f809af1063 100644 --- a/.github/workflows/validate-api-json.yml +++ b/.github/workflows/validate-api-json.yml @@ -27,7 +27,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.0.4 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.gitignore b/.gitignore index 0f896f4a98..b270d5cb3a 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ coverage !/.config/example.yml !/.config/docker_example.yml !/.config/docker_example.env +!/.config/cypress-devcontainer.yml docker-compose.yml compose.yml .devcontainer/compose.yml @@ -64,6 +65,10 @@ temp tsdoc-metadata.json misskey-assets +# Vite temporary files +vite.config.js.timestamp-* +vite.config.ts.timestamp-* + # blender backups *.blend1 *.blend2 diff --git a/CHANGELOG.md b/CHANGELOG.md index ea40797d80..0bfac44a89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,44 @@ -## Unreleased +## 2024.9.0 ### General -- +- Feat: UserWebhookとSystemWebhookのテスト送信機能を追加 (#14445) +- Feat: モデレーターはユーザーにかかわらずファイルが添付されているノートを検索できるように + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/680) +- Enhance: ユーザーによるコンテンツインポートの可否をロールポリシーで制御できるように ### Client -- サイズ制限を超過するファイルをアップロードしようとした際にエラーを出すように +- Feat: ノート単体・ユーザーのノート・クリップのノートの埋め込み機能 + - 埋め込みコードやウェブサイトへの実装方法の詳細は https://misskey-hub.net/docs/for-users/features/embed/ をご覧ください +- Enhance: サイズ制限を超過するファイルをアップロードしようとした際にエラーを出すように - Enhance: アイコンデコレーション管理画面にプレビューを追加 +- Enhance: コントロールパネル内のファイル一覧でセンシティブなファイルを区別しやすく +- Enhance: ScratchpadにUIインスペクターを追加 +- Enhance: Play編集画面の項目の並びを少しリデザイン - Fix: サーバーメトリクスが2つ以上あるとリロード直後の表示がおかしくなる問題を修正 +- Fix: コントロールパネル内のAp requests内のチャートの表示がおかしかった問題を修正 +- Fix: 月の違う同じ日はセパレータが表示されないのを修正 +- Fix: タッチ画面でレンジスライダーを操作するとツールチップが複数表示される問題を修正 + (Cherry-picked from https://github.com/taiyme/misskey/pull/265) +- Fix: 縦横比が極端なカスタム絵文字を表示する際にレイアウトが崩れる箇所があるのを修正 + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/725) +- Fix: 設定変更時のリロード確認ダイアログが複数個表示されることがある問題を修正 +- Fix: ファイルの詳細ページのファイルの説明で改行が正しく表示されない問題を修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/bde6bb0bd2e8b0d027e724d2acdb8ae0585a8110) +- Fix: 一部画面のページネーションが動作しにくくなっていたのを修正 ( #12766 , #11449 ) ### Server +- Feat: Misskey® Reactions Buffering Technology™ (RBT)により、リアクションの作成負荷を低減することが可能に - Enhance: 連合する必要のないプロフィール項目しか更新されなかった場合には連合先にUpdateアクティビティを発行しないように -- ファイルがサイズの制限を超えてアップロードされた際にエラーを返さなかった問題を修正 - +- Fix: アンテナの書き込み時にキーワードが与えられなかった場合のエラーをApiErrorとして投げるように + - この変更により、公式フロントエンドでは入力の不備が内部エラーとして報告される代わりに一般的なエラーダイアログで報告されます +- Fix: ファイルがサイズの制限を超えてアップロードされた際にエラーを返さなかった問題を修正 +- Fix: 外部ページを解析する際に、ページに紐づけられた関連リソースも読み込まれてしまう問題を修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/26e0412fbb91447c37e8fb06ffb0487346063bb8) +- Fix: Continue importing from file if single emoji import fails +- Fix: `Retry-After`ヘッダーが送信されなかった問題を修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/8a982c61c01909e7540ff1be9f019df07c3f0624) +- Fix: サーバーサイドのDOM解析完了時にリソースを開放するように + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/634) ## 2024.8.0 diff --git a/Dockerfile b/Dockerfile index e247bbcd77..e21b2a31fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,9 @@ WORKDIR /misskey COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"] COPY --link ["scripts", "./scripts"] COPY --link ["packages/backend/package.json", "./packages/backend/"] +COPY --link ["packages/frontend-shared/package.json", "./packages/frontend-shared/"] COPY --link ["packages/frontend/package.json", "./packages/frontend/"] +COPY --link ["packages/frontend-embed/package.json", "./packages/frontend-embed/"] COPY --link ["packages/sw/package.json", "./packages/sw/"] COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"] COPY --link ["packages/misskey-reversi/package.json", "./packages/misskey-reversi/"] diff --git a/chart/files/default.yml b/chart/files/default.yml index f98b8ebfee..4d17131c25 100644 --- a/chart/files/default.yml +++ b/chart/files/default.yml @@ -124,6 +124,14 @@ redis: # #prefix: example-prefix # #db: 1 +#redisForReactions: +# host: redis +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 + # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/locales/index.d.ts b/locales/index.d.ts index 9fd3441ab1..2a27eb3e15 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2384,6 +2384,14 @@ export interface Locale extends ILocale { * スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。 */ "scratchpadDescription": string; + /** + * UIインスペクター + */ + "uiInspector": string; + /** + * メモリ上に存在しているUIコンポーネントのインスタンスの一覧を見ることができます。UIコンポーネントはUi:C:系関数により生成されます。 + */ + "uiInspectorDescription": string; /** * 出力 */ @@ -3121,7 +3129,7 @@ export interface Locale extends ILocale { */ "narrow": string; /** - * 設定はページリロード後に反映されます。今すぐリロードしますか? + * 設定はページリロード後に反映されます。 */ "reloadToApplySetting": string; /** @@ -5068,10 +5076,38 @@ export interface Locale extends ILocale { * 作成したアンテナ */ "createdAntennas": string; + /** + * {x}から + */ + "fromX": ParameterizedString<"x">; + /** + * 埋め込みコードを生成 + */ + "genEmbedCode": string; + /** + * このユーザーのノート一覧 + */ + "noteOfThisUser": string; /** * これ以上このクリップにノートを追加できません。 */ "clipNoteLimitExceeded": string; + /** + * パフォーマンス + */ + "performance": string; + /** + * 変更あり + */ + "modified": string; + /** + * 破棄 + */ + "discard": string; + /** + * {n}件の変更があります + */ + "thereAreNChanges": ParameterizedString<"n">; "_delivery": { /** * 配信状態 @@ -5563,6 +5599,10 @@ export interface Locale extends ILocale { * 有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。 */ "fanoutTimelineDbFallbackDescription": string; + /** + * 有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。 + */ + "reactionsBufferingDescription": string; /** * 問い合わせ先URL */ @@ -6742,6 +6782,26 @@ export interface Locale extends ILocale { * アイコンデコレーションの最大取付個数 */ "avatarDecorationLimit": string; + /** + * アンテナのインポートを許可 + */ + "canImportAntennas": string; + /** + * ブロックのインポートを許可 + */ + "canImportBlocking": string; + /** + * フォローのインポートを許可 + */ + "canImportFollowing": string; + /** + * ミュートのインポートを許可 + */ + "canImportMuting": string; + /** + * リストのインポートを許可 + */ + "canImportUserLists": string; }; "_condition": { /** @@ -9465,6 +9525,10 @@ export interface Locale extends ILocale { * Webhookを削除しますか? */ "deleteConfirm": string; + /** + * スイッチの右にあるボタンをクリックするとダミーのデータを使用したテスト用Webhookを送信できます。 + */ + "testRemarks": string; }; "_abuseReport": { "_notificationRecipient": { @@ -10196,6 +10260,60 @@ export interface Locale extends ILocale { */ "native": string; }; + "_embedCodeGen": { + /** + * 埋め込みコードをカスタマイズ + */ + "title": string; + /** + * ヘッダーを表示 + */ + "header": string; + /** + * 自動で続きを読み込む(非推奨) + */ + "autoload": string; + /** + * 高さの最大値 + */ + "maxHeight": string; + /** + * 0で最大値の設定が無効になります。ウィジェットが縦に伸び続けるのを防ぐために、何らかの値に指定してください。 + */ + "maxHeightDescription": string; + /** + * 高さの最大値制限が無効(0)になっています。これが意図した変更ではない場合は、高さの最大値を何らかの値に設定してください。 + */ + "maxHeightWarn": string; + /** + * プレビュー画面で表示可能な範囲を超えたため、実際に埋め込んだ際とは表示が異なります。 + */ + "previewIsNotActual": string; + /** + * 角丸にする + */ + "rounded": string; + /** + * 外枠に枠線をつける + */ + "border": string; + /** + * プレビューに反映 + */ + "applyToPreview": string; + /** + * 埋め込みコードを作成 + */ + "generateCode": string; + /** + * コードが生成されました + */ + "codeGenerated": string; + /** + * 生成されたコードをウェブサイトに貼り付けてご利用ください。 + */ + "codeGeneratedDescription": string; + }; } declare const locales: { [lang: string]: Locale; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 587b67d987..80cd8dc7cc 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -592,6 +592,8 @@ ascendingOrder: "昇順" descendingOrder: "降順" scratchpad: "スクラッチパッド" scratchpadDescription: "スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。" +uiInspector: "UIインスペクター" +uiInspectorDescription: "メモリ上に存在しているUIコンポーネントのインスタンスの一覧を見ることができます。UIコンポーネントはUi:C:系関数により生成されます。" output: "出力" script: "スクリプト" disablePagesScript: "Pagesのスクリプトを無効にする" @@ -776,7 +778,7 @@ left: "左" center: "中央" wide: "広い" narrow: "狭い" -reloadToApplySetting: "設定はページリロード後に反映されます。今すぐリロードしますか?" +reloadToApplySetting: "設定はページリロード後に反映されます。" needReloadToApply: "反映には再起動が必要です。" showTitlebar: "タイトルバーを表示する" clearCache: "キャッシュをクリア" @@ -1263,7 +1265,14 @@ confirmWhenRevealingSensitiveMedia: "センシティブなメディアを表示 sensitiveMediaRevealConfirm: "センシティブなメディアです。表示しますか?" createdLists: "作成したリスト" createdAntennas: "作成したアンテナ" +fromX: "{x}から" +genEmbedCode: "埋め込みコードを生成" +noteOfThisUser: "このユーザーのノート一覧" clipNoteLimitExceeded: "これ以上このクリップにノートを追加できません。" +performance: "パフォーマンス" +modified: "変更あり" +discard: "破棄" +thereAreNChanges: "{n}件の変更があります" _delivery: status: "配信状態" @@ -1406,6 +1415,7 @@ _serverSettings: fanoutTimelineDescription: "有効にすると、各種タイムラインを取得する際のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。サーバーのメモリ容量が少ない場合、または動作が不安定な場合は無効にすることができます。" fanoutTimelineDbFallback: "データベースへのフォールバック" fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。" + reactionsBufferingDescription: "有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。" inquiryUrl: "問い合わせ先URL" inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。" @@ -1742,6 +1752,11 @@ _role: canSearchNotes: "ノート検索の利用" canUseTranslator: "翻訳機能の利用" avatarDecorationLimit: "アイコンデコレーションの最大取付個数" + canImportAntennas: "アンテナのインポートを許可" + canImportBlocking: "ブロックのインポートを許可" + canImportFollowing: "フォローのインポートを許可" + canImportMuting: "ミュートのインポートを許可" + canImportUserLists: "リストのインポートを許可" _condition: roleAssignedTo: "マニュアルロールにアサイン済み" isLocal: "ローカルユーザー" @@ -2509,6 +2524,7 @@ _webhookSettings: abuseReportResolved: "ユーザーからの通報を処理したとき" userCreated: "ユーザーが作成されたとき" deleteConfirm: "Webhookを削除しますか?" + testRemarks: "スイッチの右にあるボタンをクリックするとダミーのデータを使用したテスト用Webhookを送信できます。" _abuseReport: _notificationRecipient: @@ -2718,3 +2734,18 @@ _contextMenu: app: "アプリケーション" appWithShift: "Shiftキーでアプリケーション" native: "ブラウザのUI" + +_embedCodeGen: + title: "埋め込みコードをカスタマイズ" + header: "ヘッダーを表示" + autoload: "自動で続きを読み込む(非推奨)" + maxHeight: "高さの最大値" + maxHeightDescription: "0で最大値の設定が無効になります。ウィジェットが縦に伸び続けるのを防ぐために、何らかの値に指定してください。" + maxHeightWarn: "高さの最大値制限が無効(0)になっています。これが意図した変更ではない場合は、高さの最大値を何らかの値に設定してください。" + previewIsNotActual: "プレビュー画面で表示可能な範囲を超えたため、実際に埋め込んだ際とは表示が異なります。" + rounded: "角丸にする" + border: "外枠に枠線をつける" + applyToPreview: "プレビューに反映" + generateCode: "埋め込みコードを作成" + codeGenerated: "コードが生成されました" + codeGeneratedDescription: "生成されたコードをウェブサイトに貼り付けてご利用ください。" diff --git a/package.json b/package.json index 310ea98214..63c22cdc5b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2024.8.0", + "version": "2024.9.0-alpha.8", "codename": "nasubi", "repository": { "type": "git", @@ -8,7 +8,9 @@ }, "packageManager": "pnpm@9.6.0", "workspaces": [ + "packages/frontend-shared", "packages/frontend", + "packages/frontend-embed", "packages/backend", "packages/sw", "packages/misskey-js", @@ -35,6 +37,7 @@ "cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts", "cy:run": "pnpm cypress run", "e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run", + "e2e-dev-container": "cp ./.config/cypress-devcontainer.yml ./.config/test.yml && pnpm start-server-and-test start:test http://localhost:61812 cy:run", "jest": "cd packages/backend && pnpm jest", "jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage", "test": "pnpm -r test", @@ -53,11 +56,11 @@ "fast-glob": "3.3.2", "ignore-walk": "6.0.5", "js-yaml": "4.1.0", - "postcss": "8.4.40", + "postcss": "8.4.47", "tar": "6.2.1", - "terser": "5.31.3", - "typescript": "5.5.4", - "esbuild": "0.23.0", + "terser": "5.33.0", + "typescript": "5.6.2", + "esbuild": "0.23.1", "glob": "11.0.0" }, "devDependencies": { @@ -66,11 +69,11 @@ "@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/parser": "7.17.0", "cross-env": "7.0.3", - "cypress": "13.13.1", + "cypress": "13.14.2", "eslint": "9.8.0", - "globals": "15.8.0", + "globals": "15.9.0", "ncp": "2.0.0", - "start-server-and-test": "2.0.4" + "start-server-and-test": "2.0.8" }, "optionalDependencies": { "@tensorflow/tfjs-core": "4.4.0" diff --git a/packages/backend/assets/embed.js b/packages/backend/assets/embed.js new file mode 100644 index 0000000000..24fccc1b6c --- /dev/null +++ b/packages/backend/assets/embed.js @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: MIT + */ +//@ts-check +(() => { + /** @type {NodeListOf<HTMLIFrameElement>} */ + const els = document.querySelectorAll('iframe[data-misskey-embed-id]'); + + window.addEventListener('message', function (event) { + els.forEach((el) => { + if (event.source !== el.contentWindow) { + return; + } + + const id = el.dataset.misskeyEmbedId; + + if (event.data.type === 'misskey:embed:ready') { + el.contentWindow?.postMessage({ + type: 'misskey:embedParent:registerIframeId', + payload: { + iframeId: id, + } + }, '*'); + } + if (event.data.type === 'misskey:embed:changeHeight' && event.data.iframeId === id) { + el.style.height = event.data.payload.height + 'px'; + } + }); + }); +})(); diff --git a/packages/backend/migration/1726804538569-reactions-buffering.js b/packages/backend/migration/1726804538569-reactions-buffering.js new file mode 100644 index 0000000000..bc19e9cc8a --- /dev/null +++ b/packages/backend/migration/1726804538569-reactions-buffering.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class ReactionsBuffering1726804538569 { + name = 'ReactionsBuffering1726804538569' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "enableReactionsBuffering" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableReactionsBuffering"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index f497610af9..617df78267 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -67,24 +67,24 @@ "dependencies": { "@aws-sdk/client-s3": "3.620.0", "@aws-sdk/lib-storage": "3.620.0", - "@bull-board/api": "5.21.1", - "@bull-board/fastify": "5.21.1", - "@bull-board/ui": "5.21.1", - "@discordapp/twemoji": "15.0.3", - "@fastify/accepts": "4.3.0", - "@fastify/cookie": "9.3.1", - "@fastify/cors": "9.0.1", - "@fastify/express": "3.0.0", - "@fastify/http-proxy": "9.5.0", - "@fastify/multipart": "8.3.0", - "@fastify/static": "7.0.4", - "@fastify/view": "9.1.0", + "@bull-board/api": "5.23.0", + "@bull-board/fastify": "5.23.0", + "@bull-board/ui": "5.23.0", + "@discordapp/twemoji": "15.1.0", + "@fastify/accepts": "5.0.0", + "@fastify/cookie": "10.0.0", + "@fastify/cors": "10.0.0", + "@fastify/express": "4.0.0", + "@fastify/http-proxy": "10.0.0", + "@fastify/multipart": "9.0.0", + "@fastify/static": "8.0.0", + "@fastify/view": "10.0.0", "@misskey-dev/sharp-read-bmp": "1.2.0", "@misskey-dev/summaly": "5.1.0", - "@napi-rs/canvas": "^0.1.53", - "@nestjs/common": "10.3.10", - "@nestjs/core": "10.3.10", - "@nestjs/testing": "10.3.10", + "@napi-rs/canvas": "0.1.56", + "@nestjs/common": "10.4.3", + "@nestjs/core": "10.4.3", + "@nestjs/testing": "10.4.3", "@peertube/http-signature": "1.7.0", "@sentry/node": "8.20.0", "@sentry/profiling-node": "8.20.0", @@ -100,8 +100,8 @@ "async-mutex": "0.5.0", "bcryptjs": "2.4.3", "blurhash": "2.0.5", - "body-parser": "1.20.2", - "bullmq": "5.10.4", + "body-parser": "1.20.3", + "bullmq": "5.13.2", "cacheable-lookup": "7.0.0", "cbor": "9.0.2", "chalk": "5.3.0", @@ -112,27 +112,28 @@ "content-disposition": "0.5.4", "date-fns": "2.30.0", "deep-email-validator": "0.1.21", - "fastify": "4.28.1", - "fastify-raw-body": "4.3.0", + "fastify": "5.0.0", + "fastify-raw-body": "5.0.0", "feed": "4.2.2", - "file-type": "19.3.0", + "file-type": "19.5.0", "fluent-ffmpeg": "2.1.3", "form-data": "4.0.0", "got": "14.4.2", - "happy-dom": "10.0.3", + "happy-dom": "15.7.4", "hpagent": "1.2.0", "htmlescape": "1.1.1", "http-link-header": "1.1.3", "ioredis": "5.4.1", - "ip-cidr": "4.0.1", + "ip-cidr": "4.0.2", "ipaddr.js": "2.2.0", - "is-svg": "5.0.1", + "is-svg": "5.1.0", "js-yaml": "4.1.0", "jsdom": "24.1.1", "json5": "2.2.3", "jsonld": "8.3.2", "jsrsasign": "11.1.0", - "meilisearch": "0.41.0", + "meilisearch": "0.42.0", + "juice": "11.0.0", "mfm-js": "0.24.0", "microformats-parser": "2.0.2", "mime-types": "2.1.35", @@ -142,24 +143,24 @@ "nanoid": "5.0.7", "nested-property": "4.0.0", "node-fetch": "3.3.2", - "nodemailer": "6.9.14", + "nodemailer": "6.9.15", "nsfwjs": "2.4.2", "oauth": "0.10.0", "oauth2orize": "1.12.0", "oauth2orize-pkce": "0.1.2", "os-utils": "0.0.14", - "otpauth": "9.3.1", + "otpauth": "9.3.2", "parse5": "7.1.2", - "pg": "8.12.0", + "pg": "8.13.0", "pkce-challenge": "4.1.0", "probe-image-size": "7.2.3", "promise-limit": "2.7.0", "pug": "3.0.3", "punycode": "2.3.1", - "qrcode": "1.5.3", + "qrcode": "1.5.4", "random-seed": "0.3.0", "ratelimiter": "3.4.1", - "re2": "1.21.3", + "re2": "1.21.4", "redis-lock": "0.1.4", "reflect-metadata": "0.2.2", "rename": "1.0.4", @@ -167,17 +168,17 @@ "rxjs": "7.8.1", "sanitize-html": "2.13.0", "secure-json-parse": "2.7.0", - "sharp": "0.33.4", + "sharp": "0.33.5", "slacc": "0.0.10", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", - "systeminformation": "5.22.11", + "systeminformation": "5.23.5", "tinycolor2": "1.6.0", "tmp": "0.2.3", "tsc-alias": "1.8.10", "tsconfig-paths": "4.2.0", "typeorm": "0.3.20", - "typescript": "5.5.4", + "typescript": "5.6.2", "ulid": "2.3.0", "vary": "1.1.2", "web-push": "3.6.7", @@ -186,7 +187,7 @@ }, "devDependencies": { "@jest/globals": "29.7.0", - "@nestjs/platform-express": "10.3.10", + "@nestjs/platform-express": "10.4.3", "@simplewebauthn/types": "10.0.0", "@swc/jest": "0.2.36", "@types/accepts": "1.3.7", @@ -195,10 +196,10 @@ "@types/body-parser": "1.19.5", "@types/color-convert": "2.0.3", "@types/content-disposition": "0.5.8", - "@types/fluent-ffmpeg": "2.1.24", + "@types/fluent-ffmpeg": "2.1.26", "@types/htmlescape": "1.1.3", "@types/http-link-header": "1.0.7", - "@types/jest": "29.5.12", + "@types/jest": "29.5.13", "@types/js-yaml": "4.0.9", "@types/jsdom": "21.1.7", "@types/jsonld": "1.5.15", @@ -206,18 +207,18 @@ "@types/mime-types": "2.1.4", "@types/ms": "0.7.34", "@types/node": "20.14.12", - "@types/nodemailer": "6.4.15", + "@types/nodemailer": "6.4.16", "@types/oauth": "0.9.5", "@types/oauth2orize": "1.11.5", "@types/oauth2orize-pkce": "0.1.2", - "@types/pg": "8.11.6", + "@types/pg": "8.11.10", "@types/pug": "2.0.10", "@types/punycode": "2.1.4", "@types/qrcode": "1.5.5", "@types/random-seed": "0.3.5", "@types/ratelimiter": "3.4.6", "@types/rename": "1.0.7", - "@types/sanitize-html": "2.11.0", + "@types/sanitize-html": "2.13.0", "@types/semver": "7.5.8", "@types/simple-oauth2": "5.0.7", "@types/sinonjs__fake-timers": "8.1.5", @@ -225,17 +226,17 @@ "@types/tmp": "0.2.6", "@types/vary": "1.1.3", "@types/web-push": "3.6.3", - "@types/ws": "8.5.11", + "@types/ws": "8.5.12", "@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/parser": "7.17.0", "aws-sdk-client-mock": "4.0.1", "cross-env": "7.0.3", - "eslint-plugin-import": "2.29.1", - "execa": "9.3.0", + "eslint-plugin-import": "2.30.0", + "execa": "9.4.0", "fkill": "9.0.0", "jest": "29.7.0", "jest-mock": "29.7.0", - "nodemon": "3.1.4", + "nodemon": "3.1.7", "pid-port": "1.0.0", "simple-oauth2": "5.1.0" } diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts index 09971e8ca0..0f69cf93a9 100644 --- a/packages/backend/src/GlobalModule.ts +++ b/packages/backend/src/GlobalModule.ts @@ -13,6 +13,8 @@ import { createPostgresDataSource } from './postgres.js'; import { RepositoryModule } from './models/RepositoryModule.js'; import { allSettled } from './misc/promise-tracker.js'; import type { Provider, OnApplicationShutdown } from '@nestjs/common'; +import { MiMeta } from '@/models/Meta.js'; +import { GlobalEvents } from './core/GlobalEventService.js'; const $config: Provider = { provide: DI.config, @@ -78,11 +80,76 @@ const $redisForTimelines: Provider = { inject: [DI.config], }; +const $redisForReactions: Provider = { + provide: DI.redisForReactions, + useFactory: (config: Config) => { + return new Redis.Redis(config.redisForReactions); + }, + inject: [DI.config], +}; + +const $meta: Provider = { + provide: DI.meta, + useFactory: async (db: DataSource, redisForSub: Redis.Redis) => { + const meta = await db.transaction(async transactionalEntityManager => { + // 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する + const metas = await transactionalEntityManager.find(MiMeta, { + order: { + id: 'DESC', + }, + }); + + const meta = metas[0]; + + if (meta) { + return meta; + } else { + // metaが空のときfetchMetaが同時に呼ばれるとここが同時に呼ばれてしまうことがあるのでフェイルセーフなupsertを使う + const saved = await transactionalEntityManager + .upsert( + MiMeta, + { + id: 'x', + }, + ['id'], + ) + .then((x) => transactionalEntityManager.findOneByOrFail(MiMeta, x.identifiers[0])); + + return saved; + } + }); + + async function onMessage(_: string, data: string): Promise<void> { + const obj = JSON.parse(data); + + if (obj.channel === 'internal') { + const { type, body } = obj.message as GlobalEvents['internal']['payload']; + switch (type) { + case 'metaUpdated': { + for (const key in body) { + (meta as any)[key] = (body as any)[key]; + } + meta.proxyAccount = null; // joinなカラムは通常取ってこないので + break; + } + default: + break; + } + } + } + + redisForSub.on('message', onMessage); + + return meta; + }, + inject: [DI.db, DI.redisForSub], +}; + @Global() @Module({ imports: [RepositoryModule], - providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines], - exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, RepositoryModule], + providers: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions], + exports: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions, RepositoryModule], }) export class GlobalModule implements OnApplicationShutdown { constructor( @@ -91,6 +158,7 @@ export class GlobalModule implements OnApplicationShutdown { @Inject(DI.redisForPub) private redisForPub: Redis.Redis, @Inject(DI.redisForSub) private redisForSub: Redis.Redis, @Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis, + @Inject(DI.redisForReactions) private redisForReactions: Redis.Redis, ) { } public async dispose(): Promise<void> { @@ -103,6 +171,7 @@ export class GlobalModule implements OnApplicationShutdown { this.redisForPub.disconnect(), this.redisForSub.disconnect(), this.redisForTimelines.disconnect(), + this.redisForReactions.disconnect(), ]); } diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index cff0194780..97ba79c574 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -49,6 +49,7 @@ type Source = { redisForPubsub?: RedisOptionsSource; redisForJobQueue?: RedisOptionsSource; redisForTimelines?: RedisOptionsSource; + redisForReactions?: RedisOptionsSource; meilisearch?: { host: string; port: string; @@ -160,8 +161,10 @@ export type Config = { authUrl: string; driveUrl: string; userAgent: string; - clientEntry: string; - clientManifestExists: boolean; + frontendEntry: string; + frontendManifestExists: boolean; + frontendEmbedEntry: string; + frontendEmbedManifestExists: boolean; mediaProxy: string; externalMediaProxyEnabled: boolean; videoThumbnailGenerator: string | null; @@ -169,6 +172,7 @@ export type Config = { redisForPubsub: RedisOptions & RedisOptionsSource; redisForJobQueue: RedisOptions & RedisOptionsSource; redisForTimelines: RedisOptions & RedisOptionsSource; + redisForReactions: RedisOptions & RedisOptionsSource; sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined; sentryForFrontend: { options: Partial<Sentry.NodeOptions> } | undefined; perChannelMaxNoteCacheCount: number; @@ -196,10 +200,16 @@ const path = process.env.MISSKEY_CONFIG_YML export function loadConfig(): Config { const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8')); - const clientManifestExists = fs.existsSync(_dirname + '/../../../built/_vite_/manifest.json'); - const clientManifest = clientManifestExists ? - JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_vite_/manifest.json`, 'utf-8')) + + const frontendManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_vite_/manifest.json'); + const frontendEmbedManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_embed_vite_/manifest.json'); + const frontendManifest = frontendManifestExists ? + JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_vite_/manifest.json`, 'utf-8')) : { 'src/_boot_.ts': { file: 'src/_boot_.ts' } }; + const frontendEmbedManifest = frontendEmbedManifestExists ? + JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_embed_vite_/manifest.json`, 'utf-8')) + : { 'src/boot.ts': { file: 'src/boot.ts' } }; + const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source; const url = tryCreateUrl(config.url ?? process.env.MISSKEY_URL ?? ''); @@ -243,6 +253,7 @@ export function loadConfig(): Config { redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis, redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis, redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis, + redisForReactions: config.redisForReactions ? convertRedisOptions(config.redisForReactions, host) : redis, sentryForBackend: config.sentryForBackend, sentryForFrontend: config.sentryForFrontend, id: config.id, @@ -270,8 +281,10 @@ export function loadConfig(): Config { config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator : null, userAgent: `Misskey/${version} (${config.url})`, - clientEntry: clientManifest['src/_boot_.ts'], - clientManifestExists: clientManifestExists, + frontendEntry: frontendManifest['src/_boot_.ts'], + frontendManifestExists: frontendManifestExists, + frontendEmbedEntry: frontendEmbedManifest['src/boot.ts'], + frontendEmbedManifestExists: frontendEmbedManifestExists, perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000, perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500, deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7), diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts index a238f4973a..e3a61861f4 100644 --- a/packages/backend/src/const.ts +++ b/packages/backend/src/const.ts @@ -8,6 +8,8 @@ export const MAX_NOTE_TEXT_LENGTH = 3000; export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days +export const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16; + //#region hard limits // If you change DB_* values, you must also change the DB schema. diff --git a/packages/backend/src/core/AbuseReportNotificationService.ts b/packages/backend/src/core/AbuseReportNotificationService.ts index 7be5335885..fe2c63e7d6 100644 --- a/packages/backend/src/core/AbuseReportNotificationService.ts +++ b/packages/backend/src/core/AbuseReportNotificationService.ts @@ -14,10 +14,10 @@ import type { AbuseReportNotificationRecipientRepository, MiAbuseReportNotificationRecipient, MiAbuseUserReport, + MiMeta, MiUser, } from '@/models/_.js'; import { EmailService } from '@/core/EmailService.js'; -import { MetaService } from '@/core/MetaService.js'; import { RoleService } from '@/core/RoleService.js'; import { RecipientMethod } from '@/models/AbuseReportNotificationRecipient.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; @@ -27,15 +27,19 @@ import { IdService } from './IdService.js'; @Injectable() export class AbuseReportNotificationService implements OnApplicationShutdown { constructor( + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.abuseReportNotificationRecipientRepository) private abuseReportNotificationRecipientRepository: AbuseReportNotificationRecipientRepository, + @Inject(DI.redisForSub) private redisForSub: Redis.Redis, + private idService: IdService, private roleService: RoleService, private systemWebhookService: SystemWebhookService, private emailService: EmailService, - private metaService: MetaService, private moderationLogService: ModerationLogService, private globalEventService: GlobalEventService, ) { @@ -93,10 +97,8 @@ export class AbuseReportNotificationService implements OnApplicationShutdown { .filter(x => x != null), ); - // 送信先の鮮度を保つため、毎回取得する - const meta = await this.metaService.fetch(true); recipientEMailAddresses.push( - ...(meta.email ? [meta.email] : []), + ...(this.meta.email ? [this.meta.email] : []), ); if (recipientEMailAddresses.length <= 0) { diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index b6b591d240..6e3125044c 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -9,7 +9,7 @@ import { IsNull, In, MoreThan, Not } from 'typeorm'; import { bindThis } from '@/decorators.js'; import { DI } from '@/di-symbols.js'; import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js'; -import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListMembershipsRepository, UsersRepository } from '@/models/_.js'; +import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MiMeta, MutingsRepository, UserListMembershipsRepository, UsersRepository } from '@/models/_.js'; import type { RelationshipJobData, ThinUser } from '@/queue/types.js'; import { IdService } from '@/core/IdService.js'; @@ -22,13 +22,15 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ProxyAccountService } from '@/core/ProxyAccountService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; -import { MetaService } from '@/core/MetaService.js'; import InstanceChart from '@/core/chart/charts/instance.js'; import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; @Injectable() export class AccountMoveService { constructor( + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -57,7 +59,6 @@ export class AccountMoveService { private perUserFollowingChart: PerUserFollowingChart, private federatedInstanceService: FederatedInstanceService, private instanceChart: InstanceChart, - private metaService: MetaService, private relayService: RelayService, private queueService: QueueService, ) { @@ -276,7 +277,7 @@ export class AccountMoveService { if (this.userEntityService.isRemoteUser(oldAccount)) { this.federatedInstanceService.fetch(oldAccount.host).then(async i => { this.instancesRepository.decrement({ id: i.id }, 'followersCount', localFollowerIds.length); - if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { + if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.updateFollowers(i.host, false); } }); diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 793d8974b3..e827ffa68c 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -123,11 +123,14 @@ export class AntennaService implements OnApplicationShutdown { if (antenna.src === 'home') { // TODO } else if (antenna.src === 'list') { - const listUsers = (await this.userListMembershipsRepository.findBy({ - userListId: antenna.userListId!, - })).map(x => x.userId); - - if (!listUsers.includes(note.userId)) return false; + if (antenna.userListId == null) return false; + const exists = await this.userListMembershipsRepository.exists({ + where: { + userListId: antenna.userListId, + userId: note.userId, + }, + }); + if (!exists) return false; } else if (antenna.src === 'users') { const accts = antenna.users.map(x => { const { username, host } = Acct.parse(x); diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index c9427bbeb7..3b3c35f976 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -13,6 +13,7 @@ import { import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js'; import { SystemWebhookService } from '@/core/SystemWebhookService.js'; import { UserSearchService } from '@/core/UserSearchService.js'; +import { WebhookTestService } from '@/core/WebhookTestService.js'; import { AccountMoveService } from './AccountMoveService.js'; import { AccountUpdateService } from './AccountUpdateService.js'; import { AiService } from './AiService.js'; @@ -49,6 +50,7 @@ import { PollService } from './PollService.js'; import { PushNotificationService } from './PushNotificationService.js'; import { QueryService } from './QueryService.js'; import { ReactionService } from './ReactionService.js'; +import { ReactionsBufferingService } from './ReactionsBufferingService.js'; import { RelayService } from './RelayService.js'; import { RoleService } from './RoleService.js'; import { S3Service } from './S3Service.js'; @@ -192,6 +194,7 @@ const $ProxyAccountService: Provider = { provide: 'ProxyAccountService', useExis const $PushNotificationService: Provider = { provide: 'PushNotificationService', useExisting: PushNotificationService }; const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService }; const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService }; +const $ReactionsBufferingService: Provider = { provide: 'ReactionsBufferingService', useExisting: ReactionsBufferingService }; const $RelayService: Provider = { provide: 'RelayService', useExisting: RelayService }; const $RoleService: Provider = { provide: 'RoleService', useExisting: RoleService }; const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service }; @@ -211,6 +214,7 @@ const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: Us const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService }; const $UserWebhookService: Provider = { provide: 'UserWebhookService', useExisting: UserWebhookService }; const $SystemWebhookService: Provider = { provide: 'SystemWebhookService', useExisting: SystemWebhookService }; +const $WebhookTestService: Provider = { provide: 'WebhookTestService', useExisting: WebhookTestService }; const $UtilityService: Provider = { provide: 'UtilityService', useExisting: UtilityService }; const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService }; const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService }; @@ -340,6 +344,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting PushNotificationService, QueryService, ReactionService, + ReactionsBufferingService, RelayService, RoleService, S3Service, @@ -359,6 +364,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting VideoProcessingService, UserWebhookService, SystemWebhookService, + WebhookTestService, UtilityService, FileInfoService, SearchService, @@ -484,6 +490,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $PushNotificationService, $QueryService, $ReactionService, + $ReactionsBufferingService, $RelayService, $RoleService, $S3Service, @@ -503,6 +510,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $VideoProcessingService, $UserWebhookService, $SystemWebhookService, + $WebhookTestService, $UtilityService, $FileInfoService, $SearchService, @@ -629,6 +637,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting PushNotificationService, QueryService, ReactionService, + ReactionsBufferingService, RelayService, RoleService, S3Service, @@ -648,6 +657,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting VideoProcessingService, UserWebhookService, SystemWebhookService, + WebhookTestService, UtilityService, FileInfoService, SearchService, @@ -772,6 +782,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $PushNotificationService, $QueryService, $ReactionService, + $ReactionsBufferingService, $RelayService, $RoleService, $S3Service, @@ -791,6 +802,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $VideoProcessingService, $UserWebhookService, $SystemWebhookService, + $WebhookTestService, $UtilityService, $FileInfoService, $SearchService, diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 8aa04b4da7..c332e5a0a8 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -11,11 +11,10 @@ import { sharpBmp } from '@misskey-dev/sharp-read-bmp'; import { IsNull } from 'typeorm'; import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/_.js'; +import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository, MiMeta } from '@/models/_.js'; import type { Config } from '@/config.js'; import Logger from '@/logger.js'; import type { MiRemoteUser, MiUser } from '@/models/User.js'; -import { MetaService } from '@/core/MetaService.js'; import { MiDriveFile } from '@/models/DriveFile.js'; import { IdService } from '@/core/IdService.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; @@ -99,6 +98,9 @@ export class DriveService { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -115,7 +117,6 @@ export class DriveService { private userEntityService: UserEntityService, private driveFileEntityService: DriveFileEntityService, private idService: IdService, - private metaService: MetaService, private downloadService: DownloadService, private internalStorageService: InternalStorageService, private s3Service: S3Service, @@ -149,9 +150,7 @@ export class DriveService { // thunbnail, webpublic を必要なら生成 const alts = await this.generateAlts(path, type, !file.uri); - const meta = await this.metaService.fetch(); - - if (meta.useObjectStorage) { + if (this.meta.useObjectStorage) { //#region ObjectStorage params let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) ?? ['']); @@ -170,11 +169,11 @@ export class DriveService { ext = ''; } - const baseUrl = meta.objectStorageBaseUrl - ?? `${ meta.objectStorageUseSSL ? 'https' : 'http' }://${ meta.objectStorageEndpoint }${ meta.objectStoragePort ? `:${meta.objectStoragePort}` : '' }/${ meta.objectStorageBucket }`; + const baseUrl = this.meta.objectStorageBaseUrl + ?? `${ this.meta.objectStorageUseSSL ? 'https' : 'http' }://${ this.meta.objectStorageEndpoint }${ this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : '' }/${ this.meta.objectStorageBucket }`; // for original - const key = `${meta.objectStoragePrefix}/${randomUUID()}${ext}`; + const key = `${this.meta.objectStoragePrefix}/${randomUUID()}${ext}`; const url = `${ baseUrl }/${ key }`; // for alts @@ -191,7 +190,7 @@ export class DriveService { ]; if (alts.webpublic) { - webpublicKey = `${meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`; + webpublicKey = `${this.meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`; webpublicUrl = `${ baseUrl }/${ webpublicKey }`; this.registerLogger.info(`uploading webpublic: ${webpublicKey}`); @@ -199,7 +198,7 @@ export class DriveService { } if (alts.thumbnail) { - thumbnailKey = `${meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`; + thumbnailKey = `${this.meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`; thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`; this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`); @@ -376,10 +375,8 @@ export class DriveService { if (type === 'image/apng') type = 'image/png'; if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream'; - const meta = await this.metaService.fetch(); - const params = { - Bucket: meta.objectStorageBucket, + Bucket: this.meta.objectStorageBucket, Key: key, Body: stream, ContentType: type, @@ -392,9 +389,9 @@ export class DriveService { // 許可されているファイル形式でしか拡張子をつけない ext ? correctFilename(filename, ext) : filename, ); - if (meta.objectStorageSetPublicRead) params.ACL = 'public-read'; + if (this.meta.objectStorageSetPublicRead) params.ACL = 'public-read'; - await this.s3Service.upload(meta, params) + await this.s3Service.upload(this.meta, params) .then( result => { if ('Bucket' in result) { // CompleteMultipartUploadCommandOutput @@ -460,32 +457,31 @@ export class DriveService { ext = null, }: AddFileArgs): Promise<MiDriveFile> { let skipNsfwCheck = false; - const instance = await this.metaService.fetch(); const userRoleNSFW = user && (await this.roleService.getUserPolicies(user.id)).alwaysMarkNsfw; if (user == null) { skipNsfwCheck = true; } else if (userRoleNSFW) { skipNsfwCheck = true; } - if (instance.sensitiveMediaDetection === 'none') skipNsfwCheck = true; - if (user && instance.sensitiveMediaDetection === 'local' && this.userEntityService.isRemoteUser(user)) skipNsfwCheck = true; - if (user && instance.sensitiveMediaDetection === 'remote' && this.userEntityService.isLocalUser(user)) skipNsfwCheck = true; + if (this.meta.sensitiveMediaDetection === 'none') skipNsfwCheck = true; + if (user && this.meta.sensitiveMediaDetection === 'local' && this.userEntityService.isRemoteUser(user)) skipNsfwCheck = true; + if (user && this.meta.sensitiveMediaDetection === 'remote' && this.userEntityService.isLocalUser(user)) skipNsfwCheck = true; const info = await this.fileInfoService.getFileInfo(path, { skipSensitiveDetection: skipNsfwCheck, sensitiveThreshold: // 感度が高いほどしきい値は低くすることになる - instance.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 0.1 : - instance.sensitiveMediaDetectionSensitivity === 'high' ? 0.3 : - instance.sensitiveMediaDetectionSensitivity === 'low' ? 0.7 : - instance.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0.9 : + this.meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 0.1 : + this.meta.sensitiveMediaDetectionSensitivity === 'high' ? 0.3 : + this.meta.sensitiveMediaDetectionSensitivity === 'low' ? 0.7 : + this.meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0.9 : 0.5, sensitiveThresholdForPorn: 0.75, - enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos, + enableSensitiveMediaDetectionForVideos: this.meta.enableSensitiveMediaDetectionForVideos, }); this.registerLogger.info(`${JSON.stringify(info)}`); // 現状 false positive が多すぎて実用に耐えない - //if (info.porn && instance.disallowUploadWhenPredictedAsPorn) { + //if (info.porn && this.meta.disallowUploadWhenPredictedAsPorn) { // throw new IdentifiableError('282f77bf-5816-4f72-9264-aa14d8261a21', 'Detected as porn.'); //} @@ -589,9 +585,9 @@ export class DriveService { sensitive ?? false : false; - if (user && this.utilityService.isMediaSilencedHost(instance.mediaSilencedHosts, user.host)) file.isSensitive = true; + if (user && this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, user.host)) file.isSensitive = true; if (info.sensitive && profile!.autoSensitive) file.isSensitive = true; - if (info.sensitive && instance.setSensitiveFlagAutomatically) file.isSensitive = true; + if (info.sensitive && this.meta.setSensitiveFlagAutomatically) file.isSensitive = true; if (userRoleNSFW) file.isSensitive = true; if (url !== null) { @@ -652,7 +648,7 @@ export class DriveService { // ローカルユーザーのみ this.perUserDriveChart.update(file, true); } else { - if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { + if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.updateDrive(file, true); } } @@ -798,7 +794,7 @@ export class DriveService { // ローカルユーザーのみ this.perUserDriveChart.update(file, false); } else { - if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { + if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.updateDrive(file, false); } } @@ -820,14 +816,13 @@ export class DriveService { @bindThis public async deleteObjectStorageFile(key: string) { - const meta = await this.metaService.fetch(); try { const param = { - Bucket: meta.objectStorageBucket, + Bucket: this.meta.objectStorageBucket, Key: key, } as DeleteObjectCommandInput; - await this.s3Service.delete(meta, param); + await this.s3Service.delete(this.meta, param); } catch (err: any) { if (err.name === 'NoSuchKey') { this.deleteLogger.warn(`The object storage had no such key to delete: ${key}. Skipping this.`, err as Error); diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 435dbbae28..a176474b95 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -5,18 +5,17 @@ import { URLSearchParams } from 'node:url'; import * as nodemailer from 'nodemailer'; +import juice from 'juice'; import { Inject, Injectable } from '@nestjs/common'; import { validate as validateEmail } from 'deep-email-validator'; -import { MetaService } from '@/core/MetaService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; -import type { UserProfilesRepository } from '@/models/_.js'; +import type { MiMeta, UserProfilesRepository } from '@/models/_.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; -import { QueueService } from '@/core/QueueService.js'; @Injectable() export class EmailService { @@ -26,49 +25,41 @@ export class EmailService { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, - private metaService: MetaService, private loggerService: LoggerService, private utilityService: UtilityService, private httpRequestService: HttpRequestService, - private queueService: QueueService, ) { this.logger = this.loggerService.getLogger('email'); } @bindThis public async sendEmail(to: string, subject: string, html: string, text: string) { - const meta = await this.metaService.fetch(true); - - if (!meta.enableEmail) return; + if (!this.meta.enableEmail) return; const iconUrl = `${this.config.url}/static-assets/mi-white.png`; const emailSettingUrl = `${this.config.url}/settings/email`; - const enableAuth = meta.smtpUser != null && meta.smtpUser !== ''; + const enableAuth = this.meta.smtpUser != null && this.meta.smtpUser !== ''; const transporter = nodemailer.createTransport({ - host: meta.smtpHost, - port: meta.smtpPort, - secure: meta.smtpSecure, + host: this.meta.smtpHost, + port: this.meta.smtpPort, + secure: this.meta.smtpSecure, ignoreTLS: !enableAuth, proxy: this.config.proxySmtp, auth: enableAuth ? { - user: meta.smtpUser, - pass: meta.smtpPass, + user: this.meta.smtpUser, + pass: this.meta.smtpPass, } : undefined, } as any); - try { - // TODO: htmlサニタイズ - const info = await transporter.sendMail({ - from: meta.email!, - to: to, - subject: subject, - text: text, - html: `<!doctype html> + const htmlContent = `<!doctype html> <html> <head> <meta charset="utf-8"> @@ -133,7 +124,7 @@ export class EmailService { <body> <main> <header> - <img src="${ meta.logoImageUrl ?? meta.iconUrl ?? iconUrl }"/> + <img src="${ this.meta.logoImageUrl ?? this.meta.iconUrl ?? iconUrl }"/> </header> <article> <h1>${ subject }</h1> @@ -147,7 +138,18 @@ export class EmailService { <a href="${ this.config.url }">${ this.config.host }</a> </nav> </body> -</html>`, +</html>`; + + const inlinedHtml = juice(htmlContent); + + try { + // TODO: htmlサニタイズ + const info = await transporter.sendMail({ + from: this.meta.email!, + to: to, + subject: subject, + text: text, + html: inlinedHtml, }); this.logger.info(`Message sent: ${info.messageId}`); @@ -162,8 +164,6 @@ export class EmailService { available: boolean; reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist'; }> { - const meta = await this.metaService.fetch(); - const exist = await this.userProfilesRepository.countBy({ emailVerified: true, email: emailAddress, @@ -181,11 +181,11 @@ export class EmailService { reason?: string | null, } = { valid: true, reason: null }; - if (meta.enableActiveEmailValidation) { - if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) { - validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey); - } else if (meta.enableTruemailApi && meta.truemailInstance && meta.truemailAuthKey != null) { - validated = await this.trueMail(meta.truemailInstance, emailAddress, meta.truemailAuthKey); + if (this.meta.enableActiveEmailValidation) { + if (this.meta.enableVerifymailApi && this.meta.verifymailAuthKey != null) { + validated = await this.verifyMail(emailAddress, this.meta.verifymailAuthKey); + } else if (this.meta.enableTruemailApi && this.meta.truemailInstance && this.meta.truemailAuthKey != null) { + validated = await this.trueMail(this.meta.truemailInstance, emailAddress, this.meta.truemailAuthKey); } else { validated = await validateEmail({ email: emailAddress, @@ -215,7 +215,7 @@ export class EmailService { } const emailDomain: string = emailAddress.split('@')[1]; - const isBanned = this.utilityService.isBlockedHost(meta.bannedEmailDomains, emailDomain); + const isBanned = this.utilityService.isBlockedHost(this.meta.bannedEmailDomains, emailDomain); if (isBanned) { return { diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts index eb192ee6da..793bbeecb1 100644 --- a/packages/backend/src/core/HashtagService.ts +++ b/packages/backend/src/core/HashtagService.ts @@ -10,16 +10,18 @@ import type { MiUser } from '@/models/User.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { IdService } from '@/core/IdService.js'; import type { MiHashtag } from '@/models/Hashtag.js'; -import type { HashtagsRepository } from '@/models/_.js'; +import type { HashtagsRepository, MiMeta } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; import { FeaturedService } from '@/core/FeaturedService.js'; -import { MetaService } from '@/core/MetaService.js'; import { UtilityService } from '@/core/UtilityService.js'; @Injectable() export class HashtagService { constructor( + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.redis) private redisClient: Redis.Redis, // TODO: 専用のRedisサーバーを設定できるようにする @@ -29,7 +31,6 @@ export class HashtagService { private userEntityService: UserEntityService, private featuredService: FeaturedService, private idService: IdService, - private metaService: MetaService, private utilityService: UtilityService, ) { } @@ -160,10 +161,9 @@ export class HashtagService { @bindThis public async updateHashtagsRanking(hashtag: string, userId: MiUser['id']): Promise<void> { - const instance = await this.metaService.fetch(); - const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t)); + const hiddenTags = this.meta.hiddenTags.map(t => normalizeForSearch(t)); if (hiddenTags.includes(hashtag)) return; - if (this.utilityService.isKeyWordIncluded(hashtag, instance.sensitiveWords)) return; + if (this.utilityService.isKeyWordIncluded(hashtag, this.meta.sensitiveWords)) return; // YYYYMMDDHHmm (10分間隔) const now = new Date(); diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index 74536c68f5..d33b228c3d 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -239,7 +239,7 @@ export class MfmService { return null; } - const { window } = new Window(); + const { happyDOM, window } = new Window(); const doc = window.document; @@ -457,6 +457,10 @@ export class MfmService { appendChildren(nodes, body); - return new XMLSerializer().serializeToString(body); + const serialized = new XMLSerializer().serializeToString(body); + + happyDOM.close().catch(err => {}); + + return serialized; } } diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 1d8d248322..18efc9d562 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -8,13 +8,12 @@ import * as mfm from 'mfm-js'; import { In, DataSource, IsNull, LessThan } from 'typeorm'; import * as Redis from 'ioredis'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; -import RE2 from 're2'; import { extractMentions } from '@/misc/extract-mentions.js'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractHashtags } from '@/misc/extract-hashtags.js'; import type { IMentionedRemoteUsers } from '@/models/Note.js'; import { MiNote } from '@/models/Note.js'; -import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MiMeta, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiApp } from '@/models/App.js'; import { concat } from '@/misc/prelude/array.js'; @@ -23,11 +22,8 @@ import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js'; import type { IPoll } from '@/models/Poll.js'; import { MiPoll } from '@/models/Poll.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; -import { checkWordMute } from '@/misc/check-word-mute.js'; import type { MiChannel } from '@/models/Channel.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; -import { MemorySingleCache } from '@/misc/cache.js'; -import type { MiUserProfile } from '@/models/UserProfile.js'; import { RelayService } from '@/core/RelayService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { DI } from '@/di-symbols.js'; @@ -51,7 +47,6 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { bindThis } from '@/decorators.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { RoleService } from '@/core/RoleService.js'; -import { MetaService } from '@/core/MetaService.js'; import { SearchService } from '@/core/SearchService.js'; import { FeaturedService } from '@/core/FeaturedService.js'; import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; @@ -156,6 +151,9 @@ export class NoteCreateService implements OnApplicationShutdown { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.db) private db: DataSource, @@ -210,7 +208,6 @@ export class NoteCreateService implements OnApplicationShutdown { private apDeliverManagerService: ApDeliverManagerService, private apRendererService: ApRendererService, private roleService: RoleService, - private metaService: MetaService, private searchService: SearchService, private notesChart: NotesChart, private perUserNotesChart: PerUserNotesChart, @@ -251,10 +248,8 @@ export class NoteCreateService implements OnApplicationShutdown { if (data.channel != null) data.visibleUsers = []; if (data.channel != null) data.localOnly = true; - const meta = await this.metaService.fetch(); - if (data.visibility === 'public' && data.channel == null) { - const sensitiveWords = meta.sensitiveWords; + const sensitiveWords = this.meta.sensitiveWords; if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) { data.visibility = 'home'; } else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) { @@ -262,17 +257,17 @@ export class NoteCreateService implements OnApplicationShutdown { } } - const hasProhibitedWords = await this.checkProhibitedWordsContain({ + const hasProhibitedWords = this.checkProhibitedWordsContain({ cw: data.cw, text: data.text, pollChoices: data.poll?.choices, - }, meta.prohibitedWords); + }, this.meta.prohibitedWords); if (hasProhibitedWords) { throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words'); } - const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host); + const inSilencedInstance = this.utilityService.isSilencedHost(this.meta.silencedHosts, user.host); if (data.visibility === 'public' && inSilencedInstance && user.host !== null) { data.visibility = 'home'; @@ -365,7 +360,7 @@ export class NoteCreateService implements OnApplicationShutdown { } // if the host is media-silenced, custom emojis are not allowed - if (this.utilityService.isMediaSilencedHost(meta.mediaSilencedHosts, user.host)) emojis = []; + if (this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, user.host)) emojis = []; tags = tags.filter(tag => Array.from(tag).length <= 128).splice(0, 32); @@ -506,10 +501,8 @@ export class NoteCreateService implements OnApplicationShutdown { host: MiUser['host']; isBot: MiUser['isBot']; }, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { - const meta = await this.metaService.fetch(); - this.notesChart.update(note, true); - if (note.visibility !== 'specified' && (meta.enableChartsForRemoteUser || (user.host == null))) { + if (note.visibility !== 'specified' && (this.meta.enableChartsForRemoteUser || (user.host == null))) { this.perUserNotesChart.update(user, note, true); } @@ -517,7 +510,7 @@ export class NoteCreateService implements OnApplicationShutdown { if (this.userEntityService.isRemoteUser(user)) { this.federatedInstanceService.fetch(user.host).then(async i => { this.instancesRepository.increment({ id: i.id }, 'notesCount', 1); - if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { + if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.updateNote(i.host, note, true); } }); @@ -853,15 +846,14 @@ export class NoteCreateService implements OnApplicationShutdown { @bindThis private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) { - const meta = await this.metaService.fetch(); - if (!meta.enableFanoutTimeline) return; + if (!this.meta.enableFanoutTimeline) return; const r = this.redisForTimelines.pipeline(); if (note.channelId) { this.fanoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r); - this.fanoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); + this.fanoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r); const channelFollowings = await this.channelFollowingsRepository.find({ where: { @@ -871,9 +863,9 @@ export class NoteCreateService implements OnApplicationShutdown { }); for (const channelFollowing of channelFollowings) { - this.fanoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); + this.fanoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax, r); if (note.fileIds.length > 0) { - this.fanoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); + this.fanoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r); } } } else { @@ -911,9 +903,9 @@ export class NoteCreateService implements OnApplicationShutdown { if (!following.withReplies) continue; } - this.fanoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); + this.fanoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax, r); if (note.fileIds.length > 0) { - this.fanoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); + this.fanoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r); } } @@ -930,25 +922,25 @@ export class NoteCreateService implements OnApplicationShutdown { if (!userListMembership.withReplies) continue; } - this.fanoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r); + this.fanoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, this.meta.perUserListTimelineCacheMax, r); if (note.fileIds.length > 0) { - this.fanoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r); + this.fanoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, this.meta.perUserListTimelineCacheMax / 2, r); } } // 自分自身のHTL if (note.userHost == null) { if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { - this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r); + this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, this.meta.perUserHomeTimelineCacheMax, r); if (note.fileIds.length > 0) { - this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); + this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r); } } } // 自分自身以外への返信 if (isReply(note)) { - this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); + this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r); if (note.visibility === 'public' && note.userHost == null) { this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r); @@ -957,9 +949,9 @@ export class NoteCreateService implements OnApplicationShutdown { } } } else { - this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); + this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r); if (note.fileIds.length > 0) { - this.fanoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r); + this.fanoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax / 2 : this.meta.perRemoteUserUserTimelineCacheMax / 2, r); } if (note.visibility === 'public' && note.userHost == null) { @@ -1018,9 +1010,9 @@ export class NoteCreateService implements OnApplicationShutdown { } } - public async checkProhibitedWordsContain(content: Parameters<UtilityService['concatNoteContentsForKeyWordCheck']>[0], prohibitedWords?: string[]) { + public checkProhibitedWordsContain(content: Parameters<UtilityService['concatNoteContentsForKeyWordCheck']>[0], prohibitedWords?: string[]) { if (prohibitedWords == null) { - prohibitedWords = (await this.metaService.fetch()).prohibitedWords; + prohibitedWords = this.meta.prohibitedWords; } if ( diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index b7c01c64c8..f9f8ace386 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -7,7 +7,7 @@ import { Brackets, In } from 'typeorm'; import { Injectable, Inject } from '@nestjs/common'; import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js'; import type { MiNote, IMentionedRemoteUsers } from '@/models/Note.js'; -import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/_.js'; +import type { InstancesRepository, MiMeta, NotesRepository, UsersRepository } from '@/models/_.js'; import { RelayService } from '@/core/RelayService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { DI } from '@/di-symbols.js'; @@ -19,9 +19,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; -import { MetaService } from '@/core/MetaService.js'; import { SearchService } from '@/core/SearchService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { isQuote, isRenote } from '@/misc/is-renote.js'; @@ -32,6 +30,9 @@ export class NoteDeleteService { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -42,13 +43,11 @@ export class NoteDeleteService { private instancesRepository: InstancesRepository, private userEntityService: UserEntityService, - private noteEntityService: NoteEntityService, private globalEventService: GlobalEventService, private relayService: RelayService, private federatedInstanceService: FederatedInstanceService, private apRendererService: ApRendererService, private apDeliverManagerService: ApDeliverManagerService, - private metaService: MetaService, private searchService: SearchService, private moderationLogService: ModerationLogService, private notesChart: NotesChart, @@ -102,17 +101,15 @@ export class NoteDeleteService { } //#endregion - const meta = await this.metaService.fetch(); - this.notesChart.update(note, false); - if (meta.enableChartsForRemoteUser || (user.host == null)) { + if (this.meta.enableChartsForRemoteUser || (user.host == null)) { this.perUserNotesChart.update(user, note, false); } if (this.userEntityService.isRemoteUser(user)) { this.federatedInstanceService.fetch(user.host).then(async i => { this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1); - if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { + if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.updateNote(i.host, note, false); } }); diff --git a/packages/backend/src/core/ProxyAccountService.ts b/packages/backend/src/core/ProxyAccountService.ts index 71d663bf90..c3ff2a68d3 100644 --- a/packages/backend/src/core/ProxyAccountService.ts +++ b/packages/backend/src/core/ProxyAccountService.ts @@ -4,26 +4,25 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository } from '@/models/_.js'; +import type { MiMeta, UsersRepository } from '@/models/_.js'; import type { MiLocalUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; -import { MetaService } from '@/core/MetaService.js'; import { bindThis } from '@/decorators.js'; @Injectable() export class ProxyAccountService { constructor( + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, - - private metaService: MetaService, ) { } @bindThis public async fetch(): Promise<MiLocalUser | null> { - const meta = await this.metaService.fetch(); - if (meta.proxyAccountId == null) return null; - return await this.usersRepository.findOneByOrFail({ id: meta.proxyAccountId }) as MiLocalUser; + if (this.meta.proxyAccountId == null) return null; + return await this.usersRepository.findOneByOrFail({ id: this.meta.proxyAccountId }) as MiLocalUser; } } diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index 6a845b951d..1479bb00d9 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -10,8 +10,7 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import type { Packed } from '@/misc/json-schema.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; -import type { MiSwSubscription, SwSubscriptionsRepository } from '@/models/_.js'; -import { MetaService } from '@/core/MetaService.js'; +import type { MiMeta, MiSwSubscription, SwSubscriptionsRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { RedisKVCache } from '@/misc/cache.js'; @@ -54,13 +53,14 @@ export class PushNotificationService implements OnApplicationShutdown { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.redis) private redisClient: Redis.Redis, @Inject(DI.swSubscriptionsRepository) private swSubscriptionsRepository: SwSubscriptionsRepository, - - private metaService: MetaService, ) { this.subscriptionsCache = new RedisKVCache<MiSwSubscription[]>(this.redisClient, 'userSwSubscriptions', { lifetime: 1000 * 60 * 60 * 1, // 1h @@ -73,14 +73,12 @@ export class PushNotificationService implements OnApplicationShutdown { @bindThis public async pushNotification<T extends keyof PushNotificationsTypes>(userId: string, type: T, body: PushNotificationsTypes[T]) { - const meta = await this.metaService.fetch(); - - if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return; + if (!this.meta.enableServiceWorker || this.meta.swPublicKey == null || this.meta.swPrivateKey == null) return; // アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録 push.setVapidDetails(this.config.url, - meta.swPublicKey, - meta.swPrivateKey); + this.meta.swPublicKey, + this.meta.swPrivateKey); const subscriptions = await this.subscriptionsCache.fetch(userId); diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 80827a500b..f35e456556 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -87,6 +87,12 @@ export class QueueService { repeat: { pattern: '*/5 * * * *' }, removeOnComplete: true, }); + + this.systemQueue.add('bakeBufferedReactions', { + }, { + repeat: { pattern: '0 0 * * *' }, + removeOnComplete: true, + }); } @bindThis @@ -452,10 +458,15 @@ export class QueueService { /** * @see UserWebhookDeliverJobData - * @see WebhookDeliverProcessorService + * @see UserWebhookDeliverProcessorService */ @bindThis - public userWebhookDeliver(webhook: MiWebhook, type: typeof webhookEventTypes[number], content: unknown) { + public userWebhookDeliver( + webhook: MiWebhook, + type: typeof webhookEventTypes[number], + content: unknown, + opts?: { attempts?: number }, + ) { const data: UserWebhookDeliverJobData = { type, content, @@ -468,7 +479,7 @@ export class QueueService { }; return this.userWebhookDeliverQueue.add(webhook.id, data, { - attempts: 4, + attempts: opts?.attempts ?? 4, backoff: { type: 'custom', }, @@ -479,10 +490,15 @@ export class QueueService { /** * @see SystemWebhookDeliverJobData - * @see WebhookDeliverProcessorService + * @see SystemWebhookDeliverProcessorService */ @bindThis - public systemWebhookDeliver(webhook: MiSystemWebhook, type: SystemWebhookEventType, content: unknown) { + public systemWebhookDeliver( + webhook: MiSystemWebhook, + type: SystemWebhookEventType, + content: unknown, + opts?: { attempts?: number }, + ) { const data: SystemWebhookDeliverJobData = { type, content, @@ -494,7 +510,7 @@ export class QueueService { }; return this.systemWebhookDeliverQueue.add(webhook.id, data, { - attempts: 4, + attempts: opts?.attempts ?? 4, backoff: { type: 'custom', }, diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 371207c33a..062d64f46b 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -4,9 +4,8 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; -import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/_.js'; +import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository, MiMeta } from '@/models/_.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { MiRemoteUser, MiUser } from '@/models/User.js'; import type { MiNote } from '@/models/Note.js'; @@ -21,7 +20,6 @@ import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerServ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; -import { MetaService } from '@/core/MetaService.js'; import { bindThis } from '@/decorators.js'; import { UtilityService } from '@/core/UtilityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; @@ -30,9 +28,10 @@ import { RoleService } from '@/core/RoleService.js'; import { FeaturedService } from '@/core/FeaturedService.js'; import { trackPromise } from '@/misc/promise-tracker.js'; import { isQuote, isRenote } from '@/misc/is-renote.js'; +import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js'; +import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js'; const FALLBACK = '\u2764'; -const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16; const legacies: Record<string, string> = { 'like': '👍', @@ -71,8 +70,8 @@ const decodeCustomEmojiRegexp = /^:([\w+-]+)(?:@([\w.-]+))?:$/; @Injectable() export class ReactionService { constructor( - @Inject(DI.redis) - private redisClient: Redis.Redis, + @Inject(DI.meta) + private meta: MiMeta, @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -87,12 +86,12 @@ export class ReactionService { private emojisRepository: EmojisRepository, private utilityService: UtilityService, - private metaService: MetaService, private customEmojiService: CustomEmojiService, private roleService: RoleService, private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, private userBlockingService: UserBlockingService, + private reactionsBufferingService: ReactionsBufferingService, private idService: IdService, private featuredService: FeaturedService, private globalEventService: GlobalEventService, @@ -105,8 +104,6 @@ export class ReactionService { @bindThis public async create(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot'] }, note: MiNote, _reaction?: string | null) { - const meta = await this.metaService.fetch(); - // Check blocking if (note.userId !== user.id) { const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id); @@ -152,7 +149,7 @@ export class ReactionService { } // for media silenced host, custom emoji reactions are not allowed - if (reacterHost != null && this.utilityService.isMediaSilencedHost(meta.mediaSilencedHosts, reacterHost)) { + if (reacterHost != null && this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, reacterHost)) { reaction = FALLBACK; } } else { @@ -174,7 +171,6 @@ export class ReactionService { reaction, }; - // Create reaction try { await this.noteReactionsRepository.insert(record); } catch (e) { @@ -198,16 +194,20 @@ export class ReactionService { } // Increment reactions count - const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`; - await this.notesRepository.createQueryBuilder().update() - .set({ - reactions: () => sql, - ...(note.reactionAndUserPairCache.length < PER_NOTE_REACTION_USER_PAIR_CACHE_MAX ? { - reactionAndUserPairCache: () => `array_append("reactionAndUserPairCache", '${user.id}/${reaction}')`, - } : {}), - }) - .where('id = :id', { id: note.id }) - .execute(); + if (this.meta.enableReactionsBuffering) { + await this.reactionsBufferingService.create(note.id, user.id, reaction, note.reactionAndUserPairCache); + } else { + const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`; + await this.notesRepository.createQueryBuilder().update() + .set({ + reactions: () => sql, + ...(note.reactionAndUserPairCache.length < PER_NOTE_REACTION_USER_PAIR_CACHE_MAX ? { + reactionAndUserPairCache: () => `array_append("reactionAndUserPairCache", '${user.id}/${reaction}')`, + } : {}), + }) + .where('id = :id', { id: note.id }) + .execute(); + } // 30%の確率、セルフではない、3日以内に投稿されたノートの場合ハイライト用ランキング更新 if ( @@ -227,7 +227,7 @@ export class ReactionService { } } - if (meta.enableChartsForRemoteUser || (user.host == null)) { + if (this.meta.enableChartsForRemoteUser || (user.host == null)) { this.perUserReactionsChart.update(user, note); } @@ -305,14 +305,18 @@ export class ReactionService { } // Decrement reactions count - const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`; - await this.notesRepository.createQueryBuilder().update() - .set({ - reactions: () => sql, - reactionAndUserPairCache: () => `array_remove("reactionAndUserPairCache", '${user.id}/${exist.reaction}')`, - }) - .where('id = :id', { id: note.id }) - .execute(); + if (this.meta.enableReactionsBuffering) { + await this.reactionsBufferingService.delete(note.id, user.id, exist.reaction); + } else { + const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`; + await this.notesRepository.createQueryBuilder().update() + .set({ + reactions: () => sql, + reactionAndUserPairCache: () => `array_remove("reactionAndUserPairCache", '${user.id}/${exist.reaction}')`, + }) + .where('id = :id', { id: note.id }) + .execute(); + } this.globalEventService.publishNoteStream(note.id, 'unreacted', { reaction: this.decodeReaction(exist.reaction).reaction, @@ -333,6 +337,7 @@ export class ReactionService { //#endregion } + // TODO: 廃止 /** * 文字列タイプのレガシーな形式のリアクションを現在の形式に変換しつつ、 * データベース上には存在する「0個のリアクションがついている」という情報を削除する。 diff --git a/packages/backend/src/core/ReactionsBufferingService.ts b/packages/backend/src/core/ReactionsBufferingService.ts new file mode 100644 index 0000000000..b1a197feeb --- /dev/null +++ b/packages/backend/src/core/ReactionsBufferingService.ts @@ -0,0 +1,162 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import * as Redis from 'ioredis'; +import { DI } from '@/di-symbols.js'; +import type { MiNote } from '@/models/Note.js'; +import { bindThis } from '@/decorators.js'; +import type { MiUser, NotesRepository } from '@/models/_.js'; +import type { Config } from '@/config.js'; +import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js'; + +const REDIS_DELTA_PREFIX = 'reactionsBufferDeltas'; +const REDIS_PAIR_PREFIX = 'reactionsBufferPairs'; + +@Injectable() +export class ReactionsBufferingService { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.redisForReactions) + private redisForReactions: Redis.Redis, // TODO: 専用のRedisインスタンスにする + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + ) { + } + + @bindThis + public async create(noteId: MiNote['id'], userId: MiUser['id'], reaction: string, currentPairs: string[]): Promise<void> { + const pipeline = this.redisForReactions.pipeline(); + pipeline.hincrby(`${REDIS_DELTA_PREFIX}:${noteId}`, reaction, 1); + for (let i = 0; i < currentPairs.length; i++) { + pipeline.zadd(`${REDIS_PAIR_PREFIX}:${noteId}`, i, currentPairs[i]); + } + pipeline.zadd(`${REDIS_PAIR_PREFIX}:${noteId}`, Date.now(), `${userId}/${reaction}`); + pipeline.zremrangebyrank(`${REDIS_PAIR_PREFIX}:${noteId}`, 0, -(PER_NOTE_REACTION_USER_PAIR_CACHE_MAX + 1)); + await pipeline.exec(); + } + + @bindThis + public async delete(noteId: MiNote['id'], userId: MiUser['id'], reaction: string): Promise<void> { + const pipeline = this.redisForReactions.pipeline(); + pipeline.hincrby(`${REDIS_DELTA_PREFIX}:${noteId}`, reaction, -1); + pipeline.zrem(`${REDIS_PAIR_PREFIX}:${noteId}`, `${userId}/${reaction}`); + // TODO: 「消した要素一覧」も持っておかないとcreateされた時に上書きされて復活する + await pipeline.exec(); + } + + @bindThis + public async get(noteId: MiNote['id']): Promise<{ + deltas: Record<string, number>; + pairs: ([MiUser['id'], string])[]; + }> { + const pipeline = this.redisForReactions.pipeline(); + pipeline.hgetall(`${REDIS_DELTA_PREFIX}:${noteId}`); + pipeline.zrange(`${REDIS_PAIR_PREFIX}:${noteId}`, 0, -1); + const results = await pipeline.exec(); + + const resultDeltas = results![0][1] as Record<string, string>; + const resultPairs = results![1][1] as string[]; + + const deltas = {} as Record<string, number>; + for (const [name, count] of Object.entries(resultDeltas)) { + deltas[name] = parseInt(count); + } + + const pairs = resultPairs.map(x => x.split('/') as [MiUser['id'], string]); + + return { + deltas, + pairs, + }; + } + + @bindThis + public async getMany(noteIds: MiNote['id'][]): Promise<Map<MiNote['id'], { + deltas: Record<string, number>; + pairs: ([MiUser['id'], string])[]; + }>> { + const map = new Map<MiNote['id'], { + deltas: Record<string, number>; + pairs: ([MiUser['id'], string])[]; + }>(); + + const pipeline = this.redisForReactions.pipeline(); + for (const noteId of noteIds) { + pipeline.hgetall(`${REDIS_DELTA_PREFIX}:${noteId}`); + pipeline.zrange(`${REDIS_PAIR_PREFIX}:${noteId}`, 0, -1); + } + const results = await pipeline.exec(); + + const opsForEachNotes = 2; + for (let i = 0; i < noteIds.length; i++) { + const noteId = noteIds[i]; + const resultDeltas = results![i * opsForEachNotes][1] as Record<string, string>; + const resultPairs = results![i * opsForEachNotes + 1][1] as string[]; + + const deltas = {} as Record<string, number>; + for (const [name, count] of Object.entries(resultDeltas)) { + deltas[name] = parseInt(count); + } + + const pairs = resultPairs.map(x => x.split('/') as [MiUser['id'], string]); + + map.set(noteId, { + deltas, + pairs, + }); + } + + return map; + } + + // TODO: scanは重い可能性があるので、別途 bufferedNoteIds を直接Redis上に持っておいてもいいかもしれない + @bindThis + public async bake(): Promise<void> { + const bufferedNoteIds = []; + let cursor = '0'; + do { + // https://github.com/redis/ioredis#transparent-key-prefixing + const result = await this.redisForReactions.scan( + cursor, + 'MATCH', + `${this.config.redis.prefix}:${REDIS_DELTA_PREFIX}:*`, + 'COUNT', + '1000'); + + cursor = result[0]; + bufferedNoteIds.push(...result[1].map(x => x.replace(`${this.config.redis.prefix}:${REDIS_DELTA_PREFIX}:`, ''))); + } while (cursor !== '0'); + + const bufferedMap = await this.getMany(bufferedNoteIds); + + // clear + const pipeline = this.redisForReactions.pipeline(); + for (const noteId of bufferedNoteIds) { + pipeline.del(`${REDIS_DELTA_PREFIX}:${noteId}`); + pipeline.del(`${REDIS_PAIR_PREFIX}:${noteId}`); + } + await pipeline.exec(); + + // TODO: SQL一個にまとめたい + for (const [noteId, buffered] of bufferedMap) { + const sql = Object.entries(buffered.deltas) + .map(([reaction, count]) => + `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + ${count})::text::jsonb)`) + .join(' || '); + + this.notesRepository.createQueryBuilder().update() + .set({ + reactions: () => sql, + reactionAndUserPairCache: buffered.pairs.map(x => x.join('/')), + }) + .where('id = :id', { id: noteId }) + .execute(); + } + } +} diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 0210012a03..583eea1a34 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -8,6 +8,7 @@ import * as Redis from 'ioredis'; import { In } from 'typeorm'; import { ModuleRef } from '@nestjs/core'; import type { + MiMeta, MiRole, MiRoleAssignment, RoleAssignmentsRepository, @@ -18,7 +19,6 @@ import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js'; import type { MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; -import { MetaService } from '@/core/MetaService.js'; import { CacheService } from '@/core/CacheService.js'; import type { RoleCondFormulaValue } from '@/models/Role.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -58,6 +58,11 @@ export type RolePolicies = { userEachUserListsLimit: number; rateLimitFactor: number; avatarDecorationLimit: number; + canImportAntennas: boolean; + canImportBlocking: boolean; + canImportFollowing: boolean; + canImportMuting: boolean; + canImportUserLists: boolean; }; export const DEFAULT_POLICIES: RolePolicies = { @@ -87,6 +92,11 @@ export const DEFAULT_POLICIES: RolePolicies = { userEachUserListsLimit: 50, rateLimitFactor: 1, avatarDecorationLimit: 1, + canImportAntennas: true, + canImportBlocking: true, + canImportFollowing: true, + canImportMuting: true, + canImportUserLists: true, }; @Injectable() @@ -101,8 +111,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { constructor( private moduleRef: ModuleRef, - @Inject(DI.redis) - private redisClient: Redis.Redis, + @Inject(DI.meta) + private meta: MiMeta, @Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis, @@ -119,7 +129,6 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { @Inject(DI.roleAssignmentsRepository) private roleAssignmentsRepository: RoleAssignmentsRepository, - private metaService: MetaService, private cacheService: CacheService, private userEntityService: UserEntityService, private globalEventService: GlobalEventService, @@ -339,8 +348,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { @bindThis public async getUserPolicies(userId: MiUser['id'] | null): Promise<RolePolicies> { - const meta = await this.metaService.fetch(); - const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies }; + const basePolicies = { ...DEFAULT_POLICIES, ...this.meta.policies }; if (userId == null) return basePolicies; @@ -387,6 +395,11 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { userEachUserListsLimit: calc('userEachUserListsLimit', vs => Math.max(...vs)), rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)), avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)), + canImportAntennas: calc('canImportAntennas', vs => vs.some(v => v === true)), + canImportBlocking: calc('canImportBlocking', vs => vs.some(v => v === true)), + canImportFollowing: calc('canImportFollowing', vs => vs.some(v => v === true)), + canImportMuting: calc('canImportMuting', vs => vs.some(v => v === true)), + canImportUserLists: calc('canImportUserLists', vs => vs.some(v => v === true)), }; } diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index de45898328..cc8a3d6461 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import { DataSource, IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { UsedUsernamesRepository, UsersRepository } from '@/models/_.js'; +import type { MiMeta, UsedUsernamesRepository, UsersRepository } from '@/models/_.js'; import { MiUser } from '@/models/User.js'; import { MiUserProfile } from '@/models/UserProfile.js'; import { IdService } from '@/core/IdService.js'; @@ -20,7 +20,6 @@ import { InstanceActorService } from '@/core/InstanceActorService.js'; import { bindThis } from '@/decorators.js'; import UsersChart from '@/core/chart/charts/users.js'; import { UtilityService } from '@/core/UtilityService.js'; -import { MetaService } from '@/core/MetaService.js'; import { UserService } from '@/core/UserService.js'; @Injectable() @@ -29,6 +28,9 @@ export class SignupService { @Inject(DI.db) private db: DataSource, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -39,7 +41,6 @@ export class SignupService { private userService: UserService, private userEntityService: UserEntityService, private idService: IdService, - private metaService: MetaService, private instanceActorService: InstanceActorService, private usersChart: UsersChart, ) { @@ -88,8 +89,7 @@ export class SignupService { const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent(); if (!opts.ignorePreservedUsernames && !isTheFirstUser) { - const instance = await this.metaService.fetch(true); - const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase()); + const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase()); if (isPreserved) { throw new Error('USED_USERNAME'); } diff --git a/packages/backend/src/core/SystemWebhookService.ts b/packages/backend/src/core/SystemWebhookService.ts index bc6851f788..bb7c6b8c0e 100644 --- a/packages/backend/src/core/SystemWebhookService.ts +++ b/packages/backend/src/core/SystemWebhookService.ts @@ -54,7 +54,7 @@ export class SystemWebhookService implements OnApplicationShutdown { * SystemWebhook の一覧を取得する. */ @bindThis - public async fetchSystemWebhooks(params?: { + public fetchSystemWebhooks(params?: { ids?: MiSystemWebhook['id'][]; isActive?: MiSystemWebhook['isActive']; on?: MiSystemWebhook['on']; @@ -165,19 +165,24 @@ export class SystemWebhookService implements OnApplicationShutdown { /** * SystemWebhook をWebhook配送キューに追加する * @see QueueService.systemWebhookDeliver + * // TODO: contentの型を厳格化する */ @bindThis - public async enqueueSystemWebhook(webhook: MiSystemWebhook | MiSystemWebhook['id'], type: SystemWebhookEventType, content: unknown) { + public async enqueueSystemWebhook<T extends SystemWebhookEventType>( + webhook: MiSystemWebhook | MiSystemWebhook['id'], + type: T, + content: unknown, + ) { const webhookEntity = typeof webhook === 'string' ? (await this.fetchActiveSystemWebhooks()).find(a => a.id === webhook) : webhook; if (!webhookEntity || !webhookEntity.isActive) { - this.logger.info(`Webhook is not active or not found : ${webhook}`); + this.logger.info(`SystemWebhook is not active or not found : ${webhook}`); return; } if (!webhookEntity.on.includes(type)) { - this.logger.info(`Webhook ${webhookEntity.id} is not listening to ${type}`); + this.logger.info(`SystemWebhook ${webhookEntity.id} is not listening to ${type}`); return; } diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 6aab8fde70..3f1c6b7125 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -13,23 +13,20 @@ import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { IdService } from '@/core/IdService.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; -import type { Packed } from '@/misc/json-schema.js'; import InstanceChart from '@/core/chart/charts/instance.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { UserWebhookService } from '@/core/UserWebhookService.js'; import { NotificationService } from '@/core/NotificationService.js'; import { DI } from '@/di-symbols.js'; -import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, MiMeta, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { bindThis } from '@/decorators.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; -import { MetaService } from '@/core/MetaService.js'; import { CacheService } from '@/core/CacheService.js'; import type { Config } from '@/config.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; import { UtilityService } from '@/core/UtilityService.js'; -import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import type { ThinUser } from '@/queue/types.js'; import Logger from '../logger.js'; @@ -58,6 +55,9 @@ export class UserFollowingService implements OnModuleInit { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -79,13 +79,11 @@ export class UserFollowingService implements OnModuleInit { private idService: IdService, private queueService: QueueService, private globalEventService: GlobalEventService, - private metaService: MetaService, private notificationService: NotificationService, private federatedInstanceService: FederatedInstanceService, private webhookService: UserWebhookService, private apRendererService: ApRendererService, private accountMoveService: AccountMoveService, - private fanoutTimelineService: FanoutTimelineService, private perUserFollowingChart: PerUserFollowingChart, private instanceChart: InstanceChart, ) { @@ -172,7 +170,7 @@ export class UserFollowingService implements OnModuleInit { followee.isLocked || (followeeProfile.carefulBot && follower.isBot) || (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true') || - (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower) && this.utilityService.isSilencedHost((await this.metaService.fetch()).silencedHosts, follower.host)) + (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower) && this.utilityService.isSilencedHost(this.meta.silencedHosts, follower.host)) ) { let autoAccept = false; @@ -307,14 +305,14 @@ export class UserFollowingService implements OnModuleInit { if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { this.federatedInstanceService.fetch(follower.host).then(async i => { this.instancesRepository.increment({ id: i.id }, 'followingCount', 1); - if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { + if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.updateFollowing(i.host, true); } }); } else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { this.federatedInstanceService.fetch(followee.host).then(async i => { this.instancesRepository.increment({ id: i.id }, 'followersCount', 1); - if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { + if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.updateFollowers(i.host, true); } }); @@ -439,14 +437,14 @@ export class UserFollowingService implements OnModuleInit { if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { this.federatedInstanceService.fetch(follower.host).then(async i => { this.instancesRepository.decrement({ id: i.id }, 'followingCount', 1); - if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { + if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.updateFollowing(i.host, false); } }); } else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { this.federatedInstanceService.fetch(followee.host).then(async i => { this.instancesRepository.decrement({ id: i.id }, 'followersCount', 1); - if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { + if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.updateFollowers(i.host, false); } }); diff --git a/packages/backend/src/core/UserWebhookService.ts b/packages/backend/src/core/UserWebhookService.ts index e96bfeea95..8a40a53688 100644 --- a/packages/backend/src/core/UserWebhookService.ts +++ b/packages/backend/src/core/UserWebhookService.ts @@ -5,8 +5,8 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; -import type { WebhooksRepository } from '@/models/_.js'; -import type { MiWebhook } from '@/models/Webhook.js'; +import { type WebhooksRepository } from '@/models/_.js'; +import { MiWebhook } from '@/models/Webhook.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { GlobalEvents } from '@/core/GlobalEventService.js'; @@ -38,6 +38,31 @@ export class UserWebhookService implements OnApplicationShutdown { return this.activeWebhooks; } + /** + * UserWebhook の一覧を取得する. + */ + @bindThis + public fetchWebhooks(params?: { + ids?: MiWebhook['id'][]; + isActive?: MiWebhook['active']; + on?: MiWebhook['on']; + }): Promise<MiWebhook[]> { + const query = this.webhooksRepository.createQueryBuilder('webhook'); + if (params) { + if (params.ids && params.ids.length > 0) { + query.andWhere('webhook.id IN (:...ids)', { ids: params.ids }); + } + if (params.isActive !== undefined) { + query.andWhere('webhook.active = :isActive', { isActive: params.isActive }); + } + if (params.on && params.on.length > 0) { + query.andWhere(':on <@ webhook.on', { on: params.on }); + } + } + + return query.getMany(); + } + @bindThis private async onMessage(_: string, data: string): Promise<void> { const obj = JSON.parse(data); diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts index ec9f4484a4..a40c6ff1c9 100644 --- a/packages/backend/src/core/WebAuthnService.ts +++ b/packages/backend/src/core/WebAuthnService.ts @@ -12,10 +12,9 @@ import { } from '@simplewebauthn/server'; import { AttestationFormat, isoCBOR, isoUint8Array } from '@simplewebauthn/server/helpers'; import { DI } from '@/di-symbols.js'; -import type { UserSecurityKeysRepository } from '@/models/_.js'; +import type { MiMeta, UserSecurityKeysRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; import { bindThis } from '@/decorators.js'; -import { MetaService } from '@/core/MetaService.js'; import { MiUser } from '@/models/_.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { @@ -23,7 +22,6 @@ import type { AuthenticatorTransportFuture, CredentialDeviceType, PublicKeyCredentialCreationOptionsJSON, - PublicKeyCredentialDescriptorFuture, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON, } from '@simplewebauthn/types'; @@ -31,33 +29,33 @@ import type { @Injectable() export class WebAuthnService { constructor( - @Inject(DI.redis) - private redisClient: Redis.Redis, - @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + + @Inject(DI.redis) + private redisClient: Redis.Redis, + @Inject(DI.userSecurityKeysRepository) private userSecurityKeysRepository: UserSecurityKeysRepository, - - private metaService: MetaService, ) { } @bindThis - public async getRelyingParty(): Promise<{ origin: string; rpId: string; rpName: string; rpIcon?: string; }> { - const instance = await this.metaService.fetch(); + public getRelyingParty(): { origin: string; rpId: string; rpName: string; rpIcon?: string; } { return { origin: this.config.url, rpId: this.config.hostname, - rpName: instance.name ?? this.config.host, - rpIcon: instance.iconUrl ?? undefined, + rpName: this.meta.name ?? this.config.host, + rpIcon: this.meta.iconUrl ?? undefined, }; } @bindThis public async initiateRegistration(userId: MiUser['id'], userName: string, userDisplayName?: string): Promise<PublicKeyCredentialCreationOptionsJSON> { - const relyingParty = await this.getRelyingParty(); + const relyingParty = this.getRelyingParty(); const keys = await this.userSecurityKeysRepository.findBy({ userId: userId, }); @@ -104,7 +102,7 @@ export class WebAuthnService { await this.redisClient.del(`webauthn:challenge:${userId}`); - const relyingParty = await this.getRelyingParty(); + const relyingParty = this.getRelyingParty(); let verification; try { @@ -143,7 +141,7 @@ export class WebAuthnService { @bindThis public async initiateAuthentication(userId: MiUser['id']): Promise<PublicKeyCredentialRequestOptionsJSON> { - const relyingParty = await this.getRelyingParty(); + const relyingParty = this.getRelyingParty(); const keys = await this.userSecurityKeysRepository.findBy({ userId: userId, }); @@ -209,7 +207,7 @@ export class WebAuthnService { } } - const relyingParty = await this.getRelyingParty(); + const relyingParty = this.getRelyingParty(); let verification; try { diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts new file mode 100644 index 0000000000..0b4e107d21 --- /dev/null +++ b/packages/backend/src/core/WebhookTestService.ts @@ -0,0 +1,434 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import { MiAbuseUserReport, MiNote, MiUser, MiWebhook } from '@/models/_.js'; +import { bindThis } from '@/decorators.js'; +import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWebhook.js'; +import { SystemWebhookService } from '@/core/SystemWebhookService.js'; +import { Packed } from '@/misc/json-schema.js'; +import { type WebhookEventTypes } from '@/models/Webhook.js'; +import { UserWebhookService } from '@/core/UserWebhookService.js'; +import { QueueService } from '@/core/QueueService.js'; + +const oneDayMillis = 24 * 60 * 60 * 1000; + +function generateAbuseReport(override?: Partial<MiAbuseUserReport>): MiAbuseUserReport { + return { + id: 'dummy-abuse-report1', + targetUserId: 'dummy-target-user', + targetUser: null, + reporterId: 'dummy-reporter-user', + reporter: null, + assigneeId: null, + assignee: null, + resolved: false, + forwarded: false, + comment: 'This is a dummy report for testing purposes.', + targetUserHost: null, + reporterHost: null, + ...override, + }; +} + +function generateDummyUser(override?: Partial<MiUser>): MiUser { + return { + id: 'dummy-user-1', + updatedAt: new Date(Date.now() - oneDayMillis * 7), + lastFetchedAt: new Date(Date.now() - oneDayMillis * 5), + lastActiveDate: new Date(Date.now() - oneDayMillis * 3), + hideOnlineStatus: false, + username: 'dummy1', + usernameLower: 'dummy1', + name: 'DummyUser1', + followersCount: 10, + followingCount: 5, + movedToUri: null, + movedAt: null, + alsoKnownAs: null, + notesCount: 30, + avatarId: null, + avatar: null, + bannerId: null, + banner: null, + avatarUrl: null, + bannerUrl: null, + avatarBlurhash: null, + bannerBlurhash: null, + avatarDecorations: [], + tags: [], + isSuspended: false, + isLocked: false, + isBot: false, + isCat: true, + isRoot: false, + isExplorable: true, + isHibernated: false, + isDeleted: false, + emojis: [], + host: null, + inbox: null, + sharedInbox: null, + featured: null, + uri: null, + followersUri: null, + token: null, + ...override, + }; +} + +function generateDummyNote(override?: Partial<MiNote>): MiNote { + return { + id: 'dummy-note-1', + replyId: null, + reply: null, + renoteId: null, + renote: null, + threadId: null, + text: 'This is a dummy note for testing purposes.', + name: null, + cw: null, + userId: 'dummy-user-1', + user: null, + localOnly: true, + reactionAcceptance: 'likeOnly', + renoteCount: 10, + repliesCount: 5, + clippedCount: 0, + reactions: {}, + visibility: 'public', + uri: null, + url: null, + fileIds: [], + attachedFileTypes: [], + visibleUserIds: [], + mentions: [], + mentionedRemoteUsers: '[]', + reactionAndUserPairCache: [], + emojis: [], + tags: [], + hasPoll: false, + channelId: null, + channel: null, + userHost: null, + replyUserId: null, + replyUserHost: null, + renoteUserId: null, + renoteUserHost: null, + ...override, + }; +} + +function toPackedNote(note: MiNote, detail = true, override?: Packed<'Note'>): Packed<'Note'> { + return { + id: note.id, + createdAt: new Date().toISOString(), + deletedAt: null, + text: note.text, + cw: note.cw, + userId: note.userId, + user: toPackedUserLite(note.user ?? generateDummyUser()), + replyId: note.replyId, + renoteId: note.renoteId, + isHidden: false, + visibility: note.visibility, + mentions: note.mentions, + visibleUserIds: note.visibleUserIds, + fileIds: note.fileIds, + files: [], + tags: note.tags, + poll: null, + emojis: note.emojis, + channelId: note.channelId, + channel: note.channel, + localOnly: note.localOnly, + reactionAcceptance: note.reactionAcceptance, + reactionEmojis: {}, + reactions: {}, + reactionCount: 0, + renoteCount: note.renoteCount, + repliesCount: note.repliesCount, + uri: note.uri ?? undefined, + url: note.url ?? undefined, + reactionAndUserPairCache: note.reactionAndUserPairCache, + ...(detail ? { + clippedCount: note.clippedCount, + reply: note.reply ? toPackedNote(note.reply, false) : null, + renote: note.renote ? toPackedNote(note.renote, true) : null, + myReaction: null, + } : {}), + ...override, + }; +} + +function toPackedUserLite(user: MiUser, override?: Packed<'UserLite'>): Packed<'UserLite'> { + return { + id: user.id, + name: user.name, + username: user.username, + host: user.host, + avatarUrl: user.avatarUrl, + avatarBlurhash: user.avatarBlurhash, + avatarDecorations: user.avatarDecorations.map(it => ({ + id: it.id, + angle: it.angle, + flipH: it.flipH, + url: 'https://example.com/dummy-image001.png', + offsetX: it.offsetX, + offsetY: it.offsetY, + })), + isBot: user.isBot, + isCat: user.isCat, + emojis: user.emojis, + onlineStatus: 'active', + badgeRoles: [], + ...override, + }; +} + +function toPackedUserDetailedNotMe(user: MiUser, override?: Packed<'UserDetailedNotMe'>): Packed<'UserDetailedNotMe'> { + return { + ...toPackedUserLite(user), + url: null, + uri: null, + movedTo: null, + alsoKnownAs: [], + createdAt: new Date().toISOString(), + updatedAt: user.updatedAt?.toISOString() ?? null, + lastFetchedAt: user.lastFetchedAt?.toISOString() ?? null, + bannerUrl: user.bannerUrl, + bannerBlurhash: user.bannerBlurhash, + isLocked: user.isLocked, + isSilenced: false, + isSuspended: user.isSuspended, + description: null, + location: null, + birthday: null, + lang: null, + fields: [], + verifiedLinks: [], + followersCount: user.followersCount, + followingCount: user.followingCount, + notesCount: user.notesCount, + pinnedNoteIds: [], + pinnedNotes: [], + pinnedPageId: null, + pinnedPage: null, + publicReactions: true, + followersVisibility: 'public', + followingVisibility: 'public', + twoFactorEnabled: false, + usePasswordLessLogin: false, + securityKeys: false, + roles: [], + memo: null, + moderationNote: undefined, + isFollowing: false, + isFollowed: false, + hasPendingFollowRequestFromYou: false, + hasPendingFollowRequestToYou: false, + isBlocking: false, + isBlocked: false, + isMuted: false, + isRenoteMuted: false, + notify: 'none', + withReplies: true, + ...override, + }; +} + +const dummyUser1 = generateDummyUser(); +const dummyUser2 = generateDummyUser({ + id: 'dummy-user-2', + updatedAt: new Date(Date.now() - oneDayMillis * 30), + lastFetchedAt: new Date(Date.now() - oneDayMillis), + lastActiveDate: new Date(Date.now() - oneDayMillis), + username: 'dummy2', + usernameLower: 'dummy2', + name: 'DummyUser2', + followersCount: 40, + followingCount: 50, + notesCount: 900, +}); +const dummyUser3 = generateDummyUser({ + id: 'dummy-user-3', + updatedAt: new Date(Date.now() - oneDayMillis * 15), + lastFetchedAt: new Date(Date.now() - oneDayMillis * 2), + lastActiveDate: new Date(Date.now() - oneDayMillis * 2), + username: 'dummy3', + usernameLower: 'dummy3', + name: 'DummyUser3', + followersCount: 60, + followingCount: 70, + notesCount: 15900, +}); + +@Injectable() +export class WebhookTestService { + public static NoSuchWebhookError = class extends Error {}; + + constructor( + private userWebhookService: UserWebhookService, + private systemWebhookService: SystemWebhookService, + private queueService: QueueService, + ) { + } + + /** + * UserWebhookのテスト送信を行う. + * 送信されるペイロードはいずれもダミーの値で、実際にはデータベース上に存在しない. + * + * また、この関数経由で送信されるWebhookは以下の設定を無視する. + * - Webhookそのものの有効・無効設定(active) + * - 送信対象イベント(on)に関する設定 + */ + @bindThis + public async testUserWebhook( + params: { + webhookId: MiWebhook['id'], + type: WebhookEventTypes, + override?: Partial<Omit<MiWebhook, 'id'>>, + }, + sender: MiUser | null, + ) { + const webhooks = await this.userWebhookService.fetchWebhooks({ ids: [params.webhookId] }) + .then(it => it.filter(it => it.userId === sender?.id)); + if (webhooks.length === 0) { + throw new WebhookTestService.NoSuchWebhookError(); + } + + const webhook = webhooks[0]; + const send = (contents: unknown) => { + const merged = { + ...webhook, + ...params.override, + }; + + // テスト目的なのでUserWebhookServiceの機能を経由せず直接キューに追加する(チェック処理などをスキップする意図). + // また、Jobの試行回数も1回だけ. + this.queueService.userWebhookDeliver(merged, params.type, contents, { attempts: 1 }); + }; + + const dummyNote1 = generateDummyNote({ + userId: dummyUser1.id, + user: dummyUser1, + }); + const dummyReply1 = generateDummyNote({ + id: 'dummy-reply-1', + replyId: dummyNote1.id, + reply: dummyNote1, + userId: dummyUser1.id, + user: dummyUser1, + }); + const dummyRenote1 = generateDummyNote({ + id: 'dummy-renote-1', + renoteId: dummyNote1.id, + renote: dummyNote1, + userId: dummyUser2.id, + user: dummyUser2, + text: null, + }); + const dummyMention1 = generateDummyNote({ + id: 'dummy-mention-1', + userId: dummyUser1.id, + user: dummyUser1, + text: `@${dummyUser2.username} This is a mention to you.`, + mentions: [dummyUser2.id], + }); + + switch (params.type) { + case 'note': { + send(toPackedNote(dummyNote1)); + break; + } + case 'reply': { + send(toPackedNote(dummyReply1)); + break; + } + case 'renote': { + send(toPackedNote(dummyRenote1)); + break; + } + case 'mention': { + send(toPackedNote(dummyMention1)); + break; + } + case 'follow': { + send(toPackedUserDetailedNotMe(dummyUser1)); + break; + } + case 'followed': { + send(toPackedUserLite(dummyUser2)); + break; + } + case 'unfollow': { + send(toPackedUserDetailedNotMe(dummyUser3)); + break; + } + } + } + + /** + * SystemWebhookのテスト送信を行う. + * 送信されるペイロードはいずれもダミーの値で、実際にはデータベース上に存在しない. + * + * また、この関数経由で送信されるWebhookは以下の設定を無視する. + * - Webhookそのものの有効・無効設定(isActive) + * - 送信対象イベント(on)に関する設定 + */ + @bindThis + public async testSystemWebhook( + params: { + webhookId: MiSystemWebhook['id'], + type: SystemWebhookEventType, + override?: Partial<Omit<MiSystemWebhook, 'id'>>, + }, + ) { + const webhooks = await this.systemWebhookService.fetchSystemWebhooks({ ids: [params.webhookId] }); + if (webhooks.length === 0) { + throw new WebhookTestService.NoSuchWebhookError(); + } + + const webhook = webhooks[0]; + const send = (contents: unknown) => { + const merged = { + ...webhook, + ...params.override, + }; + + // テスト目的なのでSystemWebhookServiceの機能を経由せず直接キューに追加する(チェック処理などをスキップする意図). + // また、Jobの試行回数も1回だけ. + this.queueService.systemWebhookDeliver(merged, params.type, contents, { attempts: 1 }); + }; + + switch (params.type) { + case 'abuseReport': { + send(generateAbuseReport({ + targetUserId: dummyUser1.id, + targetUser: dummyUser1, + reporterId: dummyUser2.id, + reporter: dummyUser2, + })); + break; + } + case 'abuseReportResolved': { + send(generateAbuseReport({ + targetUserId: dummyUser1.id, + targetUser: dummyUser1, + reporterId: dummyUser2.id, + reporter: dummyUser2, + assigneeId: dummyUser3.id, + assignee: dummyUser3, + resolved: true, + })); + break; + } + case 'userCreated': { + send(toPackedUserLite(dummyUser1)); + break; + } + } + } +} diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index e2164fec1d..90da032895 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -17,14 +17,13 @@ import { NoteCreateService } from '@/core/NoteCreateService.js'; import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js'; import { AppLockService } from '@/core/AppLockService.js'; import type Logger from '@/logger.js'; -import { MetaService } from '@/core/MetaService.js'; import { IdService } from '@/core/IdService.js'; import { StatusError } from '@/misc/status-error.js'; import { UtilityService } from '@/core/UtilityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { QueueService } from '@/core/QueueService.js'; -import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/_.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import type { MiRemoteUser } from '@/models/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; @@ -48,6 +47,9 @@ export class ApInboxService { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -64,7 +66,6 @@ export class ApInboxService { private noteEntityService: NoteEntityService, private utilityService: UtilityService, private idService: IdService, - private metaService: MetaService, private abuseReportService: AbuseReportService, private userFollowingService: UserFollowingService, private apAudienceService: ApAudienceService, @@ -290,8 +291,7 @@ export class ApInboxService { } // アナウンス先をブロックしてたら中断 - const meta = await this.metaService.fetch(); - if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) return; + if (this.utilityService.isBlockedHost(this.meta.blockedHosts, this.utilityService.extractDbHost(uri))) return; const unlock = await this.appLockService.getApLock(uri); diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index 7cf8359212..7c78f3319b 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -207,16 +207,41 @@ export class ApRequestService { if ((contentType ?? '').split(';')[0].trimEnd().toLowerCase() === 'text/html' && _followAlternate === true) { const html = await res.text(); - const window = new Window(); + const { window, happyDOM } = new Window({ + settings: { + disableJavaScriptEvaluation: true, + disableJavaScriptFileLoading: true, + disableCSSFileLoading: true, + disableComputedStyleRendering: true, + handleDisabledFileLoadingAsSuccess: true, + navigation: { + disableMainFrameNavigation: true, + disableChildFrameNavigation: true, + disableChildPageNavigation: true, + disableFallbackToSetURL: true, + }, + timer: { + maxTimeout: 0, + maxIntervalTime: 0, + maxIntervalIterations: 0, + }, + }, + }); const document = window.document; - document.documentElement.innerHTML = html; + try { + document.documentElement.innerHTML = html; - const alternate = document.querySelector('head > link[rel="alternate"][type="application/activity+json"]'); - if (alternate) { - const href = alternate.getAttribute('href'); - if (href) { - return await this.signedGet(href, user, false); + const alternate = document.querySelector('head > link[rel="alternate"][type="application/activity+json"]'); + if (alternate) { + const href = alternate.getAttribute('href'); + if (href) { + return await this.signedGet(href, user, false); + } } + } catch (e) { + // something went wrong parsing the HTML, ignore the whole thing + } finally { + happyDOM.close().catch(err => {}); } } //#endregion diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts index bb3c40f093..fdef7a8ffd 100644 --- a/packages/backend/src/core/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -7,9 +7,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, Not } from 'typeorm'; import type { MiLocalUser, MiRemoteUser } from '@/models/User.js'; import { InstanceActorService } from '@/core/InstanceActorService.js'; -import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository } from '@/models/_.js'; +import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js'; import type { Config } from '@/config.js'; -import { MetaService } from '@/core/MetaService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { DI } from '@/di-symbols.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -29,6 +28,7 @@ export class Resolver { constructor( private config: Config, + private meta: MiMeta, private usersRepository: UsersRepository, private notesRepository: NotesRepository, private pollsRepository: PollsRepository, @@ -36,7 +36,6 @@ export class Resolver { private followRequestsRepository: FollowRequestsRepository, private utilityService: UtilityService, private instanceActorService: InstanceActorService, - private metaService: MetaService, private apRequestService: ApRequestService, private httpRequestService: HttpRequestService, private apRendererService: ApRendererService, @@ -94,8 +93,7 @@ export class Resolver { return await this.resolveLocal(value); } - const meta = await this.metaService.fetch(); - if (this.utilityService.isBlockedHost(meta.blockedHosts, host)) { + if (this.utilityService.isBlockedHost(this.meta.blockedHosts, host)) { throw new Error('Instance is blocked'); } @@ -178,6 +176,9 @@ export class ApResolverService { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -195,7 +196,6 @@ export class ApResolverService { private utilityService: UtilityService, private instanceActorService: InstanceActorService, - private metaService: MetaService, private apRequestService: ApRequestService, private httpRequestService: HttpRequestService, private apRendererService: ApRendererService, @@ -208,6 +208,7 @@ export class ApResolverService { public createResolver(): Resolver { return new Resolver( this.config, + this.meta, this.usersRepository, this.notesRepository, this.pollsRepository, @@ -215,7 +216,6 @@ export class ApResolverService { this.followRequestsRepository, this.utilityService, this.instanceActorService, - this.metaService, this.apRequestService, this.httpRequestService, this.apRendererService, diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts index 3691967270..e7ece87b01 100644 --- a/packages/backend/src/core/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -5,10 +5,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository } from '@/models/_.js'; +import type { DriveFilesRepository, MiMeta } from '@/models/_.js'; import type { MiRemoteUser } from '@/models/User.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; -import { MetaService } from '@/core/MetaService.js'; import { truncate } from '@/misc/truncate.js'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js'; import { DriveService } from '@/core/DriveService.js'; @@ -24,10 +23,12 @@ export class ApImageService { private logger: Logger; constructor( + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, - private metaService: MetaService, private apResolverService: ApResolverService, private driveService: DriveService, private apLoggerService: ApLoggerService, @@ -63,12 +64,10 @@ export class ApImageService { this.logger.info(`Creating the Image: ${image.url}`); - const instance = await this.metaService.fetch(); - // Cache if remote file cache is on AND either // 1. remote sensitive file is also on // 2. or the image is not sensitive - const shouldBeCached = instance.cacheRemoteFiles && (instance.cacheRemoteSensitiveFiles || !image.sensitive); + const shouldBeCached = this.meta.cacheRemoteFiles && (this.meta.cacheRemoteSensitiveFiles || !image.sensitive); const file = await this.driveService.uploadFromUrl({ url: image.url, diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 5b75da22a0..00acb19a0f 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -6,13 +6,12 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { PollsRepository, EmojisRepository } from '@/models/_.js'; +import type { PollsRepository, EmojisRepository, MiMeta } from '@/models/_.js'; import type { Config } from '@/config.js'; import type { MiRemoteUser } from '@/models/User.js'; import type { MiNote } from '@/models/Note.js'; import { toArray, toSingle, unique } from '@/misc/prelude/array.js'; import type { MiEmoji } from '@/models/Emoji.js'; -import { MetaService } from '@/core/MetaService.js'; import { AppLockService } from '@/core/AppLockService.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; import { NoteCreateService } from '@/core/NoteCreateService.js'; @@ -46,6 +45,9 @@ export class ApNoteService { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.pollsRepository) private pollsRepository: PollsRepository, @@ -65,7 +67,6 @@ export class ApNoteService { private apMentionService: ApMentionService, private apImageService: ApImageService, private apQuestionService: ApQuestionService, - private metaService: MetaService, private appLockService: AppLockService, private pollService: PollService, private noteCreateService: NoteCreateService, @@ -182,7 +183,7 @@ export class ApNoteService { /** * 禁止ワードチェック */ - const hasProhibitedWords = await this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices }); + const hasProhibitedWords = this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices }); if (hasProhibitedWords) { throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words'); } @@ -336,8 +337,7 @@ export class ApNoteService { const uri = getApId(value); // ブロックしていたら中断 - const meta = await this.metaService.fetch(); - if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) { + if (this.utilityService.isBlockedHost(this.meta.blockedHosts, this.utilityService.extractDbHost(uri))) { throw new StatusError('blocked host', 451); } diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index f3ddf3952c..39c18e5e15 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -8,7 +8,7 @@ import promiseLimit from 'promise-limit'; import { DataSource } from 'typeorm'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; -import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/_.js'; +import type { FollowingsRepository, InstancesRepository, MiMeta, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; import type { MiLocalUser, MiRemoteUser } from '@/models/User.js'; import { MiUser } from '@/models/User.js'; @@ -35,7 +35,6 @@ import type { UtilityService } from '@/core/UtilityService.js'; import type { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; -import { MetaService } from '@/core/MetaService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import type { AccountMoveService } from '@/core/AccountMoveService.js'; import { checkHttps } from '@/misc/check-https.js'; @@ -62,7 +61,6 @@ export class ApPersonService implements OnModuleInit { private driveFileEntityService: DriveFileEntityService; private idService: IdService; private globalEventService: GlobalEventService; - private metaService: MetaService; private federatedInstanceService: FederatedInstanceService; private fetchInstanceMetadataService: FetchInstanceMetadataService; private cacheService: CacheService; @@ -84,6 +82,9 @@ export class ApPersonService implements OnModuleInit { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.db) private db: DataSource, @@ -112,7 +113,6 @@ export class ApPersonService implements OnModuleInit { this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService'); this.idService = this.moduleRef.get('IdService'); this.globalEventService = this.moduleRef.get('GlobalEventService'); - this.metaService = this.moduleRef.get('MetaService'); this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService'); this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService'); this.cacheService = this.moduleRef.get('CacheService'); @@ -407,10 +407,10 @@ export class ApPersonService implements OnModuleInit { this.cacheService.uriPersonCache.set(user.uri, user); // Register host - this.federatedInstanceService.fetch(host).then(async i => { + this.federatedInstanceService.fetch(host).then(i => { this.instancesRepository.increment({ id: i.id }, 'usersCount', 1); this.fetchInstanceMetadataService.fetchInstanceMetadata(i); - if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { + if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.newUser(i.host); } }); diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts index f40a26495d..c9b43cc66d 100644 --- a/packages/backend/src/core/chart/charts/federation.ts +++ b/packages/backend/src/core/chart/charts/federation.ts @@ -5,10 +5,9 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import type { FollowingsRepository, InstancesRepository } from '@/models/_.js'; +import type { FollowingsRepository, InstancesRepository, MiMeta } from '@/models/_.js'; import { AppLockService } from '@/core/AppLockService.js'; import { DI } from '@/di-symbols.js'; -import { MetaService } from '@/core/MetaService.js'; import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; import { ChartLoggerService } from '../ChartLoggerService.js'; @@ -24,13 +23,15 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di @Inject(DI.db) private db: DataSource, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, - private metaService: MetaService, private appLockService: AppLockService, private chartLoggerService: ChartLoggerService, ) { @@ -43,8 +44,6 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di } protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> { - const meta = await this.metaService.fetch(); - const suspendedInstancesQuery = this.instancesRepository.createQueryBuilder('instance') .select('instance.host') .where('instance.suspensionState != \'none\''); @@ -65,21 +64,21 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di this.followingsRepository.createQueryBuilder('following') .select('COUNT(DISTINCT following.followeeHost)') .where('following.followeeHost IS NOT NULL') - .andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }) + .andWhere(this.meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }) .andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`) .getRawOne() .then(x => parseInt(x.count, 10)), this.followingsRepository.createQueryBuilder('following') .select('COUNT(DISTINCT following.followerHost)') .where('following.followerHost IS NOT NULL') - .andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }) + .andWhere(this.meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }) .andWhere(`following.followerHost NOT IN (${ suspendedInstancesQuery.getQuery() })`) .getRawOne() .then(x => parseInt(x.count, 10)), this.followingsRepository.createQueryBuilder('following') .select('COUNT(DISTINCT following.followeeHost)') .where('following.followeeHost IS NOT NULL') - .andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }) + .andWhere(this.meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }) .andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`) .andWhere(`following.followeeHost IN (${ pubsubSubQuery.getQuery() })`) .setParameters(pubsubSubQuery.getParameters()) @@ -88,7 +87,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di this.instancesRepository.createQueryBuilder('instance') .select('COUNT(instance.id)') .where(`instance.host IN (${ subInstancesQuery.getQuery() })`) - .andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }) + .andWhere(this.meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }) .andWhere('instance.suspensionState = \'none\'') .andWhere('instance.isNotResponding = false') .getRawOne() @@ -96,7 +95,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di this.instancesRepository.createQueryBuilder('instance') .select('COUNT(instance.id)') .where(`instance.host IN (${ pubInstancesQuery.getQuery() })`) - .andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }) + .andWhere(this.meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) }) .andWhere('instance.suspensionState = \'none\'') .andWhere('instance.isNotResponding = false') .getRawOne() diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index 4956bc22ce..284537b986 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -3,19 +3,22 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import type { Packed } from '@/misc/json-schema.js'; import type { MiInstance } from '@/models/Instance.js'; -import { MetaService } from '@/core/MetaService.js'; import { bindThis } from '@/decorators.js'; import { UtilityService } from '@/core/UtilityService.js'; import { RoleService } from '@/core/RoleService.js'; import { MiUser } from '@/models/User.js'; +import { DI } from '@/di-symbols.js'; +import { MiMeta } from '@/models/_.js'; @Injectable() export class InstanceEntityService { constructor( - private metaService: MetaService, + @Inject(DI.meta) + private meta: MiMeta, + private roleService: RoleService, private utilityService: UtilityService, @@ -27,7 +30,6 @@ export class InstanceEntityService { instance: MiInstance, me?: { id: MiUser['id']; } | null | undefined, ): Promise<Packed<'FederationInstance'>> { - const meta = await this.metaService.fetch(); const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false; return { @@ -41,7 +43,7 @@ export class InstanceEntityService { isNotResponding: instance.isNotResponding, isSuspended: instance.suspensionState !== 'none', suspensionState: instance.suspensionState, - isBlocked: this.utilityService.isBlockedHost(meta.blockedHosts, instance.host), + isBlocked: this.utilityService.isBlockedHost(this.meta.blockedHosts, instance.host), softwareName: instance.softwareName, softwareVersion: instance.softwareVersion, openRegistrations: instance.openRegistrations, @@ -49,8 +51,8 @@ export class InstanceEntityService { description: instance.description, maintainerName: instance.maintainerName, maintainerEmail: instance.maintainerEmail, - isSilenced: this.utilityService.isSilencedHost(meta.silencedHosts, instance.host), - isMediaSilenced: this.utilityService.isMediaSilencedHost(meta.mediaSilencedHosts, instance.host), + isSilenced: this.utilityService.isSilencedHost(this.meta.silencedHosts, instance.host), + isMediaSilenced: this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, instance.host), iconUrl: instance.iconUrl, faviconUrl: instance.faviconUrl, themeColor: instance.themeColor, diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index f4b1e302d0..fbd982eb34 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -10,7 +10,6 @@ import type { Packed } from '@/misc/json-schema.js'; import type { MiMeta } from '@/models/Meta.js'; import type { AdsRepository } from '@/models/_.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import { MetaService } from '@/core/MetaService.js'; import { bindThis } from '@/decorators.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { InstanceActorService } from '@/core/InstanceActorService.js'; @@ -24,11 +23,13 @@ export class MetaEntityService { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.adsRepository) private adsRepository: AdsRepository, private userEntityService: UserEntityService, - private metaService: MetaService, private instanceActorService: InstanceActorService, ) { } @@ -37,7 +38,7 @@ export class MetaEntityService { let instance = meta; if (!instance) { - instance = await this.metaService.fetch(); + instance = this.meta; } const ads = await this.adsRepository.createQueryBuilder('ads') @@ -140,7 +141,7 @@ export class MetaEntityService { let instance = meta; if (!instance) { - instance = await this.metaService.fetch(); + instance = this.meta; } const packed = await this.pack(instance); diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 2cd092231c..7e64b9fc8d 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -11,29 +11,46 @@ import type { Packed } from '@/misc/json-schema.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { MiUser } from '@/models/User.js'; import type { MiNote } from '@/models/Note.js'; -import type { MiNoteReaction } from '@/models/NoteReaction.js'; -import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository } from '@/models/_.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository, MiMeta } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { DebounceLoader } from '@/misc/loader.js'; import { IdService } from '@/core/IdService.js'; +import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js'; +import { MetaService } from '@/core/MetaService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { ReactionService } from '../ReactionService.js'; import type { UserEntityService } from './UserEntityService.js'; import type { DriveFileEntityService } from './DriveFileEntityService.js'; +function mergeReactions(src: Record<string, number>, delta: Record<string, number>) { + const reactions = { ...src }; + for (const [name, count] of Object.entries(delta)) { + if (reactions[name] != null) { + reactions[name] += count; + } else { + reactions[name] = count; + } + } + return reactions; +} + @Injectable() export class NoteEntityService implements OnModuleInit { private userEntityService: UserEntityService; private driveFileEntityService: DriveFileEntityService; private customEmojiService: CustomEmojiService; private reactionService: ReactionService; + private reactionsBufferingService: ReactionsBufferingService; private idService: IdService; private noteLoader = new DebounceLoader(this.findNoteOrFail); constructor( private moduleRef: ModuleRef, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -59,6 +76,8 @@ export class NoteEntityService implements OnModuleInit { //private driveFileEntityService: DriveFileEntityService, //private customEmojiService: CustomEmojiService, //private reactionService: ReactionService, + //private reactionsBufferingService: ReactionsBufferingService, + //private idService: IdService, ) { } @@ -67,6 +86,7 @@ export class NoteEntityService implements OnModuleInit { this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService'); this.customEmojiService = this.moduleRef.get('CustomEmojiService'); this.reactionService = this.moduleRef.get('ReactionService'); + this.reactionsBufferingService = this.moduleRef.get('ReactionsBufferingService'); this.idService = this.moduleRef.get('IdService'); } @@ -287,6 +307,7 @@ export class NoteEntityService implements OnModuleInit { skipHide?: boolean; withReactionAndUserPairCache?: boolean; _hint_?: { + bufferedReactions: Map<MiNote['id'], { deltas: Record<string, number>; pairs: ([MiUser['id'], string])[] }> | null; myReactions: Map<MiNote['id'], string | null>; packedFiles: Map<MiNote['fileIds'][number], Packed<'DriveFile'> | null>; packedUsers: Map<MiUser['id'], Packed<'UserLite'>> @@ -303,6 +324,20 @@ export class NoteEntityService implements OnModuleInit { const note = typeof src === 'object' ? src : await this.noteLoader.load(src); const host = note.userHost; + const bufferedReactions = opts._hint_?.bufferedReactions != null + ? (opts._hint_.bufferedReactions.get(note.id) ?? { deltas: {}, pairs: [] }) + : this.meta.enableReactionsBuffering + ? await this.reactionsBufferingService.get(note.id) + : { deltas: {}, pairs: [] }; + const reactions = mergeReactions(this.reactionService.convertLegacyReactions(note.reactions), bufferedReactions.deltas ?? {}); + for (const [name, count] of Object.entries(reactions)) { + if (count <= 0) { + delete reactions[name]; + } + } + + const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferedReactions.pairs.map(x => x.join('/'))); + let text = note.text; if (note.name && (note.url ?? note.uri)) { @@ -315,7 +350,7 @@ export class NoteEntityService implements OnModuleInit { : await this.channelsRepository.findOneBy({ id: note.channelId }) : null; - const reactionEmojiNames = Object.keys(note.reactions) + const reactionEmojiNames = Object.keys(reactions) .filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) // リモートカスタム絵文字のみ .map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', '')); const packedFiles = options?._hint_?.packedFiles; @@ -334,10 +369,10 @@ export class NoteEntityService implements OnModuleInit { visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined, renoteCount: note.renoteCount, repliesCount: note.repliesCount, - reactionCount: Object.values(note.reactions).reduce((a, b) => a + b, 0), - reactions: this.reactionService.convertLegacyReactions(note.reactions), + reactionCount: Object.values(reactions).reduce((a, b) => a + b, 0), + reactions: reactions, reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host), - reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined, + reactionAndUserPairCache: opts.withReactionAndUserPairCache ? reactionAndUserPairCache : undefined, emojis: host != null ? this.customEmojiService.populateEmojis(note.emojis, host) : undefined, tags: note.tags.length > 0 ? note.tags : undefined, fileIds: note.fileIds, @@ -376,8 +411,12 @@ export class NoteEntityService implements OnModuleInit { poll: note.hasPoll ? this.populatePoll(note, meId) : undefined, - ...(meId && Object.keys(note.reactions).length > 0 ? { - myReaction: this.populateMyReaction(note, meId, options?._hint_), + ...(meId && Object.keys(reactions).length > 0 ? { + myReaction: this.populateMyReaction({ + id: note.id, + reactions: reactions, + reactionAndUserPairCache: reactionAndUserPairCache, + }, meId, options?._hint_), } : {}), } : {}), }); @@ -400,6 +439,8 @@ export class NoteEntityService implements OnModuleInit { ) { if (notes.length === 0) return []; + const bufferedReactions = this.meta.enableReactionsBuffering ? await this.reactionsBufferingService.getMany(notes.map(x => x.id)) : null; + const meId = me ? me.id : null; const myReactionsMap = new Map<MiNote['id'], string | null>(); if (meId) { @@ -410,23 +451,33 @@ export class NoteEntityService implements OnModuleInit { for (const note of notes) { if (note.renote && (note.text == null && note.fileIds.length === 0)) { // pure renote - const reactionsCount = Object.values(note.renote.reactions).reduce((a, b) => a + b, 0); + const reactionsCount = Object.values(mergeReactions(note.renote.reactions, bufferedReactions?.get(note.renote.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); if (reactionsCount === 0) { myReactionsMap.set(note.renote.id, null); - } else if (reactionsCount <= note.renote.reactionAndUserPairCache.length) { - const pair = note.renote.reactionAndUserPairCache.find(p => p.startsWith(meId)); - myReactionsMap.set(note.renote.id, pair ? pair.split('/')[1] : null); + } else if (reactionsCount <= note.renote.reactionAndUserPairCache.length + (bufferedReactions?.get(note.renote.id)?.pairs.length ?? 0)) { + const pairInBuffer = bufferedReactions?.get(note.renote.id)?.pairs.find(p => p[0] === meId); + if (pairInBuffer) { + myReactionsMap.set(note.renote.id, pairInBuffer[1]); + } else { + const pair = note.renote.reactionAndUserPairCache.find(p => p.startsWith(meId)); + myReactionsMap.set(note.renote.id, pair ? pair.split('/')[1] : null); + } } else { idsNeedFetchMyReaction.add(note.renote.id); } } else { if (note.id < oldId) { - const reactionsCount = Object.values(note.reactions).reduce((a, b) => a + b, 0); + const reactionsCount = Object.values(mergeReactions(note.reactions, bufferedReactions?.get(note.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); if (reactionsCount === 0) { myReactionsMap.set(note.id, null); - } else if (reactionsCount <= note.reactionAndUserPairCache.length) { - const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId)); - myReactionsMap.set(note.id, pair ? pair.split('/')[1] : null); + } else if (reactionsCount <= note.reactionAndUserPairCache.length + (bufferedReactions?.get(note.id)?.pairs.length ?? 0)) { + const pairInBuffer = bufferedReactions?.get(note.id)?.pairs.find(p => p[0] === meId); + if (pairInBuffer) { + myReactionsMap.set(note.id, pairInBuffer[1]); + } else { + const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId)); + myReactionsMap.set(note.id, pair ? pair.split('/')[1] : null); + } } else { idsNeedFetchMyReaction.add(note.id); } @@ -461,6 +512,7 @@ export class NoteEntityService implements OnModuleInit { return await Promise.all(notes.map(n => this.pack(n, me, { ...options, _hint_: { + bufferedReactions, myReactions: myReactionsMap, packedFiles, packedUsers, diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts index 2c70344c94..d229efb123 100644 --- a/packages/backend/src/daemons/ServerStatsService.ts +++ b/packages/backend/src/daemons/ServerStatsService.ts @@ -3,13 +3,14 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import si from 'systeminformation'; import Xev from 'xev'; import * as osUtils from 'os-utils'; import { bindThis } from '@/decorators.js'; -import { MetaService } from '@/core/MetaService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +import { MiMeta } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; const ev = new Xev(); @@ -23,7 +24,8 @@ export class ServerStatsService implements OnApplicationShutdown { private intervalId: NodeJS.Timeout | null = null; constructor( - private metaService: MetaService, + @Inject(DI.meta) + private meta: MiMeta, ) { } @@ -32,7 +34,7 @@ export class ServerStatsService implements OnApplicationShutdown { */ @bindThis public async start(): Promise<void> { - if (!(await this.metaService.fetch(true)).enableServerMachineStats) return; + if (!this.meta.enableServerMachineStats) return; const log = [] as any[]; diff --git a/packages/backend/src/decorators.ts b/packages/backend/src/decorators.ts index 21777657d1..42f925e125 100644 --- a/packages/backend/src/decorators.ts +++ b/packages/backend/src/decorators.ts @@ -10,8 +10,9 @@ * The getter will return a .bind version of the function * and memoize the result against a symbol on the instance */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function bindThis(target: any, key: string, descriptor: any) { - let fn = descriptor.value; + const fn = descriptor.value; if (typeof fn !== 'function') { throw new TypeError(`@bindThis decorator can only be applied to methods not: ${typeof fn}`); @@ -21,26 +22,18 @@ export function bindThis(target: any, key: string, descriptor: any) { configurable: true, get() { // eslint-disable-next-line no-prototype-builtins - if (this === target.prototype || this.hasOwnProperty(key) || - typeof fn !== 'function') { + if (this === target.prototype || this.hasOwnProperty(key)) { return fn; } const boundFn = fn.bind(this); - Object.defineProperty(this, key, { + Reflect.defineProperty(this, key, { + value: boundFn, configurable: true, - get() { - return boundFn; - }, - set(value) { - fn = value; - delete this[key]; - }, + writable: true, }); + return boundFn; }, - set(value: any) { - fn = value; - }, }; } diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index 271082b4ff..e599fc7b37 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -6,11 +6,13 @@ export const DI = { config: Symbol('config'), db: Symbol('db'), + meta: Symbol('meta'), meilisearch: Symbol('meilisearch'), redis: Symbol('redis'), redisForPub: Symbol('redisForPub'), redisForSub: Symbol('redisForSub'), redisForTimelines: Symbol('redisForTimelines'), + redisForReactions: Symbol('redisForReactions'), //#region Repositories usersRepository: Symbol('usersRepository'), diff --git a/packages/backend/src/misc/fastify-hook-handlers.ts b/packages/backend/src/misc/fastify-hook-handlers.ts index 3e1c099e00..fa3ef0a267 100644 --- a/packages/backend/src/misc/fastify-hook-handlers.ts +++ b/packages/backend/src/misc/fastify-hook-handlers.ts @@ -8,7 +8,7 @@ import type { onRequestHookHandler } from 'fastify'; export const handleRequestRedirectToOmitSearch: onRequestHookHandler = (request, reply, done) => { const index = request.url.indexOf('?'); if (~index) { - reply.redirect(301, request.url.slice(0, index)); + reply.redirect(request.url.slice(0, index), 301); } done(); }; diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 70d41801b5..9ab76d373f 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -589,6 +589,11 @@ export class MiMeta { }) public perUserListTimelineCacheMax: number; + @Column('boolean', { + default: false, + }) + public enableReactionsBuffering: boolean; + @Column('integer', { default: 0, }) diff --git a/packages/backend/src/models/Webhook.ts b/packages/backend/src/models/Webhook.ts index db24c03b3d..b4cab4edc8 100644 --- a/packages/backend/src/models/Webhook.ts +++ b/packages/backend/src/models/Webhook.ts @@ -8,6 +8,7 @@ import { id } from './util/id.js'; import { MiUser } from './User.js'; export const webhookEventTypes = ['mention', 'unfollow', 'follow', 'followed', 'note', 'reply', 'renote', 'reaction'] as const; +export type WebhookEventTypes = typeof webhookEventTypes[number]; @Entity('webhook') export class MiWebhook { diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index 7366f05356..3537de94c8 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -272,6 +272,26 @@ export const packedRolePoliciesSchema = { type: 'integer', optional: false, nullable: false, }, + canImportAntennas: { + type: 'boolean', + optional: false, nullable: false, + }, + canImportBlocking: { + type: 'boolean', + optional: false, nullable: false, + }, + canImportFollowing: { + type: 'boolean', + optional: false, nullable: false, + }, + canImportMuting: { + type: 'boolean', + optional: false, nullable: false, + }, + canImportUserLists: { + type: 'boolean', + optional: false, nullable: false, + }, }, } as const; diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts index a1fd38fcc5..0027b5ef3d 100644 --- a/packages/backend/src/queue/QueueProcessorModule.ts +++ b/packages/backend/src/queue/QueueProcessorModule.ts @@ -14,6 +14,7 @@ import { InboxProcessorService } from './processors/InboxProcessorService.js'; import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js'; import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js'; import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js'; +import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js'; import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js'; import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js'; @@ -51,6 +52,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor ResyncChartsProcessorService, CleanChartsProcessorService, CheckExpiredMutingsProcessorService, + BakeBufferedReactionsProcessorService, CleanProcessorService, DeleteDriveFilesProcessorService, ExportCustomEmojisProcessorService, diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 7bd74f3210..e9e1c45224 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -39,6 +39,7 @@ import { TickChartsProcessorService } from './processors/TickChartsProcessorServ import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js'; import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js'; import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js'; +import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js'; import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js'; import { QueueLoggerService } from './QueueLoggerService.js'; @@ -118,6 +119,7 @@ export class QueueProcessorService implements OnApplicationShutdown { private cleanChartsProcessorService: CleanChartsProcessorService, private aggregateRetentionProcessorService: AggregateRetentionProcessorService, private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService, + private bakeBufferedReactionsProcessorService: BakeBufferedReactionsProcessorService, private cleanProcessorService: CleanProcessorService, ) { this.logger = this.queueLoggerService.logger; @@ -147,6 +149,7 @@ export class QueueProcessorService implements OnApplicationShutdown { case 'cleanCharts': return this.cleanChartsProcessorService.process(); case 'aggregateRetention': return this.aggregateRetentionProcessorService.process(); case 'checkExpiredMutings': return this.checkExpiredMutingsProcessorService.process(); + case 'bakeBufferedReactions': return this.bakeBufferedReactionsProcessorService.process(); case 'clean': return this.cleanProcessorService.process(); default: throw new Error(`unrecognized job type ${job.name} for system`); } diff --git a/packages/backend/src/queue/processors/BakeBufferedReactionsProcessorService.ts b/packages/backend/src/queue/processors/BakeBufferedReactionsProcessorService.ts new file mode 100644 index 0000000000..d49c99f694 --- /dev/null +++ b/packages/backend/src/queue/processors/BakeBufferedReactionsProcessorService.ts @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; +import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; +import type * as Bull from 'bullmq'; +import { MiMeta } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; + +@Injectable() +export class BakeBufferedReactionsProcessorService { + private logger: Logger; + + constructor( + @Inject(DI.meta) + private meta: MiMeta, + + private reactionsBufferingService: ReactionsBufferingService, + private queueLoggerService: QueueLoggerService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('bake-buffered-reactions'); + } + + @bindThis + public async process(): Promise<void> { + if (!this.meta.enableReactionsBuffering) { + this.logger.info('Reactions buffering is disabled. Skipping...'); + return; + } + + this.logger.info('Baking buffered reactions...'); + + await this.reactionsBufferingService.bake(); + + this.logger.succ('All buffered reactions baked.'); + } +} diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 4076e9da90..fc9078251f 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -7,9 +7,8 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Bull from 'bullmq'; import { Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { InstancesRepository } from '@/models/_.js'; +import type { InstancesRepository, MiMeta } from '@/models/_.js'; import type Logger from '@/logger.js'; -import { MetaService } from '@/core/MetaService.js'; import { ApRequestService } from '@/core/activitypub/ApRequestService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; @@ -31,10 +30,12 @@ export class DeliverProcessorService { private latest: string | null; constructor( + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, - private metaService: MetaService, private utilityService: UtilityService, private federatedInstanceService: FederatedInstanceService, private fetchInstanceMetadataService: FetchInstanceMetadataService, @@ -53,8 +54,7 @@ export class DeliverProcessorService { const { host } = new URL(job.data.to); // ブロックしてたら中断 - const meta = await this.metaService.fetch(); - if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.toPuny(host))) { + if (this.utilityService.isBlockedHost(this.meta.blockedHosts, this.utilityService.toPuny(host))) { return 'skip (blocked)'; } @@ -88,7 +88,7 @@ export class DeliverProcessorService { this.apRequestChart.deliverSucc(); this.federationChart.deliverd(i.host, true); - if (meta.enableChartsForFederatedInstances) { + if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.requestSent(i.host, true); } }); @@ -120,7 +120,7 @@ export class DeliverProcessorService { this.apRequestChart.deliverFail(); this.federationChart.deliverd(i.host, false); - if (meta.enableChartsForFederatedInstances) { + if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.requestSent(i.host, false); } }); diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts index 171809d25c..9e1b8fee70 100644 --- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts @@ -87,23 +87,30 @@ export class ImportCustomEmojisProcessorService { await this.emojisRepository.delete({ name: emojiInfo.name, }); - const driveFile = await this.driveService.addFile({ - user: null, - path: emojiPath, - name: record.fileName, - force: true, - }); - await this.customEmojiService.add({ - name: emojiInfo.name, - category: emojiInfo.category, - host: null, - aliases: emojiInfo.aliases, - driveFile, - license: emojiInfo.license, - isSensitive: emojiInfo.isSensitive, - localOnly: emojiInfo.localOnly, - roleIdsThatCanBeUsedThisEmojiAsReaction: [], - }); + try { + const driveFile = await this.driveService.addFile({ + user: null, + path: emojiPath, + name: record.fileName, + force: true, + }); + await this.customEmojiService.add({ + name: emojiInfo.name, + category: emojiInfo.category, + host: null, + aliases: emojiInfo.aliases, + driveFile, + license: emojiInfo.license, + isSensitive: emojiInfo.isSensitive, + localOnly: emojiInfo.localOnly, + roleIdsThatCanBeUsedThisEmojiAsReaction: [], + }); + } catch (e) { + if (e instanceof Error || typeof e === 'string') { + this.logger.error(`couldn't import ${emojiPath} for ${emojiInfo.name}: ${e}`); + } + continue; + } } cleanup(); diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index fa7009f8f5..2df37bedf4 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -4,11 +4,10 @@ */ import { URL } from 'node:url'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import httpSignature from '@peertube/http-signature'; import * as Bull from 'bullmq'; import type Logger from '@/logger.js'; -import { MetaService } from '@/core/MetaService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; import InstanceChart from '@/core/chart/charts/instance.js'; @@ -28,14 +27,18 @@ import { bindThis } from '@/decorators.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type { InboxJobData } from '../types.js'; +import { MiMeta } from '@/models/Meta.js'; +import { DI } from '@/di-symbols.js'; @Injectable() export class InboxProcessorService { private logger: Logger; constructor( + @Inject(DI.meta) + private meta: MiMeta, + private utilityService: UtilityService, - private metaService: MetaService, private apInboxService: ApInboxService, private federatedInstanceService: FederatedInstanceService, private fetchInstanceMetadataService: FetchInstanceMetadataService, @@ -64,8 +67,7 @@ export class InboxProcessorService { const host = this.utilityService.toPuny(new URL(signature.keyId).hostname); // ブロックしてたら中断 - const meta = await this.metaService.fetch(); - if (this.utilityService.isBlockedHost(meta.blockedHosts, host)) { + if (this.utilityService.isBlockedHost(this.meta.blockedHosts, host)) { return `Blocked request: ${host}`; } @@ -166,7 +168,7 @@ export class InboxProcessorService { // ブロックしてたら中断 const ldHost = this.utilityService.extractDbHost(authUser.user.uri); - if (this.utilityService.isBlockedHost(meta.blockedHosts, ldHost)) { + if (this.utilityService.isBlockedHost(this.meta.blockedHosts, ldHost)) { throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`); } } else { @@ -197,7 +199,7 @@ export class InboxProcessorService { this.apRequestChart.inbox(); this.federationChart.inbox(i.host); - if (meta.enableChartsForFederatedInstances) { + if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.requestReceived(i.host); } }); diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 77a637d895..41b6d2e83d 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -82,7 +82,7 @@ export class FileServerService { .catch(err => this.errorHandler(request, reply, err)); }); fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => { - return await reply.redirect(301, `${this.config.url}/files/${request.params.key}`); + return await reply.redirect(`${this.config.url}/files/${request.params.key}`, 301); }); done(); }); @@ -147,12 +147,12 @@ export class FileServerService { url.searchParams.set('static', '1'); file.cleanup(); - return await reply.redirect(301, url.toString()); + return await reply.redirect(url.toString(), 301); } else if (file.mime.startsWith('video/')) { const externalThumbnail = this.videoProcessingService.getExternalVideoThumbnailUrl(file.url); if (externalThumbnail) { file.cleanup(); - return await reply.redirect(301, externalThumbnail); + return await reply.redirect(externalThumbnail, 301); } image = await this.videoProcessingService.generateVideoThumbnail(file.path); @@ -167,7 +167,7 @@ export class FileServerService { url.searchParams.set('url', file.url); file.cleanup(); - return await reply.redirect(301, url.toString()); + return await reply.redirect(url.toString(), 301); } } @@ -314,8 +314,8 @@ export class FileServerService { } return await reply.redirect( - 301, url.toString(), + 301, ); } diff --git a/packages/backend/src/server/HealthServerService.ts b/packages/backend/src/server/HealthServerService.ts index 2c3ed85925..5980609f02 100644 --- a/packages/backend/src/server/HealthServerService.ts +++ b/packages/backend/src/server/HealthServerService.ts @@ -27,6 +27,9 @@ export class HealthServerService { @Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis, + @Inject(DI.redisForReactions) + private redisForReactions: Redis.Redis, + @Inject(DI.db) private db: DataSource, @@ -43,6 +46,7 @@ export class HealthServerService { this.redisForPub.ping(), this.redisForSub.ping(), this.redisForTimelines.ping(), + this.redisForReactions.ping(), this.db.query('SELECT 1'), ...(this.meilisearch ? [this.meilisearch.health()] : []), ]).then(() => 200, () => 503)); diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 9c849480f2..fd2bd3267d 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -13,7 +13,7 @@ import fastifyRawBody from 'fastify-raw-body'; import { IsNull } from 'typeorm'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { Config } from '@/config.js'; -import type { EmojisRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import type { EmojisRepository, MiMeta, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; @@ -21,7 +21,6 @@ import { genIdenticon } from '@/misc/gen-identicon.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; -import { MetaService } from '@/core/MetaService.js'; import { ActivityPubServerService } from './ActivityPubServerService.js'; import { NodeinfoServerService } from './NodeinfoServerService.js'; import { ApiServerService } from './api/ApiServerService.js'; @@ -44,6 +43,9 @@ export class ServerService implements OnApplicationShutdown { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -53,7 +55,6 @@ export class ServerService implements OnApplicationShutdown { @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, - private metaService: MetaService, private userEntityService: UserEntityService, private apiServerService: ApiServerService, private openApiServerService: OpenApiServerService, @@ -165,8 +166,8 @@ export class ServerService implements OnApplicationShutdown { } return await reply.redirect( - 301, url.toString(), + 301, ); }); @@ -193,7 +194,7 @@ export class ServerService implements OnApplicationShutdown { reply.header('Content-Type', 'image/png'); reply.header('Cache-Control', 'public, max-age=86400'); - if ((await this.metaService.fetch()).enableIdenticonGeneration) { + if (this.meta.enableIdenticonGeneration) { return await genIdenticon(request.params.x); } else { return reply.redirect('/static-assets/avatar.png'); diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index f95c272757..aad833f126 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -13,8 +13,7 @@ import { getIpHash } from '@/misc/get-ip-hash.js'; import type { MiLocalUser, MiUser } from '@/models/User.js'; import type { MiAccessToken } from '@/models/AccessToken.js'; import type Logger from '@/logger.js'; -import type { UserIpsRepository } from '@/models/_.js'; -import { MetaService } from '@/core/MetaService.js'; +import type { MiMeta, UserIpsRepository } from '@/models/_.js'; import { createTemp } from '@/misc/create-temp.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; @@ -40,13 +39,15 @@ export class ApiCallService implements OnApplicationShutdown { private userIpHistoriesClearIntervalId: NodeJS.Timeout; constructor( + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.config) private config: Config, @Inject(DI.userIpsRepository) private userIpsRepository: UserIpsRepository, - private metaService: MetaService, private authenticateService: AuthenticateService, private rateLimiterService: RateLimiterService, private roleService: RoleService, @@ -64,15 +65,6 @@ export class ApiCallService implements OnApplicationShutdown { let statusCode = err.httpStatusCode; if (err.httpStatusCode === 401) { reply.header('WWW-Authenticate', 'Bearer realm="Misskey"'); - } else if (err.kind === 'client') { - reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="invalid_request", error_description="${err.message}"`); - statusCode = statusCode ?? 400; - } else if (err.kind === 'permission') { - // (ROLE_PERMISSION_DENIEDは関係ない) - if (err.code === 'PERMISSION_DENIED') { - reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="insufficient_scope", error_description="${err.message}"`); - } - statusCode = statusCode ?? 403; } else if (err.code === 'RATE_LIMIT_EXCEEDED') { const info: unknown = err.info; const unixEpochInSeconds = Date.now(); @@ -83,6 +75,15 @@ export class ApiCallService implements OnApplicationShutdown { } else { this.logger.warn(`rate limit information has unexpected type ${typeof(err.info?.reset)}`); } + } else if (err.kind === 'client') { + reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="invalid_request", error_description="${err.message}"`); + statusCode = statusCode ?? 400; + } else if (err.kind === 'permission') { + // (ROLE_PERMISSION_DENIEDは関係ない) + if (err.code === 'PERMISSION_DENIED') { + reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="insufficient_scope", error_description="${err.message}"`); + } + statusCode = statusCode ?? 403; } else if (!statusCode) { statusCode = 500; } @@ -265,9 +266,8 @@ export class ApiCallService implements OnApplicationShutdown { } @bindThis - private async logIp(request: FastifyRequest, user: MiLocalUser) { - const meta = await this.metaService.fetch(); - if (!meta.enableIpLogging) return; + private logIp(request: FastifyRequest, user: MiLocalUser) { + if (!this.meta.enableIpLogging) return; const ip = request.ip; const ips = this.userIpHistories.get(user.id); if (ips == null || !ips.has(ip)) { diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 41576bedaa..08a0468ab2 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -92,6 +92,7 @@ import * as ep___admin_systemWebhook_delete from './endpoints/admin/system-webho import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js'; import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js'; import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js'; +import * as ep___admin_systemWebhook_test from './endpoints/admin/system-webhook/test.js'; import * as ep___announcements from './endpoints/announcements.js'; import * as ep___announcements_show from './endpoints/announcements/show.js'; import * as ep___antennas_create from './endpoints/antennas/create.js'; @@ -258,6 +259,7 @@ import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js'; import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js'; import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js'; import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js'; +import * as ep___i_webhooks_test from './endpoints/i/webhooks/test.js'; import * as ep___invite_create from './endpoints/invite/create.js'; import * as ep___invite_delete from './endpoints/invite/delete.js'; import * as ep___invite_list from './endpoints/invite/list.js'; @@ -475,6 +477,7 @@ const $admin_systemWebhook_delete: Provider = { provide: 'ep:admin/system-webhoo const $admin_systemWebhook_list: Provider = { provide: 'ep:admin/system-webhook/list', useClass: ep___admin_systemWebhook_list.default }; const $admin_systemWebhook_show: Provider = { provide: 'ep:admin/system-webhook/show', useClass: ep___admin_systemWebhook_show.default }; const $admin_systemWebhook_update: Provider = { provide: 'ep:admin/system-webhook/update', useClass: ep___admin_systemWebhook_update.default }; +const $admin_systemWebhook_test: Provider = { provide: 'ep:admin/system-webhook/test', useClass: ep___admin_systemWebhook_test.default }; const $announcements: Provider = { provide: 'ep:announcements', useClass: ep___announcements.default }; const $announcements_show: Provider = { provide: 'ep:announcements/show', useClass: ep___announcements_show.default }; const $antennas_create: Provider = { provide: 'ep:antennas/create', useClass: ep___antennas_create.default }; @@ -641,6 +644,7 @@ const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default }; const $i_webhooks_update: Provider = { provide: 'ep:i/webhooks/update', useClass: ep___i_webhooks_update.default }; const $i_webhooks_delete: Provider = { provide: 'ep:i/webhooks/delete', useClass: ep___i_webhooks_delete.default }; +const $i_webhooks_test: Provider = { provide: 'ep:i/webhooks/test', useClass: ep___i_webhooks_test.default }; const $invite_create: Provider = { provide: 'ep:invite/create', useClass: ep___invite_create.default }; const $invite_delete: Provider = { provide: 'ep:invite/delete', useClass: ep___invite_delete.default }; const $invite_list: Provider = { provide: 'ep:invite/list', useClass: ep___invite_list.default }; @@ -862,6 +866,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $admin_systemWebhook_list, $admin_systemWebhook_show, $admin_systemWebhook_update, + $admin_systemWebhook_test, $announcements, $announcements_show, $antennas_create, @@ -1028,6 +1033,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $i_webhooks_show, $i_webhooks_update, $i_webhooks_delete, + $i_webhooks_test, $invite_create, $invite_delete, $invite_list, @@ -1243,6 +1249,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $admin_systemWebhook_list, $admin_systemWebhook_show, $admin_systemWebhook_update, + $admin_systemWebhook_test, $announcements, $announcements_show, $antennas_create, @@ -1409,6 +1416,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $i_webhooks_show, $i_webhooks_update, $i_webhooks_delete, + $i_webhooks_test, $invite_create, $invite_delete, $invite_list, diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 632b0c62bc..c499638018 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -7,9 +7,8 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, MiRegistrationTicket } from '@/models/_.js'; +import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, MiRegistrationTicket, MiMeta } from '@/models/_.js'; import type { Config } from '@/config.js'; -import { MetaService } from '@/core/MetaService.js'; import { CaptchaService } from '@/core/CaptchaService.js'; import { IdService } from '@/core/IdService.js'; import { SignupService } from '@/core/SignupService.js'; @@ -28,6 +27,9 @@ export class SignupApiService { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -45,7 +47,6 @@ export class SignupApiService { private userEntityService: UserEntityService, private idService: IdService, - private metaService: MetaService, private captchaService: CaptchaService, private signupService: SignupService, private signinService: SigninService, @@ -72,31 +73,29 @@ export class SignupApiService { ) { const body = request.body; - const instance = await this.metaService.fetch(true); - // Verify *Captcha // ただしテスト時はこの機構は障害となるため無効にする if (process.env.NODE_ENV !== 'test') { - if (instance.enableHcaptcha && instance.hcaptchaSecretKey) { - await this.captchaService.verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => { + if (this.meta.enableHcaptcha && this.meta.hcaptchaSecretKey) { + await this.captchaService.verifyHcaptcha(this.meta.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => { throw new FastifyReplyError(400, err); }); } - if (instance.enableMcaptcha && instance.mcaptchaSecretKey && instance.mcaptchaSitekey && instance.mcaptchaInstanceUrl) { - await this.captchaService.verifyMcaptcha(instance.mcaptchaSecretKey, instance.mcaptchaSitekey, instance.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => { + if (this.meta.enableMcaptcha && this.meta.mcaptchaSecretKey && this.meta.mcaptchaSitekey && this.meta.mcaptchaInstanceUrl) { + await this.captchaService.verifyMcaptcha(this.meta.mcaptchaSecretKey, this.meta.mcaptchaSitekey, this.meta.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => { throw new FastifyReplyError(400, err); }); } - if (instance.enableRecaptcha && instance.recaptchaSecretKey) { - await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => { + if (this.meta.enableRecaptcha && this.meta.recaptchaSecretKey) { + await this.captchaService.verifyRecaptcha(this.meta.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => { throw new FastifyReplyError(400, err); }); } - if (instance.enableTurnstile && instance.turnstileSecretKey) { - await this.captchaService.verifyTurnstile(instance.turnstileSecretKey, body['turnstile-response']).catch(err => { + if (this.meta.enableTurnstile && this.meta.turnstileSecretKey) { + await this.captchaService.verifyTurnstile(this.meta.turnstileSecretKey, body['turnstile-response']).catch(err => { throw new FastifyReplyError(400, err); }); } @@ -108,7 +107,7 @@ export class SignupApiService { const invitationCode = body['invitationCode']; const emailAddress = body['emailAddress']; - if (instance.emailRequiredForSignup) { + if (this.meta.emailRequiredForSignup) { if (emailAddress == null || typeof emailAddress !== 'string') { reply.code(400); return; @@ -123,7 +122,7 @@ export class SignupApiService { let ticket: MiRegistrationTicket | null = null; - if (instance.disableRegistration) { + if (this.meta.disableRegistration) { if (invitationCode == null || typeof invitationCode !== 'string') { reply.code(400); return; @@ -144,7 +143,7 @@ export class SignupApiService { } // メアド認証が有効の場合 - if (instance.emailRequiredForSignup) { + if (this.meta.emailRequiredForSignup) { // メアド認証済みならエラー if (ticket.usedBy) { reply.code(400); @@ -162,7 +161,7 @@ export class SignupApiService { } } - if (instance.emailRequiredForSignup) { + if (this.meta.emailRequiredForSignup) { if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) { throw new FastifyReplyError(400, 'DUPLICATED_USERNAME'); } @@ -172,7 +171,7 @@ export class SignupApiService { throw new FastifyReplyError(400, 'USED_USERNAME'); } - const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase()); + const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase()); if (isPreserved) { throw new FastifyReplyError(400, 'DENIED_USERNAME'); } diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 3dfb7fdad4..2462781f7b 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -98,6 +98,7 @@ import * as ep___admin_systemWebhook_delete from './endpoints/admin/system-webho import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js'; import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js'; import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js'; +import * as ep___admin_systemWebhook_test from './endpoints/admin/system-webhook/test.js'; import * as ep___announcements from './endpoints/announcements.js'; import * as ep___announcements_show from './endpoints/announcements/show.js'; import * as ep___antennas_create from './endpoints/antennas/create.js'; @@ -264,6 +265,7 @@ import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js'; import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js'; import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js'; import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js'; +import * as ep___i_webhooks_test from './endpoints/i/webhooks/test.js'; import * as ep___invite_create from './endpoints/invite/create.js'; import * as ep___invite_delete from './endpoints/invite/delete.js'; import * as ep___invite_list from './endpoints/invite/list.js'; @@ -479,6 +481,7 @@ const eps = [ ['admin/system-webhook/list', ep___admin_systemWebhook_list], ['admin/system-webhook/show', ep___admin_systemWebhook_show], ['admin/system-webhook/update', ep___admin_systemWebhook_update], + ['admin/system-webhook/test', ep___admin_systemWebhook_test], ['announcements', ep___announcements], ['announcements/show', ep___announcements_show], ['antennas/create', ep___antennas_create], @@ -645,6 +648,7 @@ const eps = [ ['i/webhooks/show', ep___i_webhooks_show], ['i/webhooks/update', ep___i_webhooks_update], ['i/webhooks/delete', ep___i_webhooks_delete], + ['i/webhooks/test', ep___i_webhooks_test], ['invite/create', ep___invite_create], ['invite/delete', ep___invite_delete], ['invite/list', ep___invite_list], diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 2e7f73da73..29e8bfaf14 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -377,6 +377,10 @@ export const meta = { type: 'number', optional: false, nullable: false, }, + enableReactionsBuffering: { + type: 'boolean', + optional: false, nullable: false, + }, notesPerOneAd: { type: 'number', optional: false, nullable: false, @@ -617,6 +621,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax, perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax, perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax, + enableReactionsBuffering: instance.enableReactionsBuffering, notesPerOneAd: instance.notesPerOneAd, summalyProxy: instance.urlPreviewSummaryProxyUrl, urlPreviewEnabled: instance.urlPreviewEnabled, diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/test.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/test.ts new file mode 100644 index 0000000000..fb2ddf4b44 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/test.ts @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { WebhookTestService } from '@/core/WebhookTestService.js'; +import { ApiError } from '@/server/api/error.js'; +import { systemWebhookEventTypes } from '@/models/SystemWebhook.js'; + +export const meta = { + tags: ['webhooks'], + + requireCredential: true, + requireModerator: true, + secure: true, + kind: 'read:admin:system-webhook', + + limit: { + duration: ms('15min'), + max: 60, + }, + + errors: { + noSuchWebhook: { + message: 'No such webhook.', + code: 'NO_SUCH_WEBHOOK', + id: '0c52149c-e913-18f8-5dc7-74870bfe0cf9', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + webhookId: { + type: 'string', + format: 'misskey:id', + }, + type: { + type: 'string', + enum: systemWebhookEventTypes, + }, + override: { + type: 'object', + properties: { + url: { type: 'string', nullable: false }, + secret: { type: 'string', nullable: false }, + }, + }, + }, + required: ['webhookId', 'type'], +} as const; + +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export + constructor( + private webhookTestService: WebhookTestService, + ) { + super(meta, paramDef, async (ps) => { + try { + await this.webhookTestService.testSystemWebhook({ + webhookId: ps.webhookId, + type: ps.type, + override: ps.override, + }); + } catch (e) { + if (e instanceof WebhookTestService.NoSuchWebhookError) { + throw new ApiError(meta.errors.noSuchWebhook); + } + throw e; + } + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 5efdc9d8c4..865e73f274 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -142,6 +142,7 @@ export const paramDef = { perRemoteUserUserTimelineCacheMax: { type: 'integer' }, perUserHomeTimelineCacheMax: { type: 'integer' }, perUserListTimelineCacheMax: { type: 'integer' }, + enableReactionsBuffering: { type: 'boolean' }, notesPerOneAd: { type: 'integer' }, silencedHosts: { type: 'array', @@ -598,6 +599,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- set.perUserListTimelineCacheMax = ps.perUserListTimelineCacheMax; } + if (ps.enableReactionsBuffering !== undefined) { + set.enableReactionsBuffering = ps.enableReactionsBuffering; + } + if (ps.notesPerOneAd !== undefined) { set.notesPerOneAd = ps.notesPerOneAd; } diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 577b9e1b1f..e0c8ddcc84 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -34,6 +34,12 @@ export const meta = { code: 'TOO_MANY_ANTENNAS', id: 'faf47050-e8b5-438c-913c-db2b1576fde4', }, + + emptyKeyword: { + message: 'Either keywords or excludeKeywords is required.', + code: 'EMPTY_KEYWORD', + id: '53ee222e-1ddd-4f9a-92e5-9fb82ddb463a', + }, }, res: { @@ -87,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- ) { super(meta, paramDef, async (ps, me) => { if (ps.keywords.flat().every(x => x === '') && ps.excludeKeywords.flat().every(x => x === '')) { - throw new Error('either keywords or excludeKeywords is required.'); + throw new ApiError(meta.errors.emptyKeyword); } const currentAntennasCount = await this.antennasRepository.countBy({ diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 0c30bca9e0..10f26b1912 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -32,6 +32,12 @@ export const meta = { code: 'NO_SUCH_USER_LIST', id: '1c6b35c9-943e-48c2-81e4-2844989407f7', }, + + emptyKeyword: { + message: 'Either keywords or excludeKeywords is required.', + code: 'EMPTY_KEYWORD', + id: '721aaff6-4e1b-4d88-8de6-877fae9f68c4', + }, }, res: { @@ -85,7 +91,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- super(meta, paramDef, async (ps, me) => { if (ps.keywords && ps.excludeKeywords) { if (ps.keywords.flat().every(x => x === '') && ps.excludeKeywords.flat().every(x => x === '')) { - throw new Error('either keywords or excludeKeywords is required.'); + throw new ApiError(meta.errors.emptyKeyword); } } // Fetch the antenna diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index d3c40dba59..577ca0b24c 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { MiNote } from '@/models/Note.js'; @@ -12,7 +12,6 @@ import { isActor, isPost, getApId } from '@/core/activitypub/type.js'; import type { SchemaType } from '@/misc/json-schema.js'; import { ApResolverService } from '@/core/activitypub/ApResolverService.js'; import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js'; -import { MetaService } from '@/core/MetaService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -20,6 +19,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import { ApiError } from '../../error.js'; +import { MiMeta } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['federation'], @@ -88,10 +89,12 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + private utilityService: UtilityService, private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, - private metaService: MetaService, private apResolverService: ApResolverService, private apDbResolverService: ApDbResolverService, private apPersonService: ApPersonService, @@ -112,9 +115,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- */ @bindThis private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> { - // ブロックしてたら中断 - const fetchedMeta = await this.metaService.fetch(); - if (this.utilityService.isBlockedHost(fetchedMeta.blockedHosts, this.utilityService.extractDbHost(uri))) return null; + // ブロックしてたら中断 + if (this.utilityService.isBlockedHost(this.serverSettings.blockedHosts, this.utilityService.extractDbHost(uri))) return null; let local = await this.mergePack(me, ...await Promise.all([ this.apDbResolverService.getUserFromApId(uri), diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 8c55673590..d4fd75e049 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -5,14 +5,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ChannelsRepository, NotesRepository } from '@/models/_.js'; +import type { ChannelsRepository, MiMeta, NotesRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; -import { CacheService } from '@/core/CacheService.js'; -import { MetaService } from '@/core/MetaService.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; import { MiLocalUser } from '@/models/User.js'; import { ApiError } from '../../error.js'; @@ -58,6 +56,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -68,16 +69,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- private noteEntityService: NoteEntityService, private queryService: QueryService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, - private cacheService: CacheService, private activeUsersChart: ActiveUsersChart, - private metaService: MetaService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); - const serverSettings = await this.metaService.fetch(); - const channel = await this.channelsRepository.findOneBy({ id: ps.channelId, }); @@ -88,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- if (me) this.activeUsersChart.read(me); - if (!serverSettings.enableFanoutTimeline) { + if (!this.serverSettings.enableFanoutTimeline) { return await this.noteEntityService.packMany(await this.getFromDb({ untilId, sinceId, limit: ps.limit, channelId: channel.id }, me), me); } diff --git a/packages/backend/src/server/api/endpoints/drive.ts b/packages/backend/src/server/api/endpoints/drive.ts index 7e9b0fa0e1..eb45e29f9e 100644 --- a/packages/backend/src/server/api/endpoints/drive.ts +++ b/packages/backend/src/server/api/endpoints/drive.ts @@ -5,7 +5,6 @@ import { Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MetaService } from '@/core/MetaService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { RoleService } from '@/core/RoleService.js'; @@ -41,14 +40,10 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( - private metaService: MetaService, private driveFileEntityService: DriveFileEntityService, private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { - const instance = await this.metaService.fetch(true); - - // Calculate drive usage const usage = await this.driveFileEntityService.calcDriveUsageOf(me.id); const policies = await this.roleService.getUserPolicies(me.id); diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index 4670392025..b86059b5e7 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -10,6 +10,7 @@ import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; +import { RoleService } from '@/core/RoleService.js'; export const meta = { tags: ['drive', 'notes'], @@ -61,12 +62,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- private noteEntityService: NoteEntityService, private queryService: QueryService, + private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { // Fetch file const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId, - userId: me.id, + userId: await this.roleService.isModerator(me) ? undefined : me.id, }); if (file == null) { diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index 9c17f93ab2..74eb4dded7 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -4,14 +4,15 @@ */ import ms from 'ms'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; -import { MetaService } from '@/core/MetaService.js'; import { DriveService } from '@/core/DriveService.js'; import { ApiError } from '../../../error.js'; +import { MiMeta } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['drive'], @@ -73,8 +74,10 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + private driveFileEntityService: DriveFileEntityService, - private metaService: MetaService, private driveService: DriveService, ) { super(meta, paramDef, async (ps, me, _, file, cleanup, ip, headers) => { @@ -91,8 +94,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- } } - const instance = await this.metaService.fetch(); - try { // Create file const driveFile = await this.driveService.addFile({ @@ -103,8 +104,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- folderId: ps.folderId, force: ps.force, sensitive: ps.isSensitive, - requestIp: instance.enableIpLogging ? ip : null, - requestHeaders: instance.enableIpLogging ? headers : null, + requestIp: this.serverSettings.enableIpLogging ? ip : null, + requestHeaders: this.serverSettings.enableIpLogging ? headers : null, }); return await this.driveFileEntityService.pack(driveFile, { self: true }); } catch (err) { diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts index bc46163e3d..bdf6c065e8 100644 --- a/packages/backend/src/server/api/endpoints/i/import-antennas.ts +++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts @@ -16,6 +16,7 @@ import { ApiError } from '../../error.js'; export const meta = { secure: true, requireCredential: true, + requireRolePolicy: 'canImportAntennas', prohibitMoved: true, limit: { diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index 2606108539..d7bb6bcd22 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -15,6 +15,7 @@ import { ApiError } from '../../error.js'; export const meta = { secure: true, requireCredential: true, + requireRolePolicy: 'canImportBlocking', prohibitMoved: true, limit: { diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index d5e824df27..e03192d8c6 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -15,6 +15,7 @@ import { ApiError } from '../../error.js'; export const meta = { secure: true, requireCredential: true, + requireRolePolicy: 'canImportFollowing', prohibitMoved: true, limit: { duration: ms('1hour'), diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index 0f5800404e..76b285bb7e 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -15,6 +15,7 @@ import { ApiError } from '../../error.js'; export const meta = { secure: true, requireCredential: true, + requireRolePolicy: 'canImportMuting', prohibitMoved: true, limit: { diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index bacdd5c88f..76ecfd082c 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -15,6 +15,7 @@ import { ApiError } from '../../error.js'; export const meta = { secure: true, requireCredential: true, + requireRolePolicy: 'canImportUserLists', prohibitMoved: true, limit: { duration: ms('1hour'), diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index eea657ebbd..da1faee30d 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import bcrypt from 'bcryptjs'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { UserProfilesRepository } from '@/models/_.js'; +import type { MiMeta, UserProfilesRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { EmailService } from '@/core/EmailService.js'; import type { Config } from '@/config.js'; @@ -15,7 +15,6 @@ import { DI } from '@/di-symbols.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js'; import { UserAuthService } from '@/core/UserAuthService.js'; -import { MetaService } from '@/core/MetaService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -70,10 +69,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, - private metaService: MetaService, private userEntityService: UserEntityService, private emailService: EmailService, private userAuthService: UserAuthService, @@ -105,7 +106,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- if (!res.available) { throw new ApiError(meta.errors.unavailable); } - } else if ((await this.metaService.fetch()).emailRequiredForSignup) { + } else if (this.serverSettings.emailRequiredForSignup) { throw new ApiError(meta.errors.emailRequired); } diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts index 9eb7f5b3a0..6e84603f7a 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -13,6 +13,7 @@ import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '@/server/api/error.js'; +// TODO: UserWebhook schemaの適用 export const meta = { tags: ['webhooks'], diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts index fe07afb2d0..394c178f2a 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts @@ -9,6 +9,7 @@ import { webhookEventTypes } from '@/models/Webhook.js'; import type { WebhooksRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; +// TODO: UserWebhook schemaの適用 export const meta = { tags: ['webhooks', 'account'], diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts index 5ddb79caf2..4a0c09ff0c 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts @@ -10,6 +10,7 @@ import type { WebhooksRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; +// TODO: UserWebhook schemaの適用 export const meta = { tags: ['webhooks'], diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/test.ts b/packages/backend/src/server/api/endpoints/i/webhooks/test.ts new file mode 100644 index 0000000000..2bf6df9ce2 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/webhooks/test.ts @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { webhookEventTypes } from '@/models/Webhook.js'; +import { WebhookTestService } from '@/core/WebhookTestService.js'; +import { ApiError } from '@/server/api/error.js'; + +export const meta = { + tags: ['webhooks'], + + requireCredential: true, + secure: true, + kind: 'read:account', + + limit: { + duration: ms('15min'), + max: 60, + }, + + errors: { + noSuchWebhook: { + message: 'No such webhook.', + code: 'NO_SUCH_WEBHOOK', + id: '0c52149c-e913-18f8-5dc7-74870bfe0cf9', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + webhookId: { + type: 'string', + format: 'misskey:id', + }, + type: { + type: 'string', + enum: webhookEventTypes, + }, + override: { + type: 'object', + properties: { + url: { type: 'string' }, + secret: { type: 'string' }, + }, + }, + }, + required: ['webhookId', 'type'], +} as const; + +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export + constructor( + private webhookTestService: WebhookTestService, + ) { + super(meta, paramDef, async (ps, me) => { + try { + await this.webhookTestService.testUserWebhook({ + webhookId: ps.webhookId, + type: ps.type, + override: ps.override, + }, me); + } catch (e) { + if (e instanceof WebhookTestService.NoSuchWebhookError) { + throw new ApiError(meta.errors.noSuchWebhook); + } + throw e; + } + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index beb77ca7ab..253a360815 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -17,8 +17,6 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { NoteCreateService } from '@/core/NoteCreateService.js'; import { DI } from '@/di-symbols.js'; import { isQuote, isRenote } from '@/misc/is-renote.js'; -import { MetaService } from '@/core/MetaService.js'; -import { UtilityService } from '@/core/UtilityService.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { ApiError } from '../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 2a2c659942..aed9065bf9 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -5,7 +5,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository, ChannelFollowingsRepository } from '@/models/_.js'; +import type { NotesRepository, ChannelFollowingsRepository, MiMeta } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -16,7 +16,6 @@ import { CacheService } from '@/core/CacheService.js'; import { FanoutTimelineName } from '@/core/FanoutTimelineService.js'; import { QueryService } from '@/core/QueryService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; -import { MetaService } from '@/core/MetaService.js'; import { MiLocalUser } from '@/models/User.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; import { ApiError } from '../../error.js'; @@ -74,6 +73,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -87,7 +89,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- private cacheService: CacheService, private queryService: QueryService, private userFollowingService: UserFollowingService, - private metaService: MetaService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, ) { super(meta, paramDef, async (ps, me) => { @@ -101,9 +102,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- if (ps.withReplies && ps.withFiles) throw new ApiError(meta.errors.bothWithRepliesAndWithFiles); - const serverSettings = await this.metaService.fetch(); - - if (!serverSettings.enableFanoutTimeline) { + if (!this.serverSettings.enableFanoutTimeline) { const timeline = await this.getFromDb({ untilId, sinceId, @@ -156,7 +155,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- allowPartial: ps.allowPartial, me, redisTimelines: timelineConfig, - useDbFallback: serverSettings.enableFanoutTimelineDbFallback, + useDbFallback: this.serverSettings.enableFanoutTimelineDbFallback, alwaysIncludeMyNotes: true, excludePureRenotes: !ps.withRenotes, noteFilter: note => { diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index be82b5a8a7..0b48f2c78b 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -5,16 +5,14 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository } from '@/models/_.js'; +import type { MiMeta, NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; import { IdService } from '@/core/IdService.js'; -import { CacheService } from '@/core/CacheService.js'; import { QueryService } from '@/core/QueryService.js'; -import { MetaService } from '@/core/MetaService.js'; import { MiLocalUser } from '@/models/User.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; import { ApiError } from '../../error.js'; @@ -66,6 +64,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -73,10 +74,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- private roleService: RoleService, private activeUsersChart: ActiveUsersChart, private idService: IdService, - private cacheService: CacheService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, private queryService: QueryService, - private metaService: MetaService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); @@ -89,9 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- if (ps.withReplies && ps.withFiles) throw new ApiError(meta.errors.bothWithRepliesAndWithFiles); - const serverSettings = await this.metaService.fetch(); - - if (!serverSettings.enableFanoutTimeline) { + if (!this.serverSettings.enableFanoutTimeline) { const timeline = await this.getFromDb({ untilId, sinceId, @@ -115,7 +112,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- limit: ps.limit, allowPartial: ps.allowPartial, me, - useDbFallback: serverSettings.enableFanoutTimelineDbFallback, + useDbFallback: this.serverSettings.enableFanoutTimelineDbFallback, redisTimelines: ps.withFiles ? ['localTimelineWithFiles'] : ps.withReplies ? ['localTimeline', 'localTimelineWithReplies'] diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index c9b43b5359..7cb11cc1eb 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -5,7 +5,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository, ChannelFollowingsRepository } from '@/models/_.js'; +import type { NotesRepository, ChannelFollowingsRepository, MiMeta } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; @@ -15,7 +15,6 @@ import { IdService } from '@/core/IdService.js'; import { CacheService } from '@/core/CacheService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { MiLocalUser } from '@/models/User.js'; -import { MetaService } from '@/core/MetaService.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; export const meta = { @@ -56,6 +55,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -69,15 +71,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- private fanoutTimelineEndpointService: FanoutTimelineEndpointService, private userFollowingService: UserFollowingService, private queryService: QueryService, - private metaService: MetaService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); - const serverSettings = await this.metaService.fetch(); - - if (!serverSettings.enableFanoutTimeline) { + if (!this.serverSettings.enableFanoutTimeline) { const timeline = await this.getFromDb({ untilId, sinceId, @@ -108,7 +107,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- limit: ps.limit, allowPartial: ps.allowPartial, me, - useDbFallback: serverSettings.enableFanoutTimelineDbFallback, + useDbFallback: this.serverSettings.enableFanoutTimelineDbFallback, redisTimelines: ps.withFiles ? [`homeTimelineWithFiles:${me.id}`] : [`homeTimeline:${me.id}`], alwaysIncludeMyNotes: true, excludePureRenotes: !ps.withRenotes, diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index 38a9660aa2..e9a6a36b02 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -4,14 +4,15 @@ */ import { URLSearchParams } from 'node:url'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; -import { MetaService } from '@/core/MetaService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { GetterService } from '@/server/api/GetterService.js'; import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; +import { MiMeta } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; export const meta = { tags: ['notes'], @@ -59,9 +60,11 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + private noteEntityService: NoteEntityService, private getterService: GetterService, - private metaService: MetaService, private httpRequestService: HttpRequestService, private roleService: RoleService, ) { @@ -84,9 +87,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- return; } - const instance = await this.metaService.fetch(); - - if (instance.deeplAuthKey == null) { + if (this.serverSettings.deeplAuthKey == null) { throw new ApiError(meta.errors.unavailable); } @@ -94,11 +95,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- if (targetLang.includes('-')) targetLang = targetLang.split('-')[0]; const params = new URLSearchParams(); - params.append('auth_key', instance.deeplAuthKey); + params.append('auth_key', this.serverSettings.deeplAuthKey); params.append('text', note.text); params.append('target_lang', targetLang); - const endpoint = instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate'; + const endpoint = this.serverSettings.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate'; const res = await this.httpRequestService.send(endpoint, { method: 'POST', diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 43877e61ef..6c7185c9eb 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -5,16 +5,14 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets } from 'typeorm'; -import type { MiUserList, NotesRepository, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; +import type { MiMeta, MiUserList, NotesRepository, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; import { DI } from '@/di-symbols.js'; -import { CacheService } from '@/core/CacheService.js'; import { IdService } from '@/core/IdService.js'; import { QueryService } from '@/core/QueryService.js'; import { MiLocalUser } from '@/models/User.js'; -import { MetaService } from '@/core/MetaService.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; import { ApiError } from '../../error.js'; @@ -69,6 +67,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -80,11 +81,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- private noteEntityService: NoteEntityService, private activeUsersChart: ActiveUsersChart, - private cacheService: CacheService, private idService: IdService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, private queryService: QueryService, - private metaService: MetaService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); @@ -99,9 +98,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- throw new ApiError(meta.errors.noSuchList); } - const serverSettings = await this.metaService.fetch(); - - if (!serverSettings.enableFanoutTimeline) { + if (!this.serverSettings.enableFanoutTimeline) { const timeline = await this.getFromDb(list, { untilId, sinceId, @@ -124,7 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- limit: ps.limit, allowPartial: ps.allowPartial, me, - useDbFallback: serverSettings.enableFanoutTimelineDbFallback, + useDbFallback: this.serverSettings.enableFanoutTimelineDbFallback, redisTimelines: ps.withFiles ? [`userListTimelineWithFiles:${list.id}`] : [`userListTimeline:${list.id}`], alwaysIncludeMyNotes: true, excludePureRenotes: !ps.withRenotes, diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 15832ef7f8..5b0b656c63 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -5,11 +5,10 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository } from '@/models/_.js'; +import type { MiMeta, UsersRepository } from '@/models/_.js'; import * as Acct from '@/misc/acct.js'; import type { MiUser } from '@/models/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MetaService } from '@/core/MetaService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -38,16 +37,16 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, - private metaService: MetaService, private userEntityService: UserEntityService, ) { super(meta, paramDef, async (ps, me) => { - const meta = await this.metaService.fetch(); - - const users = await Promise.all(meta.pinnedUsers.map(acct => Acct.parse(acct)).map(acct => this.usersRepository.findOneBy({ + const users = await Promise.all(this.serverSettings.pinnedUsers.map(acct => Acct.parse(acct)).map(acct => this.usersRepository.findOneBy({ usernameLower: acct.username.toLowerCase(), host: acct.host ?? IsNull(), }))); diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts index c13802eb06..8301c85f2e 100644 --- a/packages/backend/src/server/api/endpoints/server-info.ts +++ b/packages/backend/src/server/api/endpoints/server-info.ts @@ -5,9 +5,10 @@ import * as os from 'node:os'; import si from 'systeminformation'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MetaService } from '@/core/MetaService.js'; +import { MiMeta } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: false, @@ -73,10 +74,11 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( - private metaService: MetaService, + @Inject(DI.meta) + private serverSettings: MiMeta, ) { super(meta, paramDef, async () => { - if (!(await this.metaService.fetch()).enableServerMachineStats) return { + if (!this.serverSettings.enableServerMachineStats) return { machine: '?', cpu: { model: '?', diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index a9a33149f9..fd76df2d3c 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -5,9 +5,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { IdService } from '@/core/IdService.js'; -import type { SwSubscriptionsRepository } from '@/models/_.js'; +import type { MiMeta, SwSubscriptionsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { MetaService } from '@/core/MetaService.js'; import { DI } from '@/di-symbols.js'; import { PushNotificationService } from '@/core/PushNotificationService.js'; @@ -62,11 +61,13 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.swSubscriptionsRepository) private swSubscriptionsRepository: SwSubscriptionsRepository, private idService: IdService, - private metaService: MetaService, private pushNotificationService: PushNotificationService, ) { super(meta, paramDef, async (ps, me) => { @@ -78,12 +79,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- publickey: ps.publickey, }); - const instance = await this.metaService.fetch(true); - if (exist != null) { return { state: 'already-subscribed' as const, - key: instance.swPublicKey, + key: this.serverSettings.swPublicKey, userId: me.id, endpoint: exist.endpoint, sendReadMessage: exist.sendReadMessage, @@ -103,7 +102,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- return { state: 'subscribed' as const, - key: instance.swPublicKey, + key: this.serverSettings.swPublicKey, userId: me.id, endpoint: ps.endpoint, sendReadMessage: ps.sendReadMessage, diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts index affb0996f1..4944be9b05 100644 --- a/packages/backend/src/server/api/endpoints/username/available.ts +++ b/packages/backend/src/server/api/endpoints/username/available.ts @@ -5,11 +5,10 @@ import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsedUsernamesRepository, UsersRepository } from '@/models/_.js'; +import type { MiMeta, UsedUsernamesRepository, UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { localUsernameSchema } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; -import { MetaService } from '@/core/MetaService.js'; export const meta = { tags: ['users'], @@ -39,13 +38,14 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @Inject(DI.usedUsernamesRepository) private usedUsernamesRepository: UsedUsernamesRepository, - - private metaService: MetaService, ) { super(meta, paramDef, async (ps, me) => { const exist = await this.usersRepository.countBy({ @@ -55,8 +55,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- const exist2 = await this.usedUsernamesRepository.countBy({ username: ps.username.toLowerCase() }); - const meta = await this.metaService.fetch(); - const isPreserved = meta.preservedUsernames.map(x => x.toLowerCase()).includes(ps.username.toLowerCase()); + const isPreserved = this.serverSettings.preservedUsernames.map(x => x.toLowerCase()).includes(ps.username.toLowerCase()); return { available: exist === 0 && exist2 === 0 && !isPreserved, diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index cc76c12f1d..7fc11ba369 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -5,14 +5,13 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository } from '@/models/_.js'; +import type { MiMeta, NotesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { CacheService } from '@/core/CacheService.js'; import { IdService } from '@/core/IdService.js'; import { QueryService } from '@/core/QueryService.js'; -import { MetaService } from '@/core/MetaService.js'; import { MiLocalUser } from '@/models/User.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; import { FanoutTimelineName } from '@/core/FanoutTimelineService.js'; @@ -67,6 +66,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -75,15 +77,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- private cacheService: CacheService, private idService: IdService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, - private metaService: MetaService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null); const isSelf = me && (me.id === ps.userId); - const serverSettings = await this.metaService.fetch(); - if (ps.withReplies && ps.withFiles) throw new ApiError(meta.errors.bothWithRepliesAndWithFiles); // early return if me is blocked by requesting user @@ -94,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- } } - if (!serverSettings.enableFanoutTimeline) { + if (!this.serverSettings.enableFanoutTimeline) { const timeline = await this.getFromDb({ untilId, sinceId, diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index f55790b636..5de1f87667 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; import { createBullBoard } from '@bull-board/api'; import { BullMQAdapter } from '@bull-board/api/bullMQAdapter.js'; -import { FastifyAdapter } from '@bull-board/fastify'; +import { FastifyAdapter as BullBoardFastifyAdapter } from '@bull-board/fastify'; import ms from 'ms'; import sharp from 'sharp'; import pug from 'pug'; @@ -24,7 +24,6 @@ import type { Config } from '@/config.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; import { DI } from '@/di-symbols.js'; import * as Acct from '@/misc/acct.js'; -import { MetaService } from '@/core/MetaService.js'; import type { DbQueue, DeliverQueue, @@ -61,7 +60,8 @@ const staticAssets = `${_dirname}/../../../assets/`; const clientAssets = `${_dirname}/../../../../frontend/assets/`; const assets = `${_dirname}/../../../../../built/_frontend_dist_/`; const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`; -const viteOut = `${_dirname}/../../../../../built/_vite_/`; +const frontendViteOut = `${_dirname}/../../../../../built/_frontend_vite_/`; +const frontendEmbedViteOut = `${_dirname}/../../../../../built/_frontend_embed_vite_/`; const tarball = `${_dirname}/../../../../../built/tarball/`; @Injectable() @@ -72,6 +72,9 @@ export class ClientServerService { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -108,7 +111,6 @@ export class ClientServerService { private clipEntityService: ClipEntityService, private channelEntityService: ChannelEntityService, private reversiGameEntityService: ReversiGameEntityService, - private metaService: MetaService, private urlPreviewService: UrlPreviewService, private feedService: FeedService, private roleService: RoleService, @@ -128,32 +130,30 @@ export class ClientServerService { @bindThis private async manifestHandler(reply: FastifyReply) { - const instance = await this.metaService.fetch(true); - let manifest = { // 空文字列の場合右辺を使いたいため // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - 'short_name': instance.shortName || instance.name || this.config.host, + 'short_name': this.meta.shortName || this.meta.name || this.config.host, // 空文字列の場合右辺を使いたいため // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - 'name': instance.name || this.config.host, + 'name': this.meta.name || this.config.host, 'start_url': '/', 'display': 'standalone', 'background_color': '#313a42', // 空文字列の場合右辺を使いたいため // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - 'theme_color': instance.themeColor || '#86b300', + 'theme_color': this.meta.themeColor || '#86b300', 'icons': [{ // 空文字列の場合右辺を使いたいため // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - 'src': instance.app192IconUrl || '/static-assets/icons/192.png', + 'src': this.meta.app192IconUrl || '/static-assets/icons/192.png', 'sizes': '192x192', 'type': 'image/png', 'purpose': 'maskable', }, { // 空文字列の場合右辺を使いたいため // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - 'src': instance.app512IconUrl || '/static-assets/icons/512.png', + 'src': this.meta.app512IconUrl || '/static-assets/icons/512.png', 'sizes': '512x512', 'type': 'image/png', 'purpose': 'maskable', @@ -177,7 +177,7 @@ export class ClientServerService { manifest = { ...manifest, - ...JSON.parse(instance.manifestJsonOverride === '' ? '{}' : instance.manifestJsonOverride), + ...JSON.parse(this.meta.manifestJsonOverride === '' ? '{}' : this.meta.manifestJsonOverride), }; reply.header('Cache-Control', 'max-age=300'); @@ -239,7 +239,7 @@ export class ClientServerService { } }); - const serverAdapter = new FastifyAdapter(); + const bullBoardServerAdapter = new BullBoardFastifyAdapter(); createBullBoard({ queues: [ @@ -252,11 +252,11 @@ export class ClientServerService { this.userWebhookDeliverQueue, this.systemWebhookDeliverQueue, ].map(q => new BullMQAdapter(q)), - serverAdapter, + serverAdapter: bullBoardServerAdapter, }); - serverAdapter.setBasePath(bullBoardPath); - (fastify.register as any)(serverAdapter.registerPlugin(), { prefix: bullBoardPath }); + bullBoardServerAdapter.setBasePath(bullBoardPath); + //(fastify.register as any)(bullBoardServerAdapter.registerPlugin(), { prefix: bullBoardPath }); //#endregion fastify.register(fastifyView, { @@ -277,15 +277,22 @@ export class ClientServerService { }); //#region vite assets - if (this.config.clientManifestExists) { + if (this.config.frontendEmbedManifestExists) { fastify.register((fastify, options, done) => { fastify.register(fastifyStatic, { - root: viteOut, + root: frontendViteOut, prefix: '/vite/', maxAge: ms('30 days'), immutable: true, decorateReply: false, }); + fastify.register(fastifyStatic, { + root: frontendEmbedViteOut, + prefix: '/embed_vite/', + maxAge: ms('30 days'), + immutable: true, + decorateReply: false, + }); fastify.addHook('onRequest', handleRequestRedirectToOmitSearch); done(); }); @@ -296,6 +303,13 @@ export class ClientServerService { prefix: '/vite', rewritePrefix: '/vite', }); + + const embedPort = (process.env.EMBED_VITE_PORT ?? '5174'); + fastify.register(fastifyProxy, { + upstream: 'http://localhost:' + embedPort, + prefix: '/embed_vite', + rewritePrefix: '/embed_vite', + }); } //#endregion @@ -425,15 +439,20 @@ export class ClientServerService { // Manifest fastify.get('/manifest.json', async (request, reply) => await this.manifestHandler(reply)); + // Embed Javascript + fastify.get('/embed.js', async (request, reply) => { + return await reply.sendFile('/embed.js', staticAssets, { + maxAge: ms('1 day'), + }); + }); + fastify.get('/robots.txt', async (request, reply) => { return await reply.sendFile('/robots.txt', staticAssets); }); // OpenSearch XML fastify.get('/opensearch.xml', async (request, reply) => { - const meta = await this.metaService.fetch(); - - const name = meta.name ?? 'Misskey'; + const name = this.meta.name ?? 'Misskey'; let content = ''; content += '<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">'; content += `<ShortName>${name}</ShortName>`; @@ -450,14 +469,13 @@ export class ClientServerService { //#endregion const renderBase = async (reply: FastifyReply, data: { [key: string]: any } = {}) => { - const meta = await this.metaService.fetch(); reply.header('Cache-Control', 'public, max-age=30'); return await reply.view('base', { - img: meta.bannerUrl, + img: this.meta.bannerUrl, url: this.config.url, - title: meta.name ?? 'Misskey', - desc: meta.description, - ...await this.generateCommonPugData(meta), + title: this.meta.name ?? 'Misskey', + desc: this.meta.description, + ...await this.generateCommonPugData(this.meta), ...data, }); }; @@ -535,7 +553,6 @@ export class ClientServerService { if (user != null) { const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); - const meta = await this.metaService.fetch(); const me = profile.fields ? profile.fields .filter(filed => filed.value != null && filed.value.match(/^https?:/)) @@ -551,7 +568,7 @@ export class ClientServerService { user, profile, me, avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user), sub: request.params.sub, - ...await this.generateCommonPugData(meta), + ...await this.generateCommonPugData(this.meta), }); } else { // リモートユーザーなので @@ -589,7 +606,6 @@ export class ClientServerService { if (note) { const _note = await this.noteEntityService.pack(note); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId }); - const meta = await this.metaService.fetch(); reply.header('Cache-Control', 'public, max-age=15'); if (profile.preventAiLearning) { reply.header('X-Robots-Tag', 'noimageai'); @@ -601,7 +617,7 @@ export class ClientServerService { avatarUrl: _note.user.avatarUrl, // TODO: Let locale changeable by instance setting summary: getNoteSummary(_note), - ...await this.generateCommonPugData(meta), + ...await this.generateCommonPugData(this.meta), }); } else { return await renderBase(reply); @@ -626,7 +642,6 @@ export class ClientServerService { if (page) { const _page = await this.pageEntityService.pack(page); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: page.userId }); - const meta = await this.metaService.fetch(); if (['public'].includes(page.visibility)) { reply.header('Cache-Control', 'public, max-age=15'); } else { @@ -640,7 +655,7 @@ export class ClientServerService { page: _page, profile, avatarUrl: _page.user.avatarUrl, - ...await this.generateCommonPugData(meta), + ...await this.generateCommonPugData(this.meta), }); } else { return await renderBase(reply); @@ -656,7 +671,6 @@ export class ClientServerService { if (flash) { const _flash = await this.flashEntityService.pack(flash); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: flash.userId }); - const meta = await this.metaService.fetch(); reply.header('Cache-Control', 'public, max-age=15'); if (profile.preventAiLearning) { reply.header('X-Robots-Tag', 'noimageai'); @@ -666,7 +680,7 @@ export class ClientServerService { flash: _flash, profile, avatarUrl: _flash.user.avatarUrl, - ...await this.generateCommonPugData(meta), + ...await this.generateCommonPugData(this.meta), }); } else { return await renderBase(reply); @@ -682,7 +696,6 @@ export class ClientServerService { if (clip && clip.isPublic) { const _clip = await this.clipEntityService.pack(clip); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId }); - const meta = await this.metaService.fetch(); reply.header('Cache-Control', 'public, max-age=15'); if (profile.preventAiLearning) { reply.header('X-Robots-Tag', 'noimageai'); @@ -692,7 +705,7 @@ export class ClientServerService { clip: _clip, profile, avatarUrl: _clip.user.avatarUrl, - ...await this.generateCommonPugData(meta), + ...await this.generateCommonPugData(this.meta), }); } else { return await renderBase(reply); @@ -706,7 +719,6 @@ export class ClientServerService { if (post) { const _post = await this.galleryPostEntityService.pack(post); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId }); - const meta = await this.metaService.fetch(); reply.header('Cache-Control', 'public, max-age=15'); if (profile.preventAiLearning) { reply.header('X-Robots-Tag', 'noimageai'); @@ -716,7 +728,7 @@ export class ClientServerService { post: _post, profile, avatarUrl: _post.user.avatarUrl, - ...await this.generateCommonPugData(meta), + ...await this.generateCommonPugData(this.meta), }); } else { return await renderBase(reply); @@ -731,11 +743,10 @@ export class ClientServerService { if (channel) { const _channel = await this.channelEntityService.pack(channel); - const meta = await this.metaService.fetch(); reply.header('Cache-Control', 'public, max-age=15'); return await reply.view('channel', { channel: _channel, - ...await this.generateCommonPugData(meta), + ...await this.generateCommonPugData(this.meta), }); } else { return await renderBase(reply); @@ -750,11 +761,10 @@ export class ClientServerService { if (game) { const _game = await this.reversiGameEntityService.packDetail(game); - const meta = await this.metaService.fetch(); reply.header('Cache-Control', 'public, max-age=3600'); return await reply.view('reversi-game', { game: _game, - ...await this.generateCommonPugData(meta), + ...await this.generateCommonPugData(this.meta), }); } else { return await renderBase(reply); @@ -762,7 +772,7 @@ export class ClientServerService { }); //#endregion - //region noindex pages + //#region noindex pages // Tags fastify.get<{ Params: { clip: string; } }>('/tags/:tag', async (request, reply) => { return await renderBase(reply, { noindex: true }); @@ -772,21 +782,97 @@ export class ClientServerService { fastify.get<{ Params: { clip: string; } }>('/user-tags/:tag', async (request, reply) => { return await renderBase(reply, { noindex: true }); }); - //endregion + //#endregion + + //#region embed pages + fastify.get<{ Params: { user: string; } }>('/embed/user-timeline/:user', async (request, reply) => { + reply.removeHeader('X-Frame-Options'); + + const user = await this.usersRepository.findOneBy({ + id: request.params.user, + }); + + if (user == null) return; + if (user.host != null) return; + + const _user = await this.userEntityService.pack(user); + + reply.header('Cache-Control', 'public, max-age=3600'); + return await reply.view('base-embed', { + title: this.meta.name ?? 'Misskey', + ...await this.generateCommonPugData(this.meta), + embedCtx: htmlSafeJsonStringify({ + user: _user, + }), + }); + }); + + fastify.get<{ Params: { note: string; } }>('/embed/notes/:note', async (request, reply) => { + reply.removeHeader('X-Frame-Options'); + + const note = await this.notesRepository.findOneBy({ + id: request.params.note, + }); + + if (note == null) return; + if (note.visibility !== 'public') return; + if (note.userHost != null) return; + + const _note = await this.noteEntityService.pack(note, null, { detail: true }); + + reply.header('Cache-Control', 'public, max-age=3600'); + return await reply.view('base-embed', { + title: this.meta.name ?? 'Misskey', + ...await this.generateCommonPugData(this.meta), + embedCtx: htmlSafeJsonStringify({ + note: _note, + }), + }); + }); + + fastify.get<{ Params: { clip: string; } }>('/embed/clips/:clip', async (request, reply) => { + reply.removeHeader('X-Frame-Options'); + + const clip = await this.clipsRepository.findOneBy({ + id: request.params.clip, + }); + + if (clip == null) return; + + const _clip = await this.clipEntityService.pack(clip); + + reply.header('Cache-Control', 'public, max-age=3600'); + return await reply.view('base-embed', { + title: this.meta.name ?? 'Misskey', + ...await this.generateCommonPugData(this.meta), + embedCtx: htmlSafeJsonStringify({ + clip: _clip, + }), + }); + }); + + fastify.get('/embed/*', async (request, reply) => { + reply.removeHeader('X-Frame-Options'); + + reply.header('Cache-Control', 'public, max-age=3600'); + return await reply.view('base-embed', { + title: this.meta.name ?? 'Misskey', + ...await this.generateCommonPugData(this.meta), + }); + }); fastify.get('/_info_card_', async (request, reply) => { - const meta = await this.metaService.fetch(true); - reply.removeHeader('X-Frame-Options'); return await reply.view('info-card', { version: this.config.version, host: this.config.host, - meta: meta, + meta: this.meta, originalUsersCount: await this.usersRepository.countBy({ host: IsNull() }), originalNotesCount: await this.notesRepository.countBy({ userHost: IsNull() }), }); }); + //#endregion fastify.get('/bios', async (request, reply) => { return await reply.view('bios', { diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 8f8f08a305..5d493c2c46 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -8,7 +8,6 @@ import { summaly } from '@misskey-dev/summaly'; import { SummalyResult } from '@misskey-dev/summaly/built/summary.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; -import { MetaService } from '@/core/MetaService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import type Logger from '@/logger.js'; import { query } from '@/misc/prelude/url.js'; @@ -26,7 +25,9 @@ export class UrlPreviewService { @Inject(DI.config) private config: Config, - private metaService: MetaService, + @Inject(DI.meta) + private meta: MiMeta, + private httpRequestService: HttpRequestService, private loggerService: LoggerService, ) { @@ -62,9 +63,7 @@ export class UrlPreviewService { return; } - const meta = await this.metaService.fetch(); - - if (!meta.urlPreviewEnabled) { + if (!this.meta.urlPreviewEnabled) { reply.code(403); return { error: new ApiError({ @@ -75,14 +74,14 @@ export class UrlPreviewService { }; } - this.logger.info(meta.urlPreviewSummaryProxyUrl + this.logger.info(this.meta.urlPreviewSummaryProxyUrl ? `(Proxy) Getting preview of ${url}@${lang} ...` : `Getting preview of ${url}@${lang} ...`); try { - const summary = meta.urlPreviewSummaryProxyUrl - ? await this.fetchSummaryFromProxy(url, meta, lang) - : await this.fetchSummary(url, meta, lang); + const summary = this.meta.urlPreviewSummaryProxyUrl + ? await this.fetchSummaryFromProxy(url, this.meta, lang) + : await this.fetchSummary(url, this.meta, lang); this.logger.succ(`Got preview of ${url}: ${summary.title}`); diff --git a/packages/backend/src/server/web/boot.embed.js b/packages/backend/src/server/web/boot.embed.js new file mode 100644 index 0000000000..48d1cd262b --- /dev/null +++ b/packages/backend/src/server/web/boot.embed.js @@ -0,0 +1,219 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +'use strict'; + +// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので +(async () => { + window.onerror = (e) => { + console.error(e); + renderError('SOMETHING_HAPPENED'); + }; + window.onunhandledrejection = (e) => { + console.error(e); + renderError('SOMETHING_HAPPENED_IN_PROMISE'); + }; + + let forceError = localStorage.getItem('forceError'); + if (forceError != null) { + renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.'); + return; + } + + // パラメータに応じてsplashのスタイルを変更 + const params = new URLSearchParams(location.search); + if (params.has('rounded') && params.get('rounded') === 'false') { + document.documentElement.classList.add('norounded'); + } + if (params.has('border') && params.get('border') === 'false') { + document.documentElement.classList.add('noborder'); + } + + //#region Detect language & fetch translations + if (!localStorage.hasOwnProperty('locale')) { + const supportedLangs = LANGS; + let lang = localStorage.getItem('lang'); + if (lang == null || !supportedLangs.includes(lang)) { + if (supportedLangs.includes(navigator.language)) { + lang = navigator.language; + } else { + lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); + + // Fallback + if (lang == null) lang = 'en-US'; + } + } + + const metaRes = await window.fetch('/api/meta', { + method: 'POST', + body: JSON.stringify({}), + credentials: 'omit', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + }); + if (metaRes.status !== 200) { + renderError('META_FETCH'); + return; + } + const meta = await metaRes.json(); + const v = meta.version; + if (v == null) { + renderError('META_FETCH_V'); + return; + } + + // for https://github.com/misskey-dev/misskey/issues/10202 + if (lang == null || lang.toString == null || lang.toString() === 'null') { + console.error('invalid lang value detected!!!', typeof lang, lang); + lang = 'en-US'; + } + + const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`); + if (localRes.status === 200) { + localStorage.setItem('lang', lang); + localStorage.setItem('locale', await localRes.text()); + localStorage.setItem('localeVersion', v); + } else { + renderError('LOCALE_FETCH'); + return; + } + } + //#endregion + + //#region Script + async function importAppScript() { + await import(`/embed_vite/${CLIENT_ENTRY}`) + .catch(async e => { + console.error(e); + renderError('APP_IMPORT'); + }); + } + + // タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある + if (document.readyState !== 'loading') { + importAppScript(); + } else { + window.addEventListener('DOMContentLoaded', () => { + importAppScript(); + }); + } + //#endregion + + async function addStyle(styleText) { + let css = document.createElement('style'); + css.appendChild(document.createTextNode(styleText)); + document.head.appendChild(css); + } + + async function renderError(code) { + // Cannot set property 'innerHTML' of null を回避 + if (document.readyState === 'loading') { + await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve)); + } + document.body.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M12 9v4" /><path d="M12 16v.01" /></svg> + <div class="message">読み込みに失敗しました</div> + <div class="submessage">Failed to initialize Misskey</div> + <div class="submessage">Error Code: ${code}</div> + <button onclick="location.reload(!0)"> + <div>リロード</div> + <div><small>Reload</small></div> + </button>`; + addStyle(` + #misskey_app, + #splash { + display: none !important; + } + + html, + body { + margin: 0; + } + + body { + position: relative; + color: #dee7e4; + font-family: Hiragino Maru Gothic Pro, BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif; + line-height: 1.35; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 100vh; + margin: 0; + padding: 24px; + box-sizing: border-box; + overflow: hidden; + + border-radius: var(--radius, 12px); + border: 1px solid rgba(231, 255, 251, 0.14); + } + + body::before { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #192320; + border-radius: var(--radius, 12px); + z-index: -1; + } + + html.embed.norounded body, + html.embed.norounded body::before { + border-radius: 0; + } + + html.embed.noborder body { + border: none; + } + + .icon { + max-width: 60px; + width: 100%; + height: auto; + margin-bottom: 20px; + color: #dec340; + } + + .message { + text-align: center; + font-size: 20px; + font-weight: 700; + margin-bottom: 20px; + } + + .submessage { + text-align: center; + font-size: 90%; + margin-bottom: 7.5px; + } + + .submessage:last-of-type { + margin-bottom: 20px; + } + + button { + padding: 7px 14px; + min-width: 100px; + font-weight: 700; + font-family: Hiragino Maru Gothic Pro, BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif; + line-height: 1.35; + border-radius: 99rem; + background-color: #b4e900; + color: #192320; + border: none; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + } + + button:hover { + background-color: #c6ff03; + }`); + } +})(); diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index 5283596316..7c6a533429 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -3,17 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -/** - * BOOT LOADER - * サーバーからレスポンスされるHTMLに埋め込まれるスクリプトで、以下の役割を持ちます。 - * - 翻訳ファイルをフェッチする。 - * - バージョンに基づいて適切なメインスクリプトを読み込む。 - * - キャッシュされたコンパイル済みテーマを適用する。 - * - クライアントの設定値に基づいて対応するHTMLクラス等を設定する。 - * テーマをこの段階で設定するのは、メインスクリプトが読み込まれる間もテーマを適用したいためです。 - * 注: webpackは介さないため、このファイルではrequireやimportは使えません。 - */ - 'use strict'; // ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css index e4723c24fd..dbcc8f537c 100644 --- a/packages/backend/src/server/web/style.css +++ b/packages/backend/src/server/web/style.css @@ -47,6 +47,7 @@ html { transform: translateY(70px); color: var(--accent); } + #splashSpinner > .spinner { position: absolute; top: 0; diff --git a/packages/backend/src/server/web/style.embed.css b/packages/backend/src/server/web/style.embed.css new file mode 100644 index 0000000000..a7b110d80a --- /dev/null +++ b/packages/backend/src/server/web/style.embed.css @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +html { + background-color: var(--bg); + color: var(--fg); +} + +html.embed { + box-sizing: border-box; + background-color: transparent; + color-scheme: light dark; + max-width: 500px; +} + +#splash { + position: fixed; + z-index: 10000; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + cursor: wait; + background-color: var(--bg); + opacity: 1; + transition: opacity 0.5s ease; +} + +html.embed #splash { + box-sizing: border-box; + min-height: 300px; + border-radius: var(--radius, 12px); + border: 1px solid var(--divider, #e8e8e8); +} + +html.embed.norounded #splash { + border-radius: 0; +} + +html.embed.noborder #splash { + border: none; +} + +#splashIcon { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: auto; + width: 64px; + height: 64px; + pointer-events: none; +} + +#splashSpinner { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: auto; + display: inline-block; + width: 28px; + height: 28px; + transform: translateY(70px); + color: var(--accent); +} + +#splashSpinner > .spinner { + position: absolute; + top: 0; + left: 0; + width: 28px; + height: 28px; + fill-rule: evenodd; + clip-rule: evenodd; + stroke-linecap: round; + stroke-linejoin: round; + stroke-miterlimit: 1.5; +} +#splashSpinner > .spinner.bg { + opacity: 0.275; +} +#splashSpinner > .spinner.fg { + animation: splashSpinner 0.5s linear infinite; +} + +@keyframes splashSpinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/packages/backend/src/server/web/views/base-embed.pug b/packages/backend/src/server/web/views/base-embed.pug new file mode 100644 index 0000000000..2bab20a36c --- /dev/null +++ b/packages/backend/src/server/web/views/base-embed.pug @@ -0,0 +1,70 @@ +block vars + +block loadClientEntry + - const entry = config.frontendEmbedEntry; + +doctype html + +html(class='embed') + + head + meta(charset='utf-8') + meta(name='application-name' content='Misskey') + meta(name='referrer' content='origin') + meta(name='theme-color' content= themeColor || '#86b300') + meta(name='theme-color-orig' content= themeColor || '#86b300') + meta(name='viewport' content='width=device-width, initial-scale=1') + meta(name='format-detection' content='telephone=no,date=no,address=no,email=no,url=no') + link(rel='icon' href= icon || '/favicon.ico') + link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png') + link(rel='modulepreload' href=`/embed_vite/${entry.file}`) + + if !config.frontendEmbedManifestExists + script(type="module" src="/embed_vite/@vite/client") + + if Array.isArray(entry.css) + each href in entry.css + link(rel='stylesheet' href=`/embed_vite/${href}`) + + title + block title + = title || 'Misskey' + + block meta + meta(name='robots' content='noindex') + + style + include ../style.embed.css + + script. + var VERSION = "#{version}"; + var CLIENT_ENTRY = "#{entry.file}"; + + script(type='application/json' id='misskey_meta' data-generated-at=now) + != metaJson + + script(type='application/json' id='misskey_embedCtx' data-generated-at=now) + != embedCtx + + script + include ../boot.embed.js + + body + noscript: p + | JavaScriptを有効にしてください + br + | Please turn on your JavaScript + div#splash + img#splashIcon(src= icon || '/static-assets/splash.png') + div#splashSpinner + <svg class="spinner bg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg"> + <g transform="matrix(1,0,0,1,12,12)"> + <circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:24px;"/> + </g> + </svg> + <svg class="spinner fg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg"> + <g transform="matrix(1,0,0,1,12,12)"> + <path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:24px;"/> + </g> + </svg> + block content diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index da6d1eafd3..88714b2556 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -1,7 +1,7 @@ block vars block loadClientEntry - - const clientEntry = config.clientEntry; + - const entry = config.frontendEntry; doctype html @@ -36,13 +36,13 @@ html link(rel='prefetch' href=serverErrorImageUrl) link(rel='prefetch' href=infoImageUrl) link(rel='prefetch' href=notFoundImageUrl) - link(rel='modulepreload' href=`/vite/${clientEntry.file}`) + link(rel='modulepreload' href=`/vite/${entry.file}`) - if !config.clientManifestExists + if !config.frontendManifestExists script(type="module" src="/vite/@vite/client") - if Array.isArray(clientEntry.css) - each href in clientEntry.css + if Array.isArray(entry.css) + each href in entry.css link(rel='stylesheet' href=`/vite/${href}`) title @@ -68,7 +68,7 @@ html script. var VERSION = "#{version}"; - var CLIENT_ENTRY = "#{clientEntry.file}"; + var CLIENT_ENTRY = "#{entry.file}"; script(type='application/json' id='misskey_meta' data-generated-at=now) != metaJson diff --git a/packages/backend/test-server/entry.ts b/packages/backend/test-server/entry.ts index 866a7e1f5b..04bf62d209 100644 --- a/packages/backend/test-server/entry.ts +++ b/packages/backend/test-server/entry.ts @@ -6,12 +6,16 @@ import { MainModule } from '@/MainModule.js'; import { ServerService } from '@/server/ServerService.js'; import { loadConfig } from '@/config.js'; import { NestLogger } from '@/NestLogger.js'; +import { INestApplicationContext } from '@nestjs/common'; const config = loadConfig(); const originEnv = JSON.stringify(process.env); process.env.NODE_ENV = 'test'; +let app: INestApplicationContext; +let serverService: ServerService; + /** * テスト用のサーバインスタンスを起動する */ @@ -20,10 +24,10 @@ async function launch() { console.log('starting application...'); - const app = await NestFactory.createApplicationContext(MainModule, { + app = await NestFactory.createApplicationContext(MainModule, { logger: new NestLogger(), }); - const serverService = app.get(ServerService); + serverService = app.get(ServerService); await serverService.launch(); await startControllerEndpoints(); @@ -71,6 +75,20 @@ async function startControllerEndpoints(port = config.port + 1000) { fastify.post<{ Body: { key?: string, value?: string } }>('/env-reset', async (req, res) => { process.env = JSON.parse(originEnv); + + await serverService.dispose(); + await app.close(); + + await killTestServer(); + + console.log('starting application...'); + + app = await NestFactory.createApplicationContext(MainModule, { + logger: new NestLogger(), + }); + serverService = app.get(ServerService); + await serverService.launch(); + res.code(200).send({ success: true }); }); diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts index 6ac14cd8dc..a544db955a 100644 --- a/packages/backend/test/e2e/antennas.ts +++ b/packages/backend/test/e2e/antennas.ts @@ -228,6 +228,17 @@ describe('アンテナ', () => { assert.deepStrictEqual(response, expected); }); + test('を作成する時キーワードが指定されていないとエラーになる', async () => { + await failedApiCall({ + endpoint: 'antennas/create', + parameters: { ...defaultParam, keywords: [[]], excludeKeywords: [[]] }, + user: alice + }, { + status: 400, + code: 'EMPTY_KEYWORD', + id: '53ee222e-1ddd-4f9a-92e5-9fb82ddb463a' + }) + }); //#endregion //#region 更新(antennas/update) @@ -255,6 +266,18 @@ describe('アンテナ', () => { id: '1c6b35c9-943e-48c2-81e4-2844989407f7', }); }); + test('を変更する時キーワードが指定されていないとエラーになる', async () => { + const antenna = await successfulApiCall({ endpoint: 'antennas/create', parameters: defaultParam, user: alice }); + await failedApiCall({ + endpoint: 'antennas/update', + parameters: { ...defaultParam, antennaId: antenna.id, keywords: [[]], excludeKeywords: [[]] }, + user: alice + }, { + status: 400, + code: 'EMPTY_KEYWORD', + id: '721aaff6-4e1b-4d88-8de6-877fae9f68c4' + }) + }); //#endregion //#region 表示(antennas/show) diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts index 7efd688ec2..f8aa67cfac 100644 --- a/packages/backend/test/e2e/fetch-resource.ts +++ b/packages/backend/test/e2e/fetch-resource.ts @@ -180,6 +180,7 @@ describe('Webリソース', () => { })); }); + /* queueは一時的に無効化されている describe.each([{ path: '/queue' }])('$path', ({ path }) => { test('はログインしないとGETできない。', async () => await notOk({ path, @@ -197,6 +198,7 @@ describe('Webリソース', () => { cookie: cookie(alice), })); }); + */ describe.each([{ path: '/streaming' }])('$path', ({ path }) => { test('はGETできない。', async () => await notOk({ diff --git a/packages/backend/test/jest.setup.ts b/packages/backend/test/jest.setup.ts index 861bc6db66..7c6dd6a55f 100644 --- a/packages/backend/test/jest.setup.ts +++ b/packages/backend/test/jest.setup.ts @@ -6,8 +6,6 @@ import { initTestDb, sendEnvResetRequest } from './utils.js'; beforeAll(async () => { - await Promise.all([ - initTestDb(false), - sendEnvResetRequest(), - ]); + await initTestDb(false); + await sendEnvResetRequest(); }); diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts index 3c7e796700..c8f3db8aac 100644 --- a/packages/backend/test/misc/mock-resolver.ts +++ b/packages/backend/test/misc/mock-resolver.ts @@ -17,6 +17,7 @@ import type { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import type { FollowRequestsRepository, + MiMeta, NoteReactionsRepository, NotesRepository, PollsRepository, @@ -35,6 +36,7 @@ export class MockResolver extends Resolver { constructor(loggerService: LoggerService) { super( {} as Config, + {} as MiMeta, {} as UsersRepository, {} as NotesRepository, {} as PollsRepository, @@ -42,7 +44,6 @@ export class MockResolver extends Resolver { {} as FollowRequestsRepository, {} as UtilityService, {} as InstanceActorService, - {} as MetaService, {} as ApRequestService, {} as HttpRequestService, {} as ApRendererService, diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts index b6cbe4c520..ef80d25f81 100644 --- a/packages/backend/test/unit/RoleService.ts +++ b/packages/backend/test/unit/RoleService.ts @@ -13,6 +13,7 @@ import * as lolex from '@sinonjs/fake-timers'; import { GlobalModule } from '@/GlobalModule.js'; import { RoleService } from '@/core/RoleService.js'; import { + MiMeta, MiRole, MiRoleAssignment, MiUser, @@ -41,7 +42,7 @@ describe('RoleService', () => { let usersRepository: UsersRepository; let rolesRepository: RolesRepository; let roleAssignmentsRepository: RoleAssignmentsRepository; - let metaService: jest.Mocked<MetaService>; + let meta: jest.Mocked<MiMeta>; let notificationService: jest.Mocked<NotificationService>; let clock: lolex.InstalledClock; @@ -142,7 +143,7 @@ describe('RoleService', () => { rolesRepository = app.get<RolesRepository>(DI.rolesRepository); roleAssignmentsRepository = app.get<RoleAssignmentsRepository>(DI.roleAssignmentsRepository); - metaService = app.get<MetaService>(MetaService) as jest.Mocked<MetaService>; + meta = app.get<MiMeta>(DI.meta) as jest.Mocked<MiMeta>; notificationService = app.get<NotificationService>(NotificationService) as jest.Mocked<NotificationService>; await roleService.onModuleInit(); @@ -164,11 +165,9 @@ describe('RoleService', () => { describe('getUserPolicies', () => { test('instance default policies', async () => { const user = await createUser(); - metaService.fetch.mockResolvedValue({ - policies: { - canManageCustomEmojis: false, - }, - } as any); + meta.policies = { + canManageCustomEmojis: false, + }; const result = await roleService.getUserPolicies(user.id); @@ -177,11 +176,9 @@ describe('RoleService', () => { test('instance default policies 2', async () => { const user = await createUser(); - metaService.fetch.mockResolvedValue({ - policies: { - canManageCustomEmojis: true, - }, - } as any); + meta.policies = { + canManageCustomEmojis: true, + }; const result = await roleService.getUserPolicies(user.id); @@ -201,11 +198,9 @@ describe('RoleService', () => { }, }); await roleService.assign(user.id, role.id); - metaService.fetch.mockResolvedValue({ - policies: { - canManageCustomEmojis: false, - }, - } as any); + meta.policies = { + canManageCustomEmojis: false, + }; const result = await roleService.getUserPolicies(user.id); @@ -236,11 +231,9 @@ describe('RoleService', () => { }); await roleService.assign(user.id, role1.id); await roleService.assign(user.id, role2.id); - metaService.fetch.mockResolvedValue({ - policies: { - driveCapacityMb: 50, - }, - } as any); + meta.policies = { + driveCapacityMb: 50, + }; const result = await roleService.getUserPolicies(user.id); @@ -260,11 +253,9 @@ describe('RoleService', () => { }, }); await roleService.assign(user.id, role.id, new Date(Date.now() + (1000 * 60 * 60 * 24))); - metaService.fetch.mockResolvedValue({ - policies: { - canManageCustomEmojis: false, - }, - } as any); + meta.policies = { + canManageCustomEmojis: false, + }; const result = await roleService.getUserPolicies(user.id); expect(result.canManageCustomEmojis).toBe(true); diff --git a/packages/backend/test/unit/SystemWebhookService.ts b/packages/backend/test/unit/SystemWebhookService.ts index 790cd1490e..5401dd74d8 100644 --- a/packages/backend/test/unit/SystemWebhookService.ts +++ b/packages/backend/test/unit/SystemWebhookService.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /* * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only @@ -6,6 +7,7 @@ import { setTimeout } from 'node:timers/promises'; import { afterEach, beforeEach, describe, expect, jest } from '@jest/globals'; import { Test, TestingModule } from '@nestjs/testing'; +import { randomString } from '../utils.js'; import { MiUser } from '@/models/User.js'; import { MiSystemWebhook, SystemWebhookEventType } from '@/models/SystemWebhook.js'; import { SystemWebhooksRepository, UsersRepository } from '@/models/_.js'; @@ -17,7 +19,6 @@ import { DI } from '@/di-symbols.js'; import { QueueService } from '@/core/QueueService.js'; import { LoggerService } from '@/core/LoggerService.js'; import { SystemWebhookService } from '@/core/SystemWebhookService.js'; -import { randomString } from '../utils.js'; describe('SystemWebhookService', () => { let app: TestingModule; @@ -313,7 +314,7 @@ describe('SystemWebhookService', () => { isActive: true, on: ['abuseReport'], }); - await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' }); + await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' } as any); expect(queueService.systemWebhookDeliver).toHaveBeenCalled(); }); @@ -323,7 +324,7 @@ describe('SystemWebhookService', () => { isActive: false, on: ['abuseReport'], }); - await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' }); + await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' } as any); expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled(); }); @@ -337,8 +338,8 @@ describe('SystemWebhookService', () => { isActive: true, on: ['abuseReportResolved'], }); - await service.enqueueSystemWebhook(webhook1.id, 'abuseReport', { foo: 'bar' }); - await service.enqueueSystemWebhook(webhook2.id, 'abuseReport', { foo: 'bar' }); + await service.enqueueSystemWebhook(webhook1.id, 'abuseReport', { foo: 'bar' } as any); + await service.enqueueSystemWebhook(webhook2.id, 'abuseReport', { foo: 'bar' } as any); expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled(); }); diff --git a/packages/backend/test/unit/UserWebhookService.ts b/packages/backend/test/unit/UserWebhookService.ts new file mode 100644 index 0000000000..0e88835a02 --- /dev/null +++ b/packages/backend/test/unit/UserWebhookService.ts @@ -0,0 +1,245 @@ + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { afterEach, beforeEach, describe, expect, jest } from '@jest/globals'; +import { Test, TestingModule } from '@nestjs/testing'; +import { randomString } from '../utils.js'; +import { MiUser } from '@/models/User.js'; +import { MiWebhook, UsersRepository, WebhooksRepository } from '@/models/_.js'; +import { IdService } from '@/core/IdService.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { DI } from '@/di-symbols.js'; +import { QueueService } from '@/core/QueueService.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import { UserWebhookService } from '@/core/UserWebhookService.js'; + +describe('UserWebhookService', () => { + let app: TestingModule; + let service: UserWebhookService; + + // -------------------------------------------------------------------------------------- + + let usersRepository: UsersRepository; + let userWebhooksRepository: WebhooksRepository; + let idService: IdService; + let queueService: jest.Mocked<QueueService>; + + // -------------------------------------------------------------------------------------- + + let root: MiUser; + + // -------------------------------------------------------------------------------------- + + async function createUser(data: Partial<MiUser> = {}) { + return await usersRepository + .insert({ + id: idService.gen(), + ...data, + }) + .then(x => usersRepository.findOneByOrFail(x.identifiers[0])); + } + + async function createWebhook(data: Partial<MiWebhook> = {}) { + return userWebhooksRepository + .insert({ + id: idService.gen(), + name: randomString(), + on: ['mention'], + url: 'https://example.com', + secret: randomString(), + userId: root.id, + ...data, + }) + .then(x => userWebhooksRepository.findOneByOrFail(x.identifiers[0])); + } + + // -------------------------------------------------------------------------------------- + + async function beforeAllImpl() { + app = await Test + .createTestingModule({ + imports: [ + GlobalModule, + ], + providers: [ + UserWebhookService, + IdService, + LoggerService, + GlobalEventService, + { + provide: QueueService, useFactory: () => ({ systemWebhookDeliver: jest.fn() }), + }, + ], + }) + .compile(); + + usersRepository = app.get(DI.usersRepository); + userWebhooksRepository = app.get(DI.webhooksRepository); + + service = app.get(UserWebhookService); + idService = app.get(IdService); + queueService = app.get(QueueService) as jest.Mocked<QueueService>; + + app.enableShutdownHooks(); + } + + async function afterAllImpl() { + await app.close(); + } + + async function beforeEachImpl() { + root = await createUser({ isRoot: true, username: 'root', usernameLower: 'root' }); + } + + async function afterEachImpl() { + await usersRepository.delete({}); + await userWebhooksRepository.delete({}); + } + + // -------------------------------------------------------------------------------------- + + describe('アプリを毎回作り直す必要のないグループ', () => { + beforeAll(beforeAllImpl); + afterAll(afterAllImpl); + beforeEach(beforeEachImpl); + afterEach(afterEachImpl); + + describe('fetchSystemWebhooks', () => { + test('フィルタなし', async () => { + const webhook1 = await createWebhook({ + active: true, + on: ['mention'], + }); + const webhook2 = await createWebhook({ + active: false, + on: ['mention'], + }); + const webhook3 = await createWebhook({ + active: true, + on: ['reply'], + }); + const webhook4 = await createWebhook({ + active: false, + on: [], + }); + + const fetchedWebhooks = await service.fetchWebhooks(); + expect(fetchedWebhooks).toEqual([webhook1, webhook2, webhook3, webhook4]); + }); + + test('activeのみ', async () => { + const webhook1 = await createWebhook({ + active: true, + on: ['mention'], + }); + const webhook2 = await createWebhook({ + active: false, + on: ['mention'], + }); + const webhook3 = await createWebhook({ + active: true, + on: ['reply'], + }); + const webhook4 = await createWebhook({ + active: false, + on: [], + }); + + const fetchedWebhooks = await service.fetchWebhooks({ isActive: true }); + expect(fetchedWebhooks).toEqual([webhook1, webhook3]); + }); + + test('特定のイベントのみ', async () => { + const webhook1 = await createWebhook({ + active: true, + on: ['mention'], + }); + const webhook2 = await createWebhook({ + active: false, + on: ['mention'], + }); + const webhook3 = await createWebhook({ + active: true, + on: ['reply'], + }); + const webhook4 = await createWebhook({ + active: false, + on: [], + }); + + const fetchedWebhooks = await service.fetchWebhooks({ on: ['mention'] }); + expect(fetchedWebhooks).toEqual([webhook1, webhook2]); + }); + + test('activeな特定のイベントのみ', async () => { + const webhook1 = await createWebhook({ + active: true, + on: ['mention'], + }); + const webhook2 = await createWebhook({ + active: false, + on: ['mention'], + }); + const webhook3 = await createWebhook({ + active: true, + on: ['reply'], + }); + const webhook4 = await createWebhook({ + active: false, + on: [], + }); + + const fetchedWebhooks = await service.fetchWebhooks({ on: ['mention'], isActive: true }); + expect(fetchedWebhooks).toEqual([webhook1]); + }); + + test('ID指定', async () => { + const webhook1 = await createWebhook({ + active: true, + on: ['mention'], + }); + const webhook2 = await createWebhook({ + active: false, + on: ['mention'], + }); + const webhook3 = await createWebhook({ + active: true, + on: ['reply'], + }); + const webhook4 = await createWebhook({ + active: false, + on: [], + }); + + const fetchedWebhooks = await service.fetchWebhooks({ ids: [webhook1.id, webhook4.id] }); + expect(fetchedWebhooks).toEqual([webhook1, webhook4]); + }); + + test('ID指定(他条件とANDになるか見たい)', async () => { + const webhook1 = await createWebhook({ + active: true, + on: ['mention'], + }); + const webhook2 = await createWebhook({ + active: false, + on: ['mention'], + }); + const webhook3 = await createWebhook({ + active: true, + on: ['reply'], + }); + const webhook4 = await createWebhook({ + active: false, + on: [], + }); + + const fetchedWebhooks = await service.fetchWebhooks({ ids: [webhook1.id, webhook4.id], isActive: false }); + expect(fetchedWebhooks).toEqual([webhook4]); + }); + }); + }); +}); diff --git a/packages/backend/test/unit/WebhookTestService.ts b/packages/backend/test/unit/WebhookTestService.ts new file mode 100644 index 0000000000..5e63b86f8f --- /dev/null +++ b/packages/backend/test/unit/WebhookTestService.ts @@ -0,0 +1,225 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Test, TestingModule } from '@nestjs/testing'; +import { beforeAll, describe, jest } from '@jest/globals'; +import { WebhookTestService } from '@/core/WebhookTestService.js'; +import { UserWebhookService } from '@/core/UserWebhookService.js'; +import { SystemWebhookService } from '@/core/SystemWebhookService.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import { IdService } from '@/core/IdService.js'; +import { DI } from '@/di-symbols.js'; +import { QueueService } from '@/core/QueueService.js'; + +describe('WebhookTestService', () => { + let app: TestingModule; + let service: WebhookTestService; + + // -------------------------------------------------------------------------------------- + + let usersRepository: UsersRepository; + let userProfilesRepository: UserProfilesRepository; + let queueService: jest.Mocked<QueueService>; + let userWebhookService: jest.Mocked<UserWebhookService>; + let systemWebhookService: jest.Mocked<SystemWebhookService>; + let idService: IdService; + + let root: MiUser; + let alice: MiUser; + + async function createUser(data: Partial<MiUser> = {}) { + const user = await usersRepository + .insert({ + id: idService.gen(), + ...data, + }) + .then(x => usersRepository.findOneByOrFail(x.identifiers[0])); + + await userProfilesRepository.insert({ + userId: user.id, + }); + + return user; + } + + // -------------------------------------------------------------------------------------- + + beforeAll(async () => { + app = await Test.createTestingModule({ + imports: [ + GlobalModule, + ], + providers: [ + WebhookTestService, + IdService, + { + provide: QueueService, useFactory: () => ({ + systemWebhookDeliver: jest.fn(), + userWebhookDeliver: jest.fn(), + }), + }, + { + provide: UserWebhookService, useFactory: () => ({ + fetchWebhooks: jest.fn(), + }), + }, + { + provide: SystemWebhookService, useFactory: () => ({ + fetchSystemWebhooks: jest.fn(), + }), + }, + ], + }).compile(); + + usersRepository = app.get(DI.usersRepository); + userProfilesRepository = app.get(DI.userProfilesRepository); + + service = app.get(WebhookTestService); + idService = app.get(IdService); + queueService = app.get(QueueService) as jest.Mocked<QueueService>; + userWebhookService = app.get(UserWebhookService) as jest.Mocked<UserWebhookService>; + systemWebhookService = app.get(SystemWebhookService) as jest.Mocked<SystemWebhookService>; + + app.enableShutdownHooks(); + }); + + beforeEach(async () => { + root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true }); + alice = await createUser({ username: 'alice', usernameLower: 'alice', isRoot: false }); + + userWebhookService.fetchWebhooks.mockReturnValue(Promise.resolve([ + { id: 'dummy-webhook', active: true, userId: alice.id } as MiWebhook, + ])); + systemWebhookService.fetchSystemWebhooks.mockReturnValue(Promise.resolve([ + { id: 'dummy-webhook', isActive: true } as MiSystemWebhook, + ])); + }); + + afterEach(async () => { + queueService.systemWebhookDeliver.mockClear(); + queueService.userWebhookDeliver.mockClear(); + userWebhookService.fetchWebhooks.mockClear(); + systemWebhookService.fetchSystemWebhooks.mockClear(); + + await usersRepository.delete({}); + await userProfilesRepository.delete({}); + }); + + afterAll(async () => { + await app.close(); + }); + + // -------------------------------------------------------------------------------------- + + describe('testUserWebhook', () => { + test('note', async () => { + await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'note' }, alice); + + const calls = queueService.userWebhookDeliver.mock.calls[0]; + expect((calls[0] as any).id).toBe('dummy-webhook'); + expect(calls[1]).toBe('note'); + expect((calls[2] as any).id).toBe('dummy-note-1'); + }); + + test('reply', async () => { + await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'reply' }, alice); + + const calls = queueService.userWebhookDeliver.mock.calls[0]; + expect((calls[0] as any).id).toBe('dummy-webhook'); + expect(calls[1]).toBe('reply'); + expect((calls[2] as any).id).toBe('dummy-reply-1'); + }); + + test('renote', async () => { + await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'renote' }, alice); + + const calls = queueService.userWebhookDeliver.mock.calls[0]; + expect((calls[0] as any).id).toBe('dummy-webhook'); + expect(calls[1]).toBe('renote'); + expect((calls[2] as any).id).toBe('dummy-renote-1'); + }); + + test('mention', async () => { + await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'mention' }, alice); + + const calls = queueService.userWebhookDeliver.mock.calls[0]; + expect((calls[0] as any).id).toBe('dummy-webhook'); + expect(calls[1]).toBe('mention'); + expect((calls[2] as any).id).toBe('dummy-mention-1'); + }); + + test('follow', async () => { + await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'follow' }, alice); + + const calls = queueService.userWebhookDeliver.mock.calls[0]; + expect((calls[0] as any).id).toBe('dummy-webhook'); + expect(calls[1]).toBe('follow'); + expect((calls[2] as any).id).toBe('dummy-user-1'); + }); + + test('followed', async () => { + await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'followed' }, alice); + + const calls = queueService.userWebhookDeliver.mock.calls[0]; + expect((calls[0] as any).id).toBe('dummy-webhook'); + expect(calls[1]).toBe('followed'); + expect((calls[2] as any).id).toBe('dummy-user-2'); + }); + + test('unfollow', async () => { + await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'unfollow' }, alice); + + const calls = queueService.userWebhookDeliver.mock.calls[0]; + expect((calls[0] as any).id).toBe('dummy-webhook'); + expect(calls[1]).toBe('unfollow'); + expect((calls[2] as any).id).toBe('dummy-user-3'); + }); + + describe('NoSuchWebhookError', () => { + test('user not match', async () => { + userWebhookService.fetchWebhooks.mockClear(); + userWebhookService.fetchWebhooks.mockReturnValue(Promise.resolve([ + { id: 'dummy-webhook', active: true } as MiWebhook, + ])); + + await expect(service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'note' }, root)) + .rejects.toThrow(WebhookTestService.NoSuchWebhookError); + }); + }); + }); + + describe('testSystemWebhook', () => { + test('abuseReport', async () => { + await service.testSystemWebhook({ webhookId: 'dummy-webhook', type: 'abuseReport' }); + + const calls = queueService.systemWebhookDeliver.mock.calls[0]; + expect((calls[0] as any).id).toBe('dummy-webhook'); + expect(calls[1]).toBe('abuseReport'); + expect((calls[2] as any).id).toBe('dummy-abuse-report1'); + expect((calls[2] as any).resolved).toBe(false); + }); + + test('abuseReportResolved', async () => { + await service.testSystemWebhook({ webhookId: 'dummy-webhook', type: 'abuseReportResolved' }); + + const calls = queueService.systemWebhookDeliver.mock.calls[0]; + expect((calls[0] as any).id).toBe('dummy-webhook'); + expect(calls[1]).toBe('abuseReportResolved'); + expect((calls[2] as any).id).toBe('dummy-abuse-report1'); + expect((calls[2] as any).resolved).toBe(true); + }); + + test('userCreated', async () => { + await service.testSystemWebhook({ webhookId: 'dummy-webhook', type: 'userCreated' }); + + const calls = queueService.systemWebhookDeliver.mock.calls[0]; + expect((calls[0] as any).id).toBe('dummy-webhook'); + expect(calls[1]).toBe('userCreated'); + expect((calls[2] as any).id).toBe('dummy-user-1'); + }); + }); +}); diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts index 763ce2b336..2fc08aec91 100644 --- a/packages/backend/test/unit/activitypub.ts +++ b/packages/backend/test/unit/activitypub.ts @@ -24,7 +24,6 @@ import { MiMeta, MiNote, UserProfilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { DownloadService } from '@/core/DownloadService.js'; -import { MetaService } from '@/core/MetaService.js'; import type { MiRemoteUser } from '@/models/User.js'; import { genAidx } from '@/misc/id/aidx.js'; import { MockResolver } from '../misc/mock-resolver.js'; @@ -107,7 +106,14 @@ describe('ActivityPub', () => { sensitiveWords: [] as string[], prohibitedWords: [] as string[], } as MiMeta; - let meta = metaInitial; + const meta = { ...metaInitial }; + + function updateMeta(newMeta: Partial<MiMeta>): void { + for (const key in meta) { + delete (meta as any)[key]; + } + Object.assign(meta, newMeta); + } beforeAll(async () => { const app = await Test.createTestingModule({ @@ -120,11 +126,8 @@ describe('ActivityPub', () => { }; }, }) - .overrideProvider(MetaService).useValue({ - async fetch(): Promise<MiMeta> { - return meta; - }, - }).compile(); + .overrideProvider(DI.meta).useFactory({ factory: () => meta }) + .compile(); await app.init(); app.enableShutdownHooks(); @@ -367,7 +370,7 @@ describe('ActivityPub', () => { }); test('cacheRemoteFiles=false disables caching', async () => { - meta = { ...metaInitial, cacheRemoteFiles: false }; + updateMeta({ ...metaInitial, cacheRemoteFiles: false }); const imageObject: IApDocument = { type: 'Document', @@ -396,7 +399,7 @@ describe('ActivityPub', () => { }); test('cacheRemoteSensitiveFiles=false only affects sensitive files', async () => { - meta = { ...metaInitial, cacheRemoteSensitiveFiles: false }; + updateMeta({ ...metaInitial, cacheRemoteSensitiveFiles: false }); const imageObject: IApDocument = { type: 'Document', diff --git a/packages/backend/test/unit/entities/UserEntityService.ts b/packages/backend/test/unit/entities/UserEntityService.ts index ee16d421c4..e4f42809f8 100644 --- a/packages/backend/test/unit/entities/UserEntityService.ts +++ b/packages/backend/test/unit/entities/UserEntityService.ts @@ -4,10 +4,10 @@ */ import { Test, TestingModule } from '@nestjs/testing'; +import type { MiUser } from '@/models/User.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GlobalModule } from '@/GlobalModule.js'; import { CoreModule } from '@/core/CoreModule.js'; -import type { MiUser } from '@/models/User.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { genAidx } from '@/misc/id/aidx.js'; import { @@ -49,6 +49,7 @@ import { ApLoggerService } from '@/core/activitypub/ApLoggerService.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; import { ReactionService } from '@/core/ReactionService.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js'; process.env.NODE_ENV = 'test'; @@ -169,6 +170,7 @@ describe('UserEntityService', () => { ApLoggerService, AccountMoveService, ReactionService, + ReactionsBufferingService, NotificationService, ]; diff --git a/packages/frontend-embed/.gitignore b/packages/frontend-embed/.gitignore new file mode 100644 index 0000000000..1aa0ac14e8 --- /dev/null +++ b/packages/frontend-embed/.gitignore @@ -0,0 +1 @@ +/storybook-static diff --git a/packages/frontend-embed/@types/global.d.ts b/packages/frontend-embed/@types/global.d.ts new file mode 100644 index 0000000000..1025d1bedb --- /dev/null +++ b/packages/frontend-embed/@types/global.d.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +type FIXME = any; + +declare const _LANGS_: string[][]; +declare const _VERSION_: string; +declare const _ENV_: string; +declare const _DEV_: boolean; +declare const _PERF_PREFIX_: string; +declare const _DATA_TRANSFER_DRIVE_FILE_: string; +declare const _DATA_TRANSFER_DRIVE_FOLDER_: string; +declare const _DATA_TRANSFER_DECK_COLUMN_: string; + +// for dev-mode +declare const _LANGS_FULL_: string[][]; + +// TagCanvas +interface Window { + TagCanvas: any; +} diff --git a/packages/frontend-embed/@types/theme.d.ts b/packages/frontend-embed/@types/theme.d.ts new file mode 100644 index 0000000000..6ac1037493 --- /dev/null +++ b/packages/frontend-embed/@types/theme.d.ts @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +declare module '@@/themes/*.json5' { + import { Theme } from '@/theme.js'; + + const theme: Theme; + + export default theme; +} diff --git a/packages/frontend-embed/assets/dummy.png b/packages/frontend-embed/assets/dummy.png new file mode 100644 index 0000000000..39332b0c1b Binary files /dev/null and b/packages/frontend-embed/assets/dummy.png differ diff --git a/packages/frontend-embed/eslint.config.js b/packages/frontend-embed/eslint.config.js new file mode 100644 index 0000000000..dd8f03dac5 --- /dev/null +++ b/packages/frontend-embed/eslint.config.js @@ -0,0 +1,95 @@ +import globals from 'globals'; +import tsParser from '@typescript-eslint/parser'; +import parser from 'vue-eslint-parser'; +import pluginVue from 'eslint-plugin-vue'; +import pluginMisskey from '@misskey-dev/eslint-plugin'; +import sharedConfig from '../shared/eslint.config.js'; + +export default [ + ...sharedConfig, + { + files: ['src/**/*.vue'], + ...pluginMisskey.configs.typescript, + }, + ...pluginVue.configs['flat/recommended'], + { + files: ['src/**/*.{ts,vue}'], + languageOptions: { + globals: { + ...Object.fromEntries(Object.entries(globals.node).map(([key]) => [key, 'off'])), + ...globals.browser, + + // Node.js + module: false, + require: false, + __dirname: false, + + // Misskey + _DEV_: false, + _LANGS_: false, + _VERSION_: false, + _ENV_: false, + _PERF_PREFIX_: false, + _DATA_TRANSFER_DRIVE_FILE_: false, + _DATA_TRANSFER_DRIVE_FOLDER_: false, + _DATA_TRANSFER_DECK_COLUMN_: false, + }, + parser, + parserOptions: { + extraFileExtensions: ['.vue'], + parser: tsParser, + project: ['./tsconfig.json'], + sourceType: 'module', + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + '@typescript-eslint/no-empty-interface': ['error', { + allowSingleExtends: true, + }], + // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため + // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため + 'id-denylist': ['error', 'window', 'e'], + 'no-shadow': ['warn'], + 'vue/attributes-order': ['error', { + alphabetical: false, + }], + 'vue/no-use-v-if-with-v-for': ['error', { + allowUsingIterationVar: false, + }], + 'vue/no-ref-as-operand': 'error', + 'vue/no-multi-spaces': ['error', { + ignoreProperties: false, + }], + 'vue/no-v-html': 'warn', + 'vue/order-in-components': 'error', + 'vue/html-indent': ['warn', 'tab', { + attribute: 1, + baseIndent: 0, + closeBracket: 0, + alignAttributesVertically: true, + ignores: [], + }], + 'vue/html-closing-bracket-spacing': ['warn', { + startTag: 'never', + endTag: 'never', + selfClosingTag: 'never', + }], + 'vue/multi-word-component-names': 'warn', + 'vue/require-v-for-key': 'warn', + 'vue/no-unused-components': 'warn', + 'vue/no-unused-vars': 'warn', + 'vue/no-dupe-keys': 'warn', + 'vue/valid-v-for': 'warn', + 'vue/return-in-computed-property': 'warn', + 'vue/no-setup-props-reactivity-loss': 'warn', + 'vue/max-attributes-per-line': 'off', + 'vue/html-self-closing': 'off', + 'vue/singleline-html-element-content-newline': 'off', + 'vue/v-on-event-hyphenation': ['error', 'never', { + autofix: true, + }], + 'vue/attribute-hyphenation': ['error', 'never'], + }, + }, +]; diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json new file mode 100644 index 0000000000..46a626edf4 --- /dev/null +++ b/packages/frontend-embed/package.json @@ -0,0 +1,85 @@ +{ + "name": "frontend-embed", + "private": true, + "type": "module", + "scripts": { + "watch": "vite", + "dev": "vite --config vite.config.local-dev.ts --debug hmr", + "build": "vite build", + "typecheck": "vue-tsc --noEmit", + "eslint": "eslint --quiet \"src/**/*.{ts,vue}\"", + "lint": "pnpm typecheck && pnpm eslint" + }, + "dependencies": { + "@discordapp/twemoji": "15.1.0", + "@github/webauthn-json": "2.1.1", + "@rollup/plugin-json": "6.1.0", + "@rollup/plugin-replace": "5.0.7", + "@rollup/pluginutils": "5.1.0", + "@tabler/icons-webfont": "3.3.0", + "@twemoji/parser": "15.1.1", + "@vitejs/plugin-vue": "5.1.4", + "@vue/compiler-sfc": "3.5.7", + "astring": "1.9.0", + "buraha": "0.0.1", + "compare-versions": "6.1.1", + "date-fns": "2.30.0", + "escape-regexp": "0.0.1", + "estree-walker": "3.0.3", + "eventemitter3": "5.0.1", + "idb-keyval": "6.2.1", + "is-file-animated": "1.0.2", + "mfm-js": "0.24.0", + "misskey-js": "workspace:*", + "frontend-shared": "workspace:*", + "punycode": "2.3.1", + "rollup": "4.22.2", + "sanitize-html": "2.13.0", + "sass": "1.79.3", + "shiki": "1.12.0", + "strict-event-emitter-types": "2.0.0", + "throttle-debounce": "5.0.2", + "tinycolor2": "1.6.0", + "tsc-alias": "1.8.10", + "tsconfig-paths": "4.2.0", + "typescript": "5.6.2", + "uuid": "10.0.0", + "json5": "2.2.3", + "vite": "5.4.7", + "vue": "3.5.7" + }, + "devDependencies": { + "@misskey-dev/summaly": "5.1.0", + "@testing-library/vue": "8.1.0", + "@types/escape-regexp": "0.0.3", + "@types/estree": "1.0.6", + "@types/micromatch": "4.0.9", + "@types/node": "20.14.12", + "@types/punycode": "2.1.4", + "@types/sanitize-html": "2.13.0", + "@types/throttle-debounce": "5.0.2", + "@types/tinycolor2": "1.4.6", + "@types/uuid": "10.0.0", + "@types/ws": "8.5.12", + "@typescript-eslint/eslint-plugin": "7.17.0", + "@typescript-eslint/parser": "7.17.0", + "@vitest/coverage-v8": "1.6.0", + "@vue/runtime-core": "3.5.7", + "acorn": "8.12.1", + "cross-env": "7.0.3", + "eslint-plugin-import": "2.30.0", + "eslint-plugin-vue": "9.28.0", + "fast-glob": "3.3.2", + "happy-dom": "10.0.3", + "intersection-observer": "0.12.2", + "micromatch": "4.0.8", + "msw": "2.3.4", + "nodemon": "3.1.7", + "prettier": "3.3.3", + "start-server-and-test": "2.0.8", + "vite-plugin-turbosnap": "1.0.3", + "vue-component-type-helpers": "2.1.6", + "vue-eslint-parser": "9.4.3", + "vue-tsc": "2.1.6" + } +} diff --git a/packages/frontend-embed/src/boot.ts b/packages/frontend-embed/src/boot.ts new file mode 100644 index 0000000000..00c7944eb3 --- /dev/null +++ b/packages/frontend-embed/src/boot.ts @@ -0,0 +1,141 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +// https://vitejs.dev/config/build-options.html#build-modulepreload +import 'vite/modulepreload-polyfill'; + +import '@tabler/icons-webfont/dist/tabler-icons.scss'; + +import '@/style.scss'; +import { createApp, defineAsyncComponent } from 'vue'; +import defaultLightTheme from '@@/themes/l-light.json5'; +import defaultDarkTheme from '@@/themes/d-dark.json5'; +import { MediaProxy } from '@@/js/media-proxy.js'; +import { applyTheme, assertIsTheme } from '@/theme.js'; +import { fetchCustomEmojis } from '@/custom-emojis.js'; +import { DI } from '@/di.js'; +import { serverMetadata } from '@/server-metadata.js'; +import { url } from '@@/js/config.js'; +import { parseEmbedParams } from '@@/js/embed-page.js'; +import { postMessageToParentWindow, setIframeId } from '@/post-message.js'; +import { serverContext } from '@/server-context.js'; + +import type { Theme } from '@/theme.js'; + +console.log('Misskey Embed'); + +//#region Embedパラメータの取得・パース +const params = new URLSearchParams(location.search); +const embedParams = parseEmbedParams(params); +if (_DEV_) console.log(embedParams); +//#endregion + +//#region テーマ +function parseThemeOrNull(theme: string | null): Theme | null { + if (theme == null) return null; + try { + const parsed = JSON.parse(theme); + if (assertIsTheme(parsed)) { + return parsed; + } else { + return null; + } + } catch (err) { + return null; + } +} + +const lightTheme = parseThemeOrNull(serverMetadata.defaultLightTheme) ?? defaultLightTheme; +const darkTheme = parseThemeOrNull(serverMetadata.defaultDarkTheme) ?? defaultDarkTheme; + +if (embedParams.colorMode === 'dark') { + applyTheme(darkTheme); +} else if (embedParams.colorMode === 'light') { + applyTheme(lightTheme); +} else { + if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + applyTheme(darkTheme); + } else { + applyTheme(lightTheme); + } + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (mql) => { + if (mql.matches) { + applyTheme(darkTheme); + } else { + applyTheme(lightTheme); + } + }); +} +//#endregion + +// サイズの制限 +document.documentElement.style.maxWidth = '500px'; + +// iframeIdの設定 +function setIframeIdHandler(event: MessageEvent) { + if (event.data?.type === 'misskey:embedParent:registerIframeId' && event.data.payload?.iframeId != null) { + setIframeId(event.data.payload.iframeId); + window.removeEventListener('message', setIframeIdHandler); + } +} + +window.addEventListener('message', setIframeIdHandler); + +try { + await fetchCustomEmojis(); +} catch (err) { /* empty */ } + +const app = createApp( + defineAsyncComponent(() => import('@/ui.vue')), +); + +app.provide(DI.mediaProxy, new MediaProxy(serverMetadata, url)); + +app.provide(DI.serverMetadata, serverMetadata); + +app.provide(DI.serverContext, serverContext); + +app.provide(DI.embedParams, embedParams); + +// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210 +// なぜか2回実行されることがあるため、mountするdivを1つに制限する +const rootEl = ((): HTMLElement => { + const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; + + const currentRoot = document.getElementById(MISSKEY_MOUNT_DIV_ID); + + if (currentRoot) { + console.warn('multiple import detected'); + return currentRoot; + } + + const root = document.createElement('div'); + root.id = MISSKEY_MOUNT_DIV_ID; + document.body.appendChild(root); + return root; +})(); + +postMessageToParentWindow('misskey:embed:ready'); + +app.mount(rootEl); + +// boot.jsのやつを解除 +window.onerror = null; +window.onunhandledrejection = null; + +removeSplash(); + +function removeSplash() { + const splash = document.getElementById('splash'); + if (splash) { + splash.style.opacity = '0'; + splash.style.pointerEvents = 'none'; + + // transitionendイベントが発火しない場合があるため + window.setTimeout(() => { + splash.remove(); + }, 1000); + } +} diff --git a/packages/frontend-embed/src/components/EmA.vue b/packages/frontend-embed/src/components/EmA.vue new file mode 100644 index 0000000000..1c236b9a35 --- /dev/null +++ b/packages/frontend-embed/src/components/EmA.vue @@ -0,0 +1,21 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<a :href="to" target="_blank" rel="noopener"> + <slot></slot> +</a> +</template> + +<script lang="ts" setup> +import { } from 'vue'; + +const props = withDefaults(defineProps<{ + to: string; + activeClass?: null | string; +}>(), { + activeClass: null, +}); +</script> diff --git a/packages/frontend-embed/src/components/EmAcct.vue b/packages/frontend-embed/src/components/EmAcct.vue new file mode 100644 index 0000000000..6856b8272e --- /dev/null +++ b/packages/frontend-embed/src/components/EmAcct.vue @@ -0,0 +1,24 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<span> + <span>@{{ user.username }}</span> + <span v-if="user.host || detail" style="opacity: 0.5;">@{{ user.host || host }}</span> +</span> +</template> + +<script lang="ts" setup> +import * as Misskey from 'misskey-js'; +import { toUnicode } from 'punycode/'; +import { host as hostRaw } from '@@/js/config.js'; + +defineProps<{ + user: Misskey.entities.UserLite; + detail?: boolean; +}>(); + +const host = toUnicode(hostRaw); +</script> diff --git a/packages/frontend-embed/src/components/EmAvatar.vue b/packages/frontend-embed/src/components/EmAvatar.vue new file mode 100644 index 0000000000..58c35c8ef0 --- /dev/null +++ b/packages/frontend-embed/src/components/EmAvatar.vue @@ -0,0 +1,250 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<component :is="link ? EmA : 'span'" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.cat]: user.isCat }]"> + <EmImgWithBlurhash :class="$style.inner" :src="url" :hash="user.avatarBlurhash" :cover="true" :onlyAvgColor="true"/> + <div v-if="user.isCat" :class="[$style.ears]"> + <div :class="$style.earLeft"> + <div v-if="false" :class="$style.layer"> + <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> + <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> + <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> + </div> + </div> + <div :class="$style.earRight"> + <div v-if="false" :class="$style.layer"> + <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> + <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> + <div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/> + </div> + </div> + </div> + <img + v-for="decoration in user.avatarDecorations" + :class="[$style.decoration]" + :src="getDecorationUrl(decoration)" + :style="{ + rotate: getDecorationAngle(decoration), + scale: getDecorationScale(decoration), + translate: getDecorationOffset(decoration), + }" + alt="" + > +</component> +</template> + +<script lang="ts" setup> +import { computed } from 'vue'; +import * as Misskey from 'misskey-js'; +import EmImgWithBlurhash from './EmImgWithBlurhash.vue'; +import EmA from './EmA.vue'; +import { userPage } from '@/utils.js'; + +const props = withDefaults(defineProps<{ + user: Misskey.entities.User; + link?: boolean; + preview?: boolean; + indicator?: boolean; +}>(), { + link: false, + preview: false, + indicator: false, +}); + +const emit = defineEmits<{ + (ev: 'click', v: MouseEvent): void; +}>(); + +const bound = computed(() => props.link + ? { to: userPage(props.user) } + : {}); + +const url = computed(() => { + if (props.user.avatarUrl == null) return null; + return props.user.avatarUrl; +}); + +function getDecorationUrl(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) { + return decoration.url; +} + +function getDecorationAngle(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) { + const angle = decoration.angle ?? 0; + return angle === 0 ? undefined : `${angle * 360}deg`; +} + +function getDecorationScale(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) { + const scaleX = decoration.flipH ? -1 : 1; + return scaleX === 1 ? undefined : `${scaleX} 1`; +} + +function getDecorationOffset(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) { + const offsetX = decoration.offsetX ?? 0; + const offsetY = decoration.offsetY ?? 0; + return offsetX === 0 && offsetY === 0 ? undefined : `${offsetX * 100}% ${offsetY * 100}%`; +} +</script> + +<style lang="scss" module> +.root { + position: relative; + display: inline-block; + vertical-align: bottom; + flex-shrink: 0; + border-radius: 100%; + line-height: 16px; +} + +.inner { + position: absolute; + bottom: 0; + left: 0; + right: 0; + top: 0; + border-radius: 100%; + z-index: 1; + overflow: clip; + object-fit: cover; + width: 100%; + height: 100%; +} + +.indicator { + position: absolute; + z-index: 2; + bottom: 0; + left: 0; + width: 20%; + height: 20%; +} + +.cat { + > .ears { + contain: strict; + position: absolute; + top: -50%; + left: -50%; + width: 100%; + height: 100%; + padding: 50%; + pointer-events: none; + + > .earLeft, + > .earRight { + contain: strict; + display: inline-block; + height: 50%; + width: 50%; + background: currentColor; + + &::after { + contain: strict; + content: ''; + display: block; + width: 60%; + height: 60%; + margin: 20%; + background: #df548f; + } + + > .layer { + contain: strict; + position: absolute; + top: 0; + width: 280%; + height: 280%; + + > .plot { + contain: strict; + position: absolute; + width: 100%; + height: 100%; + clip-path: path('M0 0H1V1H0z'); + transform: scale(32767); + transform-origin: 0 0; + opacity: 0.5; + + &:first-child { + opacity: 1; + } + + &:last-child { + opacity: calc(1 / 3); + } + } + } + } + + > .earLeft { + transform: rotate(37.5deg) skew(30deg); + + &, &::after { + border-radius: 25% 75% 75%; + } + + > .layer { + left: 0; + transform: + skew(-30deg) + rotate(-37.5deg) + translate(-2.82842712475%, /* -2 * sqrt(2) */ + -38.5857864376%); /* 40 - 2 * sqrt(2) */ + + > .plot { + background-position: 20% 10%; /* ~= 37.5deg */ + + &:first-child { + background-position-x: 21%; + } + + &:last-child { + background-position-y: 11%; + } + } + } + } + + > .earRight { + transform: rotate(-37.5deg) skew(-30deg); + + &, &::after { + border-radius: 75% 25% 75% 75%; + } + + > .layer { + right: 0; + transform: + skew(30deg) + rotate(37.5deg) + translate(2.82842712475%, /* 2 * sqrt(2) */ + -38.5857864376%); /* 40 - 2 * sqrt(2) */ + + > .plot { + position: absolute; + background-position: 80% 10%; /* ~= 37.5deg */ + + &:first-child { + background-position-x: 79%; + } + + &:last-child { + background-position-y: 11%; + } + } + } + } + } +} + +.decoration { + position: absolute; + z-index: 1; + top: -50%; + left: -50%; + width: 200%; + pointer-events: none; +} +</style> diff --git a/packages/frontend-embed/src/components/EmCustomEmoji.vue b/packages/frontend-embed/src/components/EmCustomEmoji.vue new file mode 100644 index 0000000000..e4149cf363 --- /dev/null +++ b/packages/frontend-embed/src/components/EmCustomEmoji.vue @@ -0,0 +1,101 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<img + v-if="errored && fallbackToImage" + :class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" + src="/client-assets/dummy.png" + :title="alt" +/> +<span v-else-if="errored">:{{ customEmojiName }}:</span> +<img + v-else + :class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" + :src="url" + :alt="alt" + :title="alt" + decoding="async" + @error="errored = true" + @load="errored = false" +/> +</template> + +<script lang="ts" setup> +import { computed, inject, ref } from 'vue'; +import { customEmojisMap } from '@/custom-emojis.js'; + +import { DI } from '@/di.js'; + +const mediaProxy = inject(DI.mediaProxy)!; + +const props = defineProps<{ + name: string; + normal?: boolean; + noStyle?: boolean; + host?: string | null; + url?: string; + useOriginalSize?: boolean; + menu?: boolean; + menuReaction?: boolean; + fallbackToImage?: boolean; +}>(); + +const customEmojiName = computed(() => (props.name[0] === ':' ? props.name.substring(1, props.name.length - 1) : props.name).replace('@.', '')); +const isLocal = computed(() => !props.host && (customEmojiName.value.endsWith('@.') || !customEmojiName.value.includes('@'))); + +const rawUrl = computed(() => { + if (props.url) { + return props.url; + } + if (isLocal.value) { + return customEmojisMap.get(customEmojiName.value)?.url ?? null; + } + return props.host ? `/emoji/${customEmojiName.value}@${props.host}.webp` : `/emoji/${customEmojiName.value}.webp`; +}); + +const url = computed(() => { + if (rawUrl.value == null) return undefined; + + const proxied = + (rawUrl.value.startsWith('/emoji/') || (props.useOriginalSize && isLocal.value)) + ? rawUrl.value + : mediaProxy.getProxiedImageUrl( + rawUrl.value, + props.useOriginalSize ? undefined : 'emoji', + false, + true, + ); + return proxied; +}); + +const alt = computed(() => `:${customEmojiName.value}:`); +const errored = ref(url.value == null); +</script> + +<style lang="scss" module> +.root { + height: 2em; + vertical-align: middle; + transition: transform 0.2s ease; + + &:hover { + transform: scale(1.2); + } +} + +.normal { + height: 1.25em; + vertical-align: -0.25em; + + &:hover { + transform: none; + } +} + +.noStyle { + height: auto !important; +} +</style> diff --git a/packages/frontend-embed/src/components/EmEmoji.vue b/packages/frontend-embed/src/components/EmEmoji.vue new file mode 100644 index 0000000000..224979707b --- /dev/null +++ b/packages/frontend-embed/src/components/EmEmoji.vue @@ -0,0 +1,26 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<img :class="$style.root" :src="url" :alt="props.emoji" decoding="async"/> +</template> + +<script lang="ts" setup> +import { computed } from 'vue'; +import { char2twemojiFilePath } from '@@/js/emoji-base.js'; + +const props = defineProps<{ + emoji: string; +}>(); + +const url = computed(() => char2twemojiFilePath(props.emoji)); +</script> + +<style lang="scss" module> +.root { + height: 1.25em; + vertical-align: -0.25em; +} +</style> diff --git a/packages/frontend-embed/src/components/EmError.vue b/packages/frontend-embed/src/components/EmError.vue new file mode 100644 index 0000000000..d376b29a7f --- /dev/null +++ b/packages/frontend-embed/src/components/EmError.vue @@ -0,0 +1,43 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="$style.root"> + <p :class="$style.text"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</p> + <button class="_buttonGray _buttonRounded" :class="$style.button" @click="() => emit('retry')">{{ i18n.ts.retry }}</button> +</div> +</template> + +<script lang="ts" setup> +import { i18n } from '@/i18n.js'; + +const emit = defineEmits<{ + (ev: 'retry'): void; +}>(); +</script> + +<style lang="scss" module> +.root { + padding: 32px; + text-align: center; + align-items: center; +} + +.text { + margin: 0 0 8px 0; +} + +.button { + margin: 0 auto; +} + +.img { + vertical-align: bottom; + width: 128px; + height: 128px; + margin-bottom: 16px; + border-radius: 16px; +} +</style> diff --git a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue new file mode 100644 index 0000000000..bf976c71ae --- /dev/null +++ b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue @@ -0,0 +1,240 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div ref="root" :class="['chromatic-ignore', $style.root, { [$style.cover]: cover }]" :title="title ?? ''"> + <canvas v-show="hide" key="canvas" ref="canvas" :class="$style.canvas" :width="canvasWidth" :height="canvasHeight" :title="title ?? undefined" tabindex="-1"/> + <img v-show="!hide" key="img" ref="img" :height="imgHeight ?? undefined" :width="imgWidth ?? undefined" :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined" loading="eager" decoding="async" tabindex="-1"/> +</div> +</template> + +<script lang="ts"> +import DrawBlurhash from '@/workers/draw-blurhash?worker'; +import TestWebGL2 from '@/workers/test-webgl2?worker'; +import { WorkerMultiDispatch } from '@@/js/worker-multi-dispatch.js'; +import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurhash.js'; + +const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resolve => { + // テスト環境で Web Worker インスタンスは作成できない + if (import.meta.env.MODE === 'test') { + const canvas = document.createElement('canvas'); + canvas.width = 64; + canvas.height = 64; + resolve(canvas); + return; + } + const testWorker = new TestWebGL2(); + testWorker.addEventListener('message', event => { + if (event.data.result) { + const workers = new WorkerMultiDispatch( + () => new DrawBlurhash(), + Math.min(navigator.hardwareConcurrency - 1, 4), + ); + resolve(workers); + if (_DEV_) console.log('WebGL2 in worker is supported!'); + } else { + const canvas = document.createElement('canvas'); + canvas.width = 64; + canvas.height = 64; + resolve(canvas); + if (_DEV_) console.log('WebGL2 in worker is not supported...'); + } + testWorker.terminate(); + }); +}); +</script> + +<script lang="ts" setup> +import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch, ref } from 'vue'; +import { v4 as uuid } from 'uuid'; +import { render } from 'buraha'; + +const props = withDefaults(defineProps<{ + src?: string | null; + hash?: string | null; + alt?: string | null; + title?: string | null; + height?: number; + width?: number; + cover?: boolean; + forceBlurhash?: boolean; + onlyAvgColor?: boolean; // 軽量化のためにBlurhashを使わずに平均色だけを描画 +}>(), { + src: null, + alt: '', + title: null, + height: 64, + width: 64, + cover: true, + forceBlurhash: false, + onlyAvgColor: false, +}); + +const viewId = uuid(); +const canvas = shallowRef<HTMLCanvasElement>(); +const root = shallowRef<HTMLDivElement>(); +const img = shallowRef<HTMLImageElement>(); +const loaded = ref(false); +const canvasWidth = ref(64); +const canvasHeight = ref(64); +const imgWidth = ref(props.width); +const imgHeight = ref(props.height); +const bitmapTmp = ref<CanvasImageSource | undefined>(); +const hide = computed(() => !loaded.value || props.forceBlurhash); + +function waitForDecode() { + if (props.src != null && props.src !== '') { + nextTick() + .then(() => img.value?.decode()) + .then(() => { + loaded.value = true; + }, error => { + console.log('Error occurred during decoding image', img.value, error); + }); + } else { + loaded.value = false; + } +} + +watch([() => props.width, () => props.height, root], () => { + const ratio = props.width / props.height; + if (ratio > 1) { + canvasWidth.value = Math.round(64 * ratio); + canvasHeight.value = 64; + } else { + canvasWidth.value = 64; + canvasHeight.value = Math.round(64 / ratio); + } + + const clientWidth = root.value?.clientWidth ?? 300; + imgWidth.value = clientWidth; + imgHeight.value = Math.round(clientWidth / ratio); +}, { + immediate: true, +}); + +function drawImage(bitmap: CanvasImageSource) { + // canvasがない(mountedされていない)場合はTmpに保存しておく + if (!canvas.value) { + bitmapTmp.value = bitmap; + return; + } + + // canvasがあれば描画する + bitmapTmp.value = undefined; + const ctx = canvas.value.getContext('2d'); + if (!ctx) return; + ctx.drawImage(bitmap, 0, 0, canvasWidth.value, canvasHeight.value); +} + +function drawAvg() { + if (!canvas.value) return; + + const color = (props.hash != null && extractAvgColorFromBlurhash(props.hash)) || '#888'; + + const ctx = canvas.value.getContext('2d'); + if (!ctx) return; + + // avgColorでお茶をにごす + ctx.beginPath(); + ctx.fillStyle = color; + ctx.fillRect(0, 0, canvasWidth.value, canvasHeight.value); +} + +async function draw() { + if (import.meta.env.MODE === 'test' && props.hash == null) return; + + drawAvg(); + + if (props.hash == null) return; + + if (props.onlyAvgColor) return; + + const work = await canvasPromise; + if (work instanceof WorkerMultiDispatch) { + work.postMessage( + { + id: viewId, + hash: props.hash, + }, + undefined, + ); + } else { + try { + render(props.hash, work); + drawImage(work); + } catch (error) { + console.error('Error occurred during drawing blurhash', error); + } + } +} + +function workerOnMessage(event: MessageEvent) { + if (event.data.id !== viewId) return; + drawImage(event.data.bitmap as ImageBitmap); +} + +canvasPromise.then(work => { + if (work instanceof WorkerMultiDispatch) { + work.addListener(workerOnMessage); + } + + draw(); +}); + +watch(() => props.src, () => { + waitForDecode(); +}); + +watch(() => props.hash, () => { + draw(); +}); + +onMounted(() => { + // drawImageがmountedより先に呼ばれている場合はここで描画する + if (bitmapTmp.value) { + drawImage(bitmapTmp.value); + } + waitForDecode(); +}); + +onUnmounted(() => { + canvasPromise.then(work => { + if (work instanceof WorkerMultiDispatch) { + work.removeListener(workerOnMessage); + } + }); +}); +</script> + +<style lang="scss" module> +.root { + position: relative; + width: 100%; + height: 100%; + + &.cover { + > .canvas, + > .img { + object-fit: cover; + } + } +} + +.canvas, +.img { + display: block; + width: 100%; + height: 100%; +} + +.canvas { + object-fit: contain; +} + +.img { + object-fit: contain; +} +</style> diff --git a/packages/frontend-embed/src/components/EmInstanceTicker.vue b/packages/frontend-embed/src/components/EmInstanceTicker.vue new file mode 100644 index 0000000000..eeeaee528e --- /dev/null +++ b/packages/frontend-embed/src/components/EmInstanceTicker.vue @@ -0,0 +1,87 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="$style.root" :style="bg"> + <img v-if="faviconUrl" :class="$style.icon" :src="faviconUrl"/> + <div :class="$style.name">{{ instance.name }}</div> +</div> +</template> + +<script lang="ts" setup> +import { computed, inject } from 'vue'; + +import { DI } from '@/di.js'; + +const serverMetadata = inject(DI.serverMetadata)!; +const mediaProxy = inject(DI.mediaProxy)!; + +const props = defineProps<{ + instance?: { + faviconUrl?: string | null + name?: string | null + themeColor?: string | null + } +}>(); + +// if no instance data is given, this is for the local instance +const instance = props.instance ?? { + name: serverMetadata.name, + themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement).content, +}; + +const faviconUrl = computed(() => props.instance ? mediaProxy.getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : mediaProxy.getProxiedImageUrlNullable(serverMetadata.iconUrl, 'preview') ?? '/favicon.ico'); + +const themeColor = serverMetadata.themeColor ?? '#777777'; + +const bg = { + background: `linear-gradient(90deg, ${themeColor}, ${themeColor}00)`, +}; +</script> + +<style lang="scss" module> +$height: 2ex; + +.root { + display: flex; + align-items: center; + height: $height; + border-radius: 4px 0 0 4px; + overflow: clip; + color: #fff; + text-shadow: /* .866 ≈ sin(60deg) */ + 1px 0 1px #000, + .866px .5px 1px #000, + .5px .866px 1px #000, + 0 1px 1px #000, + -.5px .866px 1px #000, + -.866px .5px 1px #000, + -1px 0 1px #000, + -.866px -.5px 1px #000, + -.5px -.866px 1px #000, + 0 -1px 1px #000, + .5px -.866px 1px #000, + .866px -.5px 1px #000; + mask-image: linear-gradient(90deg, + rgb(0,0,0), + rgb(0,0,0) calc(100% - 16px), + rgba(0,0,0,0) 100% + ); +} + +.icon { + height: $height; + flex-shrink: 0; +} + +.name { + margin-left: 4px; + line-height: 1; + font-size: 0.9em; + font-weight: bold; + white-space: nowrap; + overflow: visible; +} +</style> diff --git a/packages/frontend-embed/src/components/EmLink.vue b/packages/frontend-embed/src/components/EmLink.vue new file mode 100644 index 0000000000..aec9b33072 --- /dev/null +++ b/packages/frontend-embed/src/components/EmLink.vue @@ -0,0 +1,40 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<component + :is="self ? EmA : 'a'" ref="el" style="word-break: break-all;" class="_link" :[attr]="self ? url.substring(local.length) : url" :rel="rel ?? 'nofollow noopener'" :target="target" + :title="url" +> + <slot></slot> + <i v-if="target === '_blank'" class="ti ti-external-link" :class="$style.icon"></i> +</component> +</template> + +<script lang="ts" setup> +import { ref } from 'vue'; +import EmA from './EmA.vue'; +import { url as local } from '@@/js/config.js'; + +const props = withDefaults(defineProps<{ + url: string; + rel?: null | string; +}>(), { +}); + +const self = props.url.startsWith(local); +const attr = self ? 'to' : 'href'; +const target = self ? null : '_blank'; + +const el = ref<HTMLElement | { $el: HTMLElement }>(); + +</script> + +<style lang="scss" module> +.icon { + padding-left: 2px; + font-size: .9em; +} +</style> diff --git a/packages/frontend-embed/src/components/EmLoading.vue b/packages/frontend-embed/src/components/EmLoading.vue new file mode 100644 index 0000000000..49d8ace37b --- /dev/null +++ b/packages/frontend-embed/src/components/EmLoading.vue @@ -0,0 +1,112 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="[$style.root, { [$style.inline]: inline, [$style.colored]: colored, [$style.mini]: mini, [$style.em]: em }]"> + <div :class="$style.container"> + <svg :class="[$style.spinner, $style.bg]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg"> + <g transform="matrix(1.125,0,0,1.125,12,12)"> + <circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/> + </g> + </svg> + <svg :class="[$style.spinner, $style.fg, { [$style.static]: static }]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg"> + <g transform="matrix(1.125,0,0,1.125,12,12)"> + <path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/> + </g> + </svg> + </div> +</div> +</template> + +<script lang="ts" setup> +import { } from 'vue'; + +const props = withDefaults(defineProps<{ + static?: boolean; + inline?: boolean; + colored?: boolean; + mini?: boolean; + em?: boolean; +}>(), { + static: false, + inline: false, + colored: true, + mini: false, + em: false, +}); +</script> + +<style lang="scss" module> +@keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.root { + padding: 32px; + text-align: center; + cursor: wait; + + --size: 38px; + + &.colored { + color: var(--accent); + } + + &.inline { + display: inline; + padding: 0; + --size: 32px; + } + + &.mini { + padding: 16px; + --size: 32px; + } + + &.em { + display: inline-block; + vertical-align: middle; + padding: 0; + --size: 1em; + } +} + +.container { + position: relative; + width: var(--size); + height: var(--size); + margin: 0 auto; +} + +.spinner { + position: absolute; + top: 0; + left: 0; + width: var(--size); + height: var(--size); + fill-rule: evenodd; + clip-rule: evenodd; + stroke-linecap: round; + stroke-linejoin: round; + stroke-miterlimit: 1.5; +} + +.bg { + opacity: 0.275; +} + +.fg { + animation: spinner 0.5s linear infinite; + + &.static { + animation-play-state: paused; + } +} +</style> diff --git a/packages/frontend-embed/src/components/EmMediaBanner.vue b/packages/frontend-embed/src/components/EmMediaBanner.vue new file mode 100644 index 0000000000..435da238a4 --- /dev/null +++ b/packages/frontend-embed/src/components/EmMediaBanner.vue @@ -0,0 +1,55 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<a :href="href" target="_blank" :class="$style.root"> + <div :class="$style.label"> + <template v-if="media.type.startsWith('audio')"><i class="ti ti-music"></i> {{ i18n.ts.audio }}</template> + <template v-else><i class="ti ti-file"></i> {{ i18n.ts.file }}</template> + </div> + <div :class="$style.go"> + <i class="ti ti-chevron-right"></i> + </div> +</a> +</template> + +<script setup lang="ts"> +import * as Misskey from 'misskey-js'; +import { i18n } from '@/i18n.js'; + +defineProps<{ + media: Misskey.entities.DriveFile; + href: string; +}>(); +</script> + +<style lang="scss" module> +.root { + box-sizing: border-box; + display: flex; + align-items: center; + width: 100%; + padding: var(--margin); + margin-top: 4px; + border: 1px solid var(--inputBorder); + border-radius: var(--radius); + background-color: var(--panel); + transition: background-color .1s, border-color .1s; + + &:hover { + text-decoration: none; + border-color: var(--inputBorderHover); + background-color: var(--buttonHoverBg); + } +} + +.label { + font-size: .9em; +} + +.go { + margin-left: auto; +} +</style> diff --git a/packages/frontend-embed/src/components/EmMediaImage.vue b/packages/frontend-embed/src/components/EmMediaImage.vue new file mode 100644 index 0000000000..fe1aa5a877 --- /dev/null +++ b/packages/frontend-embed/src/components/EmMediaImage.vue @@ -0,0 +1,154 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="[hide ? $style.hidden : $style.visible]" :style="darkMode ? '--c: rgb(255 255 255 / 2%);' : '--c: rgb(0 0 0 / 2%);'" @click="onclick"> + <a + :title="image.name" + :class="$style.imageContainer" + :href="href ?? image.url" + target="_blank" + rel="noopener" + > + <ImgWithBlurhash + :hash="image.blurhash" + :src="hide ? null : url" + :forceBlurhash="hide" + :cover="hide || cover" + :alt="image.comment || image.name" + :title="image.comment || image.name" + :width="image.properties.width" + :height="image.properties.height" + :style="hide ? 'filter: brightness(0.7);' : null" + /> + </a> + <template v-if="hide"> + <div :class="$style.hiddenText"> + <div :class="$style.hiddenTextWrapper"> + <b v-if="image.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}</b> + <b v-else style="display: block;"><i class="ti ti-photo"></i> {{ i18n.ts.image }}</b> + <span style="display: block;">{{ i18n.ts.clickToShow }}</span> + </div> + </div> + </template> + <div :class="$style.indicators"> + <div v-if="['image/gif', 'image/apng'].includes(image.type)" :class="$style.indicator">GIF</div> + <div v-if="image.comment" :class="$style.indicator">ALT</div> + <div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div> + </div> + <i v-if="!hide" class="ti ti-eye-off" :class="$style.hide" @click.stop="hide = true"></i> +</div> +</template> + +<script lang="ts" setup> +import { ref, computed } from 'vue'; +import * as Misskey from 'misskey-js'; +import ImgWithBlurhash from '@/components/EmImgWithBlurhash.vue'; +import { i18n } from '@/i18n.js'; + +const props = withDefaults(defineProps<{ + image: Misskey.entities.DriveFile; + href?: string; + raw?: boolean; + cover?: boolean; +}>(), { + cover: false, +}); + +const hide = ref(props.image.isSensitive); +const darkMode = ref<boolean>(false); // TODO + +const url = computed(() => (props.raw) + ? props.image.url + : props.image.thumbnailUrl, +); + +async function onclick(ev: MouseEvent) { + if (hide.value) { + ev.stopPropagation(); + hide.value = false; + } +} +</script> + +<style lang="scss" module> +.hidden { + position: relative; +} + +.hiddenText { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + z-index: 1; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; +} + +.hide { + display: block; + position: absolute; + border-radius: 6px; + background-color: var(--fg); + color: var(--accentLighten); + font-size: 12px; + opacity: .5; + padding: 5px 8px; + text-align: center; + cursor: pointer; + top: 12px; + right: 12px; +} + +.hiddenTextWrapper { + display: table-cell; + text-align: center; + font-size: 0.8em; + color: #fff; +} + +.visible { + position: relative; + //box-shadow: 0 0 0 1px var(--divider) inset; + background: var(--bg); + background-image: linear-gradient(45deg, var(--c) 16.67%, var(--bg) 16.67%, var(--bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--bg) 66.67%, var(--bg) 100%); + background-size: 16px 16px; +} + +.imageContainer { + display: block; + overflow: hidden; + width: 100%; + height: 100%; + background-position: center; + background-size: contain; + background-repeat: no-repeat; +} + +.indicators { + display: inline-flex; + position: absolute; + top: 10px; + left: 10px; + pointer-events: none; + opacity: .5; + gap: 6px; +} + +.indicator { + /* Hardcode to black because either --bg or --fg makes it hard to read in dark/light mode */ + background-color: black; + border-radius: 6px; + color: var(--accentLighten); + display: inline-block; + font-weight: bold; + font-size: 0.8em; + padding: 2px 5px; +} +</style> diff --git a/packages/frontend-embed/src/components/EmMediaList.vue b/packages/frontend-embed/src/components/EmMediaList.vue new file mode 100644 index 0000000000..0b2d835abe --- /dev/null +++ b/packages/frontend-embed/src/components/EmMediaList.vue @@ -0,0 +1,146 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div> + <div v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :class="$style.banner"> + <XBanner :media="media" :href="originalEntityUrl"/> + </div> + <div v-if="mediaList.filter(media => previewable(media)).length > 0" :class="$style.container"> + <div + :class="[ + $style.medias, + count === 1 ? [$style.n1] : count === 2 ? $style.n2 : count === 3 ? $style.n3 : count === 4 ? $style.n4 : $style.nMany, + ]" + > + <div v-for="media in mediaList.filter(media => previewable(media))" :class="$style.media"> + <XVideo v-if="media.type.startsWith('video')" :key="`video:${media.id}`" :class="$style.mediaInner" :video="media" :href="originalEntityUrl"/> + <XImage v-else-if="media.type.startsWith('image')" :key="`image:${media.id}`" :class="$style.mediaInner" class="image" :image="media" :raw="raw" :href="originalEntityUrl"/> + </div> + </div> + </div> +</div> +</template> + +<script lang="ts" setup> +import { computed } from 'vue'; +import * as Misskey from 'misskey-js'; +import XBanner from './EmMediaBanner.vue'; +import XImage from './EmMediaImage.vue'; +import XVideo from './EmMediaVideo.vue'; +import { FILE_TYPE_BROWSERSAFE } from '@@/js/const.js'; + +const props = defineProps<{ + mediaList: Misskey.entities.DriveFile[]; + raw?: boolean; + + /** 埋め込みページ用 親要素の正規URL */ + originalEntityUrl: string; +}>(); + +const count = computed(() => props.mediaList.filter(media => previewable(media)).length); + +const previewable = (file: Misskey.entities.DriveFile): boolean => { + if (file.type === 'image/svg+xml') return true; // svgのwebpublic/thumbnailはpngなのでtrue + // FILE_TYPE_BROWSERSAFEに適合しないものはブラウザで表示するのに不適切 + return (file.type.startsWith('video') || file.type.startsWith('image')) && FILE_TYPE_BROWSERSAFE.includes(file.type); +}; +</script> + +<style lang="scss" module> +.container { + position: relative; + width: 100%; + margin-top: 4px; +} + +.medias { + display: grid; + grid-gap: 8px; + + height: 100%; + width: 100%; + + &.n1 { + grid-template-rows: 1fr; + + // default but fallback (expand) + min-height: 64px; + max-height: clamp( + 64px, + 50cqh, + min(360px, 50vh) + ); + + &.n116_9 { + min-height: initial; + max-height: initial; + aspect-ratio: 16 / 9; // fallback + } + + &.n11_1{ + min-height: initial; + max-height: initial; + aspect-ratio: 1 / 1; // fallback + } + + &.n12_3 { + min-height: initial; + max-height: initial; + aspect-ratio: 2 / 3; // fallback + } + } + + &.n2 { + aspect-ratio: 16/9; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr; + } + + &.n3 { + aspect-ratio: 16/9; + grid-template-columns: 1fr 0.5fr; + grid-template-rows: 1fr 1fr; + + > .media:nth-child(1) { + grid-row: 1 / 3; + } + + > .media:nth-child(3) { + grid-column: 2 / 3; + grid-row: 2 / 3; + } + } + + &.n4 { + aspect-ratio: 16/9; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr 1fr; + } + + &.nMany { + grid-template-columns: 1fr 1fr; + + > .media { + aspect-ratio: 16/9; + } + } +} + +.media { + overflow: hidden; // clipにするとバグる + border-radius: 8px; + position: relative; + + >.mediaInner { + width: 100%; + height: 100%; + } +} + +.banner { + position: relative; +} +</style> diff --git a/packages/frontend-embed/src/components/EmMediaVideo.vue b/packages/frontend-embed/src/components/EmMediaVideo.vue new file mode 100644 index 0000000000..ce751f9acd --- /dev/null +++ b/packages/frontend-embed/src/components/EmMediaVideo.vue @@ -0,0 +1,64 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<a :href="href" target="_blank" :class="$style.root"> + <img v-if="!video.isSensitive && video.thumbnailUrl" :class="$style.thumbnail" :src="video.thumbnailUrl"> + <div :class="$style.videoOverlayPlayButton"><i class="ti ti-player-play-filled"></i></div> +</a> +</template> + +<script setup lang="ts"> +import * as Misskey from 'misskey-js'; + +defineProps<{ + video: Misskey.entities.DriveFile; + href: string; +}>(); +</script> + +<style lang="scss" module> +.root { + position: relative; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: auto; + aspect-ratio: 16 / 9; + padding: var(--margin); + border: 1px solid var(--divider); + border-radius: var(--radius); + background-color: #000; + + &:hover { + text-decoration: none; + } +} + +.thumbnail { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; +} + +.videoOverlayPlayButton { + background: var(--accent); + color: #fff; + padding: 1rem; + border-radius: 99rem; + + font-size: 1rem; + line-height: 1rem; + + &:focus-visible { + outline: none; + } +} +</style> diff --git a/packages/frontend-embed/src/components/EmMention.vue b/packages/frontend-embed/src/components/EmMention.vue new file mode 100644 index 0000000000..777033bd3e --- /dev/null +++ b/packages/frontend-embed/src/components/EmMention.vue @@ -0,0 +1,46 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<MkA v-user-preview="canonical" :class="[$style.root]" :to="url" :style="{ background: bgCss }"> + <span> + <span>@{{ username }}</span> + <span v-if="(host != localHost)" :class="$style.host">@{{ toUnicode(host) }}</span> + </span> +</MkA> +</template> + +<script lang="ts" setup> +import { toUnicode } from 'punycode'; +import { } from 'vue'; +import tinycolor from 'tinycolor2'; +import { host as localHost } from '@@/js/config.js'; + +const props = defineProps<{ + username: string; + host: string; +}>(); + +const canonical = props.host === localHost ? `@${props.username}` : `@${props.username}@${toUnicode(props.host)}`; + +const url = `/${canonical}`; + +const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--mention')); +bg.setAlpha(0.1); +const bgCss = bg.toRgbString(); +</script> + +<style lang="scss" module> +.root { + display: inline-block; + padding: 4px 8px 4px 4px; + border-radius: 999px; + color: var(--mention); +} + +.host { + opacity: 0.5; +} +</style> diff --git a/packages/frontend-embed/src/components/EmMfm.ts b/packages/frontend-embed/src/components/EmMfm.ts new file mode 100644 index 0000000000..b2bcf4597e --- /dev/null +++ b/packages/frontend-embed/src/components/EmMfm.ts @@ -0,0 +1,461 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { VNode, h, SetupContext, provide } from 'vue'; +import * as mfm from 'mfm-js'; +import * as Misskey from 'misskey-js'; +import EmUrl from '@/components/EmUrl.vue'; +import EmTime from '@/components/EmTime.vue'; +import EmLink from '@/components/EmLink.vue'; +import EmMention from '@/components/EmMention.vue'; +import EmEmoji from '@/components/EmEmoji.vue'; +import EmCustomEmoji from '@/components/EmCustomEmoji.vue'; +import EmA from '@/components/EmA.vue'; +import { host } from '@@/js/config.js'; + +function safeParseFloat(str: unknown): number | null { + if (typeof str !== 'string' || str === '') return null; + const num = parseFloat(str); + if (isNaN(num)) return null; + return num; +} + +const QUOTE_STYLE = ` +display: block; +margin: 8px; +padding: 6px 0 6px 12px; +color: var(--fg); +border-left: solid 3px var(--fg); +opacity: 0.7; +`.split('\n').join(' '); + +type MfmProps = { + text: string; + plain?: boolean; + nowrap?: boolean; + author?: Misskey.entities.UserLite; + isNote?: boolean; + emojiUrls?: Record<string, string>; + rootScale?: number; + nyaize?: boolean | 'respect'; + parsedNodes?: mfm.MfmNode[] | null; + enableEmojiMenu?: boolean; + enableEmojiMenuReaction?: boolean; + linkNavigationBehavior?: string; +}; + +type MfmEvents = { + clickEv(id: string): void; +}; + +// eslint-disable-next-line import/no-default-export +export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEvents>['emit'] }) { + provide('linkNavigationBehavior', props.linkNavigationBehavior); + + const isNote = props.isNote ?? true; + const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat : false : false; + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (props.text == null || props.text === '') return; + + const rootAst = props.parsedNodes ?? (props.plain ? mfm.parseSimple : mfm.parse)(props.text); + + const validTime = (t: string | boolean | null | undefined) => { + if (t == null) return null; + if (typeof t === 'boolean') return null; + return t.match(/^\-?[0-9.]+s$/) ? t : null; + }; + + const validColor = (c: unknown): string | null => { + if (typeof c !== 'string') return null; + return c.match(/^[0-9a-f]{3,6}$/i) ? c : null; + }; + + const useAnim = true; + + /** + * Gen Vue Elements from MFM AST + * @param ast MFM AST + * @param scale How times large the text is + * @param disableNyaize Whether nyaize is disabled or not + */ + const genEl = (ast: mfm.MfmNode[], scale: number, disableNyaize = false) => ast.map((token): VNode | string | (VNode | string)[] => { + switch (token.type) { + case 'text': { + let text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n'); + if (!disableNyaize && shouldNyaize) { + text = Misskey.nyaize(text); + } + + if (!props.plain) { + const res: (VNode | string)[] = []; + for (const t of text.split('\n')) { + res.push(h('br')); + res.push(t); + } + res.shift(); + return res; + } else { + return [text.replace(/\n/g, ' ')]; + } + } + + case 'bold': { + return [h('b', genEl(token.children, scale))]; + } + + case 'strike': { + return [h('del', genEl(token.children, scale))]; + } + + case 'italic': { + return h('i', { + style: 'font-style: oblique;', + }, genEl(token.children, scale)); + } + + case 'fn': { + // TODO: CSSを文字列で組み立てていくと token.props.args.~~~ 経由でCSSインジェクションできるのでよしなにやる + let style: string | undefined; + switch (token.props.name) { + case 'tada': { + const speed = validTime(token.props.args.speed) ?? '1s'; + const delay = validTime(token.props.args.delay) ?? '0s'; + style = 'font-size: 150%;' + (useAnim ? `animation: global-tada ${speed} linear infinite both; animation-delay: ${delay};` : ''); + break; + } + case 'jelly': { + const speed = validTime(token.props.args.speed) ?? '1s'; + const delay = validTime(token.props.args.delay) ?? '0s'; + style = (useAnim ? `animation: mfm-rubberBand ${speed} linear infinite both; animation-delay: ${delay};` : ''); + break; + } + case 'twitch': { + const speed = validTime(token.props.args.speed) ?? '0.5s'; + const delay = validTime(token.props.args.delay) ?? '0s'; + style = useAnim ? `animation: mfm-twitch ${speed} ease infinite; animation-delay: ${delay};` : ''; + break; + } + case 'shake': { + const speed = validTime(token.props.args.speed) ?? '0.5s'; + const delay = validTime(token.props.args.delay) ?? '0s'; + style = useAnim ? `animation: mfm-shake ${speed} ease infinite; animation-delay: ${delay};` : ''; + break; + } + case 'spin': { + const direction = + token.props.args.left ? 'reverse' : + token.props.args.alternate ? 'alternate' : + 'normal'; + const anime = + token.props.args.x ? 'mfm-spinX' : + token.props.args.y ? 'mfm-spinY' : + 'mfm-spin'; + const speed = validTime(token.props.args.speed) ?? '1.5s'; + const delay = validTime(token.props.args.delay) ?? '0s'; + style = useAnim ? `animation: ${anime} ${speed} linear infinite; animation-direction: ${direction}; animation-delay: ${delay};` : ''; + break; + } + case 'jump': { + const speed = validTime(token.props.args.speed) ?? '0.75s'; + const delay = validTime(token.props.args.delay) ?? '0s'; + style = useAnim ? `animation: mfm-jump ${speed} linear infinite; animation-delay: ${delay};` : ''; + break; + } + case 'bounce': { + const speed = validTime(token.props.args.speed) ?? '0.75s'; + const delay = validTime(token.props.args.delay) ?? '0s'; + style = useAnim ? `animation: mfm-bounce ${speed} linear infinite; transform-origin: center bottom; animation-delay: ${delay};` : ''; + break; + } + case 'flip': { + const transform = + (token.props.args.h && token.props.args.v) ? 'scale(-1, -1)' : + token.props.args.v ? 'scaleY(-1)' : + 'scaleX(-1)'; + style = `transform: ${transform};`; + break; + } + case 'x2': { + return h('span', { + class: 'mfm-x2', + }, genEl(token.children, scale * 2)); + } + case 'x3': { + return h('span', { + class: 'mfm-x3', + }, genEl(token.children, scale * 3)); + } + case 'x4': { + return h('span', { + class: 'mfm-x4', + }, genEl(token.children, scale * 4)); + } + case 'font': { + const family = + token.props.args.serif ? 'serif' : + token.props.args.monospace ? 'monospace' : + token.props.args.cursive ? 'cursive' : + token.props.args.fantasy ? 'fantasy' : + token.props.args.emoji ? 'emoji' : + token.props.args.math ? 'math' : + null; + if (family) style = `font-family: ${family};`; + break; + } + case 'blur': { + return h('span', { + class: '_mfm_blur_', + }, genEl(token.children, scale)); + } + case 'rainbow': { + if (!useAnim) { + return h('span', { + class: '_mfm_rainbow_fallback_', + }, genEl(token.children, scale)); + } + const speed = validTime(token.props.args.speed) ?? '1s'; + const delay = validTime(token.props.args.delay) ?? '0s'; + style = `animation: mfm-rainbow ${speed} linear infinite; animation-delay: ${delay};`; + break; + } + case 'sparkle': { + return genEl(token.children, scale); + } + case 'rotate': { + const degrees = safeParseFloat(token.props.args.deg) ?? 90; + style = `transform: rotate(${degrees}deg); transform-origin: center center;`; + break; + } + case 'position': { + const x = safeParseFloat(token.props.args.x) ?? 0; + const y = safeParseFloat(token.props.args.y) ?? 0; + style = `transform: translateX(${x}em) translateY(${y}em);`; + break; + } + case 'scale': { + const x = Math.min(safeParseFloat(token.props.args.x) ?? 1, 5); + const y = Math.min(safeParseFloat(token.props.args.y) ?? 1, 5); + style = `transform: scale(${x}, ${y});`; + scale = scale * Math.max(x, y); + break; + } + case 'fg': { + let color = validColor(token.props.args.color); + color = color ?? 'f00'; + style = `color: #${color}; overflow-wrap: anywhere;`; + break; + } + case 'bg': { + let color = validColor(token.props.args.color); + color = color ?? 'f00'; + style = `background-color: #${color}; overflow-wrap: anywhere;`; + break; + } + case 'border': { + let color = validColor(token.props.args.color); + color = color ? `#${color}` : 'var(--accent)'; + let b_style = token.props.args.style; + if ( + typeof b_style !== 'string' || + !['hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset'] + .includes(b_style) + ) b_style = 'solid'; + const width = safeParseFloat(token.props.args.width) ?? 1; + const radius = safeParseFloat(token.props.args.radius) ?? 0; + style = `border: ${width}px ${b_style} ${color}; border-radius: ${radius}px;${token.props.args.noclip ? '' : ' overflow: clip;'}`; + break; + } + case 'ruby': { + if (token.children.length === 1) { + const child = token.children[0]; + let text = child.type === 'text' ? child.props.text : ''; + if (!disableNyaize && shouldNyaize) { + text = Misskey.nyaize(text); + } + return h('ruby', {}, [text.split(' ')[0], h('rt', text.split(' ')[1])]); + } else { + const rt = token.children.at(-1)!; + let text = rt.type === 'text' ? rt.props.text : ''; + if (!disableNyaize && shouldNyaize) { + text = Misskey.nyaize(text); + } + return h('ruby', {}, [...genEl(token.children.slice(0, token.children.length - 1), scale), h('rt', text.trim())]); + } + } + case 'unixtime': { + const child = token.children[0]; + const unixtime = parseInt(child.type === 'text' ? child.props.text : ''); + return h('span', { + style: 'display: inline-block; font-size: 90%; border: solid 1px var(--divider); border-radius: 999px; padding: 4px 10px 4px 6px;', + }, [ + h('i', { + class: 'ti ti-clock', + style: 'margin-right: 0.25em;', + }), + h(EmTime, { + key: Math.random(), + time: unixtime * 1000, + mode: 'detail', + }), + ]); + } + case 'clickable': { + return h('span', { onClick(ev: MouseEvent): void { + ev.stopPropagation(); + ev.preventDefault(); + const clickEv = typeof token.props.args.ev === 'string' ? token.props.args.ev : ''; + emit('clickEv', clickEv); + } }, genEl(token.children, scale)); + } + } + if (style === undefined) { + return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']); + } else { + return h('span', { + style: 'display: inline-block; ' + style, + }, genEl(token.children, scale)); + } + } + + case 'small': { + return [h('small', { + style: 'opacity: 0.7;', + }, genEl(token.children, scale))]; + } + + case 'center': { + return [h('div', { + style: 'text-align:center;', + }, genEl(token.children, scale))]; + } + + case 'url': { + return [h(EmUrl, { + key: Math.random(), + url: token.props.url, + rel: 'nofollow noopener', + })]; + } + + case 'link': { + return [h(EmLink, { + key: Math.random(), + url: token.props.url, + rel: 'nofollow noopener', + }, genEl(token.children, scale, true))]; + } + + case 'mention': { + return [h(EmMention, { + key: Math.random(), + host: (token.props.host == null && props.author && props.author.host != null ? props.author.host : token.props.host) ?? host, + username: token.props.username, + })]; + } + + case 'hashtag': { + return [h(EmA, { + key: Math.random(), + to: isNote ? `/tags/${encodeURIComponent(token.props.hashtag)}` : `/user-tags/${encodeURIComponent(token.props.hashtag)}`, + style: 'color:var(--hashtag);', + }, `#${token.props.hashtag}`)]; + } + + case 'blockCode': { + return [h('code', { + key: Math.random(), + lang: token.props.lang ?? undefined, + }, token.props.code)]; + } + + case 'inlineCode': { + return [h('code', { + key: Math.random(), + }, token.props.code)]; + } + + case 'quote': { + if (!props.nowrap) { + return [h('div', { + style: QUOTE_STYLE, + }, genEl(token.children, scale, true))]; + } else { + return [h('span', { + style: QUOTE_STYLE, + }, genEl(token.children, scale, true))]; + } + } + + case 'emojiCode': { + if (props.author?.host == null) { + return [h(EmCustomEmoji, { + key: Math.random(), + name: token.props.name, + normal: props.plain, + host: null, + useOriginalSize: scale >= 2.5, + menu: props.enableEmojiMenu, + menuReaction: props.enableEmojiMenuReaction, + fallbackToImage: false, + })]; + } else { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (props.emojiUrls && (props.emojiUrls[token.props.name] == null)) { + return [h('span', `:${token.props.name}:`)]; + } else { + return [h(EmCustomEmoji, { + key: Math.random(), + name: token.props.name, + url: props.emojiUrls && props.emojiUrls[token.props.name], + normal: props.plain, + host: props.author.host, + useOriginalSize: scale >= 2.5, + })]; + } + } + } + + case 'unicodeEmoji': { + return [h(EmEmoji, { + key: Math.random(), + emoji: token.props.emoji, + menu: props.enableEmojiMenu, + menuReaction: props.enableEmojiMenuReaction, + })]; + } + + case 'mathInline': { + return [h('code', token.props.formula)]; + } + + case 'mathBlock': { + return [h('code', token.props.formula)]; + } + + case 'search': { + return [h('div', { + key: Math.random(), + }, token.props.query)]; + } + + case 'plain': { + return [h('span', genEl(token.children, scale, true))]; + } + + default: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + console.error('unrecognized ast type:', (token as any).type); + + return []; + } + } + }).flat(Infinity) as (VNode | string)[]; + + return h('span', { + // https://codeday.me/jp/qa/20190424/690106.html + style: props.nowrap ? 'white-space: pre; word-wrap: normal; overflow: hidden; text-overflow: ellipsis;' : 'white-space: pre-wrap;', + }, genEl(rootAst, props.rootScale ?? 1)); +} diff --git a/packages/frontend-embed/src/components/EmNote.vue b/packages/frontend-embed/src/components/EmNote.vue new file mode 100644 index 0000000000..02475898c5 --- /dev/null +++ b/packages/frontend-embed/src/components/EmNote.vue @@ -0,0 +1,609 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div + v-show="!isDeleted" + ref="rootEl" + :class="[$style.root]" + :tabindex="isDeleted ? '-1' : '0'" +> + <EmNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo"/> + <div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div> + <!--<div v-if="appearNote._prId_" class="tip"><i class="ti ti-speakerphone"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>--> + <!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>--> + <div v-if="isRenote" :class="$style.renote"> + <div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div> + <EmAvatar :class="$style.renoteAvatar" :user="note.user" link/> + <i class="ti ti-repeat" style="margin-right: 4px;"></i> + <I18n :src="i18n.ts.renotedBy" tag="span" :class="$style.renoteText"> + <template #user> + <EmA v-user-preview="true ? undefined : note.userId" :class="$style.renoteUserName" :to="userPage(note.user)"> + <EmUserName :user="note.user"/> + </EmA> + </template> + </I18n> + <div :class="$style.renoteInfo"> + <button ref="renoteTime" :class="$style.renoteTime" class="_button"> + <i class="ti ti-dots" :class="$style.renoteMenu"></i> + <EmTime :time="note.createdAt"/> + </button> + <span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]"> + <i v-if="note.visibility === 'home'" class="ti ti-home"></i> + <i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i> + <i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i> + </span> + <span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span> + <span v-if="note.channel" style="margin-left: 0.5em;" :title="note.channel.name"><i class="ti ti-device-tv"></i></span> + </div> + </div> + <article :class="$style.article"> + <div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div> + <EmAvatar :class="$style.avatar" :user="appearNote.user" link/> + <div :class="$style.main"> + <EmNoteHeader :note="appearNote" :mini="true"/> + <div style="container-type: inline-size;"> + <p v-if="appearNote.cw != null" :class="$style.cw"> + <EmMfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/> + <button style="display: block; width: 100%; margin: 4px 0;" class="_buttonGray _buttonRounded" @click="showContent = !showContent">{{ showContent ? i18n.ts._cw.hide : i18n.ts._cw.show }}</button> + </p> + <div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]"> + <div :class="$style.text"> + <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> + <EmA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></EmA> + <EmMfm + v-if="appearNote.text" + :parsedNodes="parsed" + :text="appearNote.text" + :author="appearNote.user" + :nyaize="'respect'" + :emojiUrls="appearNote.emojis" + :enableEmojiMenu="!true" + :enableEmojiMenuReaction="true" + /> + </div> + <div v-if="appearNote.files && appearNote.files.length > 0"> + <EmMediaList :mediaList="appearNote.files" :originalEntityUrl="`${url}/notes/${appearNote.id}`"/> + </div> + <EmPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :readOnly="true" :class="$style.poll"/> + <div v-if="appearNote.renote" :class="$style.quote"><EmNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div> + <button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false"> + <span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span> + </button> + <button v-else-if="isLong && !collapsed" :class="$style.showLess" class="_button" @click="collapsed = true"> + <span :class="$style.showLessLabel">{{ i18n.ts.showLess }}</span> + </button> + </div> + <EmA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</EmA> + </div> + <EmReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" :note="appearNote" :maxNumber="16"> + <template #more> + <EmA :to="`/notes/${appearNote.id}/reactions`" :class="[$style.reactionOmitted]">{{ i18n.ts.more }}</EmA> + </template> + </EmReactionsViewer> + <footer :class="$style.footer"> + <a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.footerButton, $style.footerButtonLink]" class="_button"> + <i class="ti ti-arrow-back-up"></i> + </a> + <a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.footerButton, $style.footerButtonLink]" class="_button"> + <i class="ti ti-repeat"></i> + </a> + <a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.footerButton, $style.footerButtonLink]" class="_button"> + <i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i> + <i v-else class="ti ti-plus"></i> + </a> + <a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.footerButton, $style.footerButtonLink]" class="_button"> + <i class="ti ti-dots"></i> + </a> + </footer> + </div> + </article> +</div> +</template> + +<script lang="ts" setup> +import { computed, inject, ref, shallowRef } from 'vue'; +import * as mfm from 'mfm-js'; +import * as Misskey from 'misskey-js'; +import I18n from '@/components/I18n.vue'; +import EmNoteSub from '@/components/EmNoteSub.vue'; +import EmNoteHeader from '@/components/EmNoteHeader.vue'; +import EmNoteSimple from '@/components/EmNoteSimple.vue'; +import EmReactionsViewer from '@/components/EmReactionsViewer.vue'; +import EmMediaList from '@/components/EmMediaList.vue'; +import EmPoll from '@/components/EmPoll.vue'; +import EmMfm from '@/components/EmMfm.js'; +import EmA from '@/components/EmA.vue'; +import EmAvatar from '@/components/EmAvatar.vue'; +import EmUserName from '@/components/EmUserName.vue'; +import EmTime from '@/components/EmTime.vue'; +import { userPage } from '@/utils.js'; +import { i18n } from '@/i18n.js'; +import { shouldCollapsed } from '@@/js/collapsed.js'; +import { url } from '@@/js/config.js'; + +function getAppearNote(note: Misskey.entities.Note) { + return Misskey.note.isPureRenote(note) ? note.renote : note; +} + +const props = withDefaults(defineProps<{ + note: Misskey.entities.Note; + pinned?: boolean; +}>(), { +}); + +const emit = defineEmits<{ + (ev: 'reaction', emoji: string): void; + (ev: 'removeReaction', emoji: string): void; +}>(); + +const inChannel = inject('inChannel', null); + +const note = ref((props.note)); + +const isRenote = Misskey.note.isPureRenote(note.value); + +const rootEl = shallowRef<HTMLElement>(); +const renoteTime = shallowRef<HTMLElement>(); +const appearNote = computed(() => getAppearNote(note.value)); +const showContent = ref(false); +const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null); +const isLong = shouldCollapsed(appearNote.value, []); +const collapsed = ref(appearNote.value.cw == null && isLong); +const isDeleted = ref(false); +</script> + +<style lang="scss" module> +.root { + position: relative; + transition: box-shadow 0.1s ease; + font-size: 1.05em; + overflow: clip; + contain: content; + + // これらの指定はパフォーマンス向上には有効だが、ノートの高さは一定でないため、 + // 下の方までスクロールすると上のノートの高さがここで決め打ちされたものに変化し、表示しているノートの位置が変わってしまう + // ノートがマウントされたときに自身の高さを取得し contain-intrinsic-size を設定しなおせばほぼ解決できそうだが、 + // 今度はその処理自体がパフォーマンス低下の原因にならないか懸念される。また、被リアクションでも高さは変化するため、やはり多少のズレは生じる + // 一度レンダリングされた要素はブラウザがよしなにサイズを覚えておいてくれるような実装になるまで待った方が良さそう(なるのか?) + //content-visibility: auto; + //contain-intrinsic-size: 0 128px; + + &:focus-visible { + outline: none; + + &::after { + content: ""; + pointer-events: none; + display: block; + position: absolute; + z-index: 10; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + width: calc(100% - 8px); + height: calc(100% - 8px); + border: dashed 2px var(--focus); + border-radius: var(--radius); + box-sizing: border-box; + } + } + + .footer { + position: relative; + z-index: 1; + } + + &:hover > .article > .main > .footer > .footerButton { + opacity: 1; + } + + &.showActionsOnlyHover { + .footer { + visibility: hidden; + position: absolute; + top: 12px; + right: 12px; + padding: 0 4px; + margin-bottom: 0 !important; + background: var(--popup); + border-radius: 8px; + box-shadow: 0px 4px 32px var(--shadow); + } + + .footerButton { + font-size: 90%; + + &:not(:last-child) { + margin-right: 0; + } + } + } + + &.showActionsOnlyHover:hover { + .footer { + visibility: visible; + } + } +} + +.tip { + display: flex; + align-items: center; + padding: 16px 32px 8px 32px; + line-height: 24px; + font-size: 90%; + white-space: pre; + color: #d28a3f; +} + +.tip + .article { + padding-top: 8px; +} + +.replyTo { + opacity: 0.7; + padding-bottom: 0; +} + +.renote { + position: relative; + display: flex; + align-items: center; + padding: 16px 32px 8px 32px; + line-height: 28px; + white-space: pre; + color: var(--renote); + + & + .article { + padding-top: 8px; + } + + > .colorBar { + height: calc(100% - 6px); + } +} + +.renoteAvatar { + flex-shrink: 0; + display: inline-block; + width: 28px; + height: 28px; + margin: 0 8px 0 0; +} + +.renoteText { + overflow: hidden; + flex-shrink: 1; + text-overflow: ellipsis; + white-space: nowrap; +} + +.renoteUserName { + font-weight: bold; +} + +.renoteInfo { + margin-left: auto; + font-size: 0.9em; +} + +.renoteTime { + flex-shrink: 0; + color: inherit; +} + +.renoteMenu { + margin-right: 4px; +} + +.collapsedRenoteTarget { + display: flex; + align-items: center; + line-height: 28px; + white-space: pre; + padding: 0 32px 18px; +} + +.collapsedRenoteTargetAvatar { + flex-shrink: 0; + display: inline-block; + width: 28px; + height: 28px; + margin: 0 8px 0 0; +} + +.collapsedRenoteTargetText { + overflow: hidden; + flex-shrink: 1; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 90%; + opacity: 0.7; + cursor: pointer; + + &:hover { + text-decoration: underline; + } +} + +.article { + position: relative; + display: flex; + padding: 28px 32px; +} + +.colorBar { + position: absolute; + top: 8px; + left: 8px; + width: 5px; + height: calc(100% - 16px); + border-radius: 999px; + pointer-events: none; +} + +.avatar { + flex-shrink: 0; + display: block !important; + margin: 0 14px 0 0; + width: 58px; + height: 58px; + position: sticky !important; + top: calc(22px + var(--stickyTop, 0px)); + left: 0; +} + +.main { + flex: 1; + min-width: 0; +} + +.cw { + cursor: default; + display: block; + margin: 0; + padding: 0; + overflow-wrap: break-word; +} + +.showLess { + width: 100%; + margin-top: 14px; + position: sticky; + bottom: calc(var(--stickyBottom, 0px) + 14px); +} + +.showLessLabel { + display: inline-block; + background: var(--popup); + padding: 6px 10px; + font-size: 0.8em; + border-radius: 999px; + box-shadow: 0 2px 6px rgb(0 0 0 / 20%); +} + +.contentCollapsed { + position: relative; + max-height: 9em; + overflow: clip; +} + +.collapsed { + display: block; + position: absolute; + bottom: 0; + left: 0; + z-index: 2; + width: 100%; + height: 64px; + background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + + &:hover > .collapsedLabel { + background: var(--panelHighlight); + } +} + +.collapsedLabel { + display: inline-block; + background: var(--panel); + padding: 6px 10px; + font-size: 0.8em; + border-radius: 999px; + box-shadow: 0 2px 6px rgb(0 0 0 / 20%); +} + +.text { + overflow-wrap: break-word; +} + +.replyIcon { + color: var(--accent); + margin-right: 0.5em; +} + +.translation { + border: solid 0.5px var(--divider); + border-radius: var(--radius); + padding: 12px; + margin-top: 8px; +} + +.urlPreview { + margin-top: 8px; +} + +.poll { + font-size: 80%; +} + +.quote { + padding: 8px 0; +} + +.quoteNote { + padding: 16px; + border: dashed 1px var(--renote); + border-radius: 8px; + overflow: clip; +} + +.channel { + opacity: 0.7; + font-size: 80%; +} + +.footer { + margin-bottom: -14px; +} + +.footerButton { + margin: 0; + padding: 8px; + opacity: 0.7; + + &:not(:last-child) { + margin-right: 28px; + } + + &:hover { + color: var(--fgHighlighted); + } +} + +.footerButtonLink:hover, +.footerButtonLink:focus, +.footerButtonLink:active { + text-decoration: none; +} + +.footerButtonCount { + display: inline; + margin: 0 0 0 8px; + opacity: 0.7; +} + +@container (max-width: 580px) { + .root { + font-size: 0.95em; + } + + .renote { + padding: 12px 26px 0 26px; + } + + .article { + padding: 24px 26px; + } + + .avatar { + width: 50px; + height: 50px; + } +} + +@container (max-width: 500px) { + .root { + font-size: 0.9em; + } + + .renote { + padding: 10px 22px 0 22px; + } + + .article { + padding: 20px 22px; + } + + .footer { + margin-bottom: -8px; + } +} + +@container (max-width: 480px) { + .renote { + padding: 8px 16px 0 16px; + } + + .tip { + padding: 8px 16px 0 16px; + } + + .collapsedRenoteTarget { + padding: 0 16px 9px; + margin-top: 4px; + } + + .article { + padding: 14px 16px; + } +} + +@container (max-width: 450px) { + .avatar { + margin: 0 10px 0 0; + width: 46px; + height: 46px; + top: calc(14px + var(--stickyTop, 0px)); + } +} + +@container (max-width: 400px) { + .root:not(.showActionsOnlyHover) { + .footerButton { + &:not(:last-child) { + margin-right: 18px; + } + } + } +} + +@container (max-width: 350px) { + .root:not(.showActionsOnlyHover) { + .footerButton { + &:not(:last-child) { + margin-right: 12px; + } + } + } + + .colorBar { + top: 6px; + left: 6px; + width: 4px; + height: calc(100% - 12px); + } +} + +@container (max-width: 300px) { + .avatar { + width: 44px; + height: 44px; + } + + .root:not(.showActionsOnlyHover) { + .footerButton { + &:not(:last-child) { + margin-right: 8px; + } + } + } +} + +@container (max-width: 250px) { + .quoteNote { + padding: 12px; + } +} + +.reactionOmitted { + display: inline-block; + margin-left: 8px; + opacity: .8; + font-size: 95%; +} +</style> diff --git a/packages/frontend-embed/src/components/EmNoteDetailed.vue b/packages/frontend-embed/src/components/EmNoteDetailed.vue new file mode 100644 index 0000000000..a233011af7 --- /dev/null +++ b/packages/frontend-embed/src/components/EmNoteDetailed.vue @@ -0,0 +1,488 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div + v-show="!isDeleted" + ref="rootEl" + :class="$style.root" +> + <EmNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo"/> + <div v-if="isRenote" :class="$style.renote"> + <EmAvatar :class="$style.renoteAvatar" :user="note.user" link/> + <i class="ti ti-repeat" style="margin-right: 4px;"></i> + <span :class="$style.renoteText"> + <I18n :src="i18n.ts.renotedBy" tag="span"> + <template #user> + <EmA :class="$style.renoteName" :to="userPage(note.user)"> + <EmUserName :user="note.user"/> + </EmA> + </template> + </I18n> + </span> + <div :class="$style.renoteInfo"> + <div class="$style.renoteTime"> + <EmTime :time="note.createdAt"/> + </div> + <span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]"> + <i v-if="note.visibility === 'home'" class="ti ti-home"></i> + <i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i> + <i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i> + </span> + <span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span> + </div> + </div> + <article :class="$style.note"> + <header :class="$style.noteHeader"> + <EmAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link/> + <div :class="$style.noteHeaderBody"> + <div :class="$style.noteHeaderBodyUpper"> + <div style="min-width: 0;"> + <div class="_nowrap"> + <EmA :class="$style.noteHeaderName" :to="userPage(appearNote.user)"> + <EmUserName :nowrap="true" :user="appearNote.user"/> + </EmA> + <span v-if="appearNote.user.isBot" :class="$style.isBot">bot</span> + </div> + <div :class="$style.noteHeaderUsername"><EmAcct :user="appearNote.user"/></div> + </div> + <div :class="$style.noteHeaderInfo"> + <a :href="url" :class="$style.noteHeaderInstanceIconLink" target="_blank" rel="noopener noreferrer"> + <img :src="serverMetadata.iconUrl || '/favicon.ico'" alt="" :class="$style.noteHeaderInstanceIcon"/> + </a> + </div> + </div> + </div> + </header> + <div :class="[$style.noteContent, { [$style.contentCollapsed]: collapsed }]"> + <p v-if="appearNote.cw != null" :class="$style.cw"> + <EmMfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/> + <button style="display: block; width: 100%; margin: 4px 0;" class="_buttonGray _buttonRounded" @click="showContent = !showContent">{{ showContent ? i18n.ts._cw.hide : i18n.ts._cw.show }}</button> + </p> + <div v-show="appearNote.cw == null || showContent"> + <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> + <EmA v-if="appearNote.replyId" :class="$style.noteReplyTarget" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></EmA> + <EmMfm + v-if="appearNote.text" + :parsedNodes="parsed" + :text="appearNote.text" + :author="appearNote.user" + :nyaize="'respect'" + :emojiUrls="appearNote.emojis" + /> + <a v-if="appearNote.renote != null" :class="$style.rn">RN:</a> + <div v-if="appearNote.files && appearNote.files.length > 0"> + <EmMediaList :mediaList="appearNote.files" :originalEntityUrl="`${url}/notes/${appearNote.id}`"/> + </div> + <EmPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :readOnly="true" :class="$style.poll"/> + <div v-if="appearNote.renote" :class="$style.quote"><EmNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div> + <button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false"> + <span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span> + </button> + <button v-else-if="isLong && !collapsed" :class="$style.showLess" class="_button" @click="collapsed = true"> + <span :class="$style.showLessLabel">{{ i18n.ts.showLess }}</span> + </button> + </div> + <EmA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</EmA> + </div> + <footer> + <div :class="$style.noteFooterInfo"> + <span v-if="appearNote.visibility !== 'public'" style="display: inline-block; margin-right: 0.5em;" :title="i18n.ts._visibility[appearNote.visibility]"> + <i v-if="appearNote.visibility === 'home'" class="ti ti-home"></i> + <i v-else-if="appearNote.visibility === 'followers'" class="ti ti-lock"></i> + <i v-else-if="appearNote.visibility === 'specified'" ref="specified" class="ti ti-mail"></i> + </span> + <span v-if="appearNote.localOnly" style="display: inline-block; margin-right: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span> + <EmA :to="notePage(appearNote)"> + <EmTime :time="appearNote.createdAt" mode="detail" colored/> + </EmA> + </div> + <EmReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" ref="reactionsViewer" :maxNumber="16" :note="appearNote"> + <template #more> + <EmA :to="`/notes/${appearNote.id}`" :class="[$style.reactionOmitted]">{{ i18n.ts.more }}</EmA> + </template> + </EmReactionsViewer> + <a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.noteFooterButton, $style.footerButtonLink]" class="_button"> + <i class="ti ti-arrow-back-up"></i> + </a> + <a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.noteFooterButton, $style.footerButtonLink]" class="_button"> + <i class="ti ti-repeat"></i> + <p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ (appearNote.renoteCount) }}</p> + </a> + <a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.noteFooterButton, $style.footerButtonLink]" class="_button"> + <i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i> + <i v-else class="ti ti-plus"></i> + <p v-if="(appearNote.reactionAcceptance === 'likeOnly') && appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ (appearNote.reactionCount) }}</p> + </a> + <a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.noteFooterButton, $style.footerButtonLink]" class="_button"> + <i class="ti ti-dots"></i> + </a> + </footer> + </article> +</div> +</template> + +<script lang="ts" setup> +import { computed, inject, ref } from 'vue'; +import * as mfm from 'mfm-js'; +import * as Misskey from 'misskey-js'; +import I18n from '@/components/I18n.vue'; +import EmMediaList from '@/components/EmMediaList.vue'; +import EmNoteSub from '@/components/EmNoteSub.vue'; +import EmNoteSimple from '@/components/EmNoteSimple.vue'; +import EmReactionsViewer from '@/components/EmReactionsViewer.vue'; +import EmPoll from '@/components/EmPoll.vue'; +import EmA from '@/components/EmA.vue'; +import EmAvatar from '@/components/EmAvatar.vue'; +import EmTime from '@/components/EmTime.vue'; +import EmUserName from '@/components/EmUserName.vue'; +import EmAcct from '@/components/EmAcct.vue'; +import { userPage } from '@/utils.js'; +import { notePage } from '@/utils.js'; +import { i18n } from '@/i18n.js'; +import { DI } from '@/di.js'; +import { shouldCollapsed } from '@@/js/collapsed.js'; +import { url } from '@@/js/config.js'; +import EmMfm from '@/components/EmMfm.js'; + +const props = defineProps<{ + note: Misskey.entities.Note; +}>(); + +const serverMetadata = inject(DI.serverMetadata)!; + +const inChannel = inject('inChannel', null); + +const note = ref(props.note); + +const isRenote = ( + note.value.renote != null && + note.value.reply == null && + note.value.text == null && + note.value.cw == null && + note.value.fileIds && note.value.fileIds.length === 0 && + note.value.poll == null +); + +const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value); +const showContent = ref(false); +const isDeleted = ref(false); +const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null; +const isLong = shouldCollapsed(appearNote.value, []); +const collapsed = ref(appearNote.value.cw == null && isLong); +</script> + +<style lang="scss" module> +.root { + position: relative; + transition: box-shadow 0.1s ease; + overflow: clip; + contain: content; +} + +.replyTo { + opacity: 0.7; + padding-bottom: 0; +} + +.renote { + display: flex; + align-items: center; + padding: 16px 32px 8px 32px; + line-height: 28px; + white-space: pre; + color: var(--renote); +} + +.renoteAvatar { + flex-shrink: 0; + display: inline-block; + width: 28px; + height: 28px; + margin: 0 8px 0 0; + border-radius: 6px; +} + +.renoteText { + overflow: hidden; + flex-shrink: 1; + text-overflow: ellipsis; + white-space: nowrap; +} + +.renoteName { + font-weight: bold; +} + +.renoteInfo { + margin-left: auto; + font-size: 0.9em; +} + +.renoteTime { + flex-shrink: 0; + color: inherit; +} + +.renote + .note { + padding-top: 8px; +} + +.note { + padding: 24px 32px 16px; + font-size: 1.2em; + + &:hover > .main > .footer > .button { + opacity: 1; + } +} + +.noteHeader { + display: flex; + position: relative; + margin-bottom: 16px; + align-items: center; +} + +.noteHeaderAvatar { + display: block; + flex-shrink: 0; + width: 50px; + height: 50px; +} + +.noteHeaderBody { + flex: 1; + display: flex; + min-width: 0; + flex-direction: column; + justify-content: center; + padding-left: 16px; + font-size: 0.95em; +} + +.noteHeaderBodyUpper { + display: flex; + min-width: 0; +} + +.noteHeaderName { + font-weight: bold; + line-height: 1.3; +} + +.isBot { + display: inline-block; + margin: 0 0.5em; + padding: 4px 6px; + font-size: 80%; + line-height: 1; + border: solid 0.5px var(--divider); + border-radius: 4px; +} + +.noteHeaderInfo { + margin-left: auto; + display: flex; + gap: 0.5em; + align-items: center; +} + +.noteHeaderInstanceIconLink { + display: inline-block; + margin-left: 4px; +} + +.noteHeaderInstanceIcon { + width: 32px; + height: 32px; + border-radius: 4px; +} + +.noteHeaderUsername { + margin-bottom: 2px; + line-height: 1.3; + word-wrap: anywhere; +} + +.noteContent { + container-type: inline-size; + overflow-wrap: break-word; +} + +.cw { + cursor: default; + display: block; + margin: 0; + padding: 0; + overflow-wrap: break-word; +} + +.noteReplyTarget { + color: var(--accent); + margin-right: 0.5em; +} + +.rn { + margin-left: 4px; + font-style: oblique; + color: var(--renote); +} + +.reactionOmitted { + display: inline-block; + margin-left: 8px; + opacity: .8; + font-size: 95%; +} + +.poll { + font-size: 80%; +} + +.quote { + padding: 8px 0; +} + +.quoteNote { + padding: 16px; + border: dashed 1px var(--renote); + border-radius: 8px; + overflow: clip; +} + +.channel { + opacity: 0.7; + font-size: 80%; +} + +.showLess { + width: 100%; + margin-top: 14px; + position: sticky; + bottom: calc(var(--stickyBottom, 0px) + 14px); +} + +.showLessLabel { + display: inline-block; + background: var(--popup); + padding: 6px 10px; + font-size: 0.8em; + border-radius: 999px; + box-shadow: 0 2px 6px rgb(0 0 0 / 20%); +} + +.contentCollapsed { + position: relative; + max-height: 9em; + overflow: clip; +} + +.collapsed { + display: block; + position: absolute; + bottom: 0; + left: 0; + z-index: 2; + width: 100%; + height: 64px; + background: linear-gradient(0deg, var(--panel), var(--X15)); + + &:hover > .collapsedLabel { + background: var(--panelHighlight); + } +} + +.collapsedLabel { + display: inline-block; + background: var(--panel); + padding: 6px 10px; + font-size: 0.8em; + border-radius: 999px; + box-shadow: 0 2px 6px rgb(0 0 0 / 20%); +} + +.noteFooterInfo { + margin: 16px 0; + opacity: 0.7; + font-size: 0.9em; +} + +.noteFooterButton { + margin: 0; + padding: 8px; + opacity: 0.7; + + &:not(:last-child) { + margin-right: 28px; + } + + &:hover { + color: var(--fgHighlighted); + } +} + +.footerButtonLink:hover, +.footerButtonLink:focus, +.footerButtonLink:active { + text-decoration: none; +} + +.noteFooterButtonCount { + display: inline; + margin: 0 0 0 8px; + opacity: 0.7; + + &.reacted { + color: var(--accent); + } +} + +@container (max-width: 500px) { + .root { + font-size: 0.9em; + } +} + +@container (max-width: 450px) { + .renote { + padding: 8px 16px 0 16px; + } + + .note { + padding: 16px; + } + + .noteHeaderAvatar { + width: 50px; + height: 50px; + } +} + +@container (max-width: 350px) { + .noteFooterButton { + &:not(:last-child) { + margin-right: 18px; + } + } +} + +@container (max-width: 300px) { + .root { + font-size: 0.825em; + } + + .noteHeaderAvatar { + width: 50px; + height: 50px; + } + + .noteFooterButton { + &:not(:last-child) { + margin-right: 12px; + } + } +} +</style> diff --git a/packages/frontend-embed/src/components/EmNoteHeader.vue b/packages/frontend-embed/src/components/EmNoteHeader.vue new file mode 100644 index 0000000000..e4add9501f --- /dev/null +++ b/packages/frontend-embed/src/components/EmNoteHeader.vue @@ -0,0 +1,104 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<header :class="$style.root"> + <EmA :class="$style.name" :to="userPage(note.user)"> + <EmUserName :user="note.user"/> + </EmA> + <div v-if="note.user.isBot" :class="$style.isBot">bot</div> + <div :class="$style.username"><EmAcct :user="note.user"/></div> + <div v-if="note.user.badgeRoles" :class="$style.badgeRoles"> + <img v-for="(role, i) in note.user.badgeRoles" :key="i" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl!"/> + </div> + <div :class="$style.info"> + <EmA :to="notePage(note)"> + <EmTime :time="note.createdAt" colored/> + </EmA> + <span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;"> + <i v-if="note.visibility === 'home'" class="ti ti-home"></i> + <i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i> + <i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i> + </span> + <span v-if="note.localOnly" style="margin-left: 0.5em;"><i class="ti ti-rocket-off"></i></span> + <span v-if="note.channel" style="margin-left: 0.5em;" :title="note.channel.name"><i class="ti ti-device-tv"></i></span> + </div> +</header> +</template> + +<script lang="ts" setup> +import { } from 'vue'; +import * as Misskey from 'misskey-js'; +import { notePage } from '@/utils.js'; +import { userPage } from '@/utils.js'; +import EmA from '@/components/EmA.vue'; +import EmUserName from '@/components/EmUserName.vue'; +import EmAcct from '@/components/EmAcct.vue'; +import EmTime from '@/components/EmTime.vue'; + +defineProps<{ + note: Misskey.entities.Note; +}>(); +</script> + +<style lang="scss" module> +.root { + display: flex; + align-items: baseline; + white-space: nowrap; +} + +.name { + flex-shrink: 1; + display: block; + margin: 0 .5em 0 0; + padding: 0; + overflow: hidden; + font-size: 1em; + font-weight: bold; + text-decoration: none; + text-overflow: ellipsis; + + &:hover { + text-decoration: underline; + } +} + +.isBot { + flex-shrink: 0; + align-self: center; + margin: 0 .5em 0 0; + padding: 1px 6px; + font-size: 80%; + border: solid 0.5px var(--divider); + border-radius: 3px; +} + +.username { + flex-shrink: 9999999; + margin: 0 .5em 0 0; + overflow: hidden; + text-overflow: ellipsis; +} + +.info { + flex-shrink: 0; + margin-left: auto; + font-size: 0.9em; +} + +.badgeRoles { + margin: 0 .5em 0 0; +} + +.badgeRole { + height: 1.3em; + vertical-align: -20%; + + & + .badgeRole { + margin-left: 0.2em; + } +} +</style> diff --git a/packages/frontend-embed/src/components/EmNoteSimple.vue b/packages/frontend-embed/src/components/EmNoteSimple.vue new file mode 100644 index 0000000000..704a876e59 --- /dev/null +++ b/packages/frontend-embed/src/components/EmNoteSimple.vue @@ -0,0 +1,106 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="$style.root"> + <EmAvatar :class="$style.avatar" :user="note.user" link preview/> + <div :class="$style.main"> + <EmNoteHeader :class="$style.header" :note="note" :mini="true"/> + <div> + <p v-if="note.cw != null" :class="$style.cw"> + <EmMfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis"/> + <button style="display: block; width: 100%;" class="_buttonGray _buttonRounded" @click="showContent = !showContent">{{ showContent ? i18n.ts._cw.hide : i18n.ts._cw.show }}</button> + </p> + <div v-show="note.cw == null || showContent"> + <EmSubNoteContent :class="$style.text" :note="note"/> + </div> + </div> + </div> +</div> +</template> + +<script lang="ts" setup> +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; +import { i18n } from '@/i18n.js'; +import EmAvatar from '@/components/EmAvatar.vue'; +import EmNoteHeader from '@/components/EmNoteHeader.vue'; +import EmSubNoteContent from '@/components/EmSubNoteContent.vue'; +import EmMfm from '@/components/EmMfm.js'; + +const props = defineProps<{ + note: Misskey.entities.Note; +}>(); + +const showContent = ref(false); +</script> + +<style lang="scss" module> +.root { + display: flex; + margin: 0; + padding: 0; + font-size: 0.95em; +} + +.avatar { + flex-shrink: 0; + display: block; + margin: 0 10px 0 0; + width: 34px; + height: 34px; + border-radius: 8px; + position: sticky !important; + top: calc(16px + var(--stickyTop, 0px)); + left: 0; +} + +.main { + flex: 1; + min-width: 0; +} + +.header { + margin-bottom: 2px; +} + +.cw { + cursor: default; + display: block; + margin: 0; + padding: 0; + overflow-wrap: break-word; +} + +.text { + cursor: default; + margin: 0; + padding: 0; +} + +@container (min-width: 250px) { + .avatar { + margin: 0 10px 0 0; + width: 40px; + height: 40px; + } +} + +@container (min-width: 350px) { + .avatar { + margin: 0 10px 0 0; + width: 44px; + height: 44px; + } +} + +@container (min-width: 500px) { + .avatar { + margin: 0 12px 0 0; + width: 48px; + height: 48px; + } +} +</style> diff --git a/packages/frontend-embed/src/components/EmNoteSub.vue b/packages/frontend-embed/src/components/EmNoteSub.vue new file mode 100644 index 0000000000..f60aea3e7e --- /dev/null +++ b/packages/frontend-embed/src/components/EmNoteSub.vue @@ -0,0 +1,151 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="[$style.root, { [$style.children]: depth > 1 }]"> + <div :class="$style.main"> + <div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div> + <EmAvatar :class="$style.avatar" :user="note.user" link preview/> + <div :class="$style.body"> + <EmNoteHeader :class="$style.header" :note="note" :mini="true"/> + <div> + <p v-if="note.cw != null" :class="$style.cw"> + <EmMfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'respect'"/> + <button style="display: block; width: 100%;" class="_buttonGray _buttonRounded" @click="showContent = !showContent">{{ showContent ? i18n.ts._cw.hide : i18n.ts._cw.show }}</button> + </p> + <div v-show="note.cw == null || showContent"> + <EmSubNoteContent :class="$style.text" :note="note"/> + </div> + </div> + </div> + </div> + <template v-if="depth < 5"> + <EmNoteSub v-for="reply in replies" :key="reply.id" :note="reply" :class="$style.reply" :detail="true" :depth="depth + 1"/> + </template> + <div v-else :class="$style.more"> + <EmA class="_link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ti ti-chevron-double-right"></i></EmA> + </div> +</div> +</template> + +<script lang="ts" setup> +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; +import EmA from '@/components/EmA.vue'; +import EmAvatar from '@/components/EmAvatar.vue'; +import EmNoteHeader from '@/components/EmNoteHeader.vue'; +import EmSubNoteContent from '@/components/EmSubNoteContent.vue'; +import { notePage } from '@/utils.js'; +import { misskeyApi } from '@/misskey-api.js'; +import { i18n } from '@/i18n.js'; +import EmMfm from '@/components/EmMfm.js'; + +const props = withDefaults(defineProps<{ + note: Misskey.entities.Note; + detail?: boolean; + + // how many notes are in between this one and the note being viewed in detail + depth?: number; +}>(), { + depth: 1, +}); + +const showContent = ref(false); +const replies = ref<Misskey.entities.Note[]>([]); + +if (props.detail) { + misskeyApi('notes/children', { + noteId: props.note.id, + limit: 5, + }).then(res => { + replies.value = res; + }); +} +</script> + +<style lang="scss" module> +.root { + padding: 16px 32px; + font-size: 0.9em; + position: relative; + + &.children { + padding: 10px 0 0 16px; + font-size: 1em; + } +} + +.main { + display: flex; +} + +.colorBar { + position: absolute; + top: 8px; + left: 8px; + width: 5px; + height: calc(100% - 8px); + border-radius: 999px; + pointer-events: none; +} + +.avatar { + flex-shrink: 0; + display: block; + margin: 0 8px 0 0; + width: 38px; + height: 38px; + border-radius: 8px; +} + +.body { + flex: 1; + min-width: 0; +} + +.header { + margin-bottom: 2px; +} + +.cw { + cursor: default; + display: block; + margin: 0; + padding: 0; + overflow-wrap: break-word; +} + +.text { + margin: 0; + padding: 0; +} + +.reply, .more { + border-left: solid 0.5px var(--divider); + margin-top: 10px; +} + +.more { + padding: 10px 0 0 16px; +} + +@container (max-width: 450px) { + .root { + padding: 14px 16px; + + &.children { + padding: 10px 0 0 8px; + } + } +} + +.muted { + text-align: center; + padding: 8px !important; + border: 1px solid var(--divider); + margin: 8px 8px 0 8px; + border-radius: 8px; +} +</style> diff --git a/packages/frontend-embed/src/components/EmNotes.vue b/packages/frontend-embed/src/components/EmNotes.vue new file mode 100644 index 0000000000..3418d97f77 --- /dev/null +++ b/packages/frontend-embed/src/components/EmNotes.vue @@ -0,0 +1,52 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<EmPagination ref="pagingComponent" :pagination="pagination" :disableAutoLoad="disableAutoLoad"> + <template #empty> + <div class="_fullinfo"> + <div>{{ i18n.ts.noNotes }}</div> + </div> + </template> + + <template #default="{ items: notes }"> + <div :class="[$style.root]"> + <EmNote v-for="note in notes" :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note"/> + </div> + </template> +</EmPagination> +</template> + +<script lang="ts" setup> +import { useTemplateRef } from 'vue'; +import EmNote from '@/components/EmNote.vue'; +import EmPagination, { Paging } from '@/components/EmPagination.vue'; +import { i18n } from '@/i18n.js'; + +withDefaults(defineProps<{ + pagination: Paging; + noGap?: boolean; + disableAutoLoad?: boolean; + ad?: boolean; +}>(), { + ad: true, +}); + +const pagingComponent = useTemplateRef('pagingComponent'); + +defineExpose({ + pagingComponent, +}); +</script> + +<style lang="scss" module> +.root { + background: var(--panel); +} + +.note { + border-bottom: 0.5px solid var(--divider); +} +</style> diff --git a/packages/frontend-embed/src/components/EmPagination.vue b/packages/frontend-embed/src/components/EmPagination.vue new file mode 100644 index 0000000000..5d5317a912 --- /dev/null +++ b/packages/frontend-embed/src/components/EmPagination.vue @@ -0,0 +1,504 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<EmLoading v-if="fetching"/> + +<EmError v-else-if="error" @retry="init()"/> + +<div v-else-if="empty" key="_empty_" class="empty"> + <slot name="empty"> + <div class="_fullinfo"> + <div>{{ i18n.ts.nothing }}</div> + </div> + </slot> +</div> + +<div v-else ref="rootEl"> + <div v-show="pagination.reversed && more" key="_more_" class="_margin"> + <button v-if="!moreFetching" class="_buttonPrimary" :class="$style.more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMoreAhead"> + {{ i18n.ts.loadMore }} + </button> + <EmLoading v-else class="loading"/> + </div> + <slot :items="Array.from(items.values())" :fetching="fetching || moreFetching"></slot> + <div v-show="!pagination.reversed && more" key="_more_" class="_margin"> + <button v-if="!moreFetching" class="_buttonRounded _buttonPrimary" :class="$style.more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> + {{ i18n.ts.loadMore }} + </button> + <EmLoading v-else class="loading"/> + </div> +</div> +</template> + +<script lang="ts"> +import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue'; +import * as Misskey from 'misskey-js'; +import { useDocumentVisibility } from '@@/js/use-document-visibility.js'; +import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@@/js/scroll.js'; +import { misskeyApi } from '@/misskey-api.js'; +import { i18n } from '@/i18n.js'; + +const SECOND_FETCH_LIMIT = 30; +const TOLERANCE = 16; +const APPEAR_MINIMUM_INTERVAL = 600; + +export type Paging<E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints> = { + endpoint: E; + limit: number; + params?: Misskey.Endpoints[E]['req'] | ComputedRef<Misskey.Endpoints[E]['req']>; + + /** + * 検索APIのような、ページング不可なエンドポイントを利用する場合 + * (そのようなAPIをこの関数で使うのは若干矛盾してるけど) + */ + noPaging?: boolean; + + /** + * items 配列の中身を逆順にする(新しい方が最後) + */ + reversed?: boolean; + + offsetMode?: boolean; + + pageEl?: HTMLElement; +}; + +type MisskeyEntity = { + id: string; + createdAt: string; + _shouldInsertAd_?: boolean; + [x: string]: any; +}; + +type MisskeyEntityMap = Map<string, MisskeyEntity>; + +function arrayToEntries(entities: MisskeyEntity[]): [string, MisskeyEntity][] { + return entities.map(en => [en.id, en]); +} + +function concatMapWithArray(map: MisskeyEntityMap, entities: MisskeyEntity[]): MisskeyEntityMap { + return new Map([...map, ...arrayToEntries(entities)]); +} + +</script> +<script lang="ts" setup> +import EmError from '@/components/EmError.vue'; +import EmLoading from '@/components/EmLoading.vue'; + +const props = withDefaults(defineProps<{ + pagination: Paging; + disableAutoLoad?: boolean; + displayLimit?: number; +}>(), { + displayLimit: 20, +}); + +const emit = defineEmits<{ + (ev: 'queue', count: number): void; + (ev: 'status', error: boolean): void; +}>(); + +const rootEl = shallowRef<HTMLElement>(); + +// 遡り中かどうか +const backed = ref(false); + +const scrollRemove = ref<(() => void) | null>(null); + +/** + * 表示するアイテムのソース + * 最新が0番目 + */ +const items = ref<MisskeyEntityMap>(new Map()); + +/** + * タブが非アクティブなどの場合に更新を貯めておく + * 最新が0番目 + */ +const queue = ref<MisskeyEntityMap>(new Map()); + +const offset = ref(0); + +/** + * 初期化中かどうか(trueならEmLoadingで全て隠す) + */ +const fetching = ref(true); + +const moreFetching = ref(false); +const more = ref(false); +const preventAppearFetchMore = ref(false); +const preventAppearFetchMoreTimer = ref<number | null>(null); +const isBackTop = ref(false); +const empty = computed(() => items.value.size === 0); +const error = ref(false); + +const contentEl = computed(() => props.pagination.pageEl ?? rootEl.value); +const scrollableElement = computed(() => contentEl.value ? getScrollContainer(contentEl.value) : document.body); + +const visibility = useDocumentVisibility(); + +let isPausingUpdate = false; +let timerForSetPause: number | null = null; +const BACKGROUND_PAUSE_WAIT_SEC = 10; + +// 先頭が表示されているかどうかを検出 +// https://qiita.com/mkataigi/items/0154aefd2223ce23398e +const scrollObserver = ref<IntersectionObserver>(); + +watch([() => props.pagination.reversed, scrollableElement], () => { + if (scrollObserver.value) scrollObserver.value.disconnect(); + + scrollObserver.value = new IntersectionObserver(entries => { + backed.value = entries[0].isIntersecting; + }, { + root: scrollableElement.value, + rootMargin: props.pagination.reversed ? '-100% 0px 100% 0px' : '100% 0px -100% 0px', + threshold: 0.01, + }); +}, { immediate: true }); + +watch(rootEl, () => { + scrollObserver.value?.disconnect(); + nextTick(() => { + if (rootEl.value) scrollObserver.value?.observe(rootEl.value); + }); +}); + +watch([backed, contentEl], () => { + if (!backed.value) { + if (!contentEl.value) return; + + scrollRemove.value = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl.value, executeQueue, TOLERANCE); + } else { + if (scrollRemove.value) scrollRemove.value(); + scrollRemove.value = null; + } +}); + +// パラメータに何らかの変更があった際、再読込したい(チャンネル等のIDが変わったなど) +watch(() => [props.pagination.endpoint, props.pagination.params], init, { deep: true }); + +watch(queue, (a, b) => { + if (a.size === 0 && b.size === 0) return; + emit('queue', queue.value.size); +}, { deep: true }); + +watch(error, (n, o) => { + if (n === o) return; + emit('status', n); +}); + +async function init(): Promise<void> { + items.value = new Map(); + queue.value = new Map(); + fetching.value = true; + const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; + await misskeyApi<MisskeyEntity[]>(props.pagination.endpoint, { + ...params, + limit: props.pagination.limit ?? 10, + allowPartial: true, + }).then(res => { + for (let i = 0; i < res.length; i++) { + const item = res[i]; + if (i === 3) item._shouldInsertAd_ = true; + } + + if (res.length === 0 || props.pagination.noPaging) { + concatItems(res); + more.value = false; + } else { + if (props.pagination.reversed) moreFetching.value = true; + concatItems(res); + more.value = true; + } + + offset.value = res.length; + error.value = false; + fetching.value = false; + }, err => { + error.value = true; + fetching.value = false; + }); +} + +const reload = (): Promise<void> => { + return init(); +}; + +const fetchMore = async (): Promise<void> => { + if (!more.value || fetching.value || moreFetching.value || items.value.size === 0) return; + moreFetching.value = true; + const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; + await misskeyApi<MisskeyEntity[]>(props.pagination.endpoint, { + ...params, + limit: SECOND_FETCH_LIMIT, + ...(props.pagination.offsetMode ? { + offset: offset.value, + } : { + untilId: Array.from(items.value.keys()).at(-1), + }), + }).then(res => { + for (let i = 0; i < res.length; i++) { + const item = res[i]; + if (i === 10) item._shouldInsertAd_ = true; + } + + const reverseConcat = _res => { + const oldHeight = scrollableElement.value ? scrollableElement.value.scrollHeight : getBodyScrollHeight(); + const oldScroll = scrollableElement.value ? scrollableElement.value.scrollTop : window.scrollY; + + items.value = concatMapWithArray(items.value, _res); + + return nextTick(() => { + if (scrollableElement.value) { + scroll(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' }); + } else { + window.scroll({ top: oldScroll + (getBodyScrollHeight() - oldHeight), behavior: 'instant' }); + } + + return nextTick(); + }); + }; + + if (res.length === 0) { + if (props.pagination.reversed) { + reverseConcat(res).then(() => { + more.value = false; + moreFetching.value = false; + }); + } else { + items.value = concatMapWithArray(items.value, res); + more.value = false; + moreFetching.value = false; + } + } else { + if (props.pagination.reversed) { + reverseConcat(res).then(() => { + more.value = true; + moreFetching.value = false; + }); + } else { + items.value = concatMapWithArray(items.value, res); + more.value = true; + moreFetching.value = false; + } + } + offset.value += res.length; + }, err => { + moreFetching.value = false; + }); +}; + +const fetchMoreAhead = async (): Promise<void> => { + if (!more.value || fetching.value || moreFetching.value || items.value.size === 0) return; + moreFetching.value = true; + const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; + await misskeyApi<MisskeyEntity[]>(props.pagination.endpoint, { + ...params, + limit: SECOND_FETCH_LIMIT, + ...(props.pagination.offsetMode ? { + offset: offset.value, + } : { + sinceId: Array.from(items.value.keys()).at(-1), + }), + }).then(res => { + if (res.length === 0) { + items.value = concatMapWithArray(items.value, res); + more.value = false; + } else { + items.value = concatMapWithArray(items.value, res); + more.value = true; + } + offset.value += res.length; + moreFetching.value = false; + }, err => { + moreFetching.value = false; + }); +}; + +/** + * Appear(IntersectionObserver)によってfetchMoreが呼ばれる場合、 + * APPEAR_MINIMUM_INTERVALミリ秒以内に2回fetchMoreが呼ばれるのを防ぐ + */ +const fetchMoreApperTimeoutFn = (): void => { + preventAppearFetchMore.value = false; + preventAppearFetchMoreTimer.value = null; +}; +const fetchMoreAppearTimeout = (): void => { + preventAppearFetchMore.value = true; + preventAppearFetchMoreTimer.value = window.setTimeout(fetchMoreApperTimeoutFn, APPEAR_MINIMUM_INTERVAL); +}; + +const appearFetchMore = async (): Promise<void> => { + if (preventAppearFetchMore.value) return; + await fetchMore(); + fetchMoreAppearTimeout(); +}; + +const appearFetchMoreAhead = async (): Promise<void> => { + if (preventAppearFetchMore.value) return; + await fetchMoreAhead(); + fetchMoreAppearTimeout(); +}; + +const isTop = (): boolean => isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl.value!, TOLERANCE); + +watch(visibility, () => { + if (visibility.value === 'hidden') { + timerForSetPause = window.setTimeout(() => { + isPausingUpdate = true; + timerForSetPause = null; + }, + BACKGROUND_PAUSE_WAIT_SEC * 1000); + } else { // 'visible' + if (timerForSetPause) { + clearTimeout(timerForSetPause); + timerForSetPause = null; + } else { + isPausingUpdate = false; + if (isTop()) { + executeQueue(); + } + } + } +}); + +/** + * 最新のものとして1つだけアイテムを追加する + * ストリーミングから降ってきたアイテムはこれで追加する + * @param item アイテム + */ +const prepend = (item: MisskeyEntity): void => { + if (items.value.size === 0) { + items.value.set(item.id, item); + fetching.value = false; + return; + } + + if (isTop() && !isPausingUpdate) unshiftItems([item]); + else prependQueue(item); +}; + +/** + * 新着アイテムをitemsの先頭に追加し、displayLimitを適用する + * @param newItems 新しいアイテムの配列 + */ +function unshiftItems(newItems: MisskeyEntity[]) { + const length = newItems.length + items.value.size; + items.value = new Map([...arrayToEntries(newItems), ...items.value].slice(0, props.displayLimit)); + + if (length >= props.displayLimit) more.value = true; +} + +/** + * 古いアイテムをitemsの末尾に追加し、displayLimitを適用する + * @param oldItems 古いアイテムの配列 + */ +function concatItems(oldItems: MisskeyEntity[]) { + const length = oldItems.length + items.value.size; + items.value = new Map([...items.value, ...arrayToEntries(oldItems)].slice(0, props.displayLimit)); + + if (length >= props.displayLimit) more.value = true; +} + +function executeQueue() { + unshiftItems(Array.from(queue.value.values())); + queue.value = new Map(); +} + +function prependQueue(newItem: MisskeyEntity) { + queue.value = new Map([[newItem.id, newItem], ...queue.value].slice(0, props.displayLimit) as [string, MisskeyEntity][]); +} + +/* + * アイテムを末尾に追加する(使うの?) + */ +const appendItem = (item: MisskeyEntity): void => { + items.value.set(item.id, item); +}; + +const removeItem = (id: string) => { + items.value.delete(id); + queue.value.delete(id); +}; + +const updateItem = (id: MisskeyEntity['id'], replacer: (old: MisskeyEntity) => MisskeyEntity): void => { + const item = items.value.get(id); + if (item) items.value.set(id, replacer(item)); + + const queueItem = queue.value.get(id); + if (queueItem) queue.value.set(id, replacer(queueItem)); +}; + +onActivated(() => { + isBackTop.value = false; +}); + +onDeactivated(() => { + isBackTop.value = props.pagination.reversed ? window.scrollY >= (rootEl.value ? rootEl.value.scrollHeight - window.innerHeight : 0) : window.scrollY === 0; +}); + +function toBottom() { + scrollToBottom(contentEl.value!); +} + +onBeforeMount(() => { + init().then(() => { + if (props.pagination.reversed) { + nextTick(() => { + setTimeout(toBottom, 800); + + // scrollToBottomでmoreFetchingボタンが画面外まで出るまで + // more = trueを遅らせる + setTimeout(() => { + moreFetching.value = false; + }, 2000); + }); + } + }); +}); + +onBeforeUnmount(() => { + if (timerForSetPause) { + clearTimeout(timerForSetPause); + timerForSetPause = null; + } + if (preventAppearFetchMoreTimer.value) { + clearTimeout(preventAppearFetchMoreTimer.value); + preventAppearFetchMoreTimer.value = null; + } + scrollObserver.value?.disconnect(); +}); + +defineExpose({ + items, + queue, + backed: backed.value, + more, + reload, + prepend, + append: appendItem, + removeItem, + updateItem, +}); +</script> + +<style lang="scss" module> +.transition_fade_enterActive, +.transition_fade_leaveActive { + transition: opacity 0.125s ease; +} +.transition_fade_enterFrom, +.transition_fade_leaveTo { + opacity: 0; +} + +.more { + display: block; + margin-left: auto; + margin-right: auto; +} +</style> diff --git a/packages/frontend-embed/src/components/EmPoll.vue b/packages/frontend-embed/src/components/EmPoll.vue new file mode 100644 index 0000000000..a2b1203449 --- /dev/null +++ b/packages/frontend-embed/src/components/EmPoll.vue @@ -0,0 +1,82 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div> + <ul :class="$style.choices"> + <li v-for="(choice, i) in poll.choices" :key="i" :class="$style.choice"> + <div :class="$style.bg" :style="{ 'width': `${choice.votes / total * 100}%` }"></div> + <span :class="$style.fg"> + <template v-if="choice.isVoted"><i class="ti ti-check" style="margin-right: 4px; color: var(--accent);"></i></template> + <EmMfm :text="choice.text" :plain="true"/> + <span style="margin-left: 4px; opacity: 0.7;">({{ i18n.tsx._poll.votesCount({ n: choice.votes }) }})</span> + </span> + </li> + </ul> + <p :class="$style.info"> + <span>{{ i18n.tsx._poll.totalVotes({ n: total }) }}</span> + </p> +</div> +</template> + +<script lang="ts" setup> +import { computed } from 'vue'; +import * as Misskey from 'misskey-js'; +import { i18n } from '@/i18n.js'; +import EmMfm from '@/components/EmMfm.js'; + +function sum(xs: number[]): number { + return xs.reduce((a, b) => a + b, 0); +} + +const props = defineProps<{ + noteId: string; + poll: NonNullable<Misskey.entities.Note['poll']>; +}>(); + +const total = computed(() => sum(props.poll.choices.map(x => x.votes))); +</script> + +<style lang="scss" module> +.choices { + display: block; + margin: 0; + padding: 0; + list-style: none; +} + +.choice { + display: block; + position: relative; + margin: 4px 0; + padding: 4px; + //border: solid 0.5px var(--divider); + background: var(--accentedBg); + border-radius: 4px; + overflow: clip; +} + +.bg { + position: absolute; + top: 0; + left: 0; + height: 100%; + background: var(--accent); + background: linear-gradient(90deg,var(--buttonGradateA),var(--buttonGradateB)); + transition: width 1s ease; +} + +.fg { + position: relative; + display: inline-block; + padding: 3px 5px; + background: var(--panel); + border-radius: 3px; +} + +.info { + color: var(--fg); +} +</style> diff --git a/packages/frontend-embed/src/components/EmReactionIcon.vue b/packages/frontend-embed/src/components/EmReactionIcon.vue new file mode 100644 index 0000000000..5c38ecb0ed --- /dev/null +++ b/packages/frontend-embed/src/components/EmReactionIcon.vue @@ -0,0 +1,23 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<EmCustomEmoji v-if="reaction[0] === ':'" ref="elRef" :name="reaction" :normal="true" :noStyle="noStyle" :url="emojiUrl" :fallbackToImage="true"/> +<EmEmoji v-else ref="elRef" :emoji="reaction" :normal="true" :noStyle="noStyle"/> +</template> + +<script lang="ts" setup> +import { } from 'vue'; +import EmCustomEmoji from './EmCustomEmoji.vue'; +import EmEmoji from './EmEmoji.vue'; + +const props = defineProps<{ + reaction: string; + noStyle?: boolean; + emojiUrl?: string; + withTooltip?: boolean; +}>(); + +</script> diff --git a/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue b/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue new file mode 100644 index 0000000000..2e43eb8d17 --- /dev/null +++ b/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue @@ -0,0 +1,99 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<button + class="_button" + :class="[$style.root, { [$style.reacted]: note.myReaction == reaction }]" +> + <EmReactionIcon :class="$style.limitWidth" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/> + <span :class="$style.count">{{ count }}</span> +</button> +</template> + +<script lang="ts" setup> +import { } from 'vue'; +import * as Misskey from 'misskey-js'; +import EmReactionIcon from '@/components/EmReactionIcon.vue'; + +const props = defineProps<{ + reaction: string; + count: number; + isInitial: boolean; + note: Misskey.entities.Note; +}>(); +</script> + +<style lang="scss" module> +.root { + display: inline-flex; + height: 42px; + margin: 2px; + padding: 0 6px; + font-size: 1.5em; + border-radius: 6px; + align-items: center; + justify-content: center; + + &.canToggle { + background: var(--buttonBg); + + &:hover { + background: rgba(0, 0, 0, 0.1); + } + } + + &:not(.canToggle) { + cursor: default; + } + + &.small { + height: 32px; + font-size: 1em; + border-radius: 4px; + + > .count { + font-size: 0.9em; + line-height: 32px; + } + } + + &.large { + height: 52px; + font-size: 2em; + border-radius: 8px; + + > .count { + font-size: 0.6em; + line-height: 52px; + } + } + + &.reacted, &.reacted:hover { + background: var(--accentedBg); + color: var(--accent); + box-shadow: 0 0 0 1px var(--accent) inset; + + > .count { + color: var(--accent); + } + + > .icon { + filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5)); + } + } +} + +.limitWidth { + max-width: 70px; + object-fit: contain; +} + +.count { + font-size: 0.7em; + line-height: 42px; + margin: 0 0 0 4px; +} +</style> diff --git a/packages/frontend-embed/src/components/EmReactionsViewer.vue b/packages/frontend-embed/src/components/EmReactionsViewer.vue new file mode 100644 index 0000000000..014dd1c935 --- /dev/null +++ b/packages/frontend-embed/src/components/EmReactionsViewer.vue @@ -0,0 +1,104 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="$style.root"> + <XReaction v-for="[reaction, count] in reactions" :key="reaction" :reaction="reaction" :count="count" :isInitial="initialReactions.has(reaction)" :note="note" @reactionToggled="onMockToggleReaction"/> + <slot v-if="hasMoreReactions" name="more"></slot> +</div> +</template> + +<script lang="ts" setup> +import * as Misskey from 'misskey-js'; +import { inject, watch, ref } from 'vue'; +import XReaction from '@/components/EmReactionsViewer.reaction.vue'; + +const props = withDefaults(defineProps<{ + note: Misskey.entities.Note; + maxNumber?: number; +}>(), { + maxNumber: Infinity, +}); + +const mock = inject<boolean>('mock', false); + +const emit = defineEmits<{ + (ev: 'mockUpdateMyReaction', emoji: string, delta: number): void; +}>(); + +const initialReactions = new Set(Object.keys(props.note.reactions)); + +const reactions = ref<[string, number][]>([]); +const hasMoreReactions = ref(false); + +if (props.note.myReaction && !Object.keys(reactions.value).includes(props.note.myReaction)) { + reactions.value[props.note.myReaction] = props.note.reactions[props.note.myReaction]; +} + +function onMockToggleReaction(emoji: string, count: number) { + if (!mock) return; + + const i = reactions.value.findIndex((item) => item[0] === emoji); + if (i < 0) return; + + emit('mockUpdateMyReaction', emoji, (count - reactions.value[i][1])); +} + +watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumber]) => { + let newReactions: [string, number][] = []; + hasMoreReactions.value = Object.keys(newSource).length > maxNumber; + + for (let i = 0; i < reactions.value.length; i++) { + const reaction = reactions.value[i][0]; + if (reaction in newSource && newSource[reaction] !== 0) { + reactions.value[i][1] = newSource[reaction]; + newReactions.push(reactions.value[i]); + } + } + + const newReactionsNames = newReactions.map(([x]) => x); + newReactions = [ + ...newReactions, + ...Object.entries(newSource) + .sort(([, a], [, b]) => b - a) + .filter(([y], i) => i < maxNumber && !newReactionsNames.includes(y)), + ]; + + newReactions = newReactions.slice(0, props.maxNumber); + + if (props.note.myReaction && !newReactions.map(([x]) => x).includes(props.note.myReaction)) { + newReactions.push([props.note.myReaction, newSource[props.note.myReaction]]); + } + + reactions.value = newReactions; +}, { immediate: true, deep: true }); +</script> + +<style lang="scss" module> +.transition_x_move, +.transition_x_enterActive, +.transition_x_leaveActive { + transition: opacity 0.2s cubic-bezier(0,.5,.5,1), transform 0.2s cubic-bezier(0,.5,.5,1) !important; +} +.transition_x_enterFrom, +.transition_x_leaveTo { + opacity: 0; + transform: scale(0.7); +} +.transition_x_leaveActive { + position: absolute; +} + +.root { + display: flex; + flex-wrap: wrap; + align-items: center; + margin: 4px -2px 0 -2px; + + &:empty { + display: none; + } +} +</style> diff --git a/packages/frontend-embed/src/components/EmSubNoteContent.vue b/packages/frontend-embed/src/components/EmSubNoteContent.vue new file mode 100644 index 0000000000..db2666a45f --- /dev/null +++ b/packages/frontend-embed/src/components/EmSubNoteContent.vue @@ -0,0 +1,114 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="[$style.root, { [$style.collapsed]: collapsed }]"> + <div> + <span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> + <span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deletedNote }})</span> + <EmA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></EmA> + <EmMfm v-if="note.text" :text="note.text" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis"/> + <EmA v-if="note.renoteId" :class="$style.rp" :to="`/notes/${note.renoteId}`">RN: ...</EmA> + </div> + <details v-if="note.files && note.files.length > 0"> + <summary>({{ i18n.tsx.withNFiles({ n: note.files.length }) }})</summary> + <EmMediaList :mediaList="note.files" :originalEntityUrl="`${url}/notes/${note.id}`"/> + </details> + <details v-if="note.poll"> + <summary>{{ i18n.ts.poll }}</summary> + <EmPoll :noteId="note.id" :poll="note.poll"/> + </details> + <button v-if="isLong && collapsed" :class="$style.fade" class="_button" @click="collapsed = false"> + <span :class="$style.fadeLabel">{{ i18n.ts.showMore }}</span> + </button> + <button v-else-if="isLong && !collapsed" :class="$style.showLess" class="_button" @click="collapsed = true"> + <span :class="$style.showLessLabel">{{ i18n.ts.showLess }}</span> + </button> +</div> +</template> + +<script lang="ts" setup> +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; +import EmMediaList from '@/components/EmMediaList.vue'; +import EmPoll from '@/components/EmPoll.vue'; +import { i18n } from '@/i18n.js'; +import { url } from '@@/js/config.js'; +import { shouldCollapsed } from '@@/js/collapsed.js'; +import EmA from '@/components/EmA.vue'; +import EmMfm from '@/components/EmMfm.js'; + +const props = defineProps<{ + note: Misskey.entities.Note; +}>(); + +const isLong = shouldCollapsed(props.note, []); + +const collapsed = ref(isLong); +</script> + +<style lang="scss" module> +.root { + overflow-wrap: break-word; + + &.collapsed { + position: relative; + max-height: 9em; + overflow: clip; + + > .fade { + display: block; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 64px; + background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + + > .fadeLabel { + display: inline-block; + background: var(--panel); + padding: 6px 10px; + font-size: 0.8em; + border-radius: 999px; + box-shadow: 0 2px 6px rgb(0 0 0 / 20%); + } + + &:hover { + > .fadeLabel { + background: var(--panelHighlight); + } + } + } + } +} + +.reply { + margin-right: 6px; + color: var(--accent); +} + +.rp { + margin-left: 4px; + font-style: oblique; + color: var(--renote); +} + +.showLess { + width: 100%; + margin-top: 14px; + position: sticky; + bottom: calc(var(--stickyBottom, 0px) + 14px); +} + +.showLessLabel { + display: inline-block; + background: var(--popup); + padding: 6px 10px; + font-size: 0.8em; + border-radius: 999px; + box-shadow: 0 2px 6px rgb(0 0 0 / 20%); +} +</style> diff --git a/packages/frontend-embed/src/components/EmTime.vue b/packages/frontend-embed/src/components/EmTime.vue new file mode 100644 index 0000000000..c3986f7d70 --- /dev/null +++ b/packages/frontend-embed/src/components/EmTime.vue @@ -0,0 +1,107 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<time :title="absolute" :class="{ [$style.old1]: colored && (ago > 60 * 60 * 24 * 90), [$style.old2]: colored && (ago > 60 * 60 * 24 * 180) }"> + <template v-if="invalid">{{ i18n.ts._ago.invalid }}</template> + <template v-else-if="mode === 'relative'">{{ relative }}</template> + <template v-else-if="mode === 'absolute'">{{ absolute }}</template> + <template v-else-if="mode === 'detail'">{{ absolute }} ({{ relative }})</template> +</time> +</template> + +<script lang="ts" setup> +import { onMounted, onUnmounted, ref, computed } from 'vue'; +import { i18n } from '@/i18n.js'; +import { dateTimeFormat } from '@@/js/intl-const.js'; + +const props = withDefaults(defineProps<{ + time: Date | string | number | null; + origin?: Date | null; + mode?: 'relative' | 'absolute' | 'detail'; + colored?: boolean; +}>(), { + origin: null, + mode: 'relative', +}); + +function getDateSafe(n: Date | string | number) { + try { + if (n instanceof Date) { + return n; + } + return new Date(n); + } catch (err) { + return { + getTime: () => NaN, + }; + } +} + +// eslint-disable-next-line vue/no-setup-props-reactivity-loss +const _time = props.time == null ? NaN : getDateSafe(props.time).getTime(); +const invalid = Number.isNaN(_time); +const absolute = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid; + +// eslint-disable-next-line vue/no-setup-props-reactivity-loss +const now = ref(props.origin?.getTime() ?? Date.now()); +const ago = computed(() => (now.value - _time) / 1000/*ms*/); + +const relative = computed<string>(() => { + if (props.mode === 'absolute') return ''; // absoluteではrelativeを使わないので計算しない + if (invalid) return i18n.ts._ago.invalid; + + return ( + ago.value >= 31536000 ? i18n.tsx._ago.yearsAgo({ n: Math.round(ago.value / 31536000).toString() }) : + ago.value >= 2592000 ? i18n.tsx._ago.monthsAgo({ n: Math.round(ago.value / 2592000).toString() }) : + ago.value >= 604800 ? i18n.tsx._ago.weeksAgo({ n: Math.round(ago.value / 604800).toString() }) : + ago.value >= 86400 ? i18n.tsx._ago.daysAgo({ n: Math.round(ago.value / 86400).toString() }) : + ago.value >= 3600 ? i18n.tsx._ago.hoursAgo({ n: Math.round(ago.value / 3600).toString() }) : + ago.value >= 60 ? i18n.tsx._ago.minutesAgo({ n: (~~(ago.value / 60)).toString() }) : + ago.value >= 10 ? i18n.tsx._ago.secondsAgo({ n: (~~(ago.value % 60)).toString() }) : + ago.value >= -3 ? i18n.ts._ago.justNow : + ago.value < -31536000 ? i18n.tsx._timeIn.years({ n: Math.round(-ago.value / 31536000).toString() }) : + ago.value < -2592000 ? i18n.tsx._timeIn.months({ n: Math.round(-ago.value / 2592000).toString() }) : + ago.value < -604800 ? i18n.tsx._timeIn.weeks({ n: Math.round(-ago.value / 604800).toString() }) : + ago.value < -86400 ? i18n.tsx._timeIn.days({ n: Math.round(-ago.value / 86400).toString() }) : + ago.value < -3600 ? i18n.tsx._timeIn.hours({ n: Math.round(-ago.value / 3600).toString() }) : + ago.value < -60 ? i18n.tsx._timeIn.minutes({ n: (~~(-ago.value / 60)).toString() }) : + i18n.tsx._timeIn.seconds({ n: (~~(-ago.value % 60)).toString() }) + ); +}); + +let tickId: number; +let currentInterval: number; + +function tick() { + now.value = Date.now(); + const nextInterval = ago.value < 60 ? 10000 : ago.value < 3600 ? 60000 : 180000; + + if (currentInterval !== nextInterval) { + if (tickId) window.clearInterval(tickId); + currentInterval = nextInterval; + tickId = window.setInterval(tick, nextInterval); + } +} + +if (!invalid && props.origin === null && (props.mode === 'relative' || props.mode === 'detail')) { + onMounted(() => { + tick(); + }); + onUnmounted(() => { + if (tickId) window.clearInterval(tickId); + }); +} +</script> + +<style lang="scss" module> +.old1 { + color: var(--warn); +} + +.old1.old2 { + color: var(--error); +} +</style> diff --git a/packages/frontend-embed/src/components/EmTimelineContainer.vue b/packages/frontend-embed/src/components/EmTimelineContainer.vue new file mode 100644 index 0000000000..6c30b1102d --- /dev/null +++ b/packages/frontend-embed/src/components/EmTimelineContainer.vue @@ -0,0 +1,39 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="$style.timelineRoot"> + <div v-if="showHeader" :class="$style.header"><slot name="header"></slot></div> + <div :class="$style.body"><slot name="body"></slot></div> +</div> +</template> + +<script setup lang="ts"> +withDefaults(defineProps<{ + showHeader?: boolean; +}>(), { + showHeader: true, +}); +</script> + +<style module lang="scss"> +.timelineRoot { + background-color: var(--panel); + height: 100%; + max-height: var(--embedMaxHeight, none); + display: flex; + flex-direction: column; +} + +.header { + flex-shrink: 0; + border-bottom: 1px solid var(--divider); +} + +.body { + flex-grow: 1; + overflow-y: auto; +} +</style> diff --git a/packages/frontend-embed/src/components/EmUrl.vue b/packages/frontend-embed/src/components/EmUrl.vue new file mode 100644 index 0000000000..94424cab28 --- /dev/null +++ b/packages/frontend-embed/src/components/EmUrl.vue @@ -0,0 +1,96 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<component + :is="self ? EmA : 'a'" ref="el" :class="$style.root" class="_link" :[attr]="self ? props.url.substring(local.length) : props.url" :rel="rel ?? 'nofollow noopener'" :target="target" + @contextmenu.stop="() => {}" +> + <template v-if="!self"> + <span :class="$style.schema">{{ schema }}//</span> + <span :class="$style.hostname">{{ hostname }}</span> + <span v-if="port != ''">:{{ port }}</span> + </template> + <template v-if="pathname === '/' && self"> + <span :class="$style.self">{{ hostname }}</span> + </template> + <span v-if="pathname != ''" :class="$style.pathname">{{ self ? pathname.substring(1) : pathname }}</span> + <span :class="$style.query">{{ query }}</span> + <span :class="$style.hash">{{ hash }}</span> + <i v-if="target === '_blank'" :class="$style.icon" class="ti ti-external-link"></i> +</component> +</template> + +<script lang="ts" setup> +import { ref } from 'vue'; +import { toUnicode as decodePunycode } from 'punycode/'; +import EmA from './EmA.vue'; +import { url as local } from '@@/js/config.js'; + +function safeURIDecode(str: string): string { + try { + return decodeURIComponent(str); + } catch { + return str; + } +} + +const props = withDefaults(defineProps<{ + url: string; + rel?: string; + showUrlPreview?: boolean; +}>(), { + showUrlPreview: true, +}); + +const self = props.url.startsWith(local); +const url = new URL(props.url); +if (!['http:', 'https:'].includes(url.protocol)) throw new Error('invalid url'); +const el = ref(); + +const schema = url.protocol; +const hostname = decodePunycode(url.hostname); +const port = url.port; +const pathname = safeURIDecode(url.pathname); +const query = safeURIDecode(url.search); +const hash = safeURIDecode(url.hash); +const attr = self ? 'to' : 'href'; +const target = self ? null : '_blank'; +</script> + +<style lang="scss" module> +.root { + word-break: break-all; +} + +.icon { + padding-left: 2px; + font-size: .9em; +} + +.self { + font-weight: bold; +} + +.schema { + opacity: 0.5; +} + +.hostname { + font-weight: bold; +} + +.pathname { + opacity: 0.8; +} + +.query { + opacity: 0.5; +} + +.hash { + font-style: italic; +} +</style> diff --git a/packages/frontend-embed/src/components/EmUserName.vue b/packages/frontend-embed/src/components/EmUserName.vue new file mode 100644 index 0000000000..c0c7c443ca --- /dev/null +++ b/packages/frontend-embed/src/components/EmUserName.vue @@ -0,0 +1,21 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<EmMfm :text="user.name ?? user.username" :author="user" :plain="true" :nowrap="nowrap" :emojiUrls="user.emojis"/> +</template> + +<script lang="ts" setup> +import { } from 'vue'; +import * as Misskey from 'misskey-js'; +import EmMfm from './EmMfm.js'; + +const props = withDefaults(defineProps<{ + user: Misskey.entities.User; + nowrap?: boolean; +}>(), { + nowrap: true, +}); +</script> diff --git a/packages/frontend-embed/src/components/I18n.vue b/packages/frontend-embed/src/components/I18n.vue new file mode 100644 index 0000000000..b621110ec9 --- /dev/null +++ b/packages/frontend-embed/src/components/I18n.vue @@ -0,0 +1,51 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<render/> +</template> + +<script setup lang="ts" generic="T extends string | ParameterizedString"> +import { computed, h } from 'vue'; +import type { ParameterizedString } from '../../../../locales/index.js'; + +const props = withDefaults(defineProps<{ + src: T; + tag?: string; + // eslint-disable-next-line vue/require-default-prop + textTag?: string; +}>(), { + tag: 'span', +}); + +const slots = defineSlots<T extends ParameterizedString<infer R> ? { [K in R]: () => unknown } : NonNullable<unknown>>(); + +const parsed = computed(() => { + let str = props.src as string; + const value: (string | { arg: string; })[] = []; + for (;;) { + const nextBracketOpen = str.indexOf('{'); + const nextBracketClose = str.indexOf('}'); + + if (nextBracketOpen === -1) { + value.push(str); + break; + } else { + if (nextBracketOpen > 0) value.push(str.substring(0, nextBracketOpen)); + value.push({ + arg: str.substring(nextBracketOpen + 1, nextBracketClose), + }); + } + + str = str.substring(nextBracketClose + 1); + } + + return value; +}); + +const render = () => { + return h(props.tag, parsed.value.map(x => typeof x === 'string' ? (props.textTag ? h(props.textTag, x) : x) : slots[x.arg]())); +}; +</script> diff --git a/packages/frontend-embed/src/custom-emojis.ts b/packages/frontend-embed/src/custom-emojis.ts new file mode 100644 index 0000000000..d5b40885c1 --- /dev/null +++ b/packages/frontend-embed/src/custom-emojis.ts @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { shallowRef, watch } from 'vue'; +import * as Misskey from 'misskey-js'; +import { misskeyApi, misskeyApiGet } from '@/misskey-api.js'; + +function get(key: string) { + const value = localStorage.getItem(key); + if (value === null) return null; + return JSON.parse(value); +} + +function set(key: string, value: any) { + localStorage.setItem(key, JSON.stringify(value)); +} + +const storageCache = await get('emojis'); +export const customEmojis = shallowRef<Misskey.entities.EmojiSimple[]>(Array.isArray(storageCache) ? storageCache : []); + +export const customEmojisMap = new Map<string, Misskey.entities.EmojiSimple>(); +watch(customEmojis, emojis => { + customEmojisMap.clear(); + for (const emoji of emojis) { + customEmojisMap.set(emoji.name, emoji); + } +}, { immediate: true }); + +export async function fetchCustomEmojis(force = false) { + const now = Date.now(); + + let res; + if (force) { + res = await misskeyApi('emojis', {}); + } else { + const lastFetchedAt = await get('lastEmojisFetchedAt'); + if (lastFetchedAt && (now - lastFetchedAt) < 1000 * 60 * 60) return; + res = await misskeyApiGet('emojis', {}); + } + + customEmojis.value = res.emojis; + set('emojis', res.emojis); + set('lastEmojisFetchedAt', now); +} + +let cachedTags; +export function getCustomEmojiTags() { + if (cachedTags) return cachedTags; + + const tags = new Set(); + for (const emoji of customEmojis.value) { + for (const tag of emoji.aliases) { + tags.add(tag); + } + } + const res = Array.from(tags); + cachedTags = res; + return res; +} diff --git a/packages/frontend-embed/src/di.ts b/packages/frontend-embed/src/di.ts new file mode 100644 index 0000000000..22f6276630 --- /dev/null +++ b/packages/frontend-embed/src/di.ts @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { InjectionKey } from 'vue'; +import * as Misskey from 'misskey-js'; +import { MediaProxy } from '@@/js/media-proxy.js'; +import type { ParsedEmbedParams } from '@@/js/embed-page.js'; +import type { ServerContext } from '@/server-context.js'; + +export const DI = { + serverMetadata: Symbol() as InjectionKey<Misskey.entities.MetaDetailed>, + embedParams: Symbol() as InjectionKey<ParsedEmbedParams>, + serverContext: Symbol() as InjectionKey<ServerContext>, + mediaProxy: Symbol() as InjectionKey<MediaProxy>, +}; diff --git a/packages/frontend-embed/src/i18n.ts b/packages/frontend-embed/src/i18n.ts new file mode 100644 index 0000000000..6ad503b089 --- /dev/null +++ b/packages/frontend-embed/src/i18n.ts @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { markRaw } from 'vue'; +import { I18n } from '@@/js/i18n.js'; +import type { Locale } from '../../../locales/index.js'; +import { locale } from '@@/js/config.js'; + +export const i18n = markRaw(new I18n<Locale>(locale, _DEV_)); + +export function updateI18n(newLocale: Locale) { + i18n.locale = newLocale; +} diff --git a/packages/frontend-embed/src/index.html b/packages/frontend-embed/src/index.html new file mode 100644 index 0000000000..47b0b0e84e --- /dev/null +++ b/packages/frontend-embed/src/index.html @@ -0,0 +1,36 @@ +<!-- + SPDX-FileCopyrightText: syuilo and misskey-project + SPDX-License-Identifier: AGPL-3.0-only +--> + +<!-- + 開発モードのviteはこのファイルを起点にサーバーを起動します。 + このファイルに書かれた [t]js のリンクと (s)cssのリンクと、その依存関係にあるファイルはビルドされます +--> + +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8" /> + <title>[DEV] Loading...</title> + <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> + <meta + http-equiv="Content-Security-Policy" + content="default-src 'self' https://newassets.hcaptcha.com/ https://challenges.cloudflare.com/ http://localhost:7493/; + worker-src 'self'; + script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com https://esm.sh; + style-src 'self' 'unsafe-inline'; + img-src 'self' data: blob: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000; + media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000; + connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com; + frame-src *;" + /> + <meta property="og:site_name" content="[DEV BUILD] Misskey" /> + <meta name="viewport" content="width=device-width, initial-scale=1"> +</head> + +<body> +<div id="misskey_app"></div> +<script type="module" src="./boot.ts"></script> +</body> +</html> diff --git a/packages/frontend-embed/src/misskey-api.ts b/packages/frontend-embed/src/misskey-api.ts new file mode 100644 index 0000000000..0d3c679359 --- /dev/null +++ b/packages/frontend-embed/src/misskey-api.ts @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as Misskey from 'misskey-js'; +import { ref } from 'vue'; +import { apiUrl } from '@@/js/config.js'; + +export const pendingApiRequestsCount = ref(0); + +// Implements Misskey.api.ApiClient.request +export function misskeyApi< + ResT = void, + E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints, + P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req'], + _ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT, +>( + endpoint: E, + data: P = {} as any, + signal?: AbortSignal, +): Promise<_ResT> { + if (endpoint.includes('://')) throw new Error('invalid endpoint'); + pendingApiRequestsCount.value++; + + const onFinally = () => { + pendingApiRequestsCount.value--; + }; + + const promise = new Promise<_ResT>((resolve, reject) => { + // Send request + window.fetch(`${apiUrl}/${endpoint}`, { + method: 'POST', + body: JSON.stringify(data), + credentials: 'omit', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + signal, + }).then(async (res) => { + const body = res.status === 204 ? null : await res.json(); + + if (res.status === 200) { + resolve(body); + } else if (res.status === 204) { + resolve(undefined as _ResT); // void -> undefined + } else { + reject(body.error); + } + }).catch(reject); + }); + + promise.then(onFinally, onFinally); + + return promise; +} + +// Implements Misskey.api.ApiClient.request +export function misskeyApiGet< + ResT = void, + E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints, + P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req'], + _ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT, +>( + endpoint: E, + data: P = {} as any, +): Promise<_ResT> { + pendingApiRequestsCount.value++; + + const onFinally = () => { + pendingApiRequestsCount.value--; + }; + + const query = new URLSearchParams(data as any); + + const promise = new Promise<_ResT>((resolve, reject) => { + // Send request + window.fetch(`${apiUrl}/${endpoint}?${query}`, { + method: 'GET', + credentials: 'omit', + cache: 'default', + }).then(async (res) => { + const body = res.status === 204 ? null : await res.json(); + + if (res.status === 200) { + resolve(body); + } else if (res.status === 204) { + resolve(undefined as _ResT); // void -> undefined + } else { + reject(body.error); + } + }).catch(reject); + }); + + promise.then(onFinally, onFinally); + + return promise; +} diff --git a/packages/frontend-embed/src/pages/clip.vue b/packages/frontend-embed/src/pages/clip.vue new file mode 100644 index 0000000000..2528dc4b80 --- /dev/null +++ b/packages/frontend-embed/src/pages/clip.vue @@ -0,0 +1,143 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div> + <EmTimelineContainer v-if="clip" :showHeader="embedParams.header"> + <template #header> + <div :class="$style.clipHeader"> + <div :class="$style.headerClipIconRoot"> + <i class="ti ti-paperclip"></i> + </div> + <div :class="$style.headerTitle" @click="top"> + <div class="_nowrap"><a :href="`/clips/${clip.id}`" target="_blank" rel="noopener">{{ clip.name }}</a></div> + <div :class="$style.sub">{{ i18n.tsx.fromX({ x: instanceName }) }}</div> + </div> + <a :href="url" :class="$style.instanceIconLink" target="_blank" rel="noopener noreferrer"> + <img + :class="$style.instanceIcon" + :src="serverMetadata.iconUrl || '/favicon.ico'" + /> + </a> + </div> + </template> + <template #body> + <EmNotes + ref="notesEl" + :pagination="pagination" + :disableAutoLoad="!embedParams.autoload" + :noGap="true" + :ad="false" + /> + </template> + </EmTimelineContainer> + <XNotFound v-else/> +</div> +</template> + +<script setup lang="ts"> +import { ref, computed, inject, useTemplateRef } from 'vue'; +import * as Misskey from 'misskey-js'; +import { scrollToTop } from '@@/js/scroll.js'; +import { url, instanceName } from '@@/js/config.js'; +import { isLink } from '@@/js/is-link.js'; +import { defaultEmbedParams } from '@@/js/embed-page.js'; +import type { Paging } from '@/components/EmPagination.vue'; +import EmNotes from '@/components/EmNotes.vue'; +import XNotFound from '@/pages/not-found.vue'; +import EmTimelineContainer from '@/components/EmTimelineContainer.vue'; +import { misskeyApi } from '@/misskey-api.js'; +import { i18n } from '@/i18n.js'; +import { assertServerContext } from '@/server-context.js'; +import { DI } from '@/di.js'; + +const props = defineProps<{ + clipId: string; +}>(); + +const embedParams = inject(DI.embedParams, defaultEmbedParams); + +const serverMetadata = inject(DI.serverMetadata)!; + +const serverContext = inject(DI.serverContext)!; + +const clip = ref<Misskey.entities.Clip | null>(); + +if (assertServerContext(serverContext, 'clip')) { + clip.value = serverContext.clip; +} else { + clip.value = await misskeyApi('clips/show', { + clipId: props.clipId, + }).catch(() => { + return null; + }); +} + +const pagination = computed(() => ({ + endpoint: 'clips/notes', + params: { + clipId: props.clipId, + }, +} as Paging)); + +const notesEl = useTemplateRef('notesEl'); + +function top(ev: MouseEvent) { + const target = ev.target as HTMLElement | null; + if (target && isLink(target)) return; + + if (notesEl.value) { + scrollToTop(notesEl.value.$el as HTMLElement, { behavior: 'smooth' }); + } +} +</script> + +<style lang="scss" module> +.clipHeader { + padding: 8px 16px; + display: flex; + min-width: 0; + align-items: center; + gap: var(--margin); + overflow: hidden; + + .headerClipIconRoot { + flex-shrink: 0; + width: 32px; + height: 32px; + line-height: 32px; + font-size: 14px; + text-align: center; + background-color: var(--accentedBg); + color: var(--accent); + border-radius: 50%; + } + + .headerTitle { + flex-grow: 1; + font-weight: 700; + line-height: 1.1; + min-width: 0; + + .sub { + font-size: 0.8em; + font-weight: 400; + opacity: 0.7; + } + } + + .instanceIconLink { + flex-shrink: 0; + display: block; + margin-left: auto; + height: 24px; + } + + .instanceIcon { + height: 24px; + border-radius: 3px; + } +} +</style> diff --git a/packages/frontend-embed/src/pages/not-found.vue b/packages/frontend-embed/src/pages/not-found.vue new file mode 100644 index 0000000000..bbb03b4e64 --- /dev/null +++ b/packages/frontend-embed/src/pages/not-found.vue @@ -0,0 +1,24 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div> + <div class="_fullinfo"> + <img :src="notFoundImageUrl" class="_ghost"/> + <div>{{ i18n.ts.notFoundDescription }}</div> + </div> +</div> +</template> + +<script lang="ts" setup> +import { inject, computed } from 'vue'; +import { DEFAULT_NOT_FOUND_IMAGE_URL } from '@@/js/const.js'; +import { DI } from '@/di.js'; +import { i18n } from '@/i18n.js'; + +const serverMetadata = inject(DI.serverMetadata)!; + +const notFoundImageUrl = computed(() => serverMetadata?.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL); +</script> diff --git a/packages/frontend-embed/src/pages/note.vue b/packages/frontend-embed/src/pages/note.vue new file mode 100644 index 0000000000..918583ecc7 --- /dev/null +++ b/packages/frontend-embed/src/pages/note.vue @@ -0,0 +1,45 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="$style.noteEmbedRoot"> + <EmNoteDetailed v-if="note" :note="note"/> + <XNotFound v-else/> +</div> +</template> + +<script setup lang="ts"> +import { inject, ref } from 'vue'; +import * as Misskey from 'misskey-js'; +import EmNoteDetailed from '@/components/EmNoteDetailed.vue'; +import XNotFound from '@/pages/not-found.vue'; +import { DI } from '@/di.js'; +import { misskeyApi } from '@/misskey-api.js'; +import { assertServerContext } from '@/server-context'; + +const props = defineProps<{ + noteId: string; +}>(); + +const serverContext = inject(DI.serverContext)!; + +const note = ref<Misskey.entities.Note | null>(null); + +if (assertServerContext(serverContext, 'note')) { + note.value = serverContext.note; +} else { + note.value = await misskeyApi('notes/show', { + noteId: props.noteId, + }).catch(() => { + return null; + }); +} +</script> + +<style lang="scss" module> +.noteEmbedRoot { + background-color: var(--panel); +} +</style> diff --git a/packages/frontend-embed/src/pages/tag.vue b/packages/frontend-embed/src/pages/tag.vue new file mode 100644 index 0000000000..b481b3ebe5 --- /dev/null +++ b/packages/frontend-embed/src/pages/tag.vue @@ -0,0 +1,126 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div> + <EmTimelineContainer v-if="tag" :showHeader="embedParams.header"> + <template #header> + <div :class="$style.clipHeader"> + <div :class="$style.headerClipIconRoot"> + <i class="ti ti-hash"></i> + </div> + <div :class="$style.headerTitle" @click="top"> + <div class="_nowrap"><a :href="`/tags/${tag}`" target="_blank" rel="noopener">#{{ tag }}</a></div> + <div :class="$style.sub">{{ i18n.tsx.fromX({ x: instanceName }) }}</div> + </div> + <a :href="url" :class="$style.instanceIconLink" target="_blank" rel="noopener noreferrer"> + <img + :class="$style.instanceIcon" + :src="serverMetadata.iconUrl || '/favicon.ico'" + /> + </a> + </div> + </template> + <template #body> + <EmNotes + ref="notesEl" + :pagination="pagination" + :disableAutoLoad="!embedParams.autoload" + :noGap="true" + :ad="false" + /> + </template> + </EmTimelineContainer> + <XNotFound v-else/> +</div> +</template> + +<script setup lang="ts"> +import { computed, inject, useTemplateRef } from 'vue'; +import { scrollToTop } from '@@/js/scroll.js'; +import type { Paging } from '@/components/EmPagination.vue'; +import EmNotes from '@/components/EmNotes.vue'; +import XNotFound from '@/pages/not-found.vue'; +import EmTimelineContainer from '@/components/EmTimelineContainer.vue'; +import { i18n } from '@/i18n.js'; +import { url, instanceName } from '@@/js/config.js'; +import { isLink } from '@@/js/is-link.js'; +import { DI } from '@/di.js'; +import { defaultEmbedParams } from '@@/js/embed-page.js'; + +const props = defineProps<{ + tag: string; +}>(); + +const serverMetadata = inject(DI.serverMetadata)!; + +const embedParams = inject(DI.embedParams, defaultEmbedParams); + +const pagination = computed(() => ({ + endpoint: 'notes/search-by-tag', + params: { + tag: props.tag, + }, +} as Paging)); + +const notesEl = useTemplateRef('notesEl'); + +function top(ev: MouseEvent) { + const target = ev.target as HTMLElement | null; + if (target && isLink(target)) return; + + if (notesEl.value) { + scrollToTop(notesEl.value.$el as HTMLElement, { behavior: 'smooth' }); + } +} +</script> + +<style lang="scss" module> +.clipHeader { + padding: 8px 16px; + display: flex; + min-width: 0; + align-items: center; + gap: var(--margin); + overflow: hidden; + + .headerClipIconRoot { + flex-shrink: 0; + width: 32px; + height: 32px; + line-height: 32px; + font-size: 14px; + text-align: center; + background-color: var(--accentedBg); + color: var(--accent); + border-radius: 50%; + } + + .headerTitle { + flex-grow: 1; + font-weight: 700; + line-height: 1.1; + min-width: 0; + + .sub { + font-size: 0.8em; + font-weight: 400; + opacity: 0.7; + } + } + + .instanceIconLink { + flex-shrink: 0; + display: block; + margin-left: auto; + height: 24px; + } + + .instanceIcon { + height: 24px; + border-radius: 3px; + } +} +</style> diff --git a/packages/frontend-embed/src/pages/user-timeline.vue b/packages/frontend-embed/src/pages/user-timeline.vue new file mode 100644 index 0000000000..2d5dbb687b --- /dev/null +++ b/packages/frontend-embed/src/pages/user-timeline.vue @@ -0,0 +1,147 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div> + <EmTimelineContainer v-if="user && !prohibited" :showHeader="embedParams.header"> + <template #header> + <div :class="$style.userHeader"> + <a :href="`/@${user.username}`" target="_blank" rel="noopener noreferrer" :class="$style.avatarLink"> + <EmAvatar :class="$style.avatar" :user="user"/> + </a> + <div :class="$style.headerTitle"> + <I18n :src="i18n.ts.noteOf" tag="div" class="_nowrap"> + <template #user> + <a v-if="user != null" :href="`/@${user.username}`" target="_blank" rel="noopener noreferrer"> + <EmUserName :user="user"/> + </a> + <span v-else>{{ i18n.ts.user }}</span> + </template> + </I18n> + <div :class="$style.sub">{{ i18n.tsx.fromX({ x: instanceName }) }}</div> + </div> + <a :href="url" :class="$style.instanceIconLink" target="_blank" rel="noopener noreferrer"> + <img + :class="$style.instanceIcon" + :src="serverMetadata.iconUrl || '/favicon.ico'" + /> + </a> + </div> + </template> + <template #body> + <EmNotes + ref="notesEl" + :pagination="pagination" + :disableAutoLoad="!embedParams.autoload" + :noGap="true" + :ad="false" + /> + </template> + </EmTimelineContainer> + <XNotFound v-else/> +</div> +</template> + +<script setup lang="ts"> +import { ref, computed, inject, useTemplateRef } from 'vue'; +import * as Misskey from 'misskey-js'; +import { url, instanceName } from '@@/js/config.js'; +import { defaultEmbedParams } from '@@/js/embed-page.js'; +import type { Paging } from '@/components/EmPagination.vue'; +import EmNotes from '@/components/EmNotes.vue'; +import EmAvatar from '@/components/EmAvatar.vue'; +import EmUserName from '@/components/EmUserName.vue'; +import I18n from '@/components/I18n.vue'; +import XNotFound from '@/pages/not-found.vue'; +import EmTimelineContainer from '@/components/EmTimelineContainer.vue'; +import { misskeyApi } from '@/misskey-api.js'; +import { i18n } from '@/i18n.js'; +import { assertServerContext } from '@/server-context.js'; +import { DI } from '@/di.js'; + +const props = defineProps<{ + userId: string; +}>(); + +const embedParams = inject(DI.embedParams, defaultEmbedParams); + +const serverMetadata = inject(DI.serverMetadata)!; + +const serverContext = inject(DI.serverContext)!; + +const user = ref<Misskey.entities.UserLite | null>(); + +const prohibited = ref(false); + +if (assertServerContext(serverContext, 'user')) { + user.value = serverContext.user; +} else { + user.value = await misskeyApi('users/show', { + userId: props.userId, + }).catch(() => { + return null; + }); +} + +if (user.value?.host != null) { + // リモートサーバーのユーザーは弾く + prohibited.value = true; +} + +const pagination = computed(() => ({ + endpoint: 'users/notes', + params: { + userId: user.value?.id, + }, +} as Paging)); + +const notesEl = useTemplateRef('notesEl'); +</script> + +<style lang="scss" module> +.userHeader { + padding: 8px 16px; + display: flex; + min-width: 0; + align-items: center; + gap: var(--margin); + overflow: hidden; + + .avatarLink { + display: block; + } + + .avatar { + display: inline-block; + width: 32px; + height: 32px; + } + + .headerTitle { + flex-grow: 1; + font-weight: 700; + line-height: 1.1; + min-width: 0; + + .sub { + font-size: 0.8em; + font-weight: 400; + opacity: 0.7; + } + } + + .instanceIconLink { + flex-shrink: 0; + display: block; + margin-left: auto; + height: 24px; + } + + .instanceIcon { + height: 24px; + border-radius: 3px; + } +} +</style> diff --git a/packages/frontend-embed/src/post-message.ts b/packages/frontend-embed/src/post-message.ts new file mode 100644 index 0000000000..fd8eb8a5d2 --- /dev/null +++ b/packages/frontend-embed/src/post-message.ts @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export const postMessageEventTypes = [ + 'misskey:embed:ready', + 'misskey:embed:changeHeight', +] as const; + +export type PostMessageEventType = typeof postMessageEventTypes[number]; + +export interface PostMessageEventPayload extends Record<PostMessageEventType, any> { + 'misskey:embed:ready': undefined; + 'misskey:embed:changeHeight': { + height: number; + }; +} + +export type MiPostMessageEvent<T extends PostMessageEventType = PostMessageEventType> = { + type: T; + iframeId?: string; + payload?: PostMessageEventPayload[T]; +} + +let defaultIframeId: string | null = null; + +export function setIframeId(id: string): void { + if (defaultIframeId != null) return; + + if (_DEV_) console.log('setIframeId', id); + defaultIframeId = id; +} + +/** + * 親フレームにイベントを送信 + */ +export function postMessageToParentWindow<T extends PostMessageEventType = PostMessageEventType>(type: T, payload?: PostMessageEventPayload[T], iframeId: string | null = null): void { + let _iframeId = iframeId; + if (_iframeId == null) { + _iframeId = defaultIframeId; + } + if (_DEV_) console.log('postMessageToParentWindow', type, _iframeId, payload); + window.parent.postMessage({ + type, + iframeId: _iframeId, + payload, + }, '*'); +} diff --git a/packages/frontend-embed/src/server-context.ts b/packages/frontend-embed/src/server-context.ts new file mode 100644 index 0000000000..a84a1a726a --- /dev/null +++ b/packages/frontend-embed/src/server-context.ts @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ +import * as Misskey from 'misskey-js'; + +const providedContextEl = document.getElementById('misskey_embedCtx'); + +export type ServerContext = { + clip?: Misskey.entities.Clip; + note?: Misskey.entities.Note; + user?: Misskey.entities.UserLite; +} | null; + +// NOTE: devモードのときしか embedCtx が null になることは無い +export const serverContext: ServerContext = (providedContextEl && providedContextEl.textContent) ? JSON.parse(providedContextEl.textContent) : null; + +export function assertServerContext<K extends keyof NonNullable<ServerContext>>(ctx: ServerContext, entity: K): ctx is Required<Pick<NonNullable<ServerContext>, K>> { + if (ctx == null) return false; + return entity in ctx; +} diff --git a/packages/frontend-embed/src/server-metadata.ts b/packages/frontend-embed/src/server-metadata.ts new file mode 100644 index 0000000000..6c94aacd48 --- /dev/null +++ b/packages/frontend-embed/src/server-metadata.ts @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as Misskey from 'misskey-js'; +import { misskeyApi } from '@/misskey-api.js'; + +const providedMetaEl = document.getElementById('misskey_meta'); + +const _serverMetadata: Misskey.entities.MetaDetailed | null = (providedMetaEl && providedMetaEl.textContent) ? JSON.parse(providedMetaEl.textContent) : null; + +// NOTE: devモードのときしか _serverMetadata が null になることは無い +export const serverMetadata: Misskey.entities.MetaDetailed = _serverMetadata ?? await misskeyApi('meta', { + detail: true, +}); diff --git a/packages/frontend-embed/src/style.scss b/packages/frontend-embed/src/style.scss new file mode 100644 index 0000000000..02008ddbd0 --- /dev/null +++ b/packages/frontend-embed/src/style.scss @@ -0,0 +1,453 @@ +@charset "utf-8"; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +:root { + --radius: 12px; + --marginFull: 14px; + --marginHalf: 10px; + + --margin: var(--marginFull); +} + +html { + background-color: transparent; + color-scheme: light dark; + color: var(--fg); + accent-color: var(--accent); + overflow: clip; + overflow-wrap: break-word; + font-family: 'Hiragino Maru Gothic Pro', "BIZ UDGothic", Roboto, HelveticaNeue, Arial, sans-serif; + font-size: 14px; + line-height: 1.35; + text-size-adjust: 100%; + tab-size: 2; + -webkit-text-size-adjust: 100%; + + &, * { + scrollbar-color: var(--scrollbarHandle) transparent; + scrollbar-width: thin; + + &::-webkit-scrollbar { + width: 6px; + height: 6px; + } + + &::-webkit-scrollbar-track { + background: inherit; + } + + &::-webkit-scrollbar-thumb { + background: var(--scrollbarHandle); + + &:hover { + background: var(--scrollbarHandleHover); + } + + &:active { + background: var(--accent); + } + } + } +} + +html, body { + height: 100%; + touch-action: manipulation; + margin: 0; + padding: 0; + scroll-behavior: smooth; +} + +#misskey_app { + height: 100%; +} + +a { + text-decoration: none; + cursor: pointer; + color: inherit; + tap-highlight-color: transparent; + -webkit-tap-highlight-color: transparent; + -webkit-touch-callout: none; + + &:focus-visible { + outline-offset: 2px; + } + + &:hover { + text-decoration: underline; + } + + &[target="_blank"] { + -webkit-touch-callout: default; + } +} + +rt { + white-space: initial; +} + +:focus-visible { + outline: var(--focus) solid 2px; + outline-offset: -2px; + + &:hover { + text-decoration: none; + } +} + +.ti { + width: 1.28em; + vertical-align: -12%; + line-height: 1em; + + &::before { + font-size: 128%; + } +} + +.ti-fw { + display: inline-block; + text-align: center; +} + +._nowrap { + white-space: pre !important; + word-wrap: normal !important; // https://codeday.me/jp/qa/20190424/690106.html + overflow: hidden; + text-overflow: ellipsis; +} + +._button { + user-select: none; + -webkit-user-select: none; + -webkit-touch-callout: none; + appearance: none; + display: inline-block; + padding: 0; + margin: 0; // for Safari + background: none; + border: none; + cursor: pointer; + color: inherit; + touch-action: manipulation; + tap-highlight-color: transparent; + -webkit-tap-highlight-color: transparent; + font-size: 1em; + font-family: inherit; + line-height: inherit; + max-width: 100%; + + &:disabled { + opacity: 0.5; + cursor: default; + } +} + +._buttonGray { + @extend ._button; + background: var(--buttonBg); + + &:not(:disabled):hover { + background: var(--buttonHoverBg); + } +} + +._buttonPrimary { + @extend ._button; + color: var(--fgOnAccent); + background: var(--accent); + + &:not(:disabled):hover { + background: hsl(from var(--accent) h s calc(l + 5)); + } + + &:not(:disabled):active { + background: hsl(from var(--accent) h s calc(l - 5)); + } +} + +._buttonGradate { + @extend ._buttonPrimary; + color: var(--fgOnAccent); + background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + + &:not(:disabled):hover { + background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); + } + + &:not(:disabled):active { + background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); + } +} + +._buttonRounded { + font-size: 0.95em; + padding: 0.5em 1em; + min-width: 100px; + border-radius: 99rem; + + &._buttonPrimary, + &._buttonGradate { + font-weight: 700; + } +} + +._help { + color: var(--accent); + cursor: help; +} + +._textButton { + @extend ._button; + color: var(--accent); + + &:focus-visible { + outline-offset: 2px; + } + + &:not(:disabled):hover { + text-decoration: underline; + } +} + +._panel { + background: var(--panel); + border-radius: var(--radius); + overflow: clip; +} + +._margin { + margin: var(--margin) 0; +} + +._gaps_m { + display: flex; + flex-direction: column; + gap: 1.5em; +} + +._gaps_s { + display: flex; + flex-direction: column; + gap: 0.75em; +} + +._gaps { + display: flex; + flex-direction: column; + gap: var(--margin); +} + +._buttons { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +._buttonsCenter { + @extend ._buttons; + + justify-content: center; +} + +._borderButton { + @extend ._button; + display: block; + width: 100%; + padding: 10px; + box-sizing: border-box; + text-align: center; + border: solid 0.5px var(--divider); + border-radius: var(--radius); + + &:active { + border-color: var(--accent); + } +} + +._popup { + background: var(--popup); + border-radius: var(--radius); + contain: content; +} + +._acrylic { + background: var(--acrylicPanel); + -webkit-backdrop-filter: var(--blur, blur(15px)); + backdrop-filter: var(--blur, blur(15px)); +} + +._fullinfo { + padding: 64px 32px; + text-align: center; + + > img { + vertical-align: bottom; + height: 128px; + margin-bottom: 16px; + border-radius: 16px; + } +} + +._link { + color: var(--link); +} + +._caption { + font-size: 0.8em; + opacity: 0.7; +} + +._monospace { + font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace !important; +} + +// MFM ----------------------------- + +._mfm_blur_ { + filter: blur(6px); + transition: filter 0.3s; + + &:hover { + filter: blur(0px); + } +} + +.mfm-x2 { + --mfm-zoom-size: 200%; +} + +.mfm-x3 { + --mfm-zoom-size: 400%; +} + +.mfm-x4 { + --mfm-zoom-size: 600%; +} + +.mfm-x2, .mfm-x3, .mfm-x4 { + font-size: var(--mfm-zoom-size); + + .mfm-x2, .mfm-x3, .mfm-x4 { + /* only half effective */ + font-size: calc(var(--mfm-zoom-size) / 2 + 50%); + + .mfm-x2, .mfm-x3, .mfm-x4 { + /* disabled */ + font-size: 100%; + } + } +} + +._mfm_rainbow_fallback_ { + background-image: linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(255, 165, 0) 17%, rgb(255, 255, 0) 33%, rgb(0, 255, 0) 50%, rgb(0, 255, 255) 67%, rgb(0, 0, 255) 83%, rgb(255, 0, 255) 100%); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +@keyframes mfm-spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +@keyframes mfm-spinX { + 0% { transform: perspective(128px) rotateX(0deg); } + 100% { transform: perspective(128px) rotateX(360deg); } +} + +@keyframes mfm-spinY { + 0% { transform: perspective(128px) rotateY(0deg); } + 100% { transform: perspective(128px) rotateY(360deg); } +} + +@keyframes mfm-jump { + 0% { transform: translateY(0); } + 25% { transform: translateY(-16px); } + 50% { transform: translateY(0); } + 75% { transform: translateY(-8px); } + 100% { transform: translateY(0); } +} + +@keyframes mfm-bounce { + 0% { transform: translateY(0) scale(1, 1); } + 25% { transform: translateY(-16px) scale(1, 1); } + 50% { transform: translateY(0) scale(1, 1); } + 75% { transform: translateY(0) scale(1.5, 0.75); } + 100% { transform: translateY(0) scale(1, 1); } +} + +// const val = () => `translate(${Math.floor(Math.random() * 20) - 10}px, ${Math.floor(Math.random() * 20) - 10}px)`; +// let css = ''; +// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; } +@keyframes mfm-twitch { + 0% { transform: translate(7px, -2px) } + 5% { transform: translate(-3px, 1px) } + 10% { transform: translate(-7px, -1px) } + 15% { transform: translate(0px, -1px) } + 20% { transform: translate(-8px, 6px) } + 25% { transform: translate(-4px, -3px) } + 30% { transform: translate(-4px, -6px) } + 35% { transform: translate(-8px, -8px) } + 40% { transform: translate(4px, 6px) } + 45% { transform: translate(-3px, 1px) } + 50% { transform: translate(2px, -10px) } + 55% { transform: translate(-7px, 0px) } + 60% { transform: translate(-2px, 4px) } + 65% { transform: translate(3px, -8px) } + 70% { transform: translate(6px, 7px) } + 75% { transform: translate(-7px, -2px) } + 80% { transform: translate(-7px, -8px) } + 85% { transform: translate(9px, 3px) } + 90% { transform: translate(-3px, -2px) } + 95% { transform: translate(-10px, 2px) } + 100% { transform: translate(-2px, -6px) } +} + +// const val = () => `translate(${Math.floor(Math.random() * 6) - 3}px, ${Math.floor(Math.random() * 6) - 3}px) rotate(${Math.floor(Math.random() * 24) - 12}deg)`; +// let css = ''; +// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; } +@keyframes mfm-shake { + 0% { transform: translate(-3px, -1px) rotate(-8deg) } + 5% { transform: translate(0px, -1px) rotate(-10deg) } + 10% { transform: translate(1px, -3px) rotate(0deg) } + 15% { transform: translate(1px, 1px) rotate(11deg) } + 20% { transform: translate(-2px, 1px) rotate(1deg) } + 25% { transform: translate(-1px, -2px) rotate(-2deg) } + 30% { transform: translate(-1px, 2px) rotate(-3deg) } + 35% { transform: translate(2px, 1px) rotate(6deg) } + 40% { transform: translate(-2px, -3px) rotate(-9deg) } + 45% { transform: translate(0px, -1px) rotate(-12deg) } + 50% { transform: translate(1px, 2px) rotate(10deg) } + 55% { transform: translate(0px, -3px) rotate(8deg) } + 60% { transform: translate(1px, -1px) rotate(8deg) } + 65% { transform: translate(0px, -1px) rotate(-7deg) } + 70% { transform: translate(-1px, -3px) rotate(6deg) } + 75% { transform: translate(0px, -2px) rotate(4deg) } + 80% { transform: translate(-2px, -1px) rotate(3deg) } + 85% { transform: translate(1px, -3px) rotate(-10deg) } + 90% { transform: translate(1px, 0px) rotate(3deg) } + 95% { transform: translate(-2px, 0px) rotate(-3deg) } + 100% { transform: translate(2px, 1px) rotate(2deg) } +} + +@keyframes mfm-rubberBand { + from { transform: scale3d(1, 1, 1); } + 30% { transform: scale3d(1.25, 0.75, 1); } + 40% { transform: scale3d(0.75, 1.25, 1); } + 50% { transform: scale3d(1.15, 0.85, 1); } + 65% { transform: scale3d(0.95, 1.05, 1); } + 75% { transform: scale3d(1.05, 0.95, 1); } + to { transform: scale3d(1, 1, 1); } +} + +@keyframes mfm-rainbow { + 0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); } + 100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); } +} diff --git a/packages/frontend-embed/src/theme.ts b/packages/frontend-embed/src/theme.ts new file mode 100644 index 0000000000..ee633fae94 --- /dev/null +++ b/packages/frontend-embed/src/theme.ts @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import tinycolor from 'tinycolor2'; +import lightTheme from '@@/themes/_light.json5'; +import darkTheme from '@@/themes/_dark.json5'; +import type { BundledTheme } from 'shiki/themes'; + +export type Theme = { + id: string; + name: string; + author: string; + desc?: string; + base?: 'dark' | 'light'; + props: Record<string, string>; + codeHighlighter?: { + base: BundledTheme; + overrides?: Record<string, any>; + } | { + base: '_none_'; + overrides: Record<string, any>; + }; +}; + +let timeout: number | null = null; + +export function assertIsTheme(theme: Record<string, unknown>): theme is Theme { + return typeof theme === 'object' && theme !== null && 'id' in theme && 'name' in theme && 'author' in theme && 'props' in theme; +} + +export function applyTheme(theme: Theme, persist = true) { + if (timeout) window.clearTimeout(timeout); + + document.documentElement.classList.add('_themeChanging_'); + + timeout = window.setTimeout(() => { + document.documentElement.classList.remove('_themeChanging_'); + }, 1000); + + // Deep copy + const _theme = JSON.parse(JSON.stringify(theme)); + + if (_theme.base) { + const base = [lightTheme, darkTheme].find(x => x.id === _theme.base); + if (base) _theme.props = Object.assign({}, base.props, _theme.props); + } + + const props = compile(_theme); + + for (const tag of document.head.children) { + if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { + tag.setAttribute('content', props['htmlThemeColor']); + break; + } + } + + for (const [k, v] of Object.entries(props)) { + document.documentElement.style.setProperty(`--${k}`, v.toString()); + } + + // iframeを正常に透過させるために、cssのcolor-schemeは `light dark;` 固定にしてある。style.scss参照 +} + +function compile(theme: Theme): Record<string, string> { + function getColor(val: string): tinycolor.Instance { + if (val[0] === '@') { // ref (prop) + return getColor(theme.props[val.substring(1)]); + } else if (val[0] === '$') { // ref (const) + return getColor(theme.props[val]); + } else if (val[0] === ':') { // func + const parts = val.split('<'); + const func = parts.shift().substring(1); + const arg = parseFloat(parts.shift()); + const color = getColor(parts.join('<')); + + switch (func) { + case 'darken': return color.darken(arg); + case 'lighten': return color.lighten(arg); + case 'alpha': return color.setAlpha(arg); + case 'hue': return color.spin(arg); + case 'saturate': return color.saturate(arg); + } + } + + // other case + return tinycolor(val); + } + + const props = {}; + + for (const [k, v] of Object.entries(theme.props)) { + if (k.startsWith('$')) continue; // ignore const + + props[k] = v.startsWith('"') ? v.replace(/^"\s*/, '') : genValue(getColor(v)); + } + + return props; +} + +function genValue(c: tinycolor.Instance): string { + return c.toRgbString(); +} diff --git a/packages/frontend-embed/src/ui.vue b/packages/frontend-embed/src/ui.vue new file mode 100644 index 0000000000..f426778898 --- /dev/null +++ b/packages/frontend-embed/src/ui.vue @@ -0,0 +1,102 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div + ref="rootEl" + :class="[ + $style.rootForEmbedPage, + { + [$style.rounded]: embedRounded, + [$style.noBorder]: embedNoBorder, + } + ]" + :style="maxHeight > 0 ? { maxHeight: `${maxHeight}px`, '--embedMaxHeight': `${maxHeight}px` } : {}" +> + <div + :class="$style.routerViewContainer" + > + <Suspense :timeout="0"> + <EmNotePage v-if="page === 'notes'" :noteId="contentId"/> + <EmUserTimelinePage v-else-if="page === 'user-timeline'" :userId="contentId"/> + <EmClipPage v-else-if="page === 'clips'" :clipId="contentId"/> + <EmTagPage v-else-if="page === 'tags'" :tag="contentId"/> + <XNotFound v-else/> + <template #fallback> + <EmLoading/> + </template> + </Suspense> + </div> +</div> +</template> + +<script lang="ts" setup> +import { ref, shallowRef, onMounted, onUnmounted, inject } from 'vue'; +import { postMessageToParentWindow } from '@/post-message.js'; +import { DI } from '@/di.js'; +import { defaultEmbedParams } from '@@/js/embed-page.js'; +import EmNotePage from '@/pages/note.vue'; +import EmUserTimelinePage from '@/pages/user-timeline.vue'; +import EmClipPage from '@/pages/clip.vue'; +import EmTagPage from '@/pages/tag.vue'; +import XNotFound from '@/pages/not-found.vue'; +import EmLoading from '@/components/EmLoading.vue'; + +const page = location.pathname.split('/')[2]; +const contentId = location.pathname.split('/')[3]; +if (_DEV_) console.log(page, contentId); + +const embedParams = inject(DI.embedParams, defaultEmbedParams); + +//#region Embed Style +const embedRounded = ref(embedParams.rounded); +const embedNoBorder = ref(!embedParams.border); +const maxHeight = ref(embedParams.maxHeight ?? 0); +//#endregion + +//#region Embed Resizer +const rootEl = shallowRef<HTMLElement | null>(null); + +let previousHeight = 0; +const resizeObserver = new ResizeObserver(async () => { + const height = rootEl.value!.scrollHeight + (embedNoBorder.value ? 0 : 2); // border 上下1px + if (Math.abs(previousHeight - height) < 1) return; // 1px未満の変化は無視 + postMessageToParentWindow('misskey:embed:changeHeight', { + height: (maxHeight.value > 0 && height > maxHeight.value) ? maxHeight.value : height, + }); + previousHeight = height; +}); +onMounted(() => { + resizeObserver.observe(rootEl.value!); +}); +onUnmounted(() => { + resizeObserver.disconnect(); +}); +//#endregion +</script> + +<style lang="scss" module> +.rootForEmbedPage { + box-sizing: border-box; + border: 1px solid var(--divider); + background-color: var(--bg); + overflow: hidden; + position: relative; + height: auto; + + &.rounded { + border-radius: var(--radius); + } + + &.noBorder { + border: none; + } +} + +.routerViewContainer { + container-type: inline-size; + max-height: var(--embedMaxHeight, none); +} +</style> diff --git a/packages/frontend-embed/src/utils.ts b/packages/frontend-embed/src/utils.ts new file mode 100644 index 0000000000..48e06b21ef --- /dev/null +++ b/packages/frontend-embed/src/utils.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as Misskey from 'misskey-js'; +import { url } from '@@/js/config.js'; + +export const acct = (user: Misskey.Acct) => { + return Misskey.acct.toString(user); +}; + +export const userName = (user: Misskey.entities.User) => { + return user.name || user.username; +}; + +export const userPage = (user: Misskey.Acct, path?: string, absolute = false) => { + return `${absolute ? url : ''}/@${acct(user)}${(path ? `/${path}` : '')}`; +}; + +export const notePage = note => { + return `/notes/${note.id}`; +}; diff --git a/packages/frontend-embed/src/workers/draw-blurhash.ts b/packages/frontend-embed/src/workers/draw-blurhash.ts new file mode 100644 index 0000000000..22de6cd3a8 --- /dev/null +++ b/packages/frontend-embed/src/workers/draw-blurhash.ts @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { render } from 'buraha'; + +const canvas = new OffscreenCanvas(64, 64); + +onmessage = (event) => { + // console.log(event.data); + if (!('id' in event.data && typeof event.data.id === 'string')) { + return; + } + if (!('hash' in event.data && typeof event.data.hash === 'string')) { + return; + } + + render(event.data.hash, canvas); + const bitmap = canvas.transferToImageBitmap(); + postMessage({ id: event.data.id, bitmap }); +}; diff --git a/packages/frontend-embed/src/workers/test-webgl2.ts b/packages/frontend-embed/src/workers/test-webgl2.ts new file mode 100644 index 0000000000..b203ebe666 --- /dev/null +++ b/packages/frontend-embed/src/workers/test-webgl2.ts @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +const canvas = globalThis.OffscreenCanvas && new OffscreenCanvas(1, 1); +// 環境によってはOffscreenCanvasが存在しないため +// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition +const gl = canvas?.getContext('webgl2'); +if (gl) { + postMessage({ result: true }); +} else { + postMessage({ result: false }); +} diff --git a/packages/frontend-embed/src/workers/tsconfig.json b/packages/frontend-embed/src/workers/tsconfig.json new file mode 100644 index 0000000000..8ee8930465 --- /dev/null +++ b/packages/frontend-embed/src/workers/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "lib": ["esnext", "webworker"], + } +} diff --git a/packages/frontend-embed/tsconfig.json b/packages/frontend-embed/tsconfig.json new file mode 100644 index 0000000000..3701343623 --- /dev/null +++ b/packages/frontend-embed/tsconfig.json @@ -0,0 +1,53 @@ +{ + "compilerOptions": { + "allowJs": true, + "noEmitOnError": false, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedParameters": false, + "noUnusedLocals": false, + "noFallthroughCasesInSwitch": true, + "declaration": false, + "sourceMap": false, + "target": "ES2022", + "module": "nodenext", + "moduleResolution": "nodenext", + "removeComments": false, + "noLib": false, + "strict": true, + "strictNullChecks": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "isolatedModules": true, + "useDefineForClassFields": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"], + "@@/*": ["../frontend-shared/*"] + }, + "typeRoots": [ + "./@types", + "./node_modules/@types", + "./node_modules/@vue-macros", + "./node_modules" + ], + "types": [ + "vite/client", + ], + "lib": [ + "esnext", + "dom", + "dom.iterable" + ], + "jsx": "preserve" + }, + "compileOnSave": false, + "include": [ + "./**/*.ts", + "./**/*.vue" + ], + "exclude": [ + ".storybook/**/*" + ] +} diff --git a/packages/frontend-embed/vite.config.local-dev.ts b/packages/frontend-embed/vite.config.local-dev.ts new file mode 100644 index 0000000000..bf2f478887 --- /dev/null +++ b/packages/frontend-embed/vite.config.local-dev.ts @@ -0,0 +1,96 @@ +import dns from 'dns'; +import { readFile } from 'node:fs/promises'; +import type { IncomingMessage } from 'node:http'; +import { defineConfig } from 'vite'; +import type { UserConfig } from 'vite'; +import * as yaml from 'js-yaml'; +import locales from '../../locales/index.js'; +import { getConfig } from './vite.config.js'; + +dns.setDefaultResultOrder('ipv4first'); + +const defaultConfig = getConfig(); + +const { port } = yaml.load(await readFile('../../.config/default.yml', 'utf-8')); + +const httpUrl = `http://localhost:${port}/`; +const websocketUrl = `ws://localhost:${port}/`; + +// activitypubリクエストはProxyを通し、それ以外はViteの開発サーバーを返す +function varyHandler(req: IncomingMessage) { + if (req.headers.accept?.includes('application/activity+json')) { + return null; + } + return '/index.html'; +} + +const devConfig: UserConfig = { + // 基本の設定は vite.config.js から引き継ぐ + ...defaultConfig, + root: 'src', + publicDir: '../assets', + base: '/embed', + server: { + host: 'localhost', + port: 5174, + proxy: { + '/api': { + changeOrigin: true, + target: httpUrl, + }, + '/assets': httpUrl, + '/static-assets': httpUrl, + '/client-assets': httpUrl, + '/files': httpUrl, + '/twemoji': httpUrl, + '/fluent-emoji': httpUrl, + '/sw.js': httpUrl, + '/streaming': { + target: websocketUrl, + ws: true, + }, + '/favicon.ico': httpUrl, + '/robots.txt': httpUrl, + '/embed.js': httpUrl, + '/identicon': { + target: httpUrl, + rewrite(path) { + return path.replace('@localhost:5173', ''); + }, + }, + '/url': httpUrl, + '/proxy': httpUrl, + '/_info_card_': httpUrl, + '/bios': httpUrl, + '/cli': httpUrl, + '/inbox': httpUrl, + '/emoji/': httpUrl, + '/notes': { + target: httpUrl, + bypass: varyHandler, + }, + '/users': { + target: httpUrl, + bypass: varyHandler, + }, + '/.well-known': { + target: httpUrl, + }, + }, + }, + build: { + ...defaultConfig.build, + rollupOptions: { + ...defaultConfig.build?.rollupOptions, + input: 'index.html', + }, + }, + + define: { + ...defaultConfig.define, + _LANGS_FULL_: JSON.stringify(Object.entries(locales)), + }, +}; + +export default defineConfig(({ command, mode }) => devConfig); + diff --git a/packages/frontend-embed/vite.config.ts b/packages/frontend-embed/vite.config.ts new file mode 100644 index 0000000000..64e67401c2 --- /dev/null +++ b/packages/frontend-embed/vite.config.ts @@ -0,0 +1,156 @@ +import path from 'path'; +import pluginVue from '@vitejs/plugin-vue'; +import { type UserConfig, defineConfig } from 'vite'; + +import locales from '../../locales/index.js'; +import meta from '../../package.json'; +import packageInfo from './package.json' with { type: 'json' }; +import pluginJson5 from './vite.json5.js'; + +const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue']; + +/** + * Misskeyのフロントエンドにバンドルせず、CDNなどから別途読み込むリソースを記述する。 + * CDNを使わずにバンドルしたい場合、以下の配列から該当要素を削除orコメントアウトすればOK + */ +const externalPackages = [ + // shiki(コードブロックのシンタックスハイライトで使用中)はテーマ・言語の定義の容量が大きいため、それらはCDNから読み込む + { + name: 'shiki', + match: /^shiki\/(?<subPkg>(langs|themes))$/, + path(id: string, pattern: RegExp): string { + const match = pattern.exec(id)?.groups; + return match + ? `https://esm.sh/shiki@${packageInfo.dependencies.shiki}/${match['subPkg']}` + : id; + }, + }, +]; + +const hash = (str: string, seed = 0): number => { + let h1 = 0xdeadbeef ^ seed, + h2 = 0x41c6ce57 ^ seed; + for (let i = 0, ch; i < str.length; i++) { + ch = str.charCodeAt(i); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909); + + return 4294967296 * (2097151 & h2) + (h1 >>> 0); +}; + +const BASE62_DIGITS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + +function toBase62(n: number): string { + if (n === 0) { + return '0'; + } + let result = ''; + while (n > 0) { + result = BASE62_DIGITS[n % BASE62_DIGITS.length] + result; + n = Math.floor(n / BASE62_DIGITS.length); + } + + return result; +} + +export function getConfig(): UserConfig { + return { + base: '/embed_vite/', + + server: { + port: 5174, + }, + + plugins: [ + pluginVue(), + pluginJson5(), + ], + + resolve: { + extensions, + alias: { + '@/': __dirname + '/src/', + '@@/': __dirname + '/../frontend-shared/', + '/client-assets/': __dirname + '/assets/', + '/static-assets/': __dirname + '/../backend/assets/' + }, + }, + + css: { + modules: { + generateScopedName(name, filename, _css): string { + const id = (path.relative(__dirname, filename.split('?')[0]) + '-' + name).replace(/[\\\/\.\?&=]/g, '-').replace(/(src-|vue-)/g, ''); + if (process.env.NODE_ENV === 'production') { + return 'x' + toBase62(hash(id)).substring(0, 4); + } else { + return id; + } + }, + }, + }, + + define: { + _VERSION_: JSON.stringify(meta.version), + _LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])), + _ENV_: JSON.stringify(process.env.NODE_ENV), + _DEV_: process.env.NODE_ENV !== 'production', + _PERF_PREFIX_: JSON.stringify('Misskey:'), + __VUE_OPTIONS_API__: false, + __VUE_PROD_DEVTOOLS__: false, + }, + + build: { + target: [ + 'chrome116', + 'firefox116', + 'safari16', + ], + manifest: 'manifest.json', + rollupOptions: { + input: { + app: './src/boot.ts', + }, + external: externalPackages.map(p => p.match), + output: { + manualChunks: { + vue: ['vue'], + }, + chunkFileNames: process.env.NODE_ENV === 'production' ? '[hash:8].js' : '[name]-[hash:8].js', + assetFileNames: process.env.NODE_ENV === 'production' ? '[hash:8][extname]' : '[name]-[hash:8][extname]', + paths(id) { + for (const p of externalPackages) { + if (p.match.test(id)) { + return p.path(id, p.match); + } + } + + return id; + }, + }, + }, + cssCodeSplit: true, + outDir: __dirname + '/../../built/_frontend_embed_vite_', + assetsDir: '.', + emptyOutDir: false, + sourcemap: process.env.NODE_ENV === 'development', + reportCompressedSize: false, + + // https://vitejs.dev/guide/dep-pre-bundling.html#monorepos-and-linked-dependencies + commonjsOptions: { + include: [/misskey-js/, /node_modules/], + }, + }, + + worker: { + format: 'es', + }, + }; +} + +const config = defineConfig(({ command, mode }) => getConfig()); + +export default config; diff --git a/packages/frontend-embed/vite.json5.ts b/packages/frontend-embed/vite.json5.ts new file mode 100644 index 0000000000..87b67c2142 --- /dev/null +++ b/packages/frontend-embed/vite.json5.ts @@ -0,0 +1,48 @@ +// Original: https://github.com/rollup/plugins/tree/8835dd2aed92f408d7dc72d7cc25a9728e16face/packages/json + +import JSON5 from 'json5'; +import { Plugin } from 'rollup'; +import { createFilter, dataToEsm } from '@rollup/pluginutils'; +import { RollupJsonOptions } from '@rollup/plugin-json'; + +// json5 extends SyntaxError with additional fields (without subclassing) +// https://github.com/json5/json5/blob/de344f0619bda1465a6e25c76f1c0c3dda8108d9/lib/parse.js#L1111-L1112 +interface Json5SyntaxError extends SyntaxError { + lineNumber: number; + columnNumber: number; +} + +export default function json5(options: RollupJsonOptions = {}): Plugin { + const filter = createFilter(options.include, options.exclude); + const indent = 'indent' in options ? options.indent : '\t'; + + return { + name: 'json5', + + // eslint-disable-next-line no-shadow + transform(json, id) { + if (id.slice(-6) !== '.json5' || !filter(id)) return null; + + try { + const parsed = JSON5.parse(json); + return { + code: dataToEsm(parsed, { + preferConst: options.preferConst, + compact: options.compact, + namedExports: options.namedExports, + indent, + }), + map: { mappings: '' }, + }; + } catch (err) { + if (!(err instanceof SyntaxError)) { + throw err; + } + const message = 'Could not parse JSON5 file'; + const { lineNumber, columnNumber } = err as Json5SyntaxError; + this.warn({ message, id, loc: { line: lineNumber, column: columnNumber } }); + return null; + } + }, + }; +} diff --git a/packages/frontend-embed/vue-shims.d.ts b/packages/frontend-embed/vue-shims.d.ts new file mode 100644 index 0000000000..eba994772d --- /dev/null +++ b/packages/frontend-embed/vue-shims.d.ts @@ -0,0 +1,6 @@ +/* eslint-disable */ +declare module "*.vue" { + import { defineComponent } from "vue"; + const component: ReturnType<typeof defineComponent>; + export default component; +} diff --git a/packages/frontend-shared/.gitignore b/packages/frontend-shared/.gitignore new file mode 100644 index 0000000000..5f6be09d7c --- /dev/null +++ b/packages/frontend-shared/.gitignore @@ -0,0 +1,2 @@ +/storybook-static +js-built diff --git a/packages/frontend-shared/@types/global.d.ts b/packages/frontend-shared/@types/global.d.ts new file mode 100644 index 0000000000..4b8d679e75 --- /dev/null +++ b/packages/frontend-shared/@types/global.d.ts @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type FIXME = any; + +declare const _LANGS_: string[][]; +declare const _VERSION_: string; +declare const _ENV_: string; +declare const _DEV_: boolean; +declare const _PERF_PREFIX_: string; +declare const _DATA_TRANSFER_DRIVE_FILE_: string; +declare const _DATA_TRANSFER_DRIVE_FOLDER_: string; +declare const _DATA_TRANSFER_DECK_COLUMN_: string; + +// for dev-mode +declare const _LANGS_FULL_: string[][]; + +// TagCanvas +interface Window { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TagCanvas: any; +} diff --git a/packages/frontend-shared/build.js b/packages/frontend-shared/build.js new file mode 100644 index 0000000000..17b6da8d30 --- /dev/null +++ b/packages/frontend-shared/build.js @@ -0,0 +1,106 @@ +import fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; +import * as esbuild from 'esbuild'; +import { build } from 'esbuild'; +import { globSync } from 'glob'; +import { execa } from 'execa'; + +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); +const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8')); + +const entryPoints = globSync('./js/**/**.{ts,tsx}'); + +/** @type {import('esbuild').BuildOptions} */ +const options = { + entryPoints, + minify: process.env.NODE_ENV === 'production', + outdir: './js-built', + target: 'es2022', + platform: 'browser', + format: 'esm', + sourcemap: 'linked', +}; + +// js-built配下をすべて削除する +fs.rmSync('./js-built', { recursive: true, force: true }); + +if (process.argv.map(arg => arg.toLowerCase()).includes('--watch')) { + await watchSrc(); +} else { + await buildSrc(); +} + +async function buildSrc() { + console.log(`[${_package.name}] start building...`); + + await build(options) + .then(() => { + console.log(`[${_package.name}] build succeeded.`); + }) + .catch((err) => { + process.stderr.write(err.stderr); + process.exit(1); + }); + + if (process.env.NODE_ENV === 'production') { + console.log(`[${_package.name}] skip building d.ts because NODE_ENV is production.`); + } else { + await buildDts(); + } + + fs.copyFileSync('./js/emojilist.json', './js-built/emojilist.json'); + + console.log(`[${_package.name}] finish building.`); +} + +function buildDts() { + return execa( + 'tsc', + [ + '--project', 'tsconfig.json', + '--outDir', 'js-built', + '--declaration', 'true', + '--emitDeclarationOnly', 'true', + ], + { + stdout: process.stdout, + stderr: process.stderr, + }, + ); +} + +async function watchSrc() { + const plugins = [{ + name: 'gen-dts', + setup(build) { + build.onStart(() => { + console.log(`[${_package.name}] detect changed...`); + }); + build.onEnd(async result => { + if (result.errors.length > 0) { + console.error(`[${_package.name}] watch build failed:`, result); + return; + } + await buildDts(); + }); + }, + }]; + + console.log(`[${_package.name}] start watching...`); + + const context = await esbuild.context({ ...options, plugins }); + await context.watch(); + + await new Promise((resolve, reject) => { + process.on('SIGHUP', resolve); + process.on('SIGINT', resolve); + process.on('SIGTERM', resolve); + process.on('uncaughtException', reject); + process.on('exit', resolve); + }).finally(async () => { + await context.dispose(); + console.log(`[${_package.name}] finish watching.`); + }); +} diff --git a/packages/frontend-shared/eslint.config.js b/packages/frontend-shared/eslint.config.js new file mode 100644 index 0000000000..cd4641a270 --- /dev/null +++ b/packages/frontend-shared/eslint.config.js @@ -0,0 +1,100 @@ +import globals from 'globals'; +import tsParser from '@typescript-eslint/parser'; +import parser from 'vue-eslint-parser'; +import pluginVue from 'eslint-plugin-vue'; +import pluginMisskey from '@misskey-dev/eslint-plugin'; +import sharedConfig from '../shared/eslint.config.js'; + +// eslint-disable-next-line import/no-default-export +export default [ + ...sharedConfig, + { + files: ['**/*.vue'], + ...pluginMisskey.configs.typescript, + }, + ...pluginVue.configs['flat/recommended'], + { + files: [ + '@types/**/*.ts', + 'js/**/*.ts', + '**/*.vue', + ], + languageOptions: { + globals: { + ...Object.fromEntries(Object.entries(globals.node).map(([key]) => [key, 'off'])), + ...globals.browser, + + // Node.js + module: false, + require: false, + __dirname: false, + + // Misskey + _DEV_: false, + _LANGS_: false, + _VERSION_: false, + _ENV_: false, + _PERF_PREFIX_: false, + _DATA_TRANSFER_DRIVE_FILE_: false, + _DATA_TRANSFER_DRIVE_FOLDER_: false, + _DATA_TRANSFER_DECK_COLUMN_: false, + }, + parser, + parserOptions: { + extraFileExtensions: ['.vue'], + parser: tsParser, + project: ['./tsconfig.json'], + sourceType: 'module', + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + '@typescript-eslint/no-empty-interface': ['error', { + allowSingleExtends: true, + }], + // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため + // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため + 'id-denylist': ['error', 'window', 'e'], + 'no-shadow': ['warn'], + 'vue/attributes-order': ['error', { + alphabetical: false, + }], + 'vue/no-use-v-if-with-v-for': ['error', { + allowUsingIterationVar: false, + }], + 'vue/no-ref-as-operand': 'error', + 'vue/no-multi-spaces': ['error', { + ignoreProperties: false, + }], + 'vue/no-v-html': 'warn', + 'vue/order-in-components': 'error', + 'vue/html-indent': ['warn', 'tab', { + attribute: 1, + baseIndent: 0, + closeBracket: 0, + alignAttributesVertically: true, + ignores: [], + }], + 'vue/html-closing-bracket-spacing': ['warn', { + startTag: 'never', + endTag: 'never', + selfClosingTag: 'never', + }], + 'vue/multi-word-component-names': 'warn', + 'vue/require-v-for-key': 'warn', + 'vue/no-unused-components': 'warn', + 'vue/no-unused-vars': 'warn', + 'vue/no-dupe-keys': 'warn', + 'vue/valid-v-for': 'warn', + 'vue/return-in-computed-property': 'warn', + 'vue/no-setup-props-reactivity-loss': 'warn', + 'vue/max-attributes-per-line': 'off', + 'vue/html-self-closing': 'off', + 'vue/singleline-html-element-content-newline': 'off', + 'vue/v-on-event-hyphenation': ['error', 'never', { + autofix: true, + }], + 'vue/attribute-hyphenation': ['error', 'never'], + }, + }, +]; diff --git a/packages/frontend/src/scripts/collapsed.ts b/packages/frontend-shared/js/collapsed.ts similarity index 86% rename from packages/frontend/src/scripts/collapsed.ts rename to packages/frontend-shared/js/collapsed.ts index 4ec88a3c65..af1f88cb73 100644 --- a/packages/frontend/src/scripts/collapsed.ts +++ b/packages/frontend-shared/js/collapsed.ts @@ -7,7 +7,7 @@ import * as Misskey from 'misskey-js'; export function shouldCollapsed(note: Misskey.entities.Note, urls: string[]): boolean { const collapsed = note.cw == null && ( - note.text != null && ( + (note.text != null && ( (note.text.includes('$[x2')) || (note.text.includes('$[x3')) || (note.text.includes('$[x4')) || @@ -15,7 +15,7 @@ export function shouldCollapsed(note: Misskey.entities.Note, urls: string[]): bo (note.text.split('\n').length > 9) || (note.text.length > 500) || (urls.length >= 4) - ) || note.files.length >= 5 + )) || (note.files != null && note.files.length >= 5) ); return collapsed; diff --git a/packages/frontend/src/config.ts b/packages/frontend-shared/js/config.ts similarity index 51% rename from packages/frontend/src/config.ts rename to packages/frontend-shared/js/config.ts index 277dfc12aa..ae1dcae10b 100644 --- a/packages/frontend/src/config.ts +++ b/packages/frontend-shared/js/config.ts @@ -3,8 +3,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { miLocalStorage } from '@/local-storage.js'; +import type { Locale } from '../../../locales/index.js'; +// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const address = new URL(document.querySelector<HTMLMetaElement>('meta[property="instance_url"]')?.content || location.href); const siteName = document.querySelector<HTMLMetaElement>('meta[property="og:site_name"]')?.content; @@ -13,15 +14,15 @@ export const hostname = address.hostname; export const url = address.origin; export const apiUrl = location.origin + '/api'; export const wsOrigin = location.origin; -export const lang = miLocalStorage.getItem('lang') ?? 'en-US'; +export const lang = localStorage.getItem('lang') ?? 'en-US'; export const langs = _LANGS_; -const preParseLocale = miLocalStorage.getItem('locale'); -export let locale = preParseLocale ? JSON.parse(preParseLocale) : null; +const preParseLocale = localStorage.getItem('locale'); +export let locale: Locale = preParseLocale ? JSON.parse(preParseLocale) : null; export const version = _VERSION_; -export const instanceName = siteName === 'Misskey' || siteName == null ? host : siteName; -export const ui = miLocalStorage.getItem('ui'); -export const debug = miLocalStorage.getItem('debug') === 'true'; +export const instanceName = (siteName === 'Misskey' || siteName == null) ? host : siteName; +export const ui = localStorage.getItem('ui'); +export const debug = localStorage.getItem('debug') === 'true'; -export function updateLocale(newLocale): void { +export function updateLocale(newLocale: Locale): void { locale = newLocale; } diff --git a/packages/frontend/src/const.ts b/packages/frontend-shared/js/const.ts similarity index 95% rename from packages/frontend/src/const.ts rename to packages/frontend-shared/js/const.ts index e135bc69a0..b62a69ba24 100644 --- a/packages/frontend/src/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -98,6 +98,11 @@ export const ROLE_POLICIES = [ 'userEachUserListsLimit', 'rateLimitFactor', 'avatarDecorationLimit', + 'canImportAntennas', + 'canImportBlocking', + 'canImportFollowing', + 'canImportMuting', + 'canImportUserLists', ] as const; // なんか動かない @@ -127,7 +132,7 @@ export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = { position: ['x=', 'y='], fg: ['color='], bg: ['color='], - border: ['width=', 'style=', 'color=', 'radius=', 'noclip'], + border: ['width=', 'style=', 'color=', 'radius=', 'noclip'], font: ['serif', 'monospace', 'cursive', 'fantasy', 'emoji', 'math'], blur: [], rainbow: ['speed=', 'delay='], diff --git a/packages/frontend-shared/js/embed-page.ts b/packages/frontend-shared/js/embed-page.ts new file mode 100644 index 0000000000..d5555a98c3 --- /dev/null +++ b/packages/frontend-shared/js/embed-page.ts @@ -0,0 +1,97 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +//#region Embed関連の定義 + +/** 埋め込みの対象となるエンティティ(/embed/xxx の xxx の部分と対応させる) */ +const embeddableEntities = [ + 'notes', + 'user-timeline', + 'clips', + 'tags', +] as const; + +/** 埋め込みの対象となるエンティティ */ +export type EmbeddableEntity = typeof embeddableEntities[number]; + +/** 内部でスクロールがあるページ */ +export const embedRouteWithScrollbar: EmbeddableEntity[] = [ + 'clips', + 'tags', + 'user-timeline', +]; + +/** 埋め込みコードのパラメータ */ +export type EmbedParams = { + maxHeight?: number; + colorMode?: 'light' | 'dark'; + rounded?: boolean; + border?: boolean; + autoload?: boolean; + header?: boolean; +}; + +/** 正規化されたパラメータ */ +export type ParsedEmbedParams = Required<Omit<EmbedParams, 'maxHeight' | 'colorMode'>> & Pick<EmbedParams, 'maxHeight' | 'colorMode'>; + +/** パラメータのデフォルトの値 */ +export const defaultEmbedParams = { + maxHeight: undefined, + colorMode: undefined, + rounded: true, + border: true, + autoload: false, + header: true, +} as const satisfies EmbedParams; + +//#endregion + +/** + * パラメータを正規化する(埋め込みページ初期化用) + * @param searchParams URLSearchParamsもしくはクエリ文字列 + * @returns 正規化されたパラメータ + */ +export function parseEmbedParams(searchParams: URLSearchParams | string): ParsedEmbedParams { + let _searchParams: URLSearchParams; + if (typeof searchParams === 'string') { + _searchParams = new URLSearchParams(searchParams); + } else if (searchParams instanceof URLSearchParams) { + _searchParams = searchParams; + } else { + throw new Error('searchParams must be URLSearchParams or string'); + } + + function convertBoolean(value: string | null): boolean | undefined { + if (value === 'true') { + return true; + } else if (value === 'false') { + return false; + } + return undefined; + } + + function convertNumber(value: string | null): number | undefined { + if (value != null && !isNaN(Number(value))) { + return Number(value); + } + return undefined; + } + + function convertColorMode(value: string | null): 'light' | 'dark' | undefined { + if (value != null && ['light', 'dark'].includes(value)) { + return value as 'light' | 'dark'; + } + return undefined; + } + + return { + maxHeight: convertNumber(_searchParams.get('maxHeight')) ?? defaultEmbedParams.maxHeight, + colorMode: convertColorMode(_searchParams.get('colorMode')) ?? defaultEmbedParams.colorMode, + rounded: convertBoolean(_searchParams.get('rounded')) ?? defaultEmbedParams.rounded, + border: convertBoolean(_searchParams.get('border')) ?? defaultEmbedParams.border, + autoload: convertBoolean(_searchParams.get('autoload')) ?? defaultEmbedParams.autoload, + header: convertBoolean(_searchParams.get('header')) ?? defaultEmbedParams.header, + }; +} diff --git a/packages/frontend/src/scripts/emoji-base.ts b/packages/frontend-shared/js/emoji-base.ts similarity index 87% rename from packages/frontend/src/scripts/emoji-base.ts rename to packages/frontend-shared/js/emoji-base.ts index a01540a3e4..5fbbc4ea84 100644 --- a/packages/frontend/src/scripts/emoji-base.ts +++ b/packages/frontend-shared/js/emoji-base.ts @@ -19,7 +19,7 @@ export function char2fluentEmojiFilePath(char: string): string { // Fluent Emojiは国旗非対応 https://github.com/microsoft/fluentui-emoji/issues/25 if (codes[0]?.startsWith('1f1')) return char2twemojiFilePath(char); if (!codes.includes('200d')) codes = codes.filter(x => x !== 'fe0f'); - codes = codes.filter(x => x && x.length); - const fileName = codes.map(x => x!.padStart(4, '0')).join('-'); + codes = codes.filter(x => x != null && x.length > 0); + const fileName = (codes as string[]).map(x => x.padStart(4, '0')).join('-'); return `${fluentEmojiPngBase}/${fileName}.png`; } diff --git a/packages/frontend/src/emojilist.json b/packages/frontend-shared/js/emojilist.json similarity index 100% rename from packages/frontend/src/emojilist.json rename to packages/frontend-shared/js/emojilist.json diff --git a/packages/frontend/src/scripts/emojilist.ts b/packages/frontend-shared/js/emojilist.ts similarity index 96% rename from packages/frontend/src/scripts/emojilist.ts rename to packages/frontend-shared/js/emojilist.ts index 6565feba97..bde30a864f 100644 --- a/packages/frontend/src/scripts/emojilist.ts +++ b/packages/frontend-shared/js/emojilist.ts @@ -12,12 +12,12 @@ export type UnicodeEmojiDef = { } // initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb -import _emojilist from '../emojilist.json'; +import _emojilist from './emojilist.json'; export const emojilist: UnicodeEmojiDef[] = _emojilist.map(x => ({ name: x[1] as string, char: x[0] as string, - category: unicodeEmojiCategories[x[2]], + category: unicodeEmojiCategories[x[2] as number], })); const unicodeEmojisMap = new Map<string, UnicodeEmojiDef>( diff --git a/packages/frontend/src/scripts/extract-avg-color-from-blurhash.ts b/packages/frontend-shared/js/extract-avg-color-from-blurhash.ts similarity index 100% rename from packages/frontend/src/scripts/extract-avg-color-from-blurhash.ts rename to packages/frontend-shared/js/extract-avg-color-from-blurhash.ts diff --git a/packages/frontend/src/scripts/i18n.ts b/packages/frontend-shared/js/i18n.ts similarity index 91% rename from packages/frontend/src/scripts/i18n.ts rename to packages/frontend-shared/js/i18n.ts index b258a2a678..18232691fa 100644 --- a/packages/frontend/src/scripts/i18n.ts +++ b/packages/frontend-shared/js/i18n.ts @@ -2,7 +2,10 @@ * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ -import type { ILocale, ParameterizedString } from '../../../../locales/index.js'; +import type { ILocale, ParameterizedString } from '../../../locales/index.js'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type TODO = any; type FlattenKeys<T extends ILocale, TPrediction> = keyof { [K in keyof T as T[K] extends ILocale @@ -32,15 +35,18 @@ type Tsx<T extends ILocale> = { export class I18n<T extends ILocale> { private tsxCache?: Tsx<T>; + private devMode: boolean; + + constructor(public locale: T, devMode = false) { + this.devMode = devMode; - constructor(public locale: T) { //#region BIND this.t = this.t.bind(this); //#endregion } public get ts(): T { - if (_DEV_) { + if (this.devMode) { class Handler<TTarget extends ILocale> implements ProxyHandler<TTarget> { get(target: TTarget, p: string | symbol): unknown { const value = target[p as keyof TTarget]; @@ -72,7 +78,7 @@ export class I18n<T extends ILocale> { } public get tsx(): Tsx<T> { - if (_DEV_) { + if (this.devMode) { if (this.tsxCache) { return this.tsxCache; } @@ -113,7 +119,7 @@ export class I18n<T extends ILocale> { return () => value; } - return (arg) => { + return (arg: TODO) => { let str = quasis[0]; for (let i = 0; i < expressions.length; i++) { @@ -152,7 +158,7 @@ export class I18n<T extends ILocale> { const value = target[k as keyof typeof target]; if (typeof value === 'object') { - result[k] = build(value as ILocale); + (result as TODO)[k] = build(value as ILocale); } else if (typeof value === 'string') { const quasis: string[] = []; const expressions: string[] = []; @@ -179,7 +185,7 @@ export class I18n<T extends ILocale> { continue; } - result[k] = (arg) => { + (result as TODO)[k] = (arg: TODO) => { let str = quasis[0]; for (let i = 0; i < expressions.length; i++) { @@ -208,9 +214,9 @@ export class I18n<T extends ILocale> { let str: string | ParameterizedString | ILocale = this.locale; for (const k of key.split('.')) { - str = str[k]; + str = (str as TODO)[k]; - if (_DEV_) { + if (this.devMode) { if (typeof str === 'undefined') { console.error(`Unexpected locale key: ${key}`); return key; @@ -219,7 +225,7 @@ export class I18n<T extends ILocale> { } if (args) { - if (_DEV_) { + if (this.devMode) { const missing = Array.from((str as string).matchAll(/\{(\w+)\}/g), ([, parameter]) => parameter).filter(parameter => !Object.hasOwn(args, parameter)); if (missing.length) { @@ -230,7 +236,7 @@ export class I18n<T extends ILocale> { for (const [k, v] of Object.entries(args)) { const search = `{${k}}`; - if (_DEV_) { + if (this.devMode) { if (!(str as string).includes(search)) { console.error(`Unexpected locale parameter: ${k} at ${key}`); } diff --git a/packages/frontend-shared/js/intl-const.ts b/packages/frontend-shared/js/intl-const.ts new file mode 100644 index 0000000000..33b65b6e9b --- /dev/null +++ b/packages/frontend-shared/js/intl-const.ts @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { lang } from '@@/js/config.js'; + +// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition +export const versatileLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP'); + +let _dateTimeFormat: Intl.DateTimeFormat; +try { + _dateTimeFormat = new Intl.DateTimeFormat(versatileLang, { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + }); +} catch (err) { + console.warn(err); + if (_DEV_) console.log('[Intl] Fallback to en-US'); + + // Fallback to en-US + _dateTimeFormat = new Intl.DateTimeFormat('en-US', { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + }); +} +export const dateTimeFormat = _dateTimeFormat; + +export const timeZone = dateTimeFormat.resolvedOptions().timeZone; + +export const hemisphere = /^(australia|pacific|antarctica|indian)\//i.test(timeZone) ? 'S' : 'N'; + +let _numberFormat: Intl.NumberFormat; +try { + _numberFormat = new Intl.NumberFormat(versatileLang); +} catch (err) { + console.warn(err); + if (_DEV_) console.log('[Intl] Fallback to en-US'); + + // Fallback to en-US + _numberFormat = new Intl.NumberFormat('en-US'); +} +export const numberFormat = _numberFormat; diff --git a/packages/frontend-shared/js/is-link.ts b/packages/frontend-shared/js/is-link.ts new file mode 100644 index 0000000000..946f86400e --- /dev/null +++ b/packages/frontend-shared/js/is-link.ts @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export function isLink(el: HTMLElement) { + if (el.tagName === 'A') return true; + if (el.parentElement) { + return isLink(el.parentElement); + } + return false; +} diff --git a/packages/frontend-shared/js/media-proxy.ts b/packages/frontend-shared/js/media-proxy.ts new file mode 100644 index 0000000000..2837870c9a --- /dev/null +++ b/packages/frontend-shared/js/media-proxy.ts @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as Misskey from 'misskey-js'; +import { query } from './url.js'; + +export class MediaProxy { + private serverMetadata: Misskey.entities.MetaDetailed; + private url: string; + + constructor(serverMetadata: Misskey.entities.MetaDetailed, url: string) { + this.serverMetadata = serverMetadata; + this.url = url; + } + + public getProxiedImageUrl(imageUrl: string, type?: 'preview' | 'emoji' | 'avatar', mustOrigin = false, noFallback = false): string { + const localProxy = `${this.url}/proxy`; + let _imageUrl = imageUrl; + + if (imageUrl.startsWith(this.serverMetadata.mediaProxy + '/') || imageUrl.startsWith('/proxy/') || imageUrl.startsWith(localProxy + '/')) { + // もう既にproxyっぽそうだったらurlを取り出す + _imageUrl = (new URL(imageUrl)).searchParams.get('url') ?? imageUrl; + } + + return `${mustOrigin ? localProxy : this.serverMetadata.mediaProxy}/${ + type === 'preview' ? 'preview.webp' + : 'image.webp' + }?${query({ + url: _imageUrl, + ...(!noFallback ? { 'fallback': '1' } : {}), + ...(type ? { [type]: '1' } : {}), + ...(mustOrigin ? { origin: '1' } : {}), + })}`; + } + + public getProxiedImageUrlNullable(imageUrl: string | null | undefined, type?: 'preview'): string | null { + if (imageUrl == null) return null; + return this.getProxiedImageUrl(imageUrl, type); + } + + public getStaticImageUrl(baseUrl: string): string { + const u = baseUrl.startsWith('http') ? new URL(baseUrl) : new URL(baseUrl, this.url); + + if (u.href.startsWith(`${this.url}/emoji/`)) { + // もう既にemojiっぽそうだったらsearchParams付けるだけ + u.searchParams.set('static', '1'); + return u.href; + } + + if (u.href.startsWith(this.serverMetadata.mediaProxy + '/')) { + // もう既にproxyっぽそうだったらsearchParams付けるだけ + u.searchParams.set('static', '1'); + return u.href; + } + + return `${this.serverMetadata.mediaProxy}/static.webp?${query({ + url: u.href, + static: '1', + })}`; + } +} diff --git a/packages/frontend/src/scripts/scroll.ts b/packages/frontend-shared/js/scroll.ts similarity index 82% rename from packages/frontend/src/scripts/scroll.ts rename to packages/frontend-shared/js/scroll.ts index f0274034b5..4f2e9105c3 100644 --- a/packages/frontend/src/scripts/scroll.ts +++ b/packages/frontend-shared/js/scroll.ts @@ -36,19 +36,27 @@ export function getScrollPosition(el: HTMLElement | null): number { return container == null ? window.scrollY : container.scrollTop; } -export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false) { +export function onScrollTop(el: HTMLElement, cb: (topVisible: boolean) => unknown, tolerance = 1, once = false) { // とりあえず評価してみる - if (el.isConnected && isTopVisible(el)) { - cb(); + const firstTopVisible = isTopVisible(el); + if (el.isConnected && firstTopVisible) { + cb(firstTopVisible); if (once) return null; } const container = getScrollContainer(el) ?? window; - const onScroll = ev => { + // 以下のケースにおいて、cbが何度も呼び出されてしまって具合が悪いので1回呼んだら以降は無視するようにする + // - スクロールイベントは1回のスクロールで複数回発生することがある + // - toleranceの範囲内に収まる程度の微量なスクロールが発生した + let prevTopVisible = firstTopVisible; + const onScroll = () => { if (!document.body.contains(el)) return; - if (isTopVisible(el, tolerance)) { - cb(); + + const topVisible = isTopVisible(el, tolerance); + if (topVisible !== prevTopVisible) { + prevTopVisible = topVisible; + cb(topVisible); if (once) removeListener(); } }; @@ -69,7 +77,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1 } const containerOrWindow = container ?? window; - const onScroll = ev => { + const onScroll = () => { if (!document.body.contains(el)) return; if (isBottomVisible(el, 1, container)) { cb(); @@ -126,6 +134,7 @@ export function scrollToBottom( export function isTopVisible(el: HTMLElement, tolerance = 1): boolean { const scrollTop = getScrollPosition(el); + if (_DEV_) console.log(scrollTop, tolerance, scrollTop <= tolerance); return scrollTop <= tolerance; } diff --git a/packages/frontend/src/scripts/url.ts b/packages/frontend-shared/js/url.ts similarity index 70% rename from packages/frontend/src/scripts/url.ts rename to packages/frontend-shared/js/url.ts index 5a8265af9e..eb830b1eea 100644 --- a/packages/frontend/src/scripts/url.ts +++ b/packages/frontend-shared/js/url.ts @@ -8,18 +8,18 @@ * 2. プロパティがundefinedの時はクエリを付けない * (new URLSearchParams(obj)ではそこまで丁寧なことをしてくれない) */ -export function query(obj: Record<string, any>): string { +export function query(obj: Record<string, string | number | boolean>): string { const params = Object.entries(obj) - .filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined) - .reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>); + .filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined) // eslint-disable-line @typescript-eslint/no-unnecessary-condition + .reduce<Record<string, string | number | boolean>>((a, [k, v]) => (a[k] = v, a), {}); return Object.entries(params) .map((p) => `${p[0]}=${encodeURIComponent(p[1])}`) .join('&'); } -export function appendQuery(url: string, query: string): string { - return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${query}`; +export function appendQuery(url: string, queryString: string): string { + return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${queryString}`; } export function extractDomain(url: string) { diff --git a/packages/frontend/src/scripts/use-document-visibility.ts b/packages/frontend-shared/js/use-document-visibility.ts similarity index 85% rename from packages/frontend/src/scripts/use-document-visibility.ts rename to packages/frontend-shared/js/use-document-visibility.ts index a8f4d5e03a..b1197e68da 100644 --- a/packages/frontend/src/scripts/use-document-visibility.ts +++ b/packages/frontend-shared/js/use-document-visibility.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { onMounted, onUnmounted, ref, Ref } from 'vue'; +import { onMounted, onUnmounted, ref } from 'vue'; +import type { Ref } from 'vue'; export function useDocumentVisibility(): Ref<DocumentVisibilityState> { const visibility = ref(document.visibilityState); diff --git a/packages/frontend/src/scripts/use-interval.ts b/packages/frontend-shared/js/use-interval.ts similarity index 100% rename from packages/frontend/src/scripts/use-interval.ts rename to packages/frontend-shared/js/use-interval.ts diff --git a/packages/frontend/src/scripts/worker-multi-dispatch.ts b/packages/frontend-shared/js/worker-multi-dispatch.ts similarity index 84% rename from packages/frontend/src/scripts/worker-multi-dispatch.ts rename to packages/frontend-shared/js/worker-multi-dispatch.ts index 6b3fcd9383..5d393ed1ed 100644 --- a/packages/frontend/src/scripts/worker-multi-dispatch.ts +++ b/packages/frontend-shared/js/worker-multi-dispatch.ts @@ -3,16 +3,18 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -function defaultUseWorkerNumber(prev: number, totalWorkers: number) { +function defaultUseWorkerNumber(prev: number) { return prev + 1; } -export class WorkerMultiDispatch<POST = any, RETURN = any> { +type WorkerNumberGetter = (prev: number, totalWorkers: number) => number; + +export class WorkerMultiDispatch<POST = unknown, RETURN = unknown> { private symbol = Symbol('WorkerMultiDispatch'); private workers: Worker[] = []; private terminated = false; private prevWorkerNumber = 0; - private getUseWorkerNumber = defaultUseWorkerNumber; + private getUseWorkerNumber: WorkerNumberGetter; private finalizationRegistry: FinalizationRegistry<symbol>; constructor(workerConstructor: () => Worker, concurrency: number, getUseWorkerNumber = defaultUseWorkerNumber) { @@ -29,7 +31,7 @@ export class WorkerMultiDispatch<POST = any, RETURN = any> { if (_DEV_) console.log('WorkerMultiDispatch: Created', this); } - public postMessage(message: POST, options?: Transferable[] | StructuredSerializeOptions, useWorkerNumber: typeof defaultUseWorkerNumber = this.getUseWorkerNumber) { + public postMessage(message: POST, options?: Transferable[] | StructuredSerializeOptions, useWorkerNumber: WorkerNumberGetter = this.getUseWorkerNumber) { let workerNumber = useWorkerNumber(this.prevWorkerNumber, this.workers.length); workerNumber = Math.abs(Math.round(workerNumber)) % this.workers.length; if (_DEV_) console.log('WorkerMultiDispatch: Posting message to worker', workerNumber, useWorkerNumber); @@ -46,12 +48,14 @@ export class WorkerMultiDispatch<POST = any, RETURN = any> { return workerNumber; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any public addListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) { this.workers.forEach(worker => { worker.addEventListener('message', callback, options); }); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any public removeListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) { this.workers.forEach(worker => { worker.removeEventListener('message', callback, options); diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json new file mode 100644 index 0000000000..9981d10dd2 --- /dev/null +++ b/packages/frontend-shared/package.json @@ -0,0 +1,39 @@ +{ + "name": "frontend-shared", + "type": "module", + "main": "./js-built/index.js", + "types": "./js-built/index.d.ts", + "exports": { + ".": { + "import": "./js-built/index.js", + "types": "./js-built/index.d.ts" + }, + "./*": { + "import": "./js-built/*", + "types": "./js-built/*" + } + }, + "scripts": { + "build": "node ./build.js", + "watch": "nodemon -w package.json -e json --exec \"node ./build.js --watch\"", + "eslint": "eslint './**/*.{js,jsx,ts,tsx}'", + "typecheck": "tsc --noEmit", + "lint": "pnpm typecheck && pnpm eslint" + }, + "devDependencies": { + "@types/node": "20.14.12", + "@typescript-eslint/eslint-plugin": "7.17.0", + "@typescript-eslint/parser": "7.17.0", + "esbuild": "0.23.0", + "eslint-plugin-vue": "9.27.0", + "typescript": "5.5.4", + "vue-eslint-parser": "9.4.3" + }, + "files": [ + "js-built" + ], + "dependencies": { + "misskey-js": "workspace:*", + "vue": "3.4.37" + } +} diff --git a/packages/frontend/src/themes/_dark.json5 b/packages/frontend-shared/themes/_dark.json5 similarity index 100% rename from packages/frontend/src/themes/_dark.json5 rename to packages/frontend-shared/themes/_dark.json5 diff --git a/packages/frontend/src/themes/_light.json5 b/packages/frontend-shared/themes/_light.json5 similarity index 100% rename from packages/frontend/src/themes/_light.json5 rename to packages/frontend-shared/themes/_light.json5 diff --git a/packages/frontend/src/themes/d-astro.json5 b/packages/frontend-shared/themes/d-astro.json5 similarity index 100% rename from packages/frontend/src/themes/d-astro.json5 rename to packages/frontend-shared/themes/d-astro.json5 diff --git a/packages/frontend/src/themes/d-botanical.json5 b/packages/frontend-shared/themes/d-botanical.json5 similarity index 100% rename from packages/frontend/src/themes/d-botanical.json5 rename to packages/frontend-shared/themes/d-botanical.json5 diff --git a/packages/frontend/src/themes/d-cherry.json5 b/packages/frontend-shared/themes/d-cherry.json5 similarity index 100% rename from packages/frontend/src/themes/d-cherry.json5 rename to packages/frontend-shared/themes/d-cherry.json5 diff --git a/packages/frontend/src/themes/d-dark.json5 b/packages/frontend-shared/themes/d-dark.json5 similarity index 100% rename from packages/frontend/src/themes/d-dark.json5 rename to packages/frontend-shared/themes/d-dark.json5 diff --git a/packages/frontend/src/themes/d-future.json5 b/packages/frontend-shared/themes/d-future.json5 similarity index 100% rename from packages/frontend/src/themes/d-future.json5 rename to packages/frontend-shared/themes/d-future.json5 diff --git a/packages/frontend/src/themes/d-green-lime.json5 b/packages/frontend-shared/themes/d-green-lime.json5 similarity index 100% rename from packages/frontend/src/themes/d-green-lime.json5 rename to packages/frontend-shared/themes/d-green-lime.json5 diff --git a/packages/frontend/src/themes/d-green-orange.json5 b/packages/frontend-shared/themes/d-green-orange.json5 similarity index 100% rename from packages/frontend/src/themes/d-green-orange.json5 rename to packages/frontend-shared/themes/d-green-orange.json5 diff --git a/packages/frontend/src/themes/d-ice.json5 b/packages/frontend-shared/themes/d-ice.json5 similarity index 100% rename from packages/frontend/src/themes/d-ice.json5 rename to packages/frontend-shared/themes/d-ice.json5 diff --git a/packages/frontend/src/themes/d-persimmon.json5 b/packages/frontend-shared/themes/d-persimmon.json5 similarity index 100% rename from packages/frontend/src/themes/d-persimmon.json5 rename to packages/frontend-shared/themes/d-persimmon.json5 diff --git a/packages/frontend/src/themes/d-u0.json5 b/packages/frontend-shared/themes/d-u0.json5 similarity index 100% rename from packages/frontend/src/themes/d-u0.json5 rename to packages/frontend-shared/themes/d-u0.json5 diff --git a/packages/frontend/src/themes/l-apricot.json5 b/packages/frontend-shared/themes/l-apricot.json5 similarity index 100% rename from packages/frontend/src/themes/l-apricot.json5 rename to packages/frontend-shared/themes/l-apricot.json5 diff --git a/packages/frontend/src/themes/l-botanical.json5 b/packages/frontend-shared/themes/l-botanical.json5 similarity index 100% rename from packages/frontend/src/themes/l-botanical.json5 rename to packages/frontend-shared/themes/l-botanical.json5 diff --git a/packages/frontend/src/themes/l-cherry.json5 b/packages/frontend-shared/themes/l-cherry.json5 similarity index 100% rename from packages/frontend/src/themes/l-cherry.json5 rename to packages/frontend-shared/themes/l-cherry.json5 diff --git a/packages/frontend/src/themes/l-coffee.json5 b/packages/frontend-shared/themes/l-coffee.json5 similarity index 100% rename from packages/frontend/src/themes/l-coffee.json5 rename to packages/frontend-shared/themes/l-coffee.json5 diff --git a/packages/frontend/src/themes/l-light.json5 b/packages/frontend-shared/themes/l-light.json5 similarity index 100% rename from packages/frontend/src/themes/l-light.json5 rename to packages/frontend-shared/themes/l-light.json5 diff --git a/packages/frontend/src/themes/l-rainy.json5 b/packages/frontend-shared/themes/l-rainy.json5 similarity index 100% rename from packages/frontend/src/themes/l-rainy.json5 rename to packages/frontend-shared/themes/l-rainy.json5 diff --git a/packages/frontend/src/themes/l-sushi.json5 b/packages/frontend-shared/themes/l-sushi.json5 similarity index 100% rename from packages/frontend/src/themes/l-sushi.json5 rename to packages/frontend-shared/themes/l-sushi.json5 diff --git a/packages/frontend/src/themes/l-u0.json5 b/packages/frontend-shared/themes/l-u0.json5 similarity index 100% rename from packages/frontend/src/themes/l-u0.json5 rename to packages/frontend-shared/themes/l-u0.json5 diff --git a/packages/frontend/src/themes/l-vivid.json5 b/packages/frontend-shared/themes/l-vivid.json5 similarity index 100% rename from packages/frontend/src/themes/l-vivid.json5 rename to packages/frontend-shared/themes/l-vivid.json5 diff --git a/packages/frontend-shared/tsconfig.json b/packages/frontend-shared/tsconfig.json new file mode 100644 index 0000000000..09a8ff76aa --- /dev/null +++ b/packages/frontend-shared/tsconfig.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "ES2022", + "module": "nodenext", + "moduleResolution": "nodenext", + "declaration": true, + "declarationMap": true, + "sourceMap": false, + "outDir": "./js-built/", + "removeComments": true, + "resolveJsonModule": true, + "strict": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "experimentalDecorators": true, + "noImplicitReturns": true, + "esModuleInterop": true, + "baseUrl": ".", + "paths": { + "@/*": ["./*"], + "@@/*": ["./*"] + }, + "typeRoots": [ + "./@types", + "./node_modules/@types" + ], + "lib": [ + "esnext", + "dom" + ] + }, + "include": [ + "@types/**/*.ts", + "js/**/*" + ], + "exclude": [ + "node_modules", + "test/**/*" + ] +} diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx index 490a441b70..42d1a10f0a 100644 --- a/packages/frontend/.storybook/generate.tsx +++ b/packages/frontend/.storybook/generate.tsx @@ -405,8 +405,9 @@ function toStories(component: string): Promise<string> { glob('src/components/MkUserSetupDialog.*.vue'), glob('src/components/MkInstanceCardMini.vue'), glob('src/components/MkInviteCode.vue'), - glob('src/pages/search.vue'), + glob('src/pages/admin/overview.ap-requests.vue'), glob('src/pages/user/home.vue'), + glob('src/pages/search.vue'), ]); const components = globs.flat(); await Promise.all(components.map(async (component) => { diff --git a/packages/frontend/.storybook/preload-theme.ts b/packages/frontend/.storybook/preload-theme.ts index fb93d7be13..e5573f2ac3 100644 --- a/packages/frontend/.storybook/preload-theme.ts +++ b/packages/frontend/.storybook/preload-theme.ts @@ -30,7 +30,7 @@ const keys = [ 'd-u0', ] -await Promise.all(keys.map((key) => readFile(new URL(`../src/themes/${key}.json5`, import.meta.url), 'utf8'))).then((sources) => { +await Promise.all(keys.map((key) => readFile(new URL(`../../frontend-shared/themes/${key}.json5`, import.meta.url), 'utf8'))).then((sources) => { writeFile( new URL('./themes.ts', import.meta.url), `export default ${JSON.stringify( diff --git a/packages/frontend/@types/theme.d.ts b/packages/frontend/@types/theme.d.ts index 0a7281898d..70afc356c1 100644 --- a/packages/frontend/@types/theme.d.ts +++ b/packages/frontend/@types/theme.d.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -declare module '@/themes/*.json5' { +declare module '@@/themes/*.json5' { import { Theme } from '@/scripts/theme.js'; const theme: Theme; diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 1464be18a7..76af417550 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -17,7 +17,7 @@ "lint": "pnpm typecheck && pnpm eslint" }, "dependencies": { - "@discordapp/twemoji": "15.0.3", + "@discordapp/twemoji": "15.1.0", "@github/webauthn-json": "2.1.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@misskey-dev/browser-image-resizer": "2024.1.0", @@ -27,21 +27,21 @@ "@syuilo/aiscript": "0.19.0", "@tabler/icons-webfont": "3.3.0", "@twemoji/parser": "15.1.1", - "@vitejs/plugin-vue": "5.1.0", - "@vue/compiler-sfc": "3.4.37", + "@vitejs/plugin-vue": "5.1.4", + "@vue/compiler-sfc": "3.5.7", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.11", - "astring": "1.8.6", + "astring": "1.9.0", "broadcast-channel": "7.0.0", "buraha": "0.0.1", "canvas-confetti": "1.9.3", - "chart.js": "4.4.3", + "chart.js": "4.4.4", "chartjs-adapter-date-fns": "3.0.0", "chartjs-chart-matrix": "2.0.1", "chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-zoom": "2.0.1", - "chromatic": "11.5.6", + "chromatic": "11.10.2", "compare-versions": "6.1.1", - "cropperjs": "2.0.0-rc.1", + "cropperjs": "2.0.0-rc.2", "date-fns": "2.30.0", "escape-regexp": "0.0.1", "estree-walker": "3.0.3", @@ -55,87 +55,88 @@ "misskey-bubble-game": "workspace:*", "misskey-js": "workspace:*", "misskey-reversi": "workspace:*", + "frontend-shared": "workspace:*", "photoswipe": "5.4.4", "punycode": "2.3.1", - "rollup": "4.19.1", + "rollup": "4.22.2", "sanitize-html": "2.13.0", - "sass": "1.77.8", + "sass": "1.79.3", "shiki": "1.12.0", "strict-event-emitter-types": "2.0.0", "textarea-caret": "3.1.0", - "three": "0.167.0", + "three": "0.168.0", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", "tsc-alias": "1.8.10", "tsconfig-paths": "4.2.0", - "typescript": "5.5.4", + "typescript": "5.6.2", "uuid": "10.0.0", - "v-code-diff": "1.12.0", - "vite": "5.3.5", - "vue": "3.4.37", + "v-code-diff": "1.13.1", + "vite": "5.4.7", + "vue": "3.5.7", "vuedraggable": "next" }, "devDependencies": { "@misskey-dev/summaly": "5.1.0", - "@storybook/addon-actions": "8.2.6", - "@storybook/addon-essentials": "8.2.6", - "@storybook/addon-interactions": "8.2.6", - "@storybook/addon-links": "8.2.6", - "@storybook/addon-mdx-gfm": "8.2.6", - "@storybook/addon-storysource": "8.2.6", - "@storybook/blocks": "8.2.6", - "@storybook/components": "8.2.6", - "@storybook/core-events": "8.2.6", - "@storybook/manager-api": "8.2.6", - "@storybook/preview-api": "8.2.6", - "@storybook/react": "8.2.6", - "@storybook/react-vite": "8.2.6", - "@storybook/test": "8.2.6", - "@storybook/theming": "8.2.6", - "@storybook/types": "8.2.6", - "@storybook/vue3": "8.2.6", - "@storybook/vue3-vite": "8.1.11", + "@storybook/addon-actions": "8.3.2", + "@storybook/addon-essentials": "8.3.2", + "@storybook/addon-interactions": "8.3.2", + "@storybook/addon-links": "8.3.2", + "@storybook/addon-mdx-gfm": "8.3.2", + "@storybook/addon-storysource": "8.3.2", + "@storybook/blocks": "8.3.2", + "@storybook/components": "8.3.2", + "@storybook/core-events": "8.3.2", + "@storybook/manager-api": "8.3.2", + "@storybook/preview-api": "8.3.2", + "@storybook/react": "8.3.2", + "@storybook/react-vite": "8.3.2", + "@storybook/test": "8.3.2", + "@storybook/theming": "8.3.2", + "@storybook/types": "8.3.2", + "@storybook/vue3": "8.3.2", + "@storybook/vue3-vite": "8.3.2", "@testing-library/vue": "8.1.0", "@types/escape-regexp": "0.0.3", - "@types/estree": "1.0.5", + "@types/estree": "1.0.6", "@types/matter-js": "0.19.7", "@types/micromatch": "4.0.9", "@types/node": "20.14.12", "@types/punycode": "2.1.4", - "@types/sanitize-html": "2.11.0", + "@types/sanitize-html": "2.13.0", "@types/seedrandom": "3.0.8", "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/uuid": "10.0.0", - "@types/ws": "8.5.11", + "@types/ws": "8.5.12", "@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/parser": "7.17.0", "@vitest/coverage-v8": "1.6.0", - "@vue/runtime-core": "3.4.37", + "@vue/runtime-core": "3.5.7", "acorn": "8.12.1", "cross-env": "7.0.3", - "cypress": "13.13.1", - "eslint-plugin-import": "2.29.1", - "eslint-plugin-vue": "9.27.0", + "cypress": "13.14.2", + "eslint-plugin-import": "2.30.0", + "eslint-plugin-vue": "9.28.0", "fast-glob": "3.3.2", "happy-dom": "10.0.3", "intersection-observer": "0.12.2", - "micromatch": "4.0.7", - "msw": "2.3.4", + "micromatch": "4.0.8", + "msw": "2.4.9", "msw-storybook-addon": "2.0.3", - "nodemon": "3.1.4", + "nodemon": "3.1.7", "prettier": "3.3.3", "react": "18.3.1", "react-dom": "18.3.1", "seedrandom": "3.0.5", - "start-server-and-test": "2.0.4", - "storybook": "8.2.6", + "start-server-and-test": "2.0.8", + "storybook": "8.3.2", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "vite-plugin-turbosnap": "1.0.3", "vitest": "1.6.0", "vitest-fetch-mock": "0.2.2", - "vue-component-type-helpers": "2.0.29", + "vue-component-type-helpers": "2.1.6", "vue-eslint-parser": "9.4.3", - "vue-tsc": "2.0.29" + "vue-tsc": "2.1.6" } } diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts index 4172016f89..84d89b1b3f 100644 --- a/packages/frontend/src/account.ts +++ b/packages/frontend/src/account.ts @@ -8,9 +8,9 @@ import * as Misskey from 'misskey-js'; import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js'; import { i18n } from '@/i18n.js'; import { miLocalStorage } from '@/local-storage.js'; -import { MenuButton } from '@/types/menu.js'; +import type { MenuItem, MenuButton } from '@/types/menu.js'; import { del, get, set } from '@/scripts/idb-proxy.js'; -import { apiUrl } from '@/config.js'; +import { apiUrl } from '@@/js/config.js'; import { waiting, popup, popupMenu, success, alert } from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { unisonReload, reloadChannel } from '@/scripts/unison-reload.js'; @@ -288,14 +288,26 @@ export async function openAccountMenu(opts: { }); })); + const menuItems: MenuItem[] = []; + if (opts.withExtraOperation) { - popupMenu([...[{ - type: 'link' as const, + menuItems.push({ + type: 'link', text: i18n.ts.profile, - to: `/@${ $i.username }`, + to: `/@${$i.username}`, avatar: $i, - }, { type: 'divider' as const }, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, { - type: 'parent' as const, + }, { + type: 'divider', + }); + + if (opts.includeCurrentAccount) { + menuItems.push(createItem($i)); + } + + menuItems.push(...accountItemPromises); + + menuItems.push({ + type: 'parent', icon: 'ti ti-plus', text: i18n.ts.addAccount, children: [{ @@ -306,18 +318,22 @@ export async function openAccountMenu(opts: { action: () => { createAccount(); }, }], }, { - type: 'link' as const, + type: 'link', icon: 'ti ti-users', text: i18n.ts.manageAccounts, to: '/settings/accounts', - }]], ev.currentTarget ?? ev.target, { - align: 'left', }); } else { - popupMenu([...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises], ev.currentTarget ?? ev.target, { - align: 'left', - }); + if (opts.includeCurrentAccount) { + menuItems.push(createItem($i)); + } + + menuItems.push(...accountItemPromises); } + + popupMenu(menuItems, ev.currentTarget ?? ev.target, { + align: 'left', + }); } if (_DEV_) { diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index d86ae18ffe..287788bc8e 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -8,7 +8,7 @@ import { compareVersions } from 'compare-versions'; import widgets from '@/widgets/index.js'; import directives from '@/directives/index.js'; import components from '@/components/index.js'; -import { version, lang, updateLocale, locale } from '@/config.js'; +import { version, lang, updateLocale, locale } from '@@/js/config.js'; import { applyTheme } from '@/scripts/theme.js'; import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js'; import { updateI18n } from '@/i18n.js'; @@ -22,7 +22,8 @@ import { getAccountFromId } from '@/scripts/get-account-from-id.js'; import { deckStore } from '@/ui/deck/deck-store.js'; import { miLocalStorage } from '@/local-storage.js'; import { fetchCustomEmojis } from '@/custom-emojis.js'; -import { setupRouter } from '@/router/definition.js'; +import { setupRouter } from '@/router/main.js'; +import { createMainRouter } from '@/router/definition.js'; export async function common(createVue: () => App<Element>) { console.info(`Misskey v${version}`); @@ -239,7 +240,7 @@ export async function common(createVue: () => App<Element>) { const app = createVue(); - setupRouter(app); + setupRouter(app, createMainRouter); if (_DEV_) { app.config.performance = true; diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 3e7c4f26f8..ddd47ca448 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -6,7 +6,7 @@ import { createApp, defineAsyncComponent, markRaw } from 'vue'; import { common } from './common.js'; import type * as Misskey from 'misskey-js'; -import { ui } from '@/config.js'; +import { ui } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { alert, confirm, popup, post, toast } from '@/os.js'; import { useStream } from '@/stream.js'; @@ -22,6 +22,7 @@ import { deckStore } from '@/ui/deck/deck-store.js'; import { emojiPicker } from '@/scripts/emoji-picker.js'; import { mainRouter } from '@/router/main.js'; import { type Keymap, makeHotkey } from '@/scripts/hotkey.js'; +import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js'; export async function mainBoot() { const { isClientUpdated } = await common(() => createApp( @@ -62,6 +63,18 @@ export async function mainBoot() { } }); + stream.on('emojiAdded', emojiData => { + addCustomEmoji(emojiData.emoji); + }); + + stream.on('emojiUpdated', emojiData => { + updateCustomEmojis(emojiData.emojis); + }); + + stream.on('emojiDeleted', emojiData => { + removeCustomEmojis(emojiData.emojis); + }); + for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) { import('@/plugin.js').then(async ({ install }) => { // Workaround for https://bugs.webkit.org/show_bug.cgi?id=242740 diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue index 6c0774b634..796524fce9 100644 --- a/packages/frontend/src/components/MkAccountMoved.vue +++ b/packages/frontend/src/components/MkAccountMoved.vue @@ -16,7 +16,7 @@ import { ref } from 'vue'; import * as Misskey from 'misskey-js'; import MkMention from './MkMention.vue'; import { i18n } from '@/i18n.js'; -import { host as localHost } from '@/config.js'; +import { host as localHost } from '@@/js/config.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; const user = ref<Misskey.entities.UserLite>(); diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 932c4ecb2e..f547991369 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -46,17 +46,17 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts"> import { markRaw, ref, shallowRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; import sanitizeHtml from 'sanitize-html'; +import { emojilist, getEmojiName } from '@@/js/emojilist.js'; import contains from '@/scripts/contains.js'; -import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base.js'; +import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@@/js/emoji-base.js'; import { acct } from '@/filters/user.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { defaultStore } from '@/store.js'; -import { emojilist, getEmojiName } from '@/scripts/emojilist.js'; import { i18n } from '@/i18n.js'; import { miLocalStorage } from '@/local-storage.js'; import { customEmojis } from '@/custom-emojis.js'; -import { MFM_TAGS, MFM_PARAMS } from '@/const.js'; +import { MFM_TAGS, MFM_PARAMS } from '@@/js/const.js'; import { searchEmoji, EmojiDef } from '@/scripts/search-emoji.js'; const lib = emojilist.filter(x => x.category !== 'flags'); diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index fb9c036fbc..aab5b8a4b2 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -246,7 +246,7 @@ function onMousedown(evt: MouseEvent): void { } &:disabled { - opacity: 0.7; + opacity: 0.5; } &:focus-visible { diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue index 4b24562249..57d325b11a 100644 --- a/packages/frontend/src/components/MkChart.vue +++ b/packages/frontend/src/components/MkChart.vue @@ -13,29 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </template> -<script lang="ts" setup> -/* eslint-disable id-denylist -- - Chart.js has a `data` attribute in most chart definitions, which triggers the - id-denylist violation when setting it. This is causing about 60+ lint issues. - As this is part of Chart.js's API it makes sense to disable the check here. -*/ -import { onMounted, ref, shallowRef, watch } from 'vue'; -import { Chart } from 'chart.js'; -import * as Misskey from 'misskey-js'; -import { misskeyApiGet } from '@/scripts/misskey-api.js'; -import { defaultStore } from '@/store.js'; -import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; -import { chartVLine } from '@/scripts/chart-vline.js'; -import { alpha } from '@/scripts/color.js'; -import date from '@/filters/date.js'; -import bytes from '@/filters/bytes.js'; -import { initChart } from '@/scripts/init-chart.js'; -import { chartLegend } from '@/scripts/chart-legend.js'; -import MkChartLegend from '@/components/MkChartLegend.vue'; - -initChart(); - -type ChartSrc = +<script lang="ts"> +export type ChartSrc = | 'federation' | 'ap-request' | 'users' @@ -62,7 +41,30 @@ type ChartSrc = | 'per-user-pv' | 'per-user-following' | 'per-user-followers' - | 'per-user-drive' + | 'per-user-drive'; +</script> + +<script lang="ts" setup> +/* eslint-disable id-denylist -- + Chart.js has a `data` attribute in most chart definitions, which triggers the + id-denylist violation when setting it. This is causing about 60+ lint issues. + As this is part of Chart.js's API it makes sense to disable the check here. +*/ +import { onMounted, ref, shallowRef, watch } from 'vue'; +import { Chart } from 'chart.js'; +import * as Misskey from 'misskey-js'; +import { misskeyApiGet } from '@/scripts/misskey-api.js'; +import { defaultStore } from '@/store.js'; +import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; +import { chartVLine } from '@/scripts/chart-vline.js'; +import { alpha } from '@/scripts/color.js'; +import date from '@/filters/date.js'; +import bytes from '@/filters/bytes.js'; +import { initChart } from '@/scripts/init-chart.js'; +import { chartLegend } from '@/scripts/chart-legend.js'; +import MkChartLegend from '@/components/MkChartLegend.vue'; + +initChart(); const props = withDefaults(defineProps<{ src: ChartSrc; diff --git a/packages/frontend/src/components/MkClickerGame.vue b/packages/frontend/src/components/MkClickerGame.vue index 00506fb735..9a0a9fba05 100644 --- a/packages/frontend/src/components/MkClickerGame.vue +++ b/packages/frontend/src/components/MkClickerGame.vue @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, onMounted, onUnmounted, ref } from 'vue'; import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue'; import * as os from '@/os.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import * as game from '@/scripts/clicker-game.js'; import number from '@/filters/number.js'; import { claimAchievement } from '@/scripts/achievements.js'; diff --git a/packages/frontend/src/components/MkCode.vue b/packages/frontend/src/components/MkCode.vue index 1d4c0b6366..716dd92678 100644 --- a/packages/frontend/src/components/MkCode.vue +++ b/packages/frontend/src/components/MkCode.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div :class="$style.codeBlockRoot"> - <button :class="$style.codeBlockCopyButton" class="_button" @click="copy"> + <button v-if="copyButton" :class="$style.codeBlockCopyButton" class="_button" @click="copy"> <i class="ti ti-copy"></i> </button> <Suspense> @@ -32,12 +32,17 @@ import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; -const props = defineProps<{ +const props = withDefaults(defineProps<{ code: string; + forceShow?: boolean; + copyButton?: boolean; lang?: string; -}>(); +}>(), { + copyButton: true, + forceShow: false, +}); -const show = ref(!defaultStore.state.dataSaver.code); +const show = ref(props.forceShow === true ? true : !defaultStore.state.dataSaver.code); const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue')); diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue index 8ea8fa6cf3..f51fefa0c0 100644 --- a/packages/frontend/src/components/MkContextMenu.vue +++ b/packages/frontend/src/components/MkContextMenu.vue @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue'; import MkMenu from './MkMenu.vue'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; import contains from '@/scripts/contains.js'; import { defaultStore } from '@/store.js'; import * as os from '@/os.js'; diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index 54f6f39c9d..2e1e92cbdf 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -39,7 +39,7 @@ import MkModalWindow from '@/components/MkModalWindow.vue'; import * as os from '@/os.js'; import { $i } from '@/account.js'; import { defaultStore } from '@/store.js'; -import { apiUrl } from '@/config.js'; +import { apiUrl } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { getProxiedImageUrl } from '@/scripts/media-proxy.js'; diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue index f16981716c..4b94bef4b6 100644 --- a/packages/frontend/src/components/MkDateSeparatedList.vue +++ b/packages/frontend/src/components/MkDateSeparatedList.vue @@ -43,9 +43,9 @@ export default defineComponent({ setup(props, { slots, expose }) { const $style = useCssModule(); // カスタムレンダラなので使っても大丈夫 - function getDateText(time: string) { - const date = new Date(time).getDate(); - const month = new Date(time).getMonth() + 1; + function getDateText(dateInstance: Date) { + const date = dateInstance.getDate(); + const month = dateInstance.getMonth() + 1; return i18n.tsx.monthAndDay({ month: month.toString(), day: date.toString(), @@ -62,9 +62,16 @@ export default defineComponent({ })[0]; if (el.key == null && item.id) el.key = item.id; + const date = new Date(item.createdAt); + const nextDate = props.items[i + 1] ? new Date(props.items[i + 1].createdAt) : null; + if ( i !== props.items.length - 1 && - new Date(item.createdAt).getDate() !== new Date(props.items[i + 1].createdAt).getDate() + nextDate != null && ( + date.getFullYear() !== nextDate.getFullYear() || + date.getMonth() !== nextDate.getMonth() || + date.getDate() !== nextDate.getDate() + ) ) { const separator = h('div', { class: $style['separator'], @@ -78,12 +85,12 @@ export default defineComponent({ h('i', { class: `ti ti-chevron-up ${$style['date-1-icon']}`, }), - getDateText(item.createdAt), + getDateText(date), ]), h('span', { class: $style['date-2'], }, [ - getDateText(props.items[i + 1].createdAt), + getDateText(nextDate), h('i', { class: `ti ti-chevron-down ${$style['date-2-icon']}`, }), diff --git a/packages/frontend/src/components/MkDonation.vue b/packages/frontend/src/components/MkDonation.vue index 434fc81582..098be07a8c 100644 --- a/packages/frontend/src/components/MkDonation.vue +++ b/packages/frontend/src/components/MkDonation.vue @@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import MkButton from '@/components/MkButton.vue'; import MkLink from '@/components/MkLink.vue'; -import { host } from '@/config.js'; +import { host } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { miLocalStorage } from '@/local-storage.js'; diff --git a/packages/frontend/src/components/MkDrive.folder.vue b/packages/frontend/src/components/MkDrive.folder.vue index d6dfaf34e5..92b3a23662 100644 --- a/packages/frontend/src/components/MkDrive.folder.vue +++ b/packages/frontend/src/components/MkDrive.folder.vue @@ -42,7 +42,7 @@ import { i18n } from '@/i18n.js'; import { defaultStore } from '@/store.js'; import { claimAchievement } from '@/scripts/achievements.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; const props = withDefaults(defineProps<{ folder: Misskey.entities.DriveFolder; diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue index dbb4917069..d9ca0a72a0 100644 --- a/packages/frontend/src/components/MkDrive.vue +++ b/packages/frontend/src/components/MkDrive.vue @@ -620,7 +620,9 @@ function fetchMoreFiles() { } function getMenu() { - const menu: MenuItem[] = [{ + const menu: MenuItem[] = []; + + menu.push({ type: 'switch', text: i18n.ts.keepOriginalUploading, ref: keepOriginal, @@ -638,19 +640,25 @@ function getMenu() { }, { type: 'divider' }, { text: folder.value ? folder.value.name : i18n.ts.drive, type: 'label', - }, folder.value ? { - text: i18n.ts.renameFolder, - icon: 'ti ti-forms', - action: () => { if (folder.value) renameFolder(folder.value); }, - } : undefined, folder.value ? { - text: i18n.ts.deleteFolder, - icon: 'ti ti-trash', - action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); }, - } : undefined, { + }); + + if (folder.value) { + menu.push({ + text: i18n.ts.renameFolder, + icon: 'ti ti-forms', + action: () => { if (folder.value) renameFolder(folder.value); }, + }, { + text: i18n.ts.deleteFolder, + icon: 'ti ti-trash', + action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); }, + }); + } + + menu.push({ text: i18n.ts.createFolder, icon: 'ti ti-folder-plus', action: () => { createFolder(); }, - }]; + }); return menu; } diff --git a/packages/frontend/src/components/MkDriveFileThumbnail.vue b/packages/frontend/src/components/MkDriveFileThumbnail.vue index 2c47a70970..eb93aaab6e 100644 --- a/packages/frontend/src/components/MkDriveFileThumbnail.vue +++ b/packages/frontend/src/components/MkDriveFileThumbnail.vue @@ -4,7 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div ref="thumbnail" :class="$style.root"> +<div + ref="thumbnail" + :class="[ + $style.root, + { [$style.sensitiveHighlight]: highlightWhenSensitive && file.isSensitive }, + ]" +> <ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :cover="fit !== 'contain'"/> <i v-else-if="is === 'image'" class="ti ti-photo" :class="$style.icon"></i> <i v-else-if="is === 'video'" class="ti ti-video" :class="$style.icon"></i> @@ -27,6 +33,7 @@ import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; const props = defineProps<{ file: Misskey.entities.DriveFile; fit: 'cover' | 'contain'; + highlightWhenSensitive?: boolean; }>(); const is = computed(() => { @@ -67,6 +74,18 @@ const isThumbnailAvailable = computed(() => { overflow: clip; } +.sensitiveHighlight::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + border-radius: inherit; + box-shadow: inset 0 0 0 4px var(--warn); +} + .iconSub { position: absolute; width: 30%; diff --git a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue new file mode 100644 index 0000000000..c060c3a659 --- /dev/null +++ b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue @@ -0,0 +1,414 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<MkModalWindow + ref="dialogEl" + :width="1000" + :height="600" + :scroll="false" + :withOkButton="false" + @close="cancel()" + @closed="$emit('closed')" +> + <template #header><i class="ti ti-code"></i> {{ i18n.ts._embedCodeGen.title }}</template> + + <div :class="$style.embedCodeGenRoot"> + <Transition + mode="out-in" + :enterActiveClass="$style.transition_x_enterActive" + :leaveActiveClass="$style.transition_x_leaveActive" + :enterFromClass="$style.transition_x_enterFrom" + :leaveToClass="$style.transition_x_leaveTo" + > + <div v-if="phase === 'input'" key="input" :class="$style.embedCodeGenInputRoot"> + <div + :class="$style.embedCodeGenPreviewRoot" + > + <MkLoading v-if="iframeLoading" :class="$style.embedCodeGenPreviewSpinner"/> + <div :class="$style.embedCodeGenPreviewWrapper"> + <div class="_acrylic" :class="$style.embedCodeGenPreviewTitle">{{ i18n.ts.preview }}</div> + <div ref="resizerRootEl" :class="$style.embedCodeGenPreviewResizerRoot" inert> + <div + :class="$style.embedCodeGenPreviewResizer" + :style="{ transform: iframeStyle }" + > + <iframe + ref="iframeEl" + :src="embedPreviewUrl" + :class="$style.embedCodeGenPreviewIframe" + :style="{ height: `${iframeHeight}px` }" + @load="iframeOnLoad" + ></iframe> + </div> + </div> + </div> + </div> + <div :class="$style.embedCodeGenSettings" class="_gaps"> + <MkInput v-if="isEmbedWithScrollbar" v-model="maxHeight" type="number" :min="0"> + <template #label>{{ i18n.ts._embedCodeGen.maxHeight }}</template> + <template #suffix>px</template> + <template #caption>{{ i18n.ts._embedCodeGen.maxHeightDescription }}</template> + </MkInput> + <MkSelect v-model="colorMode"> + <template #label>{{ i18n.ts.theme }}</template> + <option value="auto">{{ i18n.ts.syncDeviceDarkMode }}</option> + <option value="light">{{ i18n.ts.light }}</option> + <option value="dark">{{ i18n.ts.dark }}</option> + </MkSelect> + <MkSwitch v-if="isEmbedWithScrollbar" v-model="header">{{ i18n.ts._embedCodeGen.header }}</MkSwitch> + <MkSwitch v-model="rounded">{{ i18n.ts._embedCodeGen.rounded }}</MkSwitch> + <MkSwitch v-model="border">{{ i18n.ts._embedCodeGen.border }}</MkSwitch> + <MkInfo v-if="isEmbedWithScrollbar && (!maxHeight || maxHeight <= 0)" warn>{{ i18n.ts._embedCodeGen.maxHeightWarn }}</MkInfo> + <MkInfo v-if="typeof maxHeight === 'number' && (maxHeight <= 0 || maxHeight > 700)">{{ i18n.ts._embedCodeGen.previewIsNotActual }}</MkInfo> + <div class="_buttons"> + <MkButton :disabled="iframeLoading" @click="applyToPreview">{{ i18n.ts._embedCodeGen.applyToPreview }}</MkButton> + <MkButton :disabled="iframeLoading" primary @click="generate">{{ i18n.ts._embedCodeGen.generateCode }} <i class="ti ti-arrow-right"></i></MkButton> + </div> + </div> + </div> + <div v-else-if="phase === 'result'" key="result" :class="$style.embedCodeGenResultRoot"> + <div :class="$style.embedCodeGenResultWrapper" class="_gaps"> + <div class="_gaps_s"> + <div :class="$style.embedCodeGenResultHeadingIcon"><i class="ti ti-check"></i></div> + <div :class="$style.embedCodeGenResultHeading">{{ i18n.ts._embedCodeGen.codeGenerated }}</div> + <div :class="$style.embedCodeGenResultDescription">{{ i18n.ts._embedCodeGen.codeGeneratedDescription }}</div> + </div> + <div class="_gaps_s"> + <MkCode :code="result" lang="html" :forceShow="true" :copyButton="false" :class="$style.embedCodeGenResultCode"/> + <MkButton :class="$style.embedCodeGenResultButtons" rounded primary @click="doCopy"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton> + </div> + <MkButton :class="$style.embedCodeGenResultButtons" rounded transparent @click="close">{{ i18n.ts.close }}</MkButton> + </div> + </div> + </Transition> + </div> +</MkModalWindow> +</template> + +<script setup lang="ts"> +import { shallowRef, ref, computed, nextTick, onMounted, onDeactivated, onUnmounted } from 'vue'; +import { url } from '@@/js/config.js'; +import { embedRouteWithScrollbar } from '@@/js/embed-page.js'; +import type { EmbeddableEntity, EmbedParams } from '@@/js/embed-page.js'; +import MkModalWindow from '@/components/MkModalWindow.vue'; + +import MkInput from '@/components/MkInput.vue'; +import MkSelect from '@/components/MkSelect.vue'; +import MkSwitch from '@/components/MkSwitch.vue'; +import MkButton from '@/components/MkButton.vue'; + +import MkCode from '@/components/MkCode.vue'; +import MkInfo from '@/components/MkInfo.vue'; + +import * as os from '@/os.js'; +import { i18n } from '@/i18n.js'; +import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; +import { normalizeEmbedParams, getEmbedCode } from '@/scripts/get-embed-code.js'; + +const emit = defineEmits<{ + (ev: 'ok'): void; + (ev: 'cancel'): void; + (ev: 'closed'): void; +}>(); + +const props = defineProps<{ + entity: EmbeddableEntity; + id: string; + params?: EmbedParams; +}>(); + +//#region Modalの制御 +const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>(); + +function cancel() { + emit('cancel'); + dialogEl.value?.close(); +} + +function close() { + dialogEl.value?.close(); +} + +const phase = ref<'input' | 'result'>('input'); +//#endregion + +//#region 埋め込みURL生成・カスタマイズ + +// 本URL生成用params +const paramsForUrl = computed<EmbedParams>(() => ({ + header: header.value, + maxHeight: typeof maxHeight.value === 'number' ? Math.max(0, maxHeight.value) : undefined, + colorMode: colorMode.value === 'auto' ? undefined : colorMode.value, + rounded: rounded.value, + border: border.value, +})); + +// プレビュー用params(手動で更新を掛けるのでref) +const paramsForPreview = ref<EmbedParams>(props.params ?? {}); + +const embedPreviewUrl = computed(() => { + const paramClass = new URLSearchParams(normalizeEmbedParams(paramsForPreview.value)); + if (paramClass.has('maxHeight')) { + const maxHeight = parseInt(paramClass.get('maxHeight')!); + paramClass.set('maxHeight', maxHeight === 0 ? '500' : Math.min(maxHeight, 700).toString()); // プレビューであまりにも縮小されると見づらいため、700pxまでに制限 + } + return `${url}/embed/${props.entity}/${props.id}${paramClass.toString() ? '?' + paramClass.toString() : ''}`; +}); + +const isEmbedWithScrollbar = computed(() => embedRouteWithScrollbar.includes(props.entity)); +const header = ref(props.params?.header ?? true); +const maxHeight = ref(props.params?.maxHeight !== 0 ? props.params?.maxHeight ?? undefined : 500); + +const colorMode = ref<'light' | 'dark' | 'auto'>(props.params?.colorMode ?? 'auto'); +const rounded = ref(props.params?.rounded ?? true); +const border = ref(props.params?.border ?? true); + +function applyToPreview() { + const currentPreviewUrl = embedPreviewUrl.value; + + paramsForPreview.value = { + header: header.value, + maxHeight: typeof maxHeight.value === 'number' ? Math.max(0, maxHeight.value) : undefined, + colorMode: colorMode.value === 'auto' ? undefined : colorMode.value, + rounded: rounded.value, + border: border.value, + }; + + nextTick(() => { + if (currentPreviewUrl === embedPreviewUrl.value) { + // URLが変わらなくてもリロード + iframeEl.value?.contentWindow?.location.reload(); + } + }); +} + +const result = ref(''); + +function generate() { + result.value = getEmbedCode(`/embed/${props.entity}/${props.id}`, paramsForUrl.value); + phase.value = 'result'; +} + +function doCopy() { + copyToClipboard(result.value); + os.success(); +} +//#endregion + +//#region プレビューのリサイズ +const resizerRootEl = shallowRef<HTMLDivElement>(); +const iframeLoading = ref(true); +const iframeEl = shallowRef<HTMLIFrameElement>(); +const iframeHeight = ref(0); +const iframeScale = ref(1); +const iframeStyle = computed(() => { + return `translate(-50%, -50%) scale(${iframeScale.value})`; +}); +const resizeObserver = new ResizeObserver(() => { + calcScale(); +}); + +function iframeOnLoad() { + iframeEl.value?.contentWindow?.addEventListener('beforeunload', () => { + iframeLoading.value = true; + nextTick(() => { + iframeHeight.value = 0; + iframeScale.value = 1; + }); + }); +} + +function windowEventHandler(event: MessageEvent) { + if (event.source !== iframeEl.value?.contentWindow) { + return; + } + if (event.data.type === 'misskey:embed:ready') { + iframeEl.value!.contentWindow?.postMessage({ + type: 'misskey:embedParent:registerIframeId', + payload: { + iframeId: 'embedCodeGen', // 同じタイミングで複数のembed iframeがある際の区別用なのでここではなんでもいい + }, + }); + } + if (event.data.type === 'misskey:embed:changeHeight') { + iframeHeight.value = event.data.payload.height; + nextTick(() => { + calcScale(); + iframeLoading.value = false; // 初回の高さ変更まで待つ + }); + } +} + +function calcScale() { + if (!resizerRootEl.value) return; + const previewWidth = resizerRootEl.value.clientWidth - 40; // 左右の余白 20pxずつ + const previewHeight = resizerRootEl.value.clientHeight - 40; // 上下の余白 20pxずつ + const iframeWidth = 500; + const scale = Math.min(previewWidth / iframeWidth, previewHeight / iframeHeight.value, 1); // 拡大はしないので1を上限に + iframeScale.value = scale; +} + +onMounted(() => { + window.addEventListener('message', windowEventHandler); + if (!resizerRootEl.value) return; + resizeObserver.observe(resizerRootEl.value); +}); + +function reset() { + window.removeEventListener('message', windowEventHandler); + resizeObserver.disconnect(); + + // プレビューのリセット + iframeHeight.value = 0; + iframeScale.value = 1; + iframeLoading.value = true; + result.value = ''; + phase.value = 'input'; +} + +onDeactivated(() => { + reset(); +}); + +onUnmounted(() => { + reset(); +}); +//#endregion +</script> + +<style module> +.transition_x_enterActive, +.transition_x_leaveActive { + transition: opacity 0.3s cubic-bezier(0,0,.35,1), transform 0.3s cubic-bezier(0,0,.35,1); +} +.transition_x_enterFrom { + opacity: 0; + transform: translateX(50px); +} +.transition_x_leaveTo { + opacity: 0; + transform: translateX(-50px); +} + +.embedCodeGenRoot { + container-type: inline-size; + height: 100%; +} + +.embedCodeGenInputRoot { + height: 100%; + display: grid; + grid-template-columns: 1fr 400px; +} + +.embedCodeGenPreviewRoot { + position: relative; + background-color: var(--bg); + background-size: auto auto; + background-image: repeating-linear-gradient(135deg, transparent, transparent 6px, var(--panel) 6px, var(--panel) 12px); + cursor: not-allowed; +} + +.embedCodeGenPreviewWrapper { + display: flex; + flex-direction: column; + height: 100%; + pointer-events: none; + user-select: none; + -webkit-user-drag: none; +} + +.embedCodeGenPreviewTitle { + position: absolute; + z-index: 100; + top: 8px; + left: 8px; + padding: 6px 10px; + border-radius: 6px; + font-size: 85%; +} + +.embedCodeGenPreviewSpinner { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + pointer-events: none; + user-select: none; + -webkit-user-drag: none; +} + +.embedCodeGenPreviewResizerRoot { + position: relative; + flex: 1 0; +} + +.embedCodeGenPreviewResizer { + position: absolute; + top: 50%; + left: 50%; +} + +.embedCodeGenPreviewIframe { + display: block; + border: none; + width: 500px; + color-scheme: light dark; +} + +.embedCodeGenSettings { + padding: 24px; + overflow-y: scroll; +} + +.embedCodeGenResultRoot { + box-sizing: border-box; + padding: 24px; + height: 100%; + max-width: 700px; + margin: 0 auto; + display: flex; + align-items: center; +} + +.embedCodeGenResultHeading { + text-align: center; + font-size: 1.2em; +} + +.embedCodeGenResultHeadingIcon { + margin: 0 auto; + background-color: var(--accentedBg); + color: var(--accent); + text-align: center; + height: 64px; + width: 64px; + font-size: 24px; + line-height: 64px; + border-radius: 50%; +} + +.embedCodeGenResultDescription { + text-align: center; + white-space: pre-wrap; +} + +.embedCodeGenResultWrapper, +.embedCodeGenResultCode { + width: 100%; +} + +.embedCodeGenResultButtons { + margin: 0 auto; +} + +@container (max-width: 800px) { + .embedCodeGenInputRoot { + grid-template-columns: 1fr; + grid-template-rows: 1fr 1fr; + } +} +</style> diff --git a/packages/frontend/src/components/MkEmojiPicker.section.vue b/packages/frontend/src/components/MkEmojiPicker.section.vue index c13164c296..fca7aa2f4e 100644 --- a/packages/frontend/src/components/MkEmojiPicker.section.vue +++ b/packages/frontend/src/components/MkEmojiPicker.section.vue @@ -62,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed, Ref } from 'vue'; -import { CustomEmojiFolderTree, getEmojiName } from '@/scripts/emojilist.js'; +import { CustomEmojiFolderTree, getEmojiName } from '@@/js/emojilist.js'; import { i18n } from '@/i18n.js'; import { customEmojis } from '@/custom-emojis.js'; import MkEmojiPickerSection from '@/components/MkEmojiPicker.section.vue'; diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 4a3ed69f47..3bad8da06f 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -117,7 +117,6 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, shallowRef, computed, watch, onMounted } from 'vue'; import * as Misskey from 'misskey-js'; -import XSection from '@/components/MkEmojiPicker.section.vue'; import { emojilist, emojiCharByCategory, @@ -126,7 +125,8 @@ import { getEmojiName, CustomEmojiFolderTree, getUnicodeEmoji, -} from '@/scripts/emojilist.js'; +} from '@@/js/emojilist.js'; +import XSection from '@/components/MkEmojiPicker.section.vue'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import * as os from '@/os.js'; import { isTouchUsing } from '@/scripts/touch.js'; @@ -611,6 +611,7 @@ defineExpose({ width: auto; height: auto; min-width: 0; + padding: 0; &:disabled { cursor: not-allowed; @@ -717,7 +718,7 @@ defineExpose({ > .item { position: relative; - padding: 0; + padding: 0 3px; width: var(--eachSize); height: var(--eachSize); contain: strict; diff --git a/packages/frontend/src/components/MkFileListForAdmin.vue b/packages/frontend/src/components/MkFileListForAdmin.vue index 30822ef655..13295c455b 100644 --- a/packages/frontend/src/components/MkFileListForAdmin.vue +++ b/packages/frontend/src/components/MkFileListForAdmin.vue @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only class="file _button" > <div v-if="file.isSensitive" class="sensitive-label">{{ i18n.ts.sensitive }}</div> - <MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/> + <MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain" :highlightWhenSensitive="true"/> <div v-if="viewMode === 'list'" class="body"> <div> <small style="opacity: 0.7;">{{ file.name }}</small> diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue index f805be7b57..6d7b8307b3 100644 --- a/packages/frontend/src/components/MkFolder.vue +++ b/packages/frontend/src/components/MkFolder.vue @@ -41,6 +41,9 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSpacer :marginMin="14" :marginMax="22"> <slot></slot> </MkSpacer> + <div v-if="$slots.footer" :class="$style.footer"> + <slot name="footer"></slot> + </div> </div> </KeepAlive> </Transition> @@ -224,4 +227,18 @@ onMounted(() => { background: var(--bg); } } + +.footer { + position: sticky !important; + z-index: 1; + bottom: var(--stickyBottom, 0px); + left: 0; + padding: 9px 12px; + background: var(--acrylicBg); + -webkit-backdrop-filter: var(--blur, blur(15px)); + backdrop-filter: var(--blur, blur(15px)); + background-size: auto auto; + background-image: repeating-linear-gradient(135deg, transparent, transparent 5px, var(--panel) 5px, var(--panel) 10px); + border-radius: 0 0 6px 6px; +} </style> diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index d8ac8024b4..370d5f75c5 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -43,7 +43,7 @@ import { useStream } from '@/stream.js'; import { i18n } from '@/i18n.js'; import { claimAchievement } from '@/scripts/achievements.js'; import { pleaseLogin } from '@/scripts/please-login.js'; -import { host } from '@/config.js'; +import { host } from '@@/js/config.js'; import { $i } from '@/account.js'; import { defaultStore } from '@/store.js'; diff --git a/packages/frontend/src/components/MkFormFooter.vue b/packages/frontend/src/components/MkFormFooter.vue new file mode 100644 index 0000000000..1e88d59d8e --- /dev/null +++ b/packages/frontend/src/components/MkFormFooter.vue @@ -0,0 +1,49 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="$style.root"> + <div :class="$style.text">{{ i18n.tsx.thereAreNChanges({ n: form.modifiedCount.value }) }}</div> + <div style="margin-left: auto;" class="_buttons"> + <MkButton danger rounded @click="form.discard"><i class="ti ti-x"></i> {{ i18n.ts.discard }}</MkButton> + <MkButton primary rounded @click="form.save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> + </div> +</div> +</template> + +<script lang="ts" setup> +import { } from 'vue'; +import MkButton from './MkButton.vue'; +import { i18n } from '@/i18n.js'; + +const props = defineProps<{ + form: { + modifiedCount: { + value: number; + }; + discard: () => void; + save: () => void; + }; +}>(); +</script> + +<style lang="scss" module> +.root { + display: flex; + align-items: center; +} + +.text { + color: var(--warn); + font-size: 90%; + animation: modified-blink 2s infinite; +} + +@keyframes modified-blink { + 0% { opacity: 1; } + 50% { opacity: 0.5; } + 100% { opacity: 1; } +} +</style> diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue index 8d301f16bd..c04d0864fb 100644 --- a/packages/frontend/src/components/MkImgWithBlurhash.vue +++ b/packages/frontend/src/components/MkImgWithBlurhash.vue @@ -23,8 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts"> import DrawBlurhash from '@/workers/draw-blurhash?worker'; import TestWebGL2 from '@/workers/test-webgl2?worker'; -import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch.js'; -import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash.js'; +import { WorkerMultiDispatch } from '@@/js/worker-multi-dispatch.js'; +import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurhash.js'; const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resolve => { // テスト環境で Web Worker インスタンスは作成できない diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue index e695564f92..4c2fc1ba00 100644 --- a/packages/frontend/src/components/MkInput.vue +++ b/packages/frontend/src/components/MkInput.vue @@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue'; import { debounce } from 'throttle-debounce'; import MkButton from '@/components/MkButton.vue'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import { i18n } from '@/i18n.js'; import { Autocomplete, SuggestionType } from '@/scripts/autocomplete.js'; diff --git a/packages/frontend/src/components/MkInstanceTicker.vue b/packages/frontend/src/components/MkInstanceTicker.vue index 82c82199b5..fae22baa3f 100644 --- a/packages/frontend/src/components/MkInstanceTicker.vue +++ b/packages/frontend/src/components/MkInstanceTicker.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed } from 'vue'; -import { instanceName } from '@/config.js'; +import { instanceName } from '@@/js/config.js'; import { instance as Instance } from '@/instance.js'; import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js'; diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue index e842ec2d6e..bda2161eb8 100644 --- a/packages/frontend/src/components/MkLink.vue +++ b/packages/frontend/src/components/MkLink.vue @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { defineAsyncComponent, ref } from 'vue'; -import { url as local } from '@/config.js'; +import { url as local } from '@@/js/config.js'; import { useTooltip } from '@/scripts/use-tooltip.js'; import * as os from '@/os.js'; import { isEnabledUrlPreview } from '@/instance.js'; diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue index a080550ddf..b41705d5e6 100644 --- a/packages/frontend/src/components/MkMediaAudio.vue +++ b/packages/frontend/src/components/MkMediaAudio.vue @@ -172,9 +172,7 @@ async function show() { const menuShowing = ref(false); function showMenu(ev: MouseEvent) { - let menu: MenuItem[] = []; - - menu = [ + const menu: MenuItem[] = [ // TODO: 再生キューに追加 { type: 'switch', @@ -222,7 +220,7 @@ function showMenu(ev: MouseEvent) { menu.push({ type: 'divider', }, { - type: 'link' as const, + type: 'link', text: i18n.ts._fileViewer.title, icon: 'ti ti-info-circle', to: `/my/drive/file/${props.audio.id}`, diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index 0d1409e2c8..91e90ec99d 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -60,6 +60,7 @@ import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { $i, iAmModerator } from '@/account.js'; +import type { MenuItem } from '@/types/menu.js'; const props = withDefaults(defineProps<{ image: Misskey.entities.DriveFile; @@ -111,27 +112,39 @@ watch(() => props.image, () => { }); function showMenu(ev: MouseEvent) { - os.popupMenu([{ + const menuItems: MenuItem[] = []; + + menuItems.push({ text: i18n.ts.hide, icon: 'ti ti-eye-off', action: () => { hide.value = true; }, - }, ...(iAmModerator ? [{ - text: i18n.ts.markAsSensitive, - icon: 'ti ti-eye-exclamation', - danger: true, - action: () => { - os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true }); - }, - }] : []), ...($i?.id === props.image.userId ? [{ - type: 'divider' as const, - }, { - type: 'link' as const, - text: i18n.ts._fileViewer.title, - icon: 'ti ti-info-circle', - to: `/my/drive/file/${props.image.id}`, - }] : [])], ev.currentTarget ?? ev.target); + }); + + if (iAmModerator) { + menuItems.push({ + text: i18n.ts.markAsSensitive, + icon: 'ti ti-eye-exclamation', + danger: true, + action: () => { + os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true }); + }, + }); + } + + if ($i?.id === props.image.userId) { + menuItems.push({ + type: 'divider', + }, { + type: 'link', + text: i18n.ts._fileViewer.title, + icon: 'ti ti-info-circle', + to: `/my/drive/file/${props.image.id}`, + }); + } + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target); } </script> diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index 2300802dcf..4a4a99be25 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -37,7 +37,7 @@ import XBanner from '@/components/MkMediaBanner.vue'; import XImage from '@/components/MkMediaImage.vue'; import XVideo from '@/components/MkMediaVideo.vue'; import * as os from '@/os.js'; -import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; +import { FILE_TYPE_BROWSERSAFE } from '@@/js/const.js'; import { defaultStore } from '@/store.js'; import { focusParent } from '@/scripts/focus.js'; diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index 7c5a365148..1b1915e6c8 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -192,9 +192,7 @@ async function show() { const menuShowing = ref(false); function showMenu(ev: MouseEvent) { - let menu: MenuItem[] = []; - - menu = [ + const menu: MenuItem[] = [ // TODO: 再生キューに追加 { type: 'switch', @@ -247,7 +245,7 @@ function showMenu(ev: MouseEvent) { menu.push({ type: 'divider', }, { - type: 'link' as const, + type: 'link', text: i18n.ts._fileViewer.title, icon: 'ti ti-info-circle', to: `/my/drive/file/${props.video.id}`, diff --git a/packages/frontend/src/components/MkMention.vue b/packages/frontend/src/components/MkMention.vue index bfb49a416e..9d9661e816 100644 --- a/packages/frontend/src/components/MkMention.vue +++ b/packages/frontend/src/components/MkMention.vue @@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { toUnicode } from 'punycode'; import { computed } from 'vue'; import tinycolor from 'tinycolor2'; -import { host as localHost } from '@/config.js'; +import { host as localHost } from '@@/js/config.js'; import { $i } from '@/account.js'; import { defaultStore } from '@/store.js'; import { getStaticImageUrl } from '@/scripts/media-proxy.js'; diff --git a/packages/frontend/src/components/MkMenu.child.vue b/packages/frontend/src/components/MkMenu.child.vue index 235790556c..086573ba6d 100644 --- a/packages/frontend/src/components/MkMenu.child.vue +++ b/packages/frontend/src/components/MkMenu.child.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { nextTick, onMounted, onUnmounted, provide, shallowRef, watch } from 'vue'; import MkMenu from './MkMenu.vue'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; const props = defineProps<{ items: MenuItem[]; diff --git a/packages/frontend/src/components/MkMiniChart.vue b/packages/frontend/src/components/MkMiniChart.vue index f2f2bf47a8..1b6f6cef31 100644 --- a/packages/frontend/src/components/MkMiniChart.vue +++ b/packages/frontend/src/components/MkMiniChart.vue @@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { watch, ref } from 'vue'; import { v4 as uuid } from 'uuid'; import tinycolor from 'tinycolor2'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; const props = defineProps<{ src: number[]; diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 4caafe54bf..b6bab27820 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -163,6 +163,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue'; import * as mfm from 'mfm-js'; import * as Misskey from 'misskey-js'; +import { isLink } from '@@/js/is-link.js'; import MkNoteSub from '@/components/MkNoteSub.vue'; import MkNoteHeader from '@/components/MkNoteHeader.vue'; import MkNoteSimple from '@/components/MkNoteSimple.vue'; @@ -192,11 +193,11 @@ import { deepClone } from '@/scripts/clone.js'; import { useTooltip } from '@/scripts/use-tooltip.js'; import { claimAchievement } from '@/scripts/achievements.js'; import { getNoteSummary } from '@/scripts/get-note-summary.js'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; -import { shouldCollapsed } from '@/scripts/collapsed.js'; -import { host } from '@/config.js'; +import { shouldCollapsed } from '@@/js/collapsed.js'; +import { host } from '@@/js/config.js'; import { isEnabledUrlPreview } from '@/instance.js'; import { type Keymap } from '@/scripts/hotkey.js'; import { focusPrev, focusNext } from '@/scripts/focus.js'; @@ -506,16 +507,6 @@ function onContextmenu(ev: MouseEvent): void { return; } - const isLink = (el: HTMLElement): boolean => { - if (el.tagName === 'A') return true; - // 再生速度の選択などのために、Audio要素のコンテキストメニューはブラウザデフォルトとする。 - if (el.tagName === 'AUDIO') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - return false; - }; - if (ev.target && isLink(ev.target as HTMLElement)) return; if (window.getSelection()?.toString() !== '') return; @@ -627,7 +618,7 @@ function emitUpdReaction(emoji: string, delta: number) { // 今度はその処理自体がパフォーマンス低下の原因にならないか懸念される。また、被リアクションでも高さは変化するため、やはり多少のズレは生じる // 一度レンダリングされた要素はブラウザがよしなにサイズを覚えておいてくれるような実装になるまで待った方が良さそう(なるのか?) //content-visibility: auto; - //contain-intrinsic-size: 0 128px; + //contain-intrinsic-size: 0 128px; &:focus-visible { outline: none; diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 2b7d2afa04..1867f82c0f 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -199,6 +199,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, inject, onMounted, provide, ref, shallowRef } from 'vue'; import * as mfm from 'mfm-js'; import * as Misskey from 'misskey-js'; +import { isLink } from '@@/js/is-link.js'; import MkNoteSub from '@/components/MkNoteSub.vue'; import MkNoteSimple from '@/components/MkNoteSimple.vue'; import MkReactionsViewer from '@/components/MkReactionsViewer.vue'; @@ -222,7 +223,7 @@ import { reactionPicker } from '@/scripts/reaction-picker.js'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js'; import { $i } from '@/account.js'; import { i18n } from '@/i18n.js'; -import { host } from '@/config.js'; +import { host } from '@@/js/config.js'; import { getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/scripts/get-note-menu.js'; import { useNoteCapture } from '@/scripts/use-note-capture.js'; import { deepClone } from '@/scripts/clone.js'; @@ -468,14 +469,6 @@ function toggleReact() { } function onContextmenu(ev: MouseEvent): void { - const isLink = (el: HTMLElement): boolean => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - return false; - }; - if (ev.target && isLink(ev.target as HTMLElement)) return; if (window.getSelection()?.toString() !== '') return; diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index ee65743574..738cba2134 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only :withTooltip="true" :reaction="notification.reaction.replace(/^:(\w+):$/, ':$1@.:')" :noStyle="true" - style="width: 100%; height: 100%;" + style="width: 100%; height: 100% !important; object-fit: contain;" /> </div> </div> @@ -122,7 +122,7 @@ SPDX-License-Identifier: AGPL-3.0-only :withTooltip="true" :reaction="reaction.reaction.replace(/^:(\w+):$/, ':$1@.:')" :noStyle="true" - style="width: 100%; height: 100%;" + style="width: 100%; height: 100% !important; object-fit: contain;" /> </div> </div> diff --git a/packages/frontend/src/components/MkNotificationSelectWindow.vue b/packages/frontend/src/components/MkNotificationSelectWindow.vue index 71b38d99ed..47a9c79e45 100644 --- a/packages/frontend/src/components/MkNotificationSelectWindow.vue +++ b/packages/frontend/src/components/MkNotificationSelectWindow.vue @@ -35,7 +35,7 @@ import MkSwitch from './MkSwitch.vue'; import MkInfo from './MkInfo.vue'; import MkButton from './MkButton.vue'; import MkModalWindow from '@/components/MkModalWindow.vue'; -import { notificationTypes } from '@/const.js'; +import { notificationTypes } from '@@/js/const.js'; import { i18n } from '@/i18n.js'; type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>> diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue index 389987338d..d67616e6b2 100644 --- a/packages/frontend/src/components/MkNotifications.vue +++ b/packages/frontend/src/components/MkNotifications.vue @@ -31,7 +31,7 @@ import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; import MkNote from '@/components/MkNote.vue'; import { useStream } from '@/stream.js'; import { i18n } from '@/i18n.js'; -import { notificationTypes } from '@/const.js'; +import { notificationTypes } from '@@/js/const.js'; import { infoImageUrl } from '@/instance.js'; import { defaultStore } from '@/store.js'; import MkPullToRefresh from '@/components/MkPullToRefresh.vue'; diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue index bd86b01591..2b993ab12f 100644 --- a/packages/frontend/src/components/MkPageWindow.vue +++ b/packages/frontend/src/components/MkPageWindow.vue @@ -34,13 +34,13 @@ import RouterView from '@/components/global/RouterView.vue'; import MkWindow from '@/components/MkWindow.vue'; import { popout as _popout } from '@/scripts/popout.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; -import { url } from '@/config.js'; +import { url } from '@@/js/config.js'; import { useScrollPositionManager } from '@/nirax.js'; import { i18n } from '@/i18n.js'; import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js'; import { openingWindowsCount } from '@/os.js'; import { claimAchievement } from '@/scripts/achievements.js'; -import { getScrollContainer } from '@/scripts/scroll.js'; +import { getScrollContainer } from '@@/js/scroll.js'; import { useRouterFactory } from '@/router/supplier.js'; import { mainRouter } from '@/router/main.js'; diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index 62a85389ad..ea299c319e 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -45,10 +45,10 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts"> import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue'; import * as Misskey from 'misskey-js'; +import { useDocumentVisibility } from '@@/js/use-document-visibility.js'; +import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@@/js/scroll.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll.js'; -import { useDocumentVisibility } from '@/scripts/use-document-visibility.js'; import { defaultStore } from '@/store.js'; import { MisskeyEntity } from '@/types/date-separated-list.js'; import { i18n } from '@/i18n.js'; @@ -125,8 +125,6 @@ const items = ref<MisskeyEntityMap>(new Map()); */ const queue = ref<MisskeyEntityMap>(new Map()); -const offset = ref(0); - /** * 初期化中かどうか(trueならMkLoadingで全て隠す) */ @@ -179,7 +177,9 @@ watch([backed, contentEl], () => { if (!backed.value) { if (!contentEl.value) return; - scrollRemove.value = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl.value, executeQueue, TOLERANCE); + scrollRemove.value = props.pagination.reversed + ? onScrollBottom(contentEl.value, executeQueue, TOLERANCE) + : onScrollTop(contentEl.value, (topVisible) => { if (topVisible) executeQueue(); }, TOLERANCE); } else { if (scrollRemove.value) scrollRemove.value(); scrollRemove.value = null; @@ -223,7 +223,6 @@ async function init(): Promise<void> { more.value = true; } - offset.value = res.length; error.value = false; fetching.value = false; }, err => { @@ -244,7 +243,7 @@ const fetchMore = async (): Promise<void> => { ...params, limit: SECOND_FETCH_LIMIT, ...(props.pagination.offsetMode ? { - offset: offset.value, + offset: items.value.size, } : { untilId: Array.from(items.value.keys()).at(-1), }), @@ -294,7 +293,6 @@ const fetchMore = async (): Promise<void> => { moreFetching.value = false; } } - offset.value += res.length; }, err => { moreFetching.value = false; }); @@ -308,7 +306,7 @@ const fetchMoreAhead = async (): Promise<void> => { ...params, limit: SECOND_FETCH_LIMIT, ...(props.pagination.offsetMode ? { - offset: offset.value, + offset: items.value.size, } : { sinceId: Array.from(items.value.keys()).at(-1), }), @@ -320,7 +318,6 @@ const fetchMoreAhead = async (): Promise<void> => { items.value = concatMapWithArray(items.value, res); more.value = true; } - offset.value += res.length; moreFetching.value = false; }, err => { moreFetching.value = false; diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue index 72bd8f4f6c..e1d5db2730 100644 --- a/packages/frontend/src/components/MkPoll.vue +++ b/packages/frontend/src/components/MkPoll.vue @@ -29,14 +29,14 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, ref } from 'vue'; import * as Misskey from 'misskey-js'; +import type { OpenOnRemoteOptions } from '@/scripts/please-login.js'; import { sum } from '@/scripts/array.js'; import { pleaseLogin } from '@/scripts/please-login.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; -import { host } from '@/config.js'; -import { useInterval } from '@/scripts/use-interval.js'; -import type { OpenOnRemoteOptions } from '@/scripts/please-login.js'; +import { host } from '@@/js/config.js'; +import { useInterval } from '@@/js/use-interval.js'; const props = defineProps<{ noteId: string; @@ -83,10 +83,10 @@ if (props.poll.expiresAt) { } const vote = async (id) => { - pleaseLogin(undefined, pleaseLoginContext.value); - if (props.readOnly || closed.value || isVoted.value) return; + pleaseLogin(undefined, pleaseLoginContext.value); + const { canceled } = await os.confirm({ type: 'question', text: i18n.tsx.voteConfirm({ choice: props.poll.choices[id].text }), @@ -145,7 +145,7 @@ const vote = async (id) => { .done { .choice { - cursor: default; + cursor: initial; } } </style> diff --git a/packages/frontend/src/components/MkPopupMenu.vue b/packages/frontend/src/components/MkPopupMenu.vue index 8a0c7b1e54..26c251a8d2 100644 --- a/packages/frontend/src/components/MkPopupMenu.vue +++ b/packages/frontend/src/components/MkPopupMenu.vue @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref, shallowRef } from 'vue'; import MkModal from './MkModal.vue'; import MkMenu from './MkMenu.vue'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; defineProps<{ items: MenuItem[]; diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index df251d9192..039393887d 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -109,7 +109,7 @@ import MkNoteSimple from '@/components/MkNoteSimple.vue'; import MkNotePreview from '@/components/MkNotePreview.vue'; import XPostFormAttaches from '@/components/MkPostFormAttaches.vue'; import MkPollEditor, { type PollEditorModelValue } from '@/components/MkPollEditor.vue'; -import { host, url } from '@/config.js'; +import { host, url } from '@@/js/config.js'; import { erase, unique } from '@/scripts/array.js'; import { extractMentions } from '@/scripts/extract-mentions.js'; import { formatTimeString } from '@/scripts/format-time-string.js'; diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue index 8854babb6b..80b75a0875 100644 --- a/packages/frontend/src/components/MkPostFormAttaches.vue +++ b/packages/frontend/src/components/MkPostFormAttaches.vue @@ -26,6 +26,7 @@ import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; +import type { MenuItem } from '@/types/menu.js'; const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); @@ -63,7 +64,7 @@ async function detachAndDeleteMedia(file: Misskey.entities.DriveFile) { const { canceled } = await os.confirm({ type: 'warning', - text: i18n.t('driveFileDeleteConfirm', { name: file.name }), + text: i18n.tsx.driveFileDeleteConfirm({ name: file.name }), }); if (canceled) return; @@ -136,7 +137,10 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent): void { if (menuShowing) return; const isImage = file.type.startsWith('image/'); - os.popupMenu([{ + + const menuItems: MenuItem[] = []; + + menuItems.push({ text: i18n.ts.renameFile, icon: 'ti ti-forms', action: () => { rename(file); }, @@ -148,11 +152,17 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent): void { text: i18n.ts.describeFile, icon: 'ti ti-text-caption', action: () => { describe(file); }, - }, ...isImage ? [{ - text: i18n.ts.cropImage, - icon: 'ti ti-crop', - action: () : void => { crop(file); }, - }] : [], { + }); + + if (isImage) { + menuItems.push({ + text: i18n.ts.cropImage, + icon: 'ti ti-crop', + action: () : void => { crop(file); }, + }); + } + + menuItems.push({ type: 'divider', }, { text: i18n.ts.attachCancel, @@ -163,7 +173,9 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent): void { icon: 'ti ti-trash', danger: true, action: () => { detachAndDeleteMedia(file); }, - }], ev.currentTarget ?? ev.target).then(() => menuShowing = false); + }); + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target).then(() => menuShowing = false); menuShowing = true; } </script> diff --git a/packages/frontend/src/components/MkPreview.vue b/packages/frontend/src/components/MkPreview.vue index 649dee2fdb..6efd99d14b 100644 --- a/packages/frontend/src/components/MkPreview.vue +++ b/packages/frontend/src/components/MkPreview.vue @@ -42,7 +42,7 @@ import MkSwitch from '@/components/MkSwitch.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkRadio from '@/components/MkRadio.vue'; import * as os from '@/os.js'; -import * as config from '@/config.js'; +import * as config from '@@/js/config.js'; import { $i } from '@/account.js'; const text = ref(''); diff --git a/packages/frontend/src/components/MkPullToRefresh.vue b/packages/frontend/src/components/MkPullToRefresh.vue index e0d0b561be..4fb4c6fe56 100644 --- a/packages/frontend/src/components/MkPullToRefresh.vue +++ b/packages/frontend/src/components/MkPullToRefresh.vue @@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, onUnmounted, ref, shallowRef } from 'vue'; import { i18n } from '@/i18n.js'; -import { getScrollContainer } from '@/scripts/scroll.js'; +import { getScrollContainer } from '@@/js/scroll.js'; import { isHorizontalSwipeSwiping } from '@/scripts/touch.js'; const SCROLL_STOP = 10; diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue index 1eae642937..cfaaa67d58 100644 --- a/packages/frontend/src/components/MkRange.vue +++ b/packages/frontend/src/components/MkRange.vue @@ -5,7 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div class="timctyfi" :class="{ disabled, easing }"> - <div class="label"><slot name="label"></slot></div> + <div class="label"> + <slot name="label"></slot> + </div> <div v-adaptive-border class="body"> <div ref="containerEl" class="container"> <div class="track"> @@ -14,15 +16,25 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="steps && showTicks" class="ticks"> <div v-for="i in (steps + 1)" class="tick" :style="{ left: (((i - 1) / steps) * 100) + '%' }"></div> </div> - <div ref="thumbEl" v-tooltip="textConverter(finalValue)" class="thumb" :style="{ left: thumbPosition + 'px' }" @mousedown="onMousedown" @touchstart="onMousedown"></div> + <div + ref="thumbEl" + class="thumb" + :style="{ left: thumbPosition + 'px' }" + @mouseenter.passive="onMouseenter" + @mousedown="onMousedown" + @touchstart="onMousedown" + ></div> </div> </div> - <div class="caption"><slot name="caption"></slot></div> + <div class="caption"> + <slot name="caption"></slot> + </div> </div> </template> <script lang="ts" setup> -import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, watch, shallowRef } from 'vue'; +import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue'; +import { isTouchUsing } from '@/scripts/touch.js'; import * as os from '@/os.js'; const props = withDefaults(defineProps<{ @@ -101,12 +113,36 @@ const steps = computed(() => { } }); +const tooltipForDragShowing = ref(false); +const tooltipForHoverShowing = ref(false); + +function onMouseenter() { + if (isTouchUsing) return; + + tooltipForHoverShowing.value = true; + + const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTooltip.vue')), { + showing: computed(() => tooltipForHoverShowing.value && !tooltipForDragShowing.value), + text: computed(() => { + return props.textConverter(finalValue.value); + }), + targetElement: thumbEl, + }, { + closed: () => dispose(), + }); + + thumbEl.value!.addEventListener('mouseleave', () => { + tooltipForHoverShowing.value = false; + }, { once: true, passive: true }); +} + function onMousedown(ev: MouseEvent | TouchEvent) { ev.preventDefault(); - const tooltipShowing = ref(true); + tooltipForDragShowing.value = true; + const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTooltip.vue')), { - showing: tooltipShowing, + showing: tooltipForDragShowing, text: computed(() => { return props.textConverter(finalValue.value); }), @@ -137,7 +173,7 @@ function onMousedown(ev: MouseEvent | TouchEvent) { const onMouseup = () => { document.head.removeChild(style); - tooltipShowing.value = false; + tooltipForDragShowing.value = false; window.removeEventListener('mousemove', onDrag); window.removeEventListener('touchmove', onDrag); window.removeEventListener('mouseup', onMouseup); @@ -261,12 +297,12 @@ function onMousedown(ev: MouseEvent | TouchEvent) { > .container { > .track { > .highlight { - transition: width 0.2s cubic-bezier(0,0,0,1); + transition: width 0.2s cubic-bezier(0, 0, 0, 1); } } > .thumb { - transition: left 0.2s cubic-bezier(0,0,0,1); + transition: left 0.2s cubic-bezier(0, 0, 0, 1); } } } diff --git a/packages/frontend/src/components/MkReactionTooltip.vue b/packages/frontend/src/components/MkReactionTooltip.vue index 15409a216a..77ca841ad0 100644 --- a/packages/frontend/src/components/MkReactionTooltip.vue +++ b/packages/frontend/src/components/MkReactionTooltip.vue @@ -36,6 +36,7 @@ const emit = defineEmits<{ .icon { display: block; width: 60px; + max-height: 60px; font-size: 60px; // unicodeな絵文字についてはwidthが効かないため margin: 0 auto; object-fit: contain; diff --git a/packages/frontend/src/components/MkReactionsViewer.details.vue b/packages/frontend/src/components/MkReactionsViewer.details.vue index 60118fadd2..8038ec7429 100644 --- a/packages/frontend/src/components/MkReactionsViewer.details.vue +++ b/packages/frontend/src/components/MkReactionsViewer.details.vue @@ -23,9 +23,9 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { } from 'vue'; +import { getEmojiName } from '@@/js/emojilist.js'; import MkTooltip from './MkTooltip.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; -import { getEmojiName } from '@/scripts/emojilist.js'; defineProps<{ showing: boolean; @@ -63,6 +63,7 @@ function getReactionName(reaction: string): string { .reactionIcon { display: block; width: 60px; + max-height: 60px; font-size: 60px; // unicodeな絵文字についてはwidthが効かないため object-fit: contain; margin: 0 auto; diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index 26223364ab..f42a0b3227 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -20,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, inject, onMounted, shallowRef, watch } from 'vue'; import * as Misskey from 'misskey-js'; +import { getUnicodeEmoji } from '@@/js/emojilist.js'; import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue'; import XDetails from '@/components/MkReactionsViewer.details.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; @@ -34,7 +35,6 @@ import { i18n } from '@/i18n.js'; import * as sound from '@/scripts/sound.js'; import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js'; import { customEmojisMap } from '@/custom-emojis.js'; -import { getUnicodeEmoji } from '@/scripts/emojilist.js'; const props = defineProps<{ reaction: string; diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue index 0eba8d6a9c..343524fc82 100644 --- a/packages/frontend/src/components/MkSelect.vue +++ b/packages/frontend/src/components/MkSelect.vue @@ -44,9 +44,9 @@ SPDX-License-Identifier: AGPL-3.0-only import { onMounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots, VNodeChild } from 'vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import { i18n } from '@/i18n.js'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; const props = defineProps<{ modelValue: string | null; diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index 781145e1bc..231a6dfcf5 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -66,15 +66,15 @@ import { defineAsyncComponent, ref } from 'vue'; import { toUnicode } from 'punycode/'; import * as Misskey from 'misskey-js'; import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill'; +import { query, extractDomain } from '@@/js/url.js'; import type { OpenOnRemoteOptions } from '@/scripts/please-login.js'; import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkInfo from '@/components/MkInfo.vue'; -import { host as configHost } from '@/config.js'; +import { host as configHost } from '@@/js/config.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { query, extractDomain } from '@/scripts/url.js'; import { login } from '@/account.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue index 5f08e416c1..4ab4380ad5 100644 --- a/packages/frontend/src/components/MkSignupDialog.form.vue +++ b/packages/frontend/src/components/MkSignupDialog.form.vue @@ -84,7 +84,7 @@ import * as Misskey from 'misskey-js'; import MkButton from './MkButton.vue'; import MkInput from './MkInput.vue'; import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue'; -import * as config from '@/config.js'; +import * as config from '@@/js/config.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { login } from '@/account.js'; diff --git a/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue b/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue index 80f3a6709c..1845b01b69 100644 --- a/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue +++ b/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue @@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import MkButton from '@/components/MkButton.vue'; -import { host } from '@/config.js'; +import { host } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; import { miLocalStorage } from '@/local-storage.js'; diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index 25412cc2e5..3bbb163f0f 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -35,7 +35,7 @@ import * as Misskey from 'misskey-js'; import MkMediaList from '@/components/MkMediaList.vue'; import MkPoll from '@/components/MkPoll.vue'; import { i18n } from '@/i18n.js'; -import { shouldCollapsed } from '@/scripts/collapsed.js'; +import { shouldCollapsed } from '@@/js/collapsed.js'; const props = defineProps<{ note: Misskey.entities.Note; diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts b/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts index 69b8edd85a..19e4eea733 100644 --- a/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts +++ b/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts @@ -4,9 +4,10 @@ */ import { defineAsyncComponent } from 'vue'; +import * as Misskey from 'misskey-js'; import * as os from '@/os.js'; -export type SystemWebhookEventType = 'abuseReport' | 'abuseReportResolved'; +export type SystemWebhookEventType = Misskey.entities.SystemWebhook['on'][number]; export type MkSystemWebhookEditorProps = { mode: 'create' | 'edit'; diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.vue b/packages/frontend/src/components/MkSystemWebhookEditor.vue index f5c7a3160b..ec3b1c90ca 100644 --- a/packages/frontend/src/components/MkSystemWebhookEditor.vue +++ b/packages/frontend/src/components/MkSystemWebhookEditor.vue @@ -35,16 +35,31 @@ SPDX-License-Identifier: AGPL-3.0-only <MkFolder :defaultOpen="true"> <template #label>{{ i18n.ts._webhookSettings.trigger }}</template> - <div class="_gaps_s"> - <MkSwitch v-model="events.abuseReport" :disabled="disabledEvents.abuseReport"> - <template #label>{{ i18n.ts._webhookSettings._systemEvents.abuseReport }}</template> - </MkSwitch> - <MkSwitch v-model="events.abuseReportResolved" :disabled="disabledEvents.abuseReportResolved"> - <template #label>{{ i18n.ts._webhookSettings._systemEvents.abuseReportResolved }}</template> - </MkSwitch> - <MkSwitch v-model="events.userCreated" :disabled="disabledEvents.userCreated"> - <template #label>{{ i18n.ts._webhookSettings._systemEvents.userCreated }}</template> - </MkSwitch> + <div class="_gaps"> + <div class="_gaps_s"> + <div :class="$style.switchBox"> + <MkSwitch v-model="events.abuseReport" :disabled="disabledEvents.abuseReport"> + <template #label>{{ i18n.ts._webhookSettings._systemEvents.abuseReport }}</template> + </MkSwitch> + <MkButton v-show="mode === 'edit'" transparent :class="$style.testButton" :disabled="!(isActive && events.abuseReport)" @click="test('abuseReport')"><i class="ti ti-send"></i></MkButton> + </div> + <div :class="$style.switchBox"> + <MkSwitch v-model="events.abuseReportResolved" :disabled="disabledEvents.abuseReportResolved"> + <template #label>{{ i18n.ts._webhookSettings._systemEvents.abuseReportResolved }}</template> + </MkSwitch> + <MkButton v-show="mode === 'edit'" transparent :class="$style.testButton" :disabled="!(isActive && events.abuseReportResolved)" @click="test('abuseReportResolved')"><i class="ti ti-send"></i></MkButton> + </div> + <div :class="$style.switchBox"> + <MkSwitch v-model="events.userCreated" :disabled="disabledEvents.userCreated"> + <template #label>{{ i18n.ts._webhookSettings._systemEvents.userCreated }}</template> + </MkSwitch> + <MkButton v-show="mode === 'edit'" transparent :class="$style.testButton" :disabled="!(isActive && events.userCreated)" @click="test('userCreated')"><i class="ti ti-send"></i></MkButton> + </div> + </div> + + <div v-show="mode === 'edit'" :class="$style.description"> + {{ i18n.ts._webhookSettings.testRemarks }} + </div> </div> </MkFolder> @@ -66,6 +81,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script setup lang="ts"> import { computed, onMounted, ref, shallowRef, toRefs } from 'vue'; +import * as Misskey from 'misskey-js'; import MkInput from '@/components/MkInput.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import { @@ -180,6 +196,21 @@ async function loadingScope<T>(fn: () => Promise<T>): Promise<T> { } } +async function test(type: Misskey.entities.SystemWebhook['on'][number]): Promise<void> { + if (!id.value) { + return Promise.resolve(); + } + + await os.apiWithDialog('admin/system-webhook/test', { + webhookId: id.value, + type, + override: { + secret: secret.value, + url: url.value, + }, + }); +} + onMounted(async () => { await loadingScope(async () => { switch (mode.value) { @@ -235,4 +266,29 @@ onMounted(async () => { -webkit-backdrop-filter: var(--blur, blur(15px)); backdrop-filter: var(--blur, blur(15px)); } + +.switchBox { + display: flex; + align-items: center; + justify-content: start; + + .testButton { + $buttonSize: 28px; + padding: 0; + width: $buttonSize; + min-width: $buttonSize; + max-width: $buttonSize; + height: $buttonSize; + margin-left: auto; + line-height: normal; + font-size: 90%; + border-radius: 9999px; + } +} + +.description { + font-size: 0.85em; + padding: 8px 0 0 0; + color: var(--fgTransparentWeak); +} </style> diff --git a/packages/frontend/src/components/MkTutorialDialog.vue b/packages/frontend/src/components/MkTutorialDialog.vue index 9adc8d466c..1f5a2b9381 100644 --- a/packages/frontend/src/components/MkTutorialDialog.vue +++ b/packages/frontend/src/components/MkTutorialDialog.vue @@ -158,7 +158,7 @@ import XSensitive from '@/components/MkTutorialDialog.Sensitive.vue'; import MkAnimBg from '@/components/MkAnimBg.vue'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; -import { host } from '@/config.js'; +import { host } from '@@/js/config.js'; import { claimAchievement } from '@/scripts/achievements.js'; import * as os from '@/os.js'; diff --git a/packages/frontend/src/components/MkUpdated.vue b/packages/frontend/src/components/MkUpdated.vue index 188cc37f41..f8af276836 100644 --- a/packages/frontend/src/components/MkUpdated.vue +++ b/packages/frontend/src/components/MkUpdated.vue @@ -19,7 +19,7 @@ import { onMounted, shallowRef } from 'vue'; import MkModal from '@/components/MkModal.vue'; import MkButton from '@/components/MkButton.vue'; import MkSparkle from '@/components/MkSparkle.vue'; -import { version } from '@/config.js'; +import { version } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { confetti } from '@/scripts/confetti.js'; diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index c868a22045..f5f9b43197 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -85,12 +85,12 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { defineAsyncComponent, onDeactivated, onUnmounted, ref } from 'vue'; import type { summaly } from '@misskey-dev/summaly'; -import { url as local } from '@/config.js'; +import { url as local } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { deviceKind } from '@/scripts/device-kind.js'; import MkButton from '@/components/MkButton.vue'; -import { versatileLang } from '@/scripts/intl-const.js'; +import { versatileLang } from '@@/js/intl-const.js'; import { transformPlayerUrl } from '@/scripts/player-url-transform.js'; import { defaultStore } from '@/store.js'; diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue index cbb40924f6..1374817c72 100644 --- a/packages/frontend/src/components/MkUserSelectDialog.vue +++ b/packages/frontend/src/components/MkUserSelectDialog.vue @@ -70,7 +70,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import { $i } from '@/account.js'; -import { host as currentHost, hostname } from '@/config.js'; +import { host as currentHost, hostname } from '@@/js/config.js'; const emit = defineEmits<{ (ev: 'ok', selected: Misskey.entities.UserDetailed): void; diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue index 514350c930..1fb1eda039 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.vue @@ -137,7 +137,7 @@ import XPrivacy from '@/components/MkUserSetupDialog.Privacy.vue'; import MkAnimBg from '@/components/MkAnimBg.vue'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; -import { host } from '@/config.js'; +import { host } from '@@/js/config.js'; import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue'; import { defaultStore } from '@/store.js'; import * as os from '@/os.js'; diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue index 445780eca7..a6c8baeaaa 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.vue @@ -58,7 +58,7 @@ import XSignupDialog from '@/components/MkSignupDialog.vue'; import MkButton from '@/components/MkButton.vue'; import MkTimeline from '@/components/MkTimeline.vue'; import MkInfo from '@/components/MkInfo.vue'; -import { instanceName } from '@/config.js'; +import { instanceName } from '@@/js/config.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue index 7550edd120..0c51cfa9ce 100644 --- a/packages/frontend/src/components/MkWidgets.vue +++ b/packages/frontend/src/components/MkWidgets.vue @@ -57,6 +57,7 @@ import MkButton from '@/components/MkButton.vue'; import { widgets as widgetDefs } from '@/widgets/index.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; +import { isLink } from '@@/js/is-link.js'; const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); @@ -98,13 +99,6 @@ const updateWidget = (id, data) => { function onContextmenu(widget: Widget, ev: MouseEvent) { const element = ev.target as HTMLElement | null; - const isLink = (el: HTMLElement): boolean => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - return false; - }; if (element && isLink(element)) return; if (element && (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(element.tagName) || element.attributes['contenteditable'])) return; if (window.getSelection()?.toString() !== '') return; diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue index 26ba598498..08906a1205 100644 --- a/packages/frontend/src/components/MkWindow.vue +++ b/packages/frontend/src/components/MkWindow.vue @@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { onBeforeUnmount, onMounted, provide, shallowRef, ref } from 'vue'; import contains from '@/scripts/contains.js'; import * as os from '@/os.js'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; import { i18n } from '@/i18n.js'; import { defaultStore } from '@/store.js'; diff --git a/packages/frontend/src/components/MkYouTubePlayer.vue b/packages/frontend/src/components/MkYouTubePlayer.vue index e3711b3463..1122976436 100644 --- a/packages/frontend/src/components/MkYouTubePlayer.vue +++ b/packages/frontend/src/components/MkYouTubePlayer.vue @@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref } from 'vue'; import MkWindow from '@/components/MkWindow.vue'; -import { versatileLang } from '@/scripts/intl-const.js'; +import { versatileLang } from '@@/js/intl-const.js'; import { transformPlayerUrl } from '@/scripts/player-url-transform.js'; import { defaultStore } from '@/store.js'; diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue index 3a45ca429f..87fa9c8252 100644 --- a/packages/frontend/src/components/global/MkA.vue +++ b/packages/frontend/src/components/global/MkA.vue @@ -17,7 +17,7 @@ export type MkABehavior = 'window' | 'browser' | null; import { computed, inject, shallowRef } from 'vue'; import * as os from '@/os.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; -import { url } from '@/config.js'; +import { url } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { useRouter } from '@/router/supplier.js'; diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue index bbcb070803..8a03f7846e 100644 --- a/packages/frontend/src/components/global/MkAcct.vue +++ b/packages/frontend/src/components/global/MkAcct.vue @@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import * as Misskey from 'misskey-js'; import { toUnicode } from 'punycode/'; -import { host as hostRaw } from '@/config.js'; +import { host as hostRaw } from '@@/js/config.js'; import { defaultStore } from '@/store.js'; defineProps<{ diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue index bdaa8a809f..f0e943960d 100644 --- a/packages/frontend/src/components/global/MkAd.vue +++ b/packages/frontend/src/components/global/MkAd.vue @@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref, computed } from 'vue'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; -import { url as local, host } from '@/config.js'; +import { url as local, host } from '@@/js/config.js'; import MkButton from '@/components/MkButton.vue'; import { defaultStore } from '@/store.js'; import * as os from '@/os.js'; diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue index ee224dba49..35c07bc80c 100644 --- a/packages/frontend/src/components/global/MkAvatar.vue +++ b/packages/frontend/src/components/global/MkAvatar.vue @@ -42,10 +42,10 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { watch, ref, computed } from 'vue'; import * as Misskey from 'misskey-js'; +import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurhash.js'; import MkImgWithBlurhash from '../MkImgWithBlurhash.vue'; import MkA from './MkA.vue'; import { getStaticImageUrl } from '@/scripts/media-proxy.js'; -import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash.js'; import { acct, userPage } from '@/filters/user.js'; import MkUserOnlineIndicator from '@/components/MkUserOnlineIndicator.vue'; import { defaultStore } from '@/store.js'; diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue index dff56cd7f0..66f82a7898 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.vue +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -35,6 +35,7 @@ import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import * as sound from '@/scripts/sound.js'; import { i18n } from '@/i18n.js'; import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue'; +import type { MenuItem } from '@/types/menu.js'; const props = defineProps<{ name: string; @@ -85,7 +86,9 @@ const errored = ref(url.value == null); function onClick(ev: MouseEvent) { if (props.menu) { - os.popupMenu([{ + const menuItems: MenuItem[] = []; + + menuItems.push({ type: 'label', text: `:${props.name}:`, }, { @@ -95,14 +98,20 @@ function onClick(ev: MouseEvent) { copyToClipboard(`:${props.name}:`); os.success(); }, - }, ...(props.menuReaction && react ? [{ - text: i18n.ts.doReaction, - icon: 'ti ti-plus', - action: () => { - react(`:${props.name}:`); - sound.playMisskeySfx('reaction'); - }, - }] : []), { + }); + + if (props.menuReaction && react) { + menuItems.push({ + text: i18n.ts.doReaction, + icon: 'ti ti-plus', + action: () => { + react(`:${props.name}:`); + sound.playMisskeySfx('reaction'); + }, + }); + } + + menuItems.push({ text: i18n.ts.info, icon: 'ti ti-info-circle', action: async () => { @@ -114,7 +123,9 @@ function onClick(ev: MouseEvent) { closed: () => dispose(), }); }, - }], ev.currentTarget ?? ev.target); + }); + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target); } } </script> diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index fa780d4ad3..f0acd3bc27 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -10,13 +10,14 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, inject } from 'vue'; -import { char2fluentEmojiFilePath, char2twemojiFilePath } from '@/scripts/emoji-base.js'; +import { colorizeEmoji, getEmojiName } from '@@/js/emojilist.js'; +import { char2fluentEmojiFilePath, char2twemojiFilePath } from '@@/js/emoji-base.js'; import { defaultStore } from '@/store.js'; -import { colorizeEmoji, getEmojiName } from '@/scripts/emojilist.js'; import * as os from '@/os.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import * as sound from '@/scripts/sound.js'; import { i18n } from '@/i18n.js'; +import type { MenuItem } from '@/types/menu.js'; const props = defineProps<{ emoji: string; @@ -39,7 +40,9 @@ function computeTitle(event: PointerEvent): void { function onClick(ev: MouseEvent) { if (props.menu) { - os.popupMenu([{ + const menuItems: MenuItem[] = []; + + menuItems.push({ type: 'label', text: props.emoji, }, { @@ -49,14 +52,20 @@ function onClick(ev: MouseEvent) { copyToClipboard(props.emoji); os.success(); }, - }, ...(props.menuReaction && react ? [{ - text: i18n.ts.doReaction, - icon: 'ti ti-plus', - action: () => { - react(props.emoji); - sound.playMisskeySfx('reaction'); - }, - }] : [])], ev.currentTarget ?? ev.target); + }); + + if (props.menuReaction && react) { + menuItems.push({ + text: i18n.ts.doReaction, + icon: 'ti ti-plus', + action: () => { + react(props.emoji); + sound.playMisskeySfx('reaction'); + }, + }); + } + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target); } } </script> diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts b/packages/frontend/src/components/global/MkMfm.stories.impl.ts similarity index 78% rename from packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts rename to packages/frontend/src/components/global/MkMfm.stories.impl.ts index 730351f795..1daf7a29cb 100644 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts +++ b/packages/frontend/src/components/global/MkMfm.stories.impl.ts @@ -2,16 +2,15 @@ * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ - -/* eslint-disable @typescript-eslint/explicit-function-return-type */ + import { StoryObj } from '@storybook/vue3'; import { expect, within } from '@storybook/test'; -import MkMisskeyFlavoredMarkdown from './MkMisskeyFlavoredMarkdown.js'; +import MkMfm from './MkMfm.js'; export const Default = { render(args) { return { components: { - MkMisskeyFlavoredMarkdown, + MkMfm, }, setup() { return { @@ -25,7 +24,7 @@ export const Default = { }; }, }, - template: '<MkMisskeyFlavoredMarkdown v-bind="props" />', + template: '<MkMfm v-bind="props" />', }; }, async play({ canvasElement, args }) { @@ -54,25 +53,25 @@ export const Default = { parameters: { layout: 'centered', }, -} satisfies StoryObj<typeof MkMisskeyFlavoredMarkdown>; +} satisfies StoryObj<typeof MkMfm>; export const Plain = { ...Default, args: { ...Default.args, plain: true, }, -} satisfies StoryObj<typeof MkMisskeyFlavoredMarkdown>; +} satisfies StoryObj<typeof MkMfm>; export const Nowrap = { ...Default, args: { ...Default.args, nowrap: true, }, -} satisfies StoryObj<typeof MkMisskeyFlavoredMarkdown>; +} satisfies StoryObj<typeof MkMfm>; export const IsNotNote = { ...Default, args: { ...Default.args, isNote: false, }, -} satisfies StoryObj<typeof MkMisskeyFlavoredMarkdown>; +} satisfies StoryObj<typeof MkMfm>; diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMfm.ts similarity index 97% rename from packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts rename to packages/frontend/src/components/global/MkMfm.ts index 0d869892bd..d914492231 100644 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts +++ b/packages/frontend/src/components/global/MkMfm.ts @@ -17,10 +17,15 @@ import MkCodeInline from '@/components/MkCodeInline.vue'; import MkGoogle from '@/components/MkGoogle.vue'; import MkSparkle from '@/components/MkSparkle.vue'; import MkA, { MkABehavior } from '@/components/global/MkA.vue'; -import { host } from '@/config.js'; +import { host } from '@@/js/config.js'; import { defaultStore } from '@/store.js'; -import { nyaize as doNyaize } from '@/scripts/nyaize.js'; -import { safeParseFloat } from '@/scripts/safe-parse.js'; + +function safeParseFloat(str: unknown): number | null { + if (typeof str !== 'string' || str === '') return null; + const num = parseFloat(str); + if (isNaN(num)) return null; + return num; +} const QUOTE_STYLE = ` display: block; @@ -86,7 +91,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven case 'text': { let text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n'); if (!disableNyaize && shouldNyaize) { - text = doNyaize(text); + text = Misskey.nyaize(text); } if (!props.plain) { @@ -281,14 +286,14 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven const child = token.children[0]; let text = child.type === 'text' ? child.props.text : ''; if (!disableNyaize && shouldNyaize) { - text = doNyaize(text); + text = Misskey.nyaize(text); } return h('ruby', {}, [text.split(' ')[0], h('rt', text.split(' ')[1])]); } else { const rt = token.children.at(-1)!; let text = rt.type === 'text' ? rt.props.text : ''; if (!disableNyaize && shouldNyaize) { - text = doNyaize(text); + text = Misskey.nyaize(text); } return h('ruby', {}, [...genEl(token.children.slice(0, token.children.length - 1), scale), h('rt', text.trim())]); } @@ -400,7 +405,6 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven } case 'emojiCode': { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (props.author?.host == null) { return [h(MkCustomEmoji, { key: Math.random(), diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index f16d951679..f1a451808f 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { onMounted, onUnmounted, ref, inject, shallowRef, computed } from 'vue'; import tinycolor from 'tinycolor2'; import XTabs, { Tab } from './MkPageHeader.tabs.vue'; -import { scrollToTop } from '@/scripts/scroll.js'; +import { scrollToTop } from '@@/js/scroll.js'; import { globalEvents } from '@/events.js'; import { injectReactiveMetadata } from '@/scripts/page-metadata.js'; import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js'; diff --git a/packages/frontend/src/components/global/MkStickyContainer.vue b/packages/frontend/src/components/global/MkStickyContainer.vue index b12dc8cb31..72993991ce 100644 --- a/packages/frontend/src/components/global/MkStickyContainer.vue +++ b/packages/frontend/src/components/global/MkStickyContainer.vue @@ -12,6 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only ref="bodyEl" :data-sticky-container-header-height="headerHeight" :data-sticky-container-footer-height="footerHeight" + style="position: relative; z-index: 0;" > <slot></slot> </div> @@ -24,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, onUnmounted, provide, inject, Ref, ref, watch, shallowRef } from 'vue'; -import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@/const.js'; +import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@@/js/const.js'; const rootEl = shallowRef<HTMLElement>(); const headerEl = shallowRef<HTMLElement>(); @@ -83,14 +84,14 @@ onMounted(() => { if (headerEl.value != null) { headerEl.value.style.position = 'sticky'; headerEl.value.style.top = 'var(--stickyTop, 0)'; - headerEl.value.style.zIndex = '1000'; + headerEl.value.style.zIndex = '1'; observer.observe(headerEl.value); } if (footerEl.value != null) { footerEl.value.style.position = 'sticky'; footerEl.value.style.bottom = 'var(--stickyBottom, 0)'; - footerEl.value.style.zIndex = '1000'; + footerEl.value.style.zIndex = '1'; observer.observe(footerEl.value); } }); diff --git a/packages/frontend/src/components/global/MkTime.stories.impl.ts b/packages/frontend/src/components/global/MkTime.stories.impl.ts index ffd4a849a2..ccf7f200b5 100644 --- a/packages/frontend/src/components/global/MkTime.stories.impl.ts +++ b/packages/frontend/src/components/global/MkTime.stories.impl.ts @@ -8,7 +8,7 @@ import { expect } from '@storybook/test'; import { StoryObj } from '@storybook/vue3'; import MkTime from './MkTime.vue'; import { i18n } from '@/i18n.js'; -import { dateTimeFormat } from '@/scripts/intl-const.js'; +import { dateTimeFormat } from '@@/js/intl-const.js'; const now = new Date('2023-04-01T00:00:00.000Z'); const future = new Date('2024-04-01T00:00:00.000Z'); const oneHourAgo = new Date(now.getTime() - 3600000); diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue index 027b226f3f..50bec990a1 100644 --- a/packages/frontend/src/components/global/MkTime.vue +++ b/packages/frontend/src/components/global/MkTime.vue @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only import isChromatic from 'chromatic/isChromatic'; import { onMounted, onUnmounted, ref, computed } from 'vue'; import { i18n } from '@/i18n.js'; -import { dateTimeFormat } from '@/scripts/intl-const.js'; +import { dateTimeFormat } from '@@/js/intl-const.js'; const props = withDefaults(defineProps<{ time: Date | string | number | null; diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue index d2ddd4aa85..e789251659 100644 --- a/packages/frontend/src/components/global/MkUrl.vue +++ b/packages/frontend/src/components/global/MkUrl.vue @@ -27,13 +27,20 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { defineAsyncComponent, ref } from 'vue'; import { toUnicode as decodePunycode } from 'punycode/'; -import { url as local } from '@/config.js'; +import { url as local } from '@@/js/config.js'; import * as os from '@/os.js'; import { useTooltip } from '@/scripts/use-tooltip.js'; -import { safeURIDecode } from '@/scripts/safe-uri-decode.js'; import { isEnabledUrlPreview } from '@/instance.js'; import { MkABehavior } from '@/components/global/MkA.vue'; +function safeURIDecode(str: string): string { + try { + return decodeURIComponent(str); + } catch { + return str; + } +} + const props = withDefaults(defineProps<{ url: string; rel?: string; diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index 44d8d59941..b36625ed1b 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -5,7 +5,7 @@ import { App } from 'vue'; -import Mfm from './global/MkMisskeyFlavoredMarkdown.js'; +import Mfm from './global/MkMfm.js'; import MkA from './global/MkA.vue'; import MkAcct from './global/MkAcct.vue'; import MkAvatar from './global/MkAvatar.vue'; diff --git a/packages/frontend/src/custom-emojis.ts b/packages/frontend/src/custom-emojis.ts index 9da3582e1a..0d03282cee 100644 --- a/packages/frontend/src/custom-emojis.ts +++ b/packages/frontend/src/custom-emojis.ts @@ -6,7 +6,6 @@ import { shallowRef, computed, markRaw, watch } from 'vue'; import * as Misskey from 'misskey-js'; import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js'; -import { useStream } from '@/stream.js'; import { get, set } from '@/scripts/idb-proxy.js'; const storageCache = await get('emojis'); @@ -29,23 +28,20 @@ watch(customEmojis, emojis => { } }, { immediate: true }); -// TODO: ここら辺副作用なのでいい感じにする -const stream = useStream(); - -stream.on('emojiAdded', emojiData => { - customEmojis.value = [emojiData.emoji, ...customEmojis.value]; +export function addCustomEmoji(emoji: Misskey.entities.EmojiSimple) { + customEmojis.value = [emoji, ...customEmojis.value]; set('emojis', customEmojis.value); -}); +} -stream.on('emojiUpdated', emojiData => { - customEmojis.value = customEmojis.value.map(item => emojiData.emojis.find(search => search.name === item.name) as Misskey.entities.EmojiSimple ?? item); +export function updateCustomEmojis(emojis: Misskey.entities.EmojiSimple[]) { + customEmojis.value = customEmojis.value.map(item => emojis.find(search => search.name === item.name) ?? item); set('emojis', customEmojis.value); -}); +} -stream.on('emojiDeleted', emojiData => { - customEmojis.value = customEmojis.value.filter(item => !emojiData.emojis.some(search => search.name === item.name)); +export function removeCustomEmojis(emojis: Misskey.entities.EmojiSimple[]) { + customEmojis.value = customEmojis.value.filter(item => !emojis.some(search => search.name === item.name)); set('emojis', customEmojis.value); -}); +} export async function fetchCustomEmojis(force = false) { const now = Date.now(); diff --git a/packages/frontend/src/directives/follow-append.ts b/packages/frontend/src/directives/follow-append.ts index f200f242ed..615dd99fa8 100644 --- a/packages/frontend/src/directives/follow-append.ts +++ b/packages/frontend/src/directives/follow-append.ts @@ -4,7 +4,7 @@ */ import { Directive } from 'vue'; -import { getScrollContainer, getScrollPosition } from '@/scripts/scroll.js'; +import { getScrollContainer, getScrollPosition } from '@@/js/scroll.js'; export default { mounted(src, binding, vn) { diff --git a/packages/frontend/src/filters/date.ts b/packages/frontend/src/filters/date.ts index 2ffe93e868..d13d1a5e42 100644 --- a/packages/frontend/src/filters/date.ts +++ b/packages/frontend/src/filters/date.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { dateTimeFormat } from '@/scripts/intl-const.js'; +import { dateTimeFormat } from '@@/js/intl-const.js'; export default (d: Date | number | undefined) => dateTimeFormat.format(d); export const dateString = (d: string) => dateTimeFormat.format(new Date(d)); diff --git a/packages/frontend/src/filters/number.ts b/packages/frontend/src/filters/number.ts index 2e7cc60ff4..10fb64deb4 100644 --- a/packages/frontend/src/filters/number.ts +++ b/packages/frontend/src/filters/number.ts @@ -3,6 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { numberFormat } from '@/scripts/intl-const.js'; +import { numberFormat } from '@@/js/intl-const.js'; export default n => n == null ? 'N/A' : numberFormat.format(n); diff --git a/packages/frontend/src/filters/user.ts b/packages/frontend/src/filters/user.ts index a87766764d..d9bc316764 100644 --- a/packages/frontend/src/filters/user.ts +++ b/packages/frontend/src/filters/user.ts @@ -4,7 +4,7 @@ */ import * as Misskey from 'misskey-js'; -import { url } from '@/config.js'; +import { url } from '@@/js/config.js'; export const acct = (user: Misskey.Acct) => { return Misskey.acct.toString(user); diff --git a/packages/frontend/src/i18n.ts b/packages/frontend/src/i18n.ts index 10d6adbcd0..6ad503b089 100644 --- a/packages/frontend/src/i18n.ts +++ b/packages/frontend/src/i18n.ts @@ -4,11 +4,11 @@ */ import { markRaw } from 'vue'; +import { I18n } from '@@/js/i18n.js'; import type { Locale } from '../../../locales/index.js'; -import { locale } from '@/config.js'; -import { I18n } from '@/scripts/i18n.js'; +import { locale } from '@@/js/config.js'; -export const i18n = markRaw(new I18n<Locale>(locale)); +export const i18n = markRaw(new I18n<Locale>(locale, _DEV_)); export function updateI18n(newLocale: Locale) { i18n.locale = newLocale; diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts index 6847321d6c..71cb42b30c 100644 --- a/packages/frontend/src/instance.ts +++ b/packages/frontend/src/instance.ts @@ -7,7 +7,7 @@ import { computed, reactive } from 'vue'; import * as Misskey from 'misskey-js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { miLocalStorage } from '@/local-storage.js'; -import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@/const.js'; +import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@@/js/const.js'; // TODO: 他のタブと永続化されたstateを同期 diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts index 8029bca68d..5b8ba77e01 100644 --- a/packages/frontend/src/local-storage.ts +++ b/packages/frontend/src/local-storage.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -type Keys = +export type Keys = 'v' | 'lastVersion' | 'instance' | @@ -38,12 +38,22 @@ type Keys = `aiscript:${string}` | 'lastEmojisFetchedAt' | // DEPRECATED, stored in indexeddb (13.9.0~) 'emojis' | // DEPRECATED, stored in indexeddb (13.9.0~); - `channelLastReadedAt:${string}` + `channelLastReadedAt:${string}` | + `idbfallback::${string}` + +// セッション毎に廃棄されるLocalStorage代替(セーフモードなどで使用できそう) +//const safeSessionStorage = new Map<Keys, string>(); export const miLocalStorage = { - getItem: (key: Keys): string | null => window.localStorage.getItem(key), - setItem: (key: Keys, value: string): void => window.localStorage.setItem(key, value), - removeItem: (key: Keys): void => window.localStorage.removeItem(key), + getItem: (key: Keys): string | null => { + return window.localStorage.getItem(key); + }, + setItem: (key: Keys, value: string): void => { + window.localStorage.setItem(key, value); + }, + removeItem: (key: Keys): void => { + window.localStorage.removeItem(key); + }, getItemAsJson: (key: Keys): any | undefined => { const item = miLocalStorage.getItem(key); if (item === null) { @@ -51,5 +61,7 @@ export const miLocalStorage = { } return JSON.parse(item); }, - setItemAsJson: (key: Keys, value: any): void => window.localStorage.setItem(key, JSON.stringify(value)), + setItemAsJson: (key: Keys, value: any): void => { + miLocalStorage.setItem(key, JSON.stringify(value)); + }, }; diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts index d7910935fa..ac730f8021 100644 --- a/packages/frontend/src/navbar.ts +++ b/packages/frontend/src/navbar.ts @@ -11,7 +11,7 @@ import { openInstanceMenu, openToolsMenu } from '@/ui/_common_/common.js'; import { lookup } from '@/scripts/lookup.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; -import { ui } from '@/config.js'; +import { ui } from '@@/js/config.js'; import { unisonReload } from '@/scripts/unison-reload.js'; export const navbarItemDef = reactive({ @@ -125,7 +125,7 @@ export const navbarItemDef = reactive({ ui: { title: i18n.ts.switchUi, icon: 'ti ti-devices', - action: (ev) => { + action: (ev: MouseEvent) => { os.popupMenu([{ text: i18n.ts.default, active: ui === 'default' || ui === null, diff --git a/packages/frontend/src/nirax.ts b/packages/frontend/src/nirax.ts index 6a8ea09ed6..25f853453a 100644 --- a/packages/frontend/src/nirax.ts +++ b/packages/frontend/src/nirax.ts @@ -7,7 +7,14 @@ import { Component, onMounted, shallowRef, ShallowRef } from 'vue'; import { EventEmitter } from 'eventemitter3'; -import { safeURIDecode } from '@/scripts/safe-uri-decode.js'; + +function safeURIDecode(str: string): string { + try { + return decodeURIComponent(str); + } catch { + return str; + } +} interface RouteDefBase { path: string; diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index f42e2ed3c5..60e4218a48 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -22,7 +22,7 @@ import MkPasswordDialog from '@/components/MkPasswordDialog.vue'; import MkEmojiPickerDialog from '@/components/MkEmojiPickerDialog.vue'; import MkPopupMenu from '@/components/MkPopupMenu.vue'; import MkContextMenu from '@/components/MkContextMenu.vue'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { pleaseLogin } from '@/scripts/please-login.js'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; diff --git a/packages/frontend/src/pages/_error_.vue b/packages/frontend/src/pages/_error_.vue index c04f399c6d..f09a8e4285 100644 --- a/packages/frontend/src/pages/_error_.vue +++ b/packages/frontend/src/pages/_error_.vue @@ -29,7 +29,7 @@ import { ref, computed } from 'vue'; import * as Misskey from 'misskey-js'; import MkButton from '@/components/MkButton.vue'; import MkLink from '@/components/MkLink.vue'; -import { version } from '@/config.js'; +import { version } from '@@/js/config.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { unisonReload } from '@/scripts/unison-reload.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index a16c1eeacc..960df59485 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -132,7 +132,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { nextTick, onBeforeUnmount, ref, shallowRef, computed } from 'vue'; -import { version } from '@/config.js'; +import { version } from '@@/js/config.js'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import MkButton from '@/components/MkButton.vue'; diff --git a/packages/frontend/src/pages/about.overview.vue b/packages/frontend/src/pages/about.overview.vue index 84419b3bef..b645506eff 100644 --- a/packages/frontend/src/pages/about.overview.vue +++ b/packages/frontend/src/pages/about.overview.vue @@ -126,7 +126,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { host, version } from '@/config.js'; +import { host, version } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; import number from '@/filters/number.js'; diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue index d8311186ab..60f6be51d4 100644 --- a/packages/frontend/src/pages/admin-file.vue +++ b/packages/frontend/src/pages/admin-file.vue @@ -44,6 +44,9 @@ SPDX-License-Identifier: AGPL-3.0-only <MkButton danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> </div> </div> + <div v-else-if="tab === 'notes' && info" class="_gaps_m"> + <XNotes :fileId="fileId"/> + </div> <div v-else-if="tab === 'ip' && info" class="_gaps_m"> <MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo> <MkKeyValue v-if="info.requestIp" class="_monospace" :copy="info.requestIp" oneline> @@ -67,7 +70,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { computed, ref } from 'vue'; +import { computed, defineAsyncComponent, ref } from 'vue'; import * as Misskey from 'misskey-js'; import MkButton from '@/components/MkButton.vue'; import MkSwitch from '@/components/MkSwitch.vue'; @@ -88,6 +91,7 @@ const tab = ref('overview'); const file = ref<Misskey.entities.DriveFile | null>(null); const info = ref<Misskey.entities.AdminDriveShowFileResponse | null>(null); const isSensitive = ref<boolean>(false); +const XNotes = defineAsyncComponent(() => import('./drive.file.notes.vue')); const props = defineProps<{ fileId: string, @@ -131,6 +135,10 @@ const headerTabs = computed(() => [{ title: i18n.ts.overview, icon: 'ti ti-info-circle', }, iAmModerator ? { + key: 'notes', + title: i18n.ts._fileViewer.attachedNotes, + icon: 'ti ti-pencil', +} : null, iAmModerator ? { key: 'ip', title: 'IP', icon: 'ti ti-password', diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 7cab8bf8bd..d40d1eee58 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -220,7 +220,7 @@ import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue'; import MkInfo from '@/components/MkInfo.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { url } from '@/config.js'; +import { url } from '@@/js/config.js'; import { acct } from '@/filters/user.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/pages/admin/_header_.vue b/packages/frontend/src/pages/admin/_header_.vue index b88f078598..d22e078c2a 100644 --- a/packages/frontend/src/pages/admin/_header_.vue +++ b/packages/frontend/src/pages/admin/_header_.vue @@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, onMounted, onUnmounted, ref, shallowRef, watch, nextTick } from 'vue'; import tinycolor from 'tinycolor2'; import { popupMenu } from '@/os.js'; -import { scrollToTop } from '@/scripts/scroll.js'; +import { scrollToTop } from '@@/js/scroll.js'; import MkButton from '@/components/MkButton.vue'; import { globalEvents } from '@/events.js'; import { injectReactiveMetadata } from '@/scripts/page-metadata.js'; diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue index 73c5e1919f..b34592cd6a 100644 --- a/packages/frontend/src/pages/admin/bot-protection.vue +++ b/packages/frontend/src/pages/admin/bot-protection.vue @@ -4,145 +4,143 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div> - <FormSuspense :p="init"> - <div class="_gaps_m"> - <MkRadios v-model="provider"> - <option :value="null">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option> - <option value="hcaptcha">hCaptcha</option> - <option value="mcaptcha">mCaptcha</option> - <option value="recaptcha">reCAPTCHA</option> - <option value="turnstile">Turnstile</option> - </MkRadios> +<MkFolder> + <template #icon><i class="ti ti-shield"></i></template> + <template #label>{{ i18n.ts.botProtection }}</template> + <template v-if="botProtectionForm.savedState.provider === 'hcaptcha'" #suffix>hCaptcha</template> + <template v-else-if="botProtectionForm.savedState.provider === 'mcaptcha'" #suffix>mCaptcha</template> + <template v-else-if="botProtectionForm.savedState.provider === 'recaptcha'" #suffix>reCAPTCHA</template> + <template v-else-if="botProtectionForm.savedState.provider === 'turnstile'" #suffix>Turnstile</template> + <template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template> + <template v-if="botProtectionForm.modified.value" #footer> + <MkFormFooter :form="botProtectionForm"/> + </template> - <template v-if="provider === 'hcaptcha'"> - <MkInput v-model="hcaptchaSiteKey"> - <template #prefix><i class="ti ti-key"></i></template> - <template #label>{{ i18n.ts.hcaptchaSiteKey }}</template> - </MkInput> - <MkInput v-model="hcaptchaSecretKey"> - <template #prefix><i class="ti ti-key"></i></template> - <template #label>{{ i18n.ts.hcaptchaSecretKey }}</template> - </MkInput> - <FormSlot> - <template #label>{{ i18n.ts.preview }}</template> - <MkCaptcha provider="hcaptcha" :sitekey="hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/> - </FormSlot> - </template> - <template v-else-if="provider === 'mcaptcha'"> - <MkInput v-model="mcaptchaSiteKey"> - <template #prefix><i class="ti ti-key"></i></template> - <template #label>{{ i18n.ts.mcaptchaSiteKey }}</template> - </MkInput> - <MkInput v-model="mcaptchaSecretKey"> - <template #prefix><i class="ti ti-key"></i></template> - <template #label>{{ i18n.ts.mcaptchaSecretKey }}</template> - </MkInput> - <MkInput v-model="mcaptchaInstanceUrl"> - <template #prefix><i class="ti ti-link"></i></template> - <template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template> - </MkInput> - <FormSlot v-if="mcaptchaSiteKey && mcaptchaInstanceUrl"> - <template #label>{{ i18n.ts.preview }}</template> - <MkCaptcha provider="mcaptcha" :sitekey="mcaptchaSiteKey" :instanceUrl="mcaptchaInstanceUrl"/> - </FormSlot> - </template> - <template v-else-if="provider === 'recaptcha'"> - <MkInput v-model="recaptchaSiteKey"> - <template #prefix><i class="ti ti-key"></i></template> - <template #label>{{ i18n.ts.recaptchaSiteKey }}</template> - </MkInput> - <MkInput v-model="recaptchaSecretKey"> - <template #prefix><i class="ti ti-key"></i></template> - <template #label>{{ i18n.ts.recaptchaSecretKey }}</template> - </MkInput> - <FormSlot v-if="recaptchaSiteKey"> - <template #label>{{ i18n.ts.preview }}</template> - <MkCaptcha provider="recaptcha" :sitekey="recaptchaSiteKey"/> - </FormSlot> - </template> - <template v-else-if="provider === 'turnstile'"> - <MkInput v-model="turnstileSiteKey"> - <template #prefix><i class="ti ti-key"></i></template> - <template #label>{{ i18n.ts.turnstileSiteKey }}</template> - </MkInput> - <MkInput v-model="turnstileSecretKey"> - <template #prefix><i class="ti ti-key"></i></template> - <template #label>{{ i18n.ts.turnstileSecretKey }}</template> - </MkInput> - <FormSlot> - <template #label>{{ i18n.ts.preview }}</template> - <MkCaptcha provider="turnstile" :sitekey="turnstileSiteKey || '1x00000000000000000000AA'"/> - </FormSlot> - </template> + <div class="_gaps_m"> + <MkRadios v-model="botProtectionForm.state.provider"> + <option :value="null">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option> + <option value="hcaptcha">hCaptcha</option> + <option value="mcaptcha">mCaptcha</option> + <option value="recaptcha">reCAPTCHA</option> + <option value="turnstile">Turnstile</option> + </MkRadios> - <MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> - </div> - </FormSuspense> -</div> + <template v-if="botProtectionForm.state.provider === 'hcaptcha'"> + <MkInput v-model="botProtectionForm.state.hcaptchaSiteKey"> + <template #prefix><i class="ti ti-key"></i></template> + <template #label>{{ i18n.ts.hcaptchaSiteKey }}</template> + </MkInput> + <MkInput v-model="botProtectionForm.state.hcaptchaSecretKey"> + <template #prefix><i class="ti ti-key"></i></template> + <template #label>{{ i18n.ts.hcaptchaSecretKey }}</template> + </MkInput> + <FormSlot> + <template #label>{{ i18n.ts.preview }}</template> + <MkCaptcha provider="hcaptcha" :sitekey="botProtectionForm.state.hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/> + </FormSlot> + </template> + <template v-else-if="botProtectionForm.state.provider === 'mcaptcha'"> + <MkInput v-model="botProtectionForm.state.mcaptchaSiteKey"> + <template #prefix><i class="ti ti-key"></i></template> + <template #label>{{ i18n.ts.mcaptchaSiteKey }}</template> + </MkInput> + <MkInput v-model="botProtectionForm.state.mcaptchaSecretKey"> + <template #prefix><i class="ti ti-key"></i></template> + <template #label>{{ i18n.ts.mcaptchaSecretKey }}</template> + </MkInput> + <MkInput v-model="botProtectionForm.state.mcaptchaInstanceUrl"> + <template #prefix><i class="ti ti-link"></i></template> + <template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template> + </MkInput> + <FormSlot v-if="botProtectionForm.state.mcaptchaSiteKey && botProtectionForm.state.mcaptchaInstanceUrl"> + <template #label>{{ i18n.ts.preview }}</template> + <MkCaptcha provider="mcaptcha" :sitekey="botProtectionForm.state.mcaptchaSiteKey" :instanceUrl="botProtectionForm.state.mcaptchaInstanceUrl"/> + </FormSlot> + </template> + <template v-else-if="botProtectionForm.state.provider === 'recaptcha'"> + <MkInput v-model="botProtectionForm.state.recaptchaSiteKey"> + <template #prefix><i class="ti ti-key"></i></template> + <template #label>{{ i18n.ts.recaptchaSiteKey }}</template> + </MkInput> + <MkInput v-model="botProtectionForm.state.recaptchaSecretKey"> + <template #prefix><i class="ti ti-key"></i></template> + <template #label>{{ i18n.ts.recaptchaSecretKey }}</template> + </MkInput> + <FormSlot v-if="botProtectionForm.state.recaptchaSiteKey"> + <template #label>{{ i18n.ts.preview }}</template> + <MkCaptcha provider="recaptcha" :sitekey="botProtectionForm.state.recaptchaSiteKey"/> + </FormSlot> + </template> + <template v-else-if="botProtectionForm.state.provider === 'turnstile'"> + <MkInput v-model="botProtectionForm.state.turnstileSiteKey"> + <template #prefix><i class="ti ti-key"></i></template> + <template #label>{{ i18n.ts.turnstileSiteKey }}</template> + </MkInput> + <MkInput v-model="botProtectionForm.state.turnstileSecretKey"> + <template #prefix><i class="ti ti-key"></i></template> + <template #label>{{ i18n.ts.turnstileSecretKey }}</template> + </MkInput> + <FormSlot> + <template #label>{{ i18n.ts.preview }}</template> + <MkCaptcha provider="turnstile" :sitekey="botProtectionForm.state.turnstileSiteKey || '1x00000000000000000000AA'"/> + </FormSlot> + </template> + </div> +</MkFolder> </template> <script lang="ts" setup> import { defineAsyncComponent, ref } from 'vue'; -import type { CaptchaProvider } from '@/components/MkCaptcha.vue'; import MkRadios from '@/components/MkRadios.vue'; import MkInput from '@/components/MkInput.vue'; -import MkButton from '@/components/MkButton.vue'; -import FormSuspense from '@/components/form/suspense.vue'; import FormSlot from '@/components/form/slot.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { fetchInstance } from '@/instance.js'; import { i18n } from '@/i18n.js'; +import { useForm } from '@/scripts/use-form.js'; +import MkFormFooter from '@/components/MkFormFooter.vue'; +import MkFolder from '@/components/MkFolder.vue'; const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue')); -const provider = ref<CaptchaProvider | null>(null); -const hcaptchaSiteKey = ref<string | null>(null); -const hcaptchaSecretKey = ref<string | null>(null); -const mcaptchaSiteKey = ref<string | null>(null); -const mcaptchaSecretKey = ref<string | null>(null); -const mcaptchaInstanceUrl = ref<string | null>(null); -const recaptchaSiteKey = ref<string | null>(null); -const recaptchaSecretKey = ref<string | null>(null); -const turnstileSiteKey = ref<string | null>(null); -const turnstileSecretKey = ref<string | null>(null); +const meta = await misskeyApi('admin/meta'); -async function init() { - const meta = await misskeyApi('admin/meta'); - hcaptchaSiteKey.value = meta.hcaptchaSiteKey; - hcaptchaSecretKey.value = meta.hcaptchaSecretKey; - mcaptchaSiteKey.value = meta.mcaptchaSiteKey; - mcaptchaSecretKey.value = meta.mcaptchaSecretKey; - mcaptchaInstanceUrl.value = meta.mcaptchaInstanceUrl; - recaptchaSiteKey.value = meta.recaptchaSiteKey; - recaptchaSecretKey.value = meta.recaptchaSecretKey; - turnstileSiteKey.value = meta.turnstileSiteKey; - turnstileSecretKey.value = meta.turnstileSecretKey; - - provider.value = meta.enableHcaptcha ? 'hcaptcha' : - meta.enableRecaptcha ? 'recaptcha' : - meta.enableTurnstile ? 'turnstile' : - meta.enableMcaptcha ? 'mcaptcha' : null; -} - -function save() { - os.apiWithDialog('admin/update-meta', { - enableHcaptcha: provider.value === 'hcaptcha', - hcaptchaSiteKey: hcaptchaSiteKey.value, - hcaptchaSecretKey: hcaptchaSecretKey.value, - enableMcaptcha: provider.value === 'mcaptcha', - mcaptchaSiteKey: mcaptchaSiteKey.value, - mcaptchaSecretKey: mcaptchaSecretKey.value, - mcaptchaInstanceUrl: mcaptchaInstanceUrl.value, - enableRecaptcha: provider.value === 'recaptcha', - recaptchaSiteKey: recaptchaSiteKey.value, - recaptchaSecretKey: recaptchaSecretKey.value, - enableTurnstile: provider.value === 'turnstile', - turnstileSiteKey: turnstileSiteKey.value, - turnstileSecretKey: turnstileSecretKey.value, - }).then(() => { - fetchInstance(true); +const botProtectionForm = useForm({ + provider: meta.enableHcaptcha + ? 'hcaptcha' + : meta.enableRecaptcha + ? 'recaptcha' + : meta.enableTurnstile + ? 'turnstile' + : meta.enableMcaptcha + ? 'mcaptcha' + : null, + hcaptchaSiteKey: meta.hcaptchaSiteKey, + hcaptchaSecretKey: meta.hcaptchaSecretKey, + mcaptchaSiteKey: meta.mcaptchaSiteKey, + mcaptchaSecretKey: meta.mcaptchaSecretKey, + mcaptchaInstanceUrl: meta.mcaptchaInstanceUrl, + recaptchaSiteKey: meta.recaptchaSiteKey, + recaptchaSecretKey: meta.recaptchaSecretKey, + turnstileSiteKey: meta.turnstileSiteKey, + turnstileSecretKey: meta.turnstileSecretKey, +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + enableHcaptcha: state.provider === 'hcaptcha', + hcaptchaSiteKey: state.hcaptchaSiteKey, + hcaptchaSecretKey: state.hcaptchaSecretKey, + enableMcaptcha: state.provider === 'mcaptcha', + mcaptchaSiteKey: state.mcaptchaSiteKey, + mcaptchaSecretKey: state.mcaptchaSecretKey, + mcaptchaInstanceUrl: state.mcaptchaInstanceUrl, + enableRecaptcha: state.provider === 'recaptcha', + recaptchaSiteKey: state.recaptchaSiteKey, + recaptchaSecretKey: state.recaptchaSecretKey, + enableTurnstile: state.provider === 'turnstile', + turnstileSiteKey: state.turnstileSiteKey, + turnstileSecretKey: state.turnstileSecretKey, }); -} + fetchInstance(true); +}); </script> diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue index fe1b7c561d..947dde767e 100644 --- a/packages/frontend/src/pages/admin/branding.vue +++ b/packages/frontend/src/pages/admin/branding.vue @@ -117,7 +117,7 @@ import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkButton from '@/components/MkButton.vue'; import MkColorInput from '@/components/MkColorInput.vue'; -import { host } from '@/config.js'; +import { host } from '@@/js/config.js'; const iconUrl = ref<string | null>(null); const app192IconUrl = ref<string | null>(null); diff --git a/packages/frontend/src/pages/admin/external-services.vue b/packages/frontend/src/pages/admin/external-services.vue index e0b82eb02e..91f41166e9 100644 --- a/packages/frontend/src/pages/admin/external-services.vue +++ b/packages/frontend/src/pages/admin/external-services.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> <FormSuspense :p="init"> - <FormSection> + <MkFolder> <template #label>DeepL Translation</template> <div class="_gaps_m"> @@ -19,17 +19,11 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSwitch v-model="deeplIsPro"> <template #label>Pro account</template> </MkSwitch> + <MkButton primary @click="save_deepl">Save</MkButton> </div> - </FormSection> + </MkFolder> </FormSuspense> </MkSpacer> - <template #footer> - <div :class="$style.footer"> - <MkSpacer :contentMax="700" :marginMin="16" :marginMax="16"> - <MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> - </MkSpacer> - </div> - </template> </MkStickyContainer> </template> @@ -40,12 +34,12 @@ import MkInput from '@/components/MkInput.vue'; import MkButton from '@/components/MkButton.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import FormSuspense from '@/components/form/suspense.vue'; -import FormSection from '@/components/form/section.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { fetchInstance } from '@/instance.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import MkFolder from '@/components/MkFolder.vue'; const deeplAuthKey = ref<string>(''); const deeplIsPro = ref<boolean>(false); @@ -56,7 +50,7 @@ async function init() { deeplIsPro.value = meta.deeplIsPro; } -function save() { +function save_deepl() { os.apiWithDialog('admin/update-meta', { deeplAuthKey: deeplAuthKey.value, deeplIsPro: deeplIsPro.value, @@ -74,10 +68,3 @@ definePageMetadata(() => ({ icon: 'ti ti-link', })); </script> - -<style lang="scss" module> -.footer { - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); -} -</style> diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index 40dec55deb..db87bd996d 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -199,16 +199,6 @@ const menuDef = computed(() => [{ text: i18n.ts.relays, to: '/admin/relays', active: currentPage.value?.route.name === 'relays', - }, { - icon: 'ti ti-ban', - text: i18n.ts.instanceBlocking, - to: '/admin/instance-block', - active: currentPage.value?.route.name === 'instance-block', - }, { - icon: 'ti ti-ghost', - text: i18n.ts.proxyAccount, - to: '/admin/proxy-account', - active: currentPage.value?.route.name === 'proxy-account', }, { icon: 'ti ti-link', text: i18n.ts.externalServices, @@ -220,10 +210,10 @@ const menuDef = computed(() => [{ to: '/admin/system-webhook', active: currentPage.value?.route.name === 'system-webhook', }, { - icon: 'ti ti-adjustments', - text: i18n.ts.other, - to: '/admin/other-settings', - active: currentPage.value?.route.name === 'other-settings', + icon: 'ti ti-bolt', + text: i18n.ts.performance, + to: '/admin/performance', + active: currentPage.value?.route.name === 'performance', }], }, { title: i18n.ts.info, diff --git a/packages/frontend/src/pages/admin/instance-block.vue b/packages/frontend/src/pages/admin/instance-block.vue deleted file mode 100644 index e090616b26..0000000000 --- a/packages/frontend/src/pages/admin/instance-block.vue +++ /dev/null @@ -1,84 +0,0 @@ -<!-- -SPDX-FileCopyrightText: syuilo and misskey-project -SPDX-License-Identifier: AGPL-3.0-only ---> - -<template> -<MkStickyContainer> - <template #header><XHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> - <FormSuspense :p="init"> - <template v-if="tab === 'block'"> - <MkTextarea v-model="blockedHosts"> - <span>{{ i18n.ts.blockedInstances }}</span> - <template #caption>{{ i18n.ts.blockedInstancesDescription }}</template> - </MkTextarea> - </template> - <template v-else-if="tab === 'silence'"> - <MkTextarea v-model="silencedHosts" class="_formBlock"> - <span>{{ i18n.ts.silencedInstances }}</span> - <template #caption>{{ i18n.ts.silencedInstancesDescription }}</template> - </MkTextarea> - <MkTextarea v-model="mediaSilencedHosts" class="_formBlock"> - <span>{{ i18n.ts.mediaSilencedInstances }}</span> - <template #caption>{{ i18n.ts.mediaSilencedInstancesDescription }}</template> - </MkTextarea> - </template> - <MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> - </FormSuspense> - </MkSpacer> -</MkStickyContainer> -</template> - -<script lang="ts" setup> -import { ref, computed } from 'vue'; -import XHeader from './_header_.vue'; -import MkButton from '@/components/MkButton.vue'; -import MkTextarea from '@/components/MkTextarea.vue'; -import FormSuspense from '@/components/form/suspense.vue'; -import * as os from '@/os.js'; -import { misskeyApi } from '@/scripts/misskey-api.js'; -import { fetchInstance } from '@/instance.js'; -import { i18n } from '@/i18n.js'; -import { definePageMetadata } from '@/scripts/page-metadata.js'; - -const blockedHosts = ref<string>(''); -const silencedHosts = ref<string>(''); -const mediaSilencedHosts = ref<string>(''); -const tab = ref('block'); - -async function init() { - const meta = await misskeyApi('admin/meta'); - blockedHosts.value = meta.blockedHosts.join('\n'); - silencedHosts.value = meta.silencedHosts.join('\n'); - mediaSilencedHosts.value = meta.mediaSilencedHosts.join('\n'); -} - -function save() { - os.apiWithDialog('admin/update-meta', { - blockedHosts: blockedHosts.value.split('\n') || [], - silencedHosts: silencedHosts.value.split('\n') || [], - mediaSilencedHosts: mediaSilencedHosts.value.split('\n') || [], - - }).then(() => { - fetchInstance(true); - }); -} - -const headerActions = computed(() => []); - -const headerTabs = computed(() => [{ - key: 'block', - title: i18n.ts.block, - icon: 'ti ti-ban', -}, { - key: 'silence', - title: i18n.ts.silence, - icon: 'ti ti-eye-off', -}]); - -definePageMetadata(() => ({ - title: i18n.ts.instanceBlocking, - icon: 'ti ti-ban', -})); -</script> diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue index a75799696d..54eb95cd51 100644 --- a/packages/frontend/src/pages/admin/moderation.vue +++ b/packages/frontend/src/pages/admin/moderation.vue @@ -10,61 +10,102 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> <FormSuspense :p="init"> <div class="_gaps_m"> - <MkSwitch v-model="enableRegistration"> + <MkSwitch v-model="enableRegistration" @change="onChange_enableRegistration"> <template #label>{{ i18n.ts.enableRegistration }}</template> </MkSwitch> - <MkSwitch v-model="emailRequiredForSignup"> + <MkSwitch v-model="emailRequiredForSignup" @change="onChange_emailRequiredForSignup"> <template #label>{{ i18n.ts.emailRequiredForSignup }}</template> </MkSwitch> <FormLink to="/admin/server-rules">{{ i18n.ts.serverRules }}</FormLink> - <MkInput v-model="tosUrl" type="url"> - <template #prefix><i class="ti ti-link"></i></template> - <template #label>{{ i18n.ts.tosUrl }}</template> - </MkInput> - - <MkInput v-model="privacyPolicyUrl" type="url"> - <template #prefix><i class="ti ti-link"></i></template> - <template #label>{{ i18n.ts.privacyPolicyUrl }}</template> - </MkInput> - - <MkInput v-model="inquiryUrl" type="url"> - <template #prefix><i class="ti ti-link"></i></template> - <template #label>{{ i18n.ts._serverSettings.inquiryUrl }}</template> - <template #caption>{{ i18n.ts._serverSettings.inquiryUrlDescription }}</template> - </MkInput> - - <MkTextarea v-model="preservedUsernames"> + <MkFolder> + <template #icon><i class="ti ti-lock-star"></i></template> <template #label>{{ i18n.ts.preservedUsernames }}</template> - <template #caption>{{ i18n.ts.preservedUsernamesDescription }}</template> - </MkTextarea> - <MkTextarea v-model="sensitiveWords"> + <div class="_gaps"> + <MkTextarea v-model="preservedUsernames"> + <template #caption>{{ i18n.ts.preservedUsernamesDescription }}</template> + </MkTextarea> + <MkButton primary @click="save_preservedUsernames">{{ i18n.ts.save }}</MkButton> + </div> + </MkFolder> + + <MkFolder> + <template #icon><i class="ti ti-message-exclamation"></i></template> <template #label>{{ i18n.ts.sensitiveWords }}</template> - <template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template> - </MkTextarea> - <MkTextarea v-model="prohibitedWords"> + <div class="_gaps"> + <MkTextarea v-model="sensitiveWords"> + <template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template> + </MkTextarea> + <MkButton primary @click="save_sensitiveWords">{{ i18n.ts.save }}</MkButton> + </div> + </MkFolder> + + <MkFolder> + <template #icon><i class="ti ti-message-x"></i></template> <template #label>{{ i18n.ts.prohibitedWords }}</template> - <template #caption>{{ i18n.ts.prohibitedWordsDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template> - </MkTextarea> - <MkTextarea v-model="hiddenTags"> + <div class="_gaps"> + <MkTextarea v-model="prohibitedWords"> + <template #caption>{{ i18n.ts.prohibitedWordsDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template> + </MkTextarea> + <MkButton primary @click="save_prohibitedWords">{{ i18n.ts.save }}</MkButton> + </div> + </MkFolder> + + <MkFolder> + <template #icon><i class="ti ti-eye-off"></i></template> <template #label>{{ i18n.ts.hiddenTags }}</template> - <template #caption>{{ i18n.ts.hiddenTagsDescription }}</template> - </MkTextarea> + + <div class="_gaps"> + <MkTextarea v-model="hiddenTags"> + <template #caption>{{ i18n.ts.hiddenTagsDescription }}</template> + </MkTextarea> + <MkButton primary @click="save_hiddenTags">{{ i18n.ts.save }}</MkButton> + </div> + </MkFolder> + + <MkFolder> + <template #icon><i class="ti ti-eye-off"></i></template> + <template #label>{{ i18n.ts.silencedInstances }}</template> + + <div class="_gaps"> + <MkTextarea v-model="silencedHosts"> + <template #caption>{{ i18n.ts.silencedInstancesDescription }}</template> + </MkTextarea> + <MkButton primary @click="save_silencedHosts">{{ i18n.ts.save }}</MkButton> + </div> + </MkFolder> + + <MkFolder> + <template #icon><i class="ti ti-eye-off"></i></template> + <template #label>{{ i18n.ts.mediaSilencedInstances }}</template> + + <div class="_gaps"> + <MkTextarea v-model="mediaSilencedHosts"> + <template #caption>{{ i18n.ts.mediaSilencedInstancesDescription }}</template> + </MkTextarea> + <MkButton primary @click="save_mediaSilencedHosts">{{ i18n.ts.save }}</MkButton> + </div> + </MkFolder> + + <MkFolder> + <template #icon><i class="ti ti-ban"></i></template> + <template #label>{{ i18n.ts.blockedInstances }}</template> + + <div class="_gaps"> + <MkTextarea v-model="blockedHosts"> + <template #caption>{{ i18n.ts.blockedInstancesDescription }}</template> + </MkTextarea> + <MkButton primary @click="save_blockedHosts">{{ i18n.ts.save }}</MkButton> + </div> + </MkFolder> </div> </FormSuspense> </MkSpacer> - <template #footer> - <div :class="$style.footer"> - <MkSpacer :contentMax="700" :marginMin="16" :marginMax="16"> - <MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> - </MkSpacer> - </div> - </template> </MkStickyContainer> </div> </template> @@ -83,6 +124,7 @@ import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkButton from '@/components/MkButton.vue'; import FormLink from '@/components/form/link.vue'; +import MkFolder from '@/components/MkFolder.vue'; const enableRegistration = ref<boolean>(false); const emailRequiredForSignup = ref<boolean>(false); @@ -90,9 +132,9 @@ const sensitiveWords = ref<string>(''); const prohibitedWords = ref<string>(''); const hiddenTags = ref<string>(''); const preservedUsernames = ref<string>(''); -const tosUrl = ref<string | null>(null); -const privacyPolicyUrl = ref<string | null>(null); -const inquiryUrl = ref<string | null>(null); +const blockedHosts = ref<string>(''); +const silencedHosts = ref<string>(''); +const mediaSilencedHosts = ref<string>(''); async function init() { const meta = await misskeyApi('admin/meta'); @@ -102,27 +144,83 @@ async function init() { prohibitedWords.value = meta.prohibitedWords.join('\n'); hiddenTags.value = meta.hiddenTags.join('\n'); preservedUsernames.value = meta.preservedUsernames.join('\n'); - tosUrl.value = meta.tosUrl; - privacyPolicyUrl.value = meta.privacyPolicyUrl; - inquiryUrl.value = meta.inquiryUrl; + blockedHosts.value = meta.blockedHosts.join('\n'); + silencedHosts.value = meta.silencedHosts.join('\n'); + mediaSilencedHosts.value = meta.mediaSilencedHosts.join('\n'); } -function save() { +function onChange_enableRegistration(value: boolean) { + os.apiWithDialog('admin/update-meta', { + disableRegistration: !value, + }).then(() => { + fetchInstance(true); + }); +} + +function onChange_emailRequiredForSignup(value: boolean) { + os.apiWithDialog('admin/update-meta', { + emailRequiredForSignup: value, + }).then(() => { + fetchInstance(true); + }); +} + +function save_preservedUsernames() { os.apiWithDialog('admin/update-meta', { - disableRegistration: !enableRegistration.value, - emailRequiredForSignup: emailRequiredForSignup.value, - tosUrl: tosUrl.value, - privacyPolicyUrl: privacyPolicyUrl.value, - inquiryUrl: inquiryUrl.value, - sensitiveWords: sensitiveWords.value.split('\n'), - prohibitedWords: prohibitedWords.value.split('\n'), - hiddenTags: hiddenTags.value.split('\n'), preservedUsernames: preservedUsernames.value.split('\n'), }).then(() => { fetchInstance(true); }); } +function save_sensitiveWords() { + os.apiWithDialog('admin/update-meta', { + sensitiveWords: sensitiveWords.value.split('\n'), + }).then(() => { + fetchInstance(true); + }); +} + +function save_prohibitedWords() { + os.apiWithDialog('admin/update-meta', { + prohibitedWords: prohibitedWords.value.split('\n'), + }).then(() => { + fetchInstance(true); + }); +} + +function save_hiddenTags() { + os.apiWithDialog('admin/update-meta', { + hiddenTags: hiddenTags.value.split('\n'), + }).then(() => { + fetchInstance(true); + }); +} + +function save_blockedHosts() { + os.apiWithDialog('admin/update-meta', { + blockedHosts: blockedHosts.value.split('\n') || [], + }).then(() => { + fetchInstance(true); + }); +} + +function save_silencedHosts() { + os.apiWithDialog('admin/update-meta', { + silencedHosts: silencedHosts.value.split('\n') || [], + }).then(() => { + fetchInstance(true); + }); +} + +function save_mediaSilencedHosts() { + os.apiWithDialog('admin/update-meta', { + mediaSilencedHosts: mediaSilencedHosts.value.split('\n') || [], + }).then(() => { + fetchInstance(true); + }); +} + const headerTabs = computed(() => []); definePageMetadata(() => ({ @@ -130,10 +228,3 @@ definePageMetadata(() => ({ icon: 'ti ti-shield', })); </script> - -<style lang="scss" module> -.footer { - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); -} -</style> diff --git a/packages/frontend/src/pages/admin/other-settings.vue b/packages/frontend/src/pages/admin/other-settings.vue deleted file mode 100644 index 345cf333b5..0000000000 --- a/packages/frontend/src/pages/admin/other-settings.vue +++ /dev/null @@ -1,93 +0,0 @@ -<!-- -SPDX-FileCopyrightText: syuilo and misskey-project -SPDX-License-Identifier: AGPL-3.0-only ---> - -<template> -<MkStickyContainer> - <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> - <FormSuspense :p="init"> - <div class="_gaps"> - <div class="_panel" style="padding: 16px;"> - <MkSwitch v-model="enableServerMachineStats"> - <template #label>{{ i18n.ts.enableServerMachineStats }}</template> - <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template> - </MkSwitch> - </div> - - <div class="_panel" style="padding: 16px;"> - <MkSwitch v-model="enableIdenticonGeneration"> - <template #label>{{ i18n.ts.enableIdenticonGeneration }}</template> - <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template> - </MkSwitch> - </div> - - <div class="_panel" style="padding: 16px;"> - <MkSwitch v-model="enableChartsForRemoteUser"> - <template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template> - <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template> - </MkSwitch> - </div> - - <div class="_panel" style="padding: 16px;"> - <MkSwitch v-model="enableChartsForFederatedInstances"> - <template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template> - <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template> - </MkSwitch> - </div> - </div> - </FormSuspense> - </MkSpacer> -</MkStickyContainer> -</template> - -<script lang="ts" setup> -import { ref, computed } from 'vue'; -import XHeader from './_header_.vue'; -import FormSuspense from '@/components/form/suspense.vue'; -import * as os from '@/os.js'; -import { misskeyApi } from '@/scripts/misskey-api.js'; -import { fetchInstance } from '@/instance.js'; -import { i18n } from '@/i18n.js'; -import { definePageMetadata } from '@/scripts/page-metadata.js'; -import MkSwitch from '@/components/MkSwitch.vue'; - -const enableServerMachineStats = ref<boolean>(false); -const enableIdenticonGeneration = ref<boolean>(false); -const enableChartsForRemoteUser = ref<boolean>(false); -const enableChartsForFederatedInstances = ref<boolean>(false); - -async function init() { - const meta = await misskeyApi('admin/meta'); - enableServerMachineStats.value = meta.enableServerMachineStats; - enableIdenticonGeneration.value = meta.enableIdenticonGeneration; - enableChartsForRemoteUser.value = meta.enableChartsForRemoteUser; - enableChartsForFederatedInstances.value = meta.enableChartsForFederatedInstances; -} - -function save() { - os.apiWithDialog('admin/update-meta', { - enableServerMachineStats: enableServerMachineStats.value, - enableIdenticonGeneration: enableIdenticonGeneration.value, - enableChartsForRemoteUser: enableChartsForRemoteUser.value, - enableChartsForFederatedInstances: enableChartsForFederatedInstances.value, - }).then(() => { - fetchInstance(true); - }); -} - -const headerActions = computed(() => [{ - asFullButton: true, - icon: 'ti ti-check', - text: i18n.ts.save, - handler: save, -}]); - -const headerTabs = computed(() => []); - -definePageMetadata(() => ({ - title: i18n.ts.other, - icon: 'ti ti-adjustments', -})); -</script> diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts b/packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts new file mode 100644 index 0000000000..584cd3e4d9 --- /dev/null +++ b/packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { StoryObj } from '@storybook/vue3'; +import { http, HttpResponse } from 'msw'; +import { action } from '@storybook/addon-actions'; +import { commonHandlers } from '../../../.storybook/mocks.js'; +import overview_ap_requests from './overview.ap-requests.vue'; +export const Default = { + render(args) { + return { + components: { + overview_ap_requests, + }, + setup() { + return { + args, + }; + }, + template: '<overview_ap_requests />', + }; + }, + parameters: { + layout: 'fullscreen', + msw: { + handlers: [ + ...commonHandlers, + http.post('/api/charts/ap-request', async ({ request }) => { + action('POST /api/charts/ap-request')(await request.json()); + return HttpResponse.json({ + deliverFailed: [0, 0, 0, 2, 0, 0, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1, 0, 0, 0, 3, 1, 1, 2, 0, 0], + deliverSucceeded: [0, 1, 51, 34, 136, 189, 51, 17, 17, 34, 1, 17, 18, 51, 34, 68, 287, 0, 17, 33, 32, 96, 96, 0, 49, 64, 0, 32, 0, 32, 81, 48, 65, 1, 16, 50, 90, 148, 33, 43, 72, 127, 17, 138, 78, 91, 78, 91, 13, 52], + inboxReceived: [507, 1173, 1096, 871, 958, 937, 908, 1026, 956, 909, 807, 1002, 832, 995, 1039, 1047, 1109, 930, 711, 835, 764, 679, 835, 958, 634, 654, 691, 895, 811, 676, 1044, 1389, 1318, 863, 887, 952, 1011, 1061, 592, 900, 611, 595, 604, 562, 607, 621, 854, 666, 1197, 644], + }); + }), + ], + }, + }, +} satisfies StoryObj<typeof overview_ap_requests>; diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.vue b/packages/frontend/src/pages/admin/overview.ap-requests.vue index d4c83f21b6..4bbb9210af 100644 --- a/packages/frontend/src/pages/admin/overview.ap-requests.vue +++ b/packages/frontend/src/pages/admin/overview.ap-requests.vue @@ -23,6 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { onMounted, shallowRef, ref } from 'vue'; import { Chart } from 'chart.js'; import gradient from 'chartjs-plugin-gradient'; +import isChromatic from 'chromatic'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; import { chartVLine } from '@/scripts/chart-vline.js'; @@ -41,7 +42,7 @@ const { handler: externalTooltipHandler } = useChartTooltip(); const { handler: externalTooltipHandler2 } = useChartTooltip(); onMounted(async () => { - const now = new Date(); + const now = isChromatic() ? new Date('2024-08-31T10:00:00Z') : new Date(); const getDate = (ago: number) => { const y = now.getFullYear(); @@ -51,14 +52,14 @@ onMounted(async () => { return new Date(y, m, d - ago); }; - const format = (arr) => { + const format = (arr: number[]) => { return arr.map((v, i) => ({ x: getDate(i).getTime(), y: v, })); }; - const formatMinus = (arr) => { + const formatMinus = (arr: number[]) => { return arr.map((v, i) => ({ x: getDate(i).getTime(), y: -v, @@ -78,7 +79,6 @@ onMounted(async () => { type: 'line', data: { datasets: [{ - stack: 'a', parsing: false, label: 'Out: Succ', data: format(raw.deliverSucceeded).slice().reverse(), @@ -92,7 +92,6 @@ onMounted(async () => { fill: true, clip: 8, }, { - stack: 'a', parsing: false, label: 'Out: Fail', data: formatMinus(raw.deliverFailed).slice().reverse(), @@ -137,7 +136,6 @@ onMounted(async () => { min: getDate(chartLimit).getTime(), }, y: { - stacked: true, position: 'left', suggestedMax: 10, grid: { @@ -171,6 +169,9 @@ onMounted(async () => { duration: 0, }, external: externalTooltipHandler, + callbacks: { + label: context => `${context.dataset.label}: ${Math.abs(context.parsed.y)}`, + }, }, gradient, }, diff --git a/packages/frontend/src/pages/admin/overview.instances.vue b/packages/frontend/src/pages/admin/overview.instances.vue index a09db2a6d5..292e2e1dbc 100644 --- a/packages/frontend/src/pages/admin/overview.instances.vue +++ b/packages/frontend/src/pages/admin/overview.instances.vue @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref } from 'vue'; import { misskeyApi } from '@/scripts/misskey-api.js'; import * as Misskey from 'misskey-js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue'; import { defaultStore } from '@/store.js'; diff --git a/packages/frontend/src/pages/admin/overview.users.vue b/packages/frontend/src/pages/admin/overview.users.vue index a7dd4c0a48..8c9d7a8197 100644 --- a/packages/frontend/src/pages/admin/overview.users.vue +++ b/packages/frontend/src/pages/admin/overview.users.vue @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref } from 'vue'; import { misskeyApi } from '@/scripts/misskey-api.js'; import * as Misskey from 'misskey-js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; import { defaultStore } from '@/store.js'; diff --git a/packages/frontend/src/pages/admin/performance.vue b/packages/frontend/src/pages/admin/performance.vue new file mode 100644 index 0000000000..57f68a2a26 --- /dev/null +++ b/packages/frontend/src/pages/admin/performance.vue @@ -0,0 +1,192 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<MkStickyContainer> + <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> + <div class="_gaps"> + <div class="_panel" style="padding: 16px;"> + <MkSwitch v-model="enableServerMachineStats" @change="onChange_enableServerMachineStats"> + <template #label>{{ i18n.ts.enableServerMachineStats }}</template> + <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template> + </MkSwitch> + </div> + + <div class="_panel" style="padding: 16px;"> + <MkSwitch v-model="enableIdenticonGeneration" @change="onChange_enableIdenticonGeneration"> + <template #label>{{ i18n.ts.enableIdenticonGeneration }}</template> + <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template> + </MkSwitch> + </div> + + <div class="_panel" style="padding: 16px;"> + <MkSwitch v-model="enableChartsForRemoteUser" @change="onChange_enableChartsForRemoteUser"> + <template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template> + <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template> + </MkSwitch> + </div> + + <div class="_panel" style="padding: 16px;"> + <MkSwitch v-model="enableChartsForFederatedInstances" @change="onChange_enableChartsForFederatedInstances"> + <template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template> + <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template> + </MkSwitch> + </div> + + <MkFolder :defaultOpen="true"> + <template #icon><i class="ti ti-bolt"></i></template> + <template #label>Misskey® Fan-out Timeline Technology™ (FTT)</template> + <template v-if="fttForm.savedState.enableFanoutTimeline" #suffix>Enabled</template> + <template v-else #suffix>Disabled</template> + <template v-if="fttForm.modified.value" #footer> + <MkFormFooter :form="fttForm"/> + </template> + + <div class="_gaps_m"> + <MkSwitch v-model="fttForm.state.enableFanoutTimeline"> + <template #label>{{ i18n.ts.enable }}<span v-if="fttForm.modifiedStates.enableFanoutTimeline" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption> + <div>{{ i18n.ts._serverSettings.fanoutTimelineDescription }}</div> + <div><MkLink target="_blank" url="https://misskey-hub.net/docs/for-admin/features/ftt/">{{ i18n.ts.details }}</MkLink></div> + </template> + </MkSwitch> + + <MkSwitch v-model="fttForm.state.enableFanoutTimelineDbFallback"> + <template #label>{{ i18n.ts._serverSettings.fanoutTimelineDbFallback }}<span v-if="fttForm.modifiedStates.enableFanoutTimelineDbFallback" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDbFallbackDescription }}</template> + </MkSwitch> + + <MkInput v-model="fttForm.state.perLocalUserUserTimelineCacheMax" type="number"> + <template #label>perLocalUserUserTimelineCacheMax<span v-if="fttForm.modifiedStates.perLocalUserUserTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template> + </MkInput> + + <MkInput v-model="fttForm.state.perRemoteUserUserTimelineCacheMax" type="number"> + <template #label>perRemoteUserUserTimelineCacheMax<span v-if="fttForm.modifiedStates.perRemoteUserUserTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template> + </MkInput> + + <MkInput v-model="fttForm.state.perUserHomeTimelineCacheMax" type="number"> + <template #label>perUserHomeTimelineCacheMax<span v-if="fttForm.modifiedStates.perUserHomeTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template> + </MkInput> + + <MkInput v-model="fttForm.state.perUserListTimelineCacheMax" type="number"> + <template #label>perUserListTimelineCacheMax<span v-if="fttForm.modifiedStates.perUserListTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template> + </MkInput> + </div> + </MkFolder> + + <MkFolder :defaultOpen="true"> + <template #icon><i class="ti ti-bolt"></i></template> + <template #label>Misskey® Reactions Boost Technology™ (RBT)<span class="_beta">{{ i18n.ts.beta }}</span></template> + <template v-if="rbtForm.savedState.enableReactionsBuffering" #suffix>Enabled</template> + <template v-else #suffix>Disabled</template> + <template v-if="rbtForm.modified.value" #footer> + <MkFormFooter :form="rbtForm"/> + </template> + + <div class="_gaps_m"> + <MkSwitch v-model="rbtForm.state.enableReactionsBuffering"> + <template #label>{{ i18n.ts.enable }}<span v-if="rbtForm.modifiedStates.enableReactionsBuffering" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>{{ i18n.ts._serverSettings.reactionsBufferingDescription }}</template> + </MkSwitch> + </div> + </MkFolder> + </div> + </MkSpacer> +</MkStickyContainer> +</template> + +<script lang="ts" setup> +import { ref, computed } from 'vue'; +import XHeader from './_header_.vue'; +import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; +import { fetchInstance } from '@/instance.js'; +import { i18n } from '@/i18n.js'; +import { definePageMetadata } from '@/scripts/page-metadata.js'; +import MkSwitch from '@/components/MkSwitch.vue'; +import MkFolder from '@/components/MkFolder.vue'; +import MkInput from '@/components/MkInput.vue'; +import MkLink from '@/components/MkLink.vue'; +import MkButton from '@/components/MkButton.vue'; +import { useForm } from '@/scripts/use-form.js'; +import MkFormFooter from '@/components/MkFormFooter.vue'; + +const meta = await misskeyApi('admin/meta'); + +const enableServerMachineStats = ref(meta.enableServerMachineStats); +const enableIdenticonGeneration = ref(meta.enableIdenticonGeneration); +const enableChartsForRemoteUser = ref(meta.enableChartsForRemoteUser); +const enableChartsForFederatedInstances = ref(meta.enableChartsForFederatedInstances); + +function onChange_enableServerMachineStats(value: boolean) { + os.apiWithDialog('admin/update-meta', { + enableServerMachineStats: value, + }).then(() => { + fetchInstance(true); + }); +} + +function onChange_enableIdenticonGeneration(value: boolean) { + os.apiWithDialog('admin/update-meta', { + enableIdenticonGeneration: value, + }).then(() => { + fetchInstance(true); + }); +} + +function onChange_enableChartsForRemoteUser(value: boolean) { + os.apiWithDialog('admin/update-meta', { + enableChartsForRemoteUser: value, + }).then(() => { + fetchInstance(true); + }); +} + +function onChange_enableChartsForFederatedInstances(value: boolean) { + os.apiWithDialog('admin/update-meta', { + enableChartsForFederatedInstances: value, + }).then(() => { + fetchInstance(true); + }); +} + +const fttForm = useForm({ + enableFanoutTimeline: meta.enableFanoutTimeline, + enableFanoutTimelineDbFallback: meta.enableFanoutTimelineDbFallback, + perLocalUserUserTimelineCacheMax: meta.perLocalUserUserTimelineCacheMax, + perRemoteUserUserTimelineCacheMax: meta.perRemoteUserUserTimelineCacheMax, + perUserHomeTimelineCacheMax: meta.perUserHomeTimelineCacheMax, + perUserListTimelineCacheMax: meta.perUserListTimelineCacheMax, +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + enableFanoutTimeline: state.enableFanoutTimeline, + enableFanoutTimelineDbFallback: state.enableFanoutTimelineDbFallback, + perLocalUserUserTimelineCacheMax: state.perLocalUserUserTimelineCacheMax, + perRemoteUserUserTimelineCacheMax: state.perRemoteUserUserTimelineCacheMax, + perUserHomeTimelineCacheMax: state.perUserHomeTimelineCacheMax, + perUserListTimelineCacheMax: state.perUserListTimelineCacheMax, + }); + fetchInstance(true); +}); + +const rbtForm = useForm({ + enableReactionsBuffering: meta.enableReactionsBuffering, +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + enableReactionsBuffering: state.enableReactionsBuffering, + }); + fetchInstance(true); +}); + +const headerActions = computed(() => []); + +const headerTabs = computed(() => []); + +definePageMetadata(() => ({ + title: i18n.ts.other, + icon: 'ti ti-adjustments', +})); +</script> diff --git a/packages/frontend/src/pages/admin/proxy-account.vue b/packages/frontend/src/pages/admin/proxy-account.vue deleted file mode 100644 index 81db9f1da9..0000000000 --- a/packages/frontend/src/pages/admin/proxy-account.vue +++ /dev/null @@ -1,71 +0,0 @@ -<!-- -SPDX-FileCopyrightText: syuilo and misskey-project -SPDX-License-Identifier: AGPL-3.0-only ---> - -<template> -<MkStickyContainer> - <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> - <FormSuspense :p="init"> - <MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo> - <MkKeyValue> - <template #key>{{ i18n.ts.proxyAccount }}</template> - <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : i18n.ts.none }}</template> - </MkKeyValue> - - <MkButton primary @click="chooseProxyAccount">{{ i18n.ts.selectAccount }}</MkButton> - </FormSuspense> - </MkSpacer> -</MkStickyContainer> -</template> - -<script lang="ts" setup> -import { ref, computed } from 'vue'; -import * as Misskey from 'misskey-js'; -import MkKeyValue from '@/components/MkKeyValue.vue'; -import MkButton from '@/components/MkButton.vue'; -import MkInfo from '@/components/MkInfo.vue'; -import FormSuspense from '@/components/form/suspense.vue'; -import * as os from '@/os.js'; -import { misskeyApi } from '@/scripts/misskey-api.js'; -import { fetchInstance } from '@/instance.js'; -import { i18n } from '@/i18n.js'; -import { definePageMetadata } from '@/scripts/page-metadata.js'; - -const proxyAccount = ref<Misskey.entities.UserDetailed | null>(null); -const proxyAccountId = ref<string | null>(null); - -async function init() { - const meta = await misskeyApi('admin/meta'); - proxyAccountId.value = meta.proxyAccountId; - if (proxyAccountId.value) { - proxyAccount.value = await misskeyApi('users/show', { userId: proxyAccountId.value }); - } -} - -function chooseProxyAccount() { - os.selectUser({ localOnly: true }).then(user => { - proxyAccount.value = user; - proxyAccountId.value = user.id; - save(); - }); -} - -function save() { - os.apiWithDialog('admin/update-meta', { - proxyAccountId: proxyAccountId.value, - }).then(() => { - fetchInstance(true); - }); -} - -const headerActions = computed(() => []); - -const headerTabs = computed(() => []); - -definePageMetadata(() => ({ - title: i18n.ts.proxyAccount, - icon: 'ti ti-ghost', -})); -</script> diff --git a/packages/frontend/src/pages/admin/queue.vue b/packages/frontend/src/pages/admin/queue.vue index 284db894b8..512039242e 100644 --- a/packages/frontend/src/pages/admin/queue.vue +++ b/packages/frontend/src/pages/admin/queue.vue @@ -20,7 +20,7 @@ import { ref, computed, type Ref } from 'vue'; import XQueue from './queue.chart.vue'; import XHeader from './_header_.vue'; import * as os from '@/os.js'; -import * as config from '@/config.js'; +import * as config from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkButton from '@/components/MkButton.vue'; diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 3e948abdf1..ae01432d0c 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -590,6 +590,106 @@ SPDX-License-Identifier: AGPL-3.0-only </MkRange> </div> </MkFolder> + + <MkFolder v-if="matchQuery([i18n.ts._role._options.canImportAntennas, 'canImportAntennas'])"> + <template #label>{{ i18n.ts._role._options.canImportAntennas }}</template> + <template #suffix> + <span v-if="role.policies.canImportAntennas.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span> + <span v-else>{{ role.policies.canImportAntennas.value ? i18n.ts.yes : i18n.ts.no }}</span> + <span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canImportAntennas)"></i></span> + </template> + <div class="_gaps"> + <MkSwitch v-model="role.policies.canImportAntennas.useDefault" :readonly="readonly"> + <template #label>{{ i18n.ts._role.useBaseValue }}</template> + </MkSwitch> + <MkSwitch v-model="role.policies.canImportAntennas.value" :disabled="role.policies.canImportAntennas.useDefault" :readonly="readonly"> + <template #label>{{ i18n.ts.enable }}</template> + </MkSwitch> + <MkRange v-model="role.policies.canImportAntennas.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <template #label>{{ i18n.ts._role.priority }}</template> + </MkRange> + </div> + </MkFolder> + + <MkFolder v-if="matchQuery([i18n.ts._role._options.canImportBlocking, 'canImportBlocking'])"> + <template #label>{{ i18n.ts._role._options.canImportBlocking }}</template> + <template #suffix> + <span v-if="role.policies.canImportBlocking.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span> + <span v-else>{{ role.policies.canImportBlocking.value ? i18n.ts.yes : i18n.ts.no }}</span> + <span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canImportBlocking)"></i></span> + </template> + <div class="_gaps"> + <MkSwitch v-model="role.policies.canImportBlocking.useDefault" :readonly="readonly"> + <template #label>{{ i18n.ts._role.useBaseValue }}</template> + </MkSwitch> + <MkSwitch v-model="role.policies.canImportBlocking.value" :disabled="role.policies.canImportBlocking.useDefault" :readonly="readonly"> + <template #label>{{ i18n.ts.enable }}</template> + </MkSwitch> + <MkRange v-model="role.policies.canImportBlocking.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <template #label>{{ i18n.ts._role.priority }}</template> + </MkRange> + </div> + </MkFolder> + + <MkFolder v-if="matchQuery([i18n.ts._role._options.canImportFollowing, 'canImportFollowing'])"> + <template #label>{{ i18n.ts._role._options.canImportFollowing }}</template> + <template #suffix> + <span v-if="role.policies.canImportFollowing.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span> + <span v-else>{{ role.policies.canImportFollowing.value ? i18n.ts.yes : i18n.ts.no }}</span> + <span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canImportFollowing)"></i></span> + </template> + <div class="_gaps"> + <MkSwitch v-model="role.policies.canImportFollowing.useDefault" :readonly="readonly"> + <template #label>{{ i18n.ts._role.useBaseValue }}</template> + </MkSwitch> + <MkSwitch v-model="role.policies.canImportFollowing.value" :disabled="role.policies.canImportFollowing.useDefault" :readonly="readonly"> + <template #label>{{ i18n.ts.enable }}</template> + </MkSwitch> + <MkRange v-model="role.policies.canImportFollowing.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <template #label>{{ i18n.ts._role.priority }}</template> + </MkRange> + </div> + </MkFolder> + + <MkFolder v-if="matchQuery([i18n.ts._role._options.canImportMuting, 'canImportMuting'])"> + <template #label>{{ i18n.ts._role._options.canImportMuting }}</template> + <template #suffix> + <span v-if="role.policies.canImportMuting.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span> + <span v-else>{{ role.policies.canImportMuting.value ? i18n.ts.yes : i18n.ts.no }}</span> + <span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canImportMuting)"></i></span> + </template> + <div class="_gaps"> + <MkSwitch v-model="role.policies.canImportMuting.useDefault" :readonly="readonly"> + <template #label>{{ i18n.ts._role.useBaseValue }}</template> + </MkSwitch> + <MkSwitch v-model="role.policies.canImportMuting.value" :disabled="role.policies.canImportMuting.useDefault" :readonly="readonly"> + <template #label>{{ i18n.ts.enable }}</template> + </MkSwitch> + <MkRange v-model="role.policies.canImportMuting.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <template #label>{{ i18n.ts._role.priority }}</template> + </MkRange> + </div> + </MkFolder> + + <MkFolder v-if="matchQuery([i18n.ts._role._options.canImportUserLists, 'canImportUserLists'])"> + <template #label>{{ i18n.ts._role._options.canImportUserLists }}</template> + <template #suffix> + <span v-if="role.policies.canImportUserLists.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span> + <span v-else>{{ role.policies.canImportUserLists.value ? i18n.ts.yes : i18n.ts.no }}</span> + <span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canImportUserLists)"></i></span> + </template> + <div class="_gaps"> + <MkSwitch v-model="role.policies.canImportUserLists.useDefault" :readonly="readonly"> + <template #label>{{ i18n.ts._role.useBaseValue }}</template> + </MkSwitch> + <MkSwitch v-model="role.policies.canImportUserLists.value" :disabled="role.policies.canImportUserLists.useDefault" :readonly="readonly"> + <template #label>{{ i18n.ts.enable }}</template> + </MkSwitch> + <MkRange v-model="role.policies.canImportUserLists.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''"> + <template #label>{{ i18n.ts._role.priority }}</template> + </MkRange> + </div> + </MkFolder> </div> </FormSlot> </div> @@ -608,7 +708,7 @@ import MkSwitch from '@/components/MkSwitch.vue'; import MkRange from '@/components/MkRange.vue'; import FormSlot from '@/components/form/slot.vue'; import { i18n } from '@/i18n.js'; -import { ROLE_POLICIES } from '@/const.js'; +import { ROLE_POLICIES } from '@@/js/const.js'; import { instance } from '@/instance.js'; import { deepClone } from '@/scripts/clone.js'; diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index 6fb950494b..b1cbdad137 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -11,6 +11,9 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps"> <MkFolder> <template #label>{{ i18n.ts._role.baseRole }}</template> + <template #footer> + <MkButton primary rounded @click="updateBaseRole">{{ i18n.ts.save }}</MkButton> + </template> <div class="_gaps_s"> <MkInput v-model="baseRoleQ" type="search"> <template #prefix><i class="ti ti-search"></i></template> @@ -214,7 +217,45 @@ SPDX-License-Identifier: AGPL-3.0-only </MkInput> </MkFolder> - <MkButton primary rounded @click="updateBaseRole">{{ i18n.ts.save }}</MkButton> + <MkFolder v-if="matchQuery([i18n.ts._role._options.canImportAntennas, 'canImportAntennas'])"> + <template #label>{{ i18n.ts._role._options.canImportAntennas }}</template> + <template #suffix>{{ policies.canImportAntennas ? i18n.ts.yes : i18n.ts.no }}</template> + <MkSwitch v-model="policies.canImportAntennas"> + <template #label>{{ i18n.ts.enable }}</template> + </MkSwitch> + </MkFolder> + + <MkFolder v-if="matchQuery([i18n.ts._role._options.canImportBlocking, 'canImportBlocking'])"> + <template #label>{{ i18n.ts._role._options.canImportBlocking }}</template> + <template #suffix>{{ policies.canImportBlocking ? i18n.ts.yes : i18n.ts.no }}</template> + <MkSwitch v-model="policies.canImportBlocking"> + <template #label>{{ i18n.ts.enable }}</template> + </MkSwitch> + </MkFolder> + + <MkFolder v-if="matchQuery([i18n.ts._role._options.canImportFollowing, 'canImportFollowing'])"> + <template #label>{{ i18n.ts._role._options.canImportFollowing }}</template> + <template #suffix>{{ policies.canImportFollowing ? i18n.ts.yes : i18n.ts.no }}</template> + <MkSwitch v-model="policies.canImportFollowing"> + <template #label>{{ i18n.ts.enable }}</template> + </MkSwitch> + </MkFolder> + + <MkFolder v-if="matchQuery([i18n.ts._role._options.canImportMuting, 'canImportMuting'])"> + <template #label>{{ i18n.ts._role._options.canImportMuting }}</template> + <template #suffix>{{ policies.canImportMuting ? i18n.ts.yes : i18n.ts.no }}</template> + <MkSwitch v-model="policies.canImportMuting"> + <template #label>{{ i18n.ts.enable }}</template> + </MkSwitch> + </MkFolder> + + <MkFolder v-if="matchQuery([i18n.ts._role._options.canImportUserLists, 'canImportUserList'])"> + <template #label>{{ i18n.ts._role._options.canImportUserLists }}</template> + <template #suffix>{{ policies.canImportUserLists ? i18n.ts.yes : i18n.ts.no }}</template> + <MkSwitch v-model="policies.canImportUserLists"> + <template #label>{{ i18n.ts.enable }}</template> + </MkSwitch> + </MkFolder> </div> </MkFolder> <MkButton primary rounded @click="create"><i class="ti ti-plus"></i> {{ i18n.ts._role.new }}</MkButton> @@ -240,6 +281,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, reactive, ref } from 'vue'; +import { ROLE_POLICIES } from '@@/js/const.js'; import XHeader from './_header_.vue'; import MkInput from '@/components/MkInput.vue'; import MkFolder from '@/components/MkFolder.vue'; @@ -253,7 +295,6 @@ import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { instance, fetchInstance } from '@/instance.js'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; -import { ROLE_POLICIES } from '@/const.js'; import { useRouter } from '@/router/supplier.js'; const router = useRouter(); diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue index 9bccee89a5..975a4a1265 100644 --- a/packages/frontend/src/pages/admin/security.vue +++ b/packages/frontend/src/pages/admin/security.vue @@ -7,119 +7,115 @@ SPDX-License-Identifier: AGPL-3.0-only <MkStickyContainer> <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> - <FormSuspense :p="init"> - <div class="_gaps_m"> - <MkFolder> - <template #icon><i class="ti ti-shield"></i></template> - <template #label>{{ i18n.ts.botProtection }}</template> - <template v-if="enableHcaptcha" #suffix>hCaptcha</template> - <template v-else-if="enableMcaptcha" #suffix>mCaptcha</template> - <template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template> - <template v-else-if="enableTurnstile" #suffix>Turnstile</template> - <template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template> + <div class="_gaps_m"> + <XBotProtection/> - <XBotProtection/> - </MkFolder> + <MkFolder> + <template #icon><i class="ti ti-eye-off"></i></template> + <template #label>{{ i18n.ts.sensitiveMediaDetection }}</template> + <template v-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'all'" #suffix>{{ i18n.ts.all }}</template> + <template v-else-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'local'" #suffix>{{ i18n.ts.localOnly }}</template> + <template v-else-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'remote'" #suffix>{{ i18n.ts.remoteOnly }}</template> + <template v-else #suffix>{{ i18n.ts.none }}</template> + <template v-if="sensitiveMediaDetectionForm.modified.value" #footer> + <MkFormFooter :form="sensitiveMediaDetectionForm"/> + </template> - <MkFolder> - <template #icon><i class="ti ti-eye-off"></i></template> - <template #label>{{ i18n.ts.sensitiveMediaDetection }}</template> - <template v-if="sensitiveMediaDetection === 'all'" #suffix>{{ i18n.ts.all }}</template> - <template v-else-if="sensitiveMediaDetection === 'local'" #suffix>{{ i18n.ts.localOnly }}</template> - <template v-else-if="sensitiveMediaDetection === 'remote'" #suffix>{{ i18n.ts.remoteOnly }}</template> - <template v-else #suffix>{{ i18n.ts.none }}</template> + <div class="_gaps_m"> + <span>{{ i18n.ts._sensitiveMediaDetection.description }}</span> - <div class="_gaps_m"> - <span>{{ i18n.ts._sensitiveMediaDetection.description }}</span> + <MkRadios v-model="sensitiveMediaDetectionForm.state.sensitiveMediaDetection"> + <option value="none">{{ i18n.ts.none }}</option> + <option value="all">{{ i18n.ts.all }}</option> + <option value="local">{{ i18n.ts.localOnly }}</option> + <option value="remote">{{ i18n.ts.remoteOnly }}</option> + </MkRadios> - <MkRadios v-model="sensitiveMediaDetection"> - <option value="none">{{ i18n.ts.none }}</option> - <option value="all">{{ i18n.ts.all }}</option> - <option value="local">{{ i18n.ts.localOnly }}</option> - <option value="remote">{{ i18n.ts.remoteOnly }}</option> - </MkRadios> + <MkRange v-model="sensitiveMediaDetectionForm.state.sensitiveMediaDetectionSensitivity" :min="0" :max="4" :step="1" :textConverter="(v) => `${v + 1}`"> + <template #label>{{ i18n.ts._sensitiveMediaDetection.sensitivity }}</template> + <template #caption>{{ i18n.ts._sensitiveMediaDetection.sensitivityDescription }}</template> + </MkRange> - <MkRange v-model="sensitiveMediaDetectionSensitivity" :min="0" :max="4" :step="1" :textConverter="(v) => `${v + 1}`"> - <template #label>{{ i18n.ts._sensitiveMediaDetection.sensitivity }}</template> - <template #caption>{{ i18n.ts._sensitiveMediaDetection.sensitivityDescription }}</template> - </MkRange> + <MkSwitch v-model="sensitiveMediaDetectionForm.state.enableSensitiveMediaDetectionForVideos"> + <template #label>{{ i18n.ts._sensitiveMediaDetection.analyzeVideos }}<span class="_beta">{{ i18n.ts.beta }}</span></template> + <template #caption>{{ i18n.ts._sensitiveMediaDetection.analyzeVideosDescription }}</template> + </MkSwitch> - <MkSwitch v-model="enableSensitiveMediaDetectionForVideos"> - <template #label>{{ i18n.ts._sensitiveMediaDetection.analyzeVideos }}<span class="_beta">{{ i18n.ts.beta }}</span></template> - <template #caption>{{ i18n.ts._sensitiveMediaDetection.analyzeVideosDescription }}</template> - </MkSwitch> + <MkSwitch v-model="sensitiveMediaDetectionForm.state.setSensitiveFlagAutomatically"> + <template #label>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomatically }} ({{ i18n.ts.notRecommended }})</template> + <template #caption>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomaticallyDescription }}</template> + </MkSwitch> - <MkSwitch v-model="setSensitiveFlagAutomatically"> - <template #label>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomatically }} ({{ i18n.ts.notRecommended }})</template> - <template #caption>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomaticallyDescription }}</template> - </MkSwitch> + <!-- 現状 false positive が多すぎて実用に耐えない + <MkSwitch v-model="disallowUploadWhenPredictedAsPorn"> + <template #label>{{ i18n.ts._sensitiveMediaDetection.disallowUploadWhenPredictedAsPorn }}</template> + </MkSwitch> + --> + </div> + </MkFolder> - <!-- 現状 false positive が多すぎて実用に耐えない - <MkSwitch v-model="disallowUploadWhenPredictedAsPorn"> - <template #label>{{ i18n.ts._sensitiveMediaDetection.disallowUploadWhenPredictedAsPorn }}</template> - </MkSwitch> - --> + <MkFolder> + <template #label>Active Email Validation</template> + <template v-if="emailValidationForm.savedState.enableActiveEmailValidation" #suffix>Enabled</template> + <template v-else #suffix>Disabled</template> + <template v-if="emailValidationForm.modified.value" #footer> + <MkFormFooter :form="emailValidationForm"/> + </template> - <MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> - </div> - </MkFolder> + <div class="_gaps_m"> + <span>{{ i18n.ts.activeEmailValidationDescription }}</span> + <MkSwitch v-model="emailValidationForm.state.enableActiveEmailValidation"> + <template #label>Enable</template> + </MkSwitch> + <MkSwitch v-model="emailValidationForm.state.enableVerifymailApi"> + <template #label>Use Verifymail.io API</template> + </MkSwitch> + <MkInput v-model="emailValidationForm.state.verifymailAuthKey"> + <template #prefix><i class="ti ti-key"></i></template> + <template #label>Verifymail.io API Auth Key</template> + </MkInput> + <MkSwitch v-model="emailValidationForm.state.enableTruemailApi"> + <template #label>Use TrueMail API</template> + </MkSwitch> + <MkInput v-model="emailValidationForm.state.truemailInstance"> + <template #prefix><i class="ti ti-key"></i></template> + <template #label>TrueMail API Instance</template> + </MkInput> + <MkInput v-model="emailValidationForm.state.truemailAuthKey"> + <template #prefix><i class="ti ti-key"></i></template> + <template #label>TrueMail API Auth Key</template> + </MkInput> + </div> + </MkFolder> - <MkFolder> - <template #label>Active Email Validation</template> - <template v-if="enableActiveEmailValidation" #suffix>Enabled</template> - <template v-else #suffix>Disabled</template> + <MkFolder> + <template #label>Banned Email Domains</template> + <template v-if="bannedEmailDomainsForm.modified.value" #footer> + <MkFormFooter :form="bannedEmailDomainsForm"/> + </template> - <div class="_gaps_m"> - <span>{{ i18n.ts.activeEmailValidationDescription }}</span> - <MkSwitch v-model="enableActiveEmailValidation"> - <template #label>Enable</template> - </MkSwitch> - <MkSwitch v-model="enableVerifymailApi"> - <template #label>Use Verifymail.io API</template> - </MkSwitch> - <MkInput v-model="verifymailAuthKey"> - <template #prefix><i class="ti ti-key"></i></template> - <template #label>Verifymail.io API Auth Key</template> - </MkInput> - <MkSwitch v-model="enableTruemailApi"> - <template #label>Use TrueMail API</template> - </MkSwitch> - <MkInput v-model="truemailInstance"> - <template #prefix><i class="ti ti-key"></i></template> - <template #label>TrueMail API Instance</template> - </MkInput> - <MkInput v-model="truemailAuthKey"> - <template #prefix><i class="ti ti-key"></i></template> - <template #label>TrueMail API Auth Key</template> - </MkInput> - <MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> - </div> - </MkFolder> + <div class="_gaps_m"> + <MkTextarea v-model="bannedEmailDomainsForm.state.bannedEmailDomains"> + <template #label>Banned Email Domains List</template> + </MkTextarea> + </div> + </MkFolder> - <MkFolder> - <template #label>Banned Email Domains</template> + <MkFolder> + <template #label>Log IP address</template> + <template v-if="ipLoggingForm.savedState.enableIpLogging" #suffix>Enabled</template> + <template v-else #suffix>Disabled</template> + <template v-if="ipLoggingForm.modified.value" #footer> + <MkFormFooter :form="ipLoggingForm"/> + </template> - <div class="_gaps_m"> - <MkTextarea v-model="bannedEmailDomains"> - <template #label>Banned Email Domains List</template> - </MkTextarea> - <MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> - </div> - </MkFolder> - - <MkFolder> - <template #label>Log IP address</template> - <template v-if="enableIpLogging" #suffix>Enabled</template> - <template v-else #suffix>Disabled</template> - - <div class="_gaps_m"> - <MkSwitch v-model="enableIpLogging" @update:modelValue="save"> - <template #label>Enable</template> - </MkSwitch> - </div> - </MkFolder> - </div> - </FormSuspense> + <div class="_gaps_m"> + <MkSwitch v-model="ipLoggingForm.state.enableIpLogging"> + <template #label>Enable</template> + </MkSwitch> + </div> + </MkFolder> + </div> </MkSpacer> </MkStickyContainer> </template> @@ -131,83 +127,80 @@ import XHeader from './_header_.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkRadios from '@/components/MkRadios.vue'; import MkSwitch from '@/components/MkSwitch.vue'; -import FormSuspense from '@/components/form/suspense.vue'; import MkRange from '@/components/MkRange.vue'; import MkInput from '@/components/MkInput.vue'; -import MkButton from '@/components/MkButton.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { fetchInstance } from '@/instance.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import { useForm } from '@/scripts/use-form.js'; +import MkFormFooter from '@/components/MkFormFooter.vue'; -const enableHcaptcha = ref<boolean>(false); -const enableMcaptcha = ref<boolean>(false); -const enableRecaptcha = ref<boolean>(false); -const enableTurnstile = ref<boolean>(false); -const sensitiveMediaDetection = ref<string>('none'); -const sensitiveMediaDetectionSensitivity = ref<number>(0); -const setSensitiveFlagAutomatically = ref<boolean>(false); -const enableSensitiveMediaDetectionForVideos = ref<boolean>(false); -const enableIpLogging = ref<boolean>(false); -const enableActiveEmailValidation = ref<boolean>(false); -const enableVerifymailApi = ref<boolean>(false); -const verifymailAuthKey = ref<string | null>(null); -const enableTruemailApi = ref<boolean>(false); -const truemailInstance = ref<string | null>(null); -const truemailAuthKey = ref<string | null>(null); -const bannedEmailDomains = ref<string>(''); +const meta = await misskeyApi('admin/meta'); -async function init() { - const meta = await misskeyApi('admin/meta'); - enableHcaptcha.value = meta.enableHcaptcha; - enableMcaptcha.value = meta.enableMcaptcha; - enableRecaptcha.value = meta.enableRecaptcha; - enableTurnstile.value = meta.enableTurnstile; - sensitiveMediaDetection.value = meta.sensitiveMediaDetection; - sensitiveMediaDetectionSensitivity.value = - meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0 : - meta.sensitiveMediaDetectionSensitivity === 'low' ? 1 : - meta.sensitiveMediaDetectionSensitivity === 'medium' ? 2 : - meta.sensitiveMediaDetectionSensitivity === 'high' ? 3 : - meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 4 : 0; - setSensitiveFlagAutomatically.value = meta.setSensitiveFlagAutomatically; - enableSensitiveMediaDetectionForVideos.value = meta.enableSensitiveMediaDetectionForVideos; - enableIpLogging.value = meta.enableIpLogging; - enableActiveEmailValidation.value = meta.enableActiveEmailValidation; - enableVerifymailApi.value = meta.enableVerifymailApi; - verifymailAuthKey.value = meta.verifymailAuthKey; - enableTruemailApi.value = meta.enableTruemailApi; - truemailInstance.value = meta.truemailInstance; - truemailAuthKey.value = meta.truemailAuthKey; - bannedEmailDomains.value = meta.bannedEmailDomains?.join('\n') || ''; -} - -function save() { - os.apiWithDialog('admin/update-meta', { - sensitiveMediaDetection: sensitiveMediaDetection.value, +const sensitiveMediaDetectionForm = useForm({ + sensitiveMediaDetection: meta.sensitiveMediaDetection, + sensitiveMediaDetectionSensitivity: meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0 : + meta.sensitiveMediaDetectionSensitivity === 'low' ? 1 : + meta.sensitiveMediaDetectionSensitivity === 'medium' ? 2 : + meta.sensitiveMediaDetectionSensitivity === 'high' ? 3 : + meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 4 : 0, + setSensitiveFlagAutomatically: meta.setSensitiveFlagAutomatically, + enableSensitiveMediaDetectionForVideos: meta.enableSensitiveMediaDetectionForVideos, +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + sensitiveMediaDetection: state.sensitiveMediaDetection, sensitiveMediaDetectionSensitivity: - sensitiveMediaDetectionSensitivity.value === 0 ? 'veryLow' : - sensitiveMediaDetectionSensitivity.value === 1 ? 'low' : - sensitiveMediaDetectionSensitivity.value === 2 ? 'medium' : - sensitiveMediaDetectionSensitivity.value === 3 ? 'high' : - sensitiveMediaDetectionSensitivity.value === 4 ? 'veryHigh' : + state.sensitiveMediaDetectionSensitivity === 0 ? 'veryLow' : + state.sensitiveMediaDetectionSensitivity === 1 ? 'low' : + state.sensitiveMediaDetectionSensitivity === 2 ? 'medium' : + state.sensitiveMediaDetectionSensitivity === 3 ? 'high' : + state.sensitiveMediaDetectionSensitivity === 4 ? 'veryHigh' : 0, - setSensitiveFlagAutomatically: setSensitiveFlagAutomatically.value, - enableSensitiveMediaDetectionForVideos: enableSensitiveMediaDetectionForVideos.value, - enableIpLogging: enableIpLogging.value, - enableActiveEmailValidation: enableActiveEmailValidation.value, - enableVerifymailApi: enableVerifymailApi.value, - verifymailAuthKey: verifymailAuthKey.value, - enableTruemailApi: enableTruemailApi.value, - truemailInstance: truemailInstance.value, - truemailAuthKey: truemailAuthKey.value, - bannedEmailDomains: bannedEmailDomains.value.split('\n'), - }).then(() => { - fetchInstance(true); + setSensitiveFlagAutomatically: state.setSensitiveFlagAutomatically, + enableSensitiveMediaDetectionForVideos: state.enableSensitiveMediaDetectionForVideos, }); -} + fetchInstance(true); +}); + +const ipLoggingForm = useForm({ + enableIpLogging: meta.enableIpLogging, +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + enableIpLogging: state.enableIpLogging, + }); + fetchInstance(true); +}); + +const emailValidationForm = useForm({ + enableActiveEmailValidation: meta.enableActiveEmailValidation, + enableVerifymailApi: meta.enableVerifymailApi, + verifymailAuthKey: meta.verifymailAuthKey, + enableTruemailApi: meta.enableTruemailApi, + truemailInstance: meta.truemailInstance, + truemailAuthKey: meta.truemailAuthKey, +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + enableActiveEmailValidation: state.enableActiveEmailValidation, + enableVerifymailApi: state.enableVerifymailApi, + verifymailAuthKey: state.verifymailAuthKey, + enableTruemailApi: state.enableTruemailApi, + truemailInstance: state.truemailInstance, + truemailAuthKey: state.truemailAuthKey, + }); + fetchInstance(true); +}); + +const bannedEmailDomainsForm = useForm({ + bannedEmailDomains: meta.bannedEmailDomains?.join('\n') || '', +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + bannedEmailDomains: state.bannedEmailDomains.split('\n'), + }); + fetchInstance(true); +}); const headerActions = computed(() => []); diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index 6f45c212ec..537c86cb14 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -8,212 +8,235 @@ SPDX-License-Identifier: AGPL-3.0-only <MkStickyContainer> <template #header><XHeader :tabs="headerTabs"/></template> <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> - <FormSuspense :p="init"> - <div class="_gaps_m"> - <MkInput v-model="name"> - <template #label>{{ i18n.ts.instanceName }}</template> - </MkInput> + <div class="_gaps_m"> + <MkFolder :defaultOpen="true"> + <template #icon><i class="ti ti-info-circle"></i></template> + <template #label>{{ i18n.ts.info }}</template> + <template v-if="infoForm.modified.value" #footer> + <MkFormFooter :form="infoForm"/> + </template> - <MkInput v-model="shortName"> - <template #label>{{ i18n.ts._serverSettings.shortName }} ({{ i18n.ts.optional }})</template> - <template #caption>{{ i18n.ts._serverSettings.shortNameDescription }}</template> - </MkInput> - - <MkTextarea v-model="description"> - <template #label>{{ i18n.ts.instanceDescription }}</template> - </MkTextarea> - - <FormSplit :minWidth="300"> - <MkInput v-model="maintainerName"> - <template #label>{{ i18n.ts.maintainerName }}</template> + <div class="_gaps"> + <MkInput v-model="infoForm.state.name"> + <template #label>{{ i18n.ts.instanceName }}<span v-if="infoForm.modifiedStates.name" class="_modified">{{ i18n.ts.modified }}</span></template> </MkInput> - <MkInput v-model="maintainerEmail" type="email"> - <template #prefix><i class="ti ti-mail"></i></template> - <template #label>{{ i18n.ts.maintainerEmail }}</template> + <MkInput v-model="infoForm.state.shortName"> + <template #label>{{ i18n.ts._serverSettings.shortName }} ({{ i18n.ts.optional }})<span v-if="infoForm.modifiedStates.shortName" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>{{ i18n.ts._serverSettings.shortNameDescription }}</template> </MkInput> - </FormSplit> - <MkInput v-model="repositoryUrl" type="url"> - <template #label>{{ i18n.ts.repositoryUrl }}</template> - <template #prefix><i class="ti ti-link"></i></template> - <template #caption>{{ i18n.ts.repositoryUrlDescription }}</template> - </MkInput> + <MkTextarea v-model="infoForm.state.description"> + <template #label>{{ i18n.ts.instanceDescription }}<span v-if="infoForm.modifiedStates.description" class="_modified">{{ i18n.ts.modified }}</span></template> + </MkTextarea> - <MkInfo v-if="!instance.providesTarball && !repositoryUrl" warn> - {{ i18n.ts.repositoryUrlOrTarballRequired }} - </MkInfo> + <FormSplit :minWidth="300"> + <MkInput v-model="infoForm.state.maintainerName"> + <template #label>{{ i18n.ts.maintainerName }}<span v-if="infoForm.modifiedStates.maintainerName" class="_modified">{{ i18n.ts.modified }}</span></template> + </MkInput> - <MkInput v-model="impressumUrl" type="url"> - <template #label>{{ i18n.ts.impressumUrl }}</template> - <template #prefix><i class="ti ti-link"></i></template> - <template #caption>{{ i18n.ts.impressumDescription }}</template> - </MkInput> + <MkInput v-model="infoForm.state.maintainerEmail" type="email"> + <template #label>{{ i18n.ts.maintainerEmail }}<span v-if="infoForm.modifiedStates.maintainerEmail" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #prefix><i class="ti ti-mail"></i></template> + </MkInput> + </FormSplit> - <MkTextarea v-model="pinnedUsers"> - <template #label>{{ i18n.ts.pinnedUsers }}</template> + <MkInput v-model="infoForm.state.tosUrl" type="url"> + <template #label>{{ i18n.ts.tosUrl }}<span v-if="infoForm.modifiedStates.tosUrl" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #prefix><i class="ti ti-link"></i></template> + </MkInput> + + <MkInput v-model="infoForm.state.privacyPolicyUrl" type="url"> + <template #label>{{ i18n.ts.privacyPolicyUrl }}<span v-if="infoForm.modifiedStates.privacyPolicyUrl" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #prefix><i class="ti ti-link"></i></template> + </MkInput> + + <MkInput v-model="infoForm.state.inquiryUrl" type="url"> + <template #label>{{ i18n.ts._serverSettings.inquiryUrl }}<span v-if="infoForm.modifiedStates.inquiryUrl" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>{{ i18n.ts._serverSettings.inquiryUrlDescription }}</template> + <template #prefix><i class="ti ti-link"></i></template> + </MkInput> + + <MkInput v-model="infoForm.state.repositoryUrl" type="url"> + <template #label>{{ i18n.ts.repositoryUrl }}<span v-if="infoForm.modifiedStates.repositoryUrl" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>{{ i18n.ts.repositoryUrlDescription }}</template> + <template #prefix><i class="ti ti-link"></i></template> + </MkInput> + + <MkInfo v-if="!instance.providesTarball && !infoForm.state.repositoryUrl" warn> + {{ i18n.ts.repositoryUrlOrTarballRequired }} + </MkInfo> + + <MkInput v-model="infoForm.state.impressumUrl" type="url"> + <template #label>{{ i18n.ts.impressumUrl }}<span v-if="infoForm.modifiedStates.impressumUrl" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>{{ i18n.ts.impressumDescription }}</template> + <template #prefix><i class="ti ti-link"></i></template> + </MkInput> + </div> + </MkFolder> + + <MkFolder> + <template #icon><i class="ti ti-user-star"></i></template> + <template #label>{{ i18n.ts.pinnedUsers }}</template> + <template v-if="pinnedUsersForm.modified.value" #footer> + <MkFormFooter :form="pinnedUsersForm"/> + </template> + + <MkTextarea v-model="pinnedUsersForm.state.pinnedUsers"> + <template #label>{{ i18n.ts.pinnedUsers }}<span v-if="pinnedUsersForm.modifiedStates.pinnedUsers" class="_modified">{{ i18n.ts.modified }}</span></template> <template #caption>{{ i18n.ts.pinnedUsersDescription }}</template> </MkTextarea> + </MkFolder> - <FormSection> - <template #label>{{ i18n.ts.files }}</template> + <MkFolder> + <template #icon><i class="ti ti-cloud"></i></template> + <template #label>{{ i18n.ts.files }}</template> + <template v-if="filesForm.modified.value" #footer> + <MkFormFooter :form="filesForm"/> + </template> - <div class="_gaps_m"> - <MkSwitch v-model="cacheRemoteFiles"> - <template #label>{{ i18n.ts.cacheRemoteFiles }}</template> - <template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}{{ i18n.ts.youCanCleanRemoteFilesCache }}</template> + <div class="_gaps"> + <MkSwitch v-model="filesForm.state.cacheRemoteFiles"> + <template #label>{{ i18n.ts.cacheRemoteFiles }}<span v-if="filesForm.modifiedStates.cacheRemoteFiles" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}{{ i18n.ts.youCanCleanRemoteFilesCache }}</template> + </MkSwitch> + + <template v-if="filesForm.state.cacheRemoteFiles"> + <MkSwitch v-model="filesForm.state.cacheRemoteSensitiveFiles"> + <template #label>{{ i18n.ts.cacheRemoteSensitiveFiles }}<span v-if="filesForm.modifiedStates.cacheRemoteSensitiveFiles" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>{{ i18n.ts.cacheRemoteSensitiveFilesDescription }}</template> </MkSwitch> + </template> + </div> + </MkFolder> - <template v-if="cacheRemoteFiles"> - <MkSwitch v-model="cacheRemoteSensitiveFiles"> - <template #label>{{ i18n.ts.cacheRemoteSensitiveFiles }}</template> - <template #caption>{{ i18n.ts.cacheRemoteSensitiveFilesDescription }}</template> - </MkSwitch> - </template> + <MkFolder> + <template #icon><i class="ti ti-world-cog"></i></template> + <template #label>ServiceWorker</template> + <template v-if="serviceWorkerForm.modified.value" #footer> + <MkFormFooter :form="serviceWorkerForm"/> + </template> + + <div class="_gaps"> + <MkSwitch v-model="serviceWorkerForm.state.enableServiceWorker"> + <template #label>{{ i18n.ts.enableServiceworker }}<span v-if="serviceWorkerForm.modifiedStates.enableServiceWorker" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>{{ i18n.ts.serviceworkerInfo }}</template> + </MkSwitch> + + <template v-if="serviceWorkerForm.state.enableServiceWorker"> + <MkInput v-model="serviceWorkerForm.state.swPublicKey"> + <template #label>Public key<span v-if="serviceWorkerForm.modifiedStates.swPublicKey" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #prefix><i class="ti ti-key"></i></template> + </MkInput> + + <MkInput v-model="serviceWorkerForm.state.swPrivateKey"> + <template #label>Private key<span v-if="serviceWorkerForm.modifiedStates.swPrivateKey" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #prefix><i class="ti ti-key"></i></template> + </MkInput> + </template> + </div> + </MkFolder> + + <MkFolder> + <template #icon><i class="ti ti-ad"></i></template> + <template #label>{{ i18n.ts._ad.adsSettings }}</template> + <template v-if="adForm.modified.value" #footer> + <MkFormFooter :form="adForm"/> + </template> + + <div class="_gaps"> + <div class="_gaps_s"> + <MkInput v-model="adForm.state.notesPerOneAd" :min="0" type="number"> + <template #label>{{ i18n.ts._ad.notesPerOneAd }}<span v-if="adForm.modifiedStates.notesPerOneAd" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>{{ i18n.ts._ad.setZeroToDisable }}</template> + </MkInput> + <MkInfo v-if="adForm.state.notesPerOneAd > 0 && adForm.state.notesPerOneAd < 20" :warn="true"> + {{ i18n.ts._ad.adsTooClose }} + </MkInfo> </div> - </FormSection> + </div> + </MkFolder> - <FormSection> - <template #label>ServiceWorker</template> + <MkFolder> + <template #icon><i class="ti ti-world-search"></i></template> + <template #label>{{ i18n.ts._urlPreviewSetting.title }}</template> + <template v-if="urlPreviewForm.modified.value" #footer> + <MkFormFooter :form="urlPreviewForm"/> + </template> - <div class="_gaps_m"> - <MkSwitch v-model="enableServiceWorker"> - <template #label>{{ i18n.ts.enableServiceworker }}</template> - <template #caption>{{ i18n.ts.serviceworkerInfo }}</template> - </MkSwitch> + <div class="_gaps"> + <MkSwitch v-model="urlPreviewForm.state.urlPreviewEnabled"> + <template #label>{{ i18n.ts._urlPreviewSetting.enable }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewEnabled" class="_modified">{{ i18n.ts.modified }}</span></template> + </MkSwitch> - <template v-if="enableServiceWorker"> - <MkInput v-model="swPublicKey"> - <template #prefix><i class="ti ti-key"></i></template> - <template #label>Public key</template> - </MkInput> + <MkSwitch v-model="urlPreviewForm.state.urlPreviewRequireContentLength"> + <template #label>{{ i18n.ts._urlPreviewSetting.requireContentLength }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewRequireContentLength" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>{{ i18n.ts._urlPreviewSetting.requireContentLengthDescription }}</template> + </MkSwitch> - <MkInput v-model="swPrivateKey"> - <template #prefix><i class="ti ti-key"></i></template> - <template #label>Private key</template> - </MkInput> - </template> - </div> - </FormSection> + <MkInput v-model="urlPreviewForm.state.urlPreviewMaximumContentLength" type="number"> + <template #label>{{ i18n.ts._urlPreviewSetting.maximumContentLength }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewMaximumContentLength" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>{{ i18n.ts._urlPreviewSetting.maximumContentLengthDescription }}</template> + </MkInput> - <FormSection> - <template #label>Misskey® Fan-out Timeline Technology™ (FTT)</template> + <MkInput v-model="urlPreviewForm.state.urlPreviewTimeout" type="number"> + <template #label>{{ i18n.ts._urlPreviewSetting.timeout }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewTimeout" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>{{ i18n.ts._urlPreviewSetting.timeoutDescription }}</template> + </MkInput> - <div class="_gaps_m"> - <MkSwitch v-model="enableFanoutTimeline"> - <template #label>{{ i18n.ts.enable }}</template> - <template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDescription }}</template> - </MkSwitch> + <MkInput v-model="urlPreviewForm.state.urlPreviewUserAgent" type="text"> + <template #label>{{ i18n.ts._urlPreviewSetting.userAgent }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewUserAgent" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>{{ i18n.ts._urlPreviewSetting.userAgentDescription }}</template> + </MkInput> - <MkSwitch v-model="enableFanoutTimelineDbFallback"> - <template #label>{{ i18n.ts._serverSettings.fanoutTimelineDbFallback }}</template> - <template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDbFallbackDescription }}</template> - </MkSwitch> - - <MkInput v-model="perLocalUserUserTimelineCacheMax" type="number"> - <template #label>perLocalUserUserTimelineCacheMax</template> + <div> + <MkInput v-model="urlPreviewForm.state.urlPreviewSummaryProxyUrl" type="text"> + <template #label>{{ i18n.ts._urlPreviewSetting.summaryProxy }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewSummaryProxyUrl" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>[{{ i18n.ts.notUsePleaseLeaveBlank }}] {{ i18n.ts._urlPreviewSetting.summaryProxyDescription }}</template> </MkInput> - <MkInput v-model="perRemoteUserUserTimelineCacheMax" type="number"> - <template #label>perRemoteUserUserTimelineCacheMax</template> - </MkInput> - - <MkInput v-model="perUserHomeTimelineCacheMax" type="number"> - <template #label>perUserHomeTimelineCacheMax</template> - </MkInput> - - <MkInput v-model="perUserListTimelineCacheMax" type="number"> - <template #label>perUserListTimelineCacheMax</template> - </MkInput> - </div> - </FormSection> - - <FormSection> - <template #label>{{ i18n.ts._ad.adsSettings }}</template> - - <div class="_gaps_m"> - <div class="_gaps_s"> - <MkInput v-model="notesPerOneAd" :min="0" type="number"> - <template #label>{{ i18n.ts._ad.notesPerOneAd }}</template> - <template #caption>{{ i18n.ts._ad.setZeroToDisable }}</template> - </MkInput> - <MkInfo v-if="notesPerOneAd > 0 && notesPerOneAd < 20" :warn="true"> - {{ i18n.ts._ad.adsTooClose }} - </MkInfo> + <div :class="$style.subCaption"> + {{ i18n.ts._urlPreviewSetting.summaryProxyDescription2 }} + <ul style="padding-left: 20px; margin: 4px 0"> + <li>{{ i18n.ts._urlPreviewSetting.timeout }} / key:timeout</li> + <li>{{ i18n.ts._urlPreviewSetting.maximumContentLength }} / key:contentLengthLimit</li> + <li>{{ i18n.ts._urlPreviewSetting.requireContentLength }} / key:contentLengthRequired</li> + <li>{{ i18n.ts._urlPreviewSetting.userAgent }} / key:userAgent</li> + </ul> </div> </div> - </FormSection> + </div> + </MkFolder> - <FormSection> - <template #label>{{ i18n.ts._urlPreviewSetting.title }}</template> + <MkFolder> + <template #icon><i class="ti ti-ghost"></i></template> + <template #label>{{ i18n.ts.proxyAccount }}</template> - <div class="_gaps_m"> - <MkSwitch v-model="urlPreviewEnabled"> - <template #label>{{ i18n.ts._urlPreviewSetting.enable }}</template> - </MkSwitch> + <div class="_gaps"> + <MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo> + <MkKeyValue> + <template #key>{{ i18n.ts.proxyAccount }}</template> + <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : i18n.ts.none }}</template> + </MkKeyValue> - <MkSwitch v-model="urlPreviewRequireContentLength"> - <template #label>{{ i18n.ts._urlPreviewSetting.requireContentLength }}</template> - <template #caption>{{ i18n.ts._urlPreviewSetting.requireContentLengthDescription }}</template> - </MkSwitch> - - <MkInput v-model="urlPreviewMaximumContentLength" type="number"> - <template #label>{{ i18n.ts._urlPreviewSetting.maximumContentLength }}</template> - <template #caption>{{ i18n.ts._urlPreviewSetting.maximumContentLengthDescription }}</template> - </MkInput> - - <MkInput v-model="urlPreviewTimeout" type="number"> - <template #label>{{ i18n.ts._urlPreviewSetting.timeout }}</template> - <template #caption>{{ i18n.ts._urlPreviewSetting.timeoutDescription }}</template> - </MkInput> - - <MkInput v-model="urlPreviewUserAgent" type="text"> - <template #label>{{ i18n.ts._urlPreviewSetting.userAgent }}</template> - <template #caption>{{ i18n.ts._urlPreviewSetting.userAgentDescription }}</template> - </MkInput> - - <div> - <MkInput v-model="urlPreviewSummaryProxyUrl" type="text"> - <template #label>{{ i18n.ts._urlPreviewSetting.summaryProxy }}</template> - <template #caption>[{{ i18n.ts.notUsePleaseLeaveBlank }}] {{ i18n.ts._urlPreviewSetting.summaryProxyDescription }}</template> - </MkInput> - - <div :class="$style.subCaption"> - {{ i18n.ts._urlPreviewSetting.summaryProxyDescription2 }} - <ul style="padding-left: 20px; margin: 4px 0"> - <li>{{ i18n.ts._urlPreviewSetting.timeout }} / key:timeout</li> - <li>{{ i18n.ts._urlPreviewSetting.maximumContentLength }} / key:contentLengthLimit</li> - <li>{{ i18n.ts._urlPreviewSetting.requireContentLength }} / key:contentLengthRequired</li> - <li>{{ i18n.ts._urlPreviewSetting.userAgent }} / key:userAgent</li> - </ul> - </div> - </div> - </div> - </FormSection> - </div> - </FormSuspense> - </MkSpacer> - <template #footer> - <div :class="$style.footer"> - <MkSpacer :contentMax="700" :marginMin="16" :marginMax="16"> - <MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> - </MkSpacer> + <MkButton primary @click="chooseProxyAccount">{{ i18n.ts.selectAccount }}</MkButton> + </div> + </MkFolder> </div> - </template> + </MkSpacer> </MkStickyContainer> </div> </template> <script lang="ts" setup> import { ref, computed } from 'vue'; +import * as Misskey from 'misskey-js'; import XHeader from './_header_.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkInput from '@/components/MkInput.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkInfo from '@/components/MkInfo.vue'; -import FormSection from '@/components/form/section.vue'; import FormSplit from '@/components/form/split.vue'; -import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { fetchInstance, instance } from '@/instance.js'; @@ -222,95 +245,111 @@ import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkButton from '@/components/MkButton.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkSelect from '@/components/MkSelect.vue'; +import MkKeyValue from '@/components/MkKeyValue.vue'; +import { useForm } from '@/scripts/use-form.js'; +import MkFormFooter from '@/components/MkFormFooter.vue'; -const name = ref<string | null>(null); -const shortName = ref<string | null>(null); -const description = ref<string | null>(null); -const maintainerName = ref<string | null>(null); -const maintainerEmail = ref<string | null>(null); -const repositoryUrl = ref<string | null>(null); -const impressumUrl = ref<string | null>(null); -const pinnedUsers = ref<string>(''); -const cacheRemoteFiles = ref<boolean>(false); -const cacheRemoteSensitiveFiles = ref<boolean>(false); -const enableServiceWorker = ref<boolean>(false); -const swPublicKey = ref<string | null>(null); -const swPrivateKey = ref<string | null>(null); -const enableFanoutTimeline = ref<boolean>(false); -const enableFanoutTimelineDbFallback = ref<boolean>(false); -const perLocalUserUserTimelineCacheMax = ref<number>(0); -const perRemoteUserUserTimelineCacheMax = ref<number>(0); -const perUserHomeTimelineCacheMax = ref<number>(0); -const perUserListTimelineCacheMax = ref<number>(0); -const notesPerOneAd = ref<number>(0); -const urlPreviewEnabled = ref<boolean>(true); -const urlPreviewTimeout = ref<number>(10000); -const urlPreviewMaximumContentLength = ref<number>(1024 * 1024 * 10); -const urlPreviewRequireContentLength = ref<boolean>(true); -const urlPreviewUserAgent = ref<string | null>(null); -const urlPreviewSummaryProxyUrl = ref<string | null>(null); +const meta = await misskeyApi('admin/meta'); -async function init(): Promise<void> { - const meta = await misskeyApi('admin/meta'); - name.value = meta.name; - shortName.value = meta.shortName; - description.value = meta.description; - maintainerName.value = meta.maintainerName; - maintainerEmail.value = meta.maintainerEmail; - repositoryUrl.value = meta.repositoryUrl; - impressumUrl.value = meta.impressumUrl; - pinnedUsers.value = meta.pinnedUsers.join('\n'); - cacheRemoteFiles.value = meta.cacheRemoteFiles; - cacheRemoteSensitiveFiles.value = meta.cacheRemoteSensitiveFiles; - enableServiceWorker.value = meta.enableServiceWorker; - swPublicKey.value = meta.swPublickey; - swPrivateKey.value = meta.swPrivateKey; - enableFanoutTimeline.value = meta.enableFanoutTimeline; - enableFanoutTimelineDbFallback.value = meta.enableFanoutTimelineDbFallback; - perLocalUserUserTimelineCacheMax.value = meta.perLocalUserUserTimelineCacheMax; - perRemoteUserUserTimelineCacheMax.value = meta.perRemoteUserUserTimelineCacheMax; - perUserHomeTimelineCacheMax.value = meta.perUserHomeTimelineCacheMax; - perUserListTimelineCacheMax.value = meta.perUserListTimelineCacheMax; - notesPerOneAd.value = meta.notesPerOneAd; - urlPreviewEnabled.value = meta.urlPreviewEnabled; - urlPreviewTimeout.value = meta.urlPreviewTimeout; - urlPreviewMaximumContentLength.value = meta.urlPreviewMaximumContentLength; - urlPreviewRequireContentLength.value = meta.urlPreviewRequireContentLength; - urlPreviewUserAgent.value = meta.urlPreviewUserAgent; - urlPreviewSummaryProxyUrl.value = meta.urlPreviewSummaryProxyUrl; -} +const proxyAccount = ref(meta.proxyAccountId ? await misskeyApi('users/show', { userId: meta.proxyAccountId }) : null); -async function save() { +const infoForm = useForm({ + name: meta.name ?? '', + shortName: meta.shortName ?? '', + description: meta.description ?? '', + maintainerName: meta.maintainerName ?? '', + maintainerEmail: meta.maintainerEmail ?? '', + tosUrl: meta.tosUrl ?? '', + privacyPolicyUrl: meta.privacyPolicyUrl ?? '', + inquiryUrl: meta.inquiryUrl ?? '', + repositoryUrl: meta.repositoryUrl ?? '', + impressumUrl: meta.impressumUrl ?? '', +}, async (state) => { await os.apiWithDialog('admin/update-meta', { - name: name.value, - shortName: shortName.value === '' ? null : shortName.value, - description: description.value, - maintainerName: maintainerName.value, - maintainerEmail: maintainerEmail.value, - repositoryUrl: repositoryUrl.value, - impressumUrl: impressumUrl.value, - pinnedUsers: pinnedUsers.value.split('\n'), - cacheRemoteFiles: cacheRemoteFiles.value, - cacheRemoteSensitiveFiles: cacheRemoteSensitiveFiles.value, - enableServiceWorker: enableServiceWorker.value, - swPublicKey: swPublicKey.value, - swPrivateKey: swPrivateKey.value, - enableFanoutTimeline: enableFanoutTimeline.value, - enableFanoutTimelineDbFallback: enableFanoutTimelineDbFallback.value, - perLocalUserUserTimelineCacheMax: perLocalUserUserTimelineCacheMax.value, - perRemoteUserUserTimelineCacheMax: perRemoteUserUserTimelineCacheMax.value, - perUserHomeTimelineCacheMax: perUserHomeTimelineCacheMax.value, - perUserListTimelineCacheMax: perUserListTimelineCacheMax.value, - notesPerOneAd: notesPerOneAd.value, - urlPreviewEnabled: urlPreviewEnabled.value, - urlPreviewTimeout: urlPreviewTimeout.value, - urlPreviewMaximumContentLength: urlPreviewMaximumContentLength.value, - urlPreviewRequireContentLength: urlPreviewRequireContentLength.value, - urlPreviewUserAgent: urlPreviewUserAgent.value, - urlPreviewSummaryProxyUrl: urlPreviewSummaryProxyUrl.value, + name: state.name, + shortName: state.shortName === '' ? null : state.shortName, + description: state.description, + maintainerName: state.maintainerName, + maintainerEmail: state.maintainerEmail, + tosUrl: state.tosUrl, + privacyPolicyUrl: state.privacyPolicyUrl, + inquiryUrl: state.inquiryUrl, + repositoryUrl: state.repositoryUrl, + impressumUrl: state.impressumUrl, }); - fetchInstance(true); +}); + +const pinnedUsersForm = useForm({ + pinnedUsers: meta.pinnedUsers.join('\n'), +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + pinnedUsers: state.pinnedUsers.split('\n'), + }); + fetchInstance(true); +}); + +const filesForm = useForm({ + cacheRemoteFiles: meta.cacheRemoteFiles, + cacheRemoteSensitiveFiles: meta.cacheRemoteSensitiveFiles, +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + cacheRemoteFiles: state.cacheRemoteFiles, + cacheRemoteSensitiveFiles: state.cacheRemoteSensitiveFiles, + }); + fetchInstance(true); +}); + +const serviceWorkerForm = useForm({ + enableServiceWorker: meta.enableServiceWorker, + swPublicKey: meta.swPublickey ?? '', + swPrivateKey: meta.swPrivateKey ?? '', +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + enableServiceWorker: state.enableServiceWorker, + swPublicKey: state.swPublicKey, + swPrivateKey: state.swPrivateKey, + }); + fetchInstance(true); +}); + +const adForm = useForm({ + notesPerOneAd: meta.notesPerOneAd, +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + notesPerOneAd: state.notesPerOneAd, + }); + fetchInstance(true); +}); + +const urlPreviewForm = useForm({ + urlPreviewEnabled: meta.urlPreviewEnabled, + urlPreviewTimeout: meta.urlPreviewTimeout, + urlPreviewMaximumContentLength: meta.urlPreviewMaximumContentLength, + urlPreviewRequireContentLength: meta.urlPreviewRequireContentLength, + urlPreviewUserAgent: meta.urlPreviewUserAgent ?? '', + urlPreviewSummaryProxyUrl: meta.urlPreviewSummaryProxyUrl ?? '', +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + urlPreviewEnabled: state.urlPreviewEnabled, + urlPreviewTimeout: state.urlPreviewTimeout, + urlPreviewMaximumContentLength: state.urlPreviewMaximumContentLength, + urlPreviewRequireContentLength: state.urlPreviewRequireContentLength, + urlPreviewUserAgent: state.urlPreviewUserAgent, + urlPreviewSummaryProxyUrl: state.urlPreviewSummaryProxyUrl, + }); + fetchInstance(true); +}); + +function chooseProxyAccount() { + os.selectUser({ localOnly: true }).then(user => { + proxyAccount.value = user; + os.apiWithDialog('admin/update-meta', { + proxyAccountId: user.id, + }).then(() => { + fetchInstance(true); + }); + }); } const headerTabs = computed(() => []); @@ -322,11 +361,6 @@ definePageMetadata(() => ({ </script> <style lang="scss" module> -.footer { - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); -} - .subCaption { font-size: 0.85em; color: var(--fgTransparentWeak); diff --git a/packages/frontend/src/pages/admin/system-webhook.item.vue b/packages/frontend/src/pages/admin/system-webhook.item.vue index 0c07122af3..7744d1aed6 100644 --- a/packages/frontend/src/pages/admin/system-webhook.item.vue +++ b/packages/frontend/src/pages/admin/system-webhook.item.vue @@ -4,33 +4,47 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div :class="$style.main"> - <span :class="$style.icon"> - <i v-if="!entity.isActive" class="ti ti-player-pause"/> - <i v-else-if="entity.latestStatus === null" class="ti ti-circle"/> - <i - v-else-if="[200, 201, 204].includes(entity.latestStatus)" - class="ti ti-check" - :style="{ color: 'var(--success)' }" - /> - <i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--error)' }"/> - </span> - <span :class="$style.text">{{ entity.name || entity.url }}</span> - <span :class="$style.suffix"> - <MkTime v-if="entity.latestSentAt" :time="entity.latestSentAt" style="margin-right: 8px"/> - <button :class="$style.suffixButton" @click="onEditClick"> - <i class="ti ti-settings"></i> - </button> - <button :class="$style.suffixButton" @click="onDeleteClick"> - <i class="ti ti-trash"></i> - </button> - </span> -</div> + <MkFolder> + <template #label>{{ entity.name || entity.url }}</template> + <template #icon> + <i v-if="!entity.isActive" class="ti ti-player-pause"/> + <i v-else-if="entity.latestStatus === null" class="ti ti-circle"/> + <i + v-else-if="[200, 201, 204].includes(entity.latestStatus)" + class="ti ti-check" + :style="{ color: 'var(--success)' }" + /> + <i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--error)' }"/> + </template> + <template #suffix> + <MkTime v-if="entity.latestSentAt" :time="entity.latestSentAt" style="margin-right: 8px"/> + <span v-else>-</span> + </template> + + <div class="_gaps"> + <MkKeyValue> + <template #key>latestStatus</template> + <template #value>{{ entity.latestStatus ?? '-' }}</template> + </MkKeyValue> + <div class="_buttons"> + <MkButton @click="onEditClick"> + <i class="ti ti-settings"></i> {{ i18n.ts.edit }} + </MkButton> + <MkButton danger @click="onDeleteClick"> + <i class="ti ti-trash"></i> {{ i18n.ts.delete }} + </MkButton> + </div> + </div> + </MkFolder> </template> <script lang="ts" setup> import { entities } from 'misskey-js'; import { toRefs } from 'vue'; +import MkFolder from '@/components/MkFolder.vue'; +import { i18n } from '@/i18n.js'; +import MkButton from '@/components/MkButton.vue'; +import MkKeyValue from '@/components/MkKeyValue.vue'; const emit = defineEmits<{ (ev: 'edit', value: entities.SystemWebhook): void; @@ -54,64 +68,10 @@ function onDeleteClick() { </script> <style module lang="scss"> -.main { - display: flex; - align-items: center; - width: 100%; - box-sizing: border-box; - padding: 10px 14px; - background: var(--buttonBg); - border: none; - border-radius: 6px; - font-size: 0.9em; - - &:hover { - text-decoration: none; - background: var(--buttonHoverBg); - } - - &.active { - color: var(--accent); - background: var(--buttonHoverBg); - } -} - .icon { margin-right: 0.75em; flex-shrink: 0; text-align: center; color: var(--fgTransparentWeak); } - -.text { - flex-shrink: 1; - white-space: normal; - padding-right: 12px; - text-align: center; -} - -.suffix { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - gaps: 4px; - margin-left: auto; - margin-right: -8px; - opacity: 0.7; - white-space: nowrap; -} - -.suffixButton { - background: transparent; - border: none; - border-radius: 9999px; - margin-top: -8px; - margin-bottom: -8px; - padding: 8px; - - &:hover { - background: var(--buttonBg); - } -} </style> diff --git a/packages/frontend/src/pages/admin/system-webhook.vue b/packages/frontend/src/pages/admin/system-webhook.vue index 7a40eec944..c59abda24a 100644 --- a/packages/frontend/src/pages/admin/system-webhook.vue +++ b/packages/frontend/src/pages/admin/system-webhook.vue @@ -11,8 +11,8 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSpacer :contentMax="900"> <div class="_gaps_m"> - <MkButton :class="$style.linkButton" full @click="onCreateWebhookClicked"> - {{ i18n.ts._webhookSettings.createWebhook }} + <MkButton primary @click="onCreateWebhookClicked"> + <i class="ti ti-plus"></i> {{ i18n.ts._webhookSettings.createWebhook }} </MkButton> <FormSection> @@ -89,8 +89,5 @@ definePageMetadata(() => ({ </script> <style module lang="scss"> -.linkButton { - text-align: left; - padding: 10px 18px; -} + </style> diff --git a/packages/frontend/src/pages/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue index ea64e457e3..22c5231dd9 100644 --- a/packages/frontend/src/pages/antenna-timeline.vue +++ b/packages/frontend/src/pages/antenna-timeline.vue @@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, watch, ref, shallowRef } from 'vue'; import * as Misskey from 'misskey-js'; import MkTimeline from '@/components/MkTimeline.vue'; -import { scroll } from '@/scripts/scroll.js'; +import { scroll } from '@@/js/scroll.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 7e7b724023..8b014c7a4e 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -82,7 +82,7 @@ import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { deviceKind } from '@/scripts/device-kind.js'; import MkNotes from '@/components/MkNotes.vue'; -import { url } from '@/config.js'; +import { url } from '@@/js/config.js'; import { favoritedChannelsCache } from '@/cache.js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue index fb984de368..7e5f0423f6 100644 --- a/packages/frontend/src/pages/clip.vue +++ b/packages/frontend/src/pages/clip.vue @@ -39,11 +39,13 @@ import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; -import { url } from '@/config.js'; +import { url } from '@@/js/config.js'; import MkButton from '@/components/MkButton.vue'; import { clipsCache } from '@/cache.js'; import { isSupportShare } from '@/scripts/navigator.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; +import { genEmbedCode } from '@/scripts/get-embed-code.js'; +import type { MenuItem } from '@/types/menu.js'; const props = defineProps<{ clipId: string, @@ -127,21 +129,41 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{ clipsCache.delete(); }, }, ...(clip.value.isPublic ? [{ - icon: 'ti ti-link', - text: i18n.ts.copyUrl, - handler: async (): Promise<void> => { - copyToClipboard(`${url}/clips/${clip.value.id}`); - os.success(); - }, -}] : []), ...(clip.value.isPublic && isSupportShare() ? [{ icon: 'ti ti-share', text: i18n.ts.share, - handler: async (): Promise<void> => { - navigator.share({ - title: clip.value.name, - text: clip.value.description, - url: `${url}/clips/${clip.value.id}`, + handler: (ev: MouseEvent): void => { + const menuItems: MenuItem[] = []; + + menuItems.push({ + icon: 'ti ti-link', + text: i18n.ts.copyUrl, + action: () => { + copyToClipboard(`${url}/clips/${clip.value!.id}`); + os.success(); + }, + }, { + icon: 'ti ti-code', + text: i18n.ts.genEmbedCode, + action: () => { + genEmbedCode('clips', clip.value!.id); + }, }); + + if (isSupportShare()) { + menuItems.push({ + icon: 'ti ti-share', + text: i18n.ts.share, + action: async () => { + navigator.share({ + title: clip.value!.name, + text: clip.value!.description ?? '', + url: `${url}/clips/${clip.value!.id}`, + }); + }, + }); + } + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target); }, }] : []), { icon: 'ti ti-trash', diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue index eea3f68130..4747aa5205 100644 --- a/packages/frontend/src/pages/custom-emojis-manager.vue +++ b/packages/frontend/src/pages/custom-emojis-manager.vue @@ -344,6 +344,7 @@ definePageMetadata(() => ({ > .img { width: 42px; height: 42px; + object-fit: contain; } > .body { @@ -390,6 +391,7 @@ definePageMetadata(() => ({ > .img { width: 32px; height: 32px; + object-fit: contain; } > .body { diff --git a/packages/frontend/src/pages/drive.file.info.vue b/packages/frontend/src/pages/drive.file.info.vue index 3026d00a2c..12ebbbe3ff 100644 --- a/packages/frontend/src/pages/drive.file.info.vue +++ b/packages/frontend/src/pages/drive.file.info.vue @@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkKeyValue> </button> <button class="_button" :class="$style.kvEditBtn" @click="describe()"> - <MkKeyValue> + <MkKeyValue :class="$style.multiline"> <template #key>{{ i18n.ts.description }}</template> <template #value>{{ file.comment ? file.comment : `(${i18n.ts.none})` }}<i class="ti ti-pencil" :class="$style.kvEditIcon"></i></template> </MkKeyValue> @@ -99,12 +99,12 @@ const file = ref<Misskey.entities.DriveFile>(); const folderHierarchy = computed(() => { if (!file.value) return [i18n.ts.drive]; const folderNames = [i18n.ts.drive]; - + function get(folder: Misskey.entities.DriveFolder) { if (folder.parent) get(folder.parent); folderNames.push(folder.name); } - + if (file.value.folder) get(file.value.folder); return folderNames; }); @@ -313,6 +313,10 @@ onMounted(async () => { padding: .5rem 1rem; } +.multiline { + white-space: pre-wrap; +} + .kvEditBtn { text-align: start; display: block; diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue index 0f0b7e1ea8..4db952eac2 100644 --- a/packages/frontend/src/pages/drop-and-fusion.game.vue +++ b/packages/frontend/src/pages/drop-and-fusion.game.vue @@ -205,8 +205,8 @@ import { claimAchievement } from '@/scripts/achievements.js'; import { defaultStore } from '@/store.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; -import { useInterval } from '@/scripts/use-interval.js'; -import { apiUrl } from '@/config.js'; +import { useInterval } from '@@/js/use-interval.js'; +import { apiUrl } from '@@/js/config.js'; import { $i } from '@/account.js'; import * as sound from '@/scripts/sound.js'; import MkRange from '@/components/MkRange.vue'; diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue index d282ed4810..fd6fadd0b3 100644 --- a/packages/frontend/src/pages/flash/flash-edit.vue +++ b/packages/frontend/src/pages/flash/flash-edit.vue @@ -11,6 +11,12 @@ SPDX-License-Identifier: AGPL-3.0-only <MkInput v-model="title"> <template #label>{{ i18n.ts._play.title }}</template> </MkInput> + <MkSelect v-model="visibility"> + <template #label>{{ i18n.ts.visibility }}</template> + <template #caption>{{ i18n.ts._play.visibilityDescription }}</template> + <option :key="'public'" :value="'public'">{{ i18n.ts.public }}</option> + <option :key="'private'" :value="'private'">{{ i18n.ts.private }}</option> + </MkSelect> <MkTextarea v-model="summary" :mfmAutocomplete="true" :mfmPreview="true"> <template #label>{{ i18n.ts._play.summary }}</template> </MkTextarea> @@ -18,19 +24,19 @@ SPDX-License-Identifier: AGPL-3.0-only <MkCodeEditor v-model="script" lang="is"> <template #label>{{ i18n.ts._play.script }}</template> </MkCodeEditor> - <MkSelect v-model="visibility"> - <template #label>{{ i18n.ts.visibility }}</template> - <template #caption>{{ i18n.ts._play.visibilityDescription }}</template> - <option :key="'public'" :value="'public'">{{ i18n.ts.public }}</option> - <option :key="'private'" :value="'private'">{{ i18n.ts.private }}</option> - </MkSelect> - <div class="_buttons"> - <MkButton primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> - <MkButton @click="show"><i class="ti ti-eye"></i> {{ i18n.ts.show }}</MkButton> - <MkButton v-if="flash" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> - </div> </div> </MkSpacer> + <template #footer> + <div :class="$style.footer"> + <MkSpacer> + <div class="_buttons"> + <MkButton primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> + <MkButton @click="show"><i class="ti ti-eye"></i> {{ i18n.ts.show }}</MkButton> + <MkButton v-if="flash" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + </div> + </MkSpacer> + </div> + </template> </MkStickyContainer> </template> @@ -459,3 +465,10 @@ definePageMetadata(() => ({ title: flash.value ? `${i18n.ts._play.edit}: ${flash.value.title}` : i18n.ts._play.new, })); </script> +<style lang="scss" module> +.footer { + backdrop-filter: var(--blur, blur(15px)); + background: var(--acrylicBg); + border-top: solid .5px var(--divider); +} +</style> diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index a6a99ba633..cf10bee0f5 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -68,7 +68,7 @@ import { Interpreter, Parser, values } from '@syuilo/aiscript'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { url } from '@/config.js'; +import { url } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkAsUi from '@/components/MkAsUi.vue'; @@ -80,7 +80,7 @@ import { defaultStore } from '@/store.js'; import { $i } from '@/account.js'; import { isSupportShare } from '@/scripts/navigator.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; -import { MenuItem } from '@/types/menu'; +import type { MenuItem } from '@/types/menu.js'; import { pleaseLogin } from '@/scripts/please-login.js'; const props = defineProps<{ @@ -104,18 +104,23 @@ function fetchFlash() { function share(ev: MouseEvent) { if (!flash.value) return; - os.popupMenu([ - { - text: i18n.ts.shareWithNote, - icon: 'ti ti-pencil', - action: shareWithNote, - }, - ...(isSupportShare() ? [{ + const menuItems: MenuItem[] = []; + + menuItems.push({ + text: i18n.ts.shareWithNote, + icon: 'ti ti-pencil', + action: shareWithNote, + }); + + if (isSupportShare()) { + menuItems.push({ text: i18n.ts.share, icon: 'ti ti-share', action: shareWithNavigator, - }] : []), - ], ev.currentTarget ?? ev.target); + }); + } + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target); } function copyLink() { diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue index 5a9c978dab..8c4dfc3b83 100644 --- a/packages/frontend/src/pages/gallery/post.vue +++ b/packages/frontend/src/pages/gallery/post.vue @@ -72,7 +72,7 @@ import MkContainer from '@/components/MkContainer.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue'; import MkFollowButton from '@/components/MkFollowButton.vue'; -import { url } from '@/config.js'; +import { url } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { defaultStore } from '@/store.js'; @@ -80,7 +80,7 @@ import { $i } from '@/account.js'; import { isSupportShare } from '@/scripts/navigator.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { useRouter } from '@/router/supplier.js'; -import { MenuItem } from '@/types/menu'; +import type { MenuItem } from '@/types/menu.js'; const router = useRouter(); @@ -171,35 +171,35 @@ function reportAbuse() { function showMenu(ev: MouseEvent) { if (!post.value) return; - const menu: MenuItem[] = [ - ...($i && $i.id !== post.value.userId ? [ - { - icon: 'ti ti-exclamation-circle', - text: i18n.ts.reportAbuse, - action: reportAbuse, - }, - ...($i.isModerator || $i.isAdmin ? [ - { - type: 'divider' as const, - }, - { - icon: 'ti ti-trash', - text: i18n.ts.delete, - danger: true, - action: () => os.confirm({ - type: 'warning', - text: i18n.ts.deleteConfirm, - }).then(({ canceled }) => { - if (canceled || !post.value) return; + const menuItems: MenuItem[] = []; - os.apiWithDialog('gallery/posts/delete', { postId: post.value.id }); - }), - }, - ] : []), - ] : []), - ]; + if ($i && $i.id !== post.value.userId) { + menuItems.push({ + icon: 'ti ti-exclamation-circle', + text: i18n.ts.reportAbuse, + action: reportAbuse, + }); - os.popupMenu(menu, ev.currentTarget ?? ev.target); + if ($i.isModerator || $i.isAdmin) { + menuItems.push({ + type: 'divider', + }, { + icon: 'ti ti-trash', + text: i18n.ts.delete, + danger: true, + action: () => os.confirm({ + type: 'warning', + text: i18n.ts.deleteConfirm, + }).then(({ canceled }) => { + if (canceled || !post.value) return; + + os.apiWithDialog('gallery/posts/delete', { postId: post.value.id }); + }), + }); + } + } + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target); } watch(() => props.postId, fetchPost, { immediate: true }); diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 4ba428d536..c69530b343 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -134,7 +134,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed, watch } from 'vue'; import * as Misskey from 'misskey-js'; -import MkChart from '@/components/MkChart.vue'; +import MkChart, { type ChartSrc } from '@/components/MkChart.vue'; import MkObjectView from '@/components/MkObjectView.vue'; import FormLink from '@/components/form/link.vue'; import MkLink from '@/components/MkLink.vue'; @@ -150,7 +150,7 @@ import { iAmModerator, iAmAdmin } from '@/account.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; -import MkPagination from '@/components/MkPagination.vue'; +import MkPagination, { type Paging } from '@/components/MkPagination.vue'; import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js'; import { dateString } from '@/filters/date.js'; @@ -162,7 +162,7 @@ const props = defineProps<{ const tab = ref('overview'); -const chartSrc = ref('instance-requests'); +const chartSrc = ref<ChartSrc>('instance-requests'); const meta = ref<Misskey.entities.AdminMetaResponse | null>(null); const instance = ref<Misskey.entities.FederationInstance | null>(null); const suspensionState = ref<'none' | 'manuallySuspended' | 'goneSuspended' | 'autoSuspendedForNotResponding'>('none'); @@ -173,7 +173,7 @@ const faviconUrl = ref<string | null>(null); const moderationNote = ref(''); const usersPagination = { - endpoint: iAmModerator ? 'admin/show-users' : 'users' as const, + endpoint: iAmModerator ? 'admin/show-users' : 'users', limit: 10, params: { sort: '+updatedAt', @@ -181,11 +181,14 @@ const usersPagination = { hostname: props.host, }, offsetMode: true, -}; +} satisfies Paging; -watch(moderationNote, async () => { - await misskeyApi('admin/federation/update-instance', { host: instance.value.host, moderationNote: moderationNote.value }); -}); +if (iAmModerator) { + watch(moderationNote, async () => { + if (instance.value == null) return; + await misskeyApi('admin/federation/update-instance', { host: instance.value.host, moderationNote: moderationNote.value }); + }); +} async function fetch(): Promise<void> { if (iAmAdmin) { @@ -203,6 +206,7 @@ async function fetch(): Promise<void> { } async function toggleBlock(): Promise<void> { + if (!iAmAdmin) return; if (!meta.value) throw new Error('No meta?'); if (!instance.value) throw new Error('No instance?'); const { host } = instance.value; @@ -212,6 +216,7 @@ async function toggleBlock(): Promise<void> { } async function toggleSilenced(): Promise<void> { + if (!iAmAdmin) return; if (!meta.value) throw new Error('No meta?'); if (!instance.value) throw new Error('No instance?'); const { host } = instance.value; @@ -222,6 +227,7 @@ async function toggleSilenced(): Promise<void> { } async function toggleMediaSilenced(): Promise<void> { + if (!iAmAdmin) return; if (!meta.value) throw new Error('No meta?'); if (!instance.value) throw new Error('No instance?'); const { host } = instance.value; @@ -232,6 +238,7 @@ async function toggleMediaSilenced(): Promise<void> { } async function stopDelivery(): Promise<void> { + if (!iAmModerator) return; if (!instance.value) throw new Error('No instance?'); suspensionState.value = 'manuallySuspended'; await misskeyApi('admin/federation/update-instance', { @@ -241,6 +248,7 @@ async function stopDelivery(): Promise<void> { } async function resumeDelivery(): Promise<void> { + if (!iAmModerator) return; if (!instance.value) throw new Error('No instance?'); suspensionState.value = 'none'; await misskeyApi('admin/federation/update-instance', { @@ -250,6 +258,7 @@ async function resumeDelivery(): Promise<void> { } function refreshMetadata(): void { + if (!iAmModerator) return; if (!instance.value) throw new Error('No instance?'); misskeyApi('admin/federation/refresh-remote-instance-metadata', { host: instance.value.host, diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue index a2ceb222fe..5f195693cc 100644 --- a/packages/frontend/src/pages/my-lists/list.vue +++ b/packages/frontend/src/pages/my-lists/list.vue @@ -134,12 +134,14 @@ async function removeUser(item, ev) { async function showMembershipMenu(item, ev) { const withRepliesRef = ref(item.withReplies); + os.popupMenu([{ type: 'switch', text: i18n.ts.showRepliesToOthersInTimeline, icon: 'ti ti-messages', ref: withRepliesRef, }], ev.currentTarget ?? ev.target); + watch(withRepliesRef, withReplies => { misskeyApi('users/lists/update-membership', { listId: list.value!.id, diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue index 28f5838296..bd93fc8369 100644 --- a/packages/frontend/src/pages/notifications.vue +++ b/packages/frontend/src/pages/notifications.vue @@ -30,7 +30,7 @@ import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; -import { notificationTypes } from '@/const.js'; +import { notificationTypes } from '@@/js/const.js'; const tab = ref('all'); const includeTypes = ref<string[] | null>(null); diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue index eaef7c337a..ddb808390c 100644 --- a/packages/frontend/src/pages/page-editor/page-editor.vue +++ b/packages/frontend/src/pages/page-editor/page-editor.vue @@ -69,7 +69,7 @@ import MkButton from '@/components/MkButton.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkInput from '@/components/MkInput.vue'; -import { url } from '@/config.js'; +import { url } from '@@/js/config.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { selectFile } from '@/scripts/select-file.js'; diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue index 7ae61236e8..7926dab88b 100644 --- a/packages/frontend/src/pages/page.vue +++ b/packages/frontend/src/pages/page.vue @@ -104,7 +104,7 @@ import XPage from '@/components/page/page.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { url } from '@/config.js'; +import { url } from '@@/js/config.js'; import MkMediaImage from '@/components/MkMediaImage.vue'; import MkImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; import MkFollowButton from '@/components/MkFollowButton.vue'; @@ -121,7 +121,7 @@ import { instance } from '@/instance.js'; import { getStaticImageUrl } from '@/scripts/media-proxy.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { useRouter } from '@/router/supplier.js'; -import { MenuItem } from '@/types/menu'; +import type { MenuItem } from '@/types/menu.js'; const router = useRouter(); @@ -165,18 +165,23 @@ function fetchPage() { function share(ev: MouseEvent) { if (!page.value) return; - os.popupMenu([ - { - text: i18n.ts.shareWithNote, - icon: 'ti ti-pencil', - action: shareWithNote, - }, - ...(isSupportShare() ? [{ + const menuItems: MenuItem[] = []; + + menuItems.push({ + text: i18n.ts.shareWithNote, + icon: 'ti ti-pencil', + action: shareWithNote, + }); + + if (isSupportShare()) { + menuItems.push({ text: i18n.ts.share, icon: 'ti ti-share', action: shareWithNavigator, - }] : []), - ], ev.currentTarget ?? ev.target); + }); + } + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target); } function copyLink() { @@ -256,51 +261,59 @@ function reportAbuse() { function showMenu(ev: MouseEvent) { if (!page.value) return; - const menu: MenuItem[] = [ - ...($i && $i.id === page.value.userId ? [ - { - icon: 'ti ti-code', - text: i18n.ts._pages.viewSource, - action: () => router.push(`/@${props.username}/pages/${props.pageName}/view-source`), - }, - ...($i.pinnedPageId === page.value.id ? [{ + const menuItems: MenuItem[] = []; + + if ($i && $i.id === page.value.userId) { + menuItems.push({ + icon: 'ti ti-pencil', + text: i18n.ts.editThisPage, + action: () => router.push(`/pages/edit/${page.value.id}`), + }); + + if ($i.pinnedPageId === page.value.id) { + menuItems.push({ icon: 'ti ti-pinned-off', text: i18n.ts.unpin, action: () => pin(false), - }] : [{ + }); + } else { + menuItems.push({ icon: 'ti ti-pin', text: i18n.ts.pin, action: () => pin(true), - }]), - ] : []), - ...($i && $i.id !== page.value.userId ? [ - { - icon: 'ti ti-exclamation-circle', - text: i18n.ts.reportAbuse, - action: reportAbuse, - }, - ...($i.isModerator || $i.isAdmin ? [ - { - type: 'divider' as const, - }, - { - icon: 'ti ti-trash', - text: i18n.ts.delete, - danger: true, - action: () => os.confirm({ - type: 'warning', - text: i18n.ts.deleteConfirm, - }).then(({ canceled }) => { - if (canceled || !page.value) return; + }); + } + } else if ($i && $i.id !== page.value.userId) { + menuItems.push({ + icon: 'ti ti-code', + text: i18n.ts._pages.viewSource, + action: () => router.push(`/@${props.username}/pages/${props.pageName}/view-source`), + }, { + icon: 'ti ti-exclamation-circle', + text: i18n.ts.reportAbuse, + action: reportAbuse, + }); - os.apiWithDialog('pages/delete', { pageId: page.value.id }); - }), - }, - ] : []), - ] : []), - ]; + if ($i.isModerator || $i.isAdmin) { + menuItems.push({ + type: 'divider', + }, { + icon: 'ti ti-trash', + text: i18n.ts.delete, + danger: true, + action: () => os.confirm({ + type: 'warning', + text: i18n.ts.deleteConfirm, + }).then(({ canceled }) => { + if (canceled || !page.value) return; - os.popupMenu(menu, ev.currentTarget ?? ev.target); + os.apiWithDialog('pages/delete', { pageId: page.value.id }); + }), + }); + } + } + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target); } watch(() => path.value, fetchPage, { immediate: true }); diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue index 7d9cefa5c9..54e66f6e16 100644 --- a/packages/frontend/src/pages/reversi/game.board.vue +++ b/packages/frontend/src/pages/reversi/game.board.vue @@ -149,9 +149,9 @@ import MkButton from '@/components/MkButton.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import { deepClone } from '@/scripts/clone.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import { signinRequired } from '@/account.js'; -import { url } from '@/config.js'; +import { url } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { userPage } from '@/filters/user.js'; diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue index 31c0003130..08bb3cb76c 100644 --- a/packages/frontend/src/pages/reversi/game.setting.vue +++ b/packages/frontend/src/pages/reversi/game.setting.vue @@ -121,7 +121,7 @@ import MkRadios from '@/components/MkRadios.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkFolder from '@/components/MkFolder.vue'; import * as os from '@/os.js'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; import { useRouter } from '@/router/supplier.js'; const $i = signinRequired(); diff --git a/packages/frontend/src/pages/reversi/game.vue b/packages/frontend/src/pages/reversi/game.vue index 97a793753d..10ea3717ab 100644 --- a/packages/frontend/src/pages/reversi/game.vue +++ b/packages/frontend/src/pages/reversi/game.vue @@ -20,9 +20,9 @@ import { useStream } from '@/stream.js'; import { signinRequired } from '@/account.js'; import { useRouter } from '@/router/supplier.js'; import * as os from '@/os.js'; -import { url } from '@/config.js'; +import { url } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; const $i = signinRequired(); diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue index 51a03e4418..d823861b4a 100644 --- a/packages/frontend/src/pages/reversi/index.vue +++ b/packages/frontend/src/pages/reversi/index.vue @@ -117,7 +117,7 @@ import { $i } from '@/account.js'; import MkPagination from '@/components/MkPagination.vue'; import { useRouter } from '@/router/supplier.js'; import * as os from '@/os.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import { pleaseLogin } from '@/scripts/please-login.js'; import * as sound from '@/scripts/sound.js'; diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue index ce80579cf9..45f8ef21ed 100644 --- a/packages/frontend/src/pages/role.vue +++ b/packages/frontend/src/pages/role.vue @@ -43,7 +43,7 @@ import MkUserList from '@/components/MkUserList.vue'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import MkTimeline from '@/components/MkTimeline.vue'; -import { instanceName } from '@/config.js'; +import { instanceName } from '@@/js/config.js'; import { serverErrorImageUrl, infoImageUrl } from '@/instance.js'; const props = withDefaults(defineProps<{ diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue index 9aaa8ff9c6..897ff6acdf 100644 --- a/packages/frontend/src/pages/scratchpad.vue +++ b/packages/frontend/src/pages/scratchpad.vue @@ -30,6 +30,24 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </MkContainer> + <MkContainer :foldable="true" :expanded="false"> + <template #header>{{ i18n.ts.uiInspector }}</template> + <div :class="$style.uiInspector"> + <div v-for="c in components" :key="c.value.id"> + <div :class="$style.uiInspectorType">{{ c.value.type }}</div> + <div :class="$style.uiInspectorId">{{ c.value.id }}</div> + <button :class="$style.uiInspectorPropsToggle" @click="() => uiInspectorOpenedComponents.set(c, !uiInspectorOpenedComponents.get(c))"> + <i v-if="uiInspectorOpenedComponents.get(c)" class="ti ti-chevron-up icon"></i> + <i v-else class="ti ti-chevron-down icon"></i> + </button> + <div v-if="uiInspectorOpenedComponents.get(c)"> + <MkTextarea :modelValue="stringifyUiProps(c.value)" code readonly></MkTextarea> + </div> + </div> + <div :class="$style.uiInspectorDescription">{{ i18n.ts.uiInspectorDescription }}</div> + </div> + </MkContainer> + <div class=""> {{ i18n.ts.scratchpadDescription }} </div> @@ -43,6 +61,7 @@ import { onDeactivated, onUnmounted, Ref, ref, watch, computed } from 'vue'; import { Interpreter, Parser, utils } from '@syuilo/aiscript'; import MkContainer from '@/components/MkContainer.vue'; import MkButton from '@/components/MkButton.vue'; +import MkTextarea from '@/components/MkTextarea.vue'; import MkCodeEditor from '@/components/MkCodeEditor.vue'; import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js'; import * as os from '@/os.js'; @@ -61,6 +80,7 @@ const logs = ref<any[]>([]); const root = ref<AsUiRoot>(); const components = ref<Ref<AsUiComponent>[]>([]); const uiKey = ref(0); +const uiInspectorOpenedComponents = ref(new Map<string, boolean>); const saved = miLocalStorage.getItem('scratchpad'); if (saved) { @@ -71,6 +91,14 @@ watch(code, () => { miLocalStorage.setItem('scratchpad', code.value); }); +function stringifyUiProps(uiProps) { + return JSON.stringify( + { ...uiProps, type: undefined, id: undefined }, + (k, v) => typeof v === 'function' ? '<function>' : v, + 2 + ); +} + async function run() { if (aiscript) aiscript.abort(); root.value = undefined; @@ -192,4 +220,35 @@ definePageMetadata(() => ({ } } } + +.uiInspector { + display: grid; + gap: 8px; + padding: 16px; +} + +.uiInspectorType { + display: inline-block; + border: hidden; + border-radius: 10px; + background-color: var(--panelHighlight); + padding: 2px 8px; + font-size: 12px; +} + +.uiInspectorId { + display: inline-block; + padding-left: 8px; +} + +.uiInspectorDescription { + display: block; + font-size: 12px; + padding-top: 16px; +} + +.uiInspectorPropsToggle { + background: none; + border: none; +} </style> diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index 94ef3b8485..69238b0436 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -254,11 +254,11 @@ import FormSection from '@/components/form/section.vue'; import FormLink from '@/components/form/link.vue'; import MkLink from '@/components/MkLink.vue'; import MkInfo from '@/components/MkInfo.vue'; -import { langs } from '@/config.js'; +import { langs } from '@@/js/config.js'; import { defaultStore } from '@/store.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { unisonReload } from '@/scripts/unison-reload.js'; +import { reloadAsk } from '@/scripts/reload-ask.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { miLocalStorage } from '@/local-storage.js'; @@ -270,16 +270,6 @@ const fontSize = ref(miLocalStorage.getItem('fontSize')); const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null); const dataSaver = ref(defaultStore.state.dataSaver); -async function reloadAsk() { - const { canceled } = await os.confirm({ - type: 'info', - text: i18n.ts.reloadToApplySetting, - }); - if (canceled) return; - - unisonReload(); -} - const hemisphere = computed(defaultStore.makeGetterSetter('hemisphere')); const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind')); const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior')); @@ -369,7 +359,7 @@ watch([ confirmWhenRevealingSensitiveMedia, contextMenu, ], async () => { - await reloadAsk(); + await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); }); const emojiIndexLangs = ['en-US', 'ja-JP', 'ja-JP_hira'] as const; diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue index 9bb3957a84..5acbc50756 100644 --- a/packages/frontend/src/pages/settings/import-export.vue +++ b/packages/frontend/src/pages/settings/import-export.vue @@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkButton primary :class="$style.button" inline @click="exportFollowing()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> </div> </MkFolder> - <MkFolder v-if="$i && !$i.movedTo"> + <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportFollowing"> <template #label>{{ i18n.ts.import }}</template> <template #icon><i class="ti ti-upload"></i></template> <MkSwitch v-model="withReplies"> @@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #icon><i class="ti ti-download"></i></template> <MkButton primary :class="$style.button" inline @click="exportUserLists()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> </MkFolder> - <MkFolder v-if="$i && !$i.movedTo"> + <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportUserLists"> <template #label>{{ i18n.ts.import }}</template> <template #icon><i class="ti ti-upload"></i></template> <MkButton primary :class="$style.button" inline @click="importUserLists($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton> @@ -78,7 +78,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #icon><i class="ti ti-download"></i></template> <MkButton primary :class="$style.button" inline @click="exportMuting()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> </MkFolder> - <MkFolder v-if="$i && !$i.movedTo"> + <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportMuting"> <template #label>{{ i18n.ts.import }}</template> <template #icon><i class="ti ti-upload"></i></template> <MkButton primary :class="$style.button" inline @click="importMuting($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton> @@ -93,7 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #icon><i class="ti ti-download"></i></template> <MkButton primary :class="$style.button" inline @click="exportBlocking()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> </MkFolder> - <MkFolder v-if="$i && !$i.movedTo"> + <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportBlocking"> <template #label>{{ i18n.ts.import }}</template> <template #icon><i class="ti ti-upload"></i></template> <MkButton primary :class="$style.button" inline @click="importBlocking($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton> @@ -108,7 +108,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #icon><i class="ti ti-download"></i></template> <MkButton primary :class="$style.button" inline @click="exportAntennas()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> </MkFolder> - <MkFolder v-if="$i && !$i.movedTo"> + <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportAntennas"> <template #label>{{ i18n.ts.import }}</template> <template #icon><i class="ti ti-upload"></i></template> <MkButton primary :class="$style.button" inline @click="importAntennas($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton> diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue index 7f8460e316..a0e6cad9c8 100644 --- a/packages/frontend/src/pages/settings/navbar.vue +++ b/packages/frontend/src/pages/settings/navbar.vue @@ -54,7 +54,7 @@ import MkContainer from '@/components/MkContainer.vue'; import * as os from '@/os.js'; import { navbarItemDef } from '@/navbar.js'; import { defaultStore } from '@/store.js'; -import { unisonReload } from '@/scripts/unison-reload.js'; +import { reloadAsk } from '@/scripts/reload-ask.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; @@ -67,16 +67,6 @@ const items = ref(defaultStore.state.menu.map(x => ({ const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay')); -async function reloadAsk() { - const { canceled } = await os.confirm({ - type: 'info', - text: i18n.ts.reloadToApplySetting, - }); - if (canceled) return; - - unisonReload(); -} - async function addItem() { const menu = Object.keys(navbarItemDef).filter(k => !defaultStore.state.menu.includes(k)); const { canceled, result: item } = await os.select({ @@ -100,7 +90,7 @@ function removeItem(index: number) { async function save() { defaultStore.set('menu', items.value.map(x => x.type)); - await reloadAsk(); + await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); } function reset() { @@ -111,7 +101,7 @@ function reset() { } watch(menuDisplay, async () => { - await reloadAsk(); + await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); }); const headerActions = computed(() => []); diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue index 70db6a5109..cce671a7cb 100644 --- a/packages/frontend/src/pages/settings/notifications.vue +++ b/packages/frontend/src/pages/settings/notifications.vue @@ -69,7 +69,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue'; -import { notificationTypes } from '@/const.js'; +import { notificationTypes } from '@@/js/const.js'; const $i = signinRequired(); diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue index a1cb2ea1c4..0f7609c83e 100644 --- a/packages/frontend/src/pages/settings/other.vue +++ b/packages/frontend/src/pages/settings/other.vue @@ -98,7 +98,7 @@ import { defaultStore } from '@/store.js'; import { signout, signinRequired } from '@/account.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; -import { unisonReload } from '@/scripts/unison-reload.js'; +import { reloadAsk } from '@/scripts/reload-ask.js'; import FormSection from '@/components/form/section.vue'; const $i = signinRequired(); @@ -132,16 +132,6 @@ async function deleteAccount() { await signout(); } -async function reloadAsk() { - const { canceled } = await os.confirm({ - type: 'info', - text: i18n.ts.reloadToApplySetting, - }); - if (canceled) return; - - unisonReload(); -} - async function updateRepliesAll(withReplies: boolean) { const { canceled } = await os.confirm({ type: 'warning', @@ -155,7 +145,7 @@ async function updateRepliesAll(withReplies: boolean) { watch([ enableCondensedLineForAcct, ], async () => { - await reloadAsk(); + await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); }); const headerActions = computed(() => []); diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue index dace2cd847..1552a7afee 100644 --- a/packages/frontend/src/pages/settings/preferences-backups.vue +++ b/packages/frontend/src/pages/settings/preferences-backups.vue @@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div class="_gaps_m"> <div :class="$style.buttons"> - <MkButton inline primary @click="saveNew">{{ ts._preferencesBackups.saveNew }}</MkButton> - <MkButton inline @click="loadFile">{{ ts._preferencesBackups.loadFile }}</MkButton> + <MkButton inline primary @click="saveNew">{{ i18n.ts._preferencesBackups.saveNew }}</MkButton> + <MkButton inline @click="loadFile">{{ i18n.ts._preferencesBackups.loadFile }}</MkButton> </div> <FormSection> - <template #label>{{ ts._preferencesBackups.list }}</template> + <template #label>{{ i18n.ts._preferencesBackups.list }}</template> <template v-if="profiles && Object.keys(profiles).length > 0"> <div class="_gaps_s"> <div @@ -23,13 +23,13 @@ SPDX-License-Identifier: AGPL-3.0-only @contextmenu.prevent.stop="$event => menu($event, id)" > <div :class="$style.profileName">{{ profile.name }}</div> - <div :class="$style.profileTime">{{ t('_preferencesBackups.createdAt', { date: (new Date(profile.createdAt)).toLocaleDateString(), time: (new Date(profile.createdAt)).toLocaleTimeString() }) }}</div> - <div v-if="profile.updatedAt" :class="$style.profileTime">{{ t('_preferencesBackups.updatedAt', { date: (new Date(profile.updatedAt)).toLocaleDateString(), time: (new Date(profile.updatedAt)).toLocaleTimeString() }) }}</div> + <div :class="$style.profileTime">{{ i18n.tsx._preferencesBackups.createdAt({ date: (new Date(profile.createdAt)).toLocaleDateString(), time: (new Date(profile.createdAt)).toLocaleTimeString() }) }}</div> + <div v-if="profile.updatedAt" :class="$style.profileTime">{{ i18n.tsx._preferencesBackups.updatedAt({ date: (new Date(profile.updatedAt)).toLocaleDateString(), time: (new Date(profile.updatedAt)).toLocaleTimeString() }) }}</div> </div> </div> </template> <div v-else-if="profiles"> - <MkInfo>{{ ts._preferencesBackups.noBackups }}</MkInfo> + <MkInfo>{{ i18n.ts._preferencesBackups.noBackups }}</MkInfo> </div> <MkLoading v-else/> </FormSection> @@ -49,10 +49,9 @@ import { unisonReload } from '@/scripts/unison-reload.js'; import { useStream } from '@/stream.js'; import { $i } from '@/account.js'; import { i18n } from '@/i18n.js'; -import { version, host } from '@/config.js'; +import { version, host } from '@@/js/config.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { miLocalStorage } from '@/local-storage.js'; -const { t, ts } = i18n; const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [ 'collapseRenotes', @@ -201,15 +200,15 @@ async function saveNew(): Promise<void> { if (!profiles.value) return; const { canceled, result: name } = await os.inputText({ - title: ts._preferencesBackups.inputName, + title: i18n.ts._preferencesBackups.inputName, default: '', }); if (canceled) return; if (Object.values(profiles.value).some(x => x.name === name)) { return os.alert({ - title: ts._preferencesBackups.cannotSave, - text: t('_preferencesBackups.nameAlreadyExists', { name }), + title: i18n.ts._preferencesBackups.cannotSave, + text: i18n.tsx._preferencesBackups.nameAlreadyExists({ name }), }); } @@ -238,8 +237,8 @@ function loadFile(): void { if (file.type !== 'application/json') { return os.alert({ type: 'error', - title: ts._preferencesBackups.cannotLoad, - text: ts._preferencesBackups.invalidFile, + title: i18n.ts._preferencesBackups.cannotLoad, + text: i18n.ts._preferencesBackups.invalidFile, }); } @@ -250,7 +249,7 @@ function loadFile(): void { } catch (err) { return os.alert({ type: 'error', - title: ts._preferencesBackups.cannotLoad, + title: i18n.ts._preferencesBackups.cannotLoad, text: (err as any)?.message ?? '', }); } @@ -276,8 +275,8 @@ async function applyProfile(id: string): Promise<void> { const { canceled: cancel1 } = await os.confirm({ type: 'warning', - title: ts._preferencesBackups.apply, - text: t('_preferencesBackups.applyConfirm', { name: profile.name }), + title: i18n.ts._preferencesBackups.apply, + text: i18n.tsx._preferencesBackups.applyConfirm({ name: profile.name }), }); if (cancel1) return; @@ -322,7 +321,7 @@ async function applyProfile(id: string): Promise<void> { const { canceled: cancel2 } = await os.confirm({ type: 'info', - text: ts.reloadToApplySetting, + text: i18n.ts.reloadToApplySetting, }); if (cancel2) return; @@ -334,8 +333,8 @@ async function deleteProfile(id: string): Promise<void> { const { canceled } = await os.confirm({ type: 'info', - title: ts.delete, - text: t('deleteAreYouSure', { x: profiles.value[id].name }), + title: i18n.ts.delete, + text: i18n.tsx.deleteAreYouSure({ x: profiles.value[id].name }), }); if (canceled) return; @@ -350,8 +349,8 @@ async function save(id: string): Promise<void> { const { canceled } = await os.confirm({ type: 'info', - title: ts._preferencesBackups.save, - text: t('_preferencesBackups.saveConfirm', { name }), + title: i18n.ts._preferencesBackups.save, + text: i18n.tsx._preferencesBackups.saveConfirm({ name }), }); if (canceled) return; @@ -370,15 +369,15 @@ async function rename(id: string): Promise<void> { if (!profiles.value) return; const { canceled: cancel1, result: name } = await os.inputText({ - title: ts._preferencesBackups.inputName, + title: i18n.ts._preferencesBackups.inputName, default: '', }); if (cancel1 || profiles.value[id].name === name) return; if (Object.values(profiles.value).some(x => x.name === name)) { return os.alert({ - title: ts._preferencesBackups.cannotSave, - text: t('_preferencesBackups.nameAlreadyExists', { name }), + title: i18n.ts._preferencesBackups.cannotSave, + text: i18n.tsx._preferencesBackups.nameAlreadyExists({ name }), }); } @@ -386,8 +385,8 @@ async function rename(id: string): Promise<void> { const { canceled: cancel2 } = await os.confirm({ type: 'info', - title: ts.rename, - text: t('_preferencesBackups.renameConfirm', { old: registry.name, new: name }), + title: i18n.ts.rename, + text: i18n.tsx._preferencesBackups.renameConfirm({ old: registry.name, new: name }), }); if (cancel2) return; @@ -399,25 +398,25 @@ function menu(ev: MouseEvent, profileId: string) { if (!profiles.value) return; return os.popupMenu([{ - text: ts._preferencesBackups.apply, + text: i18n.ts._preferencesBackups.apply, icon: 'ti ti-check', action: () => applyProfile(profileId), }, { type: 'a', - text: ts.download, + text: i18n.ts.download, icon: 'ti ti-download', href: URL.createObjectURL(new Blob([JSON.stringify(profiles.value[profileId], null, 2)], { type: 'application/json' })), download: `${profiles.value[profileId].name}.json`, }, { type: 'divider' }, { - text: ts.rename, + text: i18n.ts.rename, icon: 'ti ti-forms', action: () => rename(profileId), }, { - text: ts._preferencesBackups.save, + text: i18n.ts._preferencesBackups.save, icon: 'ti ti-device-floppy', action: () => save(profileId), }, { type: 'divider' }, { - text: ts.delete, + text: i18n.ts.delete, icon: 'ti ti-trash', action: () => deleteProfile(profileId), danger: true, @@ -439,7 +438,7 @@ onUnmounted(() => { }); definePageMetadata(() => ({ - title: ts.preferencesBackups, + title: i18n.ts.preferencesBackups, icon: 'ti ti-device-floppy', })); </script> diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue index 7d192bcbea..ce8ec68692 100644 --- a/packages/frontend/src/pages/settings/theme.vue +++ b/packages/frontend/src/pages/settings/theme.vue @@ -88,19 +88,9 @@ import { uniqueBy } from '@/scripts/array.js'; import { fetchThemes, getThemes } from '@/theme-store.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { miLocalStorage } from '@/local-storage.js'; -import { unisonReload } from '@/scripts/unison-reload.js'; +import { reloadAsk } from '@/scripts/reload-ask.js'; import * as os from '@/os.js'; -async function reloadAsk() { - const { canceled } = await os.confirm({ - type: 'info', - text: i18n.ts.reloadToApplySetting, - }); - if (canceled) return; - - unisonReload(); -} - const installedThemes = ref(getThemes()); const builtinThemes = getBuiltinThemesRef(); @@ -148,13 +138,13 @@ watch(syncDeviceDarkMode, () => { } }); -watch(wallpaper, () => { +watch(wallpaper, async () => { if (wallpaper.value == null) { miLocalStorage.removeItem('wallpaper'); } else { miLocalStorage.setItem('wallpaper', wallpaper.value); } - reloadAsk(); + await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); }); onActivated(() => { diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue index 058ef69c35..adeaf8550c 100644 --- a/packages/frontend/src/pages/settings/webhook.edit.vue +++ b/packages/frontend/src/pages/settings/webhook.edit.vue @@ -21,14 +21,41 @@ SPDX-License-Identifier: AGPL-3.0-only <FormSection> <template #label>{{ i18n.ts._webhookSettings.trigger }}</template> - <div class="_gaps_s"> - <MkSwitch v-model="event_follow">{{ i18n.ts._webhookSettings._events.follow }}</MkSwitch> - <MkSwitch v-model="event_followed">{{ i18n.ts._webhookSettings._events.followed }}</MkSwitch> - <MkSwitch v-model="event_note">{{ i18n.ts._webhookSettings._events.note }}</MkSwitch> - <MkSwitch v-model="event_reply">{{ i18n.ts._webhookSettings._events.reply }}</MkSwitch> - <MkSwitch v-model="event_renote">{{ i18n.ts._webhookSettings._events.renote }}</MkSwitch> - <MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch> - <MkSwitch v-model="event_mention">{{ i18n.ts._webhookSettings._events.mention }}</MkSwitch> + <div class="_gaps"> + <div class="_gaps_s"> + <div :class="$style.switchBox"> + <MkSwitch v-model="event_follow">{{ i18n.ts._webhookSettings._events.follow }}</MkSwitch> + <MkButton transparent :class="$style.testButton" :disabled="!(active && event_follow)" @click="test('follow')"><i class="ti ti-send"></i></MkButton> + </div> + <div :class="$style.switchBox"> + <MkSwitch v-model="event_followed">{{ i18n.ts._webhookSettings._events.followed }}</MkSwitch> + <MkButton transparent :class="$style.testButton" :disabled="!(active && event_followed)" @click="test('followed')"><i class="ti ti-send"></i></MkButton> + </div> + <div :class="$style.switchBox"> + <MkSwitch v-model="event_note">{{ i18n.ts._webhookSettings._events.note }}</MkSwitch> + <MkButton transparent :class="$style.testButton" :disabled="!(active && event_note)" @click="test('note')"><i class="ti ti-send"></i></MkButton> + </div> + <div :class="$style.switchBox"> + <MkSwitch v-model="event_reply">{{ i18n.ts._webhookSettings._events.reply }}</MkSwitch> + <MkButton transparent :class="$style.testButton" :disabled="!(active && event_reply)" @click="test('reply')"><i class="ti ti-send"></i></MkButton> + </div> + <div :class="$style.switchBox"> + <MkSwitch v-model="event_renote">{{ i18n.ts._webhookSettings._events.renote }}</MkSwitch> + <MkButton transparent :class="$style.testButton" :disabled="!(active && event_renote)" @click="test('renote')"><i class="ti ti-send"></i></MkButton> + </div> + <div :class="$style.switchBox"> + <MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch> + <MkButton transparent :class="$style.testButton" :disabled="!(active && event_reaction)" @click="test('reaction')"><i class="ti ti-send"></i></MkButton> + </div> + <div :class="$style.switchBox"> + <MkSwitch v-model="event_mention">{{ i18n.ts._webhookSettings._events.mention }}</MkSwitch> + <MkButton transparent :class="$style.testButton" :disabled="!(active && event_mention)" @click="test('mention')"><i class="ti ti-send"></i></MkButton> + </div> + </div> + + <div :class="$style.description"> + {{ i18n.ts._webhookSettings.testRemarks }} + </div> </div> </FormSection> @@ -43,6 +70,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed } from 'vue'; +import * as Misskey from 'misskey-js'; import MkInput from '@/components/MkInput.vue'; import FormSection from '@/components/form/section.vue'; import MkSwitch from '@/components/MkSwitch.vue'; @@ -76,8 +104,8 @@ const event_renote = ref(webhook.on.includes('renote')); const event_reaction = ref(webhook.on.includes('reaction')); const event_mention = ref(webhook.on.includes('mention')); -async function save(): Promise<void> { - const events = []; +function save() { + const events: Misskey.entities.UserWebhook['on'] = []; if (event_follow.value) events.push('follow'); if (event_followed.value) events.push('followed'); if (event_note.value) events.push('note'); @@ -110,8 +138,21 @@ async function del(): Promise<void> { router.push('/settings/webhook'); } +async function test(type: Misskey.entities.UserWebhook['on'][number]): Promise<void> { + await os.apiWithDialog('i/webhooks/test', { + webhookId: props.webhookId, + type, + override: { + secret: secret.value, + url: url.value, + }, + }); +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars const headerActions = computed(() => []); +// eslint-disable-next-line @typescript-eslint/no-unused-vars const headerTabs = computed(() => []); definePageMetadata(() => ({ @@ -119,3 +160,30 @@ definePageMetadata(() => ({ icon: 'ti ti-webhook', })); </script> + +<style module lang="scss"> +.switchBox { + display: flex; + align-items: center; + justify-content: start; + + .testButton { + $buttonSize: 28px; + padding: 0; + width: $buttonSize; + min-width: $buttonSize; + max-width: $buttonSize; + height: $buttonSize; + margin-left: auto; + line-height: inherit; + font-size: 90%; + border-radius: 9999px; + } +} + +.description { + font-size: 0.85em; + padding: 8px 0 0 0; + color: var(--fgTransparentWeak); +} +</style> diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue index 9b77392872..1b3e1ecaee 100644 --- a/packages/frontend/src/pages/tag.vue +++ b/packages/frontend/src/pages/tag.vue @@ -28,6 +28,7 @@ import { i18n } from '@/i18n.js'; import { $i } from '@/account.js'; import { defaultStore } from '@/store.js'; import * as os from '@/os.js'; +import { genEmbedCode } from '@/scripts/get-embed-code.js'; const props = defineProps<{ tag: string; @@ -51,7 +52,19 @@ async function post() { notes.value?.pagingComponent?.reload(); } -const headerActions = computed(() => []); +const headerActions = computed(() => [{ + icon: 'ti ti-dots', + label: i18n.ts.more, + handler: (ev: MouseEvent) => { + os.popupMenu([{ + text: i18n.ts.genEmbedCode, + icon: 'ti ti-code', + action: () => { + genEmbedCode('tags', props.tag); + }, + }], ev.currentTarget ?? ev.target); + } +}]); const headerTabs = computed(() => []); diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue index 50c3beeabc..a62fe5d581 100644 --- a/packages/frontend/src/pages/theme-editor.vue +++ b/packages/frontend/src/pages/theme-editor.vue @@ -79,6 +79,8 @@ import tinycolor from 'tinycolor2'; import { v4 as uuid } from 'uuid'; import JSON5 from 'json5'; +import lightTheme from '@@/themes/_light.json5'; +import darkTheme from '@@/themes/_dark.json5'; import MkButton from '@/components/MkButton.vue'; import MkCodeEditor from '@/components/MkCodeEditor.vue'; import MkTextarea from '@/components/MkTextarea.vue'; @@ -86,9 +88,7 @@ import MkFolder from '@/components/MkFolder.vue'; import { $i } from '@/account.js'; import { Theme, applyTheme } from '@/scripts/theme.js'; -import lightTheme from '@/themes/_light.json5'; -import darkTheme from '@/themes/_dark.json5'; -import { host } from '@/config.js'; +import { host } from '@@/js/config.js'; import * as os from '@/os.js'; import { ColdDeviceStorage, defaultStore } from '@/store.js'; import { addTheme } from '@/theme-store.js'; diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index d5943e8fbc..12e2db2293 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -40,7 +40,7 @@ import MkTimeline from '@/components/MkTimeline.vue'; import MkInfo from '@/components/MkInfo.vue'; import MkPostForm from '@/components/MkPostForm.vue'; import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; -import { scroll } from '@/scripts/scroll.js'; +import { scroll } from '@@/js/scroll.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { defaultStore } from '@/store.js'; @@ -50,11 +50,11 @@ import { definePageMetadata } from '@/scripts/page-metadata.js'; import { antennasCache, userListsCache, favoritedChannelsCache } from '@/cache.js'; import { deviceKind } from '@/scripts/device-kind.js'; import { deepMerge } from '@/scripts/merge.js'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; import { miLocalStorage } from '@/local-storage.js'; import { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js'; import type { BasicTimelineType } from '@/timelines.js'; - + provide('shouldOmitHeaderTitle', true); const tlComponent = shallowRef<InstanceType<typeof MkTimeline>>(); @@ -189,7 +189,7 @@ async function chooseChannel(ev: MouseEvent): Promise<void> { }), (channels.length === 0 ? undefined : { type: 'divider' }), { - type: 'link' as const, + type: 'link', icon: 'ti ti-plus', text: i18n.ts.createNew, to: '/channels', @@ -258,16 +258,24 @@ const headerActions = computed(() => { icon: 'ti ti-dots', text: i18n.ts.options, handler: (ev) => { - os.popupMenu([{ + const menuItems: MenuItem[] = []; + + menuItems.push({ type: 'switch', text: i18n.ts.showRenotes, ref: withRenotes, - }, isBasicTimeline(src.value) && hasWithReplies(src.value) ? { - type: 'switch', - text: i18n.ts.showRepliesToOthersInTimeline, - ref: withReplies, - disabled: onlyFiles, - } : undefined, { + }); + + if (isBasicTimeline(src.value) && hasWithReplies(src.value)) { + menuItems.push({ + type: 'switch', + text: i18n.ts.showRepliesToOthersInTimeline, + ref: withReplies, + disabled: onlyFiles, + }); + } + + menuItems.push({ type: 'switch', text: i18n.ts.withSensitive, ref: withSensitive, @@ -276,7 +284,9 @@ const headerActions = computed(() => { text: i18n.ts.fileAttachedOnly, ref: onlyFiles, disabled: isBasicTimeline(src.value) && hasWithReplies(src.value) ? withReplies : false, - }], ev.currentTarget ?? ev.target); + }); + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target); }, }, ]; diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue index de6737f37d..31a3f1b060 100644 --- a/packages/frontend/src/pages/user-list-timeline.vue +++ b/packages/frontend/src/pages/user-list-timeline.vue @@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, watch, ref, shallowRef } from 'vue'; import * as Misskey from 'misskey-js'; import MkTimeline from '@/components/MkTimeline.vue'; -import { scroll } from '@/scripts/scroll.js'; +import { scroll } from '@@/js/scroll.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 3039ec7499..8e0292c7fe 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -161,7 +161,7 @@ import MkTextarea from '@/components/MkTextarea.vue'; import MkOmit from '@/components/MkOmit.vue'; import MkInfo from '@/components/MkInfo.vue'; import MkButton from '@/components/MkButton.vue'; -import { getScrollPosition } from '@/scripts/scroll.js'; +import { getScrollPosition } from '@@/js/scroll.js'; import { getUserMenu } from '@/scripts/get-user-menu.js'; import number from '@/filters/number.js'; import { userPage } from '@/filters/user.js'; diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue index 5c31259499..a227c7c4bc 100644 --- a/packages/frontend/src/pages/welcome.setup.vue +++ b/packages/frontend/src/pages/welcome.setup.vue @@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref } from 'vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; -import { host, version } from '@/config.js'; +import { host, version } from '@@/js/config.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { login } from '@/account.js'; diff --git a/packages/frontend/src/pages/welcome.timeline.vue b/packages/frontend/src/pages/welcome.timeline.vue index db326f9e6c..732d483615 100644 --- a/packages/frontend/src/pages/welcome.timeline.vue +++ b/packages/frontend/src/pages/welcome.timeline.vue @@ -24,7 +24,7 @@ import * as Misskey from 'misskey-js'; import { onUpdated, ref, shallowRef } from 'vue'; import XNote from '@/pages/welcome.timeline.note.vue'; import { misskeyApiGet } from '@/scripts/misskey-api.js'; -import { getScrollContainer } from '@/scripts/scroll.js'; +import { getScrollContainer } from '@@/js/scroll.js'; const notes = ref<Misskey.entities.Note[]>([]); const isScrolling = ref(false); diff --git a/packages/frontend/src/pages/welcome.vue b/packages/frontend/src/pages/welcome.vue index 915fe35025..38d257506c 100644 --- a/packages/frontend/src/pages/welcome.vue +++ b/packages/frontend/src/pages/welcome.vue @@ -15,7 +15,7 @@ import { computed, ref } from 'vue'; import * as Misskey from 'misskey-js'; import XSetup from './welcome.setup.vue'; import XEntrance from './welcome.entrance.a.vue'; -import { instanceName } from '@/config.js'; +import { instanceName } from '@@/js/config.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { fetchInstance } from '@/instance.js'; diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts index 995a2055b8..75f994b865 100644 --- a/packages/frontend/src/router/definition.ts +++ b/packages/frontend/src/router/definition.ts @@ -3,15 +3,14 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { App, AsyncComponentLoader, defineAsyncComponent, provide } from 'vue'; -import type { RouteDef } from '@/nirax.js'; -import { IRouter, Router } from '@/nirax.js'; +import { AsyncComponentLoader, defineAsyncComponent } from 'vue'; +import type { IRouter, RouteDef } from '@/nirax.js'; +import { Router } from '@/nirax.js'; import { $i, iAmModerator } from '@/account.js'; import MkLoading from '@/pages/_loading_.vue'; import MkError from '@/pages/_error_.vue'; -import { setMainRouter } from '@/router/main.js'; -const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({ +export const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({ loader: loader, loadingComponent: MkLoading, errorComponent: MkError, @@ -240,7 +239,7 @@ const routes: RouteDef[] = [{ origin: 'origin', }, }, { - // Legacy Compatibility + // Legacy Compatibility path: '/authorize-follow', redirect: '/lookup', loginRequired: true, @@ -463,22 +462,14 @@ const routes: RouteDef[] = [{ path: '/relays', name: 'relays', component: page(() => import('@/pages/admin/relays.vue')), - }, { - path: '/instance-block', - name: 'instance-block', - component: page(() => import('@/pages/admin/instance-block.vue')), - }, { - path: '/proxy-account', - name: 'proxy-account', - component: page(() => import('@/pages/admin/proxy-account.vue')), }, { path: '/external-services', name: 'external-services', component: page(() => import('@/pages/admin/external-services.vue')), }, { - path: '/other-settings', - name: 'other-settings', - component: page(() => import('@/pages/admin/other-settings.vue')), + path: '/performance', + name: 'performance', + component: page(() => import('@/pages/admin/performance.vue')), }, { path: '/server-rules', name: 'server-rules', @@ -597,32 +588,6 @@ const routes: RouteDef[] = [{ component: page(() => import('@/pages/not-found.vue')), }]; -function createRouterImpl(path: string): IRouter { +export function createMainRouter(path: string): IRouter { return new Router(routes, path, !!$i, page(() => import('@/pages/not-found.vue'))); } - -/** - * {@link Router}による画面遷移を可能とするために{@link mainRouter}をセットアップする。 - * また、{@link Router}のインスタンスを作成するためのファクトリも{@link provide}経由で公開する(`routerFactory`というキーで取得可能) - */ -export function setupRouter(app: App) { - app.provide('routerFactory', createRouterImpl); - - const mainRouter = createRouterImpl(location.pathname + location.search + location.hash); - - window.addEventListener('popstate', (event) => { - mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key); - }); - - mainRouter.addListener('push', ctx => { - window.history.pushState({ key: ctx.key }, '', ctx.path); - }); - - mainRouter.addListener('replace', ctx => { - window.history.replaceState({ key: ctx.key }, '', ctx.path); - }); - - mainRouter.init(); - - setMainRouter(mainRouter); -} diff --git a/packages/frontend/src/router/main.ts b/packages/frontend/src/router/main.ts index 7a3fde131e..6ee967e6f4 100644 --- a/packages/frontend/src/router/main.ts +++ b/packages/frontend/src/router/main.ts @@ -3,10 +3,37 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { ShallowRef } from 'vue'; import { EventEmitter } from 'eventemitter3'; import { IRouter, Resolved, RouteDef, RouterEvent } from '@/nirax.js'; +import type { App, ShallowRef } from 'vue'; + +/** + * {@link Router}による画面遷移を可能とするために{@link mainRouter}をセットアップする。 + * また、{@link Router}のインスタンスを作成するためのファクトリも{@link provide}経由で公開する(`routerFactory`というキーで取得可能) + */ +export function setupRouter(app: App, routerFactory: ((path: string) => IRouter)): void { + app.provide('routerFactory', routerFactory); + + const mainRouter = routerFactory(location.pathname + location.search + location.hash); + + window.addEventListener('popstate', (event) => { + mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key); + }); + + mainRouter.addListener('push', ctx => { + window.history.pushState({ key: ctx.key }, '', ctx.path); + }); + + mainRouter.addListener('replace', ctx => { + window.history.replaceState({ key: ctx.key }, '', ctx.path); + }); + + mainRouter.init(); + + setMainRouter(mainRouter); +} + function getMainRouter(): IRouter { const router = mainRouterHolder; if (!router) { diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts index 98a0c61752..46aed49330 100644 --- a/packages/frontend/src/scripts/aiscript/api.ts +++ b/packages/frontend/src/scripts/aiscript/api.ts @@ -4,13 +4,13 @@ */ import { utils, values } from '@syuilo/aiscript'; +import * as Misskey from 'misskey-js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { $i } from '@/account.js'; import { miLocalStorage } from '@/local-storage.js'; import { customEmojis } from '@/custom-emojis.js'; -import { url, lang } from '@/config.js'; -import { nyaize } from '@/scripts/nyaize.js'; +import { url, lang } from '@@/js/config.js'; export function aiScriptReadline(q: string): Promise<string> { return new Promise(ok => { @@ -87,7 +87,7 @@ export function createAiScriptEnv(opts) { }), 'Mk:nyaize': values.FN_NATIVE(([text]) => { utils.assertString(text); - return values.STR(nyaize(text.value)); + return values.STR(Misskey.nyaize(text.value)); }), }; } diff --git a/packages/frontend/src/scripts/check-reaction-permissions.ts b/packages/frontend/src/scripts/check-reaction-permissions.ts index 8fc857f84f..c3c3f419a9 100644 --- a/packages/frontend/src/scripts/check-reaction-permissions.ts +++ b/packages/frontend/src/scripts/check-reaction-permissions.ts @@ -4,7 +4,7 @@ */ import * as Misskey from 'misskey-js'; -import { UnicodeEmojiDef } from './emojilist.js'; +import { UnicodeEmojiDef } from '@@/js/emojilist.js'; export function checkReactionPermissions(me: Misskey.entities.MeDetailed, note: Misskey.entities.Note, emoji: Misskey.entities.EmojiSimple | UnicodeEmojiDef | string): boolean { if (typeof emoji === 'string') return true; // UnicodeEmojiDefにも無い絵文字であれば文字列で来る。Unicode絵文字であることには変わりないので常にリアクション可能とする; diff --git a/packages/frontend/src/scripts/code-highlighter.ts b/packages/frontend/src/scripts/code-highlighter.ts index e94027d302..6710d9826e 100644 --- a/packages/frontend/src/scripts/code-highlighter.ts +++ b/packages/frontend/src/scripts/code-highlighter.ts @@ -3,17 +3,17 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { getHighlighterCore, loadWasm } from 'shiki/core'; +import { createHighlighterCore, loadWasm } from 'shiki/core'; import darkPlus from 'shiki/themes/dark-plus.mjs'; import { bundledThemesInfo } from 'shiki/themes'; import { bundledLanguagesInfo } from 'shiki/langs'; +import lightTheme from '@@/themes/_light.json5'; +import darkTheme from '@@/themes/_dark.json5'; import { unique } from './array.js'; import { deepClone } from './clone.js'; import { deepMerge } from './merge.js'; import type { HighlighterCore, LanguageRegistration, ThemeRegistration, ThemeRegistrationRaw } from 'shiki/core'; import { ColdDeviceStorage } from '@/store.js'; -import lightTheme from '@/themes/_light.json5'; -import darkTheme from '@/themes/_dark.json5'; let _highlighter: HighlighterCore | null = null; @@ -69,7 +69,7 @@ async function initHighlighter() { ]); const jsLangInfo = bundledLanguagesInfo.find(t => t.id === 'javascript'); - const highlighter = await getHighlighterCore({ + const highlighter = await createHighlighterCore({ themes, langs: [ ...(jsLangInfo ? [async () => await jsLangInfo.import()] : []), diff --git a/packages/frontend/src/scripts/focus.ts b/packages/frontend/src/scripts/focus.ts index eb2da5ad86..81278b17ea 100644 --- a/packages/frontend/src/scripts/focus.ts +++ b/packages/frontend/src/scripts/focus.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { getScrollPosition, getScrollContainer, getStickyBottom, getStickyTop } from '@/scripts/scroll.js'; +import { getScrollPosition, getScrollContainer, getStickyBottom, getStickyTop } from '@@/js/scroll.js'; import { getElementOrNull, getNodeOrNull } from '@/scripts/get-dom-node-or-null.js'; type MaybeHTMLElement = EventTarget | Node | Element | HTMLElement; diff --git a/packages/frontend/src/scripts/gen-search-query.ts b/packages/frontend/src/scripts/gen-search-query.ts index 60884d08d3..a85ee01e26 100644 --- a/packages/frontend/src/scripts/gen-search-query.ts +++ b/packages/frontend/src/scripts/gen-search-query.ts @@ -4,7 +4,7 @@ */ import * as Misskey from 'misskey-js'; -import { host as localHost } from '@/config.js'; +import { host as localHost } from '@@/js/config.js'; export async function genSearchQuery(v: any, q: string) { let host: string; diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/scripts/get-drive-file-menu.ts index 108648d640..c8ab9238d3 100644 --- a/packages/frontend/src/scripts/get-drive-file-menu.ts +++ b/packages/frontend/src/scripts/get-drive-file-menu.ts @@ -9,7 +9,7 @@ import { i18n } from '@/i18n.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; import { defaultStore } from '@/store.js'; function rename(file: Misskey.entities.DriveFile) { @@ -87,8 +87,10 @@ async function deleteFile(file: Misskey.entities.DriveFile) { export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Misskey.entities.DriveFolder | null): MenuItem[] { const isImage = file.type.startsWith('image/'); - let menu; - menu = [{ + + const menuItems: MenuItem[] = []; + + menuItems.push({ type: 'link', to: `/my/drive/file/${file.id}`, text: i18n.ts._fileViewer.title, @@ -109,14 +111,20 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss text: i18n.ts.describeFile, icon: 'ti ti-text-caption', action: () => describe(file), - }, ...isImage ? [{ - text: i18n.ts.cropImage, - icon: 'ti ti-crop', - action: () => os.cropImage(file, { - aspectRatio: NaN, - uploadFolder: folder ? folder.id : folder, - }), - }] : [], { type: 'divider' }, { + }); + + if (isImage) { + menuItems.push({ + text: i18n.ts.cropImage, + icon: 'ti ti-crop', + action: () => os.cropImage(file, { + aspectRatio: NaN, + uploadFolder: folder ? folder.id : folder, + }), + }); + } + + menuItems.push({ type: 'divider' }, { text: i18n.ts.createNoteFromTheFile, icon: 'ti ti-pencil', action: () => os.post({ @@ -138,17 +146,17 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss icon: 'ti ti-trash', danger: true, action: () => deleteFile(file), - }]; + }); if (defaultStore.state.devMode) { - menu = menu.concat([{ type: 'divider' }, { + menuItems.push({ type: 'divider' }, { icon: 'ti ti-id', text: i18n.ts.copyFileId, action: () => { copyToClipboard(file.id); }, - }]); + }); } - return menu; + return menuItems; } diff --git a/packages/frontend/src/scripts/get-embed-code.ts b/packages/frontend/src/scripts/get-embed-code.ts new file mode 100644 index 0000000000..158ab9c7f8 --- /dev/null +++ b/packages/frontend/src/scripts/get-embed-code.ts @@ -0,0 +1,87 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { defineAsyncComponent } from 'vue'; +import { v4 as uuid } from 'uuid'; +import type { EmbedParams, EmbeddableEntity } from '@@/js/embed-page.js'; +import { url } from '@@/js/config.js'; +import * as os from '@/os.js'; +import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; +import { defaultEmbedParams, embedRouteWithScrollbar } from '@@/js/embed-page.js'; + +const MOBILE_THRESHOLD = 500; + +/** + * パラメータを正規化する(埋め込みコード作成用) + * @param params パラメータ + * @returns 正規化されたパラメータ + */ +export function normalizeEmbedParams(params: EmbedParams): Record<string, string> { + // paramsのvalueをすべてstringに変換。undefinedやnullはプロパティごと消す + const normalizedParams: Record<string, string> = {}; + for (const key in params) { + // デフォルトの値と同じならparamsに含めない + if (params[key] == null || params[key] === defaultEmbedParams[key]) { + continue; + } + switch (typeof params[key]) { + case 'number': + normalizedParams[key] = params[key].toString(); + break; + case 'boolean': + normalizedParams[key] = params[key] ? 'true' : 'false'; + break; + default: + normalizedParams[key] = params[key]; + break; + } + } + return normalizedParams; +} + +/** + * 埋め込みコードを生成(iframe IDの発番もやる) + */ +export function getEmbedCode(path: string, params?: EmbedParams): string { + const iframeId = 'v1_' + uuid(); // 将来embed.jsのバージョンが上がったとき用にv1_を付けておく + + let paramString = ''; + if (params) { + const searchParams = new URLSearchParams(normalizeEmbedParams(params)); + paramString = searchParams.toString() === '' ? '' : '?' + searchParams.toString(); + } + + const iframeCode = [ + `<iframe src="${url + path + paramString}" data-misskey-embed-id="${iframeId}" loading="lazy" referrerpolicy="strict-origin-when-cross-origin" style="border: none; width: 100%; max-width: 500px; height: 300px; color-scheme: light dark;"></iframe>`, + `<script defer src="${url}/embed.js"></script>`, + ]; + return iframeCode.join('\n'); +} + +/** + * 埋め込みコードを生成してコピーする(カスタマイズ機能つき) + * + * カスタマイズ機能がいらない場合(事前にパラメータを指定する場合)は getEmbedCode を直接使ってください + */ +export function genEmbedCode(entity: EmbeddableEntity, id: string, params?: EmbedParams) { + const _params = { ...params }; + + if (embedRouteWithScrollbar.includes(entity) && _params.maxHeight == null) { + _params.maxHeight = 700; + } + + // PCじゃない場合はコードカスタマイズ画面を出さずにそのままコピー + if (window.innerWidth < MOBILE_THRESHOLD) { + copyToClipboard(getEmbedCode(`/embed/${entity}/${id}`, _params)); + os.success(); + } else { + const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkEmbedCodeGenDialog.vue')), { + entity, + id, + params: _params, + }, { + closed: () => dispose(), + }); + } +} diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index b5d7350a41..4ffa0ab94d 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -12,15 +12,16 @@ import { instance } from '@/instance.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; -import { url } from '@/config.js'; +import { url } from '@@/js/config.js'; import { defaultStore, noteActions } from '@/store.js'; import { miLocalStorage } from '@/local-storage.js'; import { getUserMenu } from '@/scripts/get-user-menu.js'; import { clipsCache, favoritedChannelsCache } from '@/cache.js'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { isSupportShare } from '@/scripts/navigator.js'; import { getAppearNote } from '@/scripts/get-appear-note.js'; +import { genEmbedCode } from '@/scripts/get-embed-code.js'; export async function getNoteClipMenu(props: { note: Misskey.entities.Note; @@ -98,11 +99,13 @@ export async function getNoteClipMenu(props: { const { canceled, result } = await os.form(i18n.ts.createNewClip, { name: { type: 'string', + default: null, label: i18n.ts.name, }, description: { type: 'string', required: false, + default: null, multiline: true, label: i18n.ts.description, }, @@ -156,6 +159,19 @@ export function getCopyNoteLinkMenu(note: Misskey.entities.Note, text: string): }; } +function getNoteEmbedCodeMenu(note: Misskey.entities.Note, text: string): MenuItem | undefined { + if (note.url != null || note.uri != null) return undefined; + if (['specified', 'followers'].includes(note.visibility)) return undefined; + + return { + icon: 'ti ti-code', + text, + action: (): void => { + genEmbedCode('notes', note.id); + }, + }; +} + export function getNoteMenu(props: { note: Misskey.entities.Note; translation: Ref<Misskey.entities.NotesTranslateResponse | null>; @@ -250,7 +266,7 @@ export function getNoteMenu(props: { title: i18n.ts.numberOfDays, }); - if (canceled) return; + if (canceled || days == null) return; os.apiWithDialog('admin/promo/create', { noteId: appearNote.id, @@ -281,161 +297,23 @@ export function getNoteMenu(props: { props.translation.value = res; } - let menu: MenuItem[]; + const menuItems: MenuItem[] = []; + if ($i) { const statePromise = misskeyApi('notes/state', { noteId: appearNote.id, }); - menu = [ - ...( - props.currentClip?.userId === $i.id ? [{ - icon: 'ti ti-backspace', - text: i18n.ts.unclip, - danger: true, - action: unclip, - }, { type: 'divider' }] : [] - ), { - icon: 'ti ti-info-circle', - text: i18n.ts.details, - action: openDetail, - }, { - icon: 'ti ti-copy', - text: i18n.ts.copyContent, - action: copyContent, - }, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink) - , (appearNote.url || appearNote.uri) ? { - icon: 'ti ti-external-link', - text: i18n.ts.showOnRemote, - action: () => { - window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener'); - }, - } : undefined, - ...(isSupportShare() ? [{ - icon: 'ti ti-share', - text: i18n.ts.share, - action: share, - }] : []), - $i && $i.policies.canUseTranslator && instance.translatorAvailable ? { - icon: 'ti ti-language-hiragana', - text: i18n.ts.translate, - action: translate, - } : undefined, - { type: 'divider' }, - statePromise.then(state => state.isFavorited ? { - icon: 'ti ti-star-off', - text: i18n.ts.unfavorite, - action: () => toggleFavorite(false), - } : { - icon: 'ti ti-star', - text: i18n.ts.favorite, - action: () => toggleFavorite(true), - }), - { - type: 'parent' as const, - icon: 'ti ti-paperclip', - text: i18n.ts.clip, - children: () => getNoteClipMenu(props), - }, - statePromise.then(state => state.isMutedThread ? { - icon: 'ti ti-message-off', - text: i18n.ts.unmuteThread, - action: () => toggleThreadMute(false), - } : { - icon: 'ti ti-message-off', - text: i18n.ts.muteThread, - action: () => toggleThreadMute(true), - }), - appearNote.userId === $i.id ? ($i.pinnedNoteIds ?? []).includes(appearNote.id) ? { - icon: 'ti ti-pinned-off', - text: i18n.ts.unpin, - action: () => togglePin(false), - } : { - icon: 'ti ti-pin', - text: i18n.ts.pin, - action: () => togglePin(true), - } : undefined, - { - type: 'parent' as const, - icon: 'ti ti-user', - text: i18n.ts.user, - children: async () => { - const user = appearNote.userId === $i?.id ? $i : await misskeyApi('users/show', { userId: appearNote.userId }); - const { menu, cleanup } = getUserMenu(user); - cleanups.push(cleanup); - return menu; - }, - }, - /* - ...($i.isModerator || $i.isAdmin ? [ - { type: 'divider' }, - { - icon: 'ti ti-speakerphone', - text: i18n.ts.promote, - action: promote - }] - : [] - ),*/ - ...(appearNote.userId !== $i.id ? [ - { type: 'divider' }, - appearNote.userId !== $i.id ? getAbuseNoteMenu(appearNote, i18n.ts.reportAbuse) : undefined, - ] - : [] - ), - ...(appearNote.channel && (appearNote.channel.userId === $i.id || $i.isModerator || $i.isAdmin) ? [ - { type: 'divider' }, - { - type: 'parent' as const, - icon: 'ti ti-device-tv', - text: i18n.ts.channel, - children: async () => { - const channelChildMenu = [] as MenuItem[]; + if (props.currentClip?.userId === $i.id) { + menuItems.push({ + icon: 'ti ti-backspace', + text: i18n.ts.unclip, + danger: true, + action: unclip, + }, { type: 'divider' }); + } - const channel = await misskeyApi('channels/show', { channelId: appearNote.channel!.id }); - - if (channel.pinnedNoteIds.includes(appearNote.id)) { - channelChildMenu.push({ - icon: 'ti ti-pinned-off', - text: i18n.ts.unpin, - action: () => os.apiWithDialog('channels/update', { - channelId: appearNote.channel!.id, - pinnedNoteIds: channel.pinnedNoteIds.filter(id => id !== appearNote.id), - }), - }); - } else { - channelChildMenu.push({ - icon: 'ti ti-pin', - text: i18n.ts.pin, - action: () => os.apiWithDialog('channels/update', { - channelId: appearNote.channel!.id, - pinnedNoteIds: [...channel.pinnedNoteIds, appearNote.id], - }), - }); - } - return channelChildMenu; - }, - }, - ] - : [] - ), - ...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [ - { type: 'divider' }, - appearNote.userId === $i.id ? { - icon: 'ti ti-edit', - text: i18n.ts.deleteAndEdit, - action: delEdit, - } : undefined, - { - icon: 'ti ti-trash', - text: i18n.ts.delete, - danger: true, - action: del, - }] - : [] - )] - .filter(x => x !== undefined); - } else { - menu = [{ + menuItems.push({ icon: 'ti ti-info-circle', text: i18n.ts.details, action: openDetail, @@ -443,35 +321,194 @@ export function getNoteMenu(props: { icon: 'ti ti-copy', text: i18n.ts.copyContent, action: copyContent, - }, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink) - , (appearNote.url || appearNote.uri) ? { - icon: 'ti ti-external-link', - text: i18n.ts.showOnRemote, - action: () => { - window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener'); + }, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink)); + + if (appearNote.url || appearNote.uri) { + menuItems.push({ + icon: 'ti ti-external-link', + text: i18n.ts.showOnRemote, + action: () => { + window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener'); + }, + }); + } else { + menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode)); + } + + if (isSupportShare()) { + menuItems.push({ + icon: 'ti ti-share', + text: i18n.ts.share, + action: share, + }); + } + + if ($i.policies.canUseTranslator && instance.translatorAvailable) { + menuItems.push({ + icon: 'ti ti-language-hiragana', + text: i18n.ts.translate, + action: translate, + }); + } + + menuItems.push({ type: 'divider' }); + + menuItems.push(statePromise.then(state => state.isFavorited ? { + icon: 'ti ti-star-off', + text: i18n.ts.unfavorite, + action: () => toggleFavorite(false), + } : { + icon: 'ti ti-star', + text: i18n.ts.favorite, + action: () => toggleFavorite(true), + })); + + menuItems.push({ + type: 'parent', + icon: 'ti ti-paperclip', + text: i18n.ts.clip, + children: () => getNoteClipMenu(props), + }); + + menuItems.push(statePromise.then(state => state.isMutedThread ? { + icon: 'ti ti-message-off', + text: i18n.ts.unmuteThread, + action: () => toggleThreadMute(false), + } : { + icon: 'ti ti-message-off', + text: i18n.ts.muteThread, + action: () => toggleThreadMute(true), + })); + + if (appearNote.userId === $i.id) { + if (($i.pinnedNoteIds ?? []).includes(appearNote.id)) { + menuItems.push({ + icon: 'ti ti-pinned-off', + text: i18n.ts.unpin, + action: () => togglePin(false), + }); + } else { + menuItems.push({ + icon: 'ti ti-pin', + text: i18n.ts.pin, + action: () => togglePin(true), + }); + } + } + + menuItems.push({ + type: 'parent', + icon: 'ti ti-user', + text: i18n.ts.user, + children: async () => { + const user = appearNote.userId === $i?.id ? $i : await misskeyApi('users/show', { userId: appearNote.userId }); + const { menu, cleanup } = getUserMenu(user); + cleanups.push(cleanup); + return menu; }, - } : undefined] - .filter(x => x !== undefined); + }); + + if (appearNote.userId !== $i.id) { + menuItems.push({ type: 'divider' }); + menuItems.push(getAbuseNoteMenu(appearNote, i18n.ts.reportAbuse)); + } + + if (appearNote.channel && (appearNote.channel.userId === $i.id || $i.isModerator || $i.isAdmin)) { + menuItems.push({ type: 'divider' }); + menuItems.push({ + type: 'parent', + icon: 'ti ti-device-tv', + text: i18n.ts.channel, + children: async () => { + const channelChildMenu = [] as MenuItem[]; + + const channel = await misskeyApi('channels/show', { channelId: appearNote.channel!.id }); + + if (channel.pinnedNoteIds.includes(appearNote.id)) { + channelChildMenu.push({ + icon: 'ti ti-pinned-off', + text: i18n.ts.unpin, + action: () => os.apiWithDialog('channels/update', { + channelId: appearNote.channel!.id, + pinnedNoteIds: channel.pinnedNoteIds.filter(id => id !== appearNote.id), + }), + }); + } else { + channelChildMenu.push({ + icon: 'ti ti-pin', + text: i18n.ts.pin, + action: () => os.apiWithDialog('channels/update', { + channelId: appearNote.channel!.id, + pinnedNoteIds: [...channel.pinnedNoteIds, appearNote.id], + }), + }); + } + return channelChildMenu; + }, + }); + } + + if (appearNote.userId === $i.id || $i.isModerator || $i.isAdmin) { + menuItems.push({ type: 'divider' }); + if (appearNote.userId === $i.id) { + menuItems.push({ + icon: 'ti ti-edit', + text: i18n.ts.deleteAndEdit, + action: delEdit, + }); + } + menuItems.push({ + icon: 'ti ti-trash', + text: i18n.ts.delete, + danger: true, + action: del, + }); + } + } else { + menuItems.push({ + icon: 'ti ti-info-circle', + text: i18n.ts.details, + action: openDetail, + }, { + icon: 'ti ti-copy', + text: i18n.ts.copyContent, + action: copyContent, + }, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink)); + + if (appearNote.url || appearNote.uri) { + menuItems.push({ + icon: 'ti ti-external-link', + text: i18n.ts.showOnRemote, + action: () => { + window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener'); + }, + }); + } else { + menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode)); + } } if (noteActions.length > 0) { - menu = menu.concat([{ type: 'divider' }, ...noteActions.map(action => ({ + menuItems.push({ type: 'divider' }); + + menuItems.push(...noteActions.map(action => ({ icon: 'ti ti-plug', text: action.title, action: () => { action.handler(appearNote); }, - }))]); + }))); } if (defaultStore.state.devMode) { - menu = menu.concat([{ type: 'divider' }, { + menuItems.push({ type: 'divider' }, { icon: 'ti ti-id', text: i18n.ts.copyNoteId, action: () => { copyToClipboard(appearNote.id); + os.success(); }, - }]); + }); } const cleanup = () => { @@ -482,7 +519,7 @@ export function getNoteMenu(props: { }; return { - menu, + menu: menuItems, cleanup, }; } diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts index 33f16a68aa..d15279d633 100644 --- a/packages/frontend/src/scripts/get-user-menu.ts +++ b/packages/frontend/src/scripts/get-user-menu.ts @@ -8,7 +8,7 @@ import { defineAsyncComponent, ref, watch } from 'vue'; import * as Misskey from 'misskey-js'; import { i18n } from '@/i18n.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; -import { host, url } from '@/config.js'; +import { host, url } from '@@/js/config.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { defaultStore, userActions } from '@/store.js'; @@ -17,7 +17,8 @@ import { notesSearchAvailable, canSearchNonLocalNotes } from '@/scripts/check-pe import { IRouter } from '@/nirax.js'; import { antennasCache, rolesCache, userListsCache } from '@/cache.js'; import { mainRouter } from '@/router/main.js'; -import { MenuItem } from '@/types/menu.js'; +import { genEmbedCode } from '@/scripts/get-embed-code.js'; +import type { MenuItem } from '@/types/menu.js'; export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter = mainRouter) { const meId = $i ? $i.id : null; @@ -147,123 +148,154 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter }); } - let menu: MenuItem[] = [{ + const menuItems: MenuItem[] = []; + + menuItems.push({ icon: 'ti ti-at', text: i18n.ts.copyUsername, action: () => { copyToClipboard(`@${user.username}@${user.host ?? host}`); }, - }, ...( notesSearchAvailable && (user.host == null || canSearchNonLocalNotes) ? [{ - icon: 'ti ti-search', - text: i18n.ts.searchThisUsersNotes, - action: () => { - router.push(`/search?username=${encodeURIComponent(user.username)}${user.host != null ? '&host=' + encodeURIComponent(user.host) : ''}`); - }, - }] : []) - , ...(iAmModerator ? [{ - icon: 'ti ti-user-exclamation', - text: i18n.ts.moderation, - action: () => { - router.push(`/admin/user/${user.id}`); - }, - }] : []), { + }); + + if (notesSearchAvailable && (user.host == null || canSearchNonLocalNotes)) { + menuItems.push({ + icon: 'ti ti-search', + text: i18n.ts.searchThisUsersNotes, + action: () => { + router.push(`/search?username=${encodeURIComponent(user.username)}${user.host != null ? '&host=' + encodeURIComponent(user.host) : ''}`); + }, + }); + } + + if (iAmModerator) { + menuItems.push({ + icon: 'ti ti-user-exclamation', + text: i18n.ts.moderation, + action: () => { + router.push(`/admin/user/${user.id}`); + }, + }); + } + + menuItems.push({ icon: 'ti ti-rss', text: i18n.ts.copyRSS, action: () => { copyToClipboard(`${user.host ?? host}/@${user.username}.atom`); }, - }, ...(user.host != null && user.url != null ? [{ - icon: 'ti ti-external-link', - text: i18n.ts.showOnRemote, - action: () => { - if (user.url == null) return; - window.open(user.url, '_blank', 'noopener'); - }, - }] : []), { + }); + + if (user.host != null && user.url != null) { + menuItems.push({ + icon: 'ti ti-external-link', + text: i18n.ts.showOnRemote, + action: () => { + if (user.url == null) return; + window.open(user.url, '_blank', 'noopener'); + }, + }); + } else { + menuItems.push({ + icon: 'ti ti-code', + text: i18n.ts.genEmbedCode, + type: 'parent', + children: [{ + text: i18n.ts.noteOfThisUser, + action: () => { + genEmbedCode('user-timeline', user.id); + }, + }], // TODO: ユーザーカードの埋め込みなど + }); + } + + menuItems.push({ icon: 'ti ti-share', text: i18n.ts.copyProfileUrl, action: () => { const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`; copyToClipboard(`${url}/${canonical}`); }, - }, ...($i ? [{ - icon: 'ti ti-mail', - text: i18n.ts.sendMessage, - action: () => { - const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${user.host}`; - os.post({ specified: user, initialText: `${canonical} ` }); - }, - }, { type: 'divider' }, { - icon: 'ti ti-pencil', - text: i18n.ts.editMemo, - action: () => { - editMemo(); - }, - }, { - type: 'parent', - icon: 'ti ti-list', - text: i18n.ts.addToList, - children: async () => { - const lists = await userListsCache.fetch(); - return lists.map(list => { - const isListed = ref(list.userIds.includes(user.id)); - cleanups.push(watch(isListed, () => { - if (isListed.value) { - os.apiWithDialog('users/lists/push', { - listId: list.id, - userId: user.id, - }).then(() => { - list.userIds.push(user.id); - }); - } else { - os.apiWithDialog('users/lists/pull', { - listId: list.id, - userId: user.id, - }).then(() => { - list.userIds.splice(list.userIds.indexOf(user.id), 1); - }); - } - })); + }); - return { - type: 'switch', - text: list.name, - ref: isListed, - }; - }); - }, - }, { - type: 'parent', - icon: 'ti ti-antenna', - text: i18n.ts.addToAntenna, - children: async () => { - const antennas = await antennasCache.fetch(); - const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`; - return antennas.filter((a) => a.src === 'users').map(antenna => ({ - text: antenna.name, - action: async () => { - await os.apiWithDialog('antennas/update', { - antennaId: antenna.id, - name: antenna.name, - keywords: antenna.keywords, - excludeKeywords: antenna.excludeKeywords, - src: antenna.src, - userListId: antenna.userListId, - users: [...antenna.users, canonical], - caseSensitive: antenna.caseSensitive, - withReplies: antenna.withReplies, - withFile: antenna.withFile, - notify: antenna.notify, - }); - antennasCache.delete(); - }, - })); - }, - }] : [])] as any; + if ($i) { + menuItems.push({ + icon: 'ti ti-mail', + text: i18n.ts.sendMessage, + action: () => { + const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${user.host}`; + os.post({ specified: user, initialText: `${canonical} ` }); + }, + }, { type: 'divider' }, { + icon: 'ti ti-pencil', + text: i18n.ts.editMemo, + action: editMemo, + }, { + type: 'parent', + icon: 'ti ti-list', + text: i18n.ts.addToList, + children: async () => { + const lists = await userListsCache.fetch(); + return lists.map(list => { + const isListed = ref(list.userIds?.includes(user.id) ?? false); + cleanups.push(watch(isListed, () => { + if (isListed.value) { + os.apiWithDialog('users/lists/push', { + listId: list.id, + userId: user.id, + }).then(() => { + list.userIds?.push(user.id); + }); + } else { + os.apiWithDialog('users/lists/pull', { + listId: list.id, + userId: user.id, + }).then(() => { + list.userIds?.splice(list.userIds?.indexOf(user.id), 1); + }); + } + })); + + return { + type: 'switch', + text: list.name, + ref: isListed, + }; + }); + }, + }, { + type: 'parent', + icon: 'ti ti-antenna', + text: i18n.ts.addToAntenna, + children: async () => { + const antennas = await antennasCache.fetch(); + const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`; + return antennas.filter((a) => a.src === 'users').map(antenna => ({ + text: antenna.name, + action: async () => { + await os.apiWithDialog('antennas/update', { + antennaId: antenna.id, + name: antenna.name, + keywords: antenna.keywords, + excludeKeywords: antenna.excludeKeywords, + src: antenna.src, + userListId: antenna.userListId, + users: [...antenna.users, canonical], + caseSensitive: antenna.caseSensitive, + withReplies: antenna.withReplies, + withFile: antenna.withFile, + notify: antenna.notify, + }); + antennasCache.delete(); + }, + })); + }, + }); + } if ($i && meId !== user.id) { if (iAmModerator) { - menu = menu.concat([{ + menuItems.push({ type: 'parent', icon: 'ti ti-badges', text: i18n.ts.roles, @@ -301,13 +333,14 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter }, })); }, - }]); + }); } // フォローしたとしても user.isFollowing はリアルタイム更新されないので不便なため //if (user.isFollowing) { - const withRepliesRef = ref(user.withReplies); - menu = menu.concat([{ + const withRepliesRef = ref(user.withReplies ?? false); + + menuItems.push({ type: 'switch', icon: 'ti ti-messages', text: i18n.ts.showRepliesToOthersInTimeline, @@ -316,7 +349,8 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter icon: user.notify === 'none' ? 'ti ti-bell' : 'ti ti-bell-off', text: user.notify === 'none' ? i18n.ts.notifyNotes : i18n.ts.unnotifyNotes, action: toggleNotify, - }]); + }); + watch(withRepliesRef, (withReplies) => { misskeyApi('following/update', { userId: user.id, @@ -327,7 +361,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter }); //} - menu = menu.concat([{ type: 'divider' }, { + menuItems.push({ type: 'divider' }, { icon: user.isMuted ? 'ti ti-eye' : 'ti ti-eye-off', text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute, action: toggleMute, @@ -339,70 +373,68 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter icon: 'ti ti-ban', text: user.isBlocking ? i18n.ts.unblock : i18n.ts.block, action: toggleBlock, - }]); + }); if (user.isFollowed) { - menu = menu.concat([{ + menuItems.push({ icon: 'ti ti-link-off', text: i18n.ts.breakFollow, action: invalidateFollow, - }]); + }); } - menu = menu.concat([{ type: 'divider' }, { + menuItems.push({ type: 'divider' }, { icon: 'ti ti-exclamation-circle', text: i18n.ts.reportAbuse, action: reportAbuse, - }]); + }); } if (user.host !== null) { - menu = menu.concat([{ type: 'divider' }, { + menuItems.push({ type: 'divider' }, { icon: 'ti ti-refresh', text: i18n.ts.updateRemoteUser, action: userInfoUpdate, - }]); + }); } if (defaultStore.state.devMode) { - menu = menu.concat([{ type: 'divider' }, { + menuItems.push({ type: 'divider' }, { icon: 'ti ti-id', text: i18n.ts.copyUserId, action: () => { copyToClipboard(user.id); }, - }]); + }); } if ($i && meId === user.id) { - menu = menu.concat([{ type: 'divider' }, { + menuItems.push({ type: 'divider' }, { icon: 'ti ti-pencil', text: i18n.ts.editProfile, action: () => { router.push('/settings/profile'); }, - }]); + }); } if (userActions.length > 0) { - menu = menu.concat([{ type: 'divider' }, ...userActions.map(action => ({ + menuItems.push({ type: 'divider' }, ...userActions.map(action => ({ icon: 'ti ti-plug', text: action.title, action: () => { action.handler(user); }, - }))]); + }))); } - const cleanup = () => { - if (_DEV_) console.log('user menu cleanup', cleanups); - for (const cl of cleanups) { - cl(); - } - }; - return { - menu, - cleanup, + menu: menuItems, + cleanup: () => { + if (_DEV_) console.log('user menu cleanup', cleanups); + for (const cl of cleanups) { + cl(); + } + }, }; } diff --git a/packages/frontend/src/scripts/idb-proxy.ts b/packages/frontend/src/scripts/idb-proxy.ts index 6b511f2a5f..20f51660c7 100644 --- a/packages/frontend/src/scripts/idb-proxy.ts +++ b/packages/frontend/src/scripts/idb-proxy.ts @@ -10,10 +10,11 @@ import { set as iset, del as idel, } from 'idb-keyval'; +import { miLocalStorage } from '@/local-storage.js'; -const fallbackName = (key: string) => `idbfallback::${key}`; +const PREFIX = 'idbfallback::'; -let idbAvailable = typeof window !== 'undefined' ? !!(window.indexedDB && window.indexedDB.open) : true; +let idbAvailable = typeof window !== 'undefined' ? !!(window.indexedDB && typeof window.indexedDB.open === 'function') : true; // iframe.contentWindow.indexedDB.deleteDatabase() がchromeのバグで使用できないため、indexedDBを無効化している。 // バグが治って再度有効化するのであれば、cypressのコマンド内のコメントアウトを外すこと @@ -38,15 +39,15 @@ if (idbAvailable) { export async function get(key: string) { if (idbAvailable) return iget(key); - return JSON.parse(window.localStorage.getItem(fallbackName(key))); + return miLocalStorage.getItemAsJson(`${PREFIX}${key}`); } export async function set(key: string, val: any) { if (idbAvailable) return iset(key, val); - return window.localStorage.setItem(fallbackName(key), JSON.stringify(val)); + return miLocalStorage.setItemAsJson(`${PREFIX}${key}`, val); } export async function del(key: string) { if (idbAvailable) return idel(key); - return window.localStorage.removeItem(fallbackName(key)); + return miLocalStorage.removeItem(`${PREFIX}${key}`); } diff --git a/packages/frontend/src/scripts/initialize-sw.ts b/packages/frontend/src/scripts/initialize-sw.ts index 1517e4e1e8..867ebf19ed 100644 --- a/packages/frontend/src/scripts/initialize-sw.ts +++ b/packages/frontend/src/scripts/initialize-sw.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { lang } from '@/config.js'; +import { lang } from '@@/js/config.js'; export async function initializeSw() { if (!('serviceWorker' in navigator)) return; diff --git a/packages/frontend/src/scripts/intl-const.ts b/packages/frontend/src/scripts/intl-const.ts index aaa4f0a86e..385f59ec39 100644 --- a/packages/frontend/src/scripts/intl-const.ts +++ b/packages/frontend/src/scripts/intl-const.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { lang } from '@/config.js'; +import { lang } from '@@/js/config.js'; export const versatileLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP'); diff --git a/packages/frontend/src/scripts/media-proxy.ts b/packages/frontend/src/scripts/media-proxy.ts index 099a22163a..78eba35ead 100644 --- a/packages/frontend/src/scripts/media-proxy.ts +++ b/packages/frontend/src/scripts/media-proxy.ts @@ -3,51 +3,32 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { query } from '@/scripts/url.js'; -import { url } from '@/config.js'; +import { MediaProxy } from '@@/js/media-proxy.js'; +import { url } from '@@/js/config.js'; import { instance } from '@/instance.js'; -export function getProxiedImageUrl(imageUrl: string, type?: 'preview' | 'emoji' | 'avatar', mustOrigin = false, noFallback = false): string { - const localProxy = `${url}/proxy`; +let _mediaProxy: MediaProxy | null = null; - if (imageUrl.startsWith(instance.mediaProxy + '/') || imageUrl.startsWith('/proxy/') || imageUrl.startsWith(localProxy + '/')) { - // もう既にproxyっぽそうだったらurlを取り出す - imageUrl = (new URL(imageUrl)).searchParams.get('url') ?? imageUrl; +export function getProxiedImageUrl(...args: Parameters<MediaProxy['getProxiedImageUrl']>): string { + if (_mediaProxy == null) { + _mediaProxy = new MediaProxy(instance, url); } - return `${mustOrigin ? localProxy : instance.mediaProxy}/${ - type === 'preview' ? 'preview.webp' - : 'image.webp' - }?${query({ - url: imageUrl, - ...(!noFallback ? { 'fallback': '1' } : {}), - ...(type ? { [type]: '1' } : {}), - ...(mustOrigin ? { origin: '1' } : {}), - })}`; + return _mediaProxy.getProxiedImageUrl(...args); } -export function getProxiedImageUrlNullable(imageUrl: string | null | undefined, type?: 'preview'): string | null { - if (imageUrl == null) return null; - return getProxiedImageUrl(imageUrl, type); -} - -export function getStaticImageUrl(baseUrl: string): string { - const u = baseUrl.startsWith('http') ? new URL(baseUrl) : new URL(baseUrl, url); - - if (u.href.startsWith(`${url}/emoji/`)) { - // もう既にemojiっぽそうだったらsearchParams付けるだけ - u.searchParams.set('static', '1'); - return u.href; +export function getProxiedImageUrlNullable(...args: Parameters<MediaProxy['getProxiedImageUrlNullable']>): string | null { + if (_mediaProxy == null) { + _mediaProxy = new MediaProxy(instance, url); } - if (u.href.startsWith(instance.mediaProxy + '/')) { - // もう既にproxyっぽそうだったらsearchParams付けるだけ - u.searchParams.set('static', '1'); - return u.href; + return _mediaProxy.getProxiedImageUrlNullable(...args); +} + +export function getStaticImageUrl(...args: Parameters<MediaProxy['getStaticImageUrl']>): string { + if (_mediaProxy == null) { + _mediaProxy = new MediaProxy(instance, url); } - return `${instance.mediaProxy}/static.webp?${query({ - url: u.href, - static: '1', - })}`; + return _mediaProxy.getStaticImageUrl(...args); } diff --git a/packages/frontend/src/scripts/mfm-function-picker.ts b/packages/frontend/src/scripts/mfm-function-picker.ts index 9938e534c1..bf59fe98a0 100644 --- a/packages/frontend/src/scripts/mfm-function-picker.ts +++ b/packages/frontend/src/scripts/mfm-function-picker.ts @@ -6,7 +6,7 @@ import { Ref, nextTick } from 'vue'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; -import { MFM_TAGS } from '@/const.js'; +import { MFM_TAGS } from '@@/js/const.js'; import type { MenuItem } from '@/types/menu.js'; /** diff --git a/packages/frontend/src/scripts/misskey-api.ts b/packages/frontend/src/scripts/misskey-api.ts index 49fb6f9e59..1b1159fd01 100644 --- a/packages/frontend/src/scripts/misskey-api.ts +++ b/packages/frontend/src/scripts/misskey-api.ts @@ -5,7 +5,7 @@ import * as Misskey from 'misskey-js'; import { ref } from 'vue'; -import { apiUrl } from '@/config.js'; +import { apiUrl } from '@@/js/config.js'; import { $i } from '@/account.js'; export const pendingApiRequestsCount = ref(0); diff --git a/packages/frontend/src/scripts/player-url-transform.ts b/packages/frontend/src/scripts/player-url-transform.ts index 53b2a9e441..39c6df6500 100644 --- a/packages/frontend/src/scripts/player-url-transform.ts +++ b/packages/frontend/src/scripts/player-url-transform.ts @@ -2,7 +2,7 @@ * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ -import { hostname } from '@/config.js'; +import { hostname } from '@@/js/config.js'; export function transformPlayerUrl(url: string): string { const urlObj = new URL(url); diff --git a/packages/frontend/src/scripts/popout.ts b/packages/frontend/src/scripts/popout.ts index 1caa2dfc21..5b141222e8 100644 --- a/packages/frontend/src/scripts/popout.ts +++ b/packages/frontend/src/scripts/popout.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { appendQuery } from './url.js'; -import * as config from '@/config.js'; +import { appendQuery } from '@@/js/url.js'; +import * as config from '@@/js/config.js'; export function popout(path: string, w?: HTMLElement) { let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + path; diff --git a/packages/frontend/src/scripts/post-message.ts b/packages/frontend/src/scripts/post-message.ts index 31a9ac1ad9..11b6f52ddd 100644 --- a/packages/frontend/src/scripts/post-message.ts +++ b/packages/frontend/src/scripts/post-message.ts @@ -18,7 +18,7 @@ export type MiPostMessageEvent = { * 親フレームにイベントを送信 */ export function postMessageToParentWindow(type: PostMessageEventType, payload?: any): void { - window.postMessage({ + window.parent.postMessage({ type, payload, }, '*'); diff --git a/packages/frontend/src/scripts/reload-ask.ts b/packages/frontend/src/scripts/reload-ask.ts new file mode 100644 index 0000000000..733d91b85a --- /dev/null +++ b/packages/frontend/src/scripts/reload-ask.ts @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { i18n } from '@/i18n.js'; +import * as os from '@/os.js'; +import { unisonReload } from '@/scripts/unison-reload.js'; + +let isReloadConfirming = false; + +export async function reloadAsk(opts: { + unison?: boolean; + reason?: string; +}) { + if (isReloadConfirming) { + return; + } + + isReloadConfirming = true; + + const { canceled } = await os.confirm(opts.reason == null ? { + type: 'info', + text: i18n.ts.reloadConfirm, + } : { + type: 'info', + title: i18n.ts.reloadConfirm, + text: opts.reason, + }).finally(() => { + isReloadConfirming = false; + }); + + if (canceled) return; + + if (opts.unison) { + unisonReload(); + } else { + location.reload(); + } +} diff --git a/packages/frontend/src/scripts/safe-parse.ts b/packages/frontend/src/scripts/safe-parse.ts deleted file mode 100644 index 6bfcef6c36..0000000000 --- a/packages/frontend/src/scripts/safe-parse.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export function safeParseFloat(str: unknown): number | null { - if (typeof str !== 'string' || str === '') return null; - const num = parseFloat(str); - if (isNaN(num)) return null; - return num; -} diff --git a/packages/frontend/src/scripts/safe-uri-decode.ts b/packages/frontend/src/scripts/safe-uri-decode.ts deleted file mode 100644 index 0edf4e9eba..0000000000 --- a/packages/frontend/src/scripts/safe-uri-decode.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export function safeURIDecode(str: string): string { - try { - return decodeURIComponent(str); - } catch { - return str; - } -} diff --git a/packages/frontend/src/scripts/stream-mock.ts b/packages/frontend/src/scripts/stream-mock.ts new file mode 100644 index 0000000000..cb0e607fcb --- /dev/null +++ b/packages/frontend/src/scripts/stream-mock.ts @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { EventEmitter } from 'eventemitter3'; +import * as Misskey from 'misskey-js'; +import type { Channels, StreamEvents, IStream, IChannelConnection } from 'misskey-js'; + +type AnyOf<T extends Record<any, any>> = T[keyof T]; +type OmitFirst<T extends any[]> = T extends [any, ...infer R] ? R : never; + +/** + * Websocket無効化時に使うStreamのモック(なにもしない) + */ +export class StreamMock extends EventEmitter<StreamEvents> implements IStream { + public readonly state = 'initializing'; + + constructor(...args: ConstructorParameters<typeof Misskey.Stream>) { + super(); + // do nothing + } + + public useChannel<C extends keyof Channels>(channel: C, params?: Channels[C]['params'], name?: string): ChannelConnectionMock<Channels[C]> { + return new ChannelConnectionMock(this, channel, name); + } + + public removeSharedConnection(connection: any): void { + // do nothing + } + + public removeSharedConnectionPool(pool: any): void { + // do nothing + } + + public disconnectToChannel(): void { + // do nothing + } + + public send(typeOrPayload: string): void + public send(typeOrPayload: string, payload: any): void + public send(typeOrPayload: Record<string, any> | any[]): void + public send(typeOrPayload: string | Record<string, any> | any[], payload?: any): void { + // do nothing + } + + public ping(): void { + // do nothing + } + + public heartbeat(): void { + // do nothing + } + + public close(): void { + // do nothing + } +} + +class ChannelConnectionMock<Channel extends AnyOf<Channels> = any> extends EventEmitter<Channel['events']> implements IChannelConnection<Channel> { + public id = ''; + public name?: string; // for debug + public inCount = 0; // for debug + public outCount = 0; // for debug + public channel: string; + + constructor(stream: IStream, ...args: OmitFirst<ConstructorParameters<typeof Misskey.ChannelConnection<Channel>>>) { + super(); + + this.channel = args[0]; + this.name = args[1]; + } + + public send<T extends keyof Channel['receives']>(type: T, body: Channel['receives'][T]): void { + // do nothing + } + + public dispose(): void { + // do nothing + } +} diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts index c7f8b3d596..fc888c0908 100644 --- a/packages/frontend/src/scripts/theme.ts +++ b/packages/frontend/src/scripts/theme.ts @@ -5,11 +5,11 @@ import { ref } from 'vue'; import tinycolor from 'tinycolor2'; +import lightTheme from '@@/themes/_light.json5'; +import darkTheme from '@@/themes/_dark.json5'; import { deepClone } from './clone.js'; import type { BundledTheme } from 'shiki/themes'; import { globalEvents } from '@/events.js'; -import lightTheme from '@/themes/_light.json5'; -import darkTheme from '@/themes/_dark.json5'; import { miLocalStorage } from '@/local-storage.js'; export type Theme = { @@ -52,7 +52,7 @@ export const getBuiltinThemes = () => Promise.all( 'd-cherry', 'd-ice', 'd-u0', - ].map(name => import(`@/themes/${name}.json5`).then(({ default: _default }): Theme => _default)), + ].map(name => import(`@@/themes/${name}.json5`).then(({ default: _default }): Theme => _default)), ); export const getBuiltinThemesRef = () => { diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/scripts/upload.ts index abb0e1e677..22dce609c6 100644 --- a/packages/frontend/src/scripts/upload.ts +++ b/packages/frontend/src/scripts/upload.ts @@ -9,7 +9,7 @@ import { v4 as uuid } from 'uuid'; import { readAndCompressImage } from '@misskey-dev/browser-image-resizer'; import { getCompressionConfig } from './upload/compress-config.js'; import { defaultStore } from '@/store.js'; -import { apiUrl } from '@/config.js'; +import { apiUrl } from '@@/js/config.js'; import { $i } from '@/account.js'; import { alert } from '@/os.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/scripts/use-form.ts b/packages/frontend/src/scripts/use-form.ts new file mode 100644 index 0000000000..0d505fe466 --- /dev/null +++ b/packages/frontend/src/scripts/use-form.ts @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { computed, Reactive, reactive, watch } from 'vue'; + +function copy<T>(v: T): T { + return JSON.parse(JSON.stringify(v)); +} + +function unwrapReactive<T>(v: Reactive<T>): T { + return JSON.parse(JSON.stringify(v)); +} + +export function useForm<T extends Record<string, any>>(initialState: T, save: (newState: T) => Promise<void>) { + const currentState = reactive<T>(copy(initialState)); + const previousState = reactive<T>(copy(initialState)); + + const modifiedStates = reactive<Record<keyof T, boolean>>({} as any); + for (const key in currentState) { + modifiedStates[key] = false; + } + const modified = computed(() => Object.values(modifiedStates).some(v => v)); + const modifiedCount = computed(() => Object.values(modifiedStates).filter(v => v).length); + + watch([currentState, previousState], () => { + for (const key in modifiedStates) { + modifiedStates[key] = currentState[key] !== previousState[key]; + } + }, { deep: true }); + + async function _save() { + await save(unwrapReactive(currentState)); + for (const key in currentState) { + previousState[key] = copy(currentState[key]); + } + } + + function discard() { + for (const key in currentState) { + currentState[key] = copy(previousState[key]); + } + } + + return { + state: currentState, + savedState: previousState, + modifiedStates, + modified, + modifiedCount, + save: _save, + discard, + }; +} diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 437314074a..40615cfc7d 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -8,7 +8,7 @@ import * as Misskey from 'misskey-js'; import { miLocalStorage } from './local-storage.js'; import type { SoundType } from '@/scripts/sound.js'; import { Storage } from '@/pizzax.js'; -import { hemisphere } from '@/scripts/intl-const.js'; +import { hemisphere } from '@@/js/intl-const.js'; interface PostFormAction { title: string, @@ -458,10 +458,10 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: false, }, - contextMenu: { + contextMenu: { where: 'device', default: 'app' as 'app' | 'appWithShift' | 'native', - }, + }, sound_masterVolume: { where: 'device', @@ -520,8 +520,8 @@ interface Watcher { /** * 常にメモリにロードしておく必要がないような設定情報を保管するストレージ(非リアクティブ) */ -import lightTheme from '@/themes/l-light.json5'; -import darkTheme from '@/themes/d-green-lime.json5'; +import lightTheme from '@@/themes/l-light.json5'; +import darkTheme from '@@/themes/d-green-lime.json5'; export class ColdDeviceStorage { public static default = { @@ -558,7 +558,7 @@ export class ColdDeviceStorage { public static set<T extends keyof typeof ColdDeviceStorage.default>(key: T, value: typeof ColdDeviceStorage.default[T]): void { // 呼び出し側のバグ等で undefined が来ることがある // undefined を文字列として miLocalStorage に入れると参照する際の JSON.parse でコケて不具合の元になるため無視 - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (value === undefined) { console.error(`attempt to store undefined value for key '${key}'`); return; diff --git a/packages/frontend/src/stream.ts b/packages/frontend/src/stream.ts index 0d5bd78b09..e63dac951c 100644 --- a/packages/frontend/src/stream.ts +++ b/packages/frontend/src/stream.ts @@ -6,18 +6,21 @@ import * as Misskey from 'misskey-js'; import { markRaw } from 'vue'; import { $i } from '@/account.js'; -import { wsOrigin } from '@/config.js'; +import { wsOrigin } from '@@/js/config.js'; +// TODO: No WebsocketモードでStreamMockが使えそう +//import { StreamMock } from '@/scripts/stream-mock.js'; // heart beat interval in ms const HEART_BEAT_INTERVAL = 1000 * 60; -let stream: Misskey.Stream | null = null; -let timeoutHeartBeat: ReturnType<typeof setTimeout> | null = null; +let stream: Misskey.IStream | null = null; +let timeoutHeartBeat: number | null = null; let lastHeartbeatCall = 0; -export function useStream(): Misskey.Stream { +export function useStream(): Misskey.IStream { if (stream) return stream; + // TODO: No Websocketモードもここで判定 stream = markRaw(new Misskey.Stream(wsOrigin, $i ? { token: $i.token, } : null)); diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss index caaf9fca6f..5e19447120 100644 --- a/packages/frontend/src/style.scss +++ b/packages/frontend/src/style.scss @@ -378,6 +378,16 @@ rt { vertical-align: top; } +._modified { + margin-left: 0.7em; + font-size: 65%; + padding: 2px 3px; + color: var(--warn); + border: solid 1px var(--warn); + border-radius: 4px; + vertical-align: top; +} + ._table { > ._row { display: flex; diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts index 74c3028745..f908803f01 100644 --- a/packages/frontend/src/ui/_common_/common.ts +++ b/packages/frontend/src/ui/_common_/common.ts @@ -7,7 +7,7 @@ import { defineAsyncComponent } from 'vue'; import type { MenuItem } from '@/types/menu.js'; import * as os from '@/os.js'; import { instance } from '@/instance.js'; -import { host } from '@/config.js'; +import { host } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { $i } from '@/account.js'; @@ -41,7 +41,9 @@ function toolsMenuItems(): MenuItem[] { } export function openInstanceMenu(ev: MouseEvent) { - os.popupMenu([{ + const menuItems: MenuItem[] = []; + + menuItems.push({ text: instance.name ?? host, type: 'label', }, { @@ -69,12 +71,18 @@ export function openInstanceMenu(ev: MouseEvent) { text: i18n.ts.ads, icon: 'ti ti-ad', to: '/ads', - }, ($i && ($i.isAdmin || $i.policies.canInvite) && instance.disableRegistration) ? { - type: 'link', - to: '/invite', - text: i18n.ts.invite, - icon: 'ti ti-user-plus', - } : undefined, { + }); + + if ($i && ($i.isAdmin || $i.policies.canInvite) && instance.disableRegistration) { + menuItems.push({ + type: 'link', + to: '/invite', + text: i18n.ts.invite, + icon: 'ti ti-user-plus', + }); + } + + menuItems.push({ type: 'parent', text: i18n.ts.tools, icon: 'ti ti-tool', @@ -84,43 +92,69 @@ export function openInstanceMenu(ev: MouseEvent) { text: i18n.ts.inquiry, icon: 'ti ti-help-circle', to: '/contact', - }, (instance.impressumUrl) ? { - type: 'a', - text: i18n.ts.impressum, - icon: 'ti ti-file-invoice', - href: instance.impressumUrl, - target: '_blank', - } : undefined, (instance.tosUrl) ? { - type: 'a', - text: i18n.ts.termsOfService, - icon: 'ti ti-notebook', - href: instance.tosUrl, - target: '_blank', - } : undefined, (instance.privacyPolicyUrl) ? { - type: 'a', - text: i18n.ts.privacyPolicy, - icon: 'ti ti-shield-lock', - href: instance.privacyPolicyUrl, - target: '_blank', - } : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) ? undefined : { type: 'divider' }, { + }); + + if (instance.impressumUrl) { + menuItems.push({ + type: 'a', + text: i18n.ts.impressum, + icon: 'ti ti-file-invoice', + href: instance.impressumUrl, + target: '_blank', + }); + } + + if (instance.tosUrl) { + menuItems.push({ + type: 'a', + text: i18n.ts.termsOfService, + icon: 'ti ti-notebook', + href: instance.tosUrl, + target: '_blank', + }); + } + + if (instance.privacyPolicyUrl) { + menuItems.push({ + type: 'a', + text: i18n.ts.privacyPolicy, + icon: 'ti ti-shield-lock', + href: instance.privacyPolicyUrl, + target: '_blank', + }); + } + + if (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) { + menuItems.push({ type: 'divider' }); + } + + menuItems.push({ type: 'a', text: i18n.ts.document, icon: 'ti ti-bulb', href: 'https://misskey-hub.net/docs/for-users/', target: '_blank', - }, ($i) ? { - text: i18n.ts._initialTutorial.launchTutorial, - icon: 'ti ti-presentation', - action: () => { - const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTutorialDialog.vue')), {}, { - closed: () => dispose(), - }); - }, - } : undefined, { + }); + + if ($i) { + menuItems.push({ + text: i18n.ts._initialTutorial.launchTutorial, + icon: 'ti ti-presentation', + action: () => { + const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTutorialDialog.vue')), {}, { + closed: () => dispose(), + }); + }, + }); + } + + menuItems.push({ type: 'link', text: i18n.ts.aboutMisskey, to: '/about-misskey', - }], ev.currentTarget ?? ev.target, { + }); + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target, { align: 'left', }); } diff --git a/packages/frontend/src/ui/_common_/statusbar-federation.vue b/packages/frontend/src/ui/_common_/statusbar-federation.vue index 8dad666623..e234bb3a33 100644 --- a/packages/frontend/src/ui/_common_/statusbar-federation.vue +++ b/packages/frontend/src/ui/_common_/statusbar-federation.vue @@ -35,7 +35,7 @@ import { ref } from 'vue'; import * as Misskey from 'misskey-js'; import MarqueeText from '@/components/MkMarquee.vue'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js'; const props = defineProps<{ diff --git a/packages/frontend/src/ui/_common_/statusbar-rss.vue b/packages/frontend/src/ui/_common_/statusbar-rss.vue index 6e1d06eec1..550fc39b00 100644 --- a/packages/frontend/src/ui/_common_/statusbar-rss.vue +++ b/packages/frontend/src/ui/_common_/statusbar-rss.vue @@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref } from 'vue'; import * as Misskey from 'misskey-js'; import MarqueeText from '@/components/MkMarquee.vue'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import { shuffle } from '@/scripts/shuffle.js'; const props = defineProps<{ diff --git a/packages/frontend/src/ui/_common_/statusbar-user-list.vue b/packages/frontend/src/ui/_common_/statusbar-user-list.vue index 67f8b109c4..078b595dca 100644 --- a/packages/frontend/src/ui/_common_/statusbar-user-list.vue +++ b/packages/frontend/src/ui/_common_/statusbar-user-list.vue @@ -35,7 +35,7 @@ import { ref, watch } from 'vue'; import * as Misskey from 'misskey-js'; import MarqueeText from '@/components/MkMarquee.vue'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import { getNoteSummary } from '@/scripts/get-note-summary.js'; import { notePage } from '@/filters/note.js'; diff --git a/packages/frontend/src/ui/classic.sidebar.vue b/packages/frontend/src/ui/classic.sidebar.vue index d8574a915f..87b4515d46 100644 --- a/packages/frontend/src/ui/classic.sidebar.vue +++ b/packages/frontend/src/ui/classic.sidebar.vue @@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { defineAsyncComponent, computed, watch, ref, shallowRef } from 'vue'; import { openInstanceMenu } from './_common_/common.js'; -// import { host } from '@/config.js'; +// import { host } from '@@/js/config.js'; import * as os from '@/os.js'; import { navbarItemDef } from '@/navbar.js'; import { openAccountMenu as openAccountMenu_, $i } from '@/account.js'; diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue index b833e9f6be..fa04409d2d 100644 --- a/packages/frontend/src/ui/classic.vue +++ b/packages/frontend/src/ui/classic.vue @@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { defineAsyncComponent, onMounted, provide, ref, computed, shallowRef } from 'vue'; import XSidebar from './classic.sidebar.vue'; import XCommon from './_common_/common.vue'; -import { instanceName } from '@/config.js'; +import { instanceName } from '@@/js/config.js'; import { StickySidebar } from '@/scripts/sticky-sidebar.js'; import * as os from '@/os.js'; import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js'; @@ -57,6 +57,8 @@ import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import { miLocalStorage } from '@/local-storage.js'; import { mainRouter } from '@/router/main.js'; +import { isLink } from '@@/js/is-link.js'; + const XHeaderMenu = defineAsyncComponent(() => import('./classic.header.vue')); const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue')); @@ -104,12 +106,6 @@ function top() { } function onContextmenu(ev: MouseEvent) { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; if (isLink(ev.target)) return; if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; if (window.getSelection().toString() !== '') return; diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index 9c3addc482..750cdca90e 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -118,7 +118,7 @@ import XMentionsColumn from '@/ui/deck/mentions-column.vue'; import XDirectColumn from '@/ui/deck/direct-column.vue'; import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue'; import { mainRouter } from '@/router/main.js'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue')); const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue')); diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue index 987bd4db55..a41639e71c 100644 --- a/packages/frontend/src/ui/deck/antenna-column.vue +++ b/packages/frontend/src/ui/deck/antenna-column.vue @@ -22,7 +22,7 @@ import MkTimeline from '@/components/MkTimeline.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; import { antennasCache } from '@/cache.js'; import { SoundStore } from '@/store.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue index 42c07056e7..5479b53d90 100644 --- a/packages/frontend/src/ui/deck/channel-column.vue +++ b/packages/frontend/src/ui/deck/channel-column.vue @@ -29,7 +29,7 @@ import * as os from '@/os.js'; import { favoritedChannelsCache } from '@/cache.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; import { SoundStore } from '@/store.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; import * as sound from '@/scripts/sound.js'; diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue index 893301122e..b97d86f4a3 100644 --- a/packages/frontend/src/ui/deck/column.vue +++ b/packages/frontend/src/ui/deck/column.vue @@ -46,7 +46,7 @@ import { onBeforeUnmount, onMounted, provide, watch, shallowRef, ref, computed } import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column } from './deck-store.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; provide('shouldHeaderThin', true); provide('shouldOmitHeaderTitle', true); @@ -104,7 +104,27 @@ function toggleActive() { } function getMenu() { - let items: MenuItem[] = [{ + const menuItems: MenuItem[] = []; + + if (props.menu) { + menuItems.push(...props.menu, { + type: 'divider', + }); + } + + if (props.refresher) { + menuItems.push({ + icon: 'ti ti-refresh', + text: i18n.ts.reload, + action: () => { + if (props.refresher) { + props.refresher(); + } + }, + }); + } + + menuItems.push({ icon: 'ti ti-settings', text: i18n.ts._deck.configureColumn, action: async () => { @@ -129,74 +149,73 @@ function getMenu() { if (canceled) return; updateColumn(props.column.id, result); }, + }); + + const moveToMenuItems: MenuItem[] = []; + + moveToMenuItems.push({ + icon: 'ti ti-arrow-left', + text: i18n.ts._deck.swapLeft, + action: () => { + swapLeftColumn(props.column.id); + }, }, { - type: 'parent', - text: i18n.ts.move + '...', - icon: 'ti ti-arrows-move', - children: [{ - icon: 'ti ti-arrow-left', - text: i18n.ts._deck.swapLeft, - action: () => { - swapLeftColumn(props.column.id); - }, - }, { - icon: 'ti ti-arrow-right', - text: i18n.ts._deck.swapRight, - action: () => { - swapRightColumn(props.column.id); - }, - }, props.isStacked ? { + icon: 'ti ti-arrow-right', + text: i18n.ts._deck.swapRight, + action: () => { + swapRightColumn(props.column.id); + }, + }); + + if (props.isStacked) { + moveToMenuItems.push({ icon: 'ti ti-arrow-up', text: i18n.ts._deck.swapUp, action: () => { swapUpColumn(props.column.id); }, - } : undefined, props.isStacked ? { + }, { icon: 'ti ti-arrow-down', text: i18n.ts._deck.swapDown, action: () => { swapDownColumn(props.column.id); }, - } : undefined], + }); + } + + menuItems.push({ + type: 'parent', + text: i18n.ts.move + '...', + icon: 'ti ti-arrows-move', + children: moveToMenuItems, }, { icon: 'ti ti-stack-2', text: i18n.ts._deck.stackLeft, action: () => { stackLeftColumn(props.column.id); }, - }, props.isStacked ? { - icon: 'ti ti-window-maximize', - text: i18n.ts._deck.popRight, - action: () => { - popRightColumn(props.column.id); - }, - } : undefined, { type: 'divider' }, { + }); + + if (props.isStacked) { + menuItems.push({ + icon: 'ti ti-window-maximize', + text: i18n.ts._deck.popRight, + action: () => { + popRightColumn(props.column.id); + }, + }); + } + + menuItems.push({ type: 'divider' }, { icon: 'ti ti-trash', text: i18n.ts.remove, danger: true, action: () => { removeColumn(props.column.id); }, - }]; + }); - if (props.menu) { - items.unshift({ type: 'divider' }); - items = props.menu.concat(items); - } - - if (props.refresher) { - items = [{ - icon: 'ti ti-refresh', - text: i18n.ts.reload, - action: () => { - if (props.refresher) { - props.refresher(); - } - }, - }, ...items]; - } - - return items; + return menuItems; } function showSettingsMenu(ev: MouseEvent) { diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue index 9aa8f06476..8bb8fe7225 100644 --- a/packages/frontend/src/ui/deck/list-column.vue +++ b/packages/frontend/src/ui/deck/list-column.vue @@ -22,7 +22,7 @@ import MkTimeline from '@/components/MkTimeline.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; import { SoundStore } from '@/store.js'; import { userListsCache } from '@/cache.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; diff --git a/packages/frontend/src/ui/deck/main-column.vue b/packages/frontend/src/ui/deck/main-column.vue index 79c9671917..f8c712c371 100644 --- a/packages/frontend/src/ui/deck/main-column.vue +++ b/packages/frontend/src/ui/deck/main-column.vue @@ -26,7 +26,8 @@ import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js'; import { useScrollPositionManager } from '@/nirax.js'; -import { getScrollContainer } from '@/scripts/scroll.js'; +import { getScrollContainer } from '@@/js/scroll.js'; +import { isLink } from '@@/js/is-link.js'; import { mainRouter } from '@/router/main.js'; defineProps<{ @@ -52,12 +53,6 @@ function back() { function onContextmenu(ev: MouseEvent) { if (!ev.target) return; - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; if (isLink(ev.target as HTMLElement)) return; if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes((ev.target as HTMLElement).tagName) || (ev.target as HTMLElement).attributes['contenteditable']) return; if (window.getSelection()?.toString() !== '') return; diff --git a/packages/frontend/src/ui/deck/role-timeline-column.vue b/packages/frontend/src/ui/deck/role-timeline-column.vue index a375e9c574..beb4237978 100644 --- a/packages/frontend/src/ui/deck/role-timeline-column.vue +++ b/packages/frontend/src/ui/deck/role-timeline-column.vue @@ -21,7 +21,7 @@ import MkTimeline from '@/components/MkTimeline.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; -import { MenuItem } from '@/types/menu.js'; +import type { MenuItem } from '@/types/menu.js'; import { SoundStore } from '@/store.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; import * as sound from '@/scripts/sound.js'; diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue index e210ee7b7a..01da92f731 100644 --- a/packages/frontend/src/ui/deck/tl-column.vue +++ b/packages/frontend/src/ui/deck/tl-column.vue @@ -113,29 +113,41 @@ function onNote() { sound.playMisskeySfxFile(soundSetting.value); } -const menu = computed<MenuItem[]>(() => [{ - icon: 'ti ti-pencil', - text: i18n.ts.timeline, - action: setType, -}, { - icon: 'ti ti-bell', - text: i18n.ts._deck.newNoteNotificationSettings, - action: () => soundSettingsButton(soundSetting), -}, { - type: 'switch', - text: i18n.ts.showRenotes, - ref: withRenotes, -}, hasWithReplies(props.column.tl) ? { - type: 'switch', - text: i18n.ts.showRepliesToOthersInTimeline, - ref: withReplies, - disabled: onlyFiles, -} : undefined, { - type: 'switch', - text: i18n.ts.fileAttachedOnly, - ref: onlyFiles, - disabled: hasWithReplies(props.column.tl) ? withReplies : false, -}]); +const menu = computed<MenuItem[]>(() => { + const menuItems: MenuItem[] = []; + + menuItems.push({ + icon: 'ti ti-pencil', + text: i18n.ts.timeline, + action: setType, + }, { + icon: 'ti ti-bell', + text: i18n.ts._deck.newNoteNotificationSettings, + action: () => soundSettingsButton(soundSetting), + }, { + type: 'switch', + text: i18n.ts.showRenotes, + ref: withRenotes, + }); + + if (hasWithReplies(props.column.tl)) { + menuItems.push({ + type: 'switch', + text: i18n.ts.showRepliesToOthersInTimeline, + ref: withReplies, + disabled: onlyFiles, + }); + } + + menuItems.push({ + type: 'switch', + text: i18n.ts.fileAttachedOnly, + ref: onlyFiles, + disabled: hasWithReplies(props.column.tl) ? withReplies : false, + }); + + return menuItems; +}); </script> <style lang="scss" module> diff --git a/packages/frontend/src/ui/minimum.vue b/packages/frontend/src/ui/minimum.vue index db5eb19c20..9e41c48c5b 100644 --- a/packages/frontend/src/ui/minimum.vue +++ b/packages/frontend/src/ui/minimum.vue @@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, provide, ref } from 'vue'; import XCommon from './_common_/common.vue'; import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js'; -import { instanceName } from '@/config.js'; +import { instanceName } from '@@/js/config.js'; import { mainRouter } from '@/router/main.js'; const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index'); diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue index 073acbd4db..a2a79c74a1 100644 --- a/packages/frontend/src/ui/universal.vue +++ b/packages/frontend/src/ui/universal.vue @@ -98,7 +98,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { defineAsyncComponent, provide, onMounted, computed, ref, watch, shallowRef, Ref } from 'vue'; import XCommon from './_common_/common.vue'; import type MkStickyContainer from '@/components/global/MkStickyContainer.vue'; -import { instanceName } from '@/config.js'; +import { instanceName } from '@@/js/config.js'; import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue'; import * as os from '@/os.js'; import { defaultStore } from '@/store.js'; @@ -108,9 +108,10 @@ import { $i } from '@/account.js'; import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js'; import { deviceKind } from '@/scripts/device-kind.js'; import { miLocalStorage } from '@/local-storage.js'; -import { CURRENT_STICKY_BOTTOM } from '@/const.js'; +import { CURRENT_STICKY_BOTTOM } from '@@/js/const.js'; import { useScrollPositionManager } from '@/nirax.js'; import { mainRouter } from '@/router/main.js'; +import { isLink } from '@@/js/is-link.js'; const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue')); const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue')); @@ -195,12 +196,6 @@ onMounted(() => { }); const onContextmenu = (ev) => { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; if (isLink(ev.target)) return; if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; if (window.getSelection()?.toString() !== '') return; diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue index c229946bd4..01d0737123 100644 --- a/packages/frontend/src/ui/visitor.vue +++ b/packages/frontend/src/ui/visitor.vue @@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, provide, ref, computed } from 'vue'; import XCommon from './_common_/common.vue'; -import { instanceName } from '@/config.js'; +import { instanceName } from '@@/js/config.js'; import * as os from '@/os.js'; import { instance } from '@/instance.js'; import XSigninDialog from '@/components/MkSigninDialog.vue'; diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue index bb8cffaf52..f22bf41fd7 100644 --- a/packages/frontend/src/ui/zen.vue +++ b/packages/frontend/src/ui/zen.vue @@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, provide, ref } from 'vue'; import XCommon from './_common_/common.vue'; import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js'; -import { instanceName, ui } from '@/config.js'; +import { instanceName, ui } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { mainRouter } from '@/router/main.js'; diff --git a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue index 49fd103d37..bcfaaf00ab 100644 --- a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue +++ b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue @@ -25,11 +25,11 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref } from 'vue'; import * as Misskey from 'misskey-js'; +import { useInterval } from '@@/js/use-interval.js'; import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; import { GetFormResultType } from '@/scripts/form.js'; import MkContainer from '@/components/MkContainer.vue'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { useInterval } from '@/scripts/use-interval.js'; import { i18n } from '@/i18n.js'; import { infoImageUrl } from '@/instance.js'; import { $i } from '@/account.js'; diff --git a/packages/frontend/src/widgets/WidgetCalendar.vue b/packages/frontend/src/widgets/WidgetCalendar.vue index 6ece33eff3..412d527819 100644 --- a/packages/frontend/src/widgets/WidgetCalendar.vue +++ b/packages/frontend/src/widgets/WidgetCalendar.vue @@ -42,7 +42,7 @@ import { ref } from 'vue'; import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; import { GetFormResultType } from '@/scripts/form.js'; import { i18n } from '@/i18n.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; const name = 'calendar'; diff --git a/packages/frontend/src/widgets/WidgetFederation.vue b/packages/frontend/src/widgets/WidgetFederation.vue index ed907de9b8..c10416e4fb 100644 --- a/packages/frontend/src/widgets/WidgetFederation.vue +++ b/packages/frontend/src/widgets/WidgetFederation.vue @@ -32,7 +32,7 @@ import { GetFormResultType } from '@/scripts/form.js'; import MkContainer from '@/components/MkContainer.vue'; import MkMiniChart from '@/components/MkMiniChart.vue'; import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import { i18n } from '@/i18n.js'; import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js'; import { defaultStore } from '@/store.js'; diff --git a/packages/frontend/src/widgets/WidgetInstanceCloud.vue b/packages/frontend/src/widgets/WidgetInstanceCloud.vue index 76ccdb3971..d090372b9a 100644 --- a/packages/frontend/src/widgets/WidgetInstanceCloud.vue +++ b/packages/frontend/src/widgets/WidgetInstanceCloud.vue @@ -26,7 +26,7 @@ import MkContainer from '@/components/MkContainer.vue'; import MkTagCloud from '@/components/MkTagCloud.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js'; const name = 'instanceCloud'; diff --git a/packages/frontend/src/widgets/WidgetInstanceInfo.vue b/packages/frontend/src/widgets/WidgetInstanceInfo.vue index 5d8beaf9a9..ec12aa265c 100644 --- a/packages/frontend/src/widgets/WidgetInstanceInfo.vue +++ b/packages/frontend/src/widgets/WidgetInstanceInfo.vue @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; import { GetFormResultType } from '@/scripts/form.js'; -import { host } from '@/config.js'; +import { host } from '@@/js/config.js'; import { instance } from '@/instance.js'; const name = 'instanceInfo'; diff --git a/packages/frontend/src/widgets/WidgetOnlineUsers.vue b/packages/frontend/src/widgets/WidgetOnlineUsers.vue index 5c89a06c62..d56ee96ac1 100644 --- a/packages/frontend/src/widgets/WidgetOnlineUsers.vue +++ b/packages/frontend/src/widgets/WidgetOnlineUsers.vue @@ -18,7 +18,7 @@ import { ref } from 'vue'; import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; import { GetFormResultType } from '@/scripts/form.js'; import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import { i18n } from '@/i18n.js'; import number from '@/filters/number.js'; diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue index e5758662cc..511777a570 100644 --- a/packages/frontend/src/widgets/WidgetRss.vue +++ b/packages/frontend/src/widgets/WidgetRss.vue @@ -28,9 +28,9 @@ import * as Misskey from 'misskey-js'; import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; import { GetFormResultType } from '@/scripts/form.js'; import MkContainer from '@/components/MkContainer.vue'; -import { url as base } from '@/config.js'; +import { url as base } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import { infoImageUrl } from '@/instance.js'; const name = 'rss'; diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue index 16306ef5ba..b393ecd74b 100644 --- a/packages/frontend/src/widgets/WidgetRssTicker.vue +++ b/packages/frontend/src/widgets/WidgetRssTicker.vue @@ -34,8 +34,8 @@ import MarqueeText from '@/components/MkMarquee.vue'; import { GetFormResultType } from '@/scripts/form.js'; import MkContainer from '@/components/MkContainer.vue'; import { shuffle } from '@/scripts/shuffle.js'; -import { url as base } from '@/config.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { url as base } from '@@/js/config.js'; +import { useInterval } from '@@/js/use-interval.js'; const name = 'rssTicker'; diff --git a/packages/frontend/src/widgets/WidgetSlideshow.vue b/packages/frontend/src/widgets/WidgetSlideshow.vue index b8efd3bda9..3fea1d7053 100644 --- a/packages/frontend/src/widgets/WidgetSlideshow.vue +++ b/packages/frontend/src/widgets/WidgetSlideshow.vue @@ -23,7 +23,7 @@ import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, Wid import { GetFormResultType } from '@/scripts/form.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import { i18n } from '@/i18n.js'; const name = 'slideshow'; diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue index d02f9b8e22..a4685fd1fc 100644 --- a/packages/frontend/src/widgets/WidgetTimeline.vue +++ b/packages/frontend/src/widgets/WidgetTimeline.vue @@ -40,6 +40,7 @@ import MkContainer from '@/components/MkContainer.vue'; import MkTimeline from '@/components/MkTimeline.vue'; import { i18n } from '@/i18n.js'; import { availableBasicTimelines, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js'; +import type { MenuItem } from '@/types/menu.js'; const name = 'timeline'; @@ -109,11 +110,26 @@ const choose = async (ev) => { setSrc('list'); }, })); - os.popupMenu([...availableBasicTimelines().map(tl => ({ + + const menuItems: MenuItem[] = []; + + menuItems.push(...availableBasicTimelines().map(tl => ({ text: i18n.ts._timelines[tl], icon: basicTimelineIconClass(tl), action: () => { setSrc(tl); }, - })), antennaItems.length > 0 ? { type: 'divider' } : undefined, ...antennaItems, listItems.length > 0 ? { type: 'divider' } : undefined, ...listItems], ev.currentTarget ?? ev.target).then(() => { + }))); + + if (antennaItems.length > 0) { + menuItems.push({ type: 'divider' }); + menuItems.push(...antennaItems); + } + + if (listItems.length > 0) { + menuItems.push({ type: 'divider' }); + menuItems.push(...listItems); + } + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target).then(() => { menuOpened.value = false; }); }; diff --git a/packages/frontend/src/widgets/WidgetTrends.vue b/packages/frontend/src/widgets/WidgetTrends.vue index 4299181a27..a41db513e8 100644 --- a/packages/frontend/src/widgets/WidgetTrends.vue +++ b/packages/frontend/src/widgets/WidgetTrends.vue @@ -31,7 +31,7 @@ import { GetFormResultType } from '@/scripts/form.js'; import MkContainer from '@/components/MkContainer.vue'; import MkMiniChart from '@/components/MkMiniChart.vue'; import { misskeyApiGet } from '@/scripts/misskey-api.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import { i18n } from '@/i18n.js'; import { defaultStore } from '@/store.js'; diff --git a/packages/frontend/src/widgets/WidgetUserList.vue b/packages/frontend/src/widgets/WidgetUserList.vue index d9f4dc49ea..72391d622e 100644 --- a/packages/frontend/src/widgets/WidgetUserList.vue +++ b/packages/frontend/src/widgets/WidgetUserList.vue @@ -31,7 +31,7 @@ import { GetFormResultType } from '@/scripts/form.js'; import MkContainer from '@/components/MkContainer.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { useInterval } from '@/scripts/use-interval.js'; +import { useInterval } from '@@/js/use-interval.js'; import { i18n } from '@/i18n.js'; import MkButton from '@/components/MkButton.vue'; diff --git a/packages/frontend/test/emoji.test.ts b/packages/frontend/test/emoji.test.ts index 9a2989b373..cf686efd0d 100644 --- a/packages/frontend/test/emoji.test.ts +++ b/packages/frontend/test/emoji.test.ts @@ -6,7 +6,7 @@ import { describe, test, assert, afterEach } from 'vitest'; import { render, cleanup, type RenderResult } from '@testing-library/vue'; import { defaultStoreState } from './init.js'; -import { getEmojiName } from '@/scripts/emojilist.js'; +import { getEmojiName } from '@@/js/emojilist.js'; import { components } from '@/components/index.js'; import { directives } from '@/directives/index.js'; import MkEmoji from '@/components/global/MkEmoji.vue'; diff --git a/packages/frontend/test/i18n.test.ts b/packages/frontend/test/i18n.test.ts index e1cab1f15f..9d6cf855f3 100644 --- a/packages/frontend/test/i18n.test.ts +++ b/packages/frontend/test/i18n.test.ts @@ -4,9 +4,11 @@ */ import { describe, expect, it } from 'vitest'; -import { I18n } from '@/scripts/i18n.js'; +import { I18n } from '../../frontend-shared/js/i18n.js'; // @@で参照できなかったので import { ParameterizedString } from '../../../locales/index.js'; +// TODO: このテストはfrontend-sharedに移動する + describe('i18n', () => { it('t', () => { const i18n = new I18n({ diff --git a/packages/frontend/test/scroll.test.ts b/packages/frontend/test/scroll.test.ts index a0b56b7221..32a5a1c558 100644 --- a/packages/frontend/test/scroll.test.ts +++ b/packages/frontend/test/scroll.test.ts @@ -5,7 +5,7 @@ import { describe, test, assert, afterEach } from 'vitest'; import { Window } from 'happy-dom'; -import { onScrollBottom, onScrollTop } from '@/scripts/scroll.js'; +import { onScrollBottom, onScrollTop } from '@@/js/scroll.js'; describe('Scroll', () => { describe('onScrollTop', () => { diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json index fe4d202894..b88773b598 100644 --- a/packages/frontend/tsconfig.json +++ b/packages/frontend/tsconfig.json @@ -23,7 +23,8 @@ "useDefineForClassFields": true, "baseUrl": ".", "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], + "@@/*": ["../frontend-shared/*"] }, "typeRoots": [ "./@types", diff --git a/packages/frontend/vite.config.local-dev.ts b/packages/frontend/vite.config.local-dev.ts index 887ab7927e..922fb45995 100644 --- a/packages/frontend/vite.config.local-dev.ts +++ b/packages/frontend/vite.config.local-dev.ts @@ -15,6 +15,7 @@ const { port } = yaml.load(await readFile('../../.config/default.yml', 'utf-8')) const httpUrl = `http://localhost:${port}/`; const websocketUrl = `ws://localhost:${port}/`; +const embedUrl = `http://localhost:5174/`; // activitypubリクエストはProxyを通し、それ以外はViteの開発サーバーを返す function varyHandler(req: IncomingMessage) { @@ -50,6 +51,12 @@ const devConfig: UserConfig = { ws: true, }, '/favicon.ico': httpUrl, + '/robots.txt': httpUrl, + '/embed.js': httpUrl, + '/embed': { + target: embedUrl, + ws: true, + }, '/identicon': { target: httpUrl, rewrite(path) { diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 6decbc0ef7..e982df8ffd 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -65,6 +65,9 @@ export function getConfig(): UserConfig { server: { port: 5173, + headers: { // なんか効かない + 'X-Frame-Options': 'DENY', + }, }, plugins: [ @@ -87,6 +90,7 @@ export function getConfig(): UserConfig { extensions, alias: { '@/': __dirname + '/src/', + '@@/': __dirname + '/../frontend-shared/', '/client-assets/': __dirname + '/assets/', '/static-assets/': __dirname + '/../backend/assets/', '/fluent-emojis/': __dirname + '/../../fluent-emojis/dist/', @@ -151,7 +155,7 @@ export function getConfig(): UserConfig { }, }, cssCodeSplit: true, - outDir: __dirname + '/../../built/_vite_', + outDir: __dirname + '/../../built/_frontend_vite_', assetsDir: '.', emptyOutDir: false, sourcemap: process.env.NODE_ENV === 'development', diff --git a/packages/misskey-bubble-game/build.js b/packages/misskey-bubble-game/build.js index e626c97a59..a80b71646f 100644 --- a/packages/misskey-bubble-game/build.js +++ b/packages/misskey-bubble-game/build.js @@ -1,32 +1,32 @@ -import * as esbuild from "esbuild"; -import { build } from "esbuild"; -import { globSync } from "glob"; -import { execa } from "execa"; -import fs from "node:fs"; -import { fileURLToPath } from "node:url"; -import { dirname } from "node:path"; +import fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; +import * as esbuild from 'esbuild'; +import { build } from 'esbuild'; +import { globSync } from 'glob'; +import { execa } from 'execa'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8')); -const entryPoints = globSync("./src/**/**.{ts,tsx}"); +const entryPoints = globSync('./src/**/**.{ts,tsx}'); /** @type {import('esbuild').BuildOptions} */ const options = { entryPoints, minify: process.env.NODE_ENV === 'production', - outdir: "./built", - target: "es2022", - platform: "browser", - format: "esm", + outdir: './built', + target: 'es2022', + platform: 'browser', + format: 'esm', sourcemap: 'linked', }; // built配下をすべて削除する fs.rmSync('./built', { recursive: true, force: true }); -if (process.argv.map(arg => arg.toLowerCase()).includes("--watch")) { +if (process.argv.map(arg => arg.toLowerCase()).includes('--watch')) { await watchSrc(); } else { await buildSrc(); @@ -36,7 +36,7 @@ async function buildSrc() { console.log(`[${_package.name}] start building...`); await build(options) - .then(it => { + .then(() => { console.log(`[${_package.name}] build succeeded.`); }) .catch((err) => { @@ -65,7 +65,7 @@ function buildDts() { { stdout: process.stdout, stderr: process.stderr, - } + }, ); } @@ -86,7 +86,7 @@ async function watchSrc() { }, }]; - console.log(`[${_package.name}] start watching...`) + console.log(`[${_package.name}] start watching...`); const context = await esbuild.context({ ...options, plugins }); await context.watch(); diff --git a/packages/misskey-bubble-game/eslint.config.js b/packages/misskey-bubble-game/eslint.config.js index 86c21a22a3..bce383b1a6 100644 --- a/packages/misskey-bubble-game/eslint.config.js +++ b/packages/misskey-bubble-game/eslint.config.js @@ -1,6 +1,7 @@ import tsParser from '@typescript-eslint/parser'; import sharedConfig from '../shared/eslint.config.js'; +// eslint-disable-next-line import/no-default-export export default [ ...sharedConfig, { diff --git a/packages/misskey-bubble-game/src/game.ts b/packages/misskey-bubble-game/src/game.ts index 3bce4b1dcf..7f230e39cb 100644 --- a/packages/misskey-bubble-game/src/game.ts +++ b/packages/misskey-bubble-game/src/game.ts @@ -199,13 +199,12 @@ export class DropAndFusionGame extends EventEmitter<{ }; if (mono.shape === 'circle') { return Matter.Bodies.circle(x, y, mono.sizeX / 2, options); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition } else if (mono.shape === 'rectangle') { return Matter.Bodies.rectangle(x, y, mono.sizeX, mono.sizeY, options); - } else if (mono.shape === 'custom') { - return Matter.Bodies.fromVertices(x, y, mono.vertices!.map(i => i.map(j => ({ - x: (j.x / mono.verticesSize!) * mono.sizeX, - y: (j.y / mono.verticesSize!) * mono.sizeY, + } else if (mono.shape === 'custom' && mono.vertices != null && mono.verticesSize != null) { //eslint-disable-line @typescript-eslint/no-unnecessary-condition + return Matter.Bodies.fromVertices(x, y, mono.vertices.map(i => i.map(j => ({ + x: (j.x / mono.verticesSize!) * mono.sizeX, //eslint-disable-line @typescript-eslint/no-non-null-assertion + y: (j.y / mono.verticesSize!) * mono.sizeY, //eslint-disable-line @typescript-eslint/no-non-null-assertion }))), options); } else { throw new Error('unrecognized shape'); @@ -227,7 +226,12 @@ export class DropAndFusionGame extends EventEmitter<{ this.gameOverReadyBodyIds = this.gameOverReadyBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id); Matter.Composite.remove(this.engine.world, [bodyA, bodyB]); - const currentMono = this.monoDefinitions.find(y => y.id === bodyA.label)!; + const currentMono = this.monoDefinitions.find(y => y.id === bodyA.label); + + if (currentMono == null) { + throw new Error('Current Mono Not Found'); + } + const nextMono = this.monoDefinitions.find(x => x.level === currentMono.level + 1) ?? null; if (nextMono) { @@ -362,14 +366,18 @@ export class DropAndFusionGame extends EventEmitter<{ } public getActiveMonos() { - return this.engine.world.bodies.map(x => this.monoDefinitions.find((mono) => mono.id === x.label)!).filter(x => x !== undefined); + return this.engine.world.bodies + .map(x => this.monoDefinitions.find((mono) => mono.id === x.label)) + .filter(x => x !== undefined); } public drop(_x: number) { if (this.isGameOver) return; if (this.frame - this.latestDroppedAt < this.DROP_COOLTIME) return; - const head = this.stock.shift()!; + const head = this.stock.shift(); + if (!head) return; + this.stock.push({ id: this.rng().toString(), mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(this.rng() * this.monoDefinitions.filter(x => x.dropCandidate).length)], @@ -411,13 +419,15 @@ export class DropAndFusionGame extends EventEmitter<{ }); if (this.holding) { - const head = this.stock.shift()!; + const head = this.stock.shift(); + if (!head) return; this.stock.unshift(this.holding); this.holding = head; this.emit('changeHolding', this.holding); this.emit('changeStock', this.stock); } else { - const head = this.stock.shift()!; + const head = this.stock.shift(); + if (!head) return; this.holding = head; this.stock.push({ id: this.rng().toString(), diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index aaaa0493ca..9ffd0aa025 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -358,6 +358,9 @@ type AdminSystemWebhookShowRequest = operations['admin___system-webhook___show'] // @public (undocumented) type AdminSystemWebhookShowResponse = operations['admin___system-webhook___show']['responses']['200']['content']['application/json']; +// @public (undocumented) +type AdminSystemWebhookTestRequest = operations['admin___system-webhook___test']['requestBody']['content']['application/json']; + // @public (undocumented) type AdminSystemWebhookUpdateRequest = operations['admin___system-webhook___update']['requestBody']['content']['application/json']; @@ -551,7 +554,7 @@ type Channel = components['schemas']['Channel']; // Warning: (ae-forgotten-export) The symbol "AnyOf" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export abstract class ChannelConnection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends EventEmitter<Channel['events']> { +export abstract class ChannelConnection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends EventEmitter<Channel['events']> implements IChannelConnection<Channel> { constructor(stream: Stream, channel: string, name?: string); // (undocumented) channel: string; @@ -668,7 +671,7 @@ export type Channels = { }; hashtag: { params: { - q?: string; + q: string[][]; }; events: { note: (payload: Note) => void; @@ -1308,6 +1311,7 @@ declare namespace entities { AdminSystemWebhookShowResponse, AdminSystemWebhookUpdateRequest, AdminSystemWebhookUpdateResponse, + AdminSystemWebhookTestRequest, AnnouncementsRequest, AnnouncementsResponse, AnnouncementsShowRequest, @@ -1567,6 +1571,7 @@ declare namespace entities { IWebhooksShowResponse, IWebhooksUpdateRequest, IWebhooksDeleteRequest, + IWebhooksTestRequest, InviteCreateResponse, InviteDeleteRequest, InviteListRequest, @@ -2119,6 +2124,24 @@ type IAuthorizedAppsResponse = operations['i___authorized-apps']['responses']['2 // @public (undocumented) type IChangePasswordRequest = operations['i___change-password']['requestBody']['content']['application/json']; +// @public (undocumented) +export interface IChannelConnection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends EventEmitter<Channel['events']> { + // (undocumented) + channel: string; + // (undocumented) + dispose(): void; + // (undocumented) + id: string; + // (undocumented) + inCount: number; + // (undocumented) + name?: string; + // (undocumented) + outCount: number; + // (undocumented) + send<T extends keyof Channel['receives']>(type: T, body: Channel['receives'][T]): void; +} + // @public (undocumented) type IClaimAchievementRequest = operations['i___claim-achievement']['requestBody']['content']['application/json']; @@ -2281,6 +2304,40 @@ type ISigninHistoryResponse = operations['i___signin-history']['responses']['200 // @public (undocumented) function isPureRenote(note: Note): note is PureRenote; +// @public (undocumented) +export interface IStream extends EventEmitter<StreamEvents> { + // (undocumented) + close(): void; + // Warning: (ae-forgotten-export) The symbol "NonSharedConnection" needs to be exported by the entry point index.d.ts + // + // (undocumented) + disconnectToChannel(connection: NonSharedConnection): void; + // (undocumented) + heartbeat(): void; + // (undocumented) + ping(): void; + // Warning: (ae-forgotten-export) The symbol "SharedConnection" needs to be exported by the entry point index.d.ts + // + // (undocumented) + removeSharedConnection(connection: SharedConnection): void; + // Warning: (ae-forgotten-export) The symbol "Pool" needs to be exported by the entry point index.d.ts + // + // (undocumented) + removeSharedConnectionPool(pool: Pool): void; + // (undocumented) + send(typeOrPayload: string): void; + // (undocumented) + send(typeOrPayload: string, payload: unknown): void; + // (undocumented) + send(typeOrPayload: Record<string, unknown> | unknown[]): void; + // (undocumented) + send(typeOrPayload: string | Record<string, unknown> | unknown[], payload?: unknown): void; + // (undocumented) + state: 'initializing' | 'reconnecting' | 'connected'; + // (undocumented) + useChannel<C extends keyof Channels>(channel: C, params?: Channels[C]['params'], name?: string): IChannelConnection<Channels[C]>; +} + // @public (undocumented) type IUnpinRequest = operations['i___unpin']['requestBody']['content']['application/json']; @@ -2317,6 +2374,9 @@ type IWebhooksShowRequest = operations['i___webhooks___show']['requestBody']['co // @public (undocumented) type IWebhooksShowResponse = operations['i___webhooks___show']['responses']['200']['content']['application/json']; +// @public (undocumented) +type IWebhooksTestRequest = operations['i___webhooks___test']['requestBody']['content']['application/json']; + // @public (undocumented) type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json']; @@ -2707,6 +2767,9 @@ type NotificationsCreateRequest = operations['notifications___create']['requestB // @public (undocumented) export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "achievementEarned"]; +// @public (undocumented) +export function nyaize(text: string): string; + // @public (undocumented) type Page = components['schemas']['Page']; @@ -2997,10 +3060,8 @@ type SignupResponse = MeDetailed & { // @public (undocumented) type StatsResponse = operations['stats']['responses']['200']['content']['application/json']; -// Warning: (ae-forgotten-export) The symbol "StreamEvents" needs to be exported by the entry point index.d.ts -// // @public (undocumented) -export class Stream extends EventEmitter<StreamEvents> { +export class Stream extends EventEmitter<StreamEvents> implements IStream { constructor(origin: string, user: { token: string; } | null, options?: { @@ -3008,20 +3069,14 @@ export class Stream extends EventEmitter<StreamEvents> { }); // (undocumented) close(): void; - // Warning: (ae-forgotten-export) The symbol "NonSharedConnection" needs to be exported by the entry point index.d.ts - // // (undocumented) disconnectToChannel(connection: NonSharedConnection): void; // (undocumented) heartbeat(): void; // (undocumented) ping(): void; - // Warning: (ae-forgotten-export) The symbol "SharedConnection" needs to be exported by the entry point index.d.ts - // // (undocumented) removeSharedConnection(connection: SharedConnection): void; - // Warning: (ae-forgotten-export) The symbol "Pool" needs to be exported by the entry point index.d.ts - // // (undocumented) removeSharedConnectionPool(pool: Pool): void; // (undocumented) @@ -3036,6 +3091,14 @@ export class Stream extends EventEmitter<StreamEvents> { useChannel<C extends keyof Channels>(channel: C, params?: Channels[C]['params'], name?: string): ChannelConnection<Channels[C]>; } +// Warning: (ae-forgotten-export) The symbol "BroadcastEvents" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type StreamEvents = { + _connected_: void; + _disconnected_: void; +} & BroadcastEvents; + // Warning: (ae-forgotten-export) The symbol "SwitchCase" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "IsCaseMatched" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "GetCaseResult" needs to be exported by the entry point index.d.ts diff --git a/packages/misskey-js/generator/package.json b/packages/misskey-js/generator/package.json index 4a02bcd8ff..212c92fba5 100644 --- a/packages/misskey-js/generator/package.json +++ b/packages/misskey-js/generator/package.json @@ -7,15 +7,15 @@ "generate": "tsx src/generator.ts && eslint ./built/**/*.ts --fix" }, "devDependencies": { - "@readme/openapi-parser": "2.5.0", + "@readme/openapi-parser": "2.6.0", "@types/node": "20.9.1", - "@typescript-eslint/eslint-plugin": "6.11.0", - "@typescript-eslint/parser": "6.11.0", + "@typescript-eslint/eslint-plugin": "7.17.0", + "@typescript-eslint/parser": "7.17.0", "openapi-types": "12.1.3", "openapi-typescript": "6.7.3", - "ts-case-convert": "2.0.2", + "ts-case-convert": "2.0.7", "tsx": "4.4.0", - "typescript": "5.3.3" + "typescript": "5.6.2" }, "files": [ "built" diff --git a/packages/misskey-js/generator/src/generator.ts b/packages/misskey-js/generator/src/generator.ts index 4ae00a4522..88f2ae9ee9 100644 --- a/packages/misskey-js/generator/src/generator.ts +++ b/packages/misskey-js/generator/src/generator.ts @@ -96,15 +96,11 @@ async function generateEndpoints( endpoint.request = req; const reqType = new EndpointReqMediaType(path, req); - endpointReqMediaTypesSet.add(reqType.getMediaType()); - endpointReqMediaTypes.push(reqType); - } else { - endpointReqMediaTypesSet.add('application/json'); - endpointReqMediaTypes.push(new EndpointReqMediaType(path, undefined, 'application/json')); + if (reqType.getMediaType() !== 'application/json') { + endpointReqMediaTypesSet.add(reqType.getMediaType()); + endpointReqMediaTypes.push(reqType); + } } - } else { - endpointReqMediaTypesSet.add('application/json'); - endpointReqMediaTypes.push(new EndpointReqMediaType(path, undefined, 'application/json')); } if (operation.responses && isResponseObject(operation.responses['200']) && operation.responses['200'].content) { @@ -158,16 +154,19 @@ async function generateEndpoints( endpointOutputLine.push(''); function generateEndpointReqMediaTypesType() { - return `Record<keyof Endpoints, ${[...endpointReqMediaTypesSet].map((t) => `'${t}'`).join(' | ')}>`; + return `{ [K in keyof Endpoints]?: ${[...endpointReqMediaTypesSet].map((t) => `'${t}'`).join(' | ')}; }`; } - endpointOutputLine.push(`export const endpointReqTypes: ${generateEndpointReqMediaTypesType()} = {`); + endpointOutputLine.push(`/** + * NOTE: The content-type for all endpoints not listed here is application/json. + */`); + endpointOutputLine.push('export const endpointReqTypes = {'); endpointOutputLine.push( ...endpointReqMediaTypes.map(it => '\t' + it.toLine()), ); - endpointOutputLine.push('};'); + endpointOutputLine.push(`} as const satisfies ${generateEndpointReqMediaTypesType()};`); endpointOutputLine.push(''); await writeFile(endpointOutputPath, endpointOutputLine.join('\n')); diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 39e687d4af..0f797e8259 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2024.8.0", + "version": "2024.9.0-alpha.8", "description": "Misskey SDK for JavaScript", "license": "MIT", "main": "./built/index.js", @@ -35,9 +35,9 @@ "directory": "packages/misskey-js" }, "devDependencies": { - "@microsoft/api-extractor": "7.47.4", + "@microsoft/api-extractor": "7.47.9", "@swc/jest": "0.2.36", - "@types/jest": "29.5.12", + "@types/jest": "29.5.13", "@types/node": "20.14.12", "@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/parser": "7.17.0", @@ -46,11 +46,11 @@ "jest-websocket-mock": "2.5.0", "mock-socket": "9.3.1", "ncp": "2.0.0", - "nodemon": "3.1.4", - "execa": "9.3.0", - "tsd": "0.31.1", - "typescript": "5.5.4", - "esbuild": "0.23.0", + "nodemon": "3.1.7", + "execa": "9.4.0", + "tsd": "0.31.2", + "typescript": "5.6.2", + "esbuild": "0.23.1", "glob": "11.0.0" }, "files": [ diff --git a/packages/misskey-js/src/api.ts b/packages/misskey-js/src/api.ts index ea1df57f3d..ed1282957f 100644 --- a/packages/misskey-js/src/api.ts +++ b/packages/misskey-js/src/api.ts @@ -56,6 +56,10 @@ export class APIClient { return obj !== null && typeof obj === 'object' && !Array.isArray(obj); } + private assertSpecialEpReqType(ep: keyof Endpoints): ep is keyof typeof endpointReqTypes { + return ep in endpointReqTypes; + } + public request<E extends keyof Endpoints, P extends Endpoints[E]['req']>( endpoint: E, params: P = {} as P, @@ -63,9 +67,12 @@ export class APIClient { ): Promise<SwitchCaseResponseType<E, P>> { return new Promise((resolve, reject) => { let mediaType = 'application/json'; - if (endpoint in endpointReqTypes) { + // (autogenがバグったときのため、念の為nullチェックも行う) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (this.assertSpecialEpReqType(endpoint) && endpointReqTypes[endpoint] != null) { mediaType = endpointReqTypes[endpoint]; } + let payload: FormData | string = '{}'; if (mediaType === 'application/json') { @@ -100,7 +107,7 @@ export class APIClient { method: 'POST', body: payload, headers: { - 'Content-Type': endpointReqTypes[endpoint], + 'Content-Type': mediaType, }, credentials: 'omit', cache: 'no-cache', diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index e799d4a0c5..1d96196d1c 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -960,6 +960,18 @@ declare module '../api.js' { credential?: string | null, ): Promise<SwitchCaseResponseType<E, P>>; + /** + * No description provided. + * + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook* + */ + request<E extends 'admin/system-webhook/test', P extends Endpoints[E]['req']>( + endpoint: E, + params: P, + credential?: string | null, + ): Promise<SwitchCaseResponseType<E, P>>; + /** * No description provided. * @@ -2819,6 +2831,18 @@ declare module '../api.js' { credential?: string | null, ): Promise<SwitchCaseResponseType<E, P>>; + /** + * No description provided. + * + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *read:account* + */ + request<E extends 'i/webhooks/test', P extends Endpoints[E]['req']>( + endpoint: E, + params: P, + credential?: string | null, + ): Promise<SwitchCaseResponseType<E, P>>; + /** * No description provided. * diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index be41951e4d..42c74599a5 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -117,6 +117,7 @@ import type { AdminSystemWebhookShowResponse, AdminSystemWebhookUpdateRequest, AdminSystemWebhookUpdateResponse, + AdminSystemWebhookTestRequest, AnnouncementsRequest, AnnouncementsResponse, AnnouncementsShowRequest, @@ -376,6 +377,7 @@ import type { IWebhooksShowResponse, IWebhooksUpdateRequest, IWebhooksDeleteRequest, + IWebhooksTestRequest, InviteCreateResponse, InviteDeleteRequest, InviteListRequest, @@ -660,6 +662,7 @@ export type Endpoints = { 'admin/system-webhook/list': { req: AdminSystemWebhookListRequest; res: AdminSystemWebhookListResponse }; 'admin/system-webhook/show': { req: AdminSystemWebhookShowRequest; res: AdminSystemWebhookShowResponse }; 'admin/system-webhook/update': { req: AdminSystemWebhookUpdateRequest; res: AdminSystemWebhookUpdateResponse }; + 'admin/system-webhook/test': { req: AdminSystemWebhookTestRequest; res: EmptyResponse }; 'announcements': { req: AnnouncementsRequest; res: AnnouncementsResponse }; 'announcements/show': { req: AnnouncementsShowRequest; res: AnnouncementsShowResponse }; 'antennas/create': { req: AntennasCreateRequest; res: AntennasCreateResponse }; @@ -826,6 +829,7 @@ export type Endpoints = { 'i/webhooks/show': { req: IWebhooksShowRequest; res: IWebhooksShowResponse }; 'i/webhooks/update': { req: IWebhooksUpdateRequest; res: EmptyResponse }; 'i/webhooks/delete': { req: IWebhooksDeleteRequest; res: EmptyResponse }; + 'i/webhooks/test': { req: IWebhooksTestRequest; res: EmptyResponse }; 'invite/create': { req: EmptyRequest; res: InviteCreateResponse }; 'invite/delete': { req: InviteDeleteRequest; res: EmptyResponse }; 'invite/list': { req: InviteListRequest; res: InviteListResponse }; @@ -955,384 +959,9 @@ export type Endpoints = { 'reversi/verify': { req: ReversiVerifyRequest; res: ReversiVerifyResponse }; } -export const endpointReqTypes: Record<keyof Endpoints, 'application/json' | 'multipart/form-data'> = { - 'admin/meta': 'application/json', - 'admin/abuse-user-reports': 'application/json', - 'admin/abuse-report/notification-recipient/list': 'application/json', - 'admin/abuse-report/notification-recipient/show': 'application/json', - 'admin/abuse-report/notification-recipient/create': 'application/json', - 'admin/abuse-report/notification-recipient/update': 'application/json', - 'admin/abuse-report/notification-recipient/delete': 'application/json', - 'admin/accounts/create': 'application/json', - 'admin/accounts/delete': 'application/json', - 'admin/accounts/find-by-email': 'application/json', - 'admin/ad/create': 'application/json', - 'admin/ad/delete': 'application/json', - 'admin/ad/list': 'application/json', - 'admin/ad/update': 'application/json', - 'admin/announcements/create': 'application/json', - 'admin/announcements/delete': 'application/json', - 'admin/announcements/list': 'application/json', - 'admin/announcements/update': 'application/json', - 'admin/avatar-decorations/create': 'application/json', - 'admin/avatar-decorations/delete': 'application/json', - 'admin/avatar-decorations/list': 'application/json', - 'admin/avatar-decorations/update': 'application/json', - 'admin/delete-all-files-of-a-user': 'application/json', - 'admin/unset-user-avatar': 'application/json', - 'admin/unset-user-banner': 'application/json', - 'admin/drive/clean-remote-files': 'application/json', - 'admin/drive/cleanup': 'application/json', - 'admin/drive/files': 'application/json', - 'admin/drive/show-file': 'application/json', - 'admin/emoji/add-aliases-bulk': 'application/json', - 'admin/emoji/add': 'application/json', - 'admin/emoji/copy': 'application/json', - 'admin/emoji/delete-bulk': 'application/json', - 'admin/emoji/delete': 'application/json', - 'admin/emoji/import-zip': 'application/json', - 'admin/emoji/list-remote': 'application/json', - 'admin/emoji/list': 'application/json', - 'admin/emoji/remove-aliases-bulk': 'application/json', - 'admin/emoji/set-aliases-bulk': 'application/json', - 'admin/emoji/set-category-bulk': 'application/json', - 'admin/emoji/set-license-bulk': 'application/json', - 'admin/emoji/update': 'application/json', - 'admin/federation/delete-all-files': 'application/json', - 'admin/federation/refresh-remote-instance-metadata': 'application/json', - 'admin/federation/remove-all-following': 'application/json', - 'admin/federation/update-instance': 'application/json', - 'admin/get-index-stats': 'application/json', - 'admin/get-table-stats': 'application/json', - 'admin/get-user-ips': 'application/json', - 'admin/invite/create': 'application/json', - 'admin/invite/list': 'application/json', - 'admin/promo/create': 'application/json', - 'admin/queue/clear': 'application/json', - 'admin/queue/deliver-delayed': 'application/json', - 'admin/queue/inbox-delayed': 'application/json', - 'admin/queue/promote': 'application/json', - 'admin/queue/stats': 'application/json', - 'admin/relays/add': 'application/json', - 'admin/relays/list': 'application/json', - 'admin/relays/remove': 'application/json', - 'admin/reset-password': 'application/json', - 'admin/resolve-abuse-user-report': 'application/json', - 'admin/send-email': 'application/json', - 'admin/server-info': 'application/json', - 'admin/show-moderation-logs': 'application/json', - 'admin/show-user': 'application/json', - 'admin/show-users': 'application/json', - 'admin/suspend-user': 'application/json', - 'admin/unsuspend-user': 'application/json', - 'admin/update-meta': 'application/json', - 'admin/delete-account': 'application/json', - 'admin/update-user-note': 'application/json', - 'admin/roles/create': 'application/json', - 'admin/roles/delete': 'application/json', - 'admin/roles/list': 'application/json', - 'admin/roles/show': 'application/json', - 'admin/roles/update': 'application/json', - 'admin/roles/assign': 'application/json', - 'admin/roles/unassign': 'application/json', - 'admin/roles/update-default-policies': 'application/json', - 'admin/roles/users': 'application/json', - 'admin/system-webhook/create': 'application/json', - 'admin/system-webhook/delete': 'application/json', - 'admin/system-webhook/list': 'application/json', - 'admin/system-webhook/show': 'application/json', - 'admin/system-webhook/update': 'application/json', - 'announcements': 'application/json', - 'announcements/show': 'application/json', - 'antennas/create': 'application/json', - 'antennas/delete': 'application/json', - 'antennas/list': 'application/json', - 'antennas/notes': 'application/json', - 'antennas/show': 'application/json', - 'antennas/update': 'application/json', - 'ap/get': 'application/json', - 'ap/show': 'application/json', - 'app/create': 'application/json', - 'app/show': 'application/json', - 'auth/accept': 'application/json', - 'auth/session/generate': 'application/json', - 'auth/session/show': 'application/json', - 'auth/session/userkey': 'application/json', - 'blocking/create': 'application/json', - 'blocking/delete': 'application/json', - 'blocking/list': 'application/json', - 'channels/create': 'application/json', - 'channels/featured': 'application/json', - 'channels/follow': 'application/json', - 'channels/followed': 'application/json', - 'channels/owned': 'application/json', - 'channels/show': 'application/json', - 'channels/timeline': 'application/json', - 'channels/unfollow': 'application/json', - 'channels/update': 'application/json', - 'channels/favorite': 'application/json', - 'channels/unfavorite': 'application/json', - 'channels/my-favorites': 'application/json', - 'channels/search': 'application/json', - 'charts/active-users': 'application/json', - 'charts/ap-request': 'application/json', - 'charts/drive': 'application/json', - 'charts/federation': 'application/json', - 'charts/instance': 'application/json', - 'charts/notes': 'application/json', - 'charts/user/drive': 'application/json', - 'charts/user/following': 'application/json', - 'charts/user/notes': 'application/json', - 'charts/user/pv': 'application/json', - 'charts/user/reactions': 'application/json', - 'charts/users': 'application/json', - 'clips/add-note': 'application/json', - 'clips/remove-note': 'application/json', - 'clips/create': 'application/json', - 'clips/delete': 'application/json', - 'clips/list': 'application/json', - 'clips/notes': 'application/json', - 'clips/show': 'application/json', - 'clips/update': 'application/json', - 'clips/favorite': 'application/json', - 'clips/unfavorite': 'application/json', - 'clips/my-favorites': 'application/json', - 'drive': 'application/json', - 'drive/files': 'application/json', - 'drive/files/attached-notes': 'application/json', - 'drive/files/check-existence': 'application/json', +/** + * NOTE: The content-type for all endpoints not listed here is application/json. + */ +export const endpointReqTypes = { 'drive/files/create': 'multipart/form-data', - 'drive/files/delete': 'application/json', - 'drive/files/find-by-hash': 'application/json', - 'drive/files/find': 'application/json', - 'drive/files/show': 'application/json', - 'drive/files/update': 'application/json', - 'drive/files/upload-from-url': 'application/json', - 'drive/folders': 'application/json', - 'drive/folders/create': 'application/json', - 'drive/folders/delete': 'application/json', - 'drive/folders/find': 'application/json', - 'drive/folders/show': 'application/json', - 'drive/folders/update': 'application/json', - 'drive/stream': 'application/json', - 'email-address/available': 'application/json', - 'endpoint': 'application/json', - 'endpoints': 'application/json', - 'export-custom-emojis': 'application/json', - 'federation/followers': 'application/json', - 'federation/following': 'application/json', - 'federation/instances': 'application/json', - 'federation/show-instance': 'application/json', - 'federation/update-remote-user': 'application/json', - 'federation/users': 'application/json', - 'federation/stats': 'application/json', - 'following/create': 'application/json', - 'following/delete': 'application/json', - 'following/update': 'application/json', - 'following/update-all': 'application/json', - 'following/invalidate': 'application/json', - 'following/requests/accept': 'application/json', - 'following/requests/cancel': 'application/json', - 'following/requests/list': 'application/json', - 'following/requests/reject': 'application/json', - 'gallery/featured': 'application/json', - 'gallery/popular': 'application/json', - 'gallery/posts': 'application/json', - 'gallery/posts/create': 'application/json', - 'gallery/posts/delete': 'application/json', - 'gallery/posts/like': 'application/json', - 'gallery/posts/show': 'application/json', - 'gallery/posts/unlike': 'application/json', - 'gallery/posts/update': 'application/json', - 'get-online-users-count': 'application/json', - 'get-avatar-decorations': 'application/json', - 'hashtags/list': 'application/json', - 'hashtags/search': 'application/json', - 'hashtags/show': 'application/json', - 'hashtags/trend': 'application/json', - 'hashtags/users': 'application/json', - 'i': 'application/json', - 'i/2fa/done': 'application/json', - 'i/2fa/key-done': 'application/json', - 'i/2fa/password-less': 'application/json', - 'i/2fa/register-key': 'application/json', - 'i/2fa/register': 'application/json', - 'i/2fa/update-key': 'application/json', - 'i/2fa/remove-key': 'application/json', - 'i/2fa/unregister': 'application/json', - 'i/apps': 'application/json', - 'i/authorized-apps': 'application/json', - 'i/claim-achievement': 'application/json', - 'i/change-password': 'application/json', - 'i/delete-account': 'application/json', - 'i/export-blocking': 'application/json', - 'i/export-following': 'application/json', - 'i/export-mute': 'application/json', - 'i/export-notes': 'application/json', - 'i/export-clips': 'application/json', - 'i/export-favorites': 'application/json', - 'i/export-user-lists': 'application/json', - 'i/export-antennas': 'application/json', - 'i/favorites': 'application/json', - 'i/gallery/likes': 'application/json', - 'i/gallery/posts': 'application/json', - 'i/import-blocking': 'application/json', - 'i/import-following': 'application/json', - 'i/import-muting': 'application/json', - 'i/import-user-lists': 'application/json', - 'i/import-antennas': 'application/json', - 'i/notifications': 'application/json', - 'i/notifications-grouped': 'application/json', - 'i/page-likes': 'application/json', - 'i/pages': 'application/json', - 'i/pin': 'application/json', - 'i/read-all-unread-notes': 'application/json', - 'i/read-announcement': 'application/json', - 'i/regenerate-token': 'application/json', - 'i/registry/get-all': 'application/json', - 'i/registry/get-detail': 'application/json', - 'i/registry/get': 'application/json', - 'i/registry/keys-with-type': 'application/json', - 'i/registry/keys': 'application/json', - 'i/registry/remove': 'application/json', - 'i/registry/scopes-with-domain': 'application/json', - 'i/registry/set': 'application/json', - 'i/revoke-token': 'application/json', - 'i/signin-history': 'application/json', - 'i/unpin': 'application/json', - 'i/update-email': 'application/json', - 'i/update': 'application/json', - 'i/move': 'application/json', - 'i/webhooks/create': 'application/json', - 'i/webhooks/list': 'application/json', - 'i/webhooks/show': 'application/json', - 'i/webhooks/update': 'application/json', - 'i/webhooks/delete': 'application/json', - 'invite/create': 'application/json', - 'invite/delete': 'application/json', - 'invite/list': 'application/json', - 'invite/limit': 'application/json', - 'meta': 'application/json', - 'emojis': 'application/json', - 'emoji': 'application/json', - 'miauth/gen-token': 'application/json', - 'mute/create': 'application/json', - 'mute/delete': 'application/json', - 'mute/list': 'application/json', - 'renote-mute/create': 'application/json', - 'renote-mute/delete': 'application/json', - 'renote-mute/list': 'application/json', - 'my/apps': 'application/json', - 'notes': 'application/json', - 'notes/children': 'application/json', - 'notes/clips': 'application/json', - 'notes/conversation': 'application/json', - 'notes/create': 'application/json', - 'notes/delete': 'application/json', - 'notes/favorites/create': 'application/json', - 'notes/favorites/delete': 'application/json', - 'notes/featured': 'application/json', - 'notes/global-timeline': 'application/json', - 'notes/hybrid-timeline': 'application/json', - 'notes/local-timeline': 'application/json', - 'notes/mentions': 'application/json', - 'notes/polls/recommendation': 'application/json', - 'notes/polls/vote': 'application/json', - 'notes/reactions': 'application/json', - 'notes/reactions/create': 'application/json', - 'notes/reactions/delete': 'application/json', - 'notes/renotes': 'application/json', - 'notes/replies': 'application/json', - 'notes/search-by-tag': 'application/json', - 'notes/search': 'application/json', - 'notes/show': 'application/json', - 'notes/state': 'application/json', - 'notes/thread-muting/create': 'application/json', - 'notes/thread-muting/delete': 'application/json', - 'notes/timeline': 'application/json', - 'notes/translate': 'application/json', - 'notes/unrenote': 'application/json', - 'notes/user-list-timeline': 'application/json', - 'notifications/create': 'application/json', - 'notifications/flush': 'application/json', - 'notifications/mark-all-as-read': 'application/json', - 'notifications/test-notification': 'application/json', - 'page-push': 'application/json', - 'pages/create': 'application/json', - 'pages/delete': 'application/json', - 'pages/featured': 'application/json', - 'pages/like': 'application/json', - 'pages/show': 'application/json', - 'pages/unlike': 'application/json', - 'pages/update': 'application/json', - 'flash/create': 'application/json', - 'flash/delete': 'application/json', - 'flash/featured': 'application/json', - 'flash/like': 'application/json', - 'flash/show': 'application/json', - 'flash/unlike': 'application/json', - 'flash/update': 'application/json', - 'flash/my': 'application/json', - 'flash/my-likes': 'application/json', - 'ping': 'application/json', - 'pinned-users': 'application/json', - 'promo/read': 'application/json', - 'roles/list': 'application/json', - 'roles/show': 'application/json', - 'roles/users': 'application/json', - 'roles/notes': 'application/json', - 'request-reset-password': 'application/json', - 'reset-db': 'application/json', - 'reset-password': 'application/json', - 'server-info': 'application/json', - 'stats': 'application/json', - 'sw/show-registration': 'application/json', - 'sw/update-registration': 'application/json', - 'sw/register': 'application/json', - 'sw/unregister': 'application/json', - 'test': 'application/json', - 'username/available': 'application/json', - 'users': 'application/json', - 'users/clips': 'application/json', - 'users/followers': 'application/json', - 'users/following': 'application/json', - 'users/gallery/posts': 'application/json', - 'users/get-frequently-replied-users': 'application/json', - 'users/featured-notes': 'application/json', - 'users/lists/create': 'application/json', - 'users/lists/delete': 'application/json', - 'users/lists/list': 'application/json', - 'users/lists/pull': 'application/json', - 'users/lists/push': 'application/json', - 'users/lists/show': 'application/json', - 'users/lists/favorite': 'application/json', - 'users/lists/unfavorite': 'application/json', - 'users/lists/update': 'application/json', - 'users/lists/create-from-public': 'application/json', - 'users/lists/update-membership': 'application/json', - 'users/lists/get-memberships': 'application/json', - 'users/notes': 'application/json', - 'users/pages': 'application/json', - 'users/flashs': 'application/json', - 'users/reactions': 'application/json', - 'users/recommendation': 'application/json', - 'users/relation': 'application/json', - 'users/report-abuse': 'application/json', - 'users/search-by-username-and-host': 'application/json', - 'users/search': 'application/json', - 'users/show': 'application/json', - 'users/achievements': 'application/json', - 'users/update-memo': 'application/json', - 'fetch-rss': 'application/json', - 'fetch-external-resources': 'application/json', - 'retention': 'application/json', - 'bubble-game/register': 'application/json', - 'bubble-game/ranking': 'application/json', - 'reversi/cancel-match': 'application/json', - 'reversi/games': 'application/json', - 'reversi/match': 'application/json', - 'reversi/invitations': 'application/json', - 'reversi/show-game': 'application/json', - 'reversi/surrender': 'application/json', - 'reversi/verify': 'application/json', -}; +} as const satisfies { [K in keyof Endpoints]?: 'multipart/form-data'; }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 357b5e9eaf..87ed653d44 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -120,6 +120,7 @@ export type AdminSystemWebhookShowRequest = operations['admin___system-webhook__ export type AdminSystemWebhookShowResponse = operations['admin___system-webhook___show']['responses']['200']['content']['application/json']; export type AdminSystemWebhookUpdateRequest = operations['admin___system-webhook___update']['requestBody']['content']['application/json']; export type AdminSystemWebhookUpdateResponse = operations['admin___system-webhook___update']['responses']['200']['content']['application/json']; +export type AdminSystemWebhookTestRequest = operations['admin___system-webhook___test']['requestBody']['content']['application/json']; export type AnnouncementsRequest = operations['announcements']['requestBody']['content']['application/json']; export type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json']; export type AnnouncementsShowRequest = operations['announcements___show']['requestBody']['content']['application/json']; @@ -379,6 +380,7 @@ export type IWebhooksShowRequest = operations['i___webhooks___show']['requestBod export type IWebhooksShowResponse = operations['i___webhooks___show']['responses']['200']['content']['application/json']; export type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json']; export type IWebhooksDeleteRequest = operations['i___webhooks___delete']['requestBody']['content']['application/json']; +export type IWebhooksTestRequest = operations['i___webhooks___test']['requestBody']['content']['application/json']; export type InviteCreateResponse = operations['invite___create']['responses']['200']['content']['application/json']; export type InviteDeleteRequest = operations['invite___delete']['requestBody']['content']['application/json']; export type InviteListRequest = operations['invite___list']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index b99a5373bb..5d5bc52956 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -797,6 +797,16 @@ export type paths = { */ post: operations['admin___system-webhook___update']; }; + '/admin/system-webhook/test': { + /** + * admin/system-webhook/test + * @description No description provided. + * + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook* + */ + post: operations['admin___system-webhook___test']; + }; '/announcements': { /** * announcements @@ -2436,6 +2446,16 @@ export type paths = { */ post: operations['i___webhooks___delete']; }; + '/i/webhooks/test': { + /** + * i/webhooks/test + * @description No description provided. + * + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *read:account* + */ + post: operations['i___webhooks___test']; + }; '/invite/create': { /** * invite/create @@ -4802,6 +4822,11 @@ export type components = { userEachUserListsLimit: number; rateLimitFactor: number; avatarDecorationLimit: number; + canImportAntennas: boolean; + canImportBlocking: boolean; + canImportFollowing: boolean; + canImportMuting: boolean; + canImportUserLists: boolean; }; ReversiGameLite: { /** Format: id */ @@ -5105,6 +5130,7 @@ export type operations = { perRemoteUserUserTimelineCacheMax: number; perUserHomeTimelineCacheMax: number; perUserListTimelineCacheMax: number; + enableReactionsBuffering: boolean; notesPerOneAd: number; backgroundImageUrl: string | null; deeplAuthKey: string | null; @@ -9375,6 +9401,7 @@ export type operations = { perRemoteUserUserTimelineCacheMax?: number; perUserHomeTimelineCacheMax?: number; perUserListTimelineCacheMax?: number; + enableReactionsBuffering?: boolean; notesPerOneAd?: number; silencedHosts?: string[] | null; mediaSilencedHosts?: string[] | null; @@ -10327,6 +10354,71 @@ export type operations = { }; }; }; + /** + * admin/system-webhook/test + * @description No description provided. + * + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook* + */ + 'admin___system-webhook___test': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + webhookId: string; + /** @enum {string} */ + type: 'abuseReport' | 'abuseReportResolved' | 'userCreated'; + override?: { + url?: string; + secret?: string; + }; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description To many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; /** * announcements * @description No description provided. @@ -20146,6 +20238,71 @@ export type operations = { }; }; }; + /** + * i/webhooks/test + * @description No description provided. + * + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *read:account* + */ + i___webhooks___test: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + webhookId: string; + /** @enum {string} */ + type: 'mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction'; + override?: { + url?: string; + secret?: string; + }; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description To many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; /** * invite/create * @description No description provided. diff --git a/packages/misskey-js/src/index.ts b/packages/misskey-js/src/index.ts index ace9738e6a..e4c9364aa1 100644 --- a/packages/misskey-js/src/index.ts +++ b/packages/misskey-js/src/index.ts @@ -1,15 +1,6 @@ -import { type Endpoints } from './api.types.js'; import Stream, { Connection } from './streaming.js'; -import { type Channels } from './streaming.types.js'; -import { type Acct } from './acct.js'; import * as consts from './consts.js'; -export type { - Endpoints, - Channels, - Acct, -}; - export { Stream, Connection as ChannelConnection, @@ -31,4 +22,21 @@ import * as api from './api.js'; import * as entities from './entities.js'; import * as acct from './acct.js'; import * as note from './note.js'; -export { api, entities, acct, note }; +import { nyaize } from './nyaize.js'; +export { api, entities, acct, note, nyaize }; + +//#region standalone types +import type { Endpoints } from './api.types.js'; +import type { StreamEvents, IStream, IChannelConnection } from './streaming.js'; +import type { Channels } from './streaming.types.js'; +import type { Acct } from './acct.js'; + +export type { + Endpoints, + Channels, + Acct, + StreamEvents, + IStream, + IChannelConnection, +}; +//#endregion diff --git a/packages/frontend/src/scripts/nyaize.ts b/packages/misskey-js/src/nyaize.ts similarity index 82% rename from packages/frontend/src/scripts/nyaize.ts rename to packages/misskey-js/src/nyaize.ts index abc8ada461..729fea8fc3 100644 --- a/packages/frontend/src/scripts/nyaize.ts +++ b/packages/misskey-js/src/nyaize.ts @@ -19,9 +19,9 @@ export function nyaize(text: string): string { .replace(enRegex2, x => x === 'ING' ? 'YAN' : 'yan') .replace(enRegex3, x => x === 'ONE' ? 'NYAN' : 'nyan') // ko-KR - .replace(koRegex1, match => String.fromCharCode( - match.charCodeAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0), - )) + .replace(koRegex1, match => !isNaN(match.charCodeAt(0)) ? String.fromCharCode( + match.charCodeAt(0) + '냐'.charCodeAt(0) - '나'.charCodeAt(0), + ) : match) .replace(koRegex2, '다냥') .replace(koRegex3, '냥'); } diff --git a/packages/misskey-js/src/streaming.ts b/packages/misskey-js/src/streaming.ts index d1d131cfc1..ffb46c77f6 100644 --- a/packages/misskey-js/src/streaming.ts +++ b/packages/misskey-js/src/streaming.ts @@ -17,16 +17,32 @@ export function urlQuery(obj: Record<string, string | number | boolean | undefin type AnyOf<T extends Record<PropertyKey, unknown>> = T[keyof T]; -type StreamEvents = { +export type StreamEvents = { _connected_: void; _disconnected_: void; } & BroadcastEvents; +export interface IStream extends EventEmitter<StreamEvents> { + state: 'initializing' | 'reconnecting' | 'connected'; + + useChannel<C extends keyof Channels>(channel: C, params?: Channels[C]['params'], name?: string): IChannelConnection<Channels[C]>; + removeSharedConnection(connection: SharedConnection): void; + removeSharedConnectionPool(pool: Pool): void; + disconnectToChannel(connection: NonSharedConnection): void; + send(typeOrPayload: string): void; + send(typeOrPayload: string, payload: unknown): void; + send(typeOrPayload: Record<string, unknown> | unknown[]): void; + send(typeOrPayload: string | Record<string, unknown> | unknown[], payload?: unknown): void; + ping(): void; + heartbeat(): void; + close(): void; +} + /** * Misskey stream connection */ // eslint-disable-next-line import/no-default-export -export default class Stream extends EventEmitter<StreamEvents> { +export default class Stream extends EventEmitter<StreamEvents> implements IStream { private stream: _ReconnectingWebsocket.default; public state: 'initializing' | 'reconnecting' | 'connected' = 'initializing'; private sharedConnectionPools: Pool[] = []; @@ -277,7 +293,18 @@ class Pool { } } -export abstract class Connection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends EventEmitter<Channel['events']> { +export interface IChannelConnection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends EventEmitter<Channel['events']> { + id: string; + name?: string; + inCount: number; + outCount: number; + channel: string; + + send<T extends keyof Channel['receives']>(type: T, body: Channel['receives'][T]): void; + dispose(): void; +} + +export abstract class Connection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends EventEmitter<Channel['events']> implements IChannelConnection<Channel> { public channel: string; protected stream: Stream; public abstract id: string; diff --git a/packages/misskey-js/src/streaming.types.ts b/packages/misskey-js/src/streaming.types.ts index 4447a2e8fc..99d8a56e75 100644 --- a/packages/misskey-js/src/streaming.types.ts +++ b/packages/misskey-js/src/streaming.types.ts @@ -124,7 +124,7 @@ export type Channels = { }; hashtag: { params: { - q?: string; + q: string[][]; }; events: { note: (payload: Note) => void; diff --git a/packages/misskey-reversi/build.js b/packages/misskey-reversi/build.js index e626c97a59..a80b71646f 100644 --- a/packages/misskey-reversi/build.js +++ b/packages/misskey-reversi/build.js @@ -1,32 +1,32 @@ -import * as esbuild from "esbuild"; -import { build } from "esbuild"; -import { globSync } from "glob"; -import { execa } from "execa"; -import fs from "node:fs"; -import { fileURLToPath } from "node:url"; -import { dirname } from "node:path"; +import fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; +import * as esbuild from 'esbuild'; +import { build } from 'esbuild'; +import { globSync } from 'glob'; +import { execa } from 'execa'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8')); -const entryPoints = globSync("./src/**/**.{ts,tsx}"); +const entryPoints = globSync('./src/**/**.{ts,tsx}'); /** @type {import('esbuild').BuildOptions} */ const options = { entryPoints, minify: process.env.NODE_ENV === 'production', - outdir: "./built", - target: "es2022", - platform: "browser", - format: "esm", + outdir: './built', + target: 'es2022', + platform: 'browser', + format: 'esm', sourcemap: 'linked', }; // built配下をすべて削除する fs.rmSync('./built', { recursive: true, force: true }); -if (process.argv.map(arg => arg.toLowerCase()).includes("--watch")) { +if (process.argv.map(arg => arg.toLowerCase()).includes('--watch')) { await watchSrc(); } else { await buildSrc(); @@ -36,7 +36,7 @@ async function buildSrc() { console.log(`[${_package.name}] start building...`); await build(options) - .then(it => { + .then(() => { console.log(`[${_package.name}] build succeeded.`); }) .catch((err) => { @@ -65,7 +65,7 @@ function buildDts() { { stdout: process.stdout, stderr: process.stderr, - } + }, ); } @@ -86,7 +86,7 @@ async function watchSrc() { }, }]; - console.log(`[${_package.name}] start watching...`) + console.log(`[${_package.name}] start watching...`); const context = await esbuild.context({ ...options, plugins }); await context.watch(); diff --git a/packages/misskey-reversi/eslint.config.js b/packages/misskey-reversi/eslint.config.js index 3f81df7145..8453d9b8e7 100644 --- a/packages/misskey-reversi/eslint.config.js +++ b/packages/misskey-reversi/eslint.config.js @@ -1,6 +1,7 @@ import tsParser from '@typescript-eslint/parser'; import sharedConfig from '../shared/eslint.config.js'; +// eslint-disable-next-line import/no-default-export export default [ ...sharedConfig, { diff --git a/packages/misskey-reversi/src/game.ts b/packages/misskey-reversi/src/game.ts index 4afca9898c..35cc44feb4 100644 --- a/packages/misskey-reversi/src/game.ts +++ b/packages/misskey-reversi/src/game.ts @@ -53,9 +53,13 @@ export class Game { //#region Options this.opts = opts; + + /* eslint-disable @typescript-eslint/no-unnecessary-condition */ if (this.opts.isLlotheo == null) this.opts.isLlotheo = false; if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false; if (this.opts.loopedBoard == null) this.opts.loopedBoard = false; + /* eslint-enable */ + //#endregion //#region Parse map data @@ -123,12 +127,13 @@ export class Game { // ターン計算 this.turn = this.canPutSomewhere(!this.prevColor) ? !this.prevColor : - this.canPutSomewhere(this.prevColor!) ? this.prevColor : + this.canPutSomewhere(this.prevColor!) ? this.prevColor : //eslint-disable-line @typescript-eslint/no-non-null-assertion null; } public undo() { - const undo = this.logs.pop()!; + const undo = this.logs.pop(); + if (undo == null) return; this.prevColor = undo.color; this.prevPos = undo.pos; this.board[undo.pos] = null; @@ -183,7 +188,7 @@ export class Game { const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列 let [x, y] = this.posToXy(initPos); - while (true) { + while (true) { // eslint-disable-line @typescript-eslint/no-unnecessary-condition [x, y] = nextPos(x, y); // 座標が指し示す位置がボード外に出たとき diff --git a/packages/sw/package.json b/packages/sw/package.json index 9174f50ae3..09e604ff4c 100644 --- a/packages/sw/package.json +++ b/packages/sw/package.json @@ -9,16 +9,16 @@ "lint": "pnpm typecheck && pnpm eslint" }, "dependencies": { - "esbuild": "0.23.0", + "esbuild": "0.23.1", "idb-keyval": "6.2.1", "misskey-js": "workspace:*" }, "devDependencies": { "@typescript-eslint/parser": "7.17.0", "@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67", - "eslint-plugin-import": "2.29.1", - "nodemon": "3.1.4", - "typescript": "5.5.4" + "eslint-plugin-import": "2.30.0", + "nodemon": "3.1.7", + "typescript": "5.6.2" }, "type": "module" } diff --git a/packages/sw/src/scripts/lang.ts b/packages/sw/src/scripts/lang.ts index 0db4cc6381..3000160e41 100644 --- a/packages/sw/src/scripts/lang.ts +++ b/packages/sw/src/scripts/lang.ts @@ -7,7 +7,7 @@ * Language manager for SW */ import { get, set } from 'idb-keyval'; -import { I18n } from '../../../frontend/src/scripts/i18n.js'; +import { I18n } from '@@/js/i18n.js'; import type { Locale } from '../../../../locales/index.js'; class SwLang { diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts index 2d39d23ec7..bf980b83a4 100644 --- a/packages/sw/src/sw.ts +++ b/packages/sw/src/sw.ts @@ -6,7 +6,7 @@ import { get } from 'idb-keyval'; import * as Misskey from 'misskey-js'; import type { PushNotificationDataMap } from '@/types.js'; -import type { I18n } from '../../frontend/src/scripts/i18n.js'; +import type { I18n } from '@@/js/i18n.js'; import type { Locale } from '../../../locales/index.js'; import { createEmptyNotification, createNotification } from '@/scripts/create-notification.js'; import { swLang } from '@/scripts/lang.js'; diff --git a/packages/sw/tsconfig.json b/packages/sw/tsconfig.json index f3f3543013..2712475a37 100644 --- a/packages/sw/tsconfig.json +++ b/packages/sw/tsconfig.json @@ -21,7 +21,8 @@ "isolatedModules": true, "baseUrl": ".", "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], + "@@/*": ["../frontend-shared/*"] }, "typeRoots": [ "./node_modules/@types", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71ce6f6c63..a4a528530d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,10 +14,10 @@ importers: dependencies: cssnano: specifier: 6.1.2 - version: 6.1.2(postcss@8.4.40) + version: 6.1.2(postcss@8.4.47) esbuild: - specifier: 0.23.0 - version: 0.23.0 + specifier: 0.23.1 + version: 0.23.1 execa: specifier: 8.0.1 version: 8.0.1 @@ -34,17 +34,17 @@ importers: specifier: 4.1.0 version: 4.1.0 postcss: - specifier: 8.4.40 - version: 8.4.40 + specifier: 8.4.47 + version: 8.4.47 tar: specifier: 6.2.1 version: 6.2.1 terser: - specifier: 5.31.3 - version: 5.31.3 + specifier: 5.33.0 + version: 5.33.0 typescript: - specifier: 5.5.4 - version: 5.5.4 + specifier: 5.6.2 + version: 5.6.2 optionalDependencies: '@tensorflow/tfjs-core': specifier: 4.4.0 @@ -52,34 +52,34 @@ importers: devDependencies: '@misskey-dev/eslint-plugin': specifier: 2.0.3 - version: 2.0.3(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4))(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0))(eslint@9.8.0)(globals@15.8.0) + version: 2.0.3(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2))(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0))(eslint@9.8.0)(globals@15.9.0) '@types/node': specifier: 20.14.12 version: 20.14.12 '@typescript-eslint/eslint-plugin': specifier: 7.17.0 - version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4) + version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2) '@typescript-eslint/parser': specifier: 7.17.0 - version: 7.17.0(eslint@9.8.0)(typescript@5.5.4) + version: 7.17.0(eslint@9.8.0)(typescript@5.6.2) cross-env: specifier: 7.0.3 version: 7.0.3 cypress: - specifier: 13.13.1 - version: 13.13.1 + specifier: 13.14.2 + version: 13.14.2 eslint: specifier: 9.8.0 version: 9.8.0 globals: - specifier: 15.8.0 - version: 15.8.0 + specifier: 15.9.0 + version: 15.9.0 ncp: specifier: 2.0.0 version: 2.0.0 start-server-and-test: - specifier: 2.0.4 - version: 2.0.4 + specifier: 2.0.8 + version: 2.0.8 packages/backend: dependencies: @@ -90,41 +90,41 @@ importers: specifier: 3.620.0 version: 3.620.0(@aws-sdk/client-s3@3.620.0) '@bull-board/api': - specifier: 5.21.1 - version: 5.21.1(@bull-board/ui@5.21.1) + specifier: 5.23.0 + version: 5.23.0(@bull-board/ui@5.23.0) '@bull-board/fastify': - specifier: 5.21.1 - version: 5.21.1 + specifier: 5.23.0 + version: 5.23.0 '@bull-board/ui': - specifier: 5.21.1 - version: 5.21.1 + specifier: 5.23.0 + version: 5.23.0 '@discordapp/twemoji': - specifier: 15.0.3 - version: 15.0.3 + specifier: 15.1.0 + version: 15.1.0 '@fastify/accepts': - specifier: 4.3.0 - version: 4.3.0 + specifier: 5.0.0 + version: 5.0.0 '@fastify/cookie': - specifier: 9.3.1 - version: 9.3.1 + specifier: 10.0.0 + version: 10.0.0 '@fastify/cors': - specifier: 9.0.1 - version: 9.0.1 + specifier: 10.0.0 + version: 10.0.0 '@fastify/express': - specifier: 3.0.0 - version: 3.0.0 + specifier: 4.0.0 + version: 4.0.0 '@fastify/http-proxy': - specifier: 9.5.0 - version: 9.5.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) + specifier: 10.0.0 + version: 10.0.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) '@fastify/multipart': - specifier: 8.3.0 - version: 8.3.0 + specifier: 9.0.0 + version: 9.0.0 '@fastify/static': - specifier: 7.0.4 - version: 7.0.4 + specifier: 8.0.0 + version: 8.0.0 '@fastify/view': - specifier: 9.1.0 - version: 9.1.0 + specifier: 10.0.0 + version: 10.0.0 '@misskey-dev/sharp-read-bmp': specifier: 1.2.0 version: 1.2.0 @@ -132,17 +132,17 @@ importers: specifier: 5.1.0 version: 5.1.0 '@napi-rs/canvas': - specifier: ^0.1.53 - version: 0.1.53 + specifier: 0.1.56 + version: 0.1.56 '@nestjs/common': - specifier: 10.3.10 - version: 10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1) + specifier: 10.4.3 + version: 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': - specifier: 10.3.10 - version: 10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) + specifier: 10.4.3 + version: 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/testing': - specifier: 10.3.10 - version: 10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10)) + specifier: 10.4.3 + version: 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3)) '@peertube/http-signature': specifier: 1.7.0 version: 1.7.0 @@ -189,11 +189,11 @@ importers: specifier: 2.0.5 version: 2.0.5 body-parser: - specifier: 1.20.2 - version: 1.20.2 + specifier: 1.20.3 + version: 1.20.3 bullmq: - specifier: 5.10.4 - version: 5.10.4 + specifier: 5.13.2 + version: 5.13.2 cacheable-lookup: specifier: 7.0.0 version: 7.0.0 @@ -225,17 +225,17 @@ importers: specifier: 0.1.21 version: 0.1.21 fastify: - specifier: 4.28.1 - version: 4.28.1 + specifier: 5.0.0 + version: 5.0.0 fastify-raw-body: - specifier: 4.3.0 - version: 4.3.0 + specifier: 5.0.0 + version: 5.0.0 feed: specifier: 4.2.2 version: 4.2.2 file-type: - specifier: 19.3.0 - version: 19.3.0 + specifier: 19.5.0 + version: 19.5.0 fluent-ffmpeg: specifier: 2.1.3 version: 2.1.3 @@ -246,8 +246,8 @@ importers: specifier: 14.4.2 version: 14.4.2 happy-dom: - specifier: 10.0.3 - version: 10.0.3 + specifier: 15.7.4 + version: 15.7.4 hpagent: specifier: 1.2.0 version: 1.2.0 @@ -261,14 +261,14 @@ importers: specifier: 5.4.1 version: 5.4.1 ip-cidr: - specifier: 4.0.1 - version: 4.0.1 + specifier: 4.0.2 + version: 4.0.2 ipaddr.js: specifier: 2.2.0 version: 2.2.0 is-svg: - specifier: 5.0.1 - version: 5.0.1 + specifier: 5.1.0 + version: 5.1.0 js-yaml: specifier: 4.1.0 version: 4.1.0 @@ -284,9 +284,12 @@ importers: jsrsasign: specifier: 11.1.0 version: 11.1.0 + juice: + specifier: 11.0.0 + version: 11.0.0 meilisearch: - specifier: 0.41.0 - version: 0.41.0(encoding@0.1.13) + specifier: 0.42.0 + version: 0.42.0(encoding@0.1.13) mfm-js: specifier: 0.24.0 version: 0.24.0 @@ -315,8 +318,8 @@ importers: specifier: 3.3.2 version: 3.3.2 nodemailer: - specifier: 6.9.14 - version: 6.9.14 + specifier: 6.9.15 + version: 6.9.15 nsfwjs: specifier: 2.4.2 version: 2.4.2(@tensorflow/tfjs@4.4.0(encoding@0.1.13)(seedrandom@3.0.5)) @@ -333,14 +336,14 @@ importers: specifier: 0.0.14 version: 0.0.14 otpauth: - specifier: 9.3.1 - version: 9.3.1 + specifier: 9.3.2 + version: 9.3.2 parse5: specifier: 7.1.2 version: 7.1.2 pg: - specifier: 8.12.0 - version: 8.12.0 + specifier: 8.13.0 + version: 8.13.0 pkce-challenge: specifier: 4.1.0 version: 4.1.0 @@ -357,8 +360,8 @@ importers: specifier: 2.3.1 version: 2.3.1 qrcode: - specifier: 1.5.3 - version: 1.5.3 + specifier: 1.5.4 + version: 1.5.4 random-seed: specifier: 0.3.0 version: 0.3.0 @@ -366,8 +369,8 @@ importers: specifier: 3.4.1 version: 3.4.1 re2: - specifier: 1.21.3 - version: 1.21.3 + specifier: 1.21.4 + version: 1.21.4 redis-lock: specifier: 0.1.4 version: 0.1.4 @@ -390,8 +393,8 @@ importers: specifier: 2.7.0 version: 2.7.0 sharp: - specifier: 0.33.4 - version: 0.33.4 + specifier: 0.33.5 + version: 0.33.5 slacc: specifier: 0.0.10 version: 0.0.10 @@ -402,8 +405,8 @@ importers: specifier: 2.1.0 version: 2.1.0 systeminformation: - specifier: 5.22.11 - version: 5.22.11 + specifier: 5.23.5 + version: 5.23.5 tinycolor2: specifier: 1.6.0 version: 1.6.0 @@ -418,10 +421,10 @@ importers: version: 4.2.0 typeorm: specifier: 0.3.20 - version: 0.3.20(ioredis@5.4.1)(pg@8.12.0) + version: 0.3.20(ioredis@5.4.1)(pg@8.13.0) typescript: - specifier: 5.5.4 - version: 5.5.4 + specifier: 5.6.2 + version: 5.6.2 ulid: specifier: 2.3.0 version: 2.3.0 @@ -530,8 +533,8 @@ importers: specifier: 29.7.0 version: 29.7.0 '@nestjs/platform-express': - specifier: 10.3.10 - version: 10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10) + specifier: 10.4.3 + version: 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3) '@simplewebauthn/types': specifier: 10.0.0 version: 10.0.0 @@ -557,8 +560,8 @@ importers: specifier: 0.5.8 version: 0.5.8 '@types/fluent-ffmpeg': - specifier: 2.1.24 - version: 2.1.24 + specifier: 2.1.26 + version: 2.1.26 '@types/htmlescape': specifier: 1.1.3 version: 1.1.3 @@ -566,8 +569,8 @@ importers: specifier: 1.0.7 version: 1.0.7 '@types/jest': - specifier: 29.5.12 - version: 29.5.12 + specifier: 29.5.13 + version: 29.5.13 '@types/js-yaml': specifier: 4.0.9 version: 4.0.9 @@ -590,8 +593,8 @@ importers: specifier: 20.14.12 version: 20.14.12 '@types/nodemailer': - specifier: 6.4.15 - version: 6.4.15 + specifier: 6.4.16 + version: 6.4.16 '@types/oauth': specifier: 0.9.5 version: 0.9.5 @@ -602,8 +605,8 @@ importers: specifier: 0.1.2 version: 0.1.2 '@types/pg': - specifier: 8.11.6 - version: 8.11.6 + specifier: 8.11.10 + version: 8.11.10 '@types/pug': specifier: 2.0.10 version: 2.0.10 @@ -623,8 +626,8 @@ importers: specifier: 1.0.7 version: 1.0.7 '@types/sanitize-html': - specifier: 2.11.0 - version: 2.11.0 + specifier: 2.13.0 + version: 2.13.0 '@types/semver': specifier: 7.5.8 version: 7.5.8 @@ -647,14 +650,14 @@ importers: specifier: 3.6.3 version: 3.6.3 '@types/ws': - specifier: 8.5.11 - version: 8.5.11 + specifier: 8.5.12 + version: 8.5.12 '@typescript-eslint/eslint-plugin': specifier: 7.17.0 - version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4) + version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)(typescript@5.6.2) '@typescript-eslint/parser': specifier: 7.17.0 - version: 7.17.0(eslint@9.8.0)(typescript@5.5.4) + version: 7.17.0(eslint@9.11.0)(typescript@5.6.2) aws-sdk-client-mock: specifier: 4.0.1 version: 4.0.1 @@ -662,11 +665,11 @@ importers: specifier: 7.0.3 version: 7.0.3 eslint-plugin-import: - specifier: 2.29.1 - version: 2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0) + specifier: 2.30.0 + version: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0) execa: - specifier: 9.3.0 - version: 9.3.0 + specifier: 9.4.0 + version: 9.4.0 fkill: specifier: 9.0.0 version: 9.0.0 @@ -677,8 +680,8 @@ importers: specifier: 29.7.0 version: 29.7.0 nodemon: - specifier: 3.1.4 - version: 3.1.4 + specifier: 3.1.7 + version: 3.1.7 pid-port: specifier: 1.0.0 version: 1.0.0 @@ -689,8 +692,8 @@ importers: packages/frontend: dependencies: '@discordapp/twemoji': - specifier: 15.0.3 - version: 15.0.3 + specifier: 15.1.0 + version: 15.1.0 '@github/webauthn-json': specifier: 2.1.1 version: 2.1.1 @@ -702,13 +705,13 @@ importers: version: 2024.1.0 '@rollup/plugin-json': specifier: 6.1.0 - version: 6.1.0(rollup@4.19.1) + version: 6.1.0(rollup@4.22.2) '@rollup/plugin-replace': specifier: 5.0.7 - version: 5.0.7(rollup@4.19.1) + version: 5.0.7(rollup@4.22.2) '@rollup/pluginutils': specifier: 5.1.0 - version: 5.1.0(rollup@4.19.1) + version: 5.1.0(rollup@4.22.2) '@syuilo/aiscript': specifier: 0.19.0 version: 0.19.0 @@ -719,17 +722,17 @@ importers: specifier: 15.1.1 version: 15.1.1 '@vitejs/plugin-vue': - specifier: 5.1.0 - version: 5.1.0(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.37(typescript@5.5.4)) + specifier: 5.1.4 + version: 5.1.4(vite@5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.7(typescript@5.6.2)) '@vue/compiler-sfc': - specifier: 3.4.37 - version: 3.4.37 + specifier: 3.5.7 + version: 3.5.7 aiscript-vscode: specifier: github:aiscript-dev/aiscript-vscode#v0.1.11 version: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/e1e1b27f2f72cd28a473e004b6da0d8fc0bd40d9 astring: - specifier: 1.8.6 - version: 1.8.6 + specifier: 1.9.0 + version: 1.9.0 broadcast-channel: specifier: 7.0.0 version: 7.0.0 @@ -740,29 +743,29 @@ importers: specifier: 1.9.3 version: 1.9.3 chart.js: - specifier: 4.4.3 - version: 4.4.3 + specifier: 4.4.4 + version: 4.4.4 chartjs-adapter-date-fns: specifier: 3.0.0 - version: 3.0.0(chart.js@4.4.3)(date-fns@2.30.0) + version: 3.0.0(chart.js@4.4.4)(date-fns@2.30.0) chartjs-chart-matrix: specifier: 2.0.1 - version: 2.0.1(chart.js@4.4.3) + version: 2.0.1(chart.js@4.4.4) chartjs-plugin-gradient: specifier: 0.6.1 - version: 0.6.1(chart.js@4.4.3) + version: 0.6.1(chart.js@4.4.4) chartjs-plugin-zoom: specifier: 2.0.1 - version: 2.0.1(chart.js@4.4.3) + version: 2.0.1(chart.js@4.4.4) chromatic: - specifier: 11.5.6 - version: 11.5.6 + specifier: 11.10.2 + version: 11.10.2 compare-versions: specifier: 6.1.1 version: 6.1.1 cropperjs: - specifier: 2.0.0-rc.1 - version: 2.0.0-rc.1 + specifier: 2.0.0-rc.2 + version: 2.0.0-rc.2 date-fns: specifier: 2.30.0 version: 2.30.0 @@ -775,6 +778,9 @@ importers: eventemitter3: specifier: 5.0.1 version: 5.0.1 + frontend-shared: + specifier: workspace:* + version: link:../frontend-shared idb-keyval: specifier: 6.2.1 version: 6.2.1 @@ -809,14 +815,14 @@ importers: specifier: 2.3.1 version: 2.3.1 rollup: - specifier: 4.19.1 - version: 4.19.1 + specifier: 4.22.2 + version: 4.22.2 sanitize-html: specifier: 2.13.0 version: 2.13.0 sass: - specifier: 1.77.8 - version: 1.77.8 + specifier: 1.79.3 + version: 1.79.3 shiki: specifier: 1.12.0 version: 1.12.0 @@ -827,8 +833,8 @@ importers: specifier: 3.1.0 version: 3.1.0 three: - specifier: 0.167.0 - version: 0.167.0 + specifier: 0.168.0 + version: 0.168.0 throttle-debounce: specifier: 5.0.2 version: 5.0.2 @@ -842,90 +848,90 @@ importers: specifier: 4.2.0 version: 4.2.0 typescript: - specifier: 5.5.4 - version: 5.5.4 + specifier: 5.6.2 + version: 5.6.2 uuid: specifier: 10.0.0 version: 10.0.0 v-code-diff: - specifier: 1.12.0 - version: 1.12.0(vue@3.4.37(typescript@5.5.4)) + specifier: 1.13.1 + version: 1.13.1(vue@3.5.7(typescript@5.6.2)) vite: - specifier: 5.3.5 - version: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3) + specifier: 5.4.7 + version: 5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) vue: - specifier: 3.4.37 - version: 3.4.37(typescript@5.5.4) + specifier: 3.5.7 + version: 3.5.7(typescript@5.6.2) vuedraggable: specifier: next - version: 4.1.0(vue@3.4.37(typescript@5.5.4)) + version: 4.1.0(vue@3.5.7(typescript@5.6.2)) devDependencies: '@misskey-dev/summaly': specifier: 5.1.0 version: 5.1.0 '@storybook/addon-actions': - specifier: 8.2.6 - version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.3.2 + version: 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/addon-essentials': - specifier: 8.2.6 - version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.3.2 + version: 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/addon-interactions': - specifier: 8.2.6 - version: 8.2.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.14.12))(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3)) + specifier: 8.3.2 + version: 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/addon-links': - specifier: 8.2.6 - version: 8.2.6(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.3.2 + version: 8.3.2(react@18.3.1)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/addon-mdx-gfm': - specifier: 8.2.6 - version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.3.2 + version: 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/addon-storysource': - specifier: 8.2.6 - version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.3.2 + version: 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/blocks': - specifier: 8.2.6 - version: 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.3.2 + version: 8.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/components': - specifier: 8.2.6 - version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.3.2 + version: 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/core-events': - specifier: 8.2.6 - version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.3.2 + version: 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/manager-api': - specifier: 8.2.6 - version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.3.2 + version: 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/preview-api': - specifier: 8.2.6 - version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.3.2 + version: 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/react': - specifier: 8.2.6 - version: 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.5.4) + specifier: 8.3.2 + version: 8.3.2(@storybook/test@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2) '@storybook/react-vite': - specifier: 8.2.6 - version: 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.19.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)) + specifier: 8.3.2 + version: 8.3.2(@storybook/test@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.22.2)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)) '@storybook/test': - specifier: 8.2.6 - version: 8.2.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.14.12))(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3)) + specifier: 8.3.2 + version: 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/theming': - specifier: 8.2.6 - version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.3.2 + version: 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/types': - specifier: 8.2.6 - version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.3.2 + version: 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/vue3': - specifier: 8.2.6 - version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.4.37(typescript@5.5.4)) + specifier: 8.3.2 + version: 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.5.7(typescript@5.6.2)) '@storybook/vue3-vite': - specifier: 8.1.11 - version: 8.1.11(bufferutil@4.0.8)(encoding@0.1.13)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@6.0.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.37(typescript@5.5.4)) + specifier: 8.3.2 + version: 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vite@5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.7(typescript@5.6.2)) '@testing-library/vue': specifier: 8.1.0 - version: 8.1.0(@vue/compiler-sfc@3.4.37)(@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4)))(vue@3.4.37(typescript@5.5.4)) + version: 8.1.0(@vue/compiler-sfc@3.5.7)(@vue/server-renderer@3.5.7(vue@3.5.7(typescript@5.6.2)))(vue@3.5.7(typescript@5.6.2)) '@types/escape-regexp': specifier: 0.0.3 version: 0.0.3 '@types/estree': - specifier: 1.0.5 - version: 1.0.5 + specifier: 1.0.6 + version: 1.0.6 '@types/matter-js': specifier: 0.19.7 version: 0.19.7 @@ -939,8 +945,8 @@ importers: specifier: 2.1.4 version: 2.1.4 '@types/sanitize-html': - specifier: 2.11.0 - version: 2.11.0 + specifier: 2.13.0 + version: 2.13.0 '@types/seedrandom': specifier: 3.0.8 version: 3.0.8 @@ -954,20 +960,20 @@ importers: specifier: 10.0.0 version: 10.0.0 '@types/ws': - specifier: 8.5.11 - version: 8.5.11 + specifier: 8.5.12 + version: 8.5.12 '@typescript-eslint/eslint-plugin': specifier: 7.17.0 - version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4) + version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)(typescript@5.6.2) '@typescript-eslint/parser': specifier: 7.17.0 - version: 7.17.0(eslint@9.8.0)(typescript@5.5.4) + version: 7.17.0(eslint@9.11.0)(typescript@5.6.2) '@vitest/coverage-v8': specifier: 1.6.0 - version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3)) + version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0)) '@vue/runtime-core': - specifier: 3.4.37 - version: 3.4.37 + specifier: 3.5.7 + version: 3.5.7 acorn: specifier: 8.12.1 version: 8.12.1 @@ -975,14 +981,14 @@ importers: specifier: 7.0.3 version: 7.0.3 cypress: - specifier: 13.13.1 - version: 13.13.1 + specifier: 13.14.2 + version: 13.14.2 eslint-plugin-import: - specifier: 2.29.1 - version: 2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0) + specifier: 2.30.0 + version: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0) eslint-plugin-vue: - specifier: 9.27.0 - version: 9.27.0(eslint@9.8.0) + specifier: 9.28.0 + version: 9.28.0(eslint@9.11.0) fast-glob: specifier: 3.3.2 version: 3.3.2 @@ -993,17 +999,17 @@ importers: specifier: 0.12.2 version: 0.12.2 micromatch: - specifier: 4.0.7 - version: 4.0.7 + specifier: 4.0.8 + version: 4.0.8 msw: - specifier: 2.3.4 - version: 2.3.4(typescript@5.5.4) + specifier: 2.4.9 + version: 2.4.9(typescript@5.6.2) msw-storybook-addon: specifier: 2.0.3 - version: 2.0.3(msw@2.3.4(typescript@5.5.4)) + version: 2.0.3(msw@2.4.9(typescript@5.6.2)) nodemon: - specifier: 3.1.4 - version: 3.1.4 + specifier: 3.1.7 + version: 3.1.7 prettier: specifier: 3.3.3 version: 3.3.3 @@ -1017,32 +1023,271 @@ importers: specifier: 3.0.5 version: 3.0.5 start-server-and-test: - specifier: 2.0.4 - version: 2.0.4 + specifier: 2.0.8 + version: 2.0.8 storybook: - specifier: 8.2.6 - version: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + specifier: 8.3.2 + version: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) storybook-addon-misskey-theme: specifier: github:misskey-dev/storybook-addon-misskey-theme - version: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(3rvqj7p7l43ansgshs3zbslm7u) + version: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@8.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/components@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/core-events@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/manager-api@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/preview-api@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/theming@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/types@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) vite-plugin-turbosnap: specifier: 1.0.3 version: 1.0.3 vitest: specifier: 1.6.0 - version: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3) + version: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0) vitest-fetch-mock: specifier: 0.2.2 - version: 0.2.2(encoding@0.1.13)(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3)) + version: 0.2.2(encoding@0.1.13)(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0)) vue-component-type-helpers: - specifier: 2.0.29 - version: 2.0.29 + specifier: 2.1.6 + version: 2.1.6 vue-eslint-parser: specifier: 9.4.3 - version: 9.4.3(eslint@9.8.0) + version: 9.4.3(eslint@9.11.0) vue-tsc: - specifier: 2.0.29 - version: 2.0.29(typescript@5.5.4) + specifier: 2.1.6 + version: 2.1.6(typescript@5.6.2) + + packages/frontend-embed: + dependencies: + '@discordapp/twemoji': + specifier: 15.1.0 + version: 15.1.0 + '@github/webauthn-json': + specifier: 2.1.1 + version: 2.1.1 + '@rollup/plugin-json': + specifier: 6.1.0 + version: 6.1.0(rollup@4.22.2) + '@rollup/plugin-replace': + specifier: 5.0.7 + version: 5.0.7(rollup@4.22.2) + '@rollup/pluginutils': + specifier: 5.1.0 + version: 5.1.0(rollup@4.22.2) + '@tabler/icons-webfont': + specifier: 3.3.0 + version: 3.3.0 + '@twemoji/parser': + specifier: 15.1.1 + version: 15.1.1 + '@vitejs/plugin-vue': + specifier: 5.1.4 + version: 5.1.4(vite@5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.7(typescript@5.6.2)) + '@vue/compiler-sfc': + specifier: 3.5.7 + version: 3.5.7 + astring: + specifier: 1.9.0 + version: 1.9.0 + buraha: + specifier: 0.0.1 + version: 0.0.1 + compare-versions: + specifier: 6.1.1 + version: 6.1.1 + date-fns: + specifier: 2.30.0 + version: 2.30.0 + escape-regexp: + specifier: 0.0.1 + version: 0.0.1 + estree-walker: + specifier: 3.0.3 + version: 3.0.3 + eventemitter3: + specifier: 5.0.1 + version: 5.0.1 + frontend-shared: + specifier: workspace:* + version: link:../frontend-shared + idb-keyval: + specifier: 6.2.1 + version: 6.2.1 + is-file-animated: + specifier: 1.0.2 + version: 1.0.2 + json5: + specifier: 2.2.3 + version: 2.2.3 + mfm-js: + specifier: 0.24.0 + version: 0.24.0 + misskey-js: + specifier: workspace:* + version: link:../misskey-js + punycode: + specifier: 2.3.1 + version: 2.3.1 + rollup: + specifier: 4.22.2 + version: 4.22.2 + sanitize-html: + specifier: 2.13.0 + version: 2.13.0 + sass: + specifier: 1.79.3 + version: 1.79.3 + shiki: + specifier: 1.12.0 + version: 1.12.0 + strict-event-emitter-types: + specifier: 2.0.0 + version: 2.0.0 + throttle-debounce: + specifier: 5.0.2 + version: 5.0.2 + tinycolor2: + specifier: 1.6.0 + version: 1.6.0 + tsc-alias: + specifier: 1.8.10 + version: 1.8.10 + tsconfig-paths: + specifier: 4.2.0 + version: 4.2.0 + typescript: + specifier: 5.6.2 + version: 5.6.2 + uuid: + specifier: 10.0.0 + version: 10.0.0 + vite: + specifier: 5.4.7 + version: 5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) + vue: + specifier: 3.5.7 + version: 3.5.7(typescript@5.6.2) + devDependencies: + '@misskey-dev/summaly': + specifier: 5.1.0 + version: 5.1.0 + '@testing-library/vue': + specifier: 8.1.0 + version: 8.1.0(@vue/compiler-sfc@3.5.7)(@vue/server-renderer@3.5.7(vue@3.5.7(typescript@5.6.2)))(vue@3.5.7(typescript@5.6.2)) + '@types/escape-regexp': + specifier: 0.0.3 + version: 0.0.3 + '@types/estree': + specifier: 1.0.6 + version: 1.0.6 + '@types/micromatch': + specifier: 4.0.9 + version: 4.0.9 + '@types/node': + specifier: 20.14.12 + version: 20.14.12 + '@types/punycode': + specifier: 2.1.4 + version: 2.1.4 + '@types/sanitize-html': + specifier: 2.13.0 + version: 2.13.0 + '@types/throttle-debounce': + specifier: 5.0.2 + version: 5.0.2 + '@types/tinycolor2': + specifier: 1.4.6 + version: 1.4.6 + '@types/uuid': + specifier: 10.0.0 + version: 10.0.0 + '@types/ws': + specifier: 8.5.12 + version: 8.5.12 + '@typescript-eslint/eslint-plugin': + specifier: 7.17.0 + version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)(typescript@5.6.2) + '@typescript-eslint/parser': + specifier: 7.17.0 + version: 7.17.0(eslint@9.11.0)(typescript@5.6.2) + '@vitest/coverage-v8': + specifier: 1.6.0 + version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.3)(terser@5.33.0)) + '@vue/runtime-core': + specifier: 3.5.7 + version: 3.5.7 + acorn: + specifier: 8.12.1 + version: 8.12.1 + cross-env: + specifier: 7.0.3 + version: 7.0.3 + eslint-plugin-import: + specifier: 2.30.0 + version: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0) + eslint-plugin-vue: + specifier: 9.28.0 + version: 9.28.0(eslint@9.11.0) + fast-glob: + specifier: 3.3.2 + version: 3.3.2 + happy-dom: + specifier: 10.0.3 + version: 10.0.3 + intersection-observer: + specifier: 0.12.2 + version: 0.12.2 + micromatch: + specifier: 4.0.8 + version: 4.0.8 + msw: + specifier: 2.3.4 + version: 2.3.4(typescript@5.6.2) + nodemon: + specifier: 3.1.7 + version: 3.1.7 + prettier: + specifier: 3.3.3 + version: 3.3.3 + start-server-and-test: + specifier: 2.0.8 + version: 2.0.8 + vite-plugin-turbosnap: + specifier: 1.0.3 + version: 1.0.3 + vue-component-type-helpers: + specifier: 2.1.6 + version: 2.1.6 + vue-eslint-parser: + specifier: 9.4.3 + version: 9.4.3(eslint@9.11.0) + vue-tsc: + specifier: 2.1.6 + version: 2.1.6(typescript@5.6.2) + + packages/frontend-shared: + dependencies: + misskey-js: + specifier: workspace:* + version: link:../misskey-js + vue: + specifier: 3.4.37 + version: 3.4.37(typescript@5.5.4) + devDependencies: + '@types/node': + specifier: 20.14.12 + version: 20.14.12 + '@typescript-eslint/eslint-plugin': + specifier: 7.17.0 + version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.5.4))(eslint@9.11.0)(typescript@5.5.4) + '@typescript-eslint/parser': + specifier: 7.17.0 + version: 7.17.0(eslint@9.11.0)(typescript@5.5.4) + esbuild: + specifier: 0.23.0 + version: 0.23.0 + eslint-plugin-vue: + specifier: 9.27.0 + version: 9.27.0(eslint@9.11.0) + typescript: + specifier: 5.5.4 + version: 5.5.4 + vue-eslint-parser: + specifier: 9.4.3 + version: 9.4.3(eslint@9.11.0) packages/misskey-bubble-game: dependencies: @@ -1067,10 +1312,10 @@ importers: version: 3.0.8 '@typescript-eslint/eslint-plugin': specifier: 7.1.0 - version: 7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.8.0)(typescript@5.3.3))(eslint@9.8.0)(typescript@5.3.3) + version: 7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.11.0)(typescript@5.3.3))(eslint@9.11.0)(typescript@5.3.3) '@typescript-eslint/parser': specifier: 7.1.0 - version: 7.1.0(eslint@9.8.0)(typescript@5.3.3) + version: 7.1.0(eslint@9.11.0)(typescript@5.3.3) esbuild: specifier: 0.19.11 version: 0.19.11 @@ -1097,29 +1342,29 @@ importers: version: 4.4.0 devDependencies: '@microsoft/api-extractor': - specifier: 7.47.4 - version: 7.47.4(@types/node@20.14.12) + specifier: 7.47.9 + version: 7.47.9(@types/node@20.14.12) '@swc/jest': specifier: 0.2.36 version: 0.2.36(@swc/core@1.6.13) '@types/jest': - specifier: 29.5.12 - version: 29.5.12 + specifier: 29.5.13 + version: 29.5.13 '@types/node': specifier: 20.14.12 version: 20.14.12 '@typescript-eslint/eslint-plugin': specifier: 7.17.0 - version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4) + version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)(typescript@5.6.2) '@typescript-eslint/parser': specifier: 7.17.0 - version: 7.17.0(eslint@9.8.0)(typescript@5.5.4) + version: 7.17.0(eslint@9.11.0)(typescript@5.6.2) esbuild: - specifier: 0.23.0 - version: 0.23.0 + specifier: 0.23.1 + version: 0.23.1 execa: - specifier: 9.3.0 - version: 9.3.0 + specifier: 9.4.0 + version: 9.4.0 glob: specifier: 11.0.0 version: 11.0.0 @@ -1139,29 +1384,29 @@ importers: specifier: 2.0.0 version: 2.0.0 nodemon: - specifier: 3.1.4 - version: 3.1.4 + specifier: 3.1.7 + version: 3.1.7 tsd: - specifier: 0.31.1 - version: 0.31.1 + specifier: 0.31.2 + version: 0.31.2 typescript: - specifier: 5.5.4 - version: 5.5.4 + specifier: 5.6.2 + version: 5.6.2 packages/misskey-js/generator: devDependencies: '@readme/openapi-parser': - specifier: 2.5.0 - version: 2.5.0(openapi-types@12.1.3) + specifier: 2.6.0 + version: 2.6.0(openapi-types@12.1.3) '@types/node': specifier: 20.9.1 version: 20.9.1 '@typescript-eslint/eslint-plugin': - specifier: 6.11.0 - version: 6.11.0(@typescript-eslint/parser@6.11.0(eslint@9.8.0)(typescript@5.3.3))(eslint@9.8.0)(typescript@5.3.3) + specifier: 7.17.0 + version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)(typescript@5.6.2) '@typescript-eslint/parser': - specifier: 6.11.0 - version: 6.11.0(eslint@9.8.0)(typescript@5.3.3) + specifier: 7.17.0 + version: 7.17.0(eslint@9.11.0)(typescript@5.6.2) openapi-types: specifier: 12.1.3 version: 12.1.3 @@ -1169,14 +1414,14 @@ importers: specifier: 6.7.3 version: 6.7.3 ts-case-convert: - specifier: 2.0.2 - version: 2.0.2 + specifier: 2.0.7 + version: 2.0.7 tsx: specifier: 4.4.0 version: 4.4.0 typescript: - specifier: 5.3.3 - version: 5.3.3 + specifier: 5.6.2 + version: 5.6.2 packages/misskey-reversi: dependencies: @@ -1189,10 +1434,10 @@ importers: version: 20.11.5 '@typescript-eslint/eslint-plugin': specifier: 7.1.0 - version: 7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.8.0)(typescript@5.3.3))(eslint@9.8.0)(typescript@5.3.3) + version: 7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.11.0)(typescript@5.3.3))(eslint@9.11.0)(typescript@5.3.3) '@typescript-eslint/parser': specifier: 7.1.0 - version: 7.1.0(eslint@9.8.0)(typescript@5.3.3) + version: 7.1.0(eslint@9.11.0)(typescript@5.3.3) esbuild: specifier: 0.19.11 version: 0.19.11 @@ -1212,8 +1457,8 @@ importers: packages/sw: dependencies: esbuild: - specifier: 0.23.0 - version: 0.23.0 + specifier: 0.23.1 + version: 0.23.1 idb-keyval: specifier: 6.2.1 version: 6.2.1 @@ -1223,24 +1468,24 @@ importers: devDependencies: '@typescript-eslint/parser': specifier: 7.17.0 - version: 7.17.0(eslint@9.8.0)(typescript@5.5.4) + version: 7.17.0(eslint@9.11.0)(typescript@5.6.2) '@typescript/lib-webworker': specifier: npm:@types/serviceworker@0.0.67 version: '@types/serviceworker@0.0.67' eslint-plugin-import: - specifier: 2.29.1 - version: 2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0) + specifier: 2.30.0 + version: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0) nodemon: - specifier: 3.1.4 - version: 3.1.4 + specifier: 3.1.7 + version: 3.1.7 typescript: - specifier: 5.5.4 - version: 5.5.4 + specifier: 5.6.2 + version: 5.6.2 packages: - '@adobe/css-tools@4.3.3': - resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==} + '@adobe/css-tools@4.4.0': + resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} '@aiscript-dev/aiscript-languageserver@https://github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.6/aiscript-dev-aiscript-languageserver-0.1.6.tgz': resolution: {tarball: https://github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.6/aiscript-dev-aiscript-languageserver-0.1.6.tgz} @@ -1251,17 +1496,9 @@ packages: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} - '@apidevtools/openapi-schemas@2.1.0': - resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==} - engines: {node: '>=10'} - '@apidevtools/swagger-methods@3.0.2': resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} - '@aw-web-design/x-default-browser@1.4.126': - resolution: {integrity: sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==} - hasBin: true - '@aws-crypto/crc32@5.2.0': resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} engines: {node: '>=16.0.0'} @@ -1467,14 +1704,6 @@ packages: resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.24.7': - resolution: {integrity: sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-builder-binary-assignment-operator-visitor@7.24.7': - resolution: {integrity: sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==} - engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.22.15': resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==} engines: {node: '>=6.9.0'} @@ -1483,23 +1712,6 @@ packages: resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.24.7': - resolution: {integrity: sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-create-regexp-features-plugin@7.24.7': - resolution: {integrity: sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-define-polyfill-provider@0.6.2': - resolution: {integrity: sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - '@babel/helper-environment-visitor@7.22.20': resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} engines: {node: '>=6.9.0'} @@ -1524,10 +1736,6 @@ packages: resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} engines: {node: '>=6.9.0'} - '@babel/helper-member-expression-to-functions@7.24.7': - resolution: {integrity: sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.22.15': resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} engines: {node: '>=6.9.0'} @@ -1548,30 +1756,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-optimise-call-expression@7.24.7': - resolution: {integrity: sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==} - engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.22.5': resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.24.7': - resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-remap-async-to-generator@7.24.7': - resolution: {integrity: sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-replace-supers@7.24.7': - resolution: {integrity: sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-simple-access@7.22.5': resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} engines: {node: '>=6.9.0'} @@ -1580,10 +1768,6 @@ packages: resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} engines: {node: '>=6.9.0'} - '@babel/helper-skip-transparent-expression-wrappers@7.24.7': - resolution: {integrity: sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-split-export-declaration@7.22.6': resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} engines: {node: '>=6.9.0'} @@ -1596,6 +1780,10 @@ packages: resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.24.8': + resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.24.7': resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} @@ -1608,10 +1796,6 @@ packages: resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} engines: {node: '>=6.9.0'} - '@babel/helper-wrap-function@7.24.7': - resolution: {integrity: sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==} - engines: {node: '>=6.9.0'} - '@babel/helpers@7.23.5': resolution: {integrity: sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==} engines: {node: '>=6.9.0'} @@ -1633,35 +1817,10 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.7': - resolution: {integrity: sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.7': - resolution: {integrity: sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.7': - resolution: {integrity: sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.13.0 - - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.24.7': - resolution: {integrity: sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': - resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/parser@7.25.6': + resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==} + engines: {node: '>=6.0.0'} + hasBin: true '@babel/plugin-syntax-async-generators@7.8.4': resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} @@ -1678,40 +1837,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-class-static-block@7.14.5': - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-dynamic-import@7.8.3': - resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-export-namespace-from@7.8.3': - resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-flow@7.23.3': - resolution: {integrity: sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-assertions@7.24.7': - resolution: {integrity: sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-attributes@7.24.7': - resolution: {integrity: sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-meta@7.10.4': resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: @@ -1758,12 +1883,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-private-property-in-object@7.14.5': - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-top-level-await@7.14.5': resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} @@ -1776,344 +1895,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6': - resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-transform-arrow-functions@7.24.7': - resolution: {integrity: sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-async-generator-functions@7.24.7': - resolution: {integrity: sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-async-to-generator@7.24.7': - resolution: {integrity: sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-block-scoped-functions@7.24.7': - resolution: {integrity: sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-block-scoping@7.24.7': - resolution: {integrity: sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-class-properties@7.24.7': - resolution: {integrity: sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-class-static-block@7.24.7': - resolution: {integrity: sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.12.0 - - '@babel/plugin-transform-classes@7.24.7': - resolution: {integrity: sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-computed-properties@7.24.7': - resolution: {integrity: sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-destructuring@7.24.7': - resolution: {integrity: sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-dotall-regex@7.24.7': - resolution: {integrity: sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-duplicate-keys@7.24.7': - resolution: {integrity: sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-dynamic-import@7.24.7': - resolution: {integrity: sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-exponentiation-operator@7.24.7': - resolution: {integrity: sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-export-namespace-from@7.24.7': - resolution: {integrity: sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-flow-strip-types@7.23.3': - resolution: {integrity: sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-for-of@7.24.7': - resolution: {integrity: sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-function-name@7.24.7': - resolution: {integrity: sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-json-strings@7.24.7': - resolution: {integrity: sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-literals@7.24.7': - resolution: {integrity: sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-logical-assignment-operators@7.24.7': - resolution: {integrity: sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-member-expression-literals@7.24.7': - resolution: {integrity: sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-amd@7.24.7': - resolution: {integrity: sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-commonjs@7.24.7': - resolution: {integrity: sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-systemjs@7.24.7': - resolution: {integrity: sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-umd@7.24.7': - resolution: {integrity: sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-named-capturing-groups-regex@7.24.7': - resolution: {integrity: sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/plugin-transform-new-target@7.24.7': - resolution: {integrity: sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-nullish-coalescing-operator@7.24.7': - resolution: {integrity: sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-numeric-separator@7.24.7': - resolution: {integrity: sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-object-rest-spread@7.24.7': - resolution: {integrity: sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-object-super@7.24.7': - resolution: {integrity: sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-optional-catch-binding@7.24.7': - resolution: {integrity: sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-optional-chaining@7.24.7': - resolution: {integrity: sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-parameters@7.24.7': - resolution: {integrity: sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-private-methods@7.24.7': - resolution: {integrity: sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-private-property-in-object@7.24.7': - resolution: {integrity: sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-property-literals@7.24.7': - resolution: {integrity: sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-regenerator@7.24.7': - resolution: {integrity: sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-reserved-words@7.24.7': - resolution: {integrity: sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-shorthand-properties@7.24.7': - resolution: {integrity: sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-spread@7.24.7': - resolution: {integrity: sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-sticky-regex@7.24.7': - resolution: {integrity: sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-template-literals@7.24.7': - resolution: {integrity: sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-typeof-symbol@7.24.7': - resolution: {integrity: sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-typescript@7.23.5': - resolution: {integrity: sha512-2fMkXEJkrmwgu2Bsv1Saxgj30IXZdJ+84lQcKKI7sm719oXs0BBw2ZENKdJdR1PjWndgLCEBNXJOri0fk7RYQA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-escapes@7.24.7': - resolution: {integrity: sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-property-regex@7.24.7': - resolution: {integrity: sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-regex@7.24.7': - resolution: {integrity: sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-sets-regex@7.24.7': - resolution: {integrity: sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/preset-env@7.24.7': - resolution: {integrity: sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/preset-flow@7.23.3': - resolution: {integrity: sha512-7yn6hl8RIv+KNk6iIrGZ+D06VhVY35wLVf23Cz/mMu1zOr7u4MMP4j0nZ9tLf8+4ZFpnib8cFYgB/oYg9hfswA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/preset-modules@0.1.6-no-external-plugins': - resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} - peerDependencies: - '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - - '@babel/preset-typescript@7.23.3': - resolution: {integrity: sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/register@7.22.15': - resolution: {integrity: sha512-V3Q3EqoQdn65RCgTLwauZaTfd1ShhwPmbBv+1dkZV/HpCGMKVyn6oFcRlI7RaKqiDQjX2Qd3AuoEguBgdjIKlg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/regjsgen@0.8.0': - resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} - '@babel/runtime@7.23.4': resolution: {integrity: sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==} engines: {node: '>=6.9.0'} @@ -2142,22 +1923,26 @@ packages: resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.25.6': + resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} + engines: {node: '>=6.9.0'} + '@base2/pretty-print-object@1.0.1': resolution: {integrity: sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==} '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@bull-board/api@5.21.1': - resolution: {integrity: sha512-anzTfhOJ93eraT/GYeyxWpxRMarHwuevn6pPBfdOj0LC2eg98OPnkfdMSjcrpL3qrqsxON0RslS7kuPfCEnX6A==} + '@bull-board/api@5.23.0': + resolution: {integrity: sha512-ZZGsWJ+XBG49GAlNgAL9tTEV6Ms7gMkQnZDbzwUhjGChCKWy62RWuPoZSefNXau9QH9+QzlzHRUeFvt4xr5wiw==} peerDependencies: - '@bull-board/ui': 5.21.1 + '@bull-board/ui': 5.23.0 - '@bull-board/fastify@5.21.1': - resolution: {integrity: sha512-We33yolc70SALjDdF3cjEaLn1L/vrw85eFCsrviESaW3dFVIdB+xn0fdqMFK6NnaC0JjBa3Ypfev4Co+eaZ+1A==} + '@bull-board/fastify@5.23.0': + resolution: {integrity: sha512-woCnCAav4IByuo05D13MZtETzZp0ej1y0R+6IY33pqLKDRKa6Dor6OMx1l6/nMc/wXeng4SXC5rnrAck7Py70w==} - '@bull-board/ui@5.21.1': - resolution: {integrity: sha512-JBDeCqG7j/c3WE0uGMN9snPkRJz9/D6MpTZzyVj7KOxIJwNKPOICNFZbCrCNi7bcJYHDJ2xGTN9OO1mw7i43BQ==} + '@bull-board/ui@5.23.0': + resolution: {integrity: sha512-iI/Ssl8T5ZEn9s899Qz67m92M6RU8thf/aqD7cUHB2yHmkCjqbw7s7NaODTsyArAsnyu7DGJMWm7EhbfFXDNgQ==} '@bundled-es-modules/cookie@2.0.0': resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==} @@ -2175,41 +1960,41 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - '@cropper/element-canvas@2.0.0-rc.1': - resolution: {integrity: sha512-jRt9OM7cls+zch8U2m7pA9wp8dNOz0EtedGKkqH+DInUYw1+UtonEJirrxyl1YHRgOme5M5DTsDmkUSrUiZN6g==} + '@cropper/element-canvas@2.0.0-rc.2': + resolution: {integrity: sha512-0aqbJ3ycQM6/yn4T03vw8K/OeTB8C6+Z/jimuavy4UM2CENH9ucSLM4hAG0yYCgghIyv9Zd0unaBmtgW+I5+SQ==} - '@cropper/element-crosshair@2.0.0-rc.1': - resolution: {integrity: sha512-xfLelqM8EnRZUf7xEE88RWQQx5erUv7jrzni52bAw3/Ua8HXIz3uAMnkrGKOTBj8K4Rv/mNJY8k1DVAEfHY6Lg==} + '@cropper/element-crosshair@2.0.0-rc.2': + resolution: {integrity: sha512-yopINLvaZhL3E2GNienju1zeQ1Cifkn5f/0R7ZabXcAgUI0s2sLzNqL8+2XV2J3DzEzYEIYc+49KmMle04nVWQ==} - '@cropper/element-grid@2.0.0-rc.1': - resolution: {integrity: sha512-U/BYPl76upd9sXT+pZTFoQzUqWyNxdGs4YR2UtaVCfTMHLDTrssPAedmqEEnHgbqVcr325sIEfVmwWVA+v+8Dg==} + '@cropper/element-grid@2.0.0-rc.2': + resolution: {integrity: sha512-PzAfEya6CmIc/o/lcA/NZ1rohszz42wjq2z3E2zq2jMfNDxY/EIoFnGI6+hJrxCAaoKD8UlKOEHQdRQbtnjcMg==} - '@cropper/element-handle@2.0.0-rc.1': - resolution: {integrity: sha512-GuOHbjkg5CP1+oFzWQeD7VZffUE86dp4gKv5egLxkBEwnQp1VQxjO7L1Wkgj+KsQymoDczsl+x4bF12KDyDg2g==} + '@cropper/element-handle@2.0.0-rc.2': + resolution: {integrity: sha512-wOWX4xpryxKcrhnJC2mHebqQQ622UN2oyQoDZcaMzvlwt7nnX3bInF+SFrIj9/aCxtCUYY0oD2gaJkfd6aNJ0g==} - '@cropper/element-image@2.0.0-rc.1': - resolution: {integrity: sha512-ttzawKbUkR2A9U3bc2AN/jbNdszBP/yb83PIc5jekjOs+Z7kUBVdOo1SLIewpQ0DjUzhfCRXWUowP1McVQUXZw==} + '@cropper/element-image@2.0.0-rc.2': + resolution: {integrity: sha512-RTKnuJrqn1K8FscS11auit2W57AG04mxRNOxBldYs3lKTkwZjzJdQFkZ/Nxu+cwVXT+c6IeEiayNKvu4B7CAQg==} - '@cropper/element-selection@2.0.0-rc.1': - resolution: {integrity: sha512-AcRHRbsyt9xRfBD1QRyNDTS+vaYg6uAeuqhk/Ra58pqxlhtoimAV3oQ7uc/edwOlK60f/DxtKCc8rSOYFQ85bQ==} + '@cropper/element-selection@2.0.0-rc.2': + resolution: {integrity: sha512-UIgIHKHz4qNKlm5YRnC/Pu9+VrInm5TSOzkmU8kPt2swUk0WHNRv3ZcOjCQZ2ccTQnAH3FVM3FYDZ8HjRwLcBg==} - '@cropper/element-shade@2.0.0-rc.1': - resolution: {integrity: sha512-nHv2WujETENoIfxWQn7TYiOnXm5YUnZsoG4r6njK5cxj0gIUfPudUSbjWCQSuB2oxxpeEK8oyTdfOZtP9cxK4g==} + '@cropper/element-shade@2.0.0-rc.2': + resolution: {integrity: sha512-vHAGFxlqgflGZWkRYNWNHUY0zsV72YZGmCgtUu4sMrnWLZL/jMGhxmm8zZCe/aB94F829XcQ6uf3BoiApB+7Ng==} - '@cropper/element-viewer@2.0.0-rc.1': - resolution: {integrity: sha512-xTj0BObCygbVWXc7t7FYZ9k2eFyWN360it5uGeAkImXcwINRQGTFcLLOjs6i3SwedI7F1a1yNcTBfoT1B/sNAg==} + '@cropper/element-viewer@2.0.0-rc.2': + resolution: {integrity: sha512-2z9mIA7ic3enNS4xvq9Gq6hnRZ1tPr0h+lCrOHP55NL4he63lE9oTVJfDx19rL95wUS4VxL2ANvr2BVLNiBM7A==} - '@cropper/element@2.0.0-rc.1': - resolution: {integrity: sha512-OPKgjUgYC2Xmv77vEqtAR6bdfKOW+v9FrSjr4re3u95rcVj6NJ0JidIta41Ipp8KydHTXSmLetq4XDrA+vuIJQ==} + '@cropper/element@2.0.0-rc.2': + resolution: {integrity: sha512-4G6lTJblndwzpsb43YKeHiKcocOkDIWystGzbHNbqRysE0U0lYHuRyvV7FW6a9S63wtMFSYuwFxcdUdUcmkF8w==} - '@cropper/elements@2.0.0-rc.1': - resolution: {integrity: sha512-6qbtCq3iL3dETVav2XA03a8iLkHXWMIqHFxViMjlLr9CSuDjjaS5wp0JDuGtPv5FHxjsjyQ8Yayt8Ak5p09Zxg==} + '@cropper/elements@2.0.0-rc.2': + resolution: {integrity: sha512-NG5kdqpv7/tGvUfNjJiIHr2Ip431v5t/P5cIXTcYAgt8PRyFJmjx3fatC7NLnP/FUlv+bbzd8PMRI4LY4Gaw3Q==} - '@cropper/utils@2.0.0-rc.1': - resolution: {integrity: sha512-kreB3wdrAhmTEscfB8/j7ksGBgYSKN+28t37CAI0Vb5DvX/aUDPDH+3e2kyD7YE+DIZgdnuY2FsMYJAQ9sTThg==} + '@cropper/utils@2.0.0-rc.2': + resolution: {integrity: sha512-EEivNsyV6BtL496m4Q/IeAC6FGlyKjKIT1qMtwaxtkR+2ZlKnf9O7AdcGpClemIBA+TbwWAzp0UyIvYFtKUZ1Q==} - '@cypress/request@3.0.0': - resolution: {integrity: sha512-GKFCqwZwMYmL3IBoNeR2MM1SnxRIGERsQOTWeQKoYBt2JLqcqiy7JXqO894FLrpjZYqGxW92MNwRH2BN56obdQ==} + '@cypress/request@3.0.5': + resolution: {integrity: sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==} engines: {node: '>= 6'} '@cypress/xvfb@1.2.4': @@ -2219,20 +2004,11 @@ packages: resolution: {integrity: sha512-Ahk1N+s7urkgj7WvvUND5f8GiWEPfUw0D41hdElaqLgu8wZScI8gdI0q+qWw5N1d35x7GCRH2uk9mi+Uzo9M3g==} engines: {node: '>=14.0'} - '@discordapp/twemoji@15.0.3': - resolution: {integrity: sha512-5t0LLrNaSqViG0cSaomWwfR0+3fWqok+xLq40M8hJHxNX7s8gIoyNZYybQJo+s5/rGMjgdldpt8Ox8MapGvBUA==} + '@discordapp/twemoji@15.1.0': + resolution: {integrity: sha512-QdpV4ifTONAXvDjRrMohausZeGrQ1ac/Ox6togUh6Xl3XKJ/KAaMMuAEi0qsb0wDwoVTSZBll5Y6+N3hB2ktBw==} - '@discoveryjs/json-ext@0.5.7': - resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} - engines: {node: '>=10.0.0'} - - '@emnapi/runtime@1.1.1': - resolution: {integrity: sha512-3bfqkzuR1KLx57nZfjr2NLnFOobvyS0aTszaEGCGqmYMVDRaGvgIZbjGSV/MHSSmLgQ/b9JFHQ5xm5WRZYd+XQ==} - - '@emotion/use-insertion-effect-with-fallbacks@1.0.1': - resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==} - peerDependencies: - react: '>=16.8.0' + '@emnapi/runtime@1.2.0': + resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==} '@esbuild/aix-ppc64@0.19.11': resolution: {integrity: sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==} @@ -2252,6 +2028,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.18.20': resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -2276,6 +2058,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.18.20': resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} engines: {node: '>=12'} @@ -2300,6 +2088,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.18.20': resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} engines: {node: '>=12'} @@ -2324,6 +2118,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.18.20': resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} engines: {node: '>=12'} @@ -2348,6 +2148,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.18.20': resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} engines: {node: '>=12'} @@ -2372,6 +2178,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.18.20': resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} engines: {node: '>=12'} @@ -2396,6 +2208,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.18.20': resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} engines: {node: '>=12'} @@ -2420,6 +2238,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.18.20': resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} engines: {node: '>=12'} @@ -2444,6 +2268,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.18.20': resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} engines: {node: '>=12'} @@ -2468,6 +2298,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.18.20': resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} engines: {node: '>=12'} @@ -2492,6 +2328,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.18.20': resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} engines: {node: '>=12'} @@ -2516,6 +2358,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.18.20': resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} engines: {node: '>=12'} @@ -2540,6 +2388,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.18.20': resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} engines: {node: '>=12'} @@ -2564,6 +2418,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.18.20': resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} engines: {node: '>=12'} @@ -2588,6 +2448,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.18.20': resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} engines: {node: '>=12'} @@ -2612,6 +2478,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.18.20': resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} engines: {node: '>=12'} @@ -2636,6 +2508,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-x64@0.18.20': resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} engines: {node: '>=12'} @@ -2660,12 +2538,24 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.23.0': resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.18.20': resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} engines: {node: '>=12'} @@ -2690,6 +2580,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.18.20': resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} engines: {node: '>=12'} @@ -2714,6 +2610,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.18.20': resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} engines: {node: '>=12'} @@ -2738,6 +2640,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.18.20': resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} engines: {node: '>=12'} @@ -2762,6 +2670,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.18.20': resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} engines: {node: '>=12'} @@ -2786,6 +2700,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2808,10 +2728,18 @@ packages: resolution: {integrity: sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.18.0': + resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@3.1.0': resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.11.0': + resolution: {integrity: sha512-LPkkenkDqyzTFauZLLAPhIb48fj6drrfMvRGSL9tS3AcZBSVTllemLSNyCvHNNL2t797S/6DJNSIwRwXgMO/eQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.8.0': resolution: {integrity: sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2820,65 +2748,78 @@ packages: resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@fal-works/esbuild-plugin-global-externals@2.1.2': - resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==} + '@eslint/plugin-kit@0.2.0': + resolution: {integrity: sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@fastify/accept-negotiator@1.0.0': resolution: {integrity: sha512-4R/N2KfYeld7A5LGkai+iUFMahXcxxYbDp+XS2B1yuL3cdmZLJ9TlCnNzT3q5xFTqsYm0GPpinLUwfSwjcVjyA==} engines: {node: '>=14'} - '@fastify/accepts@4.3.0': - resolution: {integrity: sha512-QK4FoqXdwwPmaPOLL6NrxsyaXVvdviYVoS6ltHyOLdFlUyREIaMykHQIp+x0aJz9hB3B3n/Ht6QRdvBeGkptGQ==} + '@fastify/accept-negotiator@2.0.0': + resolution: {integrity: sha512-/Sce/kBzuTxIq5tJh85nVNOq9wKD8s+viIgX0fFMDBdw95gnpf53qmF1oBgJym3cPFliWUuSloVg/1w/rH0FcQ==} - '@fastify/ajv-compiler@3.5.0': - resolution: {integrity: sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==} + '@fastify/accepts@5.0.0': + resolution: {integrity: sha512-5wpgycrn+DXPkATGqUbXY9tyqLNgxo9S8f0EHUyIWvUacor2cXa3liYZggsqoyMXgpIqUbGLPBl+dN2hRcU9jQ==} + + '@fastify/ajv-compiler@4.0.0': + resolution: {integrity: sha512-dt0jyLAlay14LpIn4Fg1SY7V5NJ9KH0YFDpYVQY5cgIVBvdI8908AMx5zQ0bBYPGT6Wh+bM3f2caMmOXLP3QsQ==} '@fastify/busboy@2.1.0': resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==} engines: {node: '>=14'} - '@fastify/cookie@9.3.1': - resolution: {integrity: sha512-h1NAEhB266+ZbZ0e9qUE6NnNR07i7DnNXWG9VbbZ8uC6O/hxHpl+Zoe5sw1yfdZ2U6XhToUGDnzQtWJdCaPwfg==} + '@fastify/busboy@3.0.0': + resolution: {integrity: sha512-83rnH2nCvclWaPQQKvkJ2pdOjG4TZyEVuFDnlOF6KP08lDaaceVyw/W63mDuafQT+MKHCvXIPpE5uYWeM0rT4w==} - '@fastify/cors@9.0.1': - resolution: {integrity: sha512-YY9Ho3ovI+QHIL2hW+9X4XqQjXLjJqsU+sMV/xFsxZkE8p3GNnYVFpoOxF7SsP5ZL76gwvbo3V9L+FIekBGU4Q==} + '@fastify/cookie@10.0.0': + resolution: {integrity: sha512-S43spazwAfzm5nKlqq/spAGW+O6r+WQzg5vXXI1ArCXXFa8KBA/tiU3XRVQUehSNtbN5PA6+g183hzh5/dZ6Iw==} - '@fastify/deepmerge@1.3.0': - resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} + '@fastify/cors@10.0.0': + resolution: {integrity: sha512-kb9fkc/LVbLTQ3lhA+ZZjC/Styzysodo/MTCdVCvTtgHa/gBwxrEEkcp3fuoKIfAQt85wksrpXjUGbw5NQffEQ==} - '@fastify/error@3.4.0': - resolution: {integrity: sha512-e/mafFwbK3MNqxUcFBLgHhgxsF8UT1m8aj0dAlqEa2nJEgPsRtpHTZ3ObgrgkZ2M1eJHPTwgyUl/tXkvabsZdQ==} + '@fastify/deepmerge@2.0.0': + resolution: {integrity: sha512-fsaybTGDyQ5KpPsplQqb9yKdCf2x/pbNpMNk8Tvp3rRz7lVcupKysH4b2ELMN2P4Hak1+UqTYdTj/u4FNV2p0g==} - '@fastify/express@3.0.0': - resolution: {integrity: sha512-Ug6aulXCUiHgMyrHVYQqnQbGdsAV0aTad6nZxbOr6w3QjKn1mdQS3Kyzvc+I0xMjZ9yIyMUWHSooHgZ0l7nOng==} + '@fastify/error@4.0.0': + resolution: {integrity: sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA==} - '@fastify/fast-json-stringify-compiler@4.3.0': - resolution: {integrity: sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==} + '@fastify/express@4.0.0': + resolution: {integrity: sha512-e+IMKKV9+HRCVm7LVW8PaMrpEerHfqNLpRkbiVHYfVm0xeOphiwyNEoge4VA3Sh8gubtDfo9yKkpRzx6gx63kg==} - '@fastify/http-proxy@9.5.0': - resolution: {integrity: sha512-1iqIdV10d5k9YtfHq9ylX5zt1NiM50fG+rIX40qt00R694sqWso3ukyTFZVk33SDoSiBW8roB7n11RUVUoN+Ag==} + '@fastify/fast-json-stringify-compiler@5.0.0': + resolution: {integrity: sha512-tywfuZfXsyxLC5kEqrMubbFa9vpAxNtuPE7j9w5si1r+6p5b981pDfZ5Y8HBqmjDQl+PABT7cV5jZgXI2j+I5g==} - '@fastify/multipart@8.3.0': - resolution: {integrity: sha512-A8h80TTyqUzaMVH0Cr9Qcm6RxSkVqmhK/MVBYHYeRRSUbUYv08WecjWKSlG2aSnD4aGI841pVxAjC+G1GafUeQ==} + '@fastify/http-proxy@10.0.0': + resolution: {integrity: sha512-n5/EPspNKtzpCUavuDflYtvtB+aEkablb2sZM83gDKbxM9GF+93maJYQrGozJ2HNRqpt7wfzsDeUuGVFFkYzMQ==} - '@fastify/reply-from@9.0.1': - resolution: {integrity: sha512-q9vFNUiXZTY1x8omDPe59os2MYq+3y7KgO/kZoXpZlnud+45Nd8Ot/svEvrUATzjkizIggfS4K8LR9zXDyZZKg==} + '@fastify/merge-json-schemas@0.1.1': + resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==} + + '@fastify/multipart@9.0.0': + resolution: {integrity: sha512-B/rzOl1wmkj4LddH2i+zR8Gke8ZX1J8D7n4uJeis5VdIa7OR9Ys/TzUxI0/h1SF9ubHlNhBP+eO/FwnftarP9w==} + + '@fastify/reply-from@11.0.0': + resolution: {integrity: sha512-dv3o8hyy4sxhg1RN9l6ueM+PMMaIPKLjtL2T99H5M7h1Xt8d1RX3r+xC+sL5AqJqLvReX4N+7mTq9QDeB8i6Lg==} '@fastify/send@2.0.1': resolution: {integrity: sha512-8jdouu0o5d0FMq1+zCKeKXc1tmOQ5tTGYdQP3MpyF9+WWrZT1KCBdh6hvoEYxOm3oJG/akdE9BpehLiJgYRvGw==} + '@fastify/send@3.1.1': + resolution: {integrity: sha512-LdiV2mle/2tH8vh6GwGl0ubfUAgvY+9yF9oGI1iiwVyNUVOQamvw5n+OFu6iCNNoyuCY80FFURBn4TZCbTe8LA==} + '@fastify/static@6.12.0': resolution: {integrity: sha512-KK1B84E6QD/FcQWxDI2aiUCwHxMJBI1KeCUzm1BwYpPY1b742+jeKruGHP2uOluuM6OkBPI8CIANrXcCRtC2oQ==} - '@fastify/static@7.0.4': - resolution: {integrity: sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==} + '@fastify/static@8.0.0': + resolution: {integrity: sha512-VKGn1PQslB2VqzspyMKPu9xasF9vj+YuyGhVLb1ih6V60VVcRvcf0fFRcl3opt6c6YWwhKKdTUTfVE6COnpw6A==} + + '@fastify/view@10.0.0': + resolution: {integrity: sha512-2KnfgpSbAImKV5kKdNAkSyjV+9kYUYLvgDLx/wlzgqel92bN9Z520cwG3g3bAkr0yVnEJu62dIm2qAL9FASS1w==} '@fastify/view@8.2.0': resolution: {integrity: sha512-hBSiBofCnJNlPHEMZWpO1SL84eqOaqujJ1hR3jntFyZZCkweH5jMs12DKYyGesjVll7SJFRRxPUBB8kmUmneRQ==} - '@fastify/view@9.1.0': - resolution: {integrity: sha512-jRTGDljs/uB2p8bf6c1x4stGjP7H84VQkhbtDgCx55Mxf9Fplud5UZIHubvL4BTTX8jNYEzP1FpNAOBi7vibxg==} - '@github/webauthn-json@2.1.1': resolution: {integrity: sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ==} hasBin: true @@ -2916,116 +2857,108 @@ packages: resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} engines: {node: '>=18.18'} - '@img/sharp-darwin-arm64@0.33.4': - resolution: {integrity: sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA==} - engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.33.4': - resolution: {integrity: sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw==} - engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.0.2': - resolution: {integrity: sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==} - engines: {macos: '>=11', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.0.2': - resolution: {integrity: sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==} - engines: {macos: '>=10.13', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.0.2': - resolution: {integrity: sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==} - engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.0.2': - resolution: {integrity: sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==} - engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] os: [linux] - '@img/sharp-libvips-linux-s390x@1.0.2': - resolution: {integrity: sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==} - engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.0.2': - resolution: {integrity: sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==} - engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.0.2': - resolution: {integrity: sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==} - engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.0.2': - resolution: {integrity: sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==} - engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.33.4': - resolution: {integrity: sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q==} - engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm@0.33.4': - resolution: {integrity: sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ==} - engines: {glibc: '>=2.28', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - '@img/sharp-linux-s390x@0.33.4': - resolution: {integrity: sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==} - engines: {glibc: '>=2.31', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - '@img/sharp-linux-x64@0.33.4': - resolution: {integrity: sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw==} - engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.33.4': - resolution: {integrity: sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ==} - engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-x64@0.33.4': - resolution: {integrity: sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw==} - engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-wasm32@0.33.4': - resolution: {integrity: sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-win32-ia32@0.33.4': - resolution: {integrity: sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.33.4': - resolution: {integrity: sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] @@ -3045,18 +2978,6 @@ packages: resolution: {integrity: sha512-Pe3PFccjPVJV1vtlfVvm9OnlbxqdnP5QcscFEFEnK5quChf1ufZtM0r8mR5ToWHMxZOh0s8o/qp9ANGRTo/DAw==} engines: {node: '>=18'} - '@intlify/core-base@9.13.1': - resolution: {integrity: sha512-+bcQRkJO9pcX8d0gel9ZNfrzU22sZFSA0WVhfXrf5jdJOS24a+Bp8pozuS9sBI9Hk/tGz83pgKfmqcn/Ci7/8w==} - engines: {node: '>= 16'} - - '@intlify/message-compiler@9.13.1': - resolution: {integrity: sha512-SKsVa4ajYGBVm7sHMXd5qX70O2XXjm55zdZB3VeMFCvQyvLew/dLvq3MqnaIsTMF1VkkOb9Ttr6tHcMlyPDL9w==} - engines: {node: '>= 16'} - - '@intlify/shared@9.13.1': - resolution: {integrity: sha512-u3b6BKGhE6j/JeRU6C/RL2FgyJfy6LakbtfeVF8fJXURpZZTzfh3e05J0bu0XPw447Q6/WUp3C4ajv4TMS4YsQ==} - engines: {node: '>= 16'} - '@ioredis/commands@1.2.0': resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} @@ -3180,6 +3101,9 @@ packages: '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/trace-mapping@0.3.18': resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} @@ -3203,6 +3127,10 @@ packages: resolution: {integrity: sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==} engines: {node: '>=8'} + '@lukeed/ms@2.0.2': + resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} + engines: {node: '>=8'} + '@mapbox/node-pre-gyp@1.0.9': resolution: {integrity: sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==} hasBin: true @@ -3219,11 +3147,11 @@ packages: '@types/react': '>=16' react: '>=16' - '@microsoft/api-extractor-model@7.29.4': - resolution: {integrity: sha512-LHOMxmT8/tU1IiiiHOdHFF83Qsi+V8d0kLfscG4EvQE9cafiR8blOYr8SfkQKWB1wgEilQgXJX3MIA4vetDLZw==} + '@microsoft/api-extractor-model@7.29.8': + resolution: {integrity: sha512-t3Z/xcO6TRbMcnKGVMs4uMzv/gd5j0NhMiJIGjD4cJMeFJ1Hf8wnLSx37vxlRlL0GWlGJhnFgxvnaL6JlS+73g==} - '@microsoft/api-extractor@7.47.4': - resolution: {integrity: sha512-HKm+P4VNzWwvq1Ey+Jfhhj/3MjsD+ka2hbt8L5AcRM95lu1MFOYnz3XlU7Gr79Q/ZhOb7W/imAKeYrOI0bFydg==} + '@microsoft/api-extractor@7.47.9': + resolution: {integrity: sha512-TTq30M1rikVsO5wZVToQT/dGyJY7UXJmjiRtkHPLb74Prx3Etw8+bX7Bv7iLuby6ysb7fuu1NFWqma+csym8Jw==} hasBin: true '@microsoft/tsdoc-config@0.17.0': @@ -3289,66 +3217,70 @@ packages: resolution: {integrity: sha512-3rDakgJZ77+RiQUuSK69t1F0m8BQKA8Vh5DCS5V0DWvNY67zob2JhhQrhCO0AKLGINTRSFd1tBaHcJTkhefoSw==} engines: {node: '>=18'} - '@napi-rs/canvas-android-arm64@0.1.53': - resolution: {integrity: sha512-2YhxfVsZguATlRWE0fZdTx35SE9+r5D7HV5GPNDataZOKmHf+zZ5//dspuuBSbOriQdoicaFrgXKCUqI0pK3WQ==} + '@mswjs/interceptors@0.35.8': + resolution: {integrity: sha512-PFfqpHplKa7KMdoQdj5td03uG05VK2Ng1dG0sP4pT9h0dGSX2v9txYt/AnrzPb/vAmfyBBC0NQV7VaBEX+efgQ==} + engines: {node: '>=18'} + + '@napi-rs/canvas-android-arm64@0.1.56': + resolution: {integrity: sha512-xBGqW2RZMAupkzar9t3gpbok9r524f3Wlk4PG2qnQdxbsiEND06OB8VxVtTcql6R02uJpXJGnyIhN02Te+GMVQ==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@napi-rs/canvas-darwin-arm64@0.1.53': - resolution: {integrity: sha512-ls+CWLMusf4RAGo5BvIIzA6dNcc0elwVp6LKjHfQECHA8KKmvdB58YuE5BQcTlb2rzk0SEKtBC/Th3NI2oNdfg==} + '@napi-rs/canvas-darwin-arm64@0.1.56': + resolution: {integrity: sha512-Pvuz6Ib9YZTB5MlGL9WSu9a2asUC0DZ1zBHozDiBXr/6Zurs9l/ZH5NxFYTM829BpkdkO8kuI8b8Rz7ek30zzQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@napi-rs/canvas-darwin-x64@0.1.53': - resolution: {integrity: sha512-ZAgcoCH5+5OKS2P8Lxx+jbkAPKkyLD2x6OvSrHg1U6ppdxmLA+CkJlRl8w45HCXwuyIiP7OeymECRtiNYTwznQ==} + '@napi-rs/canvas-darwin-x64@0.1.56': + resolution: {integrity: sha512-O393jWt7G6rg0X1ralbsbBeskSG0iwlkD7mEHhMLJxqRqe+eQn0/xnwhs9l6dUNFC+5dM8LOvfFca4o9Vs2Vww==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.53': - resolution: {integrity: sha512-p9km/3C/loDxu3AvA8/vtpIS1BGMd/Ehkl2Iu/v/Gw8N/KUIt3HUvTS7AKApyVE28bxTfq96wJQjtcT8jzDncw==} + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.56': + resolution: {integrity: sha512-30NFb5lrF3YEwAO5XuATxpWDSXaBAgaFVswPJ+hYcAUyE3IkPPIFRY4ijQEh4frcSBvrzFGGYdNSoC18oLLWaQ==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@napi-rs/canvas-linux-arm64-gnu@0.1.53': - resolution: {integrity: sha512-QKK+sykEiYwjwd+ogyLcpcnH38DNZ8KViBlnfEpoGA2Wa+21/cWQKfMxnbgb/rbvm5tazJinZcihFvH577WQ5g==} + '@napi-rs/canvas-linux-arm64-gnu@0.1.56': + resolution: {integrity: sha512-ODbWH9TLvba+39UxFwPn2Hm1ImALmWOZ0pEv5do/pz0439326Oz49hlfGot4KmkSBeKK81knWxRj9EXMSPwXPg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@napi-rs/canvas-linux-arm64-musl@0.1.53': - resolution: {integrity: sha512-2N41U0X8RnrTKzpTtPv1ozlYkJtPsUdbfF3uP/KEd/BsULGd8Y8ghkGMS6CM+821au4ex0dPrWOOdT9wC1rSqQ==} + '@napi-rs/canvas-linux-arm64-musl@0.1.56': + resolution: {integrity: sha512-zqE4nz8CWiJJ0q5By7q9CDPicNkc0oyErgavK3ZV279zJL7Aapd3cIqayT6ynECArg7GgBl2WYSvr5AaRFmYgg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@napi-rs/canvas-linux-x64-gnu@0.1.53': - resolution: {integrity: sha512-7XjuTvDKCODtf/vMwF43VGDrjfgwYKgS91ggdcX3UrJaBYWyWu/+eqNvNj+zdXSe/0x+YOjf5jG4m8xIXdBMQA==} + '@napi-rs/canvas-linux-x64-gnu@0.1.56': + resolution: {integrity: sha512-JTnGAtJBQMhfSpN8/rbMnf5oxuO/juUNa0n4LA0LlW0JS9UBpmsS2BwFNCakFqOeAPaqIM6sFFsK3M4hve+Esw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@napi-rs/canvas-linux-x64-musl@0.1.53': - resolution: {integrity: sha512-970WEvB8vmj+uxvgdBZ+AGFV7uq9GJhXrqG5PGQ5lWciHX0P0d/OhS2F7TITgFR0LsKDQZ7XQgzMxsYOfwZ0FQ==} + '@napi-rs/canvas-linux-x64-musl@0.1.56': + resolution: {integrity: sha512-mpws7DhVDIj8ZKa/qcnUVLAm0fxD9RK5ojfNNSI9TOzn2E0f+GUXx8sGsCxDpMVMtN+mtyrMwRqH3F3rTUMWXw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@napi-rs/canvas-win32-x64-msvc@0.1.53': - resolution: {integrity: sha512-rLFQCSJaWg/sv54Aap9nAhaodi4Vyb4un50EgW+PNkk8icMziU6KLRKirGBdQr9ZdxnshAPeQXD1g2ArStujKA==} + '@napi-rs/canvas-win32-x64-msvc@0.1.56': + resolution: {integrity: sha512-VKAAkgXF+lbFvRFawPOtkfV/P7ogAgWTu5FMCIiBn0Gc3vnkKFG2cLo/IHIJ7FuriToKEidkJGT88iAh7W7GDA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@napi-rs/canvas@0.1.53': - resolution: {integrity: sha512-XsEZi97+kKykmAiPpY+IpZoHxJY1srqFZp8jDt1/RySzC0kB0iZYt/VMIFqQKpLCARZjD7SOAz2AULtwYlesCA==} + '@napi-rs/canvas@0.1.56': + resolution: {integrity: sha512-SujSchzG6lLc/wT+Mwxam/w30Kk2sFTiU6bLFcidecKSmlhenAhGMQhZh2iGFfKoh2+8iit0jrt99n6TqReICQ==} engines: {node: '>= 10'} - '@nestjs/common@10.3.10': - resolution: {integrity: sha512-H8k0jZtxk1IdtErGDmxFRy0PfcOAUg41Prrqpx76DQusGGJjsaovs1zjXVD1rZWaVYchfT1uczJ6L4Kio10VNg==} + '@nestjs/common@10.4.3': + resolution: {integrity: sha512-4hbLd3XIJubHSylYd/1WSi4VQvG68KM/ECYpMDqA3k3J1/T17SAg40sDoq3ZoO5OZgU0xuNyjuISdOTjs11qVg==} peerDependencies: class-transformer: '*' class-validator: '*' @@ -3360,8 +3292,8 @@ packages: class-validator: optional: true - '@nestjs/core@10.3.10': - resolution: {integrity: sha512-ZbQ4jovQyzHtCGCrzK5NdtW1SYO2fHSsgSY1+/9WdruYCUra+JDkWEXgZ4M3Hv480Dl3OXehAmY1wCOojeMyMQ==} + '@nestjs/core@10.4.3': + resolution: {integrity: sha512-6OQz+5C8mT8yRtfvE5pPCq+p6w5jDot+oQku1KzQ24ABn+lay1KGuJwcKZhdVNuselx+8xhdMxknZTA8wrGLIg==} peerDependencies: '@nestjs/common': ^10.0.0 '@nestjs/microservices': ^10.0.0 @@ -3377,14 +3309,14 @@ packages: '@nestjs/websockets': optional: true - '@nestjs/platform-express@10.3.10': - resolution: {integrity: sha512-wK2ow3CZI2KFqWeEpPmoR300OB6BcBLxARV1EiClJLCj4S1mZsoCmS0YWgpk3j1j6mo0SI8vNLi/cC2iZPEPQA==} + '@nestjs/platform-express@10.4.3': + resolution: {integrity: sha512-ss7gkofVm3eO+1P9iRhmGq6Xcjg+mIN3dWisKJZYelSV+msb0QpJmqChLvWjLkWtlqDnx915FKUk0IzCa0TVzw==} peerDependencies: '@nestjs/common': ^10.0.0 '@nestjs/core': ^10.0.0 - '@nestjs/testing@10.3.10': - resolution: {integrity: sha512-i3HAtVQJijxNxJq1k39aelyJlyEIBRONys7IipH/4r8W0J+M1V+y5EKDOyi4j1SdNSb/vmNyWpZ2/ewZjl3kRA==} + '@nestjs/testing@10.4.3': + resolution: {integrity: sha512-SBNWrMU51YAlYmW86wyjlGZ2uLnASNiOPD0lBcNIlxxei0b05/aI3nh7OPuxbXQUdedUJfPq2d2jZj4TRG4S0w==} peerDependencies: '@nestjs/common': ^10.0.0 '@nestjs/core': ^10.0.0 @@ -3648,12 +3580,16 @@ packages: '@readme/json-schema-ref-parser@1.2.0': resolution: {integrity: sha512-Bt3QVovFSua4QmHa65EHUmh2xS0XJ3rgTEUPH998f4OW4VVJke3BuS16f+kM0ZLOGdvIrzrPRqwihuv5BAjtrA==} - '@readme/openapi-parser@2.5.0': - resolution: {integrity: sha512-IbymbOqRuUzoIgxfAAR7XJt2FWl6n2yqN09fF5adacGm7W03siA3bj1Emql0X9D2T+RpBYz3x9zDsMhuoMP62A==} - engines: {node: '>=14'} + '@readme/openapi-parser@2.6.0': + resolution: {integrity: sha512-pyFJXezWj9WI1O+gdp95CoxfY+i+Uq3kKk4zXIFuRAZi9YnHpHOpjumWWr67wkmRTw19Hskh9spyY0Iyikf3fA==} + engines: {node: '>=18'} peerDependencies: openapi-types: '>=7' + '@readme/openapi-schemas@3.1.0': + resolution: {integrity: sha512-9FC/6ho8uFa8fV50+FPy/ngWN53jaUu4GRXlAjcxIRrzhltJnpKkBG2Tp0IDraFJeWrOpk84RJ9EMEEYzaI1Bw==} + engines: {node: '>=18'} + '@rollup/plugin-json@6.1.0': resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} engines: {node: '>=14.0.0'} @@ -3681,88 +3617,91 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.19.1': - resolution: {integrity: sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww==} + '@rollup/rollup-android-arm-eabi@4.22.2': + resolution: {integrity: sha512-8Ao+EDmTPjZ1ZBABc1ohN7Ylx7UIYcjReZinigedTOnGFhIctyGPxY2II+hJ6gD2/vkDKZTyQ0e7++kwv6wDrw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.19.1': - resolution: {integrity: sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A==} + '@rollup/rollup-android-arm64@4.22.2': + resolution: {integrity: sha512-I+B1v0a4iqdS9DvYt1RJZ3W+Oh9EVWjbY6gp79aAYipIbxSLEoQtFQlZEnUuwhDXCqMxJ3hluxKAdPD+GiluFQ==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.19.1': - resolution: {integrity: sha512-8o6eqeFZzVLia2hKPUZk4jdE3zW7LCcZr+MD18tXkgBBid3lssGVAYuox8x6YHoEPDdDa9ixTaStcmx88lio5Q==} + '@rollup/rollup-darwin-arm64@4.22.2': + resolution: {integrity: sha512-BTHO7rR+LC67OP7I8N8GvdvnQqzFujJYWo7qCQ8fGdQcb8Gn6EQY+K1P+daQLnDCuWKbZ+gHAQZuKiQkXkqIYg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.19.1': - resolution: {integrity: sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA==} + '@rollup/rollup-darwin-x64@4.22.2': + resolution: {integrity: sha512-1esGwDNFe2lov4I6GsEeYaAMHwkqk0IbuGH7gXGdBmd/EP9QddJJvTtTF/jv+7R8ZTYPqwcdLpMTxK8ytP6k6Q==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.19.1': - resolution: {integrity: sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==} + '@rollup/rollup-linux-arm-gnueabihf@4.22.2': + resolution: {integrity: sha512-GBHuY07x96OTEM3OQLNaUSUwrOhdMea/LDmlFHi/HMonrgF6jcFrrFFwJhhe84XtA1oK/Qh4yFS+VMREf6dobg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.19.1': - resolution: {integrity: sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==} + '@rollup/rollup-linux-arm-musleabihf@4.22.2': + resolution: {integrity: sha512-Dbfa9Sc1G1lWxop0gNguXOfGhaXQWAGhZUcqA0Vs6CnJq8JW/YOw/KvyGtQFmz4yDr0H4v9X248SM7bizYj4yQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.19.1': - resolution: {integrity: sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==} + '@rollup/rollup-linux-arm64-gnu@4.22.2': + resolution: {integrity: sha512-Z1YpgBvFYhZIyBW5BoopwSg+t7yqEhs5HCei4JbsaXnhz/eZehT18DaXl957aaE9QK7TRGFryCAtStZywcQe1A==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.19.1': - resolution: {integrity: sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==} + '@rollup/rollup-linux-arm64-musl@4.22.2': + resolution: {integrity: sha512-66Zszr7i/JaQ0u/lefcfaAw16wh3oT72vSqubIMQqWzOg85bGCPhoeykG/cC5uvMzH80DQa2L539IqKht6twVA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.19.1': - resolution: {integrity: sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==} + '@rollup/rollup-linux-powerpc64le-gnu@4.22.2': + resolution: {integrity: sha512-HpJCMnlMTfEhwo19bajvdraQMcAq3FX08QDx3OfQgb+414xZhKNf3jNvLFYKbbDSGBBrQh5yNwWZrdK0g0pokg==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.19.1': - resolution: {integrity: sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==} + '@rollup/rollup-linux-riscv64-gnu@4.22.2': + resolution: {integrity: sha512-/egzQzbOSRef2vYCINKITGrlwkzP7uXRnL+xU2j75kDVp3iPdcF0TIlfwTRF8woBZllhk3QaxNOEj2Ogh3t9hg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.19.1': - resolution: {integrity: sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==} + '@rollup/rollup-linux-s390x-gnu@4.22.2': + resolution: {integrity: sha512-qgYbOEbrPfEkH/OnUJd1/q4s89FvNJQIUldx8X2F/UM5sEbtkqZpf2s0yly2jSCKr1zUUOY1hnTP2J1WOzMAdA==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.19.1': - resolution: {integrity: sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==} + '@rollup/rollup-linux-x64-gnu@4.22.2': + resolution: {integrity: sha512-a0lkvNhFLhf+w7A95XeBqGQaG0KfS3hPFJnz1uraSdUe/XImkp/Psq0Ca0/UdD5IEAGoENVmnYrzSC9Y2a2uKQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.19.1': - resolution: {integrity: sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==} + '@rollup/rollup-linux-x64-musl@4.22.2': + resolution: {integrity: sha512-sSWBVZgzwtsuG9Dxi9kjYOUu/wKW+jrbzj4Cclabqnfkot8Z3VEHcIgyenA3lLn/Fu11uDviWjhctulkhEO60g==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.19.1': - resolution: {integrity: sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==} + '@rollup/rollup-win32-arm64-msvc@4.22.2': + resolution: {integrity: sha512-t/YgCbZ638R/r7IKb9yCM6nAek1RUvyNdfU0SHMDLOf6GFe/VG1wdiUAsxTWHKqjyzkRGg897ZfCpdo1bsCSsA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.19.1': - resolution: {integrity: sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ==} + '@rollup/rollup-win32-ia32-msvc@4.22.2': + resolution: {integrity: sha512-kTmX5uGs3WYOA+gYDgI6ITkZng9SP71FEMoHNkn+cnmb9Zuyyay8pf0oO5twtTwSjNGy1jlaWooTIr+Dw4tIbw==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.19.1': - resolution: {integrity: sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg==} + '@rollup/rollup-win32-x64-msvc@4.22.2': + resolution: {integrity: sha512-Yy8So+SoRz8I3NS4Bjh91BICPOSVgdompTIPYTByUqU66AXSIOgmW3Lv1ke3NORPqxdF+RdrZET+8vYai6f4aA==} cpu: [x64] os: [win32] - '@rushstack/node-core-library@5.5.1': - resolution: {integrity: sha512-ZutW56qIzH8xIOlfyaLQJFx+8IBqdbVCZdnj+XT1MorQ1JqqxHse8vbCpEM+2MjsrqcbxcgDIbfggB1ZSQ2A3g==} + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@rushstack/node-core-library@5.9.0': + resolution: {integrity: sha512-MMsshEWkTbXqxqFxD4gcIUWQOCeBChlGczdZbHfqmNZQFLHB3yWxDFSMHFUdu2/OB9NUk7Awn5qRL+rws4HQNg==} peerDependencies: '@types/node': '*' peerDependenciesMeta: @@ -3772,16 +3711,16 @@ packages: '@rushstack/rig-package@0.5.3': resolution: {integrity: sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==} - '@rushstack/terminal@0.13.3': - resolution: {integrity: sha512-fc3zjXOw8E0pXS5t9vTiIPx9gHA0fIdTXsu9mT4WbH+P3mYvnrX0iAQ5a6NvyK1+CqYWBTw/wVNx7SDJkI+WYQ==} + '@rushstack/terminal@0.14.2': + resolution: {integrity: sha512-2fC1wqu1VCExKC0/L+0noVcFQEXEnoBOtCIex1TOjBzEDWcw8KzJjjj7aTP6mLxepG0XIyn9OufeFb6SFsa+sg==} peerDependencies: '@types/node': '*' peerDependenciesMeta: '@types/node': optional: true - '@rushstack/ts-command-line@4.22.3': - resolution: {integrity: sha512-edMpWB3QhFFZ4KtSzS8WNjBgR4PXPPOVrOHMbb7kNpmQ1UFS9HdVtjCXg1H5fG+xYAbeE+TMPcVPUyX2p84STA==} + '@rushstack/ts-command-line@4.22.8': + resolution: {integrity: sha512-XbFjOoV7qZHJnSuFUHv0pKaFA4ixyCuki+xMjsMfDwfvQjs5MYG0IK5COal3tRnG7KCDe2l/G+9LrzYE/RJhgg==} '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -3823,6 +3762,9 @@ packages: '@sideway/address@4.1.4': resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} + '@sideway/address@4.1.5': + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + '@sideway/formula@3.0.1': resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} @@ -3851,10 +3793,6 @@ packages: resolution: {integrity: sha512-WDTlVTyvFivSOuyvMeedzg2hdoBLZ3f1uNVuEida2Rl9BrfjrIRjWA/VZIrMRLvSwJYCAlCRA3usDt1THytxWQ==} engines: {node: '>=18'} - '@sindresorhus/merge-streams@2.3.0': - resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} - engines: {node: '>=18'} - '@sindresorhus/merge-streams@4.0.0': resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} @@ -4107,99 +4045,97 @@ packages: '@sqltools/formatter@1.2.5': resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} - '@storybook/addon-actions@8.2.6': - resolution: {integrity: sha512-iCsf3V28/jJ95w2zd8aSvR4denoA2UYV3fpNCTGOURqICyKOG3cyVxvqKp8Hhcwn7trNOsK+HlL6q5gpv56ViA==} + '@storybook/addon-actions@8.3.2': + resolution: {integrity: sha512-Ds2lNyEpeVO0TexoXEHpE3kRcA7rJm5X5nWz4PdvF7kiC1aX5ZMy2qEPZOH6Jvalysm+PChw4Ib+lCaoIFGOJg==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/addon-backgrounds@8.2.6': - resolution: {integrity: sha512-61NFowA6EmCw+Eyzp0U4fat9MlPDdnT7aoDyzqSImLwWLITY9IvmWuTeo7XKJZN3fe22z1r7cZseKdYrtaHcKw==} + '@storybook/addon-backgrounds@8.3.2': + resolution: {integrity: sha512-5dPyynGRp2ZAZrpG2tadbdBk7X7GySoRuZwkQebNFGv+JZ8LoeQ/qc8yUOL+vfWKFGqvjOmX5R55IUHLYsw2NQ==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/addon-controls@8.2.6': - resolution: {integrity: sha512-EHUwHy+oZZv3pXzN7fuXWrS/meHFjqcELY3RBvOyEkGf21agl6co6R1tnf6d5N5QoYAGfIbDO7dkauSL2RfNAw==} + '@storybook/addon-controls@8.3.2': + resolution: {integrity: sha512-YHoSMWSR1fItPb5S/3gOIhn9T6HcWcTxEJrjuuDk1hySmBmA+ojVJqmcI5MoNG3XtGigSXGJ/K2wmU57wZH4xw==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/addon-docs@8.2.6': - resolution: {integrity: sha512-qe7hxntaezqjKdU9QS+Q9NFL6i/uNdBxdvOnCKgPhBAY/zY6yhk5t3sOvonynPK5nkaNAowfSNPIzNxAXlJ1sA==} + '@storybook/addon-docs@8.3.2': + resolution: {integrity: sha512-DPmWhvnHap8bmtiJOYpmo9MYpuJW5QyV6MhmGhpe60A9yH9TRTIf3h7uGpyX3TgtrYxC07Sw/8GaY0UfendJGg==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/addon-essentials@8.2.6': - resolution: {integrity: sha512-diGjGZcZNov+RCAVQBTm8JKP2kUtMRuJIQFBeXdPWpu6hYBk6lw1FlAf2GywWGCvdny1pJT90hfoD33qUMNuDg==} + '@storybook/addon-essentials@8.3.2': + resolution: {integrity: sha512-r0wnw5dbqeVklSjMkA5dTLufmm20IZSskSmadbXOOZBKFqANm15LRGdQ7+Pfr8N0XF4//tFwnvIfw+hMmKGFEQ==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/addon-highlight@8.2.6': - resolution: {integrity: sha512-03cV9USsfP3bS4wYV06DYcIaGPfoheQe53Q0Jr1B2yJUVyIPKvmO2nGjLBsqzeL3Wl7vSfLQn0/dUdxCcbqLsw==} + '@storybook/addon-highlight@8.3.2': + resolution: {integrity: sha512-JFL/JLBZfa89POgi8lBdt8TzzCS1bgN/X6Qj1MlTq3pxHYqO66eG8DtMLjpuXKOhs8Dhdgs9/uxy5Yd+MFVRmQ==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/addon-interactions@8.2.6': - resolution: {integrity: sha512-YXpHf8jWPz9HJV+Fw4GaunaCWeE6uqF24aLXdAd8xuhN1UfWJeNV6AwAvFQ0hTLqvmz0yMhX/5JXDKeKESoYDA==} + '@storybook/addon-interactions@8.3.2': + resolution: {integrity: sha512-1JeM7iErTxjMlhT1TzVpCmD6SR7QZu54paOQTCCywVpaQG/MoJ+L8MZA1YFufTzq1kpRRrde5yHj2PM0TnMdEg==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/addon-links@8.2.6': - resolution: {integrity: sha512-CUuU3nk8wyZ3bljCmOG/OCKazan+bPuNbCph8N763zyzdEx5M/CbBxV9d3pi3zjYpix7txlqrl2/YdMCejfyFw==} + '@storybook/addon-links@8.3.2': + resolution: {integrity: sha512-CHp/3XSB/AWyoP9b2tNaaKNTyftLPIPWqMhqhH1V5irjXhLDpBBEkmgbvB19xJ4qCfDjjOjokSLmSBaVOnzv2g==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.2.6 + storybook: ^8.3.2 peerDependenciesMeta: react: optional: true - '@storybook/addon-mdx-gfm@8.2.6': - resolution: {integrity: sha512-PFVfJeuydxlV1VmxEuKNQ7z2vCDvQtHa2GB0ANM11ahxDSUz8QxsO0Y/L3LOn2JjJGYiVFrsHAaC+8NW43iArQ==} + '@storybook/addon-mdx-gfm@8.3.2': + resolution: {integrity: sha512-KrkgJRre9ef1SlrvQJypjq86Sm3pFBKyWDp6+Db8EM/It28PHGZGxfve/CiUbxSBBWm0C1yDpGB71YG1TQMFMw==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/addon-measure@8.2.6': - resolution: {integrity: sha512-neI8YeSOAtOmzasLxo6O8ZLr2ebMaD7XVF+kYatl5+SpyuwwvUGcP9NkKe5S+mB8V2zxFUIsXS74XrhmQhRoaQ==} + '@storybook/addon-measure@8.3.2': + resolution: {integrity: sha512-5RPF2oEw5XnTmz2cvjqz2WGnqOrJ1NxXIuJc6QeO6EXQqqjPnj/9rV/MBmzMd9cjk8Ud8c4AA5+jJbl4IgcwhQ==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/addon-outline@8.2.6': - resolution: {integrity: sha512-uAlPtqDWlq7MQQ4zJT80qdjbSdLF/zsvtPhidX6h9cjLKNPWAv79xJQ14AJHaMv+Hzy5xKnM4wdEhgPbzKabQg==} + '@storybook/addon-outline@8.3.2': + resolution: {integrity: sha512-VxUYCHPCZQDwnj/9U4d6QLsfGi9wHGO0hOENjC5ZCwzMNCq6t7XNRToSsq4zUPucH5XKaQW2vyTdbNdUQiki4Q==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/addon-storysource@8.2.6': - resolution: {integrity: sha512-8H2kvRIM12oXN4kO/oowABu88IOY9Je7PphCUUs/nIfTIB+Ck1GrLxx5fCNNCSwUrTBEZY8bDOfxmkf9d4qngw==} + '@storybook/addon-storysource@8.3.2': + resolution: {integrity: sha512-CaCcLwZ9/YmYJ2DUdFTgAg4fX03hQF6RWWzfPMjiyCRWXeW918iBBP/EAHL8kuAt4qHlfYteXoMcbFaRgbqh0Q==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/addon-toolbars@8.2.6': - resolution: {integrity: sha512-0JmRirMpxHS6VZzBk0kY871xWTpkk3TN4S1sxoFf5fcnCfVTHDjEJ5Ws/QWru1RJlIZHuJKRdQIA6Vuq5X+KfQ==} + '@storybook/addon-toolbars@8.3.2': + resolution: {integrity: sha512-y3mokzvoeEE1ga96c8KX7anb9fU5wRGWZBsX7cQkm5ebXHsXjH2Y0pcdFnw6UxFbPMjh70LlZF9UhXnz7UC7Hw==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/addon-viewport@8.2.6': - resolution: {integrity: sha512-IAxH9H8tVFzSmZhKf5E+EALiAdkp19RzGqP/rWluD8LH7oW5HumQE/4oN0ZhVMy1RxYsCKFYjWyAp7AuxeMRSw==} + '@storybook/addon-viewport@8.3.2': + resolution: {integrity: sha512-AyXpQ2ntpRoNfOWPnaUX4CTWSj163ncgzcoUyBRWL/yiu/PcMK4tlQ141mWwoamAcXEVDK40Q0vWmRwZ06C2gw==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/blocks@8.2.6': - resolution: {integrity: sha512-nMlZJjVTyfOJ6xwORptsNuS1AZZlDbJUVXc2R8uukGd5GIXxxCdrPk4NvUsjfQslMT9LhYuFld3z62FATsM2rw==} + '@storybook/blocks@8.3.2': + resolution: {integrity: sha512-z6XTg5fC5XT/8vYYtFqVhQtBYw5MkSlkQF5HM1ntxlEesN4tGd14SjFd24nWuoAHq4G5D2D8KNt41IoNdzeD1A==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.2.6 + storybook: ^8.3.2 peerDependenciesMeta: react: optional: true react-dom: optional: true - '@storybook/builder-manager@8.1.11': - resolution: {integrity: sha512-U7bmed4Ayg+OlJ8HPmLeGxLTHzDY7rxmxM4aAs4YL01fufYfBcjkIP9kFhJm+GJOvGm+YJEUAPe5mbM1P/bn0Q==} - - '@storybook/builder-vite@8.1.11': - resolution: {integrity: sha512-hG4eoNMCPgjZ2Ai+zSmk69zjsyEihe75XbJXtYfGRqjMWtz2+SAUFO54fLc2BD5svcUiTeN+ukWcTrwApyPsKg==} + '@storybook/builder-vite@8.3.2': + resolution: {integrity: sha512-mq6T2J8gDiIuO8+nLBzQkMRncDb+zLiBmRrudwSNum3cFLPLDV1Y4JSzsoG/SjlQz1feUEqTO9by6i7wxKh+Cw==} peerDependencies: '@preact/preset-vite': '*' + storybook: ^8.3.2 typescript: '>= 4.3.x' vite: ^4.0.0 || ^5.0.0 vite-plugin-glimmerx: '*' @@ -4211,206 +4147,115 @@ packages: vite-plugin-glimmerx: optional: true - '@storybook/builder-vite@8.2.6': - resolution: {integrity: sha512-3PrsPZAedpQUbzRBEl23Fi1zG5bkQD76JsygVwmfiSm4Est4K8kW2AIB2ht9cIfKXh3mfQkyQlxXKHeQEHeQwQ==} + '@storybook/components@8.3.2': + resolution: {integrity: sha512-yB/ETNTNVZi8xvVsTMWvtiI4APRj2zzAa3nHyQO0X+DC4jjysT9D1ruL6jZJ/2DHMp7A9U6v2if83dby/kszfg==} peerDependencies: - '@preact/preset-vite': '*' - storybook: ^8.2.6 - typescript: '>= 4.3.x' - vite: ^4.0.0 || ^5.0.0 - vite-plugin-glimmerx: '*' - peerDependenciesMeta: - '@preact/preset-vite': - optional: true - typescript: - optional: true - vite-plugin-glimmerx: - optional: true + storybook: ^8.3.2 - '@storybook/channels@8.1.11': - resolution: {integrity: sha512-fu5FTqo6duOqtJFa6gFzKbiSLJoia+8Tibn3xFfB6BeifWrH81hc+AZq0lTmHo5qax2G5t8ZN8JooHjMw6k2RA==} - - '@storybook/client-logger@8.1.11': - resolution: {integrity: sha512-DVMh2usz3yYmlqCLCiCKy5fT8/UR9aTh+gSqwyNFkGZrIM4otC5A8eMXajXifzotQLT5SaOEnM3WzHwmpvMIEA==} - - '@storybook/codemod@8.2.6': - resolution: {integrity: sha512-+mFJ6R+JhJLpU7VPDlXU5Yn6nqIBq745GaEosnIiFOdNo3jaxJ58wq/sGhbQvoCHPUxMA+sDQvR7pS62YFoLRQ==} - - '@storybook/components@8.2.6': - resolution: {integrity: sha512-H8ckH1AnLkHtMtvJ3J8LxnmDtHxkJ7NJacGctHMRrsBIvdKTVwlT4su5nAVVJlan/PrEou+jESfw+OjjBYE5PA==} + '@storybook/core-events@8.3.2': + resolution: {integrity: sha512-Nf63X2MLIiw1Czc/zxZ1hWLCNr6+NujJb6Dy96pgcGYLiKduFi9nKPG5eP0VEXpPWFWOc7ccCPxZ+Iw0q+USPw==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/core-common@8.1.11': - resolution: {integrity: sha512-Ix0nplD4I4DrV2t9B+62jaw1baKES9UbR/Jz9LVKFF9nsua3ON0aVe73dOjMxFWBngpzBYWe+zYBTZ7aQtDH4Q==} + '@storybook/core@8.3.2': + resolution: {integrity: sha512-DVXs9AZzXHUKEhi5hKQ4gmH2ODFFM9hmd3odnlqenIINxGynbRtAGzU8pMhjrTRSrnlLr1liGew1IcY+hwkFjQ==} + + '@storybook/csf-plugin@8.3.2': + resolution: {integrity: sha512-9UvoBkYDLzf/0e2lQMPyBCJHrrEMxvhL7fraVX2c5OxwVUwgQnHlgNR3zxzw1Nr/AWyC5OKYlaE1eM10JVm2GA==} peerDependencies: - prettier: ^2 || ^3 - peerDependenciesMeta: - prettier: - optional: true - - '@storybook/core-events@8.1.11': - resolution: {integrity: sha512-vXaNe2KEW9BGlLrg0lzmf5cJ0xt+suPjWmEODH5JqBbrdZ67X6ApA2nb6WcxDQhykesWCuFN5gp1l+JuDOBi7A==} - - '@storybook/core-events@8.2.6': - resolution: {integrity: sha512-bmtm7sHBExKCSGiCIyhwfHKFIsdrRQqd8ZEb/iNWsR93AxHszcf/adYAVynencdWKipw1haIWBNaiDhnsOBVPA==} - peerDependencies: - storybook: ^8.2.6 - - '@storybook/core-server@8.1.11': - resolution: {integrity: sha512-L6dzQTmR0np/kagNONvvlm6lSvF1FNc9js3vxsEEPnEypLbhx8bDZaHmuhmBpYUzKyUMpRVQTE/WgjHLuBBuxA==} - - '@storybook/core@8.2.6': - resolution: {integrity: sha512-XY71g3AcpD6IiER9k9Lt+vlUMYfPIYgWekd7e0Ggzz2gJkPuLunKEdQccLGDSHf5OFAobHhrTJc7ZsvWhmDMag==} - - '@storybook/csf-plugin@8.1.11': - resolution: {integrity: sha512-hkA8gjFtSN/tabG0cuvmEqanMXtxPr3qTkp4UNSt1R6jBEgFHRG2y/KYLl367kDwOSFTT987ZgRfJJruU66Fvw==} - - '@storybook/csf-plugin@8.2.6': - resolution: {integrity: sha512-USn7E/bMQYVqvFBuW6d9rKoSuCImjk0BAmc/0wIOuMQ/yQNp2Xze0m8eVkNHUIUDokyx0TXDjRjwq10Xxk16ag==} - peerDependencies: - storybook: ^8.2.6 - - '@storybook/csf-tools@8.1.11': - resolution: {integrity: sha512-6qMWAg/dBwCVIHzANM9lSHoirwqSS+wWmv+NwAs0t9S94M75IttHYxD3IyzwaSYCC5llp0EQFvtXXAuSfFbibg==} + storybook: ^8.3.2 '@storybook/csf@0.1.11': resolution: {integrity: sha512-dHYFQH3mA+EtnCkHXzicbLgsvzYjcDJ1JWsogbItZogkPHgSJM/Wr71uMkcvw8v9mmCyP4NpXJuu6bPoVsOnzg==} - '@storybook/csf@0.1.9': - resolution: {integrity: sha512-JlZ6v/iFn+iKohKGpYXnMeNeTiiAMeFoDhYnPLIC8GnyyIWqEI9wJYrOK9i9rxlJ8NZAH/ojGC/u/xVC41qSgQ==} - - '@storybook/docs-mdx@3.1.0-next.0': - resolution: {integrity: sha512-t4syFIeSyufieNovZbLruPt2DmRKpbwL4fERCZ1MifWDRIORCKLc4NCEHy+IqvIqd71/SJV2k4B51nF7vlJfmQ==} - - '@storybook/docs-tools@8.1.11': - resolution: {integrity: sha512-mEXtR9rS7Y+OdKtT/QG6JBGYR1L41mcDhIqhnk7RmYl9qJstVAegrCKWR53sPKFdTVOHU7dmu6k+BD+TqHpyyw==} - '@storybook/global@5.0.0': resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} - '@storybook/icons@1.2.5': - resolution: {integrity: sha512-m3jnuE+zmkZy6K+cdUDzAoUuCJyl0fWCAXPCji7VZCH1TzFohyvnPqhc9JMkQpanej2TOW3wWXaplPzHghcBSg==} + '@storybook/icons@1.2.12': + resolution: {integrity: sha512-UxgyK5W3/UV4VrI3dl6ajGfHM4aOqMAkFLWe2KibeQudLf6NJpDrDMSHwZj+3iKC4jFU7dkKbbtH2h/al4sW3Q==} engines: {node: '>=14.0.0'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - '@storybook/instrumenter@8.2.6': - resolution: {integrity: sha512-RxtpcMTUSq8/wPM6cR6EXVrPEiNuRbC71cIFVFZagOFYvnnOKwSPV+GOLPK0wxMbGB4c5/+Xe8ADefmZTvxOsA==} + '@storybook/instrumenter@8.3.2': + resolution: {integrity: sha512-+H3Z9wn+D8sMuOd+KjHUr8iyRLVpYvWQ4GmV7GKH173PfFAQ2zmX/502K1BS2BAuLrS1l0e6fGZhl7G3u2fL+g==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/manager-api@8.1.11': - resolution: {integrity: sha512-QSgwKfAw01K9YvvZj30iGBMgQ4YaCT3vojmttuqdH5ukyXkiO7pENLJj4Y+alwUeSi0g+SJeadCI3PXySBHOGg==} - - '@storybook/manager-api@8.2.6': - resolution: {integrity: sha512-uv36h/b5RhlajWtEg4cVPBYV8gZs6juux0nIE+6G9i7vt8Ild6gM9tW1KNabgZcaHFiyWJYCNWxJZoKjgUmXDg==} + '@storybook/manager-api@8.3.2': + resolution: {integrity: sha512-8FuwE3BGsLPF0H154+1X/4krSbvmH5xu5YmaVTVDV8DRPlBeRIlNV0HDiZfBvftF4EB7fRYolzghXQplHIX8Fg==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/manager@8.1.11': - resolution: {integrity: sha512-e02y9dmxowo7cTKYm9am7UO6NOHoHy6Xi7xZf/UA932qLwFZUtk5pnwIEFaZWI3OQsRUCGhP+FL5zizU7uVZeg==} - - '@storybook/node-logger@8.1.11': - resolution: {integrity: sha512-wdzFo7B2naGhS52L3n1qBkt5BfvQjs8uax6B741yKRpiGgeAN8nz8+qelkD25MbSukxvbPgDot7WJvsMU/iCzg==} - - '@storybook/preview-api@8.1.11': - resolution: {integrity: sha512-8ZChmFV56GKppCJ0hnBd/kNTfGn2gWVq1242kuet13pbJtBpvOhyq4W01e/Yo14tAPXvgz8dSnMvWLbJx4QfhQ==} - - '@storybook/preview-api@8.2.6': - resolution: {integrity: sha512-5vTj2ndX5ng4nDntZYe+r8UwLjCIGFymhq5/r2adAvRKL+Bo4zQDWGO7bhvGJk16do2THb2JvPz49ComW9LLZw==} + '@storybook/preview-api@8.3.2': + resolution: {integrity: sha512-bZvqahrS5oXkiVmqt9rPhlpo/xYLKT7QUWKKIDBRJDp+1mYbQhgsP5NhjUtUdaC+HSofAFzJmVFmixyquYsoGw==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/preview@8.1.11': - resolution: {integrity: sha512-K/9NZmjnL0D1BROkTNWNoPqgL2UaocALRSqCARmkBLgU2Rn/FuZgEclHkWlYo6pUrmLNK+bZ+XzpNMu12iTbpg==} - - '@storybook/react-dom-shim@8.2.6': - resolution: {integrity: sha512-B+x8UAEQPDp1yhN3tMh09NvSL38QNfJB7PAyLgKrfE7xIAzvewq+RLW2DfGkoZCy+Zr7QSHm1p7NOgud8+sQCg==} + '@storybook/react-dom-shim@8.3.2': + resolution: {integrity: sha512-fYL7jh9yFkiKIqRJedqTcrmyoVzS/cMxZD/EFfDRaonMVlLlYJQKocuvR1li1iyeKLvd5lxZsHuQ80c98AkDMA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/react-vite@8.2.6': - resolution: {integrity: sha512-BpbteaIzsJZL1QN3iR7uuslrPfdtbZYXPhcU9awpfl5pW5MOQThuvl7728mwT8V7KdANeikJPgsnlETOb/afDA==} + '@storybook/react-vite@8.3.2': + resolution: {integrity: sha512-xxV6FJj4OnJ1lQbO7804T2xJu0aXvb02/tyLpDo0aNdi2vMZrHMroYpcOJW3RDuOIrMYq2OvXPrIHnkumidSsg==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.2.6 + storybook: ^8.3.2 vite: ^4.0.0 || ^5.0.0 - '@storybook/react@8.2.6': - resolution: {integrity: sha512-awJlzfiAMrf8l9AgiLhjXEJ+HvS3VKPxNNQaRwBELGq/vigjJe656tMrhvg4OIlJXtlS+6XPshd2knLwjIWNLw==} + '@storybook/react@8.3.2': + resolution: {integrity: sha512-GvnqhxvaYC6s8WMiDWr184UlNp5jmRVNMBHasXlUsVDYvs6J1tStJeN+XBZbAJBW/0zkHLuf4REk8lLBi2eKRQ==} engines: {node: '>=18.0.0'} peerDependencies: + '@storybook/test': 8.3.2 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.2.6 + storybook: ^8.3.2 typescript: '>= 4.2.x' peerDependenciesMeta: + '@storybook/test': + optional: true typescript: optional: true - '@storybook/router@8.1.11': - resolution: {integrity: sha512-nU5lsBvy0L8wBYOkjagh29ztZicDATpZNYrHuavlhQ2jznmmHdJvXKYk+VrMAbthjQ6ZBqfeeMNPR1UlnqR5Rw==} - - '@storybook/source-loader@8.2.6': - resolution: {integrity: sha512-mOVf+TJhlQywCymFMs7l604CxEZRKZRKVQojrrgU6CH6EhhLx/q6BT8tf1CakY9JO3Ey+PhUMBBCerYiDaHLcQ==} + '@storybook/source-loader@8.3.2': + resolution: {integrity: sha512-+h9F5KB/ccLlV1FXwoQ6sftYGHimaMttC5mQ6o5t3a3EI8cbyMxdnz5uoAO3mpO3CuVLg/jkfNO/RboHTNBEDg==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/telemetry@8.1.11': - resolution: {integrity: sha512-Jqvm7HcZismKzPuebhyLECO6KjGiSk4ycbca1WUM/TUvifxCXqgoUPlHHQEEfaRdHS63/MSqtMNjLsQRLC/vNQ==} - - '@storybook/test@8.2.6': - resolution: {integrity: sha512-nTzNxReBcMRlX1+8PNU/MuA9ArFbeQhfZXMBIwJJoHOhnNe1knYpyn1++xINxAHKOh0BBhQ0NIMoKdcGmW3V6w==} + '@storybook/test@8.3.2': + resolution: {integrity: sha512-pRrARctJoZQSKKhMyKkXZQK+fVtnilxTmd0AJx7UBJFUTZmMbp6uEdoyr4NyORCUO1xxxrdbD88vEUsSC1hdYw==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/theming@8.1.11': - resolution: {integrity: sha512-Chn/opjO6Rl1isNobutYqAH2PjKNkj09YBw/8noomk6gElSa3JbUTyaG/+JCHA6OG/9kUsqoKDb5cZmAKNq/jA==} + '@storybook/theming@8.3.2': + resolution: {integrity: sha512-JXAVc08Tlbu4GTTMGNmwUy69lShqSpJixAJc4bvWTnNAtPTRltiNJCg/KJ0GauEyRFk8ZR2Ha4KhN3DB1felNQ==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true + storybook: ^8.3.2 - '@storybook/theming@8.2.6': - resolution: {integrity: sha512-ICnYuLIVsYifVCMQljdHgrp+5vAquNybHxDGWiPeOxBicotwHF8rLhTckD2CdVQbMp0jk6r6jetvjXbFJ2MbvQ==} + '@storybook/types@8.3.2': + resolution: {integrity: sha512-4GnGjt5Q4W+hctROyCoLiTUSVIMdaSqaNigg0TkkN/6XKqcUDtuKLZVU8NuGPdUtyo5+18WdVgbU1DXlFe+aDA==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 - '@storybook/types@8.1.11': - resolution: {integrity: sha512-k9N5iRuY2+t7lVRL6xeu6diNsxO3YI3lS4Juv3RZ2K4QsE/b3yG5ElfJB8DjHDSHwRH4ORyrU71KkOCUVfvtnw==} - - '@storybook/types@8.2.6': - resolution: {integrity: sha512-9Kb5+nui8M7TP/EDGwiuOAHYQPg9U6iQl0OWwgbDIYGBpldwlCwVKAoQWzXz/LlhQijULXIpe1cLvEvJN2Uwhg==} - peerDependencies: - storybook: ^8.2.6 - - '@storybook/vue3-vite@8.1.11': - resolution: {integrity: sha512-q0bqh8XEEunaTmp4YiDqM2+YZLwEIevTb5PnNe7G7f2qOiSCE1ncBDnBK717UlCd+iYr34NTztgV2/jIhz1i5w==} + '@storybook/vue3-vite@8.3.2': + resolution: {integrity: sha512-zDBW7ET50RIxYmTON/hDo+XZtP5AS4X9reRHh+euUi33eTaTqE66g+KODKdLJOY0tx/zimwGNK6S8MdBWFWXGg==} engines: {node: '>=18.0.0'} peerDependencies: + storybook: ^8.3.2 vite: ^4.0.0 || ^5.0.0 - '@storybook/vue3@8.1.11': - resolution: {integrity: sha512-xJtvfLiCOY3UqwDMd0hZdsadPm1q8dwjfM1UN2Q2ssRWNfXzww1oi+Msj902wz9zFZMYVZypfTfgrdRgWmfEjA==} + '@storybook/vue3@8.3.2': + resolution: {integrity: sha512-DwliJ3sZGUhNMtpcdNmscGkIZTSKBfeRqufXwVYEw8+vnd3UFy4gLohqy+6aV3lXcV5eJE+S0TgJ+D9cWKMh5Q==} engines: {node: '>=18.0.0'} peerDependencies: - vue: ^3.0.0 - - '@storybook/vue3@8.2.6': - resolution: {integrity: sha512-j4gMuWc1ZDzqWSdf79YswcZmcbhmbByq/6upqxwqXtjv1mHAiBnEs8bbnnylDrzg4GOvBC8w+FjArkzlFA7uXg==} - engines: {node: '>=18.0.0'} - peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.2 vue: ^3.0.0 '@swc/cli@0.3.12': @@ -4706,34 +4551,17 @@ packages: resolution: {integrity: sha512-EmCsnzdvawyk4b+4JKaLLuicHcJQRZtL1zSy9AWJLiiHTbDDseYgLxfaCEfLk8v2bUe7SBXwl3n3B7OjgvH11Q==} hasBin: true - '@testing-library/dom@10.1.0': - resolution: {integrity: sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==} + '@testing-library/dom@10.4.0': + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} '@testing-library/dom@9.3.4': resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} engines: {node: '>=14'} - '@testing-library/jest-dom@6.4.5': - resolution: {integrity: sha512-AguB9yvTXmCnySBP1lWjfNNUwpbElsaQ567lt2VdGqAdHtpieLgjmcVyv1q7PMIvLbgpDdkWV5Ydv3FEejyp2A==} + '@testing-library/jest-dom@6.5.0': + resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} - peerDependencies: - '@jest/globals': '>= 28' - '@types/bun': latest - '@types/jest': '>= 28' - jest: '>= 28' - vitest: '>= 0.32' - peerDependenciesMeta: - '@jest/globals': - optional: true - '@types/bun': - optional: true - '@types/jest': - optional: true - jest: - optional: true - vitest: - optional: true '@testing-library/user-event@14.5.2': resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==} @@ -4765,6 +4593,9 @@ packages: '@twemoji/parser@15.0.0': resolution: {integrity: sha512-lh9515BNsvKSNvyUqbj5yFu83iIDQ77SwVcsN/SnEGawczhsKU6qWuogewN1GweTi5Imo5ToQ9s+nNTf97IXvg==} + '@twemoji/parser@15.1.0': + resolution: {integrity: sha512-3HTiSxPvkWUJ4kZeCvwyKlIwkpTUfBOk6igpBBRQni58ceQMv5YK4smkc8vX/eqOlMMNER/9qobv+Q6Q8LVrqA==} + '@twemoji/parser@15.1.1': resolution: {integrity: sha512-CChRzIu6ngkCJOmURBlYEdX5DZSu+bBTtqR60XjBkFrmvplKW7OQsea+i8XwF4bLVlUXBO7ZmHhRPDzfQyLwwg==} @@ -4822,33 +4653,15 @@ packages: '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/cross-spawn@6.0.2': - resolution: {integrity: sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==} - '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - '@types/detect-port@1.3.2': - resolution: {integrity: sha512-xxgAGA2SAU4111QefXPSp5eGbDm/hW6zhvYl9IeEPZEry9F4d66QAHm5qpUXjb6IsevZV/7emAEx5MhP6O192g==} - - '@types/diff@5.2.1': - resolution: {integrity: sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==} - '@types/disposable-email-domains@1.0.2': resolution: {integrity: sha512-SDKwyYTjk3y5aZBxxc38yRecpJPjsqn57STz1bNxYYlv4k11bBe7QB8w4llXDTmQXKT1mFvgGmJv+8Zdu3YmJw==} - '@types/doctrine@0.0.3': - resolution: {integrity: sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA==} - '@types/doctrine@0.0.9': resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} - '@types/ejs@3.1.2': - resolution: {integrity: sha512-ZmiaE3wglXVWBM9fyVC17aGPkLo/UgaOjEiI2FXQfyczrCefORPxIe+2dVmnmk3zkVIbizjrlQzmPGhSYGXG5g==} - - '@types/emscripten@1.39.7': - resolution: {integrity: sha512-tLqYV94vuqDrXh515F/FOGtBcRMTPGvVV1LzLbtYDcQmmhtpf/gLYf+hikBbQk8MzOHNz37wpFfJbYAuSn8HqA==} - '@types/escape-regexp@0.0.3': resolution: {integrity: sha512-FQMYUxaf1dVeWLUzJFSvfdDugfOpDyM13p67QfyMdagxSkBa689opkr/q9uR/VWyrWrl0jAyQaSPKxX9MpAXFw==} @@ -4864,6 +4677,9 @@ packages: '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/express-serve-static-core@4.17.33': resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} @@ -4876,8 +4692,8 @@ packages: '@types/find-cache-dir@3.2.1': resolution: {integrity: sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==} - '@types/fluent-ffmpeg@2.1.24': - resolution: {integrity: sha512-g5oQO8Jgi2kFS3tTub7wLvfLztr1s8tdXmRd8PiL/hLMLzTIAyMR2sANkTggM/rdEDAg3d63nYRRVepwBiCw5A==} + '@types/fluent-ffmpeg@2.1.26': + resolution: {integrity: sha512-0JVF3wdQG+pN0ImwWD0bNgJiKF2OHg/7CDBHw5UIbRTvlnkgGHK6V5doE54ltvhud4o31/dEiHm23CAlxFiUQg==} '@types/glob@7.2.0': resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} @@ -4906,8 +4722,8 @@ packages: '@types/istanbul-reports@3.0.1': resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} - '@types/jest@29.5.12': - resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} + '@types/jest@29.5.13': + resolution: {integrity: sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==} '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} @@ -4978,9 +4794,6 @@ packages: '@types/node-fetch@2.6.11': resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} - '@types/node@18.17.15': - resolution: {integrity: sha512-2yrWpBk32tvV/JAd3HNHWuZn/VDN1P+72hWirHnvsvTGSqbANi+kSeuQR9yAHnbvaBvHDsoTdXV0Fe+iRtHLKA==} - '@types/node@20.11.5': resolution: {integrity: sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==} @@ -4990,8 +4803,11 @@ packages: '@types/node@20.9.1': resolution: {integrity: sha512-HhmzZh5LSJNS5O8jQKpJ/3ZcrrlG6L70hpGqMIAoM9YVD0YBRNWYsfwcXq8VnSjlNpCpgLzMXdiPo+dxcvSmiA==} - '@types/nodemailer@6.4.15': - resolution: {integrity: sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==} + '@types/node@22.5.5': + resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==} + + '@types/nodemailer@6.4.16': + resolution: {integrity: sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ==} '@types/normalize-package-data@2.4.1': resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -5014,15 +4830,12 @@ packages: '@types/pg-pool@2.0.4': resolution: {integrity: sha512-qZAvkv1K3QbmHHFYSNRYPkRjOWRLBYrL4B9c+wG0GSVGBw0NtJwPcgx/DSddeDJvRGMHCEQ4VMEVfuJ/0gZ3XQ==} - '@types/pg@8.11.6': - resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} + '@types/pg@8.11.10': + resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==} '@types/pg@8.6.1': resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==} - '@types/pretty-hrtime@1.0.1': - resolution: {integrity: sha512-VjID5MJb1eGKthz2qUerWT8+R4b9N+CHvGCzg9fn4kWZgaF9AhdYikQio3R7wV8YY1NsQKPaCwKz1Yff+aHNUQ==} - '@types/prop-types@15.7.5': resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} @@ -5062,8 +4875,8 @@ packages: '@types/responselike@1.0.0': resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} - '@types/sanitize-html@2.11.0': - resolution: {integrity: sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ==} + '@types/sanitize-html@2.13.0': + resolution: {integrity: sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==} '@types/scheduler@0.16.2': resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} @@ -5143,8 +4956,8 @@ packages: '@types/wrap-ansi@3.0.0': resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} - '@types/ws@8.5.11': - resolution: {integrity: sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==} + '@types/ws@8.5.12': + resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} '@types/yargs-parser@21.0.0': resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} @@ -5155,17 +4968,6 @@ packages: '@types/yauzl@2.10.0': resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} - '@typescript-eslint/eslint-plugin@6.11.0': - resolution: {integrity: sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - '@typescript-eslint/eslint-plugin@7.1.0': resolution: {integrity: sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==} engines: {node: ^16.0.0 || >=18.0.0} @@ -5188,16 +4990,6 @@ packages: typescript: optional: true - '@typescript-eslint/parser@6.11.0': - resolution: {integrity: sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - '@typescript-eslint/parser@7.1.0': resolution: {integrity: sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==} engines: {node: ^16.0.0 || >=18.0.0} @@ -5218,10 +5010,6 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@6.11.0': - resolution: {integrity: sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==} - engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/scope-manager@7.1.0': resolution: {integrity: sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==} engines: {node: ^16.0.0 || >=18.0.0} @@ -5230,16 +5018,6 @@ packages: resolution: {integrity: sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/type-utils@6.11.0': - resolution: {integrity: sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - '@typescript-eslint/type-utils@7.1.0': resolution: {integrity: sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==} engines: {node: ^16.0.0 || >=18.0.0} @@ -5260,10 +5038,6 @@ packages: typescript: optional: true - '@typescript-eslint/types@6.11.0': - resolution: {integrity: sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==} - engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/types@7.1.0': resolution: {integrity: sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==} engines: {node: ^16.0.0 || >=18.0.0} @@ -5272,15 +5046,6 @@ packages: resolution: {integrity: sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/typescript-estree@6.11.0': - resolution: {integrity: sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - '@typescript-eslint/typescript-estree@7.1.0': resolution: {integrity: sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -5299,12 +5064,6 @@ packages: typescript: optional: true - '@typescript-eslint/utils@6.11.0': - resolution: {integrity: sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - '@typescript-eslint/utils@7.1.0': resolution: {integrity: sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==} engines: {node: ^16.0.0 || >=18.0.0} @@ -5317,10 +5076,6 @@ packages: peerDependencies: eslint: ^8.56.0 - '@typescript-eslint/visitor-keys@6.11.0': - resolution: {integrity: sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==} - engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/visitor-keys@7.1.0': resolution: {integrity: sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==} engines: {node: ^16.0.0 || >=18.0.0} @@ -5332,8 +5087,8 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - '@vitejs/plugin-vue@5.1.0': - resolution: {integrity: sha512-QMRxARyrdiwi1mj3AW4fLByoHTavreXq0itdEW696EihXglf1MB3D4C2gBvE0jMPH29ZjC3iK8aIaUMLf4EOGA==} + '@vitejs/plugin-vue@5.1.4': + resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: ^5.0.0 @@ -5347,6 +5102,15 @@ packages: '@vitest/expect@1.6.0': resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} + '@vitest/expect@2.0.5': + resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} + + '@vitest/pretty-format@2.0.5': + resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} + + '@vitest/pretty-format@2.1.1': + resolution: {integrity: sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==} + '@vitest/runner@1.6.0': resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} @@ -5356,54 +5120,63 @@ packages: '@vitest/spy@1.6.0': resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} + '@vitest/spy@2.0.5': + resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} + '@vitest/utils@1.6.0': resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} + '@vitest/utils@2.0.5': + resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} + + '@vitest/utils@2.1.1': + resolution: {integrity: sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==} + '@volar/language-core@2.2.0': resolution: {integrity: sha512-a8WG9+4OdeNDW4ywABZIM6S6UN7em8uIlM/BZ2pWQUYrVmX+m8sj/X+QadvO+Li/t/LjAqbWJQtVgxdpEWLALQ==} - '@volar/language-core@2.4.0-alpha.18': - resolution: {integrity: sha512-JAYeJvYQQROmVRtSBIczaPjP3DX4QW1fOqW1Ebs0d3Y3EwSNRglz03dSv0Dm61dzd0Yx3WgTW3hndDnTQqgmyg==} + '@volar/language-core@2.4.5': + resolution: {integrity: sha512-F4tA0DCO5Q1F5mScHmca0umsi2ufKULAnMOVBfMsZdT4myhVl4WdKRwCaKcfOkIEuyrAVvtq1ESBdZ+rSyLVww==} '@volar/source-map@2.2.0': resolution: {integrity: sha512-HQlPRlHOVqCCHK8wI76ZldHkEwKsjp7E6idUc36Ekni+KJDNrqgSqPvyHQixybXPHNU7CI9Uxd9/IkxO7LuNBw==} - '@volar/source-map@2.4.0-alpha.18': - resolution: {integrity: sha512-MTeCV9MUwwsH0sNFiZwKtFrrVZUK6p8ioZs3xFzHc2cvDXHWlYN3bChdQtwKX+FY2HG6H3CfAu1pKijolzIQ8g==} + '@volar/source-map@2.4.5': + resolution: {integrity: sha512-varwD7RaKE2J/Z+Zu6j3mNNJbNT394qIxXwdvz/4ao/vxOfyClZpSDtLKkwWmecinkOVos5+PWkWraelfMLfpw==} '@volar/typescript@2.2.0': resolution: {integrity: sha512-wC6l4zLiiCLxF+FGaHCbWlQYf4vMsnRxYhcI6WgvaNppOD6r1g+Ef1RKRJUApALWU46Yy/JDU/TbdV6w/X6Liw==} - '@volar/typescript@2.4.0-alpha.18': - resolution: {integrity: sha512-sXh5Y8sqGUkgxpMWUGvRXggxYHAVxg0Pa1C42lQZuPDrW6vHJPR0VCK8Sr7WJsAW530HuNQT/ZIskmXtxjybMQ==} - - '@vue/compiler-core@3.4.31': - resolution: {integrity: sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==} - - '@vue/compiler-core@3.4.34': - resolution: {integrity: sha512-Z0izUf32+wAnQewjHu+pQf1yw00EGOmevl1kE+ljjjMe7oEfpQ+BI3/JNK7yMB4IrUsqLDmPecUrpj3mCP+yJQ==} + '@volar/typescript@2.4.5': + resolution: {integrity: sha512-mcT1mHvLljAEtHviVcBuOyAwwMKz1ibXTi5uYtP/pf4XxoAzpdkQ+Br2IC0NPCvLCbjPZmbf3I0udndkfB1CDg==} '@vue/compiler-core@3.4.37': resolution: {integrity: sha512-ZDDT/KiLKuCRXyzWecNzC5vTcubGz4LECAtfGPENpo0nrmqJHwuWtRLxk/Sb9RAKtR9iFflFycbkjkY+W/PZUQ==} - '@vue/compiler-dom@3.4.34': - resolution: {integrity: sha512-3PUOTS1h5cskdOJMExCu2TInXuM0j60DRPpSCJDqOCupCfUZCJoyQmKtRmA8EgDNZ5kcEE7vketamRZfrEuVDw==} + '@vue/compiler-core@3.5.7': + resolution: {integrity: sha512-A0gay3lK71MddsSnGlBxRPOugIVdACze9L/rCo5X5srCyjQfZOfYtSFMJc3aOZCM+xN55EQpb4R97rYn/iEbSw==} '@vue/compiler-dom@3.4.37': resolution: {integrity: sha512-rIiSmL3YrntvgYV84rekAtU/xfogMUJIclUMeIKEtVBFngOL3IeZHhsH3UaFEgB5iFGpj6IW+8YuM/2Up+vVag==} + '@vue/compiler-dom@3.5.7': + resolution: {integrity: sha512-GYWl3+gO8/g0ZdYaJ18fYHdI/WVic2VuuUd1NsPp60DWXKy+XjdhFsDW7FbUto8siYYZcosBGn9yVBkjhq1M8Q==} + '@vue/compiler-sfc@3.4.37': resolution: {integrity: sha512-vCfetdas40Wk9aK/WWf8XcVESffsbNkBQwS5t13Y/PcfqKfIwJX2gF+82th6dOpnpbptNMlMjAny80li7TaCIg==} + '@vue/compiler-sfc@3.5.7': + resolution: {integrity: sha512-EjOJtCWJrC7HqoCEzOwpIYHm+JH7YmkxC1hG6VkqIukYRqj8KFUlTLK6hcT4nGgtVov2+ZfrdrRlcaqS78HnBA==} + '@vue/compiler-ssr@3.4.37': resolution: {integrity: sha512-TyAgYBWrHlFrt4qpdACh8e9Ms6C/AZQ6A6xLJaWrCL8GCX5DxMzxyeFAEMfU/VFr4tylHm+a2NpfJpcd7+20XA==} + '@vue/compiler-ssr@3.5.7': + resolution: {integrity: sha512-oZx+jXP2k5arV/8Ly3TpQbfFyimMw2ANrRqvHJoKjPqtEzazxQGZjCLOfq8TnZ3wy2TOXdqfmVp4q7FyYeHV4g==} + '@vue/compiler-vue2@2.7.16': resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} - '@vue/devtools-api@6.6.1': - resolution: {integrity: sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==} - '@vue/language-core@2.0.16': resolution: {integrity: sha512-Bc2sexRH99pznOph8mLw2BlRZ9edm7tW51kcBXgx8adAoOcZUWJj3UNSsdQ6H9Y8meGz7BoazVrVo/jUukIsPw==} peerDependencies: @@ -5412,8 +5185,8 @@ packages: typescript: optional: true - '@vue/language-core@2.0.29': - resolution: {integrity: sha512-o2qz9JPjhdoVj8D2+9bDXbaI4q2uZTHQA/dbyZT4Bj1FR9viZxDJnLcKVHfxdn6wsOzRgpqIzJEEmSSvgMvDTQ==} + '@vue/language-core@2.1.6': + resolution: {integrity: sha512-MW569cSky9R/ooKMh6xa2g1D0AtRKbL56k83dzus/bx//RDJk24RHWkMzbAlXjMdDNyxAaagKPRquBIxkxlCkg==} peerDependencies: typescript: '*' peerDependenciesMeta: @@ -5423,26 +5196,37 @@ packages: '@vue/reactivity@3.4.37': resolution: {integrity: sha512-UmdKXGx0BZ5kkxPqQr3PK3tElz6adTey4307NzZ3whZu19i5VavYal7u2FfOmAzlcDVgE8+X0HZ2LxLb/jgbYw==} + '@vue/reactivity@3.5.7': + resolution: {integrity: sha512-yF0EpokpOHRNXyn/h6abXc9JFIzfdAf0MJHIi92xxCWS0mqrXH6+2aZ+A6EbSrspGzX5MHTd5N8iBA28HnXu9g==} + '@vue/runtime-core@3.4.37': resolution: {integrity: sha512-MNjrVoLV/sirHZoD7QAilU1Ifs7m/KJv4/84QVbE6nyAZGQNVOa1HGxaOzp9YqCG+GpLt1hNDC4RbH+KtanV7w==} + '@vue/runtime-core@3.5.7': + resolution: {integrity: sha512-OzLpBpKbZEaZVSNfd+hQbfBrDKux+b7Yl5hYhhWWWhHD7fEpF+CdI3Brm5k5GsufHEfvMcjruPxwQZuBN6nFYQ==} + '@vue/runtime-dom@3.4.37': resolution: {integrity: sha512-Mg2EwgGZqtwKrqdL/FKMF2NEaOHuH+Ks9TQn3DHKyX//hQTYOun+7Tqp1eo0P4Ds+SjltZshOSRq6VsU0baaNg==} + '@vue/runtime-dom@3.5.7': + resolution: {integrity: sha512-fL7cETfE27U2jyTgqzE382IGFY6a6uyznErn27KbbEzNctzxxUWYDbaN3B55l9nXh0xW2LRWPuWKOvjtO2UewQ==} + '@vue/server-renderer@3.4.37': resolution: {integrity: sha512-jZ5FAHDR2KBq2FsRUJW6GKDOAG9lUTX8aBEGq4Vf6B/35I9fPce66BornuwmqmKgfiSlecwuOb6oeoamYMohkg==} peerDependencies: vue: 3.4.37 - '@vue/shared@3.4.31': - resolution: {integrity: sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA==} - - '@vue/shared@3.4.34': - resolution: {integrity: sha512-x5LmiRLpRsd9KTjAB8MPKf0CDPMcuItjP0gbNqFCIgL1I8iYp4zglhj9w9FPCdIbHG2M91RVeIbArFfFTz9I3A==} + '@vue/server-renderer@3.5.7': + resolution: {integrity: sha512-peRypij815eIDjpPpPXvYQGYqPH6QXwLJGWraJYPPn8JqWGl29A8QXnS7/Mh3TkMiOcdsJNhbFCoW2Agc2NgAQ==} + peerDependencies: + vue: 3.5.7 '@vue/shared@3.4.37': resolution: {integrity: sha512-nIh8P2fc3DflG8+5Uw8PT/1i17ccFn0xxN/5oE9RfV5SVnd7G0XEFRwakrnNFE/jlS95fpGXDVG5zDETS26nmg==} + '@vue/shared@3.5.7': + resolution: {integrity: sha512-NBE1PBIvzIedxIc2RZiKXvGbJkrZ2/hLf3h8GlS4/sP9xcXEZMFWOazFkNd6aGeUCMaproe5MHVYB3/4AW9q9g==} + '@vue/test-utils@2.4.1': resolution: {integrity: sha512-VO8nragneNzUZUah6kOjiFmD/gwRjUauG9DROh6oaOeFwX1cZRUNHhdeogE8635cISigXFTtGLUQWx5KCb0xeg==} peerDependencies: @@ -5455,20 +5239,6 @@ packages: '@webgpu/types@0.1.30': resolution: {integrity: sha512-9AXJSmL3MzY8ZL//JjudA//q+2kBRGhLBFpkdGksWIuxrMy81nFrCzj2Am+mbh8WoU6rXmv7cY5E3rdlyru2Qg==} - '@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15': - resolution: {integrity: sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==} - engines: {node: '>=14.15.0'} - peerDependencies: - esbuild: '>=0.10.0' - - '@yarnpkg/fslib@2.10.3': - resolution: {integrity: sha512-41H+Ga78xT9sHvWLlFOZLIhtU6mTGZ20pZ29EiZa97vnxdohJD2AF42rCoAoWfqUz486xY6fhjMH+DYEM9r14A==} - engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'} - - '@yarnpkg/libzip@2.3.0': - resolution: {integrity: sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==} - engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'} - abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -5520,10 +5290,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - address@1.2.2: - resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} - engines: {node: '>= 10.0.0'} - adm-zip@0.5.10: resolution: {integrity: sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==} engines: {node: '>=6.0'} @@ -5561,14 +5327,6 @@ packages: ajv: optional: true - ajv-formats@2.1.1: - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -5628,9 +5386,6 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - app-root-dir@1.0.2: - resolution: {integrity: sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==} - app-root-path@3.1.0: resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} engines: {node: '>= 6.0.0'} @@ -5652,9 +5407,6 @@ packages: resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} engines: {node: '>= 14'} - archy@1.0.0: - resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} - are-we-there-yet@2.0.0: resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} engines: {node: '>=10'} @@ -5678,19 +5430,23 @@ packages: array-buffer-byte-length@1.0.0: resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} + array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - array-includes@3.1.7: - resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} engines: {node: '>= 0.4'} array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - array.prototype.findlastindex@1.2.3: - resolution: {integrity: sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==} + array.prototype.findlastindex@1.2.5: + resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} engines: {node: '>= 0.4'} array.prototype.flat@1.3.2: @@ -5705,6 +5461,10 @@ packages: resolution: {integrity: sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==} engines: {node: '>= 0.4'} + arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + engines: {node: '>= 0.4'} + arrify@1.0.1: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} @@ -5729,12 +5489,13 @@ packages: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} engines: {node: '>=0.8'} - assert@2.1.0: - resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} - assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types@0.16.1: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} @@ -5743,8 +5504,8 @@ packages: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} - astring@1.8.6: - resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true async-mutex@0.5.0: @@ -5771,8 +5532,12 @@ packages: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} - avvio@8.3.0: - resolution: {integrity: sha512-VBVH0jubFr9LdFASy/vNtm5giTrnbVquWBhT0fyizuNK2rQ7e7ONU2plZQWUNqtE1EmxFEb+kbSkFRkstiaS9Q==} + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + avvio@9.0.0: + resolution: {integrity: sha512-UbYrOXgE/I+knFG+3kJr9AgC7uNo8DG+FGGODpH9Bj1O1kL/QDjBXnTem9leD3VdQKtaHjV3O85DQ7hHh4IIHw==} aws-sdk-client-mock@4.0.1: resolution: {integrity: sha512-yD2mmgy73Xce097G5hIpr1k7j50qzvJ49/+6osGZiCyk4m6cwhb+2x7kKFY1gEMwTzaS8+m8fXv9SB29SkRYyQ==} @@ -5786,17 +5551,12 @@ packages: axios@0.24.0: resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==} - axios@1.6.2: - resolution: {integrity: sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==} + axios@1.7.7: + resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} b4a@1.6.4: resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==} - babel-core@7.0.0-bridge.0: - resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5811,21 +5571,6 @@ packages: resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - babel-plugin-polyfill-corejs2@0.4.11: - resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - - babel-plugin-polyfill-corejs3@0.10.4: - resolution: {integrity: sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - - babel-plugin-polyfill-regenerator@0.6.2: - resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-preset-current-node-syntax@1.0.1: resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} peerDependencies: @@ -5860,10 +5605,6 @@ packages: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} engines: {node: '>=12.0.0'} - big-integer@1.6.51: - resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} - engines: {node: '>=0.6'} - bin-check@4.1.0: resolution: {integrity: sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==} engines: {node: '>=4'} @@ -5880,9 +5621,6 @@ packages: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - blob-util@2.0.2: resolution: {integrity: sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==} @@ -5899,16 +5637,16 @@ packages: resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} - bplist-parser@0.2.0: - resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} - engines: {node: '>= 5.10.0'} - brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -5972,8 +5710,8 @@ packages: resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==} engines: {node: '>=6.14.2'} - bullmq@5.10.4: - resolution: {integrity: sha512-YEssEbWBbPXvSW2YMjIBKZdkIPZsOaTGWo1y2wpCFv/wUY+tRLKiSVuHgv09x0QEieybx844f9//UWuarG1JHg==} + bullmq@5.13.2: + resolution: {integrity: sha512-McGE8k3mrCvdUHdU0sHkTKDS1xr4pff+hbEKBY51wk5S6Za0gkuejYA620VQTo3Zz37E/NVWMgumwiXPQ3yZcA==} buraha@0.0.1: resolution: {integrity: sha512-G563A0mTbzknm2jDaNxfZuNKIdeArs8T+XQN6t+KbmgnOoevXSXhKDkyf8Md/36Jrx99ikwbCag37VGe3myExQ==} @@ -5982,10 +5720,6 @@ packages: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} - bytes@3.0.0: - resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} - engines: {node: '>= 0.8'} - bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -6025,6 +5759,10 @@ packages: call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + call-me-maybe@1.0.2: resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} @@ -6073,6 +5811,10 @@ packages: resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} engines: {node: '>=4'} + chai@5.1.1: + resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + engines: {node: '>=12'} + chalk-template@1.1.0: resolution: {integrity: sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==} engines: {node: '>=14.16'} @@ -6103,8 +5845,8 @@ packages: character-parser@2.2.0: resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==} - chart.js@4.4.3: - resolution: {integrity: sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==} + chart.js@4.4.4: + resolution: {integrity: sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==} engines: {pnpm: '>=8'} chartjs-adapter-date-fns@3.0.0: @@ -6131,6 +5873,10 @@ packages: check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + check-more-types@2.24.0: resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==} engines: {node: '>= 0.8.0'} @@ -6138,6 +5884,10 @@ packages: cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + cheerio@1.0.0: + resolution: {integrity: sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==} + engines: {node: '>=18.17'} + cheerio@1.0.0-rc.12: resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} engines: {node: '>= 6'} @@ -6153,8 +5903,8 @@ packages: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} - chromatic@11.5.6: - resolution: {integrity: sha512-ycX/hlZLs69BltwwBNsEXr+As6x5/0rlwp6W/CiHMZ3tpm7dmkd+hQCsb8JGHb1h49W3qPOKQ/Lh9evqcJ1yeQ==} + chromatic@11.10.2: + resolution: {integrity: sha512-EbVlhmOLGdx9QRX3RMOTF3UzoyC1aaXNRjlzm1mc++2OI5+6C5Bzwt2ZUYJ3Jnf/pJa23q0y5Y3QEDcfRVqIbg==} hasBin: true peerDependencies: '@chromatic-com/cypress': ^0.*.* || ^1.0.0 @@ -6215,17 +5965,9 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} - clone-deep@4.0.1: - resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} - engines: {node: '>=6'} - clone-response@1.0.3: resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - cluster-key-slot@1.1.2: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} @@ -6279,6 +6021,10 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -6312,14 +6058,6 @@ packages: resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} engines: {node: '>= 14'} - compressible@2.0.18: - resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} - engines: {node: '>= 0.6'} - - compression@1.7.4: - resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==} - engines: {node: '>= 0.8.0'} - computeds@0.0.1: resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} @@ -6368,9 +6106,6 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} - core-js-compat@3.37.1: - resolution: {integrity: sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==} - core-js@3.29.1: resolution: {integrity: sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==} @@ -6402,8 +6137,8 @@ packages: resolution: {integrity: sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==} engines: {node: '>=12.0.0'} - cropperjs@2.0.0-rc.1: - resolution: {integrity: sha512-Y9ciurIuK6G1vy0ErHC8Gt6wHWvsHWJ5fgE60GL6vsuF2WzHwDpH7F1yof40XAEheeSN4v3rD09D1VZ7kiiSOA==} + cropperjs@2.0.0-rc.2: + resolution: {integrity: sha512-BTuz+UeZphGOEnBCuQiNT4rk1uFfKJaKmTgoH9XU7Q8IMkLdodW7YPWINmXJXwWMt1nXiKze5qKADVbz9xtVFg==} cross-env@7.0.3: resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} @@ -6423,10 +6158,6 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} - crypto-random-string@4.0.0: - resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} - engines: {node: '>=12'} - css-declaration-sorter@7.2.0: resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==} engines: {node: ^14 || ^16 || >=18} @@ -6488,8 +6219,8 @@ packages: cwise-compiler@1.1.3: resolution: {integrity: sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==} - cypress@13.13.1: - resolution: {integrity: sha512-8F9UjL5MDUdgC/S5hr8CGLHbS5gGht5UOV184qc2pFny43fnkoaKxlzH/U6//zmGu/xRTaKimNfjknLT8+UDFg==} + cypress@13.14.2: + resolution: {integrity: sha512-lsiQrN17vHMB2fnvxIrKLAjOr9bPwsNbPZNrWf99s4u+DVmCY6U+w7O3GGG9FvP4EUVYaDu+guWeNLiUzBrqvA==} engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} hasBin: true @@ -6508,6 +6239,18 @@ packages: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} + data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} + date-fns@2.30.0: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} @@ -6552,6 +6295,15 @@ packages: supports-color: optional: true + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} @@ -6593,6 +6345,10 @@ packages: resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} engines: {node: '>=6'} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-equal@2.2.0: resolution: {integrity: sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==} @@ -6603,17 +6359,14 @@ packages: resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} engines: {node: '>=0.10.0'} - default-browser-id@3.0.0: - resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} - engines: {node: '>=12'} - - defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - defer-to-connect@2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + define-lazy-prop@2.0.0: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} @@ -6622,8 +6375,9 @@ packages: resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} engines: {node: '>= 0.4'} - defu@6.1.4: - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} @@ -6648,10 +6402,6 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - detect-indent@6.1.0: - resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} - engines: {node: '>=8'} - detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} @@ -6660,14 +6410,6 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} - detect-package-manager@2.0.1: - resolution: {integrity: sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==} - engines: {node: '>=12'} - - detect-port@1.5.1: - resolution: {integrity: sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==} - hasBin: true - devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -6713,22 +6455,35 @@ packages: dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + domhandler@3.3.0: + resolution: {integrity: sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==} + engines: {node: '>= 4'} + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + domhandler@5.0.3: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + domutils@3.0.1: resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==} - dotenv-expand@10.0.0: - resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} - engines: {node: '>=12'} + domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} dotenv@16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} @@ -6775,13 +6530,17 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - encode-utf8@1.0.3: - resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==} - encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + encoding-sniffer@0.2.0: + resolution: {integrity: sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==} + encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} @@ -6807,11 +6566,6 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} - envinfo@7.8.1: - resolution: {integrity: sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==} - engines: {node: '>=4'} - hasBin: true - err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} @@ -6822,19 +6576,42 @@ packages: resolution: {integrity: sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==} engines: {node: '>= 0.4'} + es-abstract@1.23.3: + resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} es-module-lexer@1.5.4: resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} + es-set-tostringtag@2.0.1: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} engines: {node: '>= 0.4'} + es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} + es-shim-unscopables@1.0.0: resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} + es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + es-to-primitive@1.2.1: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} @@ -6845,9 +6622,6 @@ packages: es6-promisify@5.0.0: resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==} - esbuild-plugin-alias@0.2.1: - resolution: {integrity: sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==} - esbuild-register@3.5.0: resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==} peerDependencies: @@ -6873,10 +6647,19 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} + escape-goat@3.0.0: + resolution: {integrity: sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==} + engines: {node: '>=10'} + escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} @@ -6911,8 +6694,8 @@ packages: eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - eslint-module-utils@2.8.0: - resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + eslint-module-utils@2.11.0: + resolution: {integrity: sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -6932,8 +6715,8 @@ packages: eslint-import-resolver-webpack: optional: true - eslint-plugin-import@2.29.1: - resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} + eslint-plugin-import@2.30.0: + resolution: {integrity: sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -6948,6 +6731,12 @@ packages: peerDependencies: eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + eslint-plugin-vue@9.28.0: + resolution: {integrity: sha512-ShrihdjIhOTxs+MfWun6oJWuk+g/LAhN+CiuOl/jjkG3l0F2AuK5NMTaWqyvBgkFtpYmyks6P4603mLmhNJW8g==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + eslint-rule-docs@1.1.235: resolution: {integrity: sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==} @@ -6967,6 +6756,16 @@ packages: resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint@9.11.0: + resolution: {integrity: sha512-yVS6XODx+tMFMDFcG4+Hlh+qG7RM6cCJXtQhCKLSsr3XkLvWggHjCqjfh0XsPPnt1c56oaT6PMgW9XWQQjdHXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + eslint@9.8.0: resolution: {integrity: sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6985,10 +6784,6 @@ packages: engines: {node: '>=4'} hasBin: true - esquery@1.4.2: - resolution: {integrity: sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==} - engines: {node: '>=0.10'} - esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} @@ -7055,8 +6850,8 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} - execa@9.3.0: - resolution: {integrity: sha512-l6JFbqnHEadBoVAVpN5dl2yCyfX28WoBAGaoQcNmLLSedOxTxcn2Qa83s8I/PA5i56vWru2OHOtrwF7Om2vqlg==} + execa@9.4.0: + resolution: {integrity: sha512-yKHlle2YGxZE842MERVIplWwNH5VYmqqcPFgtnlU//K8gxuFFXu0pwd/CrfXTumFpeEiufsP7+opT/bPJa1yVw==} engines: {node: ^18.19.0 || >=20.5.0} executable@4.1.1: @@ -7078,6 +6873,10 @@ packages: resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==} engines: {node: '>= 0.10.0'} + express@4.21.0: + resolution: {integrity: sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==} + engines: {node: '>= 0.10.0'} + ext-list@2.2.2: resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==} engines: {node: '>=0.10.0'} @@ -7098,8 +6897,8 @@ packages: resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} engines: {'0': node >=0.6.0} - fast-content-type-parse@1.1.0: - resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==} + fast-content-type-parse@2.0.0: + resolution: {integrity: sha512-fCqg/6Sps8tqk8p+kqyKqYfOF0VjPNYrqpLiqNl0RBKmD80B080AJWVV6EkSkscjToNExcXg1+Mfzftrx6+iSA==} fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} @@ -7117,8 +6916,8 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - fast-json-stringify@5.8.0: - resolution: {integrity: sha512-VVwK8CFMSALIvt14U8AvrSzQAwN/0vaVRiFFUVlpnXSnDGrSkOAO5MtzyN8oQNjLd5AqTW5OZRgyjoNuAuR3jQ==} + fast-json-stringify@6.0.0: + resolution: {integrity: sha512-FGMKZwniMTgZh7zQp9b6XnBVxUmKVahQLQeRQHqwYmPDqDhcEKZ3BaQsxelFFI5PY7nN71OEeiL47/zUWcYe1A==} fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} @@ -7133,8 +6932,8 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-uri@2.2.0: - resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==} + fast-uri@2.4.0: + resolution: {integrity: sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==} fast-uri@3.0.1: resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==} @@ -7143,15 +6942,25 @@ packages: resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} hasBin: true + fast-xml-parser@4.5.0: + resolution: {integrity: sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==} + hasBin: true + fastify-plugin@4.5.0: resolution: {integrity: sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==} - fastify-raw-body@4.3.0: - resolution: {integrity: sha512-F4o8ZIMVx4YoxGfwrZys6wyjl40gF3Yv6AWWRy62ozFAyZBSS831/uyyCAqKYw3tR73g180ryG98yih6To1PUQ==} + fastify-plugin@4.5.1: + resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==} + + fastify-plugin@5.0.0: + resolution: {integrity: sha512-0725fmH/yYi8ugsjszLci+lLnGBK6cG+WSxM7edY2OXJEU7gr2JiGBoieL2h9mhTych1vFsEfXsAsGGDJ/Rd5w==} + + fastify-raw-body@5.0.0: + resolution: {integrity: sha512-2qfoaQ3BQDhZ1gtbkKZd6n0kKxJISJGM6u/skD9ljdWItAscjXrtZ1lnjr7PavmXX9j4EyCPmBDiIsLn07d5vA==} engines: {node: '>= 10'} - fastify@4.28.1: - resolution: {integrity: sha512-kFWUtpNr4i7t5vY2EJPCN2KgMVpuqfU4NjnJNCgiNB900oiDeYqaNDRcAfeBbOF5hGixixxcKnOU4KN9z6QncQ==} + fastify@5.0.0: + resolution: {integrity: sha512-Qe4dU+zGOzg7vXjw4EvcuyIbNnMwTmcuOhlOrOJsgwzvjEZmsM/IeHulgJk+r46STjdJS/ZJbxO8N70ODXDMEQ==} fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -7159,9 +6968,6 @@ packages: fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - fd-package-json@1.2.0: - resolution: {integrity: sha512-45LSPmWf+gC5tdCQMNH4s9Sr00bIkiD9aN7dc5hqkrEw1geRYyDQS1v1oMHAW3ysfxfndqGsrDREHHjNNbKUfA==} - fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} @@ -7173,9 +6979,6 @@ packages: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} - fetch-retry@5.0.4: - resolution: {integrity: sha512-LXcdgpdcVedccGg0AZqg+S8lX/FCdwXD92WNZ5k5qsb0irRhSFsBOpcJt7oevyqT2/C2nEE0zSFNdBEpj3YOSw==} - figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -7188,15 +6991,12 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - file-system-cache@2.3.0: - resolution: {integrity: sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ==} - file-type@17.1.6: resolution: {integrity: sha512-hlDw5Ev+9e883s0pwUsuuYNu4tD7GgpUnOvykjv1Gya0ZIjuKumthDRua90VUn6/nlRKAjcxLUnHNTIUWwWIiw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - file-type@19.3.0: - resolution: {integrity: sha512-mROwiKLZf/Kwa/2Rol+OOZQn1eyTkPB3ZTwC0ExY6OLFCbgxHYZvBm7xI77NvfZFMKBsmuXfmLJnD4eEftEhrA==} + file-type@19.5.0: + resolution: {integrity: sha512-dMuq6WWnP6BpQY0zYJNpTtQWgeCImSMG0BTIzUBXvxbwc1HWP/E7AE4UWU9XSCOPGJuOHda0HpDnwM2FW+d90A==} engines: {node: '>=18'} filelist@1.0.4: @@ -7222,25 +7022,21 @@ packages: resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} engines: {node: '>= 0.8'} - find-cache-dir@2.1.0: - resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} - engines: {node: '>=6'} + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + engines: {node: '>= 0.8'} find-cache-dir@3.3.2: resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} engines: {node: '>=8'} - find-my-way@8.2.0: - resolution: {integrity: sha512-HdWXgFYc6b1BJcOBDBwjqWuHJj1WYiqrxSh25qtU4DabpMFdj/gSunNBQb83t+8Zt67D7CXEzJWTkxaShMTMOA==} + find-my-way@9.0.1: + resolution: {integrity: sha512-/5NN/R0pFWuff16TMajeKt2JyiW+/OE8nOO8vo1DwZTxLaIURb7lcBYPIgRPh61yCNh9l8voeKwcrkUzmB00vw==} engines: {node: '>=14'} find-package-json@1.2.0: resolution: {integrity: sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==} - find-up@3.0.0: - resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} - engines: {node: '>=6'} - find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -7264,10 +7060,6 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - flow-parser@0.202.0: - resolution: {integrity: sha512-ZiXxSIXK3zPmY3zrzCofFonM2T+/3Jz5QZKJyPVtUERQEJUnYkXBQ+0H3FzyqiyJs+VXqb/UNU6/K6sziVYdxw==} - engines: {node: '>=0.4.0'} - fluent-ffmpeg@2.1.3: resolution: {integrity: sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==} engines: {node: '>=18'} @@ -7281,6 +7073,15 @@ packages: debug: optional: true + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -7364,6 +7165,10 @@ packages: resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} engines: {node: '>= 0.4'} + function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} @@ -7386,6 +7191,10 @@ packages: get-intrinsic@1.2.1: resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -7417,6 +7226,10 @@ packages: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} engines: {node: '>= 0.4'} + get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + engines: {node: '>= 0.4'} + get-tsconfig@4.7.2: resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} @@ -7430,10 +7243,6 @@ packages: resolution: {integrity: sha512-++rNGpDBgWQ9eXj9JfTBLHMUEd7lDOdzIvFyHQM9yL8ffxkcg4G6jWmsgu/r59Uq6nHc3wcVwtgy3geLnIWunQ==} engines: {node: '>= 0.8.0'} - giget@1.1.2: - resolution: {integrity: sha512-HsLoS07HiQ5oqvObOI+Qb2tyZH4Gj5nYGfF9qQcZNrPw+uEFhdXtgJr01aO2pWadGHucajYDLxxbtQkm97ON2A==} - hasBin: true - github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} @@ -7451,19 +7260,11 @@ packages: peerDependencies: glob: ^7.1.6 - glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - glob@10.3.10: resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true - glob@10.4.2: - resolution: {integrity: sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==} - engines: {node: '>=16 || 14 >=14.18'} - hasBin: true - glob@11.0.0: resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==} engines: {node: 20 || >=22} @@ -7494,8 +7295,8 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@15.8.0: - resolution: {integrity: sha512-VZAJ4cewHTExBWDHR6yptdIBlx9YSSZuwojj9Nt5mBRXQzrKakDsVKQ1J63sklLvzAJm0X5+RpO4i3Y2hcOnFw==} + globals@15.9.0: + resolution: {integrity: sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==} engines: {node: '>=18'} globalthis@1.0.3: @@ -7506,10 +7307,6 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} - globby@14.0.1: - resolution: {integrity: sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==} - engines: {node: '>=18'} - google-protobuf@3.21.2: resolution: {integrity: sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==} @@ -7545,14 +7342,13 @@ packages: resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==} engines: {node: '>=0.8.0'} - handlebars@4.7.7: - resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==} - engines: {node: '>=0.4.7'} - hasBin: true - happy-dom@10.0.3: resolution: {integrity: sha512-WkCP+Z5fX6U5PY+yHP3ElV5D9PoxRAHRWPFq3pG9rg/6Hjf5ak7dozAgSCywsTRUq2qfa8vV8OQvUy5pRXy8EQ==} + happy-dom@15.7.4: + resolution: {integrity: sha512-r1vadDYGMtsHAAsqhDuk4IpPvr6N8MGKy5ntBo7tSdim+pWDxus2PNqOcOt8LuDZ4t3KJHE+gCuzupcx/GKnyQ==} + engines: {node: '>=18.0.0'} + har-schema@2.0.0: resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} engines: {node: '>=4'} @@ -7580,10 +7376,17 @@ packages: has-property-descriptors@1.0.0: resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + has-proto@1.0.1: resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} engines: {node: '>= 0.4'} + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} @@ -7592,6 +7395,10 @@ packages: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} engines: {node: '>= 0.4'} + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} @@ -7609,6 +7416,10 @@ packages: resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} engines: {node: '>= 0.4'} + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + hast-util-heading-rank@3.0.0: resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} @@ -7628,8 +7439,8 @@ packages: highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} - highlight.js@11.9.0: - resolution: {integrity: sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==} + highlight.js@11.10.0: + resolution: {integrity: sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==} engines: {node: '>=12.0.0'} hosted-git-info@2.8.9: @@ -7661,9 +7472,15 @@ packages: resolution: {integrity: sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==} engines: {node: '>=0.10'} + htmlparser2@5.0.1: + resolution: {integrity: sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==} + htmlparser2@8.0.1: resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==} + htmlparser2@9.1.0: + resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} + http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} @@ -7683,8 +7500,8 @@ packages: resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} engines: {node: '>=0.8', npm: '>=1.3.7'} - http-signature@1.3.6: - resolution: {integrity: sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==} + http-signature@1.4.0: + resolution: {integrity: sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==} engines: {node: '>=0.10'} http2-wrapper@1.0.3: @@ -7711,10 +7528,6 @@ packages: resolution: {integrity: sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==} engines: {node: '>= 14'} - https-proxy-agent@7.0.4: - resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==} - engines: {node: '>= 14'} - https-proxy-agent@7.0.5: resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} engines: {node: '>= 14'} @@ -7735,8 +7548,8 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} - human-signals@7.0.0: - resolution: {integrity: sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q==} + human-signals@8.0.0: + resolution: {integrity: sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==} engines: {node: '>=18.18.0'} iconv-lite@0.4.24: @@ -7804,6 +7617,7 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -7826,6 +7640,10 @@ packages: resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} engines: {node: '>= 0.4'} + internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + engines: {node: '>= 0.4'} + intersection-observer@0.12.2: resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} @@ -7840,8 +7658,8 @@ packages: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} - ip-cidr@4.0.1: - resolution: {integrity: sha512-V5Nce94SVJ7NtyT/UKUeTM7sY3V7TEk48hURhtBgTiGduOa5t6p9Hd+zBOGvr4Gu7iWPxFVYNl017p0akQA84w==} + ip-cidr@4.0.2: + resolution: {integrity: sha512-KifhLKBjdS/hB3TD4UUOalVp1BpzPFvRpgJvXcP0Ya98tuSQTUQ71iI7EW7CKddkBJTYB3GfTWl5eJwpLOXj2A==} engines: {node: '>=16.14.0'} ip-regex@4.3.0: @@ -7874,6 +7692,10 @@ packages: is-array-buffer@3.0.2: resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -7905,6 +7727,14 @@ packages: is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} + is-date-object@1.0.5: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} engines: {node: '>= 0.4'} @@ -7944,10 +7774,6 @@ packages: resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} engines: {node: '>=10'} - is-interactive@1.0.0: - resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} - engines: {node: '>=8'} - is-ip@3.1.0: resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==} engines: {node: '>=8'} @@ -7958,14 +7784,14 @@ packages: is-map@2.0.2: resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} - is-nan@1.3.2: - resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} - engines: {node: '>= 0.4'} - is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + is-node-process@1.2.0: resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} @@ -7989,10 +7815,6 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} - is-plain-object@2.0.4: - resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} - engines: {node: '>=0.10.0'} - is-plain-object@5.0.0: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} @@ -8013,6 +7835,10 @@ packages: is-shared-array-buffer@1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} + is-stream@1.1.0: resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} engines: {node: '>=0.10.0'} @@ -8033,8 +7859,8 @@ packages: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} - is-svg@5.0.1: - resolution: {integrity: sha512-mLYxDsfisQWdS4+gSblAwhATDoNMS/tx8G7BKA+aBIf7F0m1iUwMvuKAo6mW4WMleQAEE50I1Zqef9yMMfHk3w==} + is-svg@5.1.0: + resolution: {integrity: sha512-uVg5yifaTxHoefNf5Jcx+i9RZe2OBYd/UStp1umx+EERa4xGRa3LLGXjoEph43qUORC0qkafUgrXZ6zzK89yGA==} engines: {node: '>=14.16'} is-symbol@1.0.4: @@ -8045,6 +7871,10 @@ packages: resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} engines: {node: '>= 0.4'} + is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + engines: {node: '>= 0.4'} + is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} @@ -8085,10 +7915,6 @@ packages: resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} engines: {node: '>=16'} - isobject@3.0.1: - resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} - engines: {node: '>=0.10.0'} - isstream@0.1.2: resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} @@ -8128,10 +7954,6 @@ packages: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} engines: {node: '>=14'} - jackspeak@3.4.0: - resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} - engines: {node: '>=14'} - jackspeak@4.0.1: resolution: {integrity: sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==} engines: {node: 20 || >=22} @@ -8282,6 +8104,9 @@ packages: joi@17.11.0: resolution: {integrity: sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==} + joi@17.13.3: + resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + jpeg-js@0.3.7: resolution: {integrity: sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==} @@ -8317,14 +8142,9 @@ packages: resolution: {integrity: sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ==} engines: {node: '>=0.1.90'} - jscodeshift@0.15.1: - resolution: {integrity: sha512-hIJfxUy8Rt4HkJn/zZPU9ChKfKZM1342waJ1QC2e2YsPcWhM+3BJ4dcfQCzArTrk1jJeNLB341H+qOcEHRxJZg==} - hasBin: true - peerDependencies: - '@babel/preset-env': ^7.1.6 - peerDependenciesMeta: - '@babel/preset-env': - optional: true + jsdoc-type-pratt-parser@4.1.0: + resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} + engines: {node: '>=12.0.0'} jsdom@24.1.1: resolution: {integrity: sha512-5O1wWV99Jhq4DV7rCLIoZ/UIhyQeDR7wHVyZAHAshbrvZsLs+Xzz7gtwnlJTJDjleiTKh54F4dXrX70vJQTyJQ==} @@ -8335,10 +8155,6 @@ packages: canvas: optional: true - jsesc@0.5.0: - resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} - hasBin: true - jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} @@ -8350,6 +8166,9 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-ref-resolver@1.0.1: + resolution: {integrity: sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -8412,6 +8231,11 @@ packages: jstransformer@1.0.0: resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==} + juice@11.0.0: + resolution: {integrity: sha512-sGF8hPz9/Wg+YXbaNDqc1Iuoaw+J/P9lBHNQKXAGc9pPNjCd4fyPai0Zxj7MRtdjMr0lcgk5PjEIkP2b8R9F3w==} + engines: {node: '>=18.17'} + hasBin: true + just-extend@4.2.1: resolution: {integrity: sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==} @@ -8450,10 +8274,6 @@ packages: resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==} engines: {node: '> 0.8'} - lazy-universal-dotenv@4.0.0: - resolution: {integrity: sha512-aXpZJRnTkpK6gQ/z4nk+ZBLd/Qdp118cvPruLSIQzQNRhKwEcdXCOzXuF55VDqIiuAaY3UGZ10DJtvZzDcvsxg==} - engines: {node: '>=14.0.0'} - lazystream@1.0.1: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} @@ -8466,8 +8286,8 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - light-my-request@5.11.0: - resolution: {integrity: sha512-qkFCeloXCOMpmEdZ/MV91P8AT4fjwFXWaAFz3lUeStM8RcoM1ks4J/F8r1b3r6y/H4u3ACEJ1T+Gv5bopj7oDA==} + light-my-request@6.0.0: + resolution: {integrity: sha512-kFkFXrmKCL0EEeOmJybMH5amWFd+AFvlvMlvFTRxCUwbhfapZqDmeLMPoWihntnYY6JpoQDE9k+vOzObF1fDqg==} lilconfig@3.1.1: resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==} @@ -8489,10 +8309,6 @@ packages: resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} - locate-path@3.0.0: - resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} - engines: {node: '>=6'} - locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -8501,9 +8317,6 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash.debounce@4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} @@ -8549,6 +8362,9 @@ packages: loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + loupe@3.1.1: + resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} + lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} @@ -8598,16 +8414,15 @@ packages: magic-string@0.30.10: resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + magicast@0.3.4: resolution: {integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==} mailcheck@1.1.1: resolution: {integrity: sha512-3WjL8+ZDouZwKlyJBMp/4LeziLFXgleOdsYu87piGcMLqhBzCsy2QFdbtAwv757TFC/rtqd738fgJw1tFQCSgA==} - make-dir@2.1.0: - resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} - engines: {node: '>=6'} - make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -8692,12 +8507,15 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - meilisearch@0.41.0: - resolution: {integrity: sha512-5KcGLxEXD7E+uNO7R68rCbGSHgCqeM3Q3RFFLSsN7ZrIgr8HPDXVAIlP4LHggAZfk0FkSzo8VSXifHCwa2k80g==} + meilisearch@0.42.0: + resolution: {integrity: sha512-pXaOPx/uhVGYVpejNuOcXifQVJlRVSxtvpgrGKb7ygmYo4qSNXkQXPxq1p0Tv+4/RsPJug3W04pcNnYXiqungA==} memoizerific@1.11.3: resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} + mensch@0.3.4: + resolution: {integrity: sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==} + meow@9.0.0: resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} engines: {node: '>=10'} @@ -8705,6 +8523,9 @@ packages: merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -8807,8 +8628,8 @@ packages: micromark@4.0.0: resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} mime-db@1.52.0: @@ -8824,6 +8645,11 @@ packages: engines: {node: '>=4'} hasBin: true + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} @@ -8952,8 +8778,8 @@ packages: mlly@1.5.0: resolution: {integrity: sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==} - mnemonist@0.39.6: - resolution: {integrity: sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==} + mnemonist@0.39.8: + resolution: {integrity: sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==} mock-socket@9.3.1: resolution: {integrity: sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==} @@ -8962,10 +8788,6 @@ packages: module-details-from-path@1.0.3: resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} - mri@1.2.0: - resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} - engines: {node: '>=4'} - ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -9001,6 +8823,16 @@ packages: typescript: optional: true + msw@2.4.9: + resolution: {integrity: sha512-1m8xccT6ipN4PTqLinPwmzhxQREuxaEJYdx4nIbggxP8aM7r1e71vE7RtOUSQoAm1LydjGfZKy7370XD/tsuYg==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} @@ -9063,9 +8895,6 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - nested-property@4.0.0: resolution: {integrity: sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==} @@ -9094,17 +8923,10 @@ packages: resolution: {integrity: sha512-Jx5lPaaLdIaOsj2mVLWMWulXF6GQVdyLvNSxmiYCvZ8Ma2hfKX0POoR2kgKOqz+oFsRreq0yYZjQ2wjE9VNzCA==} engines: {node: '>=v0.6.5'} - node-dir@0.1.17: - resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} - engines: {node: '>= 0.10.5'} - node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} - node-fetch-native@1.0.2: - resolution: {integrity: sha512-KIkvH1jl6b3O7es/0ShyCgWLcfXxlBrLBbP3rOr23WArC66IMcU4DeZEeYEOwnopYhawLTn7/y+YtmASe8DFVQ==} - node-fetch@2.6.13: resolution: {integrity: sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==} engines: {node: 4.x || >=6.0.0} @@ -9135,8 +8957,8 @@ packages: resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} hasBin: true - node-gyp@10.1.0: - resolution: {integrity: sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==} + node-gyp@10.2.0: + resolution: {integrity: sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==} engines: {node: ^16.14.0 || >=18.0.0} hasBin: true @@ -9146,8 +8968,8 @@ packages: node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} - nodemailer@6.9.14: - resolution: {integrity: sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==} + nodemailer@6.9.15: + resolution: {integrity: sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==} engines: {node: '>=6.0.0'} nodemon@3.0.2: @@ -9155,8 +8977,8 @@ packages: engines: {node: '>=10'} hasBin: true - nodemon@3.1.4: - resolution: {integrity: sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==} + nodemon@3.1.7: + resolution: {integrity: sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==} engines: {node: '>=10'} hasBin: true @@ -9198,10 +9020,6 @@ packages: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} - normalize-url@8.0.0: - resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==} - engines: {node: '>=14.16'} - normalize-url@8.0.1: resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==} engines: {node: '>=14.16'} @@ -9222,6 +9040,10 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + npmlog@5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} deprecated: This package is no longer supported. @@ -9257,6 +9079,10 @@ packages: object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} + object-is@1.1.5: resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} engines: {node: '>= 0.4'} @@ -9269,15 +9095,20 @@ packages: resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} engines: {node: '>= 0.4'} - object.fromentries@2.0.7: - resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} + object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} engines: {node: '>= 0.4'} - object.groupby@1.0.1: - resolution: {integrity: sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==} + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} - object.values@1.1.7: - resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.0: + resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} obliterator@2.0.4: @@ -9300,10 +9131,6 @@ packages: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} - on-headers@1.0.2: - resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} - engines: {node: '>= 0.8'} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -9336,10 +9163,6 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} - engines: {node: '>=10'} - os-filter-obj@2.0.0: resolution: {integrity: sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==} engines: {node: '>=4'} @@ -9350,12 +9173,15 @@ packages: ospath@1.2.2: resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==} - otpauth@9.3.1: - resolution: {integrity: sha512-E6d2tMxPofHNk4sRFp+kqW7vQ+WJGO9VLI2N/W00DnI+ThskU12Qa10kyNSGklrzhN5c+wRUsN4GijVgCU2N9w==} + otpauth@9.3.2: + resolution: {integrity: sha512-KixtXWN9RGdS8WHPfDo7qsOYiivCbl+VeLBT+7HBTtJebBO6aXr/bpZXr+TwY2COecdY82VeBghm31mLYQVZlQ==} outvariant@1.4.2: resolution: {integrity: sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} @@ -9384,10 +9210,6 @@ packages: resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} engines: {node: '>=18'} - p-locate@3.0.0: - resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} - engines: {node: '>=6'} - p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -9439,6 +9261,9 @@ packages: parse5-htmlparser2-tree-adapter@7.0.0: resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} + parse5-parser-stream@7.1.2: + resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} + parse5@5.1.1: resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} @@ -9455,10 +9280,6 @@ packages: path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} - path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -9486,51 +9307,53 @@ packages: resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} engines: {node: '>=16 || 14 >=14.17'} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - path-scurry@2.0.0: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} + path-to-regexp@0.1.10: + resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} + path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} path-to-regexp@1.8.0: resolution: {integrity: sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==} - path-to-regexp@3.2.0: - resolution: {integrity: sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==} + path-to-regexp@3.3.0: + resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} path-to-regexp@6.2.1: resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - path-type@5.0.0: - resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} - engines: {node: '>=12'} - pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + pause-stream@0.0.11: resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} - peek-readable@5.0.0: - resolution: {integrity: sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==} - engines: {node: '>=14.16'} - peek-readable@5.1.3: resolution: {integrity: sha512-kCsc9HwH5RgVA3H3VqkWFyGQwsxUxLdiSX1d5nqAm7hnMFjNFX1VhBLmJoUY0hZNc8gmDNgBkLjfhiWPsziXWA==} engines: {node: '>=14.16'} + peek-readable@5.2.0: + resolution: {integrity: sha512-U94a+eXHzct7vAd19GH3UQ2dH4Satbng0MyYTMaQatL0pvYYL5CTPR25HBhKtecl+4bfu1/i3vC6k0hydO5Vcw==} + engines: {node: '>=14.16'} + pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} @@ -9540,8 +9363,8 @@ packages: pg-cloudflare@1.1.1: resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} - pg-connection-string@2.6.4: - resolution: {integrity: sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==} + pg-connection-string@2.7.0: + resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} @@ -9551,14 +9374,17 @@ packages: resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} engines: {node: '>=4'} - pg-pool@3.6.2: - resolution: {integrity: sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==} + pg-pool@3.7.0: + resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==} peerDependencies: pg: '>=8.0' pg-protocol@1.6.1: resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==} + pg-protocol@1.7.0: + resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} @@ -9567,8 +9393,8 @@ packages: resolution: {integrity: sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==} engines: {node: '>=10'} - pg@8.12.0: - resolution: {integrity: sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==} + pg@8.13.0: + resolution: {integrity: sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==} engines: {node: '>= 8.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -9589,6 +9415,9 @@ packages: picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -9601,10 +9430,6 @@ packages: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} - pify@4.0.1: - resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} - engines: {node: '>=6'} - pino-abstract-transport@1.2.0: resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==} @@ -9626,18 +9451,10 @@ packages: resolution: {integrity: sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==} engines: {node: '>=16.20.0'} - pkg-dir@3.0.0: - resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==} - engines: {node: '>=6'} - pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} - pkg-dir@5.0.0: - resolution: {integrity: sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==} - engines: {node: '>=10'} - pkg-types@1.0.3: resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} @@ -9664,6 +9481,10 @@ packages: resolution: {integrity: sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==} engines: {node: '>=10'} + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + postcss-calc@9.0.1: resolution: {integrity: sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==} engines: {node: ^14 || ^16 || >=18.0} @@ -9814,10 +9635,6 @@ packages: peerDependencies: postcss: ^8.4.31 - postcss-selector-parser@6.0.15: - resolution: {integrity: sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==} - engines: {node: '>=4'} - postcss-selector-parser@6.0.16: resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==} engines: {node: '>=4'} @@ -9837,14 +9654,14 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.38: - resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.4.40: resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==} engines: {node: ^10 || ^12 || >=14} + postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} + engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -9901,10 +9718,6 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - pretty-hrtime@1.0.3: - resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==} - engines: {node: '>= 0.8'} - pretty-ms@9.0.0: resolution: {integrity: sha512-E9e9HJ9R9NasGOgPaPE8VMeiPKAyWR5jcFpNnwIejslIhWqdqOrb2wShBsncMPUb+BcCd2OPYfh7p2W6oemTng==} engines: {node: '>=18'} @@ -9915,8 +9728,8 @@ packages: probe-image-size@7.2.3: resolution: {integrity: sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==} - proc-log@3.0.0: - resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} + proc-log@4.2.0: + resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} process-exists@5.0.0: @@ -9926,12 +9739,12 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - process-warning@2.2.0: - resolution: {integrity: sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==} - process-warning@3.0.0: resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} + process-warning@4.0.0: + resolution: {integrity: sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==} + process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -10047,21 +9860,17 @@ packages: resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==} engines: {node: '>=6.0.0'} - qrcode@1.5.3: - resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==} + qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} engines: {node: '>=10.13.0'} hasBin: true - qs@6.10.4: - resolution: {integrity: sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==} - engines: {node: '>=0.6'} - qs@6.11.0: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} engines: {node: '>=0.6'} - qs@6.11.1: - resolution: {integrity: sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==} + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} qs@6.5.3: @@ -10091,9 +9900,6 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} - ramda@0.29.0: - resolution: {integrity: sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==} - random-seed@0.3.0: resolution: {integrity: sha512-y13xtn3kcTlLub3HKWXxJNeC2qK4mB59evwZ5EkeRlolx+Bp2ztF7LbcZmyCnOqlHQrLnfuNbi1sVmm9lPDlDA==} engines: {node: '>= 0.6.0'} @@ -10109,12 +9915,16 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} + rdf-canonize@3.4.0: resolution: {integrity: sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA==} engines: {node: '>=12'} - re2@1.21.3: - resolution: {integrity: sha512-GI+KoGkHT4kxTaX+9p0FgNB1XUnCndO9slG5qqeEoZ7kbf6Dk6ohQVpmwKVeSp7LPLn+g6Q3BaCopz4oHuBDuQ==} + re2@1.21.4: + resolution: {integrity: sha512-MVIfXWJmsP28mRsSt8HeL750ifb8H5+oF2UDIxGaiJCr8fkMqhLZ7kcX9ADRk2dC8qeGKedB7UVYRfBVpEiLfA==} react-colorful@5.6.1: resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==} @@ -10224,33 +10034,19 @@ packages: reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} - regenerate-unicode-properties@10.1.0: - resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==} - engines: {node: '>=4'} - - regenerate@1.4.2: - resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} - regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} regenerator-runtime@0.14.0: resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} - regenerator-transform@0.15.2: - resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} - regexp.prototype.flags@1.5.0: resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} engines: {node: '>= 0.4'} - regexpu-core@5.3.2: - resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} - engines: {node: '>=4'} - - regjsparser@0.9.1: - resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} - hasBin: true + regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} + engines: {node: '>= 0.4'} rehype-external-links@3.0.0: resolution: {integrity: sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==} @@ -10333,8 +10129,8 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} - ret@0.4.3: - resolution: {integrity: sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==} + ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} engines: {node: '>=10'} retry@0.12.0: @@ -10348,10 +10144,8 @@ packages: rfdc@1.3.0: resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} - rimraf@2.6.3: - resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} @@ -10363,8 +10157,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rollup@4.19.1: - resolution: {integrity: sha512-K5vziVlg7hTpYfFBI+91zHBEMo6jafYXpkMlqZjg7/zhIG9iHqazBf4xz9AVdjS9BruRn280ROqLI7G3OFRIlw==} + rollup@4.22.2: + resolution: {integrity: sha512-JWWpTrZmqQGQWt16xvNn6KVIUz16VtZwl984TKw0dfqqRpFwtLJYYk1/4BTgplndMQKWUk/yB4uOShYmMzA2Vg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -10387,6 +10181,10 @@ packages: resolution: {integrity: sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==} engines: {node: '>=0.4'} + safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + engines: {node: '>=0.4'} + safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -10396,8 +10194,12 @@ packages: safe-regex-test@1.0.0: resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} - safe-regex2@3.1.0: - resolution: {integrity: sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==} + safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} + + safe-regex2@4.0.0: + resolution: {integrity: sha512-Hvjfv25jPDVr3U+4LDzBuZPPOymELG3PYcSk5hcevooo1yxxamQL/bHs/GrEPGmMoMEwRrHVGiCA1pXi97B8Ew==} safe-stable-stringify@2.4.2: resolution: {integrity: sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==} @@ -10409,8 +10211,8 @@ packages: sanitize-html@2.13.0: resolution: {integrity: sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==} - sass@1.77.8: - resolution: {integrity: sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==} + sass@1.79.3: + resolution: {integrity: sha512-m7dZxh0W9EZ3cw50Me5GOuYm/tVAJAn91SUnohLRo9cXBixGUOdvmryN+dXpwR831bhoY3Zv7rEFt85PUwTmzA==} engines: {node: '>=14.0.0'} hasBin: true @@ -10427,6 +10229,9 @@ packages: secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + secure-json-parse@3.0.0: + resolution: {integrity: sha512-YO+gVWyp97H+nCG/qdC8X819iKx5g+BpnO9nYT4uFq4uyI0rSxwtx5qD9rGfScg7FGLYu/YBf8uOtwQKv+gq8g==} + seedrandom@3.0.5: resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} @@ -10456,20 +10261,41 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + serve-static@1.15.0: resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} engines: {node: '>= 0.8.0'} + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} set-cookie-parser@2.6.0: resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} @@ -10480,13 +10306,9 @@ packages: resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} hasBin: true - shallow-clone@3.0.1: - resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} - engines: {node: '>=8'} - - sharp@0.33.4: - resolution: {integrity: sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==} - engines: {libvips: '>=8.15.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} @@ -10513,6 +10335,10 @@ packages: side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -10624,10 +10450,6 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - slash@5.1.0: - resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} - engines: {node: '>=14.16'} - slice-ansi@3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} engines: {node: '>=8'} @@ -10636,6 +10458,9 @@ packages: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} + slick@1.12.2: + resolution: {integrity: sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==} + smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -10666,6 +10491,10 @@ packages: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} @@ -10713,6 +10542,11 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + sshpk@1.18.0: + resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} + engines: {node: '>=0.10.0'} + hasBin: true + ssri@10.0.4: resolution: {integrity: sha512-12+IR2CB2C28MMAw0Ncqwj5QbTcs0nGIhgJzYWzDkb21vWmfNI83KS4f3Ci6GI98WreIfG7o9UXp3C0qbpA8nQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -10727,8 +10561,8 @@ packages: standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} - start-server-and-test@2.0.4: - resolution: {integrity: sha512-CKNeBTcP0hVqIlNismHMudb9q3lLdAjcVPO13/7gfI66fcJpeIb/o4NzQd1JK/CD+lfWVqr10ZH9Y14+OwlJuw==} + start-server-and-test@2.0.8: + resolution: {integrity: sha512-v2fV6NV2F7tL1ocwfI4Wpait+IKjRbT5l3ZZ+ZikXdMLmxYsS8ynGAsCQAUVXkVyGyS+UibsRnvgHkMvJIvCsw==} engines: {node: '>=16'} hasBin: true @@ -10743,9 +10577,6 @@ packages: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} engines: {node: '>= 0.4'} - store2@2.14.2: - resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} - storybook-addon-misskey-theme@https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640: resolution: {tarball: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640} version: 0.0.0 @@ -10765,8 +10596,8 @@ packages: react-dom: optional: true - storybook@8.2.6: - resolution: {integrity: sha512-8j30wDxQmkcqI0fWcSYFsUCjErsY1yTWbTW+yjbwM8DyW18Cud6CwbFRCxjFsH+2M0CjP6Pqs/m1PGI0vcQscQ==} + storybook@8.3.2: + resolution: {integrity: sha512-jfDPtoPTtXcQ4O82u6+VE0V8q05hnj9NdmTVJvUxab796FoEbhk07xFLynOopfd9h9i0D/jc5Sf4C+iMe1bhmA==} hasBin: true stream-browserify@3.0.0: @@ -10778,10 +10609,6 @@ packages: stream-parser@0.3.1: resolution: {integrity: sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==} - stream-wormhole@1.1.0: - resolution: {integrity: sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==} - engines: {node: '>=4.0.0'} - streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -10815,12 +10642,23 @@ packages: resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==} engines: {node: '>= 0.4'} + string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + engines: {node: '>= 0.4'} + string.prototype.trimend@1.0.6: resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} + string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + string.prototype.trimstart@1.0.6: resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + string_decoder@0.10.31: resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} @@ -10891,8 +10729,8 @@ packages: resolution: {integrity: sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==} engines: {node: '>=14.16'} - strtok3@8.0.1: - resolution: {integrity: sha512-HNkTAnNWQj2YBzfTtoC5OQyu1QwPsMwiB7VyQmNvQKCrmEDSvFB857Vh97UY9InGLNRAB91sdS1ztifRo/3hdA==} + strtok3@8.1.0: + resolution: {integrity: sha512-ExzDvHYPj6F6QkSNe/JxSlBxTh3OrI6wrAIz53ulxo1c4hBJ1bT9C/JrAthEKHWG9riVH3Xzg7B03Oxty6S2Lw==} engines: {node: '>=16'} stylehacks@6.1.1: @@ -10933,8 +10771,8 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - systeminformation@5.22.11: - resolution: {integrity: sha512-aLws5yi4KCHTb0BVvbodQY5bY8eW4asMRDTxTW46hqw9lGjACX6TlLdJrkdoHYRB0qs+MekqEq1zG7WDnWE8Ug==} + systeminformation@5.23.5: + resolution: {integrity: sha512-PEpJwhRYxZgBCAlWZhWIgfMTjXLqfcaZ1pJsJn9snWNfBW/Z1YQg1mbIUSWrEV3ErAHF7l/OoVLQeaZDlPzkpA==} engines: {node: '>=8.0.0'} os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true @@ -10957,20 +10795,8 @@ packages: telejson@7.2.0: resolution: {integrity: sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ==} - temp-dir@3.0.0: - resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} - engines: {node: '>=14.16'} - - temp@0.8.4: - resolution: {integrity: sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==} - engines: {node: '>=6.0.0'} - - tempy@3.1.0: - resolution: {integrity: sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==} - engines: {node: '>=14.16'} - - terser@5.31.3: - resolution: {integrity: sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA==} + terser@5.33.0: + resolution: {integrity: sha512-JuPVaB7s1gdFKPKTelwUyRq5Sid2A3Gko2S0PncwdBq7kN9Ti9HPWDQ06MPsEDGsZeVESjKEnyGy68quBk1w6g==} engines: {node: '>=10'} hasBin: true @@ -10994,8 +10820,8 @@ packages: thread-stream@3.1.0: resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - three@0.167.0: - resolution: {integrity: sha512-9Y1a66fpjqF3rhq7ivKTaKtjQLZ97Hj/lZ00DmZWaKHaQFH4uzYT1znwRDWQOcgMmCcOloQzo61gDmqO8l9xmA==} + three@0.168.0: + resolution: {integrity: sha512-6m6jXtDwMJEK/GGMbAOTSAmxNdzKvvBzgd7q8bE/7Tr6m7PaBh5kKLrN7faWtlglXbzj7sVba48Idwx+NRsZXw==} throttle-debounce@5.0.2: resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} @@ -11013,10 +10839,6 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - tiny-lru@10.0.1: - resolution: {integrity: sha512-Vst+6kEsWvb17Zpz14sRJV/f8bUWKhqm6Dc+v08iShmIJ/WxqWytHzCTd6m88pS33rE2zpX34TRmOpAJPloNCA==} - engines: {node: '>=6'} - tinybench@2.6.0: resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==} @@ -11027,10 +10849,18 @@ packages: resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} engines: {node: '>=14.0.0'} + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + tinyspy@2.2.0: resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} engines: {node: '>=14.0.0'} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + tmp@0.2.3: resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} engines: {node: '>=14.14'} @@ -11113,9 +10943,8 @@ packages: peerDependencies: typescript: '>=4.2.0' - ts-case-convert@2.0.2: - resolution: {integrity: sha512-vdKfx1VAdpvEBOBv5OpVu5ZFqRg9HdTI4sYt6qqMeICBeNyXvitrarCnFWNDAki51IKwCyx+ZssY46Q9jH5otA==} - bundledDependencies: [] + ts-case-convert@2.0.7: + resolution: {integrity: sha512-Kqj8wrkuduWsKUOUNRczrkdHCDt4ZNNd6HKjVw42EnMIGHQUABS4pqfy0acETVLwUTppc1fzo/yi11+uMTaqzw==} ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} @@ -11135,20 +10964,20 @@ packages: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} - tsd@0.31.1: - resolution: {integrity: sha512-sSL84A0SFwx2xGMWrxlGaarKFSQszWjJS2vgNDDLwatytzg2aq6ShlwHsBYxRNmjzXISODwMva5ZOdAg/4AoOA==} + tsd@0.31.2: + resolution: {integrity: sha512-VplBAQwvYrHzVihtzXiUVXu5bGcr7uH1juQZ1lmKgkuGNGT+FechUCqmx9/zk7wibcqR2xaNEwCkDyKh+VVZnQ==} engines: {node: '>=14.16'} hasBin: true - tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} tslib@2.6.3: resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + tsx@4.4.0: resolution: {integrity: sha512-4fwcEjRUxW20ciSaMB8zkpGwCPxuRGnadDuj/pBk5S9uT29zvWz15PK36GrKJo45mSJomDxVejZ73c6lr3811Q==} engines: {node: '>=18.0.0'} @@ -11188,10 +11017,6 @@ packages: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} - type-fest@1.4.0: - resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} - engines: {node: '>=10'} - type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} @@ -11208,17 +11033,33 @@ packages: resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} engines: {node: '>= 0.4'} + typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + engines: {node: '>= 0.4'} + typed-array-byte-length@1.0.0: resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} engines: {node: '>= 0.4'} + typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + engines: {node: '>= 0.4'} + typed-array-byte-offset@1.0.0: resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} engines: {node: '>= 0.4'} + typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + engines: {node: '>= 0.4'} + typed-array-length@1.0.4: resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + typed-array-length@1.0.6: + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + engines: {node: '>= 0.4'} + typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} @@ -11295,14 +11136,14 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.6.2: + resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} + engines: {node: '>=14.17'} + hasBin: true + ufo@1.3.2: resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==} - uglify-js@3.17.4: - resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} - engines: {node: '>=0.8.0'} - hasBin: true - uid2@0.0.4: resolution: {integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==} @@ -11327,28 +11168,19 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici@5.28.2: resolution: {integrity: sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==} engines: {node: '>=14.0'} - unicode-canonical-property-names-ecmascript@2.0.0: - resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} - engines: {node: '>=4'} + undici@6.19.8: + resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==} + engines: {node: '>=18.17'} - unicode-match-property-ecmascript@2.0.0: - resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} - engines: {node: '>=4'} - - unicode-match-property-value-ecmascript@2.1.0: - resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} - engines: {node: '>=4'} - - unicode-property-aliases-ecmascript@2.1.0: - resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} - engines: {node: '>=4'} - - unicorn-magic@0.1.0: - resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} unified@11.0.4: @@ -11365,10 +11197,6 @@ packages: resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - unique-string@3.0.0: - resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} - engines: {node: '>=12'} - unist-util-is@6.0.0: resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} @@ -11454,8 +11282,8 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true - v-code-diff@1.12.0: - resolution: {integrity: sha512-vvdCBG02mIIiW6Gx6jF119hzxELt+6TlJIwchglR1JYzboHePNxIkVBjR/aoAOVlsGa+5Vtb77cd/N84nrXWPA==} + v-code-diff@1.13.1: + resolution: {integrity: sha512-9LTV1dZhC1oYTntyB94vfumGgsfIX5u0fEDSI2Txx4vCE5sI5LkgeLJRRy2SsTVZmDcV+R73sBr0GpPn0TJxMw==} peerDependencies: '@vue/composition-api': ^1.4.9 vue: ^2.6.0 || >=3.0.0 @@ -11467,6 +11295,10 @@ packages: resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} engines: {node: '>=10.12.0'} + valid-data-url@3.0.1: + resolution: {integrity: sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==} + engines: {node: '>=10'} + validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -11492,8 +11324,8 @@ packages: vite-plugin-turbosnap@1.0.3: resolution: {integrity: sha512-p4D8CFVhZS412SyQX125qxyzOgIFouwOcvjZWk6bQbNPR1wtaEzFT6jZxAjf1dejlGqa6fqHcuCvQea6EWUkUA==} - vite@5.3.5: - resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==} + vite@5.4.7: + resolution: {integrity: sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -11501,6 +11333,7 @@ packages: less: '*' lightningcss: ^1.21.0 sass: '*' + sass-embedded: '*' stylus: '*' sugarss: '*' terser: ^5.4.0 @@ -11513,6 +11346,8 @@ packages: optional: true sass: optional: true + sass-embedded: + optional: true stylus: optional: true sugarss: @@ -11593,8 +11428,8 @@ packages: vue-component-type-helpers@2.0.16: resolution: {integrity: sha512-qisL/iAfdO++7w+SsfYQJVPj6QKvxp4i1MMxvsNO41z/8zu3KuAw9LkhKUfP/kcOWGDxESp+pQObWppXusejCA==} - vue-component-type-helpers@2.0.29: - resolution: {integrity: sha512-58i+ZhUAUpwQ+9h5Hck0D+jr1qbYl4voRt5KffBx8qzELViQ4XdT/Tuo+mzq8u63teAG8K0lLaOiL5ofqW38rg==} + vue-component-type-helpers@2.1.6: + resolution: {integrity: sha512-ng11B8B/ZADUMMOsRbqv0arc442q7lifSubD0v8oDXIFoMg/mXwAPUunrroIDkY+mcD0dHKccdaznSVp8EoX3w==} vue-demi@0.14.7: resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} @@ -11618,12 +11453,6 @@ packages: peerDependencies: eslint: '>=6.0.0' - vue-i18n@9.13.1: - resolution: {integrity: sha512-mh0GIxx0wPtPlcB1q4k277y0iKgo25xmDPWioVVYanjPufDBpvu5ySTjP5wOrSvlYQ2m1xI+CFhGdauv/61uQg==} - engines: {node: '>= 16'} - peerDependencies: - vue: ^3.0.0 - vue-inbrowser-compiler-independent-utils@4.71.1: resolution: {integrity: sha512-K3wt3iVmNGaFEOUR4JIThQRWfqokxLfnPslD41FDZB2ajXp789+wCqJyGYlIFsvEQ2P61PInw6/ph5iiqg51gg==} peerDependencies: @@ -11632,8 +11461,8 @@ packages: vue-template-compiler@2.7.14: resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==} - vue-tsc@2.0.29: - resolution: {integrity: sha512-MHhsfyxO3mYShZCGYNziSbc63x7cQ5g9kvijV7dRe1TTXBRLxXyL0FnXWpUF1xII2mJ86mwYpYsUmMwkmerq7Q==} + vue-tsc@2.1.6: + resolution: {integrity: sha512-f98dyZp5FOukcYmbFpuSCJ4Z0vHSOSmxGttZJCsFeX0M4w/Rsq0s4uKXjcSRsZqsRgQa6z7SfuO+y0HVICE57Q==} hasBin: true peerDependencies: typescript: '>=5.0.0' @@ -11646,6 +11475,14 @@ packages: typescript: optional: true + vue@3.5.7: + resolution: {integrity: sha512-JcFm0f5j8DQO9E07pZRxqZ/ZsNopMVzHYXpKvnfqXFcA4JTi+4YcrikRn9wkzWsdj0YsLzlLIsR0zzGxA2P6Wg==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + vuedraggable@4.1.0: resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==} peerDependencies: @@ -11655,29 +11492,23 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} - wait-on@7.2.0: - resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} + wait-on@8.0.1: + resolution: {integrity: sha512-1wWQOyR2LVVtaqrcIL2+OM+x7bkpmzVROa0Nf6FryXkS+er5Sa1kzFGjzZRqLnHa3n1rACFLeTwUqE1ETL9Mig==} engines: {node: '>=12.0.0'} hasBin: true - walk-up-path@3.0.1: - resolution: {integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==} - walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - watchpack@2.4.0: - resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} - engines: {node: '>=10.13.0'} - - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - web-push@3.6.7: resolution: {integrity: sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A==} engines: {node: '>= 16'} hasBin: true + web-resource-inliner@7.0.0: + resolution: {integrity: sha512-NlfnGF8MY9ZUwFjyq3vOUBx7KwF8bmE+ywR781SB0nWB6MoMxN4BA8gtgP1KGTZo/O/AyWJz7HZpR704eaj4mg==} + engines: {node: '>=10.0.0'} + web-streams-polyfill@3.2.1: resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} engines: {node: '>= 8'} @@ -11736,6 +11567,10 @@ packages: resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==} engines: {node: '>= 0.4'} + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} + which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true @@ -11766,9 +11601,6 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -11784,9 +11616,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - write-file-atomic@2.4.3: - resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==} - write-file-atomic@4.0.2: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -11897,7 +11726,7 @@ packages: snapshots: - '@adobe/css-tools@4.3.3': {} + '@adobe/css-tools@4.4.0': {} '@aiscript-dev/aiscript-languageserver@https://github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.6/aiscript-dev-aiscript-languageserver-0.1.6.tgz': dependencies: @@ -11909,17 +11738,11 @@ snapshots: '@ampproject/remapping@2.2.1': dependencies: - '@jridgewell/gen-mapping': 0.3.2 - '@jridgewell/trace-mapping': 0.3.18 - - '@apidevtools/openapi-schemas@2.1.0': {} + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 '@apidevtools/swagger-methods@3.0.2': {} - '@aw-web-design/x-default-browser@1.4.126': - dependencies: - default-browser-id: 3.0.0 - '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 @@ -12435,7 +12258,7 @@ snapshots: '@babel/code-frame@7.24.7': dependencies: '@babel/highlight': 7.24.7 - picocolors: 1.0.0 + picocolors: 1.0.1 '@babel/compat-data@7.23.5': {} @@ -12495,17 +12318,6 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 - '@babel/helper-annotate-as-pure@7.24.7': - dependencies: - '@babel/types': 7.24.7 - - '@babel/helper-builder-binary-assignment-operator-visitor@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - '@babel/helper-compilation-targets@7.22.15': dependencies: '@babel/compat-data': 7.23.5 @@ -12522,39 +12334,6 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-member-expression-to-functions': 7.24.7 - '@babel/helper-optimise-call-expression': 7.24.7 - '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7) - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/helper-create-regexp-features-plugin@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - regexpu-core: 5.3.2 - semver: 6.3.1 - - '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - debug: 4.3.5(supports-color@8.1.1) - lodash.debounce: 4.0.8 - resolve: 1.22.8 - transitivePeerDependencies: - - supports-color - '@babel/helper-environment-visitor@7.22.20': {} '@babel/helper-environment-visitor@7.24.7': @@ -12579,13 +12358,6 @@ snapshots: dependencies: '@babel/types': 7.24.7 - '@babel/helper-member-expression-to-functions@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-imports@7.22.15': dependencies: '@babel/types': 7.24.7 @@ -12617,32 +12389,8 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-optimise-call-expression@7.24.7': - dependencies: - '@babel/types': 7.24.7 - '@babel/helper-plugin-utils@7.22.5': {} - '@babel/helper-plugin-utils@7.24.7': {} - - '@babel/helper-remap-async-to-generator@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-wrap-function': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-replace-supers@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-member-expression-to-functions': 7.24.7 - '@babel/helper-optimise-call-expression': 7.24.7 - transitivePeerDependencies: - - supports-color - '@babel/helper-simple-access@7.22.5': dependencies: '@babel/types': 7.24.7 @@ -12654,13 +12402,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-skip-transparent-expression-wrappers@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - '@babel/helper-split-export-declaration@7.22.6': dependencies: '@babel/types': 7.24.7 @@ -12671,21 +12412,14 @@ snapshots: '@babel/helper-string-parser@7.24.7': {} + '@babel/helper-string-parser@7.24.8': {} + '@babel/helper-validator-identifier@7.24.7': {} '@babel/helper-validator-option@7.23.5': {} '@babel/helper-validator-option@7.24.7': {} - '@babel/helper-wrap-function@7.24.7': - dependencies: - '@babel/helper-function-name': 7.24.7 - '@babel/template': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - '@babel/helpers@7.23.5': dependencies: '@babel/template': 7.22.15 @@ -12710,52 +12444,21 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.0.0 + picocolors: 1.0.1 '@babel/parser@7.24.7': dependencies: '@babel/types': 7.24.7 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.7(@babel/core@7.24.7)': + '@babel/parser@7.25.6': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/plugin-transform-optional-chaining': 7.24.7(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 + '@babel/types': 7.25.6 '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 @@ -12766,631 +12469,68 @@ snapshots: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-flow@7.23.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-import-assertions@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-import-attributes@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-arrow-functions@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-async-generator-functions@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-remap-async-to-generator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-async-to-generator@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-remap-async-to-generator': 7.24.7(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-block-scoped-functions@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-block-scoping@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-class-properties@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-class-static-block@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-classes@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7) - '@babel/helper-split-export-declaration': 7.24.7 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-computed-properties@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/template': 7.24.7 - - '@babel/plugin-transform-destructuring@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-dotall-regex@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-duplicate-keys@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-dynamic-import@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.7) - - '@babel/plugin-transform-exponentiation-operator@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-builder-binary-assignment-operator-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-export-namespace-from@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.7) - - '@babel/plugin-transform-flow-strip-types@7.23.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-flow': 7.23.3(@babel/core@7.24.7) - - '@babel/plugin-transform-for-of@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-function-name@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-json-strings@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.7) - - '@babel/plugin-transform-literals@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-logical-assignment-operators@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7) - - '@babel/plugin-transform-member-expression-literals@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-modules-amd@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-modules-commonjs@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-modules-systemjs@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-hoist-variables': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-modules-umd@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-named-capturing-groups-regex@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-new-target@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-nullish-coalescing-operator@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7) - - '@babel/plugin-transform-numeric-separator@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7) - - '@babel/plugin-transform-object-rest-spread@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.24.7) - - '@babel/plugin-transform-object-super@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-optional-catch-binding@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7) - - '@babel/plugin-transform-optional-chaining@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-parameters@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-private-methods@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-private-property-in-object@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-property-literals@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-regenerator@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - regenerator-transform: 0.15.2 - - '@babel/plugin-transform-reserved-words@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-shorthand-properties@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-spread@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-sticky-regex@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-template-literals@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-typeof-symbol@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-typescript@7.23.5(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-unicode-escapes@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-unicode-property-regex@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-unicode-regex@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-unicode-sets-regex@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/preset-env@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/compat-data': 7.24.7 - '@babel/core': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.7) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.7) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.7) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-import-assertions': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.7) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.7) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.7) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.24.7) - '@babel/plugin-transform-arrow-functions': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-async-generator-functions': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-async-to-generator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-block-scoped-functions': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-block-scoping': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-class-properties': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-class-static-block': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-classes': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-computed-properties': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-destructuring': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-dotall-regex': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-duplicate-keys': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-dynamic-import': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-exponentiation-operator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-export-namespace-from': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-for-of': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-function-name': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-json-strings': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-literals': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-logical-assignment-operators': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-member-expression-literals': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-modules-amd': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-modules-systemjs': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-modules-umd': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-named-capturing-groups-regex': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-new-target': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-nullish-coalescing-operator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-numeric-separator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-object-rest-spread': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-object-super': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-optional-catch-binding': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-optional-chaining': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-private-methods': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-private-property-in-object': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-property-literals': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-regenerator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-reserved-words': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-shorthand-properties': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-spread': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-sticky-regex': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-template-literals': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-typeof-symbol': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-unicode-escapes': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-unicode-property-regex': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-unicode-regex': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-unicode-sets-regex': 7.24.7(@babel/core@7.24.7) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.24.7) - babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.24.7) - babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.7) - babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.24.7) - core-js-compat: 3.37.1 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/preset-flow@7.23.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - '@babel/plugin-transform-flow-strip-types': 7.23.3(@babel/core@7.24.7) - - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/types': 7.24.7 - esutils: 2.0.3 - - '@babel/preset-typescript@7.23.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.24.7) - '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-typescript': 7.23.5(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color - - '@babel/register@7.22.15(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - clone-deep: 4.0.1 - find-cache-dir: 2.1.0 - make-dir: 2.1.0 - pirates: 4.0.5 - source-map-support: 0.5.21 - - '@babel/regjsgen@0.8.0': {} - '@babel/runtime@7.23.4': dependencies: regenerator-runtime: 0.14.0 '@babel/template@7.22.15': dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.24.7 '@babel/parser': 7.24.7 '@babel/types': 7.24.7 @@ -13408,7 +12548,7 @@ snapshots: '@babel/traverse@7.23.5': dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.24.7 '@babel/generator': 7.23.5 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 @@ -13442,26 +12582,32 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 + '@babel/types@7.25.6': + dependencies: + '@babel/helper-string-parser': 7.24.8 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + '@base2/pretty-print-object@1.0.1': {} '@bcoe/v8-coverage@0.2.3': {} - '@bull-board/api@5.21.1(@bull-board/ui@5.21.1)': + '@bull-board/api@5.23.0(@bull-board/ui@5.23.0)': dependencies: - '@bull-board/ui': 5.21.1 + '@bull-board/ui': 5.23.0 redis-info: 3.1.0 - '@bull-board/fastify@5.21.1': + '@bull-board/fastify@5.23.0': dependencies: - '@bull-board/api': 5.21.1(@bull-board/ui@5.21.1) - '@bull-board/ui': 5.21.1 + '@bull-board/api': 5.23.0(@bull-board/ui@5.23.0) + '@bull-board/ui': 5.23.0 '@fastify/static': 6.12.0 '@fastify/view': 8.2.0 ejs: 3.1.10 - '@bull-board/ui@5.21.1': + '@bull-board/ui@5.23.0': dependencies: - '@bull-board/api': 5.21.1(@bull-board/ui@5.21.1) + '@bull-board/api': 5.23.0(@bull-board/ui@5.23.0) '@bundled-es-modules/cookie@2.0.0': dependencies: @@ -13481,73 +12627,73 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@cropper/element-canvas@2.0.0-rc.1': + '@cropper/element-canvas@2.0.0-rc.2': dependencies: - '@cropper/element': 2.0.0-rc.1 - '@cropper/utils': 2.0.0-rc.1 + '@cropper/element': 2.0.0-rc.2 + '@cropper/utils': 2.0.0-rc.2 - '@cropper/element-crosshair@2.0.0-rc.1': + '@cropper/element-crosshair@2.0.0-rc.2': dependencies: - '@cropper/element': 2.0.0-rc.1 - '@cropper/utils': 2.0.0-rc.1 + '@cropper/element': 2.0.0-rc.2 + '@cropper/utils': 2.0.0-rc.2 - '@cropper/element-grid@2.0.0-rc.1': + '@cropper/element-grid@2.0.0-rc.2': dependencies: - '@cropper/element': 2.0.0-rc.1 - '@cropper/utils': 2.0.0-rc.1 + '@cropper/element': 2.0.0-rc.2 + '@cropper/utils': 2.0.0-rc.2 - '@cropper/element-handle@2.0.0-rc.1': + '@cropper/element-handle@2.0.0-rc.2': dependencies: - '@cropper/element': 2.0.0-rc.1 - '@cropper/utils': 2.0.0-rc.1 + '@cropper/element': 2.0.0-rc.2 + '@cropper/utils': 2.0.0-rc.2 - '@cropper/element-image@2.0.0-rc.1': + '@cropper/element-image@2.0.0-rc.2': dependencies: - '@cropper/element': 2.0.0-rc.1 - '@cropper/element-canvas': 2.0.0-rc.1 - '@cropper/utils': 2.0.0-rc.1 + '@cropper/element': 2.0.0-rc.2 + '@cropper/element-canvas': 2.0.0-rc.2 + '@cropper/utils': 2.0.0-rc.2 - '@cropper/element-selection@2.0.0-rc.1': + '@cropper/element-selection@2.0.0-rc.2': dependencies: - '@cropper/element': 2.0.0-rc.1 - '@cropper/element-canvas': 2.0.0-rc.1 - '@cropper/element-image': 2.0.0-rc.1 - '@cropper/utils': 2.0.0-rc.1 + '@cropper/element': 2.0.0-rc.2 + '@cropper/element-canvas': 2.0.0-rc.2 + '@cropper/element-image': 2.0.0-rc.2 + '@cropper/utils': 2.0.0-rc.2 - '@cropper/element-shade@2.0.0-rc.1': + '@cropper/element-shade@2.0.0-rc.2': dependencies: - '@cropper/element': 2.0.0-rc.1 - '@cropper/element-canvas': 2.0.0-rc.1 - '@cropper/element-selection': 2.0.0-rc.1 - '@cropper/utils': 2.0.0-rc.1 + '@cropper/element': 2.0.0-rc.2 + '@cropper/element-canvas': 2.0.0-rc.2 + '@cropper/element-selection': 2.0.0-rc.2 + '@cropper/utils': 2.0.0-rc.2 - '@cropper/element-viewer@2.0.0-rc.1': + '@cropper/element-viewer@2.0.0-rc.2': dependencies: - '@cropper/element': 2.0.0-rc.1 - '@cropper/element-canvas': 2.0.0-rc.1 - '@cropper/element-image': 2.0.0-rc.1 - '@cropper/element-selection': 2.0.0-rc.1 - '@cropper/utils': 2.0.0-rc.1 + '@cropper/element': 2.0.0-rc.2 + '@cropper/element-canvas': 2.0.0-rc.2 + '@cropper/element-image': 2.0.0-rc.2 + '@cropper/element-selection': 2.0.0-rc.2 + '@cropper/utils': 2.0.0-rc.2 - '@cropper/element@2.0.0-rc.1': + '@cropper/element@2.0.0-rc.2': dependencies: - '@cropper/utils': 2.0.0-rc.1 + '@cropper/utils': 2.0.0-rc.2 - '@cropper/elements@2.0.0-rc.1': + '@cropper/elements@2.0.0-rc.2': dependencies: - '@cropper/element': 2.0.0-rc.1 - '@cropper/element-canvas': 2.0.0-rc.1 - '@cropper/element-crosshair': 2.0.0-rc.1 - '@cropper/element-grid': 2.0.0-rc.1 - '@cropper/element-handle': 2.0.0-rc.1 - '@cropper/element-image': 2.0.0-rc.1 - '@cropper/element-selection': 2.0.0-rc.1 - '@cropper/element-shade': 2.0.0-rc.1 - '@cropper/element-viewer': 2.0.0-rc.1 + '@cropper/element': 2.0.0-rc.2 + '@cropper/element-canvas': 2.0.0-rc.2 + '@cropper/element-crosshair': 2.0.0-rc.2 + '@cropper/element-grid': 2.0.0-rc.2 + '@cropper/element-handle': 2.0.0-rc.2 + '@cropper/element-image': 2.0.0-rc.2 + '@cropper/element-selection': 2.0.0-rc.2 + '@cropper/element-shade': 2.0.0-rc.2 + '@cropper/element-viewer': 2.0.0-rc.2 - '@cropper/utils@2.0.0-rc.1': {} + '@cropper/utils@2.0.0-rc.2': {} - '@cypress/request@3.0.0': + '@cypress/request@3.0.5': dependencies: aws-sign2: 0.7.0 aws4: 1.12.0 @@ -13555,14 +12701,14 @@ snapshots: combined-stream: 1.0.8 extend: 3.0.2 forever-agent: 0.6.1 - form-data: 2.3.3 - http-signature: 1.3.6 + form-data: 4.0.0 + http-signature: 1.4.0 is-typedarray: 1.0.0 isstream: 0.1.2 json-stringify-safe: 5.0.1 mime-types: 2.1.35 performance-now: 2.1.0 - qs: 6.10.4 + qs: 6.13.0 safe-buffer: 5.2.1 tough-cookie: 4.1.4 tunnel-agent: 0.6.0 @@ -13583,24 +12729,18 @@ snapshots: transitivePeerDependencies: - web-streams-polyfill - '@discordapp/twemoji@15.0.3': + '@discordapp/twemoji@15.1.0': dependencies: - '@twemoji/parser': 15.0.0 + '@twemoji/parser': 15.1.0 fs-extra: 8.1.0 jsonfile: 5.0.0 universalify: 0.1.2 - '@discoveryjs/json-ext@0.5.7': {} - - '@emnapi/runtime@1.1.1': + '@emnapi/runtime@1.2.0': dependencies: tslib: 2.6.3 optional: true - '@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.3.1)': - dependencies: - react: 18.3.1 - '@esbuild/aix-ppc64@0.19.11': optional: true @@ -13610,6 +12750,9 @@ snapshots: '@esbuild/aix-ppc64@0.23.0': optional: true + '@esbuild/aix-ppc64@0.23.1': + optional: true + '@esbuild/android-arm64@0.18.20': optional: true @@ -13622,6 +12765,9 @@ snapshots: '@esbuild/android-arm64@0.23.0': optional: true + '@esbuild/android-arm64@0.23.1': + optional: true + '@esbuild/android-arm@0.18.20': optional: true @@ -13634,6 +12780,9 @@ snapshots: '@esbuild/android-arm@0.23.0': optional: true + '@esbuild/android-arm@0.23.1': + optional: true + '@esbuild/android-x64@0.18.20': optional: true @@ -13646,6 +12795,9 @@ snapshots: '@esbuild/android-x64@0.23.0': optional: true + '@esbuild/android-x64@0.23.1': + optional: true + '@esbuild/darwin-arm64@0.18.20': optional: true @@ -13658,6 +12810,9 @@ snapshots: '@esbuild/darwin-arm64@0.23.0': optional: true + '@esbuild/darwin-arm64@0.23.1': + optional: true + '@esbuild/darwin-x64@0.18.20': optional: true @@ -13670,6 +12825,9 @@ snapshots: '@esbuild/darwin-x64@0.23.0': optional: true + '@esbuild/darwin-x64@0.23.1': + optional: true + '@esbuild/freebsd-arm64@0.18.20': optional: true @@ -13682,6 +12840,9 @@ snapshots: '@esbuild/freebsd-arm64@0.23.0': optional: true + '@esbuild/freebsd-arm64@0.23.1': + optional: true + '@esbuild/freebsd-x64@0.18.20': optional: true @@ -13694,6 +12855,9 @@ snapshots: '@esbuild/freebsd-x64@0.23.0': optional: true + '@esbuild/freebsd-x64@0.23.1': + optional: true + '@esbuild/linux-arm64@0.18.20': optional: true @@ -13706,6 +12870,9 @@ snapshots: '@esbuild/linux-arm64@0.23.0': optional: true + '@esbuild/linux-arm64@0.23.1': + optional: true + '@esbuild/linux-arm@0.18.20': optional: true @@ -13718,6 +12885,9 @@ snapshots: '@esbuild/linux-arm@0.23.0': optional: true + '@esbuild/linux-arm@0.23.1': + optional: true + '@esbuild/linux-ia32@0.18.20': optional: true @@ -13730,6 +12900,9 @@ snapshots: '@esbuild/linux-ia32@0.23.0': optional: true + '@esbuild/linux-ia32@0.23.1': + optional: true + '@esbuild/linux-loong64@0.18.20': optional: true @@ -13742,6 +12915,9 @@ snapshots: '@esbuild/linux-loong64@0.23.0': optional: true + '@esbuild/linux-loong64@0.23.1': + optional: true + '@esbuild/linux-mips64el@0.18.20': optional: true @@ -13754,6 +12930,9 @@ snapshots: '@esbuild/linux-mips64el@0.23.0': optional: true + '@esbuild/linux-mips64el@0.23.1': + optional: true + '@esbuild/linux-ppc64@0.18.20': optional: true @@ -13766,6 +12945,9 @@ snapshots: '@esbuild/linux-ppc64@0.23.0': optional: true + '@esbuild/linux-ppc64@0.23.1': + optional: true + '@esbuild/linux-riscv64@0.18.20': optional: true @@ -13778,6 +12960,9 @@ snapshots: '@esbuild/linux-riscv64@0.23.0': optional: true + '@esbuild/linux-riscv64@0.23.1': + optional: true + '@esbuild/linux-s390x@0.18.20': optional: true @@ -13790,6 +12975,9 @@ snapshots: '@esbuild/linux-s390x@0.23.0': optional: true + '@esbuild/linux-s390x@0.23.1': + optional: true + '@esbuild/linux-x64@0.18.20': optional: true @@ -13802,6 +12990,9 @@ snapshots: '@esbuild/linux-x64@0.23.0': optional: true + '@esbuild/linux-x64@0.23.1': + optional: true + '@esbuild/netbsd-x64@0.18.20': optional: true @@ -13814,9 +13005,15 @@ snapshots: '@esbuild/netbsd-x64@0.23.0': optional: true + '@esbuild/netbsd-x64@0.23.1': + optional: true + '@esbuild/openbsd-arm64@0.23.0': optional: true + '@esbuild/openbsd-arm64@0.23.1': + optional: true + '@esbuild/openbsd-x64@0.18.20': optional: true @@ -13829,6 +13026,9 @@ snapshots: '@esbuild/openbsd-x64@0.23.0': optional: true + '@esbuild/openbsd-x64@0.23.1': + optional: true + '@esbuild/sunos-x64@0.18.20': optional: true @@ -13841,6 +13041,9 @@ snapshots: '@esbuild/sunos-x64@0.23.0': optional: true + '@esbuild/sunos-x64@0.23.1': + optional: true + '@esbuild/win32-arm64@0.18.20': optional: true @@ -13853,6 +13056,9 @@ snapshots: '@esbuild/win32-arm64@0.23.0': optional: true + '@esbuild/win32-arm64@0.23.1': + optional: true + '@esbuild/win32-ia32@0.18.20': optional: true @@ -13865,6 +13071,9 @@ snapshots: '@esbuild/win32-ia32@0.23.0': optional: true + '@esbuild/win32-ia32@0.23.1': + optional: true + '@esbuild/win32-x64@0.18.20': optional: true @@ -13877,6 +13086,14 @@ snapshots: '@esbuild/win32-x64@0.23.0': optional: true + '@esbuild/win32-x64@0.23.1': + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@9.11.0)': + dependencies: + eslint: 9.11.0 + eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.4.0(eslint@9.8.0)': dependencies: eslint: 9.8.0 @@ -13891,7 +13108,15 @@ snapshots: '@eslint/config-array@0.17.1': dependencies: '@eslint/object-schema': 2.1.4 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.7 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-array@0.18.0': + dependencies: + '@eslint/object-schema': 2.1.4 + debug: 4.3.7 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -13899,7 +13124,7 @@ snapshots: '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.7 espree: 10.1.0 globals: 14.0.0 ignore: 5.3.1 @@ -13910,80 +13135,91 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/js@9.11.0': {} + '@eslint/js@9.8.0': {} '@eslint/object-schema@2.1.4': {} - '@fal-works/esbuild-plugin-global-externals@2.1.2': {} + '@eslint/plugin-kit@0.2.0': + dependencies: + levn: 0.4.1 '@fastify/accept-negotiator@1.0.0': {} - '@fastify/accepts@4.3.0': + '@fastify/accept-negotiator@2.0.0': {} + + '@fastify/accepts@5.0.0': dependencies: accepts: 1.3.8 - fastify-plugin: 4.5.0 + fastify-plugin: 5.0.0 - '@fastify/ajv-compiler@3.5.0': + '@fastify/ajv-compiler@4.0.0': dependencies: ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) - fast-uri: 2.2.0 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-uri: 3.0.1 '@fastify/busboy@2.1.0': {} - '@fastify/cookie@9.3.1': + '@fastify/busboy@3.0.0': {} + + '@fastify/cookie@10.0.0': dependencies: cookie-signature: 1.2.1 - fastify-plugin: 4.5.0 + fastify-plugin: 5.0.0 - '@fastify/cors@9.0.1': + '@fastify/cors@10.0.0': dependencies: - fastify-plugin: 4.5.0 - mnemonist: 0.39.6 + fastify-plugin: 5.0.0 + mnemonist: 0.39.8 - '@fastify/deepmerge@1.3.0': {} + '@fastify/deepmerge@2.0.0': {} - '@fastify/error@3.4.0': {} + '@fastify/error@4.0.0': {} - '@fastify/express@3.0.0': + '@fastify/express@4.0.0': dependencies: express: 4.19.2 - fastify-plugin: 4.5.0 + fastify-plugin: 5.0.0 transitivePeerDependencies: - supports-color - '@fastify/fast-json-stringify-compiler@4.3.0': + '@fastify/fast-json-stringify-compiler@5.0.0': dependencies: - fast-json-stringify: 5.8.0 + fast-json-stringify: 6.0.0 - '@fastify/http-proxy@9.5.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)': + '@fastify/http-proxy@10.0.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)': dependencies: - '@fastify/reply-from': 9.0.1 + '@fastify/reply-from': 11.0.0 fast-querystring: 1.1.2 - fastify-plugin: 4.5.0 + fastify-plugin: 5.0.0 ws: 8.18.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) transitivePeerDependencies: - bufferutil - utf-8-validate - '@fastify/multipart@8.3.0': + '@fastify/merge-json-schemas@0.1.1': dependencies: - '@fastify/busboy': 2.1.0 - '@fastify/deepmerge': 1.3.0 - '@fastify/error': 3.4.0 - fastify-plugin: 4.5.0 - secure-json-parse: 2.7.0 - stream-wormhole: 1.1.0 + fast-deep-equal: 3.1.3 - '@fastify/reply-from@9.0.1': + '@fastify/multipart@9.0.0': dependencies: - '@fastify/error': 3.4.0 + '@fastify/busboy': 3.0.0 + '@fastify/deepmerge': 2.0.0 + '@fastify/error': 4.0.0 + fastify-plugin: 5.0.0 + secure-json-parse: 3.0.0 + + '@fastify/reply-from@11.0.0': + dependencies: + '@fastify/error': 4.0.0 end-of-stream: 1.4.4 + fast-content-type-parse: 2.0.0 fast-querystring: 1.1.2 - fastify-plugin: 4.5.0 - pump: 3.0.0 - tiny-lru: 10.0.1 - undici: 5.28.2 + fastify-plugin: 4.5.1 + toad-cache: 3.7.0 + undici: 6.19.8 '@fastify/send@2.0.1': dependencies: @@ -13993,6 +13229,14 @@ snapshots: http-errors: 2.0.0 mime: 3.0.0 + '@fastify/send@3.1.1': + dependencies: + '@lukeed/ms': 2.0.2 + escape-html: 1.0.3 + fast-decode-uri-component: 1.0.1 + http-errors: 2.0.0 + mime: 3.0.0 + '@fastify/static@6.12.0': dependencies: '@fastify/accept-negotiator': 1.0.0 @@ -14002,25 +13246,25 @@ snapshots: glob: 8.1.0 p-limit: 3.1.0 - '@fastify/static@7.0.4': + '@fastify/static@8.0.0': dependencies: - '@fastify/accept-negotiator': 1.0.0 - '@fastify/send': 2.0.1 + '@fastify/accept-negotiator': 2.0.0 + '@fastify/send': 3.1.1 content-disposition: 0.5.4 - fastify-plugin: 4.5.0 + fastify-plugin: 5.0.0 fastq: 1.17.1 - glob: 10.4.2 + glob: 11.0.0 + + '@fastify/view@10.0.0': + dependencies: + fastify-plugin: 5.0.0 + toad-cache: 3.7.0 '@fastify/view@8.2.0': dependencies: fastify-plugin: 4.5.0 hashlru: 2.3.0 - '@fastify/view@9.1.0': - dependencies: - fastify-plugin: 4.5.0 - toad-cache: 3.7.0 - '@github/webauthn-json@2.1.1': {} '@hapi/boom@10.0.1': @@ -14051,79 +13295,79 @@ snapshots: '@humanwhocodes/retry@0.3.0': {} - '@img/sharp-darwin-arm64@0.33.4': + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.0.2 + '@img/sharp-libvips-darwin-arm64': 1.0.4 optional: true - '@img/sharp-darwin-x64@0.33.4': + '@img/sharp-darwin-x64@0.33.5': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.0.2 + '@img/sharp-libvips-darwin-x64': 1.0.4 optional: true - '@img/sharp-libvips-darwin-arm64@1.0.2': + '@img/sharp-libvips-darwin-arm64@1.0.4': optional: true - '@img/sharp-libvips-darwin-x64@1.0.2': + '@img/sharp-libvips-darwin-x64@1.0.4': optional: true - '@img/sharp-libvips-linux-arm64@1.0.2': + '@img/sharp-libvips-linux-arm64@1.0.4': optional: true - '@img/sharp-libvips-linux-arm@1.0.2': + '@img/sharp-libvips-linux-arm@1.0.5': optional: true - '@img/sharp-libvips-linux-s390x@1.0.2': + '@img/sharp-libvips-linux-s390x@1.0.4': optional: true - '@img/sharp-libvips-linux-x64@1.0.2': + '@img/sharp-libvips-linux-x64@1.0.4': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.0.2': + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.0.2': + '@img/sharp-libvips-linuxmusl-x64@1.0.4': optional: true - '@img/sharp-linux-arm64@0.33.4': + '@img/sharp-linux-arm64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.0.2 + '@img/sharp-libvips-linux-arm64': 1.0.4 optional: true - '@img/sharp-linux-arm@0.33.4': + '@img/sharp-linux-arm@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.0.2 + '@img/sharp-libvips-linux-arm': 1.0.5 optional: true - '@img/sharp-linux-s390x@0.33.4': + '@img/sharp-linux-s390x@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.0.2 + '@img/sharp-libvips-linux-s390x': 1.0.4 optional: true - '@img/sharp-linux-x64@0.33.4': + '@img/sharp-linux-x64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.0.2 + '@img/sharp-libvips-linux-x64': 1.0.4 optional: true - '@img/sharp-linuxmusl-arm64@0.33.4': + '@img/sharp-linuxmusl-arm64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.0.2 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 optional: true - '@img/sharp-linuxmusl-x64@0.33.4': + '@img/sharp-linuxmusl-x64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.0.2 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 optional: true - '@img/sharp-wasm32@0.33.4': + '@img/sharp-wasm32@0.33.5': dependencies: - '@emnapi/runtime': 1.1.1 + '@emnapi/runtime': 1.2.0 optional: true - '@img/sharp-win32-ia32@0.33.4': + '@img/sharp-win32-ia32@0.33.5': optional: true - '@img/sharp-win32-x64@0.33.4': + '@img/sharp-win32-x64@0.33.5': optional: true '@inquirer/confirm@3.1.6': @@ -14151,18 +13395,6 @@ snapshots: '@inquirer/type@1.3.1': {} - '@intlify/core-base@9.13.1': - dependencies: - '@intlify/message-compiler': 9.13.1 - '@intlify/shared': 9.13.1 - - '@intlify/message-compiler@9.13.1': - dependencies: - '@intlify/shared': 9.13.1 - source-map-js: 1.2.0 - - '@intlify/shared@9.13.1': {} - '@ioredis/commands@1.2.0': {} '@isaacs/cliui@8.0.2': @@ -14219,7 +13451,7 @@ snapshots: jest-util: 29.7.0 jest-validate: 29.7.0 jest-watcher: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 strip-ansi: 6.0.1 @@ -14275,7 +13507,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 '@types/node': 20.14.12 chalk: 4.1.2 collect-v8-coverage: 1.0.1 @@ -14303,7 +13535,7 @@ snapshots: '@jest/source-map@29.6.3': dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 callsites: 3.1.0 graceful-fs: 4.2.11 @@ -14325,7 +13557,7 @@ snapshots: dependencies: '@babel/core': 7.24.7 '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 @@ -14334,7 +13566,7 @@ snapshots: jest-haste-map: 29.7.0 jest-regex-util: 29.6.3 jest-util: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 pirates: 4.0.5 slash: 3.0.0 write-file-atomic: 4.0.2 @@ -14350,15 +13582,15 @@ snapshots: '@types/yargs': 17.0.19 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.3.1(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.3.1(typescript@5.6.2)(vite@5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))': dependencies: glob: 7.2.3 glob-promise: 4.2.2(glob@7.2.3) magic-string: 0.27.0 - react-docgen-typescript: 2.2.2(typescript@5.5.4) - vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3) + react-docgen-typescript: 2.2.2(typescript@5.6.2) + vite: 5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 '@jridgewell/gen-mapping@0.3.2': dependencies: @@ -14387,6 +13619,8 @@ snapshots: '@jridgewell/sourcemap-codec@1.4.15': {} + '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/trace-mapping@0.3.18': dependencies: '@jridgewell/resolve-uri': 3.1.0 @@ -14407,6 +13641,8 @@ snapshots: '@lukeed/ms@2.0.1': {} + '@lukeed/ms@2.0.2': {} + '@mapbox/node-pre-gyp@1.0.9(encoding@0.1.13)': dependencies: detect-libc: 2.0.3 @@ -14435,23 +13671,23 @@ snapshots: '@types/react': 18.0.28 react: 18.3.1 - '@microsoft/api-extractor-model@7.29.4(@types/node@20.14.12)': + '@microsoft/api-extractor-model@7.29.8(@types/node@20.14.12)': dependencies: '@microsoft/tsdoc': 0.15.0 '@microsoft/tsdoc-config': 0.17.0 - '@rushstack/node-core-library': 5.5.1(@types/node@20.14.12) + '@rushstack/node-core-library': 5.9.0(@types/node@20.14.12) transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.47.4(@types/node@20.14.12)': + '@microsoft/api-extractor@7.47.9(@types/node@20.14.12)': dependencies: - '@microsoft/api-extractor-model': 7.29.4(@types/node@20.14.12) + '@microsoft/api-extractor-model': 7.29.8(@types/node@20.14.12) '@microsoft/tsdoc': 0.15.0 '@microsoft/tsdoc-config': 0.17.0 - '@rushstack/node-core-library': 5.5.1(@types/node@20.14.12) + '@rushstack/node-core-library': 5.9.0(@types/node@20.14.12) '@rushstack/rig-package': 0.5.3 - '@rushstack/terminal': 0.13.3(@types/node@20.14.12) - '@rushstack/ts-command-line': 4.22.3(@types/node@20.14.12) + '@rushstack/terminal': 0.14.2(@types/node@20.14.12) + '@rushstack/ts-command-line': 4.22.8(@types/node@20.14.12) lodash: 4.17.21 minimatch: 3.0.8 resolve: 1.22.8 @@ -14472,20 +13708,20 @@ snapshots: '@misskey-dev/browser-image-resizer@2024.1.0': {} - '@misskey-dev/eslint-plugin@2.0.3(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4))(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0))(eslint@9.8.0)(globals@15.8.0)': + '@misskey-dev/eslint-plugin@2.0.3(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2))(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0))(eslint@9.8.0)(globals@15.9.0)': dependencies: '@eslint/compat': 1.1.1 - '@typescript-eslint/eslint-plugin': 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4) - '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/eslint-plugin': 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2) + '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.6.2) eslint: 9.8.0 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0) - globals: 15.8.0 + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0) + globals: 15.9.0 '@misskey-dev/sharp-read-bmp@1.2.0': dependencies: decode-bmp: 0.2.1 decode-ico: 0.4.1 - sharp: 0.33.4 + sharp: 0.33.5 '@misskey-dev/summaly@5.1.0': dependencies: @@ -14536,88 +13772,97 @@ snapshots: outvariant: 1.4.2 strict-event-emitter: 0.5.1 - '@napi-rs/canvas-android-arm64@0.1.53': + '@mswjs/interceptors@0.35.8': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + + '@napi-rs/canvas-android-arm64@0.1.56': optional: true - '@napi-rs/canvas-darwin-arm64@0.1.53': + '@napi-rs/canvas-darwin-arm64@0.1.56': optional: true - '@napi-rs/canvas-darwin-x64@0.1.53': + '@napi-rs/canvas-darwin-x64@0.1.56': optional: true - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.53': + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.56': optional: true - '@napi-rs/canvas-linux-arm64-gnu@0.1.53': + '@napi-rs/canvas-linux-arm64-gnu@0.1.56': optional: true - '@napi-rs/canvas-linux-arm64-musl@0.1.53': + '@napi-rs/canvas-linux-arm64-musl@0.1.56': optional: true - '@napi-rs/canvas-linux-x64-gnu@0.1.53': + '@napi-rs/canvas-linux-x64-gnu@0.1.56': optional: true - '@napi-rs/canvas-linux-x64-musl@0.1.53': + '@napi-rs/canvas-linux-x64-musl@0.1.56': optional: true - '@napi-rs/canvas-win32-x64-msvc@0.1.53': + '@napi-rs/canvas-win32-x64-msvc@0.1.56': optional: true - '@napi-rs/canvas@0.1.53': + '@napi-rs/canvas@0.1.56': optionalDependencies: - '@napi-rs/canvas-android-arm64': 0.1.53 - '@napi-rs/canvas-darwin-arm64': 0.1.53 - '@napi-rs/canvas-darwin-x64': 0.1.53 - '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.53 - '@napi-rs/canvas-linux-arm64-gnu': 0.1.53 - '@napi-rs/canvas-linux-arm64-musl': 0.1.53 - '@napi-rs/canvas-linux-x64-gnu': 0.1.53 - '@napi-rs/canvas-linux-x64-musl': 0.1.53 - '@napi-rs/canvas-win32-x64-msvc': 0.1.53 + '@napi-rs/canvas-android-arm64': 0.1.56 + '@napi-rs/canvas-darwin-arm64': 0.1.56 + '@napi-rs/canvas-darwin-x64': 0.1.56 + '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.56 + '@napi-rs/canvas-linux-arm64-gnu': 0.1.56 + '@napi-rs/canvas-linux-arm64-musl': 0.1.56 + '@napi-rs/canvas-linux-x64-gnu': 0.1.56 + '@napi-rs/canvas-linux-x64-musl': 0.1.56 + '@napi-rs/canvas-win32-x64-msvc': 0.1.56 - '@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: iterare: 1.2.1 reflect-metadata: 0.2.2 rxjs: 7.8.1 - tslib: 2.6.3 + tslib: 2.7.0 uid: 2.0.2 - '@nestjs/core@10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/core@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: - '@nestjs/common': 10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nuxtjs/opencollective': 0.3.2(encoding@0.1.13) fast-safe-stringify: 2.1.1 iterare: 1.2.1 - path-to-regexp: 3.2.0 + path-to-regexp: 3.3.0 reflect-metadata: 0.2.2 rxjs: 7.8.1 - tslib: 2.6.3 + tslib: 2.7.0 uid: 2.0.2 optionalDependencies: - '@nestjs/platform-express': 10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10) + '@nestjs/platform-express': 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3) transitivePeerDependencies: - encoding - '@nestjs/platform-express@10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10)': + '@nestjs/platform-express@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3)': dependencies: - '@nestjs/common': 10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) - body-parser: 1.20.2 + '@nestjs/common': 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) + body-parser: 1.20.3 cors: 2.8.5 - express: 4.19.2 + express: 4.21.0 multer: 1.4.4-lts.1 - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - supports-color - '@nestjs/testing@10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10))': + '@nestjs/testing@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3))': dependencies: - '@nestjs/common': 10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) - tslib: 2.6.3 + '@nestjs/common': 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) + tslib: 2.7.0 optionalDependencies: - '@nestjs/platform-express': 10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10) + '@nestjs/platform-express': 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3) '@noble/hashes@1.4.0': {} @@ -14637,7 +13882,7 @@ snapshots: dependencies: agent-base: 7.1.0 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.4 + https-proxy-agent: 7.0.5 lru-cache: 10.2.2 socks-proxy-agent: 8.0.2 transitivePeerDependencies: @@ -14950,7 +14195,7 @@ snapshots: '@readme/better-ajv-errors@1.6.0(ajv@8.17.1)': dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.24.7 '@babel/runtime': 7.23.4 '@humanwhocodes/momoa': 2.0.4 ajv: 8.17.1 @@ -14962,92 +14207,96 @@ snapshots: '@readme/json-schema-ref-parser@1.2.0': dependencies: '@jsdevtools/ono': 7.1.3 - '@types/json-schema': 7.0.12 + '@types/json-schema': 7.0.15 call-me-maybe: 1.0.2 js-yaml: 4.1.0 - '@readme/openapi-parser@2.5.0(openapi-types@12.1.3)': + '@readme/openapi-parser@2.6.0(openapi-types@12.1.3)': dependencies: - '@apidevtools/openapi-schemas': 2.1.0 '@apidevtools/swagger-methods': 3.0.2 '@jsdevtools/ono': 7.1.3 '@readme/better-ajv-errors': 1.6.0(ajv@8.17.1) '@readme/json-schema-ref-parser': 1.2.0 + '@readme/openapi-schemas': 3.1.0 ajv: 8.17.1 ajv-draft-04: 1.0.0(ajv@8.17.1) call-me-maybe: 1.0.2 openapi-types: 12.1.3 - '@rollup/plugin-json@6.1.0(rollup@4.19.1)': - dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.19.1) - optionalDependencies: - rollup: 4.19.1 + '@readme/openapi-schemas@3.1.0': {} - '@rollup/plugin-replace@5.0.7(rollup@4.19.1)': + '@rollup/plugin-json@6.1.0(rollup@4.22.2)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.19.1) + '@rollup/pluginutils': 5.1.0(rollup@4.22.2) + optionalDependencies: + rollup: 4.22.2 + + '@rollup/plugin-replace@5.0.7(rollup@4.22.2)': + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.22.2) magic-string: 0.30.10 optionalDependencies: - rollup: 4.19.1 + rollup: 4.22.2 - '@rollup/pluginutils@5.1.0(rollup@4.19.1)': + '@rollup/pluginutils@5.1.0(rollup@4.22.2)': dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 2.3.1 optionalDependencies: - rollup: 4.19.1 + rollup: 4.22.2 - '@rollup/rollup-android-arm-eabi@4.19.1': + '@rollup/rollup-android-arm-eabi@4.22.2': optional: true - '@rollup/rollup-android-arm64@4.19.1': + '@rollup/rollup-android-arm64@4.22.2': optional: true - '@rollup/rollup-darwin-arm64@4.19.1': + '@rollup/rollup-darwin-arm64@4.22.2': optional: true - '@rollup/rollup-darwin-x64@4.19.1': + '@rollup/rollup-darwin-x64@4.22.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.19.1': + '@rollup/rollup-linux-arm-gnueabihf@4.22.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.19.1': + '@rollup/rollup-linux-arm-musleabihf@4.22.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.19.1': + '@rollup/rollup-linux-arm64-gnu@4.22.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.19.1': + '@rollup/rollup-linux-arm64-musl@4.22.2': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.19.1': + '@rollup/rollup-linux-powerpc64le-gnu@4.22.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.19.1': + '@rollup/rollup-linux-riscv64-gnu@4.22.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.19.1': + '@rollup/rollup-linux-s390x-gnu@4.22.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.19.1': + '@rollup/rollup-linux-x64-gnu@4.22.2': optional: true - '@rollup/rollup-linux-x64-musl@4.19.1': + '@rollup/rollup-linux-x64-musl@4.22.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.19.1': + '@rollup/rollup-win32-arm64-msvc@4.22.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.19.1': + '@rollup/rollup-win32-ia32-msvc@4.22.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.19.1': + '@rollup/rollup-win32-x64-msvc@4.22.2': optional: true - '@rushstack/node-core-library@5.5.1(@types/node@20.14.12)': + '@rtsao/scc@1.1.0': {} + + '@rushstack/node-core-library@5.9.0(@types/node@20.14.12)': dependencies: ajv: 8.13.0 ajv-draft-04: 1.0.0(ajv@8.13.0) @@ -15065,16 +14314,16 @@ snapshots: resolve: 1.22.8 strip-json-comments: 3.1.1 - '@rushstack/terminal@0.13.3(@types/node@20.14.12)': + '@rushstack/terminal@0.14.2(@types/node@20.14.12)': dependencies: - '@rushstack/node-core-library': 5.5.1(@types/node@20.14.12) + '@rushstack/node-core-library': 5.9.0(@types/node@20.14.12) supports-color: 8.1.1 optionalDependencies: '@types/node': 20.14.12 - '@rushstack/ts-command-line@4.22.3(@types/node@20.14.12)': + '@rushstack/ts-command-line@4.22.8(@types/node@20.14.12)': dependencies: - '@rushstack/terminal': 0.13.3(@types/node@20.14.12) + '@rushstack/terminal': 0.14.2(@types/node@20.14.12) '@types/argparse': 1.0.38 argparse: 1.0.10 string-argv: 0.3.1 @@ -15159,6 +14408,10 @@ snapshots: dependencies: '@hapi/hoek': 9.3.0 + '@sideway/address@4.1.5': + dependencies: + '@hapi/hoek': 9.3.0 + '@sideway/formula@3.0.1': {} '@sideway/pinpoint@2.0.0': {} @@ -15187,8 +14440,6 @@ snapshots: '@sindresorhus/is@7.0.0': {} - '@sindresorhus/merge-streams@2.3.0': {} - '@sindresorhus/merge-streams@4.0.0': {} '@sinonjs/commons@2.0.0': @@ -15578,134 +14829,124 @@ snapshots: '@sqltools/formatter@1.2.5': {} - '@storybook/addon-actions@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-actions@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: '@storybook/global': 5.0.0 '@types/uuid': 9.0.8 dequal: 2.0.3 polished: 4.2.2 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) uuid: 9.0.1 - '@storybook/addon-backgrounds@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-backgrounds@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: '@storybook/global': 5.0.0 memoizerific: 1.11.3 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 - '@storybook/addon-controls@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-controls@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: + '@storybook/global': 5.0.0 dequal: 2.0.3 lodash: 4.17.21 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 - '@storybook/addon-docs@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-docs@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: - '@babel/core': 7.24.7 '@mdx-js/react': 3.0.1(@types/react@18.0.28)(react@18.3.1) - '@storybook/blocks': 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/csf-plugin': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/blocks': 8.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/csf-plugin': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/react-dom-shim': 8.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@types/react': 18.0.28 fs-extra: 11.1.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) rehype-external-links: 3.0.0 rehype-slug: 6.0.0 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 - transitivePeerDependencies: - - supports-color - '@storybook/addon-essentials@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-essentials@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: - '@storybook/addon-actions': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-backgrounds': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-controls': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-docs': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-highlight': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-measure': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-outline': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-toolbars': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-viewport': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + '@storybook/addon-actions': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-backgrounds': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-controls': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-docs': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-highlight': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-measure': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-outline': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-toolbars': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-viewport': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 - transitivePeerDependencies: - - supports-color - '@storybook/addon-highlight@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-highlight@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) - '@storybook/addon-interactions@8.2.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.14.12))(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))': + '@storybook/addon-interactions@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/test': 8.2.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.14.12))(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3)) + '@storybook/instrumenter': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/test': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) polished: 4.2.2 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 - transitivePeerDependencies: - - '@jest/globals' - - '@types/bun' - - '@types/jest' - - jest - - vitest - '@storybook/addon-links@8.2.6(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-links@8.3.2(react@18.3.1)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: '@storybook/csf': 0.1.11 '@storybook/global': 5.0.0 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 optionalDependencies: react: 18.3.1 - '@storybook/addon-mdx-gfm@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-mdx-gfm@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: remark-gfm: 4.0.0 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 transitivePeerDependencies: - supports-color - '@storybook/addon-measure@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-measure@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) tiny-invariant: 1.3.3 - '@storybook/addon-outline@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-outline@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 - '@storybook/addon-storysource@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-storysource@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: - '@storybook/source-loader': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/source-loader': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) estraverse: 5.3.0 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) tiny-invariant: 1.3.3 - '@storybook/addon-toolbars@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-toolbars@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) - '@storybook/addon-viewport@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-viewport@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: memoizerific: 1.11.3 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) - '@storybook/blocks@8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/blocks@8.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: '@storybook/csf': 0.1.11 '@storybook/global': 5.0.0 - '@storybook/icons': 1.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@storybook/icons': 1.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/lodash': 4.14.191 color-convert: 2.0.1 dequal: 2.0.3 @@ -15714,7 +14955,7 @@ snapshots: memoizerific: 1.11.3 polished: 4.2.2 react-colorful: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) telejson: 7.2.0 ts-dedent: 2.2.0 util-deprecate: 1.0.2 @@ -15722,38 +14963,9 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/builder-manager@8.1.11(encoding@0.1.13)(prettier@3.3.3)': + '@storybook/builder-vite@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))': dependencies: - '@fal-works/esbuild-plugin-global-externals': 2.1.2 - '@storybook/core-common': 8.1.11(encoding@0.1.13)(prettier@3.3.3) - '@storybook/manager': 8.1.11 - '@storybook/node-logger': 8.1.11 - '@types/ejs': 3.1.2 - '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.19.11) - browser-assert: 1.2.1 - ejs: 3.1.10 - esbuild: 0.19.11 - esbuild-plugin-alias: 0.2.1 - express: 4.19.2 - fs-extra: 11.1.1 - process: 0.11.10 - util: 0.12.5 - transitivePeerDependencies: - - encoding - - prettier - - supports-color - - '@storybook/builder-vite@8.1.11(encoding@0.1.13)(prettier@3.3.3)(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))': - dependencies: - '@storybook/channels': 8.1.11 - '@storybook/client-logger': 8.1.11 - '@storybook/core-common': 8.1.11(encoding@0.1.13)(prettier@3.3.3) - '@storybook/core-events': 8.1.11 - '@storybook/csf-plugin': 8.1.11 - '@storybook/node-logger': 8.1.11 - '@storybook/preview': 8.1.11 - '@storybook/preview-api': 8.1.11 - '@storybook/types': 8.1.11 + '@storybook/csf-plugin': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@types/find-cache-dir': 3.2.1 browser-assert: 1.2.1 es-module-lexer: 1.5.4 @@ -15761,182 +14973,35 @@ snapshots: find-cache-dir: 3.3.2 fs-extra: 11.1.1 magic-string: 0.30.10 + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 - vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3) + vite: 5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) optionalDependencies: - typescript: 5.5.4 - transitivePeerDependencies: - - encoding - - prettier - - supports-color - - '@storybook/builder-vite@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))': - dependencies: - '@storybook/csf-plugin': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@types/find-cache-dir': 3.2.1 - browser-assert: 1.2.1 - es-module-lexer: 1.5.4 - express: 4.19.2 - find-cache-dir: 3.3.2 - fs-extra: 11.1.1 - magic-string: 0.30.10 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) - ts-dedent: 2.2.0 - vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3) - optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 transitivePeerDependencies: - supports-color - '@storybook/channels@8.1.11': + '@storybook/components@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: - '@storybook/client-logger': 8.1.11 - '@storybook/core-events': 8.1.11 - '@storybook/global': 5.0.0 - telejson: 7.2.0 - tiny-invariant: 1.3.3 + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) - '@storybook/client-logger@8.1.11': + '@storybook/core-events@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: - '@storybook/global': 5.0.0 + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) - '@storybook/codemod@8.2.6(bufferutil@4.0.8)(utf-8-validate@6.0.4)': - dependencies: - '@babel/core': 7.24.7 - '@babel/preset-env': 7.24.7(@babel/core@7.24.7) - '@babel/types': 7.24.7 - '@storybook/core': 8.2.6(bufferutil@4.0.8)(utf-8-validate@6.0.4) - '@storybook/csf': 0.1.11 - '@types/cross-spawn': 6.0.2 - cross-spawn: 7.0.3 - globby: 14.0.1 - jscodeshift: 0.15.1(@babel/preset-env@7.24.7(@babel/core@7.24.7)) - lodash: 4.17.21 - prettier: 3.3.3 - recast: 0.23.6 - tiny-invariant: 1.3.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - '@storybook/components@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': - dependencies: - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) - - '@storybook/core-common@8.1.11(encoding@0.1.13)(prettier@3.3.3)': - dependencies: - '@storybook/core-events': 8.1.11 - '@storybook/csf-tools': 8.1.11 - '@storybook/node-logger': 8.1.11 - '@storybook/types': 8.1.11 - '@yarnpkg/fslib': 2.10.3 - '@yarnpkg/libzip': 2.3.0 - chalk: 4.1.2 - cross-spawn: 7.0.3 - esbuild: 0.19.11 - esbuild-register: 3.5.0(esbuild@0.19.11) - execa: 5.1.1 - file-system-cache: 2.3.0 - find-cache-dir: 3.3.2 - find-up: 5.0.0 - fs-extra: 11.1.1 - glob: 10.3.10 - handlebars: 4.7.7 - lazy-universal-dotenv: 4.0.0 - node-fetch: 2.7.0(encoding@0.1.13) - picomatch: 2.3.1 - pkg-dir: 5.0.0 - prettier-fallback: prettier@3.3.3 - pretty-hrtime: 1.0.3 - resolve-from: 5.0.0 - semver: 7.6.0 - tempy: 3.1.0 - tiny-invariant: 1.3.3 - ts-dedent: 2.2.0 - util: 0.12.5 - optionalDependencies: - prettier: 3.3.3 - transitivePeerDependencies: - - encoding - - supports-color - - '@storybook/core-events@8.1.11': - dependencies: - '@storybook/csf': 0.1.9 - ts-dedent: 2.2.0 - - '@storybook/core-events@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': - dependencies: - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) - - '@storybook/core-server@8.1.11(bufferutil@4.0.8)(encoding@0.1.13)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@6.0.4)': - dependencies: - '@aw-web-design/x-default-browser': 1.4.126 - '@babel/core': 7.24.7 - '@babel/parser': 7.24.7 - '@discoveryjs/json-ext': 0.5.7 - '@storybook/builder-manager': 8.1.11(encoding@0.1.13)(prettier@3.3.3) - '@storybook/channels': 8.1.11 - '@storybook/core-common': 8.1.11(encoding@0.1.13)(prettier@3.3.3) - '@storybook/core-events': 8.1.11 - '@storybook/csf': 0.1.9 - '@storybook/csf-tools': 8.1.11 - '@storybook/docs-mdx': 3.1.0-next.0 - '@storybook/global': 5.0.0 - '@storybook/manager': 8.1.11 - '@storybook/manager-api': 8.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@storybook/node-logger': 8.1.11 - '@storybook/preview-api': 8.1.11 - '@storybook/telemetry': 8.1.11(encoding@0.1.13)(prettier@3.3.3) - '@storybook/types': 8.1.11 - '@types/detect-port': 1.3.2 - '@types/diff': 5.2.1 - '@types/node': 18.17.15 - '@types/pretty-hrtime': 1.0.1 - '@types/semver': 7.5.8 - better-opn: 3.0.2 - chalk: 4.1.2 - cli-table3: 0.6.3 - compression: 1.7.4 - detect-port: 1.5.1 - diff: 5.2.0 - express: 4.19.2 - fs-extra: 11.1.1 - globby: 14.0.1 - lodash: 4.17.21 - open: 8.4.2 - pretty-hrtime: 1.0.3 - prompts: 2.4.2 - read-pkg-up: 7.0.1 - semver: 7.6.0 - telejson: 7.2.0 - tiny-invariant: 1.3.3 - ts-dedent: 2.2.0 - util: 0.12.5 - util-deprecate: 1.0.2 - watchpack: 2.4.0 - ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4) - transitivePeerDependencies: - - bufferutil - - encoding - - prettier - - react - - react-dom - - supports-color - - utf-8-validate - - '@storybook/core@8.2.6(bufferutil@4.0.8)(utf-8-validate@6.0.4)': + '@storybook/core@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)': dependencies: '@storybook/csf': 0.1.11 '@types/express': 4.17.21 - '@types/node': 18.17.15 + better-opn: 3.0.2 browser-assert: 1.2.1 - esbuild: 0.19.11 - esbuild-register: 3.5.0(esbuild@0.19.11) + esbuild: 0.23.1 + esbuild-register: 3.5.0(esbuild@0.23.1) express: 4.19.2 + jsdoc-type-pratt-parser: 4.1.0 process: 0.11.10 recast: 0.23.6 + semver: 7.6.3 util: 0.12.5 ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4) transitivePeerDependencies: @@ -15944,306 +15009,154 @@ snapshots: - supports-color - utf-8-validate - '@storybook/csf-plugin@8.1.11': + '@storybook/csf-plugin@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: - '@storybook/csf-tools': 8.1.11 + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) unplugin: 1.4.0 - transitivePeerDependencies: - - supports-color - - '@storybook/csf-plugin@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': - dependencies: - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) - unplugin: 1.4.0 - - '@storybook/csf-tools@8.1.11': - dependencies: - '@babel/generator': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - '@storybook/csf': 0.1.9 - '@storybook/types': 8.1.11 - fs-extra: 11.1.1 - recast: 0.23.6 - ts-dedent: 2.2.0 - transitivePeerDependencies: - - supports-color '@storybook/csf@0.1.11': dependencies: type-fest: 2.19.0 - '@storybook/csf@0.1.9': - dependencies: - type-fest: 2.19.0 - - '@storybook/docs-mdx@3.1.0-next.0': {} - - '@storybook/docs-tools@8.1.11(encoding@0.1.13)(prettier@3.3.3)': - dependencies: - '@storybook/core-common': 8.1.11(encoding@0.1.13)(prettier@3.3.3) - '@storybook/core-events': 8.1.11 - '@storybook/preview-api': 8.1.11 - '@storybook/types': 8.1.11 - '@types/doctrine': 0.0.3 - assert: 2.1.0 - doctrine: 3.0.0 - lodash: 4.17.21 - transitivePeerDependencies: - - encoding - - prettier - - supports-color - '@storybook/global@5.0.0': {} - '@storybook/icons@1.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@storybook/icons@1.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/instrumenter@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/instrumenter@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: '@storybook/global': 5.0.0 - '@vitest/utils': 1.6.0 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + '@vitest/utils': 2.1.1 + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) util: 0.12.5 - '@storybook/manager-api@8.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@storybook/manager-api@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: - '@storybook/channels': 8.1.11 - '@storybook/client-logger': 8.1.11 - '@storybook/core-events': 8.1.11 - '@storybook/csf': 0.1.9 - '@storybook/global': 5.0.0 - '@storybook/icons': 1.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@storybook/router': 8.1.11 - '@storybook/theming': 8.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@storybook/types': 8.1.11 - dequal: 2.0.3 - lodash: 4.17.21 - memoizerific: 1.11.3 - store2: 2.14.2 - telejson: 7.2.0 - ts-dedent: 2.2.0 - transitivePeerDependencies: - - react - - react-dom + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) - '@storybook/manager-api@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/preview-api@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) - '@storybook/manager@8.1.11': {} - - '@storybook/node-logger@8.1.11': {} - - '@storybook/preview-api@8.1.11': - dependencies: - '@storybook/channels': 8.1.11 - '@storybook/client-logger': 8.1.11 - '@storybook/core-events': 8.1.11 - '@storybook/csf': 0.1.9 - '@storybook/global': 5.0.0 - '@storybook/types': 8.1.11 - '@types/qs': 6.9.7 - dequal: 2.0.3 - lodash: 4.17.21 - memoizerific: 1.11.3 - qs: 6.11.1 - tiny-invariant: 1.3.3 - ts-dedent: 2.2.0 - util-deprecate: 1.0.2 - - '@storybook/preview-api@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': - dependencies: - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) - - '@storybook/preview@8.1.11': {} - - '@storybook/react-dom-shim@8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/react-dom-shim@8.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) - '@storybook/react-vite@8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.19.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))': + '@storybook/react-vite@8.3.2(@storybook/test@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.22.2)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.1(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)) - '@rollup/pluginutils': 5.1.0(rollup@4.19.1) - '@storybook/builder-vite': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)) - '@storybook/react': 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.5.4) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.1(typescript@5.6.2)(vite@5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)) + '@rollup/pluginutils': 5.1.0(rollup@4.22.2) + '@storybook/builder-vite': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)) + '@storybook/react': 8.3.2(@storybook/test@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2) find-up: 5.0.0 magic-string: 0.30.10 react: 18.3.1 react-docgen: 7.0.1 react-dom: 18.3.1(react@18.3.1) resolve: 1.22.8 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) tsconfig-paths: 4.2.0 - vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3) + vite: 5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) transitivePeerDependencies: - '@preact/preset-vite' + - '@storybook/test' - rollup - supports-color - typescript - vite-plugin-glimmerx - '@storybook/react@8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.5.4)': + '@storybook/react@8.3.2(@storybook/test@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)': dependencies: - '@storybook/components': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/components': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/global': 5.0.0 - '@storybook/manager-api': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/preview-api': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/react-dom-shim': 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/theming': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/manager-api': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/preview-api': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/react-dom-shim': 8.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/theming': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@types/escodegen': 0.0.6 '@types/estree': 0.0.51 - '@types/node': 18.17.15 + '@types/node': 22.5.5 acorn: 7.4.1 acorn-jsx: 5.3.2(acorn@7.4.1) acorn-walk: 7.2.0 escodegen: 2.1.0 html-tags: 3.2.0 - lodash: 4.17.21 prop-types: 15.8.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-element-to-jsx-string: 15.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) semver: 7.6.0 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 type-fest: 2.19.0 util-deprecate: 1.0.2 optionalDependencies: - typescript: 5.5.4 + '@storybook/test': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + typescript: 5.6.2 - '@storybook/router@8.1.11': - dependencies: - '@storybook/client-logger': 8.1.11 - memoizerific: 1.11.3 - qs: 6.11.1 - - '@storybook/source-loader@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/source-loader@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: '@storybook/csf': 0.1.11 estraverse: 5.3.0 lodash: 4.17.21 prettier: 3.3.3 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) - '@storybook/telemetry@8.1.11(encoding@0.1.13)(prettier@3.3.3)': - dependencies: - '@storybook/client-logger': 8.1.11 - '@storybook/core-common': 8.1.11(encoding@0.1.13)(prettier@3.3.3) - '@storybook/csf-tools': 8.1.11 - chalk: 4.1.2 - detect-package-manager: 2.0.1 - fetch-retry: 5.0.4 - fs-extra: 11.1.1 - read-pkg-up: 7.0.1 - transitivePeerDependencies: - - encoding - - prettier - - supports-color - - '@storybook/test@8.2.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.14.12))(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))': + '@storybook/test@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: '@storybook/csf': 0.1.11 - '@storybook/instrumenter': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@testing-library/dom': 10.1.0 - '@testing-library/jest-dom': 6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.14.12))(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3)) - '@testing-library/user-event': 14.5.2(@testing-library/dom@10.1.0) - '@vitest/expect': 1.6.0 - '@vitest/spy': 1.6.0 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) - util: 0.12.5 - transitivePeerDependencies: - - '@jest/globals' - - '@types/bun' - - '@types/jest' - - jest - - vitest - - '@storybook/theming@8.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.3.1) - '@storybook/client-logger': 8.1.11 '@storybook/global': 5.0.0 - memoizerific: 1.11.3 - optionalDependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@storybook/instrumenter': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@testing-library/dom': 10.4.0 + '@testing-library/jest-dom': 6.5.0 + '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) + '@vitest/expect': 2.0.5 + '@vitest/spy': 2.0.5 + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) + util: 0.12.5 - '@storybook/theming@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/theming@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) - '@storybook/types@8.1.11': + '@storybook/types@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))': dependencies: - '@storybook/channels': 8.1.11 - '@types/express': 4.17.17 - file-system-cache: 2.3.0 + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) - '@storybook/types@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/vue3-vite@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vite@5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.7(typescript@5.6.2))': dependencies: - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) - - '@storybook/vue3-vite@8.1.11(bufferutil@4.0.8)(encoding@0.1.13)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@6.0.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.37(typescript@5.5.4))': - dependencies: - '@storybook/builder-vite': 8.1.11(encoding@0.1.13)(prettier@3.3.3)(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)) - '@storybook/core-server': 8.1.11(bufferutil@4.0.8)(encoding@0.1.13)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@6.0.4) - '@storybook/types': 8.1.11 - '@storybook/vue3': 8.1.11(encoding@0.1.13)(prettier@3.3.3)(vue@3.4.37(typescript@5.5.4)) + '@storybook/builder-vite': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)) + '@storybook/vue3': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.5.7(typescript@5.6.2)) find-package-json: 1.2.0 magic-string: 0.30.10 - typescript: 5.5.4 - vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3) - vue-component-meta: 2.0.16(typescript@5.5.4) - vue-docgen-api: 4.75.1(vue@3.4.37(typescript@5.5.4)) + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) + typescript: 5.6.2 + vite: 5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) + vue-component-meta: 2.0.16(typescript@5.6.2) + vue-docgen-api: 4.75.1(vue@3.5.7(typescript@5.6.2)) transitivePeerDependencies: - '@preact/preset-vite' - - bufferutil - - encoding - - prettier - - react - - react-dom - supports-color - - utf-8-validate - vite-plugin-glimmerx - vue - '@storybook/vue3@8.1.11(encoding@0.1.13)(prettier@3.3.3)(vue@3.4.37(typescript@5.5.4))': + '@storybook/vue3@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.5.7(typescript@5.6.2))': dependencies: - '@storybook/docs-tools': 8.1.11(encoding@0.1.13)(prettier@3.3.3) + '@storybook/components': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) '@storybook/global': 5.0.0 - '@storybook/preview-api': 8.1.11 - '@storybook/types': 8.1.11 - '@vue/compiler-core': 3.4.34 - lodash: 4.17.21 + '@storybook/manager-api': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/preview-api': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/theming': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@vue/compiler-core': 3.4.37 + storybook: 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 type-fest: 2.19.0 - vue: 3.4.37(typescript@5.5.4) - vue-component-type-helpers: 2.0.29 - transitivePeerDependencies: - - encoding - - prettier - - supports-color - - '@storybook/vue3@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.4.37(typescript@5.5.4))': - dependencies: - '@storybook/components': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/global': 5.0.0 - '@storybook/manager-api': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/preview-api': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/theming': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@vue/compiler-core': 3.4.31 - lodash: 4.17.21 - storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4) - ts-dedent: 2.2.0 - type-fest: 2.19.0 - vue: 3.4.37(typescript@5.5.4) - vue-component-type-helpers: 2.0.29 + vue: 3.5.7(typescript@5.6.2) + vue-component-type-helpers: 2.1.6 '@swc/cli@0.3.12(@swc/core@1.6.6)(chokidar@3.5.3)': dependencies: @@ -16514,7 +15427,7 @@ snapshots: - encoding - seedrandom - '@testing-library/dom@10.1.0': + '@testing-library/dom@10.4.0': dependencies: '@babel/code-frame': 7.24.7 '@babel/runtime': 7.23.4 @@ -16527,7 +15440,7 @@ snapshots: '@testing-library/dom@9.3.4': dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.24.7 '@babel/runtime': 7.23.4 '@types/aria-query': 5.0.1 aria-query: 5.1.3 @@ -16536,34 +15449,28 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.14.12))(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))': + '@testing-library/jest-dom@6.5.0': dependencies: - '@adobe/css-tools': 4.3.3 - '@babel/runtime': 7.23.4 + '@adobe/css-tools': 4.4.0 aria-query: 5.3.0 chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 lodash: 4.17.21 redent: 3.0.0 - optionalDependencies: - '@jest/globals': 29.7.0 - '@types/jest': 29.5.12 - jest: 29.7.0(@types/node@20.14.12) - vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3) - '@testing-library/user-event@14.5.2(@testing-library/dom@10.1.0)': + '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)': dependencies: - '@testing-library/dom': 10.1.0 + '@testing-library/dom': 10.4.0 - '@testing-library/vue@8.1.0(@vue/compiler-sfc@3.4.37)(@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4)))(vue@3.4.37(typescript@5.5.4))': + '@testing-library/vue@8.1.0(@vue/compiler-sfc@3.5.7)(@vue/server-renderer@3.5.7(vue@3.5.7(typescript@5.6.2)))(vue@3.5.7(typescript@5.6.2))': dependencies: '@babel/runtime': 7.23.4 '@testing-library/dom': 9.3.4 - '@vue/test-utils': 2.4.1(@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4)))(vue@3.4.37(typescript@5.5.4)) - vue: 3.4.37(typescript@5.5.4) + '@vue/test-utils': 2.4.1(@vue/server-renderer@3.5.7(vue@3.5.7(typescript@5.6.2)))(vue@3.5.7(typescript@5.6.2)) + vue: 3.5.7(typescript@5.6.2) optionalDependencies: - '@vue/compiler-sfc': 3.4.37 + '@vue/compiler-sfc': 3.5.7 transitivePeerDependencies: - '@vue/server-renderer' @@ -16575,6 +15482,8 @@ snapshots: '@twemoji/parser@15.0.0': {} + '@twemoji/parser@15.1.0': {} + '@twemoji/parser@15.1.1': {} '@types/accepts@1.3.7': @@ -16644,41 +15553,29 @@ snapshots: '@types/cookie@0.6.0': {} - '@types/cross-spawn@6.0.2': - dependencies: - '@types/node': 20.14.12 - '@types/debug@4.1.12': dependencies: '@types/ms': 0.7.34 - '@types/detect-port@1.3.2': {} - - '@types/diff@5.2.1': {} - '@types/disposable-email-domains@1.0.2': {} - '@types/doctrine@0.0.3': {} - '@types/doctrine@0.0.9': {} - '@types/ejs@3.1.2': {} - - '@types/emscripten@1.39.7': {} - '@types/escape-regexp@0.0.3': {} '@types/escodegen@0.0.6': {} '@types/eslint@7.29.0': dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 '@types/json-schema': 7.0.15 '@types/estree@0.0.51': {} '@types/estree@1.0.5': {} + '@types/estree@1.0.6': {} + '@types/express-serve-static-core@4.17.33': dependencies: '@types/node': 20.14.12 @@ -16701,7 +15598,7 @@ snapshots: '@types/find-cache-dir@3.2.1': {} - '@types/fluent-ffmpeg@2.1.24': + '@types/fluent-ffmpeg@2.1.26': dependencies: '@types/node': 20.14.12 @@ -16736,7 +15633,7 @@ snapshots: dependencies: '@types/istanbul-lib-report': 3.0.0 - '@types/jest@29.5.12': + '@types/jest@29.5.13': dependencies: expect: 29.7.0 pretty-format: 29.7.0 @@ -16804,8 +15701,6 @@ snapshots: '@types/node': 20.14.12 form-data: 4.0.0 - '@types/node@18.17.15': {} - '@types/node@20.11.5': dependencies: undici-types: 5.26.5 @@ -16818,7 +15713,11 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/nodemailer@6.4.15': + '@types/node@22.5.5': + dependencies: + undici-types: 6.19.8 + + '@types/nodemailer@6.4.16': dependencies: '@types/node': 20.14.12 @@ -16843,9 +15742,9 @@ snapshots: '@types/pg-pool@2.0.4': dependencies: - '@types/pg': 8.11.6 + '@types/pg': 8.11.10 - '@types/pg@8.11.6': + '@types/pg@8.11.10': dependencies: '@types/node': 20.14.12 pg-protocol: 1.6.1 @@ -16857,8 +15756,6 @@ snapshots: pg-protocol: 1.6.1 pg-types: 2.2.0 - '@types/pretty-hrtime@1.0.1': {} - '@types/prop-types@15.7.5': {} '@types/pug@2.0.10': {} @@ -16895,7 +15792,7 @@ snapshots: dependencies: '@types/node': 20.14.12 - '@types/sanitize-html@2.11.0': + '@types/sanitize-html@2.13.0': dependencies: htmlparser2: 8.0.1 @@ -16960,7 +15857,7 @@ snapshots: '@types/wrap-ansi@3.0.0': {} - '@types/ws@8.5.11': + '@types/ws@8.5.12': dependencies: '@types/node': 20.14.12 @@ -16975,36 +15872,16 @@ snapshots: '@types/node': 20.14.12 optional: true - '@typescript-eslint/eslint-plugin@6.11.0(@typescript-eslint/parser@6.11.0(eslint@9.8.0)(typescript@5.3.3))(eslint@9.8.0)(typescript@5.3.3)': + '@typescript-eslint/eslint-plugin@7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.11.0)(typescript@5.3.3))(eslint@9.11.0)(typescript@5.3.3)': dependencies: '@eslint-community/regexpp': 4.6.2 - '@typescript-eslint/parser': 6.11.0(eslint@9.8.0)(typescript@5.3.3) - '@typescript-eslint/scope-manager': 6.11.0 - '@typescript-eslint/type-utils': 6.11.0(eslint@9.8.0)(typescript@5.3.3) - '@typescript-eslint/utils': 6.11.0(eslint@9.8.0)(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 6.11.0 - debug: 4.3.4(supports-color@5.5.0) - eslint: 9.8.0 - graphemer: 1.4.0 - ignore: 5.2.4 - natural-compare: 1.4.0 - semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.3.3) - optionalDependencies: - typescript: 5.3.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/eslint-plugin@7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.8.0)(typescript@5.3.3))(eslint@9.8.0)(typescript@5.3.3)': - dependencies: - '@eslint-community/regexpp': 4.6.2 - '@typescript-eslint/parser': 7.1.0(eslint@9.8.0)(typescript@5.3.3) + '@typescript-eslint/parser': 7.1.0(eslint@9.11.0)(typescript@5.3.3) '@typescript-eslint/scope-manager': 7.1.0 - '@typescript-eslint/type-utils': 7.1.0(eslint@9.8.0)(typescript@5.3.3) - '@typescript-eslint/utils': 7.1.0(eslint@9.8.0)(typescript@5.3.3) + '@typescript-eslint/type-utils': 7.1.0(eslint@9.11.0)(typescript@5.3.3) + '@typescript-eslint/utils': 7.1.0(eslint@9.11.0)(typescript@5.3.3) '@typescript-eslint/visitor-keys': 7.1.0 debug: 4.3.4(supports-color@5.5.0) - eslint: 9.8.0 + eslint: 9.11.0 graphemer: 1.4.0 ignore: 5.2.4 natural-compare: 1.4.0 @@ -17015,15 +15892,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4)': + '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.5.4))(eslint@9.11.0)(typescript@5.5.4)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/parser': 7.17.0(eslint@9.11.0)(typescript@5.5.4) '@typescript-eslint/scope-manager': 7.17.0 - '@typescript-eslint/type-utils': 7.17.0(eslint@9.8.0)(typescript@5.5.4) - '@typescript-eslint/utils': 7.17.0(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/type-utils': 7.17.0(eslint@9.11.0)(typescript@5.5.4) + '@typescript-eslint/utils': 7.17.0(eslint@9.11.0)(typescript@5.5.4) '@typescript-eslint/visitor-keys': 7.17.0 - eslint: 9.8.0 + eslint: 9.11.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 @@ -17033,49 +15910,93 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@6.11.0(eslint@9.8.0)(typescript@5.3.3)': + '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)(typescript@5.6.2)': dependencies: - '@typescript-eslint/scope-manager': 6.11.0 - '@typescript-eslint/types': 6.11.0 - '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 6.11.0 - debug: 4.3.4(supports-color@5.5.0) - eslint: 9.8.0 + '@eslint-community/regexpp': 4.11.0 + '@typescript-eslint/parser': 7.17.0(eslint@9.11.0)(typescript@5.6.2) + '@typescript-eslint/scope-manager': 7.17.0 + '@typescript-eslint/type-utils': 7.17.0(eslint@9.11.0)(typescript@5.6.2) + '@typescript-eslint/utils': 7.17.0(eslint@9.11.0)(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 7.17.0 + eslint: 9.11.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: - typescript: 5.3.3 + typescript: 5.6.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.1.0(eslint@9.8.0)(typescript@5.3.3)': + '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2)': + dependencies: + '@eslint-community/regexpp': 4.11.0 + '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.6.2) + '@typescript-eslint/scope-manager': 7.17.0 + '@typescript-eslint/type-utils': 7.17.0(eslint@9.8.0)(typescript@5.6.2) + '@typescript-eslint/utils': 7.17.0(eslint@9.8.0)(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 7.17.0 + eslint: 9.8.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.6.2) + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@7.1.0(eslint@9.11.0)(typescript@5.3.3)': dependencies: '@typescript-eslint/scope-manager': 7.1.0 '@typescript-eslint/types': 7.1.0 '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3) '@typescript-eslint/visitor-keys': 7.1.0 debug: 4.3.4(supports-color@5.5.0) - eslint: 9.8.0 + eslint: 9.11.0 optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4)': + '@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.5.4)': dependencies: '@typescript-eslint/scope-manager': 7.17.0 '@typescript-eslint/types': 7.17.0 '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4) '@typescript-eslint/visitor-keys': 7.17.0 debug: 4.3.5(supports-color@8.1.1) - eslint: 9.8.0 + eslint: 9.11.0 optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@6.11.0': + '@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2)': dependencies: - '@typescript-eslint/types': 6.11.0 - '@typescript-eslint/visitor-keys': 6.11.0 + '@typescript-eslint/scope-manager': 7.17.0 + '@typescript-eslint/types': 7.17.0 + '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 7.17.0 + debug: 4.3.5(supports-color@8.1.1) + eslint: 9.11.0 + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2)': + dependencies: + '@typescript-eslint/scope-manager': 7.17.0 + '@typescript-eslint/types': 7.17.0 + '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 7.17.0 + debug: 4.3.5(supports-color@8.1.1) + eslint: 9.8.0 + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - supports-color '@typescript-eslint/scope-manager@7.1.0': dependencies: @@ -17087,67 +16008,63 @@ snapshots: '@typescript-eslint/types': 7.17.0 '@typescript-eslint/visitor-keys': 7.17.0 - '@typescript-eslint/type-utils@6.11.0(eslint@9.8.0)(typescript@5.3.3)': - dependencies: - '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3) - '@typescript-eslint/utils': 6.11.0(eslint@9.8.0)(typescript@5.3.3) - debug: 4.3.5(supports-color@8.1.1) - eslint: 9.8.0 - ts-api-utils: 1.0.1(typescript@5.3.3) - optionalDependencies: - typescript: 5.3.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/type-utils@7.1.0(eslint@9.8.0)(typescript@5.3.3)': + '@typescript-eslint/type-utils@7.1.0(eslint@9.11.0)(typescript@5.3.3)': dependencies: '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3) - '@typescript-eslint/utils': 7.1.0(eslint@9.8.0)(typescript@5.3.3) - debug: 4.3.4(supports-color@5.5.0) - eslint: 9.8.0 + '@typescript-eslint/utils': 7.1.0(eslint@9.11.0)(typescript@5.3.3) + debug: 4.3.5(supports-color@8.1.1) + eslint: 9.11.0 ts-api-utils: 1.0.1(typescript@5.3.3) optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@7.17.0(eslint@9.8.0)(typescript@5.5.4)': + '@typescript-eslint/type-utils@7.17.0(eslint@9.11.0)(typescript@5.5.4)': dependencies: '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4) - '@typescript-eslint/utils': 7.17.0(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/utils': 7.17.0(eslint@9.11.0)(typescript@5.5.4) debug: 4.3.5(supports-color@8.1.1) - eslint: 9.8.0 + eslint: 9.11.0 ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@6.11.0': {} + '@typescript-eslint/type-utils@7.17.0(eslint@9.11.0)(typescript@5.6.2)': + dependencies: + '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2) + '@typescript-eslint/utils': 7.17.0(eslint@9.11.0)(typescript@5.6.2) + debug: 4.3.5(supports-color@8.1.1) + eslint: 9.11.0 + ts-api-utils: 1.3.0(typescript@5.6.2) + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/type-utils@7.17.0(eslint@9.8.0)(typescript@5.6.2)': + dependencies: + '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2) + '@typescript-eslint/utils': 7.17.0(eslint@9.8.0)(typescript@5.6.2) + debug: 4.3.5(supports-color@8.1.1) + eslint: 9.8.0 + ts-api-utils: 1.3.0(typescript@5.6.2) + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - supports-color '@typescript-eslint/types@7.1.0': {} '@typescript-eslint/types@7.17.0': {} - '@typescript-eslint/typescript-estree@6.11.0(typescript@5.3.3)': - dependencies: - '@typescript-eslint/types': 6.11.0 - '@typescript-eslint/visitor-keys': 6.11.0 - debug: 4.3.5(supports-color@8.1.1) - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.3.3) - optionalDependencies: - typescript: 5.3.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/typescript-estree@7.1.0(typescript@5.3.3)': dependencies: '@typescript-eslint/types': 7.1.0 '@typescript-eslint/visitor-keys': 7.1.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.5(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -17173,49 +16090,67 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@6.11.0(eslint@9.8.0)(typescript@5.3.3)': + '@typescript-eslint/typescript-estree@7.17.0(typescript@5.6.2)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) - '@types/json-schema': 7.0.12 - '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 6.11.0 - '@typescript-eslint/types': 6.11.0 - '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3) - eslint: 9.8.0 - semver: 7.5.4 + '@typescript-eslint/types': 7.17.0 + '@typescript-eslint/visitor-keys': 7.17.0 + debug: 4.3.5(supports-color@8.1.1) + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.4 + semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@5.6.2) + optionalDependencies: + typescript: 5.6.2 transitivePeerDependencies: - supports-color - - typescript - '@typescript-eslint/utils@7.1.0(eslint@9.8.0)(typescript@5.3.3)': + '@typescript-eslint/utils@7.1.0(eslint@9.11.0)(typescript@5.3.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0) '@types/json-schema': 7.0.12 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 7.1.0 '@typescript-eslint/types': 7.1.0 '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3) - eslint: 9.8.0 + eslint: 9.11.0 semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/utils@7.17.0(eslint@9.8.0)(typescript@5.5.4)': + '@typescript-eslint/utils@7.17.0(eslint@9.11.0)(typescript@5.5.4)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0) '@typescript-eslint/scope-manager': 7.17.0 '@typescript-eslint/types': 7.17.0 '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4) - eslint: 9.8.0 + eslint: 9.11.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@6.11.0': + '@typescript-eslint/utils@7.17.0(eslint@9.11.0)(typescript@5.6.2)': dependencies: - '@typescript-eslint/types': 6.11.0 - eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0) + '@typescript-eslint/scope-manager': 7.17.0 + '@typescript-eslint/types': 7.17.0 + '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2) + eslint: 9.11.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/utils@7.17.0(eslint@9.8.0)(typescript@5.6.2)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) + '@typescript-eslint/scope-manager': 7.17.0 + '@typescript-eslint/types': 7.17.0 + '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2) + eslint: 9.8.0 + transitivePeerDependencies: + - supports-color + - typescript '@typescript-eslint/visitor-keys@7.1.0': dependencies: @@ -17229,27 +16164,46 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-vue@5.1.0(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.37(typescript@5.5.4))': + '@vitejs/plugin-vue@5.1.4(vite@5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.7(typescript@5.6.2))': dependencies: - vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3) - vue: 3.4.37(typescript@5.5.4) + vite: 5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) + vue: 3.5.7(typescript@5.6.2) - '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))': + '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0))': dependencies: '@ampproject/remapping': 2.2.1 '@bcoe/v8-coverage': 0.2.3 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.5(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.4 istanbul-reports: 3.1.6 magic-string: 0.30.10 magicast: 0.3.4 - picocolors: 1.0.0 + picocolors: 1.0.1 std-env: 3.7.0 strip-literal: 2.1.0 test-exclude: 6.0.0 - vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3) + vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0) + transitivePeerDependencies: + - supports-color + + '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.3)(terser@5.33.0))': + dependencies: + '@ampproject/remapping': 2.2.1 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.3.5(supports-color@8.1.1) + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.4 + istanbul-reports: 3.1.6 + magic-string: 0.30.10 + magicast: 0.3.4 + picocolors: 1.0.1 + std-env: 3.7.0 + strip-literal: 2.1.0 + test-exclude: 6.0.0 + vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.3)(terser@5.33.0) transitivePeerDependencies: - supports-color @@ -17259,6 +16213,21 @@ snapshots: '@vitest/utils': 1.6.0 chai: 4.3.10 + '@vitest/expect@2.0.5': + dependencies: + '@vitest/spy': 2.0.5 + '@vitest/utils': 2.0.5 + chai: 5.1.1 + tinyrainbow: 1.2.0 + + '@vitest/pretty-format@2.0.5': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/pretty-format@2.1.1': + dependencies: + tinyrainbow: 1.2.0 + '@vitest/runner@1.6.0': dependencies: '@vitest/utils': 1.6.0 @@ -17275,6 +16244,10 @@ snapshots: dependencies: tinyspy: 2.2.0 + '@vitest/spy@2.0.5': + dependencies: + tinyspy: 3.0.2 + '@vitest/utils@1.6.0': dependencies: diff-sequences: 29.6.3 @@ -17282,47 +16255,44 @@ snapshots: loupe: 2.3.7 pretty-format: 29.7.0 + '@vitest/utils@2.0.5': + dependencies: + '@vitest/pretty-format': 2.0.5 + estree-walker: 3.0.3 + loupe: 3.1.1 + tinyrainbow: 1.2.0 + + '@vitest/utils@2.1.1': + dependencies: + '@vitest/pretty-format': 2.1.1 + loupe: 3.1.1 + tinyrainbow: 1.2.0 + '@volar/language-core@2.2.0': dependencies: '@volar/source-map': 2.2.0 - '@volar/language-core@2.4.0-alpha.18': + '@volar/language-core@2.4.5': dependencies: - '@volar/source-map': 2.4.0-alpha.18 + '@volar/source-map': 2.4.5 '@volar/source-map@2.2.0': dependencies: muggle-string: 0.4.1 - '@volar/source-map@2.4.0-alpha.18': {} + '@volar/source-map@2.4.5': {} '@volar/typescript@2.2.0': dependencies: '@volar/language-core': 2.2.0 path-browserify: 1.0.1 - '@volar/typescript@2.4.0-alpha.18': + '@volar/typescript@2.4.5': dependencies: - '@volar/language-core': 2.4.0-alpha.18 + '@volar/language-core': 2.4.5 path-browserify: 1.0.1 vscode-uri: 3.0.8 - '@vue/compiler-core@3.4.31': - dependencies: - '@babel/parser': 7.24.7 - '@vue/shared': 3.4.31 - entities: 4.5.0 - estree-walker: 2.0.2 - source-map-js: 1.2.0 - - '@vue/compiler-core@3.4.34': - dependencies: - '@babel/parser': 7.24.7 - '@vue/shared': 3.4.34 - entities: 4.5.0 - estree-walker: 2.0.2 - source-map-js: 1.2.0 - '@vue/compiler-core@3.4.37': dependencies: '@babel/parser': 7.24.7 @@ -17331,16 +16301,24 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.0 - '@vue/compiler-dom@3.4.34': + '@vue/compiler-core@3.5.7': dependencies: - '@vue/compiler-core': 3.4.34 - '@vue/shared': 3.4.34 + '@babel/parser': 7.25.6 + '@vue/shared': 3.5.7 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.0 '@vue/compiler-dom@3.4.37': dependencies: '@vue/compiler-core': 3.4.37 '@vue/shared': 3.4.37 + '@vue/compiler-dom@3.5.7': + dependencies: + '@vue/compiler-core': 3.5.7 + '@vue/shared': 3.5.7 + '@vue/compiler-sfc@3.4.37': dependencies: '@babel/parser': 7.24.7 @@ -17350,7 +16328,19 @@ snapshots: '@vue/shared': 3.4.37 estree-walker: 2.0.2 magic-string: 0.30.10 - postcss: 8.4.40 + postcss: 8.4.47 + source-map-js: 1.2.0 + + '@vue/compiler-sfc@3.5.7': + dependencies: + '@babel/parser': 7.25.6 + '@vue/compiler-core': 3.5.7 + '@vue/compiler-dom': 3.5.7 + '@vue/compiler-ssr': 3.5.7 + '@vue/shared': 3.5.7 + estree-walker: 2.0.2 + magic-string: 0.30.11 + postcss: 8.4.47 source-map-js: 1.2.0 '@vue/compiler-ssr@3.4.37': @@ -17358,47 +16348,59 @@ snapshots: '@vue/compiler-dom': 3.4.37 '@vue/shared': 3.4.37 + '@vue/compiler-ssr@3.5.7': + dependencies: + '@vue/compiler-dom': 3.5.7 + '@vue/shared': 3.5.7 + '@vue/compiler-vue2@2.7.16': dependencies: de-indent: 1.0.2 he: 1.2.0 - '@vue/devtools-api@6.6.1': {} - - '@vue/language-core@2.0.16(typescript@5.5.4)': + '@vue/language-core@2.0.16(typescript@5.6.2)': dependencies: '@volar/language-core': 2.2.0 - '@vue/compiler-dom': 3.4.34 - '@vue/shared': 3.4.34 + '@vue/compiler-dom': 3.4.37 + '@vue/shared': 3.4.37 computeds: 0.0.1 minimatch: 9.0.4 path-browserify: 1.0.1 vue-template-compiler: 2.7.14 optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 - '@vue/language-core@2.0.29(typescript@5.5.4)': + '@vue/language-core@2.1.6(typescript@5.6.2)': dependencies: - '@volar/language-core': 2.4.0-alpha.18 - '@vue/compiler-dom': 3.4.34 + '@volar/language-core': 2.4.5 + '@vue/compiler-dom': 3.4.37 '@vue/compiler-vue2': 2.7.16 - '@vue/shared': 3.4.34 + '@vue/shared': 3.4.37 computeds: 0.0.1 minimatch: 9.0.4 muggle-string: 0.4.1 path-browserify: 1.0.1 optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 '@vue/reactivity@3.4.37': dependencies: '@vue/shared': 3.4.37 + '@vue/reactivity@3.5.7': + dependencies: + '@vue/shared': 3.5.7 + '@vue/runtime-core@3.4.37': dependencies: '@vue/reactivity': 3.4.37 '@vue/shared': 3.4.37 + '@vue/runtime-core@3.5.7': + dependencies: + '@vue/reactivity': 3.5.7 + '@vue/shared': 3.5.7 + '@vue/runtime-dom@3.4.37': dependencies: '@vue/reactivity': 3.4.37 @@ -17406,43 +16408,39 @@ snapshots: '@vue/shared': 3.4.37 csstype: 3.1.3 + '@vue/runtime-dom@3.5.7': + dependencies: + '@vue/reactivity': 3.5.7 + '@vue/runtime-core': 3.5.7 + '@vue/shared': 3.5.7 + csstype: 3.1.3 + '@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4))': dependencies: '@vue/compiler-ssr': 3.4.37 '@vue/shared': 3.4.37 vue: 3.4.37(typescript@5.5.4) - '@vue/shared@3.4.31': {} - - '@vue/shared@3.4.34': {} + '@vue/server-renderer@3.5.7(vue@3.5.7(typescript@5.6.2))': + dependencies: + '@vue/compiler-ssr': 3.5.7 + '@vue/shared': 3.5.7 + vue: 3.5.7(typescript@5.6.2) '@vue/shared@3.4.37': {} - '@vue/test-utils@2.4.1(@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4)))(vue@3.4.37(typescript@5.5.4))': + '@vue/shared@3.5.7': {} + + '@vue/test-utils@2.4.1(@vue/server-renderer@3.5.7(vue@3.5.7(typescript@5.6.2)))(vue@3.5.7(typescript@5.6.2))': dependencies: js-beautify: 1.14.9 - vue: 3.4.37(typescript@5.5.4) + vue: 3.5.7(typescript@5.6.2) vue-component-type-helpers: 1.8.4 optionalDependencies: - '@vue/server-renderer': 3.4.37(vue@3.4.37(typescript@5.5.4)) + '@vue/server-renderer': 3.5.7(vue@3.5.7(typescript@5.6.2)) '@webgpu/types@0.1.30': {} - '@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.19.11)': - dependencies: - esbuild: 0.19.11 - tslib: 2.6.3 - - '@yarnpkg/fslib@2.10.3': - dependencies: - '@yarnpkg/libzip': 2.3.0 - tslib: 1.14.1 - - '@yarnpkg/libzip@2.3.0': - dependencies: - '@types/emscripten': 1.39.7 - tslib: 1.14.1 - abbrev@1.1.1: {} abbrev@2.0.0: {} @@ -17483,8 +16481,6 @@ snapshots: acorn@8.12.1: {} - address@1.2.2: {} - adm-zip@0.5.10: optional: true @@ -17498,6 +16494,7 @@ snapshots: debug: 4.3.5(supports-color@8.1.1) transitivePeerDependencies: - supports-color + optional: true agent-base@7.1.0: dependencies: @@ -17528,14 +16525,14 @@ snapshots: optionalDependencies: ajv: 8.17.1 - ajv-formats@2.1.1(ajv@8.17.1): - optionalDependencies: - ajv: 8.17.1 - ajv-formats@3.0.1(ajv@8.13.0): optionalDependencies: ajv: 8.13.0 + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -17593,8 +16590,6 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 - app-root-dir@1.0.2: {} - app-root-path@3.1.0: {} append-field@1.0.0: {} @@ -17624,8 +16619,6 @@ snapshots: tar-stream: 3.1.6 zip-stream: 6.0.1 - archy@1.0.0: {} - are-we-there-yet@2.0.0: dependencies: delegates: 1.0.0 @@ -17650,39 +16643,46 @@ snapshots: array-buffer-byte-length@1.0.0: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 is-array-buffer: 3.0.2 + array-buffer-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + is-array-buffer: 3.0.4 + array-flatten@1.1.1: {} - array-includes@3.1.7: + array-includes@3.1.8: dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 - get-intrinsic: 1.2.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 is-string: 1.0.7 array-union@2.1.0: {} - array.prototype.findlastindex@1.2.3: + array.prototype.findlastindex@1.2.5: dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 - es-shim-unscopables: 1.0.0 - get-intrinsic: 1.2.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 array.prototype.flat@1.3.2: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.0 es-abstract: 1.22.1 es-shim-unscopables: 1.0.0 array.prototype.flatmap@1.3.2: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.0 es-abstract: 1.22.1 es-shim-unscopables: 1.0.0 @@ -17690,12 +16690,23 @@ snapshots: arraybuffer.prototype.slice@1.0.1: dependencies: array-buffer-byte-length: 1.0.0 - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.0 - get-intrinsic: 1.2.1 + get-intrinsic: 1.2.4 is-array-buffer: 3.0.2 is-shared-array-buffer: 1.0.2 + arraybuffer.prototype.slice@1.0.3: + dependencies: + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + is-array-buffer: 3.0.4 + is-shared-array-buffer: 1.0.3 + arrify@1.0.1: {} asap@2.0.6: {} @@ -17721,23 +16732,17 @@ snapshots: assert-plus@1.0.0: {} - assert@2.1.0: - dependencies: - call-bind: 1.0.2 - is-nan: 1.3.2 - object-is: 1.1.5 - object.assign: 4.1.4 - util: 0.12.5 - assertion-error@1.1.0: {} + assertion-error@2.0.1: {} + ast-types@0.16.1: dependencies: tslib: 2.6.3 astral-regex@2.0.0: {} - astring@1.8.6: {} + astring@1.9.0: {} async-mutex@0.5.0: dependencies: @@ -17755,14 +16760,14 @@ snapshots: available-typed-arrays@1.0.5: {} - avvio@8.3.0: + available-typed-arrays@1.0.7: dependencies: - '@fastify/error': 3.4.0 - archy: 1.0.0 - debug: 4.3.5(supports-color@8.1.1) + possible-typed-array-names: 1.0.0 + + avvio@9.0.0: + dependencies: + '@fastify/error': 4.0.0 fastq: 1.17.1 - transitivePeerDependencies: - - supports-color aws-sdk-client-mock@4.0.1: dependencies: @@ -17776,13 +16781,13 @@ snapshots: axios@0.24.0: dependencies: - follow-redirects: 1.15.2(debug@4.3.5) + follow-redirects: 1.15.2 transitivePeerDependencies: - debug - axios@1.6.2(debug@4.3.5): + axios@1.7.7(debug@4.3.7): dependencies: - follow-redirects: 1.15.2(debug@4.3.5) + follow-redirects: 1.15.9(debug@4.3.7) form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -17790,10 +16795,6 @@ snapshots: b4a@1.6.4: {} - babel-core@7.0.0-bridge.0(@babel/core@7.24.7): - dependencies: - '@babel/core': 7.24.7 - babel-jest@29.7.0(@babel/core@7.23.5): dependencies: '@babel/core': 7.23.5 @@ -17824,30 +16825,6 @@ snapshots: '@types/babel__core': 7.20.0 '@types/babel__traverse': 7.20.0 - babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.24.7): - dependencies: - '@babel/compat-data': 7.24.7 - '@babel/core': 7.24.7 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.7) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - babel-plugin-polyfill-corejs3@0.10.4(@babel/core@7.24.7): - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.7) - core-js-compat: 3.37.1 - transitivePeerDependencies: - - supports-color - - babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.24.7): - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.7) - transitivePeerDependencies: - - supports-color - babel-preset-current-node-syntax@1.0.1(@babel/core@7.23.5): dependencies: '@babel/core': 7.23.5 @@ -17890,8 +16867,6 @@ snapshots: dependencies: open: 8.4.2 - big-integer@1.6.51: {} - bin-check@4.1.0: dependencies: execa: 0.7.0 @@ -17910,12 +16885,6 @@ snapshots: binary-extensions@2.2.0: {} - bl@4.1.0: - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.0 - blob-util@2.0.2: {} bluebird@3.7.2: {} @@ -17941,14 +16910,27 @@ snapshots: transitivePeerDependencies: - supports-color + body-parser@1.20.3: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + boolbase@1.0.0: {} bowser@2.11.0: {} - bplist-parser@0.2.0: - dependencies: - big-integer: 1.6.51 - brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -18026,7 +17008,7 @@ snapshots: node-gyp-build: 4.6.0 optional: true - bullmq@5.10.4: + bullmq@5.13.2: dependencies: cron-parser: 4.8.1 ioredis: 5.4.1 @@ -18044,8 +17026,6 @@ snapshots: dependencies: streamsearch: 1.1.0 - bytes@3.0.0: {} - bytes@3.1.2: {} cac@6.7.14: {} @@ -18076,7 +17056,7 @@ snapshots: http-cache-semantics: 4.1.1 keyv: 4.5.4 mimic-response: 4.0.0 - normalize-url: 8.0.0 + normalize-url: 8.0.1 responselike: 3.0.0 cacheable-request@12.0.1: @@ -18106,6 +17086,14 @@ snapshots: function-bind: 1.1.2 get-intrinsic: 1.2.1 + call-bind@1.0.7: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + call-me-maybe@1.0.2: {} callsites@3.1.0: {} @@ -18153,6 +17141,14 @@ snapshots: pathval: 1.1.1 type-detect: 4.0.8 + chai@5.1.1: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.1 + pathval: 2.0.0 + chalk-template@1.1.0: dependencies: chalk: 5.3.0 @@ -18183,32 +17179,34 @@ snapshots: dependencies: is-regex: 1.1.4 - chart.js@4.4.3: + chart.js@4.4.4: dependencies: '@kurkle/color': 0.3.2 - chartjs-adapter-date-fns@3.0.0(chart.js@4.4.3)(date-fns@2.30.0): + chartjs-adapter-date-fns@3.0.0(chart.js@4.4.4)(date-fns@2.30.0): dependencies: - chart.js: 4.4.3 + chart.js: 4.4.4 date-fns: 2.30.0 - chartjs-chart-matrix@2.0.1(chart.js@4.4.3): + chartjs-chart-matrix@2.0.1(chart.js@4.4.4): dependencies: - chart.js: 4.4.3 + chart.js: 4.4.4 - chartjs-plugin-gradient@0.6.1(chart.js@4.4.3): + chartjs-plugin-gradient@0.6.1(chart.js@4.4.4): dependencies: - chart.js: 4.4.3 + chart.js: 4.4.4 - chartjs-plugin-zoom@2.0.1(chart.js@4.4.3): + chartjs-plugin-zoom@2.0.1(chart.js@4.4.4): dependencies: - chart.js: 4.4.3 + chart.js: 4.4.4 hammerjs: 2.0.8 check-error@1.0.3: dependencies: get-func-name: 2.0.2 + check-error@2.1.1: {} + check-more-types@2.24.0: {} cheerio-select@2.1.0: @@ -18220,6 +17218,20 @@ snapshots: domhandler: 5.0.3 domutils: 3.0.1 + cheerio@1.0.0: + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.1.0 + encoding-sniffer: 0.2.0 + htmlparser2: 9.1.0 + parse5: 7.1.2 + parse5-htmlparser2-tree-adapter: 7.0.0 + parse5-parser-stream: 7.1.2 + undici: 6.19.8 + whatwg-mimetype: 4.0.0 + cheerio@1.0.0-rc.12: dependencies: cheerio-select: 2.1.0 @@ -18247,7 +17259,7 @@ snapshots: chownr@2.0.0: {} - chromatic@11.5.6: {} + chromatic@11.10.2: {} ci-info@3.7.1: {} @@ -18305,18 +17317,10 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - clone-deep@4.0.1: - dependencies: - is-plain-object: 2.0.4 - kind-of: 6.0.3 - shallow-clone: 3.0.1 - clone-response@1.0.3: dependencies: mimic-response: 1.0.1 - clone@1.0.4: {} - cluster-key-slot@1.1.2: {} co@4.6.0: {} @@ -18360,6 +17364,8 @@ snapshots: commander@10.0.1: {} + commander@12.1.0: {} + commander@2.20.3: {} commander@6.2.1: {} @@ -18384,22 +17390,6 @@ snapshots: normalize-path: 3.0.0 readable-stream: 4.3.0 - compressible@2.0.18: - dependencies: - mime-db: 1.52.0 - - compression@1.7.4: - dependencies: - accepts: 1.3.8 - bytes: 3.0.0 - compressible: 2.0.18 - debug: 2.6.9 - on-headers: 1.0.2 - safe-buffer: 5.1.2 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - computeds@0.0.1: {} concat-map@0.0.1: {} @@ -18442,10 +17432,6 @@ snapshots: cookie@0.6.0: {} - core-js-compat@3.37.1: - dependencies: - browserslist: 4.23.0 - core-js@3.29.1: {} core-util-is@1.0.2: {} @@ -18483,10 +17469,10 @@ snapshots: dependencies: luxon: 3.3.0 - cropperjs@2.0.0-rc.1: + cropperjs@2.0.0-rc.2: dependencies: - '@cropper/elements': 2.0.0-rc.1 - '@cropper/utils': 2.0.0-rc.1 + '@cropper/elements': 2.0.0-rc.2 + '@cropper/utils': 2.0.0-rc.2 cross-env@7.0.3: dependencies: @@ -18516,13 +17502,9 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crypto-random-string@4.0.0: + css-declaration-sorter@7.2.0(postcss@8.4.47): dependencies: - type-fest: 1.4.0 - - css-declaration-sorter@7.2.0(postcss@8.4.40): - dependencies: - postcss: 8.4.40 + postcss: 8.4.47 css-select@5.1.0: dependencies: @@ -18548,49 +17530,49 @@ snapshots: cssesc@3.0.0: {} - cssnano-preset-default@6.1.2(postcss@8.4.40): + cssnano-preset-default@6.1.2(postcss@8.4.47): dependencies: browserslist: 4.23.0 - css-declaration-sorter: 7.2.0(postcss@8.4.40) - cssnano-utils: 4.0.2(postcss@8.4.40) - postcss: 8.4.40 - postcss-calc: 9.0.1(postcss@8.4.40) - postcss-colormin: 6.1.0(postcss@8.4.40) - postcss-convert-values: 6.1.0(postcss@8.4.40) - postcss-discard-comments: 6.0.2(postcss@8.4.40) - postcss-discard-duplicates: 6.0.3(postcss@8.4.40) - postcss-discard-empty: 6.0.3(postcss@8.4.40) - postcss-discard-overridden: 6.0.2(postcss@8.4.40) - postcss-merge-longhand: 6.0.5(postcss@8.4.40) - postcss-merge-rules: 6.1.1(postcss@8.4.40) - postcss-minify-font-values: 6.1.0(postcss@8.4.40) - postcss-minify-gradients: 6.0.3(postcss@8.4.40) - postcss-minify-params: 6.1.0(postcss@8.4.40) - postcss-minify-selectors: 6.0.4(postcss@8.4.40) - postcss-normalize-charset: 6.0.2(postcss@8.4.40) - postcss-normalize-display-values: 6.0.2(postcss@8.4.40) - postcss-normalize-positions: 6.0.2(postcss@8.4.40) - postcss-normalize-repeat-style: 6.0.2(postcss@8.4.40) - postcss-normalize-string: 6.0.2(postcss@8.4.40) - postcss-normalize-timing-functions: 6.0.2(postcss@8.4.40) - postcss-normalize-unicode: 6.1.0(postcss@8.4.40) - postcss-normalize-url: 6.0.2(postcss@8.4.40) - postcss-normalize-whitespace: 6.0.2(postcss@8.4.40) - postcss-ordered-values: 6.0.2(postcss@8.4.40) - postcss-reduce-initial: 6.1.0(postcss@8.4.40) - postcss-reduce-transforms: 6.0.2(postcss@8.4.40) - postcss-svgo: 6.0.3(postcss@8.4.40) - postcss-unique-selectors: 6.0.4(postcss@8.4.40) + css-declaration-sorter: 7.2.0(postcss@8.4.47) + cssnano-utils: 4.0.2(postcss@8.4.47) + postcss: 8.4.47 + postcss-calc: 9.0.1(postcss@8.4.47) + postcss-colormin: 6.1.0(postcss@8.4.47) + postcss-convert-values: 6.1.0(postcss@8.4.47) + postcss-discard-comments: 6.0.2(postcss@8.4.47) + postcss-discard-duplicates: 6.0.3(postcss@8.4.47) + postcss-discard-empty: 6.0.3(postcss@8.4.47) + postcss-discard-overridden: 6.0.2(postcss@8.4.47) + postcss-merge-longhand: 6.0.5(postcss@8.4.47) + postcss-merge-rules: 6.1.1(postcss@8.4.47) + postcss-minify-font-values: 6.1.0(postcss@8.4.47) + postcss-minify-gradients: 6.0.3(postcss@8.4.47) + postcss-minify-params: 6.1.0(postcss@8.4.47) + postcss-minify-selectors: 6.0.4(postcss@8.4.47) + postcss-normalize-charset: 6.0.2(postcss@8.4.47) + postcss-normalize-display-values: 6.0.2(postcss@8.4.47) + postcss-normalize-positions: 6.0.2(postcss@8.4.47) + postcss-normalize-repeat-style: 6.0.2(postcss@8.4.47) + postcss-normalize-string: 6.0.2(postcss@8.4.47) + postcss-normalize-timing-functions: 6.0.2(postcss@8.4.47) + postcss-normalize-unicode: 6.1.0(postcss@8.4.47) + postcss-normalize-url: 6.0.2(postcss@8.4.47) + postcss-normalize-whitespace: 6.0.2(postcss@8.4.47) + postcss-ordered-values: 6.0.2(postcss@8.4.47) + postcss-reduce-initial: 6.1.0(postcss@8.4.47) + postcss-reduce-transforms: 6.0.2(postcss@8.4.47) + postcss-svgo: 6.0.3(postcss@8.4.47) + postcss-unique-selectors: 6.0.4(postcss@8.4.47) - cssnano-utils@4.0.2(postcss@8.4.40): + cssnano-utils@4.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 - cssnano@6.1.2(postcss@8.4.40): + cssnano@6.1.2(postcss@8.4.47): dependencies: - cssnano-preset-default: 6.1.2(postcss@8.4.40) + cssnano-preset-default: 6.1.2(postcss@8.4.47) lilconfig: 3.1.1 - postcss: 8.4.40 + postcss: 8.4.47 csso@5.0.5: dependencies: @@ -18606,9 +17588,9 @@ snapshots: dependencies: uniq: 1.0.1 - cypress@13.13.1: + cypress@13.14.2: dependencies: - '@cypress/request': 3.0.0 + '@cypress/request': 3.0.5 '@cypress/xvfb': 1.2.4(supports-color@8.1.1) '@types/sinonjs__fake-timers': 8.1.1 '@types/sizzle': 2.3.3 @@ -18664,6 +17646,24 @@ snapshots: whatwg-mimetype: 4.0.0 whatwg-url: 14.0.0 + data-view-buffer@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-offset@1.0.0: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + date-fns@2.30.0: dependencies: '@babel/runtime': 7.23.4 @@ -18688,12 +17688,22 @@ snapshots: optionalDependencies: supports-color: 5.5.0 + debug@4.3.5(supports-color@5.5.0): + dependencies: + ms: 2.1.2 + optionalDependencies: + supports-color: 5.5.0 + debug@4.3.5(supports-color@8.1.1): dependencies: ms: 2.1.2 optionalDependencies: supports-color: 8.1.1 + debug@4.3.7: + dependencies: + ms: 2.1.3 + decamelize-keys@1.1.1: dependencies: decamelize: 1.2.0 @@ -18737,6 +17747,8 @@ snapshots: dependencies: type-detect: 4.0.8 + deep-eql@5.0.2: {} + deep-equal@2.2.0: dependencies: call-bind: 1.0.2 @@ -18761,17 +17773,14 @@ snapshots: deepmerge@4.2.2: {} - default-browser-id@3.0.0: - dependencies: - bplist-parser: 0.2.0 - untildify: 4.0.0 - - defaults@1.0.4: - dependencies: - clone: 1.0.4 - defer-to-connect@2.0.1: {} + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + define-lazy-prop@2.0.0: {} define-properties@1.2.0: @@ -18779,7 +17788,11 @@ snapshots: has-property-descriptors: 1.0.0 object-keys: 1.1.1 - defu@6.1.4: {} + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 delayed-stream@1.0.0: {} @@ -18794,23 +17807,10 @@ snapshots: destroy@1.2.0: {} - detect-indent@6.1.0: {} - detect-libc@2.0.3: {} detect-newline@3.1.0: {} - detect-package-manager@2.0.1: - dependencies: - execa: 5.1.1 - - detect-port@1.5.1: - dependencies: - address: 1.2.2 - debug: 4.3.5(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color - devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -18845,6 +17845,12 @@ snapshots: dom-accessibility-api@0.6.3: {} + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -18853,17 +17859,35 @@ snapshots: domelementtype@2.3.0: {} + domhandler@3.3.0: + dependencies: + domelementtype: 2.3.0 + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + domhandler@5.0.3: dependencies: domelementtype: 2.3.0 + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + domutils@3.0.1: dependencies: dom-serializer: 2.0.0 domelementtype: 2.3.0 domhandler: 5.0.3 - dotenv-expand@10.0.0: {} + domutils@3.1.0: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 dotenv@16.0.3: {} @@ -18903,10 +17927,15 @@ snapshots: emoji-regex@9.2.2: {} - encode-utf8@1.0.3: {} - encodeurl@1.0.2: {} + encodeurl@2.0.0: {} + + encoding-sniffer@0.2.0: + dependencies: + iconv-lite: 0.6.3 + whatwg-encoding: 3.1.1 + encoding@0.1.13: dependencies: iconv-lite: 0.6.3 @@ -18928,8 +17957,6 @@ snapshots: env-paths@2.2.1: {} - envinfo@7.8.1: {} - err-code@2.0.3: {} error-ex@1.3.2: @@ -18941,16 +17968,16 @@ snapshots: array-buffer-byte-length: 1.0.0 arraybuffer.prototype.slice: 1.0.1 available-typed-arrays: 1.0.5 - call-bind: 1.0.2 + call-bind: 1.0.7 es-set-tostringtag: 2.0.1 es-to-primitive: 1.2.1 function.prototype.name: 1.1.5 - get-intrinsic: 1.2.1 + get-intrinsic: 1.2.4 get-symbol-description: 1.0.0 globalthis: 1.0.3 gopd: 1.0.1 has: 1.0.3 - has-property-descriptors: 1.0.0 + has-property-descriptors: 1.0.2 has-proto: 1.0.1 has-symbols: 1.0.3 internal-slot: 1.0.5 @@ -18962,7 +17989,7 @@ snapshots: is-string: 1.0.7 is-typed-array: 1.1.10 is-weakref: 1.0.2 - object-inspect: 1.12.3 + object-inspect: 1.13.2 object-keys: 1.1.1 object.assign: 4.1.4 regexp.prototype.flags: 1.5.0 @@ -18978,6 +18005,61 @@ snapshots: unbox-primitive: 1.0.2 which-typed-array: 1.1.11 + es-abstract@1.23.3: + dependencies: + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 + data-view-byte-offset: 1.0.0 + es-define-property: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-set-tostringtag: 2.0.3 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.2.4 + get-symbol-description: 1.0.2 + globalthis: 1.0.3 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 + is-callable: 1.2.7 + is-data-view: 1.0.1 + is-negative-zero: 2.0.3 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.3 + is-string: 1.0.7 + is-typed-array: 1.1.13 + is-weakref: 1.0.2 + object-inspect: 1.13.2 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.2 + safe-array-concat: 1.1.2 + safe-regex-test: 1.0.3 + string.prototype.trim: 1.2.9 + string.prototype.trimend: 1.0.8 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.2 + typed-array-byte-length: 1.0.1 + typed-array-byte-offset: 1.0.2 + typed-array-length: 1.0.6 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.15 + + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 + + es-errors@1.3.0: {} + es-get-iterator@1.1.3: dependencies: call-bind: 1.0.2 @@ -18992,16 +18074,30 @@ snapshots: es-module-lexer@1.5.4: {} + es-object-atoms@1.0.0: + dependencies: + es-errors: 1.3.0 + es-set-tostringtag@2.0.1: dependencies: - get-intrinsic: 1.2.1 + get-intrinsic: 1.2.4 has: 1.0.3 has-tostringtag: 1.0.0 + es-set-tostringtag@2.0.3: + dependencies: + get-intrinsic: 1.2.4 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + es-shim-unscopables@1.0.0: dependencies: has: 1.0.3 + es-shim-unscopables@1.0.2: + dependencies: + hasown: 2.0.2 + es-to-primitive@1.2.1: dependencies: is-callable: 1.2.7 @@ -19016,12 +18112,10 @@ snapshots: es6-promise: 4.2.8 optional: true - esbuild-plugin-alias@0.2.1: {} - - esbuild-register@3.5.0(esbuild@0.19.11): + esbuild-register@3.5.0(esbuild@0.23.1): dependencies: debug: 4.3.5(supports-color@8.1.1) - esbuild: 0.19.11 + esbuild: 0.23.1 transitivePeerDependencies: - supports-color @@ -19129,8 +18223,37 @@ snapshots: '@esbuild/win32-ia32': 0.23.0 '@esbuild/win32-x64': 0.23.0 + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + escalade@3.1.1: {} + escape-goat@3.0.0: {} + escape-html@1.0.3: {} escape-regexp@0.0.1: {} @@ -19165,58 +18288,111 @@ snapshots: eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7(supports-color@8.1.1) - is-core-module: 2.13.1 + is-core-module: 2.15.1 resolve: 1.22.8 transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.8.0): + eslint-module-utils@2.11.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.11.0): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: - '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/parser': 7.17.0(eslint@9.11.0)(typescript@5.6.2) + eslint: 9.11.0 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.11.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.8.0): + dependencies: + debug: 3.2.7(supports-color@8.1.1) + optionalDependencies: + '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.6.2) eslint: 9.8.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0): dependencies: - array-includes: 3.1.7 - array.prototype.findlastindex: 1.2.3 + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7(supports-color@8.1.1) + doctrine: 2.1.0 + eslint: 9.11.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.11.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.11.0) + hasown: 2.0.2 + is-core-module: 2.15.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 7.17.0(eslint@9.11.0)(typescript@5.6.2) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 eslint: 9.8.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.8.0) - hasown: 2.0.0 - is-core-module: 2.13.1 + eslint-module-utils: 2.11.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.8.0) + hasown: 2.0.2 + is-core-module: 2.15.1 is-glob: 4.0.3 minimatch: 3.1.2 - object.fromentries: 2.0.7 - object.groupby: 1.0.1 - object.values: 1.1.7 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 semver: 6.3.1 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.6.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-vue@9.27.0(eslint@9.8.0): + eslint-plugin-vue@9.27.0(eslint@9.11.0): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) - eslint: 9.8.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0) + eslint: 9.11.0 globals: 13.24.0 natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.0.16 semver: 7.6.0 - vue-eslint-parser: 9.4.3(eslint@9.8.0) + vue-eslint-parser: 9.4.3(eslint@9.11.0) + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - supports-color + + eslint-plugin-vue@9.28.0(eslint@9.11.0): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0) + eslint: 9.11.0 + globals: 13.24.0 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.0.16 + semver: 7.6.3 + vue-eslint-parser: 9.4.3(eslint@9.11.0) xml-name-validator: 4.0.0 transitivePeerDependencies: - supports-color @@ -19237,6 +18413,45 @@ snapshots: eslint-visitor-keys@4.0.0: {} + eslint@9.11.0: + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0) + '@eslint-community/regexpp': 4.11.0 + '@eslint/config-array': 0.18.0 + '@eslint/eslintrc': 3.1.0 + '@eslint/js': 9.11.0 + '@eslint/plugin-kit': 0.2.0 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.3.0 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.7 + escape-string-regexp: 4.0.0 + eslint-scope: 8.0.2 + eslint-visitor-keys: 4.0.0 + espree: 10.1.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.1 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + eslint@9.8.0: dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) @@ -19250,7 +18465,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.7 escape-string-regexp: 4.0.0 eslint-scope: 8.0.2 eslint-visitor-keys: 4.0.0 @@ -19290,10 +18505,6 @@ snapshots: esprima@4.0.1: {} - esquery@1.4.2: - dependencies: - estraverse: 5.3.0 - esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -19308,7 +18519,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 esutils@2.0.3: {} @@ -19392,16 +18603,16 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 - execa@9.3.0: + execa@9.4.0: dependencies: '@sindresorhus/merge-streams': 4.0.0 cross-spawn: 7.0.3 figures: 6.1.0 get-stream: 9.0.1 - human-signals: 7.0.0 + human-signals: 8.0.0 is-plain-obj: 4.1.0 is-stream: 4.0.1 - npm-run-path: 5.3.0 + npm-run-path: 6.0.0 pretty-ms: 9.0.0 signal-exit: 4.1.0 strip-final-newline: 4.0.0 @@ -19459,6 +18670,42 @@ snapshots: transitivePeerDependencies: - supports-color + express@4.21.0: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.3 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.6.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.1 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.10 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + ext-list@2.2.2: dependencies: mime-db: 1.52.0 @@ -19482,7 +18729,7 @@ snapshots: extsprintf@1.3.0: {} - fast-content-type-parse@1.1.0: {} + fast-content-type-parse@2.0.0: {} fast-decode-uri-component@1.0.1: {} @@ -19496,18 +18743,19 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.7 + micromatch: 4.0.8 fast-json-stable-stringify@2.1.0: {} - fast-json-stringify@5.8.0: + fast-json-stringify@6.0.0: dependencies: - '@fastify/deepmerge': 1.3.0 + '@fastify/merge-json-schemas': 0.1.1 ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) + ajv-formats: 3.0.1(ajv@8.17.1) fast-deep-equal: 3.1.3 - fast-uri: 2.2.0 - rfdc: 1.3.0 + fast-uri: 2.4.0 + json-schema-ref-resolver: 1.0.1 + rfdc: 1.4.1 fast-levenshtein@2.0.6: {} @@ -19519,7 +18767,7 @@ snapshots: fast-safe-stringify@2.1.1: {} - fast-uri@2.2.0: {} + fast-uri@2.4.0: {} fast-uri@3.0.1: {} @@ -19527,34 +18775,39 @@ snapshots: dependencies: strnum: 1.0.5 + fast-xml-parser@4.5.0: + dependencies: + strnum: 1.0.5 + fastify-plugin@4.5.0: {} - fastify-raw-body@4.3.0: + fastify-plugin@4.5.1: {} + + fastify-plugin@5.0.0: {} + + fastify-raw-body@5.0.0: dependencies: - fastify-plugin: 4.5.0 - raw-body: 2.5.2 + fastify-plugin: 5.0.0 + raw-body: 3.0.0 secure-json-parse: 2.7.0 - fastify@4.28.1: + fastify@5.0.0: dependencies: - '@fastify/ajv-compiler': 3.5.0 - '@fastify/error': 3.4.0 - '@fastify/fast-json-stringify-compiler': 4.3.0 + '@fastify/ajv-compiler': 4.0.0 + '@fastify/error': 4.0.0 + '@fastify/fast-json-stringify-compiler': 5.0.0 abstract-logging: 2.0.1 - avvio: 8.3.0 - fast-content-type-parse: 1.1.0 - fast-json-stringify: 5.8.0 - find-my-way: 8.2.0 - light-my-request: 5.11.0 + avvio: 9.0.0 + fast-json-stringify: 6.0.0 + find-my-way: 9.0.1 + light-my-request: 6.0.0 pino: 9.2.0 - process-warning: 3.0.0 + process-warning: 4.0.0 proxy-addr: 2.0.7 - rfdc: 1.3.0 + rfdc: 1.4.1 secure-json-parse: 2.7.0 semver: 7.6.0 toad-cache: 3.7.0 - transitivePeerDependencies: - - supports-color fastq@1.17.1: dependencies: @@ -19564,10 +18817,6 @@ snapshots: dependencies: bser: 2.1.1 - fd-package-json@1.2.0: - dependencies: - walk-up-path: 3.0.1 - fd-slicer@1.1.0: dependencies: pend: 1.2.0 @@ -19581,8 +18830,6 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 3.2.1 - fetch-retry@5.0.4: {} - figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 @@ -19595,20 +18842,16 @@ snapshots: dependencies: flat-cache: 4.0.1 - file-system-cache@2.3.0: - dependencies: - fs-extra: 11.1.1 - ramda: 0.29.0 - file-type@17.1.6: dependencies: readable-web-to-node-stream: 3.0.2 strtok3: 7.0.0 token-types: 5.0.1 - file-type@19.3.0: + file-type@19.5.0: dependencies: - strtok3: 8.0.1 + get-stream: 9.0.1 + strtok3: 8.1.0 token-types: 6.0.0 uint8array-extras: 1.4.0 @@ -19644,11 +18887,17 @@ snapshots: transitivePeerDependencies: - supports-color - find-cache-dir@2.1.0: + finalhandler@1.3.1: dependencies: - commondir: 1.0.1 - make-dir: 2.1.0 - pkg-dir: 3.0.0 + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color find-cache-dir@3.3.2: dependencies: @@ -19656,18 +18905,14 @@ snapshots: make-dir: 3.1.0 pkg-dir: 4.2.0 - find-my-way@8.2.0: + find-my-way@9.0.1: dependencies: fast-deep-equal: 3.1.3 fast-querystring: 1.1.2 - safe-regex2: 3.1.0 + safe-regex2: 4.0.0 find-package-json@1.2.0: {} - find-up@3.0.0: - dependencies: - locate-path: 3.0.0 - find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -19698,16 +18943,16 @@ snapshots: flatted@3.3.1: {} - flow-parser@0.202.0: {} - fluent-ffmpeg@2.1.3: dependencies: async: 0.2.10 which: 1.3.1 - follow-redirects@1.15.2(debug@4.3.5): + follow-redirects@1.15.2: {} + + follow-redirects@1.15.9(debug@4.3.7): optionalDependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.7 for-each@0.3.3: dependencies: @@ -19793,11 +19038,18 @@ snapshots: function.prototype.name@1.1.5: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.0 es-abstract: 1.22.1 functions-have-names: 1.2.3 + function.prototype.name@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + functions-have-names: 1.2.3 + functions-have-names@1.2.3: {} gauge@3.0.2: @@ -19826,6 +19078,14 @@ snapshots: has-proto: 1.0.1 has-symbols: 1.0.3 + get-intrinsic@1.2.4: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.1 + has-symbols: 1.0.3 + hasown: 2.0.0 + get-package-type@0.1.0: {} get-pixels-frame-info-update@3.3.2: @@ -19859,8 +19119,14 @@ snapshots: get-symbol-description@1.0.0: dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + get-symbol-description@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 get-tsconfig@4.7.2: dependencies: @@ -19878,18 +19144,6 @@ snapshots: dependencies: readable-stream: 1.1.14 - giget@1.1.2: - dependencies: - colorette: 2.0.19 - defu: 6.1.4 - https-proxy-agent: 5.0.1 - mri: 1.2.0 - node-fetch-native: 1.0.2 - pathe: 1.1.2 - tar: 6.2.1 - transitivePeerDependencies: - - supports-color - github-slugger@2.0.0: {} glob-parent@5.1.2: @@ -19905,8 +19159,6 @@ snapshots: '@types/glob': 7.2.0 glob: 7.2.3 - glob-to-regexp@0.4.1: {} - glob@10.3.10: dependencies: foreground-child: 3.1.1 @@ -19915,15 +19167,6 @@ snapshots: minipass: 7.0.4 path-scurry: 1.10.1 - glob@10.4.2: - dependencies: - foreground-child: 3.1.1 - jackspeak: 3.4.0 - minimatch: 9.0.4 - minipass: 7.1.2 - package-json-from-dist: 1.0.0 - path-scurry: 1.11.1 - glob@11.0.0: dependencies: foreground-child: 3.1.1 @@ -19962,7 +19205,7 @@ snapshots: globals@14.0.0: {} - globals@15.8.0: {} + globals@15.9.0: {} globalthis@1.0.3: dependencies: @@ -19977,21 +19220,12 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 - globby@14.0.1: - dependencies: - '@sindresorhus/merge-streams': 2.3.0 - fast-glob: 3.3.2 - ignore: 5.3.1 - path-type: 5.0.0 - slash: 5.1.0 - unicorn-magic: 0.1.0 - google-protobuf@3.21.2: optional: true gopd@1.0.1: dependencies: - get-intrinsic: 1.2.1 + get-intrinsic: 1.2.4 got@11.8.5: dependencies: @@ -20045,15 +19279,6 @@ snapshots: hammerjs@2.0.8: {} - handlebars@4.7.7: - dependencies: - minimist: 1.2.8 - neo-async: 2.6.2 - source-map: 0.6.1 - wordwrap: 1.0.0 - optionalDependencies: - uglify-js: 3.17.4 - happy-dom@10.0.3: dependencies: css.escape: 1.5.1 @@ -20063,6 +19288,12 @@ snapshots: whatwg-encoding: 2.0.0 whatwg-mimetype: 3.0.0 + happy-dom@15.7.4: + dependencies: + entities: 4.5.0 + webidl-conversions: 7.0.0 + whatwg-mimetype: 3.0.0 + har-schema@2.0.0: {} har-validator@5.1.5: @@ -20082,14 +19313,24 @@ snapshots: dependencies: get-intrinsic: 1.2.1 + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.0 + has-proto@1.0.1: {} + has-proto@1.0.3: {} + has-symbols@1.0.3: {} has-tostringtag@1.0.0: dependencies: has-symbols: 1.0.3 + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.0.3 + has-unicode@2.0.1: optional: true @@ -20105,6 +19346,10 @@ snapshots: dependencies: function-bind: 1.1.2 + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + hast-util-heading-rank@3.0.0: dependencies: '@types/hast': 3.0.4 @@ -20123,7 +19368,7 @@ snapshots: highlight.js@10.7.3: {} - highlight.js@11.9.0: {} + highlight.js@11.10.0: {} hosted-git-info@2.8.9: {} @@ -20145,6 +19390,13 @@ snapshots: htmlescape@1.1.1: {} + htmlparser2@5.0.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 3.3.0 + domutils: 2.8.0 + entities: 2.2.0 + htmlparser2@8.0.1: dependencies: domelementtype: 2.3.0 @@ -20152,6 +19404,13 @@ snapshots: domutils: 3.0.1 entities: 4.5.0 + htmlparser2@9.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + entities: 4.5.0 + http-cache-semantics@4.1.1: {} http-errors@2.0.0: @@ -20177,11 +19436,11 @@ snapshots: jsprim: 1.4.2 sshpk: 1.17.0 - http-signature@1.3.6: + http-signature@1.4.0: dependencies: assert-plus: 1.0.0 jsprim: 2.0.2 - sshpk: 1.17.0 + sshpk: 1.18.0 http2-wrapper@1.0.3: dependencies: @@ -20209,15 +19468,9 @@ snapshots: debug: 4.3.5(supports-color@8.1.1) transitivePeerDependencies: - supports-color + optional: true https-proxy-agent@7.0.2: - dependencies: - agent-base: 7.1.0 - debug: 4.3.4(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - - https-proxy-agent@7.0.4: dependencies: agent-base: 7.1.0 debug: 4.3.5(supports-color@8.1.1) @@ -20239,7 +19492,7 @@ snapshots: human-signals@5.0.0: {} - human-signals@7.0.0: {} + human-signals@8.0.0: {} iconv-lite@0.4.24: dependencies: @@ -20319,6 +19572,12 @@ snapshots: has: 1.0.3 side-channel: 1.0.4 + internal-slot@1.0.7: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.0.6 + intersection-observer@0.12.2: {} ioredis@5.4.1: @@ -20342,7 +19601,7 @@ snapshots: jsbn: 1.1.0 sprintf-js: 1.1.3 - ip-cidr@4.0.1: + ip-cidr@4.0.2: dependencies: ip-address: 9.0.5 @@ -20369,6 +19628,11 @@ snapshots: get-intrinsic: 1.2.1 is-typed-array: 1.1.10 + is-array-buffer@3.0.4: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + is-arrayish@0.2.1: {} is-arrayish@0.3.2: {} @@ -20398,6 +19662,14 @@ snapshots: dependencies: hasown: 2.0.0 + is-core-module@2.15.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.1: + dependencies: + is-typed-array: 1.1.13 + is-date-object@1.0.5: dependencies: has-tostringtag: 1.0.0 @@ -20430,8 +19702,6 @@ snapshots: global-dirs: 3.0.1 is-path-inside: 3.0.3 - is-interactive@1.0.0: {} - is-ip@3.1.0: dependencies: ip-regex: 4.3.0 @@ -20440,13 +19710,10 @@ snapshots: is-map@2.0.2: {} - is-nan@1.3.2: - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - is-negative-zero@2.0.2: {} + is-negative-zero@2.0.3: {} + is-node-process@1.2.0: {} is-number-object@1.0.7: @@ -20461,10 +19728,6 @@ snapshots: is-plain-obj@4.1.0: {} - is-plain-object@2.0.4: - dependencies: - isobject: 3.0.1 - is-plain-object@5.0.0: {} is-potential-custom-element-name@1.0.1: {} @@ -20482,6 +19745,10 @@ snapshots: dependencies: call-bind: 1.0.2 + is-shared-array-buffer@1.0.3: + dependencies: + call-bind: 1.0.7 + is-stream@1.1.0: {} is-stream@2.0.1: {} @@ -20494,9 +19761,9 @@ snapshots: dependencies: has-tostringtag: 1.0.0 - is-svg@5.0.1: + is-svg@5.1.0: dependencies: - fast-xml-parser: 4.2.5 + fast-xml-parser: 4.5.0 is-symbol@1.0.4: dependencies: @@ -20510,6 +19777,10 @@ snapshots: gopd: 1.0.1 has-tostringtag: 1.0.0 + is-typed-array@1.1.13: + dependencies: + which-typed-array: 1.1.15 + is-typedarray@1.0.0: {} is-unicode-supported@0.1.0: {} @@ -20520,7 +19791,7 @@ snapshots: is-weakref@1.0.2: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 is-weakset@2.0.2: dependencies: @@ -20541,8 +19812,6 @@ snapshots: isexe@3.1.1: {} - isobject@3.0.1: {} - isstream@0.1.2: {} istanbul-lib-coverage@3.2.2: {} @@ -20602,12 +19871,6 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jackspeak@3.4.0: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - jackspeak@4.0.1: dependencies: '@isaacs/cliui': 8.0.2 @@ -20691,7 +19954,7 @@ snapshots: jest-runner: 29.7.0 jest-util: 29.7.0 jest-validate: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 parse-json: 5.2.0 pretty-format: 29.7.0 slash: 3.0.0 @@ -20750,7 +20013,7 @@ snapshots: jest-regex-util: 29.6.3 jest-util: 29.7.0 jest-worker: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 @@ -20774,7 +20037,7 @@ snapshots: '@types/stack-utils': 2.0.1 chalk: 4.1.2 graceful-fs: 4.2.11 - micromatch: 4.0.7 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 @@ -20884,7 +20147,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.5.4 + semver: 7.6.0 transitivePeerDependencies: - supports-color @@ -20951,6 +20214,14 @@ snapshots: '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 + joi@17.13.3: + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + jpeg-js@0.3.7: {} js-beautify@1.14.9: @@ -20981,32 +20252,36 @@ snapshots: jschardet@3.0.0: {} - jscodeshift@0.15.1(@babel/preset-env@7.24.7(@babel/core@7.24.7)): + jsdoc-type-pratt-parser@4.1.0: {} + + jsdom@24.1.1: dependencies: - '@babel/core': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/plugin-transform-class-properties': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-nullish-coalescing-operator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-optional-chaining': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-private-methods': 7.24.7(@babel/core@7.24.7) - '@babel/preset-flow': 7.23.3(@babel/core@7.24.7) - '@babel/preset-typescript': 7.23.3(@babel/core@7.24.7) - '@babel/register': 7.22.15(@babel/core@7.24.7) - babel-core: 7.0.0-bridge.0(@babel/core@7.24.7) - chalk: 4.1.2 - flow-parser: 0.202.0 - graceful-fs: 4.2.11 - micromatch: 4.0.7 - neo-async: 2.6.2 - node-dir: 0.1.17 - recast: 0.23.6 - temp: 0.8.4 - write-file-atomic: 2.4.3 - optionalDependencies: - '@babel/preset-env': 7.24.7(@babel/core@7.24.7) + cssstyle: 4.0.1 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.5 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.12 + parse5: 7.1.2 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4) + xml-name-validator: 5.0.0 transitivePeerDependencies: + - bufferutil - supports-color + - utf-8-validate + optional: true jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3): dependencies: @@ -21065,14 +20340,16 @@ snapshots: - utf-8-validate optional: true - jsesc@0.5.0: {} - jsesc@2.5.2: {} json-buffer@3.0.1: {} json-parse-even-better-errors@2.3.1: {} + json-schema-ref-resolver@1.0.1: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -21144,6 +20421,14 @@ snapshots: is-promise: 2.2.2 promise: 7.3.1 + juice@11.0.0: + dependencies: + cheerio: 1.0.0 + commander: 12.1.0 + mensch: 0.3.4 + slick: 1.12.2 + web-resource-inliner: 7.0.0 + just-extend@4.2.1: {} jwa@2.0.0: @@ -21177,12 +20462,6 @@ snapshots: lazy-ass@1.6.0: {} - lazy-universal-dotenv@4.0.0: - dependencies: - app-root-dir: 1.0.2 - dotenv: 16.0.3 - dotenv-expand: 10.0.0 - lazystream@1.0.1: dependencies: readable-stream: 2.3.7 @@ -21194,10 +20473,10 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - light-my-request@5.11.0: + light-my-request@6.0.0: dependencies: - cookie: 0.5.0 - process-warning: 2.2.0 + cookie: 0.6.0 + process-warning: 4.0.0 set-cookie-parser: 2.6.0 lilconfig@3.1.1: {} @@ -21222,11 +20501,6 @@ snapshots: mlly: 1.5.0 pkg-types: 1.0.3 - locate-path@3.0.0: - dependencies: - p-locate: 3.0.0 - path-exists: 3.0.0 - locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -21235,8 +20509,6 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash.debounce@4.0.8: {} - lodash.defaults@4.2.0: {} lodash.get@4.4.2: {} @@ -21277,6 +20549,10 @@ snapshots: dependencies: get-func-name: 2.0.2 + loupe@3.1.1: + dependencies: + get-func-name: 2.0.2 + lowercase-keys@2.0.0: {} lowercase-keys@3.0.0: {} @@ -21316,6 +20592,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 + magic-string@0.30.11: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + magicast@0.3.4: dependencies: '@babel/parser': 7.24.7 @@ -21324,11 +20604,6 @@ snapshots: mailcheck@1.1.1: {} - make-dir@2.1.0: - dependencies: - pify: 4.0.1 - semver: 5.7.1 - make-dir@3.1.0: dependencies: semver: 6.3.1 @@ -21480,7 +20755,7 @@ snapshots: media-typer@0.3.0: {} - meilisearch@0.41.0(encoding@0.1.13): + meilisearch@0.42.0(encoding@0.1.13): dependencies: cross-fetch: 3.1.6(encoding@0.1.13) transitivePeerDependencies: @@ -21490,6 +20765,8 @@ snapshots: dependencies: map-or-similar: 1.5.0 + mensch@0.3.4: {} + meow@9.0.0: dependencies: '@types/minimist': 1.2.2 @@ -21507,6 +20784,8 @@ snapshots: merge-descriptors@1.0.1: {} + merge-descriptors@1.0.3: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -21712,7 +20991,7 @@ snapshots: transitivePeerDependencies: - supports-color - micromatch@4.0.7: + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 @@ -21725,6 +21004,8 @@ snapshots: mime@1.6.0: {} + mime@2.6.0: {} + mime@3.0.0: {} mimic-fn@2.1.0: {} @@ -21842,7 +21123,7 @@ snapshots: pkg-types: 1.0.3 ufo: 1.3.2 - mnemonist@0.39.6: + mnemonist@0.39.8: dependencies: obliterator: 2.0.4 @@ -21850,8 +21131,6 @@ snapshots: module-details-from-path@1.0.3: {} - mri@1.2.0: {} - ms@2.0.0: {} ms@2.1.2: {} @@ -21876,12 +21155,12 @@ snapshots: optionalDependencies: msgpackr-extract: 3.0.2 - msw-storybook-addon@2.0.3(msw@2.3.4(typescript@5.5.4)): + msw-storybook-addon@2.0.3(msw@2.4.9(typescript@5.6.2)): dependencies: is-node-process: 1.2.0 - msw: 2.3.4(typescript@5.5.4) + msw: 2.4.9(typescript@5.6.2) - msw@2.3.4(typescript@5.5.4): + msw@2.3.4(typescript@5.6.2): dependencies: '@bundled-es-modules/cookie': 2.0.0 '@bundled-es-modules/statuses': 1.0.1 @@ -21901,7 +21180,29 @@ snapshots: type-fest: 4.20.1 yargs: 17.7.2 optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 + + msw@2.4.9(typescript@5.6.2): + dependencies: + '@bundled-es-modules/cookie': 2.0.0 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 3.1.6 + '@mswjs/interceptors': 0.35.8 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.4 + chalk: 4.1.2 + graphql: 16.8.1 + headers-polyfill: 4.0.2 + is-node-process: 1.2.0 + outvariant: 1.4.2 + path-to-regexp: 6.3.0 + strict-event-emitter: 0.5.1 + type-fest: 4.20.1 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.6.2 muggle-string@0.4.1: {} @@ -21966,8 +21267,6 @@ snapshots: negotiator@0.6.3: {} - neo-async@2.6.2: {} - nested-property@4.0.0: {} netmask@2.0.2: {} @@ -21997,14 +21296,8 @@ snapshots: node-bitmap@0.0.1: {} - node-dir@0.1.17: - dependencies: - minimatch: 3.1.2 - node-domexception@1.0.0: {} - node-fetch-native@1.0.2: {} - node-fetch@2.6.13(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 @@ -22029,7 +21322,7 @@ snapshots: node-gyp-build@4.6.0: optional: true - node-gyp@10.1.0: + node-gyp@10.2.0: dependencies: env-paths: 2.2.1 exponential-backoff: 3.1.1 @@ -22037,7 +21330,7 @@ snapshots: graceful-fs: 4.2.11 make-fetch-happen: 13.0.0 nopt: 7.2.0 - proc-log: 3.0.0 + proc-log: 4.2.0 semver: 7.6.0 tar: 6.2.1 which: 4.0.0 @@ -22048,7 +21341,7 @@ snapshots: node-releases@2.0.14: {} - nodemailer@6.9.14: {} + nodemailer@6.9.15: {} nodemon@3.0.2: dependencies: @@ -22063,10 +21356,10 @@ snapshots: touch: 3.1.0 undefsafe: 2.0.5 - nodemon@3.1.4: + nodemon@3.1.7: dependencies: chokidar: 3.5.3 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.5(supports-color@5.5.0) ignore-by-default: 1.0.1 minimatch: 3.1.2 pstree.remy: 1.1.8 @@ -22113,8 +21406,6 @@ snapshots: normalize-url@6.1.0: {} - normalize-url@8.0.0: {} - normalize-url@8.0.1: {} npm-run-path@2.0.2: @@ -22133,6 +21424,11 @@ snapshots: dependencies: path-key: 4.0.0 + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + npmlog@5.0.1: dependencies: are-we-there-yet: 2.0.0 @@ -22170,6 +21466,8 @@ snapshots: object-inspect@1.12.3: {} + object-inspect@1.13.2: {} + object-is@1.1.5: dependencies: call-bind: 1.0.2 @@ -22184,24 +21482,31 @@ snapshots: has-symbols: 1.0.3 object-keys: 1.1.1 - object.fromentries@2.0.7: + object.assign@4.1.5: dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 - object.groupby@1.0.1: + object.fromentries@2.0.8: dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 - get-intrinsic: 1.2.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 - object.values@1.1.7: + object.groupby@1.0.3: dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + + object.values@1.2.0: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 obliterator@2.0.4: {} @@ -22217,8 +21522,6 @@ snapshots: dependencies: ee-first: 1.1.1 - on-headers@1.0.2: {} - once@1.4.0: dependencies: wrappy: 1.0.2 @@ -22266,18 +21569,6 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - ora@5.4.1: - dependencies: - bl: 4.1.0 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-spinners: 2.9.2 - is-interactive: 1.0.0 - is-unicode-supported: 0.1.0 - log-symbols: 4.1.0 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - os-filter-obj@2.0.0: dependencies: arch: 2.2.0 @@ -22286,12 +21577,14 @@ snapshots: ospath@1.2.2: {} - otpauth@9.3.1: + otpauth@9.3.2: dependencies: '@noble/hashes': 1.4.0 outvariant@1.4.2: {} + outvariant@1.4.3: {} + p-cancelable@2.1.1: {} p-cancelable@3.0.0: {} @@ -22312,10 +21605,6 @@ snapshots: dependencies: yocto-queue: 1.0.0 - p-locate@3.0.0: - dependencies: - p-limit: 2.3.0 - p-locate@4.1.0: dependencies: p-limit: 2.3.0 @@ -22369,6 +21658,10 @@ snapshots: domhandler: 5.0.3 parse5: 7.1.2 + parse5-parser-stream@7.1.2: + dependencies: + parse5: 7.1.2 + parse5@5.1.1: {} parse5@6.0.1: {} @@ -22381,8 +21674,6 @@ snapshots: path-browserify@1.0.1: {} - path-exists@3.0.0: {} - path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -22400,42 +21691,41 @@ snapshots: lru-cache: 10.0.2 minipass: 7.0.4 - path-scurry@1.11.1: - dependencies: - lru-cache: 10.2.2 - minipass: 7.1.2 - path-scurry@2.0.0: dependencies: lru-cache: 11.0.0 minipass: 7.1.2 + path-to-regexp@0.1.10: {} + path-to-regexp@0.1.7: {} path-to-regexp@1.8.0: dependencies: isarray: 0.0.1 - path-to-regexp@3.2.0: {} + path-to-regexp@3.3.0: {} path-to-regexp@6.2.1: {} - path-type@4.0.0: {} + path-to-regexp@6.3.0: {} - path-type@5.0.0: {} + path-type@4.0.0: {} pathe@1.1.2: {} pathval@1.1.1: {} + pathval@2.0.0: {} + pause-stream@0.0.11: dependencies: through: 2.3.8 - peek-readable@5.0.0: {} - peek-readable@5.1.3: {} + peek-readable@5.2.0: {} + pend@1.2.0: {} performance-now@2.1.0: {} @@ -22443,18 +21733,20 @@ snapshots: pg-cloudflare@1.1.1: optional: true - pg-connection-string@2.6.4: {} + pg-connection-string@2.7.0: {} pg-int8@1.0.1: {} pg-numeric@1.0.2: {} - pg-pool@3.6.2(pg@8.12.0): + pg-pool@3.7.0(pg@8.13.0): dependencies: - pg: 8.12.0 + pg: 8.13.0 pg-protocol@1.6.1: {} + pg-protocol@1.7.0: {} + pg-types@2.2.0: dependencies: pg-int8: 1.0.1 @@ -22473,11 +21765,11 @@ snapshots: postgres-interval: 3.0.0 postgres-range: 1.1.3 - pg@8.12.0: + pg@8.13.0: dependencies: - pg-connection-string: 2.6.4 - pg-pool: 3.6.2(pg@8.12.0) - pg-protocol: 1.6.1 + pg-connection-string: 2.7.0 + pg-pool: 3.7.0(pg@8.13.0) + pg-protocol: 1.7.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: @@ -22493,6 +21785,8 @@ snapshots: picocolors@1.0.1: {} + picocolors@1.1.0: {} + picomatch@2.3.1: {} pid-port@1.0.0: @@ -22501,8 +21795,6 @@ snapshots: pify@2.3.0: {} - pify@4.0.1: {} - pino-abstract-transport@1.2.0: dependencies: readable-stream: 4.3.0 @@ -22532,18 +21824,10 @@ snapshots: pkce-challenge@4.1.0: {} - pkg-dir@3.0.0: - dependencies: - find-up: 3.0.0 - pkg-dir@4.2.0: dependencies: find-up: 4.1.0 - pkg-dir@5.0.0: - dependencies: - find-up: 5.0.0 - pkg-types@1.0.3: dependencies: jsonc-parser: 3.2.0 @@ -22568,177 +21852,174 @@ snapshots: dependencies: '@babel/runtime': 7.23.4 - postcss-calc@9.0.1(postcss@8.4.40): + possible-typed-array-names@1.0.0: {} + + postcss-calc@9.0.1(postcss@8.4.47): dependencies: - postcss: 8.4.40 - postcss-selector-parser: 6.0.15 + postcss: 8.4.47 + postcss-selector-parser: 6.0.16 postcss-value-parser: 4.2.0 - postcss-colormin@6.1.0(postcss@8.4.40): + postcss-colormin@6.1.0(postcss@8.4.47): dependencies: browserslist: 4.23.0 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.4.40 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-convert-values@6.1.0(postcss@8.4.40): + postcss-convert-values@6.1.0(postcss@8.4.47): dependencies: browserslist: 4.23.0 - postcss: 8.4.40 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-discard-comments@6.0.2(postcss@8.4.40): + postcss-discard-comments@6.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 - postcss-discard-duplicates@6.0.3(postcss@8.4.40): + postcss-discard-duplicates@6.0.3(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 - postcss-discard-empty@6.0.3(postcss@8.4.40): + postcss-discard-empty@6.0.3(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 - postcss-discard-overridden@6.0.2(postcss@8.4.40): + postcss-discard-overridden@6.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 - postcss-merge-longhand@6.0.5(postcss@8.4.40): + postcss-merge-longhand@6.0.5(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - stylehacks: 6.1.1(postcss@8.4.40) + stylehacks: 6.1.1(postcss@8.4.47) - postcss-merge-rules@6.1.1(postcss@8.4.40): + postcss-merge-rules@6.1.1(postcss@8.4.47): dependencies: browserslist: 4.23.0 caniuse-api: 3.0.0 - cssnano-utils: 4.0.2(postcss@8.4.40) - postcss: 8.4.40 + cssnano-utils: 4.0.2(postcss@8.4.47) + postcss: 8.4.47 postcss-selector-parser: 6.0.16 - postcss-minify-font-values@6.1.0(postcss@8.4.40): + postcss-minify-font-values@6.1.0(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-minify-gradients@6.0.3(postcss@8.4.40): + postcss-minify-gradients@6.0.3(postcss@8.4.47): dependencies: colord: 2.9.3 - cssnano-utils: 4.0.2(postcss@8.4.40) - postcss: 8.4.40 + cssnano-utils: 4.0.2(postcss@8.4.47) + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-minify-params@6.1.0(postcss@8.4.40): + postcss-minify-params@6.1.0(postcss@8.4.47): dependencies: browserslist: 4.23.0 - cssnano-utils: 4.0.2(postcss@8.4.40) - postcss: 8.4.40 + cssnano-utils: 4.0.2(postcss@8.4.47) + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-minify-selectors@6.0.4(postcss@8.4.40): + postcss-minify-selectors@6.0.4(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 postcss-selector-parser: 6.0.16 - postcss-normalize-charset@6.0.2(postcss@8.4.40): + postcss-normalize-charset@6.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 - postcss-normalize-display-values@6.0.2(postcss@8.4.40): + postcss-normalize-display-values@6.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-normalize-positions@6.0.2(postcss@8.4.40): + postcss-normalize-positions@6.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-normalize-repeat-style@6.0.2(postcss@8.4.40): + postcss-normalize-repeat-style@6.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-normalize-string@6.0.2(postcss@8.4.40): + postcss-normalize-string@6.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-normalize-timing-functions@6.0.2(postcss@8.4.40): + postcss-normalize-timing-functions@6.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-normalize-unicode@6.1.0(postcss@8.4.40): + postcss-normalize-unicode@6.1.0(postcss@8.4.47): dependencies: browserslist: 4.23.0 - postcss: 8.4.40 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-normalize-url@6.0.2(postcss@8.4.40): + postcss-normalize-url@6.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-normalize-whitespace@6.0.2(postcss@8.4.40): + postcss-normalize-whitespace@6.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-ordered-values@6.0.2(postcss@8.4.40): + postcss-ordered-values@6.0.2(postcss@8.4.47): dependencies: - cssnano-utils: 4.0.2(postcss@8.4.40) - postcss: 8.4.40 + cssnano-utils: 4.0.2(postcss@8.4.47) + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-reduce-initial@6.1.0(postcss@8.4.40): + postcss-reduce-initial@6.1.0(postcss@8.4.47): dependencies: browserslist: 4.23.0 caniuse-api: 3.0.0 - postcss: 8.4.40 + postcss: 8.4.47 - postcss-reduce-transforms@6.0.2(postcss@8.4.40): + postcss-reduce-transforms@6.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-selector-parser@6.0.15: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - postcss-selector-parser@6.0.16: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-svgo@6.0.3(postcss@8.4.40): + postcss-svgo@6.0.3(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 postcss-value-parser: 4.2.0 svgo: 3.2.0 - postcss-unique-selectors@6.0.4(postcss@8.4.40): + postcss-unique-selectors@6.0.4(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 postcss-selector-parser: 6.0.16 postcss-value-parser@4.2.0: {} - postcss@8.4.38: - dependencies: - nanoid: 3.3.7 - picocolors: 1.0.0 - source-map-js: 1.2.0 - postcss@8.4.40: dependencies: nanoid: 3.3.7 picocolors: 1.0.1 source-map-js: 1.2.0 + postcss@8.4.47: + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.0 + source-map-js: 1.2.1 + postgres-array@2.0.0: {} postgres-array@3.0.2: {} @@ -22779,8 +22060,6 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.2.0 - pretty-hrtime@1.0.3: {} - pretty-ms@9.0.0: dependencies: parse-ms: 4.0.0 @@ -22800,7 +22079,7 @@ snapshots: transitivePeerDependencies: - supports-color - proc-log@3.0.0: {} + proc-log@4.2.0: {} process-exists@5.0.0: dependencies: @@ -22808,10 +22087,10 @@ snapshots: process-nextick-args@2.0.1: {} - process-warning@2.2.0: {} - process-warning@3.0.0: {} + process-warning@4.0.0: {} + process@0.11.10: {} progress@2.0.3: @@ -22948,24 +22227,19 @@ snapshots: pvutils@1.1.3: {} - qrcode@1.5.3: + qrcode@1.5.4: dependencies: dijkstrajs: 1.0.2 - encode-utf8: 1.0.3 pngjs: 5.0.0 yargs: 15.4.1 - qs@6.10.4: - dependencies: - side-channel: 1.0.4 - qs@6.11.0: dependencies: - side-channel: 1.0.4 + side-channel: 1.0.6 - qs@6.11.1: + qs@6.13.0: dependencies: - side-channel: 1.0.4 + side-channel: 1.0.6 qs@6.5.3: {} @@ -22983,8 +22257,6 @@ snapshots: quick-lru@5.1.1: {} - ramda@0.29.0: {} - random-seed@0.3.0: dependencies: json-stringify-safe: 5.0.1 @@ -23000,15 +22272,22 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + raw-body@3.0.0: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + unpipe: 1.0.0 + rdf-canonize@3.4.0: dependencies: setimmediate: 1.0.5 - re2@1.21.3: + re2@1.21.4: dependencies: install-artifact-from-github: 1.3.5 nan: 2.20.0 - node-gyp: 10.1.0 + node-gyp: 10.2.0 transitivePeerDependencies: - supports-color @@ -23017,9 +22296,9 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-docgen-typescript@2.2.2(typescript@5.5.4): + react-docgen-typescript@2.2.2(typescript@5.6.2): dependencies: - typescript: 5.5.4 + typescript: 5.6.2 react-docgen@7.0.1: dependencies: @@ -23148,38 +22427,22 @@ snapshots: reflect-metadata@0.2.2: {} - regenerate-unicode-properties@10.1.0: - dependencies: - regenerate: 1.4.2 - - regenerate@1.4.2: {} - regenerator-runtime@0.13.11: {} regenerator-runtime@0.14.0: {} - regenerator-transform@0.15.2: - dependencies: - '@babel/runtime': 7.23.4 - regexp.prototype.flags@1.5.0: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 functions-have-names: 1.2.3 - regexpu-core@5.3.2: + regexp.prototype.flags@1.5.2: dependencies: - '@babel/regjsgen': 0.8.0 - regenerate: 1.4.2 - regenerate-unicode-properties: 10.1.0 - regjsparser: 0.9.1 - unicode-match-property-ecmascript: 2.0.0 - unicode-match-property-value-ecmascript: 2.1.0 - - regjsparser@0.9.1: - dependencies: - jsesc: 0.5.0 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-errors: 1.3.0 + set-function-name: 2.0.2 rehype-external-links@3.0.0: dependencies: @@ -23306,7 +22569,7 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - ret@0.4.3: {} + ret@0.5.0: {} retry@0.12.0: {} @@ -23314,9 +22577,7 @@ snapshots: rfdc@1.3.0: {} - rimraf@2.6.3: - dependencies: - glob: 7.2.3 + rfdc@1.4.1: {} rimraf@2.7.1: dependencies: @@ -23328,26 +22589,26 @@ snapshots: glob: 7.2.3 optional: true - rollup@4.19.1: + rollup@4.22.2: dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.19.1 - '@rollup/rollup-android-arm64': 4.19.1 - '@rollup/rollup-darwin-arm64': 4.19.1 - '@rollup/rollup-darwin-x64': 4.19.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.19.1 - '@rollup/rollup-linux-arm-musleabihf': 4.19.1 - '@rollup/rollup-linux-arm64-gnu': 4.19.1 - '@rollup/rollup-linux-arm64-musl': 4.19.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.19.1 - '@rollup/rollup-linux-riscv64-gnu': 4.19.1 - '@rollup/rollup-linux-s390x-gnu': 4.19.1 - '@rollup/rollup-linux-x64-gnu': 4.19.1 - '@rollup/rollup-linux-x64-musl': 4.19.1 - '@rollup/rollup-win32-arm64-msvc': 4.19.1 - '@rollup/rollup-win32-ia32-msvc': 4.19.1 - '@rollup/rollup-win32-x64-msvc': 4.19.1 + '@rollup/rollup-android-arm-eabi': 4.22.2 + '@rollup/rollup-android-arm64': 4.22.2 + '@rollup/rollup-darwin-arm64': 4.22.2 + '@rollup/rollup-darwin-x64': 4.22.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.22.2 + '@rollup/rollup-linux-arm-musleabihf': 4.22.2 + '@rollup/rollup-linux-arm64-gnu': 4.22.2 + '@rollup/rollup-linux-arm64-musl': 4.22.2 + '@rollup/rollup-linux-powerpc64le-gnu': 4.22.2 + '@rollup/rollup-linux-riscv64-gnu': 4.22.2 + '@rollup/rollup-linux-s390x-gnu': 4.22.2 + '@rollup/rollup-linux-x64-gnu': 4.22.2 + '@rollup/rollup-linux-x64-musl': 4.22.2 + '@rollup/rollup-win32-arm64-msvc': 4.22.2 + '@rollup/rollup-win32-ia32-msvc': 4.22.2 + '@rollup/rollup-win32-x64-msvc': 4.22.2 fsevents: 2.3.3 rrweb-cssom@0.6.0: {} @@ -23369,8 +22630,15 @@ snapshots: safe-array-concat@1.0.0: dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + isarray: 2.0.5 + + safe-array-concat@1.1.2: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 has-symbols: 1.0.3 isarray: 2.0.5 @@ -23380,13 +22648,19 @@ snapshots: safe-regex-test@1.0.0: dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 is-regex: 1.1.4 - safe-regex2@3.1.0: + safe-regex-test@1.0.3: dependencies: - ret: 0.4.3 + call-bind: 1.0.7 + es-errors: 1.3.0 + is-regex: 1.1.4 + + safe-regex2@4.0.0: + dependencies: + ret: 0.5.0 safe-stable-stringify@2.4.2: {} @@ -23399,9 +22673,9 @@ snapshots: htmlparser2: 8.0.1 is-plain-object: 5.0.0 parse-srcset: 1.0.2 - postcss: 8.4.38 + postcss: 8.4.40 - sass@1.77.8: + sass@1.79.3: dependencies: chokidar: 3.5.3 immutable: 4.2.2 @@ -23419,6 +22693,8 @@ snapshots: secure-json-parse@2.7.0: {} + secure-json-parse@3.0.0: {} + seedrandom@3.0.5: {} semver-regex@4.0.5: {} @@ -23439,6 +22715,8 @@ snapshots: dependencies: lru-cache: 6.0.0 + semver@7.6.3: {} + send@0.18.0: dependencies: debug: 2.6.9 @@ -23457,6 +22735,24 @@ snapshots: transitivePeerDependencies: - supports-color + send@0.19.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + serve-static@1.15.0: dependencies: encodeurl: 1.0.2 @@ -23466,10 +22762,35 @@ snapshots: transitivePeerDependencies: - supports-color + serve-static@1.16.2: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + set-blocking@2.0.0: {} set-cookie-parser@2.6.0: {} + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + setimmediate@1.0.5: {} setprototypeof@1.2.0: {} @@ -23479,35 +22800,31 @@ snapshots: inherits: 2.0.4 safe-buffer: 5.2.1 - shallow-clone@3.0.1: - dependencies: - kind-of: 6.0.3 - - sharp@0.33.4: + sharp@0.33.5: dependencies: color: 4.2.3 detect-libc: 2.0.3 - semver: 7.6.0 + semver: 7.6.3 optionalDependencies: - '@img/sharp-darwin-arm64': 0.33.4 - '@img/sharp-darwin-x64': 0.33.4 - '@img/sharp-libvips-darwin-arm64': 1.0.2 - '@img/sharp-libvips-darwin-x64': 1.0.2 - '@img/sharp-libvips-linux-arm': 1.0.2 - '@img/sharp-libvips-linux-arm64': 1.0.2 - '@img/sharp-libvips-linux-s390x': 1.0.2 - '@img/sharp-libvips-linux-x64': 1.0.2 - '@img/sharp-libvips-linuxmusl-arm64': 1.0.2 - '@img/sharp-libvips-linuxmusl-x64': 1.0.2 - '@img/sharp-linux-arm': 0.33.4 - '@img/sharp-linux-arm64': 0.33.4 - '@img/sharp-linux-s390x': 0.33.4 - '@img/sharp-linux-x64': 0.33.4 - '@img/sharp-linuxmusl-arm64': 0.33.4 - '@img/sharp-linuxmusl-x64': 0.33.4 - '@img/sharp-wasm32': 0.33.4 - '@img/sharp-win32-ia32': 0.33.4 - '@img/sharp-win32-x64': 0.33.4 + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 shebang-command@1.2.0: dependencies: @@ -23534,6 +22851,13 @@ snapshots: get-intrinsic: 1.2.1 object-inspect: 1.12.3 + side-channel@1.0.6: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.2 + siginfo@2.0.0: {} signal-exit@3.0.7: {} @@ -23555,7 +22879,7 @@ snapshots: simple-update-notifier@2.0.0: dependencies: - semver: 7.5.4 + semver: 7.6.0 sinon@16.1.3: dependencies: @@ -23625,8 +22949,6 @@ snapshots: slash@3.0.0: {} - slash@5.1.0: {} - slice-ansi@3.0.0: dependencies: ansi-styles: 4.3.0 @@ -23639,6 +22961,8 @@ snapshots: astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 + slick@1.12.2: {} + smart-buffer@4.2.0: {} socks-proxy-agent@8.0.2: @@ -23670,6 +22994,8 @@ snapshots: source-map-js@1.2.0: {} + source-map-js@1.2.1: {} + source-map-support@0.5.13: dependencies: buffer-from: 1.1.2 @@ -23722,6 +23048,18 @@ snapshots: safer-buffer: 2.1.2 tweetnacl: 0.14.5 + sshpk@1.18.0: + dependencies: + asn1: 0.2.6 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + ssri@10.0.4: dependencies: minipass: 5.0.0 @@ -23734,16 +23072,16 @@ snapshots: standard-as-callback@2.1.0: {} - start-server-and-test@2.0.4: + start-server-and-test@2.0.8: dependencies: arg: 5.0.2 bluebird: 3.7.2 check-more-types: 2.24.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.7 execa: 5.1.1 lazy-ass: 1.6.0 ps-tree: 1.2.0 - wait-on: 7.2.0(debug@4.3.5) + wait-on: 8.0.1(debug@4.3.7) transitivePeerDependencies: - supports-color @@ -23755,53 +23093,23 @@ snapshots: dependencies: internal-slot: 1.0.5 - store2@2.14.2: {} - - storybook-addon-misskey-theme@https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(3rvqj7p7l43ansgshs3zbslm7u): + storybook-addon-misskey-theme@https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@8.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/components@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/core-events@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/manager-api@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/preview-api@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/theming@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/types@8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@storybook/blocks': 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/components': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/core-events': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/manager-api': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/preview-api': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/theming': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/types': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/blocks': 8.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/components': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/core-events': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/manager-api': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/preview-api': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/theming': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/types': 8.3.2(storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)) optionalDependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4): + storybook@8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4): dependencies: - '@babel/core': 7.24.7 - '@babel/types': 7.24.7 - '@storybook/codemod': 8.2.6(bufferutil@4.0.8)(utf-8-validate@6.0.4) - '@storybook/core': 8.2.6(bufferutil@4.0.8)(utf-8-validate@6.0.4) - '@types/semver': 7.5.8 - '@yarnpkg/fslib': 2.10.3 - '@yarnpkg/libzip': 2.3.0 - chalk: 4.1.2 - commander: 6.2.1 - cross-spawn: 7.0.3 - detect-indent: 6.1.0 - envinfo: 7.8.1 - execa: 5.1.1 - fd-package-json: 1.2.0 - find-up: 5.0.0 - fs-extra: 11.1.1 - giget: 1.1.2 - globby: 14.0.1 - jscodeshift: 0.15.1(@babel/preset-env@7.24.7(@babel/core@7.24.7)) - leven: 3.1.0 - ora: 5.4.1 - prettier: 3.3.3 - prompts: 2.4.2 - semver: 7.6.0 - strip-json-comments: 3.1.1 - tempy: 3.1.0 - tiny-invariant: 1.3.3 - ts-dedent: 2.2.0 + '@storybook/core': 8.3.2(bufferutil@4.0.8)(utf-8-validate@6.0.4) transitivePeerDependencies: - - '@babel/preset-env' - bufferutil - supports-color - utf-8-validate @@ -23821,8 +23129,6 @@ snapshots: transitivePeerDependencies: - supports-color - stream-wormhole@1.1.0: {} - streamsearch@1.1.0: {} streamx@2.15.0: @@ -23855,22 +23161,41 @@ snapshots: string.prototype.trim@1.2.7: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.0 es-abstract: 1.22.1 + string.prototype.trim@1.2.9: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + string.prototype.trimend@1.0.6: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.0 es-abstract: 1.22.1 + string.prototype.trimend@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + string.prototype.trimstart@1.0.6: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.0 es-abstract: 1.22.1 + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + string_decoder@0.10.31: {} string_decoder@1.1.1: @@ -23924,19 +23249,19 @@ snapshots: strnum@1.0.5: {} strtok3@7.0.0: - dependencies: - '@tokenizer/token': 0.3.0 - peek-readable: 5.0.0 - - strtok3@8.0.1: dependencies: '@tokenizer/token': 0.3.0 peek-readable: 5.1.3 - stylehacks@6.1.1(postcss@8.4.40): + strtok3@8.1.0: + dependencies: + '@tokenizer/token': 0.3.0 + peek-readable: 5.2.0 + + stylehacks@6.1.1(postcss@8.4.47): dependencies: browserslist: 4.23.0 - postcss: 8.4.40 + postcss: 8.4.47 postcss-selector-parser: 6.0.16 supports-color@5.5.0: @@ -23968,11 +23293,11 @@ snapshots: css-tree: 2.3.1 css-what: 6.1.0 csso: 5.0.5 - picocolors: 1.0.0 + picocolors: 1.0.1 symbol-tree@3.2.4: {} - systeminformation@5.22.11: {} + systeminformation@5.23.5: {} tar-stream@3.1.6: dependencies: @@ -24008,20 +23333,7 @@ snapshots: dependencies: memoizerific: 1.11.3 - temp-dir@3.0.0: {} - - temp@0.8.4: - dependencies: - rimraf: 2.6.3 - - tempy@3.1.0: - dependencies: - is-stream: 3.0.0 - temp-dir: 3.0.0 - type-fest: 2.19.0 - unique-string: 3.0.0 - - terser@5.31.3: + terser@5.33.0: dependencies: '@jridgewell/source-map': 0.3.6 acorn: 8.12.1 @@ -24050,7 +23362,7 @@ snapshots: dependencies: real-require: 0.2.0 - three@0.167.0: {} + three@0.168.0: {} throttle-debounce@5.0.2: {} @@ -24062,16 +23374,18 @@ snapshots: tiny-invariant@1.3.3: {} - tiny-lru@10.0.1: {} - tinybench@2.6.0: {} tinycolor2@1.6.0: {} tinypool@0.8.4: {} + tinyrainbow@1.2.0: {} + tinyspy@2.2.0: {} + tinyspy@3.0.2: {} + tmp@0.2.3: {} tmpl@1.0.5: {} @@ -24140,7 +23454,11 @@ snapshots: dependencies: typescript: 5.5.4 - ts-case-convert@2.0.2: {} + ts-api-utils@1.3.0(typescript@5.6.2): + dependencies: + typescript: 5.6.2 + + ts-case-convert@2.0.7: {} ts-dedent@2.2.0: {} @@ -24168,7 +23486,7 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 - tsd@0.31.1: + tsd@0.31.2: dependencies: '@tsd/typescript': 5.4.5 eslint-formatter-pretty: 4.1.0 @@ -24178,12 +23496,12 @@ snapshots: path-exists: 4.0.0 read-pkg-up: 7.0.1 - tslib@1.14.1: {} - tslib@2.6.2: {} tslib@2.6.3: {} + tslib@2.7.0: {} + tsx@4.4.0: dependencies: esbuild: 0.18.20 @@ -24213,8 +23531,6 @@ snapshots: type-fest@0.8.1: {} - type-fest@1.4.0: {} - type-fest@2.19.0: {} type-fest@4.20.1: {} @@ -24226,34 +23542,66 @@ snapshots: typed-array-buffer@1.0.0: dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 is-typed-array: 1.1.10 + typed-array-buffer@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-typed-array: 1.1.13 + typed-array-byte-length@1.0.0: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 for-each: 0.3.3 has-proto: 1.0.1 is-typed-array: 1.1.10 + typed-array-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + typed-array-byte-offset@1.0.0: dependencies: available-typed-arrays: 1.0.5 - call-bind: 1.0.2 + call-bind: 1.0.7 for-each: 0.3.3 has-proto: 1.0.1 is-typed-array: 1.1.10 + typed-array-byte-offset@1.0.2: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + typed-array-length@1.0.4: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 for-each: 0.3.3 is-typed-array: 1.1.10 + typed-array-length@1.0.6: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + possible-typed-array-names: 1.0.0 + typedarray@0.0.6: {} - typeorm@0.3.20(ioredis@5.4.1)(pg@8.12.0): + typeorm@0.3.20(ioredis@5.4.1)(pg@8.13.0): dependencies: '@sqltools/formatter': 1.2.5 app-root-path: 3.1.0 @@ -24272,7 +23620,7 @@ snapshots: yargs: 17.7.2 optionalDependencies: ioredis: 5.4.1 - pg: 8.12.0 + pg: 8.13.0 transitivePeerDependencies: - supports-color @@ -24282,10 +23630,9 @@ snapshots: typescript@5.5.4: {} - ufo@1.3.2: {} + typescript@5.6.2: {} - uglify-js@3.17.4: - optional: true + ufo@1.3.2: {} uid2@0.0.4: {} @@ -24299,7 +23646,7 @@ snapshots: unbox-primitive@1.0.2: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 has-bigints: 1.0.2 has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 @@ -24308,22 +23655,15 @@ snapshots: undici-types@5.26.5: {} + undici-types@6.19.8: {} + undici@5.28.2: dependencies: '@fastify/busboy': 2.1.0 - unicode-canonical-property-names-ecmascript@2.0.0: {} + undici@6.19.8: {} - unicode-match-property-ecmascript@2.0.0: - dependencies: - unicode-canonical-property-names-ecmascript: 2.0.0 - unicode-property-aliases-ecmascript: 2.1.0 - - unicode-match-property-value-ecmascript@2.1.0: {} - - unicode-property-aliases-ecmascript@2.1.0: {} - - unicorn-magic@0.1.0: {} + unicorn-magic@0.3.0: {} unified@11.0.4: dependencies: @@ -24345,10 +23685,6 @@ snapshots: dependencies: imurmurhash: 0.1.4 - unique-string@3.0.0: - dependencies: - crypto-random-string: 4.0.0 - unist-util-is@6.0.0: dependencies: '@types/unist': 3.0.2 @@ -24391,13 +23727,13 @@ snapshots: dependencies: browserslist: 4.22.2 escalade: 3.1.1 - picocolors: 1.0.0 + picocolors: 1.0.1 update-browserslist-db@1.0.13(browserslist@4.23.0): dependencies: browserslist: 4.23.0 escalade: 3.1.1 - picocolors: 1.0.0 + picocolors: 1.0.1 uri-js@4.4.1: dependencies: @@ -24438,21 +23774,22 @@ snapshots: uuid@9.0.1: {} - v-code-diff@1.12.0(vue@3.4.37(typescript@5.5.4)): + v-code-diff@1.13.1(vue@3.5.7(typescript@5.6.2)): dependencies: - diff: 5.1.0 + diff: 5.2.0 diff-match-patch: 1.0.5 - highlight.js: 11.9.0 - vue: 3.4.37(typescript@5.5.4) - vue-demi: 0.14.7(vue@3.4.37(typescript@5.5.4)) - vue-i18n: 9.13.1(vue@3.4.37(typescript@5.5.4)) + highlight.js: 11.10.0 + vue: 3.5.7(typescript@5.6.2) + vue-demi: 0.14.7(vue@3.5.7(typescript@5.6.2)) v8-to-istanbul@9.2.0: dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 '@types/istanbul-lib-coverage': 2.0.4 convert-source-map: 2.0.0 + valid-data-url@3.0.1: {} + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.1.1 @@ -24477,18 +23814,19 @@ snapshots: unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 - vite-node@1.6.0(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3): + vite-node@1.6.0(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0): dependencies: cac: 6.7.14 debug: 4.3.5(supports-color@8.1.1) pathe: 1.1.2 - picocolors: 1.0.0 - vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3) + picocolors: 1.0.1 + vite: 5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) transitivePeerDependencies: - '@types/node' - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color @@ -24496,25 +23834,25 @@ snapshots: vite-plugin-turbosnap@1.0.3: {} - vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3): + vite@5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0): dependencies: esbuild: 0.21.5 - postcss: 8.4.40 - rollup: 4.19.1 + postcss: 8.4.47 + rollup: 4.22.2 optionalDependencies: '@types/node': 20.14.12 fsevents: 2.3.3 - sass: 1.77.8 - terser: 5.31.3 + sass: 1.79.3 + terser: 5.33.0 - vitest-fetch-mock@0.2.2(encoding@0.1.13)(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3)): + vitest-fetch-mock@0.2.2(encoding@0.1.13)(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0)): dependencies: cross-fetch: 3.1.6(encoding@0.1.13) - vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3) + vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0) transitivePeerDependencies: - encoding - vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3): + vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -24533,8 +23871,8 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.6.0 tinypool: 0.8.4 - vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3) - vite-node: 1.6.0(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3) + vite: 5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) + vite-node: 1.6.0(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) why-is-node-running: 2.2.2 optionalDependencies: '@types/node': 20.14.12 @@ -24544,6 +23882,43 @@ snapshots: - less - lightningcss - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.3)(terser@5.33.0): + dependencies: + '@vitest/expect': 1.6.0 + '@vitest/runner': 1.6.0 + '@vitest/snapshot': 1.6.0 + '@vitest/spy': 1.6.0 + '@vitest/utils': 1.6.0 + acorn-walk: 8.3.2 + chai: 4.3.10 + debug: 4.3.4(supports-color@5.5.0) + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.10 + pathe: 1.1.2 + picocolors: 1.0.0 + std-env: 3.7.0 + strip-literal: 2.1.0 + tinybench: 2.6.0 + tinypool: 0.8.4 + vite: 5.4.7(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) + vite-node: 1.6.0(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) + why-is-node-running: 2.2.2 + optionalDependencies: + '@types/node': 20.14.12 + happy-dom: 10.0.3 + jsdom: 24.1.1 + transitivePeerDependencies: + - less + - lightningcss + - sass + - sass-embedded - stylus - sugarss - supports-color @@ -24574,75 +23949,68 @@ snapshots: vscode-uri@3.0.8: {} - vue-component-meta@2.0.16(typescript@5.5.4): + vue-component-meta@2.0.16(typescript@5.6.2): dependencies: '@volar/typescript': 2.2.0 - '@vue/language-core': 2.0.16(typescript@5.5.4) + '@vue/language-core': 2.0.16(typescript@5.6.2) path-browserify: 1.0.1 vue-component-type-helpers: 2.0.16 optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 vue-component-type-helpers@1.8.4: {} vue-component-type-helpers@2.0.16: {} - vue-component-type-helpers@2.0.29: {} + vue-component-type-helpers@2.1.6: {} - vue-demi@0.14.7(vue@3.4.37(typescript@5.5.4)): + vue-demi@0.14.7(vue@3.5.7(typescript@5.6.2)): dependencies: - vue: 3.4.37(typescript@5.5.4) + vue: 3.5.7(typescript@5.6.2) - vue-docgen-api@4.75.1(vue@3.4.37(typescript@5.5.4)): + vue-docgen-api@4.75.1(vue@3.5.7(typescript@5.6.2)): dependencies: '@babel/parser': 7.24.7 '@babel/types': 7.24.7 - '@vue/compiler-dom': 3.4.34 - '@vue/compiler-sfc': 3.4.37 + '@vue/compiler-dom': 3.4.37 + '@vue/compiler-sfc': 3.5.7 ast-types: 0.16.1 hash-sum: 2.0.0 lru-cache: 8.0.4 pug: 3.0.3 recast: 0.23.6 ts-map: 1.0.3 - vue: 3.4.37(typescript@5.5.4) - vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.4.37(typescript@5.5.4)) + vue: 3.5.7(typescript@5.6.2) + vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.5.7(typescript@5.6.2)) - vue-eslint-parser@9.4.3(eslint@9.8.0): + vue-eslint-parser@9.4.3(eslint@9.11.0): dependencies: - debug: 4.3.4(supports-color@5.5.0) - eslint: 9.8.0 + debug: 4.3.5(supports-color@8.1.1) + eslint: 9.11.0 eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 - esquery: 1.4.2 + esquery: 1.6.0 lodash: 4.17.21 semver: 7.6.0 transitivePeerDependencies: - supports-color - vue-i18n@9.13.1(vue@3.4.37(typescript@5.5.4)): + vue-inbrowser-compiler-independent-utils@4.71.1(vue@3.5.7(typescript@5.6.2)): dependencies: - '@intlify/core-base': 9.13.1 - '@intlify/shared': 9.13.1 - '@vue/devtools-api': 6.6.1 - vue: 3.4.37(typescript@5.5.4) - - vue-inbrowser-compiler-independent-utils@4.71.1(vue@3.4.37(typescript@5.5.4)): - dependencies: - vue: 3.4.37(typescript@5.5.4) + vue: 3.5.7(typescript@5.6.2) vue-template-compiler@2.7.14: dependencies: de-indent: 1.0.2 he: 1.2.0 - vue-tsc@2.0.29(typescript@5.5.4): + vue-tsc@2.1.6(typescript@5.6.2): dependencies: - '@volar/typescript': 2.4.0-alpha.18 - '@vue/language-core': 2.0.29(typescript@5.5.4) + '@volar/typescript': 2.4.5 + '@vue/language-core': 2.1.6(typescript@5.6.2) semver: 7.6.0 - typescript: 5.5.4 + typescript: 5.6.2 vue@3.4.37(typescript@5.5.4): dependencies: @@ -24654,40 +24022,39 @@ snapshots: optionalDependencies: typescript: 5.5.4 - vuedraggable@4.1.0(vue@3.4.37(typescript@5.5.4)): + vue@3.5.7(typescript@5.6.2): + dependencies: + '@vue/compiler-dom': 3.5.7 + '@vue/compiler-sfc': 3.5.7 + '@vue/runtime-dom': 3.5.7 + '@vue/server-renderer': 3.5.7(vue@3.5.7(typescript@5.6.2)) + '@vue/shared': 3.5.7 + optionalDependencies: + typescript: 5.6.2 + + vuedraggable@4.1.0(vue@3.5.7(typescript@5.6.2)): dependencies: sortablejs: 1.14.0 - vue: 3.4.37(typescript@5.5.4) + vue: 3.5.7(typescript@5.6.2) w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 - wait-on@7.2.0(debug@4.3.5): + wait-on@8.0.1(debug@4.3.7): dependencies: - axios: 1.6.2(debug@4.3.5) - joi: 17.11.0 + axios: 1.7.7(debug@4.3.7) + joi: 17.13.3 lodash: 4.17.21 minimist: 1.2.8 rxjs: 7.8.1 transitivePeerDependencies: - debug - walk-up-path@3.0.1: {} - walker@1.0.8: dependencies: makeerror: 1.0.12 - watchpack@2.4.0: - dependencies: - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - - wcwidth@1.0.1: - dependencies: - defaults: 1.0.4 - web-push@3.6.7: dependencies: asn1.js: 5.4.1 @@ -24698,6 +24065,14 @@ snapshots: transitivePeerDependencies: - supports-color + web-resource-inliner@7.0.0: + dependencies: + ansi-colors: 4.1.3 + escape-goat: 3.0.0 + htmlparser2: 5.0.1 + mime: 2.6.0 + valid-data-url: 3.0.1 + web-streams-polyfill@3.2.1: {} web-streams-polyfill@4.0.0: @@ -24758,6 +24133,14 @@ snapshots: gopd: 1.0.1 has-tostringtag: 1.0.0 + which-typed-array@1.1.15: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.2 + which@1.3.1: dependencies: isexe: 2.0.0 @@ -24789,8 +24172,6 @@ snapshots: word-wrap@1.2.5: {} - wordwrap@1.0.0: {} - wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 @@ -24811,12 +24192,6 @@ snapshots: wrappy@1.0.2: {} - write-file-atomic@2.4.3: - dependencies: - graceful-fs: 4.2.11 - imurmurhash: 0.1.4 - signal-exit: 3.0.7 - write-file-atomic@4.0.2: dependencies: imurmurhash: 0.1.4 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 193669e7a4..d222614eda 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,8 @@ packages: - 'packages/backend' + - 'packages/frontend-shared' - 'packages/frontend' + - 'packages/frontend-embed' - 'packages/sw' - 'packages/misskey-js' - 'packages/misskey-js/generator' diff --git a/scripts/build-assets.mjs b/scripts/build-assets.mjs index 2b275e12d6..421d4a6d1b 100644 --- a/scripts/build-assets.mjs +++ b/scripts/build-assets.mjs @@ -58,6 +58,7 @@ async function buildBackendScript() { for (const file of [ './packages/backend/src/server/web/boot.js', + './packages/backend/src/server/web/boot.embed.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js' ]) { @@ -73,6 +74,7 @@ async function buildBackendStyle() { for (const file of [ './packages/backend/src/server/web/style.css', + './packages/backend/src/server/web/style.embed.css', './packages/backend/src/server/web/bios.css', './packages/backend/src/server/web/cli.css', './packages/backend/src/server/web/error.css' diff --git a/scripts/changelog-checker/package-lock.json b/scripts/changelog-checker/package-lock.json index 6ad3273e60..b7ec909abe 100644 --- a/scripts/changelog-checker/package-lock.json +++ b/scripts/changelog-checker/package-lock.json @@ -16,7 +16,7 @@ "remark-parse": "11.0.0", "typescript": "5.3.3", "unified": "11.0.4", - "vite": "5.0.12", + "vite": "5.4.6", "vite-node": "1.1.3", "vitest": "1.1.3" } @@ -85,9 +85,9 @@ "dev": true }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", - "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -101,9 +101,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", - "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -117,9 +117,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", - "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -133,9 +133,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", - "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -149,9 +149,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", - "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -165,9 +165,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", - "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -181,9 +181,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", - "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -197,9 +197,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", - "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -213,9 +213,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", - "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -229,9 +229,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", - "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -245,9 +245,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", - "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -261,9 +261,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", - "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -277,9 +277,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", - "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -293,9 +293,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", - "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -309,9 +309,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", - "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -325,9 +325,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", - "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -341,9 +341,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", - "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -357,9 +357,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", - "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -373,9 +373,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", - "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -389,9 +389,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", - "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -405,9 +405,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", - "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -421,9 +421,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", - "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -437,9 +437,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", - "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -522,9 +522,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.4.tgz", - "integrity": "sha512-ub/SN3yWqIv5CWiAZPHVS1DloyZsJbtXmX4HxUTIpS0BHm9pW5iYBo2mIZi+hE3AeiTzHz33blwSnhdUo+9NpA==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz", + "integrity": "sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==", "cpu": [ "arm" ], @@ -535,9 +535,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.4.tgz", - "integrity": "sha512-ehcBrOR5XTl0W0t2WxfTyHCR/3Cq2jfb+I4W+Ch8Y9b5G+vbAecVv0Fx/J1QKktOrgUYsIKxWAKgIpvw56IFNA==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz", + "integrity": "sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==", "cpu": [ "arm64" ], @@ -548,9 +548,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.4.tgz", - "integrity": "sha512-1fzh1lWExwSTWy8vJPnNbNM02WZDS8AW3McEOb7wW+nPChLKf3WG2aG7fhaUmfX5FKw9zhsF5+MBwArGyNM7NA==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz", + "integrity": "sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==", "cpu": [ "arm64" ], @@ -561,9 +561,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.4.tgz", - "integrity": "sha512-Gc6cukkF38RcYQ6uPdiXi70JB0f29CwcQ7+r4QpfNpQFVHXRd0DfWFidoGxjSx1DwOETM97JPz1RXL5ISSB0pA==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz", + "integrity": "sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==", "cpu": [ "x64" ], @@ -574,9 +574,22 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.4.tgz", - "integrity": "sha512-g21RTeFzoTl8GxosHbnQZ0/JkuFIB13C3T7Y0HtKzOXmoHhewLbVTFBQZu+z5m9STH6FZ7L/oPgU4Nm5ErN2fw==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz", + "integrity": "sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz", + "integrity": "sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==", "cpu": [ "arm" ], @@ -587,9 +600,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.4.tgz", - "integrity": "sha512-TVYVWD/SYwWzGGnbfTkrNpdE4HON46orgMNHCivlXmlsSGQOx/OHHYiQcMIOx38/GWgwr/po2LBn7wypkWw/Mg==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz", + "integrity": "sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==", "cpu": [ "arm64" ], @@ -600,9 +613,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.4.tgz", - "integrity": "sha512-XcKvuendwizYYhFxpvQ3xVpzje2HHImzg33wL9zvxtj77HvPStbSGI9czrdbfrf8DGMcNNReH9pVZv8qejAQ5A==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz", + "integrity": "sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==", "cpu": [ "arm64" ], @@ -612,10 +625,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz", + "integrity": "sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.4.tgz", - "integrity": "sha512-LFHS/8Q+I9YA0yVETyjonMJ3UA+DczeBd/MqNEzsGSTdNvSJa1OJZcSH8GiXLvcizgp9AlHs2walqRcqzjOi3A==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz", + "integrity": "sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==", "cpu": [ "riscv64" ], @@ -625,10 +651,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz", + "integrity": "sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.4.tgz", - "integrity": "sha512-dIYgo+j1+yfy81i0YVU5KnQrIJZE8ERomx17ReU4GREjGtDW4X+nvkBak2xAUpyqLs4eleDSj3RrV72fQos7zw==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz", + "integrity": "sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==", "cpu": [ "x64" ], @@ -639,9 +678,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.4.tgz", - "integrity": "sha512-RoaYxjdHQ5TPjaPrLsfKqR3pakMr3JGqZ+jZM0zP2IkDtsGa4CqYaWSfQmZVgFUCgLrTnzX+cnHS3nfl+kB6ZQ==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz", + "integrity": "sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==", "cpu": [ "x64" ], @@ -652,9 +691,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.4.tgz", - "integrity": "sha512-T8Q3XHV+Jjf5e49B4EAaLKV74BbX7/qYBRQ8Wop/+TyyU0k+vSjiLVSHNWdVd1goMjZcbhDmYZUYW5RFqkBNHQ==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz", + "integrity": "sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==", "cpu": [ "arm64" ], @@ -665,9 +704,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.4.tgz", - "integrity": "sha512-z+JQ7JirDUHAsMecVydnBPWLwJjbppU+7LZjffGf+Jvrxq+dVjIE7By163Sc9DKc3ADSU50qPVw0KonBS+a+HQ==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz", + "integrity": "sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==", "cpu": [ "ia32" ], @@ -678,9 +717,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.4.tgz", - "integrity": "sha512-LfdGXCV9rdEify1oxlN9eamvDSjv9md9ZVMAbNHA87xqIfFCxImxan9qZ8+Un54iK2nnqPlbnSi4R54ONtbWBw==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz", + "integrity": "sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==", "cpu": [ "x64" ], @@ -1060,9 +1099,9 @@ } }, "node_modules/esbuild": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", - "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -1072,29 +1111,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.11", - "@esbuild/android-arm": "0.19.11", - "@esbuild/android-arm64": "0.19.11", - "@esbuild/android-x64": "0.19.11", - "@esbuild/darwin-arm64": "0.19.11", - "@esbuild/darwin-x64": "0.19.11", - "@esbuild/freebsd-arm64": "0.19.11", - "@esbuild/freebsd-x64": "0.19.11", - "@esbuild/linux-arm": "0.19.11", - "@esbuild/linux-arm64": "0.19.11", - "@esbuild/linux-ia32": "0.19.11", - "@esbuild/linux-loong64": "0.19.11", - "@esbuild/linux-mips64el": "0.19.11", - "@esbuild/linux-ppc64": "0.19.11", - "@esbuild/linux-riscv64": "0.19.11", - "@esbuild/linux-s390x": "0.19.11", - "@esbuild/linux-x64": "0.19.11", - "@esbuild/netbsd-x64": "0.19.11", - "@esbuild/openbsd-x64": "0.19.11", - "@esbuild/sunos-x64": "0.19.11", - "@esbuild/win32-arm64": "0.19.11", - "@esbuild/win32-ia32": "0.19.11", - "@esbuild/win32-x64": "0.19.11" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/estree-walker": { @@ -2086,9 +2125,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "dev": true }, "node_modules/pkg-types": { @@ -2103,9 +2142,9 @@ } }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -2123,8 +2162,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -2198,9 +2237,9 @@ } }, "node_modules/rollup": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.4.tgz", - "integrity": "sha512-2ztU7pY/lrQyXSCnnoU4ICjT/tCG9cdH3/G25ERqE3Lst6vl2BCM5hL2Nw+sslAvAf+ccKsAq1SkKQALyqhR7g==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.3.tgz", + "integrity": "sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -2213,19 +2252,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.4", - "@rollup/rollup-android-arm64": "4.9.4", - "@rollup/rollup-darwin-arm64": "4.9.4", - "@rollup/rollup-darwin-x64": "4.9.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.4", - "@rollup/rollup-linux-arm64-gnu": "4.9.4", - "@rollup/rollup-linux-arm64-musl": "4.9.4", - "@rollup/rollup-linux-riscv64-gnu": "4.9.4", - "@rollup/rollup-linux-x64-gnu": "4.9.4", - "@rollup/rollup-linux-x64-musl": "4.9.4", - "@rollup/rollup-win32-arm64-msvc": "4.9.4", - "@rollup/rollup-win32-ia32-msvc": "4.9.4", - "@rollup/rollup-win32-x64-msvc": "4.9.4", + "@rollup/rollup-android-arm-eabi": "4.21.3", + "@rollup/rollup-android-arm64": "4.21.3", + "@rollup/rollup-darwin-arm64": "4.21.3", + "@rollup/rollup-darwin-x64": "4.21.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.21.3", + "@rollup/rollup-linux-arm-musleabihf": "4.21.3", + "@rollup/rollup-linux-arm64-gnu": "4.21.3", + "@rollup/rollup-linux-arm64-musl": "4.21.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.21.3", + "@rollup/rollup-linux-riscv64-gnu": "4.21.3", + "@rollup/rollup-linux-s390x-gnu": "4.21.3", + "@rollup/rollup-linux-x64-gnu": "4.21.3", + "@rollup/rollup-linux-x64-musl": "4.21.3", + "@rollup/rollup-win32-arm64-msvc": "4.21.3", + "@rollup/rollup-win32-ia32-msvc": "4.21.3", + "@rollup/rollup-win32-x64-msvc": "4.21.3", "fsevents": "~2.3.2" } }, @@ -2293,9 +2335,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -2558,14 +2600,14 @@ } }, "node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", + "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", "dev": true, "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.32", - "rollup": "^4.2.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -2584,6 +2626,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -2601,6 +2644,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/scripts/changelog-checker/package.json b/scripts/changelog-checker/package.json index 8b3c9843b7..dccb47d037 100644 --- a/scripts/changelog-checker/package.json +++ b/scripts/changelog-checker/package.json @@ -17,7 +17,7 @@ "remark-parse": "11.0.0", "typescript": "5.3.3", "unified": "11.0.4", - "vite": "5.0.12", + "vite": "5.4.6", "vite-node": "1.1.3", "vitest": "1.1.3" } diff --git a/scripts/clean-all.js b/scripts/clean-all.js index e9512e2d5a..dc391ecfd8 100644 --- a/scripts/clean-all.js +++ b/scripts/clean-all.js @@ -10,9 +10,15 @@ const fs = require('fs'); fs.rmSync(__dirname + '/../packages/backend/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/backend/node_modules', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/frontend-shared/built', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/frontend-shared/node_modules', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/frontend/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/frontend/node_modules', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/frontend-embed/built', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/frontend-embed/node_modules', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/sw/node_modules', { recursive: true, force: true }); diff --git a/scripts/clean.js b/scripts/clean.js index af66c24a8f..86c19281ea 100644 --- a/scripts/clean.js +++ b/scripts/clean.js @@ -7,7 +7,9 @@ const fs = require('fs'); (async () => { fs.rmSync(__dirname + '/../packages/backend/built', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/frontend-shared/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/frontend/built', { recursive: true, force: true }); + fs.rmSync(__dirname + '/../packages/frontend-embed/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/misskey-js/built', { recursive: true, force: true }); fs.rmSync(__dirname + '/../packages/misskey-reversi/built', { recursive: true, force: true }); diff --git a/scripts/dev.mjs b/scripts/dev.mjs index bbb2547758..a4c82d46e1 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -65,12 +65,24 @@ execa('pnpm', ['--filter', 'backend', 'dev'], { stderr: process.stderr, }); +execa('pnpm', ['--filter', 'frontend-shared', 'watch'], { + cwd: _dirname + '/../', + stdout: process.stdout, + stderr: process.stderr, +}); + execa('pnpm', ['--filter', 'frontend', process.env.MK_DEV_PREFER === 'backend' ? 'watch' : 'dev'], { cwd: _dirname + '/../', stdout: process.stdout, stderr: process.stderr, }); +execa('pnpm', ['--filter', 'frontend-embed', process.env.MK_DEV_PREFER === 'backend' ? 'watch' : 'dev'], { + cwd: _dirname + '/../', + stdout: process.stdout, + stderr: process.stderr, +}); + execa('pnpm', ['--filter', 'sw', 'watch'], { cwd: _dirname + '/../', stdout: process.stdout,