diff options
| author | misskey-release-bot[bot] <157398866+misskey-release-bot[bot]@users.noreply.github.com> | 2024-09-29 11:42:24 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-09-29 11:42:24 +0000 |
| commit | 5fc8b3bc5018a2cb553f114a570cc33ef6831163 (patch) | |
| tree | 40edc874ae384548fd13e55fff6e317d1ef84fbb | |
| parent | Merge pull request #14391 from misskey-dev/develop (diff) | |
| parent | Release: 2024.9.0 (diff) | |
| download | sharkey-5fc8b3bc5018a2cb553f114a570cc33ef6831163.tar.gz sharkey-5fc8b3bc5018a2cb553f114a570cc33ef6831163.tar.bz2 sharkey-5fc8b3bc5018a2cb553f114a570cc33ef6831163.zip | |
Merge pull request #14580 from misskey-dev/develop
Release: 2024.9.0
603 files changed, 21538 insertions, 8546 deletions
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 c21fc95123..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' @@ -40,22 +48,27 @@ jobs: needs: [pnpm_install] runs-on: ubuntu-latest continue-on-error: true - env: - eslint-cache-version: v1 strategy: matrix: 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 }} steps: - uses: actions/checkout@v4.1.1 with: 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' @@ -64,11 +77,10 @@ jobs: - name: Restore eslint cache uses: actions/cache@v4.0.2 with: - path: node_modules/.cache/eslint - key: eslint-${{ env.eslint-cache-version }}-${{ hashFiles('/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }} - restore-keys: | - eslint-${{ env.eslint-cache-version }}-${{ hashFiles('/pnpm-lock.yaml') }}- - - run: pnpm --filter ${{ matrix.workspace }} run eslint --cache --cache-location node_modules/.cache/eslint --cache-strategy content + path: ${{ env.eslint-cache-path }} + key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }} + restore-keys: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}- + - run: pnpm --filter ${{ matrix.workspace }} run eslint --cache --cache-location ${{ env.eslint-cache-path }} --cache-strategy content typecheck: needs: [pnpm_install] @@ -78,6 +90,7 @@ jobs: matrix: workspace: - backend + - sw - misskey-js steps: - uses: actions/checkout@v4.1.1 @@ -85,14 +98,14 @@ 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' - run: corepack enable - run: pnpm i --frozen-lockfile - run: pnpm --filter misskey-js run build - if: ${{ matrix.workspace == 'backend' }} + if: ${{ matrix.workspace == 'backend' || matrix.workspace == 'sw' }} - run: pnpm --filter misskey-reversi run build if: ${{ matrix.workspace == 'backend' }} - run: pnpm --filter ${{ matrix.workspace }} run typecheck 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 e333835715..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 @@ -44,6 +45,7 @@ compose.yml /build built built-test +js-built /data /.cache-loader /db @@ -63,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 7a8873ba0d..1fbb46786e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,56 @@ +## 2024.9.0 + +### General +- Feat: ノートå˜ä½“・ユーザーã®ãƒŽãƒ¼ãƒˆãƒ»ã‚¯ãƒªãƒƒãƒ—ã®ãƒŽãƒ¼ãƒˆã®åŸ‹ã‚è¾¼ã¿æ©Ÿèƒ½ + - 埋ã‚è¾¼ã¿ã‚³ãƒ¼ãƒ‰ã‚„ウェブサイトã¸ã®å®Ÿè£…方法ã®è©³ç´°ã¯ https://misskey-hub.net/docs/for-users/features/embed/ ã‚’ã”覧ãã ã•ã„ +- Feat: パスã‚ーã§ãƒã‚°ã‚¤ãƒ³ãƒœã‚¿ãƒ³ã‚’実装 (#14574) +- Feat: フォãƒãƒ¼ã•れãŸéš›ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’è¨å®šã§ãるよã†ã« +- Feat: 連åˆã‚’ホワイトリスト制ã«ã§ãるよã†ã« +- Feat: UserWebhookã¨SystemWebhookã®ãƒ†ã‚¹ãƒˆé€ä¿¡æ©Ÿèƒ½ã‚’è¿½åŠ (#14445) +- Feat: モデレーターã¯ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«ã‹ã‹ã‚らãšãƒ•ã‚¡ã‚¤ãƒ«ãŒæ·»ä»˜ã•れã¦ã„るノートを検索ã§ãるよã†ã« + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/680) +- Feat: データエクスãƒãƒ¼ãƒˆãŒå®Œäº†ã—ãŸéš›ã«é€šçŸ¥ã‚’発行ã™ã‚‹ã‚ˆã†ã« +- Enhance: ユーザーã«ã‚ˆã‚‹ã‚³ãƒ³ãƒ†ãƒ³ãƒ„インãƒãƒ¼ãƒˆã®å¯å¦ã‚’ãƒãƒ¼ãƒ«ãƒãƒªã‚·ãƒ¼ã§åˆ¶å¾¡ã§ãるよã†ã« +- Enhance: ä¾å˜é–¢ä¿‚ã®æ›´æ–° +- Enhance: l10nã®æ›´æ–° + +### Client +- Enhance: サイズ制é™ã‚’è¶…éŽã™ã‚‹ãƒ•ァイルをアップãƒãƒ¼ãƒ‰ã—よã†ã¨ã—ãŸéš›ã«ã‚¨ãƒ©ãƒ¼ã‚’出ã™ã‚ˆã†ã« +- Enhance: アイコンデコレーション管ç†ç”»é¢ã«ãƒ—ãƒ¬ãƒ“ãƒ¥ãƒ¼ã‚’è¿½åŠ +- Enhance: コントãƒãƒ¼ãƒ«ãƒ‘ãƒãƒ«å†…ã®ãƒ•ァイル一覧ã§ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ãªãƒ•ァイルを区別ã—ã‚„ã™ã +- Enhance: Scratchpadã«UIã‚¤ãƒ³ã‚¹ãƒšã‚¯ã‚¿ãƒ¼ã‚’è¿½åŠ +- Enhance: Play編集画é¢ã®é …ç›®ã®ä¸¦ã³ã‚’å°‘ã—リデザイン +- Enhance: å„種メニューをドãƒãƒ¯ãƒ¼è¡¨ç¤ºã™ã‚‹ã‹ã©ã†ã‹è¨å®šå¯èƒ½ã« +- Enhance: AiScriptã®Mk:C:containerã®ã‚ªãƒ—ションã«`borderStyle`ã¨`borderRadius`ã‚’è¿½åŠ +- Enhance: CWã§ã‚‚絵文å—をクリックã—ã¦ãƒ¡ãƒ‹ãƒ¥ãƒ¼ã‚’表示ã§ãるよã†ã« +- 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 Boost Technologyâ„¢ (RBT)ã«ã‚ˆã‚Šã€ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã®ä½œæˆè² è·ã‚’低減ã™ã‚‹ã“ã¨ãŒå¯èƒ½ã« +- 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) +- Fix: `<link rel="alternate">`を追ã£ã¦ç…§ä¼šã™ã‚‹ã®ã¯OKレスãƒãƒ³ã‚¹ãŒè¿”å´ã•れãŸå ´åˆã®ã¿ã« + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/633) +- Fix: メールã«ã‚¹ã‚¿ã‚¤ãƒ«ãŒé©ç”¨ã•れã¦ã„ãªã‹ã£ãŸå•é¡Œã‚’ä¿®æ£ + ## 2024.8.0 ### General diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 72c84c2f18..f61311f1e5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -572,3 +572,24 @@ marginã¯ãã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆã‚’使ã†å´ãŒè¨å®šã™ã‚‹ ### indexã¨ã„ã†ãƒ•ァイルåを使ã†ãª ESMã§ã¯ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚¤ãƒ³ãƒãƒ¼ãƒˆã¯å»ƒæ¢ã•れã¦ã„ã‚‹ã®ã¨ã€ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚¤ãƒ³ãƒãƒ¼ãƒˆã›ãšã¨ã‚‚ファイルå㌠index ã ã¨ä½•æ•…ã‹ä¸€éƒ¨ã®ãƒ©ã‚¤ãƒ–ラリ?ã§ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚¤ãƒ³ãƒãƒ¼ãƒˆã ã¨è¦‹åšã•れã¦ã‚¨ãƒ©ãƒ¼ã«ãªã‚‹ + +## CSS Recipe + +### Lighten CSS vars + +``` css +color: hsl(from var(--accent) h s calc(l + 10)); +``` + +### Darken CSS vars + +``` css +color: hsl(from var(--accent) h s calc(l - 10)); +``` + +### Add alpha to CSS vars + +``` css +color: color(from var(--accent) srgb r g b / 0.5); +``` + 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/idea/MkDisableSection.vue b/idea/MkDisableSection.vue new file mode 100644 index 0000000000..d177886569 --- /dev/null +++ b/idea/MkDisableSection.vue @@ -0,0 +1,41 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="[$style.root]"> + <div :inert="disabled" :class="[{ [$style.disabled]: disabled }]"> + <slot></slot> + </div> + <div v-if="disabled" :class="[$style.cover]"></div> +</div> +</template> + +<script lang="ts" setup> +defineProps<{ + disabled?: boolean; +}>(); +</script> + +<style lang="scss" module> +.root { + position: relative; +} + +.disabled { + opacity: 0.7; +} + +.cover { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + cursor: not-allowed; + --color: color(from var(--error) srgb r g b / 0.25); + background-size: auto auto; + background-image: repeating-linear-gradient(135deg, transparent, transparent 10px, var(--color) 4px, var(--color) 14px); +} +</style> diff --git a/idea/README.md b/idea/README.md new file mode 100644 index 0000000000..f64d16800a --- /dev/null +++ b/idea/README.md @@ -0,0 +1 @@ +使ã‚れãªããªã£ãŸã‘ã©æ¶ˆã™ã®ã¯å‹¿ä½“ãªã„(å°†æ¥ä½¿ãˆã‚‹ã‹ã‚‚ã—れãªã„)コードを入れã¦ãŠãã¨ã“ diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 6fb51ea5d8..0d9e4e116c 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -451,7 +451,6 @@ or: "অথবা" language: "à¦à¦¾à¦·à¦¾" uiLanguage: "UI à¦à¦° à¦à¦¾à¦·à¦¾" aboutX: "{x} সমà§à¦ªà¦°à§à¦•ে" -disableDrawer: "ডà§à¦°à¦¯à¦¼à¦¾à¦° মেনৠপà§à¦°à¦¦à¦°à§à¦¶à¦¨ করবেন না" noHistory: "কোনো ইতিহাস নেই" signinHistory: "পà§à¦°à¦¬à§‡à¦¶ করার ইতিহাস" doing: "পà§à¦°à¦•à§à¦°à¦¿à§Ÿà¦¾ করছে..." diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 7bd9a1bb32..9d4ef016ce 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -60,6 +60,7 @@ copyFileId: "Copiar ID d'arxiu" copyFolderId: "Copiar ID de carpeta" copyProfileUrl: "Copiar URL del perfil" searchUser: "Cercar un usuari" +searchThisUsersNotes: "Cerca les publicacions de l'usuari" reply: "Respondre" loadMore: "Carregar més" showMore: "Veure més" @@ -108,11 +109,14 @@ enterEmoji: "Introduir un emoji" renote: "Impulsa" unrenote: "Anul·la l'impuls" renoted: "S'ha impulsat" +renotedToX: "Impulsat per {name}." cantRenote: "No es pot impulsar aquesta publicació" cantReRenote: "No es pot impulsar l'impuls." quote: "Cita" inChannelRenote: "Renotar només al Canal" inChannelQuote: "Citar només al Canal" +renoteToChannel: "Impulsa a un canal" +renoteToOtherChannel: "Impulsa a un altre canal" pinnedNote: "Nota fixada" pinned: "Fixar al perfil" you: "Tu" @@ -151,6 +155,7 @@ editList: "Editar llista" selectChannel: "Selecciona un canal" selectAntenna: "Tria una antena" editAntenna: "Modificar antena" +createAntenna: "Crea una antena" selectWidget: "Triar un giny" editWidgets: "Editar ginys" editWidgetsExit: "Fet" @@ -177,6 +182,10 @@ addAccount: "Afegeix un compte" reloadAccountsList: "Recarregar la llista de contactes" loginFailed: "S'ha produït un error al accedir." showOnRemote: "Navega més en el perfil original" +continueOnRemote: "Veure perfil original" +chooseServerOnMisskeyHub: "Escull un servidor des del Hub de Misskey" +specifyServerHost: "Especifica un servidor directament" +inputHostName: "Introdueix el domini" general: "General" wallpaper: "Fons de Pantalla" setWallpaper: "Defineix el fons de pantalla" @@ -187,6 +196,7 @@ followConfirm: "Està s segur que vols deixar de seguir {name}?" proxyAccount: "Compte de proxy" proxyAccountDescription: "Un compte proxy és un compte que actua com a seguidor remot per als usuaris en determinades condicions. Per exemple, quan un usuari afegeix un usuari remot a la llista, l'activitat de l'usuari remot no es lliurarà al servidor si cap usuari local segueix aquest usuari, de manera que el compte proxy el seguirà ." host: "Amfitrió" +selectSelf: "Escollir manualment" selectUser: "Selecciona usuari/a" recipient: "Destinatari" annotation: "Comentaris" @@ -202,6 +212,7 @@ perDay: "Per dia" stopActivityDelivery: "Deixa d'enviar activitats" blockThisInstance: "Deixa d'enviar activitats" silenceThisInstance: "Silencia aquesta instà ncia " +mediaSilenceThisInstance: "Silenciar els arxius d'aquesta instà ncia " operations: "Accions" software: "Programari" version: "Versió" @@ -223,6 +234,8 @@ blockedInstances: "Instà ncies bloquejades" blockedInstancesDescription: "Llista els enllaços d'amfitrió de les instà ncies que vols bloquejar separades per un salt de pà gina. Les instà ncies llistades no podran comunicar-se amb aquesta instà ncia." silencedInstances: "Instà ncies silenciades" silencedInstancesDescription: "Llista els enllaços d'amfitrió de les instà ncies que vols silenciar. Tots els comptes de les instà ncies llistades s'establiran com silenciades i només podran fer sol·licitacions de seguiment, i no podran mencionar als comptes locals si no els segueixen. Això no afectarà les instà ncies bloquejades." +mediaSilencedInstances: "Instà ncies amb els arxius silenciats" +mediaSilencedInstancesDescription: "Llista els noms dels servidors que vulguis silenciar els arxius, un servidor per lÃnia. Tots els comptes que pertanyin als servidors llistats seran tractats com sensibles i no podran fer servir emojis personalitzats. Això no tindrà efecte sobre els servidors blocats." muteAndBlock: "Silencia i bloca" mutedUsers: "Usuaris silenciats" blockedUsers: "Usuaris bloquejats" @@ -313,6 +326,7 @@ selectFile: "Selecciona fitxers" selectFiles: "Selecciona fitxers" selectFolder: "Selecció de carpeta" selectFolders: "Selecció de carpeta" +fileNotSelected: "Cap fitxer seleccionat" renameFile: "Canvia el nom del fitxer" folderName: "Nom de la carpeta" createFolder: "Crea una carpeta" @@ -468,10 +482,12 @@ retype: "Torneu a introduir-la" noteOf: "Publicació de: {user}" quoteAttached: "Frase adjunta" quoteQuestion: "Vols annexar-la com a cita?" +attachAsFileQuestion: "El text copiat és massa llarg. Vols adjuntar-lo com un fitxer de text?" noMessagesYet: "Encara no hi ha missatges" newMessageExists: "Has rebut un nou missatge" onlyOneFileCanBeAttached: "Només pots adjuntar un fitxer a un missatge" signinRequired: "Si us plau, Registra't o inicia la sessió abans de continuar" +signinOrContinueOnRemote: "Per continuar necessites moure el teu servidor o registrar-te / iniciar sessió en aquest servidor." invitations: "Convida" invitationCode: "Codi d'invitació" checking: "Comprovació en curs..." @@ -493,7 +509,6 @@ uiLanguage: "Idioma de l'interfÃcie" aboutX: "Respecte a {x}" emojiStyle: "Estil d'emoji" native: "Nadiu" -disableDrawer: "No mostrar els menús en calaixos" showNoteActionsOnlyHover: "Només mostra accions de la nota en passar amb el cursor" showReactionsCount: "Mostra el nombre de reaccions a les publicacions" noHistory: "No hi ha un registre previ" @@ -543,7 +558,7 @@ objectStorageUseSSLDesc: "Desactiva'l si no tens pensat fer servir HTTPS per les objectStorageUseProxy: "Connectar-se mitjançant un Proxy" objectStorageUseProxyDesc: "Desactiva'l si no farà s servir un Proxy per les connexions de l'API" objectStorageSetPublicRead: "Configurar les pujades com públiques " -s3ForcePathStyleDesc: "Si s3ForcePathStyle es troba activat el nom del dipòsit s'ha d'incloure a l'adreça URL en comtes del nom del host. Potser que necessitis activar-ho quan facis servir, per exemple, Minio a un servidor propi." +s3ForcePathStyleDesc: "Si s3ForcePathStyle es troba activat el nom del cubell s'haurà d'especificar com a part de l'adreça URL en comptes del nom del servidor. Podria ser que necessitis activar aquesta opció quan facis servir serveis com ara l'allotjament a un servidor propi." serverLogs: "Registres del servidor" deleteAll: "Elimina-ho tot" showFixedPostForm: "Mostrar el formulari per escriure a l'inici de la lÃnia de temps" @@ -576,6 +591,8 @@ ascendingOrder: "Ascendent" descendingOrder: "Descendent" scratchpad: "Bloc de proves" scratchpadDescription: "El bloc de proves proporciona un entorn experimental per AiScript. Pot escriure i verificar els resultats que interactuen amb Misskey." +uiInspector: "Inspector de la interfÃcie" +uiInspectorDescription: "Podeu visualitzar una llista d'elements UI presents en la memòria. Els components de la interfÃcie d'usuari són generats per les funcions Ui:C:." output: "Sortida" script: "Script" disablePagesScript: "Desactivar AiScript a les pà gines " @@ -832,6 +849,7 @@ administration: "Administració" accounts: "Comptes" switch: "Canvia" noMaintainerInformationWarning: "La informació de l'administrador no s'ha configurat" +noInquiryUrlWarning: "No s'ha desat l'URL de consulta." noBotProtectionWarning: "La protecció contra bots no s'ha configurat." configure: "Configurar" postToGallery: "Crear una nova publicació a la galeria" @@ -1021,6 +1039,7 @@ thisPostMayBeAnnoyingHome: "Publicar a la lÃnia de temps d'Inici" thisPostMayBeAnnoyingCancel: "Cancel·lar " thisPostMayBeAnnoyingIgnore: "Publicar de totes maneres" collapseRenotes: "Col·lapsar les renotes que ja has vist" +collapseRenotesDescription: "Col·lapse les notes a les quals ja has reaccionat o que ja has renotat" internalServerError: "Error intern del servidor" internalServerErrorDescription: "El servidor ha fallat de manera inexplicable." copyErrorInfo: "Copiar la informació de l'error " @@ -1094,6 +1113,8 @@ preservedUsernames: "Noms d'usuaris reservats" preservedUsernamesDescription: "Llistat de noms d'usuaris que no es poden fer servir separats per salts de linia. Aquests noms d'usuaris no estaran disponibles quan es creï un compte d'usuari normal, però els administradors els poden fer servir per crear comptes manualment. Per altre banda els comptes ja creats amb aquests noms d'usuari no es veure'n afectats." createNoteFromTheFile: "Compon una nota des d'aquest fitxer" archive: "Arxiu" +archived: "Arxivat" +unarchive: "Desarxivar" channelArchiveConfirmTitle: "Vols arxivar {name}?" channelArchiveConfirmDescription: "Un Canal arxivat no apareixerà a la llista de canals o als resultats de cerca. Tampoc es poden afegir noves entrades." thisChannelArchived: "Aquest Canal ha sigut arxivat." @@ -1104,6 +1125,9 @@ preventAiLearning: "Descartar l'ús d'aprenentatge automà tic (IA Generativa)" preventAiLearningDescription: "Demanar els indexadors no fer servir els texts, imatges, etc. en cap conjunt de dades per alimentar l'aprenentatge automà tic (IA Predictiva/ Generativa). Això s'aconsegueix afegint la etiqueta \"noai\" com a resposta HTML al contingut corresponent. Prevenir aquest ús totalment pot ser que no sigui aconseguit, ja que molts indexadors poden obviar aquesta etiqueta." options: "Opcions" specifyUser: "Especificar usuari" +lookupConfirm: "Vols fer una cerca?" +openTagPageConfirm: "Vols obrir una pà gina d'etiquetes?" +specifyHost: "Especifica un servidor" failedToPreviewUrl: "Vista prèvia no disponible" update: "Actualitzar" rolesThatCanBeUsedThisEmojiAsReaction: "Rols que poden fer servir aquest emoji com a reacció " @@ -1172,7 +1196,10 @@ confirmShowRepliesAll: "Aquesta opció no té marxa enrere. Vols mostrar les tev confirmHideRepliesAll: "Aquesta opció no té marxa enrere. Vols ocultar les teves respostes a tots els usuaris que segueixes a la lÃnia de temps?" externalServices: "Serveis externs" sourceCode: "Codi font" +sourceCodeIsNotYetProvided: "El codi font encara no es troba disponible. Contacta amb l'administrador per solucionar aquest problema." repositoryUrl: "URL del repositori" +repositoryUrlDescription: "Si està s fent servir Misskey tal com és (sense cap canvi al codi font), introdueix https://github.com/misskey-dev/misskey" +repositoryUrlOrTarballRequired: "Si no ofereixes cap repositori, publica un fitxer tarball. Dona una ullada a .config/example.yml per a més informació." feedback: "Opinió" feedbackUrl: "URL per a opinar" impressum: "Impressum" @@ -1211,6 +1238,7 @@ showReplay: "Veure reproducció" replay: "Reproduir" replaying: "Reproduint" endReplay: "Tanca la redifusió" +copyReplayData: "Copia les dades de la resposta" ranking: "Classificació" lastNDays: "Últims {n} dies" backToTitle: "Torna al tÃtol" @@ -1224,12 +1252,42 @@ gameRetry: "Torna a provar" notUsePleaseLeaveBlank: "Si no voleu usar-ho, deixeu-ho en blanc" useTotp: "Usa una contrasenya d'un sol ús" useBackupCode: "Usa un codi de recuperació" +launchApp: "Inicia l'aplicació " +useNativeUIForVideoAudioPlayer: "Fes servir la UI del navegador quan reprodueixis vÃdeo i à udio " +keepOriginalFilename: "Desa el nom del fitxer original" +keepOriginalFilenameDescription: "Si desactives aquesta opció els noms dels fitxers se substituiran per una cadena aleatòria quan carreguis nous fitxers de forma automà tica." +noDescription: "No hi ha una descripció " +alwaysConfirmFollow: "Confirma sempre els seguiments" +inquiry: "Contacte" +tryAgain: "Intenta-ho més tard." +confirmWhenRevealingSensitiveMedia: "Confirmació quan revelis contingut sensible " +sensitiveMediaRevealConfirm: "Aquest contingut potser sensible. Segur que ho vols revelar?" +createdLists: "Llistes creades " +createdAntennas: "Antenes creades" +fromX: "De {x}" +genEmbedCode: "Obtenir el codi per incrustar" +noteOfThisUser: "Notes d'aquest usuari" +clipNoteLimitExceeded: "No es poden afegir més notes a aquest clip." _delivery: + status: "Estat d'entrega " stop: "Suspés" + resume: "Torna a enviar" _type: none: "S'està publicant" + manuallySuspended: "Suspendre manualment" + goneSuspended: "Servidor suspès perquè el servidor s'ha esborrat" + autoSuspendedForNotResponding: "Servidor suspès perquè el servidor no respon" _bubbleGame: howToPlay: "Com es juga" + hold: "Mantenir" + _score: + score: "Puntuació " + scoreYen: "Diners guanyats" + highScore: "Millor puntuació " + maxChain: "Nombre mà xim de combos" + yen: "{yen}Ien" + estimatedQty: "{qty}peces" + scoreSweets: "{onigiriQtyWithUnit}ongiris" _howToPlay: section1: "Ajusta la posició i deixa caure l'objecte dintre la caixa." section2: "Quan dos objectes del mateix tipus es toquen, canviaran en un objecte diferent i guanyares punts." @@ -1344,6 +1402,9 @@ _serverSettings: fanoutTimelineDescription: "Quan es troba activat millora bastant el rendiment quan es recuperen les lÃnies de temps i redueix la carrega de la base de dades. Com a contrapunt, l'ús de memòria de Redis es veurà incrementada. Considera d'estabilitat aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes de inestabilitat." fanoutTimelineDbFallback: "Carregar de la base de dades" fanoutTimelineDbFallbackDescription: "Quan s'activa, la lÃnia de temps fa servir la base de dades per consultes adicionals si la lÃnia de temps no es troba a la memòria cau. Si és desactiva la cà rrega del servidor és veure reduïda, però també és reduirà el nombre de lÃnies de temps que és poden obtenir." + reactionsBufferingDescription: "Quan s'activa aquesta opció millora bastant el rendiment en recuperar les lÃnies de temps reduint la cà rrega de la base. Com a contrapunt, augmentarà l'ús de memòria de RedÃs. Desactiva aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes d'inestabilitat." + inquiryUrl: "URL de consulta " + inquiryUrlDescription: "Escriu adreça URL per al formulari de consulta per al mantenidor del servidor o una pà gina web amb el contacte d'informació." _accountMigration: moveFrom: "Migrar un altre compte a aquest" moveFromSub: "Crear un à lies per un altre compte" @@ -1651,6 +1712,7 @@ _role: gtlAvailable: "Pot veure la lÃnia de temps global" ltlAvailable: "Pot veure la lÃnia de temps local" canPublicNote: "Pot enviar notes públiques" + mentionMax: "Nombre mà xim de mencions a una nota" canInvite: "Pot crear invitacions a la instà ncia " inviteLimit: "LÃmit d'invitacions " inviteLimitCycle: "Temps de refresc de les invitacions" @@ -1659,6 +1721,7 @@ _role: canManageAvatarDecorations: "Gestiona les decoracions dels avatars " driveCapacity: "Capacitat del disc" alwaysMarkNsfw: "Marca sempre els fitxers com a sensibles" + canUpdateBioMedia: "Permet l'edició d'una icona o un bà ner" pinMax: "Nombre mà xim de notes fixades" antennaMax: "Nombre mà xim d'antenes" wordMuteMax: "Nombre mà xim de carà cters permesos a les paraules silenciades" @@ -1673,9 +1736,20 @@ _role: canSearchNotes: "Pot cercar notes" canUseTranslator: "Pot fer servir el traductor" avatarDecorationLimit: "Nombre mà xim de decoracions que es poden aplicar els avatars" + canImportAntennas: "Autoritza la importació d'antenes " + canImportBlocking: "Autoritza la importació de bloquejats" + canImportFollowing: "Autoritza la importació de seguidors" + canImportMuting: "Autoritza la importació de silenciats" + canImportUserLists: "Autoritza la importació de llistes d'usuaris " _condition: + roleAssignedTo: "Assignat a rols manuals" isLocal: "Usuari local" isRemote: "Usuari remot" + isCat: "Usuaris gats" + isBot: "Usuaris bots" + isSuspended: "Usuari suspès" + isLocked: "Comptes privats" + isExplorable: "Fes que el compte aparegui a les cerques" createdLessThan: "Han passat menys de X a passat des de la creació del compte" createdMoreThan: "Han passat més de X des de la creació del compte" followersLessThanOrEq: "Té menys de X seguidors" @@ -1745,6 +1819,7 @@ _plugin: installWarn: "Si us plau, no instal·lis afegits que no siguin de confiança." manage: "Gestionar els afegits" viewSource: "Veure l'origen " + viewLog: "Mostra el registre" _preferencesBackups: list: "Llista de còpies de seguretat" saveNew: "Fer una còpia de seguretat nova" @@ -1774,6 +1849,8 @@ _aboutMisskey: contributors: "Col·laboradors principals" allContributors: "Tots els col·laboradors " source: "Codi font" + original: "Original" + thisIsModifiedVersion: "En {name} fa servir una versió modificada de Misskey." translation: "Tradueix Misskey" donate: "Fes un donatiu a Misskey" morePatrons: "També agraïm el suport d'altres col·laboradors que no surten en aquesta llista. Grà cies! 🥰" @@ -1901,6 +1978,7 @@ _soundSettings: driveFileTypeWarnDescription: "Seleccionar un fitxer d'à udio " driveFileDurationWarn: "L'à udio és massa llarg" driveFileDurationWarnDescription: "Els à udios molt llargs pot interrompre l'ús de Misskey. Vols continuar?" + driveFileError: "El so no es pot carregar. Canvia la configuració" _ago: future: "Futur " justNow: "Ara mateix" @@ -1953,6 +2031,7 @@ _2fa: backupCodesDescription: "Si l'aplicació d'autenticació no es pot utilitzar, es pot accedir al compte utilitzant els següents codis de còpia de seguretat. Assegura't de mantenir aquests codis en un lloc segur. Cada codi es pot utilitzar només una vegada." backupCodeUsedWarning: "Es va utilitzar un codi de còpia de seguretat. Si l'aplicació de certificació està disponible, reconfigura l'aplicació d'autenticació tan aviat com sigui possible." backupCodesExhaustedWarning: "Es van utilitzar tots els codis de còpia de seguretat. Si no es pot utilitzar l'aplicació d'autenticació, ja no es pot accedir al compte. Torna a registrar l'aplicació d'autenticació." + moreDetailedGuideHere: "Aquà tens una guia al detall" _permissions: "read:account": "Veure la informació del compte." "write:account": "Editar la informació del compte." @@ -2026,22 +2105,73 @@ _permissions: "read:admin:emoji": "Veure emojis" "write:admin:queue": "Gestionar la cua de feines" "read:admin:queue": "Veure la cua de feines" + "write:admin:promo": "Gestiona les notes promocionals" + "write:admin:drive": "Gestiona el disc de l'usuari" + "read:admin:drive": "Veure la informació del disc de l'usuari" + "read:admin:stream": "Fes servir l'API sobre Websocket per l'administració" + "write:admin:ad": "Gestiona la publicitat" + "read:admin:ad": "Veure anuncis" + "write:invite-codes": "Crear codis d'invitació" + "read:invite-codes": "Obtenir codis d'invitació" + "write:clip-favorite": "Gestionar els clips favorits" + "read:clip-favorite": "Veure clips favorits" + "read:federation": "Veure dades de federació" + "write:report-abuse": "Informar d'un abús" +_auth: + shareAccessTitle: "Concedeix permisos a l'aplicació" + shareAccess: "Vols que {name} pugui accedir al vostre compte?" + shareAccessAsk: "Segur que vols que aquesta aplicació pugui accedir al vostre compte?" + permission: "{name} demana els següents permisos" + permissionAsk: "Aquesta aplicació demana els següents permisos" + pleaseGoBack: "Si us plau, torna a l'aplicació" + callback: "Tornant a l'aplicació" + denied: "Accés denegat" + pleaseLogin: "Si us plau, identificat per autoritzar l'aplicació." _antennaSources: all: "Totes les publicacions" homeTimeline: "Publicacions dels usuaris seguits" users: "Publicacions d'usuaris especÃfics" userList: "Publicacions d'una llista d'usuaris" + userBlacklist: "Totes les notes excepte les d'un o alguns usuaris especificats" +_weekday: + sunday: "Diumenge" + monday: "Dilluns" + tuesday: "Dimarts" + wednesday: "Dimecres" + thursday: "Dijous" + friday: "Divendres" + saturday: "Dissabte" _widgets: profile: "Perfil" instanceInfo: "Informació del fitxer d'instal·lació" + memo: "Notes adhesives" notifications: "Notificacions" timeline: "LÃnia de temps" + calendar: "Calendari" + trends: "Tendència" + clock: "Rellotge" + rss: "Lector RSS" + rssTicker: "RSS ticker" activity: "Activitat" + photos: "Fotografies" + digitalClock: "Rellotge digital" + unixClock: "Rellotge UNIX" federation: "Federació" + instanceCloud: "Núvol d'instà ncies" + postForm: "Formulari de publicació" + slideshow: "Presentació" button: "Botó " + onlineUsers: "Usuaris actius" jobQueue: "Cua de tasques" + serverMetric: "Mètriques del servidor" + aiscript: "Consola AiScript" + aiscriptApp: "Aplicació AiScript" + aichan: "Ai" + userList: "Llistat d'usuaris" _userList: chooseList: "Tria una llista" + clicker: "Clicker" + birthdayFollowings: "Usuaris que fan l'aniversari avui" _cw: hide: "Amagar" show: "Carregar més" @@ -2107,25 +2237,74 @@ _profile: avatarDecorationMax: "Pot afegir un mà xim de {max} decoracions." _exportOrImport: allNotes: "Totes les publicacions" + favoritedNotes: "Notes preferides" clips: "Retalls" followingList: "Seguint" muteList: "Silencia" blockingList: "Bloqueja" userLists: "Llistes" + excludeMutingUsers: "Exclou usuaris silenciats" + excludeInactiveUsers: "Exclou usuaris inactius" + withReplies: "Inclou a la lÃnia de temps les respostes d'usuaris importats" _charts: federation: "Federació" + apRequest: "Peticions" + usersIncDec: "Diferència entre el nombre d'usuaris" + usersTotal: "Nombre total d'usuaris" + activeUsers: "Usuaris actius" + notesIncDec: "Diferència entre el nombre de notes" + localNotesIncDec: "Diferencia en el nombre de notes locals" + remoteNotesIncDec: "Diferencia en el nombre de notes remotes" + notesTotal: "Nombre total de notes" + filesIncDec: "Diferencia en el nombre de fitxers" + filesTotal: "Nombre total de fitxers" + storageUsageIncDec: "Diferencia en l'emmagatzematge usat" + storageUsageTotal: "Emmagatzematge usat" +_instanceCharts: + requests: "Peticions" + users: "Diferència entre el nombre d'usuaris" + usersTotal: "Usuaris totals acumulats" + notes: "Diferència entre el nombre de notes" + notesTotal: "Notes totals acumulades" + ff: "Diferència en nombre d'usuaris seguits / seguidors" + ffTotal: "Nombre total acumulat d'usuaris seguits / seguidors" + cacheSize: "Diferència a la mida de la memòria cau" + cacheSizeTotal: "Total acumulat de la mida de la memòria cau" + files: "Diferència al nombre d'arxius" + filesTotal: "Nombre acumulatiu de fitxers" _timelines: home: "Inici" local: "Local" social: "Social" global: "Global" _play: + new: "Crear un guió" + edit: "Editar guió" + created: "Guió creat" + updated: "Guió editat" + deleted: "Guió esborrat" + pageSetting: "Configuració del guió" + editThisPage: "Edita aquest guió" viewSource: "Veure l'origen " + my: "Els meus guions" + liked: "Guions que m'han agradat" featured: "Popular" title: "TÃtol " script: "Script" summary: "Descripció" + visibilityDescription: "" _pages: + newPage: "pa" + editPage: "Editar la pà gina" + readPage: "Veure el codi font d'aquesta pà gina" + created: "La pà gina ha sigut creada correctament" + updated: "La pà gina s'ha editat correctament" + deleted: "La pà gina s'ha esborrat sense problemes" + pageSetting: "Configuració de la pà gina" + nameAlreadyExists: "L'adreça URL de la pà gina ja existeix" + invalidNameTitle: "L'adreça URL de la pà gina no és và lida" + invalidNameText: "Assegurat que el tÃtol de la pà gina no és buit" + editThisPage: "Editar la pà gina" viewSource: "Veure l'origen " viewPage: "Veure les teves pà gines " like: "M'agrada " @@ -2148,6 +2327,7 @@ _pages: eyeCatchingImageSet: "Escull una miniatura" eyeCatchingImageRemove: "Esborrar la miniatura" chooseBlock: "Afegeix un bloc" + enterSectionTitle: "Escriu el tÃtol de la secció" selectType: "Seleccionar tipus" contentBlocks: "Contingut" inputBlocks: "Entrada " @@ -2158,6 +2338,8 @@ _pages: section: "Secció " image: "Imatges" button: "Botó " + dynamic: "Blocs dinà mics" + dynamicDescription: "Aquest bloc és antic. Ara en endavant fes servir {play}" note: "Incorporar una Nota" _note: id: "ID de la publicació" @@ -2187,29 +2369,50 @@ _notification: sendTestNotification: "Enviar notificació de prova" notificationWillBeDisplayedLikeThis: "Les notificacions és veure'n aixà " reactedBySomeUsers: "Han reaccionat {n} usuaris" + likedBySomeUsers: "A {n} usuaris els hi agrada la teva nota" renotedBySomeUsers: "L'han impulsat {n} usuaris" + followedBySomeUsers: "Et segueixen {n} usuaris" + flushNotification: "Netejar notificacions" _types: all: "Tots" + note: "Notes noves" follow: "Seguint" mention: "Menció" + reply: "Respostes" renote: "Renotar" quote: "Citar" reaction: "Reaccions" + pollEnded: "Enquesta terminada" + receiveFollowRequest: "Rebuda una petició de seguiment" + followRequestAccepted: "Petició de seguiment acceptada" + roleAssigned: "Rol donat" + achievementEarned: "Assoliment desbloquejat" + app: "Notificacions d'aplicacions" _actions: followBack: "t'ha seguit també" reply: "Respondre" renote: "Renotar" _deck: + alwaysShowMainColumn: "Mostrar sempre la columna principal" columnAlign: "Alinea les columnes" addColumn: "Afig una columna" + newNoteNotificationSettings: "Configuració de notificacions per a notes noves" + configureColumn: "Configuració de columnes" swapLeft: "Mou a l’esquerra" swapRight: "Mou a la dreta" swapUp: "Mou cap amunt" swapDown: "Mou cap avall" + stackLeft: "Pila a la columna esquerra" popRight: "Col·loca a la dreta" profile: "Perfil" newProfile: "Perfil nou" deleteProfile: "Elimina el perfil" + introduction: "Crea la interfÃcie perfecta posant les columnes allà on vulguis!" + introduction2: "Fes clic al botó + de la dreta per afegir noves columnes sempre que vulguis." + widgetsIntroduction: "Selecciona \"Editar ginys\" a la columna del menú i afegeix un." + useSimpleUiForNonRootPages: "Usa una interfÃcie senzilla per a les pà gines navegades" + usedAsMinWidthWhenFlexible: "L'amplada mÃnima es farà servir quan \"Ajust automà tic de l'amplada\" estigui activat" + flexible: "Ajust automà tic de l'amplada" _columns: main: "Principal" widgets: "Ginys" @@ -2220,18 +2423,77 @@ _deck: channel: "Canals" mentions: "Mencions" direct: "Publicacions directes" + roleTimeline: "LÃnia de temps dels rols" +_dialog: + charactersExceeded: "Has arribat al mà xim de carà cters! Actualment és {current} de {max}" + charactersBelow: "Ets per sota del mÃnim de carà cters! Actualment és {current} de {min}" +_disabledTimeline: + title: "LÃnia de tems desactivada" + description: "No pots fer servir aquesta lÃnia de temps amb els teus rols actuals." +_drivecleaner: + orderBySizeDesc: "Mida del fitxer descendent" + orderByCreatedAtAsc: "Data ascendent" _webhookSettings: + createWebhook: "Crear un Webhook" + modifyWebhook: "Modificar un Webhook" name: "Nom" + secret: "Secret" + trigger: "Activador" active: "Activat" + _events: + follow: "Quan se segueix a un usuari" + followed: "Quan et segueixen" + note: "Quan es publica una nota" + reply: "Quan es rep una resposta" + renote: "Quan es renoti" + reaction: "Quan es rep una reacció " + mention: "Quan et mencionen" + _systemEvents: + abuseReport: "Quan reps un nou informe de moderació " + abuseReportResolved: "Quan resols un informe de moderació " + userCreated: "Quan es crea un usuari" + deleteConfirm: "Segur que vols esborrar el webhook?" + testRemarks: "Si feu clic al botó a la dreta de l'interruptor, podeu enviar un webhook de prova amb dades dummy." _abuseReport: _notificationRecipient: + createRecipient: "Afegeix un destinatari a l'informe de moderació " + modifyRecipient: "Editar un destinatari en l'informe de moderació " + recipientType: "Tipus de notificació " _recipientType: mail: "Correu electrònic" + webhook: "Webhook" + _captions: + mail: "Enviar un correu electrònic a tots els moderadors quan es rep un informe de moderació " + webhook: "Enviar una notificació al SystemWebhook quan es rebi o es resolgui un informe de moderació " + keywords: "Paraules clau" + notifiedUser: "Usuaris que s'han de notificar " + notifiedWebhook: "Webhook que s'ha de fer servir" + deleteConfirm: "Segur que vols esborrar el destinatari de l'informe de moderació?" _moderationLogTypes: + createRole: "Rol creat" + deleteRole: "Rol esborrat" + updateRole: "Rol actualitzat" + assignRole: "Assignat al rol" + unassignRole: "Esborrat del rol" suspend: "Suspèn" + unsuspend: "Suspensió treta" + addCustomEmoji: "Afegit emoji personalitzat" + updateCustomEmoji: "Actualitzat emoji personalitzat" + deleteCustomEmoji: "Esborrat emoji personalitzat" + updateServerSettings: "Configuració del servidor actualitzada" + updateUserNote: "Nota de moderació actualitzada" + deleteDriveFile: "Fitxer esborrat" + deleteNote: "Nota esborrada" + createGlobalAnnouncement: "Anunci global creat" + createUserAnnouncement: "Anunci individual creat" + updateGlobalAnnouncement: "Anunci global actualitzat" + updateUserAnnouncement: "Anunci individual actualitzat " + deleteGlobalAnnouncement: "Anunci global esborrat" + deleteUserAnnouncement: "Anunci individual esborrat " resetPassword: "Restableix la contrasenya" suspendRemoteInstance: "Servidor remot suspès " unsuspendRemoteInstance: "S'ha tret la suspensió del servidor remot" + updateRemoteInstanceNote: "Nota de moderació de la instà ncia remota actualitzada" markSensitiveDriveFile: "Fitxer marcat com a sensible" unmarkSensitiveDriveFile: "S'ha tret la marca de sensible del fitxer" resolveAbuseReport: "Informe resolt" @@ -2244,6 +2506,16 @@ _moderationLogTypes: deleteAvatarDecoration: "S'ha esborrat la decoració de l'avatar " unsetUserAvatar: "Esborrar l'avatar d'aquest usuari" unsetUserBanner: "Esborrar el bà ner d'aquest usuari" + createSystemWebhook: "Crear un SystemWebhook" + updateSystemWebhook: "Actualitzar SystemWebhook" + deleteSystemWebhook: "Esborrar SystemWebhook" + createAbuseReportNotificationRecipient: "Crear un destinatari per l'informe de moderació " + updateAbuseReportNotificationRecipient: "Actualitzar destinatari per l'informe de moderació " + deleteAbuseReportNotificationRecipient: "Esborrar destinatari de l'informe de moderació " + deleteAccount: "Esborrar el compte " + deletePage: "Esborrar la pà gina" + deleteFlash: "Esborrar el guió" + deleteGalleryPost: "Esborrar la publicació de la galeria" _fileViewer: title: "Detall del fitxer" type: "Tipus de fitxer" @@ -2270,5 +2542,54 @@ _externalResourceInstaller: _errors: _invalidParams: title: "Parà metres no và lids " + description: "No hi ha suficient informació per carregar les dades del lloc extern. Confirma l'URL que hi ha escrita." + _resourceTypeNotSupported: + title: "El recurs extern no està suportat." + description: "Aquesta mena de recurs no està suportat. Contacta amb l'administrador." + _failedToFetch: + title: "Ha fallat l'obtenció de dades" + fetchErrorDescription: "Ha aparegut un error comunicant-se amb el lloc extern. Si després d'intentar-ho un altre cop no es resol, contacta amb l'administrador." + parseErrorDescription: "Ha aparegut un error processant les dades carregades del lloc extern. Contacta amb l'administrador." + _hashUnmatched: + title: "Ha fallat la verificació de les dades" + description: "Ha aparegut un error verificant les dades obtingudes. Com a mesura de seguretat la instal·lació no pot continuar. Contacta amb l'administrador." + _pluginParseFailed: + title: "Error d'AiScript" + description: "Les dades sol·licitades s'han obtingut correctament, però hem trobat un error durant el processament d'AiScript. Contacta amb l'autor de l'afegit. Detalls de l'error es pot veure a la consola JavaScript." + _pluginInstallFailed: + title: "La instal·lació de l'afegit a fallat" + description: "Ha aparegut un error durant la instal·lació de l'afegit. Intenta-ho una altra vegada. El detall de l'error es pot veure a la consola JavaScript." + _themeParseFailed: + title: "Ha fallat el processament del tema" + description: "Les dades sol·licitades s'han obtingut correctament, però hem trobat un error durant el processament del tema. Contacta amb l'autor de l'afegit. Detalls de l'error es pot veure a la consola JavaScript." + _themeInstallFailed: + title: "La instal·lació del tema a fallat" + description: "Ha aparegut un error durant la instal·lació del tema. Intenta-ho una altra vegada. El detall de l'error es pot veure a la consola JavaScript." +_dataSaver: + _media: + title: "Carregant multimèdia " + description: "Desactiva la cà rrega automà tica d'imatges i vÃdeos. Les imatges i els vÃdeos amagats es carregaran quan es faci clic a sobre." + _avatar: + title: "Avatars animats" + description: "Detenir l'animació dels avatars animats. Les imatges animades solen tenir un pes més gran que les imatges normals, reduint el trà fic disponible." + _urlPreview: + title: "Miniatures vista prèvia de l'URL" + description: "Les imatges en miniatura que serveixen com a vista prèvia de les URLs no es tornaran a carregar." + _code: + title: "Ressaltat del codi " _reversi: total: "Total" +_embedCodeGen: + title: "Personalitza el codi per incrustar" + header: "Mostrar la capçalera" + autoload: "Carregar automà ticament (no recomanat)" + maxHeight: "Alçada mà xima" + maxHeightDescription: "0 anul·la la configuració mà xima. Per evitar que continuï creixent verticalment, especifiqui qualsevol valor." + maxHeightWarn: "El lÃmit mà xim d'alçada és nul (0). Si això no és un canvi previst, estableix el mà xim d'alçada a un cert valor." + previewIsNotActual: "La visualització és diferent de la que es mostra quan s'implanta." + rounded: "Angle recte" + border: "Afegeix un marc al contenidor" + applyToPreview: "Aplica a la vista prèvia" + generateCode: "Crea el codi per incrustar" + codeGenerated: "Codi generat" + codeGeneratedDescription: "Si us plau, enganxeu el codi generat al lloc web." diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index 7db7424762..4a27ed7635 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -471,7 +471,6 @@ uiLanguage: "Jazyk uživatelského rozhranÃ" aboutX: "O {x}" emojiStyle: "Styl emoji" native: "VýchozÃ" -disableDrawer: "NepoužÃvat Å¡uplÃkové menu" showNoteActionsOnlyHover: "Zobrazit akce poznámky jenom pÅ™i nabÄ›hnutà myÅ¡i" noHistory: "Žádná historie" signinHistory: "Historie pÅ™ihlášenÃ" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 8e44a3bbd4..453f6308f6 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -491,7 +491,6 @@ uiLanguage: "Sprache der Benutzeroberfläche" aboutX: "Über {x}" emojiStyle: "Emoji-Stil" native: "Nativ" -disableDrawer: "Keine ausfahrbaren Menüs verwenden" showNoteActionsOnlyHover: "Notizmenü nur bei Mouseover anzeigen" noHistory: "Kein Verlauf gefunden" signinHistory: "Anmeldungsverlauf" diff --git a/locales/en-US.yml b/locales/en-US.yml index c82ea3c9a2..ad81376f89 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -509,7 +509,6 @@ uiLanguage: "User interface language" aboutX: "About {x}" emojiStyle: "Emoji style" native: "Native" -disableDrawer: "Don't use drawer-style menus" showNoteActionsOnlyHover: "Only show note actions on hover" showReactionsCount: "See the number of reactions in notes" noHistory: "No history available" @@ -1079,7 +1078,7 @@ enableChartsForRemoteUser: "Generate remote user data charts" enableChartsForFederatedInstances: "Generate remote instance data charts" showClipButtonInNoteFooter: "Add \"Clip\" to note action menu" reactionsDisplaySize: "Reaction display size" -limitWidthOfReaction: "Limits the maximum width of reactions and display them in reduced size." +limitWidthOfReaction: "Limit the maximum width of reactions and display them in reduced size." noteIdOrUrl: "Note ID or URL" video: "Video" videos: "Videos" @@ -1263,6 +1262,10 @@ confirmWhenRevealingSensitiveMedia: "Confirm when revealing sensitive media" sensitiveMediaRevealConfirm: "This might be a sensitive media. Are you sure to reveal?" createdLists: "Created lists" createdAntennas: "Created antennas" +fromX: "From {x}" +genEmbedCode: "Generate embed code" +noteOfThisUser: "Notes by this user" +clipNoteLimitExceeded: "No more notes can be added to this clip." _delivery: status: "Delivery status" stop: "Suspended" @@ -2439,7 +2442,7 @@ _webhookSettings: mention: "When being mentioned" _systemEvents: abuseReport: "When received a new abuse report" - abuseReportResolved: "When resolved abuse reports" + abuseReportResolved: "When resolved abuse report" userCreated: "When user is created" deleteConfirm: "Are you sure you want to delete the Webhook?" _abuseReport: @@ -2640,3 +2643,17 @@ _contextMenu: app: "Application" appWithShift: "Application with shift key" native: "Native" +_embedCodeGen: + title: "Customize embed code" + header: "Show header" + autoload: "Automatically load more (deprecated)" + maxHeight: "Max height" + maxHeightDescription: "Setting it to 0 disables the max height setting. Specify some value to prevent the widget from continuing to expand vertically." + maxHeightWarn: "The max height limit is disabled (0). If this was not intended, set the max height to some value." + previewIsNotActual: "The display differs from the actual embedding because it exceeds the range displayed on the preview screen." + rounded: "Make it rounded" + border: "Add a border to the outer frame" + applyToPreview: "Apply to the preview" + generateCode: "Generate embed code" + codeGenerated: "The code has been generated" + codeGeneratedDescription: "Paste the generated code into your website to embed the content." diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 2621965d1b..66cab3e957 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -109,11 +109,14 @@ enterEmoji: "Ingresar emojis" renote: "Renotar" unrenote: "Quitar renota" renoted: "Renotado" +renotedToX: "{name} usuarios han renotado。" cantRenote: "No se puede renotar este post" cantReRenote: "No se puede renotar una renota" quote: "Citar" inChannelRenote: "Renota sólo del canal" inChannelQuote: "Cita sólo del canal" +renoteToChannel: "Renotar a otro canal" +renoteToOtherChannel: "Renotar a otro canal" pinnedNote: "Nota fijada" pinned: "Fijar al perfil" you: "Tú" @@ -152,6 +155,7 @@ editList: "Editar lista" selectChannel: "Seleccionar canal" selectAntenna: "Seleccionar antena" editAntenna: "Editar antena" +createAntenna: "Crear una antena" selectWidget: "Seleccionar widget" editWidgets: "Editar widgets" editWidgetsExit: "Terminar edición" @@ -178,6 +182,10 @@ addAccount: "Agregar Cuenta" reloadAccountsList: "Recargar lista de cuentas" loginFailed: "Error al iniciar sesión." showOnRemote: "Ver en una instancia remota" +continueOnRemote: "Ver en una instancia remota" +chooseServerOnMisskeyHub: "Elegir un servidor en Misskey Hub" +specifyServerHost: "Especifica una instancia directamente" +inputHostName: "Introduzca el dominio" general: "General" wallpaper: "Fondo de pantalla" setWallpaper: "Establecer fondo de pantalla" @@ -494,7 +502,6 @@ uiLanguage: "Idioma de visualización de la interfaz" aboutX: "Acerca de {x}" emojiStyle: "Estilo de emoji" native: "Nativo" -disableDrawer: "No mostrar los menús en cajones" showNoteActionsOnlyHover: "Mostrar acciones de la nota sólo al pasar el cursor" showReactionsCount: "Mostrar el número de reacciones en las notas" noHistory: "No hay datos en el historial" @@ -1095,6 +1102,8 @@ preservedUsernames: "Nombre de usuario reservado" preservedUsernamesDescription: "La lista de nombres de usuario para reservar tienen que separarse con saltos de lÃnea.\nEstos estarán indisponibles durante la creación de cuentas, pero pueden ser usados para que los administradores puedan crear esas cuentas manualmente. Las cuentas existentes con esos nombres de usuario no se verán afectadas." createNoteFromTheFile: "Componer una nota desde éste archivo" archive: "Archivo" +archived: "Archivado" +unarchive: "Desarchivar" channelArchiveConfirmTitle: "¿Seguro de archivar {name}?" channelArchiveConfirmDescription: "Un canal archivado no aparecerá en la lista de canales ni en los resultados. Las nuevas publicaciones tampoco serán añadidas." thisChannelArchived: "El canal ha sido archivado." @@ -2396,6 +2405,8 @@ _abuseReport: _notificationRecipient: _recipientType: mail: "Correo" + webhook: "Webhook" + keywords: "Palabras Clave" _moderationLogTypes: createRole: "Rol creado" deleteRole: "Rol eliminado" @@ -2499,6 +2510,7 @@ _hemisphere: S: "Hemisferio sur" _reversi: reversi: "Reversi" + rules: "Reglas" won: "{name} ha ganado" total: "Total" _urlPreviewSetting: diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index c1e2555d0d..0cf4b65c38 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -493,7 +493,6 @@ uiLanguage: "Langue d’affichage de l’interface" aboutX: "À propos de {x}" emojiStyle: "Style des émojis" native: "Natif" -disableDrawer: "Les menus ne s'affichent pas dans le tiroir" showNoteActionsOnlyHover: "Afficher les actions de note uniquement au survol" showReactionsCount: "Afficher le nombre de réactions des notes" noHistory: "Pas d'historique" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 24f7482fca..55ca9d91ac 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -60,6 +60,7 @@ copyFileId: "Salin Berkas" copyFolderId: "Salin Folder" copyProfileUrl: "Salin Alamat Web Profil" searchUser: "Cari pengguna" +searchThisUsersNotes: "Mencari catatan pengguna" reply: "Balas" loadMore: "Selebihnya" showMore: "Selebihnya" @@ -154,6 +155,7 @@ editList: "Sunting daftar" selectChannel: "Pilih kanal" selectAntenna: "Pilih Antena" editAntenna: "Sunting antena" +createAntenna: "Membuat antena." selectWidget: "Pilih gawit" editWidgets: "Sunting gawit" editWidgetsExit: "Selesai" @@ -502,7 +504,6 @@ uiLanguage: "Bahasa antarmuka pengguna" aboutX: "Tentang {x}" emojiStyle: "Gaya emoji" native: "Native" -disableDrawer: "Jangan gunakan menu bergaya laci" showNoteActionsOnlyHover: "Hanya tampilkan aksi catatan saat ditunjuk" showReactionsCount: "Lihat jumlah reaksi dalam catatan" noHistory: "Tidak ada riwayat" diff --git a/locales/index.d.ts b/locales/index.d.ts index 75e1703b4a..32c5a21648 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -961,6 +961,14 @@ export interface Locale extends ILocale { */ "mediaSilencedInstancesDescription": string; /** + * 連åˆã‚’許å¯ã™ã‚‹ã‚µãƒ¼ãƒãƒ¼ + */ + "federationAllowedHosts": string; + /** + * 連åˆã‚’許å¯ã™ã‚‹ã‚µãƒ¼ãƒãƒ¼ã®ãƒ›ã‚¹ãƒˆã‚’改行ã§åŒºåˆ‡ã£ã¦è¨å®šã—ã¾ã™ã€‚ + */ + "federationAllowedHostsDescription": string; + /** * ミュートã¨ãƒ–ãƒãƒƒã‚¯ */ "muteAndBlock": string; @@ -1353,6 +1361,10 @@ export interface Locale extends ILocale { */ "addFile": string; /** + * ファイルを表示 + */ + "showFile": string; + /** * ドライブã¯ç©ºã§ã™ */ "emptyDrive": string; @@ -2053,9 +2065,21 @@ export interface Locale extends ILocale { */ "native": string; /** - * メニューをドãƒãƒ¯ãƒ¼ã§è¡¨ç¤ºã—ãªã„ + * メニューã®ã‚¹ã‚¿ã‚¤ãƒ« + */ + "menuStyle": string; + /** + * スタイル + */ + "style": string; + /** + * ドãƒãƒ¯ãƒ¼ + */ + "drawer": string; + /** + * ãƒãƒƒãƒ—アップ */ - "disableDrawer": string; + "popup": string; /** * ノートã®ã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã‚’ホãƒãƒ¼æ™‚ã®ã¿è¡¨ç¤ºã™ã‚‹ */ @@ -2385,6 +2409,14 @@ export interface Locale extends ILocale { */ "scratchpadDescription": string; /** + * UIインスペクター + */ + "uiInspector": string; + /** + * メモリ上ã«å˜åœ¨ã—ã¦ã„ã‚‹UIコンãƒãƒ¼ãƒãƒ³ãƒˆã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã®ä¸€è¦§ã‚’見るã“ã¨ãŒã§ãã¾ã™ã€‚UIコンãƒãƒ¼ãƒãƒ³ãƒˆã¯Ui:C:系関数ã«ã‚ˆã‚Šç”Ÿæˆã•れã¾ã™ã€‚ + */ + "uiInspectorDescription": string; + /** * 出力 */ "output": string; @@ -3121,7 +3153,7 @@ export interface Locale extends ILocale { */ "narrow": string; /** - * è¨å®šã¯ãƒšãƒ¼ã‚¸ãƒªãƒãƒ¼ãƒ‰å¾Œã«åæ˜ ã•れã¾ã™ã€‚今ã™ãリãƒãƒ¼ãƒ‰ã—ã¾ã™ã‹ï¼Ÿ + * è¨å®šã¯ãƒšãƒ¼ã‚¸ãƒªãƒãƒ¼ãƒ‰å¾Œã«åæ˜ ã•れã¾ã™ã€‚ */ "reloadToApplySetting": string; /** @@ -5068,6 +5100,54 @@ 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">; + /** + * パスã‚ーã§ãƒã‚°ã‚¤ãƒ³ + */ + "signinWithPasskey": string; + /** + * 登録ã•れã¦ã„ãªã„パスã‚ーã§ã™ã€‚ + */ + "unknownWebAuthnKey": string; + /** + * パスã‚ãƒ¼ã®æ¤œè¨¼ã«å¤±æ•—ã—ã¾ã—ãŸã€‚ + */ + "passkeyVerificationFailed": string; + /** + * パスã‚ãƒ¼ã®æ¤œè¨¼ã«æˆåŠŸã—ã¾ã—ãŸãŒã€ãƒ‘スワードレスãƒã‚°ã‚¤ãƒ³ãŒç„¡åйã«ãªã£ã¦ã„ã¾ã™ã€‚ + */ + "passkeyVerificationSucceededButPasswordlessLoginDisabled": string; "_delivery": { /** * é…信状態 @@ -5560,6 +5640,10 @@ export interface Locale extends ILocale { */ "fanoutTimelineDbFallbackDescription": string; /** + * 有効ã«ã™ã‚‹ã¨ã€ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ä½œæˆæ™‚ã®ãƒ‘フォーマンスãŒå¤§å¹…ã«å‘上ã—ã€ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã¸ã®è² è·ã‚’軽減ã™ã‚‹ã“ã¨ãŒå¯èƒ½ã§ã™ã€‚ãŸã ã—ã€Redisã®ãƒ¡ãƒ¢ãƒªä½¿ç”¨é‡ã¯å¢—åŠ ã—ã¾ã™ã€‚ + */ + "reactionsBufferingDescription": string; + /** * å•ã„åˆã‚ã›å…ˆURL */ "inquiryUrl": string; @@ -6738,6 +6822,26 @@ export interface Locale extends ILocale { * ã‚¢ã‚¤ã‚³ãƒ³ãƒ‡ã‚³ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ã®æœ€å¤§å–付個数 */ "avatarDecorationLimit": string; + /** + * アンテナã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’è¨±å¯ + */ + "canImportAntennas": string; + /** + * ブãƒãƒƒã‚¯ã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’è¨±å¯ + */ + "canImportBlocking": string; + /** + * フォãƒãƒ¼ã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’è¨±å¯ + */ + "canImportFollowing": string; + /** + * ミュートã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’è¨±å¯ + */ + "canImportMuting": string; + /** + * リストã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’è¨±å¯ + */ + "canImportUserLists": string; }; "_condition": { /** @@ -8629,6 +8733,18 @@ export interface Locale extends ILocale { * 最大{max}ã¤ã¾ã§ãƒ‡ã‚³ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ã‚’付ã‘られã¾ã™ã€‚ */ "avatarDecorationMax": ParameterizedString<"max">; + /** + * フォãƒãƒ¼ã•ã‚ŒãŸæ™‚ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ + */ + "followedMessage": string; + /** + * フォãƒãƒ¼ã•ã‚ŒãŸæ™‚ã«ç›¸æ‰‹ã«è¡¨ç¤ºã™ã‚‹çŸã„メッセージをè¨å®šã§ãã¾ã™ã€‚ + */ + "followedMessageDescription": string; + /** + * フォãƒãƒ¼ã‚’承èªåˆ¶ã«ã—ã¦ã„ã‚‹å ´åˆã€ãƒ•ã‚©ãƒãƒ¼ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’許å¯ã—ãŸæ™‚ã«è¡¨ç¤ºã•れã¾ã™ã€‚ + */ + "followedMessageDescriptionForLockedAccount": string; }; "_exportOrImport": { /** @@ -9161,6 +9277,10 @@ export interface Locale extends ILocale { * 通知ã®å±¥æ´ã‚’リセットã™ã‚‹ */ "flushNotification": string; + /** + * {x}ã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆãŒå®Œäº†ã—ã¾ã—㟠+ */ + "exportOfXCompleted": ParameterizedString<"x">; "_types": { /** * ã™ã¹ã¦ @@ -9215,6 +9335,14 @@ export interface Locale extends ILocale { */ "achievementEarned": string; /** + * エクスãƒãƒ¼ãƒˆãŒå®Œäº†ã—㟠+ */ + "exportCompleted": string; + /** + * 通知ã®ãƒ†ã‚¹ãƒˆ + */ + "test": string; + /** * 連æºã‚¢ãƒ—リã‹ã‚‰ã®é€šçŸ¥ */ "app": string; @@ -9461,6 +9589,10 @@ export interface Locale extends ILocale { * Webhookを削除ã—ã¾ã™ã‹ï¼Ÿ */ "deleteConfirm": string; + /** + * スイッãƒã®å³ã«ã‚るボタンをクリックã™ã‚‹ã¨ãƒ€ãƒŸãƒ¼ã®ãƒ‡ãƒ¼ã‚¿ã‚’使用ã—ãŸãƒ†ã‚¹ãƒˆç”¨Webhookã‚’é€ä¿¡ã§ãã¾ã™ã€‚ + */ + "testRemarks": string; }; "_abuseReport": { "_notificationRecipient": { @@ -10192,6 +10324,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/it-IT.yml b/locales/it-IT.yml index 2b4b1e425e..55b612cac5 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -1,6 +1,6 @@ --- _lang_: "Italiano" -headlineMisskey: "Rete collegata tramite note" +headlineMisskey: "Rete collegata tramite Note" introMisskey: "Eccoci! Misskey è un servizio di microblogging decentralizzato, libero e aperto. \n\n📡 Puoi pubblicare «Note» per condividere ciò che sta succedendo o per dire a tutti qualcosa su di te. \n\n👠Puoi reagire inviando emoji rapidi alle «Note» provenienti da altri profili nel Fediverso.\n\n🚀 Esplora un nuovo mondo insieme a noi!" poweredByMisskeyDescription: "{name} è uno dei servizi (chiamati istanze) che utilizzano la piattaforma open source <b>Misskey</b>." monthAndDay: "{day}/{month}" @@ -60,6 +60,7 @@ copyFileId: "Copia ID del file" copyFolderId: "Copia ID della cartella" copyProfileUrl: "Copia URL del profilo" searchUser: "Cerca profilo" +searchThisUsersNotes: "Cerca le sue Note" reply: "Rispondi" loadMore: "Mostra di più" showMore: "Espandi" @@ -154,6 +155,7 @@ editList: "Modifica Lista" selectChannel: "Seleziona canale" selectAntenna: "Scegli un'antenna" editAntenna: "Modifica Antenna" +createAntenna: "Crea Antenna" selectWidget: "Seleziona il riquadro" editWidgets: "Modifica i riquadri" editWidgetsExit: "Conferma le modifiche" @@ -194,6 +196,7 @@ followConfirm: "Vuoi seguire {name}?" proxyAccount: "Profilo proxy" proxyAccountDescription: "Un profilo proxy funziona come follower per i profili remoti, sotto certe condizioni. Ad esempio, quando un profilo locale ne inserisce uno remoto in una lista (senza seguirlo), se nessun altro segue quel profilo remoto, le attività non possono essere distribuite. Dunque, il profilo proxy le seguirà per tutti." host: "Host" +selectSelf: "Segli me" selectUser: "Seleziona profilo" recipient: "Destinatario" annotation: "Annotazione preventiva" @@ -209,6 +212,7 @@ perDay: "giornaliero" stopActivityDelivery: "Interrompi la distribuzione di attività " blockThisInstance: "Bloccare l'istanza" silenceThisInstance: "Silenziare l'istanza" +mediaSilenceThisInstance: "Silenzia i media dell'istanza" operations: "Operazioni" software: "Software" version: "Versione" @@ -230,6 +234,8 @@ blockedInstances: "Istanze bloccate" blockedInstancesDescription: "Elenca le istanze che vuoi bloccare, una per riga. Esse non potranno più interagire con la tua istanza." silencedInstances: "Istanze silenziate" silencedInstancesDescription: "Elenca i nomi host delle istanze che vuoi silenziare. Tutti i profili nelle istanze silenziate vengono trattati come tali. Possono solo inviare richieste di follow e menzionare soltanto i profili locali che seguono. Le istanze bloccate non sono interessate." +mediaSilencedInstances: "Istanze coi media silenziati" +mediaSilencedInstancesDescription: "Elenca i nomi host delle istanze di cui vuoi silenziare i media, uno per riga. Tutti gli allegati dei profili nelle istanze silenziate per via degli allegati espliciti, verranno impostati come tali, le emoji personalizzate non saranno disponibili. Le istanze bloccate sono escluse." muteAndBlock: "Silenziare e bloccare" mutedUsers: "Profili silenziati" blockedUsers: "Profili bloccati" @@ -328,6 +334,7 @@ renameFolder: "Rinomina cartella" deleteFolder: "Elimina cartella" folder: "Cartella" addFile: "Allega" +showFile: "Visualizza file" emptyDrive: "Il Drive è vuoto" emptyFolder: "La cartella è vuota" unableToDelete: "Eliminazione impossibile" @@ -449,7 +456,7 @@ securityKeyAndPasskey: "Chiave di sicurezza e accesso" securityKey: "Chiave di sicurezza" lastUsed: "Ultima attività " lastUsedAt: "Uso più recente: {t}" -unregister: "Annulla l'iscrizione" +unregister: "Rimuovi autenticazione a due fattori (2FA/MFA)" passwordLessLogin: "Accedi senza password" passwordLessLoginDescription: "Accedi senza password, usando la chiave di sicurezza" resetPassword: "Ripristina la password" @@ -503,7 +510,10 @@ uiLanguage: "Lingua di visualizzazione dell'interfaccia" aboutX: "Informazioni su {x}" emojiStyle: "Stile emoji" native: "Nativo" -disableDrawer: "Non mostrare il menù sul drawer" +menuStyle: "Stile menu" +style: "Stile" +drawer: "Drawer" +popup: "Popup" showNoteActionsOnlyHover: "Mostra le azioni delle Note solo al passaggio del mouse" showReactionsCount: "Visualizza il numero di reazioni su una nota" noHistory: "Nessuna cronologia" @@ -559,7 +569,7 @@ deleteAll: "Cancella cronologia" showFixedPostForm: "Visualizzare la finestra di pubblicazione in cima alla timeline" showFixedPostFormInChannel: "Per i canali, mostra il modulo di pubblicazione in cima alla timeline" withRepliesByDefaultForNewlyFollowed: "Quando segui nuovi profili, includi le risposte in TL come impostazione predefinita" -newNoteRecived: "Nuove note da leggere" +newNoteRecived: "Nuove Note da leggere" sounds: "Impostazioni suoni" sound: "Suono" listen: "Ascolta" @@ -586,6 +596,8 @@ ascendingOrder: "Aumenta" descendingOrder: "Diminuisce" scratchpad: "ScratchPad" scratchpadDescription: "Lo Scratchpad offre un ambiente per esperimenti di AiScript. È possibile scrivere, eseguire e confermare i risultati dell'interazione del codice con Misskey." +uiInspector: "UI Inspector" +uiInspectorDescription: "Puoi visualizzare un elenco di elementi UI presenti in memoria. I componenti dell'interfaccia utente vengono generati dalle funzioni Ui:C:." output: "Uscita" script: "Script" disablePagesScript: "Disabilita AiScript nelle pagine" @@ -1106,6 +1118,8 @@ preservedUsernames: "Nomi utente riservati" preservedUsernamesDescription: "Elenca, uno per linea, i nomi utente che non possono essere registrati durante la creazione del profilo. La restrizione non si applica agli amministratori. Inoltre, i profili già registrati sono esenti." createNoteFromTheFile: "Crea Nota da questo file" archive: "Archivio" +archived: "Archiviato" +unarchive: "Annulla archiviazione" channelArchiveConfirmTitle: "Vuoi davvero archiviare {name}?" channelArchiveConfirmDescription: "Un canale archiviato non compare nell'elenco canali, nemmeno nei risultati di ricerca. Non può ricevere nemmeno nuove Note." thisChannelArchived: "Questo canale è stato archiviato." @@ -1116,6 +1130,9 @@ preventAiLearning: "Impedisci l'apprendimento della IA" preventAiLearningDescription: "Aggiungendo il campo \"noai\" alla risposta HTML, si indica ai Robot esterni di non usare testi e allegati per addestrare sistemi di Machine Learning (IA predittiva/generativa). Anche se è impossibile sapere se la richiesta venga onorata o semplicemente ignorata." options: "Opzioni del ruolo" specifyUser: "Profilo specifico" +lookupConfirm: "Vuoi davvero richiedere informazioni?" +openTagPageConfirm: "Vuoi davvero aprire la pagina dell'hashtag?" +specifyHost: "Specifica l'host" failedToPreviewUrl: "Anteprima non disponibile" update: "Aggiorna" rolesThatCanBeUsedThisEmojiAsReaction: "Ruoli che possono usare questa emoji come reazione" @@ -1250,6 +1267,20 @@ inquiry: "Contattaci" tryAgain: "Per favore riprova" confirmWhenRevealingSensitiveMedia: "Richiedi conferma prima di mostrare gli allegati espliciti" sensitiveMediaRevealConfirm: "Questo allegato è esplicito, vuoi vederlo?" +createdLists: "Liste create" +createdAntennas: "Antenne create" +fromX: "Da {x}" +genEmbedCode: "Ottieni il codice di incorporamento" +noteOfThisUser: "Elenco di Note di questo profilo" +clipNoteLimitExceeded: "Non è possibile aggiungere ulteriori Note a questa Clip." +performance: "Prestazioni" +modified: "Modificato" +discard: "Scarta" +thereAreNChanges: "Ci sono {n} cambiamenti" +signinWithPasskey: "Accedi con passkey" +unknownWebAuthnKey: "Questa è una passkey sconosciuta." +passkeyVerificationFailed: "La verifica della passkey non è riuscita." +passkeyVerificationSucceededButPasswordlessLoginDisabled: "La verifica della passkey è riuscita, ma l'accesso senza password è disabilitato." _delivery: status: "Stato della consegna" stop: "Sospensione" @@ -1304,7 +1335,7 @@ _initialAccountSetting: skipAreYouSure: "Vuoi davvero saltare la configurazione iniziale?" laterAreYouSure: "Vuoi davvero rimandare la configurazione iniziale?" _initialTutorial: - launchTutorial: "Guarda il tutorial" + launchTutorial: "Inizia il tutorial" title: "Tutorial" wellDone: "Ottimo lavoro!" skipAreYouSure: "Vuoi davvero interrompere il tutorial?" @@ -1314,13 +1345,13 @@ _initialTutorial: _note: title: "Cosa sono le Note?" description: "Gli status su Misskey sono chiamati \"Note\". Le Note sono elencate in ordine cronologico nelle timeline e vengono aggiornate in tempo reale." - reply: "Puoi rispondere alle Note. Puoi anche rispondere alle risposte e continuare i dialoghi come un conversazioni." - renote: "Puoi ri-condividere le Note, facendole rifluire sulla Timeline. Puoi anche aggiungere testo e citare altri profili." - reaction: "Puoi aggiungere una reazione. Nella pagina successiva spiegheremo i dettagli." - menu: "Puoi svolgere varie attività , come visualizzare i dettagli delle Note o copiare i collegamenti." + reply: "Puoi rispondere alle Note, alle altre risposte e dialogare in conversazioni." + renote: "Puoi ri-condividere le Note, ritorneranno sulla Timeline. Aggiungendo del testo, scriverai una Citazione." + reaction: "Puoi aggiungere una reazione. Nella pagina successiva ti spiego come." + menu: "Per altre attività , ad esempio, vedere i dettagli delle Note o copiare i collegamenti." _reaction: title: "Cosa sono le Reazioni?" - description: "Puoi reagire alle Note. Le sensazioni che non si riescono a trasmettere con i \"Mi piace\" si possono esprimere facilmente inviando una reazione." + description: "Reazioni alle Note. Le sensazioni che non si possono descrivere con \"Mi piace\" si esprimono facilmente con le reazioni." letsTryReacting: "Puoi aggiungere una Reazione cliccando il bottone \"+\" (più) della relativa Nota. Prova ad aggiungerne una a questa Nota di esempio!" reactToContinue: "Aggiungere la Reazione ti consentirà di procedere col tutorial." reactNotification: "Quando qualcuno reagisce alle tue Note, ricevi una notifica in tempo reale." @@ -1333,7 +1364,7 @@ _initialTutorial: social: "sia le Note della Timeline Home che quelle della Timeline Locale, insieme!" global: "le Note da pubblicate da tutte le altre istanze federate con la nostra." description2: "Nella parte superiore dello schermo, puoi scegliere una Timeline o l'altra in qualsiasi momento." - description3: "Ci sono anche sequenze temporali di elenchi, sequenze temporali di canali, ecc. Per ulteriori dettagli, consultare il {link}.\nPuoi vedere anche Timeline delle liste di profili (se ne hai create), canali, ecc... Per i dettagli, visita {link}." + description3: "Ci sono anche sequenze temporali di elenchi, sequenze temporali di canali, ecc. Per ulteriori dettagli, consultare la {link}.\nPuoi vedere anche Timeline delle liste di profili (se ne hai create), canali, ecc... Per i dettagli, c'è la {link}." _postNote: title: "La Nota e le sue impostazioni" description1: "Quando scrivi una Nota su Misskey, hai a disposizione varie opzioni. Il modulo di invio è simile a questo." @@ -1384,6 +1415,7 @@ _serverSettings: fanoutTimelineDescription: "Attivando questa funzionalità migliori notevolmente la capacità delle Timeline di collezionare Note, riducendo il carico sul database. Tuttavia, aumenterà l'impiego di memoria RAM per Redis. Disattiva se il tuo server ha poca RAM o la funzionalità è irregolare." fanoutTimelineDbFallback: "Elaborazione dati alternativa" fanoutTimelineDbFallbackDescription: "Attivando l'elaborazione alternativa, verrà interrogato ulteriormente il database se la timeline non è nella cache. \nDisattivando, si può ridurre ulteriormente il carico del server, evitando l'elaborazione alternativa, ma limitando l'intervallo recuperabile delle timeline." + reactionsBufferingDescription: "Attivando questa opzione, puoi migliorare significativamente le prestazioni durante la creazione delle reazioni e ridurre il carico sul database. Tuttavia, aumenterà l'impiego di memoria Redis." inquiryUrl: "URL di contatto" inquiryUrlDescription: "Specificare l'URL al modulo di contatto, oppure le informazioni con i dati di contatto dell'amministrazione." _accountMigration: @@ -1717,6 +1749,11 @@ _role: canSearchNotes: "Ricercare nelle Note" canUseTranslator: "Tradurre le Note" avatarDecorationLimit: "Numero massimo di decorazioni foto profilo installabili" + canImportAntennas: "Può importare Antenne" + canImportBlocking: "Può importare Blocchi" + canImportFollowing: "Può importare Following" + canImportMuting: "Può importare Silenziati" + canImportUserLists: "Può importare liste di Profili" _condition: roleAssignedTo: "Assegnato a ruoli manualmente" isLocal: "Profilo locale" @@ -1954,6 +1991,7 @@ _soundSettings: driveFileTypeWarnDescription: "Per favore, scegli un file di tipo audio" driveFileDurationWarn: "La durata dell'audio è troppo lunga" driveFileDurationWarnDescription: "Scegliere un audio lungo potrebbe interferire con l'uso di Misskey. Vuoi continuare lo stesso?" + driveFileError: "Impossibile caricare l'audio. Si prega di modificare le impostazioni" _ago: future: "Futuro" justNow: "Adesso" @@ -2302,6 +2340,7 @@ _pages: eyeCatchingImageSet: "Imposta un'immagine attraente" eyeCatchingImageRemove: "Elimina immagine attraente" chooseBlock: "Aggiungi blocco" + enterSectionTitle: "Inserisci il titolo della sezione" selectType: "Seleziona tipo" contentBlocks: "Contenuto" inputBlocks: "Blocchi di input" @@ -2347,6 +2386,7 @@ _notification: renotedBySomeUsers: "{n} Rinota" followedBySomeUsers: "{n} follower" flushNotification: "Azzera le notifiche" + exportOfXCompleted: "Abbiamo completato l'esportazione di {x}" _types: all: "Tutto" note: "Nuove Note" @@ -2361,6 +2401,8 @@ _notification: followRequestAccepted: "Richiesta di follow accettata" roleAssigned: "Ruolo concesso" achievementEarned: "Risultato raggiunto" + exportCompleted: "Esportazione completata" + test: "Prova la notifica" app: "Notifiche da applicazioni" _actions: followBack: "Segui" @@ -2412,6 +2454,7 @@ _webhookSettings: modifyWebhook: "Modifica Webhook" name: "Nome" secret: "Segreto" + trigger: "Trigger" active: "Attivo" _events: follow: "Quando segui un profilo" @@ -2424,7 +2467,9 @@ _webhookSettings: _systemEvents: abuseReport: "Quando arriva una segnalazione" abuseReportResolved: "Quando una segnalazione è risolta" + userCreated: "Quando viene creato un profilo" deleteConfirm: "Vuoi davvero eliminare il Webhook?" + testRemarks: "Clicca il bottone a destra dell'interruttore, per provare l'invio di un webhook con dati fittizi." _abuseReport: _notificationRecipient: createRecipient: "Aggiungi destinatario della segnalazione" @@ -2483,6 +2528,10 @@ _moderationLogTypes: createAbuseReportNotificationRecipient: "Crea destinatario per le notifiche di segnalazioni" updateAbuseReportNotificationRecipient: "Aggiorna destinatario notifiche di segnalazioni" deleteAbuseReportNotificationRecipient: "Elimina destinatario notifiche di segnalazioni" + deleteAccount: "Quando viene eliminato un profilo" + deletePage: "Pagina eliminata" + deleteFlash: "Play eliminato" + deleteGalleryPost: "Eliminazione pubblicazione nella Galleria" _fileViewer: title: "Dettagli del file" type: "Tipo di file" @@ -2614,3 +2663,22 @@ _mediaControls: pip: "Sovraimpressione" playbackRate: "Velocità di riproduzione" loop: "Ripetizione infinita" +_contextMenu: + title: "Menu contestuale" + app: "Applicazione" + appWithShift: "Applicazione Shift+Tasto" + native: "Interfaccia utente del browser" +_embedCodeGen: + title: "Personalizza il codice di incorporamento" + header: "Mostra la testata" + autoload: "Carica automaticamente di più (sconsigliato)" + maxHeight: "Altezza massima" + maxHeightDescription: "Specifica un valore per evitare che continui a crescere verticalmente. Il valore 0 disabilita il limite d'altezza." + maxHeightWarn: "L'altezza massima è disabilitata (0). Se l'effetto è indesiderato, prova a impostare l'altezza massima a un valore specifico." + previewIsNotActual: "Poiché supera l'intervallo che può essere visualizzato in anteprima, la visualizzazione vera e propria sarà diversa quando effettivamente incorporata." + rounded: "Bordo arrotondato" + border: "Aggiungi un bordo al contenitore" + applyToPreview: "Applica all'anteprima" + generateCode: "Crea il codice di incorporamento" + codeGenerated: "Codice generato" + codeGeneratedDescription: "Incolla il codice appena generato sul tuo sito web." diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 98e3cbfa41..eebc4c995f 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -236,6 +236,8 @@ silencedInstances: "サイレンスã—ãŸã‚µãƒ¼ãƒãƒ¼" silencedInstancesDescription: "サイレンスã—ãŸã„サーãƒãƒ¼ã®ãƒ›ã‚¹ãƒˆã‚’改行ã§åŒºåˆ‡ã£ã¦è¨å®šã—ã¾ã™ã€‚サイレンスã•れãŸã‚µãƒ¼ãƒãƒ¼ã«æ‰€å±žã™ã‚‹ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯ã™ã¹ã¦ã€Œã‚µã‚¤ãƒ¬ãƒ³ã‚¹ã€ã¨ã—ã¦æ‰±ã‚れã€ãƒ•ã‚©ãƒãƒ¼ãŒã™ã¹ã¦ãƒªã‚¯ã‚¨ã‚¹ãƒˆã«ãªã‚Šã¾ã™ã€‚ブãƒãƒƒã‚¯ã—ãŸã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã«ã¯å½±éŸ¿ã—ã¾ã›ã‚“。" mediaSilencedInstances: "メディアサイレンスã—ãŸã‚µãƒ¼ãƒãƒ¼" mediaSilencedInstancesDescription: "メディアサイレンスã—ãŸã„サーãƒãƒ¼ã®ãƒ›ã‚¹ãƒˆã‚’改行ã§åŒºåˆ‡ã£ã¦è¨å®šã—ã¾ã™ã€‚メディアサイレンスã•れãŸã‚µãƒ¼ãƒãƒ¼ã«æ‰€å±žã™ã‚‹ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«ã‚ˆã‚‹ãƒ•ァイルã¯ã™ã¹ã¦ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ã¨ã—ã¦æ‰±ã‚れã€ã‚«ã‚¹ã‚¿ãƒ 絵文å—ãŒä½¿ç”¨ã§ããªã„よã†ã«ãªã‚Šã¾ã™ã€‚ブãƒãƒƒã‚¯ã—ãŸã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã«ã¯å½±éŸ¿ã—ã¾ã›ã‚“。" +federationAllowedHosts: "連åˆã‚’許å¯ã™ã‚‹ã‚µãƒ¼ãƒãƒ¼" +federationAllowedHostsDescription: "連åˆã‚’許å¯ã™ã‚‹ã‚µãƒ¼ãƒãƒ¼ã®ãƒ›ã‚¹ãƒˆã‚’改行ã§åŒºåˆ‡ã£ã¦è¨å®šã—ã¾ã™ã€‚" muteAndBlock: "ミュートã¨ãƒ–ãƒãƒƒã‚¯" mutedUsers: "ミュートã—ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼" blockedUsers: "ブãƒãƒƒã‚¯ã—ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼" @@ -334,6 +336,7 @@ renameFolder: "フォルダーåを変更" deleteFolder: "フォルダーを削除" folder: "フォルダー" addFile: "ãƒ•ã‚¡ã‚¤ãƒ«ã‚’è¿½åŠ " +showFile: "ファイルを表示" emptyDrive: "ドライブã¯ç©ºã§ã™" emptyFolder: "フォルダーã¯ç©ºã§ã™" unableToDelete: "削除ã§ãã¾ã›ã‚“" @@ -509,7 +512,10 @@ uiLanguage: "UIã®è¡¨ç¤ºè¨€èªž" aboutX: "{x}ã«ã¤ã„ã¦" emojiStyle: "絵文å—ã®ã‚¹ã‚¿ã‚¤ãƒ«" native: "ãƒã‚¤ãƒ†ã‚£ãƒ–" -disableDrawer: "メニューをドãƒãƒ¯ãƒ¼ã§è¡¨ç¤ºã—ãªã„" +menuStyle: "メニューã®ã‚¹ã‚¿ã‚¤ãƒ«" +style: "スタイル" +drawer: "ドãƒãƒ¯ãƒ¼" +popup: "ãƒãƒƒãƒ—アップ" showNoteActionsOnlyHover: "ノートã®ã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã‚’ホãƒãƒ¼æ™‚ã®ã¿è¡¨ç¤ºã™ã‚‹" showReactionsCount: "ノートã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³æ•°ã‚’表示ã™ã‚‹" noHistory: "å±¥æ´ã¯ã‚りã¾ã›ã‚“" @@ -592,6 +598,8 @@ ascendingOrder: "æ˜‡é †" descendingOrder: "é™é †" scratchpad: "スクラッãƒãƒ‘ッド" scratchpadDescription: "スクラッãƒãƒ‘ッドã¯ã€AiScriptã®å®Ÿé¨“環境をæä¾›ã—ã¾ã™ã€‚Misskeyã¨å¯¾è©±ã™ã‚‹ã‚³ãƒ¼ãƒ‰ã®è¨˜è¿°ã€å®Ÿè¡Œã€çµæžœã®ç¢ºèªãŒã§ãã¾ã™ã€‚" +uiInspector: "UIインスペクター" +uiInspectorDescription: "メモリ上ã«å˜åœ¨ã—ã¦ã„ã‚‹UIコンãƒãƒ¼ãƒãƒ³ãƒˆã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã®ä¸€è¦§ã‚’見るã“ã¨ãŒã§ãã¾ã™ã€‚UIコンãƒãƒ¼ãƒãƒ³ãƒˆã¯Ui:C:系関数ã«ã‚ˆã‚Šç”Ÿæˆã•れã¾ã™ã€‚" output: "出力" script: "スクリプト" disablePagesScript: "Pagesã®ã‚¹ã‚¯ãƒªãƒ—トを無効ã«ã™ã‚‹" @@ -776,7 +784,7 @@ left: "å·¦" center: "ä¸å¤®" wide: "広ã„" narrow: "ç‹ã„" -reloadToApplySetting: "è¨å®šã¯ãƒšãƒ¼ã‚¸ãƒªãƒãƒ¼ãƒ‰å¾Œã«åæ˜ ã•れã¾ã™ã€‚今ã™ãリãƒãƒ¼ãƒ‰ã—ã¾ã™ã‹ï¼Ÿ" +reloadToApplySetting: "è¨å®šã¯ãƒšãƒ¼ã‚¸ãƒªãƒãƒ¼ãƒ‰å¾Œã«åæ˜ ã•れã¾ã™ã€‚" needReloadToApply: "åæ˜ ã«ã¯å†èµ·å‹•ãŒå¿…è¦ã§ã™ã€‚" showTitlebar: "タイトルãƒãƒ¼ã‚’表示ã™ã‚‹" clearCache: "ã‚ャッシュをクリア" @@ -1263,6 +1271,18 @@ confirmWhenRevealingSensitiveMedia: "センシティブãªãƒ¡ãƒ‡ã‚£ã‚¢ã‚’表示ã sensitiveMediaRevealConfirm: "センシティブãªãƒ¡ãƒ‡ã‚£ã‚¢ã§ã™ã€‚表示ã—ã¾ã™ã‹ï¼Ÿ" createdLists: "作æˆã—ãŸãƒªã‚¹ãƒˆ" createdAntennas: "作æˆã—ãŸã‚¢ãƒ³ãƒ†ãƒŠ" +fromX: "{x}ã‹ã‚‰" +genEmbedCode: "埋ã‚è¾¼ã¿ã‚³ãƒ¼ãƒ‰ã‚’生æˆ" +noteOfThisUser: "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒŽãƒ¼ãƒˆä¸€è¦§" +clipNoteLimitExceeded: "ã“れ以上ã“ã®ã‚¯ãƒªãƒƒãƒ—ã«ãƒŽãƒ¼ãƒˆã‚’è¿½åŠ ã§ãã¾ã›ã‚“。" +performance: "パフォーマンス" +modified: "変更ã‚り" +discard: "ç ´æ£„" +thereAreNChanges: "{n}ä»¶ã®å¤‰æ›´ãŒã‚りã¾ã™" +signinWithPasskey: "パスã‚ーã§ãƒã‚°ã‚¤ãƒ³" +unknownWebAuthnKey: "登録ã•れã¦ã„ãªã„パスã‚ーã§ã™ã€‚" +passkeyVerificationFailed: "パスã‚ãƒ¼ã®æ¤œè¨¼ã«å¤±æ•—ã—ã¾ã—ãŸã€‚" +passkeyVerificationSucceededButPasswordlessLoginDisabled: "パスã‚ãƒ¼ã®æ¤œè¨¼ã«æˆåŠŸã—ã¾ã—ãŸãŒã€ãƒ‘スワードレスãƒã‚°ã‚¤ãƒ³ãŒç„¡åйã«ãªã£ã¦ã„ã¾ã™ã€‚" _delivery: status: "é…信状態" @@ -1405,6 +1425,7 @@ _serverSettings: fanoutTimelineDescription: "有効ã«ã™ã‚‹ã¨ã€å„種タイムラインをå–å¾—ã™ã‚‹éš›ã®ãƒ‘フォーマンスãŒå¤§å¹…ã«å‘上ã—ã€ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã¸ã®è² è·ã‚’軽減ã™ã‚‹ã“ã¨ãŒå¯èƒ½ã§ã™ã€‚ãŸã ã—ã€Redisã®ãƒ¡ãƒ¢ãƒªä½¿ç”¨é‡ã¯å¢—åŠ ã—ã¾ã™ã€‚サーãƒãƒ¼ã®ãƒ¡ãƒ¢ãƒªå®¹é‡ãŒå°‘ãªã„å ´åˆã€ã¾ãŸã¯å‹•作ãŒä¸å®‰å®šãªå ´åˆã¯ç„¡åйã«ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚" fanoutTimelineDbFallback: "データベースã¸ã®ãƒ•ォールãƒãƒƒã‚¯" fanoutTimelineDbFallbackDescription: "有効ã«ã™ã‚‹ã¨ã€ã‚¿ã‚¤ãƒ ラインãŒã‚ャッシュã•れã¦ã„ãªã„å ´åˆã«DBã¸è¿½åŠ ã§å•ã„åˆã‚ã›ã‚’行ã†ãƒ•ォールãƒãƒƒã‚¯å‡¦ç†ã‚’行ã„ã¾ã™ã€‚無効ã«ã™ã‚‹ã¨ã€ãƒ•ォールãƒãƒƒã‚¯å‡¦ç†ã‚’行ã‚ãªã„ã“ã¨ã§ã•らã«ã‚µãƒ¼ãƒãƒ¼ã®è² è·ã‚’軽減ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ãŒã€ã‚¿ã‚¤ãƒ ラインãŒå–å¾—ã§ãる範囲ã«åˆ¶é™ãŒç”Ÿã˜ã¾ã™ã€‚" + reactionsBufferingDescription: "有効ã«ã™ã‚‹ã¨ã€ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ä½œæˆæ™‚ã®ãƒ‘フォーマンスãŒå¤§å¹…ã«å‘上ã—ã€ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã¸ã®è² è·ã‚’軽減ã™ã‚‹ã“ã¨ãŒå¯èƒ½ã§ã™ã€‚ãŸã ã—ã€Redisã®ãƒ¡ãƒ¢ãƒªä½¿ç”¨é‡ã¯å¢—åŠ ã—ã¾ã™ã€‚" inquiryUrl: "å•ã„åˆã‚ã›å…ˆURL" inquiryUrlDescription: "サーãƒãƒ¼é‹å–¶è€…ã¸ã®ãŠå•ã„åˆã‚ã›ãƒ•ォームã®URLã‚„ã€é‹å–¶è€…ã®é€£çµ¡å…ˆç‰ãŒè¨˜è¼‰ã•れãŸWebページã®URLを指定ã—ã¾ã™ã€‚" @@ -1741,6 +1762,11 @@ _role: canSearchNotes: "ノート検索ã®åˆ©ç”¨" canUseTranslator: "翻訳機能ã®åˆ©ç”¨" avatarDecorationLimit: "ã‚¢ã‚¤ã‚³ãƒ³ãƒ‡ã‚³ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ã®æœ€å¤§å–付個数" + canImportAntennas: "アンテナã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’許å¯" + canImportBlocking: "ブãƒãƒƒã‚¯ã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’許å¯" + canImportFollowing: "フォãƒãƒ¼ã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’許å¯" + canImportMuting: "ミュートã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’許å¯" + canImportUserLists: "リストã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’許å¯" _condition: roleAssignedTo: "マニュアルãƒãƒ¼ãƒ«ã«ã‚¢ã‚µã‚¤ãƒ³æ¸ˆã¿" isLocal: "ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼" @@ -2273,6 +2299,9 @@ _profile: changeBanner: "ãƒãƒŠãƒ¼ç”»åƒã‚’変更" verifiedLinkDescription: "内容ã«URLã‚’è¨å®šã™ã‚‹ã¨ã€ãƒªãƒ³ã‚¯å…ˆã®Webサイトã«è‡ªåˆ†ã®ãƒ—ãƒãƒ•ィールã¸ã®ãƒªãƒ³ã‚¯ãŒå«ã¾ã‚Œã¦ã„ã‚‹å ´åˆã«æ‰€æœ‰è€…ç¢ºèªæ¸ˆã¿ã‚¢ã‚¤ã‚³ãƒ³ã‚’表示ã•ã›ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚" avatarDecorationMax: "最大{max}ã¤ã¾ã§ãƒ‡ã‚³ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ã‚’付ã‘られã¾ã™ã€‚" + followedMessage: "フォãƒãƒ¼ã•ã‚ŒãŸæ™‚ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸" + followedMessageDescription: "フォãƒãƒ¼ã•ã‚ŒãŸæ™‚ã«ç›¸æ‰‹ã«è¡¨ç¤ºã™ã‚‹çŸã„メッセージをè¨å®šã§ãã¾ã™ã€‚" + followedMessageDescriptionForLockedAccount: "フォãƒãƒ¼ã‚’承èªåˆ¶ã«ã—ã¦ã„ã‚‹å ´åˆã€ãƒ•ã‚©ãƒãƒ¼ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’許å¯ã—ãŸæ™‚ã«è¡¨ç¤ºã•れã¾ã™ã€‚" _exportOrImport: allNotes: "å…¨ã¦ã®ãƒŽãƒ¼ãƒˆ" @@ -2420,6 +2449,7 @@ _notification: renotedBySomeUsers: "{n}人ãŒãƒªãƒŽãƒ¼ãƒˆã—ã¾ã—ãŸ" followedBySomeUsers: "{n}人ã«ãƒ•ã‚©ãƒãƒ¼ã•れã¾ã—ãŸ" flushNotification: "通知ã®å±¥æ´ã‚’リセットã™ã‚‹" + exportOfXCompleted: "{x}ã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆãŒå®Œäº†ã—ã¾ã—ãŸ" _types: all: "ã™ã¹ã¦" @@ -2435,6 +2465,8 @@ _notification: followRequestAccepted: "フォãƒãƒ¼ãŒå—ç†ã•れãŸ" roleAssigned: "ãƒãƒ¼ãƒ«ãŒä»˜ä¸Žã•れãŸ" achievementEarned: "実績ã®ç²å¾—" + exportCompleted: "エクスãƒãƒ¼ãƒˆãŒå®Œäº†ã—ãŸ" + test: "通知ã®ãƒ†ã‚¹ãƒˆ" app: "連æºã‚¢ãƒ—リã‹ã‚‰ã®é€šçŸ¥" _actions: @@ -2508,6 +2540,7 @@ _webhookSettings: abuseReportResolved: "ユーザーã‹ã‚‰ã®é€šå ±ã‚’処ç†ã—ãŸã¨ã" userCreated: "ユーザーãŒä½œæˆã•れãŸã¨ã" deleteConfirm: "Webhookを削除ã—ã¾ã™ã‹ï¼Ÿ" + testRemarks: "スイッãƒã®å³ã«ã‚るボタンをクリックã™ã‚‹ã¨ãƒ€ãƒŸãƒ¼ã®ãƒ‡ãƒ¼ã‚¿ã‚’使用ã—ãŸãƒ†ã‚¹ãƒˆç”¨Webhookã‚’é€ä¿¡ã§ãã¾ã™ã€‚" _abuseReport: _notificationRecipient: @@ -2717,3 +2750,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/locales/ja-KS.yml b/locales/ja-KS.yml index 98045b43ac..660fa38e38 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -509,7 +509,6 @@ uiLanguage: "UIã®è¡¨ç¤ºè¨€èªž" aboutX: "{x}ã«ã¤ã„ã¦" emojiStyle: "絵文å—ã®ã‚¹ã‚¿ã‚¤ãƒ«" native: "ãƒã‚¤ãƒ†ã‚£ãƒ–" -disableDrawer: "メニューをドãƒãƒ¯ãƒ¼ã§è¡¨ç¤ºã›ãˆã¸ã‚“" showNoteActionsOnlyHover: "ãƒŽãƒ¼ãƒˆã®æ“作部をホãƒãƒ¼æ™‚ã®ã¿è¡¨ç¤ºã™ã‚‹ã§" showReactionsCount: "ノートã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³æ•°ã‚’表示ã™ã‚‹" noHistory: "å±¥æ´ã¯ãªã„ã‚。" diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml index 9323ed2a26..082140f2e9 100644 --- a/locales/ko-GS.yml +++ b/locales/ko-GS.yml @@ -476,7 +476,6 @@ uiLanguage: "UI 표시 언어" aboutX: "{x}ì— ëŒ€í•´ì„œ" emojiStyle: "ì´ëª¨ì§€ 모양" native: "기본" -disableDrawer: "드로어 메뉴 ì“°ì§€ 않기" showNoteActionsOnlyHover: "마우스 ì˜¬ë§€ì„ ë•Œë§Œ 노트 ì•¡ì…˜ 버턴 ë³´ì´ê¸°" noHistory: "기ë¡ì´ ì—†ì‹ë‹ˆë‹¤" signinHistory: "ë¡œê·¸ì¸ ê¸°ë¡" @@ -583,6 +582,9 @@ describeFile: "캡션 옇기" enterFileDescription: "캡션 서기" author: "ë§¨ë˜ ì‚¬ëžŒ" manage: "간리" +large: "커게" +medium: "엔갆게" +small: "쪼맪게" emailServer: "ì „ìžìš°íŽœ 서버" email: "ì „ìžìš°íŽœ" emailAddress: "ì „ìžìš°íŽœ 주소" @@ -600,6 +602,7 @@ reporter: "ì‹ ê³ í•œ 사람" reporteeOrigin: "ì‹ ê³ ë´ ì‚¬ëžŒ" reporterOrigin: "ì‹ ê³ í•œ ê³³" forwardReport: "ì›¬ê² ì„œë²„ì— ì‹ ê³ ë³´ë‚´ê¸°" +forwardReportIsAnonymous: "ì›¬ê² ì„œë²„ì„œëŠ” 나으 ì •ë³´ëŸ´ 몬 ë³´ê³ ìµë©©ìœ¼ 시스템 ê²Œì •ì–´ë¡œ 보입니다." waitingFor: "{x}(ì–¼)럴 ì§€ë‹¬ë¦¬ê³ ìž‡ì‹ë‹ˆë‹¤" random: "무작ì´" system: "시스템" @@ -613,12 +616,14 @@ followersCount: "팔로워 수" noteFavoritesCount: "질겨찾기한 노트 수" clips: "í´ë¦½ 맨걸기" clearCache: "ìºì‹œ 비우기" +nUsers: "{n} 사용ìž" typingUsers: "{users} ë‹˜ì´ ì„œê³ ìž‡ì–´ì˜ˆ" unlikeConfirm: "좋네예럴 무룹니꺼?" info: "ì •ë³´" selectAccount: "ê³„ì • 개리기" user: "사용ìž" administration: "간리" +middle: "엔갆게" translatedFrom: "{x}서 번옉" on: "í‚´" off: "껌" @@ -633,6 +638,7 @@ oneMonth: "한 달" file: "파ì¼" typeToConfirm: "게ì†í• ë¼ë¨¼ {x}럴 ëˆ„ì§ˆë¼ ì£¼ì´ì†Œ" pleaseSelect: "개리 주ì´ì†Œ" +remoteOnly: "웬ê²ë§Œ" tools: "ë„구" like: "좋네예!" unlike: "좋네예 무루기" @@ -643,7 +649,10 @@ role: "ì˜‰í• " noRole: "ì˜‰í• ì´ á„‹á…¥á‡ì‹ë‹ˆë‹¤" thisPostMayBeAnnoyingCancel: "ì•„ì´ì˜ˆ" likeOnly: "좋네예마" +hiddenTags: "수ᇚ훈 해시태그" myClips: "ë‚´ í´ë¦½" +preservedUsernames: "예약 ì‚¬ìš©ìž ì´ëŸ¼" +specifyUser: "ì‚¬ìš©ìž ì§€ì •" icon: "아바타" replies: "답하기" renotes: "리노트" @@ -709,6 +718,16 @@ _achievements: description: "0ë¶„ 0ì´ˆì— ë…¸íŠ¸ë¥¼ 섰어예" _tutorialCompleted: description: "길ë¼ìž¡ì´ëŸ´ 껕냇ì‹ë‹ˆë‹¤" +_role: + displayOrder: "보기 순서" + _priority: + middle: "엔갆게" + _options: + canHideAds: "ê°•ê³ á„‰á…®á‡ší›„ê¸°" + _condition: + isRemote: "ì›¬ê² ì‚¬ìš©ìž" + isCat: "ê°±ì´ ì‚¬ìš©ìž" + isBot: "ìžë™ 사용ìž" _gallery: my: "ë‚´ 걸" liked: "좋네예한 걸" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 34c1cc3ebf..f737a74d5d 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -52,14 +52,15 @@ deleteAndEditConfirm: "ì´ ë…¸íŠ¸ë¥¼ ì‚ì œí•œ ë’¤ 다시 íŽ¸ì§‘í•˜ì‹œê² ìŠµë‹ˆê addToList: "ë¦¬ìŠ¤íŠ¸ì— ì¶”ê°€" addToAntenna: "ì•ˆí…Œë‚˜ì— ì¶”ê°€" sendMessage: "메시지 보내기" -copyRSS: "RSS 주소 복사" +copyRSS: "RSS 복사" copyUsername: "ìœ ì €ëª… 복사" copyUserId: "ìœ ì € ID 복사" copyNoteId: "노트 ID 복사" copyFileId: "íŒŒì¼ ID 복사" copyFolderId: "í´ë” ID 복사" copyProfileUrl: "프로필 URL 복사" -searchUser: "ìœ ì € 검색" +searchUser: "ì‚¬ìš©ìž ê²€ìƒ‰" +searchThisUsersNotes: "사용ìžì˜ 노트 검색" reply: "답글" loadMore: "ë” ë³´ê¸°" showMore: "ë” ë³´ê¸°" @@ -154,6 +155,7 @@ editList: "리스트 편집" selectChannel: "ì±„ë„ ì„ íƒ" selectAntenna: "안테나 ì„ íƒ" editAntenna: "안테나 편집" +createAntenna: "안테나 만들기" selectWidget: "ìœ„ì ¯ ì„ íƒ" editWidgets: "ìœ„ì ¯ 편집" editWidgetsExit: "편집 종료" @@ -194,6 +196,7 @@ followConfirm: "{name}ë‹˜ì„ íŒ”ë¡œìš° í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" proxyAccount: "프ë¡ì‹œ ê³„ì •" proxyAccountDescription: "프ë¡ì‹œ ê³„ì •ì€ íŠ¹ì • ì¡°ê±´ 하ì—서 ìœ ì €ì˜ ë¦¬ëª¨íŠ¸ 팔로우를 대행하는 ê³„ì •ìž…ë‹ˆë‹¤. 예를 들면, ìœ ì €ê°€ 리모트 ìœ ì €ë¥¼ ë¦¬ìŠ¤íŠ¸ì— ë„£ì—ˆì„ ë•Œ, ë¦¬ìŠ¤íŠ¸ì— ë“¤ì–´ê°„ ìœ ì €ë¥¼ ì•„ë¬´ë„ íŒ”ë¡œìš°í•œ ì ì´ ì—†ë‹¤ë©´ 액티비티가 서버로 배달ë˜ì§€ 않기 때문ì—, ëŒ€ì‹ í”„ë¡ì‹œ ê³„ì •ì´ í•´ë‹¹ ìœ ì €ë¥¼ 팔로우하ë„ë¡ í•©ë‹ˆë‹¤." host: "호스트" +selectSelf: "본ì¸ì„ ì„ íƒ" selectUser: "ìœ ì € ì„ íƒ" recipient: "ìˆ˜ì‹ ì¸" annotation: "ë‚´ìš©ì— ëŒ€í•œ 주ì„" @@ -209,6 +212,7 @@ perDay: "1ì¼ë§ˆë‹¤" stopActivityDelivery: "액티비티 ë³´ë‚´ì§€ 않기" blockThisInstance: "ì´ ì„œë²„ë¥¼ 차단" silenceThisInstance: "서버를 사ì¼ëŸ°ìФ" +mediaSilenceThisInstance: "ì„œë²„ì˜ ë¯¸ë””ì–´ë¥¼ 사ì¼ëŸ°ìФ" operations: "작업" software: "소프트웨어" version: "ë²„ì „" @@ -230,6 +234,10 @@ blockedInstances: "ì°¨ë‹¨ëœ ì„œë²„" blockedInstancesDescription: "ì°¨ë‹¨í•˜ë ¤ëŠ” ì„œë²„ì˜ í˜¸ìŠ¤íŠ¸ ì´ë¦„ì„ ì¤„ë°”ê¿ˆìœ¼ë¡œ 구분하여 ì„¤ì •í•©ë‹ˆë‹¤. ì°¨ë‹¨ëœ ì¸ìŠ¤í„´ìŠ¤ëŠ” ì´ ì¸ìŠ¤í„´ìŠ¤ì™€ í†µì‹ í• ìˆ˜ 없게 ë©ë‹ˆë‹¤." silencedInstances: "사ì¼ëŸ°ìŠ¤í•œ 서버" silencedInstancesDescription: "사ì¼ëŸ°ìŠ¤í•˜ë ¤ëŠ” ì„œë²„ì˜ í˜¸ìŠ¤íŠ¸ëª…ì„ í•œ ì¤„ì— í•˜ë‚˜ì”© ìž…ë ¥í•©ë‹ˆë‹¤. 사ì¼ëŸ°ìŠ¤ëœ ì„œë²„ì— ì†Œì†ëœ ìœ ì €ëŠ” ëª¨ë‘ '사ì¼ëŸ°ìФ'ëœ ìƒíƒœë¡œ 취급ë˜ë©°, ì´ ì„œë²„ë¡œë¶€í„°ì˜ íŒ”ë¡œìš°ê°€ 프로필 ì„¤ì •ê³¼ 무관하게 승ì¸ì œë¡œ 변경ë˜ê³ , 팔로워가 아닌 로컬 ìœ ì €ì—게는 ë©˜ì…˜í• ìˆ˜ 없게 ë©ë‹ˆë‹¤. ì •ì§€ëœ ì„œë²„ì—는 ì ìš©ë˜ì§€ 않습니다." +mediaSilencedInstances: "미디어를 사ì¼ëŸ°ìŠ¤í•œ 서버" +mediaSilencedInstancesDescription: "미디어를 사ì¼ëŸ°ìФ í•˜ë ¤ëŠ” ì„œë²„ì˜ í˜¸ìŠ¤íŠ¸ë¥¼ 한 ì¤„ì— í•˜ë‚˜ì”© ìž…ë ¥í•©ë‹ˆë‹¤. 미디어가 사ì¼ëŸ°ìŠ¤ëœ ì„œë²„ì˜ ìœ ì €ê°€ 업로드한 파ì¼ì€ ëª¨ë‘ ë¯¼ê°í•œ 미디어로 처리ë˜ë©°, 커스텀 ì´ëª¨ì§€ë¥¼ ì‚¬ìš©í• ìˆ˜ 없게 ë©ë‹ˆë‹¤. ë˜í•œ, 차단한 ì¸ìŠ¤í„´ìŠ¤ì—는 ì ìš©ë˜ì§€ 않습니다." +federationAllowedHosts: "ì—°í•©ì„ í—ˆê°€í•˜ëŠ” 서버" +federationAllowedHostsDescription: "ì—°í•©ì„ í—ˆê°€í•˜ëŠ” ì„œë²„ì˜ í˜¸ìŠ¤íŠ¸ë¥¼ 엔터로 구분해서 ì„¤ì •í•©ë‹ˆë‹¤." muteAndBlock: "뮤트 ë° ì°¨ë‹¨" mutedUsers: "뮤트한 ìœ ì €" blockedUsers: "차단한 ìœ ì €" @@ -328,6 +336,7 @@ renameFolder: "í´ë” ì´ë¦„ 바꾸기" deleteFolder: "í´ë” ì‚ì œ" folder: "í´ë”" addFile: "íŒŒì¼ ì¶”ê°€" +showFile: "íŒŒì¼ í‘œì‹œí•˜ê¸°" emptyDrive: "드ë¼ì´ë¸Œê°€ 비어 있습니다" emptyFolder: "í´ë”ê°€ 비어 있습니다" unableToDelete: "ì‚ì œí• ìˆ˜ 없습니다" @@ -373,7 +382,7 @@ registration: "등ë¡" enableRegistration: "ì‹ ê·œ 회ì›ê°€ìž…ì„ í™œì„±í™”" invite: "초대" driveCapacityPerLocalAccount: "로컬 ìœ ì € 한 명당 드ë¼ì´ë¸Œ 용량" -driveCapacityPerRemoteAccount: "리모트 ìœ ì € 한 명당 드ë¼ì´ë¸Œ 용량" +driveCapacityPerRemoteAccount: "ì›ê²© 사용ìžë³„ 드ë¼ì´ë¸Œ 용량" inMb: "메가바ì´íЏ 단위" bannerUrl: "배너 ì´ë¯¸ì§€ URL" backgroundImageUrl: "ë°°ê²½ ì´ë¯¸ì§€ URL" @@ -503,7 +512,10 @@ uiLanguage: "UI 표시 언어" aboutX: "{x}ì— ëŒ€í•˜ì—¬" emojiStyle: "ì´ëª¨ì§€ 스타ì¼" native: "기본" -disableDrawer: "드로어 메뉴를 사용하지 않기" +menuStyle: "메뉴 스타ì¼" +style: "스타ì¼" +drawer: "서ëž" +popup: "íŒì—…" showNoteActionsOnlyHover: "마우스가 올ë¼ê°„ 때ì—ë§Œ 노트 ë™ìž‘ ë²„íŠ¼ì„ í‘œì‹œí•˜ê¸°" showReactionsCount: "ë…¸íŠ¸ì˜ ë°˜ì‘ ìˆ˜ë¥¼ 표시하기" noHistory: "기ë¡ì´ 없습니다" @@ -586,6 +598,8 @@ ascendingOrder: "오름차순" descendingOrder: "내림차순" scratchpad: "스í¬ëž˜ì¹˜ 패드" scratchpadDescription: "스í¬ëž˜ì¹˜ 패드는 AiScript ì˜ í…ŒìŠ¤íŠ¸ í™˜ê²½ì„ ì œê³µí•©ë‹ˆë‹¤. Misskey 와 ìƒí˜¸ 작용하는 코드를 작성, 실행 ë° ê²°ê³¼ë¥¼ 확ì¸í• 수 있습니다." +uiInspector: "UI ì¸ìŠ¤íŽ™í„°" +uiInspectorDescription: "ë©”ëª¨ë¦¬ì— ìžˆëŠ” UI ì»´í¬ë„ŒíŠ¸ì˜ ì¸ìŠ¤í„´íŠ¸ 목ë¡ì„ ë³¼ 수 있습니다. UI ì»´í¬ë„ŒíŠ¸ëŠ” Ui:C: 계열 함수로 만들어집니다." output: "ì¶œë ¥" script: "스í¬ë¦½íЏ" disablePagesScript: "Pages ì—서 AiScript 를 사용하지 않ìŒ" @@ -1106,6 +1120,8 @@ preservedUsernames: "예약한 ì‚¬ìš©ìž ì´ë¦„" preservedUsernamesDescription: "ì˜ˆì•½í• ì‚¬ìš©ìžëª…ì„ í•œ ì¤„ì— í•˜ë‚˜ì”© ìž…ë ¥í•©ë‹ˆë‹¤. 여기ì—서 ì§€ì •í•œ 사용ìžëª…으로는 ê³„ì •ì„ ìƒì„±í• 수 없게 ë©ë‹ˆë‹¤. 단, ê´€ë¦¬ìž ê¶Œí•œìœ¼ë¡œ ê³„ì •ì„ ìƒì„±í• 때ì—는 해당ë˜ì§€ 않으며, ì´ë¯¸ 존재하는 ê³„ì •ë„ ì˜í–¥ì„ 받지 않습니다." createNoteFromTheFile: "ì´ íŒŒì¼ë¡œ 노트를 작성" archive: "ì•„ì¹´ì´ë¸Œ" +archived: "ë³´ê´€ë¨" +unarchive: "ë³´ê´€ 취소" channelArchiveConfirmTitle: "{name} 채ë„ì„ ë³´ì¡´í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" channelArchiveConfirmDescription: "보존한 채ë„ì€ ì±„ë„ ëª©ë¡ê³¼ 검색 ê²°ê³¼ì— í‘œì‹œë˜ì§€ 않으며 새로운 ë…¸íŠ¸ë„ ìž‘ì„±í• ìˆ˜ 없습니다." thisChannelArchived: "ì´ ì±„ë„ì€ ë³´ì¡´ë˜ì—ˆìŠµë‹ˆë‹¤." @@ -1116,6 +1132,9 @@ preventAiLearning: "기계학습(ìƒì„±í˜• AI)ìœ¼ë¡œì˜ ì‚¬ìš©ì„ ê±°ë¶€" preventAiLearningDescription: "ì™¸ë¶€ì˜ ë¬¸ìž¥ ìƒì„± AI나 ì´ë¯¸ì§€ ìƒì„± AIì— ëŒ€í•´ ì œì¶œí•œ 노트나 ì´ë¯¸ì§€ ë“±ì˜ ì½˜í…ì¸ ë¥¼ í•™ìŠµì˜ ëŒ€ìƒìœ¼ë¡œ 사용하지 않ë„ë¡ ìš”êµ¬í•©ë‹ˆë‹¤. 다만, ì´ ìš”êµ¬ì‚¬í•ì„ ì§€í‚¬ ì˜ë¬´ëŠ” 없기 ë•Œë¬¸ì— í•™ìŠµì„ ì™„ì „ížˆ 방지하는 ê²ƒì€ ì•„ë‹™ë‹ˆë‹¤." options: "옵션" specifyUser: "ì‚¬ìš©ìž ì§€ì •" +lookupConfirm: "조회 í• ê¹Œìš”?" +openTagPageConfirm: "í•´ì‹œíƒœê·¸ì˜ íŽ˜ì´ì§€ë¥¼ 열까요?" +specifyHost: "호스트 ì§€ì •" failedToPreviewUrl: "미리 ë³¼ 수 ì—†ìŒ" update: "ì—…ë°ì´íЏ" rolesThatCanBeUsedThisEmojiAsReaction: "ì´ ì´ëª¨ì§€ë¥¼ 리액션으로 ì‚¬ìš©í• ìˆ˜ 있는 ì—í• " @@ -1249,6 +1268,21 @@ alwaysConfirmFollow: "íŒ”ë¡œìš°ì¼ ë•Œ í•ìƒ í™•ì¸í•˜ê¸°" inquiry: "문ì˜í•˜ê¸°" tryAgain: "다시 시ë„í•´ 주세요." confirmWhenRevealingSensitiveMedia: "민ê°í•œ 미디어를 ì—´ 때 ë‘ ë²ˆ 확ì¸" +sensitiveMediaRevealConfirm: "민ê°í•œ 미디어입니다. í‘œì‹œí• ê¹Œìš”?" +createdLists: "ë§Œë“ ë¦¬ìŠ¤íŠ¸" +createdAntennas: "ë§Œë“ ì•ˆí…Œë‚˜" +fromX: "{x}부터" +genEmbedCode: "ìž„ë² ë””ë“œ 코드 만들기" +noteOfThisUser: "ì´ ìœ ì €ì˜ ë…¸íŠ¸ 목ë¡" +clipNoteLimitExceeded: "ë” ì´ìƒ ì´ í´ë¦½ì— 노트를 추가 í• ìˆ˜ 없습니다." +performance: "í¼í¬ë¨¼ìФ" +modified: "변경 있ìŒ" +discard: "파기" +thereAreNChanges: "{n}ê±´ ë³€ê²½ì´ ìžˆìŠµë‹ˆë‹¤." +signinWithPasskey: "패스키로 로그ì¸" +unknownWebAuthnKey: "등ë¡ë˜ì§€ ì•Šì€ íŒ¨ìŠ¤í‚¤ìž…ë‹ˆë‹¤." +passkeyVerificationFailed: "패스키 ê²€ì¦ì„ 실패했습니다." +passkeyVerificationSucceededButPasswordlessLoginDisabled: "패스키를 ê²€ì¦í–ˆìœ¼ë‚˜, 비밀번호 ì—†ì´ ë¡œê·¸ì¸í•˜ê¸°ê°€ êº¼ì ¸ 있습니다." _delivery: status: "ì „ì†¡ ìƒíƒœ" stop: "ì •ì§€ë¨" @@ -1383,6 +1417,7 @@ _serverSettings: fanoutTimelineDescription: "활성화하면 ê°ì¢… 타임ë¼ì¸ì„ ê°€ì ¸ì˜¬ ë•Œì˜ ì„±ëŠ¥ì„ ëŒ€í í–¥ìƒí•˜ë©°, ë°ì´í„°ë² ì´ìŠ¤ì˜ ë¶€í•˜ë¥¼ ì¤„ì¼ ìˆ˜ 있습니다. 단, Redisì˜ ë©”ëª¨ë¦¬ ì‚¬ìš©ëŸ‰ì´ ì¦ê°€í•©ë‹ˆë‹¤. ì„œë²„ì˜ ë©”ëª¨ë¦¬ ìš©ëŸ‰ì´ ìž‘ê±°ë‚˜, 서비스가 ë¶ˆì•ˆì •í•´ì§€ëŠ” 경우 ë¹„í™œì„±í™”í• ìˆ˜ 있습니다." fanoutTimelineDbFallback: "ë°ì´í„°ë² ì´ìŠ¤ë¥¼ 예비로 사용하기" fanoutTimelineDbFallbackDescription: "활성화하면 타임ë¼ì¸ì˜ ìºì‹œë˜ì–´ 있지 ì•Šì€ ë¶€ë¶„ì— ëŒ€í•´ DBì— ì§ˆì˜í•˜ì—¬ ì •ë³´ë¥¼ ê°€ì ¸ì˜µë‹ˆë‹¤. 비활성화하면 ì´ë¥¼ 실행하지 않ìŒìœ¼ë¡œì¨ ì„œë²„ì˜ ë¶€í•˜ë¥¼ ì¤„ì¼ ìˆ˜ 있지만, 타임ë¼ì¸ì—서 ê°€ì ¸ì˜¬ 수 있는 게시물 범위가 í•œì •ë©ë‹ˆë‹¤." + reactionsBufferingDescription: "활성화 한 경우, 리액션 작성 í¼í¬ë¨¼ìŠ¤ê°€ 대í í–¥ìƒë˜ì–´ DBì˜ ë¶€í•˜ë¥¼ ì¤„ì¼ ìˆ˜ 있으나, Redisì˜ ë©”ëª¨ë¦¬ ì‚¬ìš©ëŸ‰ì´ ë§Žì•„ì§‘ë‹ˆë‹¤." inquiryUrl: "문ì˜ì²˜ URL" inquiryUrlDescription: "서버 ìš´ì˜ìžì—게 보내는 ë¬¸ì˜ ì–‘ì‹ì˜ URLì´ë‚˜ ìš´ì˜ìžì˜ ì—°ë½ì²˜ ë“±ì´ ì 힌 웹 페ì´ì§€ì˜ URLì„ ì„¤ì •í•©ë‹ˆë‹¤." _accountMigration: @@ -1716,10 +1751,15 @@ _role: canSearchNotes: "노트 검색 ì´ìš© 가능 여부" canUseTranslator: "ë²ˆì— ê¸°ëŠ¥ì˜ ì‚¬ìš©" avatarDecorationLimit: "아바타 장ì‹ì˜ 최대 붙임 개수" + canImportAntennas: "안테나 ê°€ì ¸ì˜¤ê¸° 허용" + canImportBlocking: "차단 ëª©ë¡ ê°€ì ¸ì˜¤ê¸° 허용" + canImportFollowing: "팔로우 ê°€ì ¸ì˜¤ê¸° 허용" + canImportMuting: "뮤트 ëª©ë¡ ê°€ì ¸ì˜¤ê¸° 허용" + canImportUserLists: "리스트 ëª©ë¡ ê°€ì ¸ì˜¤ê¸° 허용" _condition: roleAssignedTo: "ìˆ˜ë™ ì—í• ì— ì´ë¯¸ í• ë‹¹ë¨" isLocal: "로컬 사용ìž" - isRemote: "리모트 사용ìž" + isRemote: "ì›ê²© 사용ìž" isCat: "ê³ ì–‘ì´ ì‚¬ìš©ìž" isBot: "ë´‡ 사용ìž" isSuspended: "ì •ì§€ëœ ì‚¬ìš©ìž" @@ -1953,6 +1993,7 @@ _soundSettings: driveFileTypeWarnDescription: "오디오 파ì¼ì„ ì„ íƒí•˜ì„¸ìš”." driveFileDurationWarn: "오디오가 너무 ê¹ë‹ˆë‹¤" driveFileDurationWarnDescription: "긴 오디오로 ì„¤ì •í• ê²½ìš° 미스키 ì‚¬ìš©ì— ì§€ìž¥ì´ ê°ˆ ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. ê·¸ëž˜ë„ ê´œì°®ìŠµë‹ˆê¹Œ?" + driveFileError: "오디오를 불러올 수 없습니다. ì„¤ì •ì„ ë°”ê¿”ì£¼ì„¸ìš”." _ago: future: "미래" justNow: "방금 ì „" @@ -2209,6 +2250,9 @@ _profile: changeBanner: "배너 ì´ë¯¸ì§€ 변경" verifiedLinkDescription: "ë‚´ìš©ì— ìžì‹ ì˜ í”„ë¡œí•„ë¡œ 향하는 ë§í¬ê°€ í¬í•¨ëœ 페ì´ì§€ì˜ URLì„ ì‚½ìž…í•˜ë©´ ì†Œìœ ìž ì¸ì¦ 마í¬ê°€ 표시ë©ë‹ˆë‹¤." avatarDecorationMax: "최대 {max}개까지 장ì‹ì„ í• ìˆ˜ 있습니다." + followedMessage: "팔로우 ë°›ì•˜ì„ ë•Œ 메시지" + followedMessageDescription: "팔로우 ë°›ì•˜ì„ ë•Œ ìƒëŒ€ë°©ì—게 보여줄 단문 메시지를 ì„¤ì •í• ìˆ˜ 있습니다." + followedMessageDescriptionForLockedAccount: "팔로우를 승ì¸ì œë¡œ 한 경우, 팔로우 ìš”ì²ì„ 수ë½í–ˆì„ 때 ë³´ì—¬ì¤ë‹ˆë‹¤." _exportOrImport: allNotes: "ëª¨ë“ ë…¸íŠ¸" favoritedNotes: "ì¦ê²¨ì°¾ê¸°í•œ 노트" @@ -2301,6 +2345,7 @@ _pages: eyeCatchingImageSet: "ì•„ì´ìºì¹˜ ì´ë¯¸ì§€ë¥¼ ì„¤ì •" eyeCatchingImageRemove: "ì•„ì´ìºì¹˜ ì´ë¯¸ì§€ë¥¼ ì‚ì œ" chooseBlock: "ë¸”ë¡ ì¶”ê°€" + enterSectionTitle: "섹션 타ì´í‹€ì„ ìž…ë ¥í•˜ê¸°" selectType: "종류 ì„ íƒ" contentBlocks: "콘í…ì¸ " inputBlocks: "ìž…ë ¥" @@ -2346,6 +2391,7 @@ _notification: renotedBySomeUsers: "{n}ëª…ì´ ë¦¬ë…¸íŠ¸í–ˆìŠµë‹ˆë‹¤" followedBySomeUsers: "{n}명ì—게 팔로우ë¨" flushNotification: "알림 ì´ë ¥ì„ 초기화" + exportOfXCompleted: "{x} ì¶”ì¶œì— ì„±ê³µí–ˆìŠµë‹ˆë‹¤." _types: all: "ì „ë¶€" note: "사용ìžì˜ 새 글" @@ -2360,6 +2406,8 @@ _notification: followRequestAccepted: "팔로우 ìš”ì²ì´ 승ì¸ë˜ì—ˆì„ 때" roleAssigned: "ì—í• ì´ ë¶€ì—¬ ë¨" achievementEarned: "ë„ì „ ê³¼ì œ íšë“" + exportCompleted: "ì¶”ì¶œì„ ì„±ê³µí•¨" + test: "알림 테스트" app: "ì—°ë™ëœ ì•±ì„ í†µí•œ 알림" _actions: followBack: "팔로우" @@ -2411,6 +2459,7 @@ _webhookSettings: modifyWebhook: "Webhook ìˆ˜ì •" name: "ì´ë¦„" secret: "시í¬ë¦¿" + trigger: "트리거" active: "활성화" _events: follow: "누군가를 íŒ”ë¡œìš°í–ˆì„ ë•Œ" @@ -2425,11 +2474,12 @@ _webhookSettings: abuseReportResolved: "ë°›ì€ ì‹ ê³ ë¥¼ ì²˜ë¦¬í–ˆì„ ë•Œ" userCreated: "ìœ ì €ê°€ ìƒì„±ë˜ì—ˆì„ 때" deleteConfirm: "Webhookì„ ì‚ì œí• ê¹Œìš”?" + testRemarks: "스위치 ì˜¤ë¥¸ìª½ì— ìžˆëŠ” ë²„íŠ¼ì„ í´ë¦í•˜ì—¬ ë”미 ë°ì´í„°ë¥¼ 사용한 테스트용 웹 í›…ì„ ë³´ë‚¼ 수 있습니다." _abuseReport: _notificationRecipient: createRecipient: "ì‹ ê³ ìˆ˜ì‹ ìž ì¶”ê°€" modifyRecipient: "ì‹ ê³ ìˆ˜ì‹ ìž íŽ¸ì§‘" - recipientType: "알림 ìˆ˜ì‹ ìœ í˜•" + recipientType: "알림 종류" _recipientType: mail: "ì´ë©”ì¼" webhook: "Webhook" @@ -2437,7 +2487,7 @@ _abuseReport: mail: "모ë”ë ˆì´í„° ê¶Œí•œì„ ê°€ì§„ 사용ìžì˜ ì´ë©”ì¼ ì£¼ì†Œì— ì•Œë¦¼ì„ ë³´ëƒ…ë‹ˆë‹¤ (ì‹ ê³ ë¥¼ ë°›ì€ ë•Œì—ë§Œ)" webhook: "ì§€ì •í•œ SystemWebhookì— ì•Œë¦¼ì„ ë³´ëƒ…ë‹ˆë‹¤ (ì‹ ê³ ë¥¼ ë°›ì€ ë•Œì™€ í•´ê²°í–ˆì„ ë•Œì— ì†¡ì‹ )" keywords: "키워드" - notifiedUser: "ì‹ ê³ ì•Œë¦¼ì„ ë³´ë‚¼ ìœ ì €" + notifiedUser: "알릴 사용ìž" notifiedWebhook: "ì‚¬ìš©í• Webhook" deleteConfirm: "ìˆ˜ì‹ ìžë¥¼ ì‚ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" _moderationLogTypes: @@ -2483,6 +2533,10 @@ _moderationLogTypes: createAbuseReportNotificationRecipient: "ì‹ ê³ ì•Œë¦¼ ìˆ˜ì‹ ìž ìƒì„±" updateAbuseReportNotificationRecipient: "ì‹ ê³ ì•Œë¦¼ ìˆ˜ì‹ ìž íŽ¸ì§‘" deleteAbuseReportNotificationRecipient: "ì‹ ê³ ì•Œë¦¼ ìˆ˜ì‹ ìž ì‚ì œ" + deleteAccount: "ê³„ì •ì„ ì‚ì œ" + deletePage: "페ì´ì§€ë¥¼ ì‚ì œ" + deleteFlash: "Play를 ì‚ì œ" + deleteGalleryPost: "갤러리 í¬ìŠ¤íŠ¸ë¥¼ ì‚ì œ" _fileViewer: title: "íŒŒì¼ ìƒì„¸" type: "íŒŒì¼ ìœ í˜•" @@ -2614,3 +2668,22 @@ _mediaControls: pip: "화면 ì† í™”ë©´" playbackRate: "ìž¬ìƒ ì†ë„" loop: "반복 재ìƒ" +_contextMenu: + title: "컨í…스트 메뉴" + app: "ì• í”Œë¦¬ì¼€ì´ì…˜" + appWithShift: "Shift 키로 ì• í”Œë¦¬ì¼€ì´ì…˜" + native: "브ë¼ìš°ì €ì˜ UI" +_embedCodeGen: + title: "ìž„ë² ë””ë“œ 코드를 커스터마ì´ì¦ˆ" + header: "í•´ë”를 표시" + autoload: "ìžë™ìœ¼ë¡œ ë‹¤ìŒ ì½”ë“œë¥¼ 실행 (비권장)" + maxHeight: "최대 높ì´" + maxHeightDescription: "최대 ê°’ì„ ë¬´ì‹œí•˜ë ¤ë©´ 0ì„ ìž…ë ¥í•˜ì„¸ìš”. ìœ„ì ¯ì´ ìƒí•˜ë¡œ 길어지는 ê²ƒì„ ë°©ì§€í•˜ë ¤ë©´, ìž„ì˜ì˜ ê°’ì„ ìž…ë ¥í•´ 주세요." + maxHeightWarn: "ë†’ì´ ìµœëŒ€ ê°’ì´ ì„¤ì •ë˜ì–´ì ¸ 있지 않습니다(0). ì˜ë„ì 으로 ì„¤ì • 하지 않았다면 ìž„ì˜ì˜ ê°’ì„ ì„¤ì •í•´ì£¼ì„¸ìš”." + previewIsNotActual: "미리보기로 í‘œì‹œí• ìˆ˜ 있는 í¬ê¸°ë³´ë‹¤ í½ë‹ˆë‹¤. ì‹¤ì œë¡œ ë„£ì€ ì½”ë“œì˜ í‘œì‹œê°€ 다른 경우가 있습니다." + rounded: "ì™¸ê³½ì„ ì„ ë‘¥ê¸€ê²Œ 하기" + border: "ì™¸ê³½ì„ ì— í…Œë‘리를 씌우기" + applyToPreview: "ë¯¸ë¦¬ë³´ê¸°ì— ë°˜ì˜" + generateCode: "ìž„ë² ë””ë“œ 코드를 만들기" + codeGenerated: "코드를 만들었습니다." + codeGeneratedDescription: "만들어진 코드를 웹 사ì´íŠ¸ì— ë¶™ì—¬ì„œ 사용하세요." diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 73eff0941a..f586ff2bff 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -492,7 +492,6 @@ uiLanguage: "JÄ™zyk wyÅ›wietlania UI" aboutX: "O {x}" emojiStyle: "Styl emoji" native: "Natywny" -disableDrawer: "Nie używaj menu w stylu szuflady" showNoteActionsOnlyHover: "Pokazuj akcje notatek tylko po najechaniu myszkÄ…" showReactionsCount: "WyÅ›wietl liczbÄ™ reakcji na notatkÄ™" noHistory: "Brak historii" diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index 87f934201c..34de5066f3 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -1,5 +1,5 @@ --- -_lang_: "Português" +_lang_: "日本語" headlineMisskey: "Uma rede ligada por notas" introMisskey: "Bem-vindo! O Misskey é um serviço de microblog descentralizado de código aberto.\nCrie \"notas\" para compartilhar o que está acontecendo agora ou para se expressar com todos à sua volta 📡\nVocê também pode adicionar rapidamente reações à s notas de outras pessoas usando a função \"Reações\" ðŸ‘\nVamos explorar um novo mundo 🚀" poweredByMisskeyDescription: "{name} é uma instância da plataforma de código aberto <b>Misskey</b>." @@ -9,7 +9,7 @@ notifications: "Notificações" username: "Nome de usuário" password: "Senha" forgotPassword: "Esqueci-me da senha" -fetchingAsApObject: "Buscando no Fediverso" +fetchingAsApObject: "Buscando no Fediverso..." ok: "OK" gotIt: "Entendi" cancel: "Cancelar" @@ -25,7 +25,7 @@ basicSettings: "Configurações básicas" otherSettings: "Outras configurações" openInWindow: "Abrir em um janela" profile: "Perfil" -timeline: "Linha do tempo" +timeline: "Cronologia" noAccountDescription: "Este usuário não tem uma descrição." login: "Iniciar sessão" loggingIn: "Iniciando sessão…" @@ -82,7 +82,7 @@ exportRequested: "A sua solicitação de exportação foi enviada. Isso pode lev importRequested: "A sua solicitação de importação foi enviada. Isso pode levar algum tempo." lists: "Listas" noLists: "Não possui nenhuma lista" -note: "Post" +note: "Publicar" notes: "Posts" following: "Seguindo" followers: "Seguidores" @@ -272,7 +272,7 @@ more: "Mais!" featured: "Destaques" usernameOrUserId: "Nome de usuário ou ID do usuário" noSuchUser: "Usuário não encontrado" -lookup: "Buscando" +lookup: "Consultar" announcements: "Avisos" imageUrl: "URL da imagem" remove: "Remover" @@ -296,7 +296,7 @@ explore: "Explorar" messageRead: "Lida" noMoreHistory: "Não existe histórico anterior" startMessaging: "Iniciar conversação" -nUsersRead: "{n} Pessoas leem" +nUsersRead: "{n} pessoas leram" agreeTo: "Eu concordo com {0}" agree: "Concordar" agreeBelow: "Eu concordo com o seguinte" @@ -312,7 +312,7 @@ birthday: "Aniversário" yearsOld: "{age} anos" registeredDate: "Data de registro" location: "Localização" -theme: "tema" +theme: "Tema" themeForLightMode: "Temas usados ​​no modo de luz" themeForDarkMode: "Temas usados ​​no modo escuro" light: "Claro" @@ -509,7 +509,6 @@ uiLanguage: "Idioma de exibição da interface " aboutX: "Sobre {x}" emojiStyle: "Estilo de emojis" native: "Nativo" -disableDrawer: "Não mostrar o menu em formato de gaveta" showNoteActionsOnlyHover: "Exibir as ações da nota somente ao passar o cursor sobre ela" showReactionsCount: "Ver o número de reações nas notas" noHistory: "Ainda não há histórico" @@ -700,7 +699,7 @@ fileIdOrUrl: "ID do arquivo ou URL" behavior: "Comportamento" sample: "Exemplo" abuseReports: "Denúncias" -reportAbuse: "Denúncias" +reportAbuse: "Denunciar" reportAbuseRenote: "Reportar repostagem" reportAbuseOf: "Denunciar {name}" fillAbuseReportDescription: "Por favor, forneça detalhes sobre o motivo da denúncia. Se houver uma nota especÃfica envolvida, inclua também a URL dela." @@ -843,7 +842,7 @@ switchAccount: "Trocar conta" enabled: "Ativado" disabled: "Desativado" quickAction: "Ações rápidas" -user: "Usuários" +user: "Usuário" administration: "Administrar" accounts: "Contas" switch: "Trocar" @@ -1263,6 +1262,7 @@ confirmWhenRevealingSensitiveMedia: "Confirmar ao revelar mÃdia sensÃvel" sensitiveMediaRevealConfirm: "Essa mÃdia pode ser sensÃvel. Deseja revelá-la?" createdLists: "Listas criadas" createdAntennas: "Antenas criadas" +clipNoteLimitExceeded: "Não é possÃvel adicionar mais notas ao clipe." _delivery: status: "Estado de entrega" stop: "Suspenso" @@ -2316,6 +2316,7 @@ _pages: eyeCatchingImageSet: "Escolher miniatura" eyeCatchingImageRemove: "Excluir miniatura" chooseBlock: "Adicionar bloco" + enterSectionTitle: "Insira um tÃtulo à seção" selectType: "Selecionar um tipo" contentBlocks: "Conteúdo" inputBlocks: "Inserir" @@ -2368,7 +2369,7 @@ _notification: mention: "Menção" reply: "Respostas" renote: "Repostar" - quote: "Citar" + quote: "Citações" reaction: "Reações" pollEnded: "Enquetes terminando" receiveFollowRequest: "Recebeu pedidos de seguidor" @@ -2499,6 +2500,10 @@ _moderationLogTypes: createAbuseReportNotificationRecipient: "Criar um destinatário para relatórios de abuso" updateAbuseReportNotificationRecipient: "Atualizar destinatários para relatórios de abuso" deleteAbuseReportNotificationRecipient: "Remover um destinatário para relatórios de abuso" + deleteAccount: "Remover conta" + deletePage: "Remover página" + deleteFlash: "Remover Play" + deleteGalleryPost: "Remover a publicação da galeria" _fileViewer: title: "Detalhes do arquivo" type: "Tipo de arquivo" diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml index b4c9b90de9..a5f8057860 100644 --- a/locales/ro-RO.yml +++ b/locales/ro-RO.yml @@ -453,7 +453,6 @@ or: "Sau" language: "Limbă" uiLanguage: "Limba interfeÈ›ei" aboutX: "Despre {x}" -disableDrawer: "Nu folosi meniuri în stil sertar" noHistory: "Nu există istoric" signinHistory: "Istoric autentificări" doing: "Se procesează..." diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 88f59155d6..cdc4898a3b 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -2,7 +2,7 @@ _lang_: "РуÑÑкий" headlineMisskey: "Сеть, ÑÐ¿Ð»ÐµÑ‚Ñ‘Ð½Ð½Ð°Ñ Ð¸Ð· заметок" introMisskey: "Добро пожаловать! Misskey — Ñто децентрализованный ÑÐµÑ€Ð²Ð¸Ñ Ð¼Ð¸ÐºÑ€Ð¾Ð±Ð»Ð¾Ð³Ð¾Ð² Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ñ‹Ð¼ иÑходным кодом.\nПишите «заметки» — делитеÑÑŒ Ñо вÑеми проиÑходÑщим вокруг или раÑÑказывайте о Ñебе 📡\nСтавьте «реакции» — выражайте Ñвои чувÑтва и Ñмоции от заметок других ðŸ‘\nОткройте Ð´Ð»Ñ ÑÐµÐ±Ñ Ð½Ð¾Ð²Ñ‹Ð¹ мир 🚀" -poweredByMisskeyDescription: "{name} – ÑÐµÑ€Ð²Ð¸Ñ Ð½Ð° платформе Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ñ‹Ð¼ иÑходным кодом <b>Misskey</b>, называемый инÑтанÑом Misskey." +poweredByMisskeyDescription: "{name} – ÑÐµÑ€Ð²Ð¸Ñ Ð½Ð° платформе Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ñ‹Ð¼ иÑходным кодом <b>Misskey</b>, называемый ÑкземплÑром Misskey." monthAndDay: "{day}.{month}" search: "ПоиÑк" notifications: "УведомлениÑ" @@ -10,15 +10,15 @@ username: "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ" password: "Пароль" forgotPassword: "Забыли пароль?" fetchingAsApObject: "Приём Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… Ñайтов" -ok: "Окей" +ok: "Подтвердить" gotIt: "ЯÑно!" cancel: "Отмена" noThankYou: "Ðет, ÑпаÑибо" enterUsername: "Введите Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ" -renotedBy: "{user} делитÑÑ" +renotedBy: "{user} репоÑтнул(а)" noNotes: "Ðет ни одной заметки" noNotifications: "Ðет уведомлений" -instance: "ИнÑтанÑ" +instance: "ÐкземплÑÑ€" settings: "ÐаÑтройки" notificationSettings: "ÐаÑтройки уведомлений" basicSettings: "ОÑновные наÑтройки" @@ -45,22 +45,24 @@ pin: "Закрепить в профиле" unpin: "Открепить от профилÑ" copyContent: "Скопировать Ñодержимое" copyLink: "Скопировать ÑÑылку" +copyLinkRenote: "Скопировать ÑÑылку на репоÑÑ‚" delete: "Удалить" deleteAndEdit: "Удалить и отредактировать" -deleteAndEditConfirm: "Удалить Ñту заметку и Ñоздать отредактированную? Ð’Ñе реакции, ÑÑылки и ответы на ÑущеÑтвующую будут будут потерÑны." +deleteAndEditConfirm: "Удалить Ñтот поÑÑ‚ и отредактировать заново? Ð’Ñе реакции, репоÑты и ответы на него также будут удалены." addToList: "Добавить в ÑпиÑок" addToAntenna: "Добавить к антенне" sendMessage: "Отправить Ñообщение" copyRSS: "Скопировать RSS" copyUsername: "Скопировать Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ" -copyUserId: "Скопировать идентификатор пользователÑ" -copyNoteId: "Скопировать идентификатор заметки" +copyUserId: "Скопировать ID пользователÑ" +copyNoteId: "Скопировать ID поÑта" copyFileId: "Скопировать ID файла" copyFolderId: "Скопировать ID папки" -copyProfileUrl: "Скопировать URL Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ " +copyProfileUrl: "Скопировать ÑÑылку на профиль" searchUser: "ПоиÑк людей" +searchThisUsersNotes: "ИÑкать по заметкам пользователÑ" reply: "Ответ" -loadMore: "Показать еще" +loadMore: "Загрузить ещё" showMore: "Показать ещё" showLess: "Закрыть" youGotNewFollower: "Ðовый подпиÑчик" @@ -107,11 +109,14 @@ enterEmoji: "Введите Ñмодзи" renote: "РепоÑÑ‚" unrenote: "Отмена репоÑта" renoted: "РепоÑÑ‚ Ñовершён." +renotedToX: "РепоÑтнуть в {name}." cantRenote: "Ðто Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€ÐµÐ¿Ð¾Ñтить." cantReRenote: "Ðевозможно репоÑтить репоÑÑ‚." quote: "Цитата" inChannelRenote: "Ð’ канале" inChannelQuote: "Заметки в канале" +renoteToChannel: "РепоÑтнуть в канал" +renoteToOtherChannel: "РепоÑтнуть в другой канал" pinnedNote: "Ð—Ð°ÐºÑ€ÐµÐ¿Ð»Ñ‘Ð½Ð½Ð°Ñ Ð·Ð°Ð¼ÐµÑ‚ÐºÐ°" pinned: "Закрепить в профиле" you: "Ð’Ñ‹" @@ -150,6 +155,7 @@ editList: "Редактировать ÑпиÑок" selectChannel: "Выберите канал" selectAntenna: "Выберите антенну" editAntenna: "Редактировать антенну" +createAntenna: "Создать антенну" selectWidget: "Выберите виджет" editWidgets: "Редактировать виджеты" editWidgetsExit: "Готово" @@ -157,11 +163,12 @@ customEmojis: "СобÑтвенные Ñмодзи" emoji: "Ðмодзи" emojis: "Ðмодзи" emojiName: "Ðазвание Ñмодзи" -emojiUrl: "URL Ñмодзи" +emojiUrl: "СÑылка на Ñмодзи" addEmoji: "Добавить Ñмодзи" settingGuide: "Рекомендуемые наÑтройки" cacheRemoteFiles: "Кешировать внешние файлы" cacheRemoteFilesDescription: "Когда Ñта наÑтройка отключена, файлы Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… Ñайтов будут загружатьÑÑ Ð¿Ñ€Ñмо оттуда. Ðто ÑÑкономит меÑто на Ñервере, но увеличит трафик, так как не будут ÑоздаватьÑÑ ÑÑкизы." +youCanCleanRemoteFilesCache: "Ð’Ñ‹ можете очиÑтить кÑш, нажав на кнопку ðŸ—‘ï¸ Ð² меню ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð°Ð¼Ð¸." cacheRemoteSensitiveFiles: "КÑшировать внешние файлы «не Ð´Ð»Ñ Ð²Ñех»" cacheRemoteSensitiveFilesDescription: "ЕÑли отключено, файлы «не Ð´Ð»Ñ Ð²Ñех» загружаютÑÑ Ð½ÐµÐ¿Ð¾ÑредÑтвенно Ñ ÑƒÐ´Ð°Ð»Ñ‘Ð½Ð½Ñ‹Ñ… Ñерверов, не кÑшируÑÑÑŒ." flagAsBot: "Ðккаунт бота" @@ -175,6 +182,10 @@ addAccount: "Добавить учётную запиÑÑŒ" reloadAccountsList: "Обновить ÑпиÑок учётных запиÑей" loginFailed: "ÐÐµÑƒÐ´Ð°Ñ‡Ð½Ð°Ñ Ð¿Ð¾Ð¿Ñ‹Ñ‚ÐºÐ° входа" showOnRemote: "Перейти к оригиналу на Ñайт" +continueOnRemote: "Продолжить на удалённом Ñервере" +chooseServerOnMisskeyHub: "Выбрать Ñервер Ñ Misskey Hub" +specifyServerHost: "Укажите Ñервер напрÑмую" +inputHostName: "Введите домен" general: "Общее" wallpaper: "Обои" setWallpaper: "УÑтановить обои" @@ -185,6 +196,7 @@ followConfirm: "ПодпиÑатьÑÑ Ð½Ð° {name}?" proxyAccount: "Ð£Ñ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ прокÑи" proxyAccountDescription: "Ð£Ñ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ прокÑи предназначена Ñлужить подпиÑчиком на пользователей Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… Ñайтов. Ðапример, еÑли пользователь добавит кого-то Ñ Ð´Ñ€ÑƒÐ³Ð¾Ð³Ð¾ Ñайта а ÑпиÑок, деÑтельноÑть того не отобразитÑÑ, пока никто Ñ Ñтого же Ñайта не подпиÑан на него. Чтобы Ñто Ñтало возможным, на него подпиÑываетÑÑ Ð¿Ñ€Ð¾ÐºÑи." host: "ХоÑÑ‚" +selectSelf: "Выбрать ÑебÑ" selectUser: "Выберите пользователÑ" recipient: "Кому" annotation: "ОпиÑание" @@ -199,6 +211,7 @@ perHour: "По чаÑам" perDay: "По днÑм" stopActivityDelivery: "ОÑтановить отправку обновлений активноÑти" blockThisInstance: "Блокировать Ñтот инÑтанÑ" +silenceThisInstance: "Заглушить Ñтот инÑтанÑ" operations: "Операции" software: "Программы" version: "ВерÑиÑ" @@ -218,6 +231,7 @@ clearCachedFiles: "ОчиÑтить кÑш" clearCachedFilesConfirm: "Удалить вÑе закÑшированные файлы Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… Ñайтов?" blockedInstances: "Заблокированные инÑтанÑÑ‹" blockedInstancesDescription: "Введите ÑпиÑок инÑтанÑов, которые хотите заблокировать. Они больше не Ñмогут обмениватьÑÑ Ñ Ð²Ð°ÑˆÐ¸Ð¼ инÑтанÑом." +silencedInstances: "Заглушённые инÑтанÑÑ‹" muteAndBlock: "Скрытие и блокировка" mutedUsers: "Скрытые пользователи" blockedUsers: "Заблокированные пользователи" @@ -236,7 +250,7 @@ noJobs: "Ðет заданий" federating: "ФедерируетÑÑ" blocked: "Заблокировано" suspended: "Заморожено" -all: "Ð’ÑÑ‘" +all: "Ð’Ñе" subscribing: "ПодпиÑка" publishing: "ПубликациÑ" notResponding: "Ðет ответа" @@ -268,7 +282,7 @@ messaging: "СообщениÑ" upload: "Загрузить" keepOriginalUploading: "Сохранить иÑходное изображение" keepOriginalUploadingDescription: "СохранÑет иÑходную верÑию при загрузке изображений. ЕÑли выключить, то при загрузке браузер генерирует изображение Ð´Ð»Ñ Ð¿ÑƒÐ±Ð»Ð¸ÐºÐ°Ñ†Ð¸Ð¸." -fromDrive: "С «диÑка»" +fromDrive: "С ДиÑка" fromUrl: "По ÑÑылке" uploadFromUrl: "Загрузить по ÑÑылке" uploadFromUrlDescription: "СÑылка на файл, который хотите загрузить" @@ -308,6 +322,7 @@ selectFile: "Выберите файл" selectFiles: "Выберите файлы" selectFolder: "Выберите папку" selectFolders: "Выберите папки" +fileNotSelected: "Файл не выбран" renameFile: "Переименовать файл" folderName: "Ð˜Ð¼Ñ Ð¿Ð°Ð¿ÐºÐ¸" createFolder: "Создать папку" @@ -359,8 +374,8 @@ disablingTimelinesInfo: "У админиÑтраторов и модератор registration: "РегиÑтрациÑ" enableRegistration: "Разрешить региÑтрацию" invite: "ПриглаÑить" -driveCapacityPerLocalAccount: "Объём диÑка на одного локального пользователÑ" -driveCapacityPerRemoteAccount: "Объём диÑка на одного Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ñ Ð´Ñ€ÑƒÐ³Ð¾Ð³Ð¾ Ñайта" +driveCapacityPerLocalAccount: "Объём ДиÑка на одного локального пользователÑ" +driveCapacityPerRemoteAccount: "Объём ДиÑка на одного Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ñ Ð´Ñ€ÑƒÐ³Ð¾Ð³Ð¾ ÑкземплÑра" inMb: "Ð’ мегабайтах" bannerUrl: "СÑылка на изображение в шапке" backgroundImageUrl: "СÑылка на фоновое изображение" @@ -379,6 +394,7 @@ mcaptcha: "mCaptcha" enableMcaptcha: "Включить mCaptcha" mcaptchaSiteKey: "Ключ Ñайта" mcaptchaSecretKey: "Секретный ключ" +mcaptchaInstanceUrl: "СÑылка на Ñервер mCaptcha" recaptcha: "reCAPTCHA" enableRecaptcha: "Включить reCAPTCHA" recaptchaSiteKey: "Ключ Ñайта" @@ -393,7 +409,8 @@ manageAntennas: "ÐаÑтройки антенн" name: "Ðазвание" antennaSource: "ИÑточник антенны" antennaKeywords: "Ключевые Ñлова" -antennaExcludeKeywords: "ИÑключениÑ" +antennaExcludeKeywords: "Чёрный ÑпиÑок Ñлов" +antennaExcludeBots: "ИÑключать ботов" antennaKeywordsDescription: "Пишите Ñлова через пробел в одной Ñтроке, чтобы ловить их поÑвление вмеÑте; на отдельных Ñтроках раÑполагайте Ñлова, или группы Ñлов, чтобы ловить любые из них." notifyAntenna: "УведомлÑть о новых заметках" withFileAntenna: "Только заметки Ñ Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñми" @@ -426,6 +443,7 @@ totp: "Приложение-аутентификатор" totpDescription: "ОпиÑание приложениÑ-аутентификатора" moderator: "Модератор" moderation: "МодерациÑ" +moderationLogs: "Журнал модерации" nUsersMentioned: "УпомÑнуло пользователей: {n}" securityKeyAndPasskey: "Ключ безопаÑноÑти и Ð¿Ð°Ñ€Ð¾Ð»ÑŒÐ½Ð°Ñ Ñ„Ñ€Ð°Ð·Ð°" securityKey: "Ключ безопаÑноÑти" @@ -458,10 +476,12 @@ retype: "Введите ещё раз" noteOf: "Что пишет {user}" quoteAttached: "Цитата" quoteQuestion: "Хотите добавить цитату?" +attachAsFileQuestion: "ТекÑта в буфере обмена Ñлишком много. Прикрепить как текÑтовый файл?" noMessagesYet: "Пока ни одного ÑообщениÑ" newMessageExists: "Ðовое Ñообщение" onlyOneFileCanBeAttached: "К Ñообщению можно прикрепить только один файл" signinRequired: "ПожалуйÑта, войдите" +signinOrContinueOnRemote: "Чтобы продолжить, вам необходимо войти в аккаунт на Ñвоём Ñервере или зарегиÑтрироватьÑÑ / войти в аккаунт на Ñтом." invitations: "ПриглашениÑ" invitationCode: "Код приглашениÑ" checking: "Проверка" @@ -471,7 +491,7 @@ usernameInvalidFormat: "Можно иÑпользовать только латРtooShort: "Слишком короткий" tooLong: "Слишком длинный" weakPassword: "Слабый пароль" -normalPassword: "Годный пароль" +normalPassword: "Хороший пароль" strongPassword: "Ðадёжный пароль" passwordMatched: "Совпали" passwordNotMatched: "Ðе Ñовпадают" @@ -483,8 +503,8 @@ uiLanguage: "Язык интерфейÑа" aboutX: "ОпиÑание {x}" emojiStyle: "Стиль Ñмодзи" native: "СиÑтемные" -disableDrawer: "Ðе иÑпользовать выдвижные меню" showNoteActionsOnlyHover: "Показывать кнопки у заметок только при наведении" +showReactionsCount: "Видеть количеÑтво реакций на заметках" noHistory: "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð¿Ð¾ÐºÐ° пуÑта" signinHistory: "Журнал поÑещений" enableAdvancedMfm: "Включить раÑширенный MFM" @@ -547,7 +567,7 @@ popout: "Развернуть" volume: "ГромкоÑть" masterVolume: "ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ñ€ÐµÐ³ÑƒÐ»Ð¸Ñ€Ð¾Ð²ÐºÐ° громкоÑти" notUseSound: "Выключить звук" -useSoundOnlyWhenActive: "ИÑпользовать звук, когда Misskey активен." +useSoundOnlyWhenActive: "ВоÑпроизводить звук только когда Misskey активен." details: "Подробнее" chooseEmoji: "Выберите Ñмодзи" unableToProcess: "Ðе удаётÑÑ Ð·Ð°Ð²ÐµÑ€ÑˆÐ¸Ñ‚ÑŒ операцию" @@ -601,7 +621,7 @@ poll: "ОпроÑ" useCw: "Скрывать Ñодержимое под предупреждением" enablePlayer: "Включить проигрыватель" disablePlayer: "Выключить проигрыватель" -expandTweet: "Развернуть твит" +expandTweet: "Развернуть заметку" themeEditor: "Редактор темы оформлениÑ" description: "ОпиÑание" describeFile: "Добавить подпиÑÑŒ" @@ -613,7 +633,7 @@ plugins: "РаÑширениÑ" preferencesBackups: "Ð ÐµÐ·ÐµÑ€Ð²Ð½Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ" deck: "Пульт" undeck: "Покинуть пульт" -useBlurEffectForModal: "Размывка под формой поверх вÑего" +useBlurEffectForModal: "Размытие за формой ввода заметки" useFullReactionPicker: "Полнофункциональный выбор реакций" width: "Ширина" height: "Ð’Ñ‹Ñота" @@ -644,7 +664,7 @@ smtpSecure: "ИÑпользовать SSL/TLS Ð´Ð»Ñ SMTP-Ñоединений" smtpSecureInfo: "Выключите при иÑпользовании STARTTLS." testEmail: "Проверка доÑтавки Ñлектронной почты" wordMute: "Скрытие Ñлов" -hardWordMute: "" +hardWordMute: "Строгое Ñкрытие Ñлов" regexpError: "Ошибка в регулÑрном выражении" regexpErrorDescription: "Ð’ ÑпиÑке {tab} Ñкрытых Ñлов, в Ñтроке {line} обнаружена ÑинтакÑичеÑÐºÐ°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°:" instanceMute: "Глушение инÑтанÑов" @@ -726,6 +746,7 @@ lockedAccountInfo: "Даже еÑли вы вручную подтверждае alwaysMarkSensitive: "Отмечать файлы как «Ñодержимое не Ð´Ð»Ñ Ð²Ñех» по умолчанию" loadRawImages: "Сразу показывать Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð² полном размере" disableShowingAnimatedImages: "Ðе проигрывать анимацию" +highlightSensitiveMedia: "ВыделÑть Ñодержимое не Ð´Ð»Ñ Ð²Ñех" verificationEmailSent: "Вам отправлено пиÑьмо Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ. Пройдите, пожалуйÑта, по ÑÑылке из пиÑьма, чтобы завершить проверку." notSet: "Ðе наÑтроено" emailVerified: "ÐÐ´Ñ€ÐµÑ Ñлектронной почты подтверждён." @@ -743,7 +764,7 @@ makeExplorable: "Опубликовать профиль в «Обзоре»." makeExplorableDescription: "ЕÑли выключить, ваш профиль не будет показан в разделе «Обзор»." showGapBetweenNotesInTimeline: "Показывать разделитель между заметками в ленте" duplicate: "Дубликат" -left: "Влево" +left: "Слева" center: "По центру" wide: "ТолÑтый" narrow: "Тонкий" @@ -822,7 +843,7 @@ noMaintainerInformationWarning: "Ðе заполнены ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾Ð± Ð noBotProtectionWarning: "Ботозащита не наÑтроена" configure: "ÐаÑтроить" postToGallery: "Опубликовать в галерею" -postToHashtag: "ÐапиÑать заметку Ñ Ñтим Ñ…Ñштегом" +postToHashtag: "ÐапиÑать заметку Ñ Ñтим хештегом" gallery: "ГалереÑ" recentPosts: "Ðедавние публикации" popularPosts: "ПопулÑрные публикации" @@ -839,13 +860,13 @@ emailNotConfiguredWarning: "Ðе указан Ð°Ð´Ñ€ÐµÑ Ñлектронной ratio: "Соотношение" previewNoteText: "Предварительный проÑмотр" customCss: "Индивидуальный CSS" -customCssWarn: "ИÑпользуйте Ñту наÑтройку только еÑли знаете, что делаете. Ошибки здеÑÑŒ чреваты тем, что Ñайт переÑтанет нормально работать у ваÑ." +customCssWarn: "ИÑпользуйте Ñту наÑтройку только еÑли знаете, что делаете. Ошибки здеÑÑŒ чреваты тем, что у Ð²Ð°Ñ Ð¿ÐµÑ€ÐµÑтанет нормально работать Ñайт." global: "Ð’ÑеобщаÑ" squareAvatars: "Квадратные аватарки" sent: "Отправить" received: "Получено" searchResult: "Результаты поиÑка" -hashtags: "Ð¥Ñштег" +hashtags: "Хештеги" troubleshooting: "Разрешение проблем" useBlurEffect: "Размытие в интерфейÑе" learnMore: "Подробнее" @@ -857,7 +878,7 @@ accountDeletionInProgress: "Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÑÐµÑ‚Ñ usernameInfo: "ИмÑ, которое отличает вашу учетную запиÑÑŒ от других на Ñтом Ñервере. Ð’Ñ‹ можете иÑпользовать алфавит (a~z, A~Z), цифры (0~9) или Ñимволы Ð¿Ð¾Ð´Ñ‡ÐµÑ€ÐºÐ¸Ð²Ð°Ð½Ð¸Ñ (_). Имена пользователей не могут быть изменены позже." aiChanMode: "Режим Ðй" devMode: "Режим разработчика" -keepCw: "СохранÑйте ÐŸÑ€ÐµÐ´ÑƒÐ¿Ñ€ÐµÐ¶Ð´ÐµÐ½Ð¸Ñ Ð¾ Ñодержимом" +keepCw: "СохранÑйте Ð¿Ñ€ÐµÐ´ÑƒÐ¿Ñ€ÐµÐ¶Ð´ÐµÐ½Ð¸Ñ Ð¾ Ñодержимом" pubSub: "Учётные запиÑи Pub/Sub" lastCommunication: "ПоÑледнее Ñообщение" resolved: "Решено" @@ -878,6 +899,8 @@ makeReactionsPublicDescription: "СпиÑок Ñделанных вами реа classic: "КлаÑÑика" muteThread: "Скрыть цепочку" unmuteThread: "Отменить Ñокрытие цепочки" +followingVisibility: "ВидимоÑть подпиÑок" +followersVisibility: "ВидимоÑть подпиÑчиков" continueThread: "Показать Ñледующие ответы" deleteAccountConfirm: "Ð£Ñ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ будет безвозвратно удалена. Подтверждаете?" incorrectPassword: "Пароль неверен." @@ -987,6 +1010,7 @@ assign: "Ðазначить" unassign: "Отменить назначение" color: "Цвет" manageCustomEmojis: "УправлÑть пользовательÑкими Ñмодзи" +manageAvatarDecorations: "Управление украшениÑми аватара" youCannotCreateAnymore: "Ð’Ñ‹ доÑтигли лимита ÑозданиÑ." cannotPerformTemporary: "Временно недоÑтупен" cannotPerformTemporaryDescription: "Ðто дейÑтвие временно невозможно выполнить из-за Ð¿Ñ€ÐµÐ²Ñ‹ÑˆÐµÐ½Ð¸Ñ Ð»Ð¸Ð¼Ð¸Ñ‚Ð° выполнениÑ." @@ -1003,7 +1027,8 @@ thisPostMayBeAnnoying: "Ðто Ñообщение может быть непри thisPostMayBeAnnoyingHome: "Ðтот поÑÑ‚ может быть отправлен на главную" thisPostMayBeAnnoyingCancel: "Ðтот поÑÑ‚ не может быть отменен." thisPostMayBeAnnoyingIgnore: "Ðтот поÑÑ‚ может быть проигнорирован " -collapseRenotes: "Свернуть репоÑты" +collapseRenotes: "Сворачивать увиденные репоÑты" +collapseRenotesDescription: "Сворачивать поÑты Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ð¼Ð¸ вы взаимодейÑтвовали." internalServerError: "ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ° Ñервера" internalServerErrorDescription: "Внутри Ñервера произошла Ð½ÐµÐ¿Ñ€ÐµÐ´Ð²Ð¸Ð´ÐµÐ½Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°." copyErrorInfo: "Скопировать код ошибки" @@ -1027,7 +1052,10 @@ resetPasswordConfirm: "СброÑить пароль?" sensitiveWords: "ЧувÑтвительные Ñлова" sensitiveWordsDescription: "УÑтановите общедоÑтупный диапазон заметки, Ñодержащей заданное Ñлово, на домашний. Можно Ñделать неÑколько наÑтроек, разделив их переноÑами Ñтрок." sensitiveWordsDescription2: "Разделение пробелом Ñоздаёт Ñпецификацию AND, а разделение коÑой чертой Ñоздаёт регулÑрное выражение." +prohibitedWords: "Запрещённые Ñлова" +prohibitedWordsDescription: "Включает вывод ошибки при попытке опубликовать поÑÑ‚, Ñодержащий указанное Ñлово/набор Ñлов.\nМножеÑтво Ñлов может быть указано, разделÑемые новой Ñтрокой." prohibitedWordsDescription2: "Разделение пробелом Ñоздаёт Ñпецификацию AND, а разделение коÑой чертой Ñоздаёт регулÑрное выражение." +hiddenTags: "Скрытые хештеги" notesSearchNotAvailable: "ПоиÑк заметок недоÑтупен" license: "ЛицензиÑ" unfavoriteConfirm: "Удалить избранное?" @@ -1038,9 +1066,14 @@ retryAllQueuesConfirmTitle: "Хотите попробовать ещё раз?" retryAllQueuesConfirmText: "Ðагрузка на Ñервер может увеличитьÑÑ" enableChartsForRemoteUser: "Создание диаграмм Ð´Ð»Ñ ÑƒÐ´Ð°Ð»Ñ‘Ð½Ð½Ñ‹Ñ… пользователей" enableChartsForFederatedInstances: "Создание диаграмм Ð´Ð»Ñ ÑƒÐ´Ð°Ð»Ñ‘Ð½Ð½Ñ‹Ñ… Ñерверов" +showClipButtonInNoteFooter: "Показать кнопку Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð² подборку в меню дейÑтвий Ñ Ð·Ð°Ð¼ÐµÑ‚ÐºÐ¾Ð¹" +reactionsDisplaySize: "Размер реакций" +limitWidthOfReaction: "Ограничить макÑимальную ширину реакций и отображать их в уменьшенном размере." noteIdOrUrl: "ID или ÑÑылка на заметку" video: "Видео" videos: "Видео" +audio: "Звук" +audioFiles: "Звуковые файлы" dataSaver: "ÐÐºÐ¾Ð½Ð¾Ð¼Ð¸Ñ Ñ‚Ñ€Ð°Ñ„Ð¸ÐºÐ°" accountMigration: "ÐŸÐµÑ€ÐµÐ½Ð¾Ñ ÑƒÑ‡Ñ‘Ñ‚Ð½Ð¾Ð¹ запиÑи" accountMoved: "Ð£Ñ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ перенеÑена" @@ -1052,12 +1085,13 @@ editMemo: "Изменить памÑтку" reactionsList: "СпиÑок реакций" renotesList: "РепоÑты" notificationDisplay: "Отображение уведомлений" -leftTop: "Влево вверх" -rightTop: "Вправо вверх" -leftBottom: "Влево вниз" -rightBottom: "Вправо вниз" -vertical: "ВертикальнаÑ" -horizontal: "Сбоку" +leftTop: "Слева вверху" +rightTop: "Справа Ñверху" +leftBottom: "Слева внизу" +rightBottom: "Справа внизу" +stackAxis: "Положение уведомлений" +vertical: "Вертикально" +horizontal: "Горизонтально" position: "ПозициÑ" serverRules: "Правила Ñервера" pleaseConfirmBelowBeforeSignup: "Ð”Ð»Ñ Ñ€ÐµÐ³Ð¸Ñтрации на данном Ñервере, необходимо ÑоглаÑитÑÑ Ñ Ð½Ð¸Ð¶ÐµÑледующими положениÑми." @@ -1069,57 +1103,114 @@ createNoteFromTheFile: "Создать заметку из Ñтого файла archive: "Ðрхив" channelArchiveConfirmTitle: "ПеремеÑтить {name} в архив?" channelArchiveConfirmDescription: "Ðрхивированные каналы переÑтанут отображатьÑÑ Ð² ÑпиÑке каналов или результатах поиÑка. Ð’ них также Ð½ÐµÐ»ÑŒÐ·Ñ Ð±ÑƒÐ´ÐµÑ‚ добавлÑть новые запиÑи." +thisChannelArchived: "Ðтот канал находитÑÑ Ð² архиве." displayOfNote: "Отображение заметок" initialAccountSetting: "ÐаÑтройка профилÑ" youFollowing: "ПодпиÑки" preventAiLearning: "ОтказатьÑÑ Ð¾Ñ‚ иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð² машинном обучении (Генеративный ИИ)" +preventAiLearningDescription: "ЗапроÑить краулеров не иÑпользовать опубликованный текÑÑ‚ или Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¸ Ñ‚.д. Ð´Ð»Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð½Ð¾Ð³Ð¾ Ð¾Ð±ÑƒÑ‡ÐµÐ½Ð¸Ñ (Прогнозирующий / Генеративный ИИ) датаÑетов. Ðто доÑтигаетÑÑ Ð¿ÑƒÑ‚Ñ‘Ð¼ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ \"noai\" HTTP-заголовка в ответ на ÑоответÑтвующий контент. Полного Ð¿Ñ€ÐµÐ´Ð¾Ñ‚Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ñ‡ÐµÑ€ÐµÐ· Ñтот заголовок не избежать, так как он может быть проÑто проигнорирован." options: "ÐаÑтройки ролей" specifyUser: "Указанный пользователь" +openTagPageConfirm: "Открыть Ñтраницу Ñтого хештега?" +specifyHost: "Указать Ñайт" failedToPreviewUrl: "Предварительный проÑмотр недоÑтупен" update: "Обновить" rolesThatCanBeUsedThisEmojiAsReaction: "Роли тех, кому можно иÑпользовать Ñти Ñмодзи как реакцию" rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ЕÑли здеÑÑŒ ничего не указать, в качеÑтве реакции Ñту Ñмодзи Ñможет иÑпользовать каждый." +rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Ðти роли должны быть общедоÑтупными." +cancelReactionConfirm: "Ð’Ñ‹ дейÑтвительно хотите удалить Ñвою реакцию?" later: "Позже" goToMisskey: "К Misskey" additionalEmojiDictionary: "Дополнительные Ñловари Ñмодзи" installed: "УÑтановлено" branding: "Бренд" +enableServerMachineStats: "Опубликовать характериÑтики Ñервера" enableIdenticonGeneration: "Включить генерацию иконки пользователÑ" turnOffToImprovePerformance: "Отключение Ñтого параметра может повыÑить производительноÑть." +createInviteCode: "Создать код приглашениÑ" +createCount: "КоличеÑтво приглашений" expirationDate: "Дата иÑтечениÑ" -unused: "ÐеиÑпользуемый" +noExpirationDate: "БеÑÑрочно" +unused: "ÐеиÑпользованное" +used: "ИÑпользован" expired: "Срок дейÑÑ‚Ð²Ð¸Ñ Ð¿Ñ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ Ð¸Ñтёк" doYouAgree: "СоглаÑны?" icon: "Ðватар" replies: "Ответы" renotes: "РепоÑÑ‚" loadReplies: "Показать ответы" +pinnedList: "Закреплённый ÑпиÑок" +keepScreenOn: "Держать Ñкран включённым" +showRenotes: "Показывать репоÑты" +mutualFollow: "Взаимные подпиÑки" +followingOrFollower: "ПодпиÑки или подпиÑчики" +fileAttachedOnly: "Только заметки Ñ Ñ„Ð°Ð¹Ð»Ð°Ð¼Ð¸" +showRepliesToOthersInTimeline: "Показывать ответы в ленте" +showRepliesToOthersInTimelineAll: "Показывать в ленте ответы пользователей, на которых вы подпиÑаны" +hideRepliesToOthersInTimelineAll: "Скрывать в ленте ответы пользователей, на которых вы подпиÑаны" sourceCode: "ИÑходный код" +sourceCodeIsNotYetProvided: "ИÑходный код пока не доÑтупен. СвÑжитеÑÑŒ Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратором, чтобы иÑправить Ñту проблему." +repositoryUrl: "СÑылка на репозиторий" +repositoryUrlDescription: "ЕÑли вы иÑпользуете Misskey как еÑть (без изменений в иÑходном коде), введите https://github.com/misskey-dev/misskey" +privacyPolicy: "Политика КонфиденциальноÑти" +privacyPolicyUrl: "СÑылка на Политику КонфиденциальноÑти" +attach: "Прикрепить" +angle: "Угол" flip: "Переворот" +disableStreamingTimeline: "Отключить обновление ленты в режиме реального времени" +useGroupedNotifications: "Отображать ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ñгруппировано" +doReaction: "Добавить реакцию" code: "Код" +remainingN: "ОÑтаётÑÑ: {n}" +seasonalScreenEffect: "Ðффект времени года на Ñкране" +decorate: "УкраÑить" +addMfmFunction: "Добавить MFM" lastNDays: "ПоÑледние {n} Ñут" +hemisphere: "МеÑто проживаниÑ" +enableHorizontalSwipe: "Смахните в Ñторону, чтобы Ñменить вкладки" surrender: "Ðтот поÑÑ‚ не может быть отменен." +useNativeUIForVideoAudioPlayer: "ИÑпользовать Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ð° при проигрывании видео и звука" +keepOriginalFilename: "СохранÑть иÑходное Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°" +keepOriginalFilenameDescription: "ЕÑли вы выключите данную наÑтройку, имена файлов будут автоматичеÑки заменены Ñлучайной Ñтрокой при загрузке." +alwaysConfirmFollow: "Ð’Ñегда подтверждать подпиÑку" +inquiry: "СвÑзатьÑÑ" _delivery: stop: "Заморожено" _type: none: "ПубликациÑ" +_announcement: + tooManyActiveAnnouncementDescription: "Большое количеÑтво оповещений может ухудшить пользовательÑкий опыт. РаÑÑмотрите архивирование неактуальных оповещений. " _initialAccountSetting: accountCreated: "Ðккаунт уÑпешно Ñоздан!" letsStartAccountSetup: "Давайте наÑтроим вашу учётную запиÑÑŒ." profileSetting: "ÐаÑтройки профилÑ" privacySetting: "ÐаÑтройки конфиденциальноÑти" initialAccountSettingCompleted: "ÐŸÐµÑ€Ð²Ð¾Ð½Ð°Ñ‡Ð°Ð»ÑŒÐ½Ð°Ñ Ð½Ð°Ñтройка уÑпешно завершена!" + startTutorial: "Пройти Обучение" skipAreYouSure: "ПропуÑтить наÑтройку?" _initialTutorial: + launchTutorial: "Пройти обучение" _note: description: "ПоÑты в Misskey называютÑÑ 'Заметками.' Заметки отÑортированы в хронологичеÑком порÑдке в ленте и обновлÑÑŽÑ‚ÑÑ Ð² режиме реального времени." + _reaction: + reactToContinue: "Добавьте реакцию, чтобы продолжить." + _postNote: + _visibility: + public: "Ð¢Ð²Ð¾Ñ Ð·Ð°Ð¼ÐµÑ‚ÐºÐ° будет видна вÑем." + doNotSendConfidencialOnDirect2: "ÐдминиÑтратор целевого Ñервера может видеть что вы отправлÑете. Будьте оÑторожны Ñ ÐºÐ¾Ð½Ñ„Ð¸Ð´ÐµÐ½Ñ†Ð¸Ð°Ð»ÑŒÐ½Ð¾Ð¹ информацией, когда отправлÑете личные заметки пользователÑм Ñ Ð½ÐµÐ½Ð°Ð´Ñ‘Ð¶Ð½Ñ‹Ñ… Ñерверов." _timelineDescription: home: "Ð’ перÑональной ленте раÑполагаютÑÑ Ð·Ð°Ð¼ÐµÑ‚ÐºÐ¸ тех, на которых вы подпиÑаны." - local: "МеÑÑ‚Ð½Ð°Ñ Ð»ÐµÐ½Ñ‚Ð° показывает заметки вÑех пользователей Ñтого Ñайта." + local: "МеÑÑ‚Ð½Ð°Ñ Ð»ÐµÐ½Ñ‚Ð° показывает заметки вÑех пользователей Ñтого ÑкземплÑра." social: "Ð’ Ñоциальной ленте ÑобираетÑÑ Ð²ÑÑ‘, что еÑть в перÑональной и меÑтной лентах." - global: "Ð’ глобальную ленту попадает вообще вÑÑ‘ Ñо ÑвÑзанных инÑтанÑов." + global: "Ð’ глобальную ленту попадает вообще вÑÑ‘ Ñо ÑвÑзанных ÑкземплÑров." _serverSettings: iconUrl: "ÐÐ´Ñ€ÐµÑ Ð½Ð° иконку роли" +_accountMigration: + moveFrom: "ПеренеÑти другую учётную запиÑÑŒ Ñюда" + moveTo: "ПеренеÑти учётную запиÑÑŒ на другой Ñервер" + moveAccountDescription: "Ðто дейÑтвие перенеÑёт ваш аккаунт на другой Ñервер.\n ・ПодпиÑчики Ñ Ñтого аккаунта автоматичеÑки подпишутÑÑ Ð½Ð° новый\n ・Ðтот аккаунт отпишетÑÑ Ð¾Ñ‚ вÑех пользователей, на которых подпиÑан ÑейчаÑ\n ・Вы не Ñможете Ñоздавать новые заметки и Ñ‚.д. на Ñтом аккаунте\n\nТогда как Ð¿ÐµÑ€ÐµÐ½Ð¾Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñчиков проиÑходит автоматичеÑки, вы должны будете подготовитьÑÑ, Ñделав некоторые шаги, чтобы перенеÑти ÑпиÑок пользователей, на которых вы подпиÑаны. Чтобы Ñделать Ñто, ÑкÑпортируйте ÑпиÑок подпиÑчиков в файл, который затем импортируете на новом аккаунте в меню наÑтроек. То же Ñамое необходимо будет Ñделать Ñо ÑпиÑками, также как и Ñо Ñкрытыми и заблокированными пользователÑми.\n\n(Ðто объÑÑнение применÑетÑÑ Ðº Misskey v13.12.0 и выше. Другое ActivityPub программное обеÑпечение, такое, как Mastodon, может работать по-другому." + startMigration: "ПеренеÑти" + movedAndCannotBeUndone: "Ðккаунт был перемещён. Ðто дейÑтвие необратимо." _achievements: earnedAt: "Разблокировано в" _types: @@ -1395,6 +1486,7 @@ _role: canPublicNote: "Может публиковать общедоÑтупные заметки" canInvite: "Может Ñоздавать приглаÑительные коды" canManageCustomEmojis: "УправлÑть пользовательÑкими Ñмодзи" + canManageAvatarDecorations: "Управление украшениÑми аватара" driveCapacity: "ДоÑтупное проÑтранÑтво на «диÑке»" alwaysMarkNsfw: "Ð’Ñегда отмечать файлы как «не Ð´Ð»Ñ Ð²Ñех»" pinMax: "ДоÑтупное количеÑтво закреплённых заметок" @@ -1505,6 +1597,11 @@ _aboutMisskey: donate: "Пожертвование на Misskey" morePatrons: "Большое ÑпаÑибо и многим другим, кто принÑл учаÑтие в Ñтом проекте! 🥰" patrons: "ÐœÐ°Ñ‚ÐµÑ€Ð¸Ð°Ð»ÑŒÐ½Ð°Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶ÐºÐ°" + projectMembers: "УчаÑтники проекта" +_displayOfSensitiveMedia: + respect: "Скрывать Ñодержимое не Ð´Ð»Ñ Ð²Ñех" + ignore: "Показывать Ñодержимое не Ð´Ð»Ñ Ð²Ñех" + force: "Скрывать вÑÑ‘ Ñодержимое" _instanceTicker: none: "Ðе показывать" remote: "Только Ð´Ð»Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… Ñайтов" @@ -1533,7 +1630,7 @@ _wordMute: muteWordsDescription: "Пишите Ñлова через пробел в одной Ñтроке, чтобы фильтровать их поÑвление вмеÑте; а еÑли хотите фильтровать любое из них, пишите в отдельных Ñтроках." muteWordsDescription2: "ЗдеÑÑŒ можно иÑпользовать регулÑрные Ð²Ñ‹Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ â€” проÑто заключите их между Ð´Ð²ÑƒÐ¼Ñ Ð´Ñ€Ð¾Ð±Ð½Ñ‹Ð¼Ð¸ чертами (/)." _instanceMute: - instanceMuteDescription: "Заметки и репоÑты Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð½Ñ‹Ñ… здеÑÑŒ инÑтанÑов, а также ответы пользователÑм оттуда же не будут отображатьÑÑ." + instanceMuteDescription: "Любые активноÑти, затрагивающие инÑтанÑÑ‹ из данного ÑпиÑка, будут Ñкрыты." instanceMuteDescription2: "Пишите каждый инÑÑ‚Ð°Ð½Ñ Ð½Ð° отдельной Ñтроке" title: "Скрывает заметки Ñ Ð·Ð°Ð´Ð°Ð½Ð½Ñ‹Ñ… инÑтанÑов." heading: "СпиÑок Ñкрытых инÑтанÑов" @@ -1582,7 +1679,7 @@ _theme: navActive: "ТекÑÑ‚ на боковой панели (активирован)" navIndicator: "Индикатор на боковой панели" link: "СÑылка" - hashtag: "Ð¥Ñштег" + hashtag: "Хештег" mention: "Упоминание" mentionMe: "Ð£Ð¿Ð¾Ð¼Ð¸Ð½Ð°Ð½Ð¸Ñ Ð²Ð°Ñ" renote: "РепоÑÑ‚" @@ -1612,6 +1709,10 @@ _sfx: note: "Заметки" noteMy: "СобÑтвенные заметки" notification: "УведомлениÑ" + reaction: "При выборе реакции" +_soundSettings: + driveFile: "ИÑпользовать аудиофайл Ñ Ð”Ð¸Ñка." + driveFileWarn: "Выбрать аудиофайл Ñ Ð”Ð¸Ñка." _ago: future: "Из будущего" justNow: "Только что" @@ -1690,6 +1791,7 @@ _permissions: "write:gallery": "Редактирование галереи" "read:gallery-likes": "ПроÑмотр ÑпиÑка понравившегоÑÑ Ð² галерее" "write:gallery-likes": "Изменение ÑпиÑка понравившегоÑÑ Ð² галерее" + "write:admin:reset-password": "СброÑить пароль пользователю" _auth: shareAccessTitle: "Ð Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ð¹" shareAccess: "Дать доÑтуп Ð´Ð»Ñ Â«{name}» к вашей учётной запиÑи?" @@ -1743,6 +1845,7 @@ _widgets: _userList: chooseList: "Выберите ÑпиÑок" clicker: "Счётчик щелчков" + birthdayFollowings: "Пользователи, у которых ÑÐµÐ³Ð¾Ð´Ð½Ñ Ð´ÐµÐ½ÑŒ рождениÑ" _cw: hide: "СпрÑтать" show: "Показать" @@ -1796,7 +1899,7 @@ _profile: name: "ИмÑ" username: "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ" description: "О Ñебе" - youCanIncludeHashtags: "Можете иÑпользовать здеÑÑŒ Ñ…Ñштеги" + youCanIncludeHashtags: "Можете иÑпользовать здеÑÑŒ хештеги." metadata: "Дополнительные ÑведениÑ" metadataEdit: "Редактировать дополнительные ÑведениÑ" metadataDescription: "Можно добавить до четырёх дополнительных граф в профиль." @@ -1804,6 +1907,8 @@ _profile: metadataContent: "Содержимое" changeAvatar: "ПоменÑть аватар" changeBanner: "ПоменÑть изображение в шапке" + verifiedLinkDescription: "Ð£ÐºÐ°Ð·Ñ‹Ð²Ð°Ñ Ð·Ð´ÐµÑÑŒ URL, Ñодержащий ÑÑылку на профиль, иконка Ð²Ð»Ð°Ð´ÐµÐ½Ð¸Ñ Ñ€ÐµÑурÑом может быть отображена Ñ€Ñдом Ñ Ð¿Ð¾Ð»ÐµÐ¼" + avatarDecorationMax: "Ð’Ñ‹ можете добавить до {max} украшений." _exportOrImport: allNotes: "Ð’Ñе заметки\n" favoritedNotes: "Избранное" @@ -1926,6 +2031,9 @@ _notification: unreadAntennaNote: "Ðнтенна {name}" emptyPushNotificationMessage: "Обновлены push-уведомлениÑ" achievementEarned: "Получено доÑтижение" + checkNotificationBehavior: "Проверить внешний вид уведомлениÑ" + sendTestNotification: "Отправить теÑтовое уведомление" + flushNotification: "ОчиÑтить уведомлениÑ" _types: all: "Ð’Ñе" follow: "ПодпиÑки" @@ -1977,19 +2085,57 @@ _dialog: _disabledTimeline: title: "Лента отключена" description: "Ваша Ñ‚ÐµÐºÑƒÑ‰Ð°Ñ Ñ€Ð¾Ð»ÑŒ не позволÑет пользоватьÑÑ Ñтой лентой." +_drivecleaner: + orderBySizeDesc: "Размеры файлов по убыванию" + orderByCreatedAtAsc: "По увеличению даты" _webhookSettings: createWebhook: "Создать вебхук" + modifyWebhook: "Изменить Вебхук" name: "Ðазвание" + secret: "Секрет" + trigger: "УÑловие ÑрабатываниÑ" active: "Вкл." + _events: + follow: "Когда подпиÑалиÑÑŒ на пользователÑ" + followed: "Когда на Ð²Ð°Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑалиÑÑŒ" + note: "Когда Ñоздали заметку" + reply: "Когда получили ответ на заметку" + renote: "Когда Ð²Ð°Ñ Ñ€ÐµÐ¿Ð¾Ñтнули" + reaction: "Когда получили реакцию" + mention: "Когда Ð²Ð°Ñ ÑƒÐ¿Ð¾Ð¼Ð¸Ð½Ð°ÑŽÑ‚" + _systemEvents: + abuseReport: "Когда приходит жалоба" + abuseReportResolved: "Когда разрешаетÑÑ Ð¶Ð°Ð»Ð¾Ð±Ð°" + userCreated: "Когда Ñоздан пользователь" + deleteConfirm: "Ð’Ñ‹ уверены, что хотите удалить Ñтот Вебхук?" _abuseReport: _notificationRecipient: _recipientType: mail: "ÐÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð°" + webhook: "Вебхук" + _captions: + webhook: "Отправить уведомление СиÑтемному Вебхуку при получении или разрешении жалоб." + notifiedWebhook: "ИÑпользуемый Вебхук" _moderationLogTypes: suspend: "Заморозить" addCustomEmoji: "Добавлено Ñмодзи" updateCustomEmoji: "Изменено Ñмодзи" deleteCustomEmoji: "Удалено Ñмодзи" + deleteDriveFile: "Файл удалён" resetPassword: "Ð¡Ð±Ñ€Ð¾Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ:" + createInvitation: "Создать код приглашениÑ" + createSystemWebhook: "Создать СиÑтемный Вебхук" + updateSystemWebhook: "Обновить СиÑтемый Вебхук" + deleteSystemWebhook: "Удалить СиÑтемный Вебхук" +_fileViewer: + url: "СÑылка" + attachedNotes: "Закреплённые заметки" +_dataSaver: + _code: + title: "ПодÑветка кода" +_hemisphere: + N: "Северное полушарие" + S: "Южное полушарие" + caption: "ИÑпользуетÑÑ Ð´Ð»Ñ Ð½ÐµÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… наÑтроек клиента Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ñезона." _reversi: total: "Ð’Ñего" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index 41f8949196..eb1675bdb0 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -454,7 +454,6 @@ uiLanguage: "Jazyk použÃvateľského prostredia" aboutX: "O {x}" emojiStyle: "Å týl emoji" native: "NatÃvne" -disableDrawer: "NepoužÃvaÅ¥ Å¡uflÃkové menu" showNoteActionsOnlyHover: "Ovládacie prvky poznámky sa zobrazujú len po nabehnutà myÅ¡i" noHistory: "Žiadna história" signinHistory: "História prihlásenÃ" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 2fb4a5253a..f5d29a2ce5 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -509,7 +509,6 @@ uiLanguage: "ภาษาà¸à¸´à¸™à¹€à¸—à¸à¸£à¹Œà¹€à¸Ÿà¸‹à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸‡à aboutX: "เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸š {x}" emojiStyle: "สไตล์ขà¸à¸‡à¹€à¸à¹‚มจิ" native: "ภาษาà¹à¸¡à¹ˆ" -disableDrawer: "ไม่à¹à¸ªà¸”งเมนูในรูปà¹à¸šà¸šà¸¥à¸´à¹‰à¸™à¸Šà¸±à¸" showNoteActionsOnlyHover: "à¹à¸ªà¸”งà¸à¸²à¸£à¸”ำเนินà¸à¸²à¸£à¹‚น้ตเมื่à¸à¹‚ฮเวà¸à¸£à¹Œ(วางเมาส์เหนืà¸)เท่านั้น" showReactionsCount: "à¹à¸ªà¸”งจำนวนรีà¹à¸à¸à¸Šà¸±à¹ˆà¸™à¹ƒà¸™à¹‚น้ต" noHistory: "ไม่มีประวัติ" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 36d741d30e..e51156ce22 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -452,7 +452,6 @@ language: "Мова" uiLanguage: "Мова інтерфейÑу" aboutX: "Про {x}" native: "міÑцевий" -disableDrawer: "Ðе викориÑтовувати виÑувні меню" noHistory: "ІÑÑ‚Ð¾Ñ€Ñ–Ñ Ð¿Ð¾Ñ€Ð¾Ð¶Ð½Ñ" signinHistory: "ІÑÑ‚Ð¾Ñ€Ñ–Ñ Ð²Ñ…Ð¾Ð´Ñ–Ð²" enableAdvancedMfm: "Увімкнути розширений MFM" diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml index ee4ab83ce7..cf2e5f2fe7 100644 --- a/locales/uz-UZ.yml +++ b/locales/uz-UZ.yml @@ -471,7 +471,6 @@ uiLanguage: "Interfeys tili" aboutX: "{x} haqida" emojiStyle: "Emoji ko'rinishi" native: "Mahalliy" -disableDrawer: "Slayd menyusidan foydalanmang" showNoteActionsOnlyHover: "Eslatma amallarini faqat sichqonchani olib borganda ko‘rsatish" noHistory: "Tarix yo'q" signinHistory: "kirish tarixi" diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index aadbf8b16f..f3979bbd3c 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -486,7 +486,6 @@ uiLanguage: "Ngôn ngữ giao diện" aboutX: "Giá»›i thiệu {x}" emojiStyle: "Kiểu cách Emoji" native: "Bản xứ" -disableDrawer: "Không dùng menu thanh bên" showNoteActionsOnlyHover: "Chỉ hiển thị các hà nh động ghi chú khi di chuá»™t" noHistory: "Không có dữ liệu" signinHistory: "Lịch sỠđăng nháºp" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index d422c3afc5..0d76361d6f 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -334,6 +334,7 @@ renameFolder: "é‡å‘½å文件夹" deleteFolder: "åˆ é™¤æ–‡ä»¶å¤¹" folder: "文件夹" addFile: "æ·»åŠ æ–‡ä»¶" +showFile: "显示文件" emptyDrive: "ç½‘ç›˜ä¸æ— 文件" emptyFolder: "æ¤æ–‡ä»¶å¤¹ä¸æ— 文件" unableToDelete: "æ— æ³•åˆ é™¤" @@ -509,7 +510,9 @@ uiLanguage: "显示è¯è¨€" aboutX: "关于 {x}" emojiStyle: "表情符å·çš„æ ·å¼" native: "原生" -disableDrawer: "䏿˜¾ç¤ºæŠ½å±‰èœå•" +menuStyle: "èœå•æ ·å¼" +style: "æ ·å¼" +popup: "弹窗" showNoteActionsOnlyHover: "ä»…åœ¨æ‚¬åœæ—¶æ˜¾ç¤ºå¸–åæ“ä½œ" showReactionsCount: "显示帖å的回应数" noHistory: "没有历å²è®°å½•" @@ -592,6 +595,8 @@ ascendingOrder: "å‡åº" descendingOrder: "é™åº" scratchpad: "AiScript 控制å°" scratchpadDescription: "AiScript 控制å°ä¸º AiScript æä¾›äº†å®žéªŒçŽ¯å¢ƒã€‚æ‚¨å¯ä»¥ç¼–写代ç 与 Misskey 交互,è¿è¡Œå¹¶æŸ¥çœ‹ç»“果。" +uiInspector: "UI 检查器" +uiInspectorDescription: "查看所有内å˜ä¸ç”± UI 组件生æˆå‡ºçš„实例。UI 组件由 UI:C 系列函数所生æˆã€‚" output: "输出" script: "脚本" disablePagesScript: "ç¦ç”¨é¡µé¢è„šæœ¬" @@ -1263,6 +1268,15 @@ confirmWhenRevealingSensitiveMedia: "æ˜¾ç¤ºæ•æ„Ÿå†…容å‰éœ€è¦ç¡®è®¤" sensitiveMediaRevealConfirm: "è¿™æ˜¯æ•æ„Ÿå†…å®¹ã€‚æ˜¯å¦æ˜¾ç¤ºï¼Ÿ" createdLists: "已创建的列表" createdAntennas: "已创建的天线" +fromX: "从 {x}" +genEmbedCode: "生æˆåµŒå…¥ä»£ç " +noteOfThisUser: "æ¤ç”¨æˆ·çš„帖å" +clipNoteLimitExceeded: "æ— æ³•å†å¾€æ¤ä¾¿ç¾å†…æ·»åŠ æ›´å¤šå¸–å" +performance: "性能" +signinWithPasskey: "使用通行密钥登录" +unknownWebAuthnKey: "æ¤é€šè¡Œå¯†é’¥æœªæ³¨å†Œã€‚" +passkeyVerificationFailed: "验è¯é€šè¡Œå¯†é’¥å¤±è´¥ã€‚" +passkeyVerificationSucceededButPasswordlessLoginDisabled: "é€šè¡Œå¯†é’¥éªŒè¯æˆåŠŸï¼Œä½†è´¦æˆ·æœªå¼€å¯æ— 密ç 登录。" _delivery: status: "投递状æ€" stop: "åœæ¢æŠ•递" @@ -1397,6 +1411,7 @@ _serverSettings: fanoutTimelineDescription: "当å¯ç”¨æ—¶ï¼Œå¯æ˜¾è‘—æé«˜èŽ·å–å„ç§æ—¶é—´çº¿æ—¶çš„æ€§èƒ½ï¼Œå¹¶å‡è½»æ•°æ®åº“的负è·ã€‚但是相对的 Redis 的内å˜ä½¿ç”¨é‡å°†ä¼šå¢žåŠ ã€‚å¦‚æžœæœåŠ¡å™¨çš„å†…å˜ä¸æ˜¯å¾ˆå¤§ï¼Œåˆæˆ–者è¿è¡Œä¸ç¨³å®šçš„è¯å¯ä»¥æŠŠå®ƒå…³æŽ‰ã€‚" fanoutTimelineDbFallback: "回退到数æ®åº“" fanoutTimelineDbFallbackDescription: "当å¯ç”¨æ—¶ï¼Œè‹¥æ—¶é—´çº¿æœªè¢«ç¼“å˜ï¼Œåˆ™å°†é¢å¤–查询数æ®åº“。ç¦ç”¨è¯¥åŠŸèƒ½å¯é€šè¿‡ä¸æ‰§è¡Œå›žé€€å¤„ç†è¿›ä¸€æ¥å‡å°‘æœåŠ¡å™¨è´Ÿè½½ï¼Œä½†ä¼šé™åˆ¶å¯æ£€ç´¢çš„æ—¶é—´çº¿èŒƒå›´ã€‚" + reactionsBufferingDescription: "开坿—¶å¯æ˜¾è‘—æé«˜å‘é€å›žåº”时的性能,åŠå‡è½»æ•°æ®åº“è´Ÿè·ã€‚但 Redis 的内å˜ç”¨é‡ä¼šç›¸åº”å¢žåŠ ã€‚" inquiryUrl: "è”络地å€" inquiryUrlDescription: "ç”¨æ¥æŒ‡å®šè¯¸å¦‚呿œåŠ¡è¿è¥å•†å’¨è¯¢çš„论å›åœ°å€ï¼Œæˆ–记载了è¿è¥å•†è”系方å¼ä¹‹ç±»çš„网页地å€ã€‚" _accountMigration: @@ -1598,7 +1613,7 @@ _achievements: _postedAt0min0sec: title: "报时" description: "在 0 点å‘布一篇帖å" - flavor: "嘣 嘣 嘣 Biu——ï¼" + flavor: "æŠ¥æ—¶ä¿¡å·æœ€åŽä¸€å“,零点整" _selfQuote: title: "自我引用" description: "引用了自己的帖å" @@ -1647,8 +1662,8 @@ _achievements: flavor: "今年也请对本æœåŠ¡å™¨å¤šå¤šæŒ‡æ•™ï¼" _cookieClicked: title: "ç‚¹å‡»é¥¼å¹²å°æ¸¸æˆ" - description: "点击了å¯ç–‘的饼干" - flavor: "æ˜¯ä¸æ˜¯è½¯ä»¶æœ‰é—®é¢˜ï¼Ÿ" + description: "点击了饼干" + flavor: "用错软件了?" _brainDiver: title: "Brain Diver" description: "å‘å¸ƒäº†åŒ…å« Brain Diver 链接的帖å" @@ -1665,7 +1680,7 @@ _achievements: _bubbleGameDoubleExplodingHead: title: "两个🤯" description: "ä½ åˆæˆå‡ºäº†2个游æˆé‡Œæœ€å¤§çš„Emoji" - flavor: "" + flavor: "大约能 装满 这些便当盒 🤯 🤯 (比划)" _role: new: "创建角色" edit: "编辑角色" @@ -1730,6 +1745,11 @@ _role: canSearchNotes: "是å¦å¯ä»¥æœç´¢å¸–å" canUseTranslator: "使用翻译功能" avatarDecorationLimit: "坿·»åŠ å¤´åƒæŒ‚件的最大个数" + canImportAntennas: "å…许导入天线" + canImportBlocking: "å…许导入拉黑列表" + canImportFollowing: "å…许导入关注列表" + canImportMuting: "å…许导入å±è”½åˆ—表" + canImportUserLists: "å…许导入用户列表" _condition: roleAssignedTo: "已分é…给手动角色" isLocal: "是本地用户" @@ -2362,6 +2382,7 @@ _notification: renotedBySomeUsers: "{n} 人转å‘了" followedBySomeUsers: "被 {n} 人关注" flushNotification: "é‡ç½®é€šçŸ¥åކå²" + exportOfXCompleted: "å·²å®Œæˆ {x} 个导出" _types: all: "全部" note: "用户的新帖å" @@ -2376,6 +2397,8 @@ _notification: followRequestAccepted: "关注请求已通过" roleAssigned: "授予的角色" achievementEarned: "å–å¾—çš„æˆå°±" + exportCompleted: "已完æˆå¯¼å‡º" + test: "测试通知" app: "å…³è”应用的通知" _actions: followBack: "回关" @@ -2442,6 +2465,7 @@ _webhookSettings: abuseReportResolved: "å½“ä¸¾æŠ¥è¢«å¤„ç†æ—¶" userCreated: "当用户被创建时" deleteConfirm: "è¦åˆ 除 webhook å—?" + testRemarks: "点击开关å³ä¾§çš„æŒ‰é’®ï¼Œå¯ä»¥å‘é€ä½¿ç”¨å‡æ•°æ®çš„æµ‹è¯• Webhook。" _abuseReport: _notificationRecipient: createRecipient: "新建举报通知" @@ -2640,3 +2664,17 @@ _contextMenu: app: "应用" appWithShift: "Shift 键应用" native: "æµè§ˆå™¨çš„用户界é¢" +_embedCodeGen: + title: "自定义嵌入代ç " + header: "æ˜¾ç¤ºæ ‡é¢˜" + autoload: "连ç»åŠ è½½ï¼ˆä¸æŽ¨è)" + maxHeight: "最大高度" + maxHeightDescription: "若将最大值设为 0 则ä¸é™åˆ¶æœ€å¤§é«˜åº¦ã€‚为防æ¢å°å·¥å…·æ— é™å¢žé«˜ï¼Œå»ºè®®è®¾ç½®ä¸€ä¸‹ã€‚" + maxHeightWarn: "最大高度é™åˆ¶å·²ç¦ç”¨ï¼ˆ0ï¼‰ã€‚è‹¥è¿™ä¸æ˜¯æ‚¨æƒ³è¦çš„æ•ˆæžœï¼Œè¯·å°†æœ€å¤§é«˜åº¦è®¾ä¸€ä¸ªå€¼ã€‚" + previewIsNotActual: "由于超出了预览画é¢å¯æ˜¾ç¤ºçš„èŒƒå›´ï¼Œå› æ¤æ˜¾ç¤ºå†…容会与实际嵌入时有所ä¸åŒã€‚" + rounded: "圆角" + border: "外边框" + applyToPreview: "应用预览" + generateCode: "生æˆåµŒå…¥ä»£ç " + codeGenerated: "已生æˆä»£ç " + codeGeneratedDescription: "将生æˆçš„代ç 贴到网站上æ¥ä½¿ç”¨ã€‚" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 57da480d28..74c03befd1 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -236,6 +236,8 @@ silencedInstances: "被ç¦è¨€çš„伺æœå™¨" silencedInstancesDescription: "è¨å®šè¦ç¦è¨€çš„伺æœå™¨ä¸»æ©Ÿå稱,以æ›è¡Œåˆ†éš”。隸屬於ç¦è¨€ä¼ºæœå™¨çš„æ‰€æœ‰å¸³æˆ¶éƒ½å°‡è¢«è¦–為「ç¦è¨€å¸³æˆ¶ã€ï¼Œåªèƒ½ç™¼å‡ºã€Œè¿½éš¨è«‹æ±‚ã€ï¼Œè€Œä¸”無法æåŠæœªè¿½éš¨çš„æœ¬åœ°å¸³æˆ¶ã€‚這䏿œƒå½±éŸ¿å·²å°éŽ–çš„å¯¦ä¾‹ã€‚" mediaSilencedInstances: "媒體被ç¦è¨€çš„伺æœå™¨" mediaSilencedInstancesDescription: "è¨å®šæ‚¨æƒ³è¦å°åª’é«”è¨å®šç¦è¨€çš„伺æœå™¨ï¼Œä»¥æ›è¡Œç¬¦è™Ÿå€éš”。來自被媒體ç¦è¨€çš„伺æœå™¨æ‰€å±¬å¸³æˆ¶çš„æ‰€æœ‰æª”æ¡ˆéƒ½æœƒè¢«è¦–ç‚ºæ•æ„Ÿæª”案,且自訂表情符號ä¸èƒ½ä½¿ç”¨ã€‚被å°éŽ–çš„ä¼ºæœå™¨ä¸å—影響。" +federationAllowedHosts: "å…許è¯é‚¦é€šè¨Šçš„伺æœå™¨" +federationAllowedHostsDescription: "è¨å®šå…許è¯é‚¦é€šè¨Šçš„伺æœå™¨ä¸»æ©Ÿï¼Œä»¥æ›è¡Œç¬¦è™Ÿåˆ†éš”。" muteAndBlock: "éœéŸ³å’Œå°éŽ–" mutedUsers: "被éœéŸ³çš„使用者" blockedUsers: "被å°éŽ–çš„ä½¿ç”¨è€…" @@ -334,6 +336,7 @@ renameFolder: "釿–°å‘½å資料夾" deleteFolder: "刪除資料夾" folder: "資料夾" addFile: "åŠ å…¥é™„ä»¶" +showFile: "ç€è¦½æ–‡ä»¶" emptyDrive: "雲端硬碟為空" emptyFolder: "資料夾為空" unableToDelete: "無法刪除" @@ -509,7 +512,10 @@ uiLanguage: "介é¢èªžè¨€" aboutX: "關於{x}" emojiStyle: "è¡¨æƒ…ç¬¦è™Ÿçš„é¢¨æ ¼" native: "原生" -disableDrawer: "ä¸é¡¯ç¤ºä¸‹æ‹‰å¼é¸å–®" +menuStyle: "é¸å–®é¢¨æ ¼" +style: "é¢¨æ ¼" +drawer: "å´é‚Šæ¬„" +popup: "彈出å¼è¦–窗" showNoteActionsOnlyHover: "僅在游標åœç•™æ™‚顯示貼文的æ“作é¸é …" showReactionsCount: "é¡¯ç¤ºè²¼æ–‡çš„åæ‡‰æ•¸ç›®" noHistory: "沒有æ·å²ç´€éŒ„" @@ -592,6 +598,8 @@ ascendingOrder: "昇冪" descendingOrder: "é™å†ª" scratchpad: "æš«å˜è¨˜æ†¶é«”" scratchpadDescription: "AiScript 控制臺為 AiScript 的實驗環境。您å¯ä»¥åœ¨æ¤ç·¨å¯«ã€åŸ·è¡Œå’Œç¢ºèªç¨‹å¼ç¢¼èˆ‡ Misskey äº’å‹•çš„çµæžœã€‚" +uiInspector: "UI 檢查" +uiInspectorDescription: "您å¯ä»¥çœ‹åˆ°è¨˜æ†¶é«”ä¸å˜åœ¨çš„ UI 元件實例的清單。 UI 元件由 Ui:C: 系列函數產生。" output: "輸出" script: "腳本" disablePagesScript: "åœç”¨é é¢çš„ AiScript 腳本" @@ -1186,7 +1194,7 @@ edited: "已編輯" notificationRecieveConfig: "接å—通知的è¨å®š" mutualFollow: "互相追隨" followingOrFollower: "è¿½éš¨ä¸æˆ–追隨者" -fileAttachedOnly: "顯示包å«é™„件的貼文" +fileAttachedOnly: "åªé¡¯ç¤ºåŒ…å«é™„件的貼文" showRepliesToOthersInTimeline: "顯示給其他人的回覆" hideRepliesToOthersInTimeline: "在時間軸上隱è—給其他人的回覆" showRepliesToOthersInTimelineAll: "在時間軸包å«è¿½éš¨ä¸æ‰€æœ‰äººçš„回覆" @@ -1263,6 +1271,18 @@ confirmWhenRevealingSensitiveMedia: "è¦é¡¯ç¤ºæ•感媒體時需確èª" sensitiveMediaRevealConfirm: "é€™æ˜¯æ•æ„Ÿåª’體。確定è¦é¡¯ç¤ºå—Žï¼Ÿ" createdLists: "已建立的清單" createdAntennas: "已建立的天線" +fromX: "自 {x}" +genEmbedCode: "產生嵌入程å¼ç¢¼" +noteOfThisUser: "這個使用者的貼文列表" +clipNoteLimitExceeded: "沒辦法在這個摘錄ä¸å¢žåŠ æ›´å¤šè²¼æ–‡äº†ã€‚" +performance: "性能" +modified: "已變更" +discard: "å–æ¶ˆ" +thereAreNChanges: "有 {n} 處的變更" +signinWithPasskey: "使用密碼金鑰登入" +unknownWebAuthnKey: "未註冊的金鑰。" +passkeyVerificationFailed: "é©—è‰é‡‘鑰失敗。" +passkeyVerificationSucceededButPasswordlessLoginDisabled: "é›–ç„¶é©—è‰é‡‘é‘°æˆåŠŸï¼Œä½†æ˜¯ç„¡å¯†ç¢¼ç™»å…¥çš„æ–¹å¼æ˜¯åœç”¨çš„。" _delivery: status: "傳é€ç‹€æ…‹" stop: "åœæ¢ç™¼é€" @@ -1397,6 +1417,7 @@ _serverSettings: fanoutTimelineDescription: "如果啟用的話,檢索å„å€‹æ™‚é–“è»¸çš„æ€§èƒ½æœƒé¡¯è‘—ææ˜‡ï¼Œè³‡æ–™åº«çš„è² è·ä¹Ÿæœƒæ¸›å°‘。ä¸éŽï¼ŒRedis çš„è¨˜æ†¶é«”ä½¿ç”¨é‡æœƒå¢žåŠ ã€‚å¦‚æžœä¼ºæœå™¨çš„è¨˜æ†¶é«”å®¹é‡æ¯”較少或者é‹è¡Œä¸ç©©å®šï¼Œå¯ä»¥åœç”¨ã€‚" fanoutTimelineDbFallback: "資料庫的回退" fanoutTimelineDbFallbackDescription: "若啟用,在時間軸沒有快å–的情æ³ä¸‹å°‡åŸ·è¡Œå›žé€€è™•ç†ä»¥é¡å¤–查詢資料庫。若åœç”¨ï¼Œå¯ä»¥é€éŽä¸åŸ·è¡Œå›žé€€è™•ç†ä¾†é€²ä¸€æ¥æ¸›å°‘伺æœå™¨çš„è² è·ï¼Œä½†æœƒé™åˆ¶å¯å–得的時間軸範åœã€‚" + reactionsBufferingDescription: "啟用時,å¯ä»¥é¡¯è‘—æé«˜å»ºç«‹åæ‡‰æ™‚çš„æ•ˆèƒ½ä¸¦æ¸›å°‘è³‡æ–™åº«çš„è² è¼‰ã€‚ 但是,Redis è¨˜æ†¶é«”ä½¿ç”¨é‡æœƒå¢žåŠ ã€‚" inquiryUrl: "è¯çµ¡è¡¨å–®ç¶²å€" inquiryUrlDescription: "指定伺æœå™¨é‹ç‡Ÿè€…çš„è¯çµ¡è¡¨å–®ç¶²å€ï¼Œæˆ–包å«é‹ç‡Ÿè€…è¯çµ¡è³‡è¨Šç¶²é 的網å€ã€‚" _accountMigration: @@ -1730,6 +1751,11 @@ _role: canSearchNotes: "å¯å¦æœå°‹è²¼æ–‡" canUseTranslator: "使用翻è¯åŠŸèƒ½" avatarDecorationLimit: "é åƒè£é£¾çš„æœ€å¤§è¨ç½®é‡" + canImportAntennas: "å…許匯入天線" + canImportBlocking: "å…許匯入å°éŽ–åå–®" + canImportFollowing: "å…許匯入跟隨åå–®" + canImportMuting: "å…許匯入éœéŸ³åå–®" + canImportUserLists: "å…許匯入清單" _condition: roleAssignedTo: "手動指派角色完æˆ" isLocal: "本地使用者" @@ -2224,6 +2250,9 @@ _profile: changeBanner: "變更橫幅圖åƒ" verifiedLinkDescription: "å¦‚æžœè¼¸å…¥åŒ…å«æ‚¨å€‹äººè³‡æ–™çš„網站 URLï¼Œæ¬„ä½æ—邊將出ç¾é©—è‰åœ–示。" avatarDecorationMax: "最多å¯ä»¥è¨ç½® {max} 個è£é£¾ã€‚" + followedMessage: "被追隨時的訊æ¯" + followedMessageDescription: "å¯ä»¥è¨å®šè¢«è¿½éš¨æ™‚é¡¯ç¤ºçµ¦å°æ–¹çš„訊æ¯ã€‚" + followedMessageDescriptionForLockedAccount: "如果追隨是需è¦å¯©æ ¸çš„話,在å…許追隨請求之後顯示。" _exportOrImport: allNotes: "所有貼文" favoritedNotes: "「我的最愛ã€è²¼æ–‡" @@ -2362,6 +2391,7 @@ _notification: renotedBySomeUsers: "{n}人åšäº†è½‰ç™¼" followedBySomeUsers: "被{n}人追隨了" flushNotification: "é‡ç½®é€šçŸ¥æ·å²ç´€éŒ„" + exportOfXCompleted: "{x} 的匯出已完æˆã€‚" _types: all: "全部 " note: "使用者的最新貼文" @@ -2376,6 +2406,8 @@ _notification: followRequestAccepted: "追隨請求已接å—" roleAssigned: "已授予角色" achievementEarned: "ç²å¾—æˆå°±" + exportCompleted: "已完æˆåŒ¯å‡ºã€‚" + test: "通知測試" app: "應用程å¼é€šçŸ¥" _actions: followBack: "追隨回去" @@ -2442,6 +2474,7 @@ _webhookSettings: abuseReportResolved: "當處ç†äº†ä½¿ç”¨è€…的檢舉時" userCreated: "使用者被新增時" deleteConfirm: "è«‹å•æ˜¯å¦è¦åˆªé™¤ Webhook?" + testRemarks: "按下切æ›é–‹é—œå³å´çš„æŒ‰éˆ•,就會將å‡è³‡æ–™ç™¼é€è‡³ Webhook。" _abuseReport: _notificationRecipient: createRecipient: "新增接收檢舉的通知å°è±¡" @@ -2640,3 +2673,17 @@ _contextMenu: app: "應用程å¼" appWithShift: "Shift 鵿‡‰ç”¨ç¨‹å¼" native: "ç€è¦½å™¨çš„使用者介é¢" +_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..edc1d7e318 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2024.8.0", + "version": "2024.9.0", "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/1723944246767-followedMessage.js b/packages/backend/migration/1723944246767-followedMessage.js new file mode 100644 index 0000000000..fc9ad1cb85 --- /dev/null +++ b/packages/backend/migration/1723944246767-followedMessage.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class FollowedMessage1723944246767 { + name = 'FollowedMessage1723944246767'; + + async up(queryRunner) { + await queryRunner.query('ALTER TABLE "user_profile" ADD "followedMessage" character varying(256)'); + } + + async down(queryRunner) { + await queryRunner.query('ALTER TABLE "user_profile" DROP COLUMN "followedMessage"'); + } +} 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/migration/1727491883993-user-score.js b/packages/backend/migration/1727491883993-user-score.js new file mode 100644 index 0000000000..7292d5363c --- /dev/null +++ b/packages/backend/migration/1727491883993-user-score.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class UserScore1727491883993 { + name = 'UserScore1727491883993' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ADD "score" integer NOT NULL DEFAULT '0'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "score"`); + } +} diff --git a/packages/backend/migration/1727512908322-meta-federation.js b/packages/backend/migration/1727512908322-meta-federation.js new file mode 100644 index 0000000000..52c24df4f7 --- /dev/null +++ b/packages/backend/migration/1727512908322-meta-federation.js @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class MetaFederation1727512908322 { + name = 'MetaFederation1727512908322' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "federation" character varying(128) NOT NULL DEFAULT 'all'`); + await queryRunner.query(`ALTER TABLE "meta" ADD "federationHosts" character varying(1024) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "federationHosts"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "federation"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index f497610af9..6eed6fc725 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": "6.0.0", + "@bull-board/fastify": "6.0.0", + "@bull-board/ui": "6.0.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..6ae8ccfbb3 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.after) { + (meta as any)[key] = (body.after 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 3e5a1e81cd..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; @@ -133,7 +134,7 @@ export type Config = { proxySmtp: string | undefined; proxyBypassHosts: string[] | undefined; allowedPrivateNetworks: string[] | undefined; - maxFileSize: number | undefined; + maxFileSize: number; clusterLimit: number | undefined; id: string; outgoingAddress: string | undefined; @@ -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, @@ -250,7 +261,7 @@ export function loadConfig(): Config { proxySmtp: config.proxySmtp, proxyBypassHosts: config.proxyBypassHosts, allowedPrivateNetworks: config.allowedPrivateNetworks, - maxFileSize: config.maxFileSize, + maxFileSize: config.maxFileSize ?? 262144000, clusterLimit: config.clusterLimit, outgoingAddress: config.outgoingAddress, outgoingAddressFamily: config.outgoingAddressFamily, @@ -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/DownloadService.ts b/packages/backend/src/core/DownloadService.ts index 21ae798f9f..93f4a38246 100644 --- a/packages/backend/src/core/DownloadService.ts +++ b/packages/backend/src/core/DownloadService.ts @@ -42,7 +42,7 @@ export class DownloadService { const timeout = 30 * 1000; const operationTimeout = 60 * 1000; - const maxSize = this.config.maxFileSize ?? 262144000; + const maxSize = this.config.maxFileSize; const urlObj = new URL(url); let filename = urlObj.pathname.split('/').pop() ?? 'untitled'; 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/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index 87aa70713e..03646ff566 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -241,7 +241,7 @@ export interface InternalEventTypes { avatarDecorationCreated: MiAvatarDecoration; avatarDecorationDeleted: MiAvatarDecoration; avatarDecorationUpdated: MiAvatarDecoration; - metaUpdated: MiMeta; + metaUpdated: { before?: MiMeta; after: MiMeta; }; followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; }; unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; }; updateUserProfile: MiUserProfile; 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/MetaService.ts b/packages/backend/src/core/MetaService.ts index ec630f804e..3d88d0aefe 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -52,7 +52,7 @@ export class MetaService implements OnApplicationShutdown { switch (type) { case 'metaUpdated': { this.cache = { // TODO: ã“ã®ã‚ãŸã‚Šã®ãƒ‡ã‚·ãƒªã‚¢ãƒ©ã‚¤ã‚ºå‡¦ç†ã¯å„modelファイル内ã«é–¢æ•°ã¨ã—ã¦exportã—ãŸã„ - ...body, + ...(body.after), proxyAccount: null, // joinãªã‚«ãƒ©ãƒ ã¯é€šå¸¸å–ã£ã¦ã“ãªã„ã®ã§ }; break; @@ -141,7 +141,7 @@ export class MetaService implements OnApplicationShutdown { }); } - this.globalEventService.publishInternalEvent('metaUpdated', updated); + this.globalEventService.publishInternalEvent('metaUpdated', { before, after: updated }); return updated; } 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..89e3eafa0e 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'; @@ -60,6 +55,7 @@ import { UserBlockingService } from '@/core/UserBlockingService.js'; import { isReply } from '@/misc/is-reply.js'; import { trackPromise } from '@/misc/promise-tracker.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { CollapsedQueue } from '@/misc/collapsed-queue.js'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -151,11 +147,15 @@ type Option = { @Injectable() export class NoteCreateService implements OnApplicationShutdown { #shutdownController = new AbortController(); + private updateNotesCountQueue: CollapsedQueue<MiNote['id'], number>; constructor( @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.db) private db: DataSource, @@ -210,7 +210,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, @@ -218,7 +217,9 @@ export class NoteCreateService implements OnApplicationShutdown { private instanceChart: InstanceChart, private utilityService: UtilityService, private userBlockingService: UserBlockingService, - ) { } + ) { + this.updateNotesCountQueue = new CollapsedQueue(60 * 1000 * 5, this.collapseNotesCount, this.performUpdateNotesCount); + } @bindThis public async create(user: { @@ -251,10 +252,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 +261,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 +364,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,18 +505,16 @@ 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); } // Register host 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) { + this.updateNotesCountQueue.enqueue(i.id, 1); + if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.updateNote(i.host, note, true); } }); @@ -853,15 +850,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 +867,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 +907,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 +926,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 +953,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 +1014,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 ( @@ -1036,12 +1032,23 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - public dispose(): void { + private collapseNotesCount(oldValue: number, newValue: number) { + return oldValue + newValue; + } + + @bindThis + private async performUpdateNotesCount(id: MiNote['id'], incrBy: number) { + await this.instancesRepository.increment({ id: id }, 'notesCount', incrBy); + } + + @bindThis + public async dispose(): Promise<void> { this.#shutdownController.abort(); + await this.updateNotesCountQueue.performAllNow(); } @bindThis - public onApplicationShutdown(signal?: string | undefined): void { - this.dispose(); + public async onApplicationShutdown(signal?: string | undefined): Promise<void> { + await this.dispose(); } } 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..6f9fe53937 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, @@ -334,8 +338,21 @@ export class ReactionService { } /** - * æ–‡å—列タイプã®ãƒ¬ã‚¬ã‚·ãƒ¼ãªå½¢å¼ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã‚’ç¾åœ¨ã®å½¢å¼ã«å¤‰æ›ã—ã¤ã¤ã€ - * データベース上ã«ã¯å˜åœ¨ã™ã‚‹ã€Œ0個ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ãŒã¤ã„ã¦ã„ã‚‹ã€ã¨ã„ã†æƒ…å ±ã‚’å‰Šé™¤ã™ã‚‹ã€‚ + * - æ–‡å—列タイプã®ãƒ¬ã‚¬ã‚·ãƒ¼ãªå½¢å¼ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã‚’ç¾åœ¨ã®å½¢å¼ã«å¤‰æ›ã™ã‚‹ + * - ãƒãƒ¼ã‚«ãƒ«ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã®ãƒ›ã‚¹ãƒˆã‚’ `@.` ã«ã™ã‚‹ï¼ˆ`decodeReaction()`ã®åŠ¹æžœï¼‰ + */ + @bindThis + public convertLegacyReaction(reaction: string): string { + reaction = this.decodeReaction(reaction).reaction; + if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; + return reaction; + } + + // TODO: å»ƒæ¢ + /** + * - æ–‡å—列タイプã®ãƒ¬ã‚¬ã‚·ãƒ¼ãªå½¢å¼ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã‚’ç¾åœ¨ã®å½¢å¼ã«å¤‰æ›ã™ã‚‹ + * - ãƒãƒ¼ã‚«ãƒ«ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã®ãƒ›ã‚¹ãƒˆã‚’ `@.` ã«ã™ã‚‹ï¼ˆ`decodeReaction()`ã®åŠ¹æžœï¼‰ + * - データベース上ã«ã¯å˜åœ¨ã™ã‚‹ã€Œ0個ã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ãŒã¤ã„ã¦ã„ã‚‹ã€ã¨ã„ã†æƒ…å ±ã‚’å‰Šé™¤ã™ã‚‹ */ @bindThis public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] { @@ -348,10 +365,7 @@ export class ReactionService { return count > 0; }) .map(([reaction, count]) => { - // unchecked indexed access - const convertedReaction = legacies[reaction] as string | undefined; - - const key = this.decodeReaction(convertedReaction ?? reaction).reaction; + const key = this.convertLegacyReaction(reaction); return [key, count] as const; }) @@ -406,11 +420,4 @@ export class ReactionService { host: undefined, }; } - - @bindThis - public convertLegacyReaction(reaction: string): string { - reaction = this.decodeReaction(reaction).reaction; - if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; - return reaction; - } } diff --git a/packages/backend/src/core/ReactionsBufferingService.ts b/packages/backend/src/core/ReactionsBufferingService.ts new file mode 100644 index 0000000000..b4207c5106 --- /dev/null +++ b/packages/backend/src/core/ReactionsBufferingService.ts @@ -0,0 +1,211 @@ +/* + * 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'; +import type { GlobalEvents } from '@/core/GlobalEventService.js'; +import type { OnApplicationShutdown } from '@nestjs/common'; + +const REDIS_DELTA_PREFIX = 'reactionsBufferDeltas'; +const REDIS_PAIR_PREFIX = 'reactionsBufferPairs'; + +@Injectable() +export class ReactionsBufferingService implements OnApplicationShutdown { + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.redisForSub) + private redisForSub: Redis.Redis, + + @Inject(DI.redisForReactions) + private redisForReactions: Redis.Redis, // TODO: 専用ã®Redisインスタンスã«ã™ã‚‹ + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + ) { + this.redisForSub.on('message', this.onMessage); + } + + @bindThis + private async onMessage(_: string, data: string) { + const obj = JSON.parse(data); + + if (obj.channel === 'internal') { + const { type, body } = obj.message as GlobalEvents['internal']['payload']; + switch (type) { + case 'metaUpdated': { + // リアクションãƒãƒƒãƒ•ã‚¡ãƒªãƒ³ã‚°ãŒæœ‰åŠ¹â†’ç„¡åŠ¹ã«ãªã£ãŸã‚‰å³bake + if (body.before != null && body.before.enableReactionsBuffering && !body.after.enableReactionsBuffering) { + this.bake(); + } + break; + } + default: + break; + } + } + } + + @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(); + } + } + + @bindThis + public mergeReactions(src: MiNote['reactions'], delta: Record<string, number>): MiNote['reactions'] { + const reactions = { ...src }; + for (const [name, count] of Object.entries(delta)) { + if (reactions[name] != null) { + reactions[name] += count; + } else { + reactions[name] = count; + } + } + return reactions; + } + + @bindThis + public dispose(): void { + this.redisForSub.off('message', this.onMessage); + } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } +} 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..77e7b60bea 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; @@ -277,16 +275,19 @@ export class UserFollowingService implements OnModuleInit { followeeId: followee.id, followerId: follower.id, }); - - // é€šçŸ¥ã‚’ä½œæˆ - if (follower.host === null) { - this.notificationService.createNotification(follower.id, 'followRequestAccepted', { - }, followee.id); - } } if (alreadyFollowed) return; + // é€šçŸ¥ã‚’ä½œæˆ + if (follower.host === null) { + const profile = await this.cacheService.userProfileCache.fetch(followee.id); + + this.notificationService.createNotification(follower.id, 'followRequestAccepted', { + message: profile.followedMessage, + }, followee.id); + } + this.globalEventService.publishInternalEvent('follow', { followerId: follower.id, followeeId: followee.id }); const [followeeUser, followerUser] = await Promise.all([ @@ -307,14 +308,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 +440,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/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index 94729250a6..86082ccdcd 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -10,12 +10,16 @@ import RE2 from 're2'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { bindThis } from '@/decorators.js'; +import { MiMeta } from '@/models/Meta.js'; @Injectable() export class UtilityService { constructor( @Inject(DI.config) private config: Config, + + @Inject(DI.meta) + private meta: MiMeta, ) { } @@ -105,4 +109,19 @@ export class UtilityService { if (host == null) return null; return toASCII(host.toLowerCase()); } + + @bindThis + public isFederationAllowedHost(host: string): boolean { + if (this.meta.federation === 'none') return false; + if (this.meta.federation === 'specified' && !this.meta.federationHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`))) return false; + if (this.isBlockedHost(this.meta.blockedHosts, host)) return false; + + return true; + } + + @bindThis + public isFederationAllowedUri(uri: string): boolean { + const host = this.extractDbHost(uri); + return this.isFederationAllowedHost(host); + } } diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts index ec9f4484a4..75ab0a207c 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, }); @@ -166,6 +164,86 @@ export class WebAuthnService { return authenticationOptions; } + /** + * Initiate Passkey Auth (Without specifying user) + * @returns authenticationOptions + */ + @bindThis + public async initiateSignInWithPasskeyAuthentication(context: string): Promise<PublicKeyCredentialRequestOptionsJSON> { + const relyingParty = await this.getRelyingParty(); + + const authenticationOptions = await generateAuthenticationOptions({ + rpID: relyingParty.rpId, + userVerification: 'preferred', + }); + + await this.redisClient.setex(`webauthn:challenge:${context}`, 90, authenticationOptions.challenge); + + return authenticationOptions; + } + + /** + * Verify Webauthn AuthenticationCredential + * @throws IdentifiableError + * @returns If the challenge is successful, return the user ID. Otherwise, return null. + */ + @bindThis + public async verifySignInWithPasskeyAuthentication(context: string, response: AuthenticationResponseJSON): Promise<MiUser['id'] | null> { + const challenge = await this.redisClient.get(`webauthn:challenge:${context}`); + + if (!challenge) { + throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', `challenge '${context}' not found`); + } + + await this.redisClient.del(`webauthn:challenge:${context}`); + + const key = await this.userSecurityKeysRepository.findOneBy({ + id: response.id, + }); + + if (!key) { + throw new IdentifiableError('36b96a7d-b547-412d-aeed-2d611cdc8cdc', 'Unknown Webauthn key'); + } + + const relyingParty = await this.getRelyingParty(); + + let verification; + try { + verification = await verifyAuthenticationResponse({ + response: response, + expectedChallenge: challenge, + expectedOrigin: relyingParty.origin, + expectedRPID: relyingParty.rpId, + authenticator: { + credentialID: key.id, + credentialPublicKey: Buffer.from(key.publicKey, 'base64url'), + counter: key.counter, + transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined, + }, + requireUserVerification: true, + }); + } catch (error) { + throw new IdentifiableError('b18c89a7-5b5e-4cec-bb5b-0419f332d430', `verification failed: ${error}`); + } + + const { verified, authenticationInfo } = verification; + + if (!verified) { + return null; + } + + await this.userSecurityKeysRepository.update({ + id: response.id, + }, { + lastUsed: new Date(), + counter: authenticationInfo.newCounter, + credentialDeviceType: authenticationInfo.credentialDeviceType, + credentialBackedUp: authenticationInfo.credentialBackedUp, + }); + + return key.userId; + } + @bindThis public async verifyAuthentication(userId: MiUser['id'], response: AuthenticationResponseJSON): Promise<boolean> { const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`); @@ -209,7 +287,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..c2764f30e8 --- /dev/null +++ b/packages/backend/src/core/WebhookTestService.ts @@ -0,0 +1,435 @@ +/* + * 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: [], + score: 0, + 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..376c9c0151 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, @@ -289,9 +290,8 @@ export class ApInboxService { return; } - // アナウンス先をブãƒãƒƒã‚¯ã—ã¦ãŸã‚‰ä¸æ– - const meta = await this.metaService.fetch(); - if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) return; + // アナウンス先ãŒè¨±å¯ã•れã¦ã„ã‚‹ã‹ãƒã‚§ãƒƒã‚¯ + if (!this.utilityService.isFederationAllowedUri(uri)) return; const unlock = await this.appLockService.getApLock(uri); diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 98e944f347..fba8947f03 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -494,6 +494,7 @@ export class ApRendererService { name: user.name, summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null, _misskey_summary: profile.description, + _misskey_followedMessage: profile.followedMessage, icon: avatar ? this.renderImage(avatar) : null, image: banner ? this.renderImage(banner) : null, tag, diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index 7cf8359212..c7d19adfd5 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -205,18 +205,47 @@ export class ApRequestService { //#region リクエスト先ãŒhtmlã‹ã¤activity+jsonã¸ã®alternate linkã‚¿ã‚°ãŒã‚ã‚‹ã¨ã const contentType = res.headers.get('content-type'); - if ((contentType ?? '').split(';')[0].trimEnd().toLowerCase() === 'text/html' && _followAlternate === true) { + if ( + res.ok && + (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..ca35608d9b 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.isFederationAllowedHost(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/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts index feb8c42c56..3dd85b9b86 100644 --- a/packages/backend/src/core/activitypub/misc/contexts.ts +++ b/packages/backend/src/core/activitypub/misc/contexts.ts @@ -554,6 +554,7 @@ const extension_context_definition = { '_misskey_reaction': 'misskey:_misskey_reaction', '_misskey_votes': 'misskey:_misskey_votes', '_misskey_summary': 'misskey:_misskey_summary', + '_misskey_followedMessage': 'misskey:_misskey_followedMessage', 'isCat': 'misskey:isCat', // vcard vcard: 'http://www.w3.org/2006/vcard/ns#', 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..2d333b3634 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'); } @@ -335,9 +336,7 @@ export class ApNoteService { public async resolveNote(value: string | IObject, options: { sentFrom?: URL, resolver?: Resolver } = {}): Promise<MiNote | null> { const uri = getApId(value); - // ブãƒãƒƒã‚¯ã—ã¦ã„ãŸã‚‰ä¸æ– - const meta = await this.metaService.fetch(); - if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) { + if (!this.utilityService.isFederationAllowedUri(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..e042a85782 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'; @@ -46,7 +45,7 @@ import type { ApNoteService } from './ApNoteService.js'; import type { ApMfmService } from '../ApMfmService.js'; import type { ApResolverService, Resolver } from '../ApResolverService.js'; import type { ApLoggerService } from '../ApLoggerService.js'; -// eslint-disable-next-line @typescript-eslint/consistent-type-imports + import type { ApImageService } from './ApImageService.js'; import type { IActor, ICollection, IObject, IOrderedCollection } from '../type.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'); @@ -307,8 +307,8 @@ export class ApPersonService implements OnModuleInit { this.logger.error('error occurred while fetching following/followers collection', { stack: err }); } return 'private'; - }) - ) + }), + ), ); const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); @@ -370,6 +370,7 @@ export class ApPersonService implements OnModuleInit { await transactionalEntityManager.save(new MiUserProfile({ userId: user.id, description: _description, + followedMessage: person._misskey_followedMessage != null ? truncate(person._misskey_followedMessage, 256) : null, url, fields, followingVisibility, @@ -407,10 +408,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); } }); @@ -494,8 +495,8 @@ export class ApPersonService implements OnModuleInit { return undefined; } return 'private'; - }) - ) + }), + ), ); const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); @@ -566,6 +567,7 @@ export class ApPersonService implements OnModuleInit { url, fields, description: _description, + followedMessage: person._misskey_followedMessage != null ? truncate(person._misskey_followedMessage, 256) : null, followingVisibility, followersVisibility, birthday: bday?.[0] ?? null, diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts index 16812b7a4d..154965b9d5 100644 --- a/packages/backend/src/core/activitypub/type.ts +++ b/packages/backend/src/core/activitypub/type.ts @@ -13,6 +13,7 @@ export interface IObject { name?: string | null; summary?: string; _misskey_summary?: string; + _misskey_followedMessage?: string | null; published?: string; cc?: ApObject; to?: ApObject; diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts index c2329a2f73..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 ANY(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 ANY(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 ANY(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 ANY(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 ANY(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 44ec0d6a7b..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') @@ -129,6 +130,7 @@ export class MetaEntityService { mediaProxy: this.config.mediaProxy, enableUrlPreview: instance.urlPreviewEnabled, noteSearchableScope: (this.config.meilisearch == null || this.config.meilisearch.scope !== 'local') ? 'global' : 'local', + maxFileSize: this.config.maxFileSize, }; return packed; @@ -139,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..c64e9151a7 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -11,11 +11,11 @@ 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 type { OnModuleInit } from '@nestjs/common'; import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { ReactionService } from '../ReactionService.js'; @@ -28,12 +28,16 @@ export class NoteEntityService implements OnModuleInit { 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 +63,8 @@ export class NoteEntityService implements OnModuleInit { //private driveFileEntityService: DriveFileEntityService, //private customEmojiService: CustomEmojiService, //private reactionService: ReactionService, + //private reactionsBufferingService: ReactionsBufferingService, + //private idService: IdService, ) { } @@ -67,6 +73,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 +294,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 +311,15 @@ 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 = this.reactionService.convertLegacyReactions(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions.deltas ?? {})); + + const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferedReactions.pairs.map(x => x.join('/'))); + let text = note.text; if (note.name && (note.url ?? note.uri)) { @@ -315,7 +332,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 +351,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 +393,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 +421,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 +433,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(this.reactionsBufferingService.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(this.reactionsBufferingService.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 +494,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/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index f393513510..dff6968f9c 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -59,7 +59,7 @@ export class NotificationEntityService implements OnModuleInit { async #packInternal <T extends MiNotification | MiGroupedNotification> ( src: T, meId: MiUser['id'], - // eslint-disable-next-line @typescript-eslint/ban-types + options: { checkValidNotifier?: boolean; }, @@ -159,9 +159,16 @@ export class NotificationEntityService implements OnModuleInit { ...(notification.type === 'roleAssigned' ? { role: role, } : {}), + ...(notification.type === 'followRequestAccepted' ? { + message: notification.message, + } : {}), ...(notification.type === 'achievementEarned' ? { achievement: notification.achievement, } : {}), + ...(notification.type === 'exportCompleted' ? { + exportedEntity: notification.exportedEntity, + fileId: notification.fileId, + } : {}), ...(notification.type === 'app' ? { body: notification.customBody, header: notification.customHeader, @@ -229,7 +236,7 @@ export class NotificationEntityService implements OnModuleInit { public async pack( src: MiNotification | MiGroupedNotification, meId: MiUser['id'], - // eslint-disable-next-line @typescript-eslint/ban-types + options: { checkValidNotifier?: boolean; }, diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 9bf568bc90..69e2d6fc89 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -508,7 +508,7 @@ export class UserEntityService implements OnModuleInit { name: r.name, iconUrl: r.iconUrl, displayOrder: r.displayOrder, - })) + })), ) : undefined, ...(isDetailed ? { @@ -567,6 +567,7 @@ export class UserEntityService implements OnModuleInit { ...(isDetailed && isMe ? { avatarId: user.avatarId, bannerId: user.bannerId, + followedMessage: profile!.followedMessage, isModerator: isModerator, isAdmin: isAdmin, injectFeaturedNote: profile!.injectFeaturedNote, @@ -635,6 +636,7 @@ export class UserEntityService implements OnModuleInit { isRenoteMuted: relation.isRenoteMuted, notify: relation.following?.notify ?? 'none', withReplies: relation.following?.withReplies ?? false, + followedMessage: relation.isFollowing ? profile!.followedMessage : undefined, } : {}), } as Promiseable<Packed<S>>; 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/collapsed-queue.ts b/packages/backend/src/misc/collapsed-queue.ts new file mode 100644 index 0000000000..5bc20a78ae --- /dev/null +++ b/packages/backend/src/misc/collapsed-queue.ts @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +type Job<V> = { + value: V; + timer: NodeJS.Timeout; +}; + +// TODO: redis使ãˆã‚‹ã‚ˆã†ã«ã™ã‚‹ +export class CollapsedQueue<K, V> { + private jobs: Map<K, Job<V>> = new Map(); + + constructor( + private timeout: number, + private collapse: (oldValue: V, newValue: V) => V, + private perform: (key: K, value: V) => Promise<void>, + ) {} + + enqueue(key: K, value: V) { + if (this.jobs.has(key)) { + const old = this.jobs.get(key)!; + const merged = this.collapse(old.value, value); + this.jobs.set(key, { ...old, value: merged }); + } else { + const timer = setTimeout(() => { + const job = this.jobs.get(key)!; + this.jobs.delete(key); + this.perform(key, job.value); + }, this.timeout); + this.jobs.set(key, { value, timer }); + } + } + + async performAllNow() { + const entries = [...this.jobs.entries()]; + this.jobs.clear(); + for (const [_key, job] of entries) { + clearTimeout(job.timer); + } + await Promise.allSettled(entries.map(([key, job]) => this.perform(key, job.value))); + } +} 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/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index a721b8663c..040e36228c 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -144,7 +144,9 @@ export interface Schema extends OfSchema { readonly type?: TypeStringef; readonly nullable?: boolean; readonly optional?: boolean; + readonly prefixItems?: ReadonlyArray<Schema>; readonly items?: Schema; + readonly unevaluatedItems?: Schema | boolean; readonly properties?: Obj; readonly required?: ReadonlyArray<Extract<keyof NonNullable<this['properties']>, string>>; readonly description?: string; @@ -198,6 +200,7 @@ type UnionSchemaType<a extends readonly any[], X extends Schema = a[number]> = X //type UnionObjectSchemaType<a extends readonly any[], X extends Schema = a[number]> = X extends any ? ObjectSchemaType<X> : never; type UnionObjType<s extends Obj, a extends readonly any[], X extends ReadonlyArray<keyof s> = a[number]> = X extends any ? ObjType<s, X> : never; type ArrayUnion<T> = T extends any ? Array<T> : never; +type ArrayToTuple<X extends ReadonlyArray<Schema>> = { [K in keyof X]: SchemaType<X[K]> }; type ObjectSchemaTypeDef<p extends Schema> = p['ref'] extends keyof typeof refs ? Packed<p['ref']> : @@ -232,6 +235,12 @@ export type SchemaTypeDef<p extends Schema> = p['items']['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] : never ) : + p['prefixItems'] extends ReadonlyArray<Schema> ? ( + p['items'] extends NonNullable<Schema> ? [...ArrayToTuple<p['prefixItems']>, ...SchemaType<p['items']>[]] : + p['items'] extends false ? ArrayToTuple<p['prefixItems']> : + p['unevaluatedItems'] extends false ? ArrayToTuple<p['prefixItems']> : + [...ArrayToTuple<p['prefixItems']>, ...unknown[]] + ) : p['items'] extends NonNullable<Schema> ? SchemaType<p['items']>[] : any[] ) : diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 70d41801b5..d29689f907 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, }) @@ -625,4 +630,17 @@ export class MiMeta { nullable: true, }) public urlPreviewUserAgent: string | null; + + @Column('varchar', { + length: 128, + default: 'all', + }) + public federation: 'all' | 'specified' | 'none'; + + @Column('varchar', { + length: 1024, + array: true, + default: '{}', + }) + public federationHosts: string[]; } diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts index df88b99636..c1d3d42134 100644 --- a/packages/backend/src/models/Notification.ts +++ b/packages/backend/src/models/Notification.ts @@ -7,6 +7,8 @@ import { MiUser } from './User.js'; import { MiNote } from './Note.js'; import { MiAccessToken } from './AccessToken.js'; import { MiRole } from './Role.js'; +import { MiDriveFile } from './DriveFile.js'; +import { userExportableEntities } from '@/types.js'; export type MiNotification = { type: 'note'; @@ -67,6 +69,7 @@ export type MiNotification = { id: string; createdAt: string; notifierId: MiUser['id']; + message: string | null; } | { type: 'roleAssigned'; id: string; @@ -78,6 +81,12 @@ export type MiNotification = { createdAt: string; achievement: string; } | { + type: 'exportCompleted'; + id: string; + createdAt: string; + exportedEntity: typeof userExportableEntities[number]; + fileId: MiDriveFile['id']; +} | { type: 'app'; id: string; createdAt: string; @@ -85,7 +94,7 @@ export type MiNotification = { /** * アプリ通知ã®body */ - customBody: string | null; + customBody: string; /** * アプリ通知ã®header diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index 9e2d7a3444..805a1e75ae 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -155,6 +155,11 @@ export class MiUser { }) public tags: string[]; + @Column('integer', { + default: 0, + }) + public score: number; + @Column('boolean', { default: false, comment: 'Whether the User is suspended.', @@ -289,5 +294,6 @@ export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toStr export const passwordSchema = { type: 'string', minLength: 1 } as const; export const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; export const descriptionSchema = { type: 'string', minLength: 1, maxLength: 1500 } as const; +export const followedMessageSchema = { type: 'string', minLength: 1, maxLength: 256 } as const; export const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; export const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const; diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts index 7dbe0b3717..5544555296 100644 --- a/packages/backend/src/models/UserProfile.ts +++ b/packages/backend/src/models/UserProfile.ts @@ -42,6 +42,14 @@ export class MiUserProfile { }) public description: string | null; + // フォãƒãƒ¼ã•れãŸéš›ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ + @Column('varchar', { + length: 256, nullable: true, + }) + public followedMessage: string | null; + + // TODO: éµã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®å ´åˆã®ã€ãƒ•ã‚©ãƒãƒ¼ãƒªã‚¯ã‚¨ã‚¹ãƒˆå—信時ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚‚è¨å®šã§ãるよã†ã«ã™ã‚‹ + @Column('jsonb', { default: [], }) 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/meta.ts b/packages/backend/src/models/json-schema/meta.ts index 3bcf9cac92..99feeaa7d7 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -253,6 +253,10 @@ export const packedMetaLiteSchema = { optional: false, nullable: false, default: 'local', }, + maxFileSize: { + type: 'number', + optional: false, nullable: false, + }, }, } as const; diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts index b4c4442758..2645010491 100644 --- a/packages/backend/src/models/json-schema/notification.ts +++ b/packages/backend/src/models/json-schema/notification.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { notificationTypes } from '@/types.js'; +import { ACHIEVEMENT_TYPES } from '@/core/AchievementService.js'; +import { notificationTypes, userExportableEntities } from '@/types.js'; const baseSchema = { type: 'object', @@ -266,6 +267,10 @@ export const packedNotificationSchema = { optional: false, nullable: false, format: 'id', }, + message: { + type: 'string', + optional: false, nullable: true, + }, }, }, { type: 'object', @@ -294,6 +299,27 @@ export const packedNotificationSchema = { achievement: { type: 'string', optional: false, nullable: false, + enum: ACHIEVEMENT_TYPES, + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['exportCompleted'], + }, + exportedEntity: { + type: 'string', + optional: false, nullable: false, + enum: userExportableEntities, + }, + fileId: { + type: 'string', + optional: false, nullable: false, + format: 'id', }, }, }, { @@ -311,11 +337,11 @@ export const packedNotificationSchema = { }, header: { type: 'string', - optional: false, nullable: false, + optional: false, nullable: true, }, icon: { type: 'string', - optional: false, nullable: false, + optional: false, nullable: true, }, }, }, { 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/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 947a9317d7..16c8a5a097 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -370,6 +370,10 @@ export const packedUserDetailedNotMeOnlySchema = { ref: 'RoleLite', }, }, + followedMessage: { + type: 'string', + nullable: true, optional: true, + }, memo: { type: 'string', nullable: true, optional: false, @@ -437,6 +441,10 @@ export const packedMeDetailedOnlySchema = { nullable: true, optional: false, format: 'id', }, + followedMessage: { + type: 'string', + nullable: true, optional: false, + }, isModerator: { type: 'boolean', nullable: true, optional: false, 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..9590a4fe71 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, @@ -52,9 +53,7 @@ export class DeliverProcessorService { public async process(job: Bull.Job<DeliverJobData>): Promise<string> { 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.isFederationAllowedUri(job.data.to)) { return 'skip (blocked)'; } @@ -88,7 +87,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 +119,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/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts index 88c4ea29c0..b3111865ad 100644 --- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -14,6 +14,7 @@ import { DriveService } from '@/core/DriveService.js'; import { bindThis } from '@/decorators.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type { DBExportAntennasData } from '../types.js'; import type * as Bull from 'bullmq'; @@ -35,6 +36,7 @@ export class ExportAntennasProcessorService { private driveService: DriveService, private utilityService: UtilityService, private queueLoggerService: QueueLoggerService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-antennas'); } @@ -95,6 +97,11 @@ export class ExportAntennasProcessorService { const fileName = 'antennas-' + DateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); this.logger.succ('Exported to: ' + driveFile.id); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'antenna', + fileId: driveFile.id, + }); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts index 6ec3c18786..ecc439db69 100644 --- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts @@ -13,6 +13,7 @@ import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -30,6 +31,7 @@ export class ExportBlockingProcessorService { private blockingsRepository: BlockingsRepository, private utilityService: UtilityService, + private notificationService: NotificationService, private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { @@ -109,6 +111,11 @@ export class ExportBlockingProcessorService { const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'blocking', + fileId: driveFile.id, + }); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts index 01eab26e96..583ddbb745 100644 --- a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts @@ -19,6 +19,7 @@ import { bindThis } from '@/decorators.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { Packed } from '@/misc/json-schema.js'; import { IdService } from '@/core/IdService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @@ -43,6 +44,7 @@ export class ExportClipsProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, private idService: IdService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-clips'); } @@ -79,6 +81,11 @@ export class ExportClipsProcessorService { const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'clip', + fileId: driveFile.id, + }); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts index e4eb4791bd..e237cd4975 100644 --- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts @@ -16,6 +16,7 @@ import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp, createTempDir } from '@/misc/create-temp.js'; import { DownloadService } from '@/core/DownloadService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -37,6 +38,7 @@ export class ExportCustomEmojisProcessorService { private driveService: DriveService, private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-custom-emojis'); } @@ -134,6 +136,12 @@ export class ExportCustomEmojisProcessorService { const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'customEmoji', + fileId: driveFile.id, + }); + cleanup(); archiveCleanup(); resolve(); diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts index 7bb626dd31..b81feece01 100644 --- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts @@ -16,6 +16,7 @@ import type { MiPoll } from '@/models/Poll.js'; import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @@ -37,6 +38,7 @@ export class ExportFavoritesProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, private idService: IdService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-favorites'); } @@ -123,6 +125,11 @@ export class ExportFavoritesProcessorService { const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'favorite', + fileId: driveFile.id, + }); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index 1cc80e66d7..903f962515 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -14,6 +14,7 @@ import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; import type { MiFollowing } from '@/models/Following.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -36,6 +37,7 @@ export class ExportFollowingProcessorService { private utilityService: UtilityService, private driveService: DriveService, private queueLoggerService: QueueLoggerService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-following'); } @@ -113,6 +115,11 @@ export class ExportFollowingProcessorService { const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'following', + fileId: driveFile.id, + }); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts index 243b74f2c2..f9867ade29 100644 --- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts @@ -13,6 +13,7 @@ import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -32,6 +33,7 @@ export class ExportMutingProcessorService { private utilityService: UtilityService, private driveService: DriveService, private queueLoggerService: QueueLoggerService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-muting'); } @@ -110,6 +112,11 @@ export class ExportMutingProcessorService { const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'muting', + fileId: driveFile.id, + }); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index c7611012d7..9e2b678219 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -18,6 +18,7 @@ import { bindThis } from '@/decorators.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { Packed } from '@/misc/json-schema.js'; import { IdService } from '@/core/IdService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { JsonArrayStream } from '@/misc/JsonArrayStream.js'; import { FileWriterStream } from '@/misc/FileWriterStream.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; @@ -112,6 +113,7 @@ export class ExportNotesProcessorService { private queueLoggerService: QueueLoggerService, private driveFileEntityService: DriveFileEntityService, private idService: IdService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-notes'); } @@ -150,6 +152,11 @@ export class ExportNotesProcessorService { const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'note', + fileId: driveFile.id, + }); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index ee87cff5d3..c483d79854 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -13,6 +13,7 @@ import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -35,6 +36,7 @@ export class ExportUserListsProcessorService { private utilityService: UtilityService, private driveService: DriveService, private queueLoggerService: QueueLoggerService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-user-lists'); } @@ -89,6 +91,11 @@ export class ExportUserListsProcessorService { const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'userList', + fileId: driveFile.id, + }); } finally { cleanup(); } 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..09d51bec72 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, OnApplicationShutdown } 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'; @@ -26,16 +25,28 @@ import { JsonLdService } from '@/core/activitypub/JsonLdService.js'; import { ApInboxService } from '@/core/activitypub/ApInboxService.js'; import { bindThis } from '@/decorators.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { CollapsedQueue } from '@/misc/collapsed-queue.js'; +import { MiNote } from '@/models/Note.js'; +import { MiMeta } from '@/models/Meta.js'; +import { DI } from '@/di-symbols.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type { InboxJobData } from '../types.js'; +type UpdateInstanceJob = { + latestRequestReceivedAt: Date, + shouldUnsuspend: boolean, +}; + @Injectable() -export class InboxProcessorService { +export class InboxProcessorService implements OnApplicationShutdown { private logger: Logger; + private updateInstanceQueue: CollapsedQueue<MiNote['id'], UpdateInstanceJob>; constructor( + @Inject(DI.meta) + private meta: MiMeta, + private utilityService: UtilityService, - private metaService: MetaService, private apInboxService: ApInboxService, private federatedInstanceService: FederatedInstanceService, private fetchInstanceMetadataService: FetchInstanceMetadataService, @@ -48,6 +59,7 @@ export class InboxProcessorService { private queueLoggerService: QueueLoggerService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('inbox'); + this.updateInstanceQueue = new CollapsedQueue(60 * 1000 * 5, this.collapseUpdateInstanceJobs, this.performUpdateInstance); } @bindThis @@ -63,9 +75,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.isFederationAllowedHost(host)) { return `Blocked request: ${host}`; } @@ -164,9 +174,8 @@ export class InboxProcessorService { throw new Bull.UnrecoverableError(`skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`); } - // ブãƒãƒƒã‚¯ã—ã¦ãŸã‚‰ä¸æ– const ldHost = this.utilityService.extractDbHost(authUser.user.uri); - if (this.utilityService.isBlockedHost(meta.blockedHosts, ldHost)) { + if (!this.utilityService.isFederationAllowedHost(ldHost)) { throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`); } } else { @@ -185,11 +194,9 @@ export class InboxProcessorService { // Update stats this.federatedInstanceService.fetch(authUser.user.host).then(i => { - this.federatedInstanceService.update(i.id, { + this.updateInstanceQueue.enqueue(i.id, { latestRequestReceivedAt: new Date(), - isNotResponding: false, - // ã‚‚ã—サーãƒãƒ¼ãŒæ»ã‚“ã§ã‚‹ãŸã‚ã«é…ä¿¡ãŒæ¢ã¾ã£ã¦ã„ãŸå ´åˆã«ã¯è‡ªå‹•çš„ã«å¾©æ´»ã•ã›ã¦ã‚ã’ã‚‹ - suspensionState: i.suspensionState === 'autoSuspendedForNotResponding' ? 'none' : undefined, + shouldUnsuspend: i.suspensionState === 'autoSuspendedForNotResponding', }); this.fetchInstanceMetadataService.fetchInstanceMetadata(i); @@ -197,7 +204,7 @@ export class InboxProcessorService { this.apRequestChart.inbox(); this.federationChart.inbox(i.host); - if (meta.enableChartsForFederatedInstances) { + if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.requestReceived(i.host); } }); @@ -225,4 +232,36 @@ export class InboxProcessorService { } return 'ok'; } + + @bindThis + public collapseUpdateInstanceJobs(oldJob: UpdateInstanceJob, newJob: UpdateInstanceJob) { + const latestRequestReceivedAt = oldJob.latestRequestReceivedAt < newJob.latestRequestReceivedAt + ? newJob.latestRequestReceivedAt + : oldJob.latestRequestReceivedAt; + const shouldUnsuspend = oldJob.shouldUnsuspend || newJob.shouldUnsuspend; + return { + latestRequestReceivedAt, + shouldUnsuspend, + }; + } + + @bindThis + public async performUpdateInstance(id: string, job: UpdateInstanceJob) { + await this.federatedInstanceService.update(id, { + latestRequestReceivedAt: new Date(), + isNotResponding: false, + // ã‚‚ã—サーãƒãƒ¼ãŒæ»ã‚“ã§ã‚‹ãŸã‚ã«é…ä¿¡ãŒæ¢ã¾ã£ã¦ã„ãŸå ´åˆã«ã¯è‡ªå‹•çš„ã«å¾©æ´»ã•ã›ã¦ã‚ã’ã‚‹ + suspensionState: job.shouldUnsuspend ? 'none' : undefined, + }); + } + + @bindThis + public async dispose(): Promise<void> { + await this.updateInstanceQueue.performAllNow(); + } + + @bindThis + async onApplicationShutdown(signal?: string) { + await this.dispose(); + } } 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/ServerModule.ts b/packages/backend/src/server/ServerModule.ts index 12d5061985..3ab0b815f2 100644 --- a/packages/backend/src/server/ServerModule.ts +++ b/packages/backend/src/server/ServerModule.ts @@ -46,6 +46,7 @@ import { UserListChannelService } from './api/stream/channels/user-list.js'; import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js'; import { ReversiChannelService } from './api/stream/channels/reversi.js'; import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js'; +import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.js'; @Module({ imports: [ @@ -71,6 +72,7 @@ import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js AuthenticateService, RateLimiterService, SigninApiService, + SigninWithPasskeyApiService, SigninService, SignupApiService, StreamingApiServerService, 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 47f64f6609..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; } @@ -199,9 +200,18 @@ export class ApiCallService implements OnApplicationShutdown { return; } - const [path] = await createTemp(); + const [path, cleanup] = await createTemp(); await stream.pipeline(multipartData.file, fs.createWriteStream(path)); + // ファイルサイズãŒåˆ¶é™ã‚’è¶…ãˆã¦ã„ãŸå ´åˆ + // ãªãŠ truncated ã¯ã‚¹ãƒˆãƒªãƒ¼ãƒ ã‚’èªã¿åˆ‡ã£ã¦ã‹ã‚‰ã§ãªã„ã¨æ©Ÿèƒ½ã—ãªã„ãŸã‚ã€stream.pipeline より後ã«ã‚ã‚‹å¿…è¦ãŒã‚ã‚‹ + if (multipartData.file.truncated) { + cleanup(); + reply.code(413); + reply.send(); + return; + } + const fields = {} as Record<string, unknown>; for (const [k, v] of Object.entries(multipartData.fields)) { fields[k] = typeof v === 'object' && 'value' in v ? v.value : undefined; @@ -256,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/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index 4a5935f930..709a044601 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -8,6 +8,7 @@ import cors from '@fastify/cors'; import multipart from '@fastify/multipart'; import fastifyCookie from '@fastify/cookie'; import { ModuleRef } from '@nestjs/core'; +import { AuthenticationResponseJSON } from '@simplewebauthn/types'; import type { Config } from '@/config.js'; import type { InstancesRepository, AccessTokensRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; @@ -17,6 +18,7 @@ import endpoints from './endpoints.js'; import { ApiCallService } from './ApiCallService.js'; import { SignupApiService } from './SignupApiService.js'; import { SigninApiService } from './SigninApiService.js'; +import { SigninWithPasskeyApiService } from './SigninWithPasskeyApiService.js'; import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; @Injectable() @@ -37,6 +39,7 @@ export class ApiServerService { private apiCallService: ApiCallService, private signupApiService: SignupApiService, private signinApiService: SigninApiService, + private signinWithPasskeyApiService: SigninWithPasskeyApiService, ) { //this.createServer = this.createServer.bind(this); } @@ -49,7 +52,7 @@ export class ApiServerService { fastify.register(multipart, { limits: { - fileSize: this.config.maxFileSize ?? 262144000, + fileSize: this.config.maxFileSize, files: 1, }, }); @@ -131,6 +134,12 @@ export class ApiServerService { }; }>('/signin', (request, reply) => this.signinApiService.signin(request, reply)); + fastify.post<{ + Body: { + credential?: AuthenticationResponseJSON; + }; + }>('/signin-with-passkey', (request, reply) => this.signinWithPasskeyApiService.signin(request, reply)); + fastify.post<{ Body: { code: string; } }>('/signup-pending', (request, reply) => this.signupApiService.signupPending(request, reply)); fastify.get('/v1/instance/peers', async (request, reply) => { 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/SigninWithPasskeyApiService.ts b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts new file mode 100644 index 0000000000..9ba23c54e2 --- /dev/null +++ b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts @@ -0,0 +1,173 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { randomUUID } from 'crypto'; +import { Inject, Injectable } from '@nestjs/common'; +import { IsNull } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { + SigninsRepository, + UserProfilesRepository, + UsersRepository, +} from '@/models/_.js'; +import type { Config } from '@/config.js'; +import { getIpHash } from '@/misc/get-ip-hash.js'; +import type { MiLocalUser, MiUser } from '@/models/User.js'; +import { IdService } from '@/core/IdService.js'; +import { bindThis } from '@/decorators.js'; +import { WebAuthnService } from '@/core/WebAuthnService.js'; +import Logger from '@/logger.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import type { IdentifiableError } from '@/misc/identifiable-error.js'; +import { RateLimiterService } from './RateLimiterService.js'; +import { SigninService } from './SigninService.js'; +import type { AuthenticationResponseJSON } from '@simplewebauthn/types'; +import type { FastifyReply, FastifyRequest } from 'fastify'; + +@Injectable() +export class SigninWithPasskeyApiService { + private logger: Logger; + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + + @Inject(DI.signinsRepository) + private signinsRepository: SigninsRepository, + + private idService: IdService, + private rateLimiterService: RateLimiterService, + private signinService: SigninService, + private webAuthnService: WebAuthnService, + private loggerService: LoggerService, + ) { + this.logger = this.loggerService.getLogger('PasskeyAuth'); + } + + @bindThis + public async signin( + request: FastifyRequest<{ + Body: { + credential?: AuthenticationResponseJSON; + context?: string; + }; + }>, + reply: FastifyReply, + ) { + reply.header('Access-Control-Allow-Origin', this.config.url); + reply.header('Access-Control-Allow-Credentials', 'true'); + + const body = request.body; + const credential = body['credential']; + + function error(status: number, error: { id: string }) { + reply.code(status); + return { error }; + } + + const fail = async (userId: MiUser['id'], status?: number, failure?: { id: string }) => { + // Append signin history + await this.signinsRepository.insert({ + id: this.idService.gen(), + userId: userId, + ip: request.ip, + headers: request.headers as any, + success: false, + }); + return error(status ?? 500, failure ?? { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' }); + }; + + try { + // Not more than 1 API call per 250ms and not more than 100 attempts per 30min + // NOTE: 1 Sign-in require 2 API calls + await this.rateLimiterService.limit({ key: 'signin-with-passkey', duration: 60 * 30 * 1000, max: 200, minInterval: 250 }, getIpHash(request.ip)); + } catch (err) { + reply.code(429); + return { + error: { + message: 'Too many failed attempts to sign in. Try again later.', + code: 'TOO_MANY_AUTHENTICATION_FAILURES', + id: '22d05606-fbcf-421a-a2db-b32610dcfd1b', + }, + }; + } + + // Initiate Passkey Auth challenge with context + if (!credential) { + const context = randomUUID(); + this.logger.info(`Initiate Passkey challenge: context: ${context}`); + const authChallengeOptions = { + option: await this.webAuthnService.initiateSignInWithPasskeyAuthentication(context), + context: context, + }; + reply.code(200); + return authChallengeOptions; + } + + const context = body.context; + if (!context || typeof context !== 'string') { + // If try Authentication without context + return error(400, { + id: '1658cc2e-4495-461f-aee4-d403cdf073c1', + }); + } + + this.logger.debug(`Try Sign-in with Passkey: context: ${context}`); + + let authorizedUserId: MiUser['id'] | null; + try { + authorizedUserId = await this.webAuthnService.verifySignInWithPasskeyAuthentication(context, credential); + } catch (err) { + this.logger.warn(`Passkey challenge Verify error! : ${err}`); + const errorId = (err as IdentifiableError).id; + return error(403, { + id: errorId, + }); + } + + if (!authorizedUserId) { + return error(403, { + id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', + }); + } + + // Fetch user + const user = await this.usersRepository.findOneBy({ + id: authorizedUserId, + host: IsNull(), + }) as MiLocalUser | null; + + if (user == null) { + return error(403, { + id: '652f899f-66d4-490e-993e-6606c8ec04c3', + }); + } + + if (user.isSuspended) { + return error(403, { + id: 'e03a5f46-d309-4865-9b69-56282d94e1eb', + }); + } + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + + // Authentication was successful, but passwordless login is not enabled + if (!profile.usePasswordLessLogin) { + return await fail(user.id, 403, { + id: '2d84773e-f7b7-4d0b-8f72-bb69b584c912', + }); + } + + const signinResponse = this.signinService.signin(request, reply, user); + return { + signinResponse: signinResponse, + }; + } +} 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..b76ed5c524 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, @@ -491,6 +495,18 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + federation: { + type: 'string', + optional: false, nullable: false, + }, + federationHosts: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, }, }, } as const; @@ -617,6 +633,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, @@ -625,6 +642,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength, urlPreviewUserAgent: instance.urlPreviewUserAgent, urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl, + federation: instance.federation, + federationHosts: instance.federationHosts, }; }); } diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts index 7a3410ffa7..f3e440b4cb 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts @@ -21,16 +21,15 @@ export const meta = { items: { type: 'array', optional: false, nullable: false, - items: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - ], - }, + prefixItems: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + unevaluatedItems: false, }, example: [[ 'example.com', diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts index 305ae1af1d..e7589cba81 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts @@ -21,16 +21,15 @@ export const meta = { items: { type: 'array', optional: false, nullable: false, - items: { - anyOf: [ - { - type: 'string', - }, - { - type: 'number', - }, - ], - }, + prefixItems: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + unevaluatedItems: false, }, example: [[ 'example.com', diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 5a1c05f41a..655bd32bce 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -31,6 +31,10 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + followedMessage: { + type: 'string', + optional: false, nullable: true, + }, autoAcceptFollowed: { type: 'boolean', optional: false, nullable: false, @@ -226,6 +230,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- return { email: profile.email, emailVerified: profile.emailVerified, + followedMessage: profile.followedMessage, autoAcceptFollowed: profile.autoAcceptFollowed, noCrawle: profile.noCrawle, preventAiLearning: profile.preventAiLearning, 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..daef236397 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', @@ -167,6 +168,16 @@ export const paramDef = { urlPreviewRequireContentLength: { type: 'boolean' }, urlPreviewUserAgent: { type: 'string', nullable: true }, urlPreviewSummaryProxyUrl: { type: 'string', nullable: true }, + federation: { + type: 'string', + enum: ['all', 'none', 'specified'], + }, + federationHosts: { + type: 'array', + items: { + type: 'string', + }, + }, }, required: [], } as const; @@ -598,6 +609,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; } @@ -632,6 +647,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- set.urlPreviewSummaryProxyUrl = value === '' ? null : value; } + if (ps.federation !== undefined) { + set.federation = ps.federation; + } + + if (Array.isArray(ps.federationHosts)) { + set.blockedHosts = ps.federationHosts.filter(Boolean).map(x => x.toLowerCase()); + } + const before = await this.metaService.fetch(true); await this.metaService.update(set); 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..c52608cefb 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'; @@ -91,7 +90,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- private utilityService: UtilityService, private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, - private metaService: MetaService, private apResolverService: ApResolverService, private apDbResolverService: ApDbResolverService, private apPersonService: ApPersonService, @@ -112,9 +110,7 @@ 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.isFederationAllowedUri(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/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index a1e2fa5e4c..798bd98cf1 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -13,9 +13,8 @@ import { extractHashtags } from '@/misc/extract-hashtags.js'; import * as Acct from '@/misc/acct.js'; import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/_.js'; import type { MiLocalUser, MiUser } from '@/models/User.js'; -import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/User.js'; +import { birthdaySchema, descriptionSchema, followedMessageSchema, locationSchema, nameSchema } from '@/models/User.js'; import type { MiUserProfile } from '@/models/UserProfile.js'; -import { notificationTypes } from '@/types.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { langmap } from '@/misc/langmap.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -134,6 +133,7 @@ export const paramDef = { properties: { name: { ...nameSchema, nullable: true }, description: { ...descriptionSchema, nullable: true }, + followedMessage: { ...followedMessageSchema, nullable: true }, location: { ...locationSchema, nullable: true }, birthday: { ...birthdaySchema, nullable: true }, lang: { type: 'string', enum: [null, ...Object.keys(langmap)] as string[], nullable: true }, @@ -267,6 +267,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- } } if (ps.description !== undefined) profileUpdates.description = ps.description; + if (ps.followedMessage !== undefined) profileUpdates.followedMessage = ps.followedMessage; if (ps.lang !== undefined) profileUpdates.lang = ps.lang; if (ps.location !== undefined) profileUpdates.location = ps.location; if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday; 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..dd7bb7823e 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 - fastify.get('/_info_card_', async (request, reply) => { - const meta = await this.metaService.fetch(true); + //#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) => { 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 4275dc9527..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'; // ブãƒãƒƒã‚¯ã®ä¸ã«å…¥ã‚Œãªã„ã¨ã€å®šç¾©ã—ãŸå¤‰æ•°ãŒãƒ–ラウザã®ã‚°ãƒãƒ¼ãƒãƒ«ã‚¹ã‚³ãƒ¼ãƒ—ã«ç™»éŒ²ã•れã¦ã—ã¾ã„邪é”ãªã®ã§ @@ -166,7 +155,7 @@ if (!errorsElement) { document.body.innerHTML = ` - <svg class="icon-warning" xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-alert-triangle" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <svg class="icon-warning" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 9v2m0 4v.01"></path> <path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path> @@ -176,10 +165,10 @@ <span class="button-label-big">Reload / リãƒãƒ¼ãƒ‰</span> </button> <p><b>The following actions may solve the problem. / 以下を行ã†ã¨è§£æ±ºã™ã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚</b></p> - <p>Clear the browser cache / ブラウザã®ã‚ャッシュをクリアã™ã‚‹</p> <p>Update your os and browser / ブラウザãŠã‚ˆã³OSを最新ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«æ›´æ–°ã™ã‚‹</p> <p>Disable an adblocker / アドブãƒãƒƒã‚«ãƒ¼ã‚’無効ã«ã™ã‚‹</p> - <p>(Tor Browser) Set dom.webaudio.enabled to true / dom.webaudio.enabledã‚’trueã«è¨å®šã™ã‚‹</p> + <p>Clear the browser cache / ブラウザã®ã‚ャッシュをクリアã™ã‚‹</p> + <p>(Tor Browser) Set dom.webaudio.enabled to true / dom.webaudio.enabledã‚’trueã«è¨å®šã™ã‚‹</p> <details style="color: #86b300;"> <summary>Other options / ãã®ä»–ã®ã‚ªãƒ—ション</summary> <a href="/flush"> @@ -212,7 +201,7 @@ <summary> <code>ERROR CODE: ${code}</code> </summary> - <code>${JSON.stringify(details)}</code>`; + <code>${details.toString()} ${JSON.stringify(details)}</code>`; errorsElement.appendChild(detailsElement); addStyle(` * { @@ -320,6 +309,6 @@ #errorInfo { width: 50%; } - }`) + }`); } })(); 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..baa0909676 --- /dev/null +++ b/packages/backend/src/server/web/views/base-embed.pug @@ -0,0 +1,72 @@ +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(property='og:site_name' content= instanceName || 'Misskey') + meta(property='instance_url' content= instanceUrl) + 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 151b7bca6c..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,15 +36,13 @@ html link(rel='prefetch' href=serverErrorImageUrl) link(rel='prefetch' href=infoImageUrl) link(rel='prefetch' href=notFoundImageUrl) - //- https://github.com/misskey-dev/misskey/issues/9842 - link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v3.3.0') - 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 @@ -70,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/src/types.ts b/packages/backend/src/types.ts index e852cf5ae2..5854c6b392 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -16,6 +16,7 @@ * followRequestAccepted - 自分ã®é€ã£ãŸãƒ•ã‚©ãƒãƒ¼ãƒªã‚¯ã‚¨ã‚¹ãƒˆãŒæ‰¿èªã•れ㟠* roleAssigned - ãƒãƒ¼ãƒ«ãŒä»˜ä¸Žã•れ㟠* achievementEarned - 実績をç²å¾— + * exportCompleted - エクスãƒãƒ¼ãƒˆãŒå®Œäº† * app - アプリ通知 * test - テスト通知(サーãƒãƒ¼å´ï¼‰ */ @@ -32,6 +33,7 @@ export const notificationTypes = [ 'followRequestAccepted', 'roleAssigned', 'achievementEarned', + 'exportCompleted', 'app', 'test', ] as const; @@ -51,6 +53,20 @@ export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const; export const followingVisibilities = ['public', 'followers', 'private'] as const; export const followersVisibilities = ['public', 'followers', 'private'] as const; +/** + * ユーザーãŒã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆã§ãã‚‹ã‚‚ã®ã®ç¨®é¡ž + * + * (主ã«ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆå®Œäº†é€šçŸ¥ã§ä½¿ç”¨ã™ã‚‹ã‚‚ã®ã§ã‚ã‚Šã€æ—¢å˜ã®DBã®åç§°ç‰ã¨å¿…ãšã—も一致ã—ãªã„) + */ +export const userExportableEntities = ['antenna', 'blocking', 'clip', 'customEmoji', 'favorite', 'following', 'muting', 'note', 'userList'] as const; + +/** + * ユーザーãŒã‚¤ãƒ³ãƒãƒ¼ãƒˆã§ãã‚‹ã‚‚ã®ã®ç¨®é¡ž + * + * (主ã«ã‚¤ãƒ³ãƒãƒ¼ãƒˆå®Œäº†é€šçŸ¥ã§ä½¿ç”¨ã™ã‚‹ã‚‚ã®ã§ã‚ã‚Šã€æ—¢å˜ã®DBã®åç§°ç‰ã¨å¿…ãšã—も一致ã—ãªã„) + */ +export const userImportableEntities = ['antenna', 'blocking', 'customEmoji', 'following', 'muting', 'userList'] as const; + export const moderationLogTypes = [ 'updateServerSettings', 'suspend', 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/users.ts b/packages/backend/test/e2e/users.ts index 61fd759932..8ebe9af792 100644 --- a/packages/backend/test/e2e/users.ts +++ b/packages/backend/test/e2e/users.ts @@ -7,9 +7,9 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import { inspect } from 'node:util'; -import { DEFAULT_POLICIES } from '@/core/RoleService.js'; import { api, post, role, signup, successfulApiCall, uploadFile } from '../utils.js'; import type * as misskey from 'misskey-js'; +import { DEFAULT_POLICIES } from '@/core/RoleService.js'; describe('ユーザー', () => { // エンティティã¨ã—ã¦ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’主眼ã«ãŠã„ãŸãƒ†ã‚¹ãƒˆã‚’記述ã™ã‚‹ @@ -105,6 +105,7 @@ describe('ユーザー', () => { isRenoteMuted: user.isRenoteMuted ?? false, notify: user.notify ?? 'none', withReplies: user.withReplies ?? false, + followedMessage: user.isFollowing ? (user.followedMessage ?? null) : undefined, }); }; @@ -114,6 +115,7 @@ describe('ユーザー', () => { ...userDetailedNotMe(user), avatarId: user.avatarId, bannerId: user.bannerId, + followedMessage: user.followedMessage, isModerator: user.isModerator, isAdmin: user.isAdmin, injectFeaturedNote: user.injectFeaturedNote, @@ -350,6 +352,7 @@ describe('ユーザー', () => { // MeDetailedOnly assert.strictEqual(response.avatarId, null); assert.strictEqual(response.bannerId, null); + assert.strictEqual(response.followedMessage, null); assert.strictEqual(response.isModerator, false); assert.strictEqual(response.isAdmin, false); assert.strictEqual(response.injectFeaturedNote, true); @@ -413,6 +416,8 @@ describe('ユーザー', () => { { parameters: () => ({ description: 'x'.repeat(1500) }) }, { parameters: () => ({ description: 'x' }) }, { parameters: () => ({ description: 'My description' }) }, + { parameters: () => ({ followedMessage: null }) }, + { parameters: () => ({ followedMessage: 'Thank you' }) }, { parameters: () => ({ location: null }) }, { parameters: () => ({ location: 'x'.repeat(50) }) }, { parameters: () => ({ location: 'x' }) }, 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/SigninWithPasskeyApiService.ts b/packages/backend/test/unit/SigninWithPasskeyApiService.ts new file mode 100644 index 0000000000..bae2b88c60 --- /dev/null +++ b/packages/backend/test/unit/SigninWithPasskeyApiService.ts @@ -0,0 +1,182 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { IncomingHttpHeaders } from 'node:http'; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, jest, test } from '@jest/globals'; +import { Test, TestingModule } from '@nestjs/testing'; +import { FastifyReply, FastifyRequest } from 'fastify'; +import { AuthenticationResponseJSON } from '@simplewebauthn/types'; +import { HttpHeader } from 'fastify/types/utils.js'; +import { MockFunctionMetadata, ModuleMocker } from 'jest-mock'; +import { MiUser } from '@/models/User.js'; +import { MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import { IdService } from '@/core/IdService.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { DI } from '@/di-symbols.js'; +import { CoreModule } from '@/core/CoreModule.js'; +import { SigninWithPasskeyApiService } from '@/server/api/SigninWithPasskeyApiService.js'; +import { RateLimiterService } from '@/server/api/RateLimiterService.js'; +import { WebAuthnService } from '@/core/WebAuthnService.js'; +import { SigninService } from '@/server/api/SigninService.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; + +const moduleMocker = new ModuleMocker(global); + +class FakeLimiter { + public async limit() { + return; + } +} + +class FakeSigninService { + public signin(..._args: any): any { + return true; + } +} + +class DummyFastifyReply { + public statusCode: number; + code(num: number): void { + this.statusCode = num; + } + header(_key: HttpHeader, _value: any): void { + } +} +class DummyFastifyRequest { + public ip: string; + public body: {credential: any, context: string}; + public headers: IncomingHttpHeaders = { 'accept': 'application/json' }; + constructor(body?: any) { + this.ip = '0.0.0.0'; + this.body = body; + } +} + +type ApiFastifyRequestType = FastifyRequest<{ + Body: { + credential?: AuthenticationResponseJSON; + context?: string; + }; +}>; + +describe('SigninWithPasskeyApiService', () => { + let app: TestingModule; + let passkeyApiService: SigninWithPasskeyApiService; + let usersRepository: UsersRepository; + let userProfilesRepository: UserProfilesRepository; + let webAuthnService: WebAuthnService; + let idService: IdService; + let FakeWebauthnVerify: ()=>Promise<string>; + + async function createUser(data: Partial<MiUser> = {}) { + const user = await usersRepository + .save({ + ...data, + }); + return user; + } + + async function createUserProfile(data: Partial<MiUserProfile> = {}) { + const userProfile = await userProfilesRepository + .save({ ...data }, + ); + return userProfile; + } + + beforeAll(async () => { + app = await Test.createTestingModule({ + imports: [GlobalModule, CoreModule], + providers: [ + SigninWithPasskeyApiService, + { provide: RateLimiterService, useClass: FakeLimiter }, + { provide: SigninService, useClass: FakeSigninService }, + ], + }).useMocker((token) => { + if (typeof token === 'function') { + const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata<any, any>; + const Mock = moduleMocker.generateFromMetadata(mockMetadata); + return new Mock(); + } + }).compile(); + passkeyApiService = app.get<SigninWithPasskeyApiService>(SigninWithPasskeyApiService); + usersRepository = app.get<UsersRepository>(DI.usersRepository); + userProfilesRepository = app.get<UserProfilesRepository>(DI.userProfilesRepository); + webAuthnService = app.get<WebAuthnService>(WebAuthnService); + idService = app.get<IdService>(IdService); + }); + + beforeEach(async () => { + const uid = idService.gen(); + FakeWebauthnVerify = async () => { + return uid; + }; + jest.spyOn(webAuthnService, 'verifySignInWithPasskeyAuthentication').mockImplementation(FakeWebauthnVerify); + + const dummyUser = { + id: uid, username: uid, usernameLower: uid.toLocaleLowerCase(), uri: null, host: null, + }; + const dummyProfile = { + userId: uid, + password: 'qwerty', + usePasswordLessLogin: true, + }; + await createUser(dummyUser); + await createUserProfile(dummyProfile); + }); + + afterAll(async () => { + await app.close(); + }); + + describe('Get Passkey Options', () => { + it('Should return passkey Auth Options', async () => { + const req = new DummyFastifyRequest({}) as ApiFastifyRequestType; + const res = new DummyFastifyReply() as unknown as FastifyReply; + const res_body = await passkeyApiService.signin(req, res); + expect(res.statusCode).toBe(200); + expect((res_body as any).option).toBeDefined(); + expect(typeof (res_body as any).context).toBe('string'); + }); + }); + describe('Try Passkey Auth', () => { + it('Should Success', async () => { + const req = new DummyFastifyRequest({ context: 'auth-context', credential: { dummy: [] } }) as ApiFastifyRequestType; + const res = new DummyFastifyReply() as FastifyReply; + const res_body = await passkeyApiService.signin(req, res); + expect((res_body as any).signinResponse).toBeDefined(); + }); + + it('Should return 400 Without Auth Context', async () => { + const req = new DummyFastifyRequest({ credential: { dummy: [] } }) as ApiFastifyRequestType; + const res = new DummyFastifyReply() as FastifyReply; + const res_body = await passkeyApiService.signin(req, res); + expect(res.statusCode).toBe(400); + expect((res_body as any).error?.id).toStrictEqual('1658cc2e-4495-461f-aee4-d403cdf073c1'); + }); + + it('Should return 403 When Challenge Verify fail', async () => { + const req = new DummyFastifyRequest({ context: 'misskey-1234', credential: { dummy: [] } }) as ApiFastifyRequestType; + const res = new DummyFastifyReply() as FastifyReply; + jest.spyOn(webAuthnService, 'verifySignInWithPasskeyAuthentication') + .mockImplementation(async () => { + throw new IdentifiableError('THIS_ERROR_CODE_SHOULD_BE_FORWARDED'); + }); + const res_body = await passkeyApiService.signin(req, res); + expect(res.statusCode).toBe(403); + expect((res_body as any).error?.id).toStrictEqual('THIS_ERROR_CODE_SHOULD_BE_FORWARDED'); + }); + + it('Should return 403 When The user not Enabled Passwordless login', async () => { + const req = new DummyFastifyRequest({ context: 'misskey-1234', credential: { dummy: [] } }) as ApiFastifyRequestType; + const res = new DummyFastifyReply() as FastifyReply; + const userId = await FakeWebauthnVerify(); + const data = { userId: userId, usePasswordLessLogin: false }; + await userProfilesRepository.update({ userId: userId }, data); + const res_body = await passkeyApiService.signin(req, res); + expect(res.statusCode).toBe(403); + expect((res_body as any).error?.id).toStrictEqual('2d84773e-f7b7-4d0b-8f72-bb69b584c912'); + }); + }); +}); 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 Binary files differnew file mode 100644 index 0000000000..39332b0c1b --- /dev/null +++ b/packages/frontend-embed/assets/dummy.png 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..9e720b9835 --- /dev/null +++ b/packages/frontend-embed/package.json @@ -0,0 +1,72 @@ +{ + "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", + "@rollup/plugin-json": "6.1.0", + "@rollup/plugin-replace": "5.0.7", + "@rollup/pluginutils": "5.1.2", + "@tabler/icons-webfont": "3.3.0", + "@twemoji/parser": "15.1.1", + "@vitejs/plugin-vue": "5.1.4", + "@vue/compiler-sfc": "3.5.10", + "astring": "1.9.0", + "buraha": "0.0.1", + "estree-walker": "3.0.3", + "mfm-js": "0.24.0", + "misskey-js": "workspace:*", + "frontend-shared": "workspace:*", + "punycode": "2.3.1", + "rollup": "4.22.5", + "sass": "1.79.3", + "shiki": "1.12.0", + "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.8", + "vue": "3.5.10" + }, + "devDependencies": { + "@misskey-dev/summaly": "5.1.0", + "@testing-library/vue": "8.1.0", + "@types/estree": "1.0.6", + "@types/micromatch": "4.0.9", + "@types/node": "20.14.12", + "@types/punycode": "2.1.4", + "@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.10", + "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..4a116e317a --- /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 = props.instance?.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..470352469b --- /dev/null +++ b/packages/frontend-embed/src/components/EmMediaImage.vue @@ -0,0 +1,162 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="[hide ? $style.hidden : $style.visible]" @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 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-size: 16px 16px; +} + +html[data-color-scheme=dark] .visible { + --c: rgb(255 255 255 / 2%); + 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%); +} + +html[data-color-scheme=light] .visible { + --c: rgb(0 0 0 / 2%); + 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%); +} + +.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..a631783507 --- /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 :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..f7899bfb03 --- /dev/null +++ b/packages/frontend-embed/src/components/EmNote.vue @@ -0,0 +1,611 @@ +<!-- +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 :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"/> + <EmInstanceTicker v-if="appearNote.user.instance != null" :instance="appearNote.user.instance"/> + <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 EmInstanceTicker from '@/components/EmInstanceTicker.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..360de31864 --- /dev/null +++ b/packages/frontend-embed/src/components/EmNoteDetailed.vue @@ -0,0 +1,490 @@ +<!-- +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> + <EmInstanceTicker v-if="appearNote.user.instance != null" :instance="appearNote.user.instance"/> + </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 EmInstanceTicker from '@/components/EmInstanceTicker.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..7d0b9bacad --- /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" :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..6f6c8c0f63 --- /dev/null +++ b/packages/frontend-embed/src/pages/note.vue @@ -0,0 +1,52 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="$style.noteEmbedRoot"> + <EmNoteDetailed v-if="note && !prohibited" :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); + +const prohibited = ref(false); + +if (assertServerContext(serverContext, 'note')) { + note.value = serverContext.note; +} else { + note.value = await misskeyApi('notes/show', { + noteId: props.noteId, + }).catch(() => { + return null; + }); +} + +if (note.value?.url != null || note.value?.uri != null) { + // リモートサーãƒãƒ¼ã®ãƒŽãƒ¼ãƒˆã¯å¼¾ã + prohibited.value = true; +} +</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..85e6f52d50 --- /dev/null +++ b/packages/frontend-embed/src/pages/user-timeline.vue @@ -0,0 +1,158 @@ +<!-- +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" @click="top"> + <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 { scrollToTop } from '@@/js/scroll.js'; +import { isLink } from '@@/js/is-link.js'; +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'); + +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> +.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..23e70cd0d3 --- /dev/null +++ b/packages/frontend-embed/src/theme.ts @@ -0,0 +1,108 @@ +/* + * 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); + + const colorScheme = theme.base === 'dark' ? 'dark' : 'light'; + + document.documentElement.dataset.colorScheme = colorScheme; + + // 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..8da5f46a96 --- /dev/null +++ b/packages/frontend-embed/src/ui.vue @@ -0,0 +1,110 @@ +<!-- +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'; + +function safeURIDecode(str: string): string { + try { + return decodeURIComponent(str); + } catch { + return str; + } +} + +const page = location.pathname.split('/')[2]; +const contentId = safeURIDecode(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..939648aa38 --- /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: Misskey.entities.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 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 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 index e135bc69a0..aec4a4a58b 100644 --- a/packages/frontend/src/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -67,6 +67,8 @@ export const notificationTypes = [ 'followRequestAccepted', 'roleAssigned', 'achievementEarned', + 'exportCompleted', + 'test', 'app', ] as const; export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const; @@ -98,6 +100,11 @@ export const ROLE_POLICIES = [ 'userEachUserListsLimit', 'rateLimitFactor', 'avatarDecorationLimit', + 'canImportAntennas', + 'canImportBlocking', + 'canImportFollowing', + 'canImportMuting', + 'canImportUserLists', ] as const; // ãªã‚“ã‹å‹•ã‹ãªã„ @@ -127,7 +134,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 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 index 75d5c34d71..75d5c34d71 100644 --- a/packages/frontend/src/emojilist.json +++ b/packages/frontend-shared/js/emojilist.json diff --git a/packages/frontend/src/scripts/emojilist.ts b/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 index 992f6e9a16..992f6e9a16 100644 --- a/packages/frontend/src/scripts/extract-avg-color-from-blurhash.ts +++ b/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 index c2f44a33cc..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++) { @@ -137,7 +143,6 @@ export class I18n<T extends ILocale> { return this.tsxCache = new Proxy(this.locale, new Handler()) as unknown as Tsx<T>; } - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (this.tsxCache) { return this.tsxCache; } @@ -153,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[] = []; @@ -180,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++) { @@ -209,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; @@ -220,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) { @@ -231,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}`); } @@ -244,51 +249,3 @@ export class I18n<T extends ILocale> { return str; } } - -if (import.meta.vitest) { - const { describe, expect, it } = import.meta.vitest; - - describe('i18n', () => { - it('t', () => { - const i18n = new I18n({ - foo: 'foo', - bar: { - baz: 'baz', - qux: 'qux {0}' as unknown as ParameterizedString<'0'>, - quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>, - }, - }); - - expect(i18n.t('foo')).toBe('foo'); - expect(i18n.t('bar.baz')).toBe('baz'); - expect(i18n.tsx.bar.qux({ 0: 'hoge' })).toBe('qux hoge'); - expect(i18n.tsx.bar.quux({ 0: 'hoge', 1: 'fuga' })).toBe('quux hoge fuga'); - }); - it('ts', () => { - const i18n = new I18n({ - foo: 'foo', - bar: { - baz: 'baz', - qux: 'qux {0}' as unknown as ParameterizedString<'0'>, - quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>, - }, - }); - - expect(i18n.ts.foo).toBe('foo'); - expect(i18n.ts.bar.baz).toBe('baz'); - }); - it('tsx', () => { - const i18n = new I18n({ - foo: 'foo', - bar: { - baz: 'baz', - qux: 'qux {0}' as unknown as ParameterizedString<'0'>, - quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>, - }, - }); - - expect(i18n.tsx.bar.qux({ 0: 'hoge' })).toBe('qux hoge'); - expect(i18n.tsx.bar.quux({ 0: 'hoge', 1: 'fuga' })).toBe('quux hoge fuga'); - }); - }); -} 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 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 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 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 index b50e78c3cc..b50e78c3cc 100644 --- a/packages/frontend/src/scripts/use-interval.ts +++ b/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 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 index c82a956868..eb5fda3dc0 100644 --- a/packages/frontend/src/themes/_dark.json5 +++ b/packages/frontend-shared/themes/_dark.json5 @@ -13,6 +13,7 @@ accentDarken: ':darken<10<@accent', accentLighten: ':lighten<10<@accent', accentedBg: ':alpha<0.15<@accent', + love: '#dd2e44', focus: ':alpha<0.3<@accent', bg: '#000', acrylicBg: ':alpha<0.5<@bg', @@ -53,11 +54,13 @@ infoFg: '#fff', infoWarnBg: '#42321c', infoWarnFg: '#ffbd3e', - switchBg: 'rgba(255, 255, 255, 0.15)', - buttonBg: 'rgba(255, 255, 255, 0.05)', - buttonHoverBg: 'rgba(255, 255, 255, 0.1)', + folderHeaderBg: 'rgba(255, 255, 255, 0.05)', + folderHeaderHoverBg: 'rgba(255, 255, 255, 0.1)', + buttonBg: ':lighten<5<@panel', + buttonHoverBg: ':lighten<10<@panel', buttonGradateA: '@accent', buttonGradateB: ':hue<20<@accent', + switchBg: 'rgba(255, 255, 255, 0.15)', switchOffBg: 'rgba(255, 255, 255, 0.1)', switchOffFg: ':alpha<0.8<@fg', switchOnBg: '@accentedBg', @@ -77,22 +80,14 @@ codeBoolean: '#c59eff', deckBg: '#000', htmlThemeColor: '@bg', - X2: ':darken<2<@panel', X3: 'rgba(255, 255, 255, 0.05)', X4: 'rgba(255, 255, 255, 0.1)', X5: 'rgba(255, 255, 255, 0.05)', X6: 'rgba(255, 255, 255, 0.15)', X7: 'rgba(255, 255, 255, 0.05)', - X8: ':lighten<5<@accent', - X9: ':darken<5<@accent', - X10: ':alpha<0.4<@accent', X11: 'rgba(0, 0, 0, 0.3)', X12: 'rgba(255, 255, 255, 0.1)', X13: 'rgba(255, 255, 255, 0.15)', - X14: ':alpha<0.5<@navBg', - X15: ':alpha<0<@panel', - X16: ':alpha<0.7<@panel', - X17: ':alpha<0.8<@bg', }, codeHighlighter: { diff --git a/packages/frontend/src/themes/_light.json5 b/packages/frontend-shared/themes/_light.json5 index 63bc030916..e0196dcbf3 100644 --- a/packages/frontend/src/themes/_light.json5 +++ b/packages/frontend-shared/themes/_light.json5 @@ -13,6 +13,7 @@ accentDarken: ':darken<10<@accent', accentLighten: ':lighten<10<@accent', accentedBg: ':alpha<0.15<@accent', + love: '#dd2e44', focus: ':alpha<0.3<@accent', bg: '#fff', acrylicBg: ':alpha<0.5<@bg', @@ -53,11 +54,13 @@ infoFg: '#72818a', infoWarnBg: '#fff0db', infoWarnFg: '#8f6e31', - switchBg: 'rgba(0, 0, 0, 0.15)', - buttonBg: 'rgba(0, 0, 0, 0.05)', - buttonHoverBg: 'rgba(0, 0, 0, 0.1)', + folderHeaderBg: 'rgba(0, 0, 0, 0.05)', + folderHeaderHoverBg: 'rgba(0, 0, 0, 0.1)', + buttonBg: ':darken<5<@panel', + buttonHoverBg: ':darken<10<@panel', buttonGradateA: '@accent', buttonGradateB: ':hue<20<@accent', + switchBg: 'rgba(0, 0, 0, 0.15)', switchOffBg: 'rgba(0, 0, 0, 0.1)', switchOffFg: '@panel', switchOnBg: '@accent', @@ -77,22 +80,14 @@ codeBoolean: '#62b70c', deckBg: ':darken<3<@bg', htmlThemeColor: '@bg', - X2: ':darken<2<@panel', X3: 'rgba(0, 0, 0, 0.05)', X4: 'rgba(0, 0, 0, 0.1)', X5: 'rgba(0, 0, 0, 0.05)', X6: 'rgba(0, 0, 0, 0.25)', X7: 'rgba(0, 0, 0, 0.05)', - X8: ':lighten<5<@accent', - X9: ':darken<5<@accent', - X10: ':alpha<0.4<@accent', X11: 'rgba(0, 0, 0, 0.1)', X12: 'rgba(0, 0, 0, 0.1)', X13: 'rgba(0, 0, 0, 0.15)', - X14: ':alpha<0.5<@navBg', - X15: ':alpha<0<@panel', - X16: ':alpha<0.7<@panel', - X17: ':alpha<0.8<@bg', }, codeHighlighter: { diff --git a/packages/frontend/src/themes/d-astro.json5 b/packages/frontend-shared/themes/d-astro.json5 index fee25cc4a4..a674a5c5c9 100644 --- a/packages/frontend/src/themes/d-astro.json5 +++ b/packages/frontend-shared/themes/d-astro.json5 @@ -25,7 +25,6 @@ mention: '#ffd152', modalBg: 'rgba(0, 0, 0, 0.5)', success: '#86b300', - buttonBg: 'rgba(255, 255, 255, 0.05)', acrylicBg: ':alpha<0.5<@bg', indicator: '@accent', mentionMe: '#fb5d38', @@ -42,7 +41,6 @@ acrylicPanel: ':alpha<0.5<@panel', navIndicator: '@accent', accentLighten: ':lighten<10<@accent', - buttonHoverBg: 'rgba(255, 255, 255, 0.1)', buttonGradateA: '@accent', buttonGradateB: ':hue<-20<@accent', driveFolderBg: ':alpha<0.3<@accent', @@ -57,20 +55,13 @@ wallpaperOverlay: 'rgba(0, 0, 0, 0.5)', panelHeaderDivider: 'rgba(0, 0, 0, 0)', scrollbarHandleHover: 'rgba(255, 255, 255, 0.4)', - X2: ':darken<2<@panel', X3: 'rgba(255, 255, 255, 0.05)', X4: 'rgba(255, 255, 255, 0.1)', X5: 'rgba(255, 255, 255, 0.05)', X6: 'rgba(255, 255, 255, 0.15)', X7: 'rgba(255, 255, 255, 0.05)', - X8: ':lighten<5<@accent', - X9: ':darken<5<@accent', - X10: ':alpha<0.4<@accent', X11: 'rgba(0, 0, 0, 0.3)', X12: 'rgba(255, 255, 255, 0.1)', X13: 'rgba(255, 255, 255, 0.15)', - X14: ':alpha<0.5<@navBg', - X15: ':alpha<0<@panel', - X16: ':alpha<0.7<@panel', }, } diff --git a/packages/frontend/src/themes/d-botanical.json5 b/packages/frontend-shared/themes/d-botanical.json5 index 62208d2378..62208d2378 100644 --- a/packages/frontend/src/themes/d-botanical.json5 +++ b/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 index f9638124c2..f9638124c2 100644 --- a/packages/frontend/src/themes/d-cherry.json5 +++ b/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 index ae4f7d53f5..ae4f7d53f5 100644 --- a/packages/frontend/src/themes/d-dark.json5 +++ b/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 index f2c1f3eb86..f2c1f3eb86 100644 --- a/packages/frontend/src/themes/d-future.json5 +++ b/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 index ca4e688fdb..ca4e688fdb 100644 --- a/packages/frontend/src/themes/d-green-lime.json5 +++ b/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 index c2539816e2..c2539816e2 100644 --- a/packages/frontend/src/themes/d-green-orange.json5 +++ b/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 index b4abc0cacb..b4abc0cacb 100644 --- a/packages/frontend/src/themes/d-ice.json5 +++ b/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 index 0ab6523dd7..0ab6523dd7 100644 --- a/packages/frontend/src/themes/d-persimmon.json5 +++ b/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 index 3bd0b9483c..32ac9ec5cf 100644 --- a/packages/frontend/src/themes/d-u0.json5 +++ b/packages/frontend-shared/themes/d-u0.json5 @@ -3,14 +3,11 @@ base: 'dark', name: 'Mi U0 Dark', props: { - X2: ':darken<2<@panel', X3: 'rgba(255, 255, 255, 0.05)', X4: 'rgba(255, 255, 255, 0.1)', X5: 'rgba(255, 255, 255, 0.05)', X6: 'rgba(255, 255, 255, 0.15)', X7: 'rgba(255, 255, 255, 0.05)', - X8: ':lighten<5<@accent', - X9: ':darken<5<@accent', bg: '#172426', fg: '#dadada', X10: ':alpha<0.4<@accent', @@ -41,7 +38,6 @@ mention: '@accent', modalBg: 'rgba(0, 0, 0, 0.5)', success: '#86b300', - buttonBg: 'rgba(255, 255, 255, 0.05)', switchBg: 'rgba(255, 255, 255, 0.15)', acrylicBg: ':alpha<0.5<@bg', indicator: '@accent', @@ -64,7 +60,6 @@ acrylicPanel: ':alpha<0.5<@panel', navIndicator: '@indicator', accentLighten: ':lighten<10<@accent', - buttonHoverBg: 'rgba(255, 255, 255, 0.1)', driveFolderBg: ':alpha<0.3<@accent', fgHighlighted: ':lighten<3<@fg', fgTransparent: ':alpha<0.5<@fg', diff --git a/packages/frontend/src/themes/l-apricot.json5 b/packages/frontend-shared/themes/l-apricot.json5 index fe1f9f8927..fe1f9f8927 100644 --- a/packages/frontend/src/themes/l-apricot.json5 +++ b/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 index 17e9ca246f..17e9ca246f 100644 --- a/packages/frontend/src/themes/l-botanical.json5 +++ b/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 index 1189a28fe6..1189a28fe6 100644 --- a/packages/frontend/src/themes/l-cherry.json5 +++ b/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 index b64cc73583..b64cc73583 100644 --- a/packages/frontend/src/themes/l-coffee.json5 +++ b/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 index 63c2e6d278..63c2e6d278 100644 --- a/packages/frontend/src/themes/l-light.json5 +++ b/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 index e7d1d5af00..e7d1d5af00 100644 --- a/packages/frontend/src/themes/l-rainy.json5 +++ b/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 index e787d63734..e787d63734 100644 --- a/packages/frontend/src/themes/l-sushi.json5 +++ b/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 index dbc777d493..0b952b003a 100644 --- a/packages/frontend/src/themes/l-u0.json5 +++ b/packages/frontend-shared/themes/l-u0.json5 @@ -3,14 +3,11 @@ base: 'light', name: 'Mi U0 Light', props: { - X2: ':darken<2<@panel', X3: 'rgba(255, 255, 255, 0.05)', X4: 'rgba(255, 255, 255, 0.1)', X5: 'rgba(255, 255, 255, 0.05)', X6: 'rgba(255, 255, 255, 0.15)', X7: 'rgba(255, 255, 255, 0.05)', - X8: ':lighten<5<@accent', - X9: ':darken<5<@accent', bg: '#e7e7eb', fg: '#5f5f5f', X10: ':alpha<0.4<@accent', diff --git a/packages/frontend/src/themes/l-vivid.json5 b/packages/frontend-shared/themes/l-vivid.json5 index 3368855b5e..f1c63dde6e 100644 --- a/packages/frontend/src/themes/l-vivid.json5 +++ b/packages/frontend-shared/themes/l-vivid.json5 @@ -28,7 +28,6 @@ mention: '@accent', modalBg: 'rgba(0, 0, 0, 0.3)', success: '#86b300', - buttonBg: 'rgba(0, 0, 0, 0.05)', acrylicBg: ':alpha<0.5<@bg', indicator: '@accent', mentionMe: '@mention', @@ -45,7 +44,6 @@ acrylicPanel: ':alpha<0.5<@panel', navIndicator: '@accent', accentLighten: ':lighten<10<@accent', - buttonHoverBg: 'rgba(0, 0, 0, 0.1)', driveFolderBg: ':alpha<0.3<@accent', fgHighlighted: ':darken<3<@fg', fgTransparent: ':alpha<0.5<@fg', @@ -60,21 +58,13 @@ fgTransparentWeak: ':alpha<0.75<@fg', panelHeaderDivider: '@divider', scrollbarHandleHover: 'rgba(0, 0, 0, 0.4)', - X2: ':darken<2<@panel', X3: 'rgba(0, 0, 0, 0.05)', X4: 'rgba(0, 0, 0, 0.1)', X5: 'rgba(0, 0, 0, 0.05)', X6: 'rgba(0, 0, 0, 0.25)', X7: 'rgba(0, 0, 0, 0.05)', - X8: ':lighten<5<@accent', - X9: ':darken<5<@accent', - X10: ':alpha<0.4<@accent', X11: 'rgba(0, 0, 0, 0.1)', X12: 'rgba(0, 0, 0, 0.1)', X13: 'rgba(0, 0, 0, 0.15)', - X14: ':alpha<0.5<@navBg', - X15: ':alpha<0<@panel', - X16: ':alpha<0.7<@panel', - X17: ':alpha<0.8<@bg', }, } 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..d3909babfd 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -17,33 +17,32 @@ "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", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "5.0.7", - "@rollup/pluginutils": "5.1.0", + "@rollup/pluginutils": "5.1.2", "@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.10", "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.4", "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", "eventemitter3": "5.0.1", "idb-keyval": "6.2.1", @@ -55,87 +54,87 @@ "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.5", "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.169.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.8", + "vue": "3.5.10", "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.3", + "@storybook/addon-essentials": "8.3.3", + "@storybook/addon-interactions": "8.3.3", + "@storybook/addon-links": "8.3.3", + "@storybook/addon-mdx-gfm": "8.3.3", + "@storybook/addon-storysource": "8.3.3", + "@storybook/blocks": "8.3.3", + "@storybook/components": "8.3.3", + "@storybook/core-events": "8.3.3", + "@storybook/manager-api": "8.3.3", + "@storybook/preview-api": "8.3.3", + "@storybook/react": "8.3.3", + "@storybook/react-vite": "8.3.3", + "@storybook/test": "8.3.3", + "@storybook/theming": "8.3.3", + "@storybook/types": "8.3.3", + "@storybook/vue3": "8.3.3", + "@storybook/vue3-vite": "8.3.3", "@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.10", "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.15.0", + "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.3", "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/_boot_.ts b/packages/frontend/src/_boot_.ts index 875353f8a4..13a97e433c 100644 --- a/packages/frontend/src/_boot_.ts +++ b/packages/frontend/src/_boot_.ts @@ -6,6 +6,8 @@ // 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 { mainBoot } from '@/boot/main-boot.js'; import { subBoot } from '@/boot/sub-boot.js'; diff --git a/packages/frontend/src/_dev_boot_.ts b/packages/frontend/src/_dev_boot_.ts index 7c6e537fbc..1601f247d7 100644 --- a/packages/frontend/src/_dev_boot_.ts +++ b/packages/frontend/src/_dev_boot_.ts @@ -3,11 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -// devモードã§èµ·å‹•ã•れる際(index.htmlを使ã†ã¨ã)ã¯routerãŒæš´ç™ºã—ã¦ã—ã¾ã£ã¦ã†ã¾ãèªã¿è¾¼ã‚ãªã„。 -// よã£ã¦ã€devモードã¨ã—ã¦èµ·å‹•ã•れるã¨ãã¯ãƒ“ルド時ã«çµ„ã¿è¾¼ã‚€å½¢ã¨ã—ã¦ãŠã。 -// (pnpm start時ã¯pugファイルã®ä¸ã§é™çš„リソースã¨ã—ã¦èªã¿è¾¼ã‚€ã‚ˆã†ã«ãªã£ã¦ãŠã‚Šã€ã“ã®å•題ã¯èµ·ã“ã£ã¦ã„ãªã„) -import '@tabler/icons-webfont/dist/tabler-icons.scss'; - await main(); import('@/_boot_.js'); 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..af05f657c8 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -5,10 +5,10 @@ import { computed, watch, version as vueVersion, App } from 'vue'; import { compareVersions } from 'compare-versions'; +import { version, lang, updateLocale, locale } from '@@/js/config.js'; 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 { 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}`); @@ -145,10 +146,9 @@ export async function common(createVue: () => App<Element>) { // NOTE: ã“ã®å‡¦ç†ã¯å¿…ãšã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆæ›´æ–°ãƒã‚§ãƒƒã‚¯å‡¦ç†ã‚ˆã‚Šå¾Œã«æ¥ã‚‹ã“ã¨(ãƒ†ãƒ¼ãƒžå†æ§‹ç¯‰ã®ãŸã‚) watch(defaultStore.reactiveState.darkMode, (darkMode) => { applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme')); - document.documentElement.dataset.colorMode = darkMode ? 'dark' : 'light'; }, { immediate: miLocalStorage.getItem('theme') == null }); - document.documentElement.dataset.colorMode = defaultStore.state.darkMode ? 'dark' : 'light'; + document.documentElement.dataset.colorScheme = defaultStore.state.darkMode ? 'dark' : 'light'; const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme')); const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme')); @@ -239,7 +239,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/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue index 18e8e7542e..b50a7fea5c 100644 --- a/packages/frontend/src/components/MkAsUi.vue +++ b/packages/frontend/src/components/MkAsUi.vue @@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size"/> </template> </MkFolder> - <div v-else-if="c.type === 'container'" :class="[$style.container, { [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }]" :style="{ textAlign: c.align, backgroundColor: c.bgColor, color: c.fgColor, borderWidth: c.borderWidth ? `${c.borderWidth}px` : 0, borderColor: c.borderColor ?? 'var(--divider)', padding: c.padding ? `${c.padding}px` : 0, borderRadius: c.rounded ? '8px' : 0 }"> + <div v-else-if="c.type === 'container'" :class="[$style.container, { [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }]" :style="containerStyle"> <template v-for="child in c.children" :key="child"> <MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size" :align="c.align"/> </template> @@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { Ref, ref } from 'vue'; +import { Ref, ref, computed } from 'vue'; import * as os from '@/os.js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; @@ -97,6 +97,29 @@ function g(id) { } as AsUiRoot; } +const containerStyle = computed(() => { + if (c.type !== 'container') return undefined; + + // width, color, styleã®ã†ã¡ä¸€ã¤ã§ã‚‚指定ãŒã‚れã°ã€æž ç·šãŒã¡ã‚ƒã‚“ã¨è¡¨ç¤ºã•れるよã†ã«widthã¨styleã®ãƒ‡ãƒ•ォルト値をè¨å®š + // radiusã¯å˜ã«è§’を丸ã‚る用途もã‚ã‚‹ãŸã‚除外 + const isBordered = c.borderWidth ?? c.borderColor ?? c.borderStyle; + + const border = isBordered ? { + borderWidth: c.borderWidth ?? '1px', + borderColor: c.borderColor ?? 'var(--divider)', + borderStyle: c.borderStyle ?? 'solid', + } : undefined; + + return { + textAlign: c.align, + backgroundColor: c.bgColor, + color: c.fgColor, + padding: c.padding ? `${c.padding}px` : 0, + borderRadius: (c.borderRadius ?? (c.rounded ? 8 : 0)) + 'px', + ...border, + }; +}); + const valueForSwitch = ref('default' in c && typeof c.default === 'boolean' ? c.default : false); function onSwitchUpdate(v) { 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 9560efb7d9..1156b3f2b8 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -171,11 +171,11 @@ function onMousedown(evt: MouseEvent): void { background: var(--accent); &:not(:disabled):hover { - background: var(--X8); + background: hsl(from var(--accent) h s calc(l + 5)); } &:not(:disabled):active { - background: var(--X8); + background: hsl(from var(--accent) h s calc(l + 5)); } } @@ -220,15 +220,16 @@ function onMousedown(evt: MouseEvent): void { background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); &:not(:disabled):hover { - background: linear-gradient(90deg, var(--X8), var(--X8)); + 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, var(--X8), var(--X8)); + background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); } } &.danger { + font-weight: bold; color: #ff2a2a; &.primary { @@ -246,7 +247,7 @@ function onMousedown(evt: MouseEvent): void { } &:disabled { - opacity: 0.7; + opacity: 0.5; } &:focus-visible { diff --git a/packages/frontend/src/components/MkChannelPreview.vue b/packages/frontend/src/components/MkChannelPreview.vue index c30cb66c07..3c0874a1eb 100644 --- a/packages/frontend/src/components/MkChannelPreview.vue +++ b/packages/frontend/src/components/MkChannelPreview.vue @@ -117,7 +117,7 @@ const bannerStyle = computed(() => { left: 0; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), var(--X15)); + background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); } > .name { 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/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue index a399acd47f..8ad653a0bf 100644 --- a/packages/frontend/src/components/MkContainer.vue +++ b/packages/frontend/src/components/MkContainer.vue @@ -216,7 +216,7 @@ onUnmounted(() => { left: 0; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), var(--X15)); + background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); > .fadeLabel { display: inline-block; 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/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue index 7e1ffbfa9e..21c712b441 100644 --- a/packages/frontend/src/components/MkEmojiPickerDialog.vue +++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only ref="modal" v-slot="{ type, maxHeight }" :zPriority="'middle'" - :preferType="defaultStore.state.emojiPickerUseDrawerForMobile === false ? 'popup' : 'auto'" + :preferType="defaultStore.state.emojiPickerStyle" :hasInteractionWithOtherFocusTrappedEls="true" :transparentBg="true" :manualShowing="manualShowing" 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..a5f3069d45 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> @@ -136,7 +139,7 @@ onMounted(() => { width: 100%; box-sizing: border-box; padding: 9px 12px 9px 12px; - background: var(--buttonBg); + background: var(--folderHeaderBg); -webkit-backdrop-filter: var(--blur, blur(15px)); backdrop-filter: var(--blur, blur(15px)); border-radius: 6px; @@ -144,7 +147,7 @@ onMounted(() => { &:hover { text-decoration: none; - background: var(--buttonHoverBg); + background: var(--folderHeaderHoverBg); } &:focus-within { @@ -153,7 +156,7 @@ onMounted(() => { &.active { color: var(--accent); - background: var(--buttonHoverBg); + background: var(--folderHeaderHoverBg); } &.opened { @@ -224,4 +227,18 @@ onMounted(() => { background: var(--bg); } } + +.footer { + position: sticky !important; + z-index: 1; + bottom: var(--stickyBottom, 0px); + left: 0; + padding: 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..0de52906ed 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only <span v-if="full" :class="$style.text">{{ i18n.ts.processing }}</span><MkLoading :em="true" :colored="false"/> </template> <template v-else-if="isFollowing"> - <span v-if="full" :class="$style.text">{{ i18n.ts.unfollow }}</span><i class="ti ti-minus"></i> + <span v-if="full" :class="$style.text">{{ i18n.ts.youFollowing }}</span><i class="ti ti-minus"></i> </template> <template v-else-if="!isFollowing && user.isLocked"> <span v-if="full" :class="$style.text">{{ i18n.ts.followRequest }}</span><i class="ti ti-plus"></i> @@ -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/MkInviteCode.vue b/packages/frontend/src/components/MkInviteCode.vue index de51a98789..4aee64f78e 100644 --- a/packages/frontend/src/components/MkInviteCode.vue +++ b/packages/frontend/src/components/MkInviteCode.vue @@ -11,8 +11,14 @@ SPDX-License-Identifier: AGPL-3.0-only <span v-else-if="isExpired" style="color: var(--error)">{{ i18n.ts.expired }}</span> <span v-else style="color: var(--success)">{{ i18n.ts.unused }}</span> </template> + <template #footer> + <div class="_buttons"> + <MkButton v-if="!invite.used && !isExpired" primary rounded @click="copyInviteCode()"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton> + <MkButton v-if="!invite.used || moderator" danger rounded @click="deleteCode()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + </div> + </template> - <div class="_gaps_s" :class="$style.root"> + <div :class="$style.root"> <div :class="$style.items"> <div> <div :class="$style.label">{{ i18n.ts.invitationCode }}</div> @@ -49,10 +55,6 @@ SPDX-License-Identifier: AGPL-3.0-only <div><MkTime :time="invite.createdAt" mode="absolute"/></div> </div> </div> - <div :class="$style.buttons"> - <MkButton v-if="!invite.used && !isExpired" primary rounded @click="copyInviteCode()"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton> - <MkButton v-if="!invite.used || moderator" danger rounded @click="deleteCode()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> - </div> </div> </MkFolder> </template> @@ -121,9 +123,4 @@ function copyInviteCode() { width: var(--height); height: var(--height); } - -.buttons { - display: flex; - gap: 8px; -} </style> 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..0c5c8fd9de 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitive]" :style="darkMode ? '--c: rgb(255 255 255 / 2%);' : '--c: rgb(0 0 0 / 2%);'" @click="onclick"> +<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitive]" @click="onclick"> <component :is="disableImageLink ? 'div' : 'a'" v-bind="disableImageLink ? { @@ -53,6 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { watch, ref, computed } from 'vue'; import * as Misskey from 'misskey-js'; +import type { MenuItem } from '@/types/menu.js'; import { getStaticImageUrl } from '@/scripts/media-proxy.js'; import bytes from '@/filters/bytes.js'; import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; @@ -74,7 +75,6 @@ const props = withDefaults(defineProps<{ }); const hide = ref(true); -const darkMode = ref<boolean>(defaultStore.state.darkMode); const url = computed(() => (props.raw || defaultStore.state.loadRawImages) ? props.image.url @@ -111,27 +111,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> @@ -196,10 +208,19 @@ function showMenu(ev: MouseEvent) { 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; } +html[data-color-scheme=dark] .visible { + --c: rgb(255 255 255 / 2%); + 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%); +} + +html[data-color-scheme=light] .visible { + --c: rgb(0 0 0 / 2%); + 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%); +} + .menu { display: block; position: absolute; 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/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index c0728d56fa..890b99fcc2 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -4,17 +4,22 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div role="menu" @focusin.passive.stop="() => {}"> +<div + role="menu" + :class="{ + [$style.root]: true, + [$style.center]: align === 'center', + [$style.big]: big, + [$style.asDrawer]: asDrawer, + }" + @focusin.passive.stop="() => {}" +> <div ref="itemsEl" v-hotkey="keymap" tabindex="0" class="_popup _shadow" - :class="{ - [$style.root]: true, - [$style.center]: align === 'center', - [$style.asDrawer]: asDrawer, - }" + :class="$style.menu" :style="{ width: (width && !asDrawer) ? `${width}px` : '', maxHeight: maxHeight ? `min(${maxHeight}px, calc(100dvh - 32px))` : 'calc(100dvh - 32px)', @@ -200,6 +205,8 @@ const emit = defineEmits<{ (ev: 'hide'): void; }>(); +const big = isTouchUsing; + const isNestingMenu = inject<boolean>('isNestingMenu', false); const itemsEl = shallowRef<HTMLElement>(); @@ -297,6 +304,8 @@ async function showRadioOptions(item: MenuRadio, ev: Event) { } async function showChildren(item: MenuParent, ev: Event) { + ev.stopPropagation(); + const children: MenuItem[] = await (async () => { if (childrenCache.has(item)) { return childrenCache.get(item)!; @@ -418,48 +427,67 @@ onBeforeUnmount(() => { <style lang="scss" module> .root { - padding: 8px 0; - box-sizing: border-box; - max-width: 100vw; - min-width: 200px; - overflow: auto; - overscroll-behavior: contain; - - &:focus-visible { - outline: none; + &.center { + > .menu { + > .item { + text-align: center; + } + } } - &.center { - > .item { - text-align: center; + &.big:not(.asDrawer) { + > .menu { + > .item { + padding: 6px 20px; + font-size: 1em; + line-height: 24px; + } } } &.asDrawer { - padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0; - width: 100%; - border-radius: 24px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; + max-width: 600px; + margin: auto; - > .item { - font-size: 1em; - padding: 12px 24px; + > .menu { + padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0; + width: 100%; + border-radius: 24px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; - &::before { - width: calc(100% - 24px); - border-radius: 12px; + > .item { + font-size: 1em; + padding: 12px 24px; + + &::before { + width: calc(100% - 24px); + border-radius: 12px; + } + + > .icon { + margin-right: 14px; + width: 24px; + } } - > .icon { - margin-right: 14px; - width: 24px; + > .divider { + margin: 12px 0; } } + } +} - > .divider { - margin: 12px 0; - } +.menu { + padding: 8px 0; + box-sizing: border-box; + max-width: 100vw; + min-width: 200px; + overflow: auto; + overscroll-behavior: contain; + + &:focus-visible { + outline: none; } } 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/MkModal.vue b/packages/frontend/src/components/MkModal.vue index f8032f9b43..c766a33823 100644 --- a/packages/frontend/src/components/MkModal.vue +++ b/packages/frontend/src/components/MkModal.vue @@ -106,7 +106,7 @@ const zIndex = os.claimZIndex(props.zPriority); const useSendAnime = ref(false); const type = computed<ModalTypes>(() => { if (props.preferType === 'auto') { - if (!defaultStore.state.disableDrawer && isTouchUsing && deviceKind === 'smartphone') { + if ((defaultStore.state.menuStyle === 'drawer') || (defaultStore.state.menuStyle === 'auto' && isTouchUsing && deviceKind === 'smartphone')) { return 'drawer'; } else { return props.src != null ? 'popup' : 'dialog'; diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue index c3c7812036..f26959888b 100644 --- a/packages/frontend/src/components/MkModalWindow.vue +++ b/packages/frontend/src/components/MkModalWindow.vue @@ -94,12 +94,12 @@ defineExpose({ --root-margin: 24px; + --headerHeight: 46px; + --headerHeightNarrow: 42px; + @media (max-width: 500px) { --root-margin: 16px; } - - --headerHeight: 46px; - --headerHeightNarrow: 42px; } .header { diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 32d1cc5640..e8ff743bf2 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -53,7 +53,14 @@ SPDX-License-Identifier: AGPL-3.0-only <MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/> <div style="container-type: inline-size;"> <p v-if="appearNote.cw != null" :class="$style.cw"> - <Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/> + <Mfm + v-if="appearNote.cw != ''" + :text="appearNote.cw" + :author="appearNote.user" + :nyaize="'respect'" + :enableEmojiMenu="true" + :enableEmojiMenuReaction="true" + /> <MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll" style="margin: 4px 0;"/> </p> <div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]"> @@ -119,7 +126,7 @@ SPDX-License-Identifier: AGPL-3.0-only <i class="ti ti-ban"></i> </button> <button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()"> - <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i> + <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--love);"></i> <i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i> <i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i> <i v-else class="ti ti-plus"></i> @@ -163,6 +170,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 +200,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 +514,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 +625,7 @@ function emitUpdReaction(emoji: string, delta: number) { // 今度ã¯ãã®å‡¦ç†è‡ªä½“ãŒãƒ‘フォーマンス低下ã®åŽŸå› ã«ãªã‚‰ãªã„ã‹æ‡¸å¿µã•れる。ã¾ãŸã€è¢«ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã§ã‚‚高ã•ã¯å¤‰åŒ–ã™ã‚‹ãŸã‚ã€ã‚„ã¯ã‚Šå¤šå°‘ã®ã‚ºãƒ¬ã¯ç”Ÿã˜ã‚‹ // 一度レンダリングã•れãŸè¦ç´ ã¯ãƒ–ラウザãŒã‚ˆã—ãªã«ã‚µã‚¤ã‚ºã‚’覚ãˆã¦ãŠã„ã¦ãれるよã†ãªå®Ÿè£…ã«ãªã‚‹ã¾ã§å¾…ã£ãŸæ–¹ãŒè‰¯ã•ãã†(ãªã‚‹ã®ã‹ï¼Ÿ) //content-visibility: auto; - //contain-intrinsic-size: 0 128px; + //contain-intrinsic-size: 0 128px; &:focus-visible { outline: none; @@ -859,7 +857,7 @@ function emitUpdReaction(emoji: string, delta: number) { z-index: 2; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), var(--X15)); + background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); &:hover > .collapsedLabel { background: var(--panelHighlight); diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 2b7d2afa04..bdb800b32a 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -68,7 +68,14 @@ SPDX-License-Identifier: AGPL-3.0-only </header> <div :class="$style.noteContent"> <p v-if="appearNote.cw != null" :class="$style.cw"> - <Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/> + <Mfm + v-if="appearNote.cw != ''" + :text="appearNote.cw" + :author="appearNote.user" + :nyaize="'respect'" + :enableEmojiMenu="true" + :enableEmojiMenuReaction="true" + /> <MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll"/> </p> <div v-show="appearNote.cw == null || showContent"> @@ -128,7 +135,7 @@ SPDX-License-Identifier: AGPL-3.0-only <i class="ti ti-ban"></i> </button> <button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()"> - <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i> + <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--love);"></i> <i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i> <i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i> <i v-else class="ti ti-plus"></i> @@ -199,6 +206,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 +230,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 +476,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/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue index be5829d92f..888c570571 100644 --- a/packages/frontend/src/components/MkNoteHeader.vue +++ b/packages/frontend/src/components/MkNoteHeader.vue @@ -5,14 +5,18 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <header :class="$style.root"> - <div v-if="mock" :class="$style.name"> - <MkUserName :user="note.user"/> - </div> - <MkA v-else v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)"> - <MkUserName :user="note.user"/> - </MkA> - <div v-if="note.user.isBot" :class="$style.isBot">bot</div> - <div :class="$style.username"><MkAcct :user="note.user"/></div> + <component :is="defaultStore.state.enableCondensedLine ? 'MkCondensedLine' : 'div'" :minScale="0.5" style="min-width: 0;"> + <div style="display: flex; white-space: nowrap; align-items: baseline;"> + <div v-if="mock" :class="$style.name"> + <MkUserName :user="note.user"/> + </div> + <MkA v-else v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)"> + <MkUserName :user="note.user"/> + </MkA> + <div v-if="note.user.isBot" :class="$style.isBot">bot</div> + <div :class="$style.username"><MkAcct :user="note.user"/></div> + </div> + </component> <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> @@ -40,6 +44,7 @@ import * as Misskey from 'misskey-js'; import { i18n } from '@/i18n.js'; import { notePage } from '@/filters/note.js'; import { userPage } from '@/filters/user.js'; +import { defaultStore } from '@/store.js'; defineProps<{ note: Misskey.entities.Note; diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index ee65743574..12c2974de4 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -13,7 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div> <img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/> <MkAvatar v-else-if="'user' in notification" :class="$style.icon" :user="notification.user" link preview/> - <img v-else-if="'icon' in notification" :class="[$style.icon, $style.icon_app]" :src="notification.icon" alt=""/> + <MkAvatar v-else-if="notification.type === 'exportCompleted'" :class="$style.icon" :user="$i" link preview/> + <img v-else-if="'icon' in notification && notification.icon != null" :class="[$style.icon, $style.icon_app]" :src="notification.icon" alt=""/> <div :class="[$style.subIcon, { [$style.t_follow]: notification.type === 'follow', @@ -25,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only [$style.t_quote]: notification.type === 'quote', [$style.t_pollEnded]: notification.type === 'pollEnded', [$style.t_achievementEarned]: notification.type === 'achievementEarned', + [$style.t_exportCompleted]: notification.type === 'exportCompleted', [$style.t_roleAssigned]: notification.type === 'roleAssigned' && notification.role.iconUrl == null, }]" > @@ -37,6 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only <i v-else-if="notification.type === 'quote'" class="ti ti-quote"></i> <i v-else-if="notification.type === 'pollEnded'" class="ti ti-chart-arrows"></i> <i v-else-if="notification.type === 'achievementEarned'" class="ti ti-medal"></i> + <i v-else-if="notification.type === 'exportCompleted'" class="ti ti-archive"></i> <template v-else-if="notification.type === 'roleAssigned'"> <img v-if="notification.role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="notification.role.iconUrl" alt=""/> <i v-else class="ti ti-badges"></i> @@ -46,7 +49,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> @@ -57,6 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only <span v-else-if="notification.type === 'roleAssigned'">{{ i18n.ts._notification.roleAssigned }}</span> <span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span> <span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span> + <span v-else-if="notification.type === 'exportCompleted'">{{ i18n.tsx._notification.exportOfXCompleted({ x: exportEntityName[notification.exportedEntity] }) }}</span> <MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA> <span v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'">{{ i18n.tsx._notification.likedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}</span> <span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.tsx._notification.reactedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}</span> @@ -98,10 +102,20 @@ SPDX-License-Identifier: AGPL-3.0-only <MkA v-else-if="notification.type === 'achievementEarned'" :class="$style.text" to="/my/achievements"> {{ i18n.ts._achievements._types['_' + notification.achievement].title }} </MkA> + <MkA v-else-if="notification.type === 'exportCompleted'" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`"> + {{ i18n.ts.showFile }} + </MkA> <template v-else-if="notification.type === 'follow'"> <span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span> </template> - <span v-else-if="notification.type === 'followRequestAccepted'" :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span> + <template v-else-if="notification.type === 'followRequestAccepted'"> + <div :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</div> + <div v-if="notification.message" :class="$style.text" style="opacity: 0.6; font-style: oblique;"> + <i class="ti ti-quote" :class="$style.quote"></i> + <span>{{ notification.message }}</span> + <i class="ti ti-quote" :class="$style.quote"></i> + </div> + </template> <template v-else-if="notification.type === 'receiveFollowRequest'"> <span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}</span> <div v-if="full && !followRequestDone" :class="$style.followRequestCommands"> @@ -122,7 +136,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> @@ -161,6 +175,20 @@ const props = withDefaults(defineProps<{ full: false, }); +type ExportCompletedNotification = Misskey.entities.Notification & { type: 'exportCompleted' }; + +const exportEntityName = { + antenna: i18n.ts.antennas, + blocking: i18n.ts.blockedUsers, + clip: i18n.ts.clips, + customEmoji: i18n.ts.customEmojis, + favorite: i18n.ts.favorites, + following: i18n.ts.following, + muting: i18n.ts.mutedUsers, + note: i18n.ts.notes, + userList: i18n.ts.lists, +} as const satisfies Record<ExportCompletedNotification['exportedEntity'], string>; + const followRequestDone = ref(false); const acceptFollowRequest = () => { @@ -190,6 +218,14 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification) overflow-wrap: break-word; display: flex; contain: content; + + --eventFollow: #36aed2; + --eventRenote: #36d298; + --eventReply: #007aff; + --eventReactionHeart: var(--love); + --eventReaction: #e99a0b; + --eventAchievement: #cb9a11; + --eventOther: #88a6b7; } .head { @@ -298,6 +334,12 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification) pointer-events: none; } +.t_exportCompleted { + padding: 3px; + background: var(--eventOther); + pointer-events: none; +} + .t_roleAssigned { padding: 3px; background: var(--eventOther); 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/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue index 100a025653..ee1f15c189 100644 --- a/packages/frontend/src/components/MkOmit.vue +++ b/packages/frontend/src/components/MkOmit.vue @@ -62,7 +62,7 @@ onUnmounted(() => { left: 0; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), var(--X15)); + background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); > .fadeLabel { display: inline-block; 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 51ec941c97..471ffdd896 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -105,11 +105,11 @@ import * as mfm from 'mfm-js'; import * as Misskey from 'misskey-js'; import insertTextAtCursor from 'insert-text-at-cursor'; import { toASCII } from 'punycode/'; +import { host, url } from '@@/js/config.js'; 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 { erase, unique } from '@/scripts/array.js'; import { extractMentions } from '@/scripts/extract-mentions.js'; import { formatTimeString } from '@/scripts/format-time-string.js'; @@ -245,7 +245,7 @@ const submitText = computed((): string => { }); const textLength = computed((): number => { - return (text.value + imeText.value).trim().length; + return (text.value + imeText.value).length; }); const maxTextLength = computed((): number => { @@ -1128,13 +1128,13 @@ defineExpose({ &:not(:disabled):hover { > .inner { - background: linear-gradient(90deg, var(--X8), var(--X8)); + 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 { > .inner { - background: linear-gradient(90deg, var(--X8), var(--X8)); + background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); } } } @@ -1201,6 +1201,15 @@ defineExpose({ min-height: 75px; max-height: 150px; overflow: auto; + background-size: auto auto; +} + +html[data-color-scheme=dark] .preview { + background-image: repeating-linear-gradient(135deg, transparent, transparent 5px, #0004 5px, #0004 10px); +} + +html[data-color-scheme=light] .preview { + background-image: repeating-linear-gradient(135deg, transparent, transparent 5px, #00000005 5px, #00000005 10px); } .targetNote { 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..7942a84d66 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #prefix>@</template> <template #suffix>@{{ host }}</template> </MkInput> - <MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" :placeholder="i18n.ts.password" type="password" autocomplete="current-password webauthn" :withPasswordToggle="true" required data-cy-signin-password> + <MkInput v-model="password" :placeholder="i18n.ts.password" type="password" autocomplete="current-password webauthn" :withPasswordToggle="true" required data-cy-signin-password> <template #prefix><i class="ti ti-lock"></i></template> <template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template> </MkInput> @@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }"> <div v-if="user && user.securityKeys" class="twofa-group tap-group"> <p>{{ i18n.ts.useSecurityKey }}</p> - <MkButton v-if="!queryingKey" @click="queryKey"> + <MkButton v-if="!queryingKey" @click="query2FaKey"> {{ i18n.ts.retry }} </MkButton> </div> @@ -45,10 +45,6 @@ SPDX-License-Identifier: AGPL-3.0-only <p :class="$style.orMsg">{{ i18n.ts.or }}</p> </div> <div class="twofa-group totp-group _gaps"> - <MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" autocomplete="current-password" :withPasswordToggle="true" required> - <template #label>{{ i18n.ts.password }}</template> - <template #prefix><i class="ti ti-lock"></i></template> - </MkInput> <MkInput v-model="token" type="text" :pattern="isBackupCode ? '^[A-Z0-9]{32}$' :'^[0-9]{6}$'" autocomplete="one-time-code" required :spellcheck="false" :inputmode="isBackupCode ? undefined : 'numeric'"> <template #label>{{ i18n.ts.token }} ({{ i18n.ts['2fa'] }})</template> <template #prefix><i v-if="isBackupCode" class="ti ti-key"></i><i v-else class="ti ti-123"></i></template> @@ -57,6 +53,16 @@ SPDX-License-Identifier: AGPL-3.0-only <MkButton type="submit" :disabled="signing" large primary rounded style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton> </div> </div> + <div v-if="!totpLogin && usePasswordLessLogin" :class="$style.orHr"> + <p :class="$style.orMsg">{{ i18n.ts.or }}</p> + </div> + <div v-if="!totpLogin && usePasswordLessLogin" class="twofa-group tap-group"> + <MkButton v-if="!queryingKey" type="submit" :disabled="signing" style="margin: auto auto;" rounded large primary @click="onPasskeyLogin"> + <i class="ti ti-device-usb" style="font-size: medium;"></i> + {{ signing ? i18n.ts.loggingIn : i18n.ts.signinWithPasskey }} + </MkButton> + <p v-if="queryingKey">{{ i18n.ts.useSecurityKey }}</p> + </div> </div> </form> </template> @@ -66,20 +72,23 @@ 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 { SigninWithPasskeyResponse } from 'misskey-js/entities.js'; +import { query, extractDomain } from '@@/js/url.js'; +import { host as configHost } from '@@/js/config.js'; +import MkDivider from './MkDivider.vue'; 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 * 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'; const signing = ref(false); const user = ref<Misskey.entities.UserDetailed | null>(null); +const usePasswordLessLogin = ref<Misskey.entities.UserDetailed['usePasswordLessLogin']>(true); const username = ref(''); const password = ref(''); const token = ref(''); @@ -88,6 +97,7 @@ const totpLogin = ref(false); const isBackupCode = ref(false); const queryingKey = ref(false); let credentialRequest: CredentialRequestOptions | null = null; +const passkey_context = ref(''); const emit = defineEmits<{ (ev: 'login', v: any): void; @@ -110,8 +120,10 @@ function onUsernameChange(): void { username: username.value, }).then(userResponse => { user.value = userResponse; + usePasswordLessLogin.value = userResponse.usePasswordLessLogin; }, () => { user.value = null; + usePasswordLessLogin.value = true; }); } @@ -121,7 +133,7 @@ function onLogin(res: any): Promise<void> | void { } } -async function queryKey(): Promise<void> { +async function query2FaKey(): Promise<void> { if (credentialRequest == null) return; queryingKey.value = true; await webAuthnRequest(credentialRequest) @@ -150,6 +162,47 @@ async function queryKey(): Promise<void> { }); } +function onPasskeyLogin(): void { + signing.value = true; + if (webAuthnSupported()) { + misskeyApi('signin-with-passkey', {}) + .then((res: SigninWithPasskeyResponse) => { + totpLogin.value = false; + signing.value = false; + queryingKey.value = true; + passkey_context.value = res.context ?? ''; + credentialRequest = parseRequestOptionsFromJSON({ + publicKey: res.option, + }); + }) + .then(() => queryPasskey()) + .catch(loginFailed); + } +} + +async function queryPasskey(): Promise<void> { + if (credentialRequest == null) return; + queryingKey.value = true; + console.log('Waiting passkey auth...'); + await webAuthnRequest(credentialRequest) + .catch((err) => { + console.warn('Passkey Auth fail!: ', err); + queryingKey.value = false; + return Promise.reject(null); + }).then(credential => { + credentialRequest = null; + queryingKey.value = false; + signing.value = true; + return misskeyApi('signin-with-passkey', { + credential: credential.toJSON(), + context: passkey_context.value, + }); + }).then((res: SigninWithPasskeyResponse) => { + emit('login', res.signinResponse); + return onLogin(res.signinResponse); + }); +} + function onSubmit(): void { signing.value = true; if (!totpLogin.value && user.value && user.value.twoFactorEnabled) { @@ -164,7 +217,7 @@ function onSubmit(): void { publicKey: res, }); }) - .then(() => queryKey()) + .then(() => query2FaKey()) .catch(loginFailed); } else { totpLogin.value = true; @@ -212,6 +265,30 @@ function loginFailed(err: any): void { }); break; } + case '36b96a7d-b547-412d-aeed-2d611cdc8cdc': { + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: i18n.ts.unknownWebAuthnKey, + }); + break; + } + case 'b18c89a7-5b5e-4cec-bb5b-0419f332d430': { + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: i18n.ts.passkeyVerificationFailed, + }); + break; + } + case '2d84773e-f7b7-4d0b-8f72-bb69b584c912': { + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: i18n.ts.passkeyVerificationSucceededButPasswordlessLoginDisabled, + }); + break; + } default: { console.error(err); os.alert({ diff --git a/packages/frontend/src/components/MkSigninDialog.vue b/packages/frontend/src/components/MkSigninDialog.vue index 524c62b4d3..d48780e9de 100644 --- a/packages/frontend/src/components/MkSigninDialog.vue +++ b/packages/frontend/src/components/MkSigninDialog.vue @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkModalWindow ref="dialog" :width="400" - :height="430" + :height="450" @close="onClose" @closed="emit('closed')" > 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 9a07826f1a..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; @@ -62,7 +62,7 @@ const collapsed = ref(isLong); left: 0; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), var(--X15)); + background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); > .fadeLabel { display: inline-block; diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index 1a880170be..3746ffd8f3 100644 --- a/packages/frontend/src/components/MkSuperMenu.vue +++ b/packages/frontend/src/components/MkSuperMenu.vue @@ -100,14 +100,14 @@ defineProps<{ &.grid { > .group { + margin-left: 0; + margin-right: 0; + & + .group { padding-top: 0; border-top: none; } - margin-left: 0; - margin-right: 0; - > .title { font-size: 1em; opacity: 0.7; 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 303e49de00..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'; @@ -508,10 +508,6 @@ defineExpose({ .header { --height: 39px; - &.mini { - --height: 32px; - } - display: flex; position: relative; z-index: 1; @@ -524,6 +520,10 @@ defineExpose({ //border-bottom: solid 1px var(--divider); font-size: 90%; font-weight: bold; + + &.mini { + --height: 32px; + } } .headerButton { 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/form/link.vue b/packages/frontend/src/components/form/link.vue index e76ed9a849..d6585bf4a5 100644 --- a/packages/frontend/src/components/form/link.vue +++ b/packages/frontend/src/components/form/link.vue @@ -51,18 +51,18 @@ const props = defineProps<{ width: 100%; box-sizing: border-box; padding: 10px 14px; - background: var(--buttonBg); + background: var(--folderHeaderBg); border-radius: 6px; font-size: 0.9em; &:hover { text-decoration: none; - background: var(--buttonHoverBg); + background: var(--folderHeaderHoverBg); } &.active { color: var(--accent); - background: var(--buttonHoverBg); + background: var(--folderHeaderHoverBg); } } 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..9a1ac3aca2 100644 --- a/packages/frontend/src/components/global/MkAcct.vue +++ b/packages/frontend/src/components/global/MkAcct.vue @@ -4,11 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<MkCondensedLine v-if="defaultStore.state.enableCondensedLineForAcct" :minScale="2 / 3"> - <span>@{{ user.username }}</span> - <span v-if="user.host || detail || defaultStore.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span> -</MkCondensedLine> -<span v-else> +<span> <span>@{{ user.username }}</span> <span v-if="user.host || detail || defaultStore.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span> </span> @@ -17,7 +13,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 e8e1bc696b..35c07bc80c 100644 --- a/packages/frontend/src/components/global/MkAvatar.vue +++ b/packages/frontend/src/components/global/MkAvatar.vue @@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template v-if="showDecoration"> <img v-for="decoration in decorations ?? user.avatarDecorations" - :class="[$style.decoration]" + :class="[$style.decoration, { [$style.decorationBlink]: decoration.blink }]" :src="getDecorationUrl(decoration)" :style="{ rotate: getDecorationAngle(decoration), @@ -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'; @@ -60,7 +60,7 @@ const props = withDefaults(defineProps<{ link?: boolean; preview?: boolean; indicator?: boolean; - decorations?: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>[]; + decorations?: (Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'> & { blink?: boolean; })[]; forceShowDecoration?: boolean; }>(), { target: null, @@ -330,4 +330,17 @@ watch(() => props.user.avatarBlurhash, () => { width: 200%; pointer-events: none; } + +.decorationBlink { + animation: blink 1s infinite; +} + +@keyframes blink { + 0%, 100% { + filter: brightness(2); + } + 50% { + filter: brightness(1); + } +} </style> diff --git a/packages/frontend/src/components/global/MkCondensedLine.vue b/packages/frontend/src/components/global/MkCondensedLine.vue index 7c4957d77f..473d444c16 100644 --- a/packages/frontend/src/components/global/MkCondensedLine.vue +++ b/packages/frontend/src/components/global/MkCondensedLine.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <span :class="$style.container"> - <span ref="content" :class="$style.content"> + <span ref="content" :class="$style.content" :style="{ maxWidth: `${100 / minScale}%` }"> <slot/> </span> </span> 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 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 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..b481fd590c 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'; @@ -371,6 +371,7 @@ const patrons = [ 'å¡©ã‚ャベツ', 'ã¯ã¨ã½ã·ã•ã‚“', '100ã®äºº (エスパー・イーシア)', + 'ケモナーã®ã‚±ã‚·ãƒ³', ]; const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure')); 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/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue index 9a9fa472a5..0b9847fed3 100644 --- a/packages/frontend/src/pages/admin/abuses.vue +++ b/packages/frontend/src/pages/admin/abuses.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSpacer :contentMax="900"> <div :class="$style.root" class="_gaps"> <div :class="$style.subMenus" class="_gaps"> - <MkButton link to="/admin/abuse-report-notification-recipient" primary>{{ "通知è¨å®š" }}</MkButton> + <MkButton link to="/admin/abuse-report-notification-recipient" primary>{{ i18n.ts.notificationSetting }}</MkButton> </div> <div :class="$style.inputs" class="_gaps"> diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue index bd442ccc69..6c8901b10b 100644 --- a/packages/frontend/src/pages/admin/ads.vue +++ b/packages/frontend/src/pages/admin/ads.vue @@ -65,18 +65,18 @@ SPDX-License-Identifier: AGPL-3.0-only <MkTextarea v-model="ad.memo"> <template #label>{{ i18n.ts.memo }}</template> </MkTextarea> - <div class="buttons"> - <MkButton class="button" inline primary style="margin-right: 12px;" @click="save(ad)"> + <div class="_buttons"> + <MkButton inline primary style="margin-right: 12px;" @click="save(ad)"> <i class="ti ti-device-floppy" ></i> {{ i18n.ts.save }} </MkButton> - <MkButton class="button" inline danger @click="remove(ad)"> + <MkButton inline danger @click="remove(ad)"> <i class="ti ti-trash"></i> {{ i18n.ts.remove }} </MkButton> </div> </div> - <MkButton class="button" @click="more()"> + <MkButton @click="more()"> <i class="ti ti-reload"></i>{{ i18n.ts.more }} </MkButton> </div> diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue index b9e09c8d03..fd37311b21 100644 --- a/packages/frontend/src/pages/admin/announcements.vue +++ b/packages/frontend/src/pages/admin/announcements.vue @@ -29,8 +29,16 @@ SPDX-License-Identifier: AGPL-3.0-only <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i> </template> <template #caption>{{ announcement.text }}</template> + <template #footer> + <div class="_buttons"> + <MkButton rounded primary @click="save(announcement)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> + <MkButton v-if="announcement.id != null && announcement.isActive" rounded @click="archive(announcement)"><i class="ti ti-check"></i> {{ i18n.ts._announcement.end }} ({{ i18n.ts.archive }})</MkButton> + <MkButton v-if="announcement.id != null && !announcement.isActive" rounded @click="unarchive(announcement)"><i class="ti ti-restore"></i> {{ i18n.ts.unarchive }}</MkButton> + <MkButton v-if="announcement.id != null" rounded danger @click="del(announcement)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + </div> + </template> - <div class="_gaps_m"> + <div class="_gaps"> <MkInput v-model="announcement.title"> <template #label>{{ i18n.ts.title }}</template> </MkInput> @@ -64,16 +72,10 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts._announcement.needConfirmationToRead }} </MkSwitch> <p v-if="announcement.reads">{{ i18n.tsx.nUsersRead({ n: announcement.reads }) }}</p> - <div class="buttons _buttons"> - <MkButton class="button" inline primary @click="save(announcement)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> - <MkButton v-if="announcement.id != null && announcement.isActive" class="button" inline @click="archive(announcement)"><i class="ti ti-check"></i> {{ i18n.ts._announcement.end }} ({{ i18n.ts.archive }})</MkButton> - <MkButton v-if="announcement.id != null && !announcement.isActive" class="button" inline @click="unarchive(announcement)"><i class="ti ti-restore"></i> {{ i18n.ts.unarchive }}</MkButton> - <MkButton v-if="announcement.id != null" class="button" inline danger @click="del(announcement)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> - </div> </div> </MkFolder> <MkLoading v-if="loadingMore"/> - <MkButton class="button" @click="more()"> + <MkButton @click="more()"> <i class="ti ti-reload"></i>{{ i18n.ts.more }} </MkButton> </template> @@ -170,7 +172,7 @@ function more() { loadingMore.value = true; misskeyApi('admin/announcements/list', { status: announcementsStatus.value, - untilId: announcements.value.reduce((acc, announcement) => announcement.id != null ? announcement : acc).id + untilId: announcements.value.reduce((acc, announcement) => announcement.id != null ? announcement : acc).id, }).then(announcementResponse => { announcements.value = announcements.value.concat(announcementResponse); loadingMore.value = false; 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 @@ -200,16 +200,6 @@ const menuDef = computed(() => [{ 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, to: '/admin/external-services', @@ -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,22 +144,78 @@ 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', { + preservedUsernames: preservedUsernames.value.split('\n'), + }).then(() => { + fetchInstance(true); + }); +} + +function save_sensitiveWords() { 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'), + }).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'), - preservedUsernames: preservedUsernames.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); }); @@ -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.queue.vue b/packages/frontend/src/pages/admin/overview.queue.vue index c7478f252a..fb190f5325 100644 --- a/packages/frontend/src/pages/admin/overview.queue.vue +++ b/packages/frontend/src/pages/admin/overview.queue.vue @@ -36,7 +36,9 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { markRaw, onMounted, onUnmounted, ref, shallowRef } from 'vue'; +import * as Misskey from 'misskey-js'; import XChart from './overview.queue.chart.vue'; +import type { ApQueueDomain } from '@/pages/admin/queue.vue'; import number from '@/filters/number.js'; import { useStream } from '@/stream.js'; @@ -52,10 +54,10 @@ const chartDelayed = shallowRef<InstanceType<typeof XChart>>(); const chartWaiting = shallowRef<InstanceType<typeof XChart>>(); const props = defineProps<{ - domain: string; + domain: ApQueueDomain; }>(); -const onStats = (stats) => { +function onStats(stats: Misskey.entities.QueueStats) { activeSincePrevTick.value = stats[props.domain].activeSincePrevTick; active.value = stats[props.domain].active; delayed.value = stats[props.domain].delayed; @@ -65,13 +67,13 @@ const onStats = (stats) => { chartActive.value.pushData(stats[props.domain].active); chartDelayed.value.pushData(stats[props.domain].delayed); chartWaiting.value.pushData(stats[props.domain].waiting); -}; +} -const onStatsLog = (statsLog) => { - const dataProcess = []; - const dataActive = []; - const dataDelayed = []; - const dataWaiting = []; +function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) { + const dataProcess: Misskey.entities.QueueStats[ApQueueDomain]['activeSincePrevTick'][] = []; + const dataActive: Misskey.entities.QueueStats[ApQueueDomain]['active'][] = []; + const dataDelayed: Misskey.entities.QueueStats[ApQueueDomain]['delayed'][] = []; + const dataWaiting: Misskey.entities.QueueStats[ApQueueDomain]['waiting'][] = []; for (const stats of [...statsLog].reverse()) { dataProcess.push(stats[props.domain].activeSincePrevTick); @@ -84,7 +86,7 @@ const onStatsLog = (statsLog) => { chartActive.value.setData(dataActive); chartDelayed.value.setData(dataDelayed); chartWaiting.value.setData(dataWaiting); -}; +} onMounted(() => { connection.on('stats', onStats); diff --git a/packages/frontend/src/pages/admin/overview.users.vue b/packages/frontend/src/pages/admin/overview.users.vue index 408be88d47..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'; @@ -47,14 +47,14 @@ useInterval(fetch, 1000 * 60, { .root { &:global { > .users { - .chart-move { - transition: transform 1s ease; - } - display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-gap: 12px; + .chart-move { + transition: transform 1s ease; + } + > .user:hover { text-decoration: none; } diff --git a/packages/frontend/src/pages/admin/performance.vue b/packages/frontend/src/pages/admin/performance.vue new file mode 100644 index 0000000000..7e0a932f82 --- /dev/null +++ b/packages/frontend/src/pages/admin/performance.vue @@ -0,0 +1,193 @@ +<!-- +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"> + <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> + + <template v-if="fttForm.state.enableFanoutTimeline"> + <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> + </template> + </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 { 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.chart.vue b/packages/frontend/src/pages/admin/queue.chart.vue index 8d3fe35320..960a263a86 100644 --- a/packages/frontend/src/pages/admin/queue.chart.vue +++ b/packages/frontend/src/pages/admin/queue.chart.vue @@ -49,7 +49,9 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { markRaw, onMounted, onUnmounted, ref, shallowRef } from 'vue'; +import * as Misskey from 'misskey-js'; import XChart from './queue.chart.chart.vue'; +import type { ApQueueDomain } from '@/pages/admin/queue.vue'; import number from '@/filters/number.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { useStream } from '@/stream.js'; @@ -62,17 +64,17 @@ const activeSincePrevTick = ref(0); const active = ref(0); const delayed = ref(0); const waiting = ref(0); -const jobs = ref<(string | number)[][]>([]); +const jobs = ref<Misskey.Endpoints[`admin/queue/${ApQueueDomain}-delayed`]['res']>([]); const chartProcess = shallowRef<InstanceType<typeof XChart>>(); const chartActive = shallowRef<InstanceType<typeof XChart>>(); const chartDelayed = shallowRef<InstanceType<typeof XChart>>(); const chartWaiting = shallowRef<InstanceType<typeof XChart>>(); const props = defineProps<{ - domain: string; + domain: ApQueueDomain; }>(); -const onStats = (stats) => { +function onStats(stats: Misskey.entities.QueueStats) { activeSincePrevTick.value = stats[props.domain].activeSincePrevTick; active.value = stats[props.domain].active; delayed.value = stats[props.domain].delayed; @@ -82,13 +84,13 @@ const onStats = (stats) => { chartActive.value.pushData(stats[props.domain].active); chartDelayed.value.pushData(stats[props.domain].delayed); chartWaiting.value.pushData(stats[props.domain].waiting); -}; +} -const onStatsLog = (statsLog) => { - const dataProcess = []; - const dataActive = []; - const dataDelayed = []; - const dataWaiting = []; +function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) { + const dataProcess: Misskey.entities.QueueStats[ApQueueDomain]['activeSincePrevTick'][] = []; + const dataActive: Misskey.entities.QueueStats[ApQueueDomain]['active'][] = []; + const dataDelayed: Misskey.entities.QueueStats[ApQueueDomain]['delayed'][] = []; + const dataWaiting: Misskey.entities.QueueStats[ApQueueDomain]['waiting'][] = []; for (const stats of [...statsLog].reverse()) { dataProcess.push(stats[props.domain].activeSincePrevTick); @@ -101,14 +103,12 @@ const onStatsLog = (statsLog) => { chartActive.value.setData(dataActive); chartDelayed.value.setData(dataDelayed); chartWaiting.value.setData(dataWaiting); -}; +} onMounted(() => { - if (props.domain === 'inbox' || props.domain === 'deliver') { - misskeyApi(`admin/queue/${props.domain}-delayed`).then(result => { - jobs.value = result; - }); - } + misskeyApi(`admin/queue/${props.domain}-delayed`).then(result => { + jobs.value = result; + }); connection.on('stats', onStats); connection.on('statsLog', onStatsLog); diff --git a/packages/frontend/src/pages/admin/queue.vue b/packages/frontend/src/pages/admin/queue.vue index 8d77d927d7..512039242e 100644 --- a/packages/frontend/src/pages/admin/queue.vue +++ b/packages/frontend/src/pages/admin/queue.vue @@ -16,16 +16,18 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { ref, computed } from 'vue'; +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'; -const tab = ref('deliver'); +export type ApQueueDomain = 'deliver' | 'inbox'; + +const tab: Ref<ApQueueDomain> = ref('deliver'); function clear() { os.confirm({ 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>(''); - -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') || ''; -} +const meta = await misskeyApi('admin/meta'); -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..5a7cdee576 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -8,173 +8,191 @@ 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> - <template #caption>{{ i18n.ts.pinnedUsersDescription }}</template> - </MkTextarea> + <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> - <FormSection> - <template #label>{{ i18n.ts.files }}</template> + <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> - <div class="_gaps_m"> - <MkSwitch v-model="cacheRemoteFiles"> - <template #label>{{ i18n.ts.cacheRemoteFiles }}</template> - <template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}{{ i18n.ts.youCanCleanRemoteFilesCache }}</template> - </MkSwitch> + <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> - <template v-if="cacheRemoteFiles"> - <MkSwitch v-model="cacheRemoteSensitiveFiles"> - <template #label>{{ i18n.ts.cacheRemoteSensitiveFiles }}</template> - <template #caption>{{ i18n.ts.cacheRemoteSensitiveFilesDescription }}</template> - </MkSwitch> - </template> - </div> - </FormSection> + <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> - <FormSection> - <template #label>ServiceWorker</template> + <MkInfo v-if="!instance.providesTarball && !infoForm.state.repositoryUrl" warn> + {{ i18n.ts.repositoryUrlOrTarballRequired }} + </MkInfo> - <div class="_gaps_m"> - <MkSwitch v-model="enableServiceWorker"> - <template #label>{{ i18n.ts.enableServiceworker }}</template> - <template #caption>{{ i18n.ts.serviceworkerInfo }}</template> - </MkSwitch> + <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> - <template v-if="enableServiceWorker"> - <MkInput v-model="swPublicKey"> - <template #prefix><i class="ti ti-key"></i></template> - <template #label>Public key</template> - </MkInput> + <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> - <MkInput v-model="swPrivateKey"> - <template #prefix><i class="ti ti-key"></i></template> - <template #label>Private key</template> - </MkInput> - </template> - </div> - </FormSection> + <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>Misskey® Fan-out Timeline Technologyâ„¢ (FTT)</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="enableFanoutTimeline"> - <template #label>{{ i18n.ts.enable }}</template> - <template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDescription }}</template> - </MkSwitch> + <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> - <MkSwitch v-model="enableFanoutTimelineDbFallback"> - <template #label>{{ i18n.ts._serverSettings.fanoutTimelineDbFallback }}</template> - <template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDbFallbackDescription }}</template> + <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> - <MkInput v-model="perLocalUserUserTimelineCacheMax" type="number"> - <template #label>perLocalUserUserTimelineCacheMax</template> - </MkInput> + <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> - <MkInput v-model="perRemoteUserUserTimelineCacheMax" type="number"> - <template #label>perRemoteUserUserTimelineCacheMax</template> - </MkInput> + <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> - <MkInput v-model="perUserHomeTimelineCacheMax" type="number"> - <template #label>perUserHomeTimelineCacheMax</template> + <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="perUserListTimelineCacheMax" type="number"> - <template #label>perUserListTimelineCacheMax</template> + <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> - </div> - </FormSection> + </template> + </div> + </MkFolder> - <FormSection> - <template #label>{{ i18n.ts._ad.adsSettings }}</template> + <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_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> + <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>{{ i18n.ts._urlPreviewSetting.title }}</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="urlPreviewEnabled"> - <template #label>{{ i18n.ts._urlPreviewSetting.enable }}</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> - <MkSwitch v-model="urlPreviewRequireContentLength"> - <template #label>{{ i18n.ts._urlPreviewSetting.requireContentLength }}</template> + <template v-if="urlPreviewForm.state.urlPreviewEnabled"> + <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="urlPreviewMaximumContentLength" type="number"> - <template #label>{{ i18n.ts._urlPreviewSetting.maximumContentLength }}</template> + <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> - <MkInput v-model="urlPreviewTimeout" type="number"> - <template #label>{{ i18n.ts._urlPreviewSetting.timeout }}</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> - <MkInput v-model="urlPreviewUserAgent" type="text"> - <template #label>{{ i18n.ts._urlPreviewSetting.userAgent }}</template> + <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> <div> - <MkInput v-model="urlPreviewSummaryProxyUrl" type="text"> - <template #label>{{ i18n.ts._urlPreviewSetting.summaryProxy }}</template> + <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> @@ -188,18 +206,51 @@ SPDX-License-Identifier: AGPL-3.0-only </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> + </template> + </div> + </MkFolder> + + <MkFolder> + <template #icon><i class="ti ti-planet"></i></template> + <template #label>{{ i18n.ts.federation }}</template> + <template v-if="federationForm.savedState.federation === 'all'" #suffix>{{ i18n.ts.all }}</template> + <template v-else-if="federationForm.savedState.federation === 'specified'" #suffix>{{ i18n.ts.specifyHost }}</template> + <template v-else-if="federationForm.savedState.federation === 'none'" #suffix>{{ i18n.ts.none }}</template> + <template v-if="federationForm.modified.value" #footer> + <MkFormFooter :form="federationForm"/> + </template> + + <div class="_gaps"> + <MkRadios v-model="federationForm.state.federation"> + <template #label>{{ i18n.ts.behavior }}<span v-if="federationForm.modifiedStates.federation" class="_modified">{{ i18n.ts.modified }}</span></template> + <option value="all">{{ i18n.ts.all }}</option> + <option value="specified">{{ i18n.ts.specifyHost }}</option> + <option value="none">{{ i18n.ts.none }}</option> + </MkRadios> + + <MkTextarea v-if="federationForm.state.federation === 'specified'" v-model="federationForm.state.federationHosts"> + <template #label>{{ i18n.ts.federationAllowedHosts }}<span v-if="federationForm.modifiedStates.federationHosts" class="_modified">{{ i18n.ts.modified }}</span></template> + <template #caption>{{ i18n.ts.federationAllowedHostsDescription }}</template> + </MkTextarea> + </div> + </MkFolder> + + <MkFolder> + <template #icon><i class="ti ti-ghost"></i></template> + <template #label>{{ i18n.ts.proxyAccount }}</template> + + <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> + + <MkButton primary @click="chooseProxyAccount">{{ i18n.ts.selectAccount }}</MkButton> + </div> + </MkFolder> </div> - </template> + </MkSpacer> </MkStickyContainer> </div> </template> @@ -211,9 +262,7 @@ 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'; @@ -221,96 +270,123 @@ import { i18n } from '@/i18n.js'; 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'; +import MkRadios from '@/components/MkRadios.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); + +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: 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); +}); -async function save() { +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', { - 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, + urlPreviewEnabled: state.urlPreviewEnabled, + urlPreviewTimeout: state.urlPreviewTimeout, + urlPreviewMaximumContentLength: state.urlPreviewMaximumContentLength, + urlPreviewRequireContentLength: state.urlPreviewRequireContentLength, + urlPreviewUserAgent: state.urlPreviewUserAgent, + urlPreviewSummaryProxyUrl: state.urlPreviewSummaryProxyUrl, }); + fetchInstance(true); +}); +const federationForm = useForm({ + federation: meta.federation, + federationHosts: meta.federationHosts.join('\n'), +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + federation: state.federation, + federationHosts: state.federationHosts.split('\n'), + }); 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 +398,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..4e767fba16 100644 --- a/packages/frontend/src/pages/admin/system-webhook.item.vue +++ b/packages/frontend/src/pages/admin/system-webhook.item.vue @@ -4,8 +4,9 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div :class="$style.main"> - <span :class="$style.icon"> +<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 @@ -14,23 +15,38 @@ SPDX-License-Identifier: AGPL-3.0-only :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"> + </template> + <template #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> + <span v-else>-</span> + </template> + <template #footer> + <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> + </template> + + <div class="_gaps"> + <MkKeyValue> + <template #key>latestStatus</template> + <template #value>{{ entity.latestStatus ?? '-' }}</template> + </MkKeyValue> + </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 +70,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/avatar-decorations.vue b/packages/frontend/src/pages/avatar-decorations.vue index ad9ec3c4ee..b377314856 100644 --- a/packages/frontend/src/pages/avatar-decorations.vue +++ b/packages/frontend/src/pages/avatar-decorations.vue @@ -12,19 +12,31 @@ SPDX-License-Identifier: AGPL-3.0-only <template #label>{{ avatarDecoration.name }}</template> <template #caption>{{ avatarDecoration.description }}</template> - <div class="_gaps_m"> - <MkInput v-model="avatarDecoration.name"> - <template #label>{{ i18n.ts.name }}</template> - </MkInput> - <MkTextarea v-model="avatarDecoration.description"> - <template #label>{{ i18n.ts.description }}</template> - </MkTextarea> - <MkInput v-model="avatarDecoration.url"> - <template #label>{{ i18n.ts.imageUrl }}</template> - </MkInput> - <div class="buttons _buttons"> - <MkButton class="button" inline primary @click="save(avatarDecoration)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> - <MkButton v-if="avatarDecoration.id != null" class="button" inline danger @click="del(avatarDecoration)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + <div :class="$style.editorRoot"> + <div :class="$style.editorWrapper"> + <div :class="$style.preview"> + <div :class="[$style.previewItem, $style.light]"> + <MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[avatarDecoration]" forceShowDecoration/> + </div> + <div :class="[$style.previewItem, $style.dark]"> + <MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[avatarDecoration]" forceShowDecoration/> + </div> + </div> + <div class="_gaps_m"> + <MkInput v-model="avatarDecoration.name"> + <template #label>{{ i18n.ts.name }}</template> + </MkInput> + <MkTextarea v-model="avatarDecoration.description"> + <template #label>{{ i18n.ts.description }}</template> + </MkTextarea> + <MkInput v-model="avatarDecoration.url"> + <template #label>{{ i18n.ts.imageUrl }}</template> + </MkInput> + <div class="_buttons"> + <MkButton inline primary @click="save(avatarDecoration)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> + <MkButton v-if="avatarDecoration.id != null" inline danger @click="del(avatarDecoration)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + </div> + </div> </div> </div> </MkFolder> @@ -39,6 +51,7 @@ import * as Misskey from 'misskey-js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkTextarea from '@/components/MkTextarea.vue'; +import { signinRequired } from '@/account.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; @@ -47,6 +60,8 @@ import MkFolder from '@/components/MkFolder.vue'; const avatarDecorations = ref<Misskey.entities.AdminAvatarDecorationsListResponse>([]); +const $i = signinRequired(); + function add() { avatarDecorations.value.unshift({ _id: Math.random().toString(36), @@ -99,3 +114,55 @@ definePageMetadata(() => ({ icon: 'ti ti-sparkles', })); </script> + +<style lang="scss" module> +.editorRoot { + container: editor / inline-size; +} + +.editorWrapper { + display: grid; + grid-template-columns: 1fr; + grid-template-rows: auto auto; + gap: var(--margin); +} + +.preview { + display: grid; + place-items: center; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr; + gap: var(--margin); +} + +.previewItem { + width: 100%; + height: 100%; + min-height: 160px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius); + + &.light { + background: #eee; + } + + &.dark { + background: #222; + } +} + +@container editor (min-width: 600px) { + .editorWrapper { + grid-template-columns: 200px 1fr; + grid-template-rows: 1fr; + gap: calc(var(--margin) * 2); + } + + .preview { + grid-template-columns: 1fr; + grid-template-rows: 1fr 1fr; + } +} +</style> diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 3c3ff08aee..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'; @@ -310,7 +310,7 @@ definePageMetadata(() => ({ left: 0; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), var(--X15)); + background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); } .bannerStatus { 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 cb1ce9b918..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 }); @@ -433,13 +446,12 @@ definePageMetadata(() => ({ .pageBannerTitleUser { --height: 32px; flex-shrink: 0; + line-height: var(--height); .avatar { height: var(--height); width: var(--height); } - - line-height: var(--height); } .pageBannerTitleSubActions { 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..155d8b82d7 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" :class="{ [$style.uiInspectorUnShown]: !showns.has(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; @@ -152,6 +180,20 @@ const headerActions = computed(() => []); const headerTabs = computed(() => []); +const showns = computed(() => { + const result = new Set<string>(); + (function addChildrenToResult(c: AsUiComponent) { + result.add(c.id); + if (c.children) { + const childComponents = components.value.filter(v => c.children.includes(v.value.id)); + for (const child of childComponents) { + addChildrenToResult(child.value); + } + } + })(root.value); + return result; +}); + definePageMetadata(() => ({ title: i18n.ts.scratchpad, icon: 'ti ti-terminal-2', @@ -192,4 +234,39 @@ definePageMetadata(() => ({ } } } + +.uiInspector { + display: grid; + gap: 8px; + padding: 16px; +} + +.uiInspectorUnShown { + color: var(--fgTransparent); +} + +.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/avatar-decoration.dialog.vue b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue index ce1d4e48d8..1938b8d48d 100644 --- a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue +++ b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue @@ -96,6 +96,7 @@ const decorationsForPreview = computed(() => { flipH: flipH.value, offsetX: offsetX.value, offsetY: offsetY.value, + blink: true, }; const decorations = [...$i.avatarDecorations]; if (props.usingIndex != null) { diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue index dc3e3ee503..999a73df4c 100644 --- a/packages/frontend/src/pages/settings/emoji-picker.vue +++ b/packages/frontend/src/pages/settings/emoji-picker.vue @@ -113,10 +113,13 @@ SPDX-License-Identifier: AGPL-3.0-only <option :value="4">{{ i18n.ts.large }}+</option> </MkRadios> - <MkSwitch v-model="emojiPickerUseDrawerForMobile"> - {{ i18n.ts.useDrawerReactionPickerForMobile }} + <MkSelect v-model="emojiPickerStyle"> + <template #label>{{ i18n.ts.style }}</template> <template #caption>{{ i18n.ts.needReloadToApply }}</template> - </MkSwitch> + <option value="auto">{{ i18n.ts.auto }}</option> + <option value="popup">{{ i18n.ts.popup }}</option> + <option value="drawer">{{ i18n.ts.drawer }}</option> + </MkSelect> </div> </FormSection> </div> @@ -128,7 +131,7 @@ import Sortable from 'vuedraggable'; import MkRadios from '@/components/MkRadios.vue'; import MkButton from '@/components/MkButton.vue'; import FormSection from '@/components/form/section.vue'; -import MkSwitch from '@/components/MkSwitch.vue'; +import MkSelect from '@/components/MkSelect.vue'; import * as os from '@/os.js'; import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; @@ -146,7 +149,7 @@ const pinnedEmojis: Ref<string[]> = ref(deepClone(defaultStore.state.pinnedEmoji const emojiPickerScale = computed(defaultStore.makeGetterSetter('emojiPickerScale')); const emojiPickerWidth = computed(defaultStore.makeGetterSetter('emojiPickerWidth')); const emojiPickerHeight = computed(defaultStore.makeGetterSetter('emojiPickerHeight')); -const emojiPickerUseDrawerForMobile = computed(defaultStore.makeGetterSetter('emojiPickerUseDrawerForMobile')); +const emojiPickerStyle = computed(defaultStore.makeGetterSetter('emojiPickerStyle')); const removeReaction = (reaction: string, ev: MouseEvent) => remove(pinnedEmojisForReaction, reaction, ev); const chooseReaction = (ev: MouseEvent) => pickEmoji(pinnedEmojisForReaction, ev); diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index 94ef3b8485..1bfdfd0e76 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -17,13 +17,6 @@ SPDX-License-Identifier: AGPL-3.0-only </template> </MkSelect> - <MkRadios v-model="hemisphere"> - <template #label>{{ i18n.ts.hemisphere }}</template> - <option value="N">{{ i18n.ts._hemisphere.N }}</option> - <option value="S">{{ i18n.ts._hemisphere.S }}</option> - <template #caption>{{ i18n.ts._hemisphere.caption }}</template> - </MkRadios> - <MkRadios v-model="overridedDeviceKind"> <template #label>{{ i18n.ts.overridedDeviceKind }}</template> <option :value="null">{{ i18n.ts.auto }}</option> @@ -132,11 +125,18 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSwitch v-model="squareAvatars">{{ i18n.ts.squareAvatars }}</MkSwitch> <MkSwitch v-model="showAvatarDecorations">{{ i18n.ts.showAvatarDecorations }}</MkSwitch> <MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch> - <MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch> <MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch> <MkSwitch v-model="enableSeasonalScreenEffect">{{ i18n.ts.seasonalScreenEffect }}</MkSwitch> <MkSwitch v-model="useNativeUIForVideoAudioPlayer">{{ i18n.ts.useNativeUIForVideoAudioPlayer }}</MkSwitch> </div> + + <MkSelect v-model="menuStyle"> + <template #label>{{ i18n.ts.menuStyle }}</template> + <option value="auto">{{ i18n.ts.auto }}</option> + <option value="popup">{{ i18n.ts.popup }}</option> + <option value="drawer">{{ i18n.ts.drawer }}</option> + </MkSelect> + <div> <MkRadios v-model="emojiStyle"> <template #label>{{ i18n.ts.emojiStyle }}</template> @@ -225,6 +225,12 @@ SPDX-License-Identifier: AGPL-3.0-only <template #label>{{ i18n.ts.other }}</template> <div class="_gaps"> + <MkRadios v-model="hemisphere"> + <template #label>{{ i18n.ts.hemisphere }}</template> + <option value="N">{{ i18n.ts._hemisphere.N }}</option> + <option value="S">{{ i18n.ts._hemisphere.S }}</option> + <template #caption>{{ i18n.ts._hemisphere.caption }}</template> + </MkRadios> <MkFolder> <template #label>{{ i18n.ts.additionalEmojiDictionary }}</template> <div class="_buttons"> @@ -244,6 +250,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, ref, watch } from 'vue'; import * as Misskey from 'misskey-js'; +import { langs } from '@@/js/config.js'; import MkSwitch from '@/components/MkSwitch.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkRadios from '@/components/MkRadios.vue'; @@ -254,11 +261,10 @@ 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 { 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 +276,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')); @@ -297,7 +293,7 @@ const advancedMfm = computed(defaultStore.makeGetterSetter('advancedMfm')); const showReactionsCount = computed(defaultStore.makeGetterSetter('showReactionsCount')); const enableQuickAddMfmFunction = computed(defaultStore.makeGetterSetter('enableQuickAddMfmFunction')); const emojiStyle = computed(defaultStore.makeGetterSetter('emojiStyle')); -const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer')); +const menuStyle = computed(defaultStore.makeGetterSetter('menuStyle')); const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages')); const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds')); const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages')); @@ -369,7 +365,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..53b3bd4936 100644 --- a/packages/frontend/src/pages/settings/notifications.vue +++ b/packages/frontend/src/pages/settings/notifications.vue @@ -69,11 +69,11 @@ 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(); -const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'achievementEarned']; +const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'achievementEarned', 'test', 'exportCompleted'] as const satisfies (typeof notificationTypes[number])[]; const allowButton = shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>(); const pushRegistrationInServer = computed(() => allowButton.value?.pushRegistrationInServer); diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue index a1cb2ea1c4..410a3f53c7 100644 --- a/packages/frontend/src/pages/settings/other.vue +++ b/packages/frontend/src/pages/settings/other.vue @@ -51,8 +51,8 @@ SPDX-License-Identifier: AGPL-3.0-only <template #label>{{ i18n.ts.experimentalFeatures }}</template> <div class="_gaps_m"> - <MkSwitch v-model="enableCondensedLineForAcct"> - <template #label>Enable condensed line for acct</template> + <MkSwitch v-model="enableCondensedLine"> + <template #label>Enable condensed line</template> </MkSwitch> </div> </MkFolder> @@ -98,13 +98,13 @@ 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(); const reportError = computed(defaultStore.makeGetterSetter('reportError')); -const enableCondensedLineForAcct = computed(defaultStore.makeGetterSetter('enableCondensedLineForAcct')); +const enableCondensedLine = computed(defaultStore.makeGetterSetter('enableCondensedLine')); const devMode = computed(defaultStore.makeGetterSetter('devMode')); const defaultWithReplies = computed(defaultStore.makeGetterSetter('defaultWithReplies')); @@ -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', @@ -152,12 +142,6 @@ async function updateRepliesAll(withReplies: boolean) { misskeyApi('following/update-all', { withReplies }); } -watch([ - enableCondensedLineForAcct, -], async () => { - await reloadAsk(); -}); - const headerActions = computed(() => []); const headerTabs = computed(() => []); diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue index dace2cd847..80d04ec686 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> @@ -39,6 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, onUnmounted, ref } from 'vue'; import { v4 as uuid } from 'uuid'; +import { version, host } from '@@/js/config.js'; import FormSection from '@/components/form/section.vue'; import MkButton from '@/components/MkButton.vue'; import MkInfo from '@/components/MkInfo.vue'; @@ -49,10 +50,8 @@ 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 { definePageMetadata } from '@/scripts/page-metadata.js'; import { miLocalStorage } from '@/local-storage.js'; -const { t, ts } = i18n; const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [ 'collapseRenotes', @@ -76,7 +75,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [ 'dataSaver', 'disableShowingAnimatedImages', 'emojiStyle', - 'disableDrawer', + 'menuStyle', 'useBlurEffectForModal', 'useBlurEffect', 'showFixedPostForm', @@ -88,7 +87,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [ 'emojiPickerScale', 'emojiPickerWidth', 'emojiPickerHeight', - 'emojiPickerUseDrawerForMobile', + 'emojiPickerStyle', 'defaultSideView', 'menuDisplay', 'reportError', @@ -104,7 +103,6 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [ 'mediaListWithOneImageAppearance', 'notificationPosition', 'notificationStackAxis', - 'enableCondensedLineForAcct', 'keepScreenOn', 'defaultWithReplies', 'disableStreamingTimeline', @@ -201,15 +199,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 +236,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 +248,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 +274,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 +320,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 +332,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 +348,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 +368,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 +384,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 +397,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 +437,7 @@ onUnmounted(() => { }); definePageMetadata(() => ({ - title: ts.preferencesBackups, + title: i18n.ts.preferencesBackups, icon: 'ti ti-device-floppy', })); </script> diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index a328933686..9e6cd04365 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -88,14 +88,13 @@ SPDX-License-Identifier: AGPL-3.0-only <template #caption>{{ i18n.ts._profile.metadataDescription }}</template> </FormSlot> - <MkFolder> - <template #label>{{ i18n.ts.advancedSettings }}</template> - - <div class="_gaps_m"> - <MkSwitch v-model="profile.isCat">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></MkSwitch> - <MkSwitch v-model="profile.isBot">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></MkSwitch> - </div> - </MkFolder> + <MkInput v-model="profile.followedMessage" :max="200" manualSave :mfmPreview="false"> + <template #label>{{ i18n.ts._profile.followedMessage }}<span class="_beta">{{ i18n.ts.beta }}</span></template> + <template #caption> + <div>{{ i18n.ts._profile.followedMessageDescription }}</div> + <div>{{ i18n.ts._profile.followedMessageDescriptionForLockedAccount }}</div> + </template> + </MkInput> <MkSelect v-model="reactionAcceptance"> <template #label>{{ i18n.ts.reactionAcceptance }}</template> @@ -105,6 +104,15 @@ SPDX-License-Identifier: AGPL-3.0-only <option value="nonSensitiveOnlyForLocalLikeOnlyForRemote">{{ i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote }}</option> <option value="likeOnly">{{ i18n.ts.likeOnly }}</option> </MkSelect> + + <MkFolder> + <template #label>{{ i18n.ts.advancedSettings }}</template> + + <div class="_gaps_m"> + <MkSwitch v-model="profile.isCat">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></MkSwitch> + <MkSwitch v-model="profile.isBot">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></MkSwitch> + </div> + </MkFolder> </div> </template> @@ -138,6 +146,7 @@ const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAccep const profile = reactive({ name: $i.name, description: $i.description, + followedMessage: $i.followedMessage, location: $i.location, birthday: $i.birthday, lang: $i.lang, @@ -185,6 +194,8 @@ function save() { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing description: profile.description || null, // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + followedMessage: profile.followedMessage || null, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing location: profile.location || null, // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing birthday: profile.birthday || null, 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..ae8ac88361 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -47,6 +47,11 @@ SPDX-License-Identifier: AGPL-3.0-only <span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ti ti-robot"></i></span> </div> </div> + <div v-if="user.followedMessage != null" class="followedMessage"> + <div style="border: solid 1px var(--love); border-radius: 6px; background: color-mix(in srgb, var(--love), transparent 90%); padding: 6px 8px;"> + <Mfm :text="user.followedMessage" :author="user"/> + </div> + </div> <div v-if="user.roles.length > 0" class="roles"> <span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }"> <MkA v-adaptive-bg :to="`/roles/${role.id}`"> @@ -161,7 +166,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'; @@ -460,6 +465,11 @@ onUnmounted(() => { box-shadow: 1px 1px 3px rgba(#000, 0.2); } + > .followedMessage { + padding: 24px 24px 0 154px; + font-size: 0.9em; + } + > .roles { padding: 24px 24px 0 154px; font-size: 0.95em; @@ -642,6 +652,10 @@ onUnmounted(() => { margin: auto; } + > .followedMessage { + padding: 16px 16px 0 16px; + } + > .roles { padding: 16px 16px 0 16px; justify-content: center; 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.note.vue b/packages/frontend/src/pages/welcome.timeline.note.vue index f385938343..6a9ecd9a62 100644 --- a/packages/frontend/src/pages/welcome.timeline.note.vue +++ b/packages/frontend/src/pages/welcome.timeline.note.vue @@ -84,7 +84,7 @@ onUpdated(() => { left: 0; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), var(--X15)); + background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); } } 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, @@ -464,21 +463,13 @@ const routes: RouteDef[] = [{ 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/aiscript/ui.ts b/packages/frontend/src/scripts/aiscript/ui.ts index fa3fcac2e7..2b386bebb8 100644 --- a/packages/frontend/src/scripts/aiscript/ui.ts +++ b/packages/frontend/src/scripts/aiscript/ui.ts @@ -27,6 +27,8 @@ export type AsUiContainer = AsUiComponentBase & { font?: 'serif' | 'sans-serif' | 'monospace'; borderWidth?: number; borderColor?: string; + borderStyle?: 'hidden' | 'dotted' | 'dashed' | 'solid' | 'double' | 'groove' | 'ridge' | 'inset' | 'outset'; + borderRadius?: number; padding?: number; rounded?: boolean; hidden?: boolean; @@ -173,6 +175,10 @@ function getContainerOptions(def: values.Value | undefined): Omit<AsUiContainer, if (borderWidth) utils.assertNumber(borderWidth); const borderColor = def.value.get('borderColor'); if (borderColor) utils.assertString(borderColor); + const borderStyle = def.value.get('borderStyle'); + if (borderStyle) utils.assertString(borderStyle); + const borderRadius = def.value.get('borderRadius'); + if (borderRadius) utils.assertNumber(borderRadius); const padding = def.value.get('padding'); if (padding) utils.assertNumber(padding); const rounded = def.value.get('rounded'); @@ -191,6 +197,8 @@ function getContainerOptions(def: values.Value | undefined): Omit<AsUiContainer, font: font?.value, borderWidth: borderWidth?.value, borderColor: borderColor?.value, + borderStyle: borderStyle?.value, + borderRadius: borderRadius?.value, padding: padding?.value, rounded: rounded?.value, hidden: hidden?.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 2563b0baf3..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; @@ -66,6 +67,11 @@ export async function getNoteClipMenu(props: { }); if (props.currentClip?.id === clip.id) props.isDeleted.value = true; } + } else if (err.id === 'f0dba960-ff73-4615-8df4-d6ac5d9dc118') { + os.alert({ + type: 'error', + text: i18n.ts.clipNoteLimitExceeded, + }); } else { os.alert({ type: 'error', @@ -93,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, }, @@ -151,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>; @@ -245,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, @@ -276,161 +297,175 @@ 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) ? { + if (props.currentClip?.userId === $i.id) { + menuItems.push({ + icon: 'ti ti-backspace', + text: i18n.ts.unclip, + danger: true, + action: unclip, + }, { type: 'divider' }); + } + + 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'); }, - } : undefined, - ...(isSupportShare() ? [{ + }); + } else { + menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode)); + } + + if (isSupportShare()) { + menuItems.push({ icon: 'ti ti-share', text: i18n.ts.share, action: share, - }] : []), - $i && $i.policies.canUseTranslator && instance.translatorAvailable ? { + }); + } + + if ($i.policies.canUseTranslator && instance.translatorAvailable) { + menuItems.push({ 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), + }); + } + + 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; }, - 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, + }); + + 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 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[]; + const channelChildMenu = [] as MenuItem[]; - const channel = await misskeyApi('channels/show', { channelId: appearNote.channel!.id }); + 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 (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 ? { + }); + } + + 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, - } : undefined, - { - icon: 'ti ti-trash', - text: i18n.ts.delete, - danger: true, - action: del, - }] - : [] - )] - .filter(x => x !== undefined); + }); + } + menuItems.push({ + icon: 'ti ti-trash', + text: i18n.ts.delete, + danger: true, + action: del, + }); + } } else { - menu = [{ + menuItems.push({ icon: 'ti ti-info-circle', text: i18n.ts.details, action: openDetail, @@ -438,35 +473,42 @@ 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'); - }, - } : undefined] - .filter(x => x !== undefined); + }, 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 = () => { @@ -477,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); + }); + + 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(); + }, })); - - 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 && 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..b7e7a5a3f8 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 = () => { @@ -74,6 +74,8 @@ export function applyTheme(theme: Theme, persist = true) { const colorScheme = theme.base === 'dark' ? 'dark' : 'light'; + document.documentElement.dataset.colorScheme = colorScheme; + // Deep copy const _theme = deepClone(theme); diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/scripts/upload.ts index 3e947183c9..22dce609c6 100644 --- a/packages/frontend/src/scripts/upload.ts +++ b/packages/frontend/src/scripts/upload.ts @@ -9,10 +9,11 @@ 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'; +import { instance } from '@/instance.js'; type Uploading = { id: string; @@ -39,6 +40,15 @@ export function uploadFile( if (folder && typeof folder === 'object') folder = folder.id; + if (file.size > instance.maxFileSize) { + alert({ + type: 'error', + title: i18n.ts.failedToUpload, + text: i18n.ts.cannotUploadBecauseExceedsFileSizeLimit, + }); + return Promise.reject(); + } + return new Promise((resolve, reject) => { const id = uuid(); 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..1ddcca5afe 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -5,10 +5,12 @@ import { markRaw, ref } from 'vue'; import * as Misskey from 'misskey-js'; +import { hemisphere } from '@@/js/intl-const.js'; +import lightTheme from '@@/themes/l-light.json5'; +import darkTheme from '@@/themes/d-green-lime.json5'; 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'; interface PostFormAction { title: string, @@ -250,9 +252,9 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: 'twemoji', // twemoji / fluentEmoji / native }, - disableDrawer: { + menuStyle: { where: 'device', - default: false, + default: 'auto' as 'auto' | 'popup' | 'drawer', }, useBlurEffectForModal: { where: 'device', @@ -302,9 +304,9 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: 2, }, - emojiPickerUseDrawerForMobile: { + emojiPickerStyle: { where: 'device', - default: true, + default: 'auto' as 'auto' | 'popup' | 'drawer', }, recentlyUsedEmojis: { where: 'device', @@ -390,9 +392,9 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: 'horizontal' as 'vertical' | 'horizontal', }, - enableCondensedLineForAcct: { + enableCondensedLine: { where: 'device', - default: false, + default: true, }, additionalUnicodeEmojiIndexes: { where: 'device', @@ -458,10 +460,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 +522,6 @@ interface Watcher { /** * 常ã«ãƒ¡ãƒ¢ãƒªã«ãƒãƒ¼ãƒ‰ã—ã¦ãŠãå¿…è¦ãŒãªã„よã†ãªè¨å®šæƒ…å ±ã‚’ä¿ç®¡ã™ã‚‹ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸(éžãƒªã‚¢ã‚¯ãƒ†ã‚£ãƒ–) */ -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 2feb79ef81..b835096b15 100644 --- a/packages/frontend/src/style.scss +++ b/packages/frontend/src/style.scss @@ -17,18 +17,11 @@ --minBottomSpacingMobile: calc(72px + max(12px, env(safe-area-inset-bottom, 0px))); --minBottomSpacing: var(--minBottomSpacingMobile); + //--ad: rgb(255 169 0 / 10%); + @media (max-width: 500px) { --margin: var(--marginHalf); } - - //--ad: rgb(255 169 0 / 10%); - --eventFollow: #36aed2; - --eventRenote: #36d298; - --eventReply: #007aff; - --eventReactionHeart: #dd2e44; - --eventReaction: #e99a0b; - --eventAchievement: #cb9a11; - --eventOther: #88a6b7; } ::selection { @@ -255,11 +248,11 @@ rt { background: var(--accent); &:not(:disabled):hover { - background: var(--X8); + background: hsl(from var(--accent) h s calc(l + 5)); } &:not(:disabled):active { - background: var(--X9); + background: hsl(from var(--accent) h s calc(l - 5)); } } @@ -269,11 +262,11 @@ rt { background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); &:not(:disabled):hover { - background: linear-gradient(90deg, var(--X8), var(--X8)); + 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, var(--X8), var(--X8)); + background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); } } @@ -378,6 +371,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; @@ -447,7 +450,7 @@ rt { --fg: #693410; } -html[data-color-mode=dark] ._woodenFrame { +html[data-color-scheme=dark] ._woodenFrame { --bg: #1d0c02; --fg: #F1E8DC; --panel: #192320; 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_/common.vue b/packages/frontend/src/ui/_common_/common.vue index d7df2d10f9..61881c02e1 100644 --- a/packages/frontend/src/ui/_common_/common.vue +++ b/packages/frontend/src/ui/_common_/common.vue @@ -39,9 +39,9 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="pendingApiRequestsCount > 0" id="wait"></div> -<div v-if="dev" id="devTicker"><span>DEV BUILD</span></div> +<div v-if="dev" id="devTicker"><span style="animation: dev-ticker-blink 2s infinite;">DEV BUILD</span></div> -<div v-if="$i && $i.isBot" id="botWarn"><span>{{ i18n.ts.loggedInAsBot }}</span></div> +<div v-if="$i && $i.isBot" id="botWarn"><span style="animation: dev-ticker-blink 2s infinite;">{{ i18n.ts.loggedInAsBot }}</span></div> </template> <script lang="ts" setup> @@ -258,10 +258,6 @@ if ($i) { font-size: 14px; pointer-events: none; user-select: none; - - > span { - animation: dev-ticker-blink 2s infinite; - } } #devTicker { @@ -275,9 +271,5 @@ if ($i) { font-size: 14px; pointer-events: none; user-select: none; - - > span { - animation: dev-ticker-blink 2s infinite; - } } </style> diff --git a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue index 87e9e45e63..5115d21d56 100644 --- a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue +++ b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue @@ -82,6 +82,8 @@ function more() { <style lang="scss" module> .root { + --nav-bg-transparent: color(from var(--navBg) srgb r g b / 0.5); + display: flex; flex-direction: column; } @@ -91,7 +93,7 @@ function more() { top: 0; z-index: 1; padding: 20px 0; - background: var(--X14); + background: var(--nav-bg-transparent); -webkit-backdrop-filter: var(--blur, blur(8px)); backdrop-filter: var(--blur, blur(8px)); } @@ -125,7 +127,7 @@ function more() { position: sticky; bottom: 0; padding: 20px 0; - background: var(--X14); + background: var(--nav-bg-transparent); -webkit-backdrop-filter: var(--blur, blur(8px)); backdrop-filter: var(--blur, blur(8px)); } diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue index 8307da0d42..05f5ff2565 100644 --- a/packages/frontend/src/ui/_common_/navbar.vue +++ b/packages/frontend/src/ui/_common_/navbar.vue @@ -111,6 +111,7 @@ function more(ev: MouseEvent) { .root { --nav-width: 250px; --nav-icon-only-width: 80px; + --nav-bg-transparent: color(from var(--navBg) srgb r g b / 0.5); flex: 0 0 var(--nav-width); width: var(--nav-width); @@ -144,7 +145,7 @@ function more(ev: MouseEvent) { top: 0; z-index: 1; padding: 20px 0; - background: var(--X14); + background: var(--nav-bg-transparent); -webkit-backdrop-filter: var(--blur, blur(8px)); backdrop-filter: var(--blur, blur(8px)); } @@ -187,7 +188,7 @@ function more(ev: MouseEvent) { position: sticky; bottom: 0; padding-top: 20px; - background: var(--X14); + background: var(--nav-bg-transparent); -webkit-backdrop-filter: var(--blur, blur(8px)); backdrop-filter: var(--blur, blur(8px)); } @@ -378,7 +379,7 @@ function more(ev: MouseEvent) { top: 0; z-index: 1; padding: 20px 0; - background: var(--X14); + background: var(--nav-bg-transparent); -webkit-backdrop-filter: var(--blur, blur(8px)); backdrop-filter: var(--blur, blur(8px)); } @@ -408,7 +409,7 @@ function more(ev: MouseEvent) { position: sticky; bottom: 0; padding-top: 20px; - background: var(--X14); + background: var(--nav-bg-transparent); -webkit-backdrop-filter: var(--blur, blur(8px)); backdrop-filter: var(--blur, blur(8px)); } 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/_common_/statusbars.vue b/packages/frontend/src/ui/_common_/statusbars.vue index 872c69810c..690366307b 100644 --- a/packages/frontend/src/ui/_common_/statusbars.vue +++ b/packages/frontend/src/ui/_common_/statusbars.vue @@ -40,6 +40,14 @@ const XUserList = defineAsyncComponent(() => import('./statusbar-user-list.vue') --nameMargin: 10px; font-size: 0.85em; + display: flex; + vertical-align: bottom; + width: 100%; + line-height: var(--height); + height: var(--height); + overflow: clip; + contain: strict; + &.verySmall { --nameMargin: 7px; --height: 16px; @@ -64,14 +72,6 @@ const XUserList = defineAsyncComponent(() => import('./statusbar-user-list.vue') font-size: 0.9em; } - display: flex; - vertical-align: bottom; - width: 100%; - line-height: var(--height); - height: var(--height); - overflow: clip; - contain: strict; - &.black { background: #000; color: #fff; 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 af46b0641d..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')); @@ -450,7 +450,7 @@ body { } &:active { - background: var(--X2); + background: hsl(from var(--panel) h s calc(l - 2)); } } @@ -460,11 +460,11 @@ body { color: var(--fgOnAccent); &:hover { - background: linear-gradient(90deg, var(--X8), var(--X8)); + background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); } &:active { - background: linear-gradient(90deg, var(--X8), var(--X8)); + background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); } } 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 e96402d13b..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) { @@ -324,11 +343,11 @@ function onDrop(ev) { > .body { background: transparent !important; + scrollbar-color: var(--scrollbarHandle) transparent; &::-webkit-scrollbar-track { background: transparent; } - scrollbar-color: var(--scrollbarHandle) transparent; } } @@ -338,11 +357,11 @@ function onDrop(ev) { > .body { background: var(--bg) !important; overflow-y: scroll !important; + scrollbar-color: var(--scrollbarHandle) transparent; &::-webkit-scrollbar-track { background: inherit; } - scrollbar-color: var(--scrollbarHandle) transparent; } } } @@ -423,10 +442,10 @@ function onDrop(ev) { box-sizing: border-box; container-type: size; background-color: var(--bg); + scrollbar-color: var(--scrollbarHandle) var(--panel); &::-webkit-scrollbar-track { background: var(--panel); } - scrollbar-color: var(--scrollbarHandle) var(--panel); } </style> 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 3cb6f598a6..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; @@ -427,7 +422,7 @@ $widgets-hide-threshold: 1090px; } &:active { - background: var(--X2); + background: hsl(from var(--panel) h s calc(l - 2)); } } @@ -437,11 +432,11 @@ $widgets-hide-threshold: 1090px; color: var(--fgOnAccent); &:hover { - background: linear-gradient(90deg, var(--X8), var(--X8)); + background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); } &:active { - background: linear-gradient(90deg, var(--X8), var(--X8)); + background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); } } 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/src/widgets/server-metric/cpu-mem.vue b/packages/frontend/src/widgets/server-metric/cpu-mem.vue index 27d3234207..469075e2c4 100644 --- a/packages/frontend/src/widgets/server-metric/cpu-mem.vue +++ b/packages/frontend/src/widgets/server-metric/cpu-mem.vue @@ -138,7 +138,7 @@ function onStats(connStats: Misskey.entities.ServerStats) { } function onStatsLog(statsLog: Misskey.entities.ServerStatsLog) { - for (const revStats of statsLog.reverse()) { + for (const revStats of statsLog.toReversed()) { onStats(revStats); } } diff --git a/packages/frontend/src/widgets/server-metric/net.vue b/packages/frontend/src/widgets/server-metric/net.vue index d46aaa5f69..d78494b8d2 100644 --- a/packages/frontend/src/widgets/server-metric/net.vue +++ b/packages/frontend/src/widgets/server-metric/net.vue @@ -111,7 +111,7 @@ function onStats(connStats: Misskey.entities.ServerStats) { } function onStatsLog(statsLog: Misskey.entities.ServerStatsLog) { - for (const revStats of statsLog.reverse()) { + for (const revStats of statsLog.toReversed()) { onStats(revStats); } } 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 new file mode 100644 index 0000000000..9d6cf855f3 --- /dev/null +++ b/packages/frontend/test/i18n.test.ts @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { describe, expect, it } from 'vitest'; +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({ + foo: 'foo', + bar: { + baz: 'baz', + qux: 'qux {0}' as unknown as ParameterizedString<'0'>, + quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>, + }, + }); + + expect(i18n.t('foo')).toBe('foo'); + expect(i18n.t('bar.baz')).toBe('baz'); + expect(i18n.tsx.bar.qux({ 0: 'hoge' })).toBe('qux hoge'); + expect(i18n.tsx.bar.quux({ 0: 'hoge', 1: 'fuga' })).toBe('quux hoge fuga'); + }); + it('ts', () => { + const i18n = new I18n({ + foo: 'foo', + bar: { + baz: 'baz', + qux: 'qux {0}' as unknown as ParameterizedString<'0'>, + quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>, + }, + }); + + expect(i18n.ts.foo).toBe('foo'); + expect(i18n.ts.bar.baz).toBe('baz'); + }); + it('tsx', () => { + const i18n = new I18n({ + foo: 'foo', + bar: { + baz: 'baz', + qux: 'qux {0}' as unknown as ParameterizedString<'0'>, + quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>, + }, + }); + + expect(i18n.tsx.bar.qux({ 0: 'hoge' })).toBe('qux hoge'); + expect(i18n.tsx.bar.quux({ 0: 'hoge', 1: 'fuga' })).toBe('quux hoge fuga'); + }); +}); 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..a5f12b41f4 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -359,6 +359,9 @@ type AdminSystemWebhookShowRequest = operations['admin___system-webhook___show'] 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']; // @public (undocumented) @@ -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; @@ -1157,6 +1160,10 @@ export type Endpoints = Overwrite<Endpoints_2, { req: SigninRequest; res: SigninResponse; }; + 'signin-with-passkey': { + req: SigninWithPasskeyRequest; + res: SigninWithPasskeyResponse; + }; 'admin/roles/create': { req: Overwrite<AdminRolesCreateRequest, { policies: PartialRolePolicyOverride; @@ -1188,6 +1195,8 @@ declare namespace entities { SignupPendingRequest, SignupPendingResponse, SigninRequest, + SigninWithPasskeyRequest, + SigninWithPasskeyResponse, SigninResponse, PartialRolePolicyOverride, EmptyRequest, @@ -1308,6 +1317,7 @@ declare namespace entities { AdminSystemWebhookShowResponse, AdminSystemWebhookUpdateRequest, AdminSystemWebhookUpdateResponse, + AdminSystemWebhookTestRequest, AnnouncementsRequest, AnnouncementsResponse, AnnouncementsShowRequest, @@ -1567,6 +1577,7 @@ declare namespace entities { IWebhooksShowResponse, IWebhooksUpdateRequest, IWebhooksDeleteRequest, + IWebhooksTestRequest, InviteCreateResponse, InviteDeleteRequest, InviteListRequest, @@ -2120,6 +2131,24 @@ type IAuthorizedAppsResponse = operations['i___authorized-apps']['responses']['2 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']; // @public (undocumented) @@ -2282,6 +2311,40 @@ type ISigninHistoryResponse = operations['i___signin-history']['responses']['200 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']; // @public (undocumented) @@ -2318,6 +2381,9 @@ type IWebhooksShowRequest = operations['i___webhooks___show']['requestBody']['co 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']; // @public (undocumented) @@ -2708,6 +2774,9 @@ type NotificationsCreateRequest = operations['notifications___create']['requestB 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']; // @public (undocumented) @@ -2967,6 +3036,19 @@ type SigninResponse = { }; // @public (undocumented) +type SigninWithPasskeyRequest = { + credential?: object; + context?: string; +}; + +// @public (undocumented) +type SigninWithPasskeyResponse = { + option?: object; + context?: string; + signinResponse?: SigninResponse; +}; + +// @public (undocumented) type SignupPendingRequest = { code: string; }; @@ -2997,10 +3079,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 +3088,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 +3110,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..684ae381f0 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", "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/api.types.ts b/packages/misskey-js/src/api.types.ts index 5ee4194db2..4c3f2e1578 100644 --- a/packages/misskey-js/src/api.types.ts +++ b/packages/misskey-js/src/api.types.ts @@ -5,6 +5,8 @@ import { PartialRolePolicyOverride, SigninRequest, SigninResponse, + SigninWithPasskeyRequest, + SigninWithPasskeyResponse, SignupPendingRequest, SignupPendingResponse, SignupRequest, @@ -82,6 +84,10 @@ export type Endpoints = Overwrite< req: SigninRequest; res: SigninResponse; }, + 'signin-with-passkey': { + req: SigninWithPasskeyRequest; + res: SigninWithPasskeyResponse; + } 'admin/roles/create': { req: Overwrite<AdminRolesCreateRequest, { policies: PartialRolePolicyOverride }>; res: AdminRolesCreateResponse; 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 @@ -963,6 +963,18 @@ declare module '../api.js' { /** * 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. + * * **Credential required**: *No* */ request<E extends 'announcements', P extends Endpoints[E]['req']>( @@ -2822,6 +2834,18 @@ declare module '../api.js' { /** * 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. + * * **Credential required**: *Yes* / **Permission**: *write:invite-codes* */ request<E extends 'invite/create', P extends Endpoints[E]['req']>( 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 6d2f787767..0dff85183f 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 @@ -3769,6 +3789,7 @@ export type components = { /** @default false */ securityKeys: boolean; roles: components['schemas']['RoleLite'][]; + followedMessage?: string | null; memo: string | null; moderationNote?: string; isFollowing?: boolean; @@ -3788,6 +3809,7 @@ export type components = { avatarId: string | null; /** Format: id */ bannerId: string | null; + followedMessage: string | null; isModerator: boolean | null; isAdmin: boolean | null; injectFeaturedNote: boolean; @@ -4227,7 +4249,7 @@ export type components = { user: components['schemas']['UserLite']; /** Format: id */ userId: string; - } | { + } | ({ /** Format: id */ id: string; /** Format: date-time */ @@ -4237,7 +4259,8 @@ export type components = { user: components['schemas']['UserLite']; /** Format: id */ userId: string; - } | { + message: string | null; + }) | { /** Format: id */ id: string; /** Format: date-time */ @@ -4245,15 +4268,27 @@ export type components = { /** @enum {string} */ type: 'roleAssigned'; role: components['schemas']['Role']; - } | { + } | ({ /** Format: id */ id: string; /** Format: date-time */ createdAt: string; /** @enum {string} */ type: 'achievementEarned'; - achievement: string; - } | { + /** @enum {string} */ + achievement: 'notes1' | 'notes10' | 'notes100' | 'notes500' | 'notes1000' | 'notes5000' | 'notes10000' | 'notes20000' | 'notes30000' | 'notes40000' | 'notes50000' | 'notes60000' | 'notes70000' | 'notes80000' | 'notes90000' | 'notes100000' | 'login3' | 'login7' | 'login15' | 'login30' | 'login60' | 'login100' | 'login200' | 'login300' | 'login400' | 'login500' | 'login600' | 'login700' | 'login800' | 'login900' | 'login1000' | 'passedSinceAccountCreated1' | 'passedSinceAccountCreated2' | 'passedSinceAccountCreated3' | 'loggedInOnBirthday' | 'loggedInOnNewYearsDay' | 'noteClipped1' | 'noteFavorited1' | 'myNoteFavorited1' | 'profileFilled' | 'markedAsCat' | 'following1' | 'following10' | 'following50' | 'following100' | 'following300' | 'followers1' | 'followers10' | 'followers50' | 'followers100' | 'followers300' | 'followers500' | 'followers1000' | 'collectAchievements30' | 'viewAchievements3min' | 'iLoveMisskey' | 'foundTreasure' | 'client30min' | 'client60min' | 'noteDeletedWithin1min' | 'postedAtLateNight' | 'postedAt0min0sec' | 'selfQuote' | 'htl20npm' | 'viewInstanceChart' | 'outputHelloWorldOnScratchpad' | 'open3windows' | 'driveFolderCircularReference' | 'reactWithoutRead' | 'clickedClickHere' | 'justPlainLucky' | 'setNameToSyuilo' | 'cookieClicked' | 'brainDiver' | 'smashTestNotificationButton' | 'tutorialCompleted' | 'bubbleGameExplodingHead' | 'bubbleGameDoubleExplodingHead'; + }) | ({ + /** Format: id */ + id: string; + /** Format: date-time */ + createdAt: string; + /** @enum {string} */ + type: 'exportCompleted'; + /** @enum {string} */ + exportedEntity: 'antenna' | 'blocking' | 'clip' | 'customEmoji' | 'favorite' | 'following' | 'muting' | 'note' | 'userList'; + /** Format: id */ + fileId: string; + }) | ({ /** Format: id */ id: string; /** Format: date-time */ @@ -4261,9 +4296,9 @@ export type components = { /** @enum {string} */ type: 'app'; body: string; - header: string; - icon: string; - } | { + header: string | null; + icon: string | null; + }) | { /** Format: id */ id: string; /** Format: date-time */ @@ -4801,6 +4836,11 @@ export type components = { userEachUserListsLimit: number; rateLimitFactor: number; avatarDecorationLimit: number; + canImportAntennas: boolean; + canImportBlocking: boolean; + canImportFollowing: boolean; + canImportMuting: boolean; + canImportUserLists: boolean; }; ReversiGameLite: { /** Format: id */ @@ -4947,6 +4987,7 @@ export type components = { * @enum {string} */ noteSearchableScope: 'local' | 'global'; + maxFileSize: number; }; MetaDetailedOnly: { features?: { @@ -5103,6 +5144,7 @@ export type operations = { perRemoteUserUserTimelineCacheMax: number; perUserHomeTimelineCacheMax: number; perUserListTimelineCacheMax: number; + enableReactionsBuffering: boolean; notesPerOneAd: number; backgroundImageUrl: string | null; deeplAuthKey: string | null; @@ -8216,7 +8258,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': ((string | number)[])[]; + 'application/json': [string, number][]; }; }; /** @description Client error */ @@ -8262,7 +8304,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': ((string | number)[])[]; + 'application/json': [string, number][]; }; }; /** @description Client error */ @@ -8896,6 +8938,7 @@ export type operations = { 'application/json': { email: string | null; emailVerified: boolean; + followedMessage: string | null; autoAcceptFollowed: boolean; noCrawle: boolean; preventAiLearning: boolean; @@ -9373,6 +9416,7 @@ export type operations = { perRemoteUserUserTimelineCacheMax?: number; perUserHomeTimelineCacheMax?: number; perUserListTimelineCacheMax?: number; + enableReactionsBuffering?: boolean; notesPerOneAd?: number; silencedHosts?: string[] | null; mediaSilencedHosts?: string[] | null; @@ -10326,6 +10370,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. * @@ -18436,8 +18545,8 @@ export type operations = { untilId?: string; /** @default true */ markAsRead?: boolean; - includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[]; - excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[]; + includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[]; + excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[]; }; }; }; @@ -18504,8 +18613,8 @@ export type operations = { untilId?: string; /** @default true */ markAsRead?: boolean; - includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[]; - excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[]; + includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[]; + excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[]; }; }; }; @@ -19558,6 +19667,7 @@ export type operations = { 'application/json': { name?: string | null; description?: string | null; + followedMessage?: string | null; location?: string | null; birthday?: string | null; /** @enum {string|null} */ @@ -20145,6 +20255,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/entities.ts b/packages/misskey-js/src/entities.ts index 08d3dc5c6d..64ed90cbb1 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -271,6 +271,17 @@ export type SigninRequest = { token?: string; }; +export type SigninWithPasskeyRequest = { + credential?: object; + context?: string; +}; + +export type SigninWithPasskeyResponse = { + option?: object; + context?: string; + signinResponse?: SigninResponse; +}; + export type SigninResponse = { id: User['id'], i: string, 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 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..26a50f9fa4 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; @@ -233,7 +233,7 @@ export type Channels = { } }; -export type NoteUpdatedEvent = { +export type NoteUpdatedEvent = { id: Note['id'] } & ({ type: 'reacted'; body: { reaction: string; @@ -257,7 +257,7 @@ export type NoteUpdatedEvent = { choice: number; userId: User['id']; }; -}; +}); export type BroadcastEvents = { noteUpdated: (payload: NoteUpdatedEvent) => 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/build.js b/packages/sw/build.js index 9522d061e0..a9c2e428c0 100644 --- a/packages/sw/build.js +++ b/packages/sw/build.js @@ -8,10 +8,10 @@ import { fileURLToPath } from 'node:url'; import * as esbuild from 'esbuild'; import locales from '../../locales/index.js'; -import meta from '../../package.json' with { type: "json" }; +import meta from '../../package.json' with { type: 'json' }; const watch = process.argv[2]?.includes('watch'); -const __dirname = fileURLToPath(new URL('.', import.meta.url)) +const __dirname = fileURLToPath(new URL('.', import.meta.url)); console.log('Starting SW building...'); 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/create-notification.ts b/packages/sw/src/scripts/create-notification.ts index 7d28d8a694..2b7dfd4f2d 100644 --- a/packages/sw/src/scripts/create-notification.ts +++ b/packages/sw/src/scripts/create-notification.ts @@ -41,11 +41,10 @@ export async function createNotification<K extends keyof PushNotificationDataMap async function composeNotification(data: PushNotificationDataMap[keyof PushNotificationDataMap]): Promise<[string, NotificationOptions] | null> { const i18n = await (swLang.i18n ?? swLang.fetchLocale()); - const { t } = i18n; switch (data.type) { /* case 'driveFileCreated': // TODO (Server Side) - return [t('_notification.fileUploaded'), { + return [i18n.ts._notification.fileUploaded, { body: body.name, icon: body.url, data @@ -58,52 +57,52 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif const account = await getAccountFromId(data.userId); if (!account) return null; const userDetail = await cli.request('users/show', { userId: data.body.userId }, account.token); - return [t('_notification.youWereFollowed'), { + return [i18n.ts._notification.youWereFollowed, { body: getUserName(data.body.user), - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, badge: iconUrl('user-plus'), data, actions: userDetail.isFollowing ? [] : [ { action: 'follow', - title: t('_notification._actions.followBack'), + title: i18n.ts._notification._actions.followBack, }, ], }]; } case 'mention': - return [t('_notification.youGotMention', { name: getUserName(data.body.user) }), { + return [i18n.tsx._notification.youGotMention({ name: getUserName(data.body.user) }), { body: data.body.note.text ?? '', - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, badge: iconUrl('at'), data, actions: [ { action: 'reply', - title: t('_notification._actions.reply'), + title: i18n.ts._notification._actions.reply, }, ], }]; case 'reply': - return [t('_notification.youGotReply', { name: getUserName(data.body.user) }), { + return [i18n.tsx._notification.youGotReply({ name: getUserName(data.body.user) }), { body: data.body.note.text ?? '', - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, badge: iconUrl('arrow-back-up'), data, actions: [ { action: 'reply', - title: t('_notification._actions.reply'), + title: i18n.ts._notification._actions.reply, }, ], }]; case 'renote': - return [t('_notification.youRenoted', { name: getUserName(data.body.user) }), { + return [i18n.tsx._notification.youRenoted({ name: getUserName(data.body.user) }), { body: data.body.note.text ?? '', - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, badge: iconUrl('repeat'), data, actions: [ @@ -115,29 +114,29 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif }]; case 'quote': - return [t('_notification.youGotQuote', { name: getUserName(data.body.user) }), { + return [i18n.tsx._notification.youGotQuote({ name: getUserName(data.body.user) }), { body: data.body.note.text ?? '', - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, badge: iconUrl('quote'), data, actions: [ { action: 'reply', - title: t('_notification._actions.reply'), + title: i18n.ts._notification._actions.reply, }, ...((data.body.note.visibility === 'public' || data.body.note.visibility === 'home') ? [ { action: 'renote', - title: t('_notification._actions.renote'), + title: i18n.ts._notification._actions.renote, }, ] : []), ], }]; case 'note': - return [t('_notification.newNote') + ': ' + getUserName(data.body.user), { + return [i18n.ts._notification.newNote + ': ' + getUserName(data.body.user), { body: data.body.note.text ?? '', - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, data, }]; @@ -164,7 +163,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif const tag = `reaction:${data.body.note.id}`; return [`${reaction} ${getUserName(data.body.user)}`, { body: data.body.note.text ?? '', - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, tag, badge, data, @@ -178,41 +177,60 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif } case 'receiveFollowRequest': - return [t('_notification.youReceivedFollowRequest'), { + return [i18n.ts._notification.youReceivedFollowRequest, { body: getUserName(data.body.user), - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, badge: iconUrl('user-plus'), data, actions: [ { action: 'accept', - title: t('accept'), + title: i18n.ts.accept, }, { action: 'reject', - title: t('reject'), + title: i18n.ts.reject, }, ], }]; case 'followRequestAccepted': - return [t('_notification.yourFollowRequestAccepted'), { + return [i18n.ts._notification.yourFollowRequestAccepted, { body: getUserName(data.body.user), - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, badge: iconUrl('circle-check'), data, }]; case 'achievementEarned': - return [t('_notification.achievementEarned'), { - body: t(`_achievements._types._${data.body.achievement}.title`), + return [i18n.ts._notification.achievementEarned, { + body: i18n.ts._achievements._types[`_${data.body.achievement}`].title, badge: iconUrl('medal'), data, tag: `achievement:${data.body.achievement}`, }]; + case 'exportCompleted': { + const entityName = { + antenna: i18n.ts.antennas, + blocking: i18n.ts.blockedUsers, + clip: i18n.ts.clips, + customEmoji: i18n.ts.customEmojis, + favorite: i18n.ts.favorites, + following: i18n.ts.following, + muting: i18n.ts.mutedUsers, + note: i18n.ts.notes, + userList: i18n.ts.lists, + } as const satisfies Record<typeof data.body.exportedEntity, string>; + + return [i18n.tsx._notification.exportOfXCompleted({ x: entityName[data.body.exportedEntity] }), { + badge: iconUrl('circle-check'), + data, + }]; + } + case 'pollEnded': - return [t('_notification.pollEnded'), { + return [i18n.ts._notification.pollEnded, { body: data.body.note.text ?? '', badge: iconUrl('chart-arrows'), data, @@ -226,8 +244,8 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif }]; case 'test': - return [t('_notification.testNotification'), { - body: t('_notification.notificationWillBeDisplayedLikeThis'), + return [i18n.ts._notification.testNotification, { + body: i18n.ts._notification.notificationWillBeDisplayedLikeThis, badge: iconUrl('bell'), data, }]; @@ -236,9 +254,9 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif return null; } case 'unreadAntennaNote': - return [t('_notification.unreadAntennaNote', { name: data.body.antenna.name }), { + return [i18n.tsx._notification.unreadAntennaNote({ name: data.body.antenna.name }), { body: `${getUserName(data.body.note.user)}: ${data.body.note.text ?? ''}`, - icon: data.body.note.user.avatarUrl, + icon: data.body.note.user.avatarUrl ?? undefined, badge: iconUrl('antenna'), tag: `antenna:${data.body.antenna.id}`, data, @@ -252,7 +270,6 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif export async function createEmptyNotification(): Promise<void> { return new Promise<void>(async res => { const i18n = await (swLang.i18n ?? swLang.fetchLocale()); - const { t } = i18n; await globalThis.registration.showNotification( (new URL(origin)).host, @@ -264,11 +281,11 @@ export async function createEmptyNotification(): Promise<void> { actions: [ { action: 'markAllAsRead', - title: t('markAllAsRead'), + title: i18n.ts.markAllAsRead, }, { action: 'settings', - title: t('notificationSettings'), + title: i18n.ts.notificationSettings, }, ], data: {}, diff --git a/packages/sw/src/scripts/get-account-from-id.ts b/packages/sw/src/scripts/get-account-from-id.ts index 19bfe052ee..157dbd005e 100644 --- a/packages/sw/src/scripts/get-account-from-id.ts +++ b/packages/sw/src/scripts/get-account-from-id.ts @@ -4,9 +4,10 @@ */ import { get } from 'idb-keyval'; +import * as Misskey from 'misskey-js'; -export async function getAccountFromId(id: string): Promise<{ token: string; id: string } | void> { - const accounts = await get<{ token: string; id: string }[]>('accounts'); +export async function getAccountFromId(id: string): Promise<Pick<Misskey.entities.SignupResponse, 'id' | 'token'> | undefined> { + const accounts = await get<Pick<Misskey.entities.SignupResponse, 'id' | 'token'>[]>('accounts'); if (!accounts) { console.log('Accounts are not recorded'); return; diff --git a/packages/sw/src/scripts/i18n.ts b/packages/sw/src/scripts/i18n.ts deleted file mode 100644 index 77b955dbe8..0000000000 --- a/packages/sw/src/scripts/i18n.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export type Locale = { [key: string]: string | Locale }; - -export class I18n<T extends Locale = Locale> { - public ts: T; - - constructor(locale: T) { - this.ts = locale; - - //#region BIND - this.t = this.t.bind(this); - //#endregion - } - - // string ã«ã—ã¦ã„ã‚‹ã®ã¯ã€ãƒ‰ãƒƒãƒˆåŒºåˆ‡ã‚Šã§ã®ãƒ‘ス指定を許å¯ã™ã‚‹ãŸã‚ - // ãªã‚‹ã¹ãã“ã®ãƒ¡ã‚½ãƒƒãƒ‰ä½¿ã†ã‚ˆã‚Šã‚‚locale直接å‚ç…§ã®æ–¹ãŒvueã®ã‚ャッシュ効ã„ã¦ãƒ‘フォーマンスãŒè‰¯ã„ã‹ã‚‚ - public t(key: string, args?: Record<string, string>): string { - try { - let str = key.split('.').reduce<Locale | Locale[keyof Locale]>((o, i) => o[i], this.ts); - if (typeof str !== 'string') throw new Error(); - - if (args) { - for (const [k, v] of Object.entries(args)) { - str = str.replace(`{${k}}`, v); - } - } - return str; - } catch (err) { - console.warn(`missing localization '${key}'`); - return key; - } - } -} diff --git a/packages/sw/src/scripts/lang.ts b/packages/sw/src/scripts/lang.ts index 6fccedd746..3000160e41 100644 --- a/packages/sw/src/scripts/lang.ts +++ b/packages/sw/src/scripts/lang.ts @@ -7,7 +7,8 @@ * Language manager for SW */ import { get, set } from 'idb-keyval'; -import { I18n, type Locale } from '@/scripts/i18n.js'; +import { I18n } from '@@/js/i18n.js'; +import type { Locale } from '../../../../locales/index.js'; class SwLang { public cacheName = `mk-cache-${_VERSION_}`; @@ -23,7 +24,7 @@ class SwLang { return this.fetchLocale(); } - public i18n: Promise<I18n> | null = null; + public i18n: Promise<I18n<Locale>> | null = null; public fetchLocale(): Promise<I18n<Locale>> { return (this.i18n = this._fetch()); diff --git a/packages/sw/src/scripts/operations.ts b/packages/sw/src/scripts/operations.ts index 24eea06231..8862c6faa5 100644 --- a/packages/sw/src/scripts/operations.ts +++ b/packages/sw/src/scripts/operations.ts @@ -14,15 +14,22 @@ import { getUrlWithLoginId } from '@/scripts/login-id.js'; export const cli = new Misskey.api.APIClient({ origin, fetch: (...args): Promise<Response> => fetch(...args) }); -export async function api<E extends keyof Misskey.Endpoints, O extends Misskey.Endpoints[E]['req']>(endpoint: E, userId?: string, options?: O): Promise<void | ReturnType<typeof cli.request<E, O>>> { - let account: { token: string; id: string } | void = undefined; +export async function api< + E extends keyof Misskey.Endpoints, + P extends Misskey.Endpoints[E]['req'] +>(endpoint: E, userId?: string, params?: P): Promise<Misskey.api.SwitchCaseResponseType<E, P> | undefined> { + let account: Pick<Misskey.entities.SignupResponse, 'id' | 'token'> | undefined; if (userId) { account = await getAccountFromId(userId); if (!account) return; } - return cli.request(endpoint, options, account?.token); + return (cli.request as <E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req']>( + endpoint: E, + params: P, + credential?: string | null, + ) => Promise<Misskey.api.SwitchCaseResponseType<E, P>>)(endpoint, params, account?.token); } // mark-all-as-readé€å‡ºã‚’1ç§’é–“éš”ã«åˆ¶é™ã™ã‚‹ @@ -33,7 +40,7 @@ export function sendMarkAllAsRead(userId: string): Promise<null | undefined | vo return new Promise(resolve => { setTimeout(() => { readBlockingStatus.set(userId, false); - api('notifications/mark-all-as-read', userId).then(resolve, resolve); + (api('notifications/mark-all-as-read', userId) as Promise<void>).then(resolve, resolve); }, 1000); }); } diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts index cc79d88713..bf980b83a4 100644 --- a/packages/sw/src/sw.ts +++ b/packages/sw/src/sw.ts @@ -6,7 +6,8 @@ import { get } from 'idb-keyval'; import * as Misskey from 'misskey-js'; import type { PushNotificationDataMap } from '@/types.js'; -import type { I18n, Locale } from '@/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'; import * as swos from '@/scripts/operations.js'; @@ -30,8 +31,8 @@ globalThis.addEventListener('activate', ev => { async function offlineContentHTML() { const i18n = await (swLang.i18n ?? swLang.fetchLocale()) as Partial<I18n<Locale>>; const messages = { - title: i18n.ts?._offlineScreen?.title ?? 'Offline - Could not connect to server', - header: i18n.ts?._offlineScreen?.header ?? 'Could not connect to server', + title: i18n.ts?._offlineScreen.title ?? 'Offline - Could not connect to server', + header: i18n.ts?._offlineScreen.header ?? 'Could not connect to server', reload: i18n.ts?.reload ?? 'Reload', }; @@ -159,8 +160,8 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv case 'markAllAsRead': await globalThis.registration.getNotifications() .then(notifications => notifications.forEach(n => n.tag !== 'read_notification' && n.close())); - await get('accounts').then(accounts => { - return Promise.all(accounts.map(async account => { + await get<Pick<Misskey.entities.SignupResponse, 'id' | 'token'>[]>('accounts').then(accounts => { + return Promise.all((accounts ?? []).map(async account => { await swos.sendMarkAllAsRead(account.id); })); }); diff --git a/packages/sw/tsconfig.json b/packages/sw/tsconfig.json index 50d4aae19d..2712475a37 100644 --- a/packages/sw/tsconfig.json +++ b/packages/sw/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "allowJs": true, "noEmitOnError": false, + "noImplicitAny": false, "noImplicitReturns": true, "noUnusedParameters": false, "noUnusedLocals": true, @@ -20,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..0822620bf3 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: 6.0.0 + version: 6.0.0(@bull-board/ui@6.0.0) '@bull-board/fastify': - specifier: 5.21.1 - version: 5.21.1 + specifier: 6.0.0 + version: 6.0.0 '@bull-board/ui': - specifier: 5.21.1 - version: 5.21.1 + specifier: 6.0.0 + version: 6.0.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.5) '@rollup/plugin-replace': specifier: 5.0.7 - version: 5.0.7(rollup@4.19.1) + version: 5.0.7(rollup@4.22.5) '@rollup/pluginutils': - specifier: 5.1.0 - version: 5.1.0(rollup@4.19.1) + specifier: 5.1.2 + version: 5.1.2(rollup@4.22.5) '@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.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.10(typescript@5.6.2)) '@vue/compiler-sfc': - specifier: 3.4.37 - version: 3.4.37 + specifier: 3.5.10 + version: 3.5.10 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,41 +743,41 @@ 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.4 + version: 11.10.4 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 - 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 @@ -809,14 +812,14 @@ importers: specifier: 2.3.1 version: 2.3.1 rollup: - specifier: 4.19.1 - version: 4.19.1 + specifier: 4.22.5 + version: 4.22.5 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 +830,8 @@ importers: specifier: 3.1.0 version: 3.1.0 three: - specifier: 0.167.0 - version: 0.167.0 + specifier: 0.169.0 + version: 0.169.0 throttle-debounce: specifier: 5.0.2 version: 5.0.2 @@ -842,90 +845,87 @@ 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.10(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.8 + version: 5.4.8(@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.10 + version: 3.5.10(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.10(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.3 + version: 8.3.3(storybook@8.3.3(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.3 + version: 8.3.3(storybook@8.3.3(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.3 + version: 8.3.3(storybook@8.3.3(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.3 + version: 8.3.3(react@18.3.1)(storybook@8.3.3(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.3 + version: 8.3.3(storybook@8.3.3(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.3 + version: 8.3.3(storybook@8.3.3(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.3 + version: 8.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(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.3 + version: 8.3.3(storybook@8.3.3(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.3 + version: 8.3.3(storybook@8.3.3(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.3 + version: 8.3.3(storybook@8.3.3(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.3 + version: 8.3.3(storybook@8.3.3(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.3 + version: 8.3.3(@storybook/test@8.3.3(storybook@8.3.3(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.3(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.3 + version: 8.3.3(@storybook/test@8.3.3(storybook@8.3.3(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.5)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.8(@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.3 + version: 8.3.3(storybook@8.3.3(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.3 + version: 8.3.3(storybook@8.3.3(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.3 + version: 8.3.3(storybook@8.3.3(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.3 + version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.5.10(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.3 + version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.10(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)) - '@types/escape-regexp': - specifier: 0.0.3 - version: 0.0.3 + version: 8.1.0(@vue/compiler-sfc@3.5.10)(@vue/server-renderer@3.5.10(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(typescript@5.6.2)) '@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 +939,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 +954,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.10 + version: 3.5.10 acorn: specifier: 8.12.1 version: 8.12.1 @@ -975,14 +975,14 @@ importers: specifier: 7.0.3 version: 7.0.3 cypress: - specifier: 13.13.1 - version: 13.13.1 + specifier: 13.15.0 + version: 13.15.0 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 +993,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 +1017,232 @@ 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.3 + version: 8.3.3(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.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/components@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/core-events@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/manager-api@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/preview-api@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/theming@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/types@8.3.3(storybook@8.3.3(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.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-embed: + dependencies: + '@discordapp/twemoji': + specifier: 15.1.0 + version: 15.1.0 + '@rollup/plugin-json': + specifier: 6.1.0 + version: 6.1.0(rollup@4.22.5) + '@rollup/plugin-replace': + specifier: 5.0.7 + version: 5.0.7(rollup@4.22.5) + '@rollup/pluginutils': + specifier: 5.1.2 + version: 5.1.2(rollup@4.22.5) + '@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.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.10(typescript@5.6.2)) + '@vue/compiler-sfc': + specifier: 3.5.10 + version: 3.5.10 + astring: + specifier: 1.9.0 + version: 1.9.0 + buraha: + specifier: 0.0.1 + version: 0.0.1 + estree-walker: + specifier: 3.0.3 + version: 3.0.3 + frontend-shared: + specifier: workspace:* + version: link:../frontend-shared + 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.5 + version: 4.22.5 + sass: + specifier: 1.79.3 + version: 1.79.3 + shiki: + specifier: 1.12.0 + version: 1.12.0 + 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.8 + version: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) + vue: + specifier: 3.5.10 + version: 3.5.10(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.10)(@vue/server-renderer@3.5.10(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(typescript@5.6.2)) + '@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/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.10 + version: 3.5.10 + 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.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-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 +1267,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 +1297,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 +1339,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 +1369,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 +1389,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 +1412,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 +1423,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 +1451,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 +1659,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 +1667,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 +1691,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 +1711,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 +1723,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 +1735,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 +1751,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 +1772,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 +1792,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 +1838,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 +1850,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 +1878,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@6.0.0': + resolution: {integrity: sha512-O0IsIwAOU47bPTJnqRO7RtKFQToMvwRebbuPi6M+SG1gXyiqixLg9pycnfXgSeroaT9E7QQ2PsCPW1HO8VORvw==} peerDependencies: - '@bull-board/ui': 5.21.1 + '@bull-board/ui': 6.0.0 - '@bull-board/fastify@5.21.1': - resolution: {integrity: sha512-We33yolc70SALjDdF3cjEaLn1L/vrw85eFCsrviESaW3dFVIdB+xn0fdqMFK6NnaC0JjBa3Ypfev4Co+eaZ+1A==} + '@bull-board/fastify@6.0.0': + resolution: {integrity: sha512-VrKa5BdxYmXh5fJvlSPSm71b+QA9VVXHyGk6xmI/qAefUQbwd2cWJo+ppqaWSaweXa9ymJc+V4l/un0K4oomVA==} - '@bull-board/ui@5.21.1': - resolution: {integrity: sha512-JBDeCqG7j/c3WE0uGMN9snPkRJz9/D6MpTZzyVj7KOxIJwNKPOICNFZbCrCNi7bcJYHDJ2xGTN9OO1mw7i43BQ==} + '@bull-board/ui@6.0.0': + resolution: {integrity: sha512-wAFTlBTJbq5DSWxCzTV+FOyZDVwrXP+G1CQ2BpLG9o9+dpwYxUESx/VxNEDHnyPcy13gm29kB4fSRY+nkelkcQ==} '@bundled-es-modules/cookie@2.0.0': resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==} @@ -2175,41 +1915,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 +1959,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==} - - '@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==} + '@discordapp/twemoji@15.1.0': + resolution: {integrity: sha512-QdpV4ifTONAXvDjRrMohausZeGrQ1ac/Ox6togUh6Xl3XKJ/KAaMMuAEi0qsb0wDwoVTSZBll5Y6+N3hB2ktBw==} - '@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 +1983,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 +2013,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 +2043,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 +2073,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 +2103,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 +2133,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 +2163,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 +2193,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 +2223,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 +2253,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 +2283,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 +2313,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 +2343,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 +2373,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 +2403,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 +2433,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 +2463,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 +2493,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 +2535,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 +2565,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 +2595,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 +2625,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 +2655,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 +2683,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,64 +2703,70 @@ 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/accept-negotiator@2.0.0': + resolution: {integrity: sha512-/Sce/kBzuTxIq5tJh85nVNOq9wKD8s+viIgX0fFMDBdw95gnpf53qmF1oBgJym3cPFliWUuSloVg/1w/rH0FcQ==} - '@fastify/accepts@4.3.0': - resolution: {integrity: sha512-QK4FoqXdwwPmaPOLL6NrxsyaXVvdviYVoS6ltHyOLdFlUyREIaMykHQIp+x0aJz9hB3B3n/Ht6QRdvBeGkptGQ==} + '@fastify/accepts@5.0.0': + resolution: {integrity: sha512-5wpgycrn+DXPkATGqUbXY9tyqLNgxo9S8f0EHUyIWvUacor2cXa3liYZggsqoyMXgpIqUbGLPBl+dN2hRcU9jQ==} - '@fastify/ajv-compiler@3.5.0': - resolution: {integrity: sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==} + '@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/cookie@10.0.0': + resolution: {integrity: sha512-S43spazwAfzm5nKlqq/spAGW+O6r+WQzg5vXXI1ArCXXFa8KBA/tiU3XRVQUehSNtbN5PA6+g183hzh5/dZ6Iw==} - '@fastify/cors@9.0.1': - resolution: {integrity: sha512-YY9Ho3ovI+QHIL2hW+9X4XqQjXLjJqsU+sMV/xFsxZkE8p3GNnYVFpoOxF7SsP5ZL76gwvbo3V9L+FIekBGU4Q==} + '@fastify/cors@10.0.0': + resolution: {integrity: sha512-kb9fkc/LVbLTQ3lhA+ZZjC/Styzysodo/MTCdVCvTtgHa/gBwxrEEkcp3fuoKIfAQt85wksrpXjUGbw5NQffEQ==} - '@fastify/deepmerge@1.3.0': - resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} + '@fastify/deepmerge@2.0.0': + resolution: {integrity: sha512-fsaybTGDyQ5KpPsplQqb9yKdCf2x/pbNpMNk8Tvp3rRz7lVcupKysH4b2ELMN2P4Hak1+UqTYdTj/u4FNV2p0g==} - '@fastify/error@3.4.0': - resolution: {integrity: sha512-e/mafFwbK3MNqxUcFBLgHhgxsF8UT1m8aj0dAlqEa2nJEgPsRtpHTZ3ObgrgkZ2M1eJHPTwgyUl/tXkvabsZdQ==} + '@fastify/error@4.0.0': + resolution: {integrity: sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA==} - '@fastify/express@3.0.0': - resolution: {integrity: sha512-Ug6aulXCUiHgMyrHVYQqnQbGdsAV0aTad6nZxbOr6w3QjKn1mdQS3Kyzvc+I0xMjZ9yIyMUWHSooHgZ0l7nOng==} + '@fastify/express@4.0.0': + resolution: {integrity: sha512-e+IMKKV9+HRCVm7LVW8PaMrpEerHfqNLpRkbiVHYfVm0xeOphiwyNEoge4VA3Sh8gubtDfo9yKkpRzx6gx63kg==} - '@fastify/fast-json-stringify-compiler@4.3.0': - resolution: {integrity: sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==} + '@fastify/fast-json-stringify-compiler@5.0.0': + resolution: {integrity: sha512-tywfuZfXsyxLC5kEqrMubbFa9vpAxNtuPE7j9w5si1r+6p5b981pDfZ5Y8HBqmjDQl+PABT7cV5jZgXI2j+I5g==} - '@fastify/http-proxy@9.5.0': - resolution: {integrity: sha512-1iqIdV10d5k9YtfHq9ylX5zt1NiM50fG+rIX40qt00R694sqWso3ukyTFZVk33SDoSiBW8roB7n11RUVUoN+Ag==} + '@fastify/http-proxy@10.0.0': + resolution: {integrity: sha512-n5/EPspNKtzpCUavuDflYtvtB+aEkablb2sZM83gDKbxM9GF+93maJYQrGozJ2HNRqpt7wfzsDeUuGVFFkYzMQ==} - '@fastify/multipart@8.3.0': - resolution: {integrity: sha512-A8h80TTyqUzaMVH0Cr9Qcm6RxSkVqmhK/MVBYHYeRRSUbUYv08WecjWKSlG2aSnD4aGI841pVxAjC+G1GafUeQ==} + '@fastify/merge-json-schemas@0.1.1': + resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==} - '@fastify/reply-from@9.0.1': - resolution: {integrity: sha512-q9vFNUiXZTY1x8omDPe59os2MYq+3y7KgO/kZoXpZlnud+45Nd8Ot/svEvrUATzjkizIggfS4K8LR9zXDyZZKg==} + '@fastify/multipart@9.0.0': + resolution: {integrity: sha512-B/rzOl1wmkj4LddH2i+zR8Gke8ZX1J8D7n4uJeis5VdIa7OR9Ys/TzUxI0/h1SF9ubHlNhBP+eO/FwnftarP9w==} - '@fastify/send@2.0.1': - resolution: {integrity: sha512-8jdouu0o5d0FMq1+zCKeKXc1tmOQ5tTGYdQP3MpyF9+WWrZT1KCBdh6hvoEYxOm3oJG/akdE9BpehLiJgYRvGw==} + '@fastify/reply-from@11.0.0': + resolution: {integrity: sha512-dv3o8hyy4sxhg1RN9l6ueM+PMMaIPKLjtL2T99H5M7h1Xt8d1RX3r+xC+sL5AqJqLvReX4N+7mTq9QDeB8i6Lg==} - '@fastify/static@6.12.0': - resolution: {integrity: sha512-KK1B84E6QD/FcQWxDI2aiUCwHxMJBI1KeCUzm1BwYpPY1b742+jeKruGHP2uOluuM6OkBPI8CIANrXcCRtC2oQ==} + '@fastify/send@3.1.1': + resolution: {integrity: sha512-LdiV2mle/2tH8vh6GwGl0ubfUAgvY+9yF9oGI1iiwVyNUVOQamvw5n+OFu6iCNNoyuCY80FFURBn4TZCbTe8LA==} - '@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@8.2.0': - resolution: {integrity: sha512-hBSiBofCnJNlPHEMZWpO1SL84eqOaqujJ1hR3jntFyZZCkweH5jMs12DKYyGesjVll7SJFRRxPUBB8kmUmneRQ==} + '@fastify/static@8.0.1': + resolution: {integrity: sha512-7idyhbcgf14v4bjWzUeHEFvnVxvNJ1n5cyGPgFtwTZjnjUQ1wgC7a2FQai7OGKqCKywDEjzbPhAZRW+uEK1LMg==} - '@fastify/view@9.1.0': - resolution: {integrity: sha512-jRTGDljs/uB2p8bf6c1x4stGjP7H84VQkhbtDgCx55Mxf9Fplud5UZIHubvL4BTTX8jNYEzP1FpNAOBi7vibxg==} + '@fastify/view@10.0.0': + resolution: {integrity: sha512-2KnfgpSbAImKV5kKdNAkSyjV+9kYUYLvgDLx/wlzgqel92bN9Z520cwG3g3bAkr0yVnEJu62dIm2qAL9FASS1w==} + + '@fastify/view@10.0.1': + resolution: {integrity: sha512-rXtBN0oVDmoRZAS7lelrCIahf+qFtlMOOas8VPdA7JvrJ9ChcF7e36pIUPU0Vbs3KmHxESUb7XatavUZEe/k5Q==} '@github/webauthn-json@2.1.1': resolution: {integrity: sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ==} @@ -2916,116 +2805,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 +2926,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==} @@ -3142,8 +3011,8 @@ packages: resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@joshwooding/vite-plugin-react-docgen-typescript@0.3.1': - resolution: {integrity: sha512-pdoMZ9QaPnVlSM+SdU/wgg0nyD/8wQ7y90ttO2CMCyrrm7RxveYIJ5eNfjPaoMFqW41LZra7QO9j+xV4Y18Glw==} + '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0': + resolution: {integrity: sha512-2D6y7fNvFmsLmRt6UCOFJPvFoPMJGT0Uh1Wg0RaigUp7kdQPs6yYn8Dmx6GZkOH/NW0yMTwRz/p0SRMMRo50vA==} peerDependencies: typescript: '>= 4.3.x' vite: ^3.0.0 || ^4.0.0 || ^5.0.0 @@ -3180,6 +3049,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==} @@ -3199,8 +3071,8 @@ packages: resolution: {integrity: sha512-uSvJdwQU5nK+Vdf6zxcWAY2A8r7uqe+gePwLWzJ+fsQehq18pc0I2hJKwypZ2aLM90+Er9u1xn4iLJPZ+xlL4g==} engines: {node: '>=8'} - '@lukeed/ms@2.0.1': - resolution: {integrity: sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==} + '@lukeed/ms@2.0.2': + resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} '@mapbox/node-pre-gyp@1.0.9': @@ -3219,11 +3091,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 +3161,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 +3236,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 +3253,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 +3524,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'} @@ -3672,8 +3552,8 @@ packages: rollup: optional: true - '@rollup/pluginutils@5.1.0': - resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + '@rollup/pluginutils@5.1.2': + resolution: {integrity: sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -3681,88 +3561,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.5': + resolution: {integrity: sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.19.1': - resolution: {integrity: sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A==} + '@rollup/rollup-android-arm64@4.22.5': + resolution: {integrity: sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.19.1': - resolution: {integrity: sha512-8o6eqeFZzVLia2hKPUZk4jdE3zW7LCcZr+MD18tXkgBBid3lssGVAYuox8x6YHoEPDdDa9ixTaStcmx88lio5Q==} + '@rollup/rollup-darwin-arm64@4.22.5': + resolution: {integrity: sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.19.1': - resolution: {integrity: sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA==} + '@rollup/rollup-darwin-x64@4.22.5': + resolution: {integrity: sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==} 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.5': + resolution: {integrity: sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==} 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.5': + resolution: {integrity: sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==} 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.5': + resolution: {integrity: sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==} 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.5': + resolution: {integrity: sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==} 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.5': + resolution: {integrity: sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==} 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.5': + resolution: {integrity: sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==} 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.5': + resolution: {integrity: sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==} 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.5': + resolution: {integrity: sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==} 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.5': + resolution: {integrity: sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.19.1': - resolution: {integrity: sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==} + '@rollup/rollup-win32-arm64-msvc@4.22.5': + resolution: {integrity: sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==} 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.5': + resolution: {integrity: sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==} 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.5': + resolution: {integrity: sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==} 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 +3655,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 +3706,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 +3737,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,115 +3989,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.3': + resolution: {integrity: sha512-cbpksmld7iADwDGXgojZ4r8LGI3YA3NP68duAHg2n1dtnx1oUaFK5wd6dbNuz7GdjyhIOIy3OKU1dAuylYNGOQ==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/addon-backgrounds@8.2.6': - resolution: {integrity: sha512-61NFowA6EmCw+Eyzp0U4fat9MlPDdnT7aoDyzqSImLwWLITY9IvmWuTeo7XKJZN3fe22z1r7cZseKdYrtaHcKw==} + '@storybook/addon-backgrounds@8.3.3': + resolution: {integrity: sha512-aX0OIrtjIB7UgSaiv20SFkfC1iWwJIGMPsPSJ5ZPhXIIOWIEBtSujh8YXwjDEXSC4DOHalmeT4bitRRe5KrVKA==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/addon-controls@8.2.6': - resolution: {integrity: sha512-EHUwHy+oZZv3pXzN7fuXWrS/meHFjqcELY3RBvOyEkGf21agl6co6R1tnf6d5N5QoYAGfIbDO7dkauSL2RfNAw==} + '@storybook/addon-controls@8.3.3': + resolution: {integrity: sha512-78xRtVpY7eX/Lti00JLgwYCBRB6ZcvzY3SWk0uQjEqcTnQGoQkVg2L7oWFDlDoA1LBY18P5ei2vu8MYT9GXU4g==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/addon-docs@8.2.6': - resolution: {integrity: sha512-qe7hxntaezqjKdU9QS+Q9NFL6i/uNdBxdvOnCKgPhBAY/zY6yhk5t3sOvonynPK5nkaNAowfSNPIzNxAXlJ1sA==} + '@storybook/addon-docs@8.3.3': + resolution: {integrity: sha512-REUandqq1RnMNOhsocRwx5q2fdlBAYPTDFlKASYfEn4Ln5NgbQRGxOAWl7yXAAFzbDmUDU7K20hkauecF0tyMw==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/addon-essentials@8.2.6': - resolution: {integrity: sha512-diGjGZcZNov+RCAVQBTm8JKP2kUtMRuJIQFBeXdPWpu6hYBk6lw1FlAf2GywWGCvdny1pJT90hfoD33qUMNuDg==} + '@storybook/addon-essentials@8.3.3': + resolution: {integrity: sha512-E/uXoUYcg8ulG3lVbsEKb4v5hnMeGkq9YJqiZYKgVK7iRFa6p4HeVB1wU1adnm7RgjWvh+p0vQRo4KL2CTNXqw==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/addon-highlight@8.2.6': - resolution: {integrity: sha512-03cV9USsfP3bS4wYV06DYcIaGPfoheQe53Q0Jr1B2yJUVyIPKvmO2nGjLBsqzeL3Wl7vSfLQn0/dUdxCcbqLsw==} + '@storybook/addon-highlight@8.3.3': + resolution: {integrity: sha512-MB084xJM66rLU+iFFk34kjLUiAWzDiy6Kz4uZRa1CnNqEK0sdI8HaoQGgOxTIa2xgJor05/8/mlYlMkP/0INsQ==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/addon-interactions@8.2.6': - resolution: {integrity: sha512-YXpHf8jWPz9HJV+Fw4GaunaCWeE6uqF24aLXdAd8xuhN1UfWJeNV6AwAvFQ0hTLqvmz0yMhX/5JXDKeKESoYDA==} + '@storybook/addon-interactions@8.3.3': + resolution: {integrity: sha512-3w5tpCGYdF33wF44xEhTS3Zmcwd6nITtwy5q+PJvHCJAm3fpjzL3xrjtlHKDvXNwYacJPRCbWKn2QwtxZIdN0g==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/addon-links@8.2.6': - resolution: {integrity: sha512-CUuU3nk8wyZ3bljCmOG/OCKazan+bPuNbCph8N763zyzdEx5M/CbBxV9d3pi3zjYpix7txlqrl2/YdMCejfyFw==} + '@storybook/addon-links@8.3.3': + resolution: {integrity: sha512-rz4KEbzr1ca4zZEZwbOnhKiaEsokCl1KkngxT/C1YIkpW908j/kg2nnIb5MrtlAW1nirXguAR74t6CGntvdU9w==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.2.6 + storybook: ^8.3.3 peerDependenciesMeta: react: optional: true - '@storybook/addon-mdx-gfm@8.2.6': - resolution: {integrity: sha512-PFVfJeuydxlV1VmxEuKNQ7z2vCDvQtHa2GB0ANM11ahxDSUz8QxsO0Y/L3LOn2JjJGYiVFrsHAaC+8NW43iArQ==} + '@storybook/addon-mdx-gfm@8.3.3': + resolution: {integrity: sha512-jdwVXoBSEdmuw8L4MxUeJ/qIInADfCwdtShnfTQIJBBRucOl8ykgfTKKNjllT79TFiK0gsWoiZmE05P4wuBofw==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/addon-measure@8.2.6': - resolution: {integrity: sha512-neI8YeSOAtOmzasLxo6O8ZLr2ebMaD7XVF+kYatl5+SpyuwwvUGcP9NkKe5S+mB8V2zxFUIsXS74XrhmQhRoaQ==} + '@storybook/addon-measure@8.3.3': + resolution: {integrity: sha512-R20Z83gnxDRrocES344dw1Of/zDhe3XHSM6TLq80UQTJ9PhnMI+wYHQlK9DsdP3KiRkI+pQA6GCOp0s2ZRy5dg==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/addon-outline@8.2.6': - resolution: {integrity: sha512-uAlPtqDWlq7MQQ4zJT80qdjbSdLF/zsvtPhidX6h9cjLKNPWAv79xJQ14AJHaMv+Hzy5xKnM4wdEhgPbzKabQg==} + '@storybook/addon-outline@8.3.3': + resolution: {integrity: sha512-OwqYfieNuqSqWNtUZLu3UmsfQNnwA2UaSMBZyeC2Dte9Jd59PPYggcWmH+b0S6OTbYXWNAUK5U6WdK+X9Ypzdw==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/addon-storysource@8.2.6': - resolution: {integrity: sha512-8H2kvRIM12oXN4kO/oowABu88IOY9Je7PphCUUs/nIfTIB+Ck1GrLxx5fCNNCSwUrTBEZY8bDOfxmkf9d4qngw==} + '@storybook/addon-storysource@8.3.3': + resolution: {integrity: sha512-yPYQH9NepSNxoSsV9E7OV3/EVFrbU/r2B3E5WP/mCfqTXPg/5noce7iRi+rWqcVM1tsN1qPnSjfQQc7noF0h0Q==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/addon-toolbars@8.2.6': - resolution: {integrity: sha512-0JmRirMpxHS6VZzBk0kY871xWTpkk3TN4S1sxoFf5fcnCfVTHDjEJ5Ws/QWru1RJlIZHuJKRdQIA6Vuq5X+KfQ==} + '@storybook/addon-toolbars@8.3.3': + resolution: {integrity: sha512-4WyiVqDm4hlJdENIVQg9pLNLdfhnNKa+haerYYSzTVjzYrUx0X6Bxafshq+sud6aRtSYU14abwP56lfW8hgTlA==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/addon-viewport@8.2.6': - resolution: {integrity: sha512-IAxH9H8tVFzSmZhKf5E+EALiAdkp19RzGqP/rWluD8LH7oW5HumQE/4oN0ZhVMy1RxYsCKFYjWyAp7AuxeMRSw==} + '@storybook/addon-viewport@8.3.3': + resolution: {integrity: sha512-2S+UpbKAL+z1ppzUCkixjaem2UDMkfmm/kyJ1wm3A/ofGLYi4fjMSKNRckk+7NdolXGQJjBo0RcaotUTxFIFwQ==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/blocks@8.2.6': - resolution: {integrity: sha512-nMlZJjVTyfOJ6xwORptsNuS1AZZlDbJUVXc2R8uukGd5GIXxxCdrPk4NvUsjfQslMT9LhYuFld3z62FATsM2rw==} + '@storybook/blocks@8.3.3': + resolution: {integrity: sha512-8Vsvxqstop3xfbsx3Dn1nEjyxvQUcOYd8vpxyp2YumxYO8FlXIRuYL6HAkYbcX8JexsKvCZYxor52D2vUGIKZg==} 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.3 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==} - peerDependencies: - '@preact/preset-vite': '*' - 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/builder-vite@8.2.6': - resolution: {integrity: sha512-3PrsPZAedpQUbzRBEl23Fi1zG5bkQD76JsygVwmfiSm4Est4K8kW2AIB2ht9cIfKXh3mfQkyQlxXKHeQEHeQwQ==} + '@storybook/builder-vite@8.3.3': + resolution: {integrity: sha512-3yTXCLaB6bzhoPH3PqtacKkcaC1uV4L+IHTf1Zypx1NO1pLZHyhYf0T7dIOxTh2JZfqu1Pm9hTvOmWfR12m+9w==} peerDependencies: '@preact/preset-vite': '*' - storybook: ^8.2.6 + storybook: ^8.3.3 typescript: '>= 4.3.x' vite: ^4.0.0 || ^5.0.0 vite-plugin-glimmerx: '*' @@ -4227,190 +4091,115 @@ packages: vite-plugin-glimmerx: optional: true - '@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/components@8.3.3': + resolution: {integrity: sha512-i2JYtesFGkdu+Hwuj+o9fLuO3yo+LPT1/8o5xBVYtEqsgDtEAyuRUWjSz8d8NPtzloGPOv5kvR6MokWDfbeMfw==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/core-common@8.1.11': - resolution: {integrity: sha512-Ix0nplD4I4DrV2t9B+62jaw1baKES9UbR/Jz9LVKFF9nsua3ON0aVe73dOjMxFWBngpzBYWe+zYBTZ7aQtDH4Q==} + '@storybook/core-events@8.3.3': + resolution: {integrity: sha512-YL+gBuCS81qktzTkvw0MXUJW0bYAXfRzMoiLfDBTrEKZfcJOB4JAlMGmvRRar0+jygK3icD42Rl5BwWoZY6KFQ==} peerDependencies: - prettier: ^2 || ^3 - peerDependenciesMeta: - prettier: - optional: true + storybook: ^8.3.3 - '@storybook/core-events@8.1.11': - resolution: {integrity: sha512-vXaNe2KEW9BGlLrg0lzmf5cJ0xt+suPjWmEODH5JqBbrdZ67X6ApA2nb6WcxDQhykesWCuFN5gp1l+JuDOBi7A==} + '@storybook/core@8.3.3': + resolution: {integrity: sha512-pmf2bP3fzh45e56gqOuBT8sDX05hGdUKIZ/hcI84d5xmd6MeHiPW8th2v946wCHcxHzxib2/UU9vQUh+mB4VNw==} - '@storybook/core-events@8.2.6': - resolution: {integrity: sha512-bmtm7sHBExKCSGiCIyhwfHKFIsdrRQqd8ZEb/iNWsR93AxHszcf/adYAVynencdWKipw1haIWBNaiDhnsOBVPA==} + '@storybook/csf-plugin@8.3.3': + resolution: {integrity: sha512-7AD7ojpXr3THqpTcEI4K7oKUfSwt1hummgL/cASuQvEPOwAZCVZl2gpGtKxcXhtJXTkn3GMCAvlYMoe7O/1YWw==} 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.3 '@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.3': + resolution: {integrity: sha512-ZiODB9EwCQkl4PBxGJjBHXRTLxcNs68ZZvR+xeMr0eMFzzlJG+trXoX5kK95oA4BFhGN+3uM0Zl3MoRjBtJTNA==} peerDependencies: - storybook: ^8.2.6 - - '@storybook/manager-api@8.1.11': - resolution: {integrity: sha512-QSgwKfAw01K9YvvZj30iGBMgQ4YaCT3vojmttuqdH5ukyXkiO7pENLJj4Y+alwUeSi0g+SJeadCI3PXySBHOGg==} + storybook: ^8.3.3 - '@storybook/manager-api@8.2.6': - resolution: {integrity: sha512-uv36h/b5RhlajWtEg4cVPBYV8gZs6juux0nIE+6G9i7vt8Ild6gM9tW1KNabgZcaHFiyWJYCNWxJZoKjgUmXDg==} + '@storybook/manager-api@8.3.3': + resolution: {integrity: sha512-Na4U+McOeVUJAR6qzJfQ6y2Qt0kUgEDUriNoAn+curpoKPTmIaZ79RAXBzIqBl31VyQKknKpZbozoRGf861YaQ==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@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.3': + resolution: {integrity: sha512-GP2QlaF3BBQGAyo248N7549YkTQjCentsc1hUvqPnFWU4xfjkejbnFk8yLaIw0VbYbL7jfd7npBtjZ+6AnphMQ==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@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.3': + resolution: {integrity: sha512-0dPC9K7+K5+X/bt3GwYmh+pCpisUyKVjWsI+PkzqGnWqaXFakzFakjswowIAIO1rf7wYZR591x3ehUAyL2bJiQ==} 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.3 - '@storybook/react-vite@8.2.6': - resolution: {integrity: sha512-BpbteaIzsJZL1QN3iR7uuslrPfdtbZYXPhcU9awpfl5pW5MOQThuvl7728mwT8V7KdANeikJPgsnlETOb/afDA==} + '@storybook/react-vite@8.3.3': + resolution: {integrity: sha512-vzOqVaA/rv+X5J17eWKxdZztMKEKfsCSP8pNNmrqXWxK3pSlW0fAPxtn1kw3UNxGtAv71pcqvaCUtTJKqI1PYA==} 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.3 vite: ^4.0.0 || ^5.0.0 - '@storybook/react@8.2.6': - resolution: {integrity: sha512-awJlzfiAMrf8l9AgiLhjXEJ+HvS3VKPxNNQaRwBELGq/vigjJe656tMrhvg4OIlJXtlS+6XPshd2knLwjIWNLw==} + '@storybook/react@8.3.3': + resolution: {integrity: sha512-fHOW/mNqI+sZWttGOE32Q+rAIbN7/Oib091cmE8usOM0z0vPNpywUBtqC2cCQH39vp19bhTsQaSsTcoBSweAHw==} engines: {node: '>=18.0.0'} peerDependencies: + '@storybook/test': 8.3.3 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.3 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==} - peerDependencies: - storybook: ^8.2.6 - - '@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/source-loader@8.3.3': + resolution: {integrity: sha512-NeP7l53mvnnfwi+91vtRaibZer+UJi6gkoaGRCpphL3L+3qVIXN3p41uXhAy+TahdFI2dbrWvLSNgtsvdXVaFg==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/theming@8.1.11': - resolution: {integrity: sha512-Chn/opjO6Rl1isNobutYqAH2PjKNkj09YBw/8noomk6gElSa3JbUTyaG/+JCHA6OG/9kUsqoKDb5cZmAKNq/jA==} + '@storybook/test@8.3.3': + resolution: {integrity: sha512-uZ8nMIovfI2ry989K2+cYAeEVD/3dpjj2+Rbmy7DiZWWVhFALfmqaTRkzZfShLmlH0TFv+rfcBPihGccBtw0FQ==} 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.3 - '@storybook/theming@8.2.6': - resolution: {integrity: sha512-ICnYuLIVsYifVCMQljdHgrp+5vAquNybHxDGWiPeOxBicotwHF8rLhTckD2CdVQbMp0jk6r6jetvjXbFJ2MbvQ==} + '@storybook/theming@8.3.3': + resolution: {integrity: sha512-gWJKetI6XJQgkrvvry4ez10+jLaGNCQKi5ygRPM9N+qrjA3BB8F2LCuFUTBuisa4l64TILDNjfwP/YTWV5+u5A==} peerDependencies: - storybook: ^8.2.6 - - '@storybook/types@8.1.11': - resolution: {integrity: sha512-k9N5iRuY2+t7lVRL6xeu6diNsxO3YI3lS4Juv3RZ2K4QsE/b3yG5ElfJB8DjHDSHwRH4ORyrU71KkOCUVfvtnw==} + storybook: ^8.3.3 - '@storybook/types@8.2.6': - resolution: {integrity: sha512-9Kb5+nui8M7TP/EDGwiuOAHYQPg9U6iQl0OWwgbDIYGBpldwlCwVKAoQWzXz/LlhQijULXIpe1cLvEvJN2Uwhg==} + '@storybook/types@8.3.3': + resolution: {integrity: sha512-wV1kupG1tfTMOXaBrtVHXuqp19vURVDqWTQX6nqkoUFD7Xb1lz/YNVeGP1uT/zJdJy42/HIyoib9JPx9h0Vx9w==} peerDependencies: - storybook: ^8.2.6 + storybook: ^8.3.3 - '@storybook/vue3-vite@8.1.11': - resolution: {integrity: sha512-q0bqh8XEEunaTmp4YiDqM2+YZLwEIevTb5PnNe7G7f2qOiSCE1ncBDnBK717UlCd+iYr34NTztgV2/jIhz1i5w==} + '@storybook/vue3-vite@8.3.3': + resolution: {integrity: sha512-IFcoOGlUGuUkL3rpm9UFs8FK9JX1ZdfGpLXRObVOVRhW3t+MsNLpx4Fqp3a/re6WcCC3yvHzbLXgvGcjpapkbw==} engines: {node: '>=18.0.0'} peerDependencies: + storybook: ^8.3.3 vite: ^4.0.0 || ^5.0.0 - '@storybook/vue3@8.1.11': - resolution: {integrity: sha512-xJtvfLiCOY3UqwDMd0hZdsadPm1q8dwjfM1UN2Q2ssRWNfXzww1oi+Msj902wz9zFZMYVZypfTfgrdRgWmfEjA==} + '@storybook/vue3@8.3.3': + resolution: {integrity: sha512-peu8MFGwmhpXoD3n42qG6TxeVHRhfHZ0/HW4+A6FXSB1c9w0CC4AzHs5f1w3yUvshtexNN5bkw9Q4nSVKtfU7A==} 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.3 vue: ^3.0.0 '@swc/cli@0.3.12': @@ -4706,34 +4495,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 +4537,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,36 +4597,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==} - '@types/escodegen@0.0.6': resolution: {integrity: sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig==} @@ -4861,8 +4615,8 @@ packages: '@types/estree@0.0.51': resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} - '@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 +4630,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 +4660,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 +4732,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 +4741,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 +4768,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 +4813,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 +4894,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 +4906,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 +4928,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 +4948,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 +4956,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 +4976,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 +4984,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 +5002,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 +5014,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 +5025,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 +5040,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 +5058,69 @@ 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.10': + resolution: {integrity: sha512-iXWlk+Cg/ag7gLvY0SfVucU8Kh2CjysYZjhhP70w9qI4MvSox4frrP+vDGvtQuzIcgD8+sxM6lZvCtdxGunTAA==} + + '@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.10': + resolution: {integrity: sha512-DyxHC6qPcktwYGKOIy3XqnHRrrXyWR2u91AjP+nLkADko380srsC2DC3s7Y1Rk6YfOlxOlvEQKa9XXmLI+W4ZA==} + + '@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.10': + resolution: {integrity: sha512-to8E1BgpakV7224ZCm8gz1ZRSyjNCAWEplwFMWKlzCdP9DkMKhRRwt0WkCjY7jkzi/Vz3xgbpeig5Pnbly4Tow==} + '@vue/compiler-ssr@3.4.37': resolution: {integrity: sha512-TyAgYBWrHlFrt4qpdACh8e9Ms6C/AZQ6A6xLJaWrCL8GCX5DxMzxyeFAEMfU/VFr4tylHm+a2NpfJpcd7+20XA==} + '@vue/compiler-ssr@3.5.10': + resolution: {integrity: sha512-hxP4Y3KImqdtyUKXDRSxKSRkSm1H9fCvhojEYrnaoWhE4w/y8vwWhnosJoPPe2AXm5sU7CSbYYAgkt2ZPhDz+A==} + '@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 +5129,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 +5140,40 @@ packages: '@vue/reactivity@3.4.37': resolution: {integrity: sha512-UmdKXGx0BZ5kkxPqQr3PK3tElz6adTey4307NzZ3whZu19i5VavYal7u2FfOmAzlcDVgE8+X0HZ2LxLb/jgbYw==} + '@vue/reactivity@3.5.10': + resolution: {integrity: sha512-kW08v06F6xPSHhid9DJ9YjOGmwNDOsJJQk0ax21wKaUYzzuJGEuoKNU2Ujux8FLMrP7CFJJKsHhXN9l2WOVi2g==} + '@vue/runtime-core@3.4.37': resolution: {integrity: sha512-MNjrVoLV/sirHZoD7QAilU1Ifs7m/KJv4/84QVbE6nyAZGQNVOa1HGxaOzp9YqCG+GpLt1hNDC4RbH+KtanV7w==} + '@vue/runtime-core@3.5.10': + resolution: {integrity: sha512-9Q86I5Qq3swSkFfzrZ+iqEy7Vla325M7S7xc1NwKnRm/qoi1Dauz0rT6mTMmscqx4qz0EDJ1wjB+A36k7rl8mA==} + '@vue/runtime-dom@3.4.37': resolution: {integrity: sha512-Mg2EwgGZqtwKrqdL/FKMF2NEaOHuH+Ks9TQn3DHKyX//hQTYOun+7Tqp1eo0P4Ds+SjltZshOSRq6VsU0baaNg==} + '@vue/runtime-dom@3.5.10': + resolution: {integrity: sha512-t3x7ht5qF8ZRi1H4fZqFzyY2j+GTMTDxRheT+i8M9Ph0oepUxoadmbwlFwMoW7RYCpNQLpP2Yx3feKs+fyBdpA==} + '@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.10': + resolution: {integrity: sha512-IVE97tt2kGKwHNq9yVO0xdh1IvYfZCShvDSy46JIh5OQxP1/EXSpoDqetVmyIzL7CYOWnnmMkVqd7YK2QSWkdw==} + peerDependencies: + vue: 3.5.10 '@vue/shared@3.4.37': resolution: {integrity: sha512-nIh8P2fc3DflG8+5Uw8PT/1i17ccFn0xxN/5oE9RfV5SVnd7G0XEFRwakrnNFE/jlS95fpGXDVG5zDETS26nmg==} + '@vue/shared@3.5.10': + resolution: {integrity: sha512-VkkBhU97Ki+XJ0xvl4C9YJsIZ2uIlQ7HqPpZOS3m9VCvmROPaChZU6DexdMJqvz9tbgG+4EtFVrSuailUq5KGQ==} + + '@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 +5186,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 +5237,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 +5274,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 +5333,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 +5354,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 +5377,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 +5408,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 +5436,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 +5451,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 +5479,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 +5498,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 +5518,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 +5552,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 +5568,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 +5584,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 +5657,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 +5667,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 +5706,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 +5758,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 +5792,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 +5820,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 +5831,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 +5850,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.4: + resolution: {integrity: sha512-nfgDpW5gQ4FtgV1lZXXfqLjONKDCh2K4vwI3dbZrtU1ObOL9THyAzpIdnK9LRcNSeisDLX+XFCryfMg1Ql2U2g==} hasBin: true peerDependencies: '@chromatic-com/cypress': ^0.*.* || ^1.0.0 @@ -6215,17 +5912,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 +5968,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 +6005,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 +6053,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 +6084,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 +6105,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 +6166,13 @@ 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 + + cypress@13.15.0: + resolution: {integrity: sha512-53aO7PwOfi604qzOkCSzNlWquCynLlKE/rmmpSPcziRH6LNfaDUAklQT6WJIsD8ywxlIy+uVZsnTMCCQVd2kTw==} engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} hasBin: true @@ -6508,6 +6191,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 +6247,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 +6297,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 +6311,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 +6327,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 +6354,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 +6362,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 +6407,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 +6482,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 +6518,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 +6528,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 +6574,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 +6599,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 +6646,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 +6667,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 +6683,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 +6708,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 +6736,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 +6802,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 +6825,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 +6849,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 +6868,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 +6884,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 +6894,22 @@ packages: resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} hasBin: true - fastify-plugin@4.5.0: - resolution: {integrity: sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==} + fast-xml-parser@4.5.0: + resolution: {integrity: sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==} + hasBin: true + + fastify-plugin@4.5.1: + resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==} - fastify-raw-body@4.3.0: - resolution: {integrity: sha512-F4o8ZIMVx4YoxGfwrZys6wyjl40gF3Yv6AWWRy62ozFAyZBSS831/uyyCAqKYw3tR73g180ryG98yih6To1PUQ==} + 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 +6917,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 +6928,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 +6940,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 +6971,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 +7009,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 +7022,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 +7114,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 +7140,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 +7175,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 +7192,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 +7209,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 +7244,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 +7256,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 +7291,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 +7325,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 +7344,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==} @@ -7602,13 +7358,14 @@ packages: hash-sum@2.0.0: resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==} - hashlru@2.3.0: - resolution: {integrity: sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==} - hasown@2.0.0: 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 +7385,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 +7418,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 +7446,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 +7474,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 +7494,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 +7563,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 +7586,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 +7604,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 +7638,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 +7673,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 +7720,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 +7730,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 +7761,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 +7781,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 +7805,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 +7817,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 +7861,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 +7900,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 +8050,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 +8088,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 +8101,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 +8112,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 +8177,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 +8220,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 +8232,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 +8255,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 +8263,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 +8308,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 +8360,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 +8453,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 +8469,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 +8574,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 +8591,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 +8724,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 +8734,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 +8769,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 +8841,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 +8869,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 +8903,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 +8914,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 +8923,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 +8966,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 +8986,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 +9025,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 +9041,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.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} engines: {node: '>= 0.4'} - object.groupby@1.0.1: - resolution: {integrity: sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==} + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} - object.values@1.1.7: - resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} + object.values@1.2.0: + resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} obliterator@2.0.4: @@ -9300,10 +9077,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 +9109,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 +9119,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 +9156,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 +9207,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 +9226,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 +9253,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 +9309,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 +9320,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 +9339,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 +9361,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 +9376,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 +9397,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 +9427,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 +9581,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 +9600,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 +9664,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 +9674,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 +9685,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 +9806,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 +9846,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 +9861,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 +9980,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 +10075,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 +10090,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 +10103,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.5: + resolution: {integrity: sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -10387,6 +10127,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 +10140,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 +10157,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 +10175,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 +10207,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 +10252,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 +10281,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 +10396,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 +10404,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 +10437,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 +10488,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 +10507,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 +10523,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 +10542,8 @@ packages: react-dom: optional: true - storybook@8.2.6: - resolution: {integrity: sha512-8j30wDxQmkcqI0fWcSYFsUCjErsY1yTWbTW+yjbwM8DyW18Cud6CwbFRCxjFsH+2M0CjP6Pqs/m1PGI0vcQscQ==} + storybook@8.3.3: + resolution: {integrity: sha512-FG2KAVQN54T9R6voudiEftehtkXtLO+YVGP2gBPfacEdDQjY++ld7kTbHzpTT/bpCDx7Yq3dqOegLm9arVJfYw==} hasBin: true stream-browserify@3.0.0: @@ -10778,10 +10555,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 +10588,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 +10675,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 +10717,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 +10741,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 +10766,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.169.0: + resolution: {integrity: sha512-Ed906MA3dR4TS5riErd4QBsRGPcx+HBDX2O5yYE5GqJeFQTPU+M56Va/f/Oph9X7uZo3W3o4l2ZhBZ6f6qUv0w==} throttle-debounce@5.0.2: resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} @@ -11013,10 +10785,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 +10795,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 +10889,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 +10910,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 +10963,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 +10979,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 +11082,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 +11114,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'} - - 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'} + undici@6.19.8: + resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==} + engines: {node: '>=18.17'} - 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 +11143,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 +11228,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 +11241,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 +11270,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.8: + resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -11501,6 +11279,7 @@ packages: less: '*' lightningcss: ^1.21.0 sass: '*' + sass-embedded: '*' stylus: '*' sugarss: '*' terser: ^5.4.0 @@ -11513,6 +11292,8 @@ packages: optional: true sass: optional: true + sass-embedded: + optional: true stylus: optional: true sugarss: @@ -11593,8 +11374,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 +11399,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 +11407,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 +11421,14 @@ packages: typescript: optional: true + vue@3.5.10: + resolution: {integrity: sha512-Vy2kmJwHPlouC/tSnIgXVg03SG+9wSqT1xu1Vehc+ChsXsRd7jLkKgMltVEFOzUdBr3uFwBCG+41LJtfAcBRng==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + vuedraggable@4.1.0: resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==} peerDependencies: @@ -11655,29 +11438,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 +11513,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 +11547,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 +11562,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 +11672,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 +11684,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 +12204,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 +12264,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 +12280,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 +12304,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 +12335,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 +12348,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 +12358,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 +12390,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)': - 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)': + '@babel/parser@7.25.6': 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 +12415,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 +12494,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 +12528,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@6.0.0(@bull-board/ui@6.0.0)': dependencies: - '@bull-board/ui': 5.21.1 + '@bull-board/ui': 6.0.0 redis-info: 3.1.0 - '@bull-board/fastify@5.21.1': + '@bull-board/fastify@6.0.0': dependencies: - '@bull-board/api': 5.21.1(@bull-board/ui@5.21.1) - '@bull-board/ui': 5.21.1 - '@fastify/static': 6.12.0 - '@fastify/view': 8.2.0 + '@bull-board/api': 6.0.0(@bull-board/ui@6.0.0) + '@bull-board/ui': 6.0.0 + '@fastify/static': 8.0.1 + '@fastify/view': 10.0.1 ejs: 3.1.10 - '@bull-board/ui@5.21.1': + '@bull-board/ui@6.0.0': dependencies: - '@bull-board/api': 5.21.1(@bull-board/ui@5.21.1) + '@bull-board/api': 6.0.0(@bull-board/ui@6.0.0) '@bundled-es-modules/cookie@2.0.0': dependencies: @@ -13481,73 +12573,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 +12647,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 +12675,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 +12696,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 +12711,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 +12726,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 +12741,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 +12756,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 +12771,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 +12786,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 +12801,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 +12816,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 +12831,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 +12846,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 +12861,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 +12876,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 +12891,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 +12906,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 +12921,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 +12936,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 +12951,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 +12972,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 +12987,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 +13002,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 +13017,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 +13032,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 +13054,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(supports-color@8.1.1) + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-array@0.18.0': + dependencies: + '@eslint/object-schema': 2.1.4 + debug: 4.3.7(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -13899,7 +13070,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(supports-color@8.1.1) espree: 10.1.0 globals: 14.0.0 ignore: 5.3.1 @@ -13910,115 +13081,124 @@ 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/accept-negotiator@2.0.0': {} - '@fastify/accepts@4.3.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': + '@fastify/send@3.1.1': dependencies: - '@lukeed/ms': 2.0.1 + '@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': + '@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 - glob: 8.1.0 - p-limit: 3.1.0 + fastify-plugin: 5.0.0 + fastq: 1.17.1 + glob: 11.0.0 - '@fastify/static@7.0.4': + '@fastify/static@8.0.1': 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@8.2.0': + '@fastify/view@10.0.0': dependencies: - fastify-plugin: 4.5.0 - hashlru: 2.3.0 + fastify-plugin: 5.0.0 + toad-cache: 3.7.0 - '@fastify/view@9.1.0': + '@fastify/view@10.0.1': dependencies: - fastify-plugin: 4.5.0 + fastify-plugin: 5.0.0 toad-cache: 3.7.0 '@github/webauthn-json@2.1.1': {} @@ -14051,79 +13231,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 +13331,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 +13387,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 +13443,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 +13471,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 +13493,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 +13502,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 +13518,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.0(typescript@5.6.2)(vite@5.4.8(@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.8(@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 +13555,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 @@ -14405,7 +13575,7 @@ snapshots: '@lukeed/csprng@1.0.1': {} - '@lukeed/ms@2.0.1': {} + '@lukeed/ms@2.0.2': {} '@mapbox/node-pre-gyp@1.0.9(encoding@0.1.13)': dependencies: @@ -14435,23 +13605,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 +13642,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 +13706,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 +13816,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: @@ -14750,7 +13929,7 @@ snapshots: '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.25.1 - semver: 7.6.0 + semver: 7.6.3 transitivePeerDependencies: - supports-color @@ -14842,7 +14021,7 @@ snapshots: '@types/shimmer': 1.0.5 import-in-the-middle: 1.7.1 require-in-the-middle: 7.3.0 - semver: 7.6.0 + semver: 7.6.3 shimmer: 1.2.1 transitivePeerDependencies: - supports-color @@ -14855,7 +14034,7 @@ snapshots: '@types/shimmer': 1.0.5 import-in-the-middle: 1.10.0 require-in-the-middle: 7.3.0 - semver: 7.6.0 + semver: 7.6.3 shimmer: 1.2.1 transitivePeerDependencies: - supports-color @@ -14950,7 +14129,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 +14141,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)': + '@readme/openapi-schemas@3.1.0': {} + + '@rollup/plugin-json@6.1.0(rollup@4.22.5)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.19.1) + '@rollup/pluginutils': 5.1.2(rollup@4.22.5) optionalDependencies: - rollup: 4.19.1 + rollup: 4.22.5 - '@rollup/plugin-replace@5.0.7(rollup@4.19.1)': + '@rollup/plugin-replace@5.0.7(rollup@4.22.5)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.19.1) + '@rollup/pluginutils': 5.1.2(rollup@4.22.5) magic-string: 0.30.10 optionalDependencies: - rollup: 4.19.1 + rollup: 4.22.5 - '@rollup/pluginutils@5.1.0(rollup@4.19.1)': + '@rollup/pluginutils@5.1.2(rollup@4.22.5)': 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.5 - '@rollup/rollup-android-arm-eabi@4.19.1': + '@rollup/rollup-android-arm-eabi@4.22.5': optional: true - '@rollup/rollup-android-arm64@4.19.1': + '@rollup/rollup-android-arm64@4.22.5': optional: true - '@rollup/rollup-darwin-arm64@4.19.1': + '@rollup/rollup-darwin-arm64@4.22.5': optional: true - '@rollup/rollup-darwin-x64@4.19.1': + '@rollup/rollup-darwin-x64@4.22.5': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.19.1': + '@rollup/rollup-linux-arm-gnueabihf@4.22.5': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.19.1': + '@rollup/rollup-linux-arm-musleabihf@4.22.5': optional: true - '@rollup/rollup-linux-arm64-gnu@4.19.1': + '@rollup/rollup-linux-arm64-gnu@4.22.5': optional: true - '@rollup/rollup-linux-arm64-musl@4.19.1': + '@rollup/rollup-linux-arm64-musl@4.22.5': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.19.1': + '@rollup/rollup-linux-powerpc64le-gnu@4.22.5': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.19.1': + '@rollup/rollup-linux-riscv64-gnu@4.22.5': optional: true - '@rollup/rollup-linux-s390x-gnu@4.19.1': + '@rollup/rollup-linux-s390x-gnu@4.22.5': optional: true - '@rollup/rollup-linux-x64-gnu@4.19.1': + '@rollup/rollup-linux-x64-gnu@4.22.5': optional: true - '@rollup/rollup-linux-x64-musl@4.19.1': + '@rollup/rollup-linux-x64-musl@4.22.5': optional: true - '@rollup/rollup-win32-arm64-msvc@4.19.1': + '@rollup/rollup-win32-arm64-msvc@4.22.5': optional: true - '@rollup/rollup-win32-ia32-msvc@4.19.1': + '@rollup/rollup-win32-ia32-msvc@4.22.5': optional: true - '@rollup/rollup-win32-x64-msvc@4.19.1': + '@rollup/rollup-win32-x64-msvc@4.22.5': 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 +14248,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 +14342,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 +14374,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 +14763,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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/csf-plugin': 8.3.3(storybook@8.3.3(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.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-backgrounds': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-controls': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-docs': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-highlight': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-measure': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-outline': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-toolbars': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/addon-viewport': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + storybook: 8.3.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/test': 8.3.3(storybook@8.3.3(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.3(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.3(react@18.3.1)(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(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 +14889,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.3(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,221 +14897,45 @@ 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.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.8(@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.3(storybook@8.3.3(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 + express: 4.21.0 find-cache-dir: 3.3.2 fs-extra: 11.1.1 - magic-string: 0.30.10 + magic-string: 0.30.11 + storybook: 8.3.3(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.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 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))': + '@storybook/components@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': 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 - transitivePeerDependencies: - - supports-color + storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) - '@storybook/channels@8.1.11': + '@storybook/core-events@8.3.3(storybook@8.3.3(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/client-logger@8.1.11': - dependencies: - '@storybook/global': 5.0.0 - - '@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: 8.3.3(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)': + '@storybook/core@8.3.3(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) - express: 4.19.2 + esbuild: 0.23.1 + esbuild-register: 3.5.0(esbuild@0.23.1) + express: 4.21.0 + 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 +14943,154 @@ snapshots: - supports-color - utf-8-validate - '@storybook/csf-plugin@8.1.11': - dependencies: - '@storybook/csf-tools': 8.1.11 - 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))': + '@storybook/csf-plugin@8.3.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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)': - 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/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))': - 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/manager@8.1.11': {} - - '@storybook/node-logger@8.1.11': {} - - '@storybook/preview-api@8.1.11': + '@storybook/manager-api@8.3.3(storybook@8.3.3(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/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: 8.3.3(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/preview-api@8.3.3(storybook@8.3.3(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.3(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.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(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.3(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.3(@storybook/test@8.3.3(storybook@8.3.3(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.5)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.8(@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.0(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)) + '@rollup/pluginutils': 5.1.2(rollup@4.22.5) + '@storybook/builder-vite': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)) + '@storybook/react': 8.3.3(@storybook/test@8.3.3(storybook@8.3.3(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.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2) find-up: 5.0.0 - magic-string: 0.30.10 + magic-string: 0.30.11 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.3(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.8(@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.3(@storybook/test@8.3.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/preview-api': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/react-dom-shim': 8.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/theming': 8.3.3(storybook@8.3.3(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) + semver: 7.6.3 + storybook: 8.3.3(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/router@8.1.11': - dependencies: - '@storybook/client-logger': 8.1.11 - memoizerific: 1.11.3 - qs: 6.11.1 + '@storybook/test': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + typescript: 5.6.2 - '@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.3(storybook@8.3.3(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/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: 8.3.3(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/test@8.3.3(storybook@8.3.3(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/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))': - 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/instrumenter': 8.3.3(storybook@8.3.3(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.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + util: 0.12.5 - '@storybook/types@8.1.11': + '@storybook/theming@8.3.3(storybook@8.3.3(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.3(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/types@8.3.3(storybook@8.3.3(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.3(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))': + '@storybook/vue3-vite@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.10(typescript@5.6.2))': 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.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)) + '@storybook/vue3': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.5.10(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)) + magic-string: 0.30.11 + storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + typescript: 5.6.2 + vite: 5.4.8(@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.10(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.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.5.10(typescript@5.6.2))': dependencies: - '@storybook/docs-tools': 8.1.11(encoding@0.1.13)(prettier@3.3.3) + '@storybook/components': 8.3.3(storybook@8.3.3(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.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/preview-api': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/theming': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@vue/compiler-core': 3.5.7 + storybook: 8.3.3(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.10(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 +15361,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 +15374,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 +15383,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.10)(@vue/server-renderer@3.5.10(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(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.10(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(typescript@5.6.2)) + vue: 3.5.10(typescript@5.6.2) optionalDependencies: - '@vue/compiler-sfc': 3.4.37 + '@vue/compiler-sfc': 3.5.10 transitivePeerDependencies: - '@vue/server-renderer' @@ -16575,6 +15416,8 @@ snapshots: '@twemoji/parser@15.0.0': {} + '@twemoji/parser@15.1.0': {} + '@twemoji/parser@15.1.1': {} '@types/accepts@1.3.7': @@ -16644,40 +15487,24 @@ 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: @@ -16701,7 +15528,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 +15563,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 +15631,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 +15643,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 +15672,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 +15686,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 +15722,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 +15787,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,107 +15802,131 @@ 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 + '@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.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 - semver: 7.5.4 + semver: 7.6.0 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)': + '@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.6.2 - '@typescript-eslint/parser': 7.1.0(eslint@9.8.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/visitor-keys': 7.1.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.5.4) + '@typescript-eslint/scope-manager': 7.17.0 + '@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.11.0 graphemer: 1.4.0 - ignore: 5.2.4 + ignore: 5.3.1 natural-compare: 1.4.0 - semver: 7.6.0 - ts-api-utils: 1.0.1(typescript@5.3.3) + ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: - typescript: 5.3.3 + typescript: 5.5.4 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.6.2))(eslint@9.11.0)(typescript@5.6.2)': 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.6.2) '@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.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.8.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.5.4) + ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 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.8.0)(typescript@5.6.2))(eslint@9.8.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-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.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/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/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/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.8.0 + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - supports-color '@typescript-eslint/scope-manager@7.1.0': dependencies: @@ -17087,67 +15938,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)': + '@typescript-eslint/type-utils@7.1.0(eslint@9.11.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) + '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3) + '@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.8.0 + 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.1.0(eslint@9.8.0)(typescript@5.3.3)': + '@typescript-eslint/type-utils@7.17.0(eslint@9.11.0)(typescript@5.5.4)': 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 - ts-api-utils: 1.0.1(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 7.17.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.11.0 + ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: - typescript: 5.3.3 + typescript: 5.5.4 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.6.2)': 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/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.8.0 - ts-api-utils: 1.3.0(typescript@5.5.4) + eslint: 9.11.0 + ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@6.11.0': {} - - '@typescript-eslint/types@7.1.0': {} - - '@typescript-eslint/types@7.17.0': {} - - '@typescript-eslint/typescript-estree@6.11.0(typescript@5.3.3)': + '@typescript-eslint/type-utils@7.17.0(eslint@9.8.0)(typescript@5.6.2)': dependencies: - '@typescript-eslint/types': 6.11.0 - '@typescript-eslint/visitor-keys': 6.11.0 + '@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) - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.3.3) + eslint: 9.8.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/types@7.1.0': {} + + '@typescript-eslint/types@7.17.0': {} + '@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 +16020,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 +16094,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.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.10(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.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) + vue: 3.5.10(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.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(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)(sass@1.79.3)(terser@5.33.0) transitivePeerDependencies: - supports-color @@ -17259,6 +16143,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 @@ -17267,7 +16166,7 @@ snapshots: '@vitest/snapshot@1.6.0': dependencies: - magic-string: 0.30.10 + magic-string: 0.30.11 pathe: 1.1.2 pretty-format: 29.7.0 @@ -17275,6 +16174,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,65 +16185,83 @@ 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': + '@vue/compiler-core@3.4.37': dependencies: - '@babel/parser': 7.24.7 - '@vue/shared': 3.4.31 - entities: 4.5.0 + '@babel/parser': 7.25.6 + '@vue/shared': 3.4.37 + entities: 5.0.0 estree-walker: 2.0.2 - source-map-js: 1.2.0 + source-map-js: 1.2.1 - '@vue/compiler-core@3.4.34': + '@vue/compiler-core@3.5.10': dependencies: - '@babel/parser': 7.24.7 - '@vue/shared': 3.4.34 + '@babel/parser': 7.25.6 + '@vue/shared': 3.5.10 entities: 4.5.0 estree-walker: 2.0.2 - source-map-js: 1.2.0 + source-map-js: 1.2.1 - '@vue/compiler-core@3.4.37': + '@vue/compiler-core@3.5.7': dependencies: - '@babel/parser': 7.24.7 - '@vue/shared': 3.4.37 - entities: 5.0.0 + '@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.34': - dependencies: - '@vue/compiler-core': 3.4.34 - '@vue/shared': 3.4.34 + source-map-js: 1.2.1 '@vue/compiler-dom@3.4.37': dependencies: '@vue/compiler-core': 3.4.37 '@vue/shared': 3.4.37 + '@vue/compiler-dom@3.5.10': + dependencies: + '@vue/compiler-core': 3.5.10 + '@vue/shared': 3.5.10 + + '@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 @@ -17349,56 +16270,80 @@ snapshots: '@vue/compiler-ssr': 3.4.37 '@vue/shared': 3.4.37 estree-walker: 2.0.2 - magic-string: 0.30.10 - postcss: 8.4.40 + magic-string: 0.30.11 + postcss: 8.4.47 source-map-js: 1.2.0 + '@vue/compiler-sfc@3.5.10': + dependencies: + '@babel/parser': 7.25.6 + '@vue/compiler-core': 3.5.10 + '@vue/compiler-dom': 3.5.10 + '@vue/compiler-ssr': 3.5.10 + '@vue/shared': 3.5.10 + estree-walker: 2.0.2 + magic-string: 0.30.11 + postcss: 8.4.47 + source-map-js: 1.2.1 + '@vue/compiler-ssr@3.4.37': dependencies: '@vue/compiler-dom': 3.4.37 '@vue/shared': 3.4.37 + '@vue/compiler-ssr@3.5.10': + dependencies: + '@vue/compiler-dom': 3.5.10 + '@vue/shared': 3.5.10 + '@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.5.7 + '@vue/shared': 3.5.7 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.10': + dependencies: + '@vue/shared': 3.5.10 + '@vue/runtime-core@3.4.37': dependencies: '@vue/reactivity': 3.4.37 '@vue/shared': 3.4.37 + '@vue/runtime-core@3.5.10': + dependencies: + '@vue/reactivity': 3.5.10 + '@vue/shared': 3.5.10 + '@vue/runtime-dom@3.4.37': dependencies: '@vue/reactivity': 3.4.37 @@ -17406,43 +16351,41 @@ snapshots: '@vue/shared': 3.4.37 csstype: 3.1.3 + '@vue/runtime-dom@3.5.10': + dependencies: + '@vue/reactivity': 3.5.10 + '@vue/runtime-core': 3.5.10 + '@vue/shared': 3.5.10 + 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.10(vue@3.5.10(typescript@5.6.2))': + dependencies: + '@vue/compiler-ssr': 3.5.10 + '@vue/shared': 3.5.10 + vue: 3.5.10(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.10': {} + + '@vue/shared@3.5.7': {} + + '@vue/test-utils@2.4.1(@vue/server-renderer@3.5.10(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(typescript@5.6.2))': dependencies: js-beautify: 1.14.9 - vue: 3.4.37(typescript@5.5.4) + vue: 3.5.10(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.10(vue@3.5.10(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 +16426,6 @@ snapshots: acorn@8.12.1: {} - address@1.2.2: {} - adm-zip@0.5.10: optional: true @@ -17498,6 +16439,7 @@ snapshots: debug: 4.3.5(supports-color@8.1.1) transitivePeerDependencies: - supports-color + optional: true agent-base@7.1.0: dependencies: @@ -17528,14 +16470,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 +16535,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 +16564,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 +16588,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 +16635,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 +16677,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 + tslib: 2.7.0 astral-regex@2.0.0: {} - astring@1.8.6: {} + astring@1.9.0: {} async-mutex@0.5.0: dependencies: @@ -17755,14 +16705,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 +16726,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 +16740,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 +16770,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 +16812,6 @@ snapshots: dependencies: open: 8.4.2 - big-integer@1.6.51: {} - bin-check@4.1.0: dependencies: execa: 0.7.0 @@ -17910,12 +16830,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 +16855,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 +16953,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 +16971,6 @@ snapshots: dependencies: streamsearch: 1.1.0 - bytes@3.0.0: {} - bytes@3.1.2: {} cac@6.7.14: {} @@ -18076,7 +17001,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 +17031,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 +17086,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 +17124,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 +17163,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 +17204,7 @@ snapshots: chownr@2.0.0: {} - chromatic@11.5.6: {} + chromatic@11.10.4: {} ci-info@3.7.1: {} @@ -18305,18 +17262,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 +17309,8 @@ snapshots: commander@10.0.1: {} + commander@12.1.0: {} + commander@2.20.3: {} commander@6.2.1: {} @@ -18384,22 +17335,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 +17377,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 +17414,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 +17447,9 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crypto-random-string@4.0.0: - dependencies: - type-fest: 1.4.0 - - css-declaration-sorter@7.2.0(postcss@8.4.40): + css-declaration-sorter@7.2.0(postcss@8.4.47): dependencies: - postcss: 8.4.40 + postcss: 8.4.47 css-select@5.1.0: dependencies: @@ -18548,49 +17475,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 +17533,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 @@ -18651,6 +17578,51 @@ snapshots: untildify: 4.0.0 yauzl: 2.10.0 + cypress@13.15.0: + dependencies: + '@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 + arch: 2.2.0 + blob-util: 2.0.2 + bluebird: 3.7.2 + buffer: 5.7.1 + cachedir: 2.3.0 + chalk: 4.1.2 + check-more-types: 2.24.0 + cli-cursor: 3.1.0 + cli-table3: 0.6.3 + commander: 6.2.1 + common-tags: 1.8.2 + dayjs: 1.11.10 + debug: 4.3.7(supports-color@8.1.1) + enquirer: 2.3.6 + eventemitter2: 6.4.7 + execa: 4.1.0 + executable: 4.1.1 + extract-zip: 2.0.1(supports-color@8.1.1) + figures: 3.2.0 + fs-extra: 9.1.0 + getos: 3.2.1 + is-ci: 3.0.1 + is-installed-globally: 0.4.0 + lazy-ass: 1.6.0 + listr2: 3.14.0(enquirer@2.3.6) + lodash: 4.17.21 + log-symbols: 4.1.0 + minimist: 1.2.8 + ospath: 1.2.2 + pretty-bytes: 5.6.0 + process: 0.11.10 + proxy-from-env: 1.0.0 + request-progress: 3.0.0 + semver: 7.6.3 + supports-color: 8.1.1 + tmp: 0.2.3 + untildify: 4.0.0 + yauzl: 2.10.0 + dashdash@1.14.1: dependencies: assert-plus: 1.0.0 @@ -18664,6 +17636,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 +17678,24 @@ 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(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + decamelize-keys@1.1.1: dependencies: decamelize: 1.2.0 @@ -18737,6 +17739,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,16 +17765,13 @@ snapshots: deepmerge@4.2.2: {} - default-browser-id@3.0.0: - dependencies: - bplist-parser: 0.2.0 - untildify: 4.0.0 + defer-to-connect@2.0.1: {} - defaults@1.0.4: + define-data-property@1.1.4: dependencies: - clone: 1.0.4 - - defer-to-connect@2.0.1: {} + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 define-lazy-prop@2.0.0: {} @@ -18779,7 +17780,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 +17799,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 +17837,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 +17851,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 +17919,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 +17949,6 @@ snapshots: env-paths@2.2.1: {} - envinfo@7.8.1: {} - err-code@2.0.3: {} error-ex@1.3.2: @@ -18941,16 +17960,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 +17981,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 +17997,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 +18066,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 +18104,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 + debug: 4.3.7(supports-color@8.1.1) + esbuild: 0.23.1 transitivePeerDependencies: - supports-color @@ -19129,8 +18215,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 +18280,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.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.5.4) + '@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.8.0 + eslint: 9.11.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.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.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.11.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-import@2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) + '@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.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.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.6.2) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-vue@9.27.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.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 +18405,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(supports-color@8.1.1) + 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 +18457,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(supports-color@8.1.1) escape-string-regexp: 4.0.0 eslint-scope: 8.0.2 eslint-visitor-keys: 4.0.0 @@ -19290,10 +18497,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 +18511,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 esutils@2.0.3: {} @@ -19392,16 +18595,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 +18662,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 +18721,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 +18735,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 +18759,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 +18767,37 @@ snapshots: dependencies: strnum: 1.0.5 - fastify-plugin@4.5.0: {} + fast-xml-parser@4.5.0: + dependencies: + strnum: 1.0.5 + + fastify-plugin@4.5.1: {} - fastify-raw-body@4.3.0: + 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 +18807,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 +18820,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 +18832,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 +18877,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 +18895,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 +18933,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(supports-color@8.1.1) for-each@0.3.3: dependencies: @@ -19793,11 +19028,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 +19068,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 +19109,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 +19134,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 +19149,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 +19157,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 +19195,7 @@ snapshots: globals@14.0.0: {} - globals@15.8.0: {} + globals@15.9.0: {} globalthis@1.0.3: dependencies: @@ -19977,21 +19210,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 +19269,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 +19278,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 +19303,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 @@ -20099,12 +19330,14 @@ snapshots: hash-sum@2.0.0: {} - hashlru@2.3.0: {} - hasown@2.0.0: 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 +19356,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 +19378,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 +19392,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 +19424,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,17 +19456,11 @@ 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) transitivePeerDependencies: - supports-color @@ -20239,7 +19480,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 +19560,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 +19589,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 +19616,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 +19650,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 @@ -20419,7 +19679,7 @@ snapshots: is-generator-function@1.0.10: dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 is-glob@4.0.3: dependencies: @@ -20430,8 +19690,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 +19698,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 +19716,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 +19733,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 +19749,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 +19765,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 +19779,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 +19800,6 @@ snapshots: isexe@3.1.1: {} - isobject@3.0.1: {} - isstream@0.1.2: {} istanbul-lib-coverage@3.2.2: {} @@ -20602,12 +19859,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 +19942,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 +20001,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 +20025,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 +20135,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 +20202,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 +20240,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 +20328,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 +20409,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 +20450,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 +20461,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 +20489,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 +20497,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 +20537,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: {} @@ -21310,12 +20574,16 @@ snapshots: magic-string@0.27.0: dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 magic-string@0.30.10: 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 +20592,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 +20743,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 +20753,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 +20772,8 @@ snapshots: merge-descriptors@1.0.1: {} + merge-descriptors@1.0.3: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -21693,7 +20960,7 @@ snapshots: micromark@4.0.0: dependencies: '@types/debug': 4.1.12 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.0 @@ -21712,7 +20979,7 @@ snapshots: transitivePeerDependencies: - supports-color - micromatch@4.0.7: + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 @@ -21725,6 +20992,8 @@ snapshots: mime@1.6.0: {} + mime@2.6.0: {} + mime@3.0.0: {} mimic-fn@2.1.0: {} @@ -21842,7 +21111,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 +21119,6 @@ snapshots: module-details-from-path@1.0.3: {} - mri@1.2.0: {} - ms@2.0.0: {} ms@2.1.2: {} @@ -21876,12 +21143,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 +21168,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 +21255,6 @@ snapshots: negotiator@0.6.3: {} - neo-async@2.6.2: {} - nested-property@4.0.0: {} netmask@2.0.2: {} @@ -21997,14 +21284,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 +21310,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 +21318,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 +21329,7 @@ snapshots: node-releases@2.0.14: {} - nodemailer@6.9.14: {} + nodemailer@6.9.15: {} nodemon@3.0.2: dependencies: @@ -22063,10 +21344,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 +21394,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 +21412,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 +21454,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 +21470,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 +21510,6 @@ snapshots: dependencies: ee-first: 1.1.1 - on-headers@1.0.2: {} - once@1.4.0: dependencies: wrappy: 1.0.2 @@ -22266,18 +21557,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 +21565,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 +21593,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 +21646,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 +21662,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 +21679,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 +21721,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 +21753,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 +21773,8 @@ snapshots: picocolors@1.0.1: {} + picocolors@1.1.0: {} + picomatch@2.3.1: {} pid-port@1.0.0: @@ -22501,8 +21783,6 @@ snapshots: pify@2.3.0: {} - pify@4.0.1: {} - pino-abstract-transport@1.2.0: dependencies: readable-stream: 4.3.0 @@ -22532,18 +21812,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,176 +21840,173 @@ 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: + postcss@8.4.40: dependencies: nanoid: 3.3.7 - picocolors: 1.0.0 + picocolors: 1.0.1 source-map-js: 1.2.0 - postcss@8.4.40: + postcss@8.4.47: dependencies: nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 + picocolors: 1.1.0 + source-map-js: 1.2.1 postgres-array@2.0.0: {} @@ -22779,8 +22048,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 +22067,7 @@ snapshots: transitivePeerDependencies: - supports-color - proc-log@3.0.0: {} + proc-log@4.2.0: {} process-exists@5.0.0: dependencies: @@ -22808,10 +22075,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 +22215,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 +22245,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 +22260,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,15 +22284,15 @@ 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: '@babel/core': 7.24.7 '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/types': 7.25.6 '@types/babel__core': 7.20.0 '@types/babel__traverse': 7.20.0 '@types/doctrine': 0.0.9 @@ -23125,7 +22392,7 @@ snapshots: esprima: 4.0.1 source-map: 0.6.1 tiny-invariant: 1.3.3 - tslib: 2.6.3 + tslib: 2.7.0 reconnecting-websocket@4.4.0: {} @@ -23148,38 +22415,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: - 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: + regexp.prototype.flags@1.5.2: 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: @@ -23263,7 +22514,7 @@ snapshots: require-in-the-middle@7.3.0: dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) module-details-from-path: 1.0.3 resolve: 1.22.8 transitivePeerDependencies: @@ -23306,7 +22557,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 +22565,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 +22577,26 @@ snapshots: glob: 7.2.3 optional: true - rollup@4.19.1: + rollup@4.22.5: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 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.5 + '@rollup/rollup-android-arm64': 4.22.5 + '@rollup/rollup-darwin-arm64': 4.22.5 + '@rollup/rollup-darwin-x64': 4.22.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.22.5 + '@rollup/rollup-linux-arm-musleabihf': 4.22.5 + '@rollup/rollup-linux-arm64-gnu': 4.22.5 + '@rollup/rollup-linux-arm64-musl': 4.22.5 + '@rollup/rollup-linux-powerpc64le-gnu': 4.22.5 + '@rollup/rollup-linux-riscv64-gnu': 4.22.5 + '@rollup/rollup-linux-s390x-gnu': 4.22.5 + '@rollup/rollup-linux-x64-gnu': 4.22.5 + '@rollup/rollup-linux-x64-musl': 4.22.5 + '@rollup/rollup-win32-arm64-msvc': 4.22.5 + '@rollup/rollup-win32-ia32-msvc': 4.22.5 + '@rollup/rollup-win32-x64-msvc': 4.22.5 fsevents: 2.3.3 rrweb-cssom@0.6.0: {} @@ -23369,8 +22618,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 +22636,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-regex-test@1.0.3: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 is-regex: 1.1.4 - safe-regex2@3.1.0: + safe-regex2@4.0.0: dependencies: - ret: 0.4.3 + ret: 0.5.0 safe-stable-stringify@2.4.2: {} @@ -23399,9 +22661,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 +22681,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 +22703,8 @@ snapshots: dependencies: lru-cache: 6.0.0 + semver@7.6.3: {} + send@0.18.0: dependencies: debug: 2.6.9 @@ -23457,6 +22723,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 +22750,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 +22788,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 +22839,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 +22867,7 @@ snapshots: simple-update-notifier@2.0.0: dependencies: - semver: 7.5.4 + semver: 7.6.0 sinon@16.1.3: dependencies: @@ -23625,8 +22937,6 @@ snapshots: slash@3.0.0: {} - slash@5.1.0: {} - slice-ansi@3.0.0: dependencies: ansi-styles: 4.3.0 @@ -23639,6 +22949,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 +22982,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 +23036,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 +23060,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(supports-color@8.1.1) 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 +23081,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.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/components@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/core-events@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/manager-api@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/preview-api@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/theming@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/types@8.3.3(storybook@8.3.3(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.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/components': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/core-events': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/manager-api': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/preview-api': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/theming': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/types': 8.3.3(storybook@8.3.3(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.3(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.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) transitivePeerDependencies: - - '@babel/preset-env' - bufferutil - supports-color - utf-8-validate @@ -23821,8 +23117,6 @@ snapshots: transitivePeerDependencies: - supports-color - stream-wormhole@1.1.0: {} - streamsearch@1.1.0: {} streamx@2.15.0: @@ -23855,22 +23149,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: @@ -23926,17 +23239,17 @@ snapshots: strtok3@7.0.0: dependencies: '@tokenizer/token': 0.3.0 - peek-readable: 5.0.0 + peek-readable: 5.1.3 - strtok3@8.0.1: + strtok3@8.1.0: dependencies: '@tokenizer/token': 0.3.0 - peek-readable: 5.1.3 + peek-readable: 5.2.0 - stylehacks@6.1.1(postcss@8.4.40): + 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 +23281,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 +23321,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 +23350,7 @@ snapshots: dependencies: real-require: 0.2.0 - three@0.167.0: {} + three@0.169.0: {} throttle-debounce@5.0.2: {} @@ -24062,16 +23362,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 +23442,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 +23474,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 +23484,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 +23519,6 @@ snapshots: type-fest@0.8.1: {} - type-fest@1.4.0: {} - type-fest@2.19.0: {} type-fest@4.20.1: {} @@ -24226,34 +23530,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 +23608,7 @@ snapshots: yargs: 17.7.2 optionalDependencies: ioredis: 5.4.1 - pg: 8.12.0 + pg: 8.13.0 transitivePeerDependencies: - supports-color @@ -24282,10 +23618,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 +23634,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 +23643,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: {} - - 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: {} + undici@6.19.8: {} - unicorn-magic@0.1.0: {} + unicorn-magic@0.3.0: {} unified@11.0.4: dependencies: @@ -24345,10 +23673,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 +23715,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: @@ -24425,8 +23749,8 @@ snapshots: inherits: 2.0.4 is-arguments: 1.1.1 is-generator-function: 1.0.10 - is-typed-array: 1.1.10 - which-typed-array: 1.1.11 + is-typed-array: 1.1.13 + which-typed-array: 1.1.15 utils-merge@1.0.1: {} @@ -24438,21 +23762,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.10(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.10(typescript@5.6.2) + vue-demi: 0.14.7(vue@3.5.10(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 +23802,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.8(@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 +23822,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.8(@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.5 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 +23859,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.8(@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 +23870,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.8(@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 +23937,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.10(typescript@5.6.2)): dependencies: - vue: 3.4.37(typescript@5.5.4) + vue: 3.5.10(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.10(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 + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 + '@vue/compiler-dom': 3.5.7 + '@vue/compiler-sfc': 3.5.10 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.10(typescript@5.6.2) + vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.5.10(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)): - 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)): + vue-inbrowser-compiler-independent-utils@4.71.1(vue@3.5.10(typescript@5.6.2)): dependencies: - vue: 3.4.37(typescript@5.5.4) + vue: 3.5.10(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 +24010,39 @@ snapshots: optionalDependencies: typescript: 5.5.4 - vuedraggable@4.1.0(vue@3.4.37(typescript@5.5.4)): + vue@3.5.10(typescript@5.6.2): + dependencies: + '@vue/compiler-dom': 3.5.10 + '@vue/compiler-sfc': 3.5.10 + '@vue/runtime-dom': 3.5.10 + '@vue/server-renderer': 3.5.10(vue@3.5.10(typescript@5.6.2)) + '@vue/shared': 3.5.10 + optionalDependencies: + typescript: 5.6.2 + + vuedraggable@4.1.0(vue@3.5.10(typescript@5.6.2)): dependencies: sortablejs: 1.14.0 - vue: 3.4.37(typescript@5.5.4) + vue: 3.5.10(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 +24053,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 +24121,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 +24160,6 @@ snapshots: word-wrap@1.2.5: {} - wordwrap@1.0.0: {} - wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 @@ -24811,12 +24180,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, |