diff --git a/.config/docker_example.yml b/.config/docker_example.yml index af0a90dc9558a5a393905fa093e32fe2191b9160..39682e1542cbb20936ed5e7911d1635b933585ef 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -95,15 +95,14 @@ redis: # #prefix: example-prefix # #db: 1 -# ┌─────────────────────────────┠-#───┘ Elasticsearch configuration └───────────────────────────── - -#elasticsearch: -# host: localhost -# port: 9200 -# ssl: false -# user: -# pass: +# ┌───────────────────────────┠+#───┘ MeiliSearch configuration └───────────────────────────── + +#meilisearch: +# host: meilisearch +# port: 7700 +# apiKey: '' +# ssl: true # ┌───────────────┠#───┘ ID generation └─────────────────────────────────────────── diff --git a/.config/example.yml b/.config/example.yml index 57e2b56b78c52c049e56a3ef28228f53f655a46a..ef8373c4da57f3093d5eb62d813c1b1b9b02924f 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -95,15 +95,14 @@ redis: # #prefix: example-prefix # #db: 1 -# ┌─────────────────────────────┠-#───┘ Elasticsearch configuration └───────────────────────────── +# ┌───────────────────────────┠+#───┘ MeiliSearch configuration └───────────────────────────── -#elasticsearch: +#meilisearch: # host: localhost -# port: 9200 -# ssl: false -# user: -# pass: +# port: 7700 +# apiKey: '' +# ssl: true # ┌───────────────┠#───┘ ID generation └─────────────────────────────────────────── @@ -133,16 +132,20 @@ id: 'aid' #clusterLimit: 1 # Job concurrency per worker -# deliverJobConcurrency: 128 -# inboxJobConcurrency: 16 +#deliverJobConcurrency: 128 +#inboxJobConcurrency: 16 +#relashionshipJobConcurrency: 16 +# What's relashionshipJob?: +# Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations. # Job rate limiter -# deliverJobPerSec: 128 -# inboxJobPerSec: 16 +#deliverJobPerSec: 128 +#inboxJobPerSec: 16 +#relashionshipJobPerSec: 64 # Job attempts -# deliverJobMaxAttempts: 12 -# inboxJobMaxAttempts: 8 +#deliverJobMaxAttempts: 12 +#inboxJobMaxAttempts: 8 # IP address family used for outgoing request (ipv4, ipv6 or dual) #outgoingAddressFamily: ipv4 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6accd434763e8dea26a18c8bd96d8dcab5976865..a47804ab07a0c6d35ad0c9e98e48ae1040799de3 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,10 @@ "service": "app", "workspaceFolder": "/workspace", "features": { - "ghcr.io/devcontainers-contrib/features/pnpm:2": {} + "ghcr.io/devcontainers-contrib/features/pnpm:2": {}, + "ghcr.io/devcontainers/features/node:1": { + "version": "18.16.0" + } }, "forwardPorts": [3000], "postCreateCommand": "sudo chmod 755 .devcontainer/init.sh && .devcontainer/init.sh", diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml index 2af306e3dac8d16c933fafa31c8ecd9d9aadde59..1d761ae75e8449c07475169ac6cc84880eec5d10 100644 --- a/.devcontainer/devcontainer.yml +++ b/.devcontainer/devcontainer.yml @@ -95,15 +95,14 @@ redis: # #prefix: example-prefix # #db: 1 -# ┌─────────────────────────────┠-#───┘ Elasticsearch configuration └───────────────────────────── - -#elasticsearch: -# host: localhost -# port: 9200 -# ssl: false -# user: -# pass: +# ┌───────────────────────────┠+#───┘ MeiliSearch configuration └───────────────────────────── + +#meilisearch: +# host: meilisearch +# port: 7700 +# apiKey: '' +# ssl: true # ┌───────────────┠#───┘ ID generation └─────────────────────────────────────────── diff --git a/.dockerignore b/.dockerignore index 151ede038eb38120bba59957b9c3eac41343c493..1de0c7982bcdacbdd8b03b756a31a5b6a1824fd8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,7 +8,6 @@ build/ built/ db/ docker-compose.yml -elasticsearch/ node_modules/ packages/*/node_modules redis/ diff --git a/.github/ISSUE_TEMPLATE/01_bug-report.md b/.github/ISSUE_TEMPLATE/01_bug-report.md index b4a2f698793dfd6458ce0fa5ecc58405e2d65094..f6fd593c859ddf555a166a93278f150f88c6f60f 100644 --- a/.github/ISSUE_TEMPLATE/01_bug-report.md +++ b/.github/ISSUE_TEMPLATE/01_bug-report.md @@ -37,7 +37,10 @@ Please include errors from the developer console and/or server log files if you ## 📌 Environment <!-- Tell us where on the platform it happens --> +<!-- DO NOT WRITE "latest". Please provide the specific version. --> Misskey version: +PostgreSQL version: +Redis version: Your OS: Your browser: diff --git a/.github/labeler.yml b/.github/labeler.yml index b4fd0dd5dffaf97fa7a1be92f04eb35ad4b4082a..137be487c0269d08518a57c634c995e80d86d441 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,12 +1,21 @@ -'âš™ï¸Server': +'packages/backend': - packages/backend/**/* -'🖥ï¸Client': +'packages/backend:test': +- packages/backend/test/**/* + +'packages/frontend': - packages/frontend/**/* -'🧪Test': +'packages/frontend:test': - cypress/**/* -- packages/backend/test/**/* -'â€¼ï¸ wrong locales': -- any: ['locales/*.yml', '!locales/ja-JP.yml'] +'packages/sw': +- packages/sw/**/* + +'packages/misskey-js': +- packages/misskey-js/**/* + +'packages/misskey-js:test': +- packages/misskey-js/test/**/* +- packages/misskey-js/test-d/**/* diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0739fee709200d3cb23e300fc59e9e073ea09fad..e78b82c47ccb681bb28223b2d9153d34e91c1e0f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -19,5 +19,6 @@ https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md ## Checklist - [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md) - [ ] Test working in a local environment +- [ ] (If needed) Add story of storybook - [ ] (If needed) Update CHANGELOG.md - [ ] (If possible) Add tests diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml index 6411d63bd5d0c871f4928594e19eeba7cf6f0189..ed004c78dc18739717439e4d28420181928b65fe 100644 --- a/.github/workflows/api-misskey-js.yml +++ b/.github/workflows/api-misskey-js.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v3.6.0 with: - node-version: 18.x + node-version-file: '.node-version' cache: 'pnpm' - name: Install dependencies diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1c6615e17f1a070f8af63a0487b2c5ed8e49e500..0f3702f9582cfc62e791837fa6ee24761577242b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,11 +17,11 @@ jobs: submodules: true - uses: pnpm/action-setup@v2 with: - version: 7 + version: 8 run_install: false - uses: actions/setup-node@v3.6.0 with: - node-version: 18.x + node-version-file: '.node-version' cache: 'pnpm' - run: corepack enable - run: pnpm i --frozen-lockfile @@ -48,7 +48,7 @@ jobs: run_install: false - uses: actions/setup-node@v3.6.0 with: - node-version: 18.x + node-version-file: '.node-version' cache: 'pnpm' - run: corepack enable - run: pnpm i --frozen-lockfile @@ -74,7 +74,7 @@ jobs: run_install: false - uses: actions/setup-node@v3.6.0 with: - node-version: 18.x + node-version-file: '.node-version' cache: 'pnpm' - run: corepack enable - run: pnpm i --frozen-lockfile diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index 6792674d9fe0058d733b1a9d7e5c9abbe5611492..eb6ace27da0ad1d4a4e003a26b888d978e672bc1 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -2,8 +2,10 @@ name: Storybook on: push: - branches-ignore: - - l10n_develop + branches: + - master + - develop + pull_request_target: jobs: build: @@ -17,15 +19,18 @@ jobs: with: fetch-depth: 0 submodules: true + - name: Checkout HEAD + if: github.event_name == 'pull_request' + run: git checkout ${{ github.head_ref }} - name: Install pnpm uses: pnpm/action-setup@v2 with: - version: 7 + version: 8 run_install: false - name: Use Node.js 18.x uses: actions/setup-node@v3.6.0 with: - node-version: 18.x + node-version-file: '.node-version' cache: 'pnpm' - run: corepack enable - run: pnpm i --frozen-lockfile @@ -36,13 +41,13 @@ jobs: - name: Build storybook run: pnpm --filter frontend build-storybook - name: Publish to Chromatic - if: github.ref == 'refs/heads/master' + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master' run: pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static env: CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - name: Publish to Chromatic - if: github.ref != 'refs/heads/master' - id: chromatic + if: github.event_name != 'pull_request' && github.ref != 'refs/heads/master' + id: chromatic_push run: | DIFF="${{ github.event.before }} HEAD" if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then @@ -55,16 +60,31 @@ jobs: pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static $(echo "$CHROMATIC_PARAMETER") env: CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + - name: Publish to Chromatic + if: github.event_name == 'pull_request' + id: chromatic_pull_request + run: | + DIFF="${{ github.base_ref }} HEAD" + if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then + DIFF="HEAD" + fi + CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff-tree --no-commit-id --name-only -r $(echo "$DIFF") | xargs))" + if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + fi + pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static $(echo "$CHROMATIC_PARAMETER") + env: + CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - name: Notify that Chromatic will skip testing uses: actions/github-script@v6.4.0 - if: github.ref != 'refs/heads/master' && github.ref != 'refs/heads/develop' && steps.chromatic.outputs.skip == 'true' + if: github.event_name == 'pull_request' && steps.chromatic_pull_request.outputs.skip == 'true' with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - github.rest.repos.createCommitComment({ + github.rest.issues.createComment({ + issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - commit_sha: context.sha, body: 'Chromatic will skip testing but you may still have to [review the changes on Chromatic](https://www.chromatic.com/pullrequests?appId=6428f7d7b962f0b79f97d6e4).' }) - name: Upload Artifacts diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index f1e414dbbc25bc9eaaa670c453b11f4bcfab19f2..d7be15bd4f0966d944111874796a9f0063e0f917 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -35,7 +35,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v2 with: - version: 7 + version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3.6.0 diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index a5505d30d8694c15fc779977543758931bf6e92a..4ea4ba462873e34217faf6008a02eea95dc22e26 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -22,7 +22,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v2 with: - version: 7 + version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3.6.0 @@ -106,7 +106,7 @@ jobs: install: false start: pnpm start:test wait-on: 'http://localhost:61812' - headless: false + headed: true browser: ${{ matrix.browser }} - uses: actions/upload-artifact@v2 if: failure() diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml new file mode 100644 index 0000000000000000000000000000000000000000..5243a837778b189b61f14e70871aa9dbc5d9b760 --- /dev/null +++ b/.github/workflows/test-production.yml @@ -0,0 +1,42 @@ +name: Test (production install and build) + +on: + push: + branches: + - master + - develop + pull_request: + +env: + NODE_ENV: production + +jobs: + production: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x] + + steps: + - uses: actions/checkout@v3.3.0 + with: + submodules: true + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + run_install: false + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3.6.0 + with: + node-version: ${{ matrix.node-version }} + cache: 'pnpm' + - run: corepack enable + - run: pnpm i --frozen-lockfile + - name: Check pnpm-lock.yaml + run: git diff --exit-code pnpm-lock.yaml + - name: Copy Configure + run: cp .github/misskey/test.yml .config/default.yml + - name: Build + run: pnpm build diff --git a/.gitignore b/.gitignore index fbe224550263887449da212005be5e88a812c91a..537232d37fc47d3de5c0d0609ec9bc0a364cebae 100644 --- a/.gitignore +++ b/.gitignore @@ -44,7 +44,7 @@ built /data /.cache-loader /db -/elasticsearch +/meili_data npm-debug.log *.pem run.bat diff --git a/.node-version b/.node-version index 0e9dc6b586773815d114216e9e3a954c1c843eec..6d80269a4f04adb39604e8186baf6b7f62eef1b2 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -v18.13.0 +18.16.0 diff --git a/.vscode/settings.json b/.vscode/settings.json index baffbe18ecdd1a02a21af9477e9b522ef5abc17e..71fb02a59dfa3234ab67d32bccd645549ade1929 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,6 @@ "files.associations": { "*.test.ts": "typescript" }, + "jest.jestCommandLine": "pnpm run jest", "jest.autoRun": "off" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 011e1cbccf292fa65d640e8441c0ced654edc60b..003e907c5fc4589a472874819e099b0cc6892d6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,64 @@ - --> +## 13.12.0 + +### NOTE +- Node.js 18.6.0以上ãŒå¿…è¦ã«ãªã‚Šã¾ã—㟠+ +### General +- アカウントã®å¼•ã£è¶Šã—(フォãƒãƒ¯ãƒ¼å¼•ã継ãŽï¼‰ã«å¯¾å¿œ +- Meilisearchを全文検索ã«ä½¿ç”¨ã§ãるよã†ã«ãªã‚Šã¾ã—㟠+- æ–°è¦ç™»éŒ²å‰ã«ç°¡æ½”ãªãƒ«ãƒ¼ãƒ«ã‚’ユーザーã«è¡¨ç¤ºã§ãã‚‹ã€ã‚µãƒ¼ãƒãƒ¼ãƒ«ãƒ¼ãƒ«æ©Ÿèƒ½ã‚’è¿½åŠ +- ユーザーã¸ã®è‡ªåˆ†ç”¨ãƒ¡ãƒ¢æ©Ÿèƒ½ + * ユーザーã«å¯¾ã—ã¦ã€è‡ªåˆ†ã ã‘ãŒè¦‹ã‚‰ã‚Œã‚‹ãƒ¡ãƒ¢ã‚’è¿½åŠ ã§ãるよã†ã«ãªã‚Šã¾ã—ãŸã€‚ + (自分自身ã«å¯¾ã—ã¦ã‚‚ãƒ¡ãƒ¢ã‚’è¿½åŠ ã§ãã¾ã™ã€‚) + * ユーザーメニューã‹ã‚‰è¿½åŠ ã§ãã¾ã™ã€‚ + (デスクトップ表示ã§ã¯usernameã®å³å´ã®ãƒœã‚¿ãƒ³ã‹ã‚‰ã‚‚è¿½åŠ å¯èƒ½ï¼‰ +- ãƒãƒ£ãƒ³ãƒãƒ«ã«è‰²ã‚’è¨å®šã§ãるよã†ã«ãªã‚Šã¾ã—ãŸã€‚å„ノートã«è¨å®šã—ãŸè‰²ã®ã‚¤ãƒ³ã‚¸ã‚±ãƒ¼ã‚¿ãƒ¼ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚ +- ãƒãƒ£ãƒ³ãƒãƒ«ã‚’アーカイブã§ãるよã†ã«ãªã‚Šã¾ã—ãŸã€‚ + * アーカイブã™ã‚‹ã¨ã€ãƒãƒ£ãƒ³ãƒãƒ«ä¸€è¦§ã‚„検索çµæžœã«è¡¨ç¤ºã•ã‚Œãªããªã‚Šã€æ–°ãŸãªæ›¸ãè¾¼ã¿ã‚‚ã§ããªããªã‚Šã¾ã™ã€‚ +- アンテナã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆãƒ»ã‚¤ãƒ³ãƒãƒ¼ãƒˆãŒã§ãるよã†ã«ãªã‚Šã¾ã—㟠+- ãƒãƒ¼ãƒ«ã‚¿ã‚¤ãƒ ラインをãƒãƒ¼ãƒ«ã”ã¨ã«è¡¨ç¤ºã™ã‚‹ã‹ã©ã†ã‹ã®é¸æŠžã§ãるよã†ã«ãªã‚Šã¾ã—ãŸã€‚ + * デフォルトãŒã‚ªãƒ•ã«ãªã‚‹ã®ã§ã€ãƒãƒ¼ãƒ«ã‚¿ã‚¤ãƒ ラインを表示ã™ã‚‹å ´åˆã¯ã‚ªãƒ³ã«ã—ã¦ãã ã•ã„。 +- ãƒãƒ¼ãƒ«ã«å¼·åˆ¶çš„ã«NSFWを付与ã™ã‚‹ãƒãƒªã‚·ãƒ¼ã‚’è¿½åŠ + * アップãƒãƒ¼ãƒ‰æ¸ˆã¿ã®ãƒ•ã‚¡ã‚¤ãƒ«ã¯NSFWã«ãªã‚‰ãªã„為注æ„ã—ã¦ãã ã•ã„。 +- モデレーションノートãŒãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ—ãƒãƒ•ã‚£ãƒ¼ãƒ«ãƒšãƒ¼ã‚¸ã‹ã‚‰ã‚‚閲覧ãŠã‚ˆã³ç·¨é›†ã§ãるよã†ã«ãªã‚Šã¾ã—ãŸã€‚ +- カスタム絵文å—ã®ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’複数ã§ã‚»ãƒƒãƒˆã§ãるよã†ã«ãªã‚Šã¾ã—ãŸã€‚ +- 管ç†è€…ãŒäºˆç´„ユーザーåã‚’è¨å®šã§ãるよã†ã«ãªã‚Šã¾ã—ãŸã€‚ +- Fix: フォãƒãƒ¼ãƒªã‚¯ã‚¨ã‚¹ãƒˆã®é€šçŸ¥ãŒæ®‹ã‚‹å•é¡Œã‚’ä¿®æ£ + +### Client +- アカウント作æˆæ™‚ã«åˆæœŸè¨å®šã‚¦ã‚£ã‚¶ãƒ¼ãƒ‰ã‚’表示ã™ã‚‹ã‚ˆã†ã« +- ãƒãƒ£ãƒ³ãƒãƒ«å†…検索ãŒã§ãるよã†ã« +- ãƒãƒ£ãƒ³ãƒãƒ«æ¤œç´¢ã§ã™ã¹ã¦ã®ãƒãƒ£ãƒ³ãƒãƒ«ã®å–å¾—/表示ãŒã§ãるよã†ã« +- 通知ã®è¡¨ç¤ºã‚’カスタマイズã§ãるよã†ã« +- ドライブã®ãƒ•ã‚¡ã‚¤ãƒ«ä¸€è¦§ã‹ã‚‰ç›´æŽ¥ãƒŽãƒ¼ãƒˆã‚’作æˆã§ãるよã†ã« +- ノートメニューã‹ã‚‰Renoteã—ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ä¸€è¦§ã‚’見れるよã†ã« +- コントãƒãƒ¼ãƒ«ãƒ‘ãƒãƒ«ã®ã‚«ã‚¹ã‚¿ãƒ 絵文å—ページãŠã‚ˆã³aboutã®ã‚«ã‚¹ã‚¿ãƒ 絵文å—ã®æ¤œç´¢ã‚¤ãƒ³ãƒ—ットã§ã€`:emojiname1::emojiname2:`ã®ã‚ˆã†ã«æ¤œç´¢ã—ã¦çµµæ–‡å—を検索ã§ãるよã†ã« + * 絵文å—ピッカーã‹ã‚‰å…¥åŠ›å¯èƒ½ã«ãªã‚Šã¾ã™ +- データセーãƒãƒ¼ãƒ¢ãƒ¼ãƒ‰ã‚’è¿½åŠ + * ç”»åƒãŒå…¨ã¦éš ã‚ŒãŸçŠ¶æ…‹ã§è¡¨ç¤ºã•ã‚Œã‚‹ã‚ˆã†ã«ãªã‚Šã¾ã™ +- 閲覧注æ„è¨å®šã•ã‚ŒãŸç”»åƒã¯è¡¨ç¤ºã—ãŸçŠ¶æ…‹ã§ã‚‚ãã‚ŒãŒé–²è¦§æ³¨æ„ã ã¨åˆ†ã‹ã‚‹è¡¨ç¤ºã‚’ã™ã‚‹ã‚ˆã†ã« +- モデレーターã¯ãƒŽãƒ¼ãƒˆã«æ·»ä»˜ã•ã‚ŒãŸç”»åƒä¸Šã‹ã‚‰ç›´æŽ¥NSFWè¨å®šã§ãるよã†ã« +- プãƒãƒ•ã‚£ãƒ¼ãƒ«è¨å®šã€Œè¿½åŠ æƒ…å ±ã€ã®é …ç›®ã®å‰Šé™¤ã¨ä¸¦ã³æ›¿ãˆãŒã§ãるよã†ã« +- æ–°ã—ã„å®Ÿç¸¾ã‚’è¿½åŠ +- AiScriptã‚’0.13.2ã«æ›´æ–° +- Fix: AiScript APIã®Mk:dialogã§ä½•ã‚‚è¿”ã—ã¦ã„ãªã‹ã£ãŸã®ã‚’NULLã‚’è¿”ã™ã‚ˆã†ã«ä¿®æ£ +- Fix: 1:1ã§ã¯ãªã„ç”»åƒã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³é€šçŸ¥ãƒãƒƒã‚¸ãŒå·¦ã‚„上ã«å¯„ã£ã¦ã—ã¾ã£ã¦ã„ãŸã®ã‚’ä¸å¤®ã«æ¥ã‚‹ã‚ˆã†ã«ä¿®æ£ +- Fix: リアクションをホãƒãƒ¼ã—ãŸæ™‚ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãƒªã‚¹ãƒˆã§çŒ«è€³ãŒåˆ‡ã‚Œã¦ã—ã¾ã£ã¦ã„ãŸå•é¡Œã‚’ä¿®æ£ +- Fix: NSFWメディアã®ä¸Šã«è¡¨ç¤ºã•ã‚ŒãŸï½¢ã‚‚ã£ã¨è¦‹ã‚‹ï½£ãƒœã‚¿ãƒ³ãŒæŠ¼ã—ã¥ã‚‰ã„å•é¡Œã‚’ä¿®æ£ + +### Server +- channel/searchã®queryãŒç©ºã®å ´åˆã«å…¨ã¦ã®ãƒãƒ£ãƒ³ãƒãƒ«ã‚’è¿”ã™ã‚ˆã†ã«å¤‰æ›´ +- 環境変数MISSKEY_CONFIG_YMLã§è¨å®šãƒ•ã‚¡ã‚¤ãƒ«ã‚’default.ymlã‹ã‚‰å¤‰æ›´å¯èƒ½ã« +- Fix: ä»–ã®ã‚µãƒ¼ãƒãƒ¼ã®æƒ…å ±ãŒå–å¾—ã§ããªã„ã“ã¨ãŒã‚ã‚‹å•é¡Œã‚’ä¿®æ£ +- Fix: エクスãƒãƒ¼ãƒˆãƒ‡ãƒ¼ã‚¿ã®æ‹¡å¼µåãŒunknownã«ãªã‚‹å•é¡Œã‚’ä¿®æ£ +- Fix: Content-Dispositionã®ãƒ‘ースã§ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ãŸå ´åˆã«ãƒ€ã‚¦ãƒ³ãƒãƒ¼ãƒ‰ãŒå®Œäº†ã—ãªã„å•é¡Œã‚’ä¿®æ£ +- Fix: API: i/update avatarIdã¨bannerIdã«nullを渡ã—ãŸæ™‚ã€ç”»åƒãŒãƒªã‚»ãƒƒãƒˆã•ã‚Œãªã„å•é¡Œã‚’ä¿®æ£ +- Fix: .wav, .flacãŒå†ç”Ÿã§ããªã„å•é¡Œã‚’ä¿®æ£ï¼ˆæ–°ã—ãアップãƒãƒ¼ãƒ‰ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã®ã¿ä¿®æ£ãŒé©ç”¨ã•ã‚Œã¾ã™ï¼‰ +- Fix: å‡çµã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒä¸€éƒ¨APIã®ãƒ¬ã‚¹ãƒãƒ³ã‚¹ã«å«ã¾ã‚Œã‚‹å•é¡Œã‚’ä¿®æ£ +- Fix: メモリã®ä½¿ç”¨é‡ã‚’`used - buffers - cached`ã§ã¯ãªã`total - available`ã§æ±‚ã‚るよã†ã«ï¼ˆç’°å¢ƒã«ã‚ˆã£ã¦æ£å¸¸ã«è¨ˆæ¸¬ã§ãã¦ã„ãªã‹ã£ãŸãŸã‚) ## 13.11.3 @@ -107,6 +165,8 @@ - 猫耳ã®ã‚¢ãƒã‚¿ãƒ¼å†…部部分をã¼ã‹ã—ã§ãƒžã‚¹ã‚¯è¡¨ç¤ºã—ã¦ã‚ˆã‚ŠçŒ«è€³ã£ã½ã見ãˆã‚‹ã‚ˆã†ã« - 「UIã®ã‚¢ãƒ‹ãƒ¡ãƒ¼ã‚·ãƒ§ãƒ³ã‚’減らã™ã€ (`reduceAnimation`) ã§çŒ«è€³ã‚’æ’«ã§ã‚‰ã‚Œãªããªã‚Šã¾ã™ - Add Minimizing ("folding") of windows +- 「データセーãƒãƒ¼ã€ãƒ¢ãƒ¼ãƒ‰ã‚’è¿½åŠ +- éžNSFWメディアãŒéš ã‚Œã¦ã„ã‚‹éš›ã«ã‚‚「閲覧注æ„ã€ãŒå‡ºã¦ã—ã¾ã†å•é¡Œã‚’ä¿®æ£ ### Server - PostgreSQLã®ãƒ¬ãƒ—リケーション対応 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fece05d7a94060792b8ebca1e6f2b87af0ffb785..f6b3804f84f988df1fb755f214e167c783f82fc3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -165,6 +165,11 @@ pnpm jest -- foo.ts ### e2e tests TODO +## Environment Variable + +- `MISSKEY_CONFIG_YML`: Specify the file path of config.yml instead of default.yml (e.g. `2nd.yml`). +- `MISSKEY_WEBFINGER_USE_HTTP`: If it's set true, WebFinger requests will be http instead of https, useful for testing federation between servers in localhost. NEVER USE IN PRODUCTION. + ## Continuous integration Misskey uses GitHub Actions for executing automated tests. Configuration files are located in [`/.github/workflows`](/.github/workflows). @@ -245,7 +250,6 @@ You can override the default story by creating a impl story file (`MyComponent.s ```ts /* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable import/no-duplicates */ import { StoryObj } from '@storybook/vue3'; import MyComponent from './MyComponent.vue'; export const Default = { diff --git a/Dockerfile b/Dockerfile index 8db7400c9fa766bb21e6c294b025cc8f18e33ccc..fb389659bc9378f5b44b1ecfc715728fd8da60bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax = docker/dockerfile:1.4 -ARG NODE_VERSION=18.13.0-bullseye +ARG NODE_VERSION=18.16.0-bullseye # build assets & compile TypeScript diff --git a/chart/files/default.yml b/chart/files/default.yml index 1888669245db3b39f819e7a9561f4e04079602b2..342c6091f015510b93c1de19a80eb9919cfc038b 100644 --- a/chart/files/default.yml +++ b/chart/files/default.yml @@ -116,15 +116,14 @@ redis: # #prefix: example-prefix # #db: 1 -# ┌─────────────────────────────┠-#───┘ Elasticsearch configuration └───────────────────────────── +# ┌───────────────────────────┠+#───┘ MeiliSearch configuration └───────────────────────────── -#elasticsearch: +#meilisearch: # host: localhost -# port: 9200 -# ssl: false -# user: -# pass: +# port: 7700 +# apiKey: '' +# ssl: true # ┌───────────────┠#───┘ ID generation └─────────────────────────────────────────── diff --git a/cypress/e2e/basic.cy.js b/cypress/e2e/basic.cy.js index 8dc07c180066b0d292c1e34c251099991bb9b68c..73f6e7a0f0e50e94266c7a7f6f5390bd374e4c35 100644 --- a/cypress/e2e/basic.cy.js +++ b/cypress/e2e/basic.cy.js @@ -52,6 +52,11 @@ describe('After setup instance', () => { cy.intercept('POST', '/api/signup').as('signup'); cy.get('[data-cy-signup]').click(); + cy.get('[data-cy-signup-rules-continue]').should('be.disabled'); + cy.get('[data-cy-signup-rules-notes-agree] [data-cy-switch-toggle]').click(); + cy.get('[data-cy-signup-rules-continue]').should('not.be.disabled'); + cy.get('[data-cy-signup-rules-continue]').click(); + cy.get('[data-cy-signup-submit]').should('be.disabled'); cy.get('[data-cy-signup-username] input').type('alice'); cy.get('[data-cy-signup-submit]').should('be.disabled'); @@ -71,6 +76,11 @@ describe('After setup instance', () => { // ユーザーåãŒé‡è¤‡ã—ã¦ã„ã‚‹å ´åˆã®æŒ™å‹•ç¢ºèª cy.get('[data-cy-signup]').click(); + cy.get('[data-cy-signup-rules-continue]').should('be.disabled'); + cy.get('[data-cy-signup-rules-notes-agree] [data-cy-switch-toggle]').click(); + cy.get('[data-cy-signup-rules-continue]').should('not.be.disabled'); + cy.get('[data-cy-signup-rules-continue]').click(); + cy.get('[data-cy-signup-username] input').type('alice'); cy.get('[data-cy-signup-password] input').type('alice1234'); cy.get('[data-cy-signup-password-retype] input').type('alice1234'); @@ -149,10 +159,55 @@ describe('After user signed in', () => { }); it('successfully loads', () => { - cy.get('[data-cy-open-post-form]').should('be.visible'); + cy.get('[data-cy-user-setup-continue]').should('be.visible'); + }); + + it('account setup wizard', () => { + cy.get('[data-cy-user-setup-continue]').click(); + + cy.get('[data-cy-user-setup-user-name] input').type('ã‚ã‚Šã™'); + cy.get('[data-cy-user-setup-user-description] textarea').type('ã»ã’'); + // TODO: アイコンè¨å®šãƒ†ã‚¹ãƒˆ + + cy.get('[data-cy-user-setup-continue]').click(); + + // フォãƒãƒ¼ã¯ã‚¹ã‚ップ + + cy.get('[data-cy-user-setup-continue]').click(); + + // プッシュ通知è¨å®šã¯ã‚¹ã‚ップ + + cy.get('[data-cy-user-setup-continue]').click(); + + cy.get('[data-cy-user-setup-continue]').click(); }); +}); + +describe('After user setup', () => { + beforeEach(() => { + cy.resetState(); + + // インスタンスåˆæœŸã‚»ãƒƒãƒˆã‚¢ãƒƒãƒ— + cy.registerUser('admin', 'pass', true); + + // ãƒ¦ãƒ¼ã‚¶ãƒ¼ä½œæˆ + cy.registerUser('alice', 'alice1234'); + + cy.login('alice', 'alice1234'); + + // アカウントåˆæœŸè¨å®šã‚¦ã‚£ã‚¶ãƒ¼ãƒ‰ + cy.get('[data-cy-user-setup] [data-cy-modal-window-close]').click(); + cy.get('[data-cy-modal-dialog-ok]').click(); + }); + + afterEach(() => { + // テスト終了直å‰ã«ãƒšãƒ¼ã‚¸é·ç§»ã™ã‚‹ã‚ˆã†ãªãƒ†ã‚¹ãƒˆã‚±ãƒ¼ã‚¹(例ãˆã°ã‚¢ã‚«ã‚¦ãƒ³ãƒˆä½œæˆ)ã ã¨ã€ãŸã¶ã‚“Cypressã®ãƒã‚°ã§ãƒ–ラウザã®å†…容ãŒæ¬¡ã®ãƒ†ã‚¹ãƒˆã‚±ãƒ¼ã‚¹ã«å¼•ã継ãŒã‚Œã¦ã—ã¾ã†(例ãˆã°ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒä½œæˆã—終ã‚ã£ãŸæ®µéšŽã‹ã‚‰ãƒ†ã‚¹ãƒˆãŒå§‹ã¾ã‚‹)。 + // waitを入れるã“ã¨ã§ãれを防æ¢ã§ãã‚‹ + cy.wait(1000); + }); it('note', () => { + cy.get('[data-cy-open-post-form]').should('be.visible'); cy.get('[data-cy-open-post-form]').click(); cy.get('[data-cy-post-form-text]').type('Hello, Misskey!'); cy.get('[data-cy-open-post-form-submit]').click(); diff --git a/cypress/e2e/widgets.cy.js b/cypress/e2e/widgets.cy.js index a39ea85e1d82b153e90e344e21708cbc5150ba5e..f5a982eb0a6fa2af9f2acd9238f3778641c79210 100644 --- a/cypress/e2e/widgets.cy.js +++ b/cypress/e2e/widgets.cy.js @@ -10,6 +10,10 @@ describe('After user signed in', () => { cy.registerUser('alice', 'alice1234'); cy.login('alice', 'alice1234'); + + // アカウントåˆæœŸè¨å®šã‚¦ã‚£ã‚¶ãƒ¼ãƒ‰ + cy.get('[data-cy-user-setup] [data-cy-modal-window-close]').click(); + cy.get('[data-cy-modal-dialog-ok]').click(); }); afterEach(() => { @@ -19,26 +23,26 @@ describe('After user signed in', () => { }); it('widget edit toggle is visible', () => { - cy.get('.mk-widget-edit').should('be.visible'); + cy.get('[data-cy-widget-edit]').should('be.visible'); }); it('widget select should be visible in edit mode', () => { - cy.get('.mk-widget-edit').click(); - cy.get('.mk-widget-select').should('be.visible'); + cy.get('[data-cy-widget-edit]').click(); + cy.get('[data-cy-widget-select]').should('be.visible'); }); it('first widget should be removed', () => { - cy.get('.mk-widget-edit').click(); + cy.get('[data-cy-widget-edit]').click(); cy.get('[data-cy-customize-container]:first-child [data-cy-customize-container-remove]._button').click(); cy.get('[data-cy-customize-container]').should('have.length', 2); }); function buildWidgetTest(widgetName) { it(`${widgetName} widget should get added`, () => { - cy.get('.mk-widget-edit').click(); - cy.get('.mk-widget-select select').select(widgetName, { force: true }); + cy.get('[data-cy-widget-edit]').click(); + cy.get('[data-cy-widget-select] select').select(widgetName, { force: true }); cy.get('[data-cy-bg]._modalBg[data-cy-transparent]').click({ multiple: true, force: true }); - cy.get('.mk-widget-add').click({ force: true }); + cy.get('[data-cy-widget-add]').click({ force: true }); cy.get(`[data-cy-mkw-${widgetName}]`).should('exist'); }); } diff --git a/docker-compose.yml.example b/docker-compose.yml.example index b0c4a914d56612dde403b1a7030bfc0a9827c952..a0061c5c20b17f0e2df0b19b86cd83a19fd76ec3 100644 --- a/docker-compose.yml.example +++ b/docker-compose.yml.example @@ -7,7 +7,7 @@ services: links: - db - redis -# - es +# - meilisearch depends_on: db: condition: service_healthy @@ -48,16 +48,18 @@ services: interval: 5s retries: 20 -# es: +# meilisearch: # restart: always -# image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.2 +# image: getmeili/meilisearch:v1.1.1 # environment: -# - "ES_JAVA_OPTS=-Xms512m -Xmx512m" -# - "TAKE_FILE_OWNERSHIP=111" +# - MEILI_NO_ANALYTICS=true +# - MEILI_ENV=production +# env_file: +# - .config/meilisearch.env # networks: # - internal_network # volumes: -# - ./elasticsearch:/usr/share/elasticsearch/data +# - ./meili_data:/meili_data networks: internal_network: diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 4b9062051bc02450f129f98c6f6f7407b8151709..9a5936660e7b9213c138ee37aedfc0fda3e65922 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -250,12 +250,12 @@ noMoreHistory: "لا يوجد المزيد من التاريخ" startMessaging: "ابدأ Ù…Øادثة" nUsersRead: "قرأه {n}" agreeTo: "اواÙÙ‚ على {0}" -tos: "شروط الخدمة" start: "البداية" home: "الرئيسي" remoteUserCaution: "هذه المعلومات قد لا تكون مكتملة بما أن المستخدم من مثيل بعيد." activity: "النشاط" images: "الصور" +image: "الصور" birthday: "تاريخ الميلاد" yearsOld: "{age} سنة" registeredDate: "انضم ÙÙŠ" @@ -801,6 +801,8 @@ cannotLoad: "تعذر التØميل" like: "أعجبني" show: "المظهر" color: "اللون" +horizontal: "جانبي" +youFollowing: "متابَع" _role: priority: "الأولوية" _priority: @@ -977,29 +979,6 @@ _time: minute: "د" hour: "سا" day: "ÙŠ" -_tutorial: - title: "كي٠تستخدم Misskey" - step1_1: "مرØبًا!" - step1_2: "تدعى هذه الصÙØØ© 'الخيط الزمني' وهي تØوي ملاØظات الأشخاص الذي تتابعهم مرتبة Øسب تاريخ نشرها." - step1_3: "خيطك الزمني Ùارغ Øاليًا بما أنك لا تتابع أي شخص ولم تنشر أي ملاØظة." - step2_1: "لننهي إعداد ملÙÙƒ الشخصي قبل كتابة ملاØظة أو متابعة أشخاص." - step2_2: "أعطاء معلومات عن شخصيتك ÙŠÙ…Ù†Ø Ù…Ù† له Ù†Ùس إهتماماتك Ùرصة متابعتك والتÙاعل مع ملاØظاتك." - step3_1: "هل أنهيت إعداد Øسابك؟" - step3_2: "إذا تاليًا لتنشر ملاØظة. أنقر على أيقونة القلم ÙÙŠ أعلى الشاشة" - step3_3: "املأ النموذج وانقر الزرّ الموجود ÙÙŠ أعلى اليمين للإرسال." - step3_4: "ليس لديك ما تقوله؟ إذا اكتب \"بدأت٠استخدم ميسكي\"." - step4_1: "هل نشرت ملاØظتك الأولى؟" - step4_2: "مرØÙ‰! يمكنك الآن رؤية ملاØظتك ÙÙŠ الخيط الزمني." - step5_1: "والآن، لنجعل الخيط الزمني أكثر Øيوية وذلك بمتابعة بعض المستخدمين." - step5_2: "تعرض صÙØØ© {features} الملاØظات المتداولة ÙÙŠ هذا المثيل ÙˆÙŠØªÙŠØ Ù„Ùƒ {Explore} العثور على المستخدمين الرائدين. اعثر على الأشخاص الذين يثيرون إهتمامك وتابعهم!" - step5_3: "لمتابعة مستخدمين ادخل ملÙهم الشخصي بالنقر على صورتهم الشخصية ثم اضغط زر 'تابع'." - step5_4: "إذا كان لدى المستخدم رمز Ù‚ÙÙ„ بجوار اسمه ØŒ وجب عليك انتظاره ليقبل طلب المتابعة يدويًا." - step6_1: "الآن ستتمكن من رؤية ملاØظات المستخدمين المتابَعين ÙÙŠ الخيط الزمني." - step6_2: "يمكنك التÙاعل بسرعة مع الملاØظات عن طريق إضاÙØ© \"تÙاعل\"." - step6_3: "لإضاÙØ© تÙاعل لملاØظة ØŒ انقر Ùوق علامة \"+\" أسÙÙ„ للملاØظة واختر الإيموجي المطلوب." - step7_1: "مبارك ! أنهيت الدورة التعليمية الأساسية لاستخدام ميسكي." - step7_2: "إذا أردت معرÙØ© المزيد عن ميسكي زر {help}." - step7_3: "Øظًا سعيدًا واستمتع بوقتك مع ميسكي! 🚀" _2fa: alreadyRegistered: "سجلت سلÙًا جهازًا للاستيثاق بعاملين." step1: "أولًا ثبّت تطبيق استيثاق على جهازك (مثل {a} Ùˆ{b})." diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 734943960c8827df80fcde3e1404fab05367ebb5..4b1a9cb758f7ea98467b930201a44665ff336822 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -252,12 +252,12 @@ noMoreHistory: "আর কোন ইতিহাস নেই" startMessaging: "চà§à¦¯à¦¾à¦Ÿ শà§à¦°à§ করà§à¦¨" nUsersRead: "{n} জন পড়েছেন" agreeTo: "{0} à¦à¦° পà§à¦°à¦¤à¦¿ আমি সমà§à¦®à¦¤" -tos: "পরিষেবার শরà§à¦¤à¦¾à¦¦à¦¿" start: "শà§à¦°à§ করà§à¦¨" home: "মূল পাতা" remoteUserCaution: "à¦à¦‡ বà§à¦¯à¦¾à¦¬à¦¹à¦¾à¦°à¦•à¦¾à¦°à§€ রিমোট ইনà§à¦¸à¦Ÿà§à¦¯à¦¾à¦¨à§à¦¸à§‡à¦°, নিমà§à¦¨à¦•à§à¦¤ তথà§à¦¯ অসমà§à¦ªà§‚রà§à¦£ হতে পারে।" activity: "কারà§à¦¯à¦•à¦²à¦¾à¦ª" images: "ছবি" +image: "ছবি" birthday: "জনà§à¦®à¦¦à¦¿à¦¨" yearsOld: "{age} বছর" registeredDate: "যোগদানের তারিখ" @@ -836,6 +836,8 @@ account: "অà§à¦¯à¦¾à¦•à¦¾à¦‰à¦¨à§à¦Ÿà¦—à§à¦²à¦¿" like: "পছনà§à¦¦ করা" show: "পà§à¦°à¦¦à¦°à§à¦¶à¦¨" color: "রং" +horizontal: "পাশে" +youFollowing: "অনà§à¦¸à¦°à¦£ করা হচà§à¦›à§‡" _role: priority: "অগà§à¦°à¦¾à¦§à¦¿à¦•à¦¾à¦°" _priority: @@ -1039,29 +1041,6 @@ _time: minute: "মিনিট" hour: "ঘণà§à¦Ÿà¦¾" day: "দিন" -_tutorial: - title: "Misskey কিà¦à¦¾à¦¬à§‡ বà§à¦¯à¦¾à¦¬à¦¹à¦¾à¦° করবেন" - step1_1: "সà§à¦¬à¦¾à¦—তম!" - step1_2: "à¦à¦‡ সà§à¦•à§à¦°à§€à¦¨à¦Ÿà¦¿à¦•à§‡ \"টাইমলাইন\" বলা হয় à¦à¦¬à¦‚ কালানà§à¦•à§à¦°à¦®à¦¿à¦• কà§à¦°à¦®à§‡ আপনার à¦à¦¬à¦‚ আপনি যাদের \"অনà§à¦¸à¦°à¦£ করেন\" তাদের \"নোটগà§à¦²à¦¿\" দেখায়৷" - step1_3: "আপনি আপনার টাইমলাইনে কিছৠদেখতে পাবেন না কারণ আপনি à¦à¦–নও কোনো নোট পোসà§à¦Ÿ করেননি à¦à¦¬à¦‚ আপনি কাউকে অনà§à¦¸à¦°à¦£ করছেন না৷" - step2_1: "নোট তৈরি করার আগে বা কাউকে অনà§à¦¸à¦°à¦£ করার আগে পà§à¦°à¦¥à¦®à§‡ আপনার পà§à¦°à§‹à¦«à¦¾à¦‡à¦²à¦Ÿà¦¿ সমà§à¦ªà§‚রà§à¦£ করà§à¦¨à¥¤" - step2_2: "আপনি কে তা জানা অনেক লোকের জনà§à¦¯ আপনার নোটগà§à¦²à¦¿ দেখা à¦à¦¬à¦‚ অনà§à¦¸à¦°à¦£ করাকে সহজ করে তোলে৷" - step3_1: "আপনি কি সফলà¦à¦¾à¦¬à§‡ আপনার পà§à¦°à§‹à¦«à¦¾à¦‡à¦² সেট আপ করেছেন?" - step3_2: "à¦à¦–ন, কিছৠনোট পোসà§à¦Ÿ করার চেষà§à¦Ÿà¦¾ করà§à¦¨à¥¤ পোসà§à¦Ÿ ফরà§à¦® খà§à¦²à¦¤à§‡ পেনà§à¦¸à¦¿à¦² চিহà§à¦¨à¦¯à§à¦•à§à¦¤ বাটনে কà§à¦²à¦¿à¦• করà§à¦¨à¥¤" - step3_3: "বিষয়বসà§à¦¤à§ লেখার পরে, আপনি ফরà§à¦®à§‡à¦° উপরের ডানদিকের বাটনে কà§à¦²à¦¿à¦• করে পোসà§à¦Ÿ করতে পারেন।" - step3_4: "পোসà§à¦Ÿ করার মত কিছৠমনে পরছে না? \"আমি মিসকি সেট আপ করছি\" বললে কেমন হয়?" - step4_1: "পোসà§à¦Ÿ করেছেন?" - step4_2: "সাবাশ! à¦à¦–ন আপনার নোট টাইমলাইনে দেখা যাবে।" - step5_1: "à¦à¦–ন অনà§à¦¯à¦¦à§‡à¦°à¦•à§‡ অনà§à¦¸à¦°à¦£ করে আপনার টাইমলাইনকে পà§à¦°à¦¾à¦£à¦¬à¦¨à§à¦¤ করে তà§à¦²à§à¦¨à¥¤" - step5_2: "আপনি {featured}-ঠজনপà§à¦°à¦¿à¦¯à¦¼ নোটগà§à¦²à¦¿ দেখতে পারেন, যাতে আপনি যে বà§à¦¯à¦•à§à¦¤à¦¿à¦•à§‡ পছনà§à¦¦ করেন তাকে বেছে নিতে à¦à¦¬à¦‚ অনà§à¦¸à¦°à¦£ করতে পারেন, অথবা {explore}-ঠজনপà§à¦°à¦¿à¦¯à¦¼ বà§à¦¯à¦¬à¦¹à¦¾à¦°à¦•à¦¾à¦°à§€à¦¦à§‡à¦° দেখতে পারেন৷" - step5_3: "à¦à¦•à¦œà¦¨ বà§à¦¯à¦¬à¦¹à¦¾à¦°à¦•à¦¾à¦°à§€à¦•à§‡ অনà§à¦¸à¦°à¦£ করতে, বà§à¦¯à¦¬à¦¹à¦¾à¦°à¦•à¦¾à¦°à§€à¦° আইকনে কà§à¦²à¦¿à¦• করà§à¦¨ à¦à¦¬à¦‚ বà§à¦¯à¦¬à¦¹à¦¾à¦°à¦•à¦¾à¦°à§€à¦° পৃষà§à¦ াতে \"অনà§à¦¸à¦°à¦£ করà§à¦¨\" বাটনে কà§à¦²à¦¿à¦• করà§à¦¨à¥¤" - step5_4: "যদি বà§à¦¯à¦¬à¦¹à¦¾à¦°à¦•à¦¾à¦°à§€à¦° নামের পাশে à¦à¦•à¦Ÿà¦¿ লক আইকন থাকে তাহলে আপনার অনà§à¦¸à¦°à¦£à§‡à¦° অনà§à¦°à§‹à¦§ গà§à¦°à¦¹à¦£ করার জনà§à¦¯ তারা কিছৠসময় নিতে পারে।" - step6_1: "সবকিছৠঠিক থাকলে আপনি টাইমলাইনে অনà§à¦¯ বà§à¦¯à¦¬à¦¹à¦¾à¦°à¦•à¦¾à¦°à§€à¦¦à§‡à¦° নোট দেখতে পাবেন।" - step6_2: "আপনি সহজেই আপনার পà§à¦°à¦¤à¦¿à¦•à§à¦°à¦¿à¦¯à¦¼à¦¾ জানাতে অনà§à¦¯ বà§à¦¯à¦•à§à¦¤à¦¿à¦° নোটে \"রিঅà§à¦¯à¦¾à¦•à¦¶à¦¨\" যোগ করতে পারেন।" - step6_3: "à¦à¦•à¦Ÿà¦¿ রিঅà§à¦¯à¦¾à¦•à¦¶à¦¨ যোগ করতে, নোটে \"+\" চিহà§à¦¨à§‡ কà§à¦²à¦¿à¦• করà§à¦¨ à¦à¦¬à¦‚ আপনার পছনà§à¦¦à§‡à¦° রিঅà§à¦¯à¦¾à¦•à¦¶à¦¨ নিরà§à¦¬à¦¾à¦šà¦¨ করà§à¦¨à¥¤" - step7_1: "অà¦à¦¿à¦¨à¦¨à§à¦¦à¦¨! আপনি à¦à¦–ন Misskey-র পà§à¦°à¦¾à¦¥à¦®à¦¿à¦• টিউটোরিয়ালটি শেষ করেছেন।" - step7_2: "আপনি যদি Misskey সমà§à¦ªà¦°à§à¦•à§‡ আরও জানতে চান, তাহলে {help} ঠদেখà§à¦¨à¥¤" - step7_3: "à¦à¦–ন Misskey উপà¦à§‹à¦— করà§à¦¨ 🚀" _2fa: alreadyRegistered: "আপনি ইতিমধà§à¦¯à§‡ à¦à¦•à¦Ÿà¦¿ 2-ফà§à¦¯à¦¾à¦•à§à¦Ÿà¦° অথেনটিকেশন ডিà¦à¦¾à¦‡à¦¸ নিবনà§à¦§à¦¨ করেছেন৷" step1: "পà§à¦°à¦¥à¦®à§‡, আপনার ডিà¦à¦¾à¦‡à¦¸à§‡ {a} বা {b} à¦à¦° মতো à¦à¦•à¦Ÿà¦¿ অথেনটিকেশন অà§à¦¯à¦¾à¦ª ইনসà§à¦Ÿà¦² করà§à¦¨à§·" diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 2b1168f7803f5c53fc88f7b78e0cfb140e8ebde3..588a07fd8794b36884b29126eb8bfbaaec37a106 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -216,6 +216,7 @@ instanceFollowers: "Seguidors del servidor" instanceUsers: "Usuaris del servidor" changePassword: "Canvia la contrasenya" security: "Seguretat" +retypedNotMatch: "L'entrada no coincideix" currentPassword: "Contrasenya actual" newPassword: "Contrasenya nova" newPasswordRetype: "Contrasenya nou (repeteix-la)" @@ -239,6 +240,7 @@ start: "Comença" home: "Inici" activity: "Activitat" images: "Imatges" +image: "Imatges" birthday: "Aniversari" yearsOld: "{age} anys" registeredDate: "Data de registre" diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index 19815c6f37baa7c13fad7003e85aa85e2c984903..d055fde38fea1da0643eec7a6294ca0925a39ab4 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -12,12 +12,14 @@ fetchingAsApObject: "NaÄÃtám data z Fediversu..." ok: "Potvrdit" gotIt: "RozumÃm!" cancel: "ZruÅ¡it" +noThankYou: "Ne dÄ›kuji" enterUsername: "Zadej uživatelské jméno" renotedBy: "{user} pÅ™eposla/a" noNotes: "Žádné poznámky" noNotifications: "Žádná oznámenÃ" instance: "Instance" settings: "NastavenÃ" +notificationSettings: "Nastavenà oznámenÃ" basicSettings: "Obecná nastavenÃ" otherSettings: "RozÅ¡ÃÅ™ená nastavenÃ" openInWindow: "OtevÅ™Ãt v novém oknÄ›" @@ -236,12 +238,12 @@ noMoreHistory: "To je vÅ¡e" startMessaging: "Zahájit chat" nUsersRead: "pÅ™eÄteno {n} uživateli" agreeTo: "SouhlasÃm s {0}" -tos: "PodmÃnky užÃvánÃ" start: "ZaÄÃt" home: "Domů" remoteUserCaution: "Tyto informace nemusà být aktuálnà jelikož uživatel je ze vzdálené instance." activity: "Aktivita" images: "Obrázky" +image: "Obrázky" birthday: "Datum narozenÃ" yearsOld: "{age} let" registeredDate: "Datum registrace" @@ -664,6 +666,8 @@ _time: second: "Sekund" minute: "Minut" hour: "Hodin" +_2fa: + renewTOTPCancel: "Ne dÄ›kuji" _weekday: sunday: "NedÄ›le" monday: "PondÄ›lÃ" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 095c71642aa8583fbbcb14e3960696ff3b95303f..d678fadd4aefc7d9ed8d0809778b78970f5e28e7 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -263,14 +263,16 @@ noMoreHistory: "Kein weiterer Verlauf vorhanden" startMessaging: "Neuen Chat erstellen" nUsersRead: "Von {n} Benutzern gelesen" agreeTo: "Ich stimme {0} zu" +agree: "Zustimmen" agreeBelow: "Ich stimme Untenstehendem zu" basicNotesBeforeCreateAccount: "Wichtige Infos" -tos: "Nutzungsbedingungen" +termsOfService: "Nutzungsbedingungen" start: "Anfangen" home: "Startseite" remoteUserCaution: "Diese Informationen sind möglicherweise unvollständig, da der Benutzer von einer fremden Instanz stammt." activity: "Aktivität" images: "Bilder" +image: "Bild" birthday: "Geburtstag" yearsOld: "{age} Jahre alt" registeredDate: "Registrationsdatum" @@ -468,12 +470,14 @@ enableAdvancedMfm: "Erweitertes MFM aktivieren" enableAnimatedMfm: "Animiertes MFM aktivieren" doing: "In Bearbeitung …" category: "Kategorie" -tags: "Schlagwörter" +tags: "Aliasse" docSource: "Quellcode dieses Dokuments" createAccount: "Benutzerkonto erstellen" existingAccount: "Bestehendes Benutzerkonto" regenerate: "Regenerieren" fontSize: "Schriftgröße" +mediaListWithOneImageAppearance: "Höhe von Medienlisten mit nur einem Bild" +limitTo: "Auf {x} begrenzen" noFollowRequests: "Keine ausstehenden Follow-Anfragen vorhanden" openImageInNewTab: "Bilder in neuem Tab öffnen" dashboard: "Dashboard" @@ -556,6 +560,7 @@ accountDeletedDescription: "Dieses Konto wurde gelöscht." menu: "Menü" divider: "Trenner" addItem: "Element hinzufügen" +rearrange: "Sortieren" relays: "Relays" addRelay: "Relay hinzufügen" inboxUrl: "inbox-URL" @@ -699,6 +704,8 @@ contact: "Kontakt" useSystemFont: "Standardschriftart des Systems verwenden" clips: "Clips" experimentalFeatures: "Experimentelle Funktionalitäten" +experimental: "Experimentell" +thisIsExperimentalFeature: "Dies ist eine experimentelle Funktion. Änderungen an ihrer Funktionsweise sind vorbehalten, zudem kann eine Verwendung zu unerwarteten Effekten führen." developer: "Entwickler" makeExplorable: "Benutzerkonto in „Erkunden“ sichtbar machen" makeExplorableDescription: "Wenn diese Option deaktiviert ist, ist dein Benutzerkonto nicht im „Erkunden“-Bereich sichtbar." @@ -905,6 +912,7 @@ remoteOnly: "Nur für fremde Instanzen" failedToUpload: "Hochladen fehlgeschlagen" cannotUploadBecauseInappropriate: "Diese Datei kann nicht hochgeladen werden, da Anteile der Datei als möglicherweise NSFW festgestellt wurden." cannotUploadBecauseNoFreeSpace: "Die Datei konnte nicht hochgeladen werden, da dein Drive-Speicherplatz aufgebraucht ist." +cannotUploadBecauseExceedsFileSizeLimit: "Diese Datei kann wegen Ãœberschreitung der Maximalgröße nicht hochgeladen werden." beta: "Beta" enableAutoSensitive: "NSFW-Automarkierung" enableAutoSensitiveDescription: "Setzt soweit möglich durch Verwendung von Machine Learning automatisch NSFW-Markierungen für Medien, die NSFW-Anteile beinhalten. Auch wenn du diese Option deaktiviert hast, ist sie möglicherweise auf Instanzebene aktiviert." @@ -918,8 +926,8 @@ subscribePushNotification: "Push-Benachrichtigungen aktivieren" unsubscribePushNotification: "Push-Benachrichtigungen deaktivieren" pushNotificationAlreadySubscribed: "Push-Benachrichtigungen sind bereits aktiviert" pushNotificationNotSupported: "Entweder dein Browser oder deine Instanz unterstützt Push-Benachrichtigungen nicht" -sendPushNotificationReadMessage: "Push-Benachrichtigungen löschen, sobald die relevanten Benachrichtigungen oder Nachrichten gelesen wurden" -sendPushNotificationReadMessageCaption: "Eine Push-Benachrichtigungen mit dem Inhalt \"{emptyPushNotificationMessage}\" wird kurz eingeblendet. Dies kann gegebenenfalls den Batterieverbrauch deines Gerätes erhöhen." +sendPushNotificationReadMessage: "Push-Benachrichtigungen löschen, sobald sie gelesen wurden" +sendPushNotificationReadMessageCaption: "Dies kann gegebenenfalls den Batterieverbrauch deines Gerätes erhöhen." windowMaximize: "Maximieren" windowMinimize: "Minimieren" windowRestore: "Wiederherstellen" @@ -938,6 +946,7 @@ didYouLikeMisskey: "Gefällt dir Misskey?" pleaseDonate: "Misskey ist die kostenlose Software, die von {host} verwendet wird. Wir würden uns über Spenden freuen, damit dessen Entwicklung weitergeführt werden kann!" roles: "Rollen" role: "Rolle" +noRole: "Rolle nicht gefunden" normalUser: "Standardbenutzer" undefined: "Undefiniert" assign: "Zuweisen" @@ -947,6 +956,10 @@ manageCustomEmojis: "Kann benutzerdefinierte Emojis verwalten" youCannotCreateAnymore: "Du hast das Erstellungslimit erreicht." cannotPerformTemporary: "Vorübergehend nicht verfügbar" cannotPerformTemporaryDescription: "Diese Aktion ist wegen des Ãœberschreitenes des Ausführungslimits temporär nicht verfügbar. Bitte versuche es nach einiger Zeit erneut." +invalidParamError: "Ungültige Parameter" +invalidParamErrorDescription: "Die Anfrageparameter sind fehlerhaft. Dies liegt meist an einem Bug, kann aber auch durch eine zu langen Eingabe o.ä. ausgelöst werden." +permissionDeniedError: "Aktion verweigert" +permissionDeniedErrorDescription: "Dieses Benutzerkonto besitzt nicht die Berechtigung, um diese Aktion auszuführen." preset: "Vorlage" selectFromPresets: "Aus Vorlagen wählen" achievements: "Errungenschaften" @@ -990,17 +1003,70 @@ enableChartsForFederatedInstances: "Diagramme für fremde Instanzen erstellen" showClipButtonInNoteFooter: "\"Clip\" zum Notizmenu hinzufügen" largeNoteReactions: "Reaktionen vergrößert anzeigen" noteIdOrUrl: "Notiz-ID oder URL" -accountMigration: "Konto-Umzug" -accountMoved: "Dieser Benutzer ist zu einem neuen Konto umgezogen:" +video: "Video" +videos: "Videos" +dataSaver: "Datensparmodus" +accountMigration: "Kontomigration" +accountMoved: "Dieser Benutzer ist zu einem neuen Konto migriert:" +accountMovedShort: "Dieses Konto wurde migriert." +operationForbidden: "Aktion nicht möglich" forceShowAds: "Werbung immer anzeigen" +addMemo: "Bemerkung hinzufügen" +editMemo: "Bemerkung bearbeiten" +reactionsList: "Reaktionen" +renotesList: "Renotes" +notificationDisplay: "Benachrichtigungen" +leftTop: "Oben links" +rightTop: "Oben rechts" +leftBottom: "Unten links" +rightBottom: "Unten rechts" +stackAxis: "Stapelrichtung" +vertical: "Vertikal" +horizontal: "Horizontal" +position: "Position" +serverRules: "Serverregeln" +pleaseConfirmBelowBeforeSignup: "Lies bitte Untenstehendes vor der Registration." +pleaseAgreeAllToContinue: "Zum Fortfahren muss allen obigen Feldern zugestimmt werden." +continue: "Fortfahren" +preservedUsernames: "Reservierte Benutzernamen" +preservedUsernamesDescription: "Gib zu reservierende Benutzernamen durch Zeilenumbrüche getrennt an. Diese werden für die Registrierung gesperrt, können aber von Administratoren zur manuellen Erstellung von Konten verwendet werden. Existierende Konten, die diese Namen bereits verwenden, werden nicht beeinträchtigt." +createNoteFromTheFile: "Notiz für diese Datei schreiben" +archive: "Archivieren" +channelArchiveConfirmTitle: "{name} wirklich archivieren?" +channelArchiveConfirmDescription: "Ein archivierter Kanal taucht nicht mehr in der Kanalliste oder in Suchergebnissen auf. Zudem können ihm keine Beiträge mehr hinzugefügt werden." +thisChannelArchived: "Dieser Kanal wurde archiviert." +displayOfNote: "Anzeige von Notizen" +initialAccountSetting: "Kontoeinrichtung" +youFollowing: "Gefolgt" +_initialAccountSetting: + accountCreated: "Dein Konto wurde erfolgreich erstellt!" + letsFillYourProfile: "Lass uns zuerst dein Profil einrichten." + profileSetting: "Profileinstellungen" + theseSettingsCanEditLater: "Diese Einstellungen kannst du jederzeit ändern." + youCanEditMoreSettingsInSettingsPageLater: "In den Einstellungen findest du noch viele weitere Optionen. Schau dort später mal vorbei." + followUsers: "Folge zuerst ein paar Nutzern, um deine Chronik zu füllen." + pushNotificationDescription: "Durch die Aktivierung von Push-Benachrichtigungen kannst du von {name} Benachrichtigungen direkt auf dein Gerät erhalten." + initialAccountSettingCompleted: "Kontoeinrichtung abgeschlossen!" + haveFun: "Viel Spaß mit {name}!" + ifYouNeedLearnMore: "Besuche {link}, falls du mehr über {name} (Misskey) lernen möchtest." + skipAreYouSure: "Die Kontoeinrichtung wirklich überspringen?" +_serverRules: + description: "Eine Reihe von Regeln, die vor der Registrierung angezeigt werden. Eine Zusammenfassung der Nutzungsbedingungen anzuzeigen ist empfohlen." _accountMigration: - moveTo: "Dieses Konto zu einem neuen umziehen" + moveFrom: "Von einem anderen Konto zu diesem migrieren" + moveFromSub: "Alias für ein anderes Konto erstellen" + moveFromLabel: "Migrationsursprung #{n}" + moveFromDescription: "Um von einem anderen Konto zu diesem zu migrieren, muss zuvor hier ein Alias eingerichtet werden.\nGib das Konto, von dem migriert werden soll, in folgendem Format ein: @username@server.example.com\n\nZum Löschen des Alias kann das Feld leergelassen werden (nicht empfohlen)." + moveTo: "Dieses Konto zu einem neuen migrieren" moveToLabel: "Umzugsziel:" - moveAccountDescription: "Ein Umzug kann nicht rückgängig gemacht werden. Stelle zuerst sicher, dass du auf dem Umzugsziel einen Alias erstellt hast. Gib dann das Umzugsziel in folgendem Format ein: @person@instance.com" - moveFrom: "Von einem anderen Konto zu diesem umziehen" - moveFromLabel: "Umzugsursprung:" - moveFromDescription: "Stelle sicher, dass du auf dem Umzugsursprungskonto einen Alias zu diesem Konto erstellt hast, falls du die Follower des Ursprungskontos übertragen möchtest. Dies muss vor dem Umzug geschehen! Gib dann das Ursprungskonto in folgendem Format an: @person@instance.com" - migrationConfirm: "Dieses Konto wirklich zu {account} umziehen? Sobald der Umzug beginnt, kann er nicht rückgängig gemacht werden, und dieses Konto nicht wieder im ursprünglichen Zustand verwendet werden.\n\nÃœberprüfe zusätzlich, dass du auf dem Umzugsziel einen Alias eingerichtet hast." + moveCannotBeUndone: "Die Migration eines Benutzerkontos ist unwiderruflich." + moveAccountDescription: "Hierdurch wird dein Konto zu einem anderen migriert.\n ・Follower von diesem Konto werden automatisch auf das neue Konto migriert\n ・Dieses Konto wird allen Nutzern, denen es derzeit folgt, nicht mehr folgen\n ・Mit diesem Konto können keine neuen Notizen usw. erstellt werden\n\nWährend die Migration der Follower automatisch erfolgt, muss die Migration der Konten, denen du folgst, manuell vorbereitet werden. Exportiere hierzu die Liste der gefolgten Nutzer über das Einstellungsmenu, und importiere diese Liste im neuen Konto. Das gleiche Verfahren gilt für erstellte Listen und stummgeschaltete oder blockierte Nutzer.\n\n(Diese Erklärung gilt für Misskey v13.12.0 oder später. Die Funktionsweise andere ActivityPub-Software, beispielsweise Mastodon, kann hiervon abweichen.)" + moveAccountHowTo: "Um ein Konto zu migrieren, erstelle zuerst auf dem Umzugsziel einen Alias für dieses Konto.\nGib dann das Umzugsziel in folgendem Format ein: @username@server.example.com" + startMigration: "Migrieren" + migrationConfirm: "Dieses Konto wirklich zu {account} umziehen? Sobald der Umzug beginnt, kann er nicht rückgängig gemacht werden, und dieses Konto nicht wieder im ursprünglichen Zustand verwendet werden." + movedAndCannotBeUndone: "\nDieses Konto wurde migriert.\nDiese Aktion ist unwiderruflich." + postMigrationNote: "Dieses Konto wird 24 Stunden nach Abschluss der Migration allen Konten, denen es derzeit folgt, nicht mehr folgen.\n\nSowohl die Anzahl der Follower als auch die der Konten, denen dieses Konto folgt, wird dann auf Null gesetzt. Um zu vermeiden, dass Follower dieses Kontos dessen Beiträge, welche nur für Follower bestimmt sind, nicht mehr sehen können, werden sie diesem Konto jedoch weiterhin folgen." + movedTo: "Umzugsziel:" _achievements: earnedAt: "Freigeschaltet am" _types: @@ -1027,7 +1093,7 @@ _achievements: title: "Supernotiz" description: "10.000 Notizen gesendet" _notes20000: - title: "Brauche... mehr... Notizen" + title: "Brauche... mehr... Notizen..." description: "20.000 Notizen gesendet" _notes30000: title: "Notizen, Notizen, Notizen" @@ -1171,7 +1237,10 @@ _achievements: description: "Du hast einen verborgenen Schatz gefunden" _client30min: title: "Kurze Pause" - description: "Habe Misskey für 30 Minuten geöffnet" + description: "Habe Misskey für mindestens 30 Minuten geöffnet" + _client60min: + title: "Munter mit Misskey" + description: "Habe Misskey für mindestens 60 Minuten geöffnet" _noteDeletedWithin1min: title: "Ups" description: "Lösche eine Notiz innerhalb von 1 Minute nachdem sie gesendet wurde" @@ -1260,6 +1329,8 @@ _role: iconUrl: "Icon-URL" asBadge: "Als Abzeichen anzeigen" descriptionOfAsBadge: "Ist dies aktiviert, so wird das Icon dieser Rolle an der Seite der Namen von Benutzern mit dieser Rolle angezeigt." + isExplorable: "Rollenchronik veröffentlichen" + descriptionOfIsExplorable: "Ist dies aktiviert, so ist die Rollenchronik dieser Rolle frei zugänglich. Die Chronik von Rollen, welche nicht öffentlich sind, wird auch bei Aktivierung nicht veröffentlicht." displayOrder: "Position" descriptionOfDisplayOrder: "Je höher die Nummer, desto höher die UI-Position." canEditMembersByModerator: "Moderatoren können Benutzern diese Rolle zuweisen" @@ -1276,6 +1347,7 @@ _role: canInvite: "Erstellung von Einladungscodes für diese Instanz" canManageCustomEmojis: "Benutzerdefinierte Emojis verwalten" driveCapacity: "Drive-Kapazität" + alwaysMarkNsfw: "Dateien immer als NSFW markieren" pinMax: "Maximale Anzahl an angehefteten Notizen" antennaMax: "Maximale Anzahl an Antennen" wordMuteMax: "Maximale Zeichenlänge für Wortstummschaltungen" @@ -1527,32 +1599,16 @@ _time: minute: "Minute(n)" hour: "Stunde(n)" day: "Tag(en)" -_tutorial: +_timelineTutorial: title: "Wie du Misskey verwendest" - step1_1: "Willkommen!" - step1_2: "Diese Seite ist die „Chronik“. Sie zeigt dir deine geschrieben „Notizen“ sowie die aller Benutzer, denen du „folgst“, in chronologischer Reihenfolge." - step1_3: "Deine Chronik sollte momentan leer sein, da du bis jetzt noch keine Notizen geschrieben hast und auch noch keinen Benutzern folgst." - step2_1: "Lass uns zuerst dein Profil vervollständigen, bevor du Notizen schreibst oder jemandem folgst." - step2_2: "Informationen darüber, was für eine Person du bist, macht es anderen leichter zu wissen, ob sie deine Notizen sehen wollen und ob sie dir folgen möchten." - step3_1: "Mit dem Einrichten deines Profils fertig?" - step3_2: "Dann lass uns als nächstes versuchen, eine Notiz zu schreiben. Dies kannst du tun, indem du auf den Knopf mit dem Stift-Icon auf dem Bildschirm drückst." - step3_3: "Fülle das Fenster aus und drücke auf den Knopf oben rechts zum Senden." - step3_4: "Fällt dir nichts ein, das du schreiben möchtest? Versuch's mit \"Hallo Misskey!\"" - step4_1: "Fertig mit dem Senden deiner ersten Notiz?" - step4_2: "Falls deine Notiz nun in deiner Chronik auftaucht, hast du alles richtig gemacht." - step5_1: "Lass uns nun deiner Chronik etwas mehr Leben einhauchen, indem du einigen anderen Benutzern folgst." - step5_2: "{featured} zeigt dir beliebte Notizen dieser Instanz. In {explore} kannst du beliebte Benutzer finden. Schau dort, ob du Benutzer findest, die dich interessieren." - step5_3: "Klicke zum Anzeigen des Profils eines Benutzers auf dessen Profilbild und dann auf den \"Folgen\"-Knopf, um diesem zu folgen." - step5_4: "Je nach Benutzer kann es etwas Zeit in Anspruch nehmen, bis dieser deine Follow-Anfrage bestätigt." - step6_1: "Wenn du nun auch die Notizen anderer Benutzer in deiner Chronik siehst, hast du auch diesmal alles richtig gemacht." - step6_2: "Du kannst ebenso „Reaktionen“ verwenden, um schnell auf Notizen anderer Benutzer zu reagieren." - step6_3: "Um eine Reaktion anzufügen, klicke auf das „+“-Symbol in der Notiz und wähle ein Emoji aus, mit dem du reagieren möchtest." - step7_1: "Glückwunsch! Du hast die Einführung in die Verwendung von Misskey abgeschlossen." - step7_2: "Wenn du mehr über Misskey lernen möchtest, schau dich im {help}-Bereich um." - step7_3: "Und nun, viel Spaß mit Misskey! 🚀" - step8_1: "Möchtest du abschließend Push-Benachrichtigungen aktivieren?" - step8_2: "Push-Benachrichtigungen erlauben es dir, über Reaktionen, Follows oder Erwähnungen usw. zu erfahren, auch wenn Misskey zu dieser Zeit nicht geöffnet ist." - step8_3: "Diese Einstellung kannst du jederzeit ändern." + step1_1: "Dieser Bildschirm ist die \"Chronik\". Hier werden alle \"Notizen\" von {name} angezeigt." + step1_2: "Es gibt einige verschiedene Chroniken. Beispielsweise werden in der \"Startseite\" alle Notizen von Nutzern, denen du folgst, angezeigt, und in der \"Lokalen Chronik\" werden Notizen aller Nutzer auf {name} angezeigt." + step2_1: "Lass uns als nächstes versuchen, eine Notiz zu schreiben. Dies kannst du tun, indem du auf den Knopf mit dem Stift-Icon drückst." + step2_2: "Stell dich den anderen vor oder schreibe einfach \"Hallo {name}!\", wenn du darauf keine Lust hast oder dir nichts einfällt." + step3_1: "Fertig mit dem Senden deiner ersten Notiz?" + step3_2: "Falls deine Notiz nun in deiner Chronik auftaucht, hast du alles richtig gemacht." + step4_1: "Notizen können zusätzlich mit \"Reaktionen\" ausgestattet werden." + step4_2: "Um eine Reaktion anzufügen, klicke auf das „+“-Symbol einer Notiz und wähle ein Emoji aus, mit dem du reagieren möchtest." _2fa: alreadyRegistered: "Du hast bereits ein Gerät für Zwei-Faktor-Authentifizierung registriert." registerTOTP: "Authentifizierungs-App registrieren" diff --git a/locales/el-GR.yml b/locales/el-GR.yml index 32964e9982ac6c3b159db2baaa1259a1d2ce3767..41b1ea7c6566d6b67c16a29968bc2749973862aa 100644 --- a/locales/el-GR.yml +++ b/locales/el-GR.yml @@ -172,11 +172,11 @@ explore: "ΕξεÏευνήστε" messageRead: "Διαβάστηκε" startMessaging: "Ξεκινήστε μία συνομιλία" nUsersRead: "διαβάστηκε από {n}" -tos: "ÎŒÏοι χÏήσης" start: "Ας αÏχίσουμε" home: "ΚεντÏικό" activity: "ΔÏαστηÏιότητα" images: "Εικόνες" +image: "Εικόνες" birthday: "ΓενÎθλια" registeredDate: "Έγινε μÎλος στις" location: "Τοποθεσία" diff --git a/locales/en-US.yml b/locales/en-US.yml index e2407bd3e780b612efe3c6359c0ee7fb5d75c65c..ea91bcc0e578b130707112688cef8ae09a7267bf 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -263,14 +263,16 @@ noMoreHistory: "There is no further history" startMessaging: "Start a new chat" nUsersRead: "read by {n}" agreeTo: "I agree to {0}" +agree: "Agree" agreeBelow: "I agree to the below" basicNotesBeforeCreateAccount: "Important notes" -tos: "Terms of Service" +termsOfService: "Terms of Service" start: "Begin" home: "Home" remoteUserCaution: "As this user is from a remote instance, the shown information may be incomplete." activity: "Activity" images: "Images" +image: "Image" birthday: "Birthday" yearsOld: "{age} years old" registeredDate: "Joined on" @@ -468,12 +470,14 @@ enableAdvancedMfm: "Enable advanced MFM" enableAnimatedMfm: "Enable animated MFM" doing: "Processing..." category: "Category" -tags: "Tags" +tags: "Aliases" docSource: "Source of this document" createAccount: "Create account" existingAccount: "Existing account" regenerate: "Regenerate" fontSize: "Font size" +mediaListWithOneImageAppearance: "Height of media lists with one image only" +limitTo: "Limit to {x}" noFollowRequests: "You don't have any pending follow requests" openImageInNewTab: "Open images in new tab" dashboard: "Dashboard" @@ -556,6 +560,7 @@ accountDeletedDescription: "This account has been deleted." menu: "Menu" divider: "Divider" addItem: "Add Item" +rearrange: "Rearrange" relays: "Relays" addRelay: "Add Relay" inboxUrl: "Inbox URL" @@ -699,6 +704,8 @@ contact: "Contact" useSystemFont: "Use the system's default font" clips: "Clips" experimentalFeatures: "Experimental features" +experimental: "Experimental" +thisIsExperimentalFeature: "This is an experimental feature. Its functionality is subject to change, and it may not operate as intended." developer: "Developer" makeExplorable: "Make account visible in \"Explore\"" makeExplorableDescription: "If you turn this off, your account will not show up in the \"Explore\" section." @@ -905,6 +912,7 @@ remoteOnly: "Remote only" failedToUpload: "Upload failed" cannotUploadBecauseInappropriate: "This file could not be uploaded because parts of it have been detected as potentially NSFW." cannotUploadBecauseNoFreeSpace: "Upload failed due to lack of Drive capacity." +cannotUploadBecauseExceedsFileSizeLimit: "This file cannot be uploaded as it exceeds the file size limit." beta: "Beta" enableAutoSensitive: "Automatic NSFW-Marking" enableAutoSensitiveDescription: "Allows automatic detection and marking of NSFW media through Machine Learning where possible. Even if this option is disabled, it may be enabled instance-wide." @@ -918,8 +926,8 @@ subscribePushNotification: "Enable push notifications" unsubscribePushNotification: "Disable push notifications" pushNotificationAlreadySubscribed: "Push notifications are already enabled" pushNotificationNotSupported: "Your browser or instance does not support push notifications" -sendPushNotificationReadMessage: "Delete push notifications once the relevant notifications or messages have been read" -sendPushNotificationReadMessageCaption: "A notification containing the text \"{emptyPushNotificationMessage}\" will be displayed for a short time. This may increase the power consumption of your device." +sendPushNotificationReadMessage: "Delete push notifications once they have been read" +sendPushNotificationReadMessageCaption: "This may increase the power consumption of your device." windowMaximize: "Maximize" windowMinimize: "Minimize" windowRestore: "Restore" @@ -938,6 +946,7 @@ didYouLikeMisskey: "Have you taken a liking to Misskey?" pleaseDonate: "{host} uses the free software, Misskey. We would highly appreciate your donations so development of Misskey can continue!" roles: "Roles" role: "Role" +noRole: "Role not found" normalUser: "Normal user" undefined: "Undefined" assign: "Assign" @@ -947,6 +956,10 @@ manageCustomEmojis: "Manage Custom Emojis" youCannotCreateAnymore: "You've hit the creation limit." cannotPerformTemporary: "Temporarily unavailable" cannotPerformTemporaryDescription: "This action cannot be performed temporarily due to exceeding the execution limit. Please wait for a while and then try again." +invalidParamError: "Invalid parameters" +invalidParamErrorDescription: "The request parameters are invalid. This is normally caused by a bug, but may also be due to inputs exceeding size limits or similar." +permissionDeniedError: "Operation denied" +permissionDeniedErrorDescription: "This account does not have the permission to perform this action." preset: "Preset" selectFromPresets: "Choose from presets" achievements: "Achievements" @@ -990,17 +1003,70 @@ enableChartsForFederatedInstances: "Generate remote instance data charts" showClipButtonInNoteFooter: "Add \"Clip\" to note action menu" largeNoteReactions: "Enlargen displayed reactions" noteIdOrUrl: "Note ID or URL" +video: "Video" +videos: "Videos" +dataSaver: "Data Saver" accountMigration: "Account Migration" accountMoved: "This user has moved to a new account:" +accountMovedShort: "This account has been migrated." +operationForbidden: "Operation forbidden" forceShowAds: "Always show ads" +addMemo: "Add memo" +editMemo: "Edit memo" +reactionsList: "Reactions" +renotesList: "Renotes" +notificationDisplay: "Notifications" +leftTop: "Top left" +rightTop: "Top right" +leftBottom: "Bottom left" +rightBottom: "Bottom right" +stackAxis: "Stacking direction" +vertical: "Vertical" +horizontal: "Horizontal" +position: "Position" +serverRules: "Server rules" +pleaseConfirmBelowBeforeSignup: "Please confirm the below before signing up." +pleaseAgreeAllToContinue: "You must agree to all above fields to continue." +continue: "Continue" +preservedUsernames: "Reserved usernames" +preservedUsernamesDescription: "List usernames to reserve separated by linebreaks. These will become unable during normal account creation, but can be used by administrators to manually create accounts. Already existing accounts using these usernames will not be affected." +createNoteFromTheFile: "Compose note from this file" +archive: "Archive" +channelArchiveConfirmTitle: "Really archive {name}?" +channelArchiveConfirmDescription: "An archived channel won't appear in the channel list or search results anymore. New posts can also not be added to it anymore." +thisChannelArchived: "This channel has been archived." +displayOfNote: "Note display" +initialAccountSetting: "Profile configuration" +youFollowing: "Followed" +_initialAccountSetting: + accountCreated: "Your account was successfully created!" + letsFillYourProfile: "First, let's set up your profile." + profileSetting: "Profile settings" + theseSettingsCanEditLater: "You can always change these settings later." + youCanEditMoreSettingsInSettingsPageLater: "There are many more settings you can configure from the \"Settings\" page. Be sure to visit it later." + followUsers: "Try following some users that interest you to build up your timeline." + pushNotificationDescription: "Enabling push notifications will allow you to receive notifications from {name} directly on your device." + initialAccountSettingCompleted: "Profile configuration complete!" + haveFun: "Enjoy {name}!" + ifYouNeedLearnMore: "If you'd like to learn more about how to use {name} (Misskey), please visit {link}." + skipAreYouSure: "Really skip profile configuration?" +_serverRules: + description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended." _accountMigration: + moveFrom: "Migrate another account to this one" + moveFromSub: "Create alias to another account" + moveFromLabel: "Original Account #{n}" + moveFromDescription: "You must create an alias for the account to move from on this account.\nEnter the account to migrate from in the following format: @username@server.example.com\nTo delete the alias, leave the field empty (not recommended)." moveTo: "Migrate this account to a different one" moveToLabel: "Account to move to:" - moveAccountDescription: "This action is irreversible. First, create an alias for this account on the account you wish to move to. Then, enter the account to move to in the following format: @person@instance.com" - moveFrom: "Migrate another account to this one" - moveFromLabel: "Account to move from:" - moveFromDescription: "Create an alias for the account to move from on this account if you wish to transfer its followers. This has to be done before the transfer! Then, enter the account to move to in the following format: @person@instance.com" - migrationConfirm: "Really migrate this account to {account}? Once started, this process cannot be stopped or taken back, and you will not be able to use this account in its original state anymore.\n\nAlso, confirm you've created an alias at the account to migrate to." + moveCannotBeUndone: "Account migration cannot be undone." + moveAccountDescription: "This will migrate your account to a different one.\n ・Followers from this account will automatically be migrated to the new account\n ・This account will unfollow all users it is currently following\n ・You will be unable to create new notes etc. on this account\n\nWhile migration of followers is automatic, you must manually prepare some steps to migrate the list of users you are following. To do so, carry out a follows export that you will later import on the new account in the settings menu. The same procedure applies to your lists as well as your muted and blocked users.\n\n(This explanation applies to Misskey v13.12.0 and later. Other ActivityPub software, such as Mastodon, might function differently.)" + moveAccountHowTo: "To migrate, first create an alias for this account on the account to move to.\nAfter you have created the alias, enter the account to move to in the following format: @username@server.example.com" + startMigration: "Migrate" + migrationConfirm: "Really migrate this account to {account}? Once started, this process cannot be stopped or taken back, and you will not be able to use this account in its original state anymore." + movedAndCannotBeUndone: "\nThis account has been migrated.\nMigration cannot be reversed." + postMigrationNote: "This account will unfollow all accounts it is currently following 24 hours after migration finishes.\nBoth the number of follows and followers will then become zero. To avoid your followers from being unable to see followers only posts of this account, they will however continue following this account." + movedTo: "Account to move to:" _achievements: earnedAt: "Unlocked at" _types: @@ -1171,7 +1237,10 @@ _achievements: description: "You've found the hidden treasure" _client30min: title: "Short break" - description: "Spend 30 minutes on Misskey" + description: "Keep Misskey opened for at least 30 minutes" + _client60min: + title: "No \"Miss\" in Misskey" + description: "Keep Misskey opened for at least 60 minutes" _noteDeletedWithin1min: title: "Nevermind" description: "Delete a note within a minute of posting it" @@ -1260,6 +1329,8 @@ _role: iconUrl: "Icon URL" asBadge: "Show as badge" descriptionOfAsBadge: "This role's icon will be displayed next to the username of users with this role if turned on." + isExplorable: "Role timeline is public" + descriptionOfIsExplorable: "This role's timeline will become publicly accessible if enabled. Timelines of non-public roles will not be made public even if set." displayOrder: "Position" descriptionOfDisplayOrder: "The higher the number, the higher its UI position." canEditMembersByModerator: "Allow moderators to edit the list of members for this role" @@ -1276,6 +1347,7 @@ _role: canInvite: "Can create instance invite codes" canManageCustomEmojis: "Can manage custom emojis" driveCapacity: "Drive capacity" + alwaysMarkNsfw: "Always mark files as NSFW" pinMax: "Maximum number of pinned notes" antennaMax: "Maximum number of antennas" wordMuteMax: "Maximum number of characters allowed in word mutes" @@ -1527,32 +1599,16 @@ _time: minute: "Minute(s)" hour: "Hour(s)" day: "Day(s)" -_tutorial: +_timelineTutorial: title: "How to use Misskey" - step1_1: "Welcome!" - step1_2: "This page is called the \"timeline\". It shows chronologically ordered \"notes\" of people who you \"follow\"." - step1_3: "Your timeline is currently empty, since you have not posted any notes or followed anyone yet." - step2_1: "Let's finish setting up your profile before writing a note or following anyone." - step2_2: "Providing some information about who you are will make it easier for others to tell if they want to see your notes or follow you." - step3_1: "Finished setting up your profile?" - step3_2: "Then let's try posting a note next. You can do so by pressing the button with a pencil icon on the screen." - step3_3: "Fill in the modal and press the button on the top right to post." - step3_4: "Have nothing to say? Try \"just setting up my msky\"!" - step4_1: "Finished posting your first note?" - step4_2: "Hurray! Now your first note should be displayed on your timeline." - step5_1: "Now, let's try making your timeline more lively by following other people." - step5_2: "{featured} will show you popular notes in this instance. {explore} will let you find popular users. Try finding people you'd like to follow there!" - step5_3: "To follow other users, click on their icon and press the \"Follow\" button on their profile." - step5_4: "If the other user has a lock icon next to their name, it may take some time for that user to manually approve your follow request." - step6_1: "You should be able to see other users' notes on your timeline now." - step6_2: "You can also put \"reactions\" on other people's notes to quickly respond to them." - step6_3: "To attach a \"reaction\", press the \"+\" mark on another user's note and choose an emoji you'd like to react with." - step7_1: "Congratulations! You have now finished Misskey's basic tutorial." - step7_2: "If you would like to learn more about Misskey, try the {help} section." - step7_3: "Now then, have fun with Misskey! 🚀" - step8_1: "Lastly, would you like to enable push notifications?" - step8_2: "Enabling these will allow you to receive notifications for mentions, reactions, follows, etc. even when Misskey is not opened." - step8_3: "You can always change this setting later." + step1_1: "This is the \"timeline\". All \"notes\" submitted on {name} will be chronologically displayed here." + step1_2: "There are a few different timelines. For example, the \"Home timeline\" will contain notes of users you follow, and the \"Local timeline\" will contain notes from all users of {name}." + step2_1: "Let's try posting a note next. You can do so by pressing the button with a pencil icon." + step2_2: "How about writing a self-introduction, or just \"Hello {name}!\" if you don't feel like it?" + step3_1: "Finished posting your first note?" + step3_2: "Your first note should now be displayed on your timeline." + step4_1: "You can also attach \"Reactions\" to notes." + step4_2: "To attach a reaction, press the \"+\" mark on a note and choose an emoji you'd like to react with." _2fa: alreadyRegistered: "You have already registered a 2-factor authentication device." registerTOTP: "Register authenticator app" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 29cf9b3ec9637948036e7c42dd0092f26405f63c..42d52e5a2b795712def58c5cfe7ff9ed8b5d9ebd 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -264,12 +264,12 @@ nUsersRead: "LeÃdo por {n} personas" agreeTo: "De acuerdo con {0}" agreeBelow: "Estoy de acuerdo con lo siguiente" basicNotesBeforeCreateAccount: "Notas básicas" -tos: "Términos de uso" start: "Comenzar" home: "Inicio" remoteUserCaution: "Para el usuario remoto, la información está incompleta" activity: "Actividad" images: "Imágenes" +image: "Imágenes" birthday: "Fecha de nacimiento" yearsOld: "{age} años" registeredDate: "Fecha de registro" @@ -991,14 +991,17 @@ largeNoteReactions: "Agrandar las reacciones de las notas" noteIdOrUrl: "ID o URL de la nota" accountMigration: "Migración de cuenta" accountMoved: "Este usuario se ha mudado a una nueva cuenta:" +horizontal: "Horizontal" +youFollowing: "Siguiendo" _accountMigration: - moveTo: "Mover esta cuenta a una nueva" - moveToLabel: "Cuenta destino:" - moveAccountDescription: "Esta operación no puede deshacerse. En primer lugar, asegúrese de haber creado un alias para esta cuenta en la cuenta a la que se va a trasladar. Después de crear el alias, introduzca la cuenta a la que se está trasladando de la siguiente manera: @person@instance.com" moveFrom: "Trasladar de otra cuenta a ésta" moveFromLabel: "Cuenta desde la que se realiza el traslado:" moveFromDescription: "Si quieres transferir seguidores de otra cuenta a esta cuenta y trasladarlos, tendrás que crear un alias aquÃ. Asegúrate de crearlo antes de realizar el traslado. Introduce la cuenta desde la que estás moviendo los seguidores asÃ: @person@instance.com" + moveTo: "Mover esta cuenta a una nueva" + moveToLabel: "Cuenta destino:" + moveAccountDescription: "Esta operación no puede deshacerse. En primer lugar, asegúrese de haber creado un alias para esta cuenta en la cuenta a la que se va a trasladar. Después de crear el alias, introduzca la cuenta a la que se está trasladando de la siguiente manera: @person@instance.com" migrationConfirm: "¿Estás seguro de que quieres mover esta cuenta a {account}? Una vez trasladada, no podrás deshacer el traslado y no podrás volver a utilizar la cuenta original.\n\nAdemás, compruebe que ha configurado un alias en el destino del traslado." + movedTo: "Cuenta destino:" _achievements: earnedAt: "Desbloqueado el" _types: @@ -1523,32 +1526,6 @@ _time: minute: "Minutos" hour: "Horas" day: "DÃas" -_tutorial: - title: "Cómo usar Misskey" - step1_1: "Bienvenido" - step1_2: "Esta imagen se llama \"Linea de tiempo\" y muestra en orden cronológico las \"notas\" tuyas y de la gente que \"sigues\"" - step1_3: "Si no estás escribiendo ninguna nota y no estás siguiendo a nadie, es esperable que no se muestre nada en la linea de tiempo" - step2_1: "Antes de crear notas y seguir a alguien, primero vamos a crear tu perfil" - step2_2: "Si provees información sobre quien eres, será más fácil para que otros usuarios te sigan" - step3_1: "¿Has podido crear tu perfil sin problemas?" - step3_2: "Con esto, prueba hacer una nota. Aprieta el botón con forma de lápiz que está arriba de la imagen y abre el formulario." - step3_3: "Si has escrito el contenido, aprieta el botón que está arriba a la derecha del formulario para postear." - step3_4: "¿No se te ocurre un contenido? Prueba con decir \"Empecé a usar Misskey\"" - step4_1: "¿Has posteado?" - step4_2: "Si tu nota puede verse en la linea de tiempo, fue todo un éxito." - step5_1: "Luego, ponte a seguir a otra gente y haz que tu linea de tiempo esté más animada." - step5_2: "Puedes ver las notas destacadas en {featured} y desde allà seguir a usuarios que te importan. También puedes buscar usuario destacados en {explore}." - step5_3: "Para seguir a un usuario, haz click en su avatar para ver su página de usuario y allà apretar el botón \"seguir\"" - step5_4: "De esa manera, puede pasar un tiempo hasta que el usuario apruebe al seguidor." - step6_1: "Si puedes ver en la linea de tiempo las notas de otros usuarios, fue todo un éxito." - step6_2: "En las notas de otros usuarios puedes añadir una \"reacción\", para poder responder rápidamente." - step6_3: "Para añadir una reacción, haz click en el botón \"+\" de la nota y elige la reacción que prefieras." - step7_1: "Asà terminó la explicación del funcionamiento básico de Misskey. Eso fue todo." - step7_2: "Si quieres conocer más sobre Misskey, prueba con la sección {help}." - step7_3: "AsÃ, disfruta de Misskey 🚀" - step8_1: "Por último, ¿por qué no activar las notificaciones emergentes?" - step8_2: "Al recibir notificaciones emergentes, estarás al tanto de reacciones, seguimientos y menciones incluso cuando Misskey no esté abierto." - step8_3: "La configuración de las notificaciones puede modificarse posteriormente." _2fa: alreadyRegistered: "Ya has completado la configuración." registerTOTP: "Registrar aplicación autenticadora" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index e0767fdc09830cb44e5947e18128f05b9e5fae9f..e98ee9ff9befa993963d961904f620ff64c826a9 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -20,6 +20,7 @@ noNotes: "Aucune note" noNotifications: "Aucune notification" instance: "Instance" settings: "Paramètres" +notificationSettings: "Paramètres des notifications " basicSettings: "Paramètres généraux" otherSettings: "Paramètres avancés" openInWindow: "Ouvrir dans une nouvelle fenêtre" @@ -259,12 +260,12 @@ noMoreHistory: "Il n’y a plus d’historique" startMessaging: "Commencer à discuter" nUsersRead: "Lu par {n} personnes" agreeTo: "J’accepte {0}" -tos: "les conditions d’utilisation" start: "Commencer" home: "Principal" remoteUserCaution: "Les informations de ce compte risqueraient d’être incomplètes du fait que l’utilisateur·rice provient d’une instance distante." activity: "Activité" images: "Images" +image: "Images" birthday: "Date de naissance" yearsOld: "{age} ans" registeredDate: "Inscrit le" @@ -838,13 +839,16 @@ tenMinutes: "10 minutes" oneHour: "1 heure" oneDay: "1 jour" oneWeek: "1 semaine" +oneMonth: "Un mois" reflectMayTakeTime: "Cela peut prendre un certain temps avant que cela ne se termine." failedToFetchAccountInformation: "Impossible de récupérer les informations du compte." rateLimitExceeded: "Limite de taux dépassée" cropImage: "Recadrer l'image" cropImageAsk: "Voulez-vous recadrer cette image ?" +cropYes: "Rogner" file: "Fichiers" recentNHours: "Dernières {n} heures" +recentNDays: "Derniers {n} jours" noEmailServerWarning: "Serveur de courrier non configuré." thereIsUnresolvedAbuseReportWarning: "Il n’y a aucun rapport non résolu." recommended: "Recommandé" @@ -900,11 +904,47 @@ numberOfLikes: "Favoris" show: "Affichage" neverShow: "Ne plus afficher" remindMeLater: "Peut-être plus tard" +roles: "Rôles" +role: "Rôles" +normalUser: "Simple utilisateur·rice" +assign: "Attribuer" color: "Couleur" +manageCustomEmojis: "Gestion des émojis personnalisés" +preset: "Préréglage" +selectFromPresets: "Sélectionner à partir des préréglages" +license: "Licence" +video: "Vidéo" +videos: "Vidéos" +dataSaver: "Économiseur de données" +accountMigration: "Migration de compte" +accountMoved: "Cet·te utilisateur·rice a migré son compte vers :" +notificationDisplay: "Style des notifications" +leftTop: "En haut à gauche" +rightTop: "En haut à droite" +leftBottom: "En bas à gauche" +rightBottom: "En bas à droite" +vertical: "Vertical" +horizontal: "Latéral" +youFollowing: "Abonné·e" _achievements: _types: + _notes1: + description: "Publiez votre première note" + flavor: "Passez un bon moment avec Misskey !" _notes100000: title: "ALL YOUR NOTE ARE BELONG TO US" + _login3: + description: "Se connecter pour un total de 3 jours" + _login7: + description: "Se connecter pour un total de 7 jours" + _login15: + description: "Se connecter pour un total de 15 jours" + _login30: + description: "Se connecter pour un total de 30 jours" + _login60: + description: "Se connecter pour un total de 60 jours" + _login100: + description: "Se connecter pour un total de 100 jours" _login1000: flavor: "Merci d'utiliser Misskey !" _markedAsCat: @@ -912,12 +952,21 @@ _achievements: flavor: "Je n'ai pas encore de nom" _following50: title: "Beaucoup d'amis" + _followers10: + title: "Abonnez-moi !" + _viewInstanceChart: + title: "Analyste" + _loggedInOnNewYearsDay: + title: "Bonne année !" _role: + assignTarget: "Attribuer" priority: "Priorité" _priority: low: "Basse" middle: "Moyen" high: "Haute" + _options: + canManageCustomEmojis: "Gestion des émojis personnalisés" _sensitiveMediaDetection: description: "L'apprentissage automatique peut être utilisé pour détecter automatiquement les médias sensibles à modérer. La sollicitation des serveurs augmente légèrement." sensitivity: "Sensibilité de la détection" @@ -1141,31 +1190,6 @@ _time: minute: "min" hour: "h" day: "j" -_tutorial: - title: "Comment utiliser Misskey" - step1_1: "Bienvenue," - step1_2: "Cette page est appelée « un fil ». Elle affiche les « notes » des personnes auxquelles vous êtes abonné dans un ordre chronologique." - step1_3: "Votre fil est actuellement vide vu que vous ne suivez aucun compte et que vous n’avez publié aucune note, pour l’instant." - step2_1: "Procédons d’abord à la préparation de votre profil avant d’écrire une note et/ou de vous abonner à un compte." - step2_2: "En fournissant quelques informations sur vous, il sera plus facile pour les autres de s’abonner à votre compte." - step3_1: "Vous avez fini de créer votre profil ?" - step3_2: "L’étape suivante consiste à créer une note. Vous pouvez commencer en cliquant sur l’icône crayon sur l’écran." - step3_3: "Remplissez le cadran et cliquez sur le bouton en haut à droite pour envoyer." - step3_4: "Vous n’avez rien à dire ? Essayez d’écrire « J’ai commencé à utiliser Misskey »." - step4_1: "Avez-vous publié votre première note ?" - step4_2: "Youpi ! Celle-ci est maintenant affichée sur votre fil d’actualité." - step5_1: "Maintenant, essayons de nous abonner à d’autres personnes afin de rendre votre fil plus vivant." - step5_2: "La page {featured} affiche les notes en tendance sur la présente instance et {explore} vous permet de trouver des utilisateur·rice·s en tendance. Essayez de vous abonner aux gens que vous aimez !" - step5_3: "Pour pouvoir suivre d’autres utilisateur·rice, cliquez sur leur avatar afin d’afficher la page du profil utilisateur ensuite appuyez sur le bouton « S’abonner »." - step5_4: "Si l’autre utilisateur possède une icône sous forme d’un cadenas à côté de son nom, il devra accepter votre demande d’abonnement manuellement." - step6_1: "Maintenant, vous êtes en mesure de voir s’afficher les notes des autres utilisateur·rice·s sur votre propre fil." - step6_2: "Vous avez également la possibilité d’intéragir rapidement avec les notes des autres utilisateur·rice·s en ajoutant des « réactions »." - step6_3: "Pour ajouter une réaction à une note, cliquez sur le signe « + » de celle-ci et sélectionnez l’émoji souhaité." - step7_1: "Félicitations ! Vous avez atteint la fin du tutoriel de base pour l’utilisation de Misskey." - step7_2: "Si vous désirez en savoir plus sur Misskey, jetez un Å“il sur la section {help}." - step7_3: "Bon courage et amusez-vous bien sur Misskey ! 🚀" - step8_1: "Enfin, souhaitez-vous activer les notifications push ?" - step8_2: "En les activant, vous recevrez des notifications pour les mentions, les réactions, les suivis, etc., même lorsque Misskey n'est pas ouvert." _2fa: alreadyRegistered: "Configuration déjà achevée." step1: "Tout d'abord, installez une application d'authentification, telle que {a} ou {b}, sur votre appareil." diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 97c0b02fe55d408a512515011c392cf92ced3b65..5159775fc440434bbd2fbce4b4c3eb26bf9b8edf 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -263,12 +263,12 @@ nUsersRead: "Dibaca oleh {n}" agreeTo: "Saya setuju kepada {0}" agreeBelow: "Saya setuju dengan di bawah ini" basicNotesBeforeCreateAccount: "Catatan penting" -tos: "Syarat dan ketentuan" start: "Mulai" home: "Beranda" remoteUserCaution: "Informasi ini mungkin tidak mutakhir, karena pengguna ini berasal dari instansi luar." activity: "Aktivitas" images: "Gambar" +image: "Gambar" birthday: "Tanggal lahir" yearsOld: "{age} tahun" registeredDate: "Bergabung pada" @@ -953,6 +953,8 @@ letsLookAtTimeline: "LIhat timeline" disableFederationConfirm: "Matikan federasi?" disableFederationConfirmWarn: "Mematikan federasi tidak membuat kiriman menjadi privat. Umumnya, mematikan federasi tidak diperlukan." disableFederationOk: "Matikan federasi" +horizontal: "Horisontal" +youFollowing: "Mengikuti" _achievements: earnedAt: "Terbuka pada" _types: @@ -1472,32 +1474,6 @@ _time: minute: "menit" hour: "jam" day: "hari" -_tutorial: - title: "Cara menggunakan Misskey" - step1_1: "Selamat datang!" - step1_2: "Halaman ini disebut \"linimasa\". Halaman ini menampilkan \"catatan\" yang diurutkan secara kronologis dari orang-orang yang kamu \"ikuti\"." - step1_3: "Linimasa kamu kosong, karena kamu belum mencatat catatan apapun atau mengikuti siapapun." - step2_1: "Selesaikan menyetel profilmu sebelum menulis sebuah catatan atau mengikuti seseorang." - step2_2: "Menyediakan beberapa informasi tentang siapa kamu akan membuat orang lain mudah untuk mengikutimu kembali." - step3_1: "Selesai menyetel profil kamu?" - step3_2: "Langkah selanjutnya adalah membuat catatan. Kamu bisa lakukan ini dengan mengklik ikon pensil pada layar kamu." - step3_3: "Isilah di dalam modal dan tekan tombol pada atas kanan untuk memcatat catatan kamu." - step3_4: "Bingung tidak berpikiran untuk mengatakan sesuatu? Coba saja \"baru aja ikutan bikin akun misskey punyaku\"!" - step4_1: "Selesai mencatat catatan pertamamu?" - step4_2: "Horee! Sekarang catatan pertamamu sudah ditampilkan di linimasa milikmu." - step5_1: "Sekarang, mari mencoba untuk membuat linimasamu lebih hidup dengan mengikuti orang lain." - step5_2: "{featured} akan memperlihatkan catatan yang sedang tren saat ini untuk kamu. {explore} akan membantumu untuk mencari pengguna yang sedang tren juga saat ini. Coba ikuti seseorang yang kamu suka!" - step5_3: "Untuk mengikuti pengguna lain, klik pada ikon mereka dan tekan tombol follow pada profil mereka." - step5_4: "Jika pengguna lain memiliki ikon gembok di sebelah nama mereka, maka pengguna rersebut harus menyetujui permintaan mengikuti dari kamu secara manual." - step6_1: "Sekarang kamu dapat melihat catatan pengguna lain pada linimasamu." - step6_2: "Kamu juga bisa memberikan \"reaksi\" ke catatan orang lain untuk merespon dengan cepat." - step6_3: "Untuk memberikan \"reaksi\", tekan tanda \"+\" pada catatan pengguna lain dan pilih emoji yang kamu suka untuk memberikan reaksimu kepada mereka." - step7_1: "Yay, Selamat! Kamu sudah menyelesaikan tutorial dasar Misskey." - step7_2: "Jika kamu ingin mempelajari lebih lanjut tentang Misskey, cobalah berkunjung ke bagian {help}." - step7_3: "Semoga berhasil dan bersenang-senanglah! 🚀" - step8_1: "Yang terakhir, apakah kamu ingin menyalakam pemberitahuan push?" - step8_2: "Menyalakan ini akan memungkinkan kamu menerima pemberitahuan untuk sebutan, reaksi, ikuti, dll. Bahkan ketika Misskey sedang tidak dibuka." - step8_3: "Kamu dapat mengganti pengaturan ini nanti." _2fa: alreadyRegistered: "Kamu telah mendaftarkan perangkat otentikasi dua faktor." step1: "Pertama, pasang aplikasi otentikasi (seperti {a} atau {b}) di perangkat kamu." diff --git a/locales/it-IT.yml b/locales/it-IT.yml index f43adcb35bed0f4a194d6b92edfcfdf9a1ba5a63..cacd91cac940e14a2b30825ba53c246d0cfc695a 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -263,14 +263,16 @@ noMoreHistory: "Non c'è più cronologia da visualizzare" startMessaging: "Nuovo messaggio" nUsersRead: "Letto da {n} persone" agreeTo: "Sono d'accordo con {0}" +agree: "D'accordo" agreeBelow: "Accetto quanto riportato sotto" basicNotesBeforeCreateAccount: "Note importanti" -tos: "Termini di servizio" +termsOfService: "Informativa Privacy" start: "Inizia!" home: "Home" remoteUserCaution: "Può darsi che le informazioni siano incomplete perché questo è un utente remoto." activity: "Attività " images: "Immagini" +image: "Immagini" birthday: "Compleanno" yearsOld: "{age} anni" registeredDate: "Iscrizione a.." @@ -474,6 +476,8 @@ createAccount: "Crea il tuo profilo" existingAccount: "Profilo esistente" regenerate: "Generare di nuovo" fontSize: "Dimensione carattere" +mediaListWithOneImageAppearance: "Altezza dell'elenco media con una sola immagine " +limitTo: "Limita a {x}" noFollowRequests: "Non hai alcuna richiesta di follow" openImageInNewTab: "Apri le immagini in un nuovo tab" dashboard: "Pannello di controllo" @@ -947,6 +951,10 @@ manageCustomEmojis: "Gestisci le emoji personalizzate" youCannotCreateAnymore: "Non puoi creare, hai raggiunto il limite." cannotPerformTemporary: "Indisponibilità temporanea" cannotPerformTemporaryDescription: "L'attività non può essere svolta, poiché si è raggiunto il limite di esecuzioni possibili. Per favore, riprova più tardi." +invalidParamError: "Errore, parametro non valido" +invalidParamErrorDescription: "Riscontrato un errore nei parametri della richiesta. Solitamente potrebbe essere un malfunzionamento, ma a volte potrebbe essere causato dalla quantità eccessiva di caratteri nel parametro." +permissionDeniedError: "Errore, attività non autorizzata" +permissionDeniedErrorDescription: "Non si dispone dell'autorizzazione per eseguire questa operazione." preset: "Preimpostato" selectFromPresets: "Seleziona preimpostato" achievements: "Obiettivi raggiunti" @@ -970,7 +978,7 @@ invitationRequiredToRegister: "L'accesso a questa istanza è solo ad invito. Pu emailNotSupported: "L'istanza non supporta l'invio di email" postToTheChannel: "Pubblica nel canale" cannotBeChangedLater: "Non sarà più modificabile" -reactionAcceptance: "Accettazione reazioni" +reactionAcceptance: "Reazioni consentite" likeOnly: "Solo i Like" likeOnlyForRemote: "Solo Like remoti" rolesAssignedToMe: "I miei ruoli" @@ -990,17 +998,39 @@ enableChartsForFederatedInstances: "Abilita i grafici per le istanze federate" showClipButtonInNoteFooter: "Aggiungi il bottone Clip tra le azioni delle Note" largeNoteReactions: "Ingrandisci le reazioni" noteIdOrUrl: "ID della Nota o URL" +video: "Video" +videos: "Video" +dataSaver: "Risparmia dati" accountMigration: "Migrazione del profilo" accountMoved: "Questo profilo ha migrato altrove:" forceShowAds: "Mostra sempre i banner" +addMemo: "Aggiungi Memo" +editMemo: "Modifica Memo" +notificationDisplay: "Stile delle notifiche" +leftTop: "In alto a sinistra" +rightTop: "In alto a destra" +leftBottom: "In basso a sinistra" +rightBottom: "In basso a destra" +stackAxis: "Allineamento" +vertical: "Verticale" +horizontal: "Laterale" +position: "Posizione" +serverRules: "Regolamento" +pleaseConfirmBelowBeforeSignup: "Ai sensi del regolamento EU 679/2016 GDPR, autorizzo il trattamento dati personali come descritto nella informativa Privacy." +pleaseAgreeAllToContinue: "Per continuare, occorre selezionare ed essere d'accordo su tutto." +continue: "Continua" +youFollowing: "Seguiti" +_serverRules: + description: "In Europa è necessario mostrare l'informativa sul trattamento dei dati personali, prima della registrazione al servizio." _accountMigration: - moveTo: "Migrare questo profilo verso un un altro" - moveToLabel: "Profilo verso cui migrare" - moveAccountDescription: "Questa attività è irreversibile! Innanzitutto, assicurati di aver creato, nella istanza di destinazione, un alias con l'indirizzo di questo profilo. Successivamente, indica qui il profilo di destinazione in questo modo: @persona@istanza.it" moveFrom: "Migra un altro profilo dentro a questo" moveFromLabel: "Profilo da cui migrare:" moveFromDescription: "Se desideri spostare i profili follower da un altro profilo a questo, devi prima creare un alias qui. Assicurati averlo creato PRIMA di eseguire l'attività ! Inserisci l'indirizzo del profilo mittente in questo modo: @persona@istanza.it" + moveTo: "Migrare questo profilo verso un un altro" + moveToLabel: "Profilo verso cui migrare" + moveAccountDescription: "Questa attività è irreversibile! Innanzitutto, assicurati di aver creato, nella istanza di destinazione, un alias con l'indirizzo di questo profilo. Successivamente, indica qui il profilo di destinazione in questo modo: @persona@istanza.it" migrationConfirm: "Vuoi davvero migrare questo profilo su {account}? L'azione è irreversibile e non potrai più utilizzare questo profilo nel suo stato originale.\nInoltre, assicurati di aver già creato un alias sull'account a cui ti stai trasferendo." + movedTo: "Profilo verso cui migrare" _achievements: earnedAt: "Data di conseguimento" _types: @@ -1172,6 +1202,9 @@ _achievements: _client30min: title: "Piccola grande pausa" description: "Hai passato più di 30 minuti su Misskey" + _client60min: + title: "Misskey negli occhi" + description: "Hai letto Misskey almeno per un'ora" _noteDeletedWithin1min: title: "Ooops!" description: "Hai eliminato una nota entro un minuto dalla sua pubblicazione" @@ -1260,6 +1293,8 @@ _role: iconUrl: "URL dell'icona" asBadge: "Mostra come badge" descriptionOfAsBadge: "Se indicato, accanto al nome utente viene visualizzata l'icona del ruolo." + isExplorable: "La timeline del ruolo è pubblica" + descriptionOfIsExplorable: "Selezionandolo, la timeline del ruolo diventerà accessibile pubblicamente. Tranne se il ruolo non è pubblico." displayOrder: "Ordine di visualizzazione" descriptionOfDisplayOrder: "I valori più alti vengono visualizzati per primi" canEditMembersByModerator: "Anche i Moderatori assegnano profili a questo ruolo" @@ -1527,32 +1562,6 @@ _time: minute: "min" hour: "ore" day: "giorni" -_tutorial: - title: "Come usare Misskey" - step1_1: "Eccoci!" - step1_2: "Questa pagina si chiama \"Timeline \" e mostra in ordine cronologico le \"note\" delle persone che segui." - step1_3: "Attualmente la tua Timeline è vuota perché non segui alcun profilo e non hai ancora pubblicato alcuna nota." - step2_1: "Prima di scrivere una «Nota» o di seguire altri profili, prepara il tuo profilo!" - step2_2: "Se aggiungi informazioni personali aumenterai le tue possibilità di essere seguit@ da altre persone. " - step3_1: "Hai finito di impostare il tuo profilo?" - step3_2: "Ora puoi pubblicare una «Nota». Proviamo subito! Premi il bottone con l'icona «penna» per iniziare a scrivere in una finestra di dialogo. " - step3_3: "Scritto il testo della nota, puoi pubblicarla premendo il pulsante nella parte superiore destra della finestra di dialogo." - step3_4: "Non ti viene niente in mente? Perché non scrivi semplicemente \"Ho appena iniziato a usare Misskey\"?" - step4_1: "Hai pubblicato qualcosa?" - step4_2: "Se puoi visualizzare la tua nota sulla timeline, ce l'hai fatta!" - step5_1: "Adesso, cerca di seguire altre persone per vivacizzare la tua timeline. " - step5_2: "La pagina {featured} mostra le note di tendenza su questa istanza, magari ti aiuterà a trovare profili che ti piacciono e che vorrai seguire. Altrimenti potrai trovare utenti popolari anche usando {explore}." - step5_3: "Clicca l'immagine per aprire il profilo e premere il bottone \"Seguire\"" - step5_4: "Se l'altro profilo ha un lucchetto vicino al nome, significa che occorre un po' di tempo prima che approvi manualmente la tua richiesta di follow." - step6_1: "Adesso, dovresti essere in grado di vedere le note dagli altri profili sulla tua timeline." - step6_2: "Puoi anche rispondere alle note con un click, scegliendo le reazioni immediate." - step6_3: "Per inviare una reazione, premi l'icona + della nota e scegli l'emoji che vuoi mandare." - step7_1: "Congratulazioni! Hai completato l'esercitazione iniziale su come usare Misskey." - step7_2: "Se vuoi saperne di più su Misskey, puoi dare un'occhiata alla sezione {help}." - step7_3: "Da ultimo, buon divertimento su Misskey! 🚀" - step8_1: "Per concludere, vuoi attivare le notifiche push?" - step8_2: "Attivandole, otterrai notifiche di follow, reazioni e menzioni anche quando Misskey è chiuso." - step8_3: "Potrai modificare questa impostazione." _2fa: alreadyRegistered: "La configurazione è stata già completata." registerTOTP: "Registra un'app di autenticazione" @@ -1890,6 +1899,7 @@ _deck: channel: "Canale" mentions: "Menzioni" direct: "Diretta" + roleTimeline: "Timeline Ruolo" _dialog: charactersExceeded: "Hai superato il limite di {max} caratteri! ({corrente})" charactersBelow: "Sei al di sotto del minimo di {min} caratteri! ({corrente})" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 092a4aed325ad5d961c1279311fb3e3a0722a3cf..8bbf9459f58d5fe0f372f0597c1b07c0f57f7db3 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -263,14 +263,16 @@ noMoreHistory: "ã“れよりéŽåŽ»ã®å±¥æ´ã¯ã‚ã‚Šã¾ã›ã‚“" startMessaging: "ãƒãƒ£ãƒƒãƒˆã‚’開始" nUsersRead: "{n}人ãŒèªã¿ã¾ã—ãŸ" agreeTo: "{0}ã«åŒæ„" +agree: "åŒæ„ã™ã‚‹" agreeBelow: "下記ã«åŒæ„ã™ã‚‹" basicNotesBeforeCreateAccount: "基本的ãªæ³¨æ„äº‹é …" -tos: "利用è¦ç´„" +termsOfService: "利用è¦ç´„" start: "始ã‚ã‚‹" home: "ホーム" remoteUserCaution: "リモートユーザーã®ãŸã‚ã€æƒ…å ±ãŒä¸å®Œå…¨ã§ã™ã€‚" activity: "アクティビティ" images: "ç”»åƒ" +image: "ç”»åƒ" birthday: "誕生日" yearsOld: "{age}æ³" registeredDate: "登録日" @@ -474,6 +476,8 @@ createAccount: "アカウントを作æˆ" existingAccount: "æ—¢å˜ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆ" regenerate: "å†ç”Ÿæˆ" fontSize: "フォントサイズ" +mediaListWithOneImageAppearance: "ç”»åƒãŒ1æžšã®ã¿ã®ãƒ¡ãƒ‡ã‚£ã‚¢ãƒªã‚¹ãƒˆã®é«˜ã•" +limitTo: "{x}を上é™ã«" noFollowRequests: "フォãƒãƒ¼ç”³è«‹ã¯ã‚ã‚Šã¾ã›ã‚“" openImageInNewTab: "ç”»åƒã‚’æ–°ã—ã„タブã§é–‹ã" dashboard: "ダッシュボード" @@ -556,6 +560,7 @@ accountDeletedDescription: "ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯å‰Šé™¤ã•ã‚Œã¦ã„ã¾ã™ã€‚" menu: "メニュー" divider: "分割線" addItem: "é …ç›®ã‚’è¿½åŠ " +rearrange: "並ã³æ›¿ãˆ" relays: "リレー" addRelay: "リレーã®è¿½åŠ " inboxUrl: "inboxã®URL" @@ -699,6 +704,8 @@ contact: "連絡先" useSystemFont: "システムã®ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã®ãƒ•ã‚©ãƒ³ãƒˆã‚’使ã†" clips: "クリップ" experimentalFeatures: "実験的機能" +experimental: "実験的" +thisIsExperimentalFeature: "ã“ã‚Œã¯å®Ÿé¨“çš„ãªæ©Ÿèƒ½ã§ã™ã€‚仕様ãŒå¤‰æ›´ã•ã‚ŒãŸã‚Šã€æ£å¸¸ã«å‹•ä½œã—ãªã‹ã£ãŸã‚Šã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ã€‚" developer: "開発者" makeExplorable: "アカウントを見ã¤ã‘ã‚„ã™ãã™ã‚‹" makeExplorableDescription: "オフã«ã™ã‚‹ã¨ã€ã€Œã¿ã¤ã‘ã‚‹ã€ã«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒè¼‰ã‚‰ãªããªã‚Šã¾ã™ã€‚" @@ -905,6 +912,7 @@ remoteOnly: "リモートã®ã¿" failedToUpload: "アップãƒãƒ¼ãƒ‰å¤±æ•—" cannotUploadBecauseInappropriate: "ä¸é©åˆ‡ãªå†…容をå«ã‚€å¯èƒ½æ€§ãŒã‚ã‚‹ã¨åˆ¤å®šã•ã‚ŒãŸãŸã‚アップãƒãƒ¼ãƒ‰ã§ãã¾ã›ã‚“。" cannotUploadBecauseNoFreeSpace: "ドライブã®ç©ºã容é‡ãŒç„¡ã„ãŸã‚アップãƒãƒ¼ãƒ‰ã§ãã¾ã›ã‚“。" +cannotUploadBecauseExceedsFileSizeLimit: "ファイルサイズã®åˆ¶é™ã‚’超ãˆã¦ã„ã‚‹ãŸã‚アップãƒãƒ¼ãƒ‰ã§ãã¾ã›ã‚“。" beta: "ベータ" enableAutoSensitive: "自動NSFW判定" enableAutoSensitiveDescription: "利用å¯èƒ½ãªå ´åˆã¯ã€æ©Ÿæ¢°å¦ç¿’を利用ã—ã¦è‡ªå‹•ã§ãƒ¡ãƒ‡ã‚£ã‚¢ã«NSFWフラグをè¨å®šã—ã¾ã™ã€‚ã“ã®æ©Ÿèƒ½ã‚’オフã«ã—ã¦ã‚‚ã€ã‚µãƒ¼ãƒãƒ¼ã«ã‚ˆã£ã¦ã¯è‡ªå‹•ã§è¨å®šã•ã‚Œã‚‹ã“ã¨ãŒã‚ã‚Šã¾ã™ã€‚" @@ -938,6 +946,7 @@ didYouLikeMisskey: "Misskeyã‚’æ°—ã«å…¥ã£ã¦ã„ãŸã ã‘ã¾ã—ãŸã‹ï¼Ÿ" pleaseDonate: "Misskeyã¯{host}ãŒä½¿ç”¨ã—ã¦ã„ã‚‹ç„¡æ–™ã®ã‚½ãƒ•ãƒˆã‚¦ã‚§ã‚¢ã§ã™ã€‚ã“ã‚Œã‹ã‚‰ã‚‚開発を続ã‘られるよã†ã«ã€ãœã²å¯„付をãŠé¡˜ã„ã—ã¾ã™ï¼" roles: "ãƒãƒ¼ãƒ«" role: "ãƒãƒ¼ãƒ«" +noRole: "ãƒãƒ¼ãƒ«ã¯ã‚ã‚Šã¾ã›ã‚“" normalUser: "一般ユーザー" undefined: "未定義" assign: "アサイン" @@ -947,6 +956,10 @@ manageCustomEmojis: "カスタム絵文å—ã®ç®¡ç†" youCannotCreateAnymore: "ã“れ以上作æˆã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。" cannotPerformTemporary: "一時的ã«åˆ©ç”¨ã§ãã¾ã›ã‚“" cannotPerformTemporaryDescription: "æ“作回数ãŒåˆ¶é™ã‚’超éŽã™ã‚‹ãŸã‚一時的ã«åˆ©ç”¨ã§ãã¾ã›ã‚“。ã—ã°ã‚‰ã時間を置ã„ã¦ã‹ã‚‰å†åº¦ãŠè©¦ã—ãã ã•ã„。" +invalidParamError: "パラメータエラー" +invalidParamErrorDescription: "リクエストパラメータã«å•é¡ŒãŒã‚ã‚Šã¾ã™ã€‚通常ã“ã‚Œã¯ãƒã‚°ã§ã™ãŒã€å…¥åŠ›ã—ãŸæ–‡å—æ•°ãŒå¤šã™ãŽã‚‹ç‰ã®å¯èƒ½æ€§ã‚‚ã‚ã‚Šã¾ã™ã€‚" +permissionDeniedError: "æ“作ãŒæ‹’å¦ã•ã‚Œã¾ã—ãŸ" +permissionDeniedErrorDescription: "ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«ã¯ã“ã®æ“作を行ã†ãŸã‚ã®æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。" preset: "プリセット" selectFromPresets: "プリセットã‹ã‚‰é¸æŠž" achievements: "実績" @@ -990,18 +1003,74 @@ enableChartsForFederatedInstances: "リモートサーãƒãƒ¼ã®ãƒãƒ£ãƒ¼ãƒˆã‚’ showClipButtonInNoteFooter: "ノートã®ã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã«ã‚¯ãƒªãƒƒãƒ—ã‚’è¿½åŠ " largeNoteReactions: "ノートã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã‚’大ãã表示" noteIdOrUrl: "ノートIDã¾ãŸã¯URL" -accountMigration: "アカウントã®å¼•ã£è¶Šã—" -accountMoved: "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯æ–°ã—ã„アカウントã«å¼•ã£è¶Šã—ã¾ã—ãŸï¼š" +video: "å‹•ç”»" +videos: "å‹•ç”»" +dataSaver: "データセーãƒãƒ¼" +accountMigration: "アカウントã®ç§»è¡Œ" +accountMoved: "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯æ–°ã—ã„アカウントã«ç§»è¡Œã—ã¾ã—ãŸï¼š" +accountMovedShort: "ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯ç§»è¡Œã•ã‚Œã¦ã„ã¾ã™" +operationForbidden: "ã“ã®æ“作ã¯ã§ãã¾ã›ã‚“" forceShowAds: "常ã«åºƒå‘Šã‚’表示ã™ã‚‹" +addMemo: "ãƒ¡ãƒ¢ã‚’è¿½åŠ " +editMemo: "メモを編集" +reactionsList: "リアクション一覧" +renotesList: "Renote一覧" +notificationDisplay: "通知ã®è¡¨ç¤º" +leftTop: "左上" +rightTop: "å³ä¸Š" +leftBottom: "左下" +rightBottom: "å³ä¸‹" +stackAxis: "スタック方å‘" +vertical: "縦" +horizontal: "横" +position: "ä½ç½®" +serverRules: "サーãƒãƒ¼ãƒ«ãƒ¼ãƒ«" +pleaseConfirmBelowBeforeSignup: "ã“ã®ã‚µãƒ¼ãƒãƒ¼ã«ç™»éŒ²ã™ã‚‹å‰ã«ã€ä»¥ä¸‹ã‚’確èªã—ã¦ãã ã•ã„。" +pleaseAgreeAllToContinue: "続ã‘ã‚‹ã«ã¯ã€å…¨ã¦ã®ã€ŒåŒæ„ã™ã‚‹ã€ã«ãƒã‚§ãƒƒã‚¯ãŒå…¥ã£ã¦ã„ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚" +continue: "続ã‘ã‚‹" +preservedUsernames: "予約ユーザーå" +preservedUsernamesDescription: "予約ã™ã‚‹ãƒ¦ãƒ¼ã‚¶ãƒ¼åを改行ã§åˆ—挙ã—ã¾ã™ã€‚ã“ã“ã§æŒ‡å®šã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼åã¯ã‚¢ã‚«ã‚¦ãƒ³ãƒˆä½œæˆæ™‚ã«ä½¿ãˆãªããªã‚Šã¾ã™ãŒã€ç®¡ç†è€…ã«ã‚ˆã‚‹ã‚¢ã‚«ã‚¦ãƒ³ãƒˆä½œæˆæ™‚ã¯ã“ã®åˆ¶é™ã‚’å—ã‘ã¾ã›ã‚“。ã¾ãŸã€æ—¢ã«å˜åœ¨ã™ã‚‹ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚‚影響をå—ã‘ã¾ã›ã‚“。" +createNoteFromTheFile: "ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‹ã‚‰ãƒŽãƒ¼ãƒˆã‚’作æˆ" +archive: "アーカイブ" +channelArchiveConfirmTitle: "{name}をアーカイブã—ã¾ã™ã‹ï¼Ÿ" +channelArchiveConfirmDescription: "アーカイブã™ã‚‹ã¨ã€ãƒãƒ£ãƒ³ãƒãƒ«ä¸€è¦§ã‚„検索çµæžœã«è¡¨ç¤ºã•ã‚Œãªããªã‚Šã€æ–°ãŸãªæ›¸ãè¾¼ã¿ã‚‚ã§ããªããªã‚Šã¾ã™ã€‚" +thisChannelArchived: "ã“ã®ãƒãƒ£ãƒ³ãƒãƒ«ã¯ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–ã•ã‚Œã¦ã„ã¾ã™ã€‚" +displayOfNote: "ノートã®è¡¨ç¤º" +initialAccountSetting: "åˆæœŸè¨å®š" +youFollowing: "フォãƒãƒ¼ä¸" + +_initialAccountSetting: + accountCreated: "アカウントã®ä½œæˆãŒå®Œäº†ã—ã¾ã—ãŸï¼" + letsStartAccountSetup: "アカウントã®åˆæœŸè¨å®šã‚’è¡Œã„ã¾ã—ょã†ã€‚" + letsFillYourProfile: "ã¾ãšã¯ã‚ãªãŸã®ãƒ—ãƒãƒ•ã‚£ãƒ¼ãƒ«ã‚’è¨å®šã—ã¾ã—ょã†ã€‚" + profileSetting: "プãƒãƒ•ã‚£ãƒ¼ãƒ«è¨å®š" + theseSettingsCanEditLater: "ã“れらã®è¨å®šã¯å¾Œã‹ã‚‰å¤‰æ›´ã§ãã¾ã™ã€‚" + youCanEditMoreSettingsInSettingsPageLater: "ã“ã®ä»–ã«ã‚‚様々ãªè¨å®šã‚’「è¨å®šã€ãƒšãƒ¼ã‚¸ã‹ã‚‰è¡Œãˆã¾ã™ã€‚ãœã²å¾Œã§ç¢ºèªã—ã¦ã¿ã¦ãã ã•ã„。" + followUsers: "タイムラインを構築ã™ã‚‹ãŸã‚ã€æ°—ã«ãªã‚‹ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’フォãƒãƒ¼ã—ã¦ã¿ã¾ã—ょã†ã€‚" + pushNotificationDescription: "プッシュ通知を有効ã«ã™ã‚‹ã¨{name}ã®é€šçŸ¥ã‚’ãŠä½¿ã„ã®ãƒ‡ãƒã‚¤ã‚¹ã§å—ã‘å–ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚" + initialAccountSettingCompleted: "åˆæœŸè¨å®šãŒå®Œäº†ã—ã¾ã—ãŸï¼" + haveFun: "{name}ã‚’ãŠæ¥½ã—ã¿ãã ã•ã„ï¼" + ifYouNeedLearnMore: "{name}(Misskey)ã®ä½¿ã„æ–¹ãªã©ã‚’詳ã—ã知るã«ã¯{link}ã‚’ã”覧ãã ã•ã„。" + skipAreYouSure: "åˆæœŸè¨å®šã‚’スã‚ップã—ã¾ã™ã‹ï¼Ÿ" + +_serverRules: + description: "æ–°è¦ç™»éŒ²å‰ã«è¡¨ç¤ºã™ã‚‹ã€ã‚µãƒ¼ãƒãƒ¼ã®ç°¡æ½”ãªãƒ«ãƒ¼ãƒ«ã‚’è¨å®šã—ã¾ã™ã€‚内容ã¯åˆ©ç”¨è¦ç´„ã®è¦ç´„ã¨ã™ã‚‹ã“ã¨ã‚’推奨ã—ã¾ã™ã€‚" _accountMigration: - moveTo: "ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’æ–°ã—ã„アカウントã«å¼•ã£è¶Šã™" - moveToLabel: "引ã£è¶Šã—å…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆï¼š" - moveAccountDescription: "ã“ã®æ“作ã¯å–り消ã›ã¾ã›ã‚“。ã¾ãšã¯å¼•ã£è¶Šã—å…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«å¯¾ã—エイリアスを作æˆã—ãŸã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。エイリアス作æˆå¾Œã€å¼•ã£è¶Šã—å…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’ã“ã®ã‚ˆã†ã«å…¥åŠ›ã—ã¦ãã ã•ã„:@person@instance.com" - moveFrom: "別ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‹ã‚‰ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«å¼•ã£è¶Šã™" - moveFromLabel: "引ã£è¶Šã—å…ƒã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆï¼š" - moveFromDescription: "別ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‹ã‚‰ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«ãƒ•ã‚©ãƒãƒ¯ãƒ¼ã‚’引ã継ã„ã§å¼•ã£è¶Šã—ãŸã„å ´åˆã€ã“ã“ã§ã‚¨ã‚¤ãƒªã‚¢ã‚¹ã‚’作æˆã—ã¦ãŠãå¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚å¿…ãšå¼•ã£è¶Šã—を実行ã™ã‚‹å‰ã«ä½œæˆã—ã¦ãã ã•ã„ï¼å¼•ã£è¶Šã—å…ƒã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’ã“ã®ã‚ˆã†ã«å…¥åŠ›ã—ã¦ãã ã•ã„:@person@instance.com" - migrationConfirm: "本当ã«ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’ {account} ã«å¼•ã£è¶Šã—ã¾ã™ã‹ï¼Ÿä¸€åº¦å¼•ã£è¶Šã—ã‚’è¡Œã†ã¨å–り消ã›ãšã€äºŒåº¦ã¨ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’å…ƒã®çŠ¶æ…‹ã§ä½¿ç”¨ã§ããªããªã‚Šã¾ã™ã€‚\nã¾ãŸã€å¼•ã£è¶Šã—å…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã‚¨ã‚¤ãƒªã‚¢ã‚¹ã‚’作æˆã—ãŸã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。" + moveFrom: "別ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‹ã‚‰ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«ç§»è¡Œ" + moveFromSub: "別ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¸ã‚¨ã‚¤ãƒªã‚¢ã‚¹ã‚’作æˆ" + moveFromLabel: "移行元ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆ #{n}" + moveFromDescription: "別ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‹ã‚‰ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«ç§»è¡Œã—ãŸã„å ´åˆã€ã“ã“ã§ã‚¨ã‚¤ãƒªã‚¢ã‚¹ã‚’作æˆã—ã¦ãŠãå¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚\n移行元ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’ã“ã®ã‚ˆã†ã«å…¥åŠ›ã—ã¦ãã ã•ã„: @username@server.example.com\n削除ã™ã‚‹ã«ã¯ã€å…¥åŠ›æ¬„を空ã«ã—ã¦ä¿å˜ã—ã¾ã™ï¼ˆéžæŽ¨å¥¨ï¼‰ã€‚" + moveTo: "ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’æ–°ã—ã„アカウントã¸ç§»è¡Œ" + moveToLabel: "移行先ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆ:" + moveCannotBeUndone: "アカウントを移行ã™ã‚‹ã¨ã€å–り消ã™ã“ã¨ã¯ã§ãã¾ã›ã‚“。" + moveAccountDescription: "æ–°ã—ã„アカウントã¸ç§»è¡Œã—ã¾ã™ã€‚\n ・フォãƒãƒ¯ãƒ¼ãŒæ–°ã—ã„アカウントを自動ã§ãƒ•ã‚©ãƒãƒ¼ã—ã¾ã™\n ・ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‹ã‚‰ã®ãƒ•ã‚©ãƒãƒ¼ã¯å…¨ã¦è§£é™¤ã•ã‚Œã¾ã™\n ・ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã¯ãƒŽãƒ¼ãƒˆã®ä½œæˆãªã©ãŒã§ããªããªã‚Šã¾ã™\n\nフォãƒãƒ¯ãƒ¼ã®ç§»è¡Œã¯è‡ªå‹•ã§ã™ãŒã€ãƒ•ã‚©ãƒãƒ¼ã®ç§»è¡Œã¯æ‰‹å‹•ã§è¡Œã†å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚移行å‰ã«ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ãƒ•ã‚©ãƒãƒ¼ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆã—ã€ç§»è¡Œå¾Œã™ãã«ç§»è¡Œå…ˆã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’è¡Œãªã£ã¦ãã ã•ã„。\nリスト・ミュート・ブãƒãƒƒã‚¯ã«ã¤ã„ã¦ã‚‚åŒæ§˜ã§ã™ã®ã§ã€æ‰‹å‹•ã§ç§»è¡Œã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚\n\n(ã“ã®èª¬æ˜Žã¯ã“ã®ã‚µãƒ¼ãƒãƒ¼ï¼ˆMisskey v13.12.0以é™ï¼‰ã®ä»•æ§˜ã§ã™ã€‚Mastodonãªã©ã®ä»–ã®ActivityPubソフトウェアã§ã¯æŒ™å‹•ãŒç•°ãªã‚‹å ´åˆãŒã‚ã‚Šã¾ã™ã€‚)" + moveAccountHowTo: "アカウントã®ç§»è¡Œã«ã¯ã€ã¾ãšã¯ç§»è¡Œå…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«å¯¾ã—エイリアスを作æˆã—ã¾ã™ã€‚\nエイリアス作æˆå¾Œã€ç§»è¡Œå…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’次ã®ã‚ˆã†ã«å…¥åŠ›ã—ã¦ãã ã•ã„: @username@server.example.com" + startMigration: "移行ã™ã‚‹" + migrationConfirm: "本当ã«ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’ {account} ã«ç§»è¡Œã—ã¾ã™ã‹ï¼Ÿä¸€åº¦ç§»è¡Œã™ã‚‹ã¨å–り消ã›ãšã€äºŒåº¦ã¨ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’å…ƒã®çŠ¶æ…‹ã§ä½¿ç”¨ã§ããªããªã‚Šã¾ã™ã€‚" + movedAndCannotBeUndone: "\nアカウントã¯ç§»è¡Œã•ã‚Œã¦ã„ã¾ã™ã€‚\n移行をå–り消ã™ã“ã¨ã¯ã§ãã¾ã›ã‚“。" + postMigrationNote: "ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‹ã‚‰ã®ãƒ•ã‚©ãƒãƒ¼è§£é™¤ã¯ç§»è¡Œæ“作ã‹ã‚‰24時間後ã«å®Ÿè¡Œã•ã‚Œã¾ã™ã€‚\nã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®ãƒ•ã‚©ãƒãƒ¼ãƒ»ãƒ•ã‚©ãƒãƒ¯ãƒ¼æ•°ã¯0ã«ãªã£ã¦ã„ã¾ã™ã€‚フォãƒãƒ¯ãƒ¼ã®è§£é™¤ã¯ã•ã‚Œãªã„ãŸã‚ã€ã‚ãªãŸã®ãƒ•ã‚©ãƒãƒ¯ãƒ¼ã¯ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®ãƒ•ã‚©ãƒãƒ¯ãƒ¼å‘ã‘投稿を引ã続ã閲覧ã§ãã¾ã™ã€‚" + movedTo: "移行先ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆ:" _achievements: earnedAt: "ç²å¾—日時" @@ -1174,6 +1243,9 @@ _achievements: _client30min: title: "ã²ã¨ã‚„ã™ã¿" description: "クライアントを起動ã—ã¦ã‹ã‚‰30分以上経éŽã—ãŸ" + _client60min: + title: "Misskeyã®è¦‹ã™ãŽ" + description: "クライアントを起動ã—ã¦ã‹ã‚‰60分以上経éŽã—ãŸ" _noteDeletedWithin1min: title: "ã„ã¾ã®ãªã—" description: "投稿ã—ã¦ã‹ã‚‰1分以内ã«ãã®æŠ•ç¨¿ã‚’削除ã—ãŸ" @@ -1263,6 +1335,8 @@ _role: iconUrl: "アイコン画åƒã®URL" asBadge: "ãƒãƒƒã‚¸ã¨ã—ã¦è¡¨ç¤º" descriptionOfAsBadge: "オンã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼åã®æ¨ªã«ãƒãƒ¼ãƒ«ã®ã‚¢ã‚¤ã‚³ãƒ³ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚" + isExplorable: "ãƒãƒ¼ãƒ«ã‚¿ã‚¤ãƒ ラインを公開" + descriptionOfIsExplorable: "オンã«ã™ã‚‹ã¨ã€ãƒãƒ¼ãƒ«ã®ã‚¿ã‚¤ãƒ ラインを公開ã—ã¾ã™ã€‚ãƒãƒ¼ãƒ«ã®å…¬é–‹ãŒã‚ªãƒ•ã®å ´åˆã€ã‚¿ã‚¤ãƒ ラインã®å…¬é–‹ã¯ã•ã‚Œã¾ã›ã‚“。" displayOrder: "è¡¨ç¤ºé †" descriptionOfDisplayOrder: "数値ãŒå¤§ãã„ã»ã©UI上ã§å…ˆé ã«è¡¨ç¤ºã•ã‚Œã¾ã™ã€‚" canEditMembersByModerator: "モデレーターã®ãƒ¡ãƒ³ãƒãƒ¼ç·¨é›†ã‚’許å¯" @@ -1279,6 +1353,7 @@ _role: canInvite: "サーãƒãƒ¼æ‹›å¾…コードã®ç™ºè¡Œ" canManageCustomEmojis: "カスタム絵文å—ã®ç®¡ç†" driveCapacity: "ドライブ容é‡" + alwaysMarkNsfw: "ファイルã«NSFWを常ã«ä»˜ä¸Ž" pinMax: "ノートã®ãƒ”ン留ã‚ã®æœ€å¤§æ•°" antennaMax: "アンテナã®ä½œæˆå¯èƒ½æ•°" wordMuteMax: "ワードミュートã®æœ€å¤§æ–‡å—æ•°" @@ -1556,32 +1631,16 @@ _time: hour: "時間" day: "æ—¥" -_tutorial: +_timelineTutorial: title: "Misskeyã®ä½¿ã„æ–¹" - step1_1: "よã†ã“ã。" - step1_2: "ã“ã®ç”»é¢ã¯ã€Œã‚¿ã‚¤ãƒ ラインã€ã¨å‘¼ã°ã‚Œã€ã‚ãªãŸã‚„ã€ã‚ãªãŸãŒã€Œãƒ•ã‚©ãƒãƒ¼ã€ã™ã‚‹äººã®ã€ŒãƒŽãƒ¼ãƒˆã€ãŒæ™‚系列ã§è¡¨ç¤ºã•ã‚Œã¾ã™ã€‚" - step1_3: "ã‚ãªãŸã¯ã¾ã 何もノートを投稿ã—ã¦ãŠã‚‰ãšã€èª°ã‚‚フォãƒãƒ¼ã—ã¦ã„ãªã„ã®ã§ã€ã‚¿ã‚¤ãƒ ラインã«ã¯ä½•ã‚‚表示ã•ã‚Œã¦ã„ãªã„ã¯ãšã§ã™ã€‚" - step2_1: "ノートを作æˆã—ãŸã‚Šèª°ã‹ã‚’フォãƒãƒ¼ã—ãŸã‚Šã™ã‚‹å‰ã«ã€ã¾ãšã‚ãªãŸã®ãƒ—ãƒãƒ•ã‚£ãƒ¼ãƒ«ã‚’完æˆã•ã›ã¾ã—ょã†ã€‚" - step2_2: "ã‚ãªãŸãŒã©ã‚“ãªäººã‹ã‚ã‹ã‚‹ã¨ã€å¤šãã®äººã«ãƒŽãƒ¼ãƒˆã‚’見ã¦ã‚‚らãˆãŸã‚Šã€ãƒ•ã‚©ãƒãƒ¼ã—ã¦ã‚‚らã„ã‚„ã™ããªã‚Šã¾ã™ã€‚" - step3_1: "プãƒãƒ•ã‚£ãƒ¼ãƒ«è¨å®šã¯ã†ã¾ãã§ãã¾ã—ãŸã‹ï¼Ÿ" - step3_2: "ã§ã¯è©¦ã—ã«ã€ä½•ã‹ãƒŽãƒ¼ãƒˆã‚’投稿ã—ã¦ã¿ã¦ãã ã•ã„。画é¢ä¸Šã«ã‚る鉛ç†ãƒžãƒ¼ã‚¯ã®ãƒœã‚¿ãƒ³ã‚’押ã™ã¨ãƒ•ã‚©ãƒ¼ãƒ ãŒé–‹ãã¾ã™ã€‚" - step3_3: "内容を書ã„ãŸã‚‰ã€ãƒ•ã‚©ãƒ¼ãƒ å³ä¸Šã®ãƒœã‚¿ãƒ³ã‚’押ã™ã¨æŠ•ç¨¿ã§ãã¾ã™ã€‚" - step3_4: "内容ãŒæ€ã„ã¤ã‹ãªã„?「Misskey始ã‚ã¾ã—ãŸã€ã¨ã„ã†ã®ã¯ã„ã‹ãŒã§ã—ょã†ã€‚" - step4_1: "投稿ã§ãã¾ã—ãŸã‹ï¼Ÿ" - step4_2: "ã‚ãªãŸã®ãƒŽãƒ¼ãƒˆãŒã‚¿ã‚¤ãƒ ラインã«è¡¨ç¤ºã•ã‚Œã¦ã„ã‚Œã°æˆåŠŸã§ã™ã€‚" - step5_1: "次ã¯ã€ä»–ã®äººã‚’フォãƒãƒ¼ã—ã¦ã‚¿ã‚¤ãƒ ラインを賑やã‹ã«ã—ãŸã„ã¨ã“ã‚ã§ã™ã€‚" - step5_2: "{featured}ã§äººæ°—ã®ãƒŽãƒ¼ãƒˆãŒè¦‹ã‚Œã‚‹ã®ã§ã€ãã®ä¸ã‹ã‚‰æ°—ã«ãªã£ãŸäººã‚’é¸ã‚“ã§ãƒ•ã‚©ãƒãƒ¼ã—ãŸã‚Šã€{explore}ã§äººæ°—ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’探ã™ã“ã¨ã‚‚ã§ãã¾ã™ã€‚" - step5_3: "ユーザーをフォãƒãƒ¼ã™ã‚‹ã«ã¯ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ã‚¢ã‚¤ã‚³ãƒ³ã‚’クリックã—ã¦ãƒ¦ãƒ¼ã‚¶ãƒ¼ãƒšãƒ¼ã‚¸ã‚’表示ã—ã€ã€Œãƒ•ã‚©ãƒãƒ¼ã€ãƒœã‚¿ãƒ³ã‚’押ã—ã¾ã™ã€‚" - step5_4: "ユーザーã«ã‚ˆã£ã¦ã¯ã€ãƒ•ã‚©ãƒãƒ¼ãŒæ‰¿èªã•ã‚Œã‚‹ã¾ã§æ™‚é–“ãŒã‹ã‹ã‚‹å ´åˆãŒã‚ã‚Šã¾ã™ã€‚" - step6_1: "タイムラインã«ä»–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒŽãƒ¼ãƒˆãŒè¡¨ç¤ºã•ã‚Œã¦ã„ã‚Œã°æˆåŠŸã§ã™ã€‚" - step6_2: "ä»–ã®äººã®ãƒŽãƒ¼ãƒˆã«ã¯ã€ã€Œãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã€ã‚’付ã‘ã‚‹ã“ã¨ãŒã§ãã€ç°¡å˜ã«ã‚ãªãŸã®å応をä¼ãˆã‚‰ã‚Œã¾ã™ã€‚" - step6_3: "リアクションを付ã‘ã‚‹ã«ã¯ã€ãƒŽãƒ¼ãƒˆã®ã€Œ+ã€ãƒžãƒ¼ã‚¯ã‚’クリックã—ã¦ã€å¥½ããªãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã‚’é¸æŠžã—ã¾ã™ã€‚" - step7_1: "ã“ã‚Œã§ã€Misskeyã®åŸºæœ¬çš„ãªä½¿ã„æ–¹ã®èª¬æ˜Žã¯çµ‚ã‚ã‚Šã¾ã—ãŸã€‚ãŠç–²ã‚Œæ§˜ã§ã—ãŸã€‚" - step7_2: "ã‚‚ã£ã¨Misskeyã«ã¤ã„ã¦çŸ¥ã‚ŠãŸã„ã¨ãã¯ã€{help}を見ã¦ã¿ã¦ãã ã•ã„。" - step7_3: "ã§ã¯ã€Misskeyã‚’ãŠæ¥½ã—ã¿ãã ã•ã„🚀" - step8_1: "最後ã«ã€ãƒ—ッシュ通知を有効化ã—ã¦ã¿ã¾ã›ã‚“ã‹ï¼Ÿ" - step8_2: "プッシュ通知をå—ã‘å–ã‚‹ã“ã¨ã§ã€Misskeyã‚’é–‹ã„ã¦ã„ãªã„時ã«ã‚‚リアクションやフォãƒãƒ¼ã€ãƒ¡ãƒ³ã‚·ãƒ§ãƒ³ãªã©ã«æ°—ã¥ã‘ã¾ã™ã€‚" - step8_3: "通知ã®è¨å®šã¯å¾Œã‹ã‚‰å¤‰æ›´ã§ãã¾ã™ã€‚" + step1_1: "ã“ã®ç”»é¢ã¯ã€Œã‚¿ã‚¤ãƒ ラインã€ã§ã™ã€‚{name}ã«æŠ•ç¨¿ã•ã‚ŒãŸã€ŒãƒŽãƒ¼ãƒˆã€ãŒæ™‚系列ã§è¡¨ç¤ºã•ã‚Œã¾ã™ã€‚" + step1_2: "タイムラインã«ã¯ã„ãã¤ã‹ç¨®é¡žãŒã‚ã‚Šã€ä¾‹ãˆã°ã€Œãƒ›ãƒ¼ãƒ タイムラインã€ã«ã¯ã‚ãªãŸãŒãƒ•ã‚©ãƒãƒ¼ã—ã¦ã„る人ã®ãƒŽãƒ¼ãƒˆãŒæµã‚Œã€ã€Œãƒãƒ¼ã‚«ãƒ«ã‚¿ã‚¤ãƒ ラインã€ã«ã¯{name}全体ã®ãƒŽãƒ¼ãƒˆãŒæµã‚Œã¾ã™ã€‚" + step2_1: "試ã—ã«ã€ä½•ã‹ãƒŽãƒ¼ãƒˆã‚’投稿ã—ã¦ã¿ã¾ã—ょã†ã€‚ç”»é¢ä¸Šã«ã‚る鉛ç†ãƒžãƒ¼ã‚¯ã®ãƒœã‚¿ãƒ³ã‚’押ã™ã¨ãƒ•ã‚©ãƒ¼ãƒ ãŒé–‹ãã¾ã™ã€‚" + step2_2: "åˆã‚ã¦ã®ãƒŽãƒ¼ãƒˆã®å†…容ã¯ã€ã‚ãªãŸã®è‡ªå·±ç´¹ä»‹ã‚„「{name}始ã‚ã¾ã—ãŸã€ãªã©ãŒãŠã™ã™ã‚ã§ã™ã€‚" + step3_1: "投稿ã§ãã¾ã—ãŸã‹ï¼Ÿ" + step3_2: "ã‚ãªãŸã®ãƒŽãƒ¼ãƒˆãŒã‚¿ã‚¤ãƒ ラインã«è¡¨ç¤ºã•ã‚Œã¦ã„ã‚Œã°æˆåŠŸã§ã™ã€‚" + step4_1: "ノートã«ã¯ã€ã€Œãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã€ã‚’付ã‘ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚" + step4_2: "リアクションを付ã‘ã‚‹ã«ã¯ã€ãƒŽãƒ¼ãƒˆã®ã€Œ+ã€ãƒžãƒ¼ã‚¯ã‚’クリックã—ã¦ã€å¥½ããªçµµæ–‡å—ã‚’é¸æŠžã—ã¾ã™ã€‚" _2fa: alreadyRegistered: "æ—¢ã«è¨å®šã¯å®Œäº†ã—ã¦ã„ã¾ã™ã€‚" @@ -1763,7 +1822,7 @@ _profile: metadataDescription: "プãƒãƒ•ã‚£ãƒ¼ãƒ«ã«è¡¨ã¨ã—ã¦è¿½åŠ æƒ…å ±ã‚’è¡¨ç¤ºã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚" metadataLabel: "ラベル" metadataContent: "内容" - changeAvatar: "ã‚¢ãƒã‚¿ãƒ¼ç”»åƒã‚’変更" + changeAvatar: "アイコン画åƒã‚’変更" changeBanner: "ãƒãƒŠãƒ¼ç”»åƒã‚’変更" _exportOrImport: diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 4117a25b0e914a8d480750cba9a9f4d7b6ce87ec..512cce145282afed64a3763b71b1befc1f01b646 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -1,7 +1,7 @@ --- _lang_: "日本語 (関西å¼)" headlineMisskey: "ノートã§ã¤ãªãŒã‚‹ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯" -introMisskey: "よã†ãŠè¶Šã—ï¼Misskeyã¯ã€ã‚ªãƒ¼ãƒ—ンソースã®åˆ†æ•£åž‹ãƒžã‚¤ã‚¯ãƒãƒ–ãƒã‚°ã‚µãƒ¼ãƒ“スやãん。\n「ノートã€ã‚’作ã£ã¦ã€ã„ã¾èµ·ã“ã£ã¨ã‚‹ã“ã¨ã‚’共有ã—ãŸã‚Šã€ã‚ã‚“ãŸã«ã¤ã„ã¦çš†ã«ç™ºä¿¡ã—よã†ðŸ“¡\n「リアクションã€æ©Ÿèƒ½ã§ã€çš†ã®ãƒŽãƒ¼ãƒˆã«ç´ æ—©ãåå¿œã‚’è¿½åŠ ã—ãŸã‚Šã‚‚ã§ãã‚‹ã§âœŒ\nã»ãªæ–°ã—ã„世界を探検ã—よã‹ðŸš€" +introMisskey: "よã†ãŠè¶Šã—ï¼Misskeyã¯ã€ã‚ªãƒ¼ãƒ—ンソースã®åˆ†æ•£åž‹ãƒžã‚¤ã‚¯ãƒãƒ–ãƒã‚°ã‚µãƒ¼ãƒ“スやãん。\n「ノートã€ã‚’作ã£ã¦ã€ã„ã¾èµ·ã“ã£ã¨ã‚‹ã“ã¨ã‚’共有ã—ãŸã‚Šã€ã‚ã‚“ãŸã«ã¤ã„ã¦çš†ã«ç™ºä¿¡ã—よã†ðŸ“¡\n「ツッコミã€æ©Ÿèƒ½ã§ã€çš†ã®ãƒŽãƒ¼ãƒˆã«ç´ æ—©ãåå¿œã‚’è¿½åŠ ã—ãŸã‚Šã‚‚ã§ãã‚‹ã§âœŒ\nã»ãªæ–°ã—ã„世界を探検ã—よã‹ðŸš€" poweredByMisskeyDescription: "{name}ã¯ã€ã‚ªãƒ¼ãƒ—ンソースã®ãƒ—ラットフォーム<b>Misskey</b>ã®ã‚µãƒ¼ãƒãƒ¼ã®ã²ã¨ã¤ãªã‚“ã‚„ã§ã€‚" monthAndDay: "{month}月 {day}æ—¥" search: "探ã™" @@ -26,7 +26,7 @@ otherSettings: "ã»ã‹ã®è¨å®š" openInWindow: "ウィンドウã§é–‹ãã§" profile: "プãƒãƒ•ã‚£ãƒ¼ãƒ«" timeline: "タイムライン" -noAccountDescription: "自己紹介ã¯ã‚らã¸ã‚“" +noAccountDescription: "自己紹介食ã£ã¦ã‚‚ãŸ" login: "ãƒã‚°ã‚¤ãƒ³" loggingIn: "ãƒã‚°ã‚¤ãƒ³ã—よるã§" logout: "ãƒã‚°ã‚¢ã‚¦ãƒˆ" @@ -38,9 +38,9 @@ addUser: "ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’è¿½åŠ ã‚„" favorite: "ãŠæ°—ã«å…¥ã‚Š" favorites: "ãŠæ°—ã«å…¥ã‚Š" unfavorite: "ã‚„ã£ã±æ°—ã«å…¥ã‚‰ã‚“" -favorited: "ãŠæ°—ã«å…¥ã‚Šã«ç™»éŒ²ã—ãŸã§" +favorited: "ãŠæ°—ã«å…¥ã‚Šã«å…¥ã‚ŒãŸã§" alreadyFavorited: "ã‚‚ã†ãŠæ°—ã«å…¥ã‚Šã«å…¥ã‚Œã¨ã‚‹ãŒãªã€‚" -cantFavorite: "アカンã€ãŠæ°—ã«å…¥ã‚Šç™»éŒ²ã§ãã¸ã‚“ã‹ã£ãŸã§ã€‚" +cantFavorite: "アカンã€ãŠæ°—ã«å…¥ã‚Šã«å…¥ã‚Œã‚Œã‚“ã‹ã£ãŸã‚。" pin: "ピン留ã‚ã—ã¨ã" unpin: "ã‚„ã£ã±ãƒ”ン留ã‚ã›ã‚“" copyContent: "内容をコピー" @@ -61,7 +61,7 @@ youGotNewFollower: "フォãƒãƒ¼ã•ã‚ŒãŸã§" receiveFollowRequest: "フォãƒãƒ¼ãƒªã‚¯ã‚¨ã‚¹ãƒˆã•ã‚ŒãŸã§" followRequestAccepted: "フォãƒãƒ¼ãŒæ‰¿èªã•ã‚ŒãŸã§" mention: "メンション" -mentions: "ã†ã¡å®›ã¦" +mentions: "自分宛ã¦" directNotes: "ダイレクト投稿" importAndExport: "インãƒãƒ¼ãƒˆã¨ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆ" import: "インãƒãƒ¼ãƒˆ" @@ -109,12 +109,12 @@ inChannelQuote: "ãƒãƒ£ãƒ³ãƒãƒ«å†…引用" pinnedNote: "ピン留ã‚ã•ã‚Œã¨ã‚‹ãƒŽãƒ¼ãƒˆ" pinned: "ピン留ã‚ã—ã¨ã" you: "ã‚ã‚“ãŸ" -clickToShow: "押ã—ãŸã‚‰å‡ºã‚‰" +clickToShow: "押ã—ãŸã‚‰è¦‹ãˆã‚‹ã§" sensitive: "æ°—ã„ã¤ã‘ã¦è¦‹ã„ã‚„" add: "増やã™" -reaction: "リアクション" -reactions: "リアクション" -reactionSetting: "ピッカーã«å‡ºã—ã¨ãリアクション" +reaction: "ツッコミ" +reactions: "ツッコミ" +reactionSetting: "ピッカーã«å‡ºã—ã¨ãツッコミ" reactionSettingDescription2: "ドラッグã§ä¸¦ã³æ›¿ãˆã€ã‚¯ãƒªãƒƒã‚¯ã§å‰Šé™¤ã€ï¼‹ã‚’押ã—ã¦è¿½åŠ ã‚„ã§ã€‚" rememberNoteVisibility: "公開範囲覚ãˆã¨ã„ã¦" attachCancel: "ã®ã£ã‘ã‚‹ã®ã‚„ã‚ã‚‹" @@ -263,14 +263,16 @@ noMoreHistory: "ã“れより昔ã®ã‚“ã¯ã‚らã¸ã‚“ã§" startMessaging: "ãƒãƒ£ãƒƒãƒˆã‚„ã‚‹ã§" nUsersRead: "{n}人ãŒèªã‚“ã§ã‚‚ã†ãŸ" agreeTo: "{0}ã«åŒæ„ã—ãŸã§" +agree: "ã›ã‚„ãª" agreeBelow: "下記ã«åŒæ„ã—ãŸã‚‹" basicNotesBeforeCreateAccount: "よã†èªã‚“ã§ã‚„ã£ã¦ã‚„" -tos: "利用è¦ç´„" +termsOfService: "使ã†ãŸã‚ã®æ±ºã‚事" start: "始ã‚ã‚‹" home: "ホーム" remoteUserCaution: "リモートユーザーやã‹ã‚‰ã€è¶³ã‚Šã²ã‚“æƒ…å ±ã‚ã‚‹ã‹ã‚‚ã—ã‚Œã¸ã‚“。" activity: "アクティビティ" images: "ç”»åƒ" +image: "ç”»åƒ" birthday: "生ã¾ã‚ŒãŸæ—¥" yearsOld: "{age}æ³" registeredDate: "始ã‚ãŸæ—¥" @@ -322,7 +324,7 @@ instanceName: "サーãƒãƒ¼å" instanceDescription: "サーãƒãƒ¼ã®ç´¹ä»‹" maintainerName: "管ç†è€…ã¯ã‚“ã®åå‰" maintainerEmail: "管ç†è€…ã¯ã‚“ã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹" -tosUrl: "利用è¦ç´„ã®URL" +tosUrl: "使ã†ãŸã‚ã®æ±ºã‚事ã®URL" thisYear: "今年" thisMonth: "今月" today: "今日" @@ -460,7 +462,7 @@ uiLanguage: "UIã®è¡¨ç¤ºè¨€èªž" aboutX: "{x}ã«ã¤ã„ã¦" emojiStyle: "絵文å—ã®ã‚¹ã‚¿ã‚¤ãƒ«" native: "ãƒã‚¤ãƒ†ã‚£ãƒ–" -disableDrawer: "メニューをドãƒãƒ¯ãƒ¼ã§è¡¨ç¤ºã›ã‡ã¸ã‚“" +disableDrawer: "メニューをドãƒãƒ¯ãƒ¼ã§è¡¨ç¤ºã›ãˆã¸ã‚“" showNoteActionsOnlyHover: "ノートã®æ“作部をホãƒãƒ¼æ™‚ã®ã¿è¡¨ç¤ºã™ã‚‹ã§" noHistory: "å±¥æ´ã¯ãªã„ã‚。" signinHistory: "ãƒã‚°ã‚¤ãƒ³å±¥æ´" @@ -474,6 +476,8 @@ createAccount: "アカウントを作るã§" existingAccount: "å‰ã«ä½œã£ãŸã‚¢ã‚«ã‚¦ãƒ³ãƒˆ" regenerate: "ã‚‚ã£ãºã‚“生æˆã™ã‚‹ã§" fontSize: "å—ã®å¤§ãã•" +mediaListWithOneImageAppearance: "ç”»åƒãŒ1æžšã®ã¿ã®ãƒ¡ãƒ‡ã‚£ã‚¢ãƒªã‚¹ãƒˆã®é«˜ã•" +limitTo: "{x}ã‚’ã„ã£ã±ã„ã«" noFollowRequests: "フォãƒãƒ¼ç”³è«‹ã¯ã‚らã¸ã‚“ã§" openImageInNewTab: "ç”»åƒã‚’æ–°ã—ã„タブã§é–‹ãã§" dashboard: "ダッシュボード" @@ -548,7 +552,7 @@ removeAllFollowingDescription: "{host}ã‹ã‚‰ã®ãƒ•ã‚©ãƒãƒ¼ã‚’ã™ã¹ã¦è§£é™¤ã™ userSuspended: "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯...å‡çµã•ã‚Œã¨ã‚‹ã€‚" userSilenced: "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯...サイレンスã•ã‚Œã¨ã‚‹ã€‚" yourAccountSuspendedTitle: "ã‚ã‚“ãŸã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆå‡çµã•ã‚Œã¨ã‚‹ã§" -yourAccountSuspendedDescription: "ã‚ã‚“ãŸã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯ã€ã‚µãƒ¼ãƒãƒ¼ã®åˆ©ç”¨è¦ç´„ã«é•åã—ãŸã¨ã‹ã®ç†ç”±ã§ã€å‡çµã•ã‚Œã¨ã‚‹ã§ã€‚ç´°ã‹ã„ã“ã¨ã¯ç®¡ç†è€…ã¾ã§ãŠå•ã„åˆã‚ã›ãŸã£ã¦ãªãƒ¼ã€‚絶対ã«æ–°ã—ã„アカウント作ã£ãŸã‚‰ã‚ã‹ã‚“ã§ã€‚絶対やã§ã€‚" +yourAccountSuspendedDescription: "ã‚ã‚“ãŸã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯ã€ã‚µãƒ¼ãƒãƒ¼ã®ä½¿ã†ãŸã‚ã®æ±ºã‚事ã«é•åã—ãŸã¨ã‹ã®ç†ç”±ã§ã€å‡çµã•ã‚Œã¨ã‚‹ã§ã€‚ç´°ã‹ã„ã“ã¨ã¯ç®¡ç†è€…ã¾ã§ãŠå•ã„åˆã‚ã›ãŸã£ã¦ãªãƒ¼ã€‚絶対ã«æ–°ã—ã„アカウント作ã£ãŸã‚‰ã‚ã‹ã‚“ã§ã€‚絶対やã§ã€‚" tokenRevoked: "トークンãŒç„¡åŠ¹ã‚„ã§" tokenRevokedDescription: "ãƒã‚°ã‚¤ãƒ³ãƒˆãƒ¼ã‚¯ãƒ³ãŒå¤±åŠ¹ã—ã¨ã‚‹ã§ã€‚ã‚‚ã£ã‹ã„ãƒã‚°ã‚¤ãƒ³ã—ã¦ã‚‚ã‚ã¦ã‚‚ãˆãˆã‹ï¼Ÿ" accountDeleted: "アカウントã¯å‰Šé™¤ã•ã‚Œã¨ã‚‹ã§" @@ -556,6 +560,7 @@ accountDeletedDescription: "ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯å‰Šé™¤ã•ã‚Œã¨ã‚‹ã§ã€‚" menu: "メニュー" divider: "分割線" addItem: "é …ç›®ã‚’è¿½åŠ " +rearrange: "並ã³æ›¿ãˆ" relays: "リレー" addRelay: "リレーã®è¿½åŠ " inboxUrl: "inboxã®URL" @@ -582,7 +587,7 @@ preferencesBackups: "è¨å®šã®ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—" deck: "デッã‚" undeck: "デッã‚解除" useBlurEffectForModal: "モーダルã«ã¼ã‹ã—効果を使用" -useFullReactionPicker: "フル機能ã«ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ãƒ”ッカーを使用" +useFullReactionPicker: "フル機能ã®çªã£è¾¼ã¿ãƒ”ッカーを使用" width: "å¹…" height: "高ã•" large: "大" @@ -675,8 +680,8 @@ repliedCount: "返信ã•ã‚ŒãŸæ•°ã‚„ã§" renotedCount: "Renoteã•ã‚ŒãŸæ•°ã‚„ã§" followingCount: "フォãƒãƒ¼æ•°ã‚„ã§" followersCount: "フォãƒãƒ¯ãƒ¼æ•°ã‚„ã§" -sentReactionsCount: "リアクションã—ãŸæ•°ã‚„ã§" -receivedReactionsCount: "リアクションã•ã‚ŒãŸæ•°" +sentReactionsCount: "ツッコんã æ•°" +receivedReactionsCount: "ツッコã¾ã‚ŒãŸæ•°" pollVotesCount: "アンケートã«æŠ•ç¥¨ã—ãŸæ•°" pollVotedCount: "アンケートã«æŠ•ç¥¨ã•ã‚ŒãŸæ•°" yes: "ãˆãˆã§" @@ -699,6 +704,8 @@ contact: "連絡先" useSystemFont: "システムã®ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã®ãƒ•ã‚©ãƒ³ãƒˆã‚’使ã†ã§" clips: "クリップ" experimentalFeatures: "ãŠãŸã‚ã—機能やã§" +experimental: "実験的" +thisIsExperimentalFeature: "ã“ã‚Œã¯å®Ÿé¨“çš„ãªæ©Ÿèƒ½ã‚„ã§ã€‚仕様ãŒå¤‰æ›´ã«ãªã£ãŸã‚Šã¡ã‚ƒã‚“ã¨å‹•ã‹ãªã‹ã£ãŸã‚Šã™ã‚‹ã‹ã‚‚ã‚„ã§ã€‚" developer: "開発者やã§" makeExplorable: "アカウントを見ã¤ã‘ã‚„ã™ãã™ã‚‹ã§" makeExplorableDescription: "オフã«ã™ã‚‹ã¨ã€ã€Œã¿ã¤ã‘ã‚‹ã€ã«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒè¼‰ã‚‰ã‚“ããªã‚‹ã§ã€‚" @@ -745,7 +752,7 @@ receiveAnnouncementFromInstance: "サーãƒãƒ¼ã‹ã‚‰ã®ãŠçŸ¥ã‚‰ã›ã‚’å—ã‘å– emailNotification: "メール通知" publish: "公開" inChannelSearch: "ãƒãƒ£ãƒ³ãƒãƒ«å†…検索" -useReactionPickerForContextMenu: "å³ã‚¯ãƒªãƒƒã‚¯ã§ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ãƒ”ッカーを開ãよã†ã«ã™ã‚‹" +useReactionPickerForContextMenu: "å³ã‚¯ãƒªãƒƒã‚¯ã§ãƒ„ッコミピッカーを開ãよã†ã«ã™ã‚‹" typingUsers: "{users}ãŒä»Šæ›¸ãよるã§" jumpToSpecifiedDate: "特定ã®æ—¥ä»˜ã«ã‚¸ãƒ£ãƒ³ãƒ—" showingPastTimeline: "éŽåŽ»ã®ã‚¿ã‚¤ãƒ ラインを表示ã—ã¦ã‚‹ã§" @@ -830,8 +837,8 @@ unread: "未èª" filter: "フィルタ" controlPanel: "コントãƒãƒ¼ãƒ«ãƒ‘ãƒãƒ«" manageAccounts: "アカウントを管ç†" -makeReactionsPublic: "リアクション一覧を公開ã™ã‚‹ã§" -makeReactionsPublicDescription: "ã‚ã‚“ãŸãŒã—ãŸãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ä¸€è¦§ã‚’誰ã§ã‚‚見れるよã†ã«ã™ã‚‹ã§ã€‚" +makeReactionsPublic: "ツッコミ一覧を公開ã™ã‚‹ã§" +makeReactionsPublicDescription: "ã‚ã‚“ãŸãŒã—ãŸãƒ„ッコミ一覧を誰ã§ã‚‚見れるよã†ã«ã™ã‚‹ã§ã€‚" classic: "クラシック" muteThread: "スレッドをミュート" unmuteThread: "スレッドã®ãƒŸãƒ¥ãƒ¼ãƒˆã‚’解除" @@ -905,6 +912,7 @@ remoteOnly: "リモートã®ã¿" failedToUpload: "アップãƒãƒ¼ãƒ‰ã«å¤±æ•—ã—ã¦ã‚‚ã†ãŸã‚…" cannotUploadBecauseInappropriate: "ä¸é©åˆ‡ãªå†…容をå«ã‚€ã‹ã‚‚ã—ã‚Œã¸ã‚“ã£ã¦åˆ¤å®šã•ã‚ŒãŸã‹ã‚‰ã‚¢ãƒƒãƒ—ãƒãƒ¼ãƒ‰ã§ãã¸ã‚“ã‚。" cannotUploadBecauseNoFreeSpace: "ドライブã®ç©ºã容é‡ãŒç„¡ã„ã‹ã‚‰ã‚¢ãƒƒãƒ—ãƒãƒ¼ãƒ‰ã§ãã¸ã‚“ã‚。" +cannotUploadBecauseExceedsFileSizeLimit: "ファイルãŒæ€ã†ãŸã‚ˆã‚Šã‚‚大ãã„ã•ã‹ã„アップãƒãƒ¼ãƒ‰ã§ãã¸ã‚“ã§ã“れ。" beta: "ベータ" enableAutoSensitive: "自動NSFW判定" enableAutoSensitiveDescription: "使ãˆã‚‹æ™‚ã¯ã€æ©Ÿæ¢°å¦ç¿’を使ã£ã¦è‡ªå‹•ã§ãƒ¡ãƒ‡ã‚£ã‚¢ã«NSFWフラグをè¨å®šã™ã‚‹ã§ã€‚ã“ã®æ©Ÿèƒ½ã‚’オフã«ã—ã¦ã‚‚ã€ã‚µãƒ¼ãƒãƒ¼ã«ã‚ˆã£ã¦ã¯è‡ªå‹•ã§è¨å®šã•ã‚Œã‚‹ã“ã¨ãŒã‚ã‚‹ã§ã€‚" @@ -938,6 +946,7 @@ didYouLikeMisskey: "Misskeyæ°—ã«å…¥ã£ã¦ãã‚ŒãŸï¼Ÿ" pleaseDonate: "Misskeyã¯{host}ãŒä½¿ç”¨ã—ã¦ã„ã‚‹ç„¡æ–™ã®ã‚½ãƒ•ãƒˆã‚¦ã‚§ã‚¢ã‚„ã§ã€‚ã“ã‚Œã‹ã‚‰ã‚‚開発を続ã‘れるよã†ã«ã€å¯„付ã—ãŸã£ã¦ãªï½žã€‚" roles: "ãƒãƒ¼ãƒ«" role: "ãƒãƒ¼ãƒ«" +noRole: "ãƒãƒ¼ãƒ«ã¯ã‚ã‚Šã¾ã¸ã‚“" normalUser: "一般ユーザー" undefined: "未定義" assign: "アサイン" @@ -947,6 +956,10 @@ manageCustomEmojis: "カスタム絵文å—ã®ç®¡ç†" youCannotCreateAnymore: "ã“れ以上作れãªã•ãã†ã‚„ã‚" cannotPerformTemporary: "一時的ã«åˆ©ç”¨ã§ãã¸ã‚“ã§" cannotPerformTemporaryDescription: "æ“作回数ãŒåˆ¶é™ã‚’超ãˆãŸã‹ã‚‰ä¸€æ™‚çš„ã«åˆ©ç”¨ã§ãã¸ã‚“ããªã£ãŸã§ã€‚ã¡ã‚‡ã£ã¨æ™‚é–“ç½®ã„ã¦ã‹ã‚‰ã‚‚ã†ä¸€å›žã‚„ã£ã¦ã‚„ー。" +invalidParamError: "パラメータãŒã‚¨ãƒ©ãƒ¼è¨€ã†ã¨ã‚Šã¾ã™ã‚" +invalidParamErrorDescription: "リクエストパラメータã«å•é¡ŒãŒã‚ã‚“ãん。普通ã¯ãƒã‚°ã‚„ãã‚“ã‘ã©ã€ã‚‚ã—ã‹ã™ã‚‹ã¨å…¥åŠ›ã—ãŸæ–‡å—æ•°ãŒå¤šã™ãŽã‚‹ã¨ã‹ã®å¯èƒ½æ€§ã‚‚ã‚ã‚‹ã‹ã‚‰ç¢ºèªã—ã¦ã‚„〜" +permissionDeniedError: "æ“作ãŒæ‹’å¦ã•ã‚Œã¦ã‚‚ã†ãŸã€‚" +permissionDeniedErrorDescription: "自分ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«ã¯ã“ã®æ“作を行ã†æ¨©é™ãŒã‚らã¸ã‚“ãã‚“" preset: "プリセット" selectFromPresets: "プリセットã‹ã‚‰é¸ã¶" achievements: "実績" @@ -970,7 +983,7 @@ invitationRequiredToRegister: "今ã“ã®ã‚µãƒ¼ãƒãƒ¼æ‹›å¾…制ã«ãªã£ã¦ã‚‚ㆠemailNotSupported: "ã“ã®ã‚µãƒ¼ãƒãƒ¼ã¯ãƒ¡ãƒ¼ãƒ«é…ä¿¡ãŒã‚µãƒãƒ¼ãƒˆã•ã‚Œã¦ã¸ã‚“ã¿ãŸã„ã‚„ã‚" postToTheChannel: "ãƒãƒ£ãƒ³ãƒãƒ«ã«æŠ•ç¨¿" cannotBeChangedLater: "後ã‹ã‚‰ã¯å¤‰ãˆã‚‰ã‚Œã¸ã‚“ã§ã€‚" -reactionAcceptance: "リアクションã®å—ã‘入れ" +reactionAcceptance: "ツッコミã®å—ã‘入れ" likeOnly: "ã„ã„ãã ã‘" likeOnlyForRemote: "リモートã‹ã‚‰ã¯ã„ã„ãã ã‘ãª" rolesAssignedToMe: "自分ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚ŒãŸãƒãƒ¼ãƒ«" @@ -988,19 +1001,71 @@ retryAllQueuesConfirmText: "一時的ã«ã‚µãƒ¼ãƒãƒ¼é‡ãªã‚‹ã‹ã‚‚ã—ã‚Œã¸ã‚“ enableChartsForRemoteUser: "リモートユーザーã®ãƒãƒ£ãƒ¼ãƒˆã‚’作る" enableChartsForFederatedInstances: "リモートサーãƒãƒ¼ã®ãƒãƒ£ãƒ¼ãƒˆã‚’作る" showClipButtonInNoteFooter: "ノートã®ã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã«ã‚¯ãƒªãƒƒãƒ—ã‚’è¿½åŠ " -largeNoteReactions: "ノートã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã‚’大ãã™ã‚‹" +largeNoteReactions: "ノートã®ãƒ„ッコミを大ãã™ã‚‹" noteIdOrUrl: "ノートIDã‹URL" +video: "å‹•ç”»" +videos: "å‹•ç”»" +dataSaver: "データケãƒã‚±ãƒ" accountMigration: "アカウントã®ãŠå¼•ã£è¶Šã—" accountMoved: "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã•ã‚‰ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«å¼•ã£è¶Šã—ãŸã§ï¼š" +accountMovedShort: "ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯ç§»è¡Œã•ã‚Œã¨ã‚‹ã§" +operationForbidden: "ã“ã®æ“作ã¯ã§ãã¾ã¸ã‚“" forceShowAds: "常ã«åºƒå‘Šã‚’表示ã—ã¨ã" +addMemo: "メモを足ã™" +editMemo: "メモをã„らã†" +reactionsList: "ツッコミ一覧" +renotesList: "Renote一覧" +notificationDisplay: "通知見ã›ã‚‹" +leftTop: "左上" +rightTop: "å³ä¸Š" +leftBottom: "左下" +rightBottom: "å³ä¸‹" +stackAxis: "é‡ãã‚‹æ–¹å‘" +vertical: "縦" +horizontal: "横" +position: "ä½ç½®" +serverRules: "サーãƒãƒ¼ãƒ«ãƒ¼ãƒ«" +pleaseConfirmBelowBeforeSignup: "ã“ã®ã‚µãƒ¼ãƒãƒ¼ã«ç™»éŒ²ã™ã‚‹å‰ã«ã€ä¸‹ã«æ›¸ã„ã¦ã‚‹ã“ã¨ç¢ºèªã—ã¦ãªã€‚" +pleaseAgreeAllToContinue: "続ã‘ã‚‹ã‚“ã‚„ã£ãŸã‚‰ã€å…¨ã¦ã®ã€Œã›ã‚„ãªã€ã«ãƒã‚§ãƒƒã‚¯å…¥ã‚Œã¦ã‚‹å¿…è¦ãŒã‚ã‚‹ã§ã€‚" +continue: "続ã‘ã‚‹ã§" +preservedUsernames: "予約ユーザーå" +preservedUsernamesDescription: "予約ã—ã¨ãユーザーåã‚’è¡Œã”ã¨ã«æŒ™ã’ã‚‹ã§ã€‚ã“ã“ã§æŒ‡å®šã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼åã¯ã‚¢ã‚«ã‚¦ãƒ³ãƒˆä½œã‚‹ã¨ãã«ä½¿ãˆã¸ã‚“ããªã‚‹ã‘ã©ã€ç®¡ç†è€…ã¯ä¾‹å¤–や。ã‚ã¨ã€ã‚‚ã†ã‚るアカウントも例外やãªã€‚" +createNoteFromTheFile: "ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ä½¿ã†ã¦ãƒŽãƒ¼ãƒˆä½œã‚‹ã§" +archive: "アーカイブ" +channelArchiveConfirmTitle: "{name}をアーカイブã—ã¦ãˆãˆã‹ï¼Ÿ" +channelArchiveConfirmDescription: "アーカイブã—ãŸã‚‰ã€ãƒãƒ£ãƒ³ãƒãƒ«ä¸€è¦§ã¨ã‹æ¤œç´¢çµæžœã‹ã‚‰ãªããªã‚‹ã—ã€æ–°ã—ã書ãè¾¼ã¿ã‚‚ã§ãã¸ã‚“ãªã‚‹ã§ã€‚" +thisChannelArchived: "ã“ã®ãƒãƒ£ãƒ³ãƒãƒ«ã€ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–ã•ã‚Œã¨ã‚‹ã§ã€‚" +displayOfNote: "ノートã®è¡¨ç¤º" +initialAccountSetting: "åˆæœŸè¨å®š" +youFollowing: "フォãƒãƒ¼ä¸ã‚„ã§" +_initialAccountSetting: + accountCreated: "アカウント作り終ã‚ã£ãŸã§ã€‚" + letsFillYourProfile: "最åˆã¯ã‚ã‚“ãŸã®ãƒ—ãƒãƒ•ã‚£ãƒ¼ãƒ«ã‚’è¨å®šã—よã‹ã€‚" + profileSetting: "プãƒãƒ•ã‚£ãƒ¼ãƒ«è¨å®š" + theseSettingsCanEditLater: "ã“ã®è¨å®šã¯ã‚ã¨ã‹ã‚‰å¤‰ãˆã‚Œã‚‹ã§ã€‚" + youCanEditMoreSettingsInSettingsPageLater: "ã“れ以外ã«ã‚‚ã„ã‚ã‚“ãªè¨å®šã‚’「è¨å®šã€ãƒšãƒ¼ã‚¸ã‹ã‚‰ã§ãã‚‹ã§ã€‚後ã§ç¢ºèªã—ã¦ã¿ã¦ãªã€‚" + followUsers: "タイムラインを構築ã™ã‚‹ãŸã‚ã«ã€æ°—ã«ãªã‚‹ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’フォãƒãƒ¼ã—ã¦ã¿ã€‚" + pushNotificationDescription: "プッシュ通知を有効ã«ã™ã‚‹ã¨{name}ã®é€šçŸ¥ã‚’ã‚ã‚“ãŸã®ãƒ‡ãƒã‚¤ã‚¹ã§å—ã‘å–れるã§ã€‚" + initialAccountSettingCompleted: "åˆæœŸè¨å®šãŒçµ‚ã‚ã£ãŸã§ã€‚" + haveFun: "{name}ã€æ¥½ã—ã‚“ã§ãªï½ž" + ifYouNeedLearnMore: "{name}(Misskey)ã®ä½¿ã„æ–¹ã¨ã‹ã‚’よー知りãŸã„ã‚“ã‚„ã£ãŸã‚‰{link}ã‚’ã¿ã¦ãªã€‚" + skipAreYouSure: "åˆæœŸè¨å®šé£›ã°ã™ã‹ï¼Ÿ" +_serverRules: + description: "æ–°è¦ç™»éŒ²å‰ã«è¦‹ã›ã‚‹ã€ã‚µãƒ¼ãƒãƒ¼ã®ç°¡æ½”ãªãƒ«ãƒ¼ãƒ«ã‚’è¨å®šã™ã‚“ã§ã€‚内容ã¯ä½¿ã†ãŸã‚ã®æ±ºã‚事ã®è¦ç´„ã¨ã™ã‚‹ã“ã¨ã‚’推奨ã™ã‚‹ã‚。" _accountMigration: - moveTo: "ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’ã•ã‚‰ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«å¼•ã£è¶Šã™ã§" - moveToLabel: "引ã£è¶Šã—å…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆï¼š" - moveAccountDescription: "ã“ã®æ“作ã¯æˆ»ã•ã‚Œã¸ã‚“ã§ã€‚ã¾ãšå¼•ã£è¶Šã—å…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¸ã®ã‚¨ã‚¤ãƒªã‚¢ã‚¹ãŒä½œã‚ŒãŸã‹ç¢ºèªã—ã¦ããªã¯ã‚Œã‚„。エイリアスãŒã§ãã¦ãŸã‚‰ã€å¼•ã£è¶Šã—å…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’ã“ã‚“ãªé¢¨ã«å…¥åŠ›ã—ã¦ãã‚Œã¸ã‚“ã‹ï¼Ÿï¼š@person@instance.com" moveFrom: "別ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‹ã‚‰ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«å¼•ã£è¶Šã™" + moveFromSub: "別ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¸ã‚¨ã‚¤ãƒªã‚¢ã‚¹ã‚’作る" moveFromLabel: "引ã£è¶Šã—å…ƒã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆï¼š" moveFromDescription: "別ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‹ã‚‰ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«ãƒ•ã‚©ãƒãƒ¯ãƒ¼ã‚’引ã継ã„ã§å¼•ã£è¶Šã—ãŸã‹ã£ãŸã‚‰ã€ã“ã“ã§ã‚¨ã‚¤ãƒªã‚¢ã‚¹ã‚’作ã£ã¨ãå¿…è¦ãŒã‚ã‚‹ã§ã€‚å¿…ãšãŠå¼•ã£è¶Šã—を実行ã™ã‚‹å‰ã«ä½œã£ã¨ã‹ãªã‚ã‹ã‚“ã§ï¼å¼•ã£è¶Šã—å…ƒã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’ã“ã‚“ãªé¢¨ã«å…¥åŠ›ã—ã¦ãã‚Œã¸ã‚“ã‹ï¼Ÿï¼š@person@instance.com" + moveTo: "ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’ã•ã‚‰ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«å¼•ã£è¶Šã™ã§" + moveToLabel: "引ã£è¶Šã—å…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆï¼š" + moveCannotBeUndone: "アカウントを移行ã™ã‚‹ã¨ã€å–り消ã™ã“ã¨ã¯ã§ãã¸ã‚“ããªã‚Šã¾ã™ã€‚" + moveAccountDescription: "ã“ã®æ“作ã¯æˆ»ã•ã‚Œã¸ã‚“ã§ã€‚ã¾ãšå¼•ã£è¶Šã—å…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¸ã®ã‚¨ã‚¤ãƒªã‚¢ã‚¹ãŒä½œã‚ŒãŸã‹ç¢ºèªã—ã¦ããªã¯ã‚Œã‚„。エイリアスãŒã§ãã¦ãŸã‚‰ã€å¼•ã£è¶Šã—å…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’ã“ã‚“ãªé¢¨ã«å…¥åŠ›ã—ã¦ãã‚Œã¸ã‚“ã‹ï¼Ÿï¼š@person@instance.com" + moveAccountHowTo: "アカウントã®å¼•ã£è¶Šã—ã«ã¯ã€ã¾ãšå¼•ã£è¶Šã—å…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§è‡ªåˆ†ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«å¯¾ã—エイリアスを作æˆã—ãªã¯ã‚Œã‚„。\nエイリアス作æˆã—ãŸå¾Œã€å¼•ã£è¶Šã—å…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’次ã®ã‚ˆã†ã«å…¥åŠ›ã—ã¦ãã‚Œã¸ã‚“ã‹ï¼Ÿ:@username@server.example.com" + startMigration: "引ã£è¶Šã—ã™ã‚‹" migrationConfirm: "ã»ã‚“ã¾ã«ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’ {account} ã«å¼•ã£è¶Šã™ã‚“ã‹ï¼Ÿä¸€å›žå¼•ã£è¶Šã—ã¦ã‚‚ã†ãŸã‚‰å–り消ã•ã‚Œã¸ã‚“ã—ã€äºŒåº¦ã¨ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’å…ƒã«æˆ»ã•ã‚Œã¸ã‚“ããªã‚‹ã§ã€‚\nãã‚Œã¨ã€å¼•ã£è¶Šã—å…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã‚¨ã‚¤ãƒªã‚¢ã‚¹ãŒä½œã‚ŒãŸã‹ã¡ã‚ƒï½žã‚“ã¨ç¢ºèªã—ーや?" + movedAndCannotBeUndone: "\nアカウントã¯ã‚‚ã†å¼•ã£è¶Šã•ã‚Œã¦ã¾ã™ã€‚\n引ã£è¶Šã—ã‚’å–り消ã™ã“ã¨ã¯ã§ãã¾ã¸ã‚“。" + movedTo: "引ã£è¶Šã—å…ˆã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆï¼š" _achievements: earnedAt: "è²°ã£ãŸæ—¥ãƒ" _types: @@ -1172,6 +1237,9 @@ _achievements: _client30min: title: "ãã‚“ã" description: "クライアントを起動ã—ã¦ã‹ã‚‰30分以上経éŽã—ãŸ" + _client60min: + title: "Misskeyã®è¦‹éŽãŽã‚„ï¼" + description: "クライアント付ã‘ã¦ã‹ã‚‰ï¼‘時間経ã£ã¦ã‚‚ã†ãŸã§ã€‚" _noteDeletedWithin1min: title: "*ãŠãŠã£ã¨ï¼Š" description: "投稿ã—ã¦ã‹ã‚‰1分以内ã«ãã®æŠ•ç¨¿ã‚’消ã—ãŸ" @@ -1203,7 +1271,7 @@ _achievements: description: "ドライブã®ãƒ•ã‚©ãƒ«ãƒ€ã‚’å†å¸°çš„ãªå…¥ã‚Œåã«ã—よã†ã¨ã—ãŸ" _reactWithoutRead: title: "ã¡ã‚ƒã‚“ã¨èªã‚“ã ã‚“ã‹ï¼Ÿ" - description: "100æ–‡å—以上ã®ãƒ†ã‚ストをå«ã‚€ãƒŽãƒ¼ãƒˆã«æŠ•ç¨¿ã•ã‚Œã¦ã‹ã‚‰3秒以内ã«ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã—ãŸ" + description: "100æ–‡å—以上ã®ãƒ†ã‚ストをå«ã‚€ãƒŽãƒ¼ãƒˆã«æŠ•ç¨¿ã•ã‚Œã¦ã‹ã‚‰3秒以内ã«ãƒ„ッコんã " _clickedClickHere: title: "ã“ã“をクリック" description: "ã“ã“をクリックã—ãŸ" @@ -1260,6 +1328,8 @@ _role: iconUrl: "アイコン画åƒã®URL" asBadge: "ãƒãƒƒã‚¸ã¨ã—ã¦è¦‹ã›ã‚‹" descriptionOfAsBadge: "オンã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼åã®æ¨ªã‚“ã¨ã“ã«ãƒãƒ¼ãƒ«ã®ã‚¢ã‚¤ã‚³ãƒ³ãŒè¡¨ç¤ºã•ã‚Œã‚‹ã§ã€‚" + isExplorable: "ãƒãƒ¼ãƒ«ã‚¿ã‚¤ãƒ ラインを公開ã™ã‚‹ã§ã€œ" + descriptionOfIsExplorable: "オンã«ã—ãŸã‚‰ãƒãƒ¼ãƒ«ã®ã‚¿ã‚¤ãƒ ラインを公開ã™ã‚‹ã§ã€œã€‚ã§ã‚‚ãƒãƒ¼ãƒ«ã®å…¬é–‹ã‚’オフã«ã—ãŸã‚‰å…¬é–‹ã•ã‚Œã¸ã‚“よ。" displayOrder: "è¡¨ç¤ºé †" descriptionOfDisplayOrder: "æ•°ãŒã§ã‹ã„ã»ã©ã€UI上ã§å…ˆã«è¡¨ç¤ºã•ã‚Œã‚‹ã§ã€‚" canEditMembersByModerator: "モデレーターã®ãƒ¡ãƒ³ãƒãƒ¼ç·¨é›†ã‚’許å¯" @@ -1276,6 +1346,7 @@ _role: canInvite: "サーãƒãƒ¼æ‹›å¾…コードã®ç™ºè¡Œ" canManageCustomEmojis: "カスタム絵文å—ã®ç®¡ç†" driveCapacity: "ドライブ容é‡" + alwaysMarkNsfw: "å‹æ‰‹ã«ãƒ•ã‚¡ã‚¤ãƒ«ã«NSFWã‚’ãã£ã¤ã‘ã‚‹" pinMax: "ノートã®ãƒ”ン留ã‚ã®æœ€å¤§æ•°" antennaMax: "アンテナã®ä½œæˆå¯èƒ½æ•°" wordMuteMax: "ワードミュートã®æœ€å¤§æ–‡å—æ•°" @@ -1527,32 +1598,16 @@ _time: minute: "分" hour: "時間" day: "æ—¥" -_tutorial: - title: "Misskeyã®ä½¿ã„æ–¹" - step1_1: "よã†æ¥ãŸãªã‚" - step1_2: "ã“ã®ç”»é¢ã¯ã€Œã‚¿ã‚¤ãƒ ラインã€ã£ã¦è¨€ã£ã¦ã€ã‚ã‚“ãŸã‚„ã€ã‚ã‚“ãŸãŒã€Œãƒ•ã‚©ãƒãƒ¼ã€ã™ã‚‹äººã®ã€ŒãƒŽãƒ¼ãƒˆã€ãŒæ™‚系列ã§è¡¨ç¤ºã•ã‚Œã‚‹ã‚“ã‚„ã§ã€‚" - step1_3: "ã‚ã‚“ãŸã¯ã¾ã 何もノートを投稿ã—ã¦ãªãã¦ã€èª°ã‚‚フォãƒãƒ¼ã—ã¦ã¸ã‚“ã‹ã‚‰ã€ã‚¿ã‚¤ãƒ ラインã«ã¯ä½•ã‚‚表示ã•ã‚Œã¦ãªã„ã¯ãšã‚„ã§ã€‚" - step2_1: "ノートを作ã£ãŸã‚Šèª°ã‹ã‚’フォãƒãƒ¼ã—ãŸã‚Šã™ã‚‹å‰ã«ã€ã¾ãšã‚ã‚“ãŸã®ãƒ—ãƒãƒ•ã‚£ãƒ¼ãƒ«ã‚’完æˆã•ã›ã‚ˆã‹ã€‚" - step2_2: "ã‚ã‚“ãŸãŒã©ã‚“ãªäººã‹ã‚ã‹ã‚‹ã¨ã€å¤šãã®äººã«ãƒŽãƒ¼ãƒˆã‚’見ã¦ã‚‚らãˆãŸã‚Šã€ãƒ•ã‚©ãƒãƒ¼ã—ã¦ã‚‚らã„ã‚„ã™ããªã‚‹ã§ã€‚" - step3_1: "プãƒãƒ•ã‚£ãƒ¼ãƒ«è¨å®šã¯ãˆãˆæ„Ÿã˜ã«ã§ããŸã‹ï¼Ÿ" - step3_2: "ã»ãªè©¦ã—ã«ã€ä½•ã‹ãƒŽãƒ¼ãƒˆã‚’投稿ã—ã¦ã¿ã¦ã‚„ー。画é¢ä¸Šã«ã‚る鉛ç†ãƒžãƒ¼ã‚¯ã®ãƒœã‚¿ãƒ³ã‚’押ã™ã¨ãƒ•ã‚©ãƒ¼ãƒ ãŒé–‹ãã¯ãšã‚„ã§ã€‚" - step3_3: "内容を書ã„ãŸã‚‰ã€ãƒ•ã‚©ãƒ¼ãƒ å³ä¸Šã®ãƒœã‚¿ãƒ³ã‚’押ã™ã¨æŠ•ç¨¿ã§ãã‚‹ã§ã€‚" - step3_4: "内容ãŒæ€ã„ã¤ã‹ã¸ã‚“?ã»ãªã€Œé–¢è¥¿äººãªã‚‰é¢ç™½ã„ã“ã¨è¨€ã†ã¦ãˆã€œã‚„〜ã€ã¨ã‹ã©ã†ã‚„ã‚ã‹ã€‚" - step4_1: "投稿ã§ããŸã‚“?" - step4_2: "ã‚ã‚“ãŸã®ãƒŽãƒ¼ãƒˆãŒã‚¿ã‚¤ãƒ ラインã«è¡¨ç¤ºã•ã‚Œã¦ã„ã‚Œã°æˆåŠŸã‚„ã§" - step5_1: "次ã¯ã€ã»ã‹ã®äººã‚’フォãƒãƒ¼ã—ã¦ã‚¿ã‚¤ãƒ ラインを賑やã‹ã«ã—よã‹" - step5_2: "{featured}ã§äººæ°—ã®ãƒŽãƒ¼ãƒˆãŒè¦‹ã‚Œã‚‹ã‹ã‚‰ã€ãã®ä¸ã‹ã‚‰æ°—ã«ãªã£ãŸäººã‚’é¸ã‚“ã§ãƒ•ã‚©ãƒãƒ¼ã—ãŸã‚Šã€{explore}ã§äººæ°—ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’探ã™ã“ã¨ã‚‚ã§ãã‚‹ã§ã€‚" - step5_3: "ユーザーをフォãƒãƒ¼ã—ãŸã‹ã£ãŸã‚‰ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ã‚¢ã‚¤ã‚³ãƒ³ã‚’クリックã—ã¦ãƒ¦ãƒ¼ã‚¶ãƒ¼ãƒšãƒ¼ã‚¸ã‚’表示ã—ã¦ã€ã€Œãƒ•ã‚©ãƒãƒ¼ã€ãƒœã‚¿ãƒ³ã‚’押ã™ã‚“ã‚„ã§ã€‚" - step5_4: "ユーザーã«ã‚ˆã£ã¦ã¯ã€ãƒ•ã‚©ãƒãƒ¼ãŒæ‰¿èªã•ã‚Œã‚‹ã¾ã§ã¡ã‚‡ã£ã¨æ™‚é–“ãŒã‹ã‹ã‚‹ã“ã¨ãŒã‚ã‚‹ã§ã€‚" - step6_1: "タイムラインã«ä»–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒŽãƒ¼ãƒˆãŒè¡¨ç¤ºã•ã‚Œã¦ã„ã‚Œã°æˆåŠŸã‚„ã§ã€‚" - step6_2: "ä»–ã®äººã®ãƒŽãƒ¼ãƒˆã«ã¯ã€ã€Œãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã€ã‚’付ã‘ã‚‹ã“ã¨ãŒã§ãã¦ã€ç°¡å˜ã«ã‚ã‚“ãŸã®å応をä¼ãˆã‚‰ã‚Œã‚‹ã§ã€‚" - step6_3: "リアクションを付ã‘ã‚‹ã‚“ã‚„ã£ãŸã‚‰ã€ãƒŽãƒ¼ãƒˆã®ã€Œ+ã€ãƒžãƒ¼ã‚¯ã‚’クリックã—ã¦ã€å¥½ããªãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã‚’é¸æŠžã—ã¦ãªã€‚" - step7_1: "ã“ã‚Œã§ã€Misskeyã®åŸºæœ¬çš„ãªä½¿ã„æ–¹ã®èª¬æ˜Žã¯çµ‚ã‚ã‚Šã‚„ã§ã€‚ãŠç–²ã‚Œã•ã‚“。" - step7_2: "ã‚‚ã£ã¨Misskeyã«ã¤ã„ã¦çŸ¥ã‚ŠãŸã„ã¨ãã¯ã€{help}を見るã¨ãˆãˆã‹ã‚‚ãªã€‚" - step7_3: "ã»ãªã€Misskeyを楽ã—ã‚“ã§ãªãƒ¼ðŸš€" - step8_1: "最後ã«ã€ãƒ—ッシュ通知を有効化ã—ã¦ã¿ã‚„ん?" - step8_2: "プッシュ通知をå—ã‘å–ã‚‹ã“ã¨ã§ã€Misskeyã‚’é–‹ã„ã¦ã„ãªã„時ã§ã‚‚リアクションやフォãƒãƒ¼ã€ãƒ¡ãƒ³ã‚·ãƒ§ãƒ³ã¨ã‹ã«æ°—ã¥ã‘ã‚‹ã§ã€‚" - step8_3: "通知ã®è¨å®šã¯ã‚ã¨ã‹ã‚‰å¤‰æ›´ã§ãã‚‹ã§" +_timelineTutorial: + title: "Misskeyã£ã¦ãªã‚“や?" + step1_1: "ã“ã‚Œã¯ã€Œã‚¿ã‚¤ãƒ ラインã€ã‚„。{name}ã«æŠ•ç¨¿ã•ã‚ŒãŸã€ŒãƒŽãƒ¼ãƒˆã€ãŒé †ç•ªã«è¡¨ç¤ºã•ã‚Œã‚‹ã§ã€‚" + step1_2: "タイムラインã«ã¯ä½•å€‹ã‹ç¨®é¡žãŒã‚ã£ã¦ãªã€ä¾‹ãˆã°ã€Œãƒ›ãƒ¼ãƒ タイムラインã€ã ã£ãŸã‚‰ã‚ã‚“ãŸã®ãƒ•ã‚©ãƒãƒ¼ã—ã¦ã‚‹äººã®ãƒŽãƒ¼ãƒˆã€ã€Œãƒãƒ¼ã‚«ãƒ«ã‚¿ã‚¤ãƒ ラインã€ã«ã¯{name}全部ã®ãƒŽãƒ¼ãƒˆãŒæµã‚Œã¦ãã‚‹ã§ã€‚" + step2_1: "試ã—ã«ã€ä½•ã‹ãƒŽãƒ¼ãƒˆã‚’投稿ã—ã¦ã¿ã€‚ç”»é¢ã®é‰›ç†ãƒžãƒ¼ã‚¯ã®ãƒœã‚¿ãƒ³ã§ãƒ•ã‚©ãƒ¼ãƒ ãŒé–‹ãã§ã€‚" + step2_2: "最åˆã®ãƒŽãƒ¼ãƒˆã¯ã€è‡ªå·±ç´¹ä»‹ã¨ã‹ã€Œ{name}始ã‚ã¦ã¿ãŸã‚“ã‚„ã€ã¨ã‹ãŒãˆãˆã¨æ€ã†ã§ã€‚" + step3_1: "投稿ã§ããŸï¼Ÿ" + step3_2: "ã‚ã‚“ãŸã®ãƒŽãƒ¼ãƒˆãŒã‚¿ã‚¤ãƒ ラインã«å‡ºã¦ããŸã‚‰æˆåŠŸã‚„。" + step4_1: "ノートã«ã¯ã€ã€Œãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã€ã‚’付ã‘れるã§ã€‚" + step4_2: "ツッコむんやã£ãŸã‚‰ã€ãƒŽãƒ¼ãƒˆã®ã€Œ+ã€ãƒžãƒ¼ã‚¯ã‚’押ã—ã¦ã€å¥½ããªçµµæ–‡å—ã‚’é¸ã¶ã§ã€‚" _2fa: alreadyRegistered: "ã‚‚ã†è¨å®šçµ‚ã‚ã£ã¨ã‚‹ã‚。" registerTOTP: "èªè¨¼ã‚¢ãƒ—リã®è¨å®šã¯ã˜ã‚ã‚‹" @@ -1596,8 +1651,8 @@ _permissions: "write:notes": "ノートを作æˆãƒ»å‰Šé™¤ã™ã‚‹ã§" "read:notifications": "通知を見るã§" "write:notifications": "通知をæ“作ã™ã‚‹ã§" - "read:reactions": "リアクションを見る" - "write:reactions": "リアクションをæ“作ã™ã‚‹ã§" + "read:reactions": "ツッコミを見る" + "write:reactions": "ツッコミをæ“作ã™ã‚‹ã§" "write:votes": "投票ã™ã‚‹" "read:pages": "ページを見る" "write:pages": "ページをæ“作ã™ã‚‹ã§" @@ -1853,7 +1908,7 @@ _notification: reply: "リプライ" renote: "Renote" quote: "引用" - reaction: "リアクション" + reaction: "ツッコミ" pollEnded: "アンケートãŒçµ‚了ã—ãŸã§" receiveFollowRequest: "フォãƒãƒ¼è¨±å¯ã—ã¦ã»ã—ã„ã¿ãŸã„ã‚„ã§" followRequestAccepted: "フォãƒãƒ¼ãŒå—ç†ã•ã‚ŒãŸã§" @@ -1912,5 +1967,5 @@ _webhookSettings: note: "ノートを投稿ã—ãŸã¨ã~ï¼" reply: "返信ãŒã‚ã‚‹ã¨ã~ï¼" renote: "Renoteã•ã‚Œã‚‹ã¨ã~ï¼" - reaction: "リアクションãŒã‚ã‚‹ã¨ã~ï¼" + reaction: "ツッコミãŒã‚ã‚‹ã¨ã~ï¼" mention: "メンションãŒã‚ã‚‹ã¨ã~ï¼" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 3ff00482d609e77bb0486c10882a603f643fc24e..4a2fbe2a80d24b4fd85e938021c2f8e09f798d03 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -2,7 +2,7 @@ _lang_: "í•œêµì–´" headlineMisskey: "노트로 ì—°ê²°ë˜ëŠ” 네트워í¬" introMisskey: "환ì˜í•©ë‹ˆë‹¤! Misskey는 오픈 소스 분산형 마ì´í¬ë¡œ 블로그 서비스입니다.\n'노트'를 작성해서 지금 ì¼ì–´ë‚˜ê³ 있는 ì¼ì„ ê³µìœ í•˜ê±°ë‚˜, ë‹¹ì‹ ë§Œì˜ ì´ì•¼ê¸°ë¥¼ 모ë‘ì—게 ë°œì‹ í•˜ì„¸ìš”ðŸ“¡\n'리액션' 기능으로 ì¹œêµ¬ì˜ ë…¸íŠ¸ì— ì´ì•Œê°™ì´ ë°˜ì‘ì„ ì¶”ê°€í• ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤ðŸ‘\n새로운 세계를 íƒí—˜í•´ 보세요🚀" -poweredByMisskeyDescription: "{name}ì€(는) 오픈소스 플랫í¼<b>Misskey</b>를 사용한 서버 중 하나입니다." +poweredByMisskeyDescription: "{name}ì€(는) 오픈소스 í”Œëž«í¼ <b>Misskey</b>를 사용한 서버 ê°€ìš´ë° í•˜ë‚˜ìž…ë‹ˆë‹¤." monthAndDay: "{month}ì›” {day}ì¼" search: "검색" notifications: "알림" @@ -20,6 +20,7 @@ noNotes: "노트가 없습니다" noNotifications: "í‘œì‹œí• ì•Œë¦¼ì´ ì—†ìŠµë‹ˆë‹¤" instance: "서버" settings: "ì„¤ì •" +notificationSettings: "알림 ì„¤ì •" basicSettings: "기본 ì„¤ì •" otherSettings: "기타 ì„¤ì •" openInWindow: "창으로 열기" @@ -262,14 +263,16 @@ noMoreHistory: "ì´ê²ƒë³´ë‹¤ ê³¼ê±°ì˜ ê¸°ë¡ì´ 없습니다" startMessaging: "대화 시작하기" nUsersRead: "{n}ëª…ì´ ì½ìŒ" agreeTo: "{0}ì— ë™ì˜" +agree: "ë™ì˜í•©ë‹ˆë‹¤" agreeBelow: "아래 ë‚´ìš©ì— ë™ì˜í•©ë‹ˆë‹¤" basicNotesBeforeCreateAccount: "기본ì ì¸ ì£¼ì˜ì‚¬í•" -tos: "ì´ìš© 약관" +termsOfService: "ì´ìš© 약관" start: "시작하기" home: "홈" remoteUserCaution: "리모트 ìœ ì €ì´ê¸° 때문ì—, ì •ë³´ê°€ ì •í™•í•˜ì§€ ì•Šì„ ìˆ˜ 있습니다." activity: "활ë™" images: "ì´ë¯¸ì§€" +image: "ì´ë¯¸ì§€" birthday: "ìƒì¼" yearsOld: "{age}세" registeredDate: "등ë¡ì¼" @@ -473,6 +476,8 @@ createAccount: "ê³„ì • 만들기" existingAccount: "기존 ê³„ì •" regenerate: "재ìƒì„±" fontSize: "ê¸€ìž í¬ê¸°" +mediaListWithOneImageAppearance: "ì´ë¯¸ì§€ê°€ 1ê°œ ë¿ì¸ 미디어 목ë¡ì˜ 높ì´" +limitTo: "{x}ë¡œ ì œí•œ" noFollowRequests: "처리ë˜ì§€ ì•Šì€ íŒ”ë¡œìš° ìš”ì²ì´ 없습니다" openImageInNewTab: "새 íƒì—ì„œ ì´ë¯¸ì§€ 열기" dashboard: "대시보드" @@ -506,6 +511,7 @@ objectStorageUseSSLDesc: "API 호출시 HTTPS 를 사용하지 않는 경우 OFF objectStorageUseProxy: "ì—°ê²°ì— í”„ë¡ì‹œë¥¼ 사용" objectStorageUseProxyDesc: "오브ì 트 ìŠ¤í† ë¦¬ì§€ API 호출시 프ë¡ì‹œë¥¼ 사용하지 않는 경우 OFF ë¡œ ì„¤ì •í•´ 주세요" objectStorageSetPublicRead: "ì—…ë¡œë“œí• ë•Œ 'public-read'를 ì„¤ì •í•˜ê¸°" +s3ForcePathStyleDesc: "s3ForcePathStyleì„ í™œì„±í™”í•˜ë©´, 버킷 ì´ë¦„ì„ URLì˜ í˜¸ìŠ¤íŠ¸ëª…ì´ ì•„ë‹Œ ê²½ë¡œì˜ ì¼ë¶€ë¡œì¨ 취급합니다. 셀프 호스트 Minio와 ê°™ì€ ì„œë¹„ìŠ¤ë¥¼ ì‚¬ìš©í• ê²½ìš° 활성화해야 í• ìˆ˜ 있습니다." serverLogs: "서버 로그" deleteAll: "ëª¨ë‘ ì‚ì œ" showFixedPostForm: "타임ë¼ì¸ ìƒë‹¨ì— 글 ìž‘ì„±ëž€ì„ í‘œì‹œ" @@ -547,11 +553,14 @@ userSuspended: "ì´ ê³„ì •ì€ ì •ì§€ëœ ìƒíƒœìž…니다." userSilenced: "ì´ ê³„ì •ì€ ì‚¬ì¼ëŸ°ìŠ¤ëœ ìƒíƒœìž…니다." yourAccountSuspendedTitle: "ê³„ì •ì´ ì •ì§€ë˜ì—ˆìŠµë‹ˆë‹¤" yourAccountSuspendedDescription: "ì´ ê³„ì •ì€ ì„œë²„ì˜ ì´ìš© ì•½ê´€ì„ ìœ„ë°˜í•˜ê±°ë‚˜, 기타 다른 ì´ìœ ë¡œ ì¸í•´ ì •ì§€ë˜ì—ˆìŠµë‹ˆë‹¤. ìžì„¸í•œ 사í•ì€ 관리ìžì—게 문ì˜í•´ 주ì‹ì‹œì˜¤. ê³„ì •ì„ ìƒˆë¡œ ìƒì„±í•˜ì§€ 마ì‹ì‹œì˜¤." +tokenRevoked: "ìœ íš¨í•˜ì§€ ì•Šì€ í† í°ìž…니다" +tokenRevokedDescription: "ë¡œê·¸ì¸ í† í°ì´ 비활성화ë˜ì—ˆìŠµë‹ˆë‹¤. 다시 로그ì¸í•˜ì—¬ 주ì‹ì‹œì˜¤." accountDeleted: "ê³„ì •ì´ ì •ì§€ë˜ì—ˆìŠµë‹ˆë‹¤" accountDeletedDescription: "ì´ ê³„ì •ì´ ì‚ì œë˜ì—ˆìŠµë‹ˆë‹¤." menu: "메뉴" divider: "êµ¬ë¶„ì„ " addItem: "í•ëª© 추가" +rearrange: "ì •ë ¬" relays: "ë¦´ë ˆì´" addRelay: "ë¦´ë ˆì´ ì¶”ê°€" inboxUrl: "Inbox 주소" @@ -695,6 +704,8 @@ contact: "ì—°ë½ì²˜" useSystemFont: "시스템 기본 ê¸€ê¼´ì„ ì‚¬ìš©" clips: "í´ë¦½" experimentalFeatures: "실험실" +experimental: "실험실" +thisIsExperimentalFeature: "ì´ ê¸°ëŠ¥ì€ ì‹¤í—˜ì ì¸ ê¸°ëŠ¥ìž…ë‹ˆë‹¤. ì‚¬ì–‘ì´ ë³€ê²½ë˜ê±°ë‚˜ ì •ìƒì 으로 ë™ìž‘하지 ì•Šì„ ê°€ëŠ¥ì„±ì´ ìžˆìŠµë‹ˆë‹¤." developer: "개발ìž" makeExplorable: "\"발견하기\"ì— ë‚´ ê³„ì • ë³´ì´ê¸°" makeExplorableDescription: "비활성화하면 \"발견하기\"ì— ë‚˜ì˜ ê³„ì •ì„ í‘œì‹œí•˜ì§€ 않습니다." @@ -901,6 +912,7 @@ remoteOnly: "리모트만" failedToUpload: "업로드 실패" cannotUploadBecauseInappropriate: "ì´ íŒŒì¼ì€ 부ì ì ˆí•œ ë‚´ìš©ì„ í¬í•¨í•œë‹¤ê³ íŒë‹¨ë˜ì–´ ì—…ë¡œë“œí• ìˆ˜ 없습니다." cannotUploadBecauseNoFreeSpace: "ë“œë¼ì´ë¸Œ ìš©ëŸ‰ì´ ë¶€ì¡±í•˜ì—¬ ì—…ë¡œë“œí• ìˆ˜ 없습니다." +cannotUploadBecauseExceedsFileSizeLimit: "íŒŒì¼ í¬ê¸°ê°€ 너무 í¬ê¸° ë•Œë¬¸ì— ì—…ë¡œë“œí• ìˆ˜ 없습니다." beta: "ë² íƒ€" enableAutoSensitive: "ìžë™ NSFW íƒì§€" enableAutoSensitiveDescription: "ì´ìš© ê°€ëŠ¥í• ê²½ìš° ê¸°ê³„í•™ìŠµì„ í†µí•´ ìžë™ìœ¼ë¡œ 미디어 NSFW를 ì„¤ì •í•©ë‹ˆë‹¤. ì´ ê¸°ëŠ¥ì„ í•´ì œí•˜ë”ë¼ë„, 서버 ì •ì±…ì— ë”°ë¼ ìžë™ìœ¼ë¡œ ì„¤ì •ë 수 있습니다." @@ -917,6 +929,7 @@ pushNotificationNotSupported: "브ë¼ìš°ì €ë‚˜ 서버ì—ì„œ 푸시 ì•Œë¦¼ì´ ì§€ sendPushNotificationReadMessage: "푸시 알림ì´ë‚˜ 메시지를 ì½ì€ ë’¤ 푸시 ì•Œë¦¼ì„ ì‚ì œ" sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}ã€ì´ë¼ëŠ” ì•Œë¦¼ì´ ìž ê¹ í‘œì‹œë©ë‹ˆë‹¤. ê¸°ê¸°ì˜ ì „ë ¥ ì†Œë¹„ëŸ‰ì´ ì¦ê°€í• 수 있습니다." windowMaximize: "최대화" +windowMinimize: "최소화" windowRestore: "복구" caption: "캡션" loggedInAsBot: "ë´‡ ê³„ì •ìœ¼ë¡œ 로그ì¸ì¤‘" @@ -933,6 +946,7 @@ didYouLikeMisskey: "Misskeyê°€ 마ìŒì— 드시나요?" pleaseDonate: "{host}ì€(는) 무료 소프트웨어 Misskey를 사용합니다. 후ì›ì„ 통해 ì €í¬ì˜ ê°œë°œì´ ì´ì–´ì§ˆ 수 있게 ë„와주세요!" roles: "ì—í• " role: "ì—í• " +noRole: "ì—í• ì´ ì—†ìŠµë‹ˆë‹¤" normalUser: "ì¼ë°˜ 사용ìž" undefined: "ì •ì˜ë˜ì§€ ì•ŠìŒ" assign: "í• ë‹¹" @@ -942,6 +956,10 @@ manageCustomEmojis: "커스텀 ì´ëª¨ì§€ 관리" youCannotCreateAnymore: "ë” ì´ìƒ ìƒì„±í• 수 없습니다." cannotPerformTemporary: "ì¼ì‹œì 으로 ì‚¬ìš©í• ìˆ˜ ì—†ìŒ" cannotPerformTemporaryDescription: "ì¡°ìž‘ 횟수 ì œí•œì„ ì´ˆê³¼í•˜ì—¬ ì¼ì‹œì 으로 ì‚¬ìš©ì´ ë¶ˆê°€í•©ë‹ˆë‹¤. ìž ì‹œ 후 다시 ì‹œë„í•´ 주세요." +invalidParamError: "매개변수 오류" +invalidParamErrorDescription: "ìš”ì² ë§¤ê°œë³€ìˆ˜ì— ë¬¸ì œê°€ 있습니다. ëŒ€ë¶€ë¶„ì˜ ê²½ìš° Misskeyì˜ ë²„ê·¸ê°€ ì›ì¸ì´ì§€ë§Œ, ìž…ë ¥ 문ìžìˆ˜ê°€ 너무 ë§Žì•˜ì„ ê°€ëŠ¥ì„± ë“±ë„ ìžˆìŠµë‹ˆë‹¤." +permissionDeniedError: "ìž‘ì—…ì´ ê±°ë¶€ë˜ì—ˆìŠµë‹ˆë‹¤" +permissionDeniedErrorDescription: "ì´ ìž‘ì—…ì„ ìˆ˜í–‰í• ê¶Œí•œì´ ì—†ìŠµë‹ˆë‹¤." preset: "프리셋" selectFromPresets: "프리셋ì—ì„œ ì„ íƒ" achievements: "ë„ì „ ê³¼ì œ" @@ -958,10 +976,96 @@ copyErrorInfo: "오류 ì •ë³´ 복사" joinThisServer: "ì´ ì„œë²„ì— ê°€ìž…" exploreOtherServers: "다른 서버 둘러보기" letsLookAtTimeline: "타임ë¼ì¸ 구경하기" +disableFederationConfirm: "ì •ë§ë¡œ ì—°í•©ì„ ë„ì‹œê² ìŠµë‹ˆê¹Œ?" +disableFederationConfirmWarn: "ì—°í•©ì„ ë„ë”ë¼ë„ ê²Œì‹œë¬¼ì´ ë¹„ê³µê°œë¡œ ì „í™˜ë˜ëŠ” ê²ƒì€ ì•„ë‹™ë‹ˆë‹¤. ëŒ€ë¶€ë¶„ì˜ ê²½ìš° ì—°í•©ì„ ë¹„í™œì„±í™”í• í•„ìš”ê°€ 없습니다." +disableFederationOk: "ì—°í•©ì„ ë„기" invitationRequiredToRegister: "현재 ì´ ì„œë²„ëŠ” 비공개입니다. 회ì›ê°€ìž…ì„ í•˜ì‹œë ¤ë©´ 초대 코드가 필요합니다." emailNotSupported: "ì´ ì„œë²„ì—서는 ë©”ì¼ ì „ì†¡ì„ ì§€ì›í•˜ì§€ 않습니다" postToTheChannel: "채ë„ì— ê²Œì‹œí•˜ê¸°" cannotBeChangedLater: "ë‚˜ì¤‘ì— ë³€ê²½í• ìˆ˜ 없습니다." +reactionAcceptance: "리액션 ìˆ˜ì‹ " +likeOnly: "좋아요만 받기" +likeOnlyForRemote: "리모트ì—서는 좋아요만 받기" +rolesAssignedToMe: "나ì—게 í• ë‹¹ëœ ì—í• " +resetPasswordConfirm: "비밀번호를 ìž¬ì„¤ì •í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" +sensitiveWords: "민ê°í•œ 단어" +sensitiveWordsDescription: "ì„¤ì •í•œ 단어가 í¬í•¨ëœ ë…¸íŠ¸ì˜ ê³µê°œ 범위를 '홈'으로 ê°•ì œí•©ë‹ˆë‹¤. 개행으로 구분하여 여러 개를 ì§€ì •í• ìˆ˜ 있습니다." +notesSearchNotAvailable: "노트 ê²€ìƒ‰ì„ ì´ìš©í•˜ì‹¤ 수 없습니다." +license: "ë¼ì´ì„ 스" +unfavoriteConfirm: "ì¦ê²¨ì°¾ê¸°ë¥¼ í•´ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" +myClips: "ë‚´ í´ë¦½" +drivecleaner: "ë“œë¼ì´ë¸Œ ì •ë¦¬" +retryAllQueuesNow: "ëª¨ë“ í를 다시 ì‹œë„" +retryAllQueuesConfirmTitle: "지금 다시 ì‹œë„í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" +retryAllQueuesConfirmText: "ì¼ì‹œì 으로 ì„œë²„ì˜ ë¶€í•˜ê°€ ì¦ê°€í• 수 있습니다." +enableChartsForRemoteUser: "리모트 ìœ ì €ì˜ ì°¨íŠ¸ë¥¼ ìƒì„±" +enableChartsForFederatedInstances: "리모트 ì„œë²„ì˜ ì°¨íŠ¸ë¥¼ ìƒì„±" +showClipButtonInNoteFooter: "노트 ë™ìž‘ì— í´ë¦½ì„ 추가" +largeNoteReactions: "ë…¸íŠ¸ì˜ ë¦¬ì•¡ì…˜ì„ í¬ê²Œ 표시" +noteIdOrUrl: "노트 ID ë° URL" +video: "ë™ì˜ìƒ" +videos: "ë™ì˜ìƒ" +dataSaver: "ë°ì´í„° ì ˆì•½ 모드" +accountMigration: "ê³„ì • ì´ë™" +accountMoved: "ì´ ì‚¬ìš©ìžëŠ” ë‹¤ìŒ ê³„ì •ìœ¼ë¡œ ì´ì‚¬í–ˆìŠµë‹ˆë‹¤:" +accountMovedShort: "ì´ì‚¬í•œ ê³„ì •ìž…ë‹ˆë‹¤" +operationForbidden: "ì‚¬ìš©í• ìˆ˜ 없습니다" +forceShowAds: "ê´‘ê³ ë¥¼ í•ìƒ 표시" +addMemo: "메모 추가" +editMemo: "메모 편집" +reactionsList: "리액션 목ë¡" +renotesList: "Renote 목ë¡" +notificationDisplay: "알림 표시" +leftTop: "왼쪽 ìƒë‹¨" +rightTop: "오른쪽 ìƒë‹¨" +leftBottom: "왼쪽 하단" +rightBottom: "오른쪽 하단" +stackAxis: "나열 ë°©í–¥" +vertical: "세로" +horizontal: "가로" +position: "위치" +serverRules: "서버 규칙" +pleaseConfirmBelowBeforeSignup: "ì´ ì„œë²„ì— ê°€ìž…í•˜ê¸° ì „ì— ì•„ëž˜ 사í•ì„ 확ì¸í•˜ì—¬ 주ì‹ì‹œì˜¤." +pleaseAgreeAllToContinue: "계ì†í•˜ì‹œë ¤ë©´ ëª¨ë“ í•ëª©ì— ë™ì˜í•˜ì‹ì‹œì˜¤." +continue: "계ì†" +preservedUsernames: "ì˜ˆì•½ëœ ì‚¬ìš©ìžëª…" +preservedUsernamesDescription: "ì˜ˆì•½í• ì‚¬ìš©ìžëª…ì„ í•œ ì¤„ì— í•˜ë‚˜ì”© ìž…ë ¥í•©ë‹ˆë‹¤. 여기ì—ì„œ ì§€ì •í•œ 사용ìžëª…으로는 ê³„ì •ì„ ìƒì„±í• 수 없게 ë©ë‹ˆë‹¤. 단, ê´€ë¦¬ìž ê¶Œí•œìœ¼ë¡œ ê³„ì •ì„ ìƒì„±í• ë•Œì—는 해당ë˜ì§€ 않으며, ì´ë¯¸ 존재하는 ê³„ì •ë„ ì˜í–¥ì„ 받지 않습니다." +createNoteFromTheFile: "ì´ íŒŒì¼ë¡œ 노트를 작성" +archive: "ì•„ì¹´ì´ë¸Œ" +channelArchiveConfirmTitle: "{name} ì„(를) ì•„ì¹´ì´ë¸Œí•˜ì‹œê² 습니까?" +channelArchiveConfirmDescription: "ì•„ì¹´ì´ë¸Œí•œ 채ë„ì€ ì±„ë„ ëª©ë¡ê³¼ 검색 ê²°ê³¼ì— í‘œì‹œë˜ì§€ 않으며, 채ë„ì— ìƒˆë¡œìš´ 노트를 ìž‘ì„±í• ìˆ˜ 없게 ë©ë‹ˆë‹¤." +thisChannelArchived: "ì´ ì±„ë„ì€ ì•„ì¹´ì´ë¸Œë˜ì—ˆìŠµë‹ˆë‹¤." +displayOfNote: "노트 표시" +initialAccountSetting: "초기 ì„¤ì •" +youFollowing: "팔로잉" +_initialAccountSetting: + accountCreated: "ê³„ì • ìƒì„±ì´ 완료ë˜ì—ˆìŠµë‹ˆë‹¤!" + letsFillYourProfile: "ìš°ì„ ë‚˜ì˜ í”„ë¡œí•„ì„ ì„¤ì •í•´ ë³´ì•„ìš”." + profileSetting: "프로필 ì„¤ì •" + theseSettingsCanEditLater: "ì´ ì„¤ì •ë“¤ì€ ë‚˜ì¤‘ì—ë„ ë³€ê²½í• ìˆ˜ 있습니다." + youCanEditMoreSettingsInSettingsPageLater: "ì´ ì™¸ì—ë„ 'ì„¤ì •' 페ì´ì§€ì—ì„œ 다양한 ì„¤ì •ì„ ë‚˜ì˜ ìž…ë§›ì— ë§›ê²Œ ì¡°ì ˆí• ìˆ˜ 있습니다. ê¼ í™•ì¸í•´ 보세요!" + followUsers: "관심사가 맞는 ìœ ì €ë¥¼ 팔로우하여 타임ë¼ì¸ì„ 가꾸어 봅시다." + pushNotificationDescription: "푸시 ì•Œë¦¼ì„ í™œì„±í™”í•˜ë©´ {name}ì˜ ì•Œë¦¼ì„ ë‚˜ì˜ ê¸°ê¸°ì—ì„œ 받아볼 수 있게 ë©ë‹ˆë‹¤." + initialAccountSettingCompleted: "초기 ì„¤ì •ì„ ëª¨ë‘ ë§ˆì³¤ìŠµë‹ˆë‹¤!" + haveFun: "{name}와 함께 ì¦ê±°ìš´ 시간 보내세요!" + ifYouNeedLearnMore: "{name}(Misskey)ì˜ ì‚¬ìš© ë°©ë²•ì— ëŒ€í•´ ìžì„¸ížˆ ì•Œì•„ë³´ë ¤ë©´ {link}를 ì°¸ê³ í•´ 주세요." +_serverRules: + description: "íšŒì› ê°€ìž… ì´ì „ì— ê°„ë‹¨í•˜ê²Œ í‘œì‹œí• ì„œë²„ 규칙입니다. ì´ìš© ì•½ê´€ì˜ ìš”ì•½ìœ¼ë¡œ 구성하는 ê²ƒì„ ì¶”ì²œí•©ë‹ˆë‹¤." +_accountMigration: + moveFrom: "다른 ê³„ì •ì—ì„œ ì´ ê³„ì •ìœ¼ë¡œ ì´ì‚¬" + moveFromSub: "다른 ê³„ì •ì— ëŒ€í•œ 별ì¹ì„ ìƒì„±" + moveFromLabel: "기존 ê³„ì •:" + moveFromDescription: "다른 ê³„ì •ì—ì„œ ì´ ê³„ì •ìœ¼ë¡œ 팔로워를 ê°€ì ¸ì˜¤ë ¤ë©´, ìš°ì„ ì—¬ê¸°ì—ì„œ 별ì¹ì„ ì§€ì •í•´ì•¼ 합니다. 반드시 ì´ì‚¬í•˜ê¸° ì „ì— ì§€ì •í•´ì•¼ 합니다! 기존 ê³„ì •ì„ ë‹¤ìŒê³¼ ê°™ì€ í˜•ì‹ìœ¼ë¡œ ìž…ë ¥í•´ 주ì‹ì‹œì˜¤: @person@instance.com" + moveTo: "ì´ ê³„ì •ì—ì„œ 다른 ê³„ì •ìœ¼ë¡œ ì´ì‚¬" + moveToLabel: "ì´ì‚¬í• ê³„ì •:" + moveCannotBeUndone: "í•œ 번 ì´ì‚¬í•˜ë©´, ë‘ ë²ˆ 다시 ë˜ëŒë¦´ 수 없습니다." + moveAccountDescription: "ì´ ìž‘ì—…ì€ ì·¨ì†Œí• ìˆ˜ 없습니다. ë¨¼ì € ì´ì‚¬í• ê³„ì •ì—ì„œ ì´ ê³„ì •ì— ëŒ€í•œ 별ì¹ì„ ì§€ì •í•˜ì˜€ëŠ”ì§€ 다시 í•œ 번 확ì¸í•´ 주ì‹ì‹œì˜¤. 별ì¹ì„ ì§€ì •í•œ 다ìŒ, ì´ì‚¬í• ê³„ì •ì„ ë‹¤ìŒê³¼ ê°™ì€ í˜•ì‹ìœ¼ë¡œ ìž…ë ¥í•´ 주ì‹ì‹œì˜¤: @person@instance.com" + moveAccountHowTo: "ê³„ì •ì„ ì´ì‚¬í•˜ë ¤ë©´ ìš°ì„ ì´ì‚¬ê°ˆ ê³„ì •ì—ì„œ ì´ ê³„ì •ì— ëŒ€í•œ 별ì¹ì„ ì§€ì •í•´ì•¼ 합니다.\n별ì¹ì„ 작성한 다ìŒ, ì´ì‚¬ê°ˆ ê³„ì •ì„ ë‹¤ìŒê³¼ ê°™ì´ ìž…ë ¥í•˜ì‹ì‹œì˜¤:\n@username@server.example.com" + startMigration: "ì´ì‚¬í•˜ê¸°" + migrationConfirm: "ì •ë§ë¡œ ì´ ê³„ì •ì„ {account} 으로 ì´ì „í•˜ì‹œê² ìŠµë‹ˆê¹Œ? í•œ 번 ì´ì „í•œ 다ìŒì—는 ì·¨ì†Œí• ìˆ˜ 없으며, ë‘ ë²ˆ 다시 ì›ëž˜ ìƒíƒœë¡œ ë³µêµ¬í• ìˆ˜ 없습니다.\nì´ì‚¬í• ê³„ì •ì—ì„œ ê³„ì • 별ì¹ì„ ì§€ì •í•˜ì˜€ëŠ”ì§€ 다시 í•œ 번 확ì¸í•˜ì‹ì‹œì˜¤." + movedAndCannotBeUndone: "\nì´ì‚¬í•œ ê³„ì •ìž…ë‹ˆë‹¤.\nì´ì‚¬ëŠ” ì·¨ì†Œí• ìˆ˜ 없습니다." + postMigrationNote: "ì´ ê³„ì •ì˜ íŒ”ë¡œìž‰ í•´ì œëŠ” ì´ì‚¬ 후 24시간 ë’¤ì— ì‹¤í–‰ë©ë‹ˆë‹¤.\nì´ ê³„ì •ì˜ íŒ”ë¡œìš° ë° íŒ”ë¡œì›Œ 수는 0으로 표시ë©ë‹ˆë‹¤. 팔로워 í•´ì œëŠ” ì´ë£¨ì–´ì§€ì§€ 않으므로, ë‹¹ì‹ ì˜ íŒ”ë¡œì›ŒëŠ” ì´ ê³„ì •ì˜ íŒ”ë¡œì›Œ í•œì • ê²Œì‹œë¬¼ì„ ê³„ì†í•´ì„œ ì—´ëžŒí• ìˆ˜ 있습니다." + movedTo: "ì´ì‚¬í• ê³„ì •:" _achievements: earnedAt: "달성 ì¼ì‹œ" _types: @@ -1133,6 +1237,8 @@ _achievements: _client30min: title: "ìž ê¹ ì‰¬ì–´" description: "í´ë¼ì´ì–¸íŠ¸ë¥¼ ì‹œìž‘í•˜ê³ 30ë¶„ì´ ê²½ê³¼í•˜ì˜€ìŠµë‹ˆë‹¤" + _client60min: + description: "í´ë¼ì´ì–¸íŠ¸ë¥¼ ì‹œìž‘í•˜ê³ 60ë¶„ì´ ê²½ê³¼í•˜ì˜€ìŠµë‹ˆë‹¤" _noteDeletedWithin1min: title: "있었는ë°ìš” 없었습니다" description: "노트를 í¬ìŠ¤íŠ¸í•œ 후 1분 ì´ë‚´ì— ì‚ì œí–ˆìŠµë‹ˆë‹¤" @@ -1221,6 +1327,10 @@ _role: iconUrl: "ì•„ì´ì½˜ URL" asBadge: "뱃지로 표시" descriptionOfAsBadge: "활성화하면 ìœ ì €ëª… ì˜†ì— ì—í• ì˜ ì•„ì´ì½˜ì´ 표시ë©ë‹ˆë‹¤." + isExplorable: "ì—í• íƒ€ìž„ë¼ì¸ 공개" + descriptionOfIsExplorable: "활성화하면 ì—í• íƒ€ìž„ë¼ì¸ì„ 공개합니다. 비활성화 ì‹œ 타임ë¼ì¸ì´ 공개ë˜ì§€ 않습니다." + displayOrder: "표시 순서" + descriptionOfDisplayOrder: "ê°’ì´ í´ ìˆ˜ë¡ UIì—ì„œ ë¨¼ì € 표시ë©ë‹ˆë‹¤." canEditMembersByModerator: "모ë”ë ˆì´í„°ì˜ ì—í• ìˆ˜ì • 허용" descriptionOfCanEditMembersByModerator: "ì´ ì˜µì…˜ì„ ì¼œë©´ 모ë”ë ˆì´í„°ë„ ì´ ì—í• ì— ì‚¬ìš©ìžë¥¼ í• ë‹¹í•˜ê±°ë‚˜ ì‚ì œí• ìˆ˜ 있습니다. êº¼ì ¸ 있으면 관리ìžë§Œ í• ë‹¹ì´ ê°€ëŠ¥í•©ë‹ˆë‹¤." priority: "ìš°ì„ ìˆœìœ„" @@ -1235,6 +1345,7 @@ _role: canInvite: "서버 초대 코드 발행" canManageCustomEmojis: "커스텀 ì´ëª¨ì§€ 관리" driveCapacity: "ë“œë¼ì´ë¸Œ 용량" + alwaysMarkNsfw: "파ì¼ì„ í•ìƒ NSFWë¡œ ì§€ì •" pinMax: "ê³ ì •í• ìˆ˜ 있는 노트 수" antennaMax: "최대 안테나 ìƒì„± 허용 수" wordMuteMax: "단어 ë®¤íŠ¸í• ìˆ˜ 있는 ë¬¸ìž ìˆ˜" @@ -1246,15 +1357,18 @@ _role: rateLimitFactor: "ìš”ì² ë¹ˆë„ ì œí•œ" descriptionOfRateLimitFactor: "ìž‘ì„ìˆ˜ë¡ ì œí•œì´ ì™„í™”ë˜ê³ , í´ìˆ˜ë¡ ì œí•œì´ ê°•í™”ë©ë‹ˆë‹¤." canHideAds: "ê´‘ê³ ìˆ¨ê¸°ê¸°" + canSearchNotes: "노트 검색 ì´ìš© 가능 여부" _condition: isLocal: "로컬 사용ìž" isRemote: "리모트 사용ìž" - createdLessThan: "가압한 지 ë‹¤ìŒ ì¼ìˆ˜ ì´ë‚´ì¸ ìœ ì €" + createdLessThan: "가입한 지 ë‹¤ìŒ ì¼ìˆ˜ ì´ë‚´ì¸ ìœ ì €" createdMoreThan: "가입한 지 ë‹¤ìŒ ì¼ìˆ˜ ì´ìƒì¸ ìœ ì €" followersLessThanOrEq: "팔로워 수가 ë‹¤ìŒ ì´í•˜ì¸ ìœ ì €" followersMoreThanOrEq: "팔로워 수가 ë‹¤ìŒ ì´ìƒì¸ ìœ ì €" followingLessThanOrEq: "팔로잉 수가 ë‹¤ìŒ ì´í•˜ì¸ ìœ ì €" followingMoreThanOrEq: "팔로잉 수가 ë‹¤ìŒ ì´ìƒì¸ ìœ ì €" + notesLessThanOrEq: "노트 수가 ë‹¤ìŒ ì´í•˜ì¸ ìœ ì €" + notesMoreThanOrEq: "노트 수가 ë‹¤ìŒ ì´ìƒì¸ ìœ ì €" and: "다ìŒì„ ëª¨ë‘ ë§Œì¡±" or: "다ìŒì„ 하나ë¼ë„ 만족" not: "다ìŒì„ 만족하지 ì•ŠìŒ" @@ -1278,8 +1392,8 @@ _ffVisibility: private: "비공개" _signup: almostThere: "ê±°ì˜ ë‹¤ ë났습니다" - emailAddressInfo: "ë‹¹ì‹ ì´ ì‚¬ìš©í•˜ê³ ìžˆëŠ” ì´ë©”ì¼ ì£¼ì†Œë¥¼ ìž…ë ¥í•´ 주세요. ì´ë©”ì¼ ì£¼ì†ŒëŠ” 다른 ìœ ì €ì—게 공개ë˜ì§€ 않습니다." - emailSent: "ìž…ë ¥í•˜ì‹ ë©”ì¼ ì£¼ì†Œ({email})ë¡œ í™•ì¸ ë©”ì¼ì„ ë³´ë‚´ë“œë ¸ìŠµë‹ˆë‹¤. ê°€ìž…ì„ ì™„ë£Œí•˜ì‹œë ¤ë©´ 보내드린 ë©”ì¼ì— 있는 ë§í¬ë¡œ ì ‘ì†í•´ 주세요." + emailAddressInfo: "ë‹¹ì‹ ì´ ì‚¬ìš©í•˜ê³ ìžˆëŠ” ì´ë©”ì¼ ì£¼ì†Œë¥¼ ìž…ë ¥í•´ 주세요. ì´ë©”ì¼ ì£¼ì†ŒëŠ” 다른 ìœ ì €ì—게 공개ë˜ì§€ 않습니다." + emailSent: "ìž…ë ¥í•˜ì‹ ë©”ì¼ ì£¼ì†Œ({email})ë¡œ í™•ì¸ ë©”ì¼ì„ ë³´ë‚´ë“œë ¸ìŠµë‹ˆë‹¤. ê°€ìž…ì„ ì™„ë£Œí•˜ì‹œë ¤ë©´ 보내드린 ë©”ì¼ì— 있는 ë§í¬ë¡œ ì ‘ì†í•´ 주세요." _accountDelete: accountDelete: "ê³„ì • ì‚ì œ" mayTakeTime: "ê³„ì • ì‚ì œëŠ” ì„œë²„ì— ë¶€í•˜ë¥¼ 가하기 때문ì—, 작성한 콘í…ì¸ ë‚˜ 업로드한 파ì¼ì˜ 수가 많으면 완료까지 ì‹œê°„ì´ ê±¸ë¦´ 수 있습니다." @@ -1364,6 +1478,8 @@ _channel: following: "팔로잉" usersCount: "{n}명 참여 중" notesCount: "{n}노트" + nameAndDescription: "ì´ë¦„ê³¼ 설명" + nameOnly: "ì´ë¦„만" _menuDisplay: sideFull: "가로" sideIcon: "가로(ì•„ì´ì½˜)" @@ -1481,32 +1597,6 @@ _time: minute: "분" hour: "시간" day: "ì¼" -_tutorial: - title: "Misskeyì˜ ì‚¬ìš© 방법" - step1_1: "환ì˜í•©ë‹ˆë‹¤!" - step1_2: "ì´ íŽ˜ì´ì§€ëŠ” \"타임ë¼ì¸\"ì´ë¼ê³ 불립니다. ë‹¹ì‹ ì´ \"팔로우\"í•˜ê³ ìžˆëŠ” ì‚¬ëžŒë“¤ì˜ \"노트\"ê°€ 시간순으로 나타납니다." - step1_3: "ì•„ì§ ì•„ë¬´ ìœ ì €ë„ íŒ”ë¡œìš°í•˜ê³ ìžˆì§€ ì•Šê¸°ì— íƒ€ìž„ë¼ì¸ì€ 비어 ìžˆì„ ê²ƒìž…ë‹ˆë‹¤." - step2_1: "새 노트를 작성하거나 다른 ì‚¬ëžŒì„ íŒ”ë¡œìš°í•˜ê¸° ì „ì—, ë¨¼ì € í”„ë¡œí•„ì„ ì™„ì„±í•´ë³´ë„ë¡ í•©ì‹œë‹¤." - step2_2: "ë‹¹ì‹ ì´ ì–´ë–¤ 사람ì¸ì§€ë¥¼ 알린다면, 다른 ì‚¬ëžŒë“¤ì´ ë‹¹ì‹ ì„ íŒ”ë¡œìš°í• í™•ë¥ ì´ ì˜¬ë¼ê°ˆ 것입니다." - step3_1: "프로필 ì„¤ì •ì€ ìž˜ ë내셨나요?" - step3_2: "그럼 시험삼아 노트를 작성해 보세요. í™”ë©´ì— ìžˆëŠ” ì—°í•„ ë²„íŠ¼ì„ ëˆ„ë¥´ë©´ 작성 í¼ì´ 열립니다." - step3_3: "ë‚´ìš©ì„ ìž‘ì„±í•œ 후, í¼ ì˜¤ë¥¸ìª½ ìƒë‹¨ì˜ ë²„íŠ¼ì„ ëˆŒëŸ¬ 노트를 올릴 수 있습니다." - step3_4: "쓸 ë§ì´ 없나요? \"Misskey 시작했어요!\" ê°™ì€ ê±´ 어떨까요? :>" - step4_1: "노트 ìž‘ì„±ì„ ë내셨나요?" - step4_2: "ë‹¹ì‹ ì˜ ë…¸íŠ¸ê°€ 타임ë¼ì¸ì— 표시ë˜ì–´ 있다면 성공입니다." - step5_1: "ì´ì œ, 다른 ì‚¬ëžŒì„ íŒ”ë¡œìš°í•˜ì—¬ 타임ë¼ì¸ì„ 활기차게 만들어보ë„ë¡ í•©ì‹œë‹¤." - step5_2: "{featured}ì—ì„œ ì´ ì„œë²„ì˜ ì¸ê¸° 노트를 보실 수 있습니다. {explore}ì—서는 ì¸ê¸° 사용ìžë¥¼ ì°¾ì„ ìˆ˜ 있구요. 마ìŒì— 드는 ì‚¬ëžŒì„ ê³¨ë¼ íŒ”ë¡œìš°í•´ 보세요!" - step5_3: "다른 ìœ ì €ë¥¼ íŒ”ë¡œìš°í•˜ë ¤ë©´ 해당 ìœ ì €ì˜ ì•„ì´ì½˜ì„ í´ë¦í•˜ì—¬ 프로필 페ì´ì§€ë¥¼ ë„ìš´ 후, 팔로우 ë²„íŠ¼ì„ ëˆŒëŸ¬ 주세요." - step5_4: "사용ìžì— ë”°ë¼ íŒ”ë¡œìš°ê°€ 승ì¸ë 때까지 ì‹œê°„ì´ ê±¸ë¦´ 수 있습니다." - step6_1: "타임ë¼ì¸ì— 다른 사용ìžì˜ 노트가 나타난다면 성공입니다." - step6_2: "다른 ìœ ì €ì˜ ë…¸íŠ¸ì— \"리액션\"ì„ ë¶™ì—¬ 간단하게 ë‹¹ì‹ ì˜ ë°˜ì‘ì„ ì „ë‹¬í• ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤." - step6_3: "ë¦¬ì•¡ì…˜ì„ ë¶™ì´ë ¤ë©´, ë…¸íŠ¸ì˜ \"+\" ë²„íŠ¼ì„ í´ë¦í•˜ê³ ì›í•˜ëŠ” ì´ëª¨ì§€ë¥¼ ì„ íƒí•©ë‹ˆë‹¤." - step7_1: "ì´ê²ƒìœ¼ë¡œ Misskeyì˜ ê¸°ë³¸ íŠœí† ë¦¬ì–¼ì„ ë§ˆì¹˜ê² ìŠµë‹ˆë‹¤. ìˆ˜ê³ í•˜ì…¨ìŠµë‹ˆë‹¤!" - step7_2: "Misskeyì— ëŒ€í•´ ë” ì•Œê³ ì‹¶ìœ¼ì‹œë‹¤ë©´ {help}를 ì°¸ê³ í•´ 주세요." - step7_3: "그럼 Misskey를 ì¦ê¸°ì„¸ìš”! 🚀" - step8_1: "마지막으로, 푸시 ì•Œë¦¼ì„ í™œì„±í™”í•´ 보지 않으실래요?" - step8_2: "푸시 ì•Œë¦¼ì„ í™œì„±í™”í•˜ë©´, Misskey를 열지 ì•Šì•˜ì„ ë•Œì—ë„ ë¦¬ì•¡ì…˜ì´ë‚˜ 팔로우, 멘션 ë“±ì„ í™•ì¸í• 수 있습니다." - step8_3: "알림 ì„¤ì •ì€ ë‚˜ì¤‘ì—ë„ ë³€ê²½í• ìˆ˜ 있습니다." _2fa: alreadyRegistered: "ì´ë¯¸ ì„¤ì •ì´ ì™„ë£Œë˜ì—ˆìŠµë‹ˆë‹¤." registerTOTP: "ì¸ì¦ 앱 ì„¤ì • 시작" @@ -1844,9 +1934,27 @@ _deck: channel: "채ë„" mentions: "ë°›ì€ ë©˜ì…˜" direct: "다ì´ë ‰íŠ¸" + roleTimeline: "ì—í• íƒ€ìž„ë¼ì¸" _dialog: charactersExceeded: "최대 글ìžìˆ˜ë¥¼ 초과하였습니다! 현재 {current} / 최대 {min}" charactersBelow: "최소 글ìžìˆ˜ 미만입니다! 현재 {current} / 최소 {min}" +_disabledTimeline: + title: "ë¹„í™œì„±í™”ëœ íƒ€ìž„ë¼ì¸" + description: "현재 ì—í• ì—서는 ì´ íƒ€ìž„ë¼ì¸ì„ ì´ìš©í• 수 없습니다." +_drivecleaner: + orderBySizeDesc: "í¬ê¸°ê°€ í° ìˆœ" + orderByCreatedAtAsc: "등ë¡ì¼ì´ ì˜¤ëž˜ëœ ìˆœ" _webhookSettings: + createWebhook: "Webhook ìƒì„±" name: "ì´ë¦„" + secret: "ì‹œí¬ë¦¿" + events: "Webhookì„ ì‹¤í–‰í• íƒ€ì´ë°" active: "활성화" + _events: + follow: "누군가를 íŒ”ë¡œìš°í–ˆì„ ë•Œ" + followed: "누군가 나를 íŒ”ë¡œìš°í–ˆì„ ë•Œ" + note: "노트를 ê²Œì‹œí• ë•Œ" + reply: "ë‹µê¸€ì„ ë°›ì•˜ì„ ë•Œ" + renote: "누군가 ë‚´ ê¸€ì„ Renoteí–ˆì„ ë•Œ" + reaction: "누군가 ë‚´ ë…¸íŠ¸ì— ë¦¬ì•¡ì…˜í–ˆì„ ë•Œ" + mention: "누군가 나를 ë©˜ì…˜í–ˆì„ ë•Œ" diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml index 3f97b3982ca3b5ca63cc36db3bbf72bacd973e52..f686b04257debabe9228fea1251cf659976da2e8 100644 --- a/locales/lo-LA.yml +++ b/locales/lo-LA.yml @@ -211,11 +211,11 @@ uploadFromUrlRequested: "ຮ້àºàº‡àº‚à»àºàº²àº™àºàº±àºšà»‚ຫລດ" messageRead: "àºà»ˆàº²àº™à»àº¥à»‰àº§" startMessaging: "ເລີ່ມàºàº²àº™àºªàº»àº™àº—ະນາໃà»à»ˆ" nUsersRead: "àºà»ˆàº²àº™à»‚ດຠ{n}" -tos: "ເງື່àºàº™à»„ຂàºàº²àº™à»ƒàº«à»‰àºšà»àº¥àº´àºàº²àº™" start: "ເລີ່ມຕົ້ນນຳໃຊ້ເລີàº" home: "ໜ້າຫຼັàº" activity: "àºàº´àº”ຈະàºàº³" images: "ຮູບພາບ" +image: "ຮູບພາບ" birthday: "ວັນເàºàºµàº”" yearsOld: "{age} ປີ" registeredDate: "ວັນທີ່ເປັນສະມາຊິàº" diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml index 31c28a66e387aa3c7981733d2cd96ef7c4fc8a80..6bca484f141bd797ecbf67bee4216f2a49928e0f 100644 --- a/locales/nl-NL.yml +++ b/locales/nl-NL.yml @@ -254,12 +254,12 @@ noMoreHistory: "Er is geen verdere geschiedenis" startMessaging: "Start een gesprek" nUsersRead: "gelezen door {n}" agreeTo: "Ik stem in met {0}" -tos: "Gebruiksvoorwaarden" start: "Aan de slag" home: "Startpagina" remoteUserCaution: "Aangezien deze gebruiker van een externe server afkomstig is, kan de weergegeven informatie onvolledig zijn." activity: "Activiteit" images: "Afbeeldingen" +image: "Afbeeldingen" birthday: "Geboortedatum" yearsOld: "{age} jaar" registeredDate: "Inschrijvingsdatum" diff --git a/locales/no-NO.yml b/locales/no-NO.yml index 83e189b9cf1c78ce3d50251015448b388294ddff..f7f0d442d474457f2efab76375472f9278909ecd 100644 --- a/locales/no-NO.yml +++ b/locales/no-NO.yml @@ -1,2 +1,483 @@ --- _lang_: "Norsk BokmÃ¥l" +search: "Søk" +notifications: "Varsler" +username: "Brukernavn" +password: "Passord" +forgotPassword: "Glemt passord" +ok: "OK" +gotIt: "Skjønner" +cancel: "Avbryt" +noThankYou: "Avbryt" +noNotifications: "Ingen varsler" +instance: "Server" +settings: "Innstillinger" +notificationSettings: "Varslingsinnstillinger" +otherSettings: "Andre innstillinger" +profile: "Profil" +timeline: "Tidslinje" +login: "Logg inn" +loggingIn: "Logget inn" +logout: "Logg ut" +signup: "Bli med" +uploading: "Laster opp" +save: "Lagre" +users: "Brukere" +addUser: "Legg til bruker" +favorite: "Favoritt" +favorites: "Favoritter" +unfavorite: "Fjern favoritt" +pin: "Fest" +unpin: "Opphev festing" +copyContent: "Kopier innhold" +copyLink: "Kopier lenke" +delete: "Slett" +deleteAndEdit: "Slett og rediger" +addToList: "Legg til i liste" +sendMessage: "Send melding" +copyRSS: "Kopier RSS" +copyUsername: "Kopier brukernavn" +searchUser: "Søk brukere" +reply: "Svar" +loadMore: "Vis mer" +showMore: "Vis mer" +showLess: "Lukk" +files: "Filer" +download: "Nedlastinger" +lists: "Lister" +noLists: "Ingen lister" +following: "Følg" +followers: "Følgere" +followsYou: "Følger deg" +createList: "Opprett liste" +error: "Feil" +retry: "Prøv igjen" +pageLoadError: "Kunne ikke hente side." +privacy: "Personvern" +follow: "Følg" +followRequest: "Følgeforespørsel" +followRequests: "Følgeforespørsel" +unfollow: "Avfølg" +followRequestPending: "Venter pÃ¥ godkjenning" +quote: "Sitat" +pinned: "Fest" +you: "Du" +clickToShow: "Klikk for Ã¥ vise" +add: "Legg til" +reaction: "Reaksjon" +reactions: "Reaksjoner" +mute: "Skjul" +unmute: "Vis" +block: "Blokker" +unblock: "Opphev blokkering" +blockConfirm: "Blokker?" +selectList: "Velg liste" +selectChannel: "Velg kanal" +emoji: "Emoji" +emojis: "Emojier" +addEmoji: "Legg til emoji" +addAccount: "Legg til konto" +selectUser: "Velg bruker" +instances: "Server" +registeredAt: "Registrerte seg" +perHour: "Per time" +perDay: "Per dag" +blockThisInstance: "Blokker denne serveren" +version: "Versjon" +statistics: "Statistikk" +clearQueue: "Tøm kø" +clearQueueConfirmTitle: "Vil du tømme kø?" +blockedInstances: "Blokkerte severe" +muteAndBlock: "Skjul og blokker" +mutedUsers: "Skjulte brukere" +blockedUsers: "Blokkerte brukere" +editProfile: "Rediger profil" +pinLimitExceeded: "Du kan ikke feste flere." +noCustomEmojis: "Ingen emoji" +blocked: "Blokkert" +all: "Alle" +changePassword: "Endre passord" +security: "Sikkerhet" +newPassword: "Nytt passord" +newPasswordRetype: "Nytt passord (gjenta)" +more: "Mer!" +remove: "Slett" +removed: "Slettet" +saved: "Lagret" +upload: "Laste opp" +explore: "Utforsk" +messageRead: "Lest" +agree: "Jeg godtar" +home: "Hjem" +images: "Bilder" +image: "Bilder" +birthday: "Bursdag" +yearsOld: "{age} Ã¥r gammel" +light: "Lys" +dark: "Mørk" +fileName: "Filnavn" +selectFile: "Velg fil" +selectFiles: "Velg fil" +selectFolder: "Velg mappe" +selectFolders: "Velg mappe" +renameFile: "Endre filnavn" +folderName: "Mappenavn" +createFolder: "Opprett mappe" +renameFolder: "Endre mappenavn" +deleteFolder: "Slett mappe" +addFile: "Legg til fil" +emptyFolder: "Denne mappen er tom" +copyUrl: "Kopier URL" +rename: "Endre navn" +doNothing: "Gjør ingenting" +accept: "Tillatt" +reject: "AvslÃ¥" +instanceName: "Servernavn" +thisYear: "I Ã¥r" +today: "I dag" +pages: "Sider" +pinnedUsers: "Festete brukrere" +pinnedPages: "Festete sider" +hcaptcha: "hCaptcha" +recaptcha: "reCAPTCHA" +name: "Navn" +popularUsers: "Populære brukere" +exploreUsersCount: "Det finnes {count} brukere" +userList: "Lister" +about: "Infomasjon" +aboutMisskey: "Om Misskey" +share: "Del" +help: "Hjelp" +close: "Lukk" +members: "Medlemmer" +text: "Tekst" +next: "Neste" +retype: "Gjenta" +available: "Tilgjengelig" +unavailable: "Utilgjengelig" +tooShort: "For kort" +tooLong: "For langt" +or: "eller" +language: "SprÃ¥k" +aboutX: "Om {x}" +category: "Kategorier" +createAccount: "Opprett konto" +objectStorageRegion: "Region" +objectStorageUseSSL: "Bruk SSL" +objectStorageUseProxy: "Bruk Proxy" +deleteAll: "Slett alt" +listen: "Lytt" +none: "Ingen" +chooseEmoji: "Velg emoji" +recentUsed: "Sist brukte" +install: "Installer" +nothing: "Ingenting" +deleteAllFiles: "Slett alle filer" +deleteAllFilesConfirm: "Vil du slette alle filer?" +accountDeleted: "Kontoen blir slettet" +accountDeletedDescription: "Denne kontoen blir slettet" +menu: "Meny" +poll: "Avstemning" +description: "Beskrivelse" +author: "Forfatter" +height: "Høyde" +large: "Stor" +small: "Liten" +notificationType: "Varseltype" +edit: "Rediger" +email: "E-post" +smtpUser: "Brukernavn" +smtpPass: "Passord" +userSaysSomething: "{name} sa noe" +copy: "Kopier" +channel: "Kanaler" +create: "Opprett" +notificationSetting: "Varslingsinnstillinger" +other: "Andre" +behavior: "Oppførsel" +sample: "Eksempel" +abuseReports: "Rappoter" +reportAbuse: "Rappoter" +send: "Send" +openInNewTab: "Ã…pne i ny fane" +waitingFor: "Venter pÃ¥ {x}" +system: "System" +followingCount: "Følger" +followersCount: "Følgere" +yes: "Ja" +no: "Nei" +left: "Venstre" +saveAs: "Lagre som" +value: "Verdi" +deleteConfirm: "Vil du slette?" +invalidValue: "Verdien er ugyldig." +emailNotification: "E-postvarsler" +inChannelSearch: "Søk i kanal" +clear: "Tøm" +markAllAsRead: "Merk alt som lest" +addDescription: "Legg til beskrivelse" +info: "Infomasjon" +unknown: "Ukjent" +selectAccount: "Velg konto" +user: "Brukere" +accounts: "Kontoer" +switch: "Bytt" +gallery: "Galleri" +ads: "Annonser" +high: "Høy" +low: "Lav" +sent: "Send" +learnMore: "Les mer" +translate: "Oversett" +unread: "Ulest" +manageAccounts: "Administrer konto" +classic: "Klassisk" +muteThread: "Skjul denne trÃ¥den" +unmuteThread: "Vis denne trÃ¥den" +tablet: "Nettbrett" +auto: "Automatisk" +size: "Størrelse" +searchByGoogle: "Søk" +tenMinutes: "10 minutter" +oneHour: "1 time" +oneDay: "1 dag" +oneWeek: "1 uke" +oneMonth: "1 mÃ¥ned" +file: "Filer" +recommended: "Anbefalt" +check: "Sjekk" +deleteAccount: "Slett konto" +document: "Dokumenter" +logoutConfirm: "Vil du logge ut?" +pleaseSelect: "Vennligst velg" +type: "Type" +beta: "Beta" +account: "Kontoer" +move: "Flytt" +pushNotification: "Push-varsler" +tools: "Verktøy" +like: "Liker!" +unlike: "Liker ikke" +numberOfLikes: "Likerklikk" +show: "Vis" +neverShow: "Ikke vis igjen" +remindMeLater: "Kanskje senere" +didYouLikeMisskey: "Likte du Misskey?" +roles: "Roller" +role: "Rolle" +color: "Farge" +youCannotCreateAnymore: "Du kan ikke opprette flere." +cannotPerformTemporary: "Midlertidig utilgjengelig" +thisPostMayBeAnnoyingCancel: "Avbryt" +exploreOtherServers: "Utforsk andre severe" +letsLookAtTimeline: "La oss se pÃ¥ tidslinje" +cannotBeChangedLater: "Du kan ikke endre senere." +likeOnly: "Bare liker" +retryAllQueuesConfirmTitle: "Vil du prøve igjen akkurat nÃ¥?" +video: "Video" +videos: "Videoer" +continue: "Fortsett" +youFollowing: "Følger" +_initialAccountSetting: + theseSettingsCanEditLater: "Du kan endre disse innstillingene senere." +_achievements: + _types: + _notes100000: + flavor: "Du har jammen mye Ã¥ si." + _noteFavorited1: + title: "Stjernekikker" + _myNoteFavorited1: + title: "Jeg vil gjerne fÃ¥ en stjerne" + _following50: + title: "Mange venner" + _following100: + title: "100 venner" + _following300: + title: "For mange venner" + _followers10: + title: "Følg meg!" + _followers100: + title: "Populær" + _postedAtLateNight: + flavor: "Det er pÃ¥ tide Ã¥ gÃ¥ til sengs." + _driveFolderCircularReference: + title: "Rundskrivreferanse" + _reactWithoutRead: + title: "Leste du det virkelig?" + _clickedClickHere: + title: "Klikk her" + description: "Du har klikket her" + _justPlainLucky: + title: "Rett og slett heldig" + _setNameToSyuilo: + description: "Du har satt navnet ditt til \"syuilo\"" + _loggedInOnBirthday: + title: "Gratulerer med dagen" + _loggedInOnNewYearsDay: + title: "Godt nytt Ã¥r" + _brainDiver: + title: "Brain Diver" + flavor: "Misskey-Misskey La-Tu-Ma" +_role: + options: "Alternativ" + _priority: + low: "Lav" + high: "Høy" +_emailUnavailable: + used: "Allerede brukt" +_accountDelete: + accountDelete: "Slett konto" +_ad: + hide: "Ikke vis" +_gallery: + like: "Liker!" + unlike: "Liker ikke" +_preferencesBackups: + saveNew: "Lagre som ny" + cannotSave: "Kunne ikke lagre" +_registry: + key: "Nøkkel" + keys: "Nøkler" +_aboutMisskey: + about: "Misskey er programvare med Ã¥pen kildekode som har blitt utviklet av syuilo siden 2014." + translation: "Oversett Misskey" +_instanceTicker: + none: "Ikke vis" + always: "Alltid vis" +_channel: + create: "Opprett kanal" + edit: "Rediger kanal" + featured: "Populært" + following: "Følger" + nameAndDescription: "Navn og beskrivelse" +_wordMute: + soft: "Myk" + hard: "Hard" +_theme: + description: "Beskrivelse" + color: "Farge" + key: "Nøkkel" + keys: + link: "Lenke" +_sfx: + notification: "Varsler" +_ago: + future: "Fremitid" + justNow: "Akkurat nÃ¥" + secondsAgo: "{n} sekunder siden" + minutesAgo: "{n} minutter siden" + hoursAgo: "{n} timer siden" + daysAgo: "{n} dager siden" + weeksAgo: "{n} uker siden" + monthsAgo: "{n} mÃ¥neder siden" + yearsAgo: "{n} Ã¥r siden" + invalid: "Ingenting" +_time: + second: "Sekunder" + minute: "Minutter" + hour: "Timer" + day: "Dager" +_timelineTutorial: + title: "Hvordan bruke Misskey" +_2fa: + renewTOTPCancel: "Avbryt" +_weekday: + sunday: "Søndag" + monday: "Mandag" + tuesday: "Tirsdag" + wednesday: "Onsdag" + thursday: "Torsdag" + friday: "Fredag" + saturday: "Lørdag" +_widgets: + profile: "Profil" + notifications: "Varsler" + timeline: "Tidslinje" + calendar: "Kalender" + trends: "Populært" + clock: "Klokke" + photos: "Bilder" + button: "Knapp" + aiscriptApp: "AiScript App" + userList: "Brukerliste" + _userList: + chooseList: "Velg liste" +_cw: + show: "Vis mer" +_poll: + noOnlyOneChoice: "Trenger minst to valger." + choiceN: "Valg {n}" + noMore: "Du kan ikke legge til flere." + deadlineTime: "Timer" + votesCount: "{n} stemmer" + vote: "Stem" + showResult: "Vis resultatet" + voted: "Stemt" + closed: "Avsluttet" +_visibility: + home: "Hjem" + followers: "Følgere" +_postForm: + _placeholders: + a: "Hva skjer?" +_profile: + name: "Navn" + username: "Brukernavn" + metadataContent: "Innhold" +_exportOrImport: + followingList: "Følg" + muteList: "Skjul" + blockingList: "Blokker" + userLists: "Lister" +_charts: + filesIncDec: "Forskjell pÃ¥ antall filer" +_instanceCharts: + users: "Forskjell pÃ¥ antall brukere" + ff: "Forskjell pÃ¥ antall Følg/Følgere" + files: "Forskjell pÃ¥ antall filer" +_timelines: + home: "Hjem" +_play: + new: "Opprett Play" + edit: "Rediger Play" + featured: "Populært" + summary: "Beskrivelse" +_pages: + like: "Liker" + unlike: "Liker ikke" + my: "Mine sider" + featured: "Populært" + contents: "Innhold" + url: "Side URL" + fontSerif: "Serif" + fontSansSerif: "Sans Serif" + selectType: "Velg type" + blocks: + text: "Tekst" + section: "Seksjon" + image: "Bilde" + button: "Knapp" +_notification: + _types: + follow: "Følg" + reply: "Svar" + quote: "Sitat" + reaction: "Reaksjon" + _actions: + reply: "Svar" +_deck: + swapLeft: "Flytt til venstre" + swapRight: "Flytt til høyre" + swapUp: "Flytt opp" + swapDown: "Flytt ned" + profile: "Profil" + newProfile: "Ny profil" + deleteProfile: "Slett profil" + _columns: + notifications: "Varsler" + tl: "Tidslinje" + list: "Lister" + channel: "Kanaler" + direct: "Direkte" +_webhookSettings: + name: "Navn" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 517e8431f9582f1d24839ae71ed81f4f7f197995..24eee60bae43db8b0be985071a66b2dcd6773ed9 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -257,12 +257,12 @@ startMessaging: "Rozpocznij czat" nUsersRead: "przeczytano przez {n}" agreeTo: "Wyrażam zgodÄ™ na {0}" agreeBelow: "Zaakceptuj poniżej" -tos: "Regulamin" start: "Rozpocznij" home: "Strona główna" remoteUserCaution: "Te informacje mogÄ… nie być aktualne, ponieważ użytkownik pochodzi ze zdalnej instancji." activity: "Aktywność" images: "ZdjÄ™cia" +image: "ZdjÄ™cia" birthday: "Data urodzenia" yearsOld: "{age} lat" registeredDate: "Zarejestrowano" @@ -870,6 +870,7 @@ loggedInAsBot: "JesteÅ› obecnie zalogowany/a jako bot" like: "Polub" show: "WyÅ›wietlanie" color: "Kolor" +youFollowing: "Åšledzeni" _role: priority: "Priorytet" _priority: @@ -1086,32 +1087,6 @@ _time: minute: "minuta" hour: "godz." day: "dzieÅ„" -_tutorial: - title: "Jak korzystać z Misskey" - step1_1: "Witaj!" - step1_2: "Ta strona nazywa siÄ™ „oÅ› czasuâ€. Pokazuje chronologicznie uporzÄ…dkowane wpisy osób, które „śledziszâ€." - step1_3: "Twoja oÅ› czasu jest jeszcze pusta, ponieważ nie opublikowaÅ‚eÅ›(-aÅ›) jeszcze żadnych wpisów i nie obserwujesz jeszcze nikogo." - step2_1: "UkoÅ„czmy konfiguracjÄ™ profilu zanim utworzymy wpis lub zaczniemy kogoÅ› obserwować." - step2_2: "Podanie pewnych informacji o tym, kim jesteÅ›, uÅ‚atwi innym okreÅ›lenie, czy chcÄ… widzieć Twoje wpisy lub CiÄ™ obserwować." - step3_1: "ZakoÅ„czyÅ‚eÅ›(-aÅ›) konfiguracjÄ™ profilu?" - step3_2: "NastÄ™pnie spróbujmy opublikować wpis. Możesz to zrobić, naciskajÄ…c przycisk z ikonÄ… ołówka na ekranie." - step3_3: "WypeÅ‚nij pole i kliknij przycisk w prawym górnym rogu by wysÅ‚ać post." - step3_4: "Nie masz nic do powiedzenia? Spróbuj \"ustawiam swój misskey\"!" - step4_1: "ZakoÅ„czyÅ‚eÅ› publikowanie pierwszego wpisu?" - step4_2: "Hurra! Teraz Twój pierwszy wpis powinien być wyÅ›wietlany na Twojej osi czasu." - step5_1: "Teraz spróbujmy ożywić TwojÄ… oÅ› czasu, przez zaobserwowanie innych ludzi." - step5_2: "{featured} pokaże Ci popularne wpisy na tej instancji. {explore} pozwoli Ci znaleźć popularnych użytkowników. Spróbuj znaleźć tam osoby, które chcesz obserwować!" - step5_3: "Aby obserwować innych użytkowników, kliknij ich ikonÄ™ i naciÅ›nij przycisk \"Obserwuj\" na ich profilu." - step5_4: "JeÅ›li inny użytkownik ma ikonÄ™ kłódki obok swojej nazwy, może minąć trochÄ™ czasu, zanim ten użytkownik rÄ™cznie zatwierdzi TwojÄ… proÅ›bÄ™ o obserwowanie." - step6_1: "PowinieneÅ› teraz widzieć wpisy innych użytkowników na swojej osi czasu." - step6_2: "Możesz także umieÅ›cić „reakcje†na wpisach innych osób, aby szybko na nie odpowiedzieć." - step6_3: "Aby dodać \"reakcjÄ™\", naciÅ›nij znak \"+\" na wpisie innego użytkownika i wybierz emotikonÄ™, którÄ… chcesz zareagować." - step7_1: "Gratulacje! UkoÅ„czyÅ‚eÅ› podstawowy samouczek Misskey." - step7_2: "JeÅ›li chcesz dowiedzieć siÄ™ wiÄ™cej o Misskey, wypróbuj sekcjÄ™ {help}." - step7_3: "A teraz powodzenia i baw siÄ™ dobrze z Misskey! 🚀" - step8_1: "Na sam koniec, czy nie chciaÅ‚(a)byÅ› wÅ‚Ä…czyć powiadomieÅ„ push?" - step8_2: "WÅ‚Ä…czenie tej opcji pozwoli ci otrzymywać powiadomienia o reakcjach, Å›ledzeniach i wzmiankach nawet wtedy, gdy Misskey nie bÄ™dzie otwarty." - step8_3: "Ustawienia powiadomieÅ„ można zmienić później." _2fa: alreadyRegistered: "ZarejestrowaÅ‚eÅ› już urzÄ…dzenie do uwierzytelniania dwuskÅ‚adnikowego." step1: "Najpierw, zainstaluj aplikacjÄ™ uwierzytelniajÄ…cÄ… (takÄ… jak {a} lub {b}) na swoim urzÄ…dzeniu." diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index 8a7232fa2c607a67fceabaefca8b6537d937e02d..e7b43f63803bc94b5aa76c91e069212917604c6a 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -252,12 +252,12 @@ noMoreHistory: "Sem mais história" startMessaging: "Iniciar conversação" nUsersRead: "{n} Pessoas leem" agreeTo: "Eu concordo com {0}" -tos: "Termos de serviço" start: "começar" home: "casa" remoteUserCaution: "As informações estão incompletas porque é um utilizador remoto." activity: "atividade" images: "imagem" +image: "imagem" birthday: "aniversário" yearsOld: "{age} anos" registeredDate: "Data de registro" diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml index bbf0160de96e35a797289946ff5053d57c3a140f..6ad45d06366c503638688b2ad64513bcc6ca8dcc 100644 --- a/locales/ro-RO.yml +++ b/locales/ro-RO.yml @@ -252,12 +252,12 @@ noMoreHistory: "Nu există mai mult istoric" startMessaging: "ÃŽncepe un chat nou" nUsersRead: "citit de {n}" agreeTo: "Sunt de acord cu {0}" -tos: "Termenii de utilizare" start: "Să începem" home: "Acasă" remoteUserCaution: "Deoarece acest utilizator este dintr-o instanță externă, informaÈ›ia afiÈ™ată poate fi incompletă." activity: "Activitate" images: "Imagini" +image: "Imagini" birthday: "Zi de naÈ™tere" yearsOld: "{age} ani" registeredDate: "Data înregistrării" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 8a09d303067ce4ce3c6f10123f0ec72f76d4a0a0..55d4b70590be8e22ca8f45173e3b819fdf0f9098 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -20,6 +20,7 @@ noNotes: "Ðет ни одной заметки" noNotifications: "Ðет ни одного уведомлениÑ" instance: "ИнÑтанÑ" settings: "ÐаÑтройки" +notificationSettings: "ÐаÑтройки уведомлений" basicSettings: "ОÑновные наÑтройки" otherSettings: "Прочие наÑтройки" openInWindow: "Открыть в плавающем окне" @@ -122,6 +123,8 @@ unmarkAsSensitive: "СнÑÑ‚ÑŒ отметку «не Ð´Ð»Ñ Ð²Ñех»" enterFileName: "Введите Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°" mute: "Скрыть" unmute: "Отменить Ñкрытие" +renoteMute: "Заглушить репоÑÑ‚Ñ‹" +renoteUnmute: "Включить репоÑÑ‚Ñ‹" block: "Заблокировать" unblock: "Разблокировать" suspend: "Заморозить" @@ -153,6 +156,7 @@ flagShowTimelineReplies: "Показывать ответы на заметки flagShowTimelineRepliesDescription: "ЕÑли Ñтот параметр включен, то в ленте, в дополнение к заметкам пользователÑ, отображаютÑÑ Ð¾Ñ‚Ð²ÐµÑ‚Ñ‹ на другие заметки пользователÑ." autoAcceptFollowed: "Принимать подпиÑчиков автоматичеÑки" addAccount: "Добавить учётную запиÑÑŒ" +reloadAccountsList: "Обновить ÑпиÑок учётных запиÑей" loginFailed: "ÐÐµÑƒÐ´Ð°Ñ‡Ð½Ð°Ñ Ð¿Ð¾Ð¿Ñ‹Ñ‚ÐºÐ° входа" showOnRemote: "Перейти к оригиналу на Ñайт" general: "Общее" @@ -259,14 +263,16 @@ noMoreHistory: "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð·Ð°ÐºÐ¾Ð½Ñ‡Ð¸Ð»Ð°ÑÑŒ" startMessaging: "Ðачать общение" nUsersRead: "Прочитали {n}" agreeTo: "Я ÑоглашаюÑÑŒ Ñ {0}" +agree: "СоглаÑитьÑÑ" agreeBelow: "СоглаÑен Ñо Ñледующими" basicNotesBeforeCreateAccount: "ЗапиÑи, перед Ñозданием аккаунта" -tos: "ПользовательÑкое Ñоглашение" +termsOfService: "УÑÐ»Ð¾Ð²Ð¸Ñ Ð¸ÑпользованиÑ" start: "Ðачать" home: "ГлавнаÑ" remoteUserCaution: "Ðто пользователь Ñ Ð´Ñ€ÑƒÐ³Ð¾Ð³Ð¾ Ñайта, поÑтому Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶ÐµÑ‚ быть неточной." activity: "ÐктивноÑÑ‚ÑŒ" images: "ИзображениÑ" +image: "ИзображениÑ" birthday: "День рождениÑ" yearsOld: "ВозраÑÑ‚: {age}" registeredDate: "Дата региÑтрации" @@ -457,6 +463,7 @@ aboutX: "ОпиÑание {x}" emojiStyle: "Стиль Ñмодзи" native: "СиÑтемные" disableDrawer: "Ðе иÑпользовать выдвижные меню" +showNoteActionsOnlyHover: "Показывать кнопки ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð·Ð°Ð¼ÐµÑ‚ÐºÐ¾Ð¹ только при наведении" noHistory: "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð¿Ð¾ÐºÐ° пуÑта" signinHistory: "Журнал поÑещений" enableAdvancedMfm: "Включить раÑширенный MFM" @@ -469,6 +476,8 @@ createAccount: "ÐÐ¾Ð²Ð°Ñ ÑƒÑ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ" existingAccount: "СущеÑÑ‚Ð²ÑƒÑŽÑ‰Ð°Ñ ÑƒÑ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ" regenerate: "Создать повторно" fontSize: "Размер шрифта" +mediaListWithOneImageAppearance: "Показывать ÑпиÑок медиа только одним изображением" +limitTo: "Обрезать до {x}" noFollowRequests: "ÐераÑÑмотренные запроÑÑ‹ на подпиÑку отÑутÑтвуют" openImageInNewTab: "Открыть изображение в новой вкладке" dashboard: "Панель управлениÑ" @@ -502,9 +511,11 @@ objectStorageUseSSLDesc: "Отключите, еÑли не ÑÐ¾Ð±Ð¸Ñ€Ð°ÐµÑ‚ÐµÑ objectStorageUseProxy: "ИÑпользовать прокÑи" objectStorageUseProxyDesc: "Отключите, еÑли не будете иÑпоьзовать прокÑи Ð´Ð»Ñ Ñоединений по протоколу ObjectStorage." objectStorageSetPublicRead: "УÑтанавливать public-read при загрузке на Ñервер" +s3ForcePathStyleDesc: "Включение s3ForcePathStyle принудительно указывает Ð¸Ð¼Ñ ÐºÐ¾Ñ€Ð·Ð¸Ð½Ñ‹ как чаÑÑ‚ÑŒ пути в URL-адреÑе вмеÑто имени хоÑта. Может потребоватьÑÑ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð¿Ñ€Ð¸ иÑпользовании таких вещей, как локальный Minio." serverLogs: "Журнал Ñервера" deleteAll: "Удалить вÑÑ‘" showFixedPostForm: "Показывать поле Ð´Ð»Ñ Ð²Ð²Ð¾Ð´Ð° новой заметки наверху ленты" +showFixedPostFormInChannel: "Показывать поле Ð´Ð»Ñ Ð²Ð²Ð¾Ð´Ð° новой заметки наверху ленты (каналы)" newNoteRecived: "ПоÑвилаÑÑŒ Ð½Ð¾Ð²Ð°Ñ Ð·Ð°Ð¼ÐµÑ‚ÐºÐ°" sounds: "Звуки" sound: "Звуки" @@ -542,6 +553,10 @@ userSuspended: "Ðта ÑƒÑ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ заморожена" userSilenced: "Ðтот пользователь был заглушен" yourAccountSuspendedTitle: "Ðта ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ заблокирована" yourAccountSuspendedDescription: "Ðта ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ была заблокирована из-за Ð½Ð°Ñ€ÑƒÑˆÐµÐ½Ð¸Ñ ÑƒÑловий предоÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÑƒÑлуг Ñервера. СвÑжитеÑÑŒ Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратором, еÑли вы хотите узнать более подробную причину. ПожалуйÑта, не Ñоздавайте новую учетную запиÑÑŒ." +tokenRevoked: "Токен недейÑтвителен" +tokenRevokedDescription: "Срок дейÑÑ‚Ð²Ð¸Ñ Ð²Ð°ÑˆÐµÐ³Ð¾ токена входа иÑтек. ПожалуйÑта, войдите Ñнова." +accountDeleted: "Ðта ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ удалена" +accountDeletedDescription: "Ðта ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ удалена" menu: "Меню" divider: "ЛиниÑ-разделитель" addItem: "Добавить Ñлемент" @@ -688,6 +703,8 @@ contact: "Как ÑвÑзатьÑÑ" useSystemFont: "ИÑпользовать шрифт, предлагаемый ÑиÑтемой" clips: "Подборки" experimentalFeatures: "ÐкÑпериментальные функции" +experimental: "ÐкÑпериментальные" +thisIsExperimentalFeature: "Ðто ÑкÑÐ¿ÐµÑ€Ð¸Ð¼ÐµÐ½Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ. ТехничеÑкие характериÑтики могут изменитьÑÑ Ð¸Ð»Ð¸ он может работать неправильно." developer: "Разработчик" makeExplorable: "Опубликовать профиль в «Обзоре»." makeExplorableDescription: "ЕÑли выключить, ваш профиль не будет показан в разделе «Обзор»." @@ -852,6 +869,7 @@ tenMinutes: "10 минут" oneHour: "1 чаÑ" oneDay: "1 день" oneWeek: "1 неделÑ" +oneMonth: "1 меÑÑц" reflectMayTakeTime: "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ занÑÑ‚ÑŒ Ð²Ñ€ÐµÐ¼Ñ Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ" failedToFetchAccountInformation: "Ðе удалоÑÑŒ получить информацию об аккаунте" rateLimitExceeded: "Ограничение ÑкороÑти превышено" @@ -893,6 +911,7 @@ remoteOnly: "Только удалённо" failedToUpload: "Сбой выгрузки" cannotUploadBecauseInappropriate: "Файл не может быть загружен, так как было уÑтановлено, что он может Ñодержать неприемлемое Ñодержимое." cannotUploadBecauseNoFreeSpace: "Файл не может быть загружен, так как не оÑталоÑÑŒ меÑта на диÑке" +cannotUploadBecauseExceedsFileSizeLimit: "Файл не может быть загружен, так как он превышает лимит размера файла." beta: "Бета" enableAutoSensitive: "ÐвтоматичеÑкое определение NSFW" enableAutoSensitiveDescription: "ЕÑли доÑтупно, иÑпользуйте машинное обучение Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкой уÑтановки флага NSFW на ноÑителе. Даже еÑли Ñта Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ Ð¾Ñ‚ÐºÐ»ÑŽÑ‡ÐµÐ½Ð°, она может быть уÑтановлена ​​автоматичеÑки в завиÑимоÑти от инÑтанта." @@ -909,6 +928,7 @@ pushNotificationNotSupported: "Push-ÑƒÐ²ÐµÐ´Ð¼Ð»ÐµÐ½Ð¸Ñ Ð½Ðµ поддержив sendPushNotificationReadMessage: "УдалÑÑ‚ÑŒ push-ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ ÐºÐ¾Ð³Ð´Ð° Ñообщение или прочитано" sendPushNotificationReadMessageCaption: "Ðа мгновение поÑвитÑÑ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ðµ \"{emptyPushNotificationMessage}\". РаÑход зарÑда батареи может увеличитьÑÑ " windowMaximize: "Развернуть" +windowMinimize: "Свернуть" windowRestore: "ВоÑÑтановить" caption: "ПодпиÑÑŒ (Automatic Translation)" loggedInAsBot: "Ð’Ñ‹ под аккаунтом бота!" @@ -925,6 +945,7 @@ didYouLikeMisskey: "Вам нравитÑÑ Misskey?" pleaseDonate: "Сайт {host} работает на Misskey. Ðто беÑплатное программное обеÑпечение, и ваши Ð¿Ð¾Ð¶ÐµÑ€Ñ‚Ð²Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¾Ñ‡ÐµÐ½ÑŒ бы помогли продолжать его разработку!" roles: "Роли" role: "Роль" +noRole: "Ðет роли" normalUser: "Обычный пользователь" undefined: "неопределён" assign: "Ðазначить" @@ -934,6 +955,10 @@ manageCustomEmojis: "УправлÑÑ‚ÑŒ пользовательÑкими Ñм youCannotCreateAnymore: "Ð’Ñ‹ доÑтигли лимита ÑозданиÑ." cannotPerformTemporary: "Временно недоÑтупен" cannotPerformTemporaryDescription: "Ðто дейÑтвие временно невозможно выполнить из-за Ð¿Ñ€ÐµÐ²Ñ‹ÑˆÐµÐ½Ð¸Ñ Ð»Ð¸Ð¼Ð¸Ñ‚Ð° выполнениÑ." +invalidParamError: "Ошибка параметра" +invalidParamErrorDescription: "Проблема Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð°Ð¼Ð¸ запроÑа. Обычно Ñто ошибка, но Ñто также может быть ÑвÑзано Ñ Ñ‚ÐµÐ¼, что вы набрали Ñлишком много Ñимволов." +permissionDeniedError: "ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð·Ð°Ð¿Ñ€ÐµÑ‰ÐµÐ½Ð°" +permissionDeniedErrorDescription: "У Ñтой учетной запиÑи нет Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð½Ð° выполнение Ñтой операции." preset: "Шаблоны" selectFromPresets: "Выбрать из шаблонов" achievements: "ДоÑтижениÑ" @@ -950,6 +975,37 @@ copyErrorInfo: "Скопировать код ошибки" joinThisServer: "ПриÑоединÑйтеÑÑŒ к Ñтому Ñерверу" exploreOtherServers: "ИÑкать другие Ñервера" letsLookAtTimeline: "Давайте поÑмотрим на ленту" +disableFederationConfirm: "Отключить федерацию?" +disableFederationConfirmWarn: "Ð”ÐµÑ„ÐµÐ´ÐµÑ€Ð°Ñ†Ð¸Ñ Ð½Ðµ делает заметку приватной. Ð’ большинÑтве Ñлучаев без федерации не обойтиÑÑŒ." +disableFederationOk: "Ðе федерируетÑÑ" +invitationRequiredToRegister: "Ðтот Ñервер в наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ по приглашению. ЗарегиÑтрироватьÑÑ Ð¼Ð¾Ð³ÑƒÑ‚ только те, у кого еÑÑ‚ÑŒ код приглашениÑ." +emailNotSupported: "ДоÑтавка почты не поддерживаетÑÑ Ð½Ð° Ñтом Ñервере" +postToTheChannel: "Отправить в канал" +cannotBeChangedLater: "Ðто Ð½ÐµÐ»ÑŒÐ·Ñ Ð¸Ð·Ð¼ÐµÐ½Ð¸Ñ‚ÑŒ позже" +reactionAcceptance: "ПринÑтие реакций" +likeOnly: "Только лайки" +likeOnlyForRemote: "Только лайки Ñ ÑƒÐ´Ð°Ð»Ñ‘Ð½Ð½Ñ‹Ñ… Ñерверов" +rolesAssignedToMe: "Мои роли" +resetPasswordConfirm: "СброÑить пароль?" +sensitiveWords: "ЧувÑтвительные Ñлова" +sensitiveWordsDescription: "УÑтановите общедоÑтупный диапазон заметки, Ñодержащей заданное Ñлово, на домашний. Можно Ñделать неÑколько наÑтроек, разделив их переноÑами Ñтрок." +notesSearchNotAvailable: "ПоиÑк заметок недоÑтупен" +license: "ЛицензиÑ" +unfavoriteConfirm: "Удалить избранное?" +myClips: "Мои клипы" +drivecleaner: "ОчиÑтитель диÑков" +retryAllQueuesNow: "Повторить вÑе очереди ÑейчаÑ" +retryAllQueuesConfirmTitle: "Хотите попробовать ещё раз?" +retryAllQueuesConfirmText: "Ðагрузка на Ñервер может увеличитьÑÑ" +enableChartsForRemoteUser: "Создание диаграмм Ð´Ð»Ñ ÑƒÐ´Ð°Ð»Ñ‘Ð½Ð½Ñ‹Ñ… пользователей" +enableChartsForFederatedInstances: "Создание диаграмм Ð´Ð»Ñ ÑƒÐ´Ð°Ð»Ñ‘Ð½Ð½Ñ‹Ñ… Ñерверов" +largeNoteReactions: "Показывать большие реакции на заметки" +noteIdOrUrl: "ID или ÑÑылка на заметку" +video: "Видео" +videos: "Видео" +dataSaver: "ÐÐºÐ¾Ð½Ð¾Ð¼Ð¸Ñ Ñ‚Ñ€Ð°Ñ„Ð¸ÐºÐ°" +horizontal: "Сбоку" +youFollowing: "ПодпиÑки" _achievements: earnedAt: "Разблокировано в" _types: @@ -1469,32 +1525,6 @@ _time: minute: "мин" hour: "ч" day: "Ñут" -_tutorial: - title: "Как пользоватьÑÑ Misskey" - step1_1: "Добро пожаловать!" - step1_2: "Ðта Ñтраница называетÑÑ Â«Ð»ÐµÐ½Ñ‚Ð°Â». ЗдеÑÑŒ будут поÑвлÑÑ‚ÑŒÑÑ Â«Ð·Ð°Ð¼ÐµÑ‚ÐºÐ¸Â»: ваши личные и тех, на кого вы «подпиÑаны». Они будут раÑполагатьÑÑ Ð² порÑдке времени их поÑвлениÑ." - step1_3: "Правда, ваша лента пока пуÑта. Она начнёт заполнÑÑ‚ÑŒÑÑ, когда вы будете пиÑать Ñвои заметки и подпиÑыватьÑÑ Ð½Ð° других." - step2_1: "Давайте, заполним профиль, прежде чем начать пиÑать заметки и подпиÑыватьÑÑ Ð½Ð° других." - step2_2: "То, что вы раÑÑкажете в профиле, поможет лучше Ð²Ð°Ñ ÑƒÐ·Ð½Ð°Ñ‚ÑŒ, а значит, многим будет легче приÑоединитьÑÑ â€” вы Ñкорее получите новых подпиÑчиков и читателей." - step3_1: "УÑпешно заполнили профиль?" - step3_2: "Что ж, теперь Ñамое Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð¿ÑƒÐ±Ð»Ð¸ÐºÐ¾Ð²Ð°Ñ‚ÑŒ заметку. ЕÑли нажать вверху Ñтраницы на изображение карандаша, поÑвитÑÑ Ñ„Ð¾Ñ€Ð¼Ð° Ð´Ð»Ñ Ñ‚ÐµÐºÑта." - step3_3: "Ðапишите в неё, что хотите, и нажмите на кнопку в правом верхнем углу." - step3_4: "Ðичего не приходит в голову? Как наÑчёт: «Я новенький, пока оÑваиваюÑÑŒ в Misskey»?" - step4_1: "С напиÑанием первой заметки покончено?" - step4_2: "Отлично, теперь она должна поÑвитьÑÑ Ð² вашей ленте." - step5_1: "Ртеперь Ñамое Ð²Ñ€ÐµÐ¼Ñ Ð½ÐµÐ¼Ð½Ð¾Ð³Ð¾ оживить ленту, подпиÑавшиÑÑŒ на других." - step5_2: "Ðа Ñтранице «{featured}» Ñобраны популÑрные ÑÐµÐ³Ð¾Ð´Ð½Ñ Ð·Ð°Ð¼ÐµÑ‚ÐºÐ¸, Ñ‡Ð¸Ñ‚Ð°Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ðµ, вы можете найти кого-то вам интереÑного, а на Ñтранице «{explore}» можно поÑмотреть, кто популÑрен у оÑтальных." - step5_3: "Чтобы подпиÑатьÑÑ Ð½Ð° кого-нибудь, щёлкните по его аватару и в открывшемÑÑ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ðµ нажмите кнопку «ПодпиÑатьÑÑ»." - step5_4: "Ðекоторые пользователи (около их имени «виÑит замок») вручную подтверждают чужие подпиÑки. Так что иногда подпиÑка начинает работать не Ñразу.\n" - step6_1: "ЕÑли теперь в ленте видны и чужие заметки, значит у Ð²Ð°Ñ Ð¿Ð¾Ð»ÑƒÑ‡Ð¸Ð»Ð¾ÑÑŒ." - step6_2: "ЗдеÑÑŒ можно непринуждённо выразить Ñвои чувÑтва к чьей-то заметке, отметив «реакцию» под ней." - step6_3: "Отмечайте реакции, Ð½Ð°Ð¶Ð¼Ð°Ñ Ð½Ð° Ñимвол «+» под заметкой и Ð²Ñ‹Ð±Ð¸Ñ€Ð°Ñ Ð·Ð½Ð°Ñ‡Ð¾Ðº по душе." - step7_1: "Ðа Ñтом вводный урок по иÑпользованию Misskey закончен. СпаÑибо, что прошли его до конца!" - step7_2: "Хотите изучить Misskey глубже — добро пожаловать в раздел «{help}»." - step7_3: "ПриÑтно вам провеÑти Ð²Ñ€ÐµÐ¼Ñ Ñ Misskey🚀" - step8_1: "ÐÑ…, да, не хотите ли включить push-уведомлениÑ?" - step8_2: "С push-уведомлениÑми вы будете в курÑе репоÑтов, ответов, реакций и вÑего такого, даже когда закрыли Misskey." - step8_3: "Ðту наÑтройку вы вÑегда Ñможете поменÑÑ‚ÑŒ" _2fa: alreadyRegistered: "Ð”Ð²ÑƒÑ…Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð°Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ ÑƒÐ¶Ðµ наÑтроена." registerTOTP: "Ðачните наÑтраивать приложение-аутентификатор" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index 7c74060158884013b88d59cc421dc1a4c63a6b58..3e96119389df68c8b1e864bb40641d397b7c3019 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -261,12 +261,12 @@ nUsersRead: "preÄÃtané {n} použÃvateľmi" agreeTo: "SúhlasÃm s {0}" agreeBelow: "SúhlasÃm s nasledovným" basicNotesBeforeCreateAccount: "Základné bezpeÄnostné opatrenia" -tos: "Podmienky použÃvania" start: "ZaÄaÅ¥" home: "Domov" remoteUserCaution: "Tieto informácie nemusia byÅ¥ aktuálne, keÄže použÃvateľ je na vzdialenom serveri." activity: "Aktivita" images: "Obrázky" +image: "Obrázky" birthday: "Dátum narodenia" yearsOld: "{age} rokov" registeredDate: "Dátum registrácie" @@ -917,6 +917,8 @@ remindMeLater: "Pripomenúť neskôr" didYouLikeMisskey: "PáÄi sa vám Misskey?" pleaseDonate: "Misskey je bezplatný softvér, ktorý použÃva {host}. ProsÃm, prispejte, aby sme ho mohli Äalej rozvÃjaÅ¥!" color: "Farba" +horizontal: "Strana" +youFollowing: "Sledované" _role: priority: "Priorita" _priority: @@ -1146,32 +1148,6 @@ _time: minute: "min" hour: "hod" day: "dnÃ" -_tutorial: - title: "Ako použÃvaÅ¥ Misskey" - step1_1: "Vitajte!" - step1_2: "Táto stránka sa volá \"Äasová os\". Zobrazuje chronologicky zoradené \"poznámky\" od ľudÃ, ktorých sledujete." - step1_3: "VaÅ¡a Äasová os je teraz prázdna pretože ste nepridali žiadne poznámky ani nikoho zatiaľ nesledujete." - step2_1: "Podˇme dokonÄiÅ¥ nastavenia vášho profilu pred napÃsanÃm poznámky alebo sledovanÃm niekoho." - step2_2: "PoskytnutÃm informácià o vás uľahÄÃte ostatným, Äi chcú vidieÅ¥ alebo sledovaÅ¥ vaÅ¡e poznámky." - step3_1: "DokonÄili ste nastavovanie svojho profilu?" - step3_2: "PoÄme vyskúšaÅ¥ napÃsaÅ¥ poznámku. Môžete to spraviÅ¥ stlaÄenÃm ikony ceruzky na vrchu obrazovky." - step3_3: "Vyplňte polia a stlaÄte tlaÄÃtko vpravo hore." - step3_4: "Nemáte Äo povedaÅ¥? Skúste \"len si nastavujem môj msky\"!" - step4_1: "NapÃsali ste svoju prvú poznámku?" - step4_2: "Hurá! Teraz by vaÅ¡a prvá poznámka mala byÅ¥ na vaÅ¡ej Äasovej osi." - step5_1: "Teraz skúsme oživiÅ¥ Äasovú os sledovanÃm nejakých ľudÃ." - step5_2: "{featured} zobrazà populárne poznámku na tomto serveri. {explore} môžete objavovaÅ¥ populárnych použÃvateľov. Skúste tam nájsÅ¥ ľudÃ, ktorých by ste radi sledovali!" - step5_3: "Ak chcete sledovaÅ¥ ÄalÅ¡Ãch použÃvateľov, kliknite na ich ikonu a stlaÄte tlaÄidlo \"SledovaÅ¥\" na ich profile." - step5_4: "Ak má niektorý použÃvateľ ikonu zámku vedľa svojho mena, znamená to, že môže trvaÅ¥ urÄitý Äas, kým daný použÃvateľ schváli vaÅ¡u žiadosÅ¥ o sledovanie." - step6_1: "Teraz by ste mali vidieÅ¥ poznámky ÄalÅ¡Ãch použÃvateľov na svojej Äasovej osi." - step6_2: "Môžete daÅ¥ \"reakcie\" na poznámky ÄalÅ¡Ãch ľudà ako rýchlu odpoveÄ." - step6_3: "Reakciu pridáte kliknutÃm na \"+\" niekoho poznámke a vybratÃm emoji, ktorou chcete reagovaÅ¥." - step7_1: "Gralujeme! DokonÄili ste základného sprievodcu Misskey." - step7_2: "Ak sa chcete nauÄiÅ¥ viac o Misskey, skúste sekciu {help}." - step7_3: "A teraz, veľa Å¡Å¥astia, bavte sa s Misskey! 🚀" - step8_1: "A nakoniec, preÄo si neaktivovaÅ¥ push oznámenia?" - step8_2: "VÄaka push notifikáciám sa dozviete o reakciách, sledovaniach a zmienkach, aj keÄ Misskey nie je otvorené." - step8_3: "Nastavenia notifikácià môžete neskôr zmeniÅ¥." _2fa: alreadyRegistered: "Už ste zaregistrovali 2-faktorové autentifikaÄné zariadenie." step1: "Najprv si nainÅ¡talujte autentifikaÄnú aplikáciu (naprÃklad {a} alebo {b}) na svoje zariadenie." diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml index 6369adb454475a7c88a9025ce25c4e9b9d7712b5..7a00e3fcea09911b382380863e528e52463f0673 100644 --- a/locales/sv-SE.yml +++ b/locales/sv-SE.yml @@ -256,11 +256,11 @@ noMoreHistory: "Det finns ingen mer historik" startMessaging: "Starta en chatt" nUsersRead: "läst av {n}" agreeTo: "Jag accepterar {0}" -tos: "Användarvillkor" home: "Hem" remoteUserCaution: "DÃ¥ denna användaren kommer frÃ¥n en fjärrinstans, kan informationen visad vara ofullständig." activity: "Aktivitet" images: "Bilder" +image: "Bilder" birthday: "Födelsedag" yearsOld: "{age} Ã¥r gammal" registeredDate: "Gick med" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index a863103e2cb59897346975dcfedd5d9668b3c759..d1f87dbfdb2b71f36928016272960d966013c273 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -18,8 +18,9 @@ enterUsername: "ใส่ชื่à¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰" renotedBy: "รีโน้ตโดย {ผู้ใช้}" noNotes: "ไม่มีโน้ต" noNotifications: "ไม่มีà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸à¸™" -instance: "ตัวà¸à¸¢à¹ˆà¸²à¸‡" +instance: "à¸à¸´à¸™à¸ªà¹à¸•à¸™à¸‹à¹Œ" settings: "à¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸„่า" +notificationSettings: "ตั้งค่าà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸à¸™" basicSettings: "à¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸„่าพื้นà¸à¸²à¸™" otherSettings: "à¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸„่าà¸à¸·à¹ˆà¸™à¹†" openInWindow: "เปิดในหน้าต่าง" @@ -99,7 +100,7 @@ followRequestPending: "à¸à¸³à¸¥à¸±à¸‡à¸£à¸à¸”ำเนินà¸à¸²à¸£à¸£à¹‰à¸ enterEmoji: "ใส่à¸à¸µà¹‚มจิ" renote: "รีโน้ต" unrenote: "เลิà¸à¸£à¸µà¹‚น้ต" -renoted: "รีโน้ตเà¸à¸²à¹„ว้" +renoted: "รีโน้ตà¹à¸¥à¹‰à¸§à¸™à¸°" cantRenote: "โพสต์นี้ไม่สามารถรีโน้ตไว้ใหม่ได้นะ" cantReRenote: "ไม่สามารถรีโน้ตเà¸à¸²à¹„ว้ใหม่ได้นะ" quote: "à¸à¹‰à¸²à¸‡à¸„ำพูด" @@ -262,14 +263,16 @@ noMoreHistory: "ในนั้นไม่มีประวัติà¸à¸µà¸ startMessaging: "เริ่มà¸à¸²à¸£à¸ªà¸™à¸—นา" nUsersRead: "à¸à¹ˆà¸²à¸™à¹‚ดย {n}" agreeTo: "ฉันยà¸à¸¡à¸£à¸±à¸šà¸—ี่จะ {0}" +agree: "ยà¸à¸¡à¸£à¸±à¸š" agreeBelow: "ฉันยà¸à¸¡à¸£à¸±à¸šà¸–ึงด้านล่าง" basicNotesBeforeCreateAccount: "หมายเหตุสำคัà¸" -tos: "ข้à¸à¸à¸³à¸«à¸™à¸”à¹à¸¥à¸°à¹€à¸‡à¸·à¹ˆà¸à¸™à¹„ข" +termsOfService: "เงื่à¸à¸™à¹„ขà¸à¸²à¸£à¹ƒà¸«à¹‰à¸šà¸£à¸´à¸à¸²à¸£" start: "เริ่มต้น​ใช้งาน​" home: "หน้าà¹à¸£à¸" remoteUserCaution: "เนื่à¸à¸‡à¸ˆà¸²à¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸‡à¸²à¸™à¸£à¸²à¸¢à¸™à¸µà¹‰à¸™à¸±à¹‰à¸™ มาจาà¸à¸à¸´à¸™à¸ªà¹à¸•à¸™à¸‹à¹Œà¸£à¸°à¸¢à¸°à¹„à¸à¸¥ ข้à¸à¸¡à¸¹à¸¥à¸—ี่à¹à¸ªà¸”งดังà¸à¸¥à¹ˆà¸²à¸§à¸™à¸±à¹‰à¸™à¸à¸²à¸ˆà¸ˆà¸°à¹„ม่สมบูรณ์à¸à¹‡à¹„ด้นะ" activity: "à¸à¸´à¸ˆà¸à¸£à¸£à¸¡" images: "รูปภาพ" +image: "รูปภาพ" birthday: "วันเà¸à¸´à¸”" yearsOld: "{age} ปี" registeredDate: "วันที่สมัครสมาชิà¸" @@ -467,12 +470,14 @@ enableAdvancedMfm: "เปิดใช้งาน MFM ขั้นสูง" enableAnimatedMfm: "เปิดà¸à¸²à¸£à¹ƒà¸Šà¹‰à¸‡à¸²à¸™ MFM ด้วยà¹à¸à¸™à¸´à¹€à¸¡à¸Šà¸±à¹ˆà¸™" doing: "à¸à¸³à¸¥à¸±à¸‡à¸›à¸£à¸°à¸¡à¸§à¸¥à¸œà¸¥......" category: "หมวดหมู่" -tags: "à¹à¸—็à¸" +tags: "นามà¹à¸à¸‡" docSource: "ที่มาขà¸à¸‡à¹€à¸à¸à¸ªà¸²à¸£à¸™à¸µà¹‰" createAccount: "สร้างบัà¸à¸Šà¸µ" existingAccount: "บัà¸à¸Šà¸µà¸—ี่มีà¸à¸¢à¸¹à¹ˆ" regenerate: "สร้างà¸à¸µà¸à¸„รั้ง" fontSize: "ขนาดตัวà¸à¸±à¸à¸©à¸£" +mediaListWithOneImageAppearance: "ความสูงขà¸à¸‡à¸¥à¸´à¸ªà¸•à¹Œà¸ªà¸·à¹ˆà¸à¸ˆà¸°à¸•à¹‰à¸à¸‡à¸¡à¸µà¸£à¸¹à¸›à¸ าพเดียวเท่านั้น" +limitTo: "จำà¸à¸±à¸”ไว้ที่ {x}" noFollowRequests: "คุณไม่มีคำขà¸à¸•à¸´à¸”ตามที่รà¸à¸”ำเนินà¸à¸²à¸£" openImageInNewTab: "เปิดรูปภาพในà¹à¸—็บใหม่" dashboard: "หน้าà¸à¸£à¸°à¸”านหลัà¸" @@ -671,7 +676,7 @@ notesCount: "จำนวนขà¸à¸‡à¹‚น้ต" repliesCount: "จำนวนà¸à¸²à¸£à¸•à¸à¸šà¸à¸¥à¸±à¸šà¸—ี่ส่ง" renotesCount: "จำนวนรีโน้ตที่ส่ง" repliedCount: "จำนวนขà¸à¸‡à¸à¸²à¸£à¸•à¸à¸šà¸à¸¥à¸±à¸šà¸—ี่ได้รับ" -renotedCount: "จำนวนรีโน้ตที่ได้รับ" +renotedCount: "จำนวนรีโน้ตที่ได้รับà¹à¸¥à¹‰à¸§" followingCount: "จำนวนบัà¸à¸Šà¸µà¸—ี่ติดตาม" followersCount: "จำนวนผู้ติดตาม" sentReactionsCount: "จำนวนปà¸à¸´à¸à¸´à¸£à¸´à¸¢à¸²à¸—ี่ส่ง" @@ -698,6 +703,8 @@ contact: "ติดต่à¸" useSystemFont: "ใช้ฟà¸à¸™à¸•à¹Œà¹€à¸£à¸´à¹ˆà¸¡à¸•à¹‰à¸™à¸‚à¸à¸‡à¸£à¸°à¸šà¸š" clips: "คลิป" experimentalFeatures: "ฟังà¸à¹Œà¸Šà¸±à¹ˆà¸™à¸—ดสà¸à¸š" +experimental: "ทดลà¸à¸‡" +thisIsExperimentalFeature: "นี่คืà¸à¸Ÿà¸µà¹€à¸ˆà¸à¸£à¹Œà¸—ดลà¸à¸‡à¸™à¸°à¸„่ะ ฟังà¸à¹Œà¸Šà¸±à¸™à¸à¸²à¸£à¸—ำงานบางà¸à¸¢à¹ˆà¸²à¸‡à¸à¸²à¸ˆà¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¹„ด้ à¹à¸¥à¸°à¸à¸²à¸ˆà¹„ม่ทำงานหรืà¸à¹„ม่เสถียรตามที่ตั้งใจไว้นะ" developer: "สำหรับนัà¸à¸žà¸±à¸’นา" makeExplorable: "ทำให้บัà¸à¸Šà¸µà¸¡à¸à¸‡à¹€à¸«à¹‡à¸™à¹ƒà¸™ \"สำรวจ\"" makeExplorableDescription: "ถ้าหาà¸à¸„ุณปิดà¸à¸²à¸£à¸—ำงานนี้ บัà¸à¸Šà¸µà¸‚à¸à¸‡à¸„ุณนั้นจะไม่à¹à¸ªà¸”งในส่วน \"สำรวจ\" นะ" @@ -904,6 +911,7 @@ remoteOnly: "รีโมทเท่านั้น" failedToUpload: "à¸à¸²à¸£à¸à¸±à¸›à¹‚หลดล้มเหลว" cannotUploadBecauseInappropriate: "ไม่สามารถà¸à¸±à¸›à¹‚หลดไฟล์นี้ได้เนื่à¸à¸‡à¸ˆà¸²à¸à¸£à¸°à¸šà¸šà¸•à¸£à¸§à¸ˆà¸žà¸šà¸šà¸²à¸‡à¸ªà¹ˆà¸§à¸™à¸‚à¸à¸‡à¹„ฟล์ว่านี้à¸à¸²à¸ˆà¸ˆà¸°à¹€à¸›à¹‡à¸™ NSFW" cannotUploadBecauseNoFreeSpace: "à¸à¸²à¸£à¸à¸±à¸›à¹‚หลดนั้นล้มเหลวเนื่à¸à¸‡à¸ˆà¸²à¸à¹„ม่มีความจุขà¸à¸‡à¹„ดรฟ์" +cannotUploadBecauseExceedsFileSizeLimit: "ไม่สามารถà¸à¸±à¸›à¹‚หลดไฟล์นี้ได้à¹à¸¥à¹‰à¸§à¹€à¸™à¸·à¹ˆà¸à¸‡à¸ˆà¸²à¸à¹€à¸à¸´à¸™à¸‚ีดจำà¸à¸±à¸”ขà¸à¸‡à¸‚นาดไฟล์à¹à¸¥à¹‰à¸§" beta: "เบต้า" enableAutoSensitive: "ทำเครื่à¸à¸‡à¸«à¸¡à¸²à¸¢ NSFW à¸à¸±à¸•à¹‚นมัติ" enableAutoSensitiveDescription: "à¸à¸™à¸¸à¸à¸²à¸•à¹ƒà¸«à¹‰à¸•à¸£à¸§à¸ˆà¸«à¸²à¹à¸¥à¸°à¸—ำเครื่à¸à¸‡à¸«à¸¡à¸²à¸¢à¸ªà¸·à¹ˆà¸ NSFW โดยà¸à¸±à¸•à¹‚นมัติผ่านà¸à¸²à¸£à¹€à¸£à¸µà¸¢à¸™à¸£à¸¹à¹‰à¸‚à¸à¸‡à¹€à¸„รื่à¸à¸‡à¸«à¸²à¸à¹€à¸›à¹‡à¸™à¹„ปได้ à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸•à¸±à¸§à¹€à¸¥à¸·à¸à¸à¸™à¸µà¹‰à¸ˆà¸°à¸–ูà¸à¸›à¸´à¸”ใช้งาน à¹à¸•à¹ˆà¸à¹‡à¸ªà¸²à¸¡à¸²à¸£à¸–เปิดใช้งานได้ทั้งà¸à¸´à¸™à¸ªà¹à¸•à¸™à¸‹à¹Œà¸™à¸µà¹‰" @@ -937,6 +945,7 @@ didYouLikeMisskey: "คุณเคยชà¸à¸š Misskey ไหม?" pleaseDonate: "{host} ใช้ซà¸à¸Ÿà¸•à¹Œà¹à¸§à¸£à¹Œà¸Ÿà¸£à¸µ Misskey เราขà¸à¸‚à¸à¸šà¸„ุณà¸à¸²à¸£à¸šà¸£à¸´à¸ˆà¸²à¸„ขà¸à¸‡à¸„ุณà¸à¸¢à¹ˆà¸²à¸‡à¸ªà¸¹à¸‡à¹€à¸žà¸·à¹ˆà¸à¹ƒà¸«à¹‰à¸à¸²à¸£à¸žà¸±à¸’นา Misskey สามารถดำเนินต่à¸à¹„ปได้นะ!" roles: "บทบาท" role: "บทบาท" +noRole: "ไม่พบบทบาท" normalUser: "ผู้ใช้มาตรà¸à¸²à¸™" undefined: "ไม่ได้à¸à¸³à¸«à¸™à¸”" assign: "à¸à¸³à¸«à¸™à¸”" @@ -946,6 +955,10 @@ manageCustomEmojis: "จัดà¸à¸²à¸£à¸à¸µà¹‚มจิà¹à¸šà¸šà¸à¸³à¸«à¸™ youCannotCreateAnymore: "คุณถึงขีดจà¹à¸²à¸à¸±à¸”à¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¹à¸¥à¹‰à¸§à¸™à¸°" cannotPerformTemporary: "ไม่สามารถใช้à¸à¸²à¸£à¹„ด้ชั่วคราว" cannotPerformTemporaryDescription: "à¸à¸²à¸£à¸”à¹à¸²à¹€à¸™à¸´à¸™à¸à¸²à¸£à¸™à¸µà¹‰à¹„ม่สามารถดà¹à¸²à¹€à¸™à¸´à¸™à¸à¸²à¸£à¹„ด้ชั่วคราว เนื่à¸à¸‡à¸ˆà¸²à¸à¹€à¸à¸´à¸™à¸‚ีดจà¹à¸²à¸à¸±à¸”à¸à¸²à¸£à¸”à¹à¸²à¹€à¸™à¸´à¸™à¸à¸²à¸£ à¸à¸£à¸¸à¸“ารà¸à¸ªà¸±à¸à¸„รู่à¹à¸¥à¹‰à¸§à¸¥à¸à¸‡à¹ƒà¸«à¸¡à¹ˆà¸à¸µà¸à¸„รั้งนะค่ะ" +invalidParamError: "ข้à¸à¸œà¸´à¸”พลาดพารามิเตà¸à¸£à¹Œ" +invalidParamErrorDescription: "คำขà¸à¸žà¸²à¸£à¸²à¸¡à¸´à¹€à¸•à¸à¸£à¹Œà¹„ม่ถูà¸à¸•à¹‰à¸à¸‡ สิ่งนี้มัà¸à¸ˆà¸°à¹€à¸à¸´à¸”จาà¸à¸‚้à¸à¸œà¸´à¸”พลาด à¹à¸•à¹ˆà¸à¸²à¸ˆà¹€à¸à¸´à¸”จาà¸à¸à¸´à¸™à¸žà¸¸à¸•à¹€à¸à¸´à¸™à¸‚ีดจำà¸à¸±à¸”ขà¸à¸‡à¸‚นาดหรืà¸à¸—ี่คล้ายà¸à¸±à¸™" +permissionDeniedError: "à¸à¸²à¸£à¸”ำเนินถูà¸à¸›à¸à¸´à¹€à¸ªà¸˜" +permissionDeniedErrorDescription: "บัà¸à¸Šà¸µà¸™à¸µà¹‰à¹„ม่มีสิทธิ์à¸à¸™à¸¸à¸à¸²à¸•à¹ƒà¸™à¸à¸²à¸£à¸”ำเนินà¸à¸²à¸£à¸™à¸µà¹‰" preset: "พรีเซ็ต" selectFromPresets: "เลืà¸à¸à¸ˆà¸²à¸à¸à¸²à¸£à¸žà¸£à¸µà¹€à¸‹à¹‡à¸•" achievements: "ความสำเร็จ" @@ -989,17 +1002,52 @@ enableChartsForFederatedInstances: "สร้างà¹à¸œà¸™à¸ ูมิข้ showClipButtonInNoteFooter: "เพิ่ม \"คลิป\" เพื่à¸à¸šà¸±à¸™à¸—ึà¸à¹€à¸¡à¸™à¸¹à¸à¸²à¸£à¸—ำงาน" largeNoteReactions: "ขยายรีà¹à¸à¸„ชั่นà¸à¸²à¸£à¹à¸ªà¸”งผล" noteIdOrUrl: "โน้ต ID หรืภURL" +video: "วีดีโà¸" +videos: "วีดีโà¸" +dataSaver: "ประหยัดข้à¸à¸¡à¸¹à¸¥" accountMigration: "à¸à¸²à¸£à¹‚ยà¸à¸¢à¹‰à¸²à¸¢à¸šà¸±à¸à¸Šà¸µ" accountMoved: "ผู้ใช้รายนี้ได้ย้ายไปยังบัà¸à¸Šà¸µà¹ƒà¸«à¸¡à¹ˆà¹à¸¥à¹‰à¸§:" +accountMovedShort: "บัà¸à¸Šà¸µà¸™à¸µà¹‰à¸–ูà¸à¹‚à¸à¸™à¸¢à¹‰à¸²à¸¢à¹„ปà¹à¸¥à¹‰à¸§à¸„่ะ" +operationForbidden: "ห้ามดำเนินà¸à¸²à¸£" forceShowAds: "à¹à¸ªà¸”งโฆษณาเสมà¸" +addMemo: "เพิ่มมีโม" +editMemo: "à¹à¸à¹‰à¹„ขมีโม" +reactionsList: "ปà¸à¸´à¸à¸´à¸£à¸´à¸¢à¸²" +renotesList: "Renotes รีโน้ต" +notificationDisplay: "à¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸à¸™" +leftTop: "บนซ้าย" +rightTop: "บนขวา" +leftBottom: "ล่างซ้าย" +rightBottom: "ล่างขวา" +stackAxis: "ทิศทางà¸à¸²à¸£à¸‹à¹‰à¸à¸™" +vertical: "à¹à¸™à¸§à¸•à¸±à¹‰à¸‡" +horizontal: "ด้านข้าง" +position: "ตำà¹à¸«à¸™à¹ˆà¸‡" +serverRules: "à¸à¸Žà¸‚à¸à¸‡à¹€à¸‹à¸´à¸Ÿà¹€à¸§à¸à¸£à¹Œ" +pleaseConfirmBelowBeforeSignup: "โปรดยืนยันด้านล่างà¸à¹ˆà¸à¸™à¸à¸³à¸¥à¸±à¸‡à¸¥à¸‡à¸Šà¸·à¹ˆà¸à¸ªà¸¡à¸±à¸„รนะค่ะ" +pleaseAgreeAllToContinue: "คุณต้à¸à¸‡à¸¢à¸à¸¡à¸£à¸±à¸šà¸—ุà¸à¸Šà¹ˆà¸à¸‡à¸•à¸£à¸‡à¸”้านบนเพื่à¸à¸”ำเนินà¸à¸²à¸£à¸•à¹ˆà¸à¸„่ะ" +continue: "ดำเนินà¸à¸²à¸£à¸•à¹ˆà¸" +preservedUsernames: "ชื่à¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸—ี่สงวนไว้" +preservedUsernamesDescription: "ลิสต์ชื่à¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸—ี่จะสำรà¸à¸‡à¹‚ดยคั่นด้วยà¸à¸²à¸£à¹à¸šà¹ˆà¸‡à¸šà¸£à¸£à¸—ัดนั้น เพราะสิ่งเหล่านี้จะไม่สามารถทำได้ในระหว่างà¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¸šà¸±à¸à¸Šà¸µà¸•à¸²à¸¡à¸›à¸à¸•à¸´ บัà¸à¸Šà¸µà¸—ี่มีà¸à¸¢à¸¹à¹ˆà¹à¸¥à¹‰à¸§à¸™à¸±à¹‰à¸™à¹‚ดยใช้ชื่à¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¹€à¸«à¸¥à¹ˆà¸²à¸™à¸µà¹‰à¸ˆà¸°à¹„ม่ได้รับผลà¸à¸£à¸°à¸—บà¸à¸°à¹„ร" +createNoteFromTheFile: "เรียบเรียงโน้ตจาà¸à¹„ฟล์นี้" +youFollowing: "ติดตามà¹à¸¥à¹‰à¸§" +_serverRules: + description: "ชุดขà¸à¸‡à¸à¸Žà¸—ี่จะà¹à¸ªà¸”งà¸à¹ˆà¸à¸™à¸à¸²à¸£à¸¥à¸‡à¸—ะเบียนเราขà¸à¹à¸™à¸°à¸™à¸³à¹ƒà¸«à¹‰à¸•à¸±à¹‰à¸‡à¸„่าสรุปข้à¸à¸à¸³à¸«à¸™à¸”ในà¸à¸²à¸£à¹ƒà¸«à¹‰à¸šà¸£à¸´à¸à¸²à¸£" _accountMigration: - moveTo: "ย้ายข้à¸à¸¡à¸¹à¸¥à¸šà¸±à¸à¸Šà¸µà¸™à¸µà¹‰à¹„ปยังบัà¸à¸Šà¸µà¸à¸µà¸à¸«à¸™à¸¶à¹ˆà¸‡" - moveToLabel: "บัà¸à¸Šà¸µà¸—ี่จะย้ายไปที่:" - moveAccountDescription: "à¸à¸²à¸£à¸à¸£à¸°à¸—ำนี้ไม่สามารถย้à¸à¸™à¸à¸¥à¸±à¸šà¹„ด้นะ ขั้นตà¸à¸™à¹à¸£à¸ ต้à¸à¸‡à¸ªà¸£à¹‰à¸²à¸‡à¸™à¸²à¸¡à¹à¸à¸‡à¸ªà¸³à¸«à¸£à¸±à¸šà¸šà¸±à¸à¸Šà¸µà¸™à¸µà¹‰à¹ƒà¸™à¸šà¸±à¸à¸Šà¸µà¸—ี่คุณต้à¸à¸‡à¸à¸²à¸£à¸¢à¹‰à¸²à¸¢à¹„ป หลังจาà¸à¸™à¸±à¹‰à¸™à¹à¸¥à¹‰à¸§ ป้à¸à¸™à¸šà¸±à¸à¸Šà¸µà¸—ี่จะย้ายไปในรูปà¹à¸šà¸šà¸”ังต่à¸à¹„ปนี้: @person@instance.com" moveFrom: "ย้ายข้à¸à¸¡à¸¹à¸¥à¸šà¸±à¸à¸Šà¸µà¸à¸·à¹ˆà¸™à¹„ปยังà¸à¸µà¸à¸šà¸±à¸à¸Šà¸µà¸™à¸µà¹‰à¸«à¸™à¸¶à¹ˆà¸‡" + moveFromSub: "สร้างนามà¹à¸à¸‡à¹„ปยังบัà¸à¸Šà¸µà¸à¸·à¹ˆà¸™" moveFromLabel: "บัà¸à¸Šà¸µà¸—ี่จะย้ายจาà¸:" moveFromDescription: "สร้างนามà¹à¸à¸‡à¸ªà¸³à¸«à¸£à¸±à¸šà¸šà¸±à¸à¸Šà¸µà¸—ี่จะย้ายจาà¸à¸šà¸±à¸à¸Šà¸µà¸™à¸µà¹‰ ถ้าหาà¸à¸„ุณต้à¸à¸‡à¸à¸²à¸£à¹‚à¸à¸™à¸œà¸¹à¹‰à¸•à¸´à¸”ตาม สิ่งนี้ต้à¸à¸‡à¸—ำà¸à¹ˆà¸à¸™à¹‚à¸à¸™à¸à¹ˆà¸à¸™à¸™à¸°à¸„่ะ! หลังจาà¸à¸™à¸±à¹‰à¸™ ป้à¸à¸™à¸šà¸±à¸à¸Šà¸µà¸—ี่จะย้ายไปในรูปà¹à¸šà¸šà¸•à¹ˆà¸à¹„ปนี้: @person@instance.com" + moveTo: "ย้ายข้à¸à¸¡à¸¹à¸¥à¸šà¸±à¸à¸Šà¸µà¸™à¸µà¹‰à¹„ปยังบัà¸à¸Šà¸µà¸à¸µà¸à¸«à¸™à¸¶à¹ˆà¸‡" + moveToLabel: "บัà¸à¸Šà¸µà¸—ี่จะย้ายไปที่:" + moveCannotBeUndone: "ไม่สามารถยà¸à¹€à¸¥à¸´à¸à¸à¸²à¸£à¹‚à¸à¸™à¸¢à¹‰à¸²à¸¢à¸šà¸±à¸à¸Šà¸µà¹„ด้" + moveAccountDescription: "à¸à¸²à¸£à¸à¸£à¸°à¸—ำนี้ไม่สามารถย้à¸à¸™à¸à¸¥à¸±à¸šà¹„ด้นะ ขั้นตà¸à¸™à¹à¸£à¸ ต้à¸à¸‡à¸ªà¸£à¹‰à¸²à¸‡à¸™à¸²à¸¡à¹à¸à¸‡à¸ªà¸³à¸«à¸£à¸±à¸šà¸šà¸±à¸à¸Šà¸µà¸™à¸µà¹‰à¹ƒà¸™à¸šà¸±à¸à¸Šà¸µà¸—ี่คุณต้à¸à¸‡à¸à¸²à¸£à¸¢à¹‰à¸²à¸¢à¹„ป หลังจาà¸à¸™à¸±à¹‰à¸™à¹à¸¥à¹‰à¸§ ป้à¸à¸™à¸šà¸±à¸à¸Šà¸µà¸—ี่จะย้ายไปในรูปà¹à¸šà¸šà¸”ังต่à¸à¹„ปนี้: @person@instance.com" + moveAccountHowTo: "หาà¸à¸•à¹‰à¸à¸‡à¸à¸²à¸£à¸¢à¹‰à¸²à¸¢à¸‚้à¸à¸¡à¸¹à¸¥à¸à¹ˆà¸à¸™à¸à¸·à¹ˆà¸™à¹ƒà¸«à¹‰à¸ªà¸£à¹‰à¸²à¸‡à¸Šà¸·à¹ˆà¸à¹à¸—นสำหรับบัà¸à¸Šà¸µà¸™à¸µà¹‰ ในบัà¸à¸Šà¸µà¸—ี่จะต้à¸à¸‡à¸à¸²à¸£à¸¢à¹‰à¸²à¸¢à¹„ป\nหลังจาà¸à¸—ี่คุณสร้างนามà¹à¸à¸‡à¸™à¸±à¹‰à¸™à¹à¸¥à¹‰à¸§ ให้ป้à¸à¸™à¸šà¸±à¸à¸Šà¸µà¸—ี่ต้à¸à¸‡à¸à¸²à¸£à¸ˆà¸°à¸¢à¹‰à¸²à¸¢à¹„ปในรูปà¹à¸šà¸šà¸”ังต่à¸à¹„ปนี้: @username@server.example.com" + startMigration: "โà¸à¸™à¸¢à¹‰à¸²à¸¢" migrationConfirm: "ย้ายข้à¸à¸¡à¸¹à¸¥à¸šà¸±à¸à¸Šà¸µà¸™à¸µà¹‰à¹„ปที่ {account} จริงๆนะ เมื่à¸à¸¡à¸µà¸à¸²à¸£à¹€à¸£à¸´à¹ˆà¸¡à¸•à¹‰à¸™à¹à¸¥à¹‰à¸§ à¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸™à¸µà¹‰à¸ˆà¸°à¹„ม่สามารถหยุดหรืà¸à¸™à¸³à¸à¸¥à¸±à¸šà¸„ืนมาได้ à¹à¸¥à¸°à¸„ุณจะไม่สามารถใช้บัà¸à¸Šà¸µà¸™à¸µà¹‰à¹ƒà¸™à¸ªà¸–านะดั้งเดิมได้à¸à¸µà¸à¸•à¹ˆà¸à¹„ป\n\nนà¸à¸à¸ˆà¸²à¸à¸™à¸µà¹‰ เพื่à¸à¹ƒà¸«à¹‰à¹à¸™à¹ˆà¹ƒà¸ˆà¸¢à¸·à¸™à¸¢à¸±à¸™à¸§à¹ˆà¸²à¸„ุณได้สร้างนามà¹à¸à¸‡à¹ƒà¸™à¸šà¸±à¸à¸Šà¸µà¸—ี่จะย้ายข้à¸à¸¡à¸¹à¸¥à¸™à¸°à¸„่ะ" + movedAndCannotBeUndone: "\nบัà¸à¸Šà¸µà¸™à¸µà¹‰à¸–ูà¸à¹‚à¸à¸™à¸¢à¹‰à¸²à¸¢à¹„ปà¹à¸¥à¹‰à¸§\nไม่สามารถย้à¸à¸™à¸à¸¥à¸±à¸šà¹‚à¸à¸™à¸¢à¹‰à¸²à¸¢à¸‚้à¸à¸¡à¸¹à¸¥à¹„ด้" + postMigrationNote: "บัà¸à¸Šà¸µà¸™à¸µà¹‰à¸ˆà¸°à¸–ูà¸à¹€à¸¥à¸´à¸à¸•à¸´à¸”ตามบัà¸à¸Šà¸µà¸—ั้งหมดที่à¸à¸³à¸¥à¸±à¸‡à¸•à¸´à¸”ตามภายใน 24 ชั่วโมงหลังจาà¸à¸à¸²à¸£à¸¢à¹‰à¸²à¸¢à¸‚้à¸à¸¡à¸¹à¸¥à¸™à¸±à¹‰à¸™à¹€à¸ªà¸£à¹‡à¸ˆà¸ªà¸´à¹‰à¸™ ทั้งจำนวนผู้ติดตามà¹à¸¥à¸°à¸œà¸¹à¹‰à¸•à¸´à¸”ตามนั้นจะà¸à¸¥à¸²à¸¢à¹€à¸›à¹‡à¸™à¸¨à¸¹à¸™à¸¢à¹Œ เพื่à¸à¸«à¸¥à¸µà¸à¹€à¸¥à¸µà¹ˆà¸¢à¸‡à¸›à¹‰à¸à¸‡à¸à¸±à¸™à¹„ม่ให้ผู้ติดตามขà¸à¸‡à¸„ุณนั้นไม่สามารถเห็นโพสต์เฉพาะผู้ติดตามขà¸à¸‡à¸šà¸±à¸à¸Šà¸µà¸™à¸µà¹‰à¹„ด้ à¹à¸•à¹ˆà¸à¸¢à¹ˆà¸²à¸‡à¹„รà¸à¹‡à¸•à¸²à¸¡à¹à¸¥à¹‰à¸§à¸žà¸§à¸à¹€à¸‚าจะยังคงติดตามบัà¸à¸Šà¸µà¸™à¸µà¹‰à¸•à¹ˆà¸à¹„ป" + movedTo: "บัà¸à¸Šà¸µà¸—ี่จะย้ายไปที่:" _achievements: earnedAt: "ได้รับเมื่à¸" _types: @@ -1171,6 +1219,9 @@ _achievements: _client30min: title: "พัà¸à¸œà¹ˆà¸à¸™à¸ªà¸±à¸à¸«à¸™à¹ˆà¸à¸¢" description: "ใช้เวลา 30 นาทีบน Misskey" + _client60min: + title: "ไม่มี \"Miss\" ใน Misskey นะค่ะ !" + description: "เปิด Misskey ค้างไว้à¹à¸¥à¹‰à¸§à¸à¸¢à¹ˆà¸²à¸‡à¸™à¹‰à¸à¸¢ 60 นาที" _noteDeletedWithin1min: title: "ไม่เป็นไร" description: "ลบโน้ตภายในหนึ่งนาทีหลังจาà¸à¸—ี่โพสต์" @@ -1259,6 +1310,8 @@ _role: iconUrl: "ไà¸à¸„à¸à¸™ URL" asBadge: "à¹à¸ªà¸”งเป็นตรา" descriptionOfAsBadge: "ไà¸à¸„à¸à¸™à¸‚à¸à¸‡à¸šà¸—บาทนี้จะปราà¸à¸à¸–ัดจาà¸à¸Šà¸·à¹ˆà¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸‚à¸à¸‡à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸‡à¸²à¸™à¸”้วยบทบาทนี้ถ้าหาà¸à¹€à¸›à¸´à¸”ใช้งาน" + isExplorable: "บทบาทไทม์ไลน์เป็นà¹à¸šà¸šà¸ªà¸²à¸˜à¸²à¸£à¸“ะ" + descriptionOfIsExplorable: "ไทม์ไลน์ขà¸à¸‡à¸šà¸—บาทนี้จะสามารถเข้าถึงได้à¹à¸šà¸šà¸ªà¸²à¸˜à¸²à¸£à¸“ะถ้าหาà¸à¹€à¸›à¸´à¸”ใช้งาน เส้นเวลาขà¸à¸‡à¸šà¸—บาทนั้นจะไม่ถูà¸à¹€à¸›à¸´à¸”เผยต่à¸à¸ªà¸²à¸˜à¸²à¸£à¸“ะ ถึงà¹à¸¡à¹‰à¸§à¹ˆà¸²à¸ˆà¸°à¹„ม่เปิดเผยต่à¸à¸ªà¸²à¸˜à¸²à¸£à¸“ะà¹à¸¡à¹‰à¹à¸•à¹ˆà¸§à¹ˆà¸²...จะตั้งค่าไว้ยังไงà¸à¹‡à¸•à¸²à¸¡" displayOrder: "ตำà¹à¸«à¸™à¹ˆà¸‡" descriptionOfDisplayOrder: "ยิ่งตัวเลขสูง ตำà¹à¸«à¸™à¹ˆà¸‡ UI à¸à¹‡à¸¢à¸´à¹ˆà¸‡à¸ªà¸¹à¸‡à¸‚ึ้นนะ" canEditMembersByModerator: "à¸à¸™à¸¸à¸à¸²à¸•à¹ƒà¸«à¹‰à¸œà¸¹à¹‰à¸”ูà¹à¸¥à¹à¸à¹‰à¹„ขสมาชิà¸" @@ -1407,6 +1460,8 @@ _channel: following: "ติดตามà¹à¸¥à¹‰à¸§" usersCount: "{n} ผู้เข้าร่วม" notesCount: "{n} โน้ต" + nameAndDescription: "ชื่à¸à¹à¸¥à¸°à¸„ำà¸à¸˜à¸´à¸šà¸²à¸¢" + nameOnly: "ชื่à¸à¹€à¸—่านั้น" _menuDisplay: sideFull: "ด้านข้าง" sideIcon: "ด้านข้าง (ไà¸à¸„à¸à¸™)" @@ -1524,32 +1579,6 @@ _time: minute: "นาที" hour: "ชั่วโมง" day: "วัน" -_tutorial: - title: "วิธีà¸à¸²à¸£à¹ƒà¸Šà¹‰à¸‡à¸²à¸™ Misskey" - step1_1: "ยินดีต้à¸à¸™à¸£à¸±à¸šà¸„่ะ!" - step1_2: "หน้านี้เรียà¸à¸§à¹ˆà¸² \"ไทม์ไลน์\" มันจะà¹à¸ªà¸”ง \"โน้ตย่à¸\" ที่เรียงลำดับตามลำดับเวลาขà¸à¸‡à¸„นที่คุณ \"ติดตาม\"" - step1_3: "ไทม์ไลน์ขà¸à¸‡à¸„ุณนั้นว่างเปล่า เนื่à¸à¸‡à¸ˆà¸²à¸à¸„ุณยังไม่ได้โพสต์โน้ตย่à¸à¸«à¸£à¸·à¸à¹„ม่ได้ติดตามใครเลย" - step2_1: "มาตั้งค่าโปรไฟล์ขà¸à¸‡à¸„ุณให้เสร็จà¸à¹ˆà¸à¸™à¹€à¸‚ียนโน้ตย่à¸à¸«à¸£à¸·à¸à¸•à¸´à¸”ตามใครà¸à¹‡à¹„ด้" - step2_2: "à¸à¸²à¸£à¹ƒà¸«à¹‰à¸‚้à¸à¸¡à¸¹à¸¥à¸šà¸²à¸‡à¸à¸¢à¹ˆà¸²à¸‡à¹€à¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸•à¸±à¸§à¸„ุณนั้น จะทำให้ผู้à¸à¸·à¹ˆà¸™à¸—ราบว่าต้à¸à¸‡à¸à¸²à¸£à¸”ูโน้ตย่à¸à¸‚à¸à¸‡à¸„ุณหรืà¸à¸•à¸´à¸”ตามคุณได้ง่ายขึ้น" - step3_1: "ตั้งค่าโปรไฟล์ขà¸à¸‡à¸„ุณเสร็จà¹à¸¥à¹‰à¸§?" - step3_2: "จาà¸à¸™à¸±à¹‰à¸™à¸¥à¸à¸‡à¹‚พสต์โน้ตà¸à¸±à¸™à¸•à¹ˆà¸à¹„ป คุณสามารถทำได้โดยà¸à¸”ปุ่มที่มีไà¸à¸„à¸à¸™à¸”ินสà¸à¸šà¸™à¸«à¸™à¹‰à¸²à¸ˆà¸à¸™à¸°" - step3_3: "à¸à¸£à¸à¸à¹‚มดà¸à¸¥à¹à¸¥à¹‰à¸§à¸à¸”ปุ่มด้านบนขวาเพื่à¸à¹‚พสต์" - step3_4: "ไม่มีà¸à¸°à¹„รจะพูดงั้นหรภลà¸à¸‡ \"เพียงà¹à¸„่ตั้งค่าว่า Misskey ขà¸à¸‡à¸‰à¸±à¸™\"!" - step4_1: "เสร็จสิ้นà¸à¸²à¸£à¹‚พสต์โน้ตย่à¸à¹à¸£à¸à¸‚à¸à¸‡à¸„ุณà¹à¸¥à¹‰à¸§à¸à¸¢à¹ˆà¸²à¸‡à¸‡à¸±à¹‰à¸™à¸«à¸£à¸?" - step4_2: "ไชโย! ตà¸à¸™à¸™à¸µà¹‰à¹‚น้ตย่à¸à¹à¸£à¸à¸‚à¸à¸‡à¸„ุณได้ปราà¸à¸à¸šà¸™à¹„ทม์ไลน์ขà¸à¸‡à¸„ุณà¹à¸¥à¹‰à¸§à¸™à¸°" - step5_1: "ตà¸à¸™à¸™à¸µà¹‰ มาลà¸à¸‡à¸—ำไทม์ไลน์เพิ่มเติมขà¸à¸‡à¸„ุณให้ดูมีชีวิตชีวามาà¸à¸‚ึ้นโดยà¸à¸²à¸£à¸•à¸´à¸”ตามคนà¸à¸·à¹ˆà¸™" - step5_2: "{featured} จะà¹à¸ªà¸”งโน้ตยà¸à¸”นิยมให้คุณเห็นในà¸à¸£à¸“ีนี้ {explore} จะช่วยให้คุณค้นหาผู้ใช้ยà¸à¸”นิยมได้ ลà¸à¸‡à¸«à¸²à¸„นที่คุณต้à¸à¸‡à¸à¸²à¸£à¸•à¸´à¸”ตามที่นั่นสิ!" - step5_3: "หาà¸à¸•à¹‰à¸à¸‡à¸à¸²à¸£à¸•à¸´à¸”ตามผู้ใช้รายà¸à¸·à¹ˆà¸™ ให้คลิà¸à¸—ี่ไà¸à¸„à¸à¸™à¹à¸¥à¸°à¸à¸”ปุ่ม \"ติดตาม\" บนโปรไฟล์ขà¸à¸‡à¸žà¸§à¸à¹€à¸‚าได้เลยจ้า" - step5_4: "หาà¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸£à¸²à¸¢à¸à¸·à¹ˆà¸™à¸¡à¸µà¹„à¸à¸„à¸à¸™à¹à¸¡à¹ˆà¸à¸¸à¸à¹à¸ˆà¸—ี่à¸à¸¢à¸¹à¹ˆà¸‚้างชื่ภà¸à¸²à¸ˆà¸•à¹‰à¸à¸‡à¹ƒà¸Šà¹‰à¹€à¸§à¸¥à¸²à¸ªà¸±à¸à¸£à¸°à¸¢à¸°à¸à¸§à¹ˆà¸²à¸—ี่ผู้ใช้รายนั้นจะà¸à¸™à¸¸à¸¡à¸±à¸•à¸´à¸„ำขà¸à¸•à¸´à¸”ตามขà¸à¸‡à¸„ุณ" - step6_1: "คุณสามารถเห็นโน้ตย่à¸à¸‚à¸à¸‡à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸£à¸²à¸¢à¸à¸·à¹ˆà¸™à¸šà¸™à¹„ทม์ไลน์ขà¸à¸‡à¸„ุณได้à¹à¸¥à¹‰à¸§à¸•à¸à¸™à¸™à¸µà¹‰" - step6_2: "คุณยังสามารถใส่ \"ปà¸à¸´à¸à¸´à¸£à¸´à¸¢à¸²\" ลงในโน้ตขà¸à¸‡à¸„นà¸à¸·à¹ˆà¸™à¹€à¸žà¸·à¹ˆà¸à¸•à¸à¸šà¸à¸¥à¸±à¸šà¹„ด้à¸à¸¢à¹ˆà¸²à¸‡à¸£à¸§à¸”เร็ว" - step6_3: "หาà¸à¸•à¹‰à¸à¸‡à¸à¸²à¸£à¹à¸™à¸š \"ปà¸à¸´à¸à¸´à¸£à¸´à¸¢à¸²\" ให้à¸à¸”เครื่à¸à¸‡à¸«à¸¡à¸²à¸¢ \"+\" ในโน้ตขà¸à¸‡à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸£à¸²à¸¢à¸à¸·à¹ˆà¸™à¹à¸¥à¹‰à¸§à¹€à¸¥à¸·à¸à¸à¸à¸µà¹‚มจิที่คุณต้à¸à¸‡à¸à¸²à¸£à¹‚ต้ตà¸à¸šà¸”้วย" - step7_1: "ยินดีด้วยนะ! คุณได้เสร็จสิ้นà¸à¸²à¸£à¸à¸§à¸”วิชาพื้นà¸à¸²à¸™à¸‚à¸à¸‡ Misskey à¹à¸¥à¹‰à¸§" - step7_2: "ถ้าหาà¸à¸„ุณต้à¸à¸‡à¸à¸²à¸£à¹€à¸£à¸µà¸¢à¸™à¸£à¸¹à¹‰à¹€à¸žà¸´à¹ˆà¸¡à¹€à¸•à¸´à¸¡à¹€à¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸š Misskey ให้ลà¸à¸‡à¹ƒà¸Šà¹‰à¸ªà¹ˆà¸§à¸™ {help}" - step7_3: "ตà¸à¸™à¸™à¸µà¹‰ ถ้าà¸à¸¢à¹ˆà¸²à¸‡à¸™à¸±à¹‰à¸™à¸à¹‡à¸‚à¸à¹ƒà¸«à¹‰à¹‚ชคดีà¹à¸¥à¸°à¸ªà¸™à¸¸à¸à¸à¸±à¸š Misskey! 🚀" - step8_1: "สุดท้ายนี้นายต้à¸à¸‡à¸à¸²à¸£à¹€à¸›à¸´à¸”ใช้งานà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸à¸™à¹à¸šà¸šà¸žà¸¸à¸Šà¸«à¸£à¸·à¸à¸›à¹ˆà¸²à¸§?" - step8_2: "à¸à¸²à¸£à¹€à¸›à¸´à¸”ใช้งานสิ่งเหล่านี้ จะช่วยให้คุณนั้นได้รับà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸à¸™à¸ªà¸³à¸«à¸£à¸±à¸šà¸à¸²à¸£à¸à¸¥à¹ˆà¸²à¸§à¸–ึง à¸à¸²à¸£à¹à¸ªà¸”งรีà¹à¸à¸„ชั่น à¸à¸²à¸£à¸•à¸´à¸”ตาม ฯลฯ เป็นต้น ถึงà¹à¸¡à¹‰à¸§à¹ˆà¸²à¸ˆà¸°à¹„ม่ได้เปิด Misskey à¸à¹‡à¸•à¸²à¸¡" - step8_3: "คุณสามารถเปลี่ยนà¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸„่านี้ในภายหลังได้ตลà¸à¸”เวลานะ" _2fa: alreadyRegistered: "คุณได้ลงทะเบียนà¸à¸¸à¸›à¸à¸£à¸“์ยืนยันตัวตนà¹à¸šà¸š 2 ชั้นà¹à¸¥à¹‰à¸§" registerTOTP: "ลงทะเบียนà¹à¸à¸žà¸•à¸±à¸§à¸•à¸£à¸§à¸ˆà¸ªà¸à¸šà¸ªà¸´à¸—ธิ์" @@ -1887,6 +1916,7 @@ _deck: channel: "à¹à¸Šà¸™à¹à¸™à¸¥" mentions: "พูดถึง" direct: "ไดเร็ค" + roleTimeline: "บทบาทไทม์ไลน์" _dialog: charactersExceeded: "คุณà¸à¸³à¸¥à¸±à¸‡à¸¡à¸µà¸•à¸±à¸§à¸à¸±à¸à¸‚ระเà¸à¸´à¸™à¸‚ีดจำà¸à¸±à¸”สูงสุดà¹à¸¥à¹‰à¸§à¸™à¸°! ปัจจุบันà¸à¸¢à¸¹à¹ˆà¸—ี่ {current} จาภ{max}" charactersBelow: "คุณà¸à¸³à¸¥à¸±à¸‡à¹ƒà¸Šà¹‰à¸à¸±à¸à¸‚ระต่ำà¸à¸§à¹ˆà¸²à¸‚ีดจำà¸à¸±à¸”ขั้นต่ำเลยนะ! ปัจจุบันà¸à¸¢à¸¹à¹ˆà¸—ี่ {current} จาภ{min}" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 895d2e1f12882f6c6a34087cdccb0f5127a40247..4f215bc9809e8c8432044f4c75cbe0bf96c3ee15 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -258,12 +258,12 @@ startMessaging: "Розпочати діалог" nUsersRead: "Прочитали {n}" agreeTo: "Я погоджуюÑÑŒ з {0}" agreeBelow: "Я погоджуюÑÑ Ð· наведеним нижче" -tos: "Умови викориÑтаннÑ" start: "Розпочати" home: "Домівка" remoteUserCaution: "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¼Ð¾Ð¶Ðµ бути неповною, оÑкільки це віддалений кориÑтувач." activity: "ÐктивніÑÑ‚ÑŒ" images: "ЗображеннÑ" +image: "ЗображеннÑ" birthday: "День народженнÑ" yearsOld: "{age} років" registeredDate: "ПриєднаннÑ" @@ -899,6 +899,8 @@ achievements: "ДоÑÑгненнÑ" joinThisServer: "ЗареєÑтруватиÑÑ Ð½Ð° цьому Ñервері" exploreOtherServers: "Знайти інший Ñервер" letsLookAtTimeline: "ПереглÑд Ñ–Ñторії" +horizontal: "Збоку" +youFollowing: "ПідпиÑки" _achievements: earnedAt: "Відкрито" _types: @@ -1330,31 +1332,6 @@ _time: minute: "Ñ…" hour: "г" day: "д" -_tutorial: - title: "Як кориÑтуватиÑÑŒ Misskey" - step1_1: "ЛаÑкаво проÑимо!" - step1_2: "Ð¦Ñ Ñторінка має назву \"Ñтрічка подій\". Ðа ній з'ÑвлÑÑŽÑ‚ÑŒÑÑ Ð·Ð°Ð¿Ð¸Ñи кориÑтувачів на Ñких ви підпиÑані." - step1_3: "Ðаразі ваша Ñтрічка порожнÑ, оÑкільки ви ще не напиÑали жодної нотатки Ñ– не підпиÑані на інших." - step2_1: "Перш ніж зробити Ð·Ð°Ð¿Ð¸Ñ Ð°Ð±Ð¾ підпиÑатиÑÑŒ на когоÑÑŒ, заповніть Ñвій профіль." - step2_2: "ÐÐ°Ð´Ð°Ð½Ð½Ñ Ð´ÐµÑкої інформації про Ñебе допоможе іншим кориÑтувачам вирішити підпиÑатиÑÑŒ на ваÑ." - step3_1: "Ви уÑпішно налаштували Ñвій обліковий запиÑ?" - step3_2: "ÐаÑтупним кроком Ñ” напиÑÐ°Ð½Ð½Ñ Ð½Ð¾Ñ‚Ð°Ñ‚ÐºÐ¸. Це можна зробити, натиÑнувши Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð¾Ð»Ñ–Ð²Ñ†Ñ Ð½Ð° екрані." - step3_3: "ПіÑÐ»Ñ Ð½Ð°Ð¿Ð¸ÑÐ°Ð½Ð½Ñ Ð²Ð¼Ñ–Ñту ви можете опублікувати його, натиÑнувши кнопку у верхньому правому куті форми." - step3_4: "Ðе знаєте що напиÑати? Спробуйте \"Привіт, Misskey!\"" - step4_1: "Ви розміÑтили Ñвій перший запиÑ?" - step4_2: "Ура! Ваш перший Ð·Ð°Ð¿Ð¸Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶Ð°Ñ”Ñ‚ÑŒÑÑ Ð½Ð° вашій Ñтрічці подій." - step5_1: "ÐаÑтав Ñ‡Ð°Ñ Ð¾Ð¶Ð¸Ð²Ð¸Ñ‚Ð¸ вашу Ñтрічку подій підпиÑавшиÑÑŒ на інших кориÑтувачів." - step5_2: "{explore} допоможе вам знайти цікавих людей та підпиÑатиÑÑ Ð½Ð° них." - step5_3: "Щоб підпиÑатиÑÑŒ на інших кориÑтувачів, нажміть на їхнє зображеннÑ, а потім на кнопку \"підпиÑатиÑÑŒ\"." - step5_4: "Якщо кориÑтувач має замок при імені, то йому потрібно буде вручну підтвердити вашу заÑвку на підпиÑку." - step6_1: "Тепер ви повинні бачити запиÑи інших кориÑтувачів на вашій Ñтрічці подій." - step6_2: "Також ви можете швидко відповіÑти, або \"відреагувати\" на запиÑи інших кориÑтувачів." - step6_3: "Щоб \"відреагувати\", нажміть на знак Ð¿Ð»ÑŽÑ \"+\" на запиÑÑ– Ñ– виберіть емоджі Ñким ви хочете \"відреагувати\"." - step7_1: "Вітаю! Ви пройшли Ð¾Ð·Ð½Ð°Ð¹Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· Misskey." - step7_2: "Якщо ви хочете більше дізнатиÑÑŒ про Misskey, зайдіть в розділ {help}." - step7_3: "ÐаÑолоджуйтеÑÑŒ Misskey! 🚀" - step8_1: "ÐаоÑтанку, чи бажаєте ви ввімкнути push-ÑповіщеннÑ?" - step8_3: "Ви завжди можете змінити цей параметр пізніше." _2fa: alreadyRegistered: "Двофакторна Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð²Ð¶Ðµ налаштована." step1: "Спершу вÑтановіть на Ñвій приÑтрій програму автентифікації (наприклад {a} або {b})." diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index ebd046b6d4dfda8802a4acf6c67df0cdabf0ff90..d620d99d806b2da5353d377e0eb40f7491458b2e 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -261,12 +261,12 @@ nUsersRead: "Ä‘á»c bởi {n}" agreeTo: "Tôi đồng ý {0}" agreeBelow: "Äồng ý vá»›i ná»™i dung dÆ°á»›i đây" basicNotesBeforeCreateAccount: "Những Ä‘iá»u cÆ¡ bản cần chú ý " -tos: "Äiá»u khoản dịch vụ" start: "Bắt đầu" home: "Trang chÃnh" remoteUserCaution: "Vì ngÆ°á»i dùng nà y ở máy chủ khác, thông tin hiển thị có thể không đầy đủ." activity: "Hoạt Ä‘á»™ng" images: "Hình ảnh" +image: "Hình ảnh" birthday: "Sinh nhật" yearsOld: "{age} tuổi" registeredDate: "Tham gia" @@ -944,6 +944,8 @@ copyErrorInfo: "Sao chép thông tin lá»—i" joinThisServer: "Äăng ký trên chủ máy nà y" exploreOtherServers: "Tìm chủ máy khác" letsLookAtTimeline: "Thá» xem Timeline" +horizontal: "Thanh bên" +youFollowing: "Äang theo dõi" _achievements: earnedAt: "Ngà y thu nháºn" _types: @@ -1349,31 +1351,6 @@ _time: minute: "phút" hour: "giá»" day: "ngà y" -_tutorial: - title: "Cách dùng Misskey" - step1_1: "Xin chà o!" - step1_2: "Trang nà y gá»i là \"bảng tin\". Nó hiện \"tút\" từ những ngÆ°á»i mà bạn \"theo dõi\" theo thứ tá»± thá»i gian." - step1_3: "Bảng tin của bạn Ä‘ang trống, bởi vì bạn chÆ°a đăng tút nà o hoặc chÆ°a theo dõi ai." - step2_1: "Hãy hoà n thà nh việc thiết láºp hồ sÆ¡ của bạn trÆ°á»›c khi viết tút hoặc theo dõi bất kỳ ai." - step2_2: "Cung cấp má»™t số thông tin giá»›i thiệu bạn là ai sẽ giúp ngÆ°á»i khác dá»… dà ng biết được há» muốn Ä‘á»c tút hay theo dõi bạn." - step3_1: "Hoà n thà nh thiết láºp hồ sÆ¡ của bạn?" - step3_2: "Sau đó, hãy thỠđăng má»™t tút tiếp theo. Bạn có thể là m nhÆ° váºy bằng cách nhấn và o nút có biểu tượng bút chì trên mà n hình." - step3_3: "Nháºp ná»™i dung và o khung soạn thảo và nhấn nút đăng ở góc trên." - step3_4: "ChÆ°a biết nói gì? Thá» \"Tôi má»›i tham gia Misskey\"!" - step4_1: "Äăng xong tút đầu tiên của bạn?" - step4_2: "De! Tút đầu tiên của bạn đã hiện trên bảng tin." - step5_1: "Bây giá», hãy thá» là m cho bảng tin của bạn sinh Ä‘á»™ng hÆ¡n bằng cách theo dõi những ngÆ°á»i khác." - step5_2: "{feature} sẽ hiển thị cho bạn các tút nổi báºt trên máy chủ nà y. {explore} sẽ cho phép bạn tìm thấy những ngÆ°á»i dùng thú vị. Hãy thá» tìm những ngÆ°á»i bạn muốn theo dõi ở đó!" - step5_3: "Äể theo dõi những ngÆ°á»i dùng khác, hãy nhấn và o ảnh đại diện của há» và nhấn nút \"Theo dõi\" trên hồ sÆ¡ của há»." - step5_4: "Nếu ngÆ°á»i dùng khác có biểu tượng ổ khóa bên cạnh tên của há», có thể mất má»™t khoảng thá»i gian để ngÆ°á»i dùng đó phê duyệt yêu cầu theo dõi của bạn theo cách thủ công." - step6_1: "Bạn sẽ có thể xem tút của những ngÆ°á»i dùng khác trên bảng tin của mình ngay bây giá»." - step6_2: "Bạn cÅ©ng có thể đặt \"biểu cảm\" trên tút của ngÆ°á»i khác để phản hồi nhanh chúng." - step6_3: "Äể Ä‘Ãnh kèm \"biểu cảm\", hãy nhấn và o dấu \"+\" trên tút của ngÆ°á»i dùng khác rồi chá»n biểu tượng cảm xúc mà bạn muốn dùng." - step7_1: "Xin chúc mừng! Bây giá» bạn đã hoà n thà nh phần hÆ°á»›ng dẫn cÆ¡ bản của Misskey." - step7_2: "Nếu bạn muốn tìm hiểu thêm vá» Misskey, hãy thá» phần {help}." - step7_3: "Bây giá», chúc may mắn và vui vẻ vá»›i Misskey! 🚀" - step8_1: "Cuối cùng, bạn hãy báºt thông báo đẩy nha!" - step8_2: "Nháºn thông báo đẩy bạn sẽ có thể thấy phản hồi, theo dõi, lượt nhắc được trong khi đóng Misskey" _2fa: alreadyRegistered: "Bạn đã đăng ký thiết bị xác minh 2 bÆ°á»›c." passwordToTOTP: "Nhắn máºt mã" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 62f6bc6c321582da1c73d6a064aafb27f35d345f..1fe82185beed692e4a0882b377b3b81c8e3db3c2 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -114,7 +114,7 @@ sensitive: "æ•æ„Ÿå†…容" add: "æ·»åŠ " reaction: "回应" reactions: "回应" -reactionSetting: "在选择器ä¸æ˜¾ç¤ºçš„回应" +reactionSetting: "在选择器ä¸æ˜¾ç¤ºå›žåº”" reactionSettingDescription2: "拖动é‡æ–°æŽ’åºï¼Œå•å‡»åˆ 除,点击 + æ·»åŠ ã€‚" rememberNoteVisibility: "ä¿å˜ä¸Šæ¬¡è®¾ç½®çš„å¯è§æ€§" attachCancel: "åˆ é™¤é™„ä»¶" @@ -167,7 +167,7 @@ searchWith: "æœç´¢:{q}" youHaveNoLists: "列表为空" followConfirm: "ä½ ç¡®å®šè¦å…³æ³¨{name}å—?" proxyAccount: "代ç†è´¦æˆ·" -proxyAccountDescription: "代ç†è´¦æˆ·æ˜¯åœ¨æŸäº›æƒ…况下充当用户的远程关注者的账户。 例如,当一个用户列出一个远程用户时,如果没有人跟éšè¯¥åˆ—出的用户,则该活动将ä¸ä¼šä¼ é€’åˆ°è¯¥å®žä¾‹ï¼Œå› æ¤å°†ä»£ä¹‹ä»¥ä»£ç†è´¦æˆ·ã€‚" +proxyAccountDescription: "代ç†è´¦æˆ·æ˜¯åœ¨æŸäº›æƒ…况下充当用户的远程关注者的账户。 例如,当一个用户列出一个远程用户时,如果没有人跟éšè¯¥åˆ—出的用户,则该活动将ä¸ä¼šä¼ 递到该æœåŠ¡å™¨ï¼Œå› æ¤å°†ä»£ä¹‹ä»¥ä»£ç†è´¦æˆ·ã€‚" host: "主机å" selectUser: "选择用户" recipient: "收件人" @@ -263,14 +263,16 @@ noMoreHistory: "没有更多的历å²è®°å½•" startMessaging: "æ·»åŠ èŠå¤©" nUsersRead: "{n}人已读" agreeTo: "勾选则表示已阅读并åŒæ„{0}" -agreeBelow: "åŒæ„以下观点" +agree: "åŒæ„" +agreeBelow: "åŒæ„以下内容" basicNotesBeforeCreateAccount: "基本注æ„事项" -tos: "æœåŠ¡æ¡æ¬¾" +termsOfService: "æœåŠ¡æ¡æ¬¾" start: "开始" home: "首页" remoteUserCaution: "由于æ¤ç”¨æˆ·æ¥è‡ªå…¶å®ƒæœåŠ¡å™¨ï¼Œæ˜¾ç¤ºçš„ä¿¡æ¯å¯èƒ½ä¸å®Œæ•´ã€‚" activity: "活动" images: "图片" +image: "图片" birthday: "生日" yearsOld: "{age}å²" registeredDate: "注册于" @@ -403,7 +405,7 @@ totpDescription: "使用认è¯åº”用输入一次性密ç 。" moderator: "监察员" moderation: "管ç†" nUsersMentioned: "{n} 被æ到" -securityKeyAndPasskey: "安全密钥/密ç " +securityKeyAndPasskey: "安全密钥或 Passkey" securityKey: "安全密钥" lastUsed: "最åŽä½¿ç”¨ï¼š" lastUsedAt: "最åŽä½¿ç”¨: {t}" @@ -474,6 +476,8 @@ createAccount: "注册账户" existingAccount: "现有的账户" regenerate: "é‡æ–°ç”Ÿæˆ" fontSize: "å—体大å°" +mediaListWithOneImageAppearance: "ä»…ä¸€å¼ å›¾ç‰‡çš„åª’ä½“åˆ—è¡¨é«˜åº¦" +limitTo: "上é™ä¸º{x}" noFollowRequests: "没有关注申请" openImageInNewTab: "åœ¨æ–°æ ‡ç¾é¡µä¸æ‰“开图片" dashboard: "管ç†é¢æ¿" @@ -613,7 +617,7 @@ testEmail: "邮件å‘é€æµ‹è¯•" wordMute: "æ–‡å—å±è”½" regexpError: "æ£åˆ™è¡¨è¾¾å¼é”™è¯¯" regexpErrorDescription: "{tab} å±è”½æ–‡å—的第 {line} 行的æ£åˆ™è¡¨è¾¾å¼æœ‰é”™è¯¯ï¼š" -instanceMute: "实例的å±è”½" +instanceMute: "被å±è”½çš„æœåŠ¡å™¨" userSaysSomething: "{name}说了什么,但是被å±è”½è¯è¿‡æ»¤äº†" makeActive: "å¯ç”¨" display: "显示" @@ -699,6 +703,8 @@ contact: "è”系人" useSystemFont: "使用系统默认å—体" clips: "便ç¾" experimentalFeatures: "实验性功能" +experimental: "实验性的" +thisIsExperimentalFeature: "这是一项实验性功能。规范å¯èƒ½ä¼šå˜æ›´ï¼Œæˆ–者å¯èƒ½æ— 法æ£å¸¸å·¥ä½œã€‚" developer: "å¼€å‘者" makeExplorable: "使账å·å¯è§ã€‚" makeExplorableDescription: "å…³é—时,账å·ä¸ä¼šæ˜¾ç¤ºåœ¨\"å‘现\"ä¸ã€‚" @@ -886,7 +892,7 @@ typeToConfirm: "输入 {x} 以确认æ“作。" deleteAccount: "åˆ é™¤è´¦æˆ·" document: "文档" numberOfPageCache: "缓å˜é¡µæ•°" -numberOfPageCacheDescription: "设置较高的值会更方便用户,但设备的负载和内å˜ä½¿ç”¨é‡ä¼šå¢žåŠ 。" +numberOfPageCacheDescription: "设置较高的值会更方便用户,但æœåŠ¡å™¨è´Ÿè½½å’Œå†…å˜ä½¿ç”¨é‡ä¼šå¢žåŠ 。" logoutConfirm: "是å¦ç¡®è®¤ç™»å‡ºï¼Ÿ" lastActiveDate: "最åŽæ´»è·ƒæ—¶é—´" statusbar: "状æ€æ " @@ -905,6 +911,7 @@ remoteOnly: "仅远程" failedToUpload: "ä¸Šä¼ å¤±è´¥" cannotUploadBecauseInappropriate: "å› ä¸ºå¯èƒ½å«æœ‰ä¸é€‚å®œçš„å†…å®¹ï¼Œæ— æ³•ä¸Šä¼ ã€‚" cannotUploadBecauseNoFreeSpace: "å› ä¸ºå·²æ— å¯ç”¨ç©ºé—´ï¼Œæ— æ³•ä¸Šä¼ ã€‚" +cannotUploadBecauseExceedsFileSizeLimit: "æ— æ³•ä¸Šä¼ æ–‡ä»¶ï¼Œè¶…è¿‡æ–‡ä»¶å¤§å°é™åˆ¶ã€‚" beta: "测试" enableAutoSensitive: "自动 NSFW 识别" enableAutoSensitiveDescription: "如果å¯ç”¨ï¼Œè¯·ä½¿ç”¨æœºå™¨å¦ä¹ 在媒体上自动设置 NSFW æ ‡å¿—ã€‚å³ä½¿å…³é—æ¤åŠŸèƒ½ï¼Œä¹Ÿå¯èƒ½ä¼šæ ¹æ®æœåŠ¡å™¨è‡ªåŠ¨è®¾ç½®ã€‚" @@ -938,6 +945,7 @@ didYouLikeMisskey: "您喜欢Misskeyå—?" pleaseDonate: "Misskey是{host}所使用的å…费软件。为了今åŽä¹Ÿèƒ½å¤Ÿç»´æŒMisskeyçš„å¼€å‘,请在有余力的情况下进行æ助ï¼" roles: "角色" role: "角色" +noRole: "角色ä¸å˜åœ¨" normalUser: "普通用户" undefined: "未定义" assign: "分é…" @@ -947,6 +955,10 @@ manageCustomEmojis: "管ç†è‡ªå®šä¹‰è¡¨æƒ…符å·" youCannotCreateAnymore: "抱æ‰ï¼Œæ‚¨æ— 法å†åˆ›å»ºæ›´å¤šäº†ã€‚" cannotPerformTemporary: "æš‚æ—¶ä¸å¯ç”¨" cannotPerformTemporaryDescription: "å› æ“作过于频ç¹ï¼Œæš‚æ—¶ä¸å¯ç”¨ï¼Œè¯·ç¨åŽå†è¯•ã€‚" +invalidParamError: "å‚数错误" +invalidParamErrorDescription: "请求å‚æ•°å‡ºçŽ°é—®é¢˜ã€‚é€šå¸¸æ˜¯å› ä¸ºbugé€ æˆçš„,但也å¯èƒ½æ˜¯è¾“入文å—æ•°é‡è¿‡å¤šä¹‹ç±»çš„åŽŸå› ã€‚" +permissionDeniedError: "æ“作被拒ç»" +permissionDeniedErrorDescription: "本账户没有执行该æ“作的æƒé™ã€‚" preset: "é è¨å€¼" selectFromPresets: "從é è¨å€¼ä¸é¸æ“‡" achievements: "æˆå°±" @@ -990,17 +1002,45 @@ enableChartsForFederatedInstances: "生æˆè¿œç¨‹æœåŠ¡å™¨çš„图表" showClipButtonInNoteFooter: "在贴文下方显示便ç¾æŒ‰é’®" largeNoteReactions: "ä½¿ç”¨å¤§å›¾æ ‡æ¥æ˜¾ç¤ºå›žåº”" noteIdOrUrl: "帖åID或URL" +video: "视频" +videos: "视频" +dataSaver: "çœæµé‡æ¨¡å¼" accountMigration: "账户è¿ç§»" accountMoved: "æ¤ç”¨æˆ·å·²è¿ç§»è´¦æˆ·" +accountMovedShort: "æ¤å¸æˆ·å·²è¿ç§»" +operationForbidden: "ä¸å…许æ¤æ“作" forceShowAds: "总是显示广告" +addMemo: "æ·»åŠ å¤‡æ³¨" +editMemo: "编辑备注" +reactionsList: "回应列表" +renotesList: "转å‘列表" +notificationDisplay: "显示通知" +leftTop: "å±å¹•å·¦ä¸Šæ–¹" +rightTop: "å±å¹•å³ä¸Šæ–¹" +leftBottom: "å±å¹•å·¦ä¸‹æ–¹" +rightBottom: "å±å¹•å³ä¸‹æ–¹" +stackAxis: "å †å æ–¹å‘" +vertical: "纵å‘" +horizontal: "横å‘" +position: "ä½ç½®" +serverRules: "æœåŠ¡å™¨è§„则" +pleaseConfirmBelowBeforeSignup: "在这个æœåŠ¡å™¨ä¸Šæ³¨å†Œè´¦å·å‰ï¼Œè¯·ç¡®è®¤ä»¥ä¸‹ä¿¡æ¯ã€‚" +pleaseAgreeAllToContinue: "必须全部勾选「åŒæ„ã€æ‰èƒ½å¤Ÿç»§ç»ã€‚" +continue: "继ç»" +preservedUsernames: "ä¿ç•™çš„用户å" +createNoteFromTheFile: "从文件创建帖å" +youFollowing: "æ£åœ¨å…³æ³¨" +_serverRules: + description: "在新用户注册å‰æ˜¾ç¤ºæœåŠ¡å™¨çš„简å•è§„则。推è显示æœåŠ¡æ¡æ¬¾çš„主è¦å†…容。" _accountMigration: - moveTo: "把这个账户è¿ç§»åˆ°æ–°çš„账户" - moveToLabel: "è¿ç§»åŽçš„账户" - moveAccountDescription: "æ¤æ“ä½œæ— æ³•å–消。请先确认您已在è¿ç§»åŽçš„账户上,为æ¤è´¦æˆ·åˆ›é€ 了别åã€‚åˆ›é€ åˆ«ååŽï¼Œè¯·å¦‚以下输入您的è¿ç§»åŽçš„账户:@person@instance.com" moveFrom: "从别的账å·è¿ç§»åˆ°æ¤è´¦æˆ·" moveFromLabel: "è¿ç§»å‰çš„账户" moveFromDescription: "如果è¿ç§»æ—¶éœ€è¦ç»§æ‰¿å…¶ä»–账户的关注者,请在æ¤åˆ›é€ 别å。æ¤æ“作需è¦åœ¨å®žè¡Œè¿ç§»ä¹‹å‰å®Œæˆï¼è¯·å¦‚已下输入需è¦è¿ç§»çš„账户:@person@instance.com" + moveTo: "把这个账户è¿ç§»åˆ°æ–°çš„账户" + moveToLabel: "è¿ç§»åŽçš„账户" + moveAccountDescription: "æ¤æ“ä½œæ— æ³•å–消。请先确认您已在è¿ç§»åŽçš„账户上,为æ¤è´¦æˆ·åˆ›é€ 了别åã€‚åˆ›é€ åˆ«ååŽï¼Œè¯·å¦‚以下输入您的è¿ç§»åŽçš„账户:@person@instance.com" migrationConfirm: "确定è¦æŠŠæ¤è´¦æˆ·è¿ç§»åˆ°{account}å—?一旦确定åŽï¼Œæ¤æ“ä½œæ— æ³•å–消,æ¤è´¦æˆ·ä¹Ÿæ— 法以原æ¥çš„状æ€ä½¿ç”¨ã€‚\nåŒæ—¶ï¼Œè¯·ç¡®è®¤è¿ç§»åŽçš„è´¦æˆ·ï¼Œå·²åˆ›é€ åˆ«å。" + movedTo: "è¿ç§»åŽçš„账户" _achievements: earnedAt: "è¾¾æˆæ—¶é—´" _types: @@ -1172,6 +1212,9 @@ _achievements: _client30min: title: "休æ¯ä¸€ä¸‹ï¼" description: "å¯åŠ¨å®¢æˆ·ç«¯è¶…过30分钟" + _client60min: + title: "Misskeyé‡åº¦ä¾èµ–" + description: "å¯åŠ¨å®¢æˆ·ç«¯è¶…过60分钟" _noteDeletedWithin1min: title: "欲言åˆæ¢" description: "å‘帖åŽä¸€åˆ†é’Ÿå†…å°±å°†å…¶åˆ é™¤" @@ -1260,6 +1303,8 @@ _role: iconUrl: "å›¾æ ‡URL" asBadge: "ä½œä¸ºå¾½ç« æ˜¾ç¤º" descriptionOfAsBadge: "å¼€å¯åŽï¼Œç”¨æˆ·åæ—è¾¹å°†ä¼šå‡ºçŽ°è§’è‰²å›¾æ ‡ã€‚" + isExplorable: "公开角色时间线" + descriptionOfIsExplorable: "打开åŽå°†å…¬å¼€è§’色时间线。如果角色ä¸æ˜¯å…¬å¼€çš„ï¼Œå°±æ— æ³•å…¬å¼€æ—¶é—´çº¿ã€‚" displayOrder: "显示顺åº" descriptionOfDisplayOrder: "æ•°å—越大,显示ä½ç½®è¶Šé å‰ã€‚" canEditMembersByModerator: "å…许监察者编辑æˆå‘˜" @@ -1527,32 +1572,6 @@ _time: minute: "分" hour: "å°æ—¶" day: "æ—¥" -_tutorial: - title: "Misskey的使用方法" - step1_1: "欢迎ï¼" - step1_2: "这个页é¢å«åšã€Œæ—¶é—´çº¿ã€ï¼Œå®ƒä¼šæŒ‰ç…§æ—¶é—´é¡ºåºæ˜¾ç¤ºæ‰€æœ‰ä½ 「关注ã€çš„人所å‘的「帖åã€ã€‚" - step1_3: "å¦‚æžœä½ å¹¶æ²¡æœ‰å‘布任何帖åï¼Œä¹Ÿæ²¡æœ‰å…³æ³¨å…¶ä»–çš„äººï¼Œä½ çš„æ—¶é—´çº¿é¡µé¢åº”当什么都没有显示。" - step2_1: "在您想è¦å‘帖或关注其他人之å‰ï¼Œè¯·å…ˆè®¾ç½®ä¸€ä¸‹ä¸ªäººèµ„æ–™å§ã€‚" - step2_2: "å¦‚æžœåˆ«äººèƒ½å¤Ÿæ›´åŠ çš„äº†è§£ä½ ï¼Œå…³æ³¨ä½ çš„æ¦‚çŽ‡ä¹Ÿä¼šå¾—åˆ°æå‡ã€‚" - step3_1: "å·²ç»è®¾ç½®å®Œä¸ªäººèµ„料了å—?" - step3_2: "那么接下æ¥ï¼Œè¯•ç€å†™ä¸€äº›ä»€ä¹ˆä¸œè¥¿æ¥å‘布å§ã€‚ä½ å¯ä»¥é€šè¿‡ç‚¹å‡»å±å¹•ä¸Šçš„é“…ç¬”å›¾æ ‡æ¥æ‰“开投稿页é¢ã€‚" - step3_3: "写完内容åŽï¼Œç‚¹å‡»çª—å£å³ä¸Šæ–¹çš„按钮就å¯ä»¥æŠ•ç¨¿ã€‚" - step3_4: "ä¸çŸ¥é“说些什么好å—?那就写下「Misskey我æ¥å•¦ï¼ã€è¿™æ ·çš„è¯å§ã€‚" - step4_1: "å°†ä½ çš„è¯è¯å‘布出去了å—?" - step4_2: "太棒了ï¼çŽ°åœ¨ä½ å¯ä»¥åœ¨ä½ 的时间线ä¸çœ‹åˆ°ä½ 刚刚å‘布的帖å了。" - step5_1: "接下æ¥ï¼Œå…³æ³¨å…¶ä»–人æ¥ä½¿æ—¶é—´çº¿æ›´ç”ŸåŠ¨å§ã€‚" - step5_2: "{featured}å°†å‘您展示çƒé—¨è¶‹åŠ¿çš„帖å。 {explore}将让您找到çƒé—¨ç”¨æˆ·ã€‚ å°è¯•å…³æ³¨æ‚¨å–œæ¬¢çš„人ï¼" - step5_3: "è¦å…³æ³¨å…¶ä»–用户,请å•å‡»ä»–的头åƒï¼Œç„¶åŽåœ¨ä»–的个人资料上按下“关注â€æŒ‰é’®ã€‚" - step5_4: "如果用户的å称æ—边有é”å®šå›¾æ ‡ï¼Œåˆ™è¯¥ç”¨æˆ·éœ€è¦æ‰‹åŠ¨æ‰¹å‡†æ‚¨çš„关注请求。" - step6_1: "现在,您将å¯ä»¥åœ¨æ—¶é—´çº¿ä¸Šçœ‹åˆ°å…¶ä»–用户的帖å。" - step6_2: "您还å¯ä»¥åœ¨å…¶ä»–人的帖å上进行「回应ã€ï¼Œä»¥å¿«é€Ÿåšå‡ºç®€å•å›žå¤ã€‚" - step6_3: "在他人的贴å上按下「+ã€å›¾æ ‡ï¼Œå³å¯é€‰æ‹©æƒ³è¦çš„表情æ¥è¿›è¡Œã€Œå›žåº”ã€ã€‚" - step7_1: "对Misskey基本æ“作的简å•ä»‹ç»ï¼Œå°±åˆ°æ¤ç»“æŸäº†ã€‚ 辛苦了ï¼" - step7_2: "å¦‚æžœä½ æƒ³äº†è§£æ›´å¤šæœ‰å…³Misskeyçš„ä¿¡æ¯ï¼Œè¯·å‚è§{help}。" - step7_3: "接下æ¥ï¼Œäº«å—Misskey带æ¥çš„ä¹è¶£å§ðŸš€" - step8_1: "最åŽï¼Œæ‚¨æƒ³è¦å¯ç”¨æŽ¨é€é€šçŸ¥æ¶ˆæ¯å—?" - step8_2: "通过接收推é€é€šçŸ¥æ¶ˆæ¯ï¼Œå³ä½¿æ²¡å¼€ Misskey,您也å¯ä»¥æ”¶åˆ°å›žåº”ã€å…³æ³¨ã€æåŠç‰çš„消æ¯ã€‚" - step8_3: "您也å¯ä»¥ç¨åŽå†æ›´æ”¹é€šçŸ¥è®¾ç½®ã€‚" _2fa: alreadyRegistered: "æ¤è®¾å¤‡å·²è¢«æ³¨å†Œ" registerTOTP: "开始设置认è¯åº”用" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 09253db2d77df1ba8561c80618d14c624e3061f2..4073b9629d9b77fd17f9db13e871c5a1f6b570ea 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -2,7 +2,7 @@ _lang_: "ç¹é«”ä¸æ–‡" headlineMisskey: "貼文連繫網路" introMisskey: "æ¡è¿Žï¼ Misskey是一個開放原始碼且去ä¸å¿ƒåŒ–的社群網路。\né€éŽã€Œè²¼æ–‡ã€åˆ†äº«å‘¨é‚Šæ–°é®®äº‹ï¼Œä¸¦å‘Šè¨´å…¶ä»–人您的想法ï¼ðŸ“¡\né€éŽã€Œå應ã€åŠŸèƒ½ï¼Œå°å¤§å®¶çš„貼文表é”情感ï¼ðŸ‘\n一起來探索這個新的世界å§ï¼ðŸš€" -poweredByMisskeyDescription: "{name}是使用開放原始碼平å°<b>Misskey</b>çš„æœå‹™ä¹‹ä¸€ï¼ˆç¨±ç‚º Misskey 實例)。\n" +poweredByMisskeyDescription: "{name}是使用開放原始碼平å°<b>Misskey</b>çš„æœå‹™ä¹‹ä¸€ï¼ˆç¨±ç‚º Misskey 伺æœå™¨ï¼‰ã€‚\n" monthAndDay: "{month}月 {day}æ—¥" search: "æœå°‹" notifications: "通知" @@ -18,7 +18,7 @@ enterUsername: "輸入使用者å稱" renotedBy: "{user} 轉發了" noNotes: "無貼文。" noNotifications: "沒有通知" -instance: "實例" +instance: "伺æœå™¨" settings: "è¨å®š" notificationSettings: "通知é¸é …" basicSettings: "基本è¨å®š" @@ -86,7 +86,7 @@ somethingHappened: "發生錯誤" retry: "é‡è©¦" pageLoadError: "載入é é¢å¤±æ•—" pageLoadErrorDescription: "é€™é€šå¸¸æ˜¯å› ç‚ºç¶²è·¯éŒ¯èª¤æˆ–æ˜¯ç€è¦½å™¨å¿«å–æ®˜ç•™çš„åŽŸå› ã€‚è«‹å…ˆæ¸…é™¤ç€è¦½å™¨å¿«å–,ç¨å¾Œå†é‡è©¦" -serverIsDead: "伺æœå™¨æ²’有回應。請ç¨ç‰ç‰‡åˆ»ï¼Œç„¶å¾Œé‡è©¦ã€‚" +serverIsDead: "伺æœå™¨æ²’有回應。請ç¨ç‰ç‰‡åˆ»å†è©¦ã€‚" youShouldUpgradeClient: "è«‹é‡æ–°è¼‰å…¥ä»¥ä½¿ç”¨æ–°ç‰ˆæœ¬çš„客戶端顯示æ¤é é¢" enterListName: "輸入清單å稱" privacy: "éš±ç§" @@ -96,7 +96,7 @@ follow: "追隨" followRequest: "追隨請求" followRequests: "追隨請求" unfollow: "å–消追隨" -followRequestPending: "追隨許å¯æ‰¹å‡†ä¸" +followRequestPending: "追隨許å¯å¾…批准" enterEmoji: "輸入表情符號" renote: "轉發" unrenote: "å–消轉發" @@ -150,11 +150,11 @@ cacheRemoteFiles: "å¿«å–é 端檔案" cacheRemoteFilesDescription: "ç¦ç”¨æ¤è¨å®šæœƒåœæ¢é 端檔案的緩å˜ï¼Œå¾žè€Œç¯€çœå„²å˜ç©ºé–“ï¼Œä½†è³‡æ–™æœƒå› ç›´æŽ¥é€£ç·šå¾žè€Œç”¢ç”Ÿé¡å¤–連接數據。" flagAsBot: "æ¤ä½¿ç”¨è€…是機器人" flagAsBotDescription: "如果本帳戶是由程å¼æŽ§åˆ¶ï¼Œè«‹å•Ÿç”¨æ¤é¸é …。啟用後,會作為標示幫助其他開發者防æ¢æ©Ÿå™¨äººä¹‹é–“產生無é™äº’動的行為,並會調整Misskey內部系統將本帳戶è˜åˆ¥ç‚ºæ©Ÿå™¨äºº" -flagAsCat: "æ¤ä½¿ç”¨è€…是貓" +flagAsCat: "喵~~~~~~~~~~~~~~ï¼ï¼ï¼ï¼ï¼ï¼ï¼ï¼ï¼ï¼ï¼ï¼" flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟æ¤æ¨™ç¤º" flagShowTimelineReplies: "在時間軸上顯示貼文的回覆" flagShowTimelineRepliesDescription: "啟用時,時間線除了顯示用戶的貼文以外,還會顯示用戶å°å…¶ä»–貼文的回覆。" -autoAcceptFollowed: "自動追隨ä¸ä½¿ç”¨è€…的追隨請求" +autoAcceptFollowed: "自動å…許來自追隨ä¸ä½¿ç”¨è€…的追隨請求" addAccount: "æ·»åŠ å¸³æˆ¶" reloadAccountsList: "更新帳戶清單的資訊" loginFailed: "登入失敗" @@ -167,7 +167,7 @@ searchWith: "æœå°‹: {q}" youHaveNoLists: "ä½ æ²’æœ‰ä»»ä½•æ¸…å–®" followConfirm: "ä½ çœŸçš„è¦è¿½éš¨{name}嗎?" proxyAccount: "代ç†å¸³æˆ¶" -proxyAccountDescription: "代ç†å¸³æˆ¶æ˜¯åœ¨æŸäº›æƒ…æ³ä¸‹å……當其他伺æœå™¨ç”¨æˆ¶çš„帳戶。例如,當使用者將一個來自其他伺æœå™¨çš„帳戶放在列表ä¸æ™‚,由於沒有其他使用者追蹤該帳戶,該指令ä¸æœƒå‚³é€åˆ°è©²ä¼ºæœå™¨ä¸Šï¼Œå› æ¤æœƒç”±ä»£ç†å¸³æˆ¶è¿½è¹¤ã€‚" +proxyAccountDescription: "代ç†å¸³æˆ¶æ˜¯åœ¨æŸäº›æƒ…æ³ä¸‹å……當其他伺æœå™¨ç”¨æˆ¶çš„帳戶。例如,當使用者將一個來自其他伺æœå™¨çš„帳戶放在列表ä¸æ™‚,由於沒有其他使用者追隨該帳戶,該指令ä¸æœƒå‚³é€åˆ°è©²ä¼ºæœå™¨ä¸Šï¼Œå› æ¤æœƒç”±ä»£ç†å¸³æˆ¶è¿½éš¨ã€‚" host: "主機" selectUser: "é¸å–使用者" recipient: "收件人" @@ -182,7 +182,7 @@ charts: "圖表" perHour: "æ¯å°æ™‚" perDay: "æ¯æ—¥" stopActivityDelivery: "åœæ¢ç™¼é€æ´»å‹•" -blockThisInstance: "å°éŽ–æ¤å¯¦ä¾‹" +blockThisInstance: "å°éŽ–æ¤ä¼ºæœå™¨" operations: "æ“作" software: "軟體" version: "版本" @@ -193,15 +193,15 @@ jobQueue: "佇列" cpuAndMemory: "CPUåŠè¨˜æ†¶é«”用é‡" network: "網路" disk: "硬碟" -instanceInfo: "實例資訊" +instanceInfo: "伺æœå™¨è³‡è¨Š" statistics: "統計" clearQueue: "清除佇列" clearQueueConfirmTitle: "確定è¦æ¸…除佇列嗎?" clearQueueConfirmText: "未發佈的貼文將ä¸æœƒç™¼ä½ˆã€‚您通常ä¸éœ€è¦ç¢ºèªã€‚" clearCachedFiles: "清除快å–資料" clearCachedFilesConfirm: "確定è¦æ¸…除所有é 端暫å˜è³‡æ–™å—Žï¼Ÿ" -blockedInstances: "å·²å°éŽ–的實例" -blockedInstancesDescription: "è«‹é€è¡Œè¼¸å…¥éœ€è¦å°éŽ–的實例。已å°éŽ–的實例將無法與本實例進行通訊。" +blockedInstances: "å·²å°éŽ–的伺æœå™¨" +blockedInstancesDescription: "è«‹é€è¡Œè¼¸å…¥éœ€è¦å°éŽ–的伺æœå™¨ã€‚å·²å°éŽ–的伺æœå™¨å°‡ç„¡æ³•èˆ‡æœ¬ä¼ºæœå™¨é€²è¡Œé€šè¨Šã€‚" muteAndBlock: "éœéŸ³å’Œå°éŽ–" mutedUsers: "å·²éœéŸ³ç”¨æˆ¶" blockedUsers: "å·²å°éŽ–用戶" @@ -224,8 +224,8 @@ all: "全部" subscribing: "訂閱ä¸" publishing: "ç›´æ’ä¸" notResponding: "沒有回應" -instanceFollowing: "追蹤實例" -instanceFollowers: "追蹤實例" +instanceFollowing: "追隨的伺æœå™¨" +instanceFollowers: "伺æœå™¨çš„追隨者" instanceUsers: "用戶" changePassword: "修改密碼" security: "安全性" @@ -263,14 +263,16 @@ noMoreHistory: "沒有更多æ·å²ç´€éŒ„" startMessaging: "開始èŠå¤©" nUsersRead: "{n}人已讀" agreeTo: "我åŒæ„{0}" +agree: "åŒæ„" agreeBelow: "åŒæ„以下內容" basicNotesBeforeCreateAccount: "基本注æ„äº‹é …" -tos: "使用æ¢æ¬¾" +termsOfService: "æœå‹™æ¢æ¬¾" start: "開始" home: "首é " remoteUserCaution: "由於該使用者來自é ç«¯å¯¦ä¾‹ï¼Œå› æ¤è³‡è¨Šå¯èƒ½éžå³æ™‚的。" activity: "å‹•æ…‹" images: "圖片" +image: "圖片" birthday: "生日" yearsOld: "{age}æ²" registeredDate: "註冊日期" @@ -318,8 +320,8 @@ unwatch: "å–消追隨" accept: "接å—" reject: "拒絕" normal: "æ£å¸¸" -instanceName: "實例å稱" -instanceDescription: "實例說明" +instanceName: "伺æœå™¨å稱" +instanceDescription: "伺æœå™¨ä»‹ç´¹" maintainerName: "管ç†å“¡å稱" maintainerEmail: "管ç†å“¡éƒµç®±" tosUrl: "æœå‹™æ¢æ¬¾URL" @@ -390,7 +392,7 @@ recentlyUpdatedUsers: "最近發文的使用者" recentlyRegisteredUsers: "æ–°åŠ å…¥ä½¿ç”¨è€…" recentlyDiscoveredUsers: "最近發ç¾çš„使用者" exploreUsersCount: "有{count}個使用者" -exploreFediverse: "探索è¯é‚¦ä¸–ç•Œ" +exploreFediverse: "探索è¯é‚¦å®‡å®™" popularTags: "熱門標籤" userList: "使用者清單" about: "關於" @@ -474,7 +476,9 @@ createAccount: "建立帳戶" existingAccount: "ç¾æœ‰å¸³æˆ¶" regenerate: "å†ç”Ÿ" fontSize: "å—體大å°" -noFollowRequests: "沒有跟隨您的請求" +mediaListWithOneImageAppearance: "僅1枚圖片的媒體列表高度" +limitTo: "上é™ç‚º{x}" +noFollowRequests: "沒有追隨您的請求" openImageInNewTab: "於新分é ä¸é–‹å•Ÿåœ–片" dashboard: "儀表æ¿" local: "本地" @@ -483,7 +487,7 @@ total: "åˆè¨ˆ" weekOverWeekChanges: "與上週相比" dayOverDayChanges: "與å‰ä¸€æ—¥ç›¸æ¯”" appearance: "外觀" -clientSettings: "用戶端è¨å®š" +clientSettings: "客戶端è¨å®š" accountSettings: "帳戶è¨å®š" promotion: "推廣" promote: "推廣" @@ -543,8 +547,8 @@ disablePagesScript: "åœç”¨é é¢çš„AiScript腳本" updateRemoteUser: "æ›´æ–°é 端使用者資訊" deleteAllFiles: "刪除所有檔案" deleteAllFilesConfirm: "è¦åˆ 除所有檔案嗎?" -removeAllFollowing: "解除所有追蹤" -removeAllFollowingDescription: "解除{host}所有的追蹤。在實例ä¸å†å˜åœ¨æ™‚執行。" +removeAllFollowing: "解除所有追隨" +removeAllFollowingDescription: "解除{host}所有的追隨。在伺æœå™¨ä¸å†å˜åœ¨æ™‚執行。" userSuspended: "該使用者已被åœç”¨" userSilenced: "該用戶已被ç¦è¨€ã€‚" yourAccountSuspendedTitle: "帳戶已被å‡çµ" @@ -556,6 +560,7 @@ accountDeletedDescription: "這個帳戶已被刪除。" menu: "é¸å–®" divider: "分割線" addItem: "æ–°å¢žé …ç›®" +rearrange: "排åºæ–¹å¼" relays: "ä¸ç¹¼" addRelay: "新增ä¸ç¹¼" inboxUrl: "收件夾URL" @@ -673,8 +678,8 @@ repliesCount: "回覆數é‡" renotesCount: "轉發數é‡" repliedCount: "回覆數é‡" renotedCount: "轉發次數" -followingCount: "æ£åœ¨è·Ÿéš¨çš„用戶數é‡" -followersCount: "跟隨者數é‡" +followingCount: "æ£åœ¨è¿½éš¨çš„用戶數é‡" +followersCount: "追隨者數é‡" sentReactionsCount: "å應發é€æ¬¡æ•¸" receivedReactionsCount: "收到å應次數" pollVotesCount: "已統計的投票數" @@ -699,6 +704,8 @@ contact: "è¯çµ¡äºº" useSystemFont: "使用系統é è¨çš„å—åž‹" clips: "摘錄" experimentalFeatures: "實驗ä¸çš„功能" +experimental: "實驗性" +thisIsExperimentalFeature: "這是實驗性的功能。å¯èƒ½æœƒæœ‰è®Šæ›´è¦æ ¼å’Œä¸èƒ½æ£å¸¸å‹•ä½œçš„å¯èƒ½æ€§ã€‚" developer: "開發者" makeExplorable: "ä½¿è‡ªå·±çš„å¸³æˆ¶èƒ½å¤ åœ¨ã€ŒæŽ¢ç´¢ã€é é¢ä¸é¡¯ç¤º" makeExplorableDescription: "如果關閉,帳戶將ä¸æœƒè¢«é¡¯ç¤ºåœ¨ã€ŒæŽ¢ç´¢ã€é é¢ä¸ã€‚" @@ -734,8 +741,8 @@ registry: "登錄表" closeAccount: "åœç”¨å¸³æˆ¶" currentVersion: "ç›®å‰ç‰ˆæœ¬" latestVersion: "最新版本" -youAreRunningUpToDateClient: "您所使用的用戶端已經是最新的。" -newVersionOfClientAvailable: "新版本的用戶端å¯ç”¨ã€‚" +youAreRunningUpToDateClient: "您所使用的客戶端已經是最新的。" +newVersionOfClientAvailable: "新版本的客戶端å¯ç”¨ã€‚" usageAmount: "使用é‡" capacity: "容é‡" inUse: "已使用" @@ -821,7 +828,7 @@ pubSub: "Pub/Sub 帳戶" lastCommunication: "最近的通信" resolved: "已解決" unresolved: "未解決" -breakFollow: "移除追蹤者" +breakFollow: "解除追隨者" breakFollowConfirm: "確定è¦å–消被追隨嗎?" itsOn: "已開啟" itsOff: "已關閉" @@ -905,6 +912,7 @@ remoteOnly: "僅é™é 端" failedToUpload: "上傳失敗" cannotUploadBecauseInappropriate: "由於判定å¯èƒ½åŒ…å«ä¸é©ç•¶çš„å…§å®¹ï¼Œå› æ¤ç„¡æ³•ä¸Šå‚³ã€‚" cannotUploadBecauseNoFreeSpace: "由於雲端硬碟沒有å¯ç”¨ç©ºé–“ï¼Œå› æ¤ç„¡æ³•ä¸Šå‚³ã€‚" +cannotUploadBecauseExceedsFileSizeLimit: "由於超éŽäº†æª”案大å°çš„é™åˆ¶ï¼Œç„¡æ³•ä¸Šå‚³ã€‚" beta: "Beta" enableAutoSensitive: "自動NSFW判定" enableAutoSensitiveDescription: "如果å¯ç”¨ï¼Œè«‹åˆ©ç”¨æ©Ÿå™¨å¸ç¿’在媒體上自動è¨ç½® NSFW 旗標。 å³ä½¿é—œé–‰æ¤åŠŸèƒ½ï¼Œä¾å¯¦ä¾‹è€Œå®šä¹Ÿå¯èƒ½æœƒè‡ªå‹•è¨ç½®ã€‚" @@ -938,6 +946,7 @@ didYouLikeMisskey: "您是å¦å–œæ„›Misskey呢?" pleaseDonate: "Misskey是由{host}使用的å…è²»è»Ÿé«”ã€‚è«‹è´ŠåŠ©æˆ‘å€‘ï¼Œè®“é–‹ç™¼èƒ½å¤ æŒçºŒï¼" roles: "角色" role: "角色" +noRole: "沒有角色" normalUser: "一般使用者" undefined: "未定義" assign: "指派" @@ -947,6 +956,10 @@ manageCustomEmojis: "管ç†è‡ªè¨‚表情符號" youCannotCreateAnymore: "您無法å†å»ºç«‹æ›´å¤šäº†ã€‚" cannotPerformTemporary: "暫時無法進行" cannotPerformTemporaryDescription: "由於超éŽæ“作次數é™åˆ¶ï¼Œæš«æ™‚無法進行。請éŽä¸€æ®µæ™‚間之後å†å˜—試。" +invalidParamError: "åƒæ•¸éŒ¯èª¤" +invalidParamErrorDescription: "請求åƒæ•¸æœ‰å•é¡Œã€‚通常是bugé€ æˆçš„,但也有輸入的å—元數éŽå¤šä¹‹é¡žçš„å¯èƒ½æ€§ã€‚" +permissionDeniedError: "æ“作被拒絕" +permissionDeniedErrorDescription: "本帳號沒有執行這個æ“作的權é™ã€‚" preset: "é è¨å€¼" selectFromPresets: "從é è¨å€¼ä¸é¸æ“‡" achievements: "æˆå°±" @@ -990,17 +1003,57 @@ enableChartsForFederatedInstances: "生æˆé 端伺æœå™¨çš„圖表" showClipButtonInNoteFooter: "å°‡æ‘˜éŒ„æ·»åŠ è‡³è²¼æ–‡" largeNoteReactions: "將貼文的å應放大顯示" noteIdOrUrl: "貼文ID或URL" +video: "影片" +videos: "影片" +dataSaver: "數據節çœæ¨¡å¼" accountMigration: "é·ç§»å¸³æˆ¶" accountMoved: "這個使用者已é·ç§»è‡³æ–°çš„帳戶:" +accountMovedShort: "æ¤å¸³æˆ¶å·²é·ç§»" +operationForbidden: "ä¸å…許æ¤æ“作" forceShowAds: "總是顯示廣告" +addMemo: "新增備註" +editMemo: "編輯備註" +reactionsList: "å應列表" +renotesList: "轉發貼文列表" +notificationDisplay: "通知的顯示" +leftTop: "左上" +rightTop: "å³ä¸Š" +leftBottom: "左下" +rightBottom: "å³ä¸‹" +stackAxis: "å †ç–Šæ–¹å‘" +vertical: "縱å‘" +horizontal: "å´å‘" +position: "ä½ç½®" +serverRules: "伺æœå™¨è¦å‰‡" +pleaseConfirmBelowBeforeSignup: "在本伺æœå™¨è¨»å†Šä¹‹å‰ï¼Œè«‹ç¢ºèªä¸‹åˆ—äº‹é …ã€‚" +pleaseAgreeAllToContinue: "å¿…é ˆå…¨éƒ¨å‹¾é¸ã€ŒåŒæ„ã€æ‰èƒ½ç¹¼çºŒã€‚" +continue: "繼續" +preservedUsernames: "ä¿ç•™çš„使用者å稱" +preservedUsernamesDescription: "æ›è¡Œåˆ—舉è¦ä¿ç•™çš„使用者å稱。æ¤è™•æŒ‡å®šçš„使用者å稱,在建立帳戶時無法使用,但由管ç†è€…所建立的帳戶ä¸å—æ¤é™ã€‚æ¤å¤–,既有的帳戶也ä¸å—影響。" +createNoteFromTheFile: "ç”±æ¤æª”案建立貼文" +archive: "å°å˜" +channelArchiveConfirmTitle: "è¦å°å˜{name}嗎?" +channelArchiveConfirmDescription: "å°å˜ä»¥å¾Œï¼Œåœ¨é »é“列表與æœç´¢çµæžœä¸ä¸æœƒé¡¯ç¤ºï¼Œä¹Ÿç„¡æ³•ç™¼å¸ƒæ–°çš„貼文。" +thisChannelArchived: "é€™å€‹é »é“已被å°å˜ã€‚" +displayOfNote: "顯示貼文" +youFollowing: "關注ä¸" +_serverRules: + description: "è¨å®šä¼ºæœå™¨çš„ç°¡è¦è¦å‰‡ï¼Œåœ¨æ–°çš„註冊之å‰é¡¯ç¤ºã€‚建è°çš„內容是使用æ¢æ¬¾çš„摘è¦ã€‚" _accountMigration: + moveFrom: "從其他帳戶é·ç§»åˆ°é€™å€‹å¸³æˆ¶" + moveFromSub: "為å¦ä¸€å€‹å¸³æˆ¶å»ºç«‹åˆ¥å" + moveFromLabel: "è¦é·ç§»éŽä¾†çš„帳戶:" + moveFromDescription: "å¦‚æžœä½ æƒ³æŠŠè¿½éš¨è€…å¾žåˆ¥çš„å¸³æˆ¶é·ç§»éŽä¾†ï¼Œå¿…é ˆå…ˆåœ¨é€™è£¡å»ºç«‹åˆ¥å。請務必在執行é·ç§»ä¹‹å‰å»ºç«‹åˆ¥åï¼è«‹åƒé€™æ¨£è¼¸å…¥è¦é·ç§»çš„帳戶:@person@instance.com" moveTo: "將這個帳戶é·ç§»è‡³æ–°çš„帳戶" moveToLabel: "è¦é·ç§»åˆ°çš„帳戶:" + moveCannotBeUndone: "一旦é·ç§»å¸³æˆ¶ï¼Œå°±ç„¡æ³•å–消。" moveAccountDescription: "這個æ“作ä¸å¯æ’¤éŠ·ã€‚首先,請確èªå·²åœ¨è¦é·ç§»åˆ°çš„帳戶ä¸ç‚ºé€™å€‹å¸³æˆ¶å»ºç«‹äº†ä¸€å€‹åˆ¥å。建立別å之後,åƒé€™æ¨£è¼¸å…¥ä½ è¦é·ç§»åˆ°çš„帳戶:@person@instance.com" - moveFrom: "從其他帳戶é·ç§»åˆ°é€™å€‹å¸³æˆ¶" - moveFromLabel: "è¦é·ç§»éŽä¾†çš„帳戶:" - moveFromDescription: "å¦‚æžœä½ æƒ³æŠŠè·Ÿéš¨è€…å¾žåˆ¥çš„å¸³æˆ¶é·ç§»éŽä¾†ï¼Œå¿…é ˆå…ˆåœ¨é€™è£¡å»ºç«‹åˆ¥å。請務必在執行é·ç§»ä¹‹å‰å»ºç«‹åˆ¥åï¼è«‹åƒé€™æ¨£è¼¸å…¥è¦é·ç§»çš„帳戶:@person@instance.com" + moveAccountHowTo: "è¦é·ç§»å¸³æˆ¶ï¼Œé¦–å…ˆè¦åœ¨ç›®æ¨™å¸³æˆ¶ä¸ç‚ºæ¤å¸³æˆ¶å»ºç«‹ä¸€å€‹åˆ¥å。\n 建立別å後,åƒé€™æ¨£è¼¸å…¥ç›®æ¨™å¸³æˆ¶ï¼š@username@server.example.com" + startMigration: "é·ç§»" migrationConfirm: "確定è¦å°‡é€™å€‹å¸³æˆ¶é·ç§»è‡³ {account} 嗎?一旦é·ç§»å°±ç„¡æ³•æ’¤éŠ·ï¼Œä¹Ÿå°±ç„¡æ³•ä»¥åŽŸä¾†çš„狀態使用這個帳戶。\nå¦å¤–,請確èªåœ¨è¦é·ç§»åˆ°çš„帳戶已經建立了一個別å。" + movedAndCannotBeUndone: "帳戶已é·ç§»ã€‚\né·ç§»ç„¡æ³•æ’¤æ¶ˆã€‚" + postMigrationNote: "在é·ç§»æ“作後的24å°æ™‚之後解除æ¤å¸³æˆ¶çš„追隨。æ¤å¸³æˆ¶çš„追隨ä¸ã€è¿½éš¨è€…數é‡è®Šç‚º0。由於ä¸æœƒè§£é™¤è¿½éš¨è€…ï¼Œä½ çš„è¿½éš¨è€…ä»ç„¶å¯ä»¥ç¹¼çºŒæª¢è¦–這個帳戶發布給追隨者的貼文。" + movedTo: "è¦é·ç§»åˆ°çš„帳戶:" _achievements: earnedAt: "ç²å¾—日期" _types: @@ -1125,31 +1178,31 @@ _achievements: description: "首次追隨了" _following10: title: "è·Ÿè‘—è·Ÿè‘—" - description: "跟隨超éŽ10人了" + description: "追隨超éŽ10人了" _following50: title: "朋å‹å¾ˆå¤š" - description: "跟隨超éŽ50人了" + description: "追隨超éŽ50人了" _following100: title: "100ä½æœ‹å‹" - description: "跟隨超éŽ100人了" + description: "追隨超éŽ100人了" _following300: title: "朋å‹éŽå¤š" - description: "跟隨超éŽ300人了" + description: "追隨超éŽ300人了" _followers1: title: "第一個追隨者" description: "第一次被追隨" _followers10: title: "Follow me!" - description: "跟隨者超éŽ10人了" + description: "追隨者超éŽ10人了" _followers50: title: "æˆç¾¤çµéšŠ" - description: "跟隨者超éŽ50人了" + description: "追隨者超éŽ50人了" _followers100: title: "熱門人物" - description: "跟隨者超éŽ100人了" + description: "追隨者超éŽ100人了" _followers300: title: "請排æˆä¸€æŽ’" - description: "跟隨者超éŽ300人了" + description: "追隨者超éŽ300人了" _followers500: title: "基地å°" description: "超éŽ500å追隨者了" @@ -1171,7 +1224,10 @@ _achievements: description: "發ç¾äº†éš±è—的寶è—" _client30min: title: "休æ¯ä¸€ä¸‹" - description: "用戶端啟動已超éŽ30分é˜" + description: "客戶端啟動已超éŽ30分é˜" + _client60min: + title: "Misskey看太多" + description: "客戶端啟動已超éŽ60分é˜" _noteDeletedWithin1min: title: "ç¾åœ¨æ²’有了" description: "發文後1分é˜å…§åˆªæ–‡" @@ -1260,6 +1316,8 @@ _role: iconUrl: "圖示的URL" asBadge: "é¡¯ç¤ºç‚ºå¾½ç« " descriptionOfAsBadge: "開啟的話,角色圖示會顯示在用戶åæ—邊。" + isExplorable: "公開角色時間軸" + descriptionOfIsExplorable: "若開啟則公開角色時間軸。若角色ä¸æ˜¯å…¬é–‹çš„,則無法公開時間軸。" displayOrder: "é¡¯ç¤ºé †åº" descriptionOfDisplayOrder: "數å—越大,顯示在UI上的越上é¢ã€‚" canEditMembersByModerator: "å…許編輯審查員的æˆå“¡" @@ -1276,6 +1334,7 @@ _role: canInvite: "發行實例邀請碼" canManageCustomEmojis: "管ç†è‡ªè¨‚表情符號" driveCapacity: "雲端硬碟容é‡" + alwaysMarkNsfw: "總是將檔案標記為NSFW" pinMax: "ç½®é ‚è²¼æ–‡çš„æœ€å¤§æ•¸é‡" antennaMax: "å¯å»ºç«‹çš„天線數é‡" wordMuteMax: "éœéŸ³æ–‡å—的最大å—數" @@ -1527,32 +1586,6 @@ _time: minute: "分é˜" hour: "å°æ™‚" day: "æ—¥" -_tutorial: - title: "Misskey使用方法" - step1_1: "æ¡è¿Žï¼" - step1_2: "æ¤ç‚ºã€Œæ™‚間軸ã€é é¢ï¼Œå®ƒæœƒæŒ‰ç…§æ™‚é–“é †åºé¡¯ç¤ºä½ 「追隨ã€çš„人發出的「貼文ã€ã€‚" - step1_3: "ç”±æ–¼ä½ æ²’æœ‰ç™¼ä½ˆä»»ä½•è²¼æ–‡ï¼Œä¹Ÿæ²’æœ‰è¿½éš¨ä»»ä½•äººï¼Œæ‰€ä»¥ä½ çš„æ™‚é–“è»¸ç›®å‰æ˜¯ç©ºçš„。" - step2_1: "在發文或追隨其他人之å‰å…ˆè®“我們è¨å®šä¸€ä¸‹å€‹äººè³‡æ–™å§ã€‚" - step2_2: "æä¾›ä¸€äº›é—œæ–¼è‡ªå·±çš„è³‡è¨Šä¾†è®“å…¶ä»–äººæ›´æœ‰è¿½éš¨ä½ çš„æ„願。" - step3_1: "個人資料都è¨å®šå¥½äº†å—Žï¼Ÿ" - step3_2: "接下來,讓我們來試試看發個文,按一下畫é¢ä¸Šçš„鉛ç†åœ–示來開始" - step3_3: "輸入完內容後,按視窗å³ä¸Šè§’的按鈕來發文" - step3_4: "ä¸çŸ¥é“該寫什麼內容嗎?試試看「開始使用Misskey了ã€å¦‚何。" - step4_1: "貼文發出去了嗎?" - step4_2: "å¦‚æžœä½ çš„è²¼æ–‡å‡ºç¾åœ¨æ™‚間軸上,就代表發文æˆåŠŸã€‚" - step5_1: "ç¾åœ¨è©¦è©¦çœ‹è¿½éš¨å…¶ä»–äººä¾†è®“ä½ çš„æ™‚é–“è»¸è®Šå¾—æ›´ç”Ÿå‹•å§ã€‚" - step5_2: "ä½ æœƒåœ¨{featured}上看到å—æ¡è¿Žçš„è²¼æ–‡ï¼Œä½ ä¹Ÿå¯ä»¥å¾žåˆ—表ä¸è¿½éš¨ä½ å–œæ¡çš„人,或者在{explore}上找到熱門使用者。" - step5_3: "想è¦è¿½éš¨å…¶ä»–人,åªè¦é»žæ“Šä»–們的大é 貼並按「追隨ã€å³å¯ã€‚" - step5_4: "如果使用者的åå—æ—有鎖é 的圖示,代表他們需è¦æ‰‹å‹•æ ¸å‡†ä½ 的追隨請求。" - step6_1: "ç¾åœ¨ä½ å¯ä»¥åœ¨æ™‚間軸上看到其他用戶的貼文。" - step6_2: "ä½ ä¹Ÿå¯ä»¥å°åˆ¥äººçš„貼文作出「å應ã€ï¼Œä½œå‡ºç°¡å–®çš„回覆。" - step6_3: "在他人的貼文按下\"+\"圖標,å³å¯é¸æ“‡å–œå¥½çš„表情符號進行回應。" - step7_1: "以上為Misskey的基本æ“作說明,教å¸åœ¨æ¤å‘Šä¸€æ®µè½ã€‚辛苦了。" - step7_2: "æ¡è¿Žåˆ°{help}來çžè§£æ›´å¤šMisskey相關介紹。" - step7_3: "那麼,ç¥æ‚¨åœ¨Misskey玩的開心~ 🚀" - step8_1: "最後,è¦ä¸è¦è©¦è©¦çœ‹å•Ÿç”¨æŽ¨æ’通知呢?" - step8_2: "é€éŽæŽ¥æ”¶æŽ¨æ’通知,å³ä½¿æ²’有打開Misskey,您也會知é“å應ã€è¿½éš¨èˆ‡æåŠçš„情æ³ã€‚" - step8_3: "通知的è¨å®šå¯ä»¥åœ¨ä¹‹å¾Œè®Šæ›´ã€‚" _2fa: alreadyRegistered: "æ¤è¨å‚™å·²ç¶“被註冊éŽäº†" registerTOTP: "開始è¨å®šé©—è‰æ‡‰ç”¨ç¨‹å¼" diff --git a/package.json b/package.json index a22bb2873f1c54fdd7b9c45c1773de594d3eadee..5b1d21b815dcdb2f1f78b80dd9988caae1f07bd6 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "misskey", - "version": "13.11.3", + "version": "13.12.0", "codename": "nasubi", "repository": { "type": "git", "url": "https://github.com/misskey-dev/misskey.git" }, - "packageManager": "pnpm@8.1.1", + "packageManager": "pnpm@8.3.1", "workspaces": [ "packages/frontend", "packages/backend", @@ -51,19 +51,19 @@ "gulp-replace": "1.1.4", "gulp-terser": "2.1.0", "js-yaml": "4.1.0", - "typescript": "5.0.3" + "typescript": "5.0.4" }, "devDependencies": { "@types/gulp": "4.0.10", "@types/gulp-rename": "2.0.1", - "@typescript-eslint/eslint-plugin": "5.57.1", - "@typescript-eslint/parser": "5.57.1", + "@typescript-eslint/eslint-plugin": "5.59.2", + "@typescript-eslint/parser": "5.59.2", "cross-env": "7.0.3", - "cypress": "12.9.0", - "eslint": "8.37.0", + "cypress": "12.11.0", + "eslint": "8.39.0", "start-server-and-test": "2.0.0" }, "optionalDependencies": { - "@tensorflow/tfjs-core": "4.2.0" + "@tensorflow/tfjs-core": "4.4.0" } } diff --git a/packages/backend/migration/1680702787050-UserMemo.js b/packages/backend/migration/1680702787050-UserMemo.js new file mode 100644 index 0000000000000000000000000000000000000000..7446bf8da5d758adbc71a94e12f7660cc7680861 --- /dev/null +++ b/packages/backend/migration/1680702787050-UserMemo.js @@ -0,0 +1,18 @@ +export class UserMemo1680702787050 { + name = 'UserMemo1680702787050' + + async up(queryRunner) { + await queryRunner.query(`CREATE TABLE "user_memo" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "targetUserId" character varying(32) NOT NULL, "memo" character varying(2048) NOT NULL, CONSTRAINT "PK_e9aaa58f7d3699a84d79078f4d9" PRIMARY KEY ("id")); COMMENT ON COLUMN "user_memo"."userId" IS 'The ID of author.'; COMMENT ON COLUMN "user_memo"."targetUserId" IS 'The ID of target user.'; COMMENT ON COLUMN "user_memo"."memo" IS 'Memo.'`); + await queryRunner.query(`CREATE INDEX "IDX_650b49c5639b5840ee6a2b8f83" ON "user_memo" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_66ac4a82894297fd09ba61f3d3" ON "user_memo" ("targetUserId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_faef300913c738265638ba3ebc" ON "user_memo" ("userId", "targetUserId") `); + await queryRunner.query(`ALTER TABLE "user_memo" ADD CONSTRAINT "FK_650b49c5639b5840ee6a2b8f83e" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_memo" ADD CONSTRAINT "FK_66ac4a82894297fd09ba61f3d35" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_memo" DROP CONSTRAINT "FK_66ac4a82894297fd09ba61f3d35"`); + await queryRunner.query(`ALTER TABLE "user_memo" DROP CONSTRAINT "FK_650b49c5639b5840ee6a2b8f83e"`); + await queryRunner.query(`DROP TABLE "user_memo"`); + } +} diff --git a/packages/backend/migration/1681400427971-serverRules.js b/packages/backend/migration/1681400427971-serverRules.js new file mode 100644 index 0000000000000000000000000000000000000000..2364e8e1d2a5190bf15b83de3bbe3f2bd6b8d6f8 --- /dev/null +++ b/packages/backend/migration/1681400427971-serverRules.js @@ -0,0 +1,11 @@ +export class ServerRules1681400427971 { + name = 'ServerRules1681400427971' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "serverRules" character varying(280) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "serverRules"`); + } +} diff --git a/packages/backend/migration/1681870960239-RoleTLSetting.js b/packages/backend/migration/1681870960239-RoleTLSetting.js new file mode 100644 index 0000000000000000000000000000000000000000..2280f44eaa10bb7d98cb4e624f5cfa28f4d9c196 --- /dev/null +++ b/packages/backend/migration/1681870960239-RoleTLSetting.js @@ -0,0 +1,12 @@ +export class RoleTLSetting1681870960239 { + name = 'RoleTLSetting1681870960239' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "role" ADD "isExplorable" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "isExplorable"`); + } + +} diff --git a/packages/backend/migration/1682190963894-movedAt.js b/packages/backend/migration/1682190963894-movedAt.js new file mode 100644 index 0000000000000000000000000000000000000000..1f8f030a5c2eee1a86235dccf7539bfb16b291da --- /dev/null +++ b/packages/backend/migration/1682190963894-movedAt.js @@ -0,0 +1,13 @@ +export class MovedAt1682190963894 { + name = 'MovedAt1682190963894' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ADD "movedAt" TIMESTAMP WITH TIME ZONE`); + await queryRunner.query(`COMMENT ON COLUMN "user"."movedAt" IS 'When the user moved to another account'`); + } + + async down(queryRunner) { + await queryRunner.query(`COMMENT ON COLUMN "user"."movedAt" IS 'When the user moved to another account'`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "movedAt"`); + } +} diff --git a/packages/backend/migration/1682754135458-preservedUsernames.js b/packages/backend/migration/1682754135458-preservedUsernames.js new file mode 100644 index 0000000000000000000000000000000000000000..46a0826f43635f325ce95de6c060fe3786a326b7 --- /dev/null +++ b/packages/backend/migration/1682754135458-preservedUsernames.js @@ -0,0 +1,11 @@ +export class PreservedUsernames1682754135458 { + name = 'PreservedUsernames1682754135458' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "preservedUsernames" character varying(1024) array NOT NULL DEFAULT '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "preservedUsernames"`); + } +} diff --git a/packages/backend/migration/1682985520254-channelColor.js b/packages/backend/migration/1682985520254-channelColor.js new file mode 100644 index 0000000000000000000000000000000000000000..294b7372b2db5702d50a52ba5cd1c1f88c50ca9d --- /dev/null +++ b/packages/backend/migration/1682985520254-channelColor.js @@ -0,0 +1,11 @@ +export class ChannelColor1682985520254 { + name = 'ChannelColor1682985520254' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "channel" ADD "color" character varying(16) NOT NULL DEFAULT '#86b300'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "channel" DROP COLUMN "color"`); + } +} diff --git a/packages/backend/migration/1683328299359-channelArchive.js b/packages/backend/migration/1683328299359-channelArchive.js new file mode 100644 index 0000000000000000000000000000000000000000..83695ff537f61f85c888cc50e0eb658c8b75e549 --- /dev/null +++ b/packages/backend/migration/1683328299359-channelArchive.js @@ -0,0 +1,13 @@ +export class ChannelArchive1683328299359 { + name = 'ChannelArchive1683328299359' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "channel" ADD "isArchived" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`CREATE INDEX "IDX_cc7c72974f1b2f385a8921f094" ON "channel" ("isArchived") `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_cc7c72974f1b2f385a8921f094"`); + await queryRunner.query(`ALTER TABLE "channel" DROP COLUMN "isArchived"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 875774bbd5a16b7f4c8829f04c47d769d15423c3..e0ece2bfe5193b948667f531531216f5e2fea28c 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -23,33 +23,44 @@ }, "optionalDependencies": { "@swc/core-android-arm64": "1.3.11", - "@swc/core-darwin-arm64": "1.3.46", - "@swc/core-darwin-x64": "1.3.46", - "@swc/core-linux-arm-gnueabihf": "1.3.46", - "@swc/core-linux-arm64-gnu": "1.3.46", - "@swc/core-linux-arm64-musl": "1.3.46", - "@swc/core-linux-x64-gnu": "1.3.46", - "@swc/core-linux-x64-musl": "1.3.46", - "@swc/core-win32-arm64-msvc": "1.3.46", - "@swc/core-win32-ia32-msvc": "1.3.46", - "@swc/core-win32-x64-msvc": "1.3.46", - "@tensorflow/tfjs": "4.2.0", - "@tensorflow/tfjs-node": "4.2.0" + "@swc/core-darwin-arm64": "1.3.56", + "@swc/core-darwin-x64": "1.3.56", + "@swc/core-linux-arm-gnueabihf": "1.3.56", + "@swc/core-linux-arm64-gnu": "1.3.56", + "@swc/core-linux-arm64-musl": "1.3.56", + "@swc/core-linux-x64-gnu": "1.3.56", + "@swc/core-linux-x64-musl": "1.3.56", + "@swc/core-win32-arm64-msvc": "1.3.56", + "@swc/core-win32-ia32-msvc": "1.3.56", + "@swc/core-win32-x64-msvc": "1.3.56", + "@tensorflow/tfjs": "4.4.0", + "@tensorflow/tfjs-node": "4.4.0", + "slacc-android-arm-eabi": "0.0.7", + "slacc-android-arm64": "0.0.7", + "slacc-darwin-arm64": "0.0.7", + "slacc-darwin-universal": "0.0.7", + "slacc-darwin-x64": "0.0.7", + "slacc-linux-arm-gnueabihf": "0.0.7", + "slacc-linux-arm64-gnu": "0.0.7", + "slacc-linux-arm64-musl": "0.0.7", + "slacc-linux-x64-gnu": "0.0.7", + "slacc-win32-arm64-msvc": "0.0.7", + "slacc-win32-x64-msvc": "0.0.7" }, "dependencies": { - "@aws-sdk/client-s3": "3.306.0", - "@aws-sdk/lib-storage": "3.306.0", - "@aws-sdk/node-http-handler": "3.306.0", - "@bull-board/api": "5.0.0", - "@bull-board/fastify": "5.0.0", - "@bull-board/ui": "5.0.0", + "@aws-sdk/client-s3": "3.321.1", + "@aws-sdk/lib-storage": "3.321.1", + "@aws-sdk/node-http-handler": "3.321.1", + "@bull-board/api": "5.1.2", + "@bull-board/fastify": "5.1.2", + "@bull-board/ui": "5.1.2", "@discordapp/twemoji": "14.1.2", "@fastify/accepts": "4.1.0", "@fastify/cookie": "8.3.0", "@fastify/cors": "8.2.1", "@fastify/http-proxy": "9.0.0", - "@fastify/multipart": "7.5.0", - "@fastify/static": "6.10.0", + "@fastify/multipart": "7.6.0", + "@fastify/static": "6.10.1", "@fastify/view": "7.4.1", "@nestjs/common": "9.4.0", "@nestjs/core": "9.4.0", @@ -57,7 +68,7 @@ "@peertube/http-signature": "1.7.0", "@sinonjs/fake-timers": "10.0.2", "@swc/cli": "0.1.62", - "@swc/core": "1.3.46", + "@swc/core": "1.3.56", "accepts": "1.3.8", "ajv": "8.12.0", "archiver": "5.3.1", @@ -73,25 +84,26 @@ "cli-highlight": "2.1.11", "color-convert": "2.0.1", "content-disposition": "0.5.4", - "date-fns": "2.29.3", + "date-fns": "2.30.0", "deep-email-validator": "0.1.21", "escape-regexp": "0.0.1", - "fastify": "4.15.0", + "fastify": "4.17.0", "feed": "4.2.2", - "file-type": "18.2.1", + "file-type": "18.3.0", "fluent-ffmpeg": "2.1.2", "form-data": "4.0.0", "got": "12.6.0", - "happy-dom": "8.9.0", + "happy-dom": "9.10.2", "hpagent": "1.2.0", - "ioredis": "4.28.5", + "ioredis": "5.3.2", "ip-cidr": "3.1.0", "is-svg": "4.3.2", "js-yaml": "4.1.0", "jsdom": "21.1.1", "json5": "2.2.3", "jsonld": "8.1.1", - "jsrsasign": "10.7.0", + "meilisearch": "0.32.3", + "jsrsasign": "10.8.6", "mfm-js": "0.23.3", "mime-types": "2.1.35", "misskey-js": "workspace:*", @@ -111,7 +123,7 @@ "pug": "3.0.2", "punycode": "2.3.0", "pureimage": "0.3.17", - "qrcode": "1.5.1", + "qrcode": "1.5.3", "random-seed": "0.3.0", "ratelimiter": "3.4.1", "re2": "1.18.0", @@ -119,37 +131,38 @@ "reflect-metadata": "0.1.13", "rename": "1.0.4", "rndstr": "1.0.0", - "rss-parser": "3.12.0", - "rxjs": "7.8.0", + "rss-parser": "3.13.0", + "rxjs": "7.8.1", "s-age": "1.1.2", "sanitize-html": "2.10.0", "seedrandom": "3.0.5", - "semver": "7.3.8", - "sharp": "0.32.0", + "semver": "7.5.0", + "sharp": "0.32.1", "sharp-read-bmp": "github:misskey-dev/sharp-read-bmp", + "slacc": "0.0.7", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", "summaly": "github:misskey-dev/summaly", "systeminformation": "5.17.12", "tinycolor2": "1.6.0", "tmp": "0.2.1", - "tsc-alias": "1.8.5", + "tsc-alias": "1.8.6", "tsconfig-paths": "4.2.0", "twemoji-parser": "14.0.0", - "typeorm": "0.3.13", - "typescript": "5.0.3", + "typeorm": "0.3.15", + "typescript": "5.0.4", "ulid": "2.3.0", "unzipper": "0.10.11", "uuid": "9.0.0", "vary": "1.1.2", - "web-push": "3.5.0", + "web-push": "3.6.1", "websocket": "1.0.34", "ws": "8.13.0", "xev": "3.0.2" }, "devDependencies": { "@jest/globals": "29.5.0", - "@swc/jest": "0.2.24", + "@swc/jest": "0.2.26", "@types/accepts": "1.3.5", "@types/archiver": "5.3.2", "@types/bcryptjs": "2.4.2", @@ -159,14 +172,13 @@ "@types/content-disposition": "0.5.5", "@types/escape-regexp": "0.0.1", "@types/fluent-ffmpeg": "2.1.21", - "@types/ioredis": "4.28.10", - "@types/jest": "29.5.0", + "@types/jest": "29.5.1", "@types/js-yaml": "4.0.5", "@types/jsdom": "21.1.1", "@types/jsonld": "1.5.8", "@types/jsrsasign": "10.5.8", "@types/mime-types": "2.1.1", - "@types/node": "18.15.11", + "@types/node": "18.16.3", "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.7", "@types/oauth": "0.9.1", @@ -180,7 +192,7 @@ "@types/rename": "1.0.4", "@types/sanitize-html": "2.9.0", "@types/semver": "7.3.13", - "@types/sharp": "0.31.1", + "@types/sharp": "0.32.0", "@types/sinonjs__fake-timers": "8.1.2", "@types/tinycolor2": "1.4.3", "@types/tmp": "0.2.3", @@ -190,11 +202,11 @@ "@types/web-push": "3.3.2", "@types/websocket": "1.0.5", "@types/ws": "8.5.4", - "@typescript-eslint/eslint-plugin": "5.57.1", - "@typescript-eslint/parser": "5.57.1", + "@typescript-eslint/eslint-plugin": "5.59.2", + "@typescript-eslint/parser": "5.59.2", "aws-sdk-client-mock": "^2.1.1", "cross-env": "7.0.3", - "eslint": "8.37.0", + "eslint": "8.39.0", "eslint-plugin-import": "2.27.5", "execa": "6.1.0", "jest": "29.5.0", diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts index 174d0d8bebcf2c1885154980e36c185ddea545e1..5fb4e8ef3cf9880a2d078cda438d5b81372016f5 100644 --- a/packages/backend/src/GlobalModule.ts +++ b/packages/backend/src/GlobalModule.ts @@ -1,7 +1,8 @@ import { setTimeout } from 'node:timers/promises'; import { Global, Inject, Module } from '@nestjs/common'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { DataSource } from 'typeorm'; +import { MeiliSearch } from 'meilisearch'; import { DI } from './di-symbols.js'; import { loadConfig } from './config.js'; import { createPostgresDataSource } from './postgres.js'; @@ -22,10 +23,25 @@ const $db: Provider = { inject: [DI.config], }; +const $meilisearch: Provider = { + provide: DI.meilisearch, + useFactory: (config) => { + if (config.meilisearch) { + return new MeiliSearch({ + host: `${config.meilisearch.ssl ? 'https' : 'http' }://${config.meilisearch.host}:${config.meilisearch.port}`, + apiKey: config.meilisearch.apiKey, + }); + } else { + return null; + } + }, + inject: [DI.config], +}; + const $redis: Provider = { provide: DI.redis, useFactory: (config) => { - return new Redis({ + return new Redis.Redis({ port: config.redis.port, host: config.redis.host, family: config.redis.family == null ? 0 : config.redis.family, @@ -40,7 +56,7 @@ const $redis: Provider = { const $redisForPub: Provider = { provide: DI.redisForPub, useFactory: (config) => { - const redis = new Redis({ + const redis = new Redis.Redis({ port: config.redisForPubsub.port, host: config.redisForPubsub.host, family: config.redisForPubsub.family == null ? 0 : config.redisForPubsub.family, @@ -56,7 +72,7 @@ const $redisForPub: Provider = { const $redisForSub: Provider = { provide: DI.redisForSub, useFactory: (config) => { - const redis = new Redis({ + const redis = new Redis.Redis({ port: config.redisForPubsub.port, host: config.redisForPubsub.host, family: config.redisForPubsub.family == null ? 0 : config.redisForPubsub.family, @@ -73,8 +89,8 @@ const $redisForSub: Provider = { @Global() @Module({ imports: [RepositoryModule], - providers: [$config, $db, $redis, $redisForPub, $redisForSub], - exports: [$config, $db, $redis, $redisForPub, $redisForSub, RepositoryModule], + providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub], + exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, RepositoryModule], }) export class GlobalModule implements OnApplicationShutdown { constructor( diff --git a/packages/backend/src/boot/common.ts b/packages/backend/src/boot/common.ts index 279a1fe59dc9f5f11e25191a464b54d457f6d9c0..45ded5495c3d4b1bf37ce0326ac0c767b899229d 100644 --- a/packages/backend/src/boot/common.ts +++ b/packages/backend/src/boot/common.ts @@ -34,4 +34,6 @@ export async function jobQueue() { jobQueue.get(QueueProcessorService).start(); jobQueue.get(ChartManagementService).start(); + + return jobQueue; } diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index e4f7601fa9637970872ad34755e136a67529a4f2..b41fb603bb002964a960718d0dde0dad7b3b37f1 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -4,7 +4,7 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; +import { dirname, resolve } from 'node:path'; import * as yaml from 'js-yaml'; /** @@ -57,13 +57,11 @@ export type Source = { db?: number; prefix?: string; }; - elasticsearch: { + meilisearch?: { host: string; - port: number; + port: string; + apiKey: string; ssl?: boolean; - user?: string; - pass?: string; - index?: string; }; proxy?: string; @@ -84,8 +82,10 @@ export type Source = { deliverJobConcurrency?: number; inboxJobConcurrency?: number; + relashionshipJobConcurrency?: number; deliverJobPerSec?: number; inboxJobPerSec?: number; + relashionshipJobPerSec?: number; deliverJobMaxAttempts?: number; inboxJobMaxAttempts?: number; @@ -132,9 +132,11 @@ const dir = `${_dirname}/../../../.config`; /** * Path of configuration file */ -const path = process.env.NODE_ENV === 'test' - ? `${dir}/test.yml` - : `${dir}/default.yml`; +const path = process.env.MISSKEY_CONFIG_YML + ? resolve(dir, process.env.MISSKEY_CONFIG_YML) + : process.env.NODE_ENV === 'test' + ? resolve(dir, 'test.yml') + : resolve(dir, 'default.yml'); export function loadConfig() { const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8')); diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts index 6c7f2142146e8a627aff382eb2584c6ac3b9de5a..ee1a9a3093c679f585b0f256a30df53fdd0696c6 100644 --- a/packages/backend/src/const.ts +++ b/packages/backend/src/const.ts @@ -56,6 +56,11 @@ export const FILE_TYPE_BROWSERSAFE = [ 'audio/webm', 'audio/aac', + + // see https://github.com/misskey-dev/misskey/pull/10686 + 'audio/flac', + 'audio/wav', + // backward compatibility 'audio/x-flac', 'audio/vnd.wave', ]; diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index 3f2a19b771c5534e47ede03e1f7dce8c43e961ea..ab11785e28ad3f5e802ed6cddddd9b59bf13775e 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -1,55 +1,90 @@ import { Inject, Injectable } from '@nestjs/common'; -import { IsNull } from 'typeorm'; +import { IsNull, In, MoreThan, Not } from 'typeorm'; import { bindThis } from '@/decorators.js'; import { DI } from '@/di-symbols.js'; -import type { LocalUser } from '@/models/entities/User.js'; -import { User } from '@/models/entities/User.js'; -import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import type { LocalUser, RemoteUser } from '@/models/entities/User.js'; +import type { BlockingsRepository, FollowingsRepository, InstancesRepository, Muting, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/index.js'; +import type { RelationshipJobData, ThinUser } from '@/queue/types.js'; +import type { User } from '@/models/entities/User.js'; +import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; -import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { QueueService } from '@/core/QueueService.js'; +import { RelayService } from '@/core/RelayService.js'; +import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { AccountUpdateService } from '@/core/AccountUpdateService.js'; -import { RelayService } from '@/core/RelayService.js'; +import { CacheService } from '@/core/CacheService.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.config) + private config: Config, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, + @Inject(DI.blockingsRepository) + private blockingsRepository: BlockingsRepository, + + @Inject(DI.mutingsRepository) + private mutingsRepository: MutingsRepository, + + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, + + @Inject(DI.instancesRepository) + private instancesRepository: InstancesRepository, + private userEntityService: UserEntityService, + private idService: IdService, + private apPersonService: ApPersonService, private apRendererService: ApRendererService, private apDeliverManagerService: ApDeliverManagerService, private globalEventService: GlobalEventService, - private userFollowingService: UserFollowingService, - private accountUpdateService: AccountUpdateService, + private proxyAccountService: ProxyAccountService, + private perUserFollowingChart: PerUserFollowingChart, + private federatedInstanceService: FederatedInstanceService, + private instanceChart: InstanceChart, + private metaService: MetaService, private relayService: RelayService, + private cacheService: CacheService, + private queueService: QueueService, ) { } /** - * Move a local account to a remote account. + * Move a local account to a new account. * * After delivering Move activity, its local followers unfollow the old account and then follow the new one. */ @bindThis - public async moveToRemote(src: LocalUser, dst: User): Promise<unknown> { - // Make sure that the destination is a remote account. - if (this.userEntityService.isLocalUser(dst)) throw new Error('move destiantion is not remote'); - if (!dst.uri) throw new Error('destination uri is empty'); + public async moveFromLocal(src: LocalUser, dst: LocalUser | RemoteUser): Promise<unknown> { + const srcUri = this.userEntityService.getUserUri(src); + const dstUri = this.userEntityService.getUserUri(dst); // add movedToUri to indicate that the user has moved - const update = {} as Partial<User>; - update.alsoKnownAs = src.alsoKnownAs?.concat([dst.uri]) ?? [dst.uri]; - update.movedToUri = dst.uri; + const update = {} as Partial<LocalUser>; + update.alsoKnownAs = src.alsoKnownAs?.includes(dstUri) ? src.alsoKnownAs : src.alsoKnownAs?.concat([dstUri]) ?? [dstUri]; + update.movedToUri = dstUri; + update.movedAt = new Date(); await this.usersRepository.update(src.id, update); + Object.assign(src, update); + + // Update cache + this.cacheService.uriPersonCache.set(srcUri, src); const srcPerson = await this.apRendererService.renderPerson(src); const updateAct = this.apRendererService.addContext(this.apRendererService.renderUpdate(srcPerson, src)); @@ -64,51 +99,249 @@ export class AccountMoveService { const iObj = await this.userEntityService.pack<true, true>(src.id, src, { detail: true, includeSecrets: true }); this.globalEventService.publishMainStream(src.id, 'meUpdated', iObj); - // follow the new account and unfollow the old one - const followings = await this.followingsRepository.find({ - relations: { - follower: true, - }, + // Unfollow after 24 hours + const followings = await this.followingsRepository.findBy({ + followerId: src.id, + }); + this.queueService.createDelayedUnfollowJob(followings.map(following => ({ + from: { id: src.id }, + to: { id: following.followeeId }, + })), process.env.NODE_ENV === 'test' ? 10000 : 1000 * 60 * 60 * 24); + + await this.postMoveProcess(src, dst); + + return iObj; + } + + @bindThis + public async postMoveProcess(src: User, dst: User): Promise<void> { + // Copy blockings and mutings, and update lists + try { + await Promise.all([ + this.copyBlocking(src, dst), + this.copyMutings(src, dst), + this.updateLists(src, dst), + ]); + } catch { + /* skip if any error happens */ + } + + // follow the new account + const proxy = await this.proxyAccountService.fetch(); + const followings = await this.followingsRepository.findBy({ + followeeId: src.id, + followerHost: IsNull(), // follower is local + followerId: proxy ? Not(proxy.id) : undefined, + }); + const followJobs = followings.map(following => ({ + from: { id: following.followerId }, + to: { id: dst.id }, + })) as RelationshipJobData[]; + + // Decrease following count instead of unfollowing. + try { + await this.adjustFollowingCounts(followJobs.map(job => job.from.id), src); + } catch { + /* skip if any error happens */ + } + + // Should be queued because this can cause a number of follow per one move. + this.queueService.createFollowJob(followJobs); + } + + @bindThis + public async copyBlocking(src: ThinUser, dst: ThinUser): Promise<void> { + // Followers shouldn't overlap with blockers, but the destination account, different from the blockee (i.e., old account), may have followed the local user before moving. + // So block the destination account here. + const srcBlockings = await this.blockingsRepository.findBy({ blockeeId: src.id }); + const dstBlockings = await this.blockingsRepository.findBy({ blockeeId: dst.id }); + const blockerIds = dstBlockings.map(blocking => blocking.blockerId); + // reblock the destination account + const blockJobs: RelationshipJobData[] = []; + for (const blocking of srcBlockings) { + if (blockerIds.includes(blocking.blockerId)) continue; // skip if already blocked + blockJobs.push({ from: { id: blocking.blockerId }, to: { id: dst.id } }); + } + // no need to unblock the old account because it may be still functional + this.queueService.createBlockJob(blockJobs); + } + + @bindThis + public async copyMutings(src: ThinUser, dst: ThinUser): Promise<void> { + // Insert new mutings with the same values except mutee + const oldMutings = await this.mutingsRepository.findBy([ + { muteeId: src.id, expiresAt: IsNull() }, + { muteeId: src.id, expiresAt: MoreThan(new Date()) }, + ]); + if (oldMutings.length === 0) return; + + // Check if the destination account is already indefinitely muted by the muter + const existingMutingsMuterUserIds = await this.mutingsRepository.findBy( + { muteeId: dst.id, expiresAt: IsNull() }, + ).then(mutings => mutings.map(muting => muting.muterId)); + + const newMutings: Map<string, { muterId: string; muteeId: string; createdAt: Date; expiresAt: Date | null; }> = new Map(); + + // é‡è¤‡ã—ãªã„よã†ã«IDã‚’ç”Ÿæˆ + const genId = (): string => { + let id: string; + do { + id = this.idService.genId(); + } while (newMutings.has(id)); + return id; + }; + for (const muting of oldMutings) { + if (existingMutingsMuterUserIds.includes(muting.muterId)) continue; // skip if already muted indefinitely + newMutings.set(genId(), { + ...muting, + createdAt: new Date(), + muteeId: dst.id, + }); + } + + const arrayToInsert = Array.from(newMutings.entries()).map(entry => ({ ...entry[1], id: entry[0] })); + await this.mutingsRepository.insert(arrayToInsert); + } + + /** + * Update lists while moving accounts. + * - No removal of the old account from the lists + * - Users number limit is not checked + * + * @param src ThinUser (old account) + * @param dst User (new account) + * @returns Promise<void> + */ + @bindThis + public async updateLists(src: ThinUser, dst: User): Promise<void> { + // Return if there is no list to be updated. + const oldJoinings = await this.userListJoiningsRepository.find({ where: { - followeeId: src.id, - followerHost: IsNull(), // follower is local + userId: src.id, }, }); - for (const following of followings) { - if (!following.follower) continue; - try { - await this.userFollowingService.follow(following.follower, dst); - await this.userFollowingService.unfollow(following.follower, src); - } catch { - /* empty */ + if (oldJoinings.length === 0) return; + + const existingUserListIds = await this.userListJoiningsRepository.find({ + where: { + userId: dst.id, + }, + }).then(joinings => joinings.map(joining => joining.userListId)); + + const newJoinings: Map<string, { createdAt: Date; userId: string; userListId: string; }> = new Map(); + + // é‡è¤‡ã—ãªã„よã†ã«IDã‚’ç”Ÿæˆ + const genId = (): string => { + let id: string; + do { + id = this.idService.genId(); + } while (newJoinings.has(id)); + return id; + }; + for (const joining of oldJoinings) { + if (existingUserListIds.includes(joining.userListId)) continue; // skip if dst exists in this user's list + newJoinings.set(genId(), { + createdAt: new Date(), + userId: dst.id, + userListId: joining.userListId, + }); + } + + const arrayToInsert = Array.from(newJoinings.entries()).map(entry => ({ ...entry[1], id: entry[0] })); + await this.userListJoiningsRepository.insert(arrayToInsert); + + // Have the proxy account follow the new account in the same way as UserListService.push + if (this.userEntityService.isRemoteUser(dst)) { + const proxy = await this.proxyAccountService.fetch(); + if (proxy) { + this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: dst.id } }]); } } + } - return iObj; + @bindThis + private async adjustFollowingCounts(localFollowerIds: string[], oldAccount: User): Promise<void> { + if (localFollowerIds.length === 0) return; + + // Set the old account's following and followers counts to 0. + await this.usersRepository.update({ id: oldAccount.id }, { followersCount: 0, followingCount: 0 }); + + // Decrease following counts of local followers by 1. + await this.usersRepository.decrement({ id: In(localFollowerIds) }, 'followingCount', 1); + + // Decrease follower counts of local followees by 1. + const oldFollowings = await this.followingsRepository.findBy({ followerId: oldAccount.id }); + if (oldFollowings.length > 0) { + await this.usersRepository.decrement({ id: In(oldFollowings.map(following => following.followeeId)) }, 'followersCount', 1); + } + + // Update instance stats by decreasing remote followers count by the number of local followers who were following the old account. + 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) { + this.instanceChart.updateFollowers(i.host, false); + } + }); + } + + // FIXME: expensive? + for (const followerId of localFollowerIds) { + this.perUserFollowingChart.update({ id: followerId, host: null }, oldAccount, false); + } } /** - * Create an alias of an old remote account. + * dstユーザーã®alsoKnownAsã‚’fetchPersonã—ã¦ã„ãã€æœ¬å½“ã«movedToUrlã‚’dstã«æŒ‡å®šã™ã‚‹ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒå˜åœ¨ã™ã‚‹ã®ã‹ã‚’調ã¹ã‚‹ * - * The user's new profile will be published to the followers. + * @param dst movedToUrlを指定ã™ã‚‹ãƒ¦ãƒ¼ã‚¶ãƒ¼ + * @param check + * @param instant checkãŒtrueã§ã‚るユーザーãŒæœ€åˆã«è¦‹ã¤ã‹ã£ãŸã‚‰å³åº§ã«returnã™ã‚‹ã‹ã©ã†ã‹ + * @returns Promise<LocalUser | RemoteUser | null> */ @bindThis - public async createAlias(me: LocalUser, updates: Partial<User>): Promise<unknown> { - await this.usersRepository.update(me.id, updates); + public async validateAlsoKnownAs( + dst: LocalUser | RemoteUser, + check: (oldUser: LocalUser | RemoteUser | null, newUser: LocalUser | RemoteUser) => boolean | Promise<boolean> = () => true, + instant = false, + ): Promise<LocalUser | RemoteUser | null> { + let resultUser: LocalUser | RemoteUser | null = null; - // Publish meUpdated event - const iObj = await this.userEntityService.pack<true, true>(me.id, me, { - detail: true, - includeSecrets: true, - }); - this.globalEventService.publishMainStream(me.id, 'meUpdated', iObj); - - if (me.isLocked === false) { - await this.userFollowingService.acceptAllFollowRequests(me); + if (this.userEntityService.isRemoteUser(dst)) { + if ((new Date()).getTime() - (dst.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) { + await this.apPersonService.updatePerson(dst.uri); + } + dst = await this.apPersonService.fetchPerson(dst.uri) ?? dst; } - this.accountUpdateService.publishToFollowers(me.id); + if (!dst.alsoKnownAs || dst.alsoKnownAs.length === 0) return null; - return iObj; + const dstUri = this.userEntityService.getUserUri(dst); + + for (const srcUri of dst.alsoKnownAs) { + try { + let src = await this.apPersonService.fetchPerson(srcUri); + if (!src) continue; // oldAccountを探ã—ã¦ã‚‚ã“ã®ã‚µãƒ¼ãƒãƒ¼ã«å˜åœ¨ã—ãªã„å ´åˆã¯ãƒ•ã‚©ãƒãƒ¼é–¢ä¿‚ã‚‚ãªã„ã¨ã„ã†ã“ã¨ãªã®ã§ã‚¹ãƒ«ãƒ¼ + + if (this.userEntityService.isRemoteUser(dst)) { + if ((new Date()).getTime() - (src.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) { + await this.apPersonService.updatePerson(srcUri); + } + + src = await this.apPersonService.fetchPerson(srcUri) ?? src; + } + + if (src.movedToUri === dstUri) { + if (await check(resultUser, src)) { + resultUser = src; + } + if (instant && resultUser) return resultUser; + } + } catch { + /* skip if any error happens */ + } + } + + return resultUser; } } diff --git a/packages/backend/src/core/AchievementService.ts b/packages/backend/src/core/AchievementService.ts index 1ca38d8bb0556fd1c7e58b67244ecfe27038c71a..9e223f14922b6b38cc682d9faf2f09dc1061e28a 100644 --- a/packages/backend/src/core/AchievementService.ts +++ b/packages/backend/src/core/AchievementService.ts @@ -64,6 +64,7 @@ export const ACHIEVEMENT_TYPES = [ 'iLoveMisskey', 'foundTreasure', 'client30min', + 'client60min', 'noteDeletedWithin1min', 'postedAtLateNight', 'postedAt0min0sec', diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 166c78f479ac0778c7b273326b6ff616286cf45d..2d4226a32de374dd34d94f431e8995da4c55d296 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import type { Antenna } from '@/models/entities/Antenna.js'; import type { Note } from '@/models/entities/Note.js'; import type { User } from '@/models/entities/User.js'; diff --git a/packages/backend/src/core/AppLockService.ts b/packages/backend/src/core/AppLockService.ts index ee179b7f01f6cd2de6f3218dbaa9043b6852b14e..8dd805552b4e64c9839b1187d7b632540aec72a6 100644 --- a/packages/backend/src/core/AppLockService.ts +++ b/packages/backend/src/core/AppLockService.ts @@ -1,7 +1,7 @@ import { promisify } from 'node:util'; import { Inject, Injectable } from '@nestjs/common'; import redisLock from 'redis-lock'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts index 561face5c35825f3a2dbaf774149bd9b118ba0ef..cf1e81ffc821648f3dd4b9ddd75bb152fb94a43c 100644 --- a/packages/backend/src/core/CacheService.ts +++ b/packages/backend/src/core/CacheService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, UserProfile, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js'; import type { LocalUser, User } from '@/models/entities/User.js'; diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 8775536e4a3d53d44855197ca6085d97da8e3029..d3a1b1b024b3bb6e4b99e3a430c668371097bdca 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -50,6 +50,7 @@ import { WebhookService } from './WebhookService.js'; import { ProxyAccountService } from './ProxyAccountService.js'; import { UtilityService } from './UtilityService.js'; import { FileInfoService } from './FileInfoService.js'; +import { SearchService } from './SearchService.js'; import { ChartLoggerService } from './chart/ChartLoggerService.js'; import FederationChart from './chart/charts/federation.js'; import NotesChart from './chart/charts/notes.js'; @@ -171,6 +172,8 @@ const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', u const $WebhookService: Provider = { provide: 'WebhookService', useExisting: WebhookService }; const $UtilityService: Provider = { provide: 'UtilityService', useExisting: UtilityService }; const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService }; +const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService }; + const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService }; const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart }; const $NotesChart: Provider = { provide: 'NotesChart', useExisting: NotesChart }; @@ -295,6 +298,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting WebhookService, UtilityService, FileInfoService, + SearchService, ChartLoggerService, FederationChart, NotesChart, @@ -413,6 +417,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $WebhookService, $UtilityService, $FileInfoService, + $SearchService, $ChartLoggerService, $FederationChart, $NotesChart, @@ -532,6 +537,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting WebhookService, UtilityService, FileInfoService, + SearchService, FederationChart, NotesChart, UsersChart, @@ -649,6 +655,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $WebhookService, $UtilityService, $FileInfoService, + $SearchService, $FederationChart, $NotesChart, $UsersChart, diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 185171dee2396781a5415ad4be3b5f166b8651be..93557ce617998ffc0b79bd35588fcb4eadba0b59 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource, In, IsNull } from 'typeorm'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; @@ -197,6 +197,22 @@ export class CustomEmojiService { emojis: await this.emojiEntityService.packDetailedMany(ids), }); } + + @bindThis + public async setLicenseBulk(ids: Emoji['id'][], license: string | null) { + await this.emojisRepository.update({ + id: In(ids), + }, { + updatedAt: new Date(), + license: license, + }); + + this.localEmojisCache.refresh(); + + this.globalEventService.publishBroadcastStream('emojiUpdated', { + emojis: await this.emojiEntityService.packDetailedMany(ids), + }); + } @bindThis public async delete(id: Emoji['id']) { diff --git a/packages/backend/src/core/DownloadService.ts b/packages/backend/src/core/DownloadService.ts index bd999c67da0ea982714e0481a0fec95f0ee59315..bd535c603270c78bd000704cd1b50f4e035f3f4e 100644 --- a/packages/backend/src/core/DownloadService.ts +++ b/packages/backend/src/core/DownloadService.ts @@ -86,9 +86,13 @@ export class DownloadService { const contentDisposition = res.headers['content-disposition']; if (contentDisposition != null) { - const parsed = parse(contentDisposition); - if (parsed.parameters.filename) { - filename = parsed.parameters.filename; + try { + const parsed = parse(contentDisposition); + if (parsed.parameters.filename) { + filename = parsed.parameters.filename; + } + } catch (e) { + this.logger.warn(`Failed to parse content-disposition: ${contentDisposition}`, { stack: e }); } } }).on('downloadProgress', (progress: Got.Progress) => { diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index c6258474ec055654ea571e05d12c3c61ccc15f1f..1483b55469dbe7a350ba4373ea97d6c77afdebfe 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -59,6 +59,8 @@ type AddFileArgs = { uri?: string | null; /** Mark file as sensitive */ sensitive?: boolean | null; + /** Extension to force */ + ext?: string | null; requestIp?: string | null; requestHeaders?: Record<string, string> | null; @@ -125,7 +127,7 @@ export class DriveService { /*** * Save file * @param path Path for original - * @param name Name for original + * @param name Name for original (should be extention corrected) * @param type Content-Type for original * @param hash Hash for original * @param size Size for original @@ -151,7 +153,7 @@ export class DriveService { } // æ‹¡å¼µåã‹ã‚‰Content-Typeã‚’è¨å®šã—ã¦ãã†ãªæŒ™å‹•ã‚’示ã™ã‚ªãƒ–ジェクトストレージ (upcloud?) ã‚‚å˜åœ¨ã™ã‚‹ã®ã§ã€ - // 許å¯ã•ã‚Œã¦ã„るファイル形å¼ã§ã—ã‹æ‹¡å¼µåã‚’ã¤ã‘ãªã„ + // 許å¯ã•ã‚Œã¦ã„るファイル形å¼ã§ã—ã‹URLã«æ‹¡å¼µåã‚’ã¤ã‘ãªã„ if (!FILE_TYPE_BROWSERSAFE.includes(type)) { ext = ''; } @@ -173,7 +175,7 @@ export class DriveService { //#region Uploads this.registerLogger.info(`uploading original: ${key}`); const uploads = [ - this.upload(key, fs.createReadStream(path), type, ext, name), + this.upload(key, fs.createReadStream(path), type, null, name), ]; if (alts.webpublic) { @@ -189,7 +191,7 @@ export class DriveService { thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`; this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`); - uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type, alts.thumbnail.ext)); + uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type, alts.thumbnail.ext, `${name}.thumbnail`)); } await Promise.all(uploads); @@ -396,8 +398,9 @@ export class DriveService { ); } + // Expire oldest file (without avatar or banner) of remote user @bindThis - private async deleteOldFile(user: RemoteUser) { + private async expireOldFile(user: RemoteUser, driveCapacity: number) { const q = this.driveFilesRepository.createQueryBuilder('file') .where('file.userId = :userId', { userId: user.id }) .andWhere('file.isLink = FALSE'); @@ -410,12 +413,17 @@ export class DriveService { q.andWhere('file.id != :bannerId', { bannerId: user.bannerId }); } + //This selete is hard coded, be careful if change database schema + q.addSelect('SUM("file"."size") OVER (ORDER BY "file"."id" DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)', 'acc_usage'); q.orderBy('file.id', 'ASC'); - const oldFile = await q.getOne(); + const fileList = await q.getRawMany(); + const exceedFileIds = fileList.filter((x: any) => x.acc_usage > driveCapacity).map((x: any) => x.file_id); - if (oldFile) { - this.deleteFile(oldFile, true); + for (const fileId of exceedFileIds) { + const file = await this.driveFilesRepository.findOneBy({ id: fileId }); + if (file == null) continue; + this.deleteFile(file, true); } } @@ -437,10 +445,16 @@ export class DriveService { sensitive = null, requestIp = null, requestHeaders = null, + ext = null, }: AddFileArgs): Promise<DriveFile> { let skipNsfwCheck = false; const instance = await this.metaService.fetch(); - if (user == null) skipNsfwCheck = true; + 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; @@ -468,7 +482,7 @@ export class DriveService { // DriveFile.nameã¯256æ–‡å—, validateFileNameã¯200æ–‡å—制é™ã§ã‚ã‚‹ãŸã‚〠// extã‚’ä»˜åŠ ã—ã¦ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã®æ–‡å—数制é™ã«å½“ãŸã‚‹ã“ã¨ã¯ã¾ãšãªã„ (name && this.driveFileEntityService.validateFileName(name)) ? name : 'untitled', - info.type.ext, + ext ?? info.type.ext, ); if (user && !force) { @@ -489,22 +503,19 @@ export class DriveService { //#region Check drive usage if (user && !isLink) { const usage = await this.driveFileEntityService.calcDriveUsageOf(user); + const isLocalUser = this.userEntityService.isLocalUser(user); const policies = await this.roleService.getUserPolicies(user.id); const driveCapacity = 1024 * 1024 * policies.driveCapacityMb; this.registerLogger.debug('drive capacity override applied'); this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`); - this.registerLogger.debug(`drive usage is ${usage} (max: ${driveCapacity})`); - // If usage limit exceeded - if (usage + info.size > driveCapacity) { - if (this.userEntityService.isLocalUser(user)) { + if (driveCapacity < usage + info.size) { + if (isLocalUser) { throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.'); - } else { - // (ã‚¢ãƒã‚¿ãƒ¼ã¾ãŸã¯ãƒãƒŠãƒ¼ã‚’å«ã¾ãš)最もå¤ã„ファイルを削除ã™ã‚‹ - this.deleteOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as RemoteUser); } + await this.expireOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as RemoteUser, driveCapacity - info.size); } } //#endregion @@ -565,6 +576,7 @@ export class DriveService { if (info.sensitive && profile!.autoSensitive) file.isSensitive = true; if (info.sensitive && instance.setSensitiveFlagAutomatically) file.isSensitive = true; + if (userRoleNSFW) file.isSensitive = true; if (url !== null) { file.src = url; diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index 56660ae0d07715117a176cb98728b6656e5dbe84..2049bd4c60b99dca8cdbbe42c7bb2f646bd19d67 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import type { InstancesRepository } from '@/models/index.js'; import type { Instance } from '@/models/entities/Instance.js'; import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js'; @@ -23,8 +23,8 @@ export class FederatedInstanceService { private idService: IdService, ) { this.federatedInstanceCache = new RedisKVCache<Instance | null>(this.redisClient, 'federatedInstance', { - lifetime: 1000 * 60 * 60 * 24, // 24h - memoryCacheLifetime: 1000 * 60 * 30, // 30m + lifetime: 1000 * 60 * 30, // 30m + memoryCacheLifetime: 1000 * 60 * 3, // 3m fetcher: (key) => this.instancesRepository.findOneBy({ host: key }), toRedisConverter: (value) => JSON.stringify(value), fromRedisConverter: (value) => { @@ -65,15 +65,18 @@ export class FederatedInstanceService { } @bindThis - public async updateCachePartial(host: string, data: Partial<Instance>): Promise<void> { - host = this.utilityService.toPuny(host); - - const cached = await this.federatedInstanceCache.get(host); - if (cached == null) return; + public async update(id: Instance['id'], data: Partial<Instance>): Promise<void> { + const result = await this.instancesRepository.createQueryBuilder().update() + .set(data) + .where('id = :id', { id }) + .returning('*') + .execute() + .then((response) => { + return response.raw[0]; + }); + + const updated = result.raw[0]; - this.federatedInstanceCache.set(host, { - ...cached, - ...data, - }); + this.federatedInstanceCache.set(updated.host, updated); } } diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index bbc8b4332e87153dc56a213703bbe48330a743dd..8103d5afe9725aefa9a26f8f625831803f39e715 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -10,6 +10,7 @@ import { DI } from '@/di-symbols.js'; import { LoggerService } from '@/core/LoggerService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { bindThis } from '@/decorators.js'; +import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import type { DOMWindow } from 'jsdom'; type NodeInfo = { @@ -42,6 +43,7 @@ export class FetchInstanceMetadataService { private appLockService: AppLockService, private httpRequestService: HttpRequestService, private loggerService: LoggerService, + private federatedInstanceService: FederatedInstanceService, ) { this.logger = this.loggerService.getLogger('metadata', 'cyan'); } @@ -96,7 +98,7 @@ export class FetchInstanceMetadataService { if (favicon) updates.faviconUrl = favicon; if (themeColor) updates.themeColor = themeColor; - await this.instancesRepository.update(instance.id, updates); + await this.federatedInstanceService.update(instance.id, updates); this.logger.succ(`Successfuly updated metadata of ${instance.host}`); } catch (e) { diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts index e39b134b7ee1336eafc8412ffd08526fcdd80108..b6cae5ea757bc041d655c2ba855118d72f611e06 100644 --- a/packages/backend/src/core/FileInfoService.ts +++ b/packages/backend/src/core/FileInfoService.ts @@ -5,7 +5,7 @@ import * as stream from 'node:stream'; import * as util from 'node:util'; import { Injectable } from '@nestjs/common'; import { FSWatcher } from 'chokidar'; -import { fileTypeFromFile } from 'file-type'; +import * as fileType from 'file-type'; import FFmpeg from 'fluent-ffmpeg'; import isSvg from 'is-svg'; import probeImageSize from 'probe-image-size'; @@ -301,21 +301,34 @@ export class FileInfoService { return fs.promises.access(path).then(() => true, () => false); } + @bindThis + public fixMime(mime: string | fileType.MimeType): string { + // see https://github.com/misskey-dev/misskey/pull/10686 + if (mime === "audio/x-flac") { + return "audio/flac"; + } + if (mime === "audio/vnd.wave") { + return "audio/wav"; + } + + return mime; + } + /** * Detect MIME Type and extension */ @bindThis public async detectType(path: string): Promise<{ - mime: string; - ext: string | null; -}> { + mime: string; + ext: string | null; + }> { // Check 0 byte const fileSize = await this.getFileSize(path); if (fileSize === 0) { return TYPE_OCTET_STREAM; } - const type = await fileTypeFromFile(path); + const type = await fileType.fileTypeFromFile(path); if (type) { // XMLã¯SVGã‹ã‚‚ã—ã‚Œãªã„ @@ -324,7 +337,7 @@ export class FileInfoService { } return { - mime: type.mime, + mime: this.fixMime(type.mime), ext: type.ext, }; } diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index 2c2687a90ce2d74ff73ac9187643bac9e68dc483..0ed5241148755948241d4698654c7844e5efcf01 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import type { User } from '@/models/entities/User.js'; import type { Note } from '@/models/entities/Note.js'; import type { UserList } from '@/models/entities/UserList.js'; diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index 1322927c2c646eee33e95d47d5771f191c39dd8b..0b861be8d0475b55a8725ba7cb76d796f7491b5f 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import { Meta } from '@/models/entities/Meta.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 79629cb2a83cfe9874757234904ccc4338ab30e5..364976e4a79f70d8f49070fc20cf276fa26b8121 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -1,7 +1,7 @@ import { setImmediate } from 'node:timers/promises'; import * as mfm from 'mfm-js'; import { In, DataSource } from 'typeorm'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { extractMentions } from '@/misc/extract-mentions.js'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; @@ -46,6 +46,7 @@ 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'; const mutedWordsCache = new MemorySingleCache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5); @@ -198,6 +199,7 @@ export class NoteCreateService implements OnApplicationShutdown { private apRendererService: ApRendererService, private roleService: RoleService, private metaService: MetaService, + private searchService: SearchService, private notesChart: NotesChart, private perUserNotesChart: PerUserNotesChart, private activeUsersChart: ActiveUsersChart, @@ -728,17 +730,9 @@ export class NoteCreateService implements OnApplicationShutdown { @bindThis private index(note: Note) { - if (note.text == null || this.config.elasticsearch == null) return; - /* - es!.index({ - index: this.config.elasticsearch.index ?? 'misskey_note', - id: note.id.toString(), - body: { - text: normalizeForSearch(note.text), - userId: note.userId, - userHost: note.userHost, - }, - });*/ + if (note.text == null && note.cw == null) return; + + this.searchService.indexNote(note); } @bindThis diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index 6691c42836d49c2be44df7b90797322fc0dc56fd..a245908c981cddeb371e9f902da5fcea60198fe8 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -1,5 +1,5 @@ import { setTimeout } from 'node:timers/promises'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; @@ -111,7 +111,7 @@ export class NotificationService implements OnApplicationShutdown { // 2秒経ã£ã¦ã‚‚(今回作æˆã—ãŸ)通知ãŒæ—¢èªã«ãªã‚‰ãªã‹ã£ãŸã‚‰ã€Œæœªèªã®é€šçŸ¥ãŒã‚ã‚Šã¾ã™ã‚ˆã€ã‚¤ãƒ™ãƒ³ãƒˆã‚’発行ã™ã‚‹ setTimeout(2000, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => { const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${notifieeId}`); - if (latestReadNotificationId && (latestReadNotificationId >= await redisIdPromise)) return; + if (latestReadNotificationId && (latestReadNotificationId >= (await redisIdPromise)!)) return; this.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed); this.pushNotificationService.pushNotification(notifieeId, 'notification', packed); diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index 9b44cf64133a186a0617911997a9f65d0506c5cb..a4c569bdec60d95873c4fcc7de34455f5e3ed5a9 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import push from 'web-push'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import type { Packed } from '@/misc/json-schema'; diff --git a/packages/backend/src/core/QueueModule.ts b/packages/backend/src/core/QueueModule.ts index bac85d7a15f6873889f9753bb650801364883c4b..d4905a5f88ac32762ee8e454433dc7d550ba3acf 100644 --- a/packages/backend/src/core/QueueModule.ts +++ b/packages/backend/src/core/QueueModule.ts @@ -78,7 +78,7 @@ const $db: Provider = { const $relationship: Provider = { provide: 'queue:relationship', - useFactory: (config: Config) => q(config, 'relationship'), + useFactory: (config: Config) => q(config, 'relationship', config.relashionshipJobPerSec ?? 64), inject: [DI.config], }; diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 375ac499117270c0704c8dd3e04ce1ae3b93ed75..b4ffffecc0a65371b057fdc568d97011138ecf06 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -1,15 +1,16 @@ import { Inject, Injectable } from '@nestjs/common'; import { v4 as uuid } from 'uuid'; +import Bull from 'bull'; import type { IActivity } from '@/core/activitypub/type.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Webhook, webhookEventTypes } from '@/models/entities/Webhook.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; +import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js'; import type { DbJobData, RelationshipJobData, ThinUser } from '../queue/types.js'; import type httpSignature from '@peertube/http-signature'; -import Bull from 'bull'; @Injectable() export class QueueService { @@ -152,6 +153,16 @@ export class QueueService { }); } + @bindThis + public createExportAntennasJob(user: ThinUser) { + return this.dbQueue.add('exportAntennas', { + user: { id: user.id }, + }, { + removeOnComplete: true, + removeOnFail: true, + }); + } + @bindThis public createImportFollowingJob(user: ThinUser, fileId: DriveFile['id']) { return this.dbQueue.add('importFollowing', { @@ -235,6 +246,17 @@ export class QueueService { }); } + @bindThis + public createImportAntennasJob(user: ThinUser, antenna: Antenna) { + return this.dbQueue.add('importAntennas', { + user: { id: user.id }, + antenna, + }, { + removeOnComplete: true, + removeOnFail: true, + }); + } + @bindThis public createDeleteAccountJob(user: ThinUser, opts: { soft?: boolean; } = {}) { return this.dbQueue.add('deleteAccount', { @@ -258,6 +280,12 @@ export class QueueService { return this.relationshipQueue.addBulk(jobs); } + @bindThis + public createDelayedUnfollowJob(followings: { from: ThinUser, to: ThinUser, requestId?: string }[], delay: number) { + const jobs = followings.map(rel => this.generateRelationshipJobData('unfollow', rel, { delay })); + return this.relationshipQueue.addBulk(jobs); + } + @bindThis public createBlockJob(blockings: { from: ThinUser, to: ThinUser, silent?: boolean }[]) { const jobs = blockings.map(rel => this.generateRelationshipJobData('block', rel)); @@ -271,7 +299,7 @@ export class QueueService { } @bindThis - private generateRelationshipJobData(name: 'follow' | 'unfollow' | 'block' | 'unblock', data: RelationshipJobData): { + private generateRelationshipJobData(name: 'follow' | 'unfollow' | 'block' | 'unblock', data: RelationshipJobData, opts: Bull.JobOptions = {}): { name: string, data: RelationshipJobData, opts: Bull.JobOptions, @@ -287,6 +315,7 @@ export class QueueService { opts: { removeOnComplete: true, removeOnFail: true, + ...opts, }, }; } diff --git a/packages/backend/src/core/RemoteUserResolveService.ts b/packages/backend/src/core/RemoteUserResolveService.ts index b72dce5180758e76306ee6c3e325e1ef5f82f62f..ff68c242191dce10f799c0a803acf0aab5c2b4d6 100644 --- a/packages/backend/src/core/RemoteUserResolveService.ts +++ b/packages/backend/src/core/RemoteUserResolveService.ts @@ -4,7 +4,7 @@ import chalk from 'chalk'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { UsersRepository } from '@/models/index.js'; -import type { RemoteUser, User } from '@/models/entities/User.js'; +import type { LocalUser, RemoteUser } from '@/models/entities/User.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -33,7 +33,7 @@ export class RemoteUserResolveService { } @bindThis - public async resolveUser(username: string, host: string | null): Promise<User> { + public async resolveUser(username: string, host: string | null): Promise<LocalUser | RemoteUser> { const usernameLower = username.toLowerCase(); if (host == null) { @@ -44,7 +44,7 @@ export class RemoteUserResolveService { } else { return u; } - }); + }) as LocalUser; } host = this.utilityService.toPuny(host); @@ -57,7 +57,7 @@ export class RemoteUserResolveService { } else { return u; } - }); + }) as LocalUser; } const user = await this.usersRepository.findOneBy({ usernameLower, host }) as RemoteUser | null; @@ -109,7 +109,7 @@ export class RemoteUserResolveService { if (u == null) { throw new Error('user not found'); } else { - return u; + return u as LocalUser | RemoteUser; } }); } diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 2a4271aa986f7535dcd938c00574bccb770dc232..68087ccc3b34248fa38f626ade439747d5551cb3 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { In } from 'typeorm'; import type { Role, RoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js'; import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js'; @@ -25,6 +25,7 @@ export type RolePolicies = { canSearchNotes: boolean; canHideAds: boolean; driveCapacityMb: number; + alwaysMarkNsfw: boolean; pinLimit: number; antennaLimit: number; wordMuteLimit: number; @@ -45,6 +46,7 @@ export const DEFAULT_POLICIES: RolePolicies = { canSearchNotes: false, canHideAds: false, driveCapacityMb: 100, + alwaysMarkNsfw: false, pinLimit: 5, antennaLimit: 5, wordMuteLimit: 200, @@ -279,6 +281,7 @@ export class RoleService implements OnApplicationShutdown { canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)), canHideAds: calc('canHideAds', vs => vs.some(v => v === true)), driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)), + alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)), pinLimit: calc('pinLimit', vs => Math.max(...vs)), antennaLimit: calc('antennaLimit', vs => Math.max(...vs)), wordMuteLimit: calc('wordMuteLimit', vs => Math.max(...vs)), diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts new file mode 100644 index 0000000000000000000000000000000000000000..e68fde088dd8e7f35d7ed01d7a0e988e8754595c --- /dev/null +++ b/packages/backend/src/core/SearchService.ts @@ -0,0 +1,178 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { In } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import { bindThis } from '@/decorators.js'; +import { Note } from '@/models/entities/Note.js'; +import { User } from '@/models/index.js'; +import type { NotesRepository } from '@/models/index.js'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; +import { QueryService } from '@/core/QueryService.js'; +import { IdService } from '@/core/IdService.js'; +import type { Index, MeiliSearch } from 'meilisearch'; + +type K = string; +type V = string | number | boolean; +type Q = + { op: '=', k: K, v: V } | + { op: '!=', k: K, v: V } | + { op: '>', k: K, v: number } | + { op: '<', k: K, v: number } | + { op: '>=', k: K, v: number } | + { op: '<=', k: K, v: number } | + { op: 'and', qs: Q[] } | + { op: 'or', qs: Q[] } | + { op: 'not', q: Q }; + +function compileValue(value: V): string { + if (typeof value === 'string') { + return `'${value}'`; // TODO: escape + } else if (typeof value === 'number') { + return value.toString(); + } else if (typeof value === 'boolean') { + return value.toString(); + } + throw new Error('unrecognized value'); +} + +function compileQuery(q: Q): string { + switch (q.op) { + case '=': return `(${q.k} = ${compileValue(q.v)})`; + case '!=': return `(${q.k} != ${compileValue(q.v)})`; + case '>': return `(${q.k} > ${compileValue(q.v)})`; + case '<': return `(${q.k} < ${compileValue(q.v)})`; + case '>=': return `(${q.k} >= ${compileValue(q.v)})`; + case '<=': return `(${q.k} <= ${compileValue(q.v)})`; + case 'and': return q.qs.length === 0 ? '' : `(${ q.qs.map(_q => compileQuery(_q)).join(' AND ') })`; + case 'or': return q.qs.length === 0 ? '' : `(${ q.qs.map(_q => compileQuery(_q)).join(' OR ') })`; + case 'not': return `(NOT ${compileQuery(q.q)})`; + default: throw new Error('unrecognized query operator'); + } +} + +@Injectable() +export class SearchService { + private meilisearchNoteIndex: Index | null = null; + + constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.meilisearch) + private meilisearch: MeiliSearch | null, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + private queryService: QueryService, + private idService: IdService, + ) { + if (meilisearch) { + this.meilisearchNoteIndex = meilisearch.index('notes'); + this.meilisearchNoteIndex.updateSettings({ + searchableAttributes: [ + 'text', + 'cw', + ], + sortableAttributes: [ + 'createdAt', + ], + filterableAttributes: [ + 'createdAt', + 'userId', + 'userHost', + 'channelId', + ], + typoTolerance: { + enabled: false, + }, + pagination: { + maxTotalHits: 10000, + }, + }); + } + } + + @bindThis + public async indexNote(note: Note): Promise<void> { + if (note.text == null && note.cw == null) return; + if (!['home', 'public'].includes(note.visibility)) return; + + if (this.meilisearch) { + this.meilisearchNoteIndex!.addDocuments([{ + id: note.id, + createdAt: note.createdAt.getTime(), + userId: note.userId, + userHost: note.userHost, + channelId: note.channelId, + cw: note.cw, + text: note.text, + }], { + primaryKey: 'id', + }); + } + } + + @bindThis + public async searchNote(q: string, me: User | null, opts: { + userId?: Note['userId'] | null; + channelId?: Note['channelId'] | null; + host?: string | null; + }, pagination: { + untilId?: Note['id']; + sinceId?: Note['id']; + limit?: number; + }): Promise<Note[]> { + if (this.meilisearch) { + const filter: Q = { + op: 'and', + qs: [], + }; + if (pagination.untilId) filter.qs.push({ op: '<', k: 'createdAt', v: this.idService.parse(pagination.untilId).date.getTime() }); + if (pagination.sinceId) filter.qs.push({ op: '>', k: 'createdAt', v: this.idService.parse(pagination.sinceId).date.getTime() }); + if (opts.userId) filter.qs.push({ op: '=', k: 'userId', v: opts.userId }); + if (opts.channelId) filter.qs.push({ op: '=', k: 'channelId', v: opts.channelId }); + if (opts.host) { + if (opts.host === '.') { + // TODO: MeilisearchãŒ2023/05/07ç¾åœ¨å€¤ãŒNULLã‹ã©ã†ã‹ã®ã‚¯ã‚¨ãƒªãŒæ›¸ã‘ãªã„ + } else { + filter.qs.push({ op: '=', k: 'userHost', v: opts.host }); + } + } + const res = await this.meilisearchNoteIndex!.search(q, { + sort: ['createdAt:desc'], + matchingStrategy: 'all', + attributesToRetrieve: ['id', 'createdAt'], + filter: compileQuery(filter), + limit: pagination.limit, + }); + if (res.hits.length === 0) return []; + const notes = await this.notesRepository.findBy({ + id: In(res.hits.map(x => x.id)), + }); + return notes.sort((a, b) => a.id > b.id ? -1 : 1); + } else { + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), pagination.sinceId, pagination.untilId); + + if (opts.userId) { + query.andWhere('note.userId = :userId', { userId: opts.userId }); + } else if (opts.channelId) { + query.andWhere('note.channelId = :channelId', { channelId: opts.channelId }); + } + + query + .andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` }) + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); + + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); + + return await query.take(pagination.limit).getMany(); + } + } +} diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index d7bc05b8bdc5784a6d64c1bce0d8b6d8c1de7ce0..29eb65fda4e9a338e1d09f31f35fb77eed651539 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -13,8 +13,9 @@ import { UsedUsername } from '@/models/entities/UsedUsername.js'; import generateUserToken from '@/misc/generate-native-user-token.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; -import UsersChart from './chart/charts/users.js'; -import { UtilityService } from './UtilityService.js'; +import UsersChart from '@/core/chart/charts/users.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { MetaService } from '@/core/MetaService.js'; @Injectable() export class SignupService { @@ -34,6 +35,7 @@ export class SignupService { private utilityService: UtilityService, private userEntityService: UserEntityService, private idService: IdService, + private metaService: MetaService, private usersChart: UsersChart, ) { } @@ -44,6 +46,7 @@ export class SignupService { password?: string | null; passwordHash?: UserProfile['password'] | null; host?: string | null; + ignorePreservedUsernames?: boolean; }) { const { username, password, passwordHash, host } = opts; let hash = passwordHash; @@ -76,7 +79,17 @@ export class SignupService { if (await this.usedUsernamesRepository.findOneBy({ username: username.toLowerCase() })) { throw new Error('USED_USERNAME'); } - + + const isTheFirstUser = (await this.usersRepository.countBy({ host: IsNull() })) === 0; + + if (!opts.ignorePreservedUsernames && !isTheFirstUser) { + const instance = await this.metaService.fetch(true); + const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase()); + if (isPreserved) { + throw new Error('USED_USERNAME'); + } + } + const keyPair = await new Promise<string[]>((res, rej) => generateKeyPair('rsa', { modulusLength: 4096, @@ -112,9 +125,7 @@ export class SignupService { usernameLower: username.toLowerCase(), host: this.utilityService.toPunyNullable(host), token: secret, - isRoot: (await this.usersRepository.countBy({ - host: IsNull(), - })) === 0, + isRoot: isTheFirstUser, })); await transactionalEntityManager.save(new UserKeypair({ diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index a8eded673351fe1fe85550278799a33ec36f58b4..7d90bc2c080eb11228150c395e37419e90557baa 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable, OnModuleInit, forwardRef } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js'; +import type { LocalUser, PartialLocalUser, PartialRemoteUser, RemoteUser, User } from '@/models/entities/User.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { QueueService } from '@/core/QueueService.js'; import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; @@ -22,6 +22,8 @@ import { MetaService } from '@/core/MetaService.js'; import { CacheService } from '@/core/CacheService.js'; import type { Config } from '@/config.js'; import Logger from '../logger.js'; +import { IsNull } from 'typeorm'; +import { AccountMoveService } from '@/core/AccountMoveService.js'; const logger = new Logger('following/create'); @@ -73,6 +75,7 @@ export class UserFollowingService implements OnModuleInit { private federatedInstanceService: FederatedInstanceService, private webhookService: WebhookService, private apRendererService: ApRendererService, + private accountMoveService: AccountMoveService, private perUserFollowingChart: PerUserFollowingChart, private instanceChart: InstanceChart, ) { @@ -87,7 +90,7 @@ export class UserFollowingService implements OnModuleInit { const [follower, followee] = await Promise.all([ this.usersRepository.findOneByOrFail({ id: _follower.id }), this.usersRepository.findOneByOrFail({ id: _followee.id }), - ]); + ]) as [LocalUser | RemoteUser, LocalUser | RemoteUser]; // check blocking const [blocking, blocked] = await Promise.all([ @@ -137,6 +140,20 @@ export class UserFollowingService implements OnModuleInit { if (followed) autoAccept = true; } + // Automatically accept if the follower is an account who has moved and the locked followee had accepted the old account. + if (followee.isLocked && !autoAccept) { + autoAccept = !!(await this.accountMoveService.validateAlsoKnownAs( + follower, + (oldSrc, newSrc) => this.followingsRepository.exist({ + where: { + followeeId: followee.id, + followerId: newSrc.id, + }, + }), + true, + )); + } + if (!autoAccept) { await this.createFollowRequest(follower, followee, requestId); return; @@ -210,32 +227,40 @@ export class UserFollowingService implements OnModuleInit { this.globalEventService.publishInternalEvent('follow', { followerId: follower.id, followeeId: followee.id }); - //#region Increment counts - await Promise.all([ - this.usersRepository.increment({ id: follower.id }, 'followingCount', 1), - this.usersRepository.increment({ id: followee.id }, 'followersCount', 1), + const [followeeUser, followerUser] = await Promise.all([ + this.usersRepository.findOneByOrFail({ id: followee.id }), + this.usersRepository.findOneByOrFail({ id: follower.id }), ]); - //#endregion - //#region Update instance stats - 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) { - 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) { - this.instanceChart.updateFollowers(i.host, true); - } - }); - } - //#endregion + // Neither followee nor follower has moved. + if (!followeeUser.movedToUri && !followerUser.movedToUri) { + //#region Increment counts + await Promise.all([ + this.usersRepository.increment({ id: follower.id }, 'followingCount', 1), + this.usersRepository.increment({ id: followee.id }, 'followersCount', 1), + ]); + //#endregion + + //#region Update instance stats + 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) { + 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) { + this.instanceChart.updateFollowers(i.host, true); + } + }); + } + //#endregion - this.perUserFollowingChart.update(follower, followee, true); + this.perUserFollowingChart.update(follower, followee, true); + } // Publish follow event if (this.userEntityService.isLocalUser(follower) && !silent) { @@ -283,12 +308,18 @@ export class UserFollowingService implements OnModuleInit { }, silent = false, ): Promise<void> { - const following = await this.followingsRepository.findOneBy({ - followerId: follower.id, - followeeId: followee.id, + const following = await this.followingsRepository.findOne({ + relations: { + follower: true, + followee: true, + }, + where: { + followerId: follower.id, + followeeId: followee.id, + } }); - if (following == null) { + if (following === null || !following.follower || !following.followee) { logger.warn('フォãƒãƒ¼è§£é™¤ãŒãƒªã‚¯ã‚¨ã‚¹ãƒˆã•ã‚Œã¾ã—ãŸãŒãƒ•ã‚©ãƒãƒ¼ã—ã¦ã„ã¾ã›ã‚“ã§ã—ãŸ'); return; } @@ -297,7 +328,7 @@ export class UserFollowingService implements OnModuleInit { this.cacheService.userFollowingsCache.refresh(follower.id); - this.decrementFollowing(follower, followee); + this.decrementFollowing(following.follower, following.followee); // Publish unfollow event if (!silent && this.userEntityService.isLocalUser(follower)) { @@ -316,50 +347,87 @@ export class UserFollowingService implements OnModuleInit { } if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { - const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower)); + const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower as PartialLocalUser, followee as PartialRemoteUser), follower)); this.queueService.deliver(follower, content, followee.inbox, false); } if (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower)) { // local user has null host - const content = this.apRendererService.addContext(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower, followee), followee)); + const content = this.apRendererService.addContext(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower as PartialRemoteUser, followee as PartialLocalUser), followee)); this.queueService.deliver(followee, content, follower.inbox, false); } } @bindThis private async decrementFollowing( - follower: { id: User['id']; host: User['host']; }, - followee: { id: User['id']; host: User['host']; }, + follower: User, + followee: User, ): Promise<void> { this.globalEventService.publishInternalEvent('unfollow', { followerId: follower.id, followeeId: followee.id }); - //#region Decrement following / followers counts - await Promise.all([ - this.usersRepository.decrement({ id: follower.id }, 'followingCount', 1), - this.usersRepository.decrement({ id: followee.id }, 'followersCount', 1), - ]); - //#endregion + // Neither followee nor follower has moved. + if (!follower.movedToUri && !followee.movedToUri) { + //#region Decrement following / followers counts + await Promise.all([ + this.usersRepository.decrement({ id: follower.id }, 'followingCount', 1), + this.usersRepository.decrement({ id: followee.id }, 'followersCount', 1), + ]); + //#endregion + + //#region Update instance stats + 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) { + 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) { + this.instanceChart.updateFollowers(i.host, false); + } + }); + } + //#endregion - //#region Update instance stats - 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) { - 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) { - this.instanceChart.updateFollowers(i.host, false); - } - }); - } - //#endregion + this.perUserFollowingChart.update(follower, followee, false); + } else { + // Adjust following/followers counts + for (const user of [follower, followee]) { + if (user.movedToUri) continue; // No need to update if the user has already moved. + + const nonMovedFollowees = await this.followingsRepository.count({ + relations: { + followee: true, + }, + where: { + followerId: user.id, + followee: { + movedToUri: IsNull(), + } + } + }); + const nonMovedFollowers = await this.followingsRepository.count({ + relations: { + follower: true, + }, + where: { + followeeId: user.id, + follower: { + movedToUri: IsNull(), + } + } + }); + await this.usersRepository.update( + { id: user.id }, + { followingCount: nonMovedFollowees, followersCount: nonMovedFollowers }, + ); + } - this.perUserFollowingChart.update(follower, followee, false); + // TODO: adjust charts + } } @bindThis @@ -415,7 +483,7 @@ export class UserFollowingService implements OnModuleInit { } if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { - const content = this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee, requestId ?? `${this.config.url}/follows/${followRequest.id}`)); + const content = this.apRendererService.addContext(this.apRendererService.renderFollow(follower as PartialLocalUser, followee as PartialRemoteUser, requestId ?? `${this.config.url}/follows/${followRequest.id}`)); this.queueService.deliver(follower, content, followee.inbox, false); } } @@ -430,7 +498,7 @@ export class UserFollowingService implements OnModuleInit { }, ): Promise<void> { if (this.userEntityService.isRemoteUser(followee)) { - const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower)); + const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower as PartialLocalUser | PartialRemoteUser, followee as PartialRemoteUser), follower)); if (this.userEntityService.isLocalUser(follower)) { // 本æ¥ã“ã®ãƒã‚§ãƒƒã‚¯ã¯ä¸è¦ã ã‘ã©TSã«æ€’られるã®ã§ this.queueService.deliver(follower, content, followee.inbox, false); @@ -475,7 +543,7 @@ export class UserFollowingService implements OnModuleInit { await this.insertFollowingDoc(followee, follower); if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { - const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, request.requestId!), followee)); + const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee as PartialLocalUser, request.requestId!), followee)); this.queueService.deliver(followee, content, follower.inbox, false); } @@ -562,15 +630,22 @@ export class UserFollowingService implements OnModuleInit { */ @bindThis private async removeFollow(followee: Both, follower: Both): Promise<void> { - const following = await this.followingsRepository.findOneBy({ - followeeId: followee.id, - followerId: follower.id, + const following = await this.followingsRepository.findOne({ + relations: { + followee: true, + follower: true, + }, + where: { + followeeId: followee.id, + followerId: follower.id, + } }); - if (!following) return; + if (!following || !following.followee || !following.follower) return; await this.followingsRepository.delete(following.id); - this.decrementFollowing(follower, followee); + + this.decrementFollowing(following.follower, following.followee); } /** diff --git a/packages/backend/src/core/UserKeypairService.ts b/packages/backend/src/core/UserKeypairService.ts index 22a9fb2b8ef3185f352815cdb6109a69cd7c371c..72c35c529c2752cff574090eea9ce54ea9972d3a 100644 --- a/packages/backend/src/core/UserKeypairService.ts +++ b/packages/backend/src/core/UserKeypairService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import type { User } from '@/models/entities/User.js'; import type { UserKeypairsRepository } from '@/models/index.js'; import { RedisKVCache } from '@/misc/cache.js'; diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index d00bb89c7635293742355b0da6d8ea119b8b5923..b197d335d89c756fb3637a8be3619439aef8dd45 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -35,7 +35,7 @@ export class UserSuspendService { if (this.userEntityService.isLocalUser(user)) { // 知り得る全SharedInboxã«Deleteé…ä¿¡ - const content = this.apRendererService.addContext(this.apRendererService.renderDelete(`${this.config.url}/users/${user.id}`, user)); + const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user)); const queue: string[] = []; @@ -65,7 +65,7 @@ export class UserSuspendService { if (this.userEntityService.isLocalUser(user)) { // 知り得る全SharedInboxã«Undo Deleteé…ä¿¡ - const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderDelete(`${this.config.url}/users/${user.id}`, user), user)); + const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user), user)); const queue: string[] = []; diff --git a/packages/backend/src/core/WebfingerService.ts b/packages/backend/src/core/WebfingerService.ts index 69df2d0c1b67f6ddf65c9e487cc856887a4e73b8..3ee79906431f5d9aa9b05ba560d6ed435de25664 100644 --- a/packages/backend/src/core/WebfingerService.ts +++ b/packages/backend/src/core/WebfingerService.ts @@ -43,7 +43,8 @@ export class WebfingerService { const m = query.match(/^([^@]+)@(.*)/); if (m) { const hostname = m[2]; - return `https://${hostname}/.well-known/webfinger?` + urlQuery({ resource: `acct:${query}` }); + const useHttp = process.env.MISSKEY_WEBFINGER_USE_HTTP && process.env.MISSKEY_WEBFINGER_USE_HTTP.toLowerCase() === 'true'; + return `http${useHttp ? '' : 's'}://${hostname}/.well-known/webfinger?${urlQuery({ resource: `acct:${query}` })}`; } throw new Error(`Invalid query (${query})`); diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts index 926115613b85f93e1a299858f01e8267933e9407..57baade77707a60defcbdcb23a14e5326801761a 100644 --- a/packages/backend/src/core/WebhookService.ts +++ b/packages/backend/src/core/WebhookService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import type { WebhooksRepository } from '@/models/index.js'; import type { Webhook } from '@/models/entities/Webhook.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index 4b032be89adb44fca347e1a2a6215050cc84fb67..2b404ebecaeb64d7ff3a8323a4200436f77b8739 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -8,7 +8,7 @@ import type { UserPublickey } from '@/models/entities/UserPublickey.js'; import { CacheService } from '@/core/CacheService.js'; import type { Note } from '@/models/entities/Note.js'; import { bindThis } from '@/decorators.js'; -import { RemoteUser, User } from '@/models/entities/User.js'; +import { LocalUser, RemoteUser } from '@/models/entities/User.js'; import { getApId } from './type.js'; import { ApPersonService } from './models/ApPersonService.js'; import type { IObject } from './type.js'; @@ -101,7 +101,7 @@ export class ApDbResolverService { * AP Person => Misskey User in DB */ @bindThis - public async getUserFromApId(value: string | IObject): Promise<User | null> { + public async getUserFromApId(value: string | IObject): Promise<LocalUser | RemoteUser | null> { const parsed = this.parseUri(value); if (parsed.local) { @@ -109,11 +109,11 @@ export class ApDbResolverService { return await this.cacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({ id: parsed.id, - }).then(x => x ?? undefined)) ?? null; + }).then(x => x ?? undefined)) as LocalUser | undefined ?? null; } else { return await this.cacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({ uri: parsed.uri, - })); + })) as RemoteUser | null; } } diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 3fca0bb1fd5784972d8f09d528bb7158f7072381..efef777fb07e03563c0c17a754680b6f8a38afb5 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { In, IsNull } from 'typeorm'; +import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; @@ -13,13 +13,15 @@ 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 { AccountMoveService } from '@/core/AccountMoveService.js'; import { IdService } from '@/core/IdService.js'; import { StatusError } from '@/misc/status-error.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { CacheService } from '@/core/CacheService.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/index.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository, } from '@/models/index.js'; import { bindThis } from '@/decorators.js'; import type { RemoteUser } from '@/models/entities/User.js'; import { getApHrefNullable, getApId, getApIds, getApType, getOneApHrefNullable, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; @@ -76,6 +78,8 @@ export class ApInboxService { private apNoteService: ApNoteService, private apPersonService: ApPersonService, private apQuestionService: ApQuestionService, + private accountMoveService: AccountMoveService, + private cacheService: CacheService, private queueService: QueueService, ) { this.logger = this.apLoggerService.logger; @@ -140,7 +144,7 @@ export class ApInboxService { } else if (isFlag(activity)) { await this.flag(actor, activity); } else if (isMove(activity)) { - //await this.move(actor, activity); + await this.move(actor, activity); } else { this.logger.warn(`unrecognized activity type: ${activity.type}`); } @@ -158,6 +162,7 @@ export class ApInboxService { return 'skip: フォãƒãƒ¼ã—よã†ã¨ã—ã¦ã„るユーザーã¯ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã§ã¯ã‚ã‚Šã¾ã›ã‚“'; } + // don't queue because the sender may attempt again when timeout await this.userFollowingService.follow(actor, followee, activity.id); return 'ok'; } @@ -596,6 +601,7 @@ export class ApInboxService { throw e; }); + // don't queue because the sender may attempt again when timeout if (isFollow(object)) return await this.undoFollow(actor, object); if (isBlock(object)) return await this.undoBlock(actor, object); if (isLike(object)) return await this.undoLike(actor, object); @@ -736,53 +742,7 @@ export class ApInboxService { // fetch the new and old accounts const targetUri = getApHrefNullable(activity.target); if (!targetUri) return 'skip: invalid activity target'; - let new_acc = await this.apPersonService.resolvePerson(targetUri); - let old_acc = await this.apPersonService.resolvePerson(actor.uri); - - // update them if they're remote - if (new_acc.uri) await this.apPersonService.updatePerson(new_acc.uri); - if (old_acc.uri) await this.apPersonService.updatePerson(old_acc.uri); - - // retrieve updated users - new_acc = await this.apPersonService.resolvePerson(targetUri); - old_acc = await this.apPersonService.resolvePerson(actor.uri); - - // check if alsoKnownAs of the new account is valid - let isValidMove = true; - if (old_acc.uri) { - if (!new_acc.alsoKnownAs?.includes(old_acc.uri)) { - isValidMove = false; - } - } else if (!new_acc.alsoKnownAs?.includes(old_acc.id)) { - isValidMove = false; - } - if (!isValidMove) { - return 'skip: accounts invalid'; - } - - // add target uri to movedToUri in order to indicate that the user has moved - await this.usersRepository.update(old_acc.id, { movedToUri: targetUri }); - - // follow the new account and unfollow the old one - const followings = await this.followingsRepository.find({ - relations: { - follower: true, - }, - where: { - followeeId: old_acc.id, - followerHost: IsNull(), // follower is local - }, - }); - for (const following of followings) { - if (!following.follower) continue; - try { - await this.userFollowingService.follow(following.follower, new_acc); - await this.userFollowingService.unfollow(following.follower, old_acc); - } catch { - /* empty */ - } - } - return 'ok'; + return await this.apPersonService.updatePerson(actor.uri) ?? 'skip: nothing to do'; } } diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 0b22aa9bcfb26555968fef13f7fbd6faaaf1a89c..60e19bfca52f46eb194f15775f71067180077e83 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -5,7 +5,7 @@ import { v4 as uuid } from 'uuid'; import * as mfm from 'mfm-js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; -import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js'; +import type { PartialLocalUser, LocalUser, PartialRemoteUser, RemoteUser, User } from '@/models/entities/User.js'; import type { IMentionedRemoteUsers, Note } from '@/models/entities/Note.js'; import type { Blocking } from '@/models/entities/Blocking.js'; import type { Relay } from '@/models/entities/Relay.js'; @@ -66,7 +66,7 @@ export class ApRendererService { public renderAccept(object: any, user: { id: User['id']; host: null }): IAccept { return { type: 'Accept', - actor: `${this.config.url}/users/${user.id}`, + actor: this.userEntityService.genLocalUserUri(user.id), object, }; } @@ -75,7 +75,7 @@ export class ApRendererService { public renderAdd(user: LocalUser, target: any, object: any): IAdd { return { type: 'Add', - actor: `${this.config.url}/users/${user.id}`, + actor: this.userEntityService.genLocalUserUri(user.id), target, object, }; @@ -83,7 +83,7 @@ export class ApRendererService { @bindThis public renderAnnounce(object: any, note: Note): IAnnounce { - const attributedTo = `${this.config.url}/users/${note.userId}`; + const attributedTo = this.userEntityService.genLocalUserUri(note.userId); let to: string[] = []; let cc: string[] = []; @@ -103,7 +103,7 @@ export class ApRendererService { return { id: `${this.config.url}/notes/${note.id}/activity`, - actor: `${this.config.url}/users/${note.userId}`, + actor: this.userEntityService.genLocalUserUri(note.userId), type: 'Announce', published: note.createdAt.toISOString(), to, @@ -126,7 +126,7 @@ export class ApRendererService { return { type: 'Block', id: `${this.config.url}/blocks/${block.id}`, - actor: `${this.config.url}/users/${block.blockerId}`, + actor: this.userEntityService.genLocalUserUri(block.blockerId), object: block.blockee.uri, }; } @@ -135,7 +135,7 @@ export class ApRendererService { public renderCreate(object: IObject, note: Note): ICreate { const activity = { id: `${this.config.url}/notes/${note.id}/activity`, - actor: `${this.config.url}/users/${note.userId}`, + actor: this.userEntityService.genLocalUserUri(note.userId), type: 'Create', published: note.createdAt.toISOString(), object, @@ -151,7 +151,7 @@ export class ApRendererService { public renderDelete(object: IObject | string, user: { id: User['id']; host: null }): IDelete { return { type: 'Delete', - actor: `${this.config.url}/users/${user.id}`, + actor: this.userEntityService.genLocalUserUri(user.id), object, published: new Date().toISOString(), }; @@ -188,7 +188,7 @@ export class ApRendererService { public renderFlag(user: LocalUser, object: IObject | string, content: string): IFlag { return { type: 'Flag', - actor: `${this.config.url}/users/${user.id}`, + actor: this.userEntityService.genLocalUserUri(user.id), content, object, }; @@ -199,7 +199,7 @@ export class ApRendererService { return { id: `${this.config.url}/activities/follow-relay/${relay.id}`, type: 'Follow', - actor: `${this.config.url}/users/${relayActor.id}`, + actor: this.userEntityService.genLocalUserUri(relayActor.id), object: 'https://www.w3.org/ns/activitystreams#Public', }; } @@ -210,21 +210,21 @@ export class ApRendererService { */ @bindThis public async renderFollowUser(id: User['id']) { - const user = await this.usersRepository.findOneByOrFail({ id: id }); - return this.userEntityService.isLocalUser(user) ? `${this.config.url}/users/${user.id}` : user.uri; + const user = await this.usersRepository.findOneByOrFail({ id: id }) as PartialLocalUser | PartialRemoteUser; + return this.userEntityService.getUserUri(user); } @bindThis public renderFollow( - follower: { id: User['id']; host: User['host']; uri: User['host'] }, - followee: { id: User['id']; host: User['host']; uri: User['host'] }, + follower: PartialLocalUser | PartialRemoteUser, + followee: PartialLocalUser | PartialRemoteUser, requestId?: string, ): IFollow { return { id: requestId ?? `${this.config.url}/follows/${follower.id}/${followee.id}`, type: 'Follow', - actor: this.userEntityService.isLocalUser(follower) ? `${this.config.url}/users/${follower.id}` : follower.uri!, - object: this.userEntityService.isLocalUser(followee) ? `${this.config.url}/users/${followee.id}` : followee.uri!, + actor: this.userEntityService.getUserUri(follower)!, + object: this.userEntityService.getUserUri(followee)!, }; } @@ -252,7 +252,7 @@ export class ApRendererService { return { id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`, type: 'Key', - owner: `${this.config.url}/users/${user.id}`, + owner: this.userEntityService.genLocalUserUri(user.id), publicKeyPem: createPublicKey(key.publicKey).export({ type: 'spki', format: 'pem', @@ -284,21 +284,21 @@ export class ApRendererService { } @bindThis - public renderMention(mention: User): IApMention { + public renderMention(mention: PartialLocalUser | PartialRemoteUser): IApMention { return { type: 'Mention', - href: this.userEntityService.isRemoteUser(mention) ? mention.uri! : `${this.config.url}/users/${(mention as LocalUser).id}`, + href: this.userEntityService.getUserUri(mention)!, name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as LocalUser).username}`, }; } @bindThis public renderMove( - src: { id: User['id']; host: User['host']; uri: User['host'] }, - dst: { id: User['id']; host: User['host']; uri: User['host'] }, + src: PartialLocalUser | PartialRemoteUser, + dst: PartialLocalUser | PartialRemoteUser, ): IMove { - const actor = this.userEntityService.isLocalUser(src) ? `${this.config.url}/users/${src.id}` : src.uri!; - const target = this.userEntityService.isLocalUser(dst) ? `${this.config.url}/users/${dst.id}` : dst.uri!; + const actor = this.userEntityService.getUserUri(src)!; + const target = this.userEntityService.getUserUri(dst)!; return { id: `${this.config.url}/moves/${src.id}/${dst.id}`, actor, @@ -351,7 +351,7 @@ export class ApRendererService { } } - const attributedTo = `${this.config.url}/users/${note.userId}`; + const attributedTo = this.userEntityService.genLocalUserUri(note.userId); const mentions = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri); @@ -376,7 +376,7 @@ export class ApRendererService { }) : []; const hashtagTags = (note.tags ?? []).map(tag => this.renderHashtag(tag)); - const mentionTags = mentionedUsers.map(u => this.renderMention(u)); + const mentionTags = mentionedUsers.map(u => this.renderMention(u as LocalUser | RemoteUser)); const files = await getPromisedFiles(note.fileIds); @@ -450,7 +450,7 @@ export class ApRendererService { @bindThis public async renderPerson(user: LocalUser) { - const id = `${this.config.url}/users/${user.id}`; + const id = this.userEntityService.genLocalUserUri(user.id); const isSystem = !!user.username.match(/\./); const [avatar, banner, profile] = await Promise.all([ @@ -538,7 +538,7 @@ export class ApRendererService { return { type: 'Question', id: `${this.config.url}/questions/${note.id}`, - actor: `${this.config.url}/users/${user.id}`, + actor: this.userEntityService.genLocalUserUri(user.id), content: note.text ?? '', [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ name: text, @@ -555,7 +555,7 @@ export class ApRendererService { public renderReject(object: any, user: { id: User['id'] }): IReject { return { type: 'Reject', - actor: `${this.config.url}/users/${user.id}`, + actor: this.userEntityService.genLocalUserUri(user.id), object, }; } @@ -564,7 +564,7 @@ export class ApRendererService { public renderRemove(user: { id: User['id'] }, target: any, object: any): IRemove { return { type: 'Remove', - actor: `${this.config.url}/users/${user.id}`, + actor: this.userEntityService.genLocalUserUri(user.id), target, object, }; @@ -585,7 +585,7 @@ export class ApRendererService { return { type: 'Undo', ...(id ? { id } : {}), - actor: `${this.config.url}/users/${user.id}`, + actor: this.userEntityService.genLocalUserUri(user.id), object, published: new Date().toISOString(), }; @@ -595,7 +595,7 @@ export class ApRendererService { public renderUpdate(object: any, user: { id: User['id'] }): IUpdate { return { id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`, - actor: `${this.config.url}/users/${user.id}`, + actor: this.userEntityService.genLocalUserUri(user.id), type: 'Update', to: ['https://www.w3.org/ns/activitystreams#Public'], object, @@ -607,14 +607,14 @@ export class ApRendererService { public renderVote(user: { id: User['id'] }, vote: PollVote, note: Note, poll: Poll, pollOwner: RemoteUser): ICreate { return { id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`, - actor: `${this.config.url}/users/${user.id}`, + actor: this.userEntityService.genLocalUserUri(user.id), type: 'Create', to: [pollOwner.uri], published: new Date().toISOString(), object: { id: `${this.config.url}/users/${user.id}#votes/${vote.id}`, type: 'Note', - attributedTo: `${this.config.url}/users/${user.id}`, + attributedTo: this.userEntityService.genLocalUserUri(user.id), to: [pollOwner.uri], inReplyTo: note.uri, name: poll.choices[vote.choice], diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts index df7bb464056d4f1f20d61f66258c637b68509c88..d3e0345c9c275814217860e2364ffba016bfa3af 100644 --- a/packages/backend/src/core/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import type { LocalUser } from '@/models/entities/User.js'; +import type { LocalUser, RemoteUser } from '@/models/entities/User.js'; import { InstanceActorService } from '@/core/InstanceActorService.js'; import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; @@ -151,7 +151,7 @@ export class Resolver { return Promise.all( [parsed.id, parsed.rest].map(id => this.usersRepository.findOneByOrFail({ id })), ) - .then(([follower, followee]) => this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee, url))); + .then(([follower, followee]) => this.apRendererService.addContext(this.apRendererService.renderFollow(follower as LocalUser | RemoteUser, followee as LocalUser | RemoteUser, url))); default: throw new Error(`resolveLocal: type ${parsed.type} unhandled`); } diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts index 3b671af1277b4eafdaddc52733026f9f12ac4447..0043907c212f5c299db14e13f6e5109e0dec6d7e 100644 --- a/packages/backend/src/core/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -12,6 +12,7 @@ import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { ApResolverService } from '../ApResolverService.js'; import { ApLoggerService } from '../ApLoggerService.js'; +import { checkHttps } from '@/misc/check-https.js'; @Injectable() export class ApImageService { @@ -48,8 +49,8 @@ export class ApImageService { throw new Error('invalid image: url not privided'); } - if (!image.url.startsWith('https://')) { - throw new Error('invalid image: unexpected shcema of url: ' + image.url); + if (!checkHttps(image.url)) { + throw new Error('invalid image: unexpected schema of url: ' + image.url); } this.logger.info(`Creating the Image: ${image.url}`); diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 5ca5f6e843c737465978600ccee52e5c8c1ade2d..87a9db405f9d0fcc56deb27b915e3b8062bdae8a 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -32,6 +32,7 @@ import { ApQuestionService } from './ApQuestionService.js'; import { ApImageService } from './ApImageService.js'; import type { Resolver } from '../ApResolverService.js'; import type { IObject, IPost } from '../type.js'; +import { checkHttps } from '@/misc/check-https.js'; @Injectable() export class ApNoteService { @@ -71,7 +72,7 @@ export class ApNoteService { } @bindThis - public validateNote(object: any, uri: string) { + public validateNote(object: IObject, uri: string) { const expectHost = this.utilityService.extractDbHost(uri); if (object == null) { @@ -85,9 +86,10 @@ export class ApNoteService { if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) { return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`); } - - if (object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo)) !== expectHost) { - return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.attributedTo)}`); + + const actualHost = object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo)); + if (object.attributedTo && actualHost !== expectHost) { + return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`); } return null; @@ -129,13 +131,13 @@ export class ApNoteService { this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); - if (note.id && !note.id.startsWith('https://')) { + if (note.id && !checkHttps(note.id)) { throw new Error('unexpected shcema of note.id: ' + note.id); } const url = getOneApHrefNullable(note.url); - if (url && !url.startsWith('https://')) { + if (url && !checkHttps(url)) { throw new Error('unexpected shcema of note url: ' + url); } @@ -148,7 +150,7 @@ export class ApNoteService { if (actor.isSuspended) { throw new Error('actor has been suspended'); } - + const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver); let visibility = noteAudience.visibility; const visibleUsers = noteAudience.visibleUsers; diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index 21797cfcb7ae2b46bcb683bc8aacc32fbed11aa2..eea1d1b8482825e01de66924013f1de9a3c99559 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -3,9 +3,9 @@ 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/index.js'; +import type { BlockingsRepository, MutingsRepository, FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; -import type { RemoteUser } from '@/models/entities/User.js'; +import type { LocalUser, RemoteUser } from '@/models/entities/User.js'; import { User } from '@/models/entities/User.js'; import { truncate } from '@/misc/truncate.js'; import type { CacheService } from '@/core/CacheService.js'; @@ -42,6 +42,8 @@ import type { ApLoggerService } from '../ApLoggerService.js'; // eslint-disable-next-line @typescript-eslint/consistent-type-imports import type { ApImageService } from './ApImageService.js'; import type { IActor, IObject } from '../type.js'; +import type { AccountMoveService } from '@/core/AccountMoveService.js'; +import { checkHttps } from '@/misc/check-https.js'; const nameLength = 128; const summaryLength = 2048; @@ -66,6 +68,7 @@ export class ApPersonService implements OnModuleInit { private usersChart: UsersChart; private instanceChart: InstanceChart; private apLoggerService: ApLoggerService; + private accountMoveService: AccountMoveService; private logger: Logger; constructor( @@ -131,9 +134,16 @@ export class ApPersonService implements OnModuleInit { this.usersChart = this.moduleRef.get('UsersChart'); this.instanceChart = this.moduleRef.get('InstanceChart'); this.apLoggerService = this.moduleRef.get('ApLoggerService'); + this.accountMoveService = this.moduleRef.get('AccountMoveService'); this.logger = this.apLoggerService.logger; } + private punyHost(url: string): string { + const urlObj = new URL(url); + const host = `${this.utilityService.toPuny(urlObj.hostname)}${urlObj.port.length > 0 ? ':' + urlObj.port : ''}`; + return host; + } + /** * Validate and convert to actor object * @param x Fetched object @@ -141,7 +151,7 @@ export class ApPersonService implements OnModuleInit { */ @bindThis private validateActor(x: IObject, uri: string): IActor { - const expectHost = this.utilityService.toPuny(new URL(uri).hostname); + const expectHost = this.punyHost(uri); if (x == null) { throw new Error('invalid Actor: object is null'); @@ -182,7 +192,7 @@ export class ApPersonService implements OnModuleInit { x.summary = truncate(x.summary, summaryLength); } - const idHost = this.utilityService.toPuny(new URL(x.id!).hostname); + const idHost = this.punyHost(x.id); if (idHost !== expectHost) { throw new Error('invalid Actor: id has different host'); } @@ -192,7 +202,7 @@ export class ApPersonService implements OnModuleInit { throw new Error('invalid Actor: publicKey.id is not a string'); } - const publicKeyIdHost = this.utilityService.toPuny(new URL(x.publicKey.id).hostname); + const publicKeyIdHost = this.punyHost(x.publicKey.id); if (publicKeyIdHost !== expectHost) { throw new Error('invalid Actor: publicKey.id has different host'); } @@ -202,27 +212,27 @@ export class ApPersonService implements OnModuleInit { } /** - * Personをフェッãƒã—ã¾ã™ã€‚ + * uriã‹ã‚‰User(Person)をフェッãƒã—ã¾ã™ã€‚ * - * Misskeyã«å¯¾è±¡ã®PersonãŒç™»éŒ²ã•ã‚Œã¦ã„ã‚Œã°ãれを返ã—ã¾ã™ã€‚ + * Misskeyã«å¯¾è±¡ã®PersonãŒç™»éŒ²ã•ã‚Œã¦ã„ã‚Œã°ãれを返ã—ã€ç™»éŒ²ãŒãªã‘ã‚Œã°nullã‚’è¿”ã—ã¾ã™ã€‚ */ @bindThis - public async fetchPerson(uri: string, resolver?: Resolver): Promise<User | null> { + public async fetchPerson(uri: string): Promise<LocalUser | RemoteUser | null> { if (typeof uri !== 'string') throw new Error('uri is not string'); - const cached = this.cacheService.uriPersonCache.get(uri); + const cached = this.cacheService.uriPersonCache.get(uri) as LocalUser | RemoteUser | null; if (cached) return cached; // URIãŒã“ã®ã‚µãƒ¼ãƒãƒ¼ã‚’指ã—ã¦ã„ã‚‹ãªã‚‰ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã‹ã‚‰ãƒ•ã‚§ãƒƒãƒ - if (uri.startsWith(this.config.url + '/')) { + if (uri.startsWith(`${this.config.url}/`)) { const id = uri.split('/').pop(); - const u = await this.usersRepository.findOneBy({ id }); + const u = await this.usersRepository.findOneBy({ id }) as LocalUser; if (u) this.cacheService.uriPersonCache.set(uri, u); return u; } //#region ã“ã®ã‚µãƒ¼ãƒãƒ¼ã«æ—¢ã«ç™»éŒ²ã•ã‚Œã¦ã„ãŸã‚‰ãれを返㙠- const exist = await this.usersRepository.findOneBy({ uri }); + const exist = await this.usersRepository.findOneBy({ uri }) as LocalUser | RemoteUser; if (exist) { this.cacheService.uriPersonCache.set(uri, exist); @@ -237,7 +247,7 @@ export class ApPersonService implements OnModuleInit { * Personを作æˆã—ã¾ã™ã€‚ */ @bindThis - public async createPerson(uri: string, resolver?: Resolver): Promise<User> { + public async createPerson(uri: string, resolver?: Resolver): Promise<RemoteUser> { if (typeof uri !== 'string') throw new Error('uri is not string'); if (uri.startsWith(this.config.url)) { @@ -252,7 +262,7 @@ export class ApPersonService implements OnModuleInit { this.logger.info(`Creating the Person: ${person.id}`); - const host = this.utilityService.toPuny(new URL(object.id).hostname); + const host = this.punyHost(object.id); const { fields } = this.analyzeAttachments(person.attachment ?? []); @@ -264,8 +274,8 @@ export class ApPersonService implements OnModuleInit { const url = getOneApHrefNullable(person.url); - if (url && !url.startsWith('https://')) { - throw new Error('unexpected shcema of person url: ' + url); + if (url && !checkHttps(url)) { + throw new Error('unexpected schema of person url: ' + url); } // Create user @@ -282,6 +292,7 @@ export class ApPersonService implements OnModuleInit { name: truncate(person.name, nameLength), isLocked: !!person.manuallyApprovesFollowers, movedToUri: person.movedTo, + movedAt: person.movedTo ? new Date() : null, alsoKnownAs: person.alsoKnownAs, isExplorable: !!person.discoverable, username: person.preferredUsername, @@ -404,23 +415,26 @@ export class ApPersonService implements OnModuleInit { /** * Personã®æƒ…å ±ã‚’æ›´æ–°ã—ã¾ã™ã€‚ * Misskeyã«å¯¾è±¡ã®PersonãŒç™»éŒ²ã•ã‚Œã¦ã„ãªã‘ã‚Œã°ç„¡è¦–ã—ã¾ã™ã€‚ + * ã‚‚ã—アカウントã®ç§»è¡ŒãŒç¢ºèªã•ã‚ŒãŸå ´åˆã€ã‚¢ã‚«ã‚¦ãƒ³ãƒˆç§»è¡Œå‡¦ç†ã‚’è¡Œã„ã¾ã™ã€‚ + * * @param uri URI of Person * @param resolver Resolver * @param hint Hint of Person object (ã“ã®å€¤ãŒæ£å½“ãªPersonã®å ´åˆã€Remote resolveã‚’ã›ãšã«æ›´æ–°ã«åˆ©ç”¨ã—ã¾ã™) + * @param movePreventUris ã“ã“ã«æŒ‡å®šã•ã‚ŒãŸURIãŒPersonã®movedToã«æŒ‡å®šã•ã‚Œã¦ã„ãŸã‚Š10回より多ã回ã£ã¦ã„ã‚‹å ´åˆã“れ以上アカウント移行を行ã‚ãªã„(無é™ãƒ«ãƒ¼ãƒ—防æ¢ï¼‰ */ @bindThis - public async updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise<void> { + public async updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject, movePreventUris: string[] = []): Promise<string | void> { if (typeof uri !== 'string') throw new Error('uri is not string'); // URIãŒã“ã®ã‚µãƒ¼ãƒãƒ¼ã‚’指ã—ã¦ã„ã‚‹ãªã‚‰ã‚¹ã‚ップ - if (uri.startsWith(this.config.url + '/')) { + if (uri.startsWith(`${this.config.url}/`)) { return; } //#region ã“ã®ã‚µãƒ¼ãƒãƒ¼ã«æ—¢ã«ç™»éŒ²ã•ã‚Œã¦ã„ã‚‹ã‹ - const exist = await this.usersRepository.findOneBy({ uri }) as RemoteUser; + const exist = await this.usersRepository.findOneBy({ uri }) as RemoteUser | null; - if (exist == null) { + if (exist === null) { return; } //#endregion @@ -459,8 +473,8 @@ export class ApPersonService implements OnModuleInit { const url = getOneApHrefNullable(person.url); - if (url && !url.startsWith('https://')) { - throw new Error('unexpected shcema of person url: ' + url); + if (url && !checkHttps(url)) { + throw new Error('unexpected schema of person url: ' + url); } const updates = { @@ -478,7 +492,16 @@ export class ApPersonService implements OnModuleInit { movedToUri: person.movedTo ?? null, alsoKnownAs: person.alsoKnownAs ?? null, isExplorable: !!person.discoverable, - } as Partial<User>; + } as Partial<RemoteUser> & Pick<RemoteUser, 'isBot' | 'isCat' | 'isLocked' | 'movedToUri' | 'alsoKnownAs' | 'isExplorable'>; + + const moving = + // 移行先ãŒãªã„→ã‚ã‚‹ + (!exist.movedToUri && updates.movedToUri) || + // 移行先ãŒã‚る→別ã®ã‚‚ã® + (exist.movedToUri !== updates.movedToUri && exist.movedToUri && updates.movedToUri); + // 移行先ãŒã‚る→ãªã„ã€ãªã„→ãªã„ã¯ç„¡è¦– + + if (moving) updates.movedAt = new Date(); if (avatar) { updates.avatarId = avatar.id; @@ -523,6 +546,31 @@ export class ApPersonService implements OnModuleInit { }); await this.updateFeatured(exist.id, resolver).catch(err => this.logger.error(err)); + + const updated = { ...exist, ...updates }; + + this.cacheService.uriPersonCache.set(uri, updated); + + // 移行処ç†ã‚’行ㆠ+ if (updated.movedAt && ( + // åˆã‚ã¦ç§»è¡Œã™ã‚‹å ´åˆã¯movedAtãŒnullãªã®ã§ç§»è¡Œå‡¦ç†ã‚’è¨±å¯ + exist.movedAt == null || + // 以å‰ã®movingã‹ã‚‰14日以上経éŽã—ãŸå ´åˆã®ã¿ç§»è¡Œå‡¦ç†ã‚’è¨±å¯ + // (Mastodonã®ã‚¯ãƒ¼ãƒ«ãƒ€ã‚¦ãƒ³æœŸé–“ã¯30æ—¥ã ãŒè‹¥å¹²ç·©ã‚ã«è¨å®šã—ã¦ãŠã) + exist.movedAt.getTime() + 1000 * 60 * 60 * 24 * 14 < updated.movedAt.getTime() + )) { + this.logger.info(`Start to process Move of @${updated.username}@${updated.host} (${uri})`); + return this.processRemoteMove(updated, movePreventUris) + .then(result => { + this.logger.info(`Processing Move Finished [${result}] @${updated.username}@${updated.host} (${uri})`); + return result; + }) + .catch(e => { + this.logger.info(`Processing Move Failed @${updated.username}@${updated.host} (${uri})`, { stack: e }); + }); + } + + return 'skip'; } /** @@ -532,7 +580,7 @@ export class ApPersonService implements OnModuleInit { * リモートサーãƒãƒ¼ã‹ã‚‰ãƒ•ã‚§ãƒƒãƒã—ã¦Misskeyã«ç™»éŒ²ã—ãれを返ã—ã¾ã™ã€‚ */ @bindThis - public async resolvePerson(uri: string, resolver?: Resolver): Promise<User> { + public async resolvePerson(uri: string, resolver?: Resolver): Promise<LocalUser | RemoteUser> { if (typeof uri !== 'string') throw new Error('uri is not string'); //#region ã“ã®ã‚µãƒ¼ãƒãƒ¼ã«æ—¢ã«ç™»éŒ²ã•ã‚Œã¦ã„ãŸã‚‰ãれを返㙠@@ -607,4 +655,53 @@ export class ApPersonService implements OnModuleInit { } }); } + + /** + * リモート由æ¥ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆç§»è¡Œå‡¦ç†ã‚’è¡Œã„ã¾ã™ + * @param src 移行元アカウント(リモートã‹ã¤updatePerson後ã§ã‚ã‚‹å¿…è¦ãŒã‚ã‚‹ã€ã¨ã„ã†ã‹ã“れ自体ãŒupdatePersonã§å‘¼ã°ã‚Œã‚‹å‰æ) + * @param movePreventUris ã“ã“ã«åˆ—挙ã•ã‚ŒãŸURIã«src.movedToUriãŒå«ã¾ã‚Œã‚‹å ´åˆã€ç§»è¡Œå‡¦ç†ã¯ã—ãªã„(無é™ãƒ«ãƒ¼ãƒ—防æ¢ï¼‰ + */ + @bindThis + private async processRemoteMove(src: RemoteUser, movePreventUris: string[] = []): Promise<string> { + if (!src.movedToUri) return 'skip: no movedToUri'; + if (src.uri === src.movedToUri) return 'skip: movedTo itself (src)'; // ??? + if (movePreventUris.length > 10) return 'skip: too many moves'; + + // ã¾ãšã‚µãƒ¼ãƒãƒ¼å†…ã§æ¤œç´¢ã—ã¦æ§˜å見 + let dst = await this.fetchPerson(src.movedToUri); + + if (dst && this.userEntityService.isLocalUser(dst)) { + // targetãŒãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã ã£ãŸå ´åˆãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã‹ã‚‰å¼•ã£å¼µã£ã¦ãã‚‹ + dst = await this.usersRepository.findOneByOrFail({ uri: src.movedToUri }) as LocalUser; + } else if (dst) { + if (movePreventUris.includes(src.movedToUri)) return 'skip: circular move'; + + // targetを見ã¤ã‘ãŸã“ã¨ãŒã‚ã‚‹ãªã‚‰targetã‚’updatePersonã™ã‚‹ + await this.updatePerson(src.movedToUri, undefined, undefined, [...movePreventUris, src.uri]); + dst = await this.fetchPerson(src.movedToUri) ?? dst; + } else { + if (src.movedToUri.startsWith(`${this.config.url}/`)) { + // ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã£ã½ã„ã®ã«fetchPersonã§è¦‹ã¤ã‹ã‚‰ãªã„ã¨ã„ã†ã“ã¨ã¯movedToUriãŒé–“é•ã£ã¦ã„ã‚‹ + return 'failed: movedTo is local but not found'; + } + + // targetãŒçŸ¥ã‚‰ãªã„人ã ã£ãŸã‚‰resolvePerson + // (uriãŒå˜åœ¨ã—ãªã‹ã£ãŸã‚Šå¿œç”ãŒãªã‹ã£ãŸã‚Šã™ã‚‹å ´åˆresolvePersonã¯throw Errorã™ã‚‹) + dst = await this.resolvePerson(src.movedToUri); + } + + if (dst.movedToUri === dst.uri) return 'skip: movedTo itself (dst)'; // ??? + if (src.movedToUri !== dst.uri) return 'skip: missmatch uri'; // ??? + if (dst.movedToUri === src.uri) return 'skip: dst.movedToUri === src.uri'; + if (!dst.alsoKnownAs || dst.alsoKnownAs.length === 0) { + return 'skip: dst.alsoKnownAs is empty'; + } + if (!dst.alsoKnownAs?.includes(src.uri)) { + return 'skip: alsoKnownAs does not include from.uri'; + } + + await this.accountMoveService.postMoveProcess(src, dst); + + return 'ok'; + } } diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts index 987002606f418d41680f71ef35f2fe7fc0b5063a..15ffd4486136b0759ce4495e141f8be099930bad 100644 --- a/packages/backend/src/core/entities/ChannelEntityService.ts +++ b/packages/backend/src/core/entities/ChannelEntityService.ts @@ -74,6 +74,8 @@ export class ChannelEntityService { userId: channel.userId, bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null, pinnedNoteIds: channel.pinnedNoteIds, + color: channel.color, + isArchived: channel.isArchived, usersCount: channel.usersCount, notesCount: channel.notesCount, diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 26debd6adc05df355e7391fbcd8f768f13cc25ab..32269a4101418cc1664e76a4f8880cfbc8cd0175 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -335,6 +335,7 @@ export class NoteEntityService implements OnModuleInit { channel: channel ? { id: channel.id, name: channel.name, + color: channel.color, } : undefined, mentions: note.mentions.length > 0 ? note.mentions : undefined, uri: note.uri ?? undefined, diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index 0dc63d969f8a89230d2914c1c7a48a5668f5d88d..d76b863957393aa999c8fa60f91d8392d364bb58 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { AccessTokensRepository, NoteReactionsRepository, NotesRepository, User, UsersRepository } from '@/models/index.js'; +import type { AccessTokensRepository, FollowRequestsRepository, NoteReactionsRepository, NotesRepository, User, UsersRepository } from '@/models/index.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import type { Notification } from '@/models/entities/Notification.js'; import type { Note } from '@/models/entities/Note.js'; @@ -35,6 +35,9 @@ export class NotificationEntityService implements OnModuleInit { @Inject(DI.noteReactionsRepository) private noteReactionsRepository: NoteReactionsRepository, + @Inject(DI.followRequestsRepository) + private followRequestsRepository: FollowRequestsRepository, + @Inject(DI.accessTokensRepository) private accessTokensRepository: AccessTokensRepository, @@ -131,6 +134,15 @@ export class NotificationEntityService implements OnModuleInit { }); const packedUsers = new Map(packedUsersArray.map(p => [p.id, p])); + // æ—¢ã«è§£æ±ºã•ã‚ŒãŸãƒ•ã‚©ãƒãƒ¼ãƒªã‚¯ã‚¨ã‚¹ãƒˆã®é€šçŸ¥ã‚’除外 + const followRequestNotifications = validNotifications.filter(x => x.type === 'receiveFollowRequest'); + if (followRequestNotifications.length > 0) { + const reqs = await this.followRequestsRepository.find({ + where: { followerId: In(followRequestNotifications.map(x => x.notifierId!)) }, + }); + validNotifications = validNotifications.filter(x => (x.type !== 'receiveFollowRequest') || reqs.some(r => r.followerId === x.notifierId)); + } + return await Promise.all(validNotifications.map(x => this.pack(x, meId, {}, { packedNotes, packedUsers, diff --git a/packages/backend/src/core/entities/RoleEntityService.ts b/packages/backend/src/core/entities/RoleEntityService.ts index e111a10b777e66be91619ddc25f79a27a2c2ca75..54818782dd7dedfd7b289555cd63902a00af1ef3 100644 --- a/packages/backend/src/core/entities/RoleEntityService.ts +++ b/packages/backend/src/core/entities/RoleEntityService.ts @@ -59,6 +59,7 @@ export class RoleEntityService { isPublic: role.isPublic, isAdministrator: role.isAdministrator, isModerator: role.isModerator, + isExplorable: role.isExplorable, asBadge: role.asBadge, canEditMembersByModerator: role.canEditMembersByModerator, displayOrder: role.displayOrder, diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index e02f7535d4307f3a6f9779ff9388742595948a3f..a7f62c05f79af1a708348ff6c051c9b7b3c32bef 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, Not } from 'typeorm'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import Ajv from 'ajv'; import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; @@ -9,10 +9,9 @@ import type { Packed } from '@/misc/json-schema.js'; import type { Promiseable } from '@/misc/prelude/await-all.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js'; -import type { Instance } from '@/models/entities/Instance.js'; -import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js'; +import type { LocalUser, PartialLocalUser, PartialRemoteUser, RemoteUser, User } from '@/models/entities/User.js'; import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js'; -import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, PagesRepository, UserProfile, RenoteMutingsRepository } from '@/models/index.js'; +import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, PagesRepository, UserProfile, RenoteMutingsRepository, UserMemoRepository } from '@/models/index.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; @@ -35,13 +34,13 @@ type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends bo const ajv = new Ajv(); function isLocalUser(user: User): user is LocalUser; -function isLocalUser<T extends { host: User['host'] }>(user: T): user is T & { host: null; }; +function isLocalUser<T extends { host: User['host'] }>(user: T): user is (T & { host: null; }); function isLocalUser(user: User | { host: User['host'] }): boolean { return user.host == null; } function isRemoteUser(user: User): user is RemoteUser; -function isRemoteUser<T extends { host: User['host'] }>(user: T): user is T & { host: string; }; +function isRemoteUser<T extends { host: User['host'] }>(user: T): user is (T & { host: string; }); function isRemoteUser(user: User | { host: User['host'] }): boolean { return !isLocalUser(user); } @@ -113,6 +112,9 @@ export class UserEntityService implements OnModuleInit { @Inject(DI.pagesRepository) private pagesRepository: PagesRepository, + + @Inject(DI.userMemosRepository) + private userMemosRepository: UserMemoRepository, //private noteEntityService: NoteEntityService, //private driveFileEntityService: DriveFileEntityService, @@ -277,9 +279,20 @@ export class UserEntityService implements OnModuleInit { return `${this.config.url}/identicon/${user.username.toLowerCase()}@${user.host ?? this.config.host}`; } + @bindThis + public getUserUri(user: LocalUser | PartialLocalUser | RemoteUser | PartialRemoteUser): string { + return this.isRemoteUser(user) + ? user.uri : this.genLocalUserUri(user.id); + } + + @bindThis + public genLocalUserUri(userId: string): string { + return `${this.config.url}/users/${userId}`; + } + public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>( src: User['id'] | User, - me?: { id: User['id'] } | null | undefined, + me?: { id: User['id']; } | null | undefined, options?: { detail?: D, includeSecrets?: boolean, @@ -293,26 +306,9 @@ export class UserEntityService implements OnModuleInit { const user = typeof src === 'object' ? src : await this.usersRepository.findOneByOrFail({ id: src }); - // migration - if (user.avatarId != null && user.avatarUrl === null) { - const avatar = await this.driveFilesRepository.findOneByOrFail({ id: user.avatarId }); - user.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar'); - this.usersRepository.update(user.id, { - avatarUrl: user.avatarUrl, - avatarBlurhash: avatar.blurhash, - }); - } - if (user.bannerId != null && user.bannerUrl === null) { - const banner = await this.driveFilesRepository.findOneByOrFail({ id: user.bannerId }); - user.bannerUrl = this.driveFileEntityService.getPublicUrl(banner); - this.usersRepository.update(user.id, { - bannerUrl: user.bannerUrl, - bannerBlurhash: banner.blurhash, - }); - } - const meId = me ? me.id : null; const isMe = meId === user.id; + const iAmModerator = me ? await this.roleService.isModerator(me as User) : false; const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null; const pins = opts.detail ? await this.userNotePiningsRepository.createQueryBuilder('pin') @@ -366,8 +362,11 @@ export class UserEntityService implements OnModuleInit { ...(opts.detail ? { url: profile!.url, uri: user.uri, - movedToUri: user.movedToUri ? await this.apPersonService.resolvePerson(user.movedToUri) : null, - alsoKnownAs: user.alsoKnownAs, + movedTo: user.movedToUri ? this.apPersonService.resolvePerson(user.movedToUri).then(user => user.id).catch(() => null) : null, + alsoKnownAs: user.alsoKnownAs + ? Promise.all(user.alsoKnownAs.map(uri => this.apPersonService.fetchPerson(uri).then(user => user?.id).catch(() => null))) + .then(xs => xs.length === 0 ? null : xs.filter(x => x != null) as string[]) + : null, createdAt: user.createdAt.toISOString(), updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null, @@ -409,6 +408,11 @@ export class UserEntityService implements OnModuleInit { isAdministrator: role.isAdministrator, displayOrder: role.displayOrder, }))), + memo: meId == null ? null : await this.userMemosRepository.findOneBy({ + userId: meId, + targetUserId: user.id, + }).then(row => row?.memo ?? null), + moderationNote: iAmModerator ? (profile!.moderationNote ?? '') : undefined, } : {}), ...(opts.detail && isMe ? { diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts index 1688bb2ba7cc7935b2fa2ae21b2a994d4e5e56d3..bb190cf60fa581d7240adb1f9e931bbbb42dba79 100644 --- a/packages/backend/src/daemons/ServerStatsService.ts +++ b/packages/backend/src/daemons/ServerStatsService.ts @@ -40,7 +40,7 @@ export class ServerStatsService implements OnApplicationShutdown { const stats = { cpu: roundCpu(cpu), mem: { - used: round(memStats.used - memStats.buffers - memStats.cached), + used: round(memStats.total - memStats.available), active: round(memStats.active), }, net: { diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index d4b1fb31b107a10f31b6f6da1954d49ade2178cb..c06c7a7159c2114d5718dce8986b895d66f00284 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -1,6 +1,7 @@ export const DI = { config: Symbol('config'), db: Symbol('db'), + meilisearch: Symbol('meilisearch'), redis: Symbol('redis'), redisForPub: Symbol('redisForPub'), redisForSub: Symbol('redisForSub'), @@ -70,5 +71,6 @@ export const DI = { roleAssignmentsRepository: Symbol('roleAssignmentsRepository'), flashsRepository: Symbol('flashsRepository'), flashLikesRepository: Symbol('flashLikesRepository'), + userMemosRepository: Symbol('userMemosRepository'), //#endregion }; diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index f413246a1fc3ff6c0bc594b574fa4cb56256e3b9..5610929648b147341cb67d3529abb3c2667e8929 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -1,4 +1,4 @@ -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { bindThis } from '@/decorators.js'; export class RedisKVCache<T> { @@ -38,7 +38,7 @@ export class RedisKVCache<T> { await this.redisClient.set( `kvcache:${this.name}:${key}`, this.toRedisConverter(value), - 'ex', Math.round(this.lifetime / 1000), + 'EX', Math.round(this.lifetime / 1000), ); } } @@ -122,7 +122,7 @@ export class RedisSingleCache<T> { await this.redisClient.set( `singlecache:${this.name}`, this.toRedisConverter(value), - 'ex', Math.round(this.lifetime / 1000), + 'EX', Math.round(this.lifetime / 1000), ); } } diff --git a/packages/backend/src/misc/check-https.ts b/packages/backend/src/misc/check-https.ts new file mode 100644 index 0000000000000000000000000000000000000000..b33f0199735dc8215c990ab96cf04972b209742b --- /dev/null +++ b/packages/backend/src/misc/check-https.ts @@ -0,0 +1,4 @@ +export function checkHttps(url: string) { + return url.startsWith('https://') || + (url.startsWith('http://') && process.env.NODE_ENV !== 'production'); +} diff --git a/packages/backend/src/misc/check-word-mute.ts b/packages/backend/src/misc/check-word-mute.ts index e8c66683ccb364cb5bcf06080535a4ad820e2def..910bebfcfe57401fa416012cc50a3df2cde80b5c 100644 --- a/packages/backend/src/misc/check-word-mute.ts +++ b/packages/backend/src/misc/check-word-mute.ts @@ -1,3 +1,4 @@ +import { AhoCorasick } from 'slacc'; import RE2 from 're2'; import type { Note } from '@/models/entities/Note.js'; import type { User } from '@/models/entities/User.js'; @@ -12,6 +13,8 @@ type UserLike = { id: User['id']; }; +const acCache = new Map<string, AhoCorasick>(); + export async function checkWordMute(note: NoteLike, me: UserLike | null | undefined, mutedWords: Array<string | string[]>): Promise<boolean> { // 自分自身 if (me && (note.userId === me.id)) return false; @@ -21,7 +24,22 @@ export async function checkWordMute(note: NoteLike, me: UserLike | null | undefi if (text === '') return false; - const matched = mutedWords.some(filter => { + const acable = mutedWords.filter(filter => Array.isArray(filter) && filter.length === 1).map(filter => filter[0]).sort(); + const unacable = mutedWords.filter(filter => !Array.isArray(filter) || filter.length !== 1); + const acCacheKey = acable.join('\n'); + const ac = acCache.get(acCacheKey) ?? AhoCorasick.withPatterns(acable); + acCache.delete(acCacheKey); + for (const obsoleteKeys of acCache.keys()) { + if (acCache.size > 1000) { + acCache.delete(obsoleteKeys); + } + } + acCache.set(acCacheKey, ac); + if (ac.isMatch(text)) { + return true; + } + + const matched = unacable.some(filter => { if (Array.isArray(filter)) { return filter.every(keyword => text.includes(keyword)); } else { diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 7be7b8190455137e9e7d5ca0c1b4a59b4b27958e..588c98b58dd8a3baddeb88532cb531ffe4879fc6 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js'; +import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo } from './index.js'; import type { DataSource } from 'typeorm'; import type { Provider } from '@nestjs/common'; @@ -388,6 +388,12 @@ const $roleAssignmentsRepository: Provider = { inject: [DI.db], }; +const $userMemosRepository: Provider = { + provide: DI.userMemosRepository, + useFactory: (db: DataSource) => db.getRepository(UserMemo), + inject: [DI.db], +}; + @Module({ imports: [ ], @@ -456,6 +462,7 @@ const $roleAssignmentsRepository: Provider = { $roleAssignmentsRepository, $flashsRepository, $flashLikesRepository, + $userMemosRepository, ], exports: [ $usersRepository, @@ -522,6 +529,7 @@ const $roleAssignmentsRepository: Provider = { $roleAssignmentsRepository, $flashsRepository, $flashLikesRepository, + $userMemosRepository, ], }) export class RepositoryModule {} diff --git a/packages/backend/src/models/entities/Channel.ts b/packages/backend/src/models/entities/Channel.ts index 2d346fdf9d4c6dd28835415bb4c3a96f258e5928..d7c4583da34cfd992619997ba2dcda8aed6fa457 100644 --- a/packages/backend/src/models/entities/Channel.ts +++ b/packages/backend/src/models/entities/Channel.ts @@ -64,6 +64,18 @@ export class Channel { }) public pinnedNoteIds: string[]; + @Column('varchar', { + length: 16, + default: '#86b300', + }) + public color: string; + + @Index() + @Column('boolean', { + default: false, + }) + public isArchived: boolean; + @Index() @Column('integer', { default: 0, diff --git a/packages/backend/src/models/entities/Meta.ts b/packages/backend/src/models/entities/Meta.ts index 2e4f90b57f8d6e56adc1a6327c9733e1f910aef7..6d44e4edc7664c2964cb7f3119301a48e2d12df7 100644 --- a/packages/backend/src/models/entities/Meta.ts +++ b/packages/backend/src/models/entities/Meta.ts @@ -405,4 +405,16 @@ export class Meta { default: { }, }) public policies: Record<string, any>; + + @Column('varchar', { + length: 280, + array: true, + default: '{}', + }) + public serverRules: string[]; + + @Column('varchar', { + length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }', + }) + public preservedUsernames: string[]; } diff --git a/packages/backend/src/models/entities/Role.ts b/packages/backend/src/models/entities/Role.ts index eca9bcf2706d5ab54e182968cfff1859b27596ea..61f40d59dabdbef6c32b12247780fcf3cc7095fb 100644 --- a/packages/backend/src/models/entities/Role.ts +++ b/packages/backend/src/models/entities/Role.ts @@ -151,6 +151,11 @@ export class Role { }) public isAdministrator: boolean; + @Column('boolean', { + default: false, + }) + public isExplorable: boolean; + @Column('boolean', { default: false, }) diff --git a/packages/backend/src/models/entities/User.ts b/packages/backend/src/models/entities/User.ts index 04dfa2110725450aba2dff8bef01e18b99fb78c7..8e10f999b617e665a536a9b3688d808dd44bbdcf 100644 --- a/packages/backend/src/models/entities/User.ts +++ b/packages/backend/src/models/entities/User.ts @@ -75,6 +75,12 @@ export class User { }) public movedToUri: string | null; + @Column('timestamp with time zone', { + nullable: true, + comment: 'When the user moved to another account', + }) + public movedAt: Date | null; + @Column('simple-array', { nullable: true, comment: 'URIs the user is known as too', @@ -253,11 +259,23 @@ export type LocalUser = User & { uri: null; } +export type PartialLocalUser = Partial<User> & { + id: User['id']; + host: null; + uri: null; +} + export type RemoteUser = User & { host: string; uri: string; } +export type PartialRemoteUser = Partial<User> & { + id: User['id']; + host: string; + uri: string; +} + export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const; export const passwordSchema = { type: 'string', minLength: 1 } as const; export const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; diff --git a/packages/backend/src/models/entities/UserMemo.ts b/packages/backend/src/models/entities/UserMemo.ts new file mode 100644 index 0000000000000000000000000000000000000000..7dc34b434696c21ee75fa6a4453e73a1fabbe4a4 --- /dev/null +++ b/packages/backend/src/models/entities/UserMemo.ts @@ -0,0 +1,42 @@ +import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm'; +import { id } from '../id.js'; +import { User } from './User.js'; + +@Entity() +@Index(['userId', 'targetUserId'], { unique: true }) +export class UserMemo { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column({ + ...id(), + comment: 'The ID of author.', + }) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public user: User | null; + + @Index() + @Column({ + ...id(), + comment: 'The ID of target user.', + }) + public targetUserId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE', + }) + @JoinColumn() + public targetUser: User | null; + + @Column('varchar', { + length: 2048, + comment: 'Memo.', + }) + public memo: string; +} diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index 48d6e15f2a319b55c6706c80367e35d7c1026345..b8ba28db9b58fc2a69c550957e681a1b407e9a63 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -55,6 +55,7 @@ import { UserPending } from '@/models/entities/UserPending.js'; import { UserProfile } from '@/models/entities/UserProfile.js'; import { UserPublickey } from '@/models/entities/UserPublickey.js'; import { UserSecurityKey } from '@/models/entities/UserSecurityKey.js'; +import { UserMemo } from '@/models/entities/UserMemo.js'; import { Webhook } from '@/models/entities/Webhook.js'; import { Channel } from '@/models/entities/Channel.js'; import { RetentionAggregation } from '@/models/entities/RetentionAggregation.js'; @@ -129,6 +130,7 @@ export { RoleAssignment, Flash, FlashLike, + UserMemo, }; export type AbuseUserReportsRepository = Repository<AbuseUserReport>; @@ -195,3 +197,4 @@ export type RolesRepository = Repository<Role>; export type RoleAssignmentsRepository = Repository<RoleAssignment>; export type FlashsRepository = Repository<Flash>; export type FlashLikesRepository = Repository<FlashLike>; +export type UserMemoRepository = Repository<UserMemo>; diff --git a/packages/backend/src/models/json-schema/channel.ts b/packages/backend/src/models/json-schema/channel.ts index 745b39a6ba1e35344a0807cc326047d92b70d693..fd61a70c0ecca3274b56ec92c031f4552ead6d53 100644 --- a/packages/backend/src/models/json-schema/channel.ts +++ b/packages/backend/src/models/json-schema/channel.ts @@ -30,6 +30,10 @@ export const packedChannelSchema = { format: 'url', nullable: true, optional: false, }, + isArchived: { + type: 'boolean', + optional: false, nullable: false, + }, notesCount: { type: 'number', nullable: false, optional: false, @@ -59,5 +63,9 @@ export const packedChannelSchema = { format: 'id', }, }, + color: { + type: 'string', + 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 7d40979e3d5c6018d398a562cbf674a08955e2fa..529c1303d14ad2ac6d99bf142b225505b8359752 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -80,9 +80,14 @@ export const packedUserDetailedNotMeOnlySchema = { }, alsoKnownAs: { type: 'array', - format: 'uri', nullable: true, optional: false, + items: { + type: 'string', + format: 'id', + nullable: false, + optional: false, + }, }, createdAt: { type: 'string', @@ -143,6 +148,7 @@ export const packedUserDetailedNotMeOnlySchema = { fields: { type: 'array', nullable: false, optional: false, + maxItems: 16, items: { type: 'object', nullable: false, optional: false, @@ -156,7 +162,6 @@ export const packedUserDetailedNotMeOnlySchema = { nullable: false, optional: false, }, }, - maxLength: 4, }, }, followersCount: { @@ -250,6 +255,10 @@ export const packedUserDetailedNotMeOnlySchema = { type: 'boolean', nullable: false, optional: true, }, + memo: { + type: 'string', + nullable: false, optional: true, + }, //#endregion }, } as const; diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index bb21ed827ee7fd1c73a74cf3bca5b49d7b5dbeb6..f3d404e6c9b4f4197b57ef72b1d701deb34fb92f 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -70,6 +70,7 @@ import { Role } from '@/models/entities/Role.js'; import { RoleAssignment } from '@/models/entities/RoleAssignment.js'; import { Flash } from '@/models/entities/Flash.js'; import { FlashLike } from '@/models/entities/FlashLike.js'; +import { UserMemo } from '@/models/entities/UserMemo.js'; import { Config } from '@/config.js'; import MisskeyLogger from '@/logger.js'; @@ -183,6 +184,7 @@ export const entities = [ RoleAssignment, Flash, FlashLike, + UserMemo, ...charts, ]; diff --git a/packages/backend/src/queue/DbQueueProcessorsService.ts b/packages/backend/src/queue/DbQueueProcessorsService.ts index 233a36dd04beb13a1dffe42102a466dcc0698596..df8ac3a301b44a8f265680a068d5769666a63a10 100644 --- a/packages/backend/src/queue/DbQueueProcessorsService.ts +++ b/packages/backend/src/queue/DbQueueProcessorsService.ts @@ -9,11 +9,13 @@ import { ExportFollowingProcessorService } from './processors/ExportFollowingPro import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js'; import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js'; import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js'; +import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js'; import { ImportFollowingProcessorService } from './processors/ImportFollowingProcessorService.js'; import { ImportMutingProcessorService } from './processors/ImportMutingProcessorService.js'; import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js'; import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js'; import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js'; +import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js'; import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js'; import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js'; import type Bull from 'bull'; @@ -32,11 +34,13 @@ export class DbQueueProcessorsService { private exportMutingProcessorService: ExportMutingProcessorService, private exportBlockingProcessorService: ExportBlockingProcessorService, private exportUserListsProcessorService: ExportUserListsProcessorService, + private exportAntennasProcessorService: ExportAntennasProcessorService, private importFollowingProcessorService: ImportFollowingProcessorService, private importMutingProcessorService: ImportMutingProcessorService, private importBlockingProcessorService: ImportBlockingProcessorService, private importUserListsProcessorService: ImportUserListsProcessorService, private importCustomEmojisProcessorService: ImportCustomEmojisProcessorService, + private importAntennasProcessorService: ImportAntennasProcessorService, private deleteAccountProcessorService: DeleteAccountProcessorService, ) { } @@ -51,6 +55,7 @@ export class DbQueueProcessorsService { q.process('exportMuting', (job, done) => this.exportMutingProcessorService.process(job, done)); q.process('exportBlocking', (job, done) => this.exportBlockingProcessorService.process(job, done)); q.process('exportUserLists', (job, done) => this.exportUserListsProcessorService.process(job, done)); + q.process('exportAntennas', (job, done) => this.exportAntennasProcessorService.process(job, done)); q.process('importFollowing', (job, done) => this.importFollowingProcessorService.process(job, done)); q.process('importFollowingToDb', (job) => this.importFollowingProcessorService.processDb(job)); q.process('importMuting', (job, done) => this.importMutingProcessorService.process(job, done)); @@ -58,6 +63,7 @@ export class DbQueueProcessorsService { q.process('importBlockingToDb', (job) => this.importBlockingProcessorService.processDb(job)); q.process('importUserLists', (job, done) => this.importUserListsProcessorService.process(job, done)); q.process('importCustomEmojis', (job, done) => this.importCustomEmojisProcessorService.process(job, done)); + q.process('importAntennas', (job, done) => this.importAntennasProcessorService.process(job, done)); q.process('deleteAccount', (job) => this.deleteAccountProcessorService.process(job)); } } diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts index 4db9b3854769224abe5d4bc7c83b7119f36e2f6c..3d4cc773219cd23b32755c04e6fe76cd1258574c 100644 --- a/packages/backend/src/queue/QueueProcessorModule.ts +++ b/packages/backend/src/queue/QueueProcessorModule.ts @@ -24,11 +24,13 @@ import { ExportFollowingProcessorService } from './processors/ExportFollowingPro import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js'; import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js'; import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js'; +import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js'; import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js'; import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js'; import { ImportFollowingProcessorService } from './processors/ImportFollowingProcessorService.js'; import { ImportMutingProcessorService } from './processors/ImportMutingProcessorService.js'; import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js'; +import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js'; import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js'; import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js'; import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js'; @@ -55,11 +57,13 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor ExportMutingProcessorService, ExportBlockingProcessorService, ExportUserListsProcessorService, + ExportAntennasProcessorService, ImportFollowingProcessorService, ImportMutingProcessorService, ImportBlockingProcessorService, ImportUserListsProcessorService, ImportCustomEmojisProcessorService, + ImportAntennasProcessorService, DeleteAccountProcessorService, DeleteFileProcessorService, CleanRemoteFilesProcessorService, diff --git a/packages/backend/src/queue/RelationshipQueueProcessorsService.ts b/packages/backend/src/queue/RelationshipQueueProcessorsService.ts index af086fa4e74dc86422d0a5b8ff2fb468379eaf05..736b4fa80dda58c63a2cf74ef6bbb19da68aff26 100644 --- a/packages/backend/src/queue/RelationshipQueueProcessorsService.ts +++ b/packages/backend/src/queue/RelationshipQueueProcessorsService.ts @@ -17,7 +17,7 @@ export class RelationshipQueueProcessorsService { @bindThis public start(q: Bull.Queue): void { - const maxJobs = (this.config.deliverJobConcurrency ?? 128) / 4; // conservative? + const maxJobs = this.config.relashionshipJobConcurrency ?? 16; q.process('follow', maxJobs, (job) => this.relationshipProcessorService.processFollow(job)); q.process('unfollow', maxJobs, (job) => this.relationshipProcessorService.processUnfollow(job)); q.process('block', maxJobs, (job) => this.relationshipProcessorService.processBlock(job)); diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 0e99b7bcd295d397f33d4f9703f92eafd43b211a..f293bd4d7e016c5d33e4719f851dd27fc4a35730 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -79,10 +79,7 @@ export class DeliverProcessorService { // Update stats this.federatedInstanceService.fetch(host).then(i => { if (i.isNotResponding) { - this.instancesRepository.update(i.id, { - isNotResponding: false, - }); - this.federatedInstanceService.updateCachePartial(host, { + this.federatedInstanceService.update(i.id, { isNotResponding: false, }); } @@ -101,10 +98,7 @@ export class DeliverProcessorService { // Update stats this.federatedInstanceService.fetch(host).then(i => { if (!i.isNotResponding) { - this.instancesRepository.update(i.id, { - isNotResponding: true, - }); - this.federatedInstanceService.updateCachePartial(host, { + this.federatedInstanceService.update(i.id, { isNotResponding: true, }); } @@ -123,10 +117,7 @@ export class DeliverProcessorService { // 相手ãŒé–‰éŽ–ã—ã¦ã„ã‚‹ã“ã¨ã‚’明示ã—ã¦ã„ã‚‹ãŸã‚ã€é…é€åœæ¢ã™ã‚‹ if (job.data.isSharedInbox && res.statusCode === 410) { this.federatedInstanceService.fetch(host).then(i => { - this.instancesRepository.update(i.id, { - isSuspended: true, - }); - this.federatedInstanceService.updateCachePartial(host, { + this.federatedInstanceService.update(i.id, { isSuspended: true, }); }); diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts new file mode 100644 index 0000000000000000000000000000000000000000..894903e79b1073f351731652c512df3d218b3ac4 --- /dev/null +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -0,0 +1,103 @@ +import fs from 'node:fs'; +import { Inject, Injectable } from '@nestjs/common'; +import { format as DateFormat } from 'date-fns'; +import { In } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { AntennasRepository, UsersRepository, UserListJoiningsRepository, User } from '@/models/index.js'; +import type { Config } from '@/config.js'; +import Logger from '@/logger.js'; +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 { QueueLoggerService } from '../QueueLoggerService.js'; +import type { DBExportAntennasData } from '../types.js'; +import type Bull from 'bull'; + +@Injectable() +export class ExportAntennasProcessorService { + private logger: Logger; + + constructor ( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.antennasRepository) + private antennsRepository: AntennasRepository, + + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, + + private driveService: DriveService, + private utilityService: UtilityService, + private queueLoggerService: QueueLoggerService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('export-antennas'); + } + + @bindThis + public async process(job: Bull.Job<DBExportAntennasData>, done: () => void): Promise<void> { + const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); + if (user == null) { + done(); + return; + } + const [path, cleanup] = await createTemp(); + const stream = fs.createWriteStream(path, { flags: 'a' }); + const write = (input: string): Promise<void> => { + return new Promise((resolve, reject) => { + stream.write(input, err => { + if (err) { + this.logger.error(err); + reject(); + } else { + resolve(); + } + }); + }); + }; + try { + const antennas = await this.antennsRepository.findBy({ userId: job.data.user.id }); + write('['); + for (const [index, antenna] of antennas.entries()) { + let users: User[] | undefined; + if (antenna.userListId !== null) { + const joinings = await this.userListJoiningsRepository.findBy({ userListId: antenna.userListId }); + users = await this.usersRepository.findBy({ + id: In(joinings.map(j => j.userId)), + }); + } + write(JSON.stringify({ + name: antenna.name, + src: antenna.src, + keywords: antenna.keywords, + excludeKeywords: antenna.excludeKeywords, + users: antenna.users, + userListAccts: typeof users !== 'undefined' ? users.map((u) => { + return this.utilityService.getFullApAccount(u.username, u.host); // acct + }) : null, + caseSensitive: antenna.caseSensitive, + withReplies: antenna.withReplies, + withFile: antenna.withFile, + notify: antenna.notify, + })); + if (antennas.length - 1 !== index) { + write(', '); + } + } + write(']'); + stream.end(); + + 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); + } finally { + cleanup(); + done(); + } + } +} + diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts index a020006732801e47b9493a3a73e305b317e8560b..c7b54070d6a786244e7401d81592b8244b75b7a9 100644 --- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts @@ -106,7 +106,7 @@ export class ExportBlockingProcessorService { this.logger.succ(`Exported to: ${path}`); const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); + const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.succ(`Exported to: ${driveFile.id}`); } finally { diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts index daefcdf2f5be2db7f9ca61cd78abd4cbdaa740f7..f2f2383a8837d8c036fbca4a41629e6881ac7497 100644 --- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts @@ -121,7 +121,7 @@ export class ExportFavoritesProcessorService { this.logger.succ(`Exported to: ${path}`); const fileName = 'favorites-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; - const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); + const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); this.logger.succ(`Exported to: ${driveFile.id}`); } finally { diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index 59443de57f35108da682582c9ebfc6cde29cfc98..fa9c1ac1ea94b846fed0b205fae19d6a35bac0a4 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -110,7 +110,7 @@ export class ExportFollowingProcessorService { this.logger.succ(`Exported to: ${path}`); const fileName = 'following-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); + const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.succ(`Exported to: ${driveFile.id}`); } finally { diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts index a2a718b892f3404ab6bd3f6082165a1a0884ee6d..b14bf5f5b18af052e973a23f11e1a8d9beddb0bf 100644 --- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts @@ -110,7 +110,7 @@ export class ExportMutingProcessorService { this.logger.succ(`Exported to: ${path}`); const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); + const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.succ(`Exported to: ${driveFile.id}`); } finally { diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index 1aa20d6f1d9480060a2864df67a2a023b4de1309..e4f12ad101054ba6bc27df9e3911aad63937c591 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -117,7 +117,7 @@ export class ExportNotesProcessorService { this.logger.succ(`Exported to: ${path}`); const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; - const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); + const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); this.logger.succ(`Exported to: ${driveFile.id}`); } finally { diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index ce8ed2f5e8c0e5f7170e46914fb3d163106b504e..54bde44044de147a1b6198e9a4efbf61437a296a 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -86,7 +86,7 @@ export class ExportUserListsProcessorService { this.logger.succ(`Exported to: ${path}`); const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true }); + const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.succ(`Exported to: ${driveFile.id}`); } finally { diff --git a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts new file mode 100644 index 0000000000000000000000000000000000000000..d06131b8c81b80169f9413556b8beb8de5f2a01c --- /dev/null +++ b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts @@ -0,0 +1,96 @@ +import { Injectable, Inject } from '@nestjs/common'; +import Ajv from 'ajv'; +import { IdService } from '@/core/IdService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import Logger from '@/logger.js'; +import type { AntennasRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; +import { DBAntennaImportJobData } from '../types.js'; +import type Bull from 'bull'; + +const validate = new Ajv().compile({ + type: 'object', + properties: { + name: { type: 'string', minLength: 1, maxLength: 100 }, + src: { type: 'string', enum: ['home', 'all', 'users', 'list'] }, + userListAccts: { + type: 'array', + items: { + type: 'string', + }, + nullable: true, + }, + keywords: { type: 'array', items: { + type: 'array', items: { + type: 'string', + }, + } }, + excludeKeywords: { type: 'array', items: { + type: 'array', items: { + type: 'string', + }, + } }, + users: { type: 'array', items: { + type: 'string', + } }, + caseSensitive: { type: 'boolean' }, + withReplies: { type: 'boolean' }, + withFile: { type: 'boolean' }, + notify: { type: 'boolean' }, + }, + required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile', 'notify'], +}); + +@Injectable() +export class ImportAntennasProcessorService { + private logger: Logger; + + constructor ( + @Inject(DI.antennasRepository) + private antennasRepository: AntennasRepository, + + private queueLoggerService: QueueLoggerService, + private idService: IdService, + private globalEventService: GlobalEventService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('import-antennas'); + } + + @bindThis + public async process(job: Bull.Job<DBAntennaImportJobData>, done: () => void): Promise<void> { + const now = new Date(); + try { + for (const antenna of job.data.antenna) { + if (antenna.keywords.length === 0 || antenna.keywords[0].every(x => x === '')) continue; + if (!validate(antenna)) { + this.logger.warn('Validation Failed'); + continue; + } + const result = await this.antennasRepository.insert({ + id: this.idService.genId(), + createdAt: now, + lastUsedAt: now, + userId: job.data.user.id, + name: antenna.name, + src: antenna.src === 'list' && antenna.userListAccts ? 'users' : antenna.src, + userListId: null, + keywords: antenna.keywords, + excludeKeywords: antenna.excludeKeywords, + users: (antenna.src === 'list' && antenna.userListAccts !== null ? antenna.userListAccts : antenna.users).filter(Boolean), + caseSensitive: antenna.caseSensitive, + withReplies: antenna.withReplies, + withFile: antenna.withFile, + notify: antenna.notify, + }).then(x => this.antennasRepository.findOneByOrFail(x.identifiers[0])); + this.logger.succ('Antenna created: ' + result.id); + this.globalEventService.publishInternalEvent('antennaCreated', result); + } + } catch (err: any) { + this.logger.error(err); + } finally { + done(); + } + } +} diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index ed7f38d0137242e97504122089d5dad62c7c99da..ab8b1e9e22556d683f9e3b30c926858db22afc57 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -84,9 +84,9 @@ export class InboxProcessorService { // HTTP-Signature keyIdã‚’å…ƒã«DBã‹ã‚‰å–å¾— let authUser: { - user: RemoteUser; - key: UserPublickey | null; - } | null = await this.apDbResolverService.getAuthUserFromKeyId(signature.keyId); + user: RemoteUser; + key: UserPublickey | null; + } | null = await this.apDbResolverService.getAuthUserFromKeyId(signature.keyId); // keyIdã§ã‚ã‹ã‚‰ãªã‘ã‚Œã°ã€activity.actorã‚’å…ƒã«DBã‹ã‚‰å–å¾— || activity.actorã‚’å…ƒã«ãƒªãƒ¢ãƒ¼ãƒˆã‹ã‚‰å–å¾— if (authUser == null) { @@ -174,13 +174,10 @@ export class InboxProcessorService { // Update stats this.federatedInstanceService.fetch(authUser.user.host).then(i => { - this.instancesRepository.update(i.id, { + this.federatedInstanceService.update(i.id, { latestRequestReceivedAt: new Date(), isNotResponding: false, }); - this.federatedInstanceService.updateCachePartial(host, { - isNotResponding: false, - }); this.fetchInstanceMetadataService.fetchInstanceMetadata(i); diff --git a/packages/backend/src/queue/processors/RelationshipProcessorService.ts b/packages/backend/src/queue/processors/RelationshipProcessorService.ts index a5006dcf03bc58c9d5b5040023d6bf4d609fd913..ff454df455508464eb5b424c90593fe1edb7b35c 100644 --- a/packages/backend/src/queue/processors/RelationshipProcessorService.ts +++ b/packages/backend/src/queue/processors/RelationshipProcessorService.ts @@ -10,6 +10,7 @@ import { QueueLoggerService } from '../QueueLoggerService.js'; import { RelationshipJobData } from '../types.js'; import type { UsersRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; +import { LocalUser, RemoteUser } from '@/models/entities/User.js'; @Injectable() export class RelationshipProcessorService { @@ -39,7 +40,7 @@ export class RelationshipProcessorService { const [follower, followee] = await Promise.all([ this.usersRepository.findOneByOrFail({ id: job.data.from.id }), this.usersRepository.findOneByOrFail({ id: job.data.to.id }), - ]); + ]) as [LocalUser | RemoteUser, LocalUser | RemoteUser]; await this.userFollowingService.unfollow(follower, followee, job.data.silent); return 'ok'; } diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index 23c973d44902739605bfdead7834cfdc0a5e9fee..776dd3aa128a8ed60328cb8840d40b63816c9903 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -1,3 +1,4 @@ +import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Note } from '@/models/entities/Note.js'; import type { User } from '@/models/entities/User.js'; @@ -33,12 +34,14 @@ export type DbJobData<T extends keyof DbJobMap> = DbJobMap[T]; export type DbJobMap = { deleteDriveFiles: DbJobDataWithUser; exportCustomEmojis: DbJobDataWithUser; + exportAntennas: DBExportAntennasData; exportNotes: DbJobDataWithUser; exportFavorites: DbJobDataWithUser; exportFollowing: DbExportFollowingData; exportMuting: DbJobDataWithUser; exportBlocking: DbJobDataWithUser; exportUserLists: DbJobDataWithUser; + importAntennas: DBAntennaImportJobData; importFollowing: DbUserImportJobData; importFollowingToDb: DbUserImportToDbJobData; importMuting: DbUserImportJobData; @@ -59,6 +62,10 @@ export type DbExportFollowingData = { excludeInactive: boolean; }; +export type DBExportAntennasData = { + user: ThinUser +} + export type DbUserDeleteJobData = { user: ThinUser; soft?: boolean; @@ -69,6 +76,11 @@ export type DbUserImportJobData = { fileId: DriveFile['id']; }; +export type DBAntennaImportJobData = { + user: ThinUser, + antenna: Antenna +} + export type DbUserImportToDbJobData = { user: ThinUser; target: string; diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index e13e9265ab52feb5203e596580b8eec72439611d..e675d9cf1baf751e85e58f7aa082bbebce5d9f73 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -11,7 +11,7 @@ import * as url from '@/misc/prelude/url.js'; import type { Config } from '@/config.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { QueueService } from '@/core/QueueService.js'; -import type { LocalUser, User } from '@/models/entities/User.js'; +import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js'; import { UserKeypairService } from '@/core/UserKeypairService.js'; import type { Following } from '@/models/entities/Following.js'; import { countIf } from '@/misc/prelude/array.js'; @@ -630,7 +630,7 @@ export class ActivityPubServerService { id: request.params.followee, host: Not(IsNull()), }), - ]); + ]) as [LocalUser | RemoteUser | null, LocalUser | RemoteUser | null]; if (follower == null || followee == null) { reply.code(404); @@ -665,7 +665,7 @@ export class ActivityPubServerService { id: followRequest.followeeId, host: Not(IsNull()), }), - ]); + ]) as [LocalUser | RemoteUser | null, LocalUser | RemoteUser | null]; if (follower == null || followee == null) { reply.code(404); diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 794fa76d9e5cb92ed8fb4dabda4c80e49ec7e484..98329ddffa765c54c0b3b4e4e3bf1a65bad1b1b8 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -297,7 +297,8 @@ export class FileServerService { } else if ('badge' in request.query) { const mask = (await sharpBmp(file.path, file.mime)) .resize(96, 96, { - fit: 'inside', + fit: 'contain', + position: 'centre', withoutEnlargement: false, }) .greyscale() @@ -453,7 +454,8 @@ export class FileServerService { fileRole: 'original', file, filename: file.name, - mime: file.type, + // å¤ã„ファイルã¯ä¿®æ£å‰ã®mimeã‚’æŒã£ã¦ã„ã‚‹ã®ã§ã§ãã‚‹ã ã‘ä¿®æ£ã—ã¦ã‚ã’ã‚‹ + mime: this.fileInfoService.fixMime(file.type), ext: null, path, }; diff --git a/packages/backend/src/server/WellKnownServerService.ts b/packages/backend/src/server/WellKnownServerService.ts index e722563036668fcedb86ea0c817071df8dcf00c8..9bf8deb221a934f0ee55a74b936061b1d025c5f9 100644 --- a/packages/backend/src/server/WellKnownServerService.ts +++ b/packages/backend/src/server/WellKnownServerService.ts @@ -8,6 +8,7 @@ import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js'; import type { User } from '@/models/entities/User.js'; import * as Acct from '@/misc/acct.js'; import { NodeinfoServerService } from './NodeinfoServerService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { FindOptionsWhere } from 'typeorm'; import { bindThis } from '@/decorators.js'; import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; @@ -23,6 +24,7 @@ export class WellKnownServerService { private usersRepository: UsersRepository, private nodeinfoServerService: NodeinfoServerService, + private userEntityService: UserEntityService, ) { //this.createServer = this.createServer.bind(this); } @@ -130,7 +132,7 @@ fastify.get('/.well-known/change-password', async (request, reply) => { const self = { rel: 'self', type: 'application/activity+json', - href: `${this.config.url}/users/${user.id}`, + href: this.userEntityService.genLocalUserUri(user.id), }; const profilePage = { rel: 'http://webfinger.net/rel/profile-page', diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index bf5cb20918f703b1f8506c5c715400fcdd3d1313..e3483c82c67d31fa59c02639927883c7eca56e3b 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -261,6 +261,17 @@ export class ApiCallService implements OnApplicationShutdown { } } + if (ep.meta.prohibitMoved) { + if (user?.movedToUri) { + throw new ApiError({ + message: 'You have moved your account.', + code: 'YOUR_ACCOUNT_MOVED', + id: '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31', + httpStatusCode: 403, + }); + } + } + if ((ep.meta.requireModerator || ep.meta.requireAdmin) && !user!.isRoot) { const myRoles = await this.roleService.getUserRoles(user!.id); if (ep.meta.requireModerator && !myRoles.some(r => r.isModerator || r.isAdministrator)) { diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 689f90287ecb34da21e5944ea5e1ca094d644f26..ee1aae5b6c787fead3660845c5aad2e8f5e20506 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -29,6 +29,7 @@ import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js'; import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js'; import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js'; import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js'; +import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js'; import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js'; import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js'; import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js'; @@ -193,6 +194,7 @@ import * as ep___i_exportMute from './endpoints/i/export-mute.js'; import * as ep___i_exportNotes from './endpoints/i/export-notes.js'; import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js'; import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js'; +import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js'; import * as ep___i_favorites from './endpoints/i/favorites.js'; import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js'; import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js'; @@ -201,6 +203,7 @@ import * as ep___i_importBlocking from './endpoints/i/import-blocking.js'; import * as ep___i_importFollowing from './endpoints/i/import-following.js'; import * as ep___i_importMuting from './endpoints/i/import-muting.js'; import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js'; +import * as ep___i_importAntennas from './endpoints/i/import-antennas.js'; import * as ep___i_notifications from './endpoints/i/notifications.js'; import * as ep___i_pageLikes from './endpoints/i/page-likes.js'; import * as ep___i_pages from './endpoints/i/pages.js'; @@ -222,7 +225,6 @@ import * as ep___i_unpin from './endpoints/i/unpin.js'; import * as ep___i_updateEmail from './endpoints/i/update-email.js'; import * as ep___i_update from './endpoints/i/update.js'; import * as ep___i_move from './endpoints/i/move.js'; -import * as ep___i_knownAs from './endpoints/i/known-as.js'; import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js'; import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js'; import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js'; @@ -330,6 +332,7 @@ import * as ep___users_search from './endpoints/users/search.js'; import * as ep___users_show from './endpoints/users/show.js'; import * as ep___users_stats from './endpoints/users/stats.js'; import * as ep___users_achievements from './endpoints/users/achievements.js'; +import * as ep___users_updateMemo from './endpoints/users/update-memo.js'; import * as ep___fetchRss from './endpoints/fetch-rss.js'; import * as ep___retention from './endpoints/retention.js'; import { GetterService } from './GetterService.js'; @@ -364,6 +367,7 @@ const $admin_emoji_list: Provider = { provide: 'ep:admin/emoji/list', useClass: const $admin_emoji_removeAliasesBulk: Provider = { provide: 'ep:admin/emoji/remove-aliases-bulk', useClass: ep___admin_emoji_removeAliasesBulk.default }; const $admin_emoji_setAliasesBulk: Provider = { provide: 'ep:admin/emoji/set-aliases-bulk', useClass: ep___admin_emoji_setAliasesBulk.default }; const $admin_emoji_setCategoryBulk: Provider = { provide: 'ep:admin/emoji/set-category-bulk', useClass: ep___admin_emoji_setCategoryBulk.default }; +const $admin_emoji_setLicenseBulk: Provider = { provide: 'ep:admin/emoji/set-license-bulk', useClass: ep___admin_emoji_setLicenseBulk.default }; const $admin_emoji_update: Provider = { provide: 'ep:admin/emoji/update', useClass: ep___admin_emoji_update.default }; const $admin_federation_deleteAllFiles: Provider = { provide: 'ep:admin/federation/delete-all-files', useClass: ep___admin_federation_deleteAllFiles.default }; const $admin_federation_refreshRemoteInstanceMetadata: Provider = { provide: 'ep:admin/federation/refresh-remote-instance-metadata', useClass: ep___admin_federation_refreshRemoteInstanceMetadata.default }; @@ -528,6 +532,7 @@ const $i_exportMute: Provider = { provide: 'ep:i/export-mute', useClass: ep___i_ const $i_exportNotes: Provider = { provide: 'ep:i/export-notes', useClass: ep___i_exportNotes.default }; const $i_exportFavorites: Provider = { provide: 'ep:i/export-favorites', useClass: ep___i_exportFavorites.default }; const $i_exportUserLists: Provider = { provide: 'ep:i/export-user-lists', useClass: ep___i_exportUserLists.default }; +const $i_exportAntennas: Provider = { provide: 'ep:i/export-antennas', useClass: ep___i_exportAntennas.default }; const $i_favorites: Provider = { provide: 'ep:i/favorites', useClass: ep___i_favorites.default }; const $i_gallery_likes: Provider = { provide: 'ep:i/gallery/likes', useClass: ep___i_gallery_likes.default }; const $i_gallery_posts: Provider = { provide: 'ep:i/gallery/posts', useClass: ep___i_gallery_posts.default }; @@ -536,6 +541,7 @@ const $i_importBlocking: Provider = { provide: 'ep:i/import-blocking', useClass: const $i_importFollowing: Provider = { provide: 'ep:i/import-following', useClass: ep___i_importFollowing.default }; const $i_importMuting: Provider = { provide: 'ep:i/import-muting', useClass: ep___i_importMuting.default }; const $i_importUserLists: Provider = { provide: 'ep:i/import-user-lists', useClass: ep___i_importUserLists.default }; +const $i_importAntennas: Provider = { provide: 'ep:i/import-antennas', useClass: ep___i_importAntennas.default }; const $i_notifications: Provider = { provide: 'ep:i/notifications', useClass: ep___i_notifications.default }; const $i_pageLikes: Provider = { provide: 'ep:i/page-likes', useClass: ep___i_pageLikes.default }; const $i_pages: Provider = { provide: 'ep:i/pages', useClass: ep___i_pages.default }; @@ -557,7 +563,6 @@ const $i_unpin: Provider = { provide: 'ep:i/unpin', useClass: ep___i_unpin.defau const $i_updateEmail: Provider = { provide: 'ep:i/update-email', useClass: ep___i_updateEmail.default }; const $i_update: Provider = { provide: 'ep:i/update', useClass: ep___i_update.default }; const $i_move: Provider = { provide: 'ep:i/move', useClass: ep___i_move.default }; -const $i_knownAs: Provider = { provide: 'ep:i/known-as', useClass: ep___i_knownAs.default }; const $i_webhooks_create: Provider = { provide: 'ep:i/webhooks/create', useClass: ep___i_webhooks_create.default }; const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep___i_webhooks_list.default }; const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default }; @@ -665,6 +670,7 @@ const $users_search: Provider = { provide: 'ep:users/search', useClass: ep___use const $users_show: Provider = { provide: 'ep:users/show', useClass: ep___users_show.default }; const $users_stats: Provider = { provide: 'ep:users/stats', useClass: ep___users_stats.default }; const $users_achievements: Provider = { provide: 'ep:users/achievements', useClass: ep___users_achievements.default }; +const $users_updateMemo: Provider = { provide: 'ep:users/update-memo', useClass: ep___users_updateMemo.default }; const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.default }; const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention.default }; @@ -703,6 +709,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $admin_emoji_removeAliasesBulk, $admin_emoji_setAliasesBulk, $admin_emoji_setCategoryBulk, + $admin_emoji_setLicenseBulk, $admin_emoji_update, $admin_federation_deleteAllFiles, $admin_federation_refreshRemoteInstanceMetadata, @@ -867,6 +874,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_exportNotes, $i_exportFavorites, $i_exportUserLists, + $i_exportAntennas, $i_favorites, $i_gallery_likes, $i_gallery_posts, @@ -875,6 +883,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_importFollowing, $i_importMuting, $i_importUserLists, + $i_importAntennas, $i_notifications, $i_pageLikes, $i_pages, @@ -896,7 +905,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_updateEmail, $i_update, $i_move, - $i_knownAs, $i_webhooks_create, $i_webhooks_list, $i_webhooks_show, @@ -1004,6 +1012,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $users_show, $users_stats, $users_achievements, + $users_updateMemo, $fetchRss, $retention, ], @@ -1036,6 +1045,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $admin_emoji_removeAliasesBulk, $admin_emoji_setAliasesBulk, $admin_emoji_setCategoryBulk, + $admin_emoji_setLicenseBulk, $admin_emoji_update, $admin_federation_deleteAllFiles, $admin_federation_refreshRemoteInstanceMetadata, @@ -1200,6 +1210,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_exportNotes, $i_exportFavorites, $i_exportUserLists, + $i_exportAntennas, $i_favorites, $i_gallery_likes, $i_gallery_posts, @@ -1208,6 +1219,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_importFollowing, $i_importMuting, $i_importUserLists, + $i_importAntennas, $i_notifications, $i_pageLikes, $i_pages, @@ -1229,7 +1241,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_updateEmail, $i_update, $i_move, - $i_knownAs, $i_webhooks_create, $i_webhooks_list, $i_webhooks_show, @@ -1335,6 +1346,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $users_show, $users_stats, $users_achievements, + $users_updateMemo, $fetchRss, $retention, ], diff --git a/packages/backend/src/server/api/RateLimiterService.ts b/packages/backend/src/server/api/RateLimiterService.ts index 1f8915eccaf522be7d95d33bc741bbf12be67259..fe2db1d66a676a18367895deaee6532a0c9a1594 100644 --- a/packages/backend/src/server/api/RateLimiterService.ts +++ b/packages/backend/src/server/api/RateLimiterService.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import Limiter from 'ratelimiter'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index fbabf47aff29aa76f59d809da6bb797236998617..b2bd7d82e7e09a62fa4688e356a9c01dc71e4565 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import rndstr from 'rndstr'; import bcrypt from 'bcryptjs'; +import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; @@ -15,7 +16,6 @@ import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { bindThis } from '@/decorators.js'; import { SigninService } from './SigninService.js'; import type { FastifyRequest, FastifyReply } from 'fastify'; -import { IsNull } from 'typeorm'; @Injectable() export class SignupApiService { @@ -137,6 +137,11 @@ export class SignupApiService { throw new FastifyReplyError(400, 'USED_USERNAME'); } + const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase()); + if (isPreserved) { + throw new FastifyReplyError(400, 'DENIED_USERNAME'); + } + const code = rndstr('a-z0-9', 16); // Generate hash of password diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index 769a4490d61896e93c456cad3526850f0740babf..258e8de034195bb5aa00d02f4bc588ac841a5215 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'events'; import { Inject, Injectable } from '@nestjs/common'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import * as websocket from 'websocket'; import { DI } from '@/di-symbols.js'; import type { UsersRepository, BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, RenoteMutingsRepository } from '@/models/index.js'; diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index d0fe6a57c1856681c1972596ce207150cd88e809..09bd7cbff455ab12df430e88cc1ed61bd616a66e 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -29,6 +29,7 @@ import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js'; import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js'; import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js'; import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js'; +import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js'; import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js'; import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js'; import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js'; @@ -193,6 +194,7 @@ import * as ep___i_exportMute from './endpoints/i/export-mute.js'; import * as ep___i_exportNotes from './endpoints/i/export-notes.js'; import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js'; import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js'; +import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js'; import * as ep___i_favorites from './endpoints/i/favorites.js'; import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js'; import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js'; @@ -201,6 +203,7 @@ import * as ep___i_importBlocking from './endpoints/i/import-blocking.js'; import * as ep___i_importFollowing from './endpoints/i/import-following.js'; import * as ep___i_importMuting from './endpoints/i/import-muting.js'; import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js'; +import * as ep___i_importAntennas from './endpoints/i/import-antennas.js'; import * as ep___i_notifications from './endpoints/i/notifications.js'; import * as ep___i_pageLikes from './endpoints/i/page-likes.js'; import * as ep___i_pages from './endpoints/i/pages.js'; @@ -222,7 +225,6 @@ import * as ep___i_unpin from './endpoints/i/unpin.js'; import * as ep___i_updateEmail from './endpoints/i/update-email.js'; import * as ep___i_update from './endpoints/i/update.js'; import * as ep___i_move from './endpoints/i/move.js'; -import * as ep___i_knownAs from './endpoints/i/known-as.js'; import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js'; import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js'; import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js'; @@ -330,6 +332,7 @@ import * as ep___users_search from './endpoints/users/search.js'; import * as ep___users_show from './endpoints/users/show.js'; import * as ep___users_stats from './endpoints/users/stats.js'; import * as ep___users_achievements from './endpoints/users/achievements.js'; +import * as ep___users_updateMemo from './endpoints/users/update-memo.js'; import * as ep___fetchRss from './endpoints/fetch-rss.js'; import * as ep___retention from './endpoints/retention.js'; @@ -362,6 +365,7 @@ const eps = [ ['admin/emoji/remove-aliases-bulk', ep___admin_emoji_removeAliasesBulk], ['admin/emoji/set-aliases-bulk', ep___admin_emoji_setAliasesBulk], ['admin/emoji/set-category-bulk', ep___admin_emoji_setCategoryBulk], + ['admin/emoji/set-license-bulk', ep___admin_emoji_setLicenseBulk], ['admin/emoji/update', ep___admin_emoji_update], ['admin/federation/delete-all-files', ep___admin_federation_deleteAllFiles], ['admin/federation/refresh-remote-instance-metadata', ep___admin_federation_refreshRemoteInstanceMetadata], @@ -526,6 +530,7 @@ const eps = [ ['i/export-notes', ep___i_exportNotes], ['i/export-favorites', ep___i_exportFavorites], ['i/export-user-lists', ep___i_exportUserLists], + ['i/export-antennas', ep___i_exportAntennas], ['i/favorites', ep___i_favorites], ['i/gallery/likes', ep___i_gallery_likes], ['i/gallery/posts', ep___i_gallery_posts], @@ -534,6 +539,7 @@ const eps = [ ['i/import-following', ep___i_importFollowing], ['i/import-muting', ep___i_importMuting], ['i/import-user-lists', ep___i_importUserLists], + ['i/import-antennas', ep___i_importAntennas], ['i/notifications', ep___i_notifications], ['i/page-likes', ep___i_pageLikes], ['i/pages', ep___i_pages], @@ -554,8 +560,7 @@ const eps = [ ['i/unpin', ep___i_unpin], ['i/update-email', ep___i_updateEmail], ['i/update', ep___i_update], - //['i/move', ep___i_move], - //['i/known-as', ep___i_knownAs], + ['i/move', ep___i_move], ['i/webhooks/create', ep___i_webhooks_create], ['i/webhooks/list', ep___i_webhooks_list], ['i/webhooks/show', ep___i_webhooks_show], @@ -663,6 +668,7 @@ const eps = [ ['users/show', ep___users_show], ['users/stats', ep___users_stats], ['users/achievements', ep___users_achievements], + ['users/update-memo', ep___users_updateMemo], ['fetch-rss', ep___fetchRss], ['retention', ep___retention], ]; @@ -700,6 +706,12 @@ export interface IEndpointMeta { readonly requireRolePolicy?: keyof RolePolicies; + /** + * 引ã£è¶Šã—済ã¿ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«ã‚ˆã‚‹ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’ç¦æ¢ã™ã‚‹ã‹ + * çœç•¥ã—ãŸå ´åˆã¯ false ã¨ã—ã¦è§£é‡ˆã•ã‚Œã¾ã™ã€‚ + */ + readonly prohibitMoved?: boolean; + /** * エンドãƒã‚¤ãƒ³ãƒˆã®ãƒªãƒŸãƒ†ãƒ¼ã‚·ãƒ§ãƒ³ã«é–¢ã™ã‚‹ã‚„㤠* çœç•¥ã—ãŸå ´åˆã¯ãƒªãƒŸãƒ†ãƒ¼ã‚·ãƒ§ãƒ³ã¯ç„¡ã„ã‚‚ã®ã¨ã—ã¦è§£é‡ˆã•ã‚Œã¾ã™ã€‚ diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index bac8ae16e590d7dff4ed5f246c19450238a32dd1..8a3541dffea49fababcf8a3599d7e8345b6f6d56 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -52,6 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const { account, secret } = await this.signupService.signup({ username: ps.username, password: ps.password, + ignorePreservedUsernames: true, }); const res = await this.userEntityService.pack(account, account, { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index 814668294f58036a045c3248c88b1f2ad6b8949c..4aa4ad82b4064b71fa79fa75cce07acd067f77be 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -87,12 +87,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { //const emojis = await q.take(ps.limit).getMany(); emojis = await q.getMany(); + const queryarry = ps.query.match(/\:([a-z0-9_]*)\:/g); - emojis = emojis.filter(emoji => - emoji.name.includes(ps.query!) || - emoji.aliases.some(a => a.includes(ps.query!)) || - emoji.category?.includes(ps.query!)); - + if (queryarry) { + emojis = emojis.filter(emoji => + queryarry.includes(`:${emoji.name}:`) + ); + } else { + emojis = emojis.filter(emoji => + emoji.name.includes(ps.query!) || + emoji.aliases.some(a => a.includes(ps.query!)) || + emoji.category?.includes(ps.query!)); + } emojis.splice(ps.limit + 1); } else { emojis = await q.take(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts new file mode 100644 index 0000000000000000000000000000000000000000..b90b9757be28ca91e5a03cd584d7b3cabca59232 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts @@ -0,0 +1,37 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { CustomEmojiService } from '@/core/CustomEmojiService.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireRolePolicy: 'canManageCustomEmojis', +} as const; + +export const paramDef = { + type: 'object', + properties: { + ids: { type: 'array', items: { + type: 'string', format: 'misskey:id', + } }, + license: { + type: 'string', + nullable: true, + description: 'Use `null` to reset the license.', + }, + }, + required: ['ids'], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { + constructor( + private customEmojiService: CustomEmojiService, + ) { + super(meta, paramDef, async (ps, me) => { + await this.customEmojiService.setLicenseBulk(ps.ids, ps.license ?? null); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts index 54ce095488d834dd2541e5a8c338ed4931af7300..83f729953ada4c7d276cfb49827fe8aa7a490dc7 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -39,9 +39,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const pairs = await Promise.all(followings.map(f => Promise.all([ this.usersRepository.findOneByOrFail({ id: f.followerId }), this.usersRepository.findOneByOrFail({ id: f.followeeId }), - ]))); + ]).then(([from, to]) => [{ id: from.id }, { id: to.id }]))); - this.queueService.createUnfollowJob(pairs.map(p => ({ to: p[0], from: p[1], silent: true }))); + this.queueService.createUnfollowJob(pairs.map(p => ({ from: p[0], to: p[1], silent: true }))); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index 0a529ecb080b67a990e3bef5de93048de6707775..4fd74e591d193b95f3d12e887a03bd9b0cad83b0 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -3,6 +3,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { InstancesRepository } from '@/models/index.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DI } from '@/di-symbols.js'; +import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; export const meta = { tags: ['admin'], @@ -28,6 +29,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private instancesRepository: InstancesRepository, private utilityService: UtilityService, + private federatedInstanceService: FederatedInstanceService, ) { super(meta, paramDef, async (ps, me) => { const instance = await this.instancesRepository.findOneBy({ host: this.utilityService.toPuny(ps.host) }); @@ -36,7 +38,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { throw new Error('instance not found'); } - this.instancesRepository.update({ host: this.utilityService.toPuny(ps.host) }, { + this.federatedInstanceService.update(instance.id, { isSuspended: ps.isSuspended, }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index fc318a621a7766d974f2a171911f6db0534a6864..87a2d22ac2d58c285ac72eda0ceeee4a5b6b414c 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -118,6 +118,14 @@ export const meta = { optional: false, nullable: false, }, }, + preservedUsernames: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, hcaptchaSecretKey: { type: 'string', optional: true, nullable: true, @@ -311,6 +319,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { hiddenTags: instance.hiddenTags, blockedHosts: instance.blockedHosts, sensitiveWords: instance.sensitiveWords, + preservedUsernames: instance.preservedUsernames, hcaptchaSecretKey: instance.hcaptchaSecretKey, recaptchaSecretKey: instance.recaptchaSecretKey, turnstileSecretKey: instance.turnstileSecretKey, diff --git a/packages/backend/src/server/api/endpoints/admin/roles/create.ts b/packages/backend/src/server/api/endpoints/admin/roles/create.ts index 1359894634648ae819f490cfb654ecb3432d2bb1..916172f54aecec056619b7e7336c3616a7fd7d8b 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/create.ts @@ -25,6 +25,7 @@ export const paramDef = { isPublic: { type: 'boolean' }, isModerator: { type: 'boolean' }, isAdministrator: { type: 'boolean' }, + isExplorable: { type: 'boolean', default: false }, // optional for backward compatibility asBadge: { type: 'boolean' }, canEditMembersByModerator: { type: 'boolean' }, displayOrder: { type: 'number' }, @@ -76,12 +77,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { isPublic: ps.isPublic, isAdministrator: ps.isAdministrator, isModerator: ps.isModerator, + isExplorable: ps.isExplorable, asBadge: ps.asBadge, canEditMembersByModerator: ps.canEditMembersByModerator, displayOrder: ps.displayOrder, policies: ps.policies, }).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0])); - + this.globalEventService.publishInternalEvent('roleCreated', created); return await this.roleEntityService.pack(created, me); diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update.ts b/packages/backend/src/server/api/endpoints/admin/roles/update.ts index 37b68c4c41b6cef7e71e093e25a3860040f8d7c1..467f157a610e253498c89830633824d329cca77c 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/update.ts @@ -33,6 +33,7 @@ export const paramDef = { isPublic: { type: 'boolean' }, isModerator: { type: 'boolean' }, isAdministrator: { type: 'boolean' }, + isExplorable: { type: 'boolean' }, asBadge: { type: 'boolean' }, canEditMembersByModerator: { type: 'boolean' }, displayOrder: { type: 'number' }, @@ -85,6 +86,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { isPublic: ps.isPublic, isModerator: ps.isModerator, isAdministrator: ps.isAdministrator, + isExplorable: ps.isExplorable, asBadge: ps.asBadge, canEditMembersByModerator: ps.canEditMembersByModerator, displayOrder: ps.displayOrder, diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts index 9c576dffe95dd3fc97d2713b7f416fc16df183f6..4ef4fdc665afbf5ad95b6cadccceb086293f16f6 100644 --- a/packages/backend/src/server/api/endpoints/admin/server-info.ts +++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts @@ -2,7 +2,7 @@ import * as os from 'node:os'; import si from 'systeminformation'; import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; 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 9d19efbbcfefc8ca5dc9b505744b7499c7c012b1..42229c8f23421e022e6480ed44396b77c3e9813b 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -80,7 +80,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { isSilenced: isSilenced, isSuspended: user.isSuspended, lastActiveDate: user.lastActiveDate, - moderationNote: profile.moderationNote, + moderationNote: profile.moderationNote ?? '', signins, policies: await this.roleService.getUserPolicies(user.id), roles: await this.roleEntityService.packMany(roles, me), 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 11de29bf834c8fd6e6d376ac0a542d527e9a8ce6..0e94f56cfd3426ede515683fcadc19c9842e9ab1 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -94,6 +94,8 @@ export const paramDef = { enableActiveEmailValidation: { type: 'boolean' }, enableChartsForRemoteUser: { type: 'boolean' }, enableChartsForFederatedInstances: { type: 'boolean' }, + serverRules: { type: 'array', items: { type: 'string' } }, + preservedUsernames: { type: 'array', items: { type: 'string' } }, }, required: [], } as const; @@ -387,6 +389,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { set.enableChartsForFederatedInstances = ps.enableChartsForFederatedInstances; } + if (ps.serverRules !== undefined) { + set.serverRules = ps.serverRules; + } + + if (ps.preservedUsernames !== undefined) { + set.preservedUsernames = ps.preservedUsernames; + } + await this.metaService.update(set); this.moderationLogService.insertModerationLog(me, 'updateMeta'); }); diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index b7ce3363a93aeb0414280d6d4e0fead6f2ca24e4..5754a9f12aa5974f23ad7d6c080c454d7e03544e 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -13,6 +13,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:account', errors: { diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 88623ce26a0e36b5bb7999910a988e9c2b6ddb85..dca0f443b7975ebec6420e7b5f2aa24c7d2dea16 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { NotesRepository, AntennasRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 3f85442131b692f71ba57bc3eb299ba8be7441c3..5f980bdbeb10b27e02134a407a7bce94e3d30012 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -11,6 +11,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:account', errors: { @@ -71,7 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - + private antennaEntityService: AntennaEntityService, private globalEventService: GlobalEventService, ) { diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts index dff8a9d10df4cc891e233f878ca974002a505349..69e2f2504c6769ec902548742fa4f65f5d3d25ac 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -13,6 +13,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:channels', limit: { @@ -41,6 +43,7 @@ export const paramDef = { name: { type: 'string', minLength: 1, maxLength: 128 }, description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 }, bannerId: { type: 'string', format: 'misskey:id', nullable: true }, + color: { type: 'string', minLength: 1, maxLength: 16 }, }, required: ['name'], } as const; @@ -78,6 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { name: ps.name, description: ps.description ?? null, bannerId: banner ? banner.id : null, + ...(ps.color !== undefined ? { color: ps.color } : {}), } as Channel).then(x => this.channelsRepository.findOneByOrFail(x.identifiers[0])); return await this.channelEntityService.pack(channel, me); diff --git a/packages/backend/src/server/api/endpoints/channels/favorite.ts b/packages/backend/src/server/api/endpoints/channels/favorite.ts index f52b45ccf3f8ee1eb26106aa56e9f925affe1927..c8544273a17890747522fb0ac79a875d2caa743f 100644 --- a/packages/backend/src/server/api/endpoints/channels/favorite.ts +++ b/packages/backend/src/server/api/endpoints/channels/favorite.ts @@ -10,6 +10,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:channels', errors: { diff --git a/packages/backend/src/server/api/endpoints/channels/featured.ts b/packages/backend/src/server/api/endpoints/channels/featured.ts index d25faae38d94845d7ffeac2beb793b4f4e061960..1a8d1164c7ab0c87f46e46b1996a3a28d1f188f8 100644 --- a/packages/backend/src/server/api/endpoints/channels/featured.ts +++ b/packages/backend/src/server/api/endpoints/channels/featured.ts @@ -38,6 +38,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { super(meta, paramDef, async (ps, me) => { const query = this.channelsRepository.createQueryBuilder('channel') .where('channel.lastNotedAt IS NOT NULL') + .andWhere('channel.isArchived = FALSE') .orderBy('channel.lastNotedAt', 'DESC'); const channels = await query.take(10).getMany(); diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index 8ab59991c785ab3bfd6bf95e83096b893e293e0f..f3ca66cfd2d609c6295a4076730afe7a224e18d0 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -11,6 +11,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:channels', errors: { diff --git a/packages/backend/src/server/api/endpoints/channels/owned.ts b/packages/backend/src/server/api/endpoints/channels/owned.ts index 59df0616be0bc0280870779b320b8c93691995c2..8fae972cb1f1c0ba4c7993fb04a5c3029c2b1830 100644 --- a/packages/backend/src/server/api/endpoints/channels/owned.ts +++ b/packages/backend/src/server/api/endpoints/channels/owned.ts @@ -44,7 +44,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder(), ps.sinceId, ps.untilId) + const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId) + .andWhere('channel.isArchived = FALSE') .andWhere({ userId: me.id }); const channels = await query diff --git a/packages/backend/src/server/api/endpoints/channels/search.ts b/packages/backend/src/server/api/endpoints/channels/search.ts index a954ba224cfcb9af4bb3fa8f3ed355bbfd85ed00..a3b40b0bbd853e3223d52b5238df05c09b935c88 100644 --- a/packages/backend/src/server/api/endpoints/channels/search.ts +++ b/packages/backend/src/server/api/endpoints/channels/search.ts @@ -46,15 +46,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId); + const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId) + .andWhere('channel.isArchived = FALSE'); - if (ps.type === 'nameAndDescription') { - query.andWhere(new Brackets(qb => { qb - .where('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }) - .orWhere('channel.description ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }); - })); - } else { - query.andWhere('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }); + if (ps.query !== '') { + if (ps.type === 'nameAndDescription') { + query.andWhere(new Brackets(qb => { qb + .where('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }) + .orWhere('channel.description ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }); + })); + } else { + query.andWhere('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }); + } } const channels = await query diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 2491d14235e2d8114823ff410b94f38f8711d73d..c881074bab57afe3755c8de71d152675ebdc2490 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { ChannelsRepository, Note, NotesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; diff --git a/packages/backend/src/server/api/endpoints/channels/unfavorite.ts b/packages/backend/src/server/api/endpoints/channels/unfavorite.ts index 0c3f6c4855311db0114bfeae9adfb2a4ca51619a..67fb1ea03e1108353d97ad60d5d2d1292d00dc77 100644 --- a/packages/backend/src/server/api/endpoints/channels/unfavorite.ts +++ b/packages/backend/src/server/api/endpoints/channels/unfavorite.ts @@ -9,6 +9,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:channels', errors: { diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts index 855ba47f8c35c6cfef0f1fd63843f3110b83d749..f46ff9f2865cbacdb75ace00e1667adc9b1e2d57 100644 --- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts +++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts @@ -10,6 +10,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:channels', errors: { diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index 084b3f919e2a6bac508794c85617943171da156d..30d7f8b244e76049f408258e5ce60b5bcd57ccda 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -47,12 +47,14 @@ export const paramDef = { name: { type: 'string', minLength: 1, maxLength: 128 }, description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 }, bannerId: { type: 'string', format: 'misskey:id', nullable: true }, + isArchived: { type: 'boolean', nullable: true }, pinnedNoteIds: { type: 'array', items: { type: 'string', format: 'misskey:id', }, }, + color: { type: 'string', minLength: 1, maxLength: 16 }, }, required: ['channelId'], } as const; @@ -104,6 +106,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { ...(ps.name !== undefined ? { name: ps.name } : {}), ...(ps.description !== undefined ? { description: ps.description } : {}), ...(ps.pinnedNoteIds !== undefined ? { pinnedNoteIds: ps.pinnedNoteIds } : {}), + ...(ps.color !== undefined ? { color: ps.color } : {}), + ...(typeof ps.isArchived === 'boolean' ? { isArchived: ps.isArchived } : {}), ...(banner ? { bannerId: banner.id } : {}), }); diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index b9d8dce47a4ffb906f0530b3be4c8080207de5c2..c3561e2a710d47ae08eb856f2f93e44960d86b66 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -13,6 +13,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:account', limit: { diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index a770dc986d2deb584e21ce7bcb3fe1feb9323cc4..5395a5c373a97df8c1f486f5788393a136c3594e 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -12,6 +12,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:account', res: { @@ -57,7 +59,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { if (currentCount > (await this.roleService.getUserPolicies(me.id)).clipLimit) { throw new ApiError(meta.errors.tooManyClips); } - + const clip = await this.clipsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), diff --git a/packages/backend/src/server/api/endpoints/clips/favorite.ts b/packages/backend/src/server/api/endpoints/clips/favorite.ts index 6addf743a2728cb43e21ddece5481434fa4b5b25..f08caaf8d7e1e6bb4525d35cb28ae60e5abaab45 100644 --- a/packages/backend/src/server/api/endpoints/clips/favorite.ts +++ b/packages/backend/src/server/api/endpoints/clips/favorite.ts @@ -10,6 +10,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:clip-favorite', errors: { diff --git a/packages/backend/src/server/api/endpoints/clips/remove-note.ts b/packages/backend/src/server/api/endpoints/clips/remove-note.ts index 5d88870ed27f57246c6eb1f977d0428bad758726..50c5d758bda343d8d7fd8f6dc7d7ee3c55d08022 100644 --- a/packages/backend/src/server/api/endpoints/clips/remove-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/remove-note.ts @@ -10,6 +10,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:account', errors: { diff --git a/packages/backend/src/server/api/endpoints/clips/unfavorite.ts b/packages/backend/src/server/api/endpoints/clips/unfavorite.ts index 244843d50f2febee149dfb610b60f6e2cbb7b70f..3da252a2269ddb6d1631664f9b5a0731295da76b 100644 --- a/packages/backend/src/server/api/endpoints/clips/unfavorite.ts +++ b/packages/backend/src/server/api/endpoints/clips/unfavorite.ts @@ -9,6 +9,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:clip-favorite', errors: { diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts index a103c3f7d316abc58c674859376e72dba49b42aa..70f19593539258d89918ff13c7aa4ba3254f00d0 100644 --- a/packages/backend/src/server/api/endpoints/clips/update.ts +++ b/packages/backend/src/server/api/endpoints/clips/update.ts @@ -10,6 +10,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:account', errors: { 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 b3bdef41d3e3616dac2cc55f9dd9faa12051f19a..a1c1f9325ec7a89a4c16581c17023c7fca079394 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -15,6 +15,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + limit: { duration: ms('1hour'), max: 120, diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index 3141e0fc01b8ff651f864817f38461bd39fc835e..3ecbba22b54d5b4e1dce1e838ce69e8dcc693f65 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -40,8 +40,13 @@ export const meta = { code: 'NO_SUCH_FOLDER', id: 'ea8fb7a5-af77-4a08-b608-c0218176cd73', }, + + restrictedByRole: { + message: 'This feature is restricted by your role.', + code: 'RESTRICTED_BY_ROLE', + id: '7f59dccb-f465-75ab-5cf4-3ce44e3282f7', + }, }, - res: { type: 'object', optional: false, nullable: false, @@ -77,7 +82,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { ) { super(meta, paramDef, async (ps, me) => { const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); - + const alwaysMarkNsfw = (await this.roleService.getUserPolicies(me.id)).alwaysMarkNsfw; if (file == null) { throw new ApiError(meta.errors.noSuchFile); } @@ -93,6 +98,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { if (ps.comment !== undefined) file.comment = ps.comment; + if (ps.isSensitive !== undefined && ps.isSensitive !== file.isSensitive && alwaysMarkNsfw && !ps.isSensitive) { + throw new ApiError(meta.errors.restrictedByRole); + } + if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive; if (ps.folderId !== undefined) { diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index cfef7938318f67f346ad31f765cb891bab0985cf..c835587c4aa0ce9ad332914f81179f6a0b0b0379 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -19,6 +19,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:drive', } as const; diff --git a/packages/backend/src/server/api/endpoints/flash/create.ts b/packages/backend/src/server/api/endpoints/flash/create.ts index f21d9d5c336347a14e7c261a355f5c355431cdfe..3172bdbfdaf5bd906847bf7479d922cdf027d503 100644 --- a/packages/backend/src/server/api/endpoints/flash/create.ts +++ b/packages/backend/src/server/api/endpoints/flash/create.ts @@ -11,6 +11,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:flash', limit: { diff --git a/packages/backend/src/server/api/endpoints/flash/like.ts b/packages/backend/src/server/api/endpoints/flash/like.ts index 5581b8ec6069bd652dab85dc0e54c82659d62863..23de2f397098e54fee844a4432c9664f54c78b06 100644 --- a/packages/backend/src/server/api/endpoints/flash/like.ts +++ b/packages/backend/src/server/api/endpoints/flash/like.ts @@ -10,6 +10,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:flash-likes', errors: { diff --git a/packages/backend/src/server/api/endpoints/flash/unlike.ts b/packages/backend/src/server/api/endpoints/flash/unlike.ts index b994f5d3479c1df8c14e9724e915e9b020538dad..696512b06c9e331d96f638cb66a3eef04d5c1459 100644 --- a/packages/backend/src/server/api/endpoints/flash/unlike.ts +++ b/packages/backend/src/server/api/endpoints/flash/unlike.ts @@ -9,6 +9,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:flash-likes', errors: { diff --git a/packages/backend/src/server/api/endpoints/flash/update.ts b/packages/backend/src/server/api/endpoints/flash/update.ts index cd4e413a403b5bfe2197963778943289ec982f9b..78dfd4a06a71e1e615c4b310c59d1819454eafc1 100644 --- a/packages/backend/src/server/api/endpoints/flash/update.ts +++ b/packages/backend/src/server/api/endpoints/flash/update.ts @@ -10,6 +10,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:flash', limit: { diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 411c39110a5b6df743449a291e6a2fd574153029..4ad16de911ecaabcdb0d931939dc979f4070a0dd 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -19,6 +19,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:following', errors: { diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index cb8b6a2e3eb1d74555e0688bf0c23a9ec8cce74a..ca6bfa7e0f1baa8a236644398960819d072c7673 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -13,6 +13,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:gallery', limit: { diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index 519e56ed6a0f177292a712bfd86564d01a40fbb7..6ac5fa860650a8480f5c06c8d9f35138b600bc2b 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -10,6 +10,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:gallery-likes', errors: { diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts index cfbedcc4d93b3f1ed043723c1a623bc8b915e28f..513089217dfd7aa0c0272cb90d9f5e5d2e19f435 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts @@ -9,6 +9,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:gallery-likes', errors: { diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index f14d644a3aff4b49e3d8cd63d364f758aa1f1ee5..a2a10d8400389e1b79dcd091b2fd4849869ea5d1 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -11,6 +11,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:gallery', limit: { diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index c3f2ea9ea75b73cb3743e4398fee7f8b2b4f0581..dd3549020e6dcaaaed116e7209473a639499be64 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -44,7 +44,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { ) { super(meta, paramDef, async (ps, me) => { const query = this.usersRepository.createQueryBuilder('user') - .where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) }); + .where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) }) + .andWhere('user.isSuspended = FALSE'); const recent = new Date(Date.now() - (1000 * 60 * 60 * 24 * 5)); diff --git a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts index 102dae4fb765b4a1f4b827834836b363cb0b9d47..4eef49638504d65efecd40564e45d18b4bc7b5f4 100644 --- a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts +++ b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts @@ -4,6 +4,7 @@ import { AchievementService, ACHIEVEMENT_TYPES } from '@/core/AchievementService export const meta = { requireCredential: true, + prohibitMoved: true, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/export-antennas.ts b/packages/backend/src/server/api/endpoints/i/export-antennas.ts new file mode 100644 index 0000000000000000000000000000000000000000..4182c1b247095c79c27f1681e6021260a32b5fd4 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/export-antennas.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@nestjs/common'; +import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; + +export const meta = { + secure: true, + requireCredential: true, + limit: { + duration: ms('1hour'), + max: 1, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: {}, + required: [], +} as const; + +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export + constructor ( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportAntennasJob(me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts new file mode 100644 index 0000000000000000000000000000000000000000..efb5ce422349be68d3e1181ad918664084346257 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts @@ -0,0 +1,84 @@ +import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; +import type { AntennasRepository, DriveFilesRepository, UsersRepository, Antenna as _Antenna } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; +import { RoleService } from '@/core/RoleService.js'; +import { DownloadService } from '@/core/DownloadService.js'; +import { ApiError } from '../../error.js'; + +export const meta = { + secure: true, + requireCredential: true, + prohibitMoved: true, + + limit: { + duration: ms('1hour'), + max: 1, + }, + errors: { + noSuchFile: { + message: 'No such file.', + code: 'NO_SUCH_FILE', + id: '3b71d086-c3fa-431c-b01d-ded65a777172', + }, + noSuchUser: { + message: 'No such user.', + code: 'NO_SUCH_USER', + id: 'e842c379-8ac7-4cf7-b07a-4d4de7e4671c', + }, + emptyFile: { + message: 'That file is empty.', + code: 'EMPTY_FILE', + id: '7f60115d-8d93-4b0f-bd0e-3815dcbb389f', + }, + tooManyAntennas: { + message: 'You cannot create antenna any more.', + code: 'TOO_MANY_ANTENNAS', + id: '600917d4-a4cb-4cc5-8ba8-7ac8ea3c7779', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + fileId: { type: 'string', format: 'misskey:id' }, + }, + required: ['fileId'], +} as const; + +@Injectable() // eslint-disable-next-line import/no-default-export +export default class extends Endpoint<typeof meta, typeof paramDef> { + constructor ( + @Inject(DI.driveFilesRepository) + private driveFilesRepository: DriveFilesRepository, + + @Inject(DI.antennasRepository) + private antennasRepository: AntennasRepository, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private roleService: RoleService, + private queueService: QueueService, + private downloadService: DownloadService, + ) { + super(meta, paramDef, async (ps, me) => { + const users = await this.usersRepository.findOneBy({ id: me.id }); + if (users === null) throw new ApiError(meta.errors.noSuchUser); + const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); + if (file === null) throw new ApiError(meta.errors.noSuchFile); + if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + const antennas: (_Antenna & { userListAccts: string[] | null })[] = JSON.parse(await this.downloadService.downloadTextFile(file.url)); + const currentAntennasCount = await this.antennasRepository.countBy({ userId: me.id }); + if (currentAntennasCount + antennas.length > (await this.roleService.getUserPolicies(me.id)).antennaLimit) { + throw new ApiError(meta.errors.tooManyAntennas); + } + this.queueService.createImportAntennasJob(me, antennas); + }); + } +} + +export type Antenna = (_Antenna & { userListAccts: string[] | null })[]; 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 8c1c158ab1286051bc9d04ad0820d5085b2898af..811971591a107d38e4cc4f62218b88e3e762b8fc 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; +import { AccountMoveService } from '@/core/AccountMoveService.js'; import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -9,6 +10,7 @@ import { ApiError } from '../../error.js'; export const meta = { secure: true, requireCredential: true, + prohibitMoved: true, limit: { duration: ms('1hour'), @@ -58,15 +60,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private driveFilesRepository: DriveFilesRepository, private queueService: QueueService, + private accountMoveService: AccountMoveService, ) { super(meta, paramDef, async (ps, me) => { const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); if (file == null) throw new ApiError(meta.errors.noSuchFile); //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); - if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile); if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + const checkMoving = await this.accountMoveService.validateAlsoKnownAs( + me, + (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), + true + ); + if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); + this.queueService.createImportBlockingJob(me, file.id); }); } 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 383bdc02b5a64daeed23f707aaddbb4cab787a4b..8af278c8837c75abbbd7c7fa1a6d828a25649d72 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; +import { AccountMoveService } from '@/core/AccountMoveService.js'; import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -9,6 +10,7 @@ import { ApiError } from '../../error.js'; export const meta = { secure: true, requireCredential: true, + prohibitMoved: true, limit: { duration: ms('1hour'), max: 1, @@ -57,15 +59,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private driveFilesRepository: DriveFilesRepository, private queueService: QueueService, + private accountMoveService: AccountMoveService, ) { super(meta, paramDef, async (ps, me) => { const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); if (file == null) throw new ApiError(meta.errors.noSuchFile); //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); - if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile); if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + const checkMoving = await this.accountMoveService.validateAlsoKnownAs( + me, + (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), + true + ); + if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); + this.queueService.createImportFollowingJob(me, file.id); }); } 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 345ad916cb49ad8ecdc70c3e9ccb7c96edc01fa2..eb0f9ba474eb46dce07179af07d4369890cbcdba 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; +import { AccountMoveService } from '@/core/AccountMoveService.js'; import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -9,6 +10,7 @@ import { ApiError } from '../../error.js'; export const meta = { secure: true, requireCredential: true, + prohibitMoved: true, limit: { duration: ms('1hour'), @@ -58,15 +60,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private driveFilesRepository: DriveFilesRepository, private queueService: QueueService, + private accountMoveService: AccountMoveService, ) { super(meta, paramDef, async (ps, me) => { const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); if (file == null) throw new ApiError(meta.errors.noSuchFile); //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); - if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile); if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + const checkMoving = await this.accountMoveService.validateAlsoKnownAs( + me, + (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), + true + ); + if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); + this.queueService.createImportMutingJob(me, file.id); }); } 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 875af7ec2304240bad07f11c2207367c43668037..4568e939019c6919ff7e1dc7c6327d1aa9bb41e7 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 @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; +import { AccountMoveService } from '@/core/AccountMoveService.js'; import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -9,6 +10,7 @@ import { ApiError } from '../../error.js'; export const meta = { secure: true, requireCredential: true, + prohibitMoved: true, limit: { duration: ms('1hour'), max: 1, @@ -57,15 +59,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private driveFilesRepository: DriveFilesRepository, private queueService: QueueService, + private accountMoveService: AccountMoveService, ) { super(meta, paramDef, async (ps, me) => { const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); if (file == null) throw new ApiError(meta.errors.noSuchFile); //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); - if (file.size > 30000) throw new ApiError(meta.errors.tooBigFile); if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + const checkMoving = await this.accountMoveService.validateAlsoKnownAs( + me, + (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(), + true + ); + if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); + this.queueService.createImportUserListsJob(me, file.id); }); } diff --git a/packages/backend/src/server/api/endpoints/i/known-as.ts b/packages/backend/src/server/api/endpoints/i/known-as.ts deleted file mode 100644 index 964704d82b057dbe9f8f1b28bd89f19de2922318..0000000000000000000000000000000000000000 --- a/packages/backend/src/server/api/endpoints/i/known-as.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import ms from 'ms'; - -import { User } from '@/models/entities/User.js'; -import { Endpoint } from '@/server/api/endpoint-base.js'; -import { ApiError } from '@/server/api/error.js'; - -import { AccountMoveService } from '@/core/AccountMoveService.js'; -import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { ApiLoggerService } from '@/server/api/ApiLoggerService.js'; - -export const meta = { - tags: ['users'], - - secure: true, - requireCredential: true, - - limit: { - duration: ms('1day'), - max: 30, - }, - - errors: { - noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5', - }, - notRemote: { - message: 'User is not remote. You can only migrate from other instances.', - code: 'NOT_REMOTE', - id: '4362f8dc-731f-4ad8-a694-be2a88922a24', - }, - uriNull: { - message: 'User ActivityPup URI is null.', - code: 'URI_NULL', - id: 'bf326f31-d430-4f97-9933-5d61e4d48a23', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - alsoKnownAs: { type: 'string' }, - }, - required: ['alsoKnownAs'], -} as const; - -@Injectable() -export default class extends Endpoint<typeof meta, typeof paramDef> { - constructor( - private userEntityService: UserEntityService, - private remoteUserResolveService: RemoteUserResolveService, - private apiLoggerService: ApiLoggerService, - private accountMoveService: AccountMoveService, - ) { - super(meta, paramDef, async (ps, me) => { - // Check parameter - if (!ps.alsoKnownAs) throw new ApiError(meta.errors.noSuchUser); - - let unfiltered = ps.alsoKnownAs; - const updates = {} as Partial<User>; - - if (!unfiltered) { - updates.alsoKnownAs = null; - } else { - // Parse user's input into the old account - if (unfiltered.startsWith('acct:')) unfiltered = unfiltered.substring(5); - if (unfiltered.startsWith('@')) unfiltered = unfiltered.substring(1); - if (!unfiltered.includes('@')) throw new ApiError(meta.errors.notRemote); - - const userAddress = unfiltered.split('@'); - // Retrieve the old account - const knownAs = await this.remoteUserResolveService.resolveUser(userAddress[0], userAddress[1]).catch((e) => { - this.apiLoggerService.logger.warn(`failed to resolve remote user: ${e}`); - throw new ApiError(meta.errors.noSuchUser); - }); - - const toUrl: string | null = knownAs.uri; - if (!toUrl) throw new ApiError(meta.errors.uriNull); - // Only allow moving from a remote account - if (this.userEntityService.isLocalUser(knownAs)) throw new ApiError(meta.errors.notRemote); - - updates.alsoKnownAs = updates.alsoKnownAs?.concat([toUrl]) ?? [toUrl]; - } - - return await this.accountMoveService.createAlias(me, updates); - }); - } -} diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts index ac76e1f62093718c38bddba5d722c8f27c7dfb72..261dd527c09ed220952c3d8918ce74dbccb0ad77 100644 --- a/packages/backend/src/server/api/endpoints/i/move.ts +++ b/packages/backend/src/server/api/endpoints/i/move.ts @@ -7,40 +7,35 @@ import { DI } from '@/di-symbols.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { ApiError } from '@/server/api/error.js'; +import { LocalUser, RemoteUser } from '@/models/entities/User.js'; + import { AccountMoveService } from '@/core/AccountMoveService.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApiLoggerService } from '@/server/api/ApiLoggerService.js'; import { GetterService } from '@/server/api/GetterService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; + +import * as Acct from '@/misc/acct.js'; export const meta = { tags: ['users'], secure: true, requireCredential: true, + prohibitMoved: true, limit: { duration: ms('1day'), max: 5, }, errors: { - noSuchMoveTarget: { - message: 'No such move target.', - code: 'NO_SUCH_MOVE_TARGET', - id: 'b5c90186-4ab0-49c8-9bba-a1f76c202ba4', - }, - remoteAccountForbids: { + destinationAccountForbids: { message: - 'Remote account doesn\'t have proper \'Known As\' alias. Did you remember to set it?', - code: 'REMOTE_ACCOUNT_FORBIDS', + 'Destination account doesn\'t have proper \'Known As\' alias, or has already moved.', + code: 'DESTINATION_ACCOUNT_FORBIDS', id: 'b5c90186-4ab0-49c8-9bba-a1f766282ba4', }, - notRemote: { - message: 'User is not remote. You can only migrate to other instances.', - code: 'NOT_REMOTE', - id: '4362f8dc-731f-4ad8-a694-be2a88922a24', - }, rootForbidden: { message: 'The root can\'t migrate.', code: 'NOT_ROOT_FORBIDDEN', @@ -84,57 +79,52 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { @Inject(DI.config) private config: Config, - private userEntityService: UserEntityService, private remoteUserResolveService: RemoteUserResolveService, private apiLoggerService: ApiLoggerService, private accountMoveService: AccountMoveService, private getterService: GetterService, private apPersonService: ApPersonService, + private userEntityService: UserEntityService, ) { super(meta, paramDef, async (ps, me) => { // check parameter - if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchMoveTarget); + if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchUser); // abort if user is the root if (me.isRoot) throw new ApiError(meta.errors.rootForbidden); // abort if user has already moved if (me.movedToUri) throw new ApiError(meta.errors.alreadyMoved); - let unfiltered = ps.moveToAccount; - if (!unfiltered) throw new ApiError(meta.errors.noSuchMoveTarget); - // parse user's input into the destination account - if (unfiltered.startsWith('acct:')) unfiltered = unfiltered.substring(5); - if (unfiltered.startsWith('@')) unfiltered = unfiltered.substring(1); - if (!unfiltered.includes('@')) throw new ApiError(meta.errors.notRemote); - - const userAddress = unfiltered.split('@'); + const { username, host } = Acct.parse(ps.moveToAccount); // retrieve the destination account - let moveTo = await this.remoteUserResolveService.resolveUser(userAddress[0], userAddress[1]).catch((e) => { + let moveTo = await this.remoteUserResolveService.resolveUser(username, host).catch((e) => { this.apiLoggerService.logger.warn(`failed to resolve remote user: ${e}`); - throw new ApiError(meta.errors.noSuchMoveTarget); + throw new ApiError(meta.errors.noSuchUser); }); - const remoteMoveTo = await this.getterService.getRemoteUser(moveTo.id); - if (!remoteMoveTo.uri) throw new ApiError(meta.errors.uriNull); + const destination = await this.getterService.getUser(moveTo.id) as LocalUser | RemoteUser; + const newUri = this.userEntityService.getUserUri(destination); // update local db - await this.apPersonService.updatePerson(remoteMoveTo.uri); + await this.apPersonService.updatePerson(newUri); // retrieve updated user - moveTo = await this.apPersonService.resolvePerson(remoteMoveTo.uri); - // only allow moving to a remote account - if (this.userEntityService.isLocalUser(moveTo)) throw new ApiError(meta.errors.notRemote); + moveTo = await this.apPersonService.resolvePerson(newUri); - let allowed = false; - - const fromUrl = `${this.config.url}/users/${me.id}`; // make sure that the user has indicated the old account as an alias - moveTo.alsoKnownAs?.forEach((elem) => { - if (fromUrl.includes(elem)) allowed = true; - }); + const fromUrl = this.userEntityService.genLocalUserUri(me.id); + let allowed = false; + if (moveTo.alsoKnownAs) { + for (const knownAs of moveTo.alsoKnownAs) { + if (knownAs.includes(fromUrl)) { + allowed = true; + break; + } + } + } // abort if unintended - if (!(allowed && moveTo.uri && fromUrl)) throw new ApiError(meta.errors.remoteAccountForbids); + if (!allowed || moveTo.movedToUri) throw new ApiError(meta.errors.destinationAccountForbids); - return await this.accountMoveService.moveToRemote(me, moveTo); + return await this.accountMoveService.moveFromLocal(me, moveTo); }); } } diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index ba0487f2237f46f0071e4e8e20e27f965eca98bf..e141be764a795b483f00dd4f50a00a39f83c2b8d 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -1,5 +1,5 @@ import { Brackets, In } from 'typeorm'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { Inject, Injectable } from '@nestjs/common'; import type { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotesRepository } from '@/models/index.js'; import { obsoleteNotificationTypes, notificationTypes } from '@/types.js'; diff --git a/packages/backend/src/server/api/endpoints/i/pin.ts b/packages/backend/src/server/api/endpoints/i/pin.ts index d4af00027e09968aadd182c287fc91c0176b8366..2293500945d39add0b3a251b654409987cf56707 100644 --- a/packages/backend/src/server/api/endpoints/i/pin.ts +++ b/packages/backend/src/server/api/endpoints/i/pin.ts @@ -8,6 +8,7 @@ export const meta = { tags: ['account', 'notes'], requireCredential: true, + prohibitMoved: true, kind: 'write:account', diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index be1c72b207fbf41071624644cab018566348e2d5..6c66300bb7202718c7f913800d85f3ba9edf2586 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -3,6 +3,7 @@ import * as mfm from 'mfm-js'; import { Inject, Injectable } from '@nestjs/common'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractHashtags } from '@/misc/extract-hashtags.js'; +import * as Acct from '@/misc/acct.js'; import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/index.js'; import type { User } from '@/models/entities/User.js'; import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/entities/User.js'; @@ -19,7 +20,10 @@ import { HashtagService } from '@/core/HashtagService.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; import { CacheService } from '@/core/CacheService.js'; +import { AccountMoveService } from '@/core/AccountMoveService.js'; +import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { ApiLoggerService } from '../../ApiLoggerService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -71,6 +75,30 @@ export const meta = { code: 'TOO_MANY_MUTED_WORDS', id: '010665b1-a211-42d2-bc64-8f6609d79785', }, + + noSuchUser: { + message: 'No such user.', + code: 'NO_SUCH_USER', + id: 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5', + }, + + uriNull: { + message: 'User ActivityPup URI is null.', + code: 'URI_NULL', + id: 'bf326f31-d430-4f97-9933-5d61e4d48a23', + }, + + forbiddenToSetYourself: { + message: 'You can\'t set yourself as your own alias.', + code: 'FORBIDDEN_TO_SET_YOURSELF', + id: '25c90186-4ab0-49c8-9bba-a1fa6c202ba4', + }, + + restrictedByRole: { + message: 'This feature is restricted by your role.', + code: 'RESTRICTED_BY_ROLE', + id: '8feff0ba-5ab5-585b-31f4-4df816663fad', + } }, res: { @@ -129,6 +157,12 @@ export const paramDef = { emailNotificationTypes: { type: 'array', items: { type: 'string', } }, + alsoKnownAs: { + type: 'array', + maxItems: 10, + uniqueItems: true, + items: { type: 'string' }, + }, }, } as const; @@ -153,6 +187,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private globalEventService: GlobalEventService, private userFollowingService: UserFollowingService, private accountUpdateService: AccountUpdateService, + private accountMoveService: AccountMoveService, + private remoteUserResolveService: RemoteUserResolveService, + private apiLoggerService: ApiLoggerService, private hashtagService: HashtagService, private roleService: RoleService, private cacheService: CacheService, @@ -208,7 +245,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat; if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail; - if (typeof ps.alwaysMarkNsfw === 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw; + if (typeof ps.alwaysMarkNsfw === 'boolean') { + if ((await roleService.getUserPolicies(user.id)).alwaysMarkNsfw) throw new ApiError(meta.errors.restrictedByRole); + profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw; + } if (typeof ps.autoSensitive === 'boolean') profileUpdates.autoSensitive = ps.autoSensitive; if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes; @@ -221,6 +261,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { updates.avatarId = avatar.id; updates.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar'); updates.avatarBlurhash = avatar.blurhash; + } else if (ps.avatarId === null) { + updates.avatarId = null; + updates.avatarUrl = null; + updates.avatarBlurhash = null; } if (ps.bannerId) { @@ -232,6 +276,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { updates.bannerId = banner.id; updates.bannerUrl = this.driveFileEntityService.getPublicUrl(banner); updates.bannerBlurhash = banner.blurhash; + } else if (ps.bannerId === null) { + updates.bannerId = null; + updates.bannerUrl = null; + updates.bannerBlurhash = null; } if (ps.pinnedPageId) { @@ -252,6 +300,38 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { }); } + if (ps.alsoKnownAs) { + if (_user.movedToUri) { + throw new ApiError({ + message: 'You have moved your account.', + code: 'YOUR_ACCOUNT_MOVED', + id: '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31', + httpStatusCode: 403, + }); + } + + // Parse user's input into the old account + const newAlsoKnownAs = new Set<string>(); + for (const line of ps.alsoKnownAs) { + if (!line) throw new ApiError(meta.errors.noSuchUser); + const { username, host } = Acct.parse(line); + + // Retrieve the old account + const knownAs = await this.remoteUserResolveService.resolveUser(username, host).catch((e) => { + this.apiLoggerService.logger.warn(`failed to resolve dstination user: ${e}`); + throw new ApiError(meta.errors.noSuchUser); + }); + if (knownAs.id === _user.id) throw new ApiError(meta.errors.forbiddenToSetYourself); + + const toUrl = this.userEntityService.getUserUri(knownAs); + if (!toUrl) throw new ApiError(meta.errors.uriNull); + + newAlsoKnownAs.add(toUrl); + } + + updates.alsoKnownAs = newAlsoKnownAs.size > 0 ? Array.from(newAlsoKnownAs) : null; + } + //#region emojis/tags let emojis = [] as string[]; @@ -279,6 +359,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { //#endregion if (Object.keys(updates).length > 0) await this.usersRepository.update(user.id, updates); + if (Object.keys(updates).includes('alsoKnownAs')) { + this.cacheService.uriPersonCache.set(this.userEntityService.genLocalUserUri(user.id), { ...user, ...updates }); + } if (Object.keys(profileUpdates).length > 0) await this.userProfilesRepository.update(user.id, profileUpdates); const iObj = await this.userEntityService.pack<true, true>(user.id, user, { diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 37974ce2a3f1b03a452d57f27462ad17ba2d81c3..584ea07c3bdb008e6e3542076f38c2033ebfefc9 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -201,10 +201,6 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, - elasticsearch: { - type: 'boolean', - optional: false, nullable: false, - }, hcaptcha: { type: 'boolean', optional: false, nullable: false, @@ -310,6 +306,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { translatorAvailable: instance.deeplAuthKey != null, + serverRules: instance.serverRules, + policies: { ...DEFAULT_POLICIES, ...instance.policies }, mediaProxy: this.config.mediaProxy, @@ -329,7 +327,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { response.features = { registration: !instance.disableRegistration, emailRequiredForSignup: instance.emailRequiredForSignup, - elasticsearch: this.config.elasticsearch ? true : false, hcaptcha: instance.enableHcaptcha, recaptcha: instance.enableRecaptcha, turnstile: instance.enableTurnstile, diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index 6e24e1024d09a9c0dc3d70bb7b6f70753ea42105..ee358d5c6c0ec4d9b16b0d090d506fe173f64a6c 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -11,6 +11,7 @@ export const meta = { tags: ['account'], requireCredential: true, + prohibitMoved: true, kind: 'write:mutes', diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 69fafcb9c77307e108a54f6cb22b1f68cfec53a4..3f7f2cdecef3d4b2a76d4dd81be4271e52b2c056 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -18,6 +18,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + limit: { duration: ms('1hour'), max: 300, @@ -260,7 +262,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { let channel: Channel | null = null; if (ps.channelId != null) { - channel = await this.channelsRepository.findOneBy({ id: ps.channelId }); + channel = await this.channelsRepository.findOneBy({ id: ps.channelId, isArchived: false }); if (channel == null) { throw new ApiError(meta.errors.noSuchChannel); diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index 0ce80a1a63e8faefc1da1877e6a51c47e946364f..611ea19560174b405027d426b950104e662e928a 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -12,6 +12,7 @@ export const meta = { tags: ['notes', 'favorites'], requireCredential: true, + prohibitMoved: true, kind: 'write:favorites', diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 2a44dc537e8f1a7b6f3b738418ca83a5ee33b2a9..3a33b037f868abe1116fc4cf2373894a282c0724 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -17,6 +17,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:votes', errors: { diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts index 04e374d1aed37211a40cc34f5adc01e0b1a2109e..97cb026779c51a56f26d249ad04abbeed7b880c9 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts @@ -9,6 +9,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:reactions', errors: { diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index fb5abd917f9e4a60e8218e6417b05c2985594c23..f6385400c3c778c2be6531332d621569244c8a65 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -1,11 +1,10 @@ import { Inject, Injectable } from '@nestjs/common'; import type { NotesRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { QueryService } from '@/core/QueryService.js'; +import { SearchService } from '@/core/SearchService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; -import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; @@ -43,8 +42,7 @@ export const paramDef = { offset: { type: 'integer', default: 0 }, host: { type: 'string', - nullable: true, - description: 'The local host is represented with `null`.', + description: 'The local host is represented with `.`.', }, userId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, channelId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, @@ -61,11 +59,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { @Inject(DI.config) private config: Config, - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - private noteEntityService: NoteEntityService, - private queryService: QueryService, + private searchService: SearchService, private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { @@ -74,27 +69,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { throw new ApiError(meta.errors.unavailable); } - const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId); - - if (ps.userId) { - query.andWhere('note.userId = :userId', { userId: ps.userId }); - } else if (ps.channelId) { - query.andWhere('note.channelId = :channelId', { channelId: ps.channelId }); - } - - query - .andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }) - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser'); - - this.queryService.generateVisibilityQuery(query, me); - if (me) this.queryService.generateMutedUserQuery(query, me); - if (me) this.queryService.generateBlockedUserQuery(query, me); - - const notes = await query.take(ps.limit).getMany(); + const notes = await this.searchService.searchNote(ps.query, me, { + userId: ps.userId, + channelId: ps.channelId, + host: ps.host, + }, { + untilId: ps.untilId, + sinceId: ps.sinceId, + limit: ps.limit, + }); return await this.noteEntityService.packMany(notes, me); }); diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index 4015bf1f29df90bddc364be59fac9dc5493906f8..e08ab399f88f2b1dd41f1903b5102d9cca3a842f 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -13,6 +13,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:pages', limit: { diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index d27990f7e13c28c63aadc151bfd75b25b1969c8c..543c126d9cf937701c07e89e6222ed431efe7278 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -10,6 +10,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:page-likes', errors: { diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts index e397e2a23b81d48977c57931bc934cfefd077ff5..f0c0198460146a74fba76c0e143ee0b635711d38 100644 --- a/packages/backend/src/server/api/endpoints/pages/unlike.ts +++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts @@ -9,6 +9,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:page-likes', errors: { diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index 35b402ec568cb463a031dd7fedc2d22505a73caf..751274067ed276f79734aa6ad77e22c67e2e7e67 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -11,6 +11,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:pages', limit: { diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts index b285269617fa39e40ece35eea8a5fc4806b94826..beb5850d7883e6c205bd7d05b78be26e92a67700 100644 --- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts +++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts @@ -13,6 +13,7 @@ export const meta = { tags: ['account'], requireCredential: true, + prohibitMoved: true, kind: 'write:mutes', diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts index 655dd7cd83d8332696b3574f0e2cd4be1fc4fc2c..4ced6d3ff175ac1f6325e3127644e712a6ba2c1a 100644 --- a/packages/backend/src/server/api/endpoints/reset-db.ts +++ b/packages/backend/src/server/api/endpoints/reset-db.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { resetDb } from '@/misc/reset-db.js'; diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts index b45d4af1fe05373d147bda86e6558f5853558c39..6202c740f1a3e3099b3a6dcbbe693dd2b4625e58 100644 --- a/packages/backend/src/server/api/endpoints/roles/notes.ts +++ b/packages/backend/src/server/api/endpoints/roles/notes.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import Redis from 'ioredis'; +import * as Redis from 'ioredis'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { NotesRepository, RolesRepository } from '@/models/index.js'; import { QueryService } from '@/core/QueryService.js'; @@ -65,12 +65,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { super(meta, paramDef, async (ps, me) => { const role = await this.rolesRepository.findOneBy({ id: ps.roleId, + isPublic: true, }); if (role == null) { throw new ApiError(meta.errors.noSuchRole); } - + if (!role.isExplorable) { + return []; + } const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdã«æŒ‡å®šã—ãŸã‚‚ã®ã‚‚å«ã¾ã‚Œã‚‹ãŸã‚+1 const noteIdsRes = await this.redisClient.xrevrange( `roleTimeline:${role.id}`, diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts index c80b6efdcda90672bd8dfa91766c89bb29ca004d..6293c5cb501cbe3f89241b345fec57fc9635c80a 100644 --- a/packages/backend/src/server/api/endpoints/username/available.ts +++ b/packages/backend/src/server/api/endpoints/username/available.ts @@ -4,6 +4,7 @@ import type { UsedUsernamesRepository, UsersRepository } from '@/models/index.js import { Endpoint } from '@/server/api/endpoint-base.js'; import { localUsernameSchema } from '@/models/entities/User.js'; import { DI } from '@/di-symbols.js'; +import { MetaService } from '@/core/MetaService.js'; export const meta = { tags: ['users'], @@ -39,9 +40,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { @Inject(DI.usedUsernamesRepository) private usedUsernamesRepository: UsedUsernamesRepository, + + private metaService: MetaService, ) { super(meta, paramDef, async (ps, me) => { - // Get exist const exist = await this.usersRepository.countBy({ host: IsNull(), usernameLower: ps.username.toLowerCase(), @@ -49,8 +51,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { 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()); + return { - available: exist === 0 && exist2 === 0, + available: exist === 0 && exist2 === 0 && !isPreserved, }; }); } diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 8becb68a34f32efdeda55c270975974bac417e03..28cd9f6ce58d910e4b82a015c1a0125f4fc72370 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -50,8 +50,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private queryService: QueryService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.usersRepository.createQueryBuilder('user'); - query.where('user.isExplorable = TRUE'); + const query = this.usersRepository.createQueryBuilder('user') + .where('user.isExplorable = TRUE') + .andWhere('user.isSuspended = FALSE'); switch (ps.state) { case 'alive': query.andWhere('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break; diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index a840c1a04e27d5a473073848b98da2a1afa5152f..751088952634c02500a72bd380313ebc722efa52 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -13,6 +13,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:account', description: 'Create a new list of users.', @@ -58,7 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) { throw new ApiError(meta.errors.tooManyUserLists); } - + const userList = await this.userListsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index d2dd5731eebe6bd4e16d29eadb79ab1d72ab370d..d50b70efc2fb9dc3c1108199f6adda0a1960eb55 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -12,6 +12,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:account', description: 'Remove a user from a list.', diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 1c1fdc23f16f26716e8da2fcac238396850044d0..925037e48420321e5044c01d82133d9b06fec149 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -12,6 +12,8 @@ export const meta = { requireCredential: true, + prohibitMoved: true, + kind: 'write:account', description: 'Add a user to an existing list.', diff --git a/packages/backend/src/server/api/endpoints/users/update-memo.ts b/packages/backend/src/server/api/endpoints/users/update-memo.ts new file mode 100644 index 0000000000000000000000000000000000000000..ca7756ef750cd6824bce8601330815413c7f212a --- /dev/null +++ b/packages/backend/src/server/api/endpoints/users/update-memo.ts @@ -0,0 +1,85 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { IdService } from '@/core/IdService.js'; +import type { UserMemoRepository } from '@/models/index.js'; +import { DI } from '@/di-symbols.js'; +import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; + +export const meta = { + tags: ['account'], + + requireCredential: true, + + kind: 'write:account', + + errors: { + noSuchUser: { + message: 'No such user.', + code: 'NO_SUCH_USER', + id: '6fef56f3-e765-4957-88e5-c6f65329b8a5', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + memo: { + type: 'string', + nullable: true, + description: 'A personal memo for the target user. If null or empty, delete the memo.', + }, + }, + required: ['userId', 'memo'], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { + constructor( + @Inject(DI.userMemosRepository) + private userMemosRepository: UserMemoRepository, + private getterService: GetterService, + private idService: IdService, + ) { + super(meta, paramDef, async (ps, me) => { + // Get target + const target = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + // 引数ãŒnullã‹ç©ºæ–‡å—ã§ã‚ã‚Œã°ã€ãƒ‘ーソナルメモを削除ã™ã‚‹ + if (ps.memo === '' || ps.memo == null) { + await this.userMemosRepository.delete({ + userId: me.id, + targetUserId: target.id, + }); + return; + } + + // 以å‰ã«ä½œæˆã•ã‚ŒãŸãƒ‘ーソナルメモãŒã‚ã‚‹ã‹ã©ã†ã‹ç¢ºèª + const previousMemo = await this.userMemosRepository.findOneBy({ + userId: me.id, + targetUserId: target.id, + }); + + if (!previousMemo) { + await this.userMemosRepository.insert({ + id: this.idService.genId(), + userId: me.id, + targetUserId: target.id, + memo: ps.memo, + }); + } else { + await this.userMemosRepository.update(previousMemo.id, { + userId: me.id, + targetUserId: target.id, + memo: ps.memo, + }); + } + }); + } +} diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index b3e193cd34f934734eae1cb80c507284adc72377..e61e92c623f227289d2e40306f32f3c5baf45dfb 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -70,10 +70,10 @@ export class UrlPreviewService { await summaly(url, { followRedirects: false, lang: lang ?? 'ja-JP', - agent: { + agent: this.config.proxy ? { http: this.httpRequestService.httpAgent, https: this.httpRequestService.httpsAgent, - }, + } : undefined, }); this.logger.succ(`Got preview of ${url}: ${summary.title}`); diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index d772ac31849280c89255f7135acd5ba43edf87ab..cb5d05a403910528b38b53b111f34a2d12e023af 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -36,7 +36,7 @@ html link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg') link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg') //- https://github.com/misskey-dev/misskey/issues/9842 - link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.12.0') + link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.17.0') link(rel='modulepreload' href=`/vite/${clientEntry.file}`) if !config.clientManifestExists diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts index afb72c84d40e4bdcc89851c8cb9ec3c2f9ade48e..f885209b7f163b3d5c188cde6cd5302d730d790a 100644 --- a/packages/backend/test/e2e/endpoints.ts +++ b/packages/backend/test/e2e/endpoints.ts @@ -4,8 +4,9 @@ import * as assert from 'assert'; // node-fetch only supports it's own Blob yet // https://github.com/node-fetch/node-fetch/pull/1664 import { Blob } from 'node-fetch'; -import { startServer, signup, post, api, uploadFile, simpleGet } from '../utils.js'; +import { startServer, signup, post, api, uploadFile, simpleGet, initTestDb } from '../utils.js'; import type { INestApplicationContext } from '@nestjs/common'; +import { User } from '@/models/index.js'; describe('Endpoints', () => { let app: INestApplicationContext; @@ -289,6 +290,16 @@ describe('Endpoints', () => { }, bob); assert.strictEqual(res.status, 200); + + const connection = await initTestDb(true); + const Users = connection.getRepository(User); + const newBob = await Users.findOneByOrFail({ id: bob.id }); + assert.strictEqual(newBob.followersCount, 0); + assert.strictEqual(newBob.followingCount, 1); + const newAlice = await Users.findOneByOrFail({ id: alice.id }); + assert.strictEqual(newAlice.followersCount, 1); + assert.strictEqual(newAlice.followingCount, 0); + connection.destroy(); }); test('æ—¢ã«ãƒ•ã‚©ãƒãƒ¼ã—ã¦ã„ã‚‹å ´åˆã¯æ€’ã‚‹', async () => { @@ -341,6 +352,16 @@ describe('Endpoints', () => { }, bob); assert.strictEqual(res.status, 200); + + const connection = await initTestDb(true); + const Users = connection.getRepository(User); + const newBob = await Users.findOneByOrFail({ id: bob.id }); + assert.strictEqual(newBob.followersCount, 0); + assert.strictEqual(newBob.followingCount, 0); + const newAlice = await Users.findOneByOrFail({ id: alice.id }); + assert.strictEqual(newAlice.followersCount, 0); + assert.strictEqual(newAlice.followingCount, 0); + connection.destroy(); }); test('フォãƒãƒ¼ã—ã¦ã„ãªã„å ´åˆã¯æ€’ã‚‹', async () => { @@ -382,6 +403,100 @@ describe('Endpoints', () => { }); }); + describe('channels/search', () => { + test('空白検索ã§ä¸€è¦§ã‚’å–å¾—ã§ãã‚‹', async () => { + await api('/channels/create', { + name: 'aaa', + description: 'bbb', + }, bob); + await api('/channels/create', { + name: 'ccc1', + description: 'ddd1', + }, bob); + await api('/channels/create', { + name: 'ccc2', + description: 'ddd2', + }, bob); + + const res = await api('/channels/search', { + query: '', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 3); + }); + test('åå‰ã®ã¿ã®æ¤œç´¢ã§åå‰ã‚’検索ã§ãã‚‹', async () => { + const res = await api('/channels/search', { + query: 'aaa', + type: 'nameOnly', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 1); + assert.strictEqual(res.body[0].name, 'aaa'); + }); + test('åå‰ã®ã¿ã®æ¤œç´¢ã§åå‰ã‚’複数検索ã§ãã‚‹', async () => { + const res = await api('/channels/search', { + query: 'ccc', + type: 'nameOnly', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 2); + }); + test('åå‰ã®ã¿ã®æ¤œç´¢ã§èª¬æ˜Žã¯æ¤œç´¢ã§ããªã„', async () => { + const res = await api('/channels/search', { + query: 'bbb', + type: 'nameOnly', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 0); + }); + test('åå‰ã¨èª¬æ˜Žã®æ¤œç´¢ã§åå‰ã‚’検索ã§ãã‚‹', async () => { + const res = await api('/channels/search', { + query: 'ccc1', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 1); + assert.strictEqual(res.body[0].name, 'ccc1'); + }); + test('åå‰ã¨èª¬æ˜Žã§ã®æ¤œç´¢ã§èª¬æ˜Žã‚’検索ã§ãã‚‹', async () => { + const res = await api('/channels/search', { + query: 'ddd1', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 1); + assert.strictEqual(res.body[0].name, 'ccc1'); + }); + test('åå‰ã¨èª¬æ˜Žã®æ¤œç´¢ã§åå‰ã‚’複数検索ã§ãã‚‹', async () => { + const res = await api('/channels/search', { + query: 'ccc', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 2); + }); + test('åå‰ã¨èª¬æ˜Žã§ã®æ¤œç´¢ã§èª¬æ˜Žã‚’複数検索ã§ãã‚‹', async () => { + const res = await api('/channels/search', { + query: 'ddd', + }, bob); + + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 2); + }); + }); + describe('drive', () => { test('ãƒ‰ãƒ©ã‚¤ãƒ–æƒ…å ±ã‚’å–å¾—ã§ãã‚‹', async () => { await uploadFile(alice, { @@ -849,4 +964,85 @@ describe('Endpoints', () => { assert.strictEqual(res.body.error.code, 'URL_PREVIEW_FAILED'); }); }); + + describe('パーソナルメモ機能ã®ãƒ†ã‚¹ãƒˆ', () => { + test('他者ã«é–¢ã™ã‚‹ãƒ¡ãƒ¢ã‚’æ›´æ–°ã§ãã‚‹', async () => { + const memo = '10月ã¾ã§ä½Žæµ®ä¸Šã¨ã®ã“ã¨ã€‚'; + + const res1 = await api('/users/update-memo', { + memo, + userId: bob.id, + }, alice); + + const res2 = await api('/users/show', { + userId: bob.id, + }, alice); + assert.strictEqual(res1.status, 204); + assert.strictEqual(res2.body?.memo, memo); + }); + + test('自分ã«é–¢ã™ã‚‹ãƒ¡ãƒ¢ã‚’æ›´æ–°ã§ãã‚‹', async () => { + const memo = 'ãƒã‚±ãƒƒãƒˆã‚’月末ã¾ã§ã«è²·ã†ã€‚'; + + const res1 = await api('/users/update-memo', { + memo, + userId: alice.id, + }, alice); + + const res2 = await api('/users/show', { + userId: alice.id, + }, alice); + assert.strictEqual(res1.status, 204); + assert.strictEqual(res2.body?.memo, memo); + }); + + test('メモを削除ã§ãã‚‹', async () => { + const memo = '10月ã¾ã§ä½Žæµ®ä¸Šã¨ã®ã“ã¨ã€‚'; + + await api('/users/update-memo', { + memo, + userId: bob.id, + }, alice); + + await api('/users/update-memo', { + memo: '', + userId: bob.id, + }, alice); + + const res = await api('/users/show', { + userId: bob.id, + }, alice); + + // memoã«ã¯å¸¸ã«æ–‡å—列ã‹nullãŒå…¥ã£ã¦ã„ã‚‹(5cac151) + assert.strictEqual(res.body.memo, null); + }); + + test('メモã¯å€‹äººã”ã¨ã«ç‹¬ç«‹ã—ã¦ä¿å˜ã•ã‚Œã‚‹', async () => { + const memoAliceToBob = '10月ã¾ã§ä½Žæµ®ä¸Šã¨ã®ã“ã¨ã€‚'; + const memoCarolToBob = '例ã®ä»¶ã«ã¤ã„ã¦ä»Šåº¦å•ã„ãŸã ã™ã€‚'; + + await Promise.all([ + api('/users/update-memo', { + memo: memoAliceToBob, + userId: bob.id, + }, alice), + api('/users/update-memo', { + memo: memoCarolToBob, + userId: bob.id, + }, carol), + ]); + + const [resAlice, resCarol] = await Promise.all([ + api('/users/show', { + userId: bob.id, + }, alice), + api('/users/show', { + userId: bob.id, + }, carol), + ]); + + assert.strictEqual(resAlice.body.memo, memoAliceToBob); + assert.strictEqual(resCarol.body.memo, memoCarolToBob); + }); + }); }); diff --git a/packages/backend/test/e2e/move.ts b/packages/backend/test/e2e/move.ts new file mode 100644 index 0000000000000000000000000000000000000000..7d6c646090bc321bad2f58d4095d498de3f68752 --- /dev/null +++ b/packages/backend/test/e2e/move.ts @@ -0,0 +1,456 @@ +process.env.NODE_ENV = 'test'; + +import * as assert from 'assert'; +import rndstr from 'rndstr'; +import { loadConfig } from '@/config.js'; +import { User, UsersRepository } from '@/models/index.js'; +import { jobQueue } from '@/boot/common.js'; +import { uploadFile, signup, startServer, initTestDb, api, sleep, successfulApiCall } from '../utils.js'; +import type { INestApplicationContext } from '@nestjs/common'; + +describe('Account Move', () => { + let app: INestApplicationContext; + let jq: INestApplicationContext; + let url: URL; + + let root: any; + let alice: any; + let bob: any; + let carol: any; + let dave: any; + let eve: any; + let frank: any; + + let Users: UsersRepository; + + beforeAll(async () => { + app = await startServer(); + jq = await jobQueue(); + const config = loadConfig(); + url = new URL(config.url); + const connection = await initTestDb(false); + root = await signup({ username: 'root' }); + alice = await signup({ username: 'alice' }); + bob = await signup({ username: 'bob' }); + carol = await signup({ username: 'carol' }); + dave = await signup({ username: 'dave' }); + eve = await signup({ username: 'eve' }); + frank = await signup({ username: 'frank' }); + Users = connection.getRepository(User); + }, 1000 * 60 * 2); + + afterAll(async () => { + await Promise.all([app.close(), jq.close()]); + }); + + describe('Create Alias', () => { + afterEach(async () => { + await Users.update(bob.id, { alsoKnownAs: null }); + }, 1000 * 10); + + test('Able to create an alias', async () => { + const res = await api('/i/update', { + alsoKnownAs: [`@alice@${url.hostname}`], + }, bob); + + const newBob = await Users.findOneByOrFail({ id: bob.id }); + assert.strictEqual(newBob.alsoKnownAs?.length, 1); + assert.strictEqual(newBob.alsoKnownAs[0], `${url.origin}/users/${alice.id}`); + assert.strictEqual(res.body.alsoKnownAs?.length, 1); + assert.strictEqual(res.body.alsoKnownAs[0], alice.id); + }); + + test('Able to create a local alias without hostname', async () => { + await api('/i/update', { + alsoKnownAs: ['@alice'], + }, bob); + + const newBob = await Users.findOneByOrFail({ id: bob.id }); + assert.strictEqual(newBob.alsoKnownAs?.length, 1); + assert.strictEqual(newBob.alsoKnownAs[0], `${url.origin}/users/${alice.id}`); + }); + + test('Able to create a local alias without @', async () => { + await api('/i/update', { + alsoKnownAs: ['alice'], + }, bob); + + const newBob = await Users.findOneByOrFail({ id: bob.id }); + assert.strictEqual(newBob.alsoKnownAs?.length, 1); + assert.strictEqual(newBob.alsoKnownAs[0], `${url.origin}/users/${alice.id}`); + }); + + test('Able to set remote user (but may fail)', async () => { + const res = await api('/i/update', { + alsoKnownAs: ['@syuilo@example.com'], + }, bob); + + assert.strictEqual(res.status, 400); + assert.strictEqual(res.body.error.code, 'NO_SUCH_USER'); + assert.strictEqual(res.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5'); + }); + + test('Unable to add duplicated aliases to alsoKnownAs', async () => { + const res = await api('/i/update', { + alsoKnownAs: [`@alice@${url.hostname}`, `@alice@${url.hostname}`], + }, bob); + + assert.strictEqual(res.status, 400); + assert.strictEqual(res.body.error.code, 'INVALID_PARAM'); + assert.strictEqual(res.body.error.id, '3d81ceae-475f-4600-b2a8-2bc116157532'); + }); + + test('Unable to add itself', async () => { + const res = await api('/i/update', { + alsoKnownAs: [`@bob@${url.hostname}`], + }, bob); + + assert.strictEqual(res.status, 400); + assert.strictEqual(res.body.error.code, 'FORBIDDEN_TO_SET_YOURSELF'); + assert.strictEqual(res.body.error.id, '25c90186-4ab0-49c8-9bba-a1fa6c202ba4'); + }); + + test('Unable to add a nonexisting local account to alsoKnownAs', async () => { + const res1 = await api('/i/update', { + alsoKnownAs: [`@nonexist@${url.hostname}`], + }, bob); + + assert.strictEqual(res1.status, 400); + assert.strictEqual(res1.body.error.code, 'NO_SUCH_USER'); + assert.strictEqual(res1.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5'); + + const res2 = await api('/i/update', { + alsoKnownAs: ['@alice', 'nonexist'], + }, bob); + + assert.strictEqual(res2.status, 400); + assert.strictEqual(res2.body.error.code, 'NO_SUCH_USER'); + assert.strictEqual(res2.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5'); + }); + + test('Able to add two existing local account to alsoKnownAs', async () => { + await api('/i/update', { + alsoKnownAs: [`@alice@${url.hostname}`, `@carol@${url.hostname}`], + }, bob); + + const newBob = await Users.findOneByOrFail({ id: bob.id }); + assert.strictEqual(newBob.alsoKnownAs?.length, 2); + assert.strictEqual(newBob.alsoKnownAs[0], `${url.origin}/users/${alice.id}`); + assert.strictEqual(newBob.alsoKnownAs[1], `${url.origin}/users/${carol.id}`); + }); + + test('Able to properly overwrite alsoKnownAs', async () => { + await api('/i/update', { + alsoKnownAs: [`@alice@${url.hostname}`], + }, bob); + await api('/i/update', { + alsoKnownAs: [`@carol@${url.hostname}`, `@dave@${url.hostname}`], + }, bob); + + const newBob = await Users.findOneByOrFail({ id: bob.id }); + assert.strictEqual(newBob.alsoKnownAs?.length, 2); + assert.strictEqual(newBob.alsoKnownAs[0], `${url.origin}/users/${carol.id}`); + assert.strictEqual(newBob.alsoKnownAs[1], `${url.origin}/users/${dave.id}`); + }); + }); + + describe('Local to Local', () => { + let antennaId = ''; + + beforeAll(async () => { + await api('/i/update', { + alsoKnownAs: [`@alice@${url.hostname}`], + }, root); + const listRoot = await api('/users/lists/create', { + name: rndstr('0-9a-z', 8), + }, root); + await api('/users/lists/push', { + listId: listRoot.body.id, + userId: alice.id, + }, root); + + await api('/following/create', { + userId: root.id, + }, alice); + await api('/following/create', { + userId: eve.id, + }, alice); + const antenna = await api('/antennas/create', { + name: rndstr('0-9a-z', 8), + src: 'home', + keywords: [rndstr('0-9a-z', 8)], + excludeKeywords: [], + users: [], + caseSensitive: false, + withReplies: false, + withFile: false, + notify: false, + }, alice); + antennaId = antenna.body.id; + + await api('/i/update', { + alsoKnownAs: [`@alice@${url.hostname}`], + }, bob); + + await api('/following/create', { + userId: alice.id, + }, carol); + + await api('/mute/create', { + userId: alice.id, + }, dave); + await api('/blocking/create', { + userId: alice.id, + }, dave); + await api('/following/create', { + userId: eve.id, + }, dave); + + await api('/following/create', { + userId: dave.id, + }, eve); + const listEve = await api('/users/lists/create', { + name: rndstr('0-9a-z', 8), + }, eve); + await api('/users/lists/push', { + listId: listEve.body.id, + userId: bob.id, + }, eve); + + await api('/i/update', { + isLocked: true, + }, frank); + await api('/following/create', { + userId: frank.id, + }, alice); + await api('/following/requests/accept', { + userId: alice.id, + }, frank); + }, 1000 * 10); + + test('Prohibit the root account from moving', async () => { + const res = await api('/i/move', { + moveToAccount: `@bob@${url.hostname}`, + }, root); + + assert.strictEqual(res.status, 400); + assert.strictEqual(res.body.error.code, 'NOT_ROOT_FORBIDDEN'); + assert.strictEqual(res.body.error.id, '4362e8dc-731f-4ad8-a694-be2a88922a24'); + }); + + test('Unable to move to a nonexisting local account', async () => { + const res = await api('/i/move', { + moveToAccount: `@nonexist@${url.hostname}`, + }, alice); + + assert.strictEqual(res.status, 400); + assert.strictEqual(res.body.error.code, 'NO_SUCH_USER'); + assert.strictEqual(res.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5'); + }); + + test('Unable to move if alsoKnownAs is invalid', async () => { + const res = await api('/i/move', { + moveToAccount: `@carol@${url.hostname}`, + }, alice); + + assert.strictEqual(res.status, 400); + assert.strictEqual(res.body.error.code, 'DESTINATION_ACCOUNT_FORBIDS'); + assert.strictEqual(res.body.error.id, 'b5c90186-4ab0-49c8-9bba-a1f766282ba4'); + }); + + test('Relationships have been properly migrated', async () => { + const move = await api('/i/move', { + moveToAccount: `@bob@${url.hostname}`, + }, alice); + + assert.strictEqual(move.status, 200); + + await sleep(1000 * 3); // wait for jobs to finish + + // Unfollow delayed? + const aliceFollowings = await api('/users/following', { + userId: alice.id, + }, alice); + assert.strictEqual(aliceFollowings.status, 200); + assert.strictEqual(aliceFollowings.body.length, 3); + + const carolFollowings = await api('/users/following', { + userId: carol.id, + }, carol); + assert.strictEqual(carolFollowings.status, 200); + assert.strictEqual(carolFollowings.body.length, 2); + assert.strictEqual(carolFollowings.body[0].followeeId, bob.id); + assert.strictEqual(carolFollowings.body[1].followeeId, alice.id); + + const blockings = await api('/blocking/list', {}, dave); + assert.strictEqual(blockings.status, 200); + assert.strictEqual(blockings.body.length, 2); + assert.strictEqual(blockings.body[0].blockeeId, bob.id); + assert.strictEqual(blockings.body[1].blockeeId, alice.id); + + const mutings = await api('/mute/list', {}, dave); + assert.strictEqual(mutings.status, 200); + assert.strictEqual(mutings.body.length, 2); + assert.strictEqual(mutings.body[0].muteeId, bob.id); + assert.strictEqual(mutings.body[1].muteeId, alice.id); + + const rootLists = await api('/users/lists/list', {}, root); + assert.strictEqual(rootLists.status, 200); + assert.strictEqual(rootLists.body[0].userIds.length, 2); + assert.ok(rootLists.body[0].userIds.find((id: string) => id === bob.id)); + assert.ok(rootLists.body[0].userIds.find((id: string) => id === alice.id)); + + const eveLists = await api('/users/lists/list', {}, eve); + assert.strictEqual(eveLists.status, 200); + assert.strictEqual(eveLists.body[0].userIds.length, 1); + assert.ok(eveLists.body[0].userIds.find((id: string) => id === bob.id)); + }); + + test('A locked account automatically accept the follow request if it had already accepted the old account.', async () => { + await successfulApiCall({ + endpoint: '/following/create', + parameters: { + userId: frank.id, + }, + user: bob, + }); + const followers = await api('/users/followers', { + userId: frank.id, + }, frank); + + assert.strictEqual(followers.status, 200); + assert.strictEqual(followers.body.length, 2); + assert.strictEqual(followers.body[0].followerId, bob.id); + }); + + test('Unfollowed after 10 sec (24 hours in production).', async () => { + await sleep(1000 * 8); + + const following = await api('/users/following', { + userId: alice.id, + }, alice); + + assert.strictEqual(following.status, 200); + assert.strictEqual(following.body.length, 0); + }); + + test('Unable to move if the destination account has already moved.', async () => { + const res = await api('/i/move', { + moveToAccount: `@alice@${url.hostname}`, + }, bob); + + assert.strictEqual(res.status, 400); + assert.strictEqual(res.body.error.code, 'DESTINATION_ACCOUNT_FORBIDS'); + assert.strictEqual(res.body.error.id, 'b5c90186-4ab0-49c8-9bba-a1f766282ba4'); + }); + + test('Follow and follower counts are properly adjusted', async () => { + await api('/following/create', { + userId: alice.id, + }, eve); + const newAlice = await Users.findOneByOrFail({ id: alice.id }); + const newCarol = await Users.findOneByOrFail({ id: carol.id }); + let newEve = await Users.findOneByOrFail({ id: eve.id }); + assert.strictEqual(newAlice.movedToUri, `${url.origin}/users/${bob.id}`); + assert.strictEqual(newAlice.followingCount, 0); + assert.strictEqual(newAlice.followersCount, 0); + assert.strictEqual(newCarol.followingCount, 1); + assert.strictEqual(newEve.followingCount, 1); + assert.strictEqual(newEve.followersCount, 1); + + await api('/following/delete', { + userId: alice.id, + }, eve); + newEve = await Users.findOneByOrFail({ id: eve.id }); + assert.strictEqual(newEve.followingCount, 1); + assert.strictEqual(newEve.followersCount, 1); + }); + + test.each([ + '/antennas/create', + '/channels/create', + '/channels/favorite', + '/channels/follow', + '/channels/unfavorite', + '/channels/unfollow', + '/clips/add-note', + '/clips/create', + '/clips/favorite', + '/clips/remove-note', + '/clips/unfavorite', + '/clips/update', + '/drive/files/upload-from-url', + '/flash/create', + '/flash/like', + '/flash/unlike', + '/flash/update', + '/following/create', + '/gallery/posts/create', + '/gallery/posts/like', + '/gallery/posts/unlike', + '/gallery/posts/update', + '/i/claim-achievement', + '/i/move', + '/i/import-blocking', + '/i/import-following', + '/i/import-muting', + '/i/import-user-lists', + '/i/pin', + '/mute/create', + '/notes/create', + '/notes/favorites/create', + '/notes/polls/vote', + '/notes/reactions/create', + '/pages/create', + '/pages/like', + '/pages/unlike', + '/pages/update', + '/renote-mute/create', + '/users/lists/create', + '/users/lists/pull', + '/users/lists/push', + ])('Prohibit access after moving: %s', async (endpoint) => { + const res = await api(endpoint, {}, alice); + assert.strictEqual(res.status, 403); + assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED'); + assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31'); + }); + + test('Prohibit access after moving: /antennas/update', async () => { + const res = await api('/antennas/update', { + antennaId, + name: rndstr('0-9a-z', 8), + src: 'users', + keywords: [rndstr('0-9a-z', 8)], + excludeKeywords: [], + users: [eve.id], + caseSensitive: false, + withReplies: false, + withFile: false, + notify: false, + }, alice); + + assert.strictEqual(res.status, 403); + assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED'); + assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31'); + }); + + test('Prohibit access after moving: /drive/files/create', async () => { + const res = await uploadFile(alice); + + assert.strictEqual(res.status, 403); + assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED'); + assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31'); + }); + + test('Prohibit updating alsoKnownAs after moving', async () => { + const res = await api('/i/update', { + alsoKnownAs: [`@eve@${url.hostname}`], + }, alice); + + assert.strictEqual(res.status, 403); + assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED'); + assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31'); + }); + }); +}); diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts index e87045a8cf6180ebce8cb7158e1195de6c2af48a..9c851a5dd671a74ffa80e7155460c6f13bf29dd9 100644 --- a/packages/backend/test/e2e/note.ts +++ b/packages/backend/test/e2e/note.ts @@ -352,6 +352,72 @@ describe('Note', () => { assert.strictEqual(myNote.renote.reply.files.length, 1); assert.strictEqual(myNote.renote.reply.files[0].id, file.body.id); }); + + test('NSFWãŒå¼·åˆ¶ã•ã‚Œã¦ã„ã‚‹å ´åˆå¤‰æ›´ã§ããªã„', async () => { + const file = await uploadFile(alice); + + const res = await api('admin/roles/create', { + name: 'test', + description: '', + color: null, + iconUrl: null, + displayOrder: 0, + target: 'manual', + condFormula: {}, + isAdministrator: false, + isModerator: false, + isPublic: false, + isExplorable: false, + asBadge: false, + canEditMembersByModerator: false, + policies: { + alwaysMarkNsfw: { + useDefault: false, + priority: 0, + value: true, + }, + }, + }, alice); + + assert.strictEqual(res.status, 200); + + const assign = await api('admin/roles/assign', { + userId: alice.id, + roleId: res.body.id, + }, alice); + + assert.strictEqual(assign.status, 204); + assert.strictEqual(file.body.isSensitive, false); + + const nsfwfile = await uploadFile(alice); + + assert.strictEqual(nsfwfile.status, 200); + assert.strictEqual(nsfwfile.body.isSensitive, true); + + const liftnsfw = await api('drive/files/update', { + fileId: nsfwfile.body.id, + isSensitive: false, + }, alice); + + assert.strictEqual(liftnsfw.status, 400); + assert.strictEqual(liftnsfw.body.error.code, 'RESTRICTED_BY_ROLE'); + + const oldaddnsfw = await api('drive/files/update', { + fileId: file.body.id, + isSensitive: true, + }, alice); + + assert.strictEqual(oldaddnsfw.status, 200); + + await api('admin/roles/unassign', { + userId: alice.id, + roleId: res.body.id, + }); + + await api('admin/roles/delete', { + roleId: res.body.id, + }, alice); + }); }); describe('notes/create', () => { diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts index bc3455e3463ae012b3ad5af5faaa8d301c2d1f9c..51537dda16b8e7a26726944ecc284e95a872277c 100644 --- a/packages/backend/test/e2e/users.ts +++ b/packages/backend/test/e2e/users.ts @@ -31,7 +31,7 @@ describe('ユーザー', () => { }, {}); }; - // FIXME: 足りãªã„ã‚ーãŒãŸãã•ã‚“ã‚ã‚‹ + // BUG misskey-jsã¨json-schemaã¨å®Ÿéš›ã«è¿”ã£ã¦ãるデータãŒå…¨éƒ¨é•ã† type UserLite = misskey.entities.UserLite & { badgeRoles: any[], }; @@ -51,10 +51,11 @@ describe('ユーザー', () => { type User = MeDetailed & { token: string }; - const show = async (id: string, me = alice): Promise<MeDetailed | UserDetailedNotMe> => { + const show = async (id: string, me = root): Promise<MeDetailed | UserDetailedNotMe> => { return successfulApiCall({ endpoint: 'users/show', parameters: { userId: id }, user: me }) as any; }; + // UserLiteã®ã‚ーãŒéŽä¸è¶³ãªãå…¥ã£ã¦ã„る? const userLite = (user: User): Partial<UserLite> => { return stripUndefined({ id: user.id, @@ -76,12 +77,13 @@ describe('ユーザー', () => { }); }; + // UserDetailedNotMeã®ã‚ーãŒéŽä¸è¶³ãªãå…¥ã£ã¦ã„る? const userDetailedNotMe = (user: User): Partial<UserDetailedNotMe> => { return stripUndefined({ ...userLite(user), url: user.url, uri: user.uri, - movedToUri: user.movedToUri, + movedTo: user.movedTo, alsoKnownAs: user.alsoKnownAs, createdAt: user.createdAt, updatedAt: user.updatedAt, @@ -109,9 +111,11 @@ describe('ユーザー', () => { usePasswordLessLogin: user.usePasswordLessLogin, securityKeys: user.securityKeys, roles: user.roles, + memo: user.memo, }); }; + // Relations関連ã®ã‚ーãŒéŽä¸è¶³ãªãå…¥ã£ã¦ã„る? const userDetailedNotMeWithRelations = (user: User): Partial<UserDetailedNotMe> => { return stripUndefined({ ...userDetailedNotMe(user), @@ -126,6 +130,7 @@ describe('ユーザー', () => { }); }; + // MeDetailedã®ã‚ーãŒéŽä¸è¶³ãªãå…¥ã£ã¦ã„る? const meDetailed = (user: User, security = false): Partial<MeDetailed> => { return stripUndefined({ ...userDetailedNotMe(user), @@ -216,8 +221,8 @@ describe('ユーザー', () => { }, 1000 * 60 * 2); beforeAll(async () => { - root = await signup({ username: 'alice' }); - alice = root; + root = await signup({ username: 'root' }); + alice = await signup({ username: 'alice' }); aliceNote = await post(alice, { text: 'test' }) as any; alicePage = await page(alice); aliceList = (await api('users/list/create', { name: 'aliceList' }, alice)).body; @@ -343,7 +348,7 @@ describe('ユーザー', () => { // UserDetailedNotMeOnly assert.strictEqual(response.url, null); assert.strictEqual(response.uri, null); - assert.strictEqual(response.movedToUri, null); + assert.strictEqual(response.movedTo, null); assert.strictEqual(response.alsoKnownAs, null); assert.strictEqual(response.createdAt, new Date(response.createdAt).toISOString()); assert.strictEqual(response.updatedAt, null); @@ -371,6 +376,7 @@ describe('ユーザー', () => { assert.strictEqual(response.usePasswordLessLogin, false); assert.strictEqual(response.securityKeys, false); assert.deepStrictEqual(response.roles, []); + assert.strictEqual(response.memo, null); // MeDetailedOnly assert.strictEqual(response.avatarId, null); @@ -410,7 +416,7 @@ describe('ユーザー', () => { //#endregion //#region 自分ã®æƒ…å ±(i) - test('ã‚’èªã¿å–ã‚‹ã“ã¨ãŒã§ãる。(自分)', async () => { + test('ã‚’èªã¿å–ã‚‹ã“ã¨ãŒã§ãã‚‹ã“ã¨ï¼ˆè‡ªåˆ†ï¼‰ã€ã‚ーãŒéŽä¸è¶³ãªãå…¥ã£ã¦ã„ã‚‹ã“ã¨ã€‚', async () => { const response = await successfulApiCall({ endpoint: 'i', parameters: {}, @@ -502,7 +508,6 @@ describe('ユーザー', () => { }; assert.deepStrictEqual(response, expected, inspect(parameters)); - if (1) return; // BUG 521eb95 以é™ã‚¢ãƒã‚¿ãƒ¼ã®ãƒªã‚»ãƒƒãƒˆãŒã§ããªã„。 const parameters2 = { avatarId: null }; const response2 = await successfulApiCall({ endpoint: 'i/update', parameters: parameters2, user: alice }); const expected2 = { @@ -528,7 +533,6 @@ describe('ユーザー', () => { }; assert.deepStrictEqual(response, expected, inspect(parameters)); - if (1) return; // BUG 521eb95 以é™ãƒãƒŠãƒ¼ã®ãƒªã‚»ãƒƒãƒˆãŒã§ããªã„。 const parameters2 = { bannerId: null }; const response2 = await successfulApiCall({ endpoint: 'i/update', parameters: parameters2, user: alice }); const expected2 = { @@ -554,6 +558,21 @@ describe('ユーザー', () => { assert.deepStrictEqual(response2, expected2); }); + //#endregion + //#region メモã®æ›´æ–°(users/update-memo) + + test.each([ + { label: '最大長', memo: 'x'.repeat(2048) }, + { label: '空文å—', memo: '', expects: null }, + { label: 'null', memo: null }, + ])('を書ãæ›ãˆã‚‹ã“ã¨ãŒã§ãã‚‹(メモを$labelã«)', async ({ memo, expects }) => { + const expected = { ...await show(bob.id, alice), memo: expects === undefined ? memo : expects }; + const parameters = { userId: bob.id, memo }; + await successfulApiCall({ endpoint: 'users/update-memo', parameters, user: alice }); + const response = await show(bob.id, alice); + assert.deepStrictEqual(response, expected); + }); + //#endregion //#region ユーザー(users) @@ -569,7 +588,7 @@ describe('ユーザー', () => { const response = await successfulApiCall({ endpoint: 'users', parameters, user: alice }); // çµæžœã®ä¸¦ã³ã‚’事å‰ã«ã‚¢ã‚µãƒ¼ãƒˆã™ã‚‹ã®ã¯å›°é›£ãªã®ã§è¿”ã£ã¦ããŸidã«å¯¾å¿œã™ã‚‹ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒè¿”ã£ã¦ãŠã‚Šã€ã‚½ãƒ¼ãƒˆé †ãŒæ£ã—ã„ã“ã¨ã ã‘を検証ã™ã‚‹ - const users = await Promise.all(response.map(u => show(u.id))); + const users = await Promise.all(response.map(u => show(u.id, alice))); const expected = users.sort((x, y) => { const index = (selector(x) < selector(y)) ? -1 : (selector(x) > selector(y)) ? 1 : 0; return index * (parameters.sort?.startsWith('+') ? -1 : 1); @@ -583,13 +602,13 @@ describe('ユーザー', () => { { label: 'ブãƒãƒƒã‚¯ã—ã¦ãã¦ã„るユーザーãŒå«ã¾ã‚Œã‚‹', user: (): User => userBlockingAlice, excluded: true }, { label: '承èªåˆ¶ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒå«ã¾ã‚Œã‚‹', user: (): User => userLocking }, { label: 'サイレンスユーザーãŒå«ã¾ã‚Œã‚‹', user: (): User => userSilenced }, - { label: 'サスペンドユーザーãŒå«ã¾ã‚Œã‚‹', user: (): User => userSuspended }, + { label: 'サスペンドユーザーãŒå«ã¾ã‚Œãªã„', user: (): User => userSuspended, excluded: true }, { label: '削除済ユーザーãŒå«ã¾ã‚Œã‚‹', user: (): User => userDeletedBySelf }, { label: '削除済(byAdmin)ユーザーãŒå«ã¾ã‚Œã‚‹', user: (): User => userDeletedByAdmin }, ] as const)('をリスト形å¼ã§å–å¾—ã™ã‚‹ã“ã¨ãŒã§ãã€çµæžœã«$label', async ({ user, excluded }) => { const parameters = { limit: 100 }; const response = await successfulApiCall({ endpoint: 'users', parameters, user: alice }); - const expected = (excluded ?? false) ? [] : [await show(user().id)]; + const expected = (excluded ?? false) ? [] : [await show(user().id, alice)]; assert.deepStrictEqual(response.filter((u) => u.id === user().id), expected); }); test.todo('をリスト形å¼ã§å–å¾—ã™ã‚‹ã“ã¨ãŒã§ãる(リモート, hostname指定)'); @@ -616,7 +635,7 @@ describe('ユーザー', () => { { label: 'Moderatorã«ãªã£ã¦ã„ã‚‹', user: (): User => userModerator, me: (): User => userModerator, selector: (user: User): unknown => user.isModerator }, { label: '自分以外ã‹ã‚‰è¦‹ãŸã¨ãã¯Moderatorã‹åˆ¤å®šã§ããªã„', user: (): User => userModerator, selector: (user: User): unknown => user.isModerator, expected: (): undefined => undefined }, { label: 'サイレンスã«ãªã£ã¦ã„ã‚‹', user: (): User => userSilenced, selector: (user: User): unknown => user.isSilenced }, - { label: 'サスペンドã«ãªã£ã¦ã„ã‚‹', user: (): User => userSuspended, selector: (user: User): unknown => user.isSuspended }, + //{ label: 'サスペンドã«ãªã£ã¦ã„ã‚‹', user: (): User => userSuspended, selector: (user: User): unknown => user.isSuspended }, { label: '削除済ã¿ã«ãªã£ã¦ã„ã‚‹', user: (): User => userDeletedBySelf, me: (): User => userDeletedBySelf, selector: (user: User): unknown => user.isDeleted }, { label: '自分以外ã‹ã‚‰è¦‹ãŸã¨ãã¯å‰Šé™¤æ¸ˆã¿ã‹åˆ¤å®šã§ããªã„', user: (): User => userDeletedBySelf, selector: (user: User): unknown => user.isDeleted, expected: (): undefined => undefined }, { label: '削除済ã¿(byAdmin)ã«ãªã£ã¦ã„ã‚‹', user: (): User => userDeletedByAdmin, me: (): User => userDeletedByAdmin, selector: (user: User): unknown => user.isDeleted }, @@ -698,13 +717,13 @@ describe('ユーザー', () => { test('を検索ã™ã‚‹ã“ã¨ãŒã§ãã‚‹', async () => { const parameters = { query: 'carol', limit: 10 }; const response = await successfulApiCall({ endpoint: 'users/search', parameters, user: alice }); - const expected = [await show(carol.id)]; + const expected = [await show(carol.id, alice)]; assert.deepStrictEqual(response, expected); }); test('を検索ã™ã‚‹ã“ã¨ãŒã§ãã‚‹(UserLite)', async () => { const parameters = { query: 'carol', detail: false, limit: 10 }; const response = await successfulApiCall({ endpoint: 'users/search', parameters, user: alice }); - const expected = [userLite(await show(carol.id))]; + const expected = [userLite(await show(carol.id, alice))]; assert.deepStrictEqual(response, expected); }); test.each([ @@ -720,7 +739,7 @@ describe('ユーザー', () => { ] as const)('を検索ã™ã‚‹ã“ã¨ãŒã§ãã€çµæžœã«$labelãŒå«ã¾ã‚Œã‚‹', async ({ user, excluded }) => { const parameters = { query: user().username, limit: 1 }; const response = await successfulApiCall({ endpoint: 'users/search', parameters, user: alice }); - const expected = (excluded ?? false) ? [] : [await show(user().id)]; + const expected = (excluded ?? false) ? [] : [await show(user().id, alice)]; assert.deepStrictEqual(response, expected); }); test.todo('を検索ã™ã‚‹ã“ã¨ãŒã§ãã‚‹(リモート)'); @@ -741,7 +760,7 @@ describe('ユーザー', () => { { label: 'ãƒãƒ¼ã‚«ãƒ«', parameters: { host: '.', limit: 1 }, user: (): User[] => [userFollowedByAlice] }, ])('ã‚’ID&ホスト指定ã§æ¤œç´¢ã§ãã‚‹($label)', async ({ parameters, user }) => { const response = await successfulApiCall({ endpoint: 'users/search-by-username-and-host', parameters, user: alice }); - const expected = await Promise.all(user().map(u => show(u.id))); + const expected = await Promise.all(user().map(u => show(u.id, alice))); assert.deepStrictEqual(response, expected); }); test.each([ @@ -757,7 +776,7 @@ describe('ユーザー', () => { ] as const)('ã‚’ID&ホスト指定ã§æ¤œç´¢ã§ãã€çµæžœã«$label', async ({ user, excluded }) => { const parameters = { username: user().username }; const response = await successfulApiCall({ endpoint: 'users/search-by-username-and-host', parameters, user: alice }); - const expected = (excluded ?? false) ? [] : [await show(user().id)]; + const expected = (excluded ?? false) ? [] : [await show(user().id, alice)]; assert.deepStrictEqual(response, expected); }); test.todo('ã‚’ID&ホスト指定ã§æ¤œç´¢ã§ãã‚‹(リモート)'); @@ -769,7 +788,7 @@ describe('ユーザー', () => { const parameters = { userId: alice.id, limit: 5 }; const response = await successfulApiCall({ endpoint: 'users/get-frequently-replied-users', parameters, user: alice }); const expected = await Promise.all(usersReplying.slice(0, parameters.limit).map(async (s, i) => ({ - user: await show(s.id), + user: await show(s.id, alice), weight: (usersReplying.length - i) / usersReplying.length, }))); assert.deepStrictEqual(response, expected); @@ -781,7 +800,7 @@ describe('ユーザー', () => { { label: 'ブãƒãƒƒã‚¯ã—ã¦ãã¦ã„るユーザーãŒå«ã¾ã‚Œãªã„', user: (): User => userBlockingAlice, excluded: true }, { label: '承èªåˆ¶ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒå«ã¾ã‚Œã‚‹', user: (): User => userLocking }, { label: 'サイレンスユーザーãŒå«ã¾ã‚Œã‚‹', user: (): User => userSilenced }, - { label: 'サスペンドユーザーãŒå«ã¾ã‚Œãªã„', user: (): User => userSuspended }, + //{ label: 'サスペンドユーザーãŒå«ã¾ã‚Œãªã„', user: (): User => userSuspended, excluded: true }, { label: '削除済ユーザーãŒå«ã¾ã‚Œã‚‹', user: (): User => userDeletedBySelf }, { label: '削除済(byAdmin)ユーザーãŒå«ã¾ã‚Œã‚‹', user: (): User => userDeletedByAdmin }, ] as const)('ãŒã‚ˆãリプライをã™ã‚‹ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒªã‚¹ãƒˆã‚’å–å¾—ã§ãã€çµæžœã«$label', async ({ user, excluded }) => { @@ -789,7 +808,7 @@ describe('ユーザー', () => { await post(alice, { text: `@${user().username} test`, replyId: replyTo.id }); const parameters = { userId: alice.id, limit: 100 }; const response = await successfulApiCall({ endpoint: 'users/get-frequently-replied-users', parameters, user: alice }); - const expected = (excluded ?? false) ? [] : [await show(user().id)]; + const expected = (excluded ?? false) ? [] : [await show(user().id, alice)]; assert.deepStrictEqual(response.map(s => s.user).filter((u) => u.id === user().id), expected); }); @@ -808,7 +827,7 @@ describe('ユーザー', () => { await successfulApiCall({ endpoint: 'i/update', parameters: { description: `#${hashtag}` }, user: alice }); const parameters = { tag: hashtag, limit: 5, ...sort }; const response = await successfulApiCall({ endpoint: 'hashtags/users', parameters, user: alice }); - const users = await Promise.all(response.map(u => show(u.id))); + const users = await Promise.all(response.map(u => show(u.id, alice))); const expected = users.sort((x, y) => { const index = (selector(x) < selector(y)) ? -1 : (selector(x) > selector(y)) ? 1 : 0; return index * (parameters.sort.startsWith('+') ? -1 : 1); @@ -822,10 +841,10 @@ describe('ユーザー', () => { { label: 'ブãƒãƒƒã‚¯ã—ã¦ãã¦ã„るユーザーãŒå«ã¾ã‚Œã‚‹', user: (): User => userBlockingAlice }, { label: '承èªåˆ¶ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒå«ã¾ã‚Œã‚‹', user: (): User => userLocking }, { label: 'サイレンスユーザーãŒå«ã¾ã‚Œã‚‹', user: (): User => userSilenced }, - { label: 'サスペンドユーザーãŒå«ã¾ã‚Œã‚‹', user: (): User => userSuspended }, + { label: 'サスペンドユーザーãŒå«ã¾ã‚Œãªã„', user: (): User => userSuspended, excluded: true }, { label: '削除済ユーザーãŒå«ã¾ã‚Œã‚‹', user: (): User => userDeletedBySelf }, { label: '削除済(byAdmin)ユーザーãŒå«ã¾ã‚Œã‚‹', user: (): User => userDeletedByAdmin }, - ] as const)('ã‚’ãƒãƒƒã‚·ãƒ¥ã‚¿ã‚°æŒ‡å®šã§å–å¾—ã™ã‚‹ã“ã¨ãŒã§ãã€çµæžœã«$label', async ({ user }) => { + ] as const)('ã‚’ãƒãƒƒã‚·ãƒ¥ã‚¿ã‚°æŒ‡å®šã§å–å¾—ã™ã‚‹ã“ã¨ãŒã§ãã€çµæžœã«$label', async ({ user, excluded }) => { const hashtag = `user_test${user().username}`; if (user() !== userSuspended) { // サスペンドユーザーã¯updateã§ããªã„。 @@ -833,7 +852,7 @@ describe('ユーザー', () => { } const parameters = { tag: hashtag, limit: 100, sort: '-follower' } as const; const response = await successfulApiCall({ endpoint: 'hashtags/users', parameters, user: alice }); - const expected = [await show(user().id)]; + const expected = (excluded ?? false) ? [] : [await show(user().id, alice)]; assert.deepStrictEqual(response, expected); }); test.todo('ã‚’ãƒãƒƒã‚·ãƒ¥ã‚¿ã‚°æŒ‡å®šã§å–å¾—ã™ã‚‹ã“ã¨ãŒã§ãã‚‹(リモート)'); @@ -856,7 +875,7 @@ describe('ユーザー', () => { await successfulApiCall({ endpoint: 'admin/update-meta', parameters: { pinnedUsers: [bob.username, `@${carol.username}`] }, user: root }); const parameters = {} as const; const response = await successfulApiCall({ endpoint: 'pinned-users', parameters, user: alice }); - const expected = await Promise.all([bob, carol].map(u => show(u.id))); + const expected = await Promise.all([bob, carol].map(u => show(u.id, alice))); assert.deepStrictEqual(response, expected); }); diff --git a/packages/backend/test/resources/kick_gaba7.aac b/packages/backend/test/resources/kick_gaba7.aac new file mode 100644 index 0000000000000000000000000000000000000000..4644542f9664182a78bc1d8b6116c5706200ced5 Binary files /dev/null and b/packages/backend/test/resources/kick_gaba7.aac differ diff --git a/packages/backend/test/resources/kick_gaba7.flac b/packages/backend/test/resources/kick_gaba7.flac new file mode 100644 index 0000000000000000000000000000000000000000..75128120182a70cec0fcf52efc3df7b77972c38b Binary files /dev/null and b/packages/backend/test/resources/kick_gaba7.flac differ diff --git a/packages/backend/test/resources/kick_gaba7.mp3 b/packages/backend/test/resources/kick_gaba7.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..6ba317deb1a04eb62384b2cf0c79c7d901f2bbf1 Binary files /dev/null and b/packages/backend/test/resources/kick_gaba7.mp3 differ diff --git a/packages/backend/test/resources/kick_gaba7.wav b/packages/backend/test/resources/kick_gaba7.wav new file mode 100644 index 0000000000000000000000000000000000000000..2cd280148e125c77596b97836503b1777f793564 Binary files /dev/null and b/packages/backend/test/resources/kick_gaba7.wav differ diff --git a/packages/backend/test/resources/kick_gaba7.webm b/packages/backend/test/resources/kick_gaba7.webm new file mode 100644 index 0000000000000000000000000000000000000000..82c5349cd4a20dd3d890d57ab61f0354bd846e8a Binary files /dev/null and b/packages/backend/test/resources/kick_gaba7.webm differ diff --git a/packages/backend/test/unit/FileInfoService.ts b/packages/backend/test/unit/FileInfoService.ts index d05833560d60185e99b9b2ebf5e32f0fc7fca4a3..f378184c74f3bf1a7333f2a44194e7af26848945 100644 --- a/packages/backend/test/unit/FileInfoService.ts +++ b/packages/backend/test/unit/FileInfoService.ts @@ -7,10 +7,10 @@ import { ModuleMocker } from 'jest-mock'; import { Test } from '@nestjs/testing'; import { GlobalModule } from '@/GlobalModule.js'; import { FileInfoService } from '@/core/FileInfoService.js'; -import { DI } from '@/di-symbols.js'; +//import { DI } from '@/di-symbols.js'; import { AiService } from '@/core/AiService.js'; import type { TestingModule } from '@nestjs/testing'; -import type { jest } from '@jest/globals'; +import { describe, beforeAll, afterAll, test } from '@jest/globals'; import type { MockFunctionMetadata } from 'jest-mock'; const _filename = fileURLToPath(import.meta.url); @@ -74,164 +74,271 @@ describe('FileInfoService', () => { }); }); - test('Generic JPEG', async () => { - const path = `${resources}/Lenna.jpg`; - const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; - delete info.warnings; - delete info.blurhash; - delete info.sensitive; - delete info.porn; - assert.deepStrictEqual(info, { - size: 25360, - md5: '091b3f259662aa31e2ffef4519951168', - type: { - mime: 'image/jpeg', - ext: 'jpg', - }, - width: 512, - height: 512, - orientation: undefined, + describe('IMAGE', () => { + test('Generic JPEG', async () => { + const path = `${resources}/Lenna.jpg`; + const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; + delete info.warnings; + delete info.blurhash; + delete info.sensitive; + delete info.porn; + assert.deepStrictEqual(info, { + size: 25360, + md5: '091b3f259662aa31e2ffef4519951168', + type: { + mime: 'image/jpeg', + ext: 'jpg', + }, + width: 512, + height: 512, + orientation: undefined, + }); }); - }); - - test('Generic APNG', async () => { - const path = `${resources}/anime.png`; - const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; - delete info.warnings; - delete info.blurhash; - delete info.sensitive; - delete info.porn; - assert.deepStrictEqual(info, { - size: 1868, - md5: '08189c607bea3b952704676bb3c979e0', - type: { - mime: 'image/apng', - ext: 'apng', - }, - width: 256, - height: 256, - orientation: undefined, + + test('Generic APNG', async () => { + const path = `${resources}/anime.png`; + const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; + delete info.warnings; + delete info.blurhash; + delete info.sensitive; + delete info.porn; + assert.deepStrictEqual(info, { + size: 1868, + md5: '08189c607bea3b952704676bb3c979e0', + type: { + mime: 'image/apng', + ext: 'apng', + }, + width: 256, + height: 256, + orientation: undefined, + }); }); - }); - - test('Generic AGIF', async () => { - const path = `${resources}/anime.gif`; - const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; - delete info.warnings; - delete info.blurhash; - delete info.sensitive; - delete info.porn; - assert.deepStrictEqual(info, { - size: 2248, - md5: '32c47a11555675d9267aee1a86571e7e', - type: { - mime: 'image/gif', - ext: 'gif', - }, - width: 256, - height: 256, - orientation: undefined, + + test('Generic AGIF', async () => { + const path = `${resources}/anime.gif`; + const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; + delete info.warnings; + delete info.blurhash; + delete info.sensitive; + delete info.porn; + assert.deepStrictEqual(info, { + size: 2248, + md5: '32c47a11555675d9267aee1a86571e7e', + type: { + mime: 'image/gif', + ext: 'gif', + }, + width: 256, + height: 256, + orientation: undefined, + }); }); - }); - - test('PNG with alpha', async () => { - const path = `${resources}/with-alpha.png`; - const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; - delete info.warnings; - delete info.blurhash; - delete info.sensitive; - delete info.porn; - assert.deepStrictEqual(info, { - size: 3772, - md5: 'f73535c3e1e27508885b69b10cf6e991', - type: { - mime: 'image/png', - ext: 'png', - }, - width: 256, - height: 256, - orientation: undefined, + + test('PNG with alpha', async () => { + const path = `${resources}/with-alpha.png`; + const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; + delete info.warnings; + delete info.blurhash; + delete info.sensitive; + delete info.porn; + assert.deepStrictEqual(info, { + size: 3772, + md5: 'f73535c3e1e27508885b69b10cf6e991', + type: { + mime: 'image/png', + ext: 'png', + }, + width: 256, + height: 256, + orientation: undefined, + }); }); - }); - - test('Generic SVG', async () => { - const path = `${resources}/image.svg`; - const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; - delete info.warnings; - delete info.blurhash; - delete info.sensitive; - delete info.porn; - assert.deepStrictEqual(info, { - size: 505, - md5: 'b6f52b4b021e7b92cdd04509c7267965', - type: { - mime: 'image/svg+xml', - ext: 'svg', - }, - width: 256, - height: 256, - orientation: undefined, + + test('Generic SVG', async () => { + const path = `${resources}/image.svg`; + const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; + delete info.warnings; + delete info.blurhash; + delete info.sensitive; + delete info.porn; + assert.deepStrictEqual(info, { + size: 505, + md5: 'b6f52b4b021e7b92cdd04509c7267965', + type: { + mime: 'image/svg+xml', + ext: 'svg', + }, + width: 256, + height: 256, + orientation: undefined, + }); }); - }); - - test('SVG with XML definition', async () => { - // https://github.com/misskey-dev/misskey/issues/4413 - const path = `${resources}/with-xml-def.svg`; - const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; - delete info.warnings; - delete info.blurhash; - delete info.sensitive; - delete info.porn; - assert.deepStrictEqual(info, { - size: 544, - md5: '4b7a346cde9ccbeb267e812567e33397', - type: { - mime: 'image/svg+xml', - ext: 'svg', - }, - width: 256, - height: 256, - orientation: undefined, + + test('SVG with XML definition', async () => { + // https://github.com/misskey-dev/misskey/issues/4413 + const path = `${resources}/with-xml-def.svg`; + const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; + delete info.warnings; + delete info.blurhash; + delete info.sensitive; + delete info.porn; + assert.deepStrictEqual(info, { + size: 544, + md5: '4b7a346cde9ccbeb267e812567e33397', + type: { + mime: 'image/svg+xml', + ext: 'svg', + }, + width: 256, + height: 256, + orientation: undefined, + }); }); - }); - - test('Dimension limit', async () => { - const path = `${resources}/25000x25000.png`; - const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; - delete info.warnings; - delete info.blurhash; - delete info.sensitive; - delete info.porn; - assert.deepStrictEqual(info, { - size: 75933, - md5: '268c5dde99e17cf8fe09f1ab3f97df56', - type: { - mime: 'application/octet-stream', // do not treat as image - ext: null, - }, - width: 25000, - height: 25000, - orientation: undefined, + + test('Dimension limit', async () => { + const path = `${resources}/25000x25000.png`; + const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; + delete info.warnings; + delete info.blurhash; + delete info.sensitive; + delete info.porn; + assert.deepStrictEqual(info, { + size: 75933, + md5: '268c5dde99e17cf8fe09f1ab3f97df56', + type: { + mime: 'application/octet-stream', // do not treat as image + ext: null, + }, + width: 25000, + height: 25000, + orientation: undefined, + }); + }); + + test('Rotate JPEG', async () => { + const path = `${resources}/rotate.jpg`; + const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; + delete info.warnings; + delete info.blurhash; + delete info.sensitive; + delete info.porn; + assert.deepStrictEqual(info, { + size: 12624, + md5: '68d5b2d8d1d1acbbce99203e3ec3857e', + type: { + mime: 'image/jpeg', + ext: 'jpg', + }, + width: 512, + height: 256, + orientation: 8, + }); }); }); - test('Rotate JPEG', async () => { - const path = `${resources}/rotate.jpg`; - const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; - delete info.warnings; - delete info.blurhash; - delete info.sensitive; - delete info.porn; - assert.deepStrictEqual(info, { - size: 12624, - md5: '68d5b2d8d1d1acbbce99203e3ec3857e', - type: { - mime: 'image/jpeg', - ext: 'jpg', - }, - width: 512, - height: 256, - orientation: 8, + describe('AUDIO', () => { + test('MP3', async () => { + const path = `${resources}/kick_gaba7.mp3`; + const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; + delete info.warnings; + delete info.blurhash; + delete info.sensitive; + delete info.porn; + delete info.width; + delete info.height; + delete info.orientation; + assert.deepStrictEqual(info, { + size: 19853, + md5: '4f557df8548bc3cecc794c652f690446', + type: { + mime: 'audio/mpeg', + ext: 'mp3', + }, + }); + }); + + test('WAV', async () => { + const path = `${resources}/kick_gaba7.wav`; + const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; + delete info.warnings; + delete info.blurhash; + delete info.sensitive; + delete info.porn; + delete info.width; + delete info.height; + delete info.orientation; + assert.deepStrictEqual(info, { + size: 87630, + md5: '8bc9bb4fe5e77bb1871448209be635c1', + type: { + mime: 'audio/wav', + ext: 'wav', + }, + }); + }); + + test('AAC', async () => { + const path = `${resources}/kick_gaba7.aac`; + const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; + delete info.warnings; + delete info.blurhash; + delete info.sensitive; + delete info.porn; + delete info.width; + delete info.height; + delete info.orientation; + assert.deepStrictEqual(info, { + size: 7291, + md5: '2789323f05e3392b648066f50be6a2a6', + type: { + mime: 'audio/aac', + ext: 'aac', + }, + }); + }); + + test('FLAC', async () => { + const path = `${resources}/kick_gaba7.flac`; + const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; + delete info.warnings; + delete info.blurhash; + delete info.sensitive; + delete info.porn; + delete info.width; + delete info.height; + delete info.orientation; + assert.deepStrictEqual(info, { + size: 108793, + md5: 'bc0f3adfe0e1ca99ae6c7528c46b3173', + type: { + mime: 'audio/flac', + ext: 'flac', + }, + }); + }); + + /* + * video/webmã¨ã—ã¦æ¤œå‡ºã•ã‚Œã¦ã—ã¾ã† + test('WEBM AUDIO', async () => { + const path = `${resources}/kick_gaba7.webm`; + const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any; + delete info.warnings; + delete info.blurhash; + delete info.sensitive; + delete info.porn; + delete info.width; + delete info.height; + delete info.orientation; + assert.deepStrictEqual(info, { + size: 8879, + md5: '3350083dec312419cfdc06c16413aca7', + type: { + mime: 'audio/webm', + ext: 'webm', + }, + }); }); + */ }); }); diff --git a/packages/backend/test/unit/RelayService.ts b/packages/backend/test/unit/RelayService.ts index 529e923b2c038cd5bf1822ffb1a7ea26a26c1899..c2280142a6d41abc7736f5383d464b198de4bc4f 100644 --- a/packages/backend/test/unit/RelayService.ts +++ b/packages/backend/test/unit/RelayService.ts @@ -7,6 +7,7 @@ import { GlobalModule } from '@/GlobalModule.js'; import { RelayService } from '@/core/RelayService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { QueueService } from '@/core/QueueService.js'; import { IdService } from '@/core/IdService.js'; import type { RelaysRepository } from '@/models/index.js'; @@ -21,6 +22,7 @@ describe('RelayService', () => { let relayService: RelayService; let queueService: jest.Mocked<QueueService>; let relaysRepository: RelaysRepository; + let userEntityService: UserEntityService; beforeAll(async () => { app = await Test.createTestingModule({ @@ -32,6 +34,7 @@ describe('RelayService', () => { CreateSystemUserService, ApRendererService, RelayService, + UserEntityService, ], }) .useMocker((token) => { @@ -51,6 +54,7 @@ describe('RelayService', () => { relayService = app.get<RelayService>(RelayService); queueService = app.get<QueueService>(QueueService) as jest.Mocked<QueueService>; relaysRepository = app.get<RelaysRepository>(DI.relaysRepository); + userEntityService = app.get<UserEntityService>(UserEntityService); }); afterAll(async () => { @@ -63,7 +67,7 @@ describe('RelayService', () => { expect(result.inbox).toBe('https://example.com'); expect(result.status).toBe('requesting'); expect(queueService.deliver).toHaveBeenCalled(); - expect(queueService.deliver.mock.lastCall![1].type).toBe('Follow'); + expect(queueService.deliver.mock.lastCall![1]?.type).toBe('Follow'); expect(queueService.deliver.mock.lastCall![2]).toBe('https://example.com'); //expect(queueService.deliver.mock.lastCall![0].username).toBe('relay.actor'); }); @@ -80,8 +84,8 @@ describe('RelayService', () => { await relayService.removeRelay('https://example.com'); expect(queueService.deliver).toHaveBeenCalled(); - expect(queueService.deliver.mock.lastCall![1].type).toBe('Undo'); - expect(queueService.deliver.mock.lastCall![1].object.type).toBe('Follow'); + expect(queueService.deliver.mock.lastCall![1]?.type).toBe('Undo'); + expect(queueService.deliver.mock.lastCall![1]?.object.type).toBe('Follow'); expect(queueService.deliver.mock.lastCall![2]).toBe('https://example.com'); //expect(queueService.deliver.mock.lastCall![0].username).toBe('relay.actor'); diff --git a/packages/backend/test/unit/misc/check-word-mute.ts b/packages/backend/test/unit/misc/check-word-mute.ts new file mode 100644 index 0000000000000000000000000000000000000000..7ab838bdee78d4cefb81a56336364b25fcf875ec --- /dev/null +++ b/packages/backend/test/unit/misc/check-word-mute.ts @@ -0,0 +1,49 @@ +import { checkWordMute } from '@/misc/check-word-mute.js'; + +describe(checkWordMute, () => { + describe('Slacc boost mode', () => { + it('should return false if mutedWords is empty', async () => { + expect(await checkWordMute({ userId: '1', text: 'foo' }, null, [])).toBe(false); + }); + it('should return true if mutedWords is not empty and text contains muted word', async () => { + expect(await checkWordMute({ userId: '1', text: 'foo' }, null, [['foo']])).toBe(true); + }); + it('should return false if mutedWords is not empty and text does not contain muted word', async () => { + expect(await checkWordMute({ userId: '1', text: 'foo' }, null, [['bar']])).toBe(false); + }); + it('should return false when the note is written by me even if mutedWords is not empty and text contains muted word', async () => { + expect(await checkWordMute({ userId: '1', text: 'foo' }, { id: '1' }, [['foo']])).toBe(false); + }); + it('should return true if mutedWords is not empty and text contains muted word in CW', async () => { + expect(await checkWordMute({ userId: '1', text: 'foo', cw: 'bar' }, null, [['bar']])).toBe(true); + }); + it('should return true if mutedWords is not empty and text contains muted word in both CW and text', async () => { + expect(await checkWordMute({ userId: '1', text: 'foo', cw: 'bar' }, null, [['foo'], ['bar']])).toBe(true); + }); + it('should return true if mutedWords is not empty and text does not contain muted word in both CW and text', async () => { + expect(await checkWordMute({ userId: '1', text: 'foo', cw: 'bar' }, null, [['foo'], ['baz']])).toBe(true); + }); + }); + describe('normal mode', () => { + it('should return false if text does not contain muted words', async () => { + expect(await checkWordMute({ userId: '1', text: 'foo' }, null, [['foo', 'bar']])).toBe(false); + }); + it('should return true if text contains muted words', async () => { + expect(await checkWordMute({ userId: '1', text: 'foobar' }, null, [['foo', 'bar']])).toBe(true); + }); + it('should return false when the note is written by me even if text contains muted words', async () => { + expect(await checkWordMute({ userId: '1', text: 'foo bar' }, { id: '1' }, [['foo', 'bar']])).toBe(false); + }); + }); + describe('RegExp mode', () => { + it('should return false if text does not contain muted words', async () => { + expect(await checkWordMute({ userId: '1', text: 'foo' }, null, ['/bar/'])).toBe(false); + }); + it('should return true if text contains muted words', async () => { + expect(await checkWordMute({ userId: '1', text: 'foobar' }, null, ['/bar/'])).toBe(true); + }); + it('should return false when the note is written by me even if text contains muted words', async () => { + expect(await checkWordMute({ userId: '1', text: 'foo bar' }, { id: '1' }, ['/bar/'])).toBe(false); + }); + }); +}); diff --git a/packages/frontend/.storybook/changes.ts b/packages/frontend/.storybook/changes.ts index f0827331f7976beb4fcb922a042e0fcd87d654fe..755bec6869f7f5340cb641f11251b8d0a471d873 100644 --- a/packages/frontend/.storybook/changes.ts +++ b/packages/frontend/.storybook/changes.ts @@ -38,6 +38,7 @@ fs.readFile( path.resolve(__dirname, '../../..', arg) ) ) + .map((path) => path.replace(/(?:(?<=\.stories)\.(?:impl|meta)|\.msw)(?=\.ts$)/g, '')) .map((path) => (path.startsWith('.') ? path : `./${path}`)) ); if ( diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx index dd40bac2ccd27b09ca9aaa02b39975bd01639bb6..7c51d4c00ce432313fd90182624d1426321a46fb 100644 --- a/packages/frontend/.storybook/generate.tsx +++ b/packages/frontend/.storybook/generate.tsx @@ -118,7 +118,7 @@ function toStories(component: string): string { .replace(/[-.]|^(?=\d)/g, '_') .replace(/(?<=^[^A-Z_]*$)/, '_')} /> as estree.Identifier; - const parameters = ( + const parameters = <object-expression properties={[ <property @@ -137,9 +137,8 @@ function toStories(component: string): string { ] : []), ]} - /> - ) as estree.ObjectExpression; - const program = ( + /> as estree.ObjectExpression; + const program = <program body={[ <import-declaration @@ -379,11 +378,11 @@ function toStories(component: string): string { declaration={(<identifier name='meta' />) as estree.Identifier} /> as estree.ExportDefaultDeclaration, ]} - /> - ) as estree.Program; + /> as estree.Program; return format( '/* eslint-disable @typescript-eslint/explicit-function-return-type */\n' + '/* eslint-disable import/no-default-export */\n' + + '/* eslint-disable import/no-duplicates */\n' + generate(program, { generator }) + (hasImplStories ? readFileSync(`${implStories}.ts`, 'utf-8') : ''), { @@ -397,7 +396,11 @@ function toStories(component: string): string { // glob('src/{components,pages,ui,widgets}/**/*.vue') Promise.all([ glob('src/components/global/*.vue'), + glob('src/components/Mk{A,B}*.vue'), glob('src/components/MkGalleryPostPreview.vue'), + glob('src/components/MkSignupServerRules.vue'), + glob('src/components/MkUserSetupDialog.vue'), + glob('src/components/MkUserSetupDialog.*.vue'), glob('src/pages/user/home.vue'), ]) .then((globs) => globs.flat()) diff --git a/packages/frontend/.storybook/main.ts b/packages/frontend/.storybook/main.ts index 45db48fa1da6ae691a5f72a5a98d4f78cfc0c51e..1d0ce5ab63c914bb5f72baceef418ed6b9b6a78f 100644 --- a/packages/frontend/.storybook/main.ts +++ b/packages/frontend/.storybook/main.ts @@ -1,6 +1,6 @@ import { resolve } from 'node:path'; import type { StorybookConfig } from '@storybook/vue3-vite'; -import { mergeConfig } from 'vite'; +import { type Plugin, mergeConfig } from 'vite'; import turbosnap from 'vite-plugin-turbosnap'; const config = { stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], @@ -22,6 +22,10 @@ const config = { disableTelemetry: true, }, async viteFinal(config) { + const replacePluginForIsChromatic = config.plugins?.findIndex((plugin) => plugin && (plugin as Partial<Plugin>)?.name === 'replace') ?? -1; + if (~replacePluginForIsChromatic) { + config.plugins?.splice(replacePluginForIsChromatic, 1); + } return mergeConfig(config, { plugins: [ turbosnap({ diff --git a/packages/frontend/.storybook/mocks.ts b/packages/frontend/.storybook/mocks.ts index 41c3c5c4d926ae7d1aec8ec556257cd5e4cd94f7..4091e39686fbce8282d52f72b0eef7371717f06a 100644 --- a/packages/frontend/.storybook/mocks.ts +++ b/packages/frontend/.storybook/mocks.ts @@ -8,6 +8,16 @@ export const onUnhandledRequest = ((req, print) => { }) satisfies SharedOptions['onUnhandledRequest']; export const commonHandlers = [ + rest.get('/fluent-emoji/:codepoints.png', async (req, res, ctx) => { + const { codepoints } = req.params; + const value = await fetch(`https://raw.githubusercontent.com/misskey-dev/emojis/main/dist/${codepoints}.png`).then((response) => response.blob()); + return res(ctx.set('Content-Type', 'image/png'), ctx.body(value)); + }), + rest.get('/fluent-emojis/:codepoints.png', async (req, res, ctx) => { + const { codepoints } = req.params; + const value = await fetch(`https://raw.githubusercontent.com/misskey-dev/emojis/main/dist/${codepoints}.png`).then((response) => response.blob()); + return res(ctx.set('Content-Type', 'image/png'), ctx.body(value)); + }), rest.get('/twemoji/:codepoints.svg', async (req, res, ctx) => { const { codepoints } = req.params; const value = await fetch(`https://unpkg.com/@discordapp/twemoji@14.1.2/dist/svg/${codepoints}.svg`).then((response) => response.blob()); diff --git a/packages/frontend/.storybook/preview-head.html b/packages/frontend/.storybook/preview-head.html index 64e537b9310b295bfcc95f8a2b06c899af4299ad..ab694f64fb9876c7aeb014b7717bc4ef948fe11c 100644 --- a/packages/frontend/.storybook/preview-head.html +++ b/packages/frontend/.storybook/preview-head.html @@ -1,3 +1,5 @@ +<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true" as="image" type="image/png" crossorigin="anonymous"> +<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true" as="image" type="image/jpeg" crossorigin="anonymous"> <link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@2.12.0/tabler-icons.min.css"> <link rel="stylesheet" href="https://unpkg.com/@fontsource/m-plus-rounded-1c/index.css"> <style> diff --git a/packages/frontend/.storybook/preview.ts b/packages/frontend/.storybook/preview.ts index b2974276ab96ab0e08ae1c49dcd5a11d4035b0d1..e887acaa2e8fab5ecb14dc8648f03223797ffd00 100644 --- a/packages/frontend/.storybook/preview.ts +++ b/packages/frontend/.storybook/preview.ts @@ -3,6 +3,7 @@ import { FORCE_REMOUNT } from '@storybook/core-events'; import { type Preview, setup } from '@storybook/vue3'; import isChromatic from 'chromatic/isChromatic'; import { initialize, mswDecorator } from 'msw-storybook-addon'; +import { userDetailed } from './fakes'; import locale from './locale'; import { commonHandlers, onUnhandledRequest } from './mocks'; import themes from './themes'; @@ -10,6 +11,7 @@ import '../src/style.scss'; const appInitialized = Symbol(); +let lastStory = null; let moduleInitialized = false; let unobserve = () => {}; let misskeyOS = null; @@ -19,7 +21,7 @@ function loadTheme(applyTheme: typeof import('../src/scripts/theme')['applyTheme const theme = themes[document.documentElement.dataset.misskeyTheme]; if (theme) { applyTheme(themes[document.documentElement.dataset.misskeyTheme]); - } else if (isChromatic()) { + } else { applyTheme(themes['l-light']); } const observer = new MutationObserver((entries) => { @@ -42,10 +44,19 @@ function loadTheme(applyTheme: typeof import('../src/scripts/theme')['applyTheme unobserve = () => observer.disconnect(); } +function initLocalStorage() { + localStorage.clear(); + localStorage.setItem('account', JSON.stringify({ + ...userDetailed(), + policies: {}, + })); + localStorage.setItem('locale', JSON.stringify(locale)); +} + initialize({ onUnhandledRequest, }); -localStorage.setItem("locale", JSON.stringify(locale)); +initLocalStorage(); queueMicrotask(() => { Promise.all([ import('../src/components'), @@ -76,6 +87,27 @@ queueMicrotask(() => { const preview = { decorators: [ (Story, context) => { + if (lastStory === context.id) { + lastStory = null; + } else { + lastStory = context.id; + const channel = addons.getChannel(); + const resetIndexedDBPromise = globalThis.indexedDB?.databases + ? indexedDB.databases().then((r) => { + for (var i = 0; i < r.length; i++) { + indexedDB.deleteDatabase(r[i].name!); + } + }).catch(() => {}) + : Promise.resolve(); + const resetDefaultStorePromise = import('../src/store').then(({ defaultStore }) => { + // @ts-expect-error + defaultStore.init(); + }).catch(() => {}); + Promise.all([resetIndexedDBPromise, resetDefaultStorePromise]).then(() => { + initLocalStorage(); + channel.emit(FORCE_REMOUNT, { storyId: context.id }); + }); + } const story = Story(); if (!moduleInitialized) { const channel = addons.getChannel(); diff --git a/packages/frontend/.vscode/storybook.code-snippets b/packages/frontend/.vscode/storybook.code-snippets new file mode 100644 index 0000000000000000000000000000000000000000..785d0a16081a8805ddb3421e4c3b9bb20dee4a2e --- /dev/null +++ b/packages/frontend/.vscode/storybook.code-snippets @@ -0,0 +1,84 @@ +{ + "Storybook Story Impl File": { + "scope": "typescript", + "prefix": "storyimpl", + "body": [ + "/* eslint-disable @typescript-eslint/explicit-function-return-type */", + "import { StoryObj } from '@storybook/vue3';", + "import $1 from './$1.vue';", + "export const Default = {", + "\trender(args) {", + "\t\treturn {", + "\t\t\tcomponents: {", + "\t\t\t\t$1,", + "\t\t\t},", + "\t\t\tsetup() {", + "\t\t\t\treturn {", + "\t\t\t\t\targs,", + "\t\t\t\t};", + "\t\t\t},", + "\t\t\tcomputed: {", + "\t\t\t\tprops() {", + "\t\t\t\t\treturn {", + "\t\t\t\t\t\t...this.args,", + "\t\t\t\t\t};", + "\t\t\t\t},", + "\t\t\t},", + "\t\t\ttemplate: '<$1 v-bind=\"props\" />',", + "\t\t};", + "\t},", + "\targs: {", + "\t\t$2", + "\t},", + "\tparameters: {", + "\t\tlayout: 'centered',", + "\t},", + "} satisfies StoryObj<typeof $1>;", + "" + ] + }, + "Storybook Story Impl File (w/ events)": { + "scope": "typescript", + "prefix": "storyimplevent", + "body": [ + "/* eslint-disable @typescript-eslint/explicit-function-return-type */", + "import { action } from '@storybook/addon-actions';", + "import { StoryObj } from '@storybook/vue3';", + "import $1 from './$1.vue';", + "export const Default = {", + "\trender(args) {", + "\t\treturn {", + "\t\t\tcomponents: {", + "\t\t\t\t$1,", + "\t\t\t},", + "\t\t\tsetup() {", + "\t\t\t\treturn {", + "\t\t\t\t\targs,", + "\t\t\t\t};", + "\t\t\t},", + "\t\t\tcomputed: {", + "\t\t\t\tprops() {", + "\t\t\t\t\treturn {", + "\t\t\t\t\t\t...this.args,", + "\t\t\t\t\t};", + "\t\t\t\t},", + "\t\t\t\tevents() {", + "\t\t\t\t\treturn {", + "\t\t\t\t\t\t$3", + "\t\t\t\t\t};", + "\t\t\t\t},", + "\t\t\t},", + "\t\t\ttemplate: '<$1 v-bind=\"props\" v-on=\"events\" />',", + "\t\t};", + "\t},", + "\targs: {", + "\t\t$2", + "\t},", + "\tparameters: {", + "\t\tlayout: 'centered',", + "\t},", + "} satisfies StoryObj<typeof $1>;", + "" + ] + } +} diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 79fb626a9a9eaac1c06f0f9d09ac9208a25fc7b7..7646e152f9e6f2163238b2bc1cb91bb820c47c8e 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -15,28 +15,31 @@ }, "dependencies": { "@discordapp/twemoji": "14.1.2", - "@rollup/plugin-alias": "4.0.3", + "@rollup/plugin-alias": "5.0.0", "@rollup/plugin-json": "6.0.0", + "@rollup/plugin-replace": "^5.0.2", "@rollup/pluginutils": "5.0.2", - "@syuilo/aiscript": "0.13.1", - "@tabler/icons-webfont": "2.12.0", - "@vitejs/plugin-vue": "4.1.0", + "@syuilo/aiscript": "0.13.2", + "@tabler/icons-webfont": "2.17.0", + "@vitejs/plugin-vue": "4.2.1", + "@vue-macros/reactivity-transform": "^0.3.5", "@vue/compiler-sfc": "3.2.47", "autosize": "5.0.2", "blurhash": "2.0.5", "broadcast-channel": "4.20.2", "browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3", "canvas-confetti": "1.6.0", - "chart.js": "4.2.1", + "chart.js": "4.3.0", "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": "6.17.3", "compare-versions": "5.0.1", "cropperjs": "2.0.0-beta.2", - "date-fns": "2.29.3", + "date-fns": "2.30.0", "escape-regexp": "0.0.1", - "eventemitter3": "5.0.0", + "eventemitter3": "5.0.1", "gsap": "3.11.5", "idb-keyval": "6.2.0", "insert-text-at-cursor": "0.3.0", @@ -50,10 +53,10 @@ "punycode": "2.3.0", "querystring": "0.2.1", "rndstr": "1.0.0", - "rollup": "3.20.2", + "rollup": "3.21.3", "s-age": "1.1.2", "sanitize-html": "2.10.0", - "sass": "1.60.0", + "sass": "1.62.1", "seedrandom": "3.0.5", "strict-event-emitter-types": "2.0.0", "syuilo-password-strength": "0.0.1", @@ -61,45 +64,46 @@ "three": "0.151.3", "throttle-debounce": "5.0.0", "tinycolor2": "1.6.0", - "tsc-alias": "1.8.5", + "tsc-alias": "1.8.6", "tsconfig-paths": "4.2.0", "twemoji-parser": "14.0.0", - "typescript": "5.0.3", + "typescript": "5.0.4", "uuid": "9.0.0", "vanilla-tilt": "1.8.0", - "vite": "4.2.1", + "vite": "4.3.4", "vue": "3.2.47", "vue-plyr": "7.0.0", "vue-prism-editor": "2.0.0-alpha.2", "vuedraggable": "next" }, "devDependencies": { - "@storybook/addon-essentials": "7.0.2", - "@storybook/addon-interactions": "7.0.2", - "@storybook/addon-links": "7.0.2", - "@storybook/addon-storysource": "7.0.2", - "@storybook/addons": "7.0.2", - "@storybook/blocks": "7.0.2", - "@storybook/core-events": "7.0.2", + "@storybook/addon-actions": "7.0.7", + "@storybook/addon-essentials": "7.0.7", + "@storybook/addon-interactions": "7.0.7", + "@storybook/addon-links": "7.0.7", + "@storybook/addon-storysource": "7.0.7", + "@storybook/addons": "7.0.7", + "@storybook/blocks": "7.0.7", + "@storybook/core-events": "7.0.7", "@storybook/jest": "0.1.0", - "@storybook/manager-api": "7.0.2", - "@storybook/preview-api": "7.0.2", - "@storybook/react": "7.0.2", - "@storybook/react-vite": "7.0.2", - "@storybook/testing-library": "0.0.14-next.1", - "@storybook/theming": "7.0.2", - "@storybook/types": "7.0.2", - "@storybook/vue3": "7.0.2", - "@storybook/vue3-vite": "7.0.2", + "@storybook/manager-api": "7.0.7", + "@storybook/preview-api": "7.0.7", + "@storybook/react": "7.0.7", + "@storybook/react-vite": "7.0.7", + "@storybook/testing-library": "0.1.0", + "@storybook/theming": "7.0.7", + "@storybook/types": "7.0.7", + "@storybook/vue3": "7.0.7", + "@storybook/vue3-vite": "7.0.7", "@testing-library/jest-dom": "5.16.5", "@testing-library/vue": "7.0.0", "@types/escape-regexp": "0.0.1", - "@types/estree": "1.0.0", + "@types/estree": "1.0.1", "@types/gulp": "4.0.10", "@types/gulp-rename": "2.0.1", "@types/matter-js": "0.18.2", - "@types/micromatch": "3.1.1", - "@types/node": "18.15.11", + "@types/micromatch": "4.0.2", + "@types/node": "18.16.3", "@types/punycode": "2.1.0", "@types/sanitize-html": "2.9.0", "@types/seedrandom": "3.0.5", @@ -109,34 +113,33 @@ "@types/uuid": "9.0.1", "@types/websocket": "1.0.5", "@types/ws": "8.5.4", - "@typescript-eslint/eslint-plugin": "5.57.1", - "@typescript-eslint/parser": "5.57.1", - "@vitest/coverage-c8": "^0.29.8", + "@typescript-eslint/eslint-plugin": "5.59.2", + "@typescript-eslint/parser": "5.59.2", + "@vitest/coverage-c8": "0.30.1", "@vue/runtime-core": "3.2.47", "astring": "1.8.4", "chokidar-cli": "3.0.0", - "chromatic": "6.17.3", "cross-env": "7.0.3", - "cypress": "12.9.0", - "eslint": "8.37.0", + "cypress": "12.11.0", + "eslint": "8.39.0", "eslint-plugin-import": "2.27.5", - "eslint-plugin-vue": "9.10.0", + "eslint-plugin-vue": "9.11.0", "fast-glob": "3.2.12", - "happy-dom": "8.9.0", + "happy-dom": "9.10.2", "micromatch": "3.1.10", "msw": "1.2.1", "msw-storybook-addon": "1.8.0", - "prettier": "2.8.7", + "prettier": "2.8.8", "react": "18.2.0", "react-dom": "18.2.0", "start-server-and-test": "2.0.0", - "storybook": "7.0.2", + "storybook": "7.0.7", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "summaly": "github:misskey-dev/summaly", - "vite-plugin-turbosnap": "^1.0.1", - "vitest": "0.29.8", + "vite-plugin-turbosnap": "1.0.2", + "vitest": "0.30.1", "vitest-fetch-mock": "0.2.2", "vue-eslint-parser": "9.1.1", - "vue-tsc": "1.2.0" + "vue-tsc": "1.6.3" } } diff --git a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts new file mode 100644 index 0000000000000000000000000000000000000000..7d27adeb0448a1efe7c8a4cd4274a6f9306d259e --- /dev/null +++ b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts @@ -0,0 +1,49 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { action } from '@storybook/addon-actions'; +import { StoryObj } from '@storybook/vue3'; +import { rest } from 'msw'; +import { abuseUserReport } from '../../.storybook/fakes'; +import { commonHandlers } from '../../.storybook/mocks'; +import MkAbuseReport from './MkAbuseReport.vue'; +export const Default = { + render(args) { + return { + components: { + MkAbuseReport, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + events() { + return { + resolved: action('resolved'), + }; + }, + }, + template: '<MkAbuseReport v-bind="props" v-on="events" />', + }; + }, + args: { + report: abuseUserReport(), + }, + parameters: { + layout: 'fullscreen', + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/admin/resolve-abuse-user-report', async (req, res, ctx) => { + action('POST /api/admin/resolve-abuse-user-report')(await req.json()); + return res(ctx.json({})); + }), + ], + }, + }, +} satisfies StoryObj<typeof MkAbuseReport>; diff --git a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts new file mode 100644 index 0000000000000000000000000000000000000000..d0877ffd3be6b265a617c8d4a4ea8b6c439c19b0 --- /dev/null +++ b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts @@ -0,0 +1,49 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { action } from '@storybook/addon-actions'; +import { StoryObj } from '@storybook/vue3'; +import { rest } from 'msw'; +import { userDetailed } from '../../.storybook/fakes'; +import { commonHandlers } from '../../.storybook/mocks'; +import MkAbuseReportWindow from './MkAbuseReportWindow.vue'; +export const Default = { + render(args) { + return { + components: { + MkAbuseReportWindow, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + events() { + return { + 'closed': action('closed'), + }; + }, + }, + template: '<MkAbuseReportWindow v-bind="props" v-on="events" />', + }; + }, + args: { + user: userDetailed(), + }, + parameters: { + layout: 'centered', + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/users/report-abuse', async (req, res, ctx) => { + action('POST /api/users/report-abuse')(await req.json()); + return res(ctx.json({})); + }), + ], + }, + }, +} satisfies StoryObj<typeof MkAbuseReportWindow>; diff --git a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts new file mode 100644 index 0000000000000000000000000000000000000000..bed9d94311595acb25e4d1e0ae321147569d2faf --- /dev/null +++ b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts @@ -0,0 +1,33 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { userDetailed } from '../../.storybook/fakes'; +import MkAccountMoved from './MkAccountMoved.vue'; +export const Default = { + render(args) { + return { + components: { + MkAccountMoved, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkAccountMoved v-bind="props" />', + }; + }, + args: { + username: userDetailed().username, + host: userDetailed().host, + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj<typeof MkAccountMoved>; diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue index fd472de6c1df50b71004ad36833e3241f23d224f..b02bfdc2b82daa3e4a8973cfaeeab3c43d7b5f80 100644 --- a/packages/frontend/src/components/MkAccountMoved.vue +++ b/packages/frontend/src/components/MkAccountMoved.vue @@ -1,8 +1,8 @@ <template> -<div :class="$style.root"> +<div v-if="user" :class="$style.root"> <i class="ti ti-plane-departure" style="margin-right: 8px;"></i> {{ i18n.ts.accountMoved }} - <MkMention :class="$style.link" :username="acct" :host="host ?? localHost"/> + <MkMention :class="$style.link" :username="user.username" :host="user.host ?? localHost"/> </div> </template> @@ -10,11 +10,17 @@ import MkMention from './MkMention.vue'; import { i18n } from '@/i18n'; import { host as localHost } from '@/config'; +import { ref } from 'vue'; +import { UserLite } from 'misskey-js/built/entities'; +import { api } from '@/os'; -defineProps<{ - acct: string; - host: string; +const user = ref<UserLite>(); + +const props = defineProps<{ + movedTo: string; // user id }>(); + +api('users/show', { userId: props.movedTo }).then(u => user.value = u); </script> <style lang="scss" module> diff --git a/packages/frontend/src/components/MkAchievements.stories.impl.ts b/packages/frontend/src/components/MkAchievements.stories.impl.ts new file mode 100644 index 0000000000000000000000000000000000000000..477152a47b672a358f398b2b4a136cd07456e574 --- /dev/null +++ b/packages/frontend/src/components/MkAchievements.stories.impl.ts @@ -0,0 +1,56 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { rest } from 'msw'; +import { userDetailed } from '../../.storybook/fakes'; +import { commonHandlers } from '../../.storybook/mocks'; +import MkAchievements from './MkAchievements.vue'; +import { ACHIEVEMENT_TYPES } from '@/scripts/achievements'; +export const Empty = { + render(args) { + return { + components: { + MkAchievements, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkAchievements v-bind="props" />', + }; + }, + args: { + user: userDetailed(), + }, + parameters: { + layout: 'fullscreen', + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/users/achievements', (req, res, ctx) => { + return res(ctx.json([])); + }), + ], + }, + }, +} satisfies StoryObj<typeof MkAchievements>; +export const All = { + ...Empty, + parameters: { + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/users/achievements', (req, res, ctx) => { + return res(ctx.json(ACHIEVEMENT_TYPES.map((name) => ({ name, unlockedAt: 0 })))); + }), + ], + }, + }, +} satisfies StoryObj<typeof MkAchievements>; diff --git a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts index 05190aa268911c8a8cb959b127e32e7423d7e383..e7fbb472846e28a1bb79355cf56f34b673b51010 100644 --- a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts +++ b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import MkAnalogClock from './MkAnalogClock.vue'; +import isChromatic from 'chromatic'; export const Default = { render(args) { return { @@ -22,6 +23,14 @@ export const Default = { template: '<MkAnalogClock v-bind="props" />', }; }, + args: { + now: isChromatic() ? () => new Date('2023-01-01T10:10:30') : undefined, + }, + decorators: [ + () => ({ + template: '<div style="container-type:inline-size;height:100%"><div style="height:100cqmin;margin:auto;width:100cqmin"><story/></div></div>', + }), + ], parameters: { layout: 'fullscreen', }, diff --git a/packages/frontend/src/components/MkAnalogClock.vue b/packages/frontend/src/components/MkAnalogClock.vue index 12182026166afa8e558a187b227cfdeb1d180683..f12020f81047e0a391ec342fd10310e01e2e44ff 100644 --- a/packages/frontend/src/components/MkAnalogClock.vue +++ b/packages/frontend/src/components/MkAnalogClock.vue @@ -99,6 +99,7 @@ const props = withDefaults(defineProps<{ graduations?: 'none' | 'dots' | 'numbers'; fadeGraduations?: boolean; sAnimation?: 'none' | 'elastic' | 'easeOut'; + now?: () => Date; }>(), { numbers: false, thickness: 0.1, @@ -107,6 +108,7 @@ const props = withDefaults(defineProps<{ graduations: 'dots', fadeGraduations: true, sAnimation: 'elastic', + now: () => new Date(), }); const graduationsMajor = computed(() => { @@ -145,11 +147,17 @@ let disableSAnimate = $ref(false); let sOneRound = false; function tick() { - const now = new Date(); - now.setMinutes(now.getMinutes() + (new Date().getTimezoneOffset() + props.offset)); + const now = props.now(); + now.setMinutes(now.getMinutes() + now.getTimezoneOffset() + props.offset); + const previousS = s; + const previousM = m; + const previousH = h; s = now.getSeconds(); m = now.getMinutes(); h = now.getHours(); + if (previousS === s && previousM === m && previousH === h) { + return; + } hAngle = Math.PI * (h % (props.twentyfour ? 24 : 12) + (m + s / 60) / 60) / (props.twentyfour ? 12 : 6); mAngle = Math.PI * (m + s / 60) / 30; if (sOneRound) { // 秒é‡ãŒä¸€å‘¨ã—ãŸéš›ã®ã‚¢ãƒ‹ãƒ¡ãƒ¼ã‚·ãƒ§ãƒ³ã‚’よã—ãªã«å‡¦ç†ã™ã‚‹(ã“ã‚ŒãŒç„¡ã„ã¨ç§’ãŒ59->0ã«ãªã£ãŸã¨ãã«æœŸå¾…ã—ãŸã‚¢ãƒ‹ãƒ¡ãƒ¼ã‚·ãƒ§ãƒ³ã«ãªã‚‰ãªã„) diff --git a/packages/frontend/src/components/MkAsUi.stories.impl.ts b/packages/frontend/src/components/MkAsUi.stories.impl.ts new file mode 100644 index 0000000000000000000000000000000000000000..b67c0e679d5858d873e75c2c39a98520922cf102 --- /dev/null +++ b/packages/frontend/src/components/MkAsUi.stories.impl.ts @@ -0,0 +1,2 @@ +import MkAsUi from './MkAsUi.vue'; +void MkAsUi; diff --git a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts new file mode 100644 index 0000000000000000000000000000000000000000..075904d6a356037c8d93b358f797f3e0775ba354 --- /dev/null +++ b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts @@ -0,0 +1,176 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { action } from '@storybook/addon-actions'; +import { expect } from '@storybook/jest'; +import { userEvent, waitFor, within } from '@storybook/testing-library'; +import { StoryObj } from '@storybook/vue3'; +import { rest } from 'msw'; +import { userDetailed } from '../../.storybook/fakes'; +import { commonHandlers } from '../../.storybook/mocks'; +import MkAutocomplete from './MkAutocomplete.vue'; +import MkInput from './MkInput.vue'; +import { tick } from '@/scripts/test-utils'; +const common = { + render(args) { + return { + components: { + MkAutocomplete, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + events() { + return { + open: action('open'), + closed: action('closed'), + }; + }, + }, + template: '<MkAutocomplete v-bind="props" v-on="events" :textarea="textarea" />', + }; + }, + args: { + close: action('close'), + x: 0, + y: 0, + }, + decorators: [ + (_, context) => ({ + components: { + MkInput, + }, + data() { + return { + q: context.args.q, + textarea: null, + }; + }, + methods: { + inputMounted() { + this.textarea = this.$refs.input.$refs.inputEl; + }, + }, + template: '<MkInput v-model="q" ref="input" @vue:mounted="inputMounted"/><story v-if="textarea" :q="q" :textarea="textarea"/>', + }), + ], + parameters: { + controls: { + exclude: ['textarea'], + }, + layout: 'centered', + chromatic: { + // FIXME: flaky + disableSnapshot: true, + }, + }, +} satisfies StoryObj<typeof MkAutocomplete>; +export const User = { + ...common, + args: { + ...common.args, + type: 'user', + }, + async play({ canvasElement }) { + const canvas = within(canvasElement); + const input = canvas.getByRole('combobox'); + await waitFor(() => userEvent.hover(input)); + await waitFor(() => userEvent.click(input)); + await waitFor(() => userEvent.type(input, 'm')); + await waitFor(async () => { + await userEvent.type(input, ' ', { delay: 256 }); + await tick(); + return await expect(canvas.getByRole('list')).toBeInTheDocument(); + }, { timeout: 16384 }); + }, + parameters: { + ...common.parameters, + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/users/search-by-username-and-host', (req, res, ctx) => { + return res(ctx.json([ + userDetailed('44', 'mizuki', 'misskey-hub.net', 'Mizuki'), + userDetailed('49', 'momoko', 'misskey-hub.net', 'Momoko'), + ])); + }), + ], + }, + }, +}; +export const Hashtag = { + ...common, + args: { + ...common.args, + type: 'hashtag', + }, + async play({ canvasElement }) { + const canvas = within(canvasElement); + const input = canvas.getByRole('combobox'); + await waitFor(() => userEvent.hover(input)); + await waitFor(() => userEvent.click(input)); + await waitFor(() => userEvent.type(input, '気象')); + await waitFor(async () => { + await userEvent.type(input, ' ', { delay: 256 }); + await tick(); + return await expect(canvas.getByRole('list')).toBeInTheDocument(); + }, { interval: 256, timeout: 16384 }); + }, + parameters: { + ...common.parameters, + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/hashtags/search', (req, res, ctx) => { + return res(ctx.json([ + '気象è¦å ±æ³¨æ„å ±', + '気象è¦å ±', + 'æ°—è±¡æƒ…å ±', + ])); + }), + ], + }, + }, +}; +export const Emoji = { + ...common, + args: { + ...common.args, + type: 'emoji', + }, + async play({ canvasElement }) { + const canvas = within(canvasElement); + const input = canvas.getByRole('combobox'); + await waitFor(() => userEvent.hover(input)); + await waitFor(() => userEvent.click(input)); + await waitFor(() => userEvent.type(input, 'smile')); + await waitFor(async () => { + await userEvent.type(input, ' ', { delay: 256 }); + await tick(); + return await expect(canvas.getByRole('list')).toBeInTheDocument(); + }, { interval: 256, timeout: 16384 }); + }, +} satisfies StoryObj<typeof MkAutocomplete>; +export const MfmTag = { + ...common, + args: { + ...common.args, + type: 'mfmTag', + }, + async play({ canvasElement }) { + const canvas = within(canvasElement); + const input = canvas.getByRole('combobox'); + await waitFor(() => userEvent.hover(input)); + await waitFor(() => userEvent.click(input)); + await waitFor(async () => { + await tick(); + return await expect(canvas.getByRole('list')).toBeInTheDocument(); + }, { interval: 256, timeout: 16384 }); + }, +} satisfies StoryObj<typeof MkAutocomplete>; diff --git a/packages/frontend/src/components/MkAvatars.stories.impl.ts b/packages/frontend/src/components/MkAvatars.stories.impl.ts new file mode 100644 index 0000000000000000000000000000000000000000..14052c7343b490c9dc2ba20b1725b1b8f423c2f6 --- /dev/null +++ b/packages/frontend/src/components/MkAvatars.stories.impl.ts @@ -0,0 +1,46 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { rest } from 'msw'; +import { userDetailed } from '../../.storybook/fakes'; +import { commonHandlers } from '../../.storybook/mocks'; +import MkAvatars from './MkAvatars.vue'; +export const Default = { + render(args) { + return { + components: { + MkAvatars, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkAvatars v-bind="props" />', + }; + }, + args: { + userIds: ['17', '20', '18'], + }, + parameters: { + layout: 'centered', + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/users/show', (req, res, ctx) => { + return res(ctx.json([ + userDetailed('17'), + userDetailed('20'), + userDetailed('18'), + ])); + }), + ], + }, + }, +} satisfies StoryObj<typeof MkAvatars>; diff --git a/packages/frontend/src/components/MkButton.stories.impl.ts b/packages/frontend/src/components/MkButton.stories.impl.ts index e1c1c54d10f07233b3bf5a2fc724d7a77296ef5d..982a8b3be126d06aef3f11a6055d02ab7076c52f 100644 --- a/packages/frontend/src/components/MkButton.stories.impl.ts +++ b/packages/frontend/src/components/MkButton.stories.impl.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable import/no-default-export */ -/* eslint-disable import/no-duplicates */ +import { action } from '@storybook/addon-actions'; import { StoryObj } from '@storybook/vue3'; import MkButton from './MkButton.vue'; export const Default = { @@ -20,11 +20,60 @@ export const Default = { ...this.args, }; }, + events() { + return { + click: action('click'), + }; + }, }, - template: '<MkButton v-bind="props">Text</MkButton>', + template: '<MkButton v-bind="props" v-on="events">Text</MkButton>', }; }, + args: { + }, parameters: { layout: 'centered', }, } satisfies StoryObj<typeof MkButton>; +export const Primary = { + ...Default, + args: { + ...Default.args, + primary: true, + }, +} satisfies StoryObj<typeof MkButton>; +export const Gradate = { + ...Default, + args: { + ...Default.args, + gradate: true, + }, +} satisfies StoryObj<typeof MkButton>; +export const Rounded = { + ...Default, + args: { + ...Default.args, + rounded: true, + }, +} satisfies StoryObj<typeof MkButton>; +export const Danger = { + ...Default, + args: { + ...Default.args, + danger: true, + }, +} satisfies StoryObj<typeof MkButton>; +export const Small = { + ...Default, + args: { + ...Default.args, + small: true, + }, +} satisfies StoryObj<typeof MkButton>; +export const Large = { + ...Default, + args: { + ...Default.args, + large: true, + }, +} satisfies StoryObj<typeof MkButton>; diff --git a/packages/frontend/src/components/MkColorInput.vue b/packages/frontend/src/components/MkColorInput.vue new file mode 100644 index 0000000000000000000000000000000000000000..2471aa958dc6157524b93a8dd4ddc6b8beabd2d1 --- /dev/null +++ b/packages/frontend/src/components/MkColorInput.vue @@ -0,0 +1,110 @@ +<template> +<div> + <div :class="$style.label"><slot name="label"></slot></div> + <div :class="[$style.input, { disabled }]"> + <input + ref="inputEl" + v-model="v" + v-adaptive-border + :class="$style.inputCore" + type="color" + :disabled="disabled" + :required="required" + :readonly="readonly" + @input="onInput" + > + </div> + <div :class="$style.caption"><slot name="caption"></slot></div> +</div> +</template> + +<script lang="ts" setup> +import { onMounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue'; +import { i18n } from '@/i18n'; + +const props = defineProps<{ + modelValue: string | null; + required?: boolean; + readonly?: boolean; + disabled?: boolean; +}>(); + +const emit = defineEmits<{ + (ev: 'update:modelValue', value: string): void; +}>(); + +const { modelValue } = toRefs(props); +const v = ref(modelValue.value); +const inputEl = shallowRef<HTMLElement>(); + +const onInput = (ev: KeyboardEvent) => { + emit('update:modelValue', v.value); +}; +</script> + +<style lang="scss" module> +.label { + font-size: 0.85em; + padding: 0 0 8px 0; + user-select: none; + + &:empty { + display: none; + } +} + +.caption { + font-size: 0.85em; + padding: 8px 0 0 0; + color: var(--fgTransparentWeak); + + &:empty { + display: none; + } +} + +.input { + position: relative; + + &.focused { + > .inputCore { + border-color: var(--accent) !important; + //box-shadow: 0 0 0 4px var(--focus); + } + } + + &.disabled { + opacity: 0.7; + + &, + > .inputCore { + cursor: not-allowed !important; + } + } +} + +.inputCore { + appearance: none; + -webkit-appearance: none; + display: block; + height: 42px; + width: 100%; + margin: 0; + padding: 0 12px; + font: inherit; + font-weight: normal; + font-size: 1em; + color: var(--fg); + background: var(--panel); + border: solid 1px var(--panel); + border-radius: 6px; + outline: none; + box-shadow: none; + box-sizing: border-box; + transition: border-color 0.1s ease-out; + + &:hover { + border-color: var(--inputBorderHover) !important; + } +} +</style> diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue index a6372b7b6fef64d179d22703cede02596a50d1e9..d03331a6ebc14c9875dab31920b3ea431152436b 100644 --- a/packages/frontend/src/components/MkContainer.vue +++ b/packages/frontend/src/components/MkContainer.vue @@ -1,6 +1,6 @@ <template> -<div class="_panel" :class="[$style.root, { [$style.naked]: naked, [$style.thin]: thin, [$style.hideHeader]: !showHeader, [$style.scrollable]: scrollable, [$style.closed]: !showBody }]"> - <header v-if="showHeader" ref="header" :class="$style.header"> +<div ref="rootEl" class="_panel" :class="[$style.root, { [$style.naked]: naked, [$style.thin]: thin, [$style.hideHeader]: !showHeader, [$style.scrollable]: scrollable, [$style.closed]: !showBody }]"> + <header v-if="showHeader" ref="headerEl" :class="$style.header"> <div :class="$style.title"> <span :class="$style.titleIcon"><slot name="icon"></slot></span> <slot name="header"></slot> @@ -23,7 +23,7 @@ @leave="leave" @after-leave="afterLeave" > - <div v-show="showBody" ref="content" :class="[$style.content, { [$style.omitted]: omitted }]"> + <div v-show="showBody" ref="contentEl" :class="[$style.content, { [$style.omitted]: omitted }]"> <slot></slot> <button v-if="omitted" :class="$style.fade" class="_button" @click="() => { ignoreOmit = true; omitted = false; }"> <span :class="$style.fadeLabel">{{ i18n.ts.showMore }}</span> @@ -33,109 +33,80 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, ref, shallowRef, watch } from 'vue'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - showHeader: { - type: Boolean, - required: false, - default: true, - }, - thin: { - type: Boolean, - required: false, - default: false, - }, - naked: { - type: Boolean, - required: false, - default: false, - }, - foldable: { - type: Boolean, - required: false, - default: false, - }, - expanded: { - type: Boolean, - required: false, - default: true, - }, - scrollable: { - type: Boolean, - required: false, - default: false, - }, - maxHeight: { - type: Number, - required: false, - default: null, - }, - }, - data() { - return { - showBody: this.expanded, - omitted: null, - ignoreOmit: false, - defaultStore, - i18n, - }; - }, - mounted() { - this.$watch('showBody', showBody => { - const headerHeight = this.showHeader ? this.$refs.header.offsetHeight : 0; - this.$el.style.minHeight = `${headerHeight}px`; - if (showBody) { - this.$el.style.flexBasis = 'auto'; - } else { - this.$el.style.flexBasis = `${headerHeight}px`; - } - }, { - immediate: true, - }); +const props = withDefaults(defineProps<{ + showHeader?: boolean; + thin?: boolean; + naked?: boolean; + foldable?: boolean; + scrollable?: boolean; + expanded?: boolean; + maxHeight?: number | null; +}>(), { + expanded: true, + showHeader: true, + maxHeight: null, +}); - this.$el.style.setProperty('--maxHeight', this.maxHeight + 'px'); +const rootEl = shallowRef<HTMLElement>(); +const contentEl = shallowRef<HTMLElement>(); +const headerEl = shallowRef<HTMLElement>(); +const showBody = ref(props.expanded); +const ignoreOmit = ref(false); +const omitted = ref(false); + +function enter(el) { + const elementHeight = el.getBoundingClientRect().height; + el.style.height = 0; + el.offsetHeight; // reflow + el.style.height = Math.min(elementHeight, props.maxHeight ?? Infinity) + 'px'; +} - const calcOmit = () => { - if (this.omitted || this.ignoreOmit || this.maxHeight == null) return; - const height = this.$refs.content.offsetHeight; - this.omitted = height > this.maxHeight; - }; +function afterEnter(el) { + el.style.height = null; +} - calcOmit(); - new ResizeObserver((entries, observer) => { - calcOmit(); - }).observe(this.$refs.content); - }, - methods: { - toggleContent(show: boolean) { - if (!this.foldable) return; - this.showBody = show; - }, +function leave(el) { + const elementHeight = el.getBoundingClientRect().height; + el.style.height = elementHeight + 'px'; + el.offsetHeight; // reflow + el.style.height = 0; +} - enter(el) { - const elementHeight = el.getBoundingClientRect().height; - el.style.height = 0; - el.offsetHeight; // reflow - el.style.height = elementHeight + 'px'; - }, - afterEnter(el) { - el.style.height = null; - }, - leave(el) { - const elementHeight = el.getBoundingClientRect().height; - el.style.height = elementHeight + 'px'; - el.offsetHeight; // reflow - el.style.height = 0; - }, - afterLeave(el) { - el.style.height = null; - }, - }, +function afterLeave(el) { + el.style.height = null; +} + +const calcOmit = () => { + if (omitted.value || ignoreOmit.value || props.maxHeight == null) return; + const height = contentEl.value.offsetHeight; + omitted.value = height > props.maxHeight; +}; + +onMounted(() => { + watch(showBody, v => { + const headerHeight = props.showHeader ? headerEl.value.offsetHeight : 0; + rootEl.value.style.minHeight = `${headerHeight}px`; + if (v) { + rootEl.value.style.flexBasis = 'auto'; + } else { + rootEl.value.style.flexBasis = `${headerHeight}px`; + } + }, { + immediate: true, + }); + + rootEl.value.style.setProperty('--maxHeight', props.maxHeight + 'px'); + + calcOmit(); + + new ResizeObserver((entries, observer) => { + calcOmit(); + }).observe(contentEl.value); }); </script> diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue index 93c1f89199b749c672ae5bb35dba6a02fe2593b4..9f5404ce15587921f34aed1c5ddaf1440b73941d 100644 --- a/packages/frontend/src/components/MkDialog.vue +++ b/packages/frontend/src/components/MkDialog.vue @@ -9,7 +9,7 @@ <i v-else-if="type === 'error'" :class="$style.iconInner" class="ti ti-circle-x"></i> <i v-else-if="type === 'warning'" :class="$style.iconInner" class="ti ti-alert-triangle"></i> <i v-else-if="type === 'info'" :class="$style.iconInner" class="ti ti-info-circle"></i> - <i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-question-circle"></i> + <i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-help-circle"></i> <MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/> </div> <header v-if="title" :class="$style.title"><Mfm :text="title"/></header> @@ -32,8 +32,8 @@ </template> </MkSelect> <div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons"> - <MkButton v-if="showOkButton" inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabled" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton> - <MkButton v-if="showCancelButton || input || select" inline rounded @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton> + <MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabled" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton> + <MkButton v-if="showCancelButton || input || select" data-cy-modal-dialog-cancel inline rounded @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton> </div> <div v-if="actions" :class="$style.buttons"> <MkButton v-for="action in actions" :key="action.text" inline rounded :primary="action.primary" :danger="action.danger" @click="() => { action.callback(); modal?.close(); }">{{ action.text }}</MkButton> @@ -183,7 +183,7 @@ onBeforeUnmount(() => { box-sizing: border-box; text-align: center; background: var(--panel); - border-radius: var(--radius); + border-radius: 16px; } .icon { diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue index 58cc0de5c8cd8109ee834791e1df2fabc771ff32..10eee6aab106c5bd03d2ed26d8774041a0f83832 100644 --- a/packages/frontend/src/components/MkFolder.vue +++ b/packages/frontend/src/components/MkFolder.vue @@ -1,8 +1,8 @@ <template> -<div ref="rootEl" :class="$style.root"> +<div ref="rootEl" :class="$style.root" role="group" :aria-expanded="opened"> <MkStickyContainer> <template #header> - <div :class="[$style.header, { [$style.opened]: opened }]" class="_button" @click="toggle"> + <div :class="[$style.header, { [$style.opened]: opened }]" class="_button" role="button" data-cy-folder-header @click="toggle"> <div :class="$style.headerIcon"><slot name="icon"></slot></div> <div :class="$style.headerText"> <div :class="$style.headerTextMain"> @@ -20,7 +20,7 @@ </div> </template> - <div v-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : null, overflow: maxHeight ? `auto` : null }"> + <div v-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : null, overflow: maxHeight ? `auto` : null }" :aria-hidden="!opened"> <Transition :enter-active-class="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''" :leave-active-class="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''" @@ -65,7 +65,7 @@ const getBgColor = (el: HTMLElement) => { } }; -let rootEl = $ref<HTMLElement>(); +let rootEl = $shallowRef<HTMLElement>(); let bgSame = $ref(false); let opened = $ref(props.defaultOpen); let openedAtLeastOnce = $ref(props.defaultOpen); @@ -196,7 +196,7 @@ onMounted(() => { .headerRight { margin-left: auto; - opacity: 0.7; + color: var(--fgTransparentWeak); white-space: nowrap; } diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index de8db54bfad047b445b318c10d151a004842681f..beee21c647c3f83f55450b6bc02e9a3d2b7ed9e1 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -178,7 +178,7 @@ onBeforeUnmount(() => { } &.active { - color: #fff; + color: var(--fgOnAccent); background: var(--accent); &:hover { diff --git a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts index e46a70819252df5d760c915c51f3ff17254ef643..57b3e755132f9274d8fbc4cf1a4ee1fdfa6c04e1 100644 --- a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts +++ b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts @@ -28,9 +28,11 @@ export const Default = { async play({ canvasElement }) { const canvas = within(canvasElement); const links = canvas.getAllByRole('link'); - await expect(links).toHaveLength(2); - await expect(links[0]).toHaveAttribute('href', `/gallery/${galleryPost().id}`); - await expect(links[1]).toHaveAttribute('href', `/@${galleryPost().user.username}@${galleryPost().user.host}`); + expect(links).toHaveLength(2); + expect(links[0]).toHaveAttribute('href', `/gallery/${galleryPost().id}`); + expect(links[1]).toHaveAttribute('href', `/@${galleryPost().user.username}@${galleryPost().user.host}`); + const images = canvas.getAllByRole<HTMLImageElement>('img'); + await waitFor(() => expect(Promise.all(images.map((image) => image.decode()))).resolves.toBeDefined()); }, args: { post: galleryPost(), diff --git a/packages/frontend/src/components/MkGalleryPostPreview.vue b/packages/frontend/src/components/MkGalleryPostPreview.vue index 944f5ad97bc98ce6226edd66c53ad3149144be99..4f8f7b945ac65ecd6e1fd64b04cc00dc9f0c324c 100644 --- a/packages/frontend/src/components/MkGalleryPostPreview.vue +++ b/packages/frontend/src/components/MkGalleryPostPreview.vue @@ -1,9 +1,21 @@ <template> <MkA :to="`/gallery/${post.id}`" class="ttasepnz _panel" tabindex="-1" @pointerenter="enterHover" @pointerleave="leaveHover"> <div class="thumbnail"> - <ImgWithBlurhash class="img" :hash="post.files[0].blurhash"/> <Transition> - <ImgWithBlurhash v-if="show" class="img layered" :src="post.files[0].thumbnailUrl" :hash="post.files[0].blurhash"/> + <ImgWithBlurhash + class="img layered" + :transition="safe ? null : { + enterActiveClass: $style.transition_toggle_enterActive, + leaveActiveClass: $style.transition_toggle_leaveActive, + enterFromClass: $style.transition_toggle_enterFrom, + leaveToClass: $style.transition_toggle_leaveTo, + enterToClass: $style.transition_toggle_enterTo, + leaveFromClass: $style.transition_toggle_leaveFrom, + }" + :src="post.files[0].thumbnailUrl" + :hash="post.files[0].blurhash" + :force-blurhash="!show" + /> </Transition> </div> <article> @@ -28,7 +40,8 @@ const props = defineProps<{ }>(); const hover = ref(false); -const show = computed(() => defaultStore.state.nsfw === 'ignore' || defaultStore.state.nsfw === 'respect' && !props.post.isSensitive || hover.value); +const safe = computed(() => defaultStore.state.nsfw === 'ignore' || defaultStore.state.nsfw === 'respect' && !props.post.isSensitive); +const show = computed(() => safe.value || hover.value); function enterHover(): void { hover.value = true; @@ -39,6 +52,27 @@ function leaveHover(): void { } </script> +<style lang="scss" module> +.transition_toggle_enterActive, +.transition_toggle_leaveActive { + transition: opacity 0.5s; + position: absolute; + top: 0; + left: 0; +} + +.transition_toggle_enterFrom, +.transition_toggle_leaveTo { + opacity: 0; +} + +.transition_toggle_enterTo, +.transition_toggle_leaveFrom { + transition: none; + opacity: 1; +} +</style> + <style lang="scss" scoped> .ttasepnz { display: block; @@ -66,7 +100,7 @@ function leaveHover(): void { width: 100%; height: 100%; position: absolute; - transition: all 0.5s ease; + transition: transform 0.5s ease; > .img { width: 100%; @@ -76,16 +110,6 @@ function leaveHover(): void { &.layered { position: absolute; top: 0; - - &.v-enter-active, - &.v-leave-active { - transition: opacity 0.5s ease; - } - - &.v-enter-from, - &.v-leave-to { - opacity: 0; - } } } } diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue index 944c76d7dc8bae860cc3c3aeaefc668834ec18b0..6406a350601716d30d156bbb9a8fdbea0e457f8b 100644 --- a/packages/frontend/src/components/MkImgWithBlurhash.vue +++ b/packages/frontend/src/components/MkImgWithBlurhash.vue @@ -1,44 +1,90 @@ <template> -<div :class="[$style.root, { [$style.cover]: cover }]" :title="title"> - <canvas v-if="!loaded" ref="canvas" :class="$style.canvas" :width="size" :height="size" :title="title"/> - <img v-if="src" :class="$style.img" :src="src" :title="title" :alt="alt" @load="onLoad"/> +<div :class="[$style.root, { [$style.cover]: cover }]" :title="title ?? ''"> + <img v-if="!loaded && src && !forceBlurhash" :class="$style.loader" :src="src" @load="onLoad"/> + <Transition + mode="in-out" + :enter-active-class="defaultStore.state.animation && (props.transition?.enterActiveClass ?? $style['transition_toggle_enterActive']) || undefined" + :leave-active-class="defaultStore.state.animation && (props.transition?.leaveActiveClass ?? $style['transition_toggle_leaveActive']) || undefined" + :enter-from-class="defaultStore.state.animation && props.transition?.enterFromClass || undefined" + :leave-to-class="defaultStore.state.animation && props.transition?.leaveToClass || undefined" + :enter-to-class="defaultStore.state.animation && (props.transition?.enterToClass ?? $style['transition_toggle_enterTo']) || undefined" + :leave-from-class="defaultStore.state.animation && (props.transition?.leaveFromClass ?? $style['transition_toggle_leaveFrom']) || undefined" + > + <canvas v-if="!loaded || forceBlurhash" ref="canvas" :class="$style.canvas" :width="width" :height="height" :title="title ?? undefined"/> + <img v-else :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined"/> + </Transition> </div> </template> <script lang="ts" setup> -import { onMounted } from 'vue'; +import { onMounted, shallowRef, useCssModule, watch } from 'vue'; import { decode } from 'blurhash'; +import { defaultStore } from '@/store'; + +const $style = useCssModule(); const props = withDefaults(defineProps<{ + transition?: { + enterActiveClass?: string; + leaveActiveClass?: string; + enterFromClass?: string; + leaveToClass?: string; + enterToClass?: string; + leaveFromClass?: string; + } | null; src?: string | null; hash?: string; - alt?: string; + alt?: string | null; title?: string | null; - size?: number; + height?: number; + width?: number; cover?: boolean; + forceBlurhash?: boolean; }>(), { + transition: null, src: null, alt: '', title: null, - size: 64, + height: 64, + width: 64, cover: true, + forceBlurhash: false, }); -const canvas = $shallowRef<HTMLCanvasElement>(); +const canvas = shallowRef<HTMLCanvasElement>(); let loaded = $ref(false); +let width = $ref(props.width); +let height = $ref(props.height); + +function onLoad() { + loaded = true; +} + +watch([() => props.width, () => props.height], () => { + const ratio = props.width / props.height; + if (ratio > 1) { + width = Math.round(64 * ratio); + height = 64; + } else { + width = 64; + height = Math.round(64 / ratio); + } +}, { + immediate: true, +}); function draw() { - if (props.hash == null) return; - const pixels = decode(props.hash, props.size, props.size); - const ctx = canvas.getContext('2d'); - const imageData = ctx!.createImageData(props.size, props.size); + if (props.hash == null || !canvas.value) return; + const pixels = decode(props.hash, width, height); + const ctx = canvas.value.getContext('2d'); + const imageData = ctx!.createImageData(width, height); imageData.data.set(pixels); ctx!.putImageData(imageData, 0, 0); } -function onLoad() { - loaded = true; -} +watch([() => props.hash, canvas], () => { + draw(); +}); onMounted(() => { draw(); @@ -46,12 +92,33 @@ onMounted(() => { </script> <style lang="scss" module> +.transition_toggle_enterActive, +.transition_toggle_leaveActive { + position: absolute; + top: 0; + left: 0; +} + +.transition_toggle_enterTo, +.transition_toggle_leaveFrom { + opacity: 0; +} + +.loader { + position: absolute; + top: 0; + left: 0; + width: 0; + height: 0; +} + .root { position: relative; width: 100%; height: 100%; &.cover { + > .canvas, > .img { object-fit: cover; } @@ -66,8 +133,7 @@ onMounted(() => { } .canvas { - position: absolute; - object-fit: cover; + object-fit: contain; } .img { diff --git a/packages/frontend/src/components/MkInfo.vue b/packages/frontend/src/components/MkInfo.vue index dc7344d7072e88a1ac27effffaf90c72d9673734..cda428a77ccfea7c58da610578320975760e4b75 100644 --- a/packages/frontend/src/components/MkInfo.vue +++ b/packages/frontend/src/components/MkInfo.vue @@ -21,6 +21,7 @@ const props = defineProps<{ background: var(--infoBg); color: var(--infoFg); border-radius: var(--radius); + white-space: pre-wrap; &.warn { background: var(--infoWarnBg); diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue index 3e3d7354c1d6624e3c43ccd1c0aa3987edd41710..e48032d59926ce5e3baf2845d8973b66732509f1 100644 --- a/packages/frontend/src/components/MkInput.vue +++ b/packages/frontend/src/components/MkInput.vue @@ -1,12 +1,13 @@ <template> -<div class="matxzzsk"> - <div class="label" @click="focus"><slot name="label"></slot></div> - <div class="input" :class="{ inline, disabled, focused }"> - <div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div> +<div> + <div :class="$style.label" @click="focus"><slot name="label"></slot></div> + <div :class="[$style.input, { [$style.inline]: inline, [$style.disabled]: disabled, [$style.focused]: focused }]"> + <div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div> <input ref="inputEl" v-model="v" v-adaptive-border + :class="$style.inputCore" :type="type" :disabled="disabled" :required="required" @@ -25,11 +26,11 @@ <datalist v-if="datalist" :id="id"> <option v-for="data in datalist" :key="data" :value="data"/> </datalist> - <div ref="suffixEl" class="suffix"><slot name="suffix"></slot></div> + <div ref="suffixEl" :class="$style.suffix"><slot name="suffix"></slot></div> </div> - <div class="caption"><slot name="caption"></slot></div> + <div :class="$style.caption"><slot name="caption"></slot></div> - <MkButton v-if="manualSave && changed" primary class="save" @click="updated"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> + <MkButton v-if="manualSave && changed" primary :class="$style.save" @click="updated"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> </div> </template> @@ -151,115 +152,110 @@ onMounted(() => { }); </script> -<style lang="scss" scoped> -.matxzzsk { - > .label { - font-size: 0.85em; - padding: 0 0 8px 0; - user-select: none; +<style lang="scss" module> +.label { + font-size: 0.85em; + padding: 0 0 8px 0; + user-select: none; - &:empty { - display: none; - } + &:empty { + display: none; } +} - > .caption { - font-size: 0.85em; - padding: 8px 0 0 0; - color: var(--fgTransparentWeak); +.caption { + font-size: 0.85em; + padding: 8px 0 0 0; + color: var(--fgTransparentWeak); - &:empty { - display: none; - } + &:empty { + display: none; } +} - > .input { - position: relative; - - > input { - appearance: none; - -webkit-appearance: none; - display: block; - height: v-bind("height + 'px'"); - width: 100%; - margin: 0; - padding: 0 12px; - font: inherit; - font-weight: normal; - font-size: 1em; - color: var(--fg); - background: var(--panel); - border: solid 1px var(--panel); - border-radius: 6px; - outline: none; - box-shadow: none; - box-sizing: border-box; - transition: border-color 0.1s ease-out; - - &:hover { - border-color: var(--inputBorderHover) !important; - } - } - - > .prefix, - > .suffix { - display: flex; - align-items: center; - position: absolute; - z-index: 1; - top: 0; - padding: 0 12px; - font-size: 1em; - height: v-bind("height + 'px'"); - pointer-events: none; - - &:empty { - display: none; - } - - > * { - display: inline-block; - min-width: 16px; - max-width: 150px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - } +.input { + position: relative; - > .prefix { - left: 0; - padding-right: 6px; - } + &.inline { + display: inline-block; + margin: 0; + } - > .suffix { - right: 0; - padding-left: 6px; + &.focused { + > .inputCore { + border-color: var(--accent) !important; + //box-shadow: 0 0 0 4px var(--focus); } + } - &.inline { - display: inline-block; - margin: 0; - } + &.disabled { + opacity: 0.7; - &.focused { - > input { - border-color: var(--accent) !important; - //box-shadow: 0 0 0 4px var(--focus); - } + &, + > .inputCore { + cursor: not-allowed !important; } + } +} - &.disabled { - opacity: 0.7; - - &, * { - cursor: not-allowed !important; - } - } +.inputCore { + appearance: none; + -webkit-appearance: none; + display: block; + height: v-bind("height + 'px'"); + width: 100%; + margin: 0; + padding: 0 12px; + font: inherit; + font-weight: normal; + font-size: 1em; + color: var(--fg); + background: var(--panel); + border: solid 1px var(--panel); + border-radius: 6px; + outline: none; + box-shadow: none; + box-sizing: border-box; + transition: border-color 0.1s ease-out; + + &:hover { + border-color: var(--inputBorderHover) !important; } +} - > .save { - margin: 8px 0 0 0; +.prefix, +.suffix { + display: flex; + align-items: center; + position: absolute; + z-index: 1; + top: 0; + padding: 0 12px; + font-size: 1em; + height: v-bind("height + 'px'"); + min-width: 16px; + max-width: 150px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + box-sizing: border-box; + pointer-events: none; + + &:empty { + display: none; } } + +.prefix { + left: 0; + padding-right: 6px; +} + +.suffix { + right: 0; + padding-left: 6px; +} +.save { + margin: 8px 0 0 0; +} </style> diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index a4065dcd0729a442c637cb37afc011c04719ba07..42dc9e79ff55c154d0eb9d6408d0d3712f4b082f 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -1,9 +1,10 @@ <template> <div v-if="hide" :class="$style.hidden" @click="hide = false"> - <ImgWithBlurhash style="filter: brightness(0.5);" :hash="image.blurhash" :title="image.comment" :alt="image.comment"/> + <ImgWithBlurhash style="filter: brightness(0.5);" :hash="image.blurhash" :title="image.comment" :alt="image.comment" :width="image.properties.width" :height="image.properties.height" :force-blurhash="defaultStore.state.enableDataSaverMode"/> <div :class="$style.hiddenText"> <div :class="$style.hiddenTextWrapper"> - <b style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}</b> + <b v-if="image.isSensitive" style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b> + <b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.enableDataSaverMode && image.size ? bytes(image.size) : i18n.ts.image }}</b> <span style="display: block;">{{ i18n.ts.clickToShow }}</span> </div> </div> @@ -14,13 +15,15 @@ :href="image.url" :title="image.name" > - <ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment || image.name" :title="image.comment || image.name" :cover="false"/> + <ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment || image.name" :title="image.comment || image.name" :width="image.properties.width" :height="image.properties.height" :cover="false"/> </a> <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);">NSFW</div> </div> <button v-tooltip="i18n.ts.hide" :class="$style.hide" class="_button" @click="hide = true"><i class="ti ti-eye-off"></i></button> + <button :class="$style.menu" class="_button" @click.stop="showMenu"><i class="ti ti-dots"></i></button> </div> </template> @@ -28,9 +31,12 @@ import { watch } from 'vue'; import * as misskey from 'misskey-js'; import { getStaticImageUrl } from '@/scripts/media-proxy'; +import bytes from '@/filters/bytes'; import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; +import * as os from '@/os'; +import { iAmModerator } from '@/account'; const props = defineProps<{ image: misskey.entities.DriveFile; @@ -38,21 +44,33 @@ const props = defineProps<{ }>(); let hide = $ref(true); -let darkMode = $ref(defaultStore.state.darkMode); +let darkMode: boolean = $ref(defaultStore.state.darkMode); -const url = (props.raw || defaultStore.state.loadRawImages) +const url = $computed(() => (props.raw || defaultStore.state.loadRawImages) ? props.image.url : defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(props.image.url) - : props.image.thumbnailUrl; + : props.image.thumbnailUrl, +); // Plugin:register_note_view_interruptor を使ã£ã¦æ›¸ãæ›ãˆã‚‰ã‚Œã‚‹å¯èƒ½æ€§ãŒã‚ã‚‹ãŸã‚watchã™ã‚‹ watch(() => props.image, () => { - hide = (defaultStore.state.nsfw === 'force') ? true : props.image.isSensitive && (defaultStore.state.nsfw !== 'ignore'); + hide = (defaultStore.state.nsfw === 'force' || defaultStore.state.enableDataSaverMode) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore'); }, { deep: true, immediate: true, }); + +function showMenu(ev: MouseEvent) { + os.popupMenu([...(iAmModerator ? [{ + text: i18n.ts.markAsSensitive, + icon: 'ti ti-eye-off', + action: () => { + os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true }); + }, + }] : [])], ev.currentTarget ?? ev.target); +} + </script> <style lang="scss" module> @@ -102,6 +120,21 @@ watch(() => props.image, () => { right: 12px; } +.menu { + display: block; + position: absolute; + border-radius: 6px; + background-color: rgba(0, 0, 0, 0.3); + -webkit-backdrop-filter: var(--blur, blur(15px)); + backdrop-filter: var(--blur, blur(15px)); + color: #fff; + font-size: 0.8em; + padding: 6px 8px; + text-align: center; + bottom: 12px; + right: 12px; +} + .imageContainer { display: block; cursor: zoom-in; @@ -132,6 +165,7 @@ watch(() => props.image, () => { color: var(--accentLighten); display: inline-block; font-weight: bold; - padding: 0 6px; + font-size: 12px; + padding: 2px 6px; } </style> diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index d36cc2d26b5b480b97fb57308f463e5f0f0ebad4..e456ff3eec76c395ee4833bd0d3e01a4d2c16900 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -2,10 +2,16 @@ <div> <XBanner v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :media="media"/> <div v-if="mediaList.filter(media => previewable(media)).length > 0" :class="$style.container"> - <div ref="gallery" :class="[$style.medias, count <= 4 ? $style['n' + count] : $style.nMany]"> + <div + ref="gallery" + :class="[ + $style.medias, + count <= 4 ? $style['n' + count] : $style.nMany, + ]" + > <template v-for="media in mediaList.filter(media => previewable(media))"> - <XVideo v-if="media.type.startsWith('video')" :key="media.id" :class="$style.media" :video="media"/> - <XImage v-else-if="media.type.startsWith('image')" :key="media.id" :class="$style.media" class="image" :data-id="media.id" :image="media" :raw="raw"/> + <XVideo v-if="media.type.startsWith('video')" :key="`video:${media.id}`" :class="$style.media" :video="media"/> + <XImage v-else-if="media.type.startsWith('image')" :key="`image:${media.id}`" :class="$style.media" class="image" :data-id="media.id" :image="media" :raw="raw"/> </template> </div> </div> @@ -13,7 +19,7 @@ </template> <script lang="ts" setup> -import { onMounted, ref, useCssModule } from 'vue'; +import { onMounted, ref, useCssModule, watch } from 'vue'; import * as misskey from 'misskey-js'; import PhotoSwipeLightbox from 'photoswipe/lightbox'; import PhotoSwipe from 'photoswipe'; @@ -23,6 +29,7 @@ import XImage from '@/components/MkMediaImage.vue'; import XVideo from '@/components/MkMediaVideo.vue'; import * as os from '@/os'; import { FILE_TYPE_BROWSERSAFE } from '@/const'; +import { defaultStore } from '@/store'; const props = defineProps<{ mediaList: misskey.entities.DriveFile[]; @@ -31,7 +38,7 @@ const props = defineProps<{ const $style = useCssModule(); -const gallery = ref(null); +const gallery = ref<HTMLDivElement>(); const pswpZIndex = os.claimZIndex('middle'); document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString()); const count = $computed(() => props.mediaList.filter(media => previewable(media)).length); diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index e02a7af09ead97c273bd2d2f4940a65049e74d45..a4b76300e6ad71274380aa63159dba087a148ae4 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -1,7 +1,9 @@ <template> <div v-if="hide" class="icozogqfvdetwohsdglrbswgrejoxbdj" @click="hide = false"> + <!-- ã€æ³¨æ„】dataSaverMode ãŒæœ‰åŠ¹ã«ãªã£ã¦ã„ã‚‹éš›ã«ã¯ã€hide ㌠false ã«ãªã‚‹ã¾ã§ã‚µãƒ ãƒã‚¤ãƒ«ã‚„動画をèªã¿è¾¼ã¾ãªã„よã†ã«ã™ã‚‹ã“㨠--> <div> - <b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}</b> + <b v-if="video.isSensitive"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b> + <b v-else><i class="ti ti-movie"></i> {{ defaultStore.state.enableDataSaverMode && video.size ? bytes(video.size) : i18n.ts.video }}</b> <span>{{ i18n.ts.clickToShow }}</span> </div> </div> @@ -25,6 +27,7 @@ <script lang="ts" setup> import { ref } from 'vue'; import * as misskey from 'misskey-js'; +import bytes from '@/filters/bytes'; import VuePlyr from 'vue-plyr'; import { defaultStore } from '@/store'; import 'vue-plyr/dist/vue-plyr.css'; @@ -34,7 +37,7 @@ const props = defineProps<{ video: misskey.entities.DriveFile; }>(); -const hide = ref((defaultStore.state.nsfw === 'force') ? true : props.video.isSensitive && (defaultStore.state.nsfw !== 'ignore')); +const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.enableDataSaverMode) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore')); </script> <style lang="scss" scoped> diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue index 852c72f6ffc47110a0c68e0f9d9829e2b9922b0a..99df9e8150863f65a8f488ded028df5493e1814b 100644 --- a/packages/frontend/src/components/MkModal.vue +++ b/packages/frontend/src/components/MkModal.vue @@ -404,16 +404,10 @@ defineExpose({ right: 0; margin: auto; padding: 32px; - // TODO: mask-imageã¯iOSã ã¨ã‚„ãŸã‚‰é‡ã„。ãªã‚“ã¨ã‹ã—ãŸã„ - -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 32px, rgba(0,0,0,1) calc(100% - 32px), rgba(0,0,0,0) 100%); - mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 32px, rgba(0,0,0,1) calc(100% - 32px), rgba(0,0,0,0) 100%); - overflow: auto; display: flex; @media (max-width: 500px) { padding: 16px; - -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%); - mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%); } } } diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue index dd115246ff101614ea772f591b6095d31d20a848..1c942cfd0d5f34814a98e2408b5e29e5e65e18bb 100644 --- a/packages/frontend/src/components/MkModalWindow.vue +++ b/packages/frontend/src/components/MkModalWindow.vue @@ -1,12 +1,12 @@ <template> <MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')"> - <div ref="rootEl" class="ebkgoccj" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown"> + <div ref="rootEl" class="ebkgoccj" :style="{ width: `${width}px`, height: height ? `${height}px` : null }" @keydown="onKeydown"> <div ref="headerEl" class="header"> <button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="ti ti-x"></i></button> <span class="title"> <slot name="header"></slot> </span> - <button v-if="!withOkButton" class="_button" @click="$emit('close')"><i class="ti ti-x"></i></button> + <button v-if="!withOkButton" class="_button" data-cy-modal-window-close @click="$emit('close')"><i class="ti ti-x"></i></button> <button v-if="withOkButton" class="_button" :disabled="okButtonDisabled" @click="$emit('ok')"><i class="ti ti-check"></i></button> </div> <div class="body"> @@ -25,13 +25,11 @@ const props = withDefaults(defineProps<{ okButtonDisabled: boolean; width: number; height: number | null; - scroll: boolean; }>(), { withOkButton: false, okButtonDisabled: false, width: 400, height: null, - scroll: true, }); const emit = defineEmits<{ @@ -86,11 +84,11 @@ defineExpose({ <style lang="scss" scoped> .ebkgoccj { margin: auto; + max-height: 100%; overflow: hidden; display: flex; flex-direction: column; contain: content; - container-type: inline-size; border-radius: var(--radius); --root-margin: 24px; @@ -143,6 +141,7 @@ defineExpose({ flex: 1; overflow: auto; background: var(--panel); + container-type: size; } } </style> diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 36ec778a14b427719d31985864794e053de03197..d95f8de311a9568adb1904c79f96d9f43312b296 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -12,6 +12,7 @@ <!--<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> <MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/> <i class="ti ti-repeat" style="margin-right: 4px;"></i> <I18n :src="i18n.ts.renotedBy" tag="span" :class="$style.renoteText"> @@ -40,6 +41,7 @@ <Mfm :text="getNoteSummary(appearNote)" :plain="true" :nowrap="true" :author="appearNote.user" :class="$style.collapsedRenoteTargetText" @click="renoteCollapsed = false"/> </div> <article v-else :class="$style.article" @contextmenu.stop="onContextmenu"> + <div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div> <MkAvatar :class="$style.avatar" :user="appearNote.user" link preview/> <div :class="$style.main"> <MkNoteHeader :class="$style.header" :note="appearNote" :mini="true"/> @@ -162,6 +164,7 @@ import { claimAchievement } from '@/scripts/achievements'; import { getNoteSummary } from '@/scripts/get-note-summary'; import { MenuItem } from '@/types/menu'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; +import { showMovedDialog } from '@/scripts/show-moved-dialog'; const props = defineProps<{ note: misskey.entities.Note; @@ -255,6 +258,7 @@ useTooltip(renoteButton, async (showing) => { function renote(viaKeyboard = false) { pleaseLogin(); + showMovedDialog(); let items = [] as MenuItem[]; @@ -335,6 +339,7 @@ function reply(viaKeyboard = false): void { function react(viaKeyboard = false): void { pleaseLogin(); + showMovedDialog(); if (appearNote.reactionAcceptance === 'likeOnly') { os.api('notes/reactions/create', { noteId: appearNote.id, @@ -401,6 +406,7 @@ async function clip() { function showRenoteMenu(viaKeyboard = false): void { if (!isMyRenote) return; + pleaseLogin(); os.popupMenu([{ text: i18n.ts.unrenote, icon: 'ti ti-trash', @@ -484,6 +490,11 @@ function showReactions(): void { } } + .footer { + position: relative; + z-index: 1; + } + &:hover > .article > .main > .footer > .footerButton { opacity: 1; } @@ -537,6 +548,7 @@ function showReactions(): void { } .renote { + position: relative; display: flex; align-items: center; padding: 16px 32px 8px 32px; @@ -547,6 +559,10 @@ function showReactions(): void { & + .article { padding-top: 8px; } + + > .colorBar { + height: calc(100% - 6px); + } } .renoteAvatar { @@ -618,6 +634,16 @@ function showReactions(): void { 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; @@ -669,6 +695,7 @@ function showReactions(): void { position: absolute; bottom: 0; left: 0; + z-index: 2; width: 100%; height: 64px; background: linear-gradient(0deg, var(--panel), var(--X15)); @@ -833,6 +860,13 @@ function showReactions(): void { } } } + + .colorBar { + top: 6px; + left: 6px; + width: 4px; + height: calc(100% - 12px); + } } @container (max-width: 300px) { diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index b9ab3668507eaa907d98a64a17b3fb715f0f01df..0d6d329d9813087523b6c4427e33a33d2ce72b56 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -166,6 +166,7 @@ import { useTooltip } from '@/scripts/use-tooltip'; import { claimAchievement } from '@/scripts/achievements'; import { MenuItem } from '@/types/menu'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; +import { showMovedDialog } from '@/scripts/show-moved-dialog'; const props = defineProps<{ note: misskey.entities.Note; @@ -248,6 +249,7 @@ useTooltip(renoteButton, async (showing) => { function renote(viaKeyboard = false) { pleaseLogin(); + showMovedDialog(); let items = [] as MenuItem[]; @@ -318,6 +320,7 @@ function renote(viaKeyboard = false) { function reply(viaKeyboard = false): void { pleaseLogin(); + showMovedDialog(); os.post({ reply: appearNote, animation: !viaKeyboard, @@ -328,6 +331,7 @@ function reply(viaKeyboard = false): void { function react(viaKeyboard = false): void { pleaseLogin(); + showMovedDialog(); if (appearNote.reactionAcceptance === 'likeOnly') { os.api('notes/reactions/create', { noteId: appearNote.id, @@ -394,6 +398,7 @@ async function clip() { function showRenoteMenu(viaKeyboard = false): void { if (!isMyRenote) return; + pleaseLogin(); os.popupMenu([{ text: i18n.ts.unrenote, icon: 'ti ti-trash', diff --git a/packages/frontend/src/components/MkNumberDiff.vue b/packages/frontend/src/components/MkNumberDiff.vue index e7d4a5472af281d72c737d37b23ee9f6fa07dd67..303417dae846e80998a4b361383b42f4fc94903d 100644 --- a/packages/frontend/src/components/MkNumberDiff.vue +++ b/packages/frontend/src/components/MkNumberDiff.vue @@ -1,47 +1,32 @@ <template> -<span class="ceaaebcd" :class="{ isPlus, isMinus, isZero }"> +<span class="ceaaebcd" :class="{ [$style.isPlus]: isPlus, [$style.isMinus]: isMinus, [$style.isZero]: isZero }"> <slot name="before"></slot>{{ isPlus ? '+' : '' }}{{ number(value) }}<slot name="after"></slot> </span> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import number from '@/filters/number'; -export default defineComponent({ - props: { - value: { - type: Number, - required: true, - }, - }, +const props = defineProps<{ + value: number; +}>(); - setup(props) { - const isPlus = computed(() => props.value > 0); - const isMinus = computed(() => props.value < 0); - const isZero = computed(() => props.value === 0); - return { - isPlus, - isMinus, - isZero, - number, - }; - }, -}); +const isPlus = computed(() => props.value > 0); +const isMinus = computed(() => props.value < 0); +const isZero = computed(() => props.value === 0); </script> -<style lang="scss" scoped> -.ceaaebcd { - &.isPlus { - color: var(--success); - } +<style lang="scss" module> +.isPlus { + color: var(--success); +} - &.isMinus { - color: var(--error); - } +.isMinus { + color: var(--error); +} - &.isZero { - opacity: 0.5; - } +.isZero { + opacity: 0.5; } </style> diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue index 0f148022bf0f9893d6f6b21ab88a23accfe2b677..e2d68d12c348fa0f1f7717d957318c784b87be2e 100644 --- a/packages/frontend/src/components/MkOmit.vue +++ b/packages/frontend/src/components/MkOmit.vue @@ -17,7 +17,7 @@ const props = withDefaults(defineProps<{ maxHeight: 200, }); -let content = $ref<HTMLElement>(); +let content = $shallowRef<HTMLElement>(); let omitted = $ref(false); let ignoreOmit = $ref(false); diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 42a3748d9aa50472ad47fea3fbc54102d5df6f9f..c65cb7d6e5d247c0ad61e1459bb8c96ae411b1a5 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -247,6 +247,10 @@ watch($$(text), () => { checkMissingMention(); }, { immediate: true }); +watch($$(visibility), () => { + checkMissingMention(); +}, { immediate: true }); + watch($$(visibleUsers), () => { checkMissingMention(); }, { @@ -900,27 +904,28 @@ defineExpose({ } .headerLeft { - display: grid; - grid-template-columns: repeat(2, minmax(36px, 50px)); - grid-template-rows: minmax(40px, 100%); + display: flex; + flex: 0 1 100px; } .cancel { padding: 0; font-size: 1em; height: 100%; + flex: 0 1 50px; } .account { height: 100%; display: inline-flex; vertical-align: bottom; + flex: 0 1 50px; } .avatar { width: 28px; height: 28px; - margin: auto 0; + margin: auto; } .headerRight { diff --git a/packages/frontend/src/components/MkRadio.vue b/packages/frontend/src/components/MkRadio.vue index fcf454c77a93f08161a660f3fba39f5502093967..5db2f5ee6d79a4fbcb21201187ebb16a07d5cfb2 100644 --- a/packages/frontend/src/components/MkRadio.vue +++ b/packages/frontend/src/components/MkRadio.vue @@ -24,7 +24,7 @@ import { } from 'vue'; const props = defineProps<{ modelValue: any; value: any; - disabled: boolean; + disabled?: boolean; }>(); const emit = defineEmits<{ diff --git a/packages/frontend/src/components/MkRadios.vue b/packages/frontend/src/components/MkRadios.vue index 8590ccf9ae2adbedd6a5b68f22c91a2243055eef..e2240fb4e13500bf02bf73b16801e03ba2ba3fe0 100644 --- a/packages/frontend/src/components/MkRadios.vue +++ b/packages/frontend/src/components/MkRadios.vue @@ -1,5 +1,5 @@ <script lang="ts"> -import { defineComponent, h } from 'vue'; +import { VNode, defineComponent, h } from 'vue'; import MkRadio from './MkRadio.vue'; export default defineComponent({ @@ -22,31 +22,33 @@ export default defineComponent({ }, }, render() { + console.log(this.$slots, this.$slots.label && this.$slots.label()); + if (!this.$slots.default) return null; let options = this.$slots.default(); const label = this.$slots.label && this.$slots.label(); const caption = this.$slots.caption && this.$slots.caption(); // ãªãœã‹Fragmentã«ãªã‚‹ã“ã¨ãŒã‚ã‚‹ãŸã‚ - if (options.length === 1 && options[0].props == null) options = options[0].children; + if (options.length === 1 && options[0].props == null) options = options[0].children as VNode[]; return h('div', { class: 'novjtcto', }, [ ...(label ? [h('div', { class: 'label', - }, [label])] : []), + }, label)] : []), h('div', { class: 'body', }, options.map(option => h(MkRadio, { key: option.key, - value: option.props.value, + value: option.props?.value, modelValue: this.value, 'onUpdate:modelValue': value => this.value = value, - }, option.children)), + }, () => option.children)), ), ...(caption ? [h('div', { class: 'caption', - }, [caption])] : []), + }, caption)] : []), ]); }, }); diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue index a1ee6367a0da172da6ab020e82e0381ac64bba15..eaa134df25c0e91a6c454d2a99cedd91aecbc5a6 100644 --- a/packages/frontend/src/components/MkRange.vue +++ b/packages/frontend/src/components/MkRange.vue @@ -17,7 +17,7 @@ </template> <script lang="ts" setup> -import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, watch } from 'vue'; +import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, watch, shallowRef } from 'vue'; import * as os from '@/os'; const props = withDefaults(defineProps<{ @@ -39,8 +39,8 @@ const emit = defineEmits<{ (ev: 'update:modelValue', value: number): void; }>(); -const containerEl = ref<HTMLElement>(); -const thumbEl = ref<HTMLElement>(); +const containerEl = shallowRef<HTMLElement>(); +const thumbEl = shallowRef<HTMLElement>(); const rawValue = ref((props.modelValue - props.min) / (props.max - props.min)); const steppedRawValue = computed(() => { diff --git a/packages/frontend/src/components/MkReactedUsersDialog.vue b/packages/frontend/src/components/MkReactedUsersDialog.vue index 1506e24ce885ed1dc23a5a9ba633207b4e01d079..0c0cc36692ead8642e3757216721e0812e432845 100644 --- a/packages/frontend/src/components/MkReactedUsersDialog.vue +++ b/packages/frontend/src/components/MkReactedUsersDialog.vue @@ -6,7 +6,7 @@ @close="dialog.close()" @closed="emit('closed')" > - <template #header>{{ i18n.ts.reactions }}</template> + <template #header>{{ i18n.ts.reactionsList }}</template> <MkSpacer :margin-min="20" :margin-max="28"> <div v-if="note" class="_gaps"> @@ -21,7 +21,7 @@ <span style="margin-left: 4px;">{{ note.reactions[reaction] }}</span> </button> </div> - <MkA v-for="user in users" :key="user.id" :to="userPage(user)"> + <MkA v-for="user in users" :key="user.id" :to="userPage(user)" @click="dialog.close()"> <MkUserCardMini :user="user" :with-chart="false"/> </MkA> </template> diff --git a/packages/frontend/src/components/MkReactionsViewer.details.vue b/packages/frontend/src/components/MkReactionsViewer.details.vue index b4210be91175f9c4b1631e0503ce979aebb2ddd6..f5e611c62a08db711dde8c3863fa70ee8d61c734 100644 --- a/packages/frontend/src/components/MkReactionsViewer.details.vue +++ b/packages/frontend/src/components/MkReactionsViewer.details.vue @@ -10,7 +10,7 @@ <MkAvatar :class="$style.avatar" :user="u"/> <MkUserName :user="u" :nowrap="true"/> </div> - <div v-if="users.length > 10">+{{ count - 10 }}</div> + <div v-if="users.length > 10" :class="$style.more">+{{ count - 10 }}</div> </div> </div> </MkTooltip> @@ -50,7 +50,9 @@ function getReactionName(reaction: string): string { .reaction { max-width: 100px; + padding-right: 10px; text-align: center; + border-right: solid 0.5px var(--divider); } .reactionIcon { @@ -66,25 +68,20 @@ function getReactionName(reaction: string): string { } .users { + contain: content; flex: 1; min-width: 0; + margin: -4px 14px 0 10px; font-size: 0.95em; - border-left: solid 0.5px var(--divider); - padding-left: 10px; - margin-left: 10px; - margin-right: 14px; text-align: left; } .user { line-height: 24px; + padding-top: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - - &:not(:last-child) { - margin-bottom: 3px; - } } .avatar { @@ -92,4 +89,8 @@ function getReactionName(reaction: string): string { height: 24px; margin-right: 3px; } + +.more { + padding-top: 4px; +} </style> diff --git a/packages/frontend/src/components/MkRenotedUsersDialog.vue b/packages/frontend/src/components/MkRenotedUsersDialog.vue new file mode 100644 index 0000000000000000000000000000000000000000..56025535f184ed989884e452f4c5b0452df923d2 --- /dev/null +++ b/packages/frontend/src/components/MkRenotedUsersDialog.vue @@ -0,0 +1,65 @@ +<template> +<MkModalWindow + ref="dialog" + :width="400" + :height="450" + @close="dialog.close()" + @closed="emit('closed')" +> + <template #header>{{ i18n.ts.renotesList }}</template> + + <MkSpacer :margin-min="20" :margin-max="28"> + <div v-if="renotes" class="_gaps"> + <div v-if="renotes.length === 0" class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ i18n.ts.nothing }}</div> + </div> + <template v-else> + <MkA v-for="user in users" :key="user.id" :to="userPage(user)" @click="dialog.close()"> + <MkUserCardMini :user="user" :with-chart="false"/> + </MkA> + </template> + </div> + <div v-else> + <MkLoading/> + </div> + </MkSpacer> +</MkModalWindow> +</template> + +<script lang="ts" setup> +import { onMounted } from 'vue'; +import * as misskey from 'misskey-js'; +import MkModalWindow from '@/components/MkModalWindow.vue'; +import MkUserCardMini from '@/components/MkUserCardMini.vue'; +import { userPage } from '@/filters/user'; +import { i18n } from '@/i18n'; +import * as os from '@/os'; + +const emit = defineEmits<{ + (ev: 'closed'): void, +}>(); + +const props = defineProps<{ + noteId: misskey.entities.Note['id']; +}>(); + +const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>(); + +let note = $ref<misskey.entities.Note>(); +let renotes = $ref(); +let users = $ref(); + +onMounted(async () => { + const res = await os.api('notes/renotes', { + noteId: props.noteId, + limit: 30, + }); + + renotes = res; + users = res.map(x => x.user); +}); +</script> + +<style lang="scss" module> +</style> diff --git a/packages/frontend/src/components/MkRetentionHeatmap.vue b/packages/frontend/src/components/MkRetentionHeatmap.vue index 85c009f746014a858e2097fc10ef328d65ae03c7..f33f68cab7231d009a2eb593833a574d78a9159d 100644 --- a/packages/frontend/src/components/MkRetentionHeatmap.vue +++ b/packages/frontend/src/components/MkRetentionHeatmap.vue @@ -44,7 +44,13 @@ async function renderChart() { const data = []; for (const record of raw) { - let i = 0; + data.push({ + x: 0, + y: record.createdAt, + v: record.users, + }); + + let i = 1; for (const date of Object.keys(record.data).sort((a, b) => new Date(a).getTime() - new Date(b).getTime())) { data.push({ x: i, @@ -61,8 +67,14 @@ async function renderChart() { const color = defaultStore.state.darkMode ? '#b4e900' : '#86b300'; - // 視覚上ã®åˆ†ã‹ã‚Šã‚„ã™ã•ã®ãŸã‚上ã‹ã‚‰æœ€ã‚‚大ãã„3ã¤ã®å€¤ã®å¹³å‡ã‚’最大値ã¨ã™ã‚‹ - const max = raw.map(x => x.users).slice().sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / 3; + const getYYYYMMDD = (date: Date) => { + const y = date.getFullYear().toString().padStart(2, '0'); + const m = (date.getMonth() + 1).toString().padStart(2, '0'); + const d = date.getDate().toString().padStart(2, '0'); + return `${y}/${m}/${d}`; + }; + + const max = (createdAt: string) => raw.find(x => x.createdAt === createdAt)!.users; const marginEachCell = 12; @@ -78,7 +90,7 @@ async function renderChart() { borderRadius: 3, backgroundColor(c) { const value = c.dataset.data[c.dataIndex].v; - const a = value / max; + const a = value / max(c.dataset.data[c.dataIndex].y); return alpha(color, a); }, fill: true, @@ -115,7 +127,7 @@ async function renderChart() { maxRotation: 0, autoSkipPadding: 0, autoSkip: false, - callback: (value, index, values) => value + 1, + callback: (value, index, values) => value, }, }, y: { @@ -150,11 +162,11 @@ async function renderChart() { callbacks: { title(context) { const v = context[0].dataset.data[context[0].dataIndex]; - return v.d; + return getYYYYMMDD(new Date(new Date(v.y).getTime() + (v.x * 86400000))); }, label(context) { const v = context.dataset.data[context.dataIndex]; - return ['Active: ' + v.v]; + return [`Active: ${v.v} (${Math.round((v.v / max(v.y)) * 100)}%)`]; }, }, //mode: 'index', diff --git a/packages/frontend/src/components/MkSample.vue b/packages/frontend/src/components/MkSample.vue index 7a3bc208880d8e5f261c1eb8ed1658222aa3704f..922b862b47020b7ed2e07884204e21759d450302 100644 --- a/packages/frontend/src/components/MkSample.vue +++ b/packages/frontend/src/components/MkSample.vue @@ -87,7 +87,7 @@ export default defineComponent({ }, async openDrive() { - os.selectDriveFile(); + os.selectDriveFile(false); }, async selectUser() { diff --git a/packages/frontend/src/components/MkSignup.vue b/packages/frontend/src/components/MkSignup.vue deleted file mode 100644 index 30279148f80063e2f034fabd5a76100ae2bd9251..0000000000000000000000000000000000000000 --- a/packages/frontend/src/components/MkSignup.vue +++ /dev/null @@ -1,263 +0,0 @@ -<template> -<form class="qlvuhzng _gaps_m" autocomplete="new-password" @submit.prevent="onSubmit"> - <MkInput v-if="instance.disableRegistration" v-model="invitationCode" type="text" :spellcheck="false" required> - <template #label>{{ i18n.ts.invitationCode }}</template> - <template #prefix><i class="ti ti-key"></i></template> - </MkInput> - <MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-signup-username @update:model-value="onChangeUsername"> - <template #label>{{ i18n.ts.username }} <div v-tooltip:dialog="i18n.ts.usernameInfo" class="_button _help"><i class="ti ti-question-circle"></i></div></template> - <template #prefix>@</template> - <template #suffix>@{{ host }}</template> - <template #caption> - <div><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.cannotBeChangedLater }}</div> - <span v-if="usernameState === 'wait'" style="color:#999"><MkLoading :em="true"/> {{ i18n.ts.checking }}</span> - <span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span> - <span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span> - <span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span> - <span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.usernameInvalidFormat }}</span> - <span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooShort }}</span> - <span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooLong }}</span> - </template> - </MkInput> - <MkInput v-if="instance.emailRequiredForSignup" v-model="email" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:model-value="onChangeEmail"> - <template #label>{{ i18n.ts.emailAddress }} <div v-tooltip:dialog="i18n.ts._signup.emailAddressInfo" class="_button _help"><i class="ti ti-question-circle"></i></div></template> - <template #prefix><i class="ti ti-mail"></i></template> - <template #caption> - <span v-if="emailState === 'wait'" style="color:#999"><MkLoading :em="true"/> {{ i18n.ts.checking }}</span> - <span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span> - <span v-else-if="emailState === 'unavailable:used'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.used }}</span> - <span v-else-if="emailState === 'unavailable:format'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.format }}</span> - <span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.disposable }}</span> - <span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.mx }}</span> - <span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.smtp }}</span> - <span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span> - <span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span> - </template> - </MkInput> - <MkInput v-model="password" type="password" autocomplete="new-password" required data-cy-signup-password @update:model-value="onChangePassword"> - <template #label>{{ i18n.ts.password }}</template> - <template #prefix><i class="ti ti-lock"></i></template> - <template #caption> - <span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.weakPassword }}</span> - <span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.normalPassword }}</span> - <span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.strongPassword }}</span> - </template> - </MkInput> - <MkInput v-model="retypedPassword" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:model-value="onChangePasswordRetype"> - <template #label>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template> - <template #prefix><i class="ti ti-lock"></i></template> - <template #caption> - <span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.passwordMatched }}</span> - <span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.passwordNotMatched }}</span> - </template> - </MkInput> - <MkSwitch v-model="ToSAgreement" class="tou"> - <template #label>{{ i18n.ts.agreeBelow }}</template> - </MkSwitch> - <ul style="margin: 0; padding-left: 2em;"> - <li v-if="instance.tosUrl"><a :href="instance.tosUrl" class="_link" target="_blank">{{ i18n.ts.tos }}</a></li> - <li><a href="https://misskey-hub.net/docs/notes.html" class="_link" target="_blank">{{ i18n.ts.basicNotesBeforeCreateAccount }}</a></li> - </ul> - <MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/> - <MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/> - <MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" class="captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/> - <MkButton type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ i18n.ts.start }}</MkButton> -</form> -</template> - -<script lang="ts" setup> -import { } from 'vue'; -import getPasswordStrength from 'syuilo-password-strength'; -import { toUnicode } from 'punycode/'; -import MkButton from './MkButton.vue'; -import MkInput from './MkInput.vue'; -import MkSwitch from './MkSwitch.vue'; -import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue'; -import * as config from '@/config'; -import * as os from '@/os'; -import { login } from '@/account'; -import { instance } from '@/instance'; -import { i18n } from '@/i18n'; - -const props = withDefaults(defineProps<{ - autoSet?: boolean; -}>(), { - autoSet: false, -}); - -const emit = defineEmits<{ - (ev: 'signup', user: Record<string, any>): void; - (ev: 'signupEmailPending'): void; -}>(); - -const host = toUnicode(config.host); - -let hcaptcha = $ref<Captcha | undefined>(); -let recaptcha = $ref<Captcha | undefined>(); -let turnstile = $ref<Captcha | undefined>(); - -let username: string = $ref(''); -let password: string = $ref(''); -let retypedPassword: string = $ref(''); -let invitationCode: string = $ref(''); -let email = $ref(''); -let usernameState: null | 'wait' | 'ok' | 'unavailable' | 'error' | 'invalid-format' | 'min-range' | 'max-range' = $ref(null); -let emailState: null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error' = $ref(null); -let passwordStrength: '' | 'low' | 'medium' | 'high' = $ref(''); -let passwordRetypeState: null | 'match' | 'not-match' = $ref(null); -let submitting: boolean = $ref(false); -let ToSAgreement: boolean = $ref(false); -let hCaptchaResponse = $ref(null); -let reCaptchaResponse = $ref(null); -let turnstileResponse = $ref(null); -let usernameAbortController: null | AbortController = $ref(null); -let emailAbortController: null | AbortController = $ref(null); - -const shouldDisableSubmitting = $computed((): boolean => { - return submitting || - instance.tosUrl && !ToSAgreement || - instance.enableHcaptcha && !hCaptchaResponse || - instance.enableRecaptcha && !reCaptchaResponse || - instance.enableTurnstile && !turnstileResponse || - instance.emailRequiredForSignup && emailState !== 'ok' || - usernameState !== 'ok' || - passwordRetypeState !== 'match'; -}); - -function onChangeUsername(): void { - if (username === '') { - usernameState = null; - return; - } - - { - const err = - !username.match(/^[a-zA-Z0-9_]+$/) ? 'invalid-format' : - username.length < 1 ? 'min-range' : - username.length > 20 ? 'max-range' : - null; - - if (err) { - usernameState = err; - return; - } - } - - if (usernameAbortController != null) { - usernameAbortController.abort(); - } - usernameState = 'wait'; - usernameAbortController = new AbortController(); - - os.api('username/available', { - username, - }, undefined, usernameAbortController.signal).then(result => { - usernameState = result.available ? 'ok' : 'unavailable'; - }).catch((err) => { - if (err.name !== 'AbortError') { - usernameState = 'error'; - } - }); -} - -function onChangeEmail(): void { - if (email === '') { - emailState = null; - return; - } - - if (emailAbortController != null) { - emailAbortController.abort(); - } - emailState = 'wait'; - emailAbortController = new AbortController(); - - os.api('email-address/available', { - emailAddress: email, - }, undefined, emailAbortController.signal).then(result => { - emailState = result.available ? 'ok' : - result.reason === 'used' ? 'unavailable:used' : - result.reason === 'format' ? 'unavailable:format' : - result.reason === 'disposable' ? 'unavailable:disposable' : - result.reason === 'mx' ? 'unavailable:mx' : - result.reason === 'smtp' ? 'unavailable:smtp' : - 'unavailable'; - }).catch((err) => { - if (err.name !== 'AbortError') { - emailState = 'error'; - } - }); -} - -function onChangePassword(): void { - if (password === '') { - passwordStrength = ''; - return; - } - - const strength = getPasswordStrength(password); - passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low'; -} - -function onChangePasswordRetype(): void { - if (retypedPassword === '') { - passwordRetypeState = null; - return; - } - - passwordRetypeState = password === retypedPassword ? 'match' : 'not-match'; -} - -async function onSubmit(): Promise<void> { - if (submitting) return; - submitting = true; - - try { - await os.api('signup', { - username, - password, - emailAddress: email, - invitationCode, - 'hcaptcha-response': hCaptchaResponse, - 'g-recaptcha-response': reCaptchaResponse, - 'turnstile-response': turnstileResponse, - }); - if (instance.emailRequiredForSignup) { - os.alert({ - type: 'success', - title: i18n.ts._signup.almostThere, - text: i18n.t('_signup.emailSent', { email }), - }); - emit('signupEmailPending'); - } else { - const res = await os.api('signin', { - username, - password, - }); - emit('signup', res); - - if (props.autoSet) { - return login(res.i); - } - } - } catch { - submitting = false; - hcaptcha?.reset?.(); - recaptcha?.reset?.(); - turnstile?.reset?.(); - - os.alert({ - type: 'error', - text: i18n.ts.somethingHappened, - }); - } -} -</script> - -<style lang="scss" scoped> -.qlvuhzng { - .captcha { - margin: 16px 0; - } -} -</style> diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue new file mode 100644 index 0000000000000000000000000000000000000000..0e8bdb321e243aa093c3df0109ee47de4a413939 --- /dev/null +++ b/packages/frontend/src/components/MkSignupDialog.form.vue @@ -0,0 +1,272 @@ +<template> +<div> + <div :class="$style.banner"> + <i class="ti ti-user-edit"></i> + </div> + <MkSpacer :margin-min="20" :margin-max="32"> + <form class="_gaps_m" autocomplete="new-password" @submit.prevent="onSubmit"> + <MkInput v-if="instance.disableRegistration" v-model="invitationCode" type="text" :spellcheck="false" required> + <template #label>{{ i18n.ts.invitationCode }}</template> + <template #prefix><i class="ti ti-key"></i></template> + </MkInput> + <MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-signup-username @update:model-value="onChangeUsername"> + <template #label>{{ i18n.ts.username }} <div v-tooltip:dialog="i18n.ts.usernameInfo" class="_button _help"><i class="ti ti-help-circle"></i></div></template> + <template #prefix>@</template> + <template #suffix>@{{ host }}</template> + <template #caption> + <div><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.cannotBeChangedLater }}</div> + <span v-if="usernameState === 'wait'" style="color:#999"><MkLoading :em="true"/> {{ i18n.ts.checking }}</span> + <span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span> + <span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span> + <span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span> + <span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.usernameInvalidFormat }}</span> + <span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooShort }}</span> + <span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooLong }}</span> + </template> + </MkInput> + <MkInput v-if="instance.emailRequiredForSignup" v-model="email" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:model-value="onChangeEmail"> + <template #label>{{ i18n.ts.emailAddress }} <div v-tooltip:dialog="i18n.ts._signup.emailAddressInfo" class="_button _help"><i class="ti ti-help-circle"></i></div></template> + <template #prefix><i class="ti ti-mail"></i></template> + <template #caption> + <span v-if="emailState === 'wait'" style="color:#999"><MkLoading :em="true"/> {{ i18n.ts.checking }}</span> + <span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span> + <span v-else-if="emailState === 'unavailable:used'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.used }}</span> + <span v-else-if="emailState === 'unavailable:format'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.format }}</span> + <span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.disposable }}</span> + <span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.mx }}</span> + <span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.smtp }}</span> + <span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span> + <span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span> + </template> + </MkInput> + <MkInput v-model="password" type="password" autocomplete="new-password" required data-cy-signup-password @update:model-value="onChangePassword"> + <template #label>{{ i18n.ts.password }}</template> + <template #prefix><i class="ti ti-lock"></i></template> + <template #caption> + <span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.weakPassword }}</span> + <span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.normalPassword }}</span> + <span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.strongPassword }}</span> + </template> + </MkInput> + <MkInput v-model="retypedPassword" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:model-value="onChangePasswordRetype"> + <template #label>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template> + <template #prefix><i class="ti ti-lock"></i></template> + <template #caption> + <span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.passwordMatched }}</span> + <span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.passwordNotMatched }}</span> + </template> + </MkInput> + <MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :class="$style.captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/> + <MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/> + <MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/> + <MkButton type="submit" :disabled="shouldDisableSubmitting" large gradate rounded data-cy-signup-submit style="margin: 0 auto;"> + <template v-if="submitting"> + <MkLoading :em="true" :colored="false"/> + </template> + <template v-else>{{ i18n.ts.start }}</template> + </MkButton> + </form> + </MkSpacer> +</div> +</template> + +<script lang="ts" setup> +import { } from 'vue'; +import getPasswordStrength from 'syuilo-password-strength'; +import { toUnicode } from 'punycode/'; +import MkButton from './MkButton.vue'; +import MkInput from './MkInput.vue'; +import MkSwitch from './MkSwitch.vue'; +import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue'; +import * as config from '@/config'; +import * as os from '@/os'; +import { login } from '@/account'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; + +const props = withDefaults(defineProps<{ + autoSet?: boolean; +}>(), { + autoSet: false, +}); + +const emit = defineEmits<{ + (ev: 'signup', user: Record<string, any>): void; + (ev: 'signupEmailPending'): void; +}>(); + +const host = toUnicode(config.host); + +let hcaptcha = $ref<Captcha | undefined>(); +let recaptcha = $ref<Captcha | undefined>(); +let turnstile = $ref<Captcha | undefined>(); + +let username: string = $ref(''); +let password: string = $ref(''); +let retypedPassword: string = $ref(''); +let invitationCode: string = $ref(''); +let email = $ref(''); +let usernameState: null | 'wait' | 'ok' | 'unavailable' | 'error' | 'invalid-format' | 'min-range' | 'max-range' = $ref(null); +let emailState: null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error' = $ref(null); +let passwordStrength: '' | 'low' | 'medium' | 'high' = $ref(''); +let passwordRetypeState: null | 'match' | 'not-match' = $ref(null); +let submitting: boolean = $ref(false); +let hCaptchaResponse = $ref(null); +let reCaptchaResponse = $ref(null); +let turnstileResponse = $ref(null); +let usernameAbortController: null | AbortController = $ref(null); +let emailAbortController: null | AbortController = $ref(null); + +const shouldDisableSubmitting = $computed((): boolean => { + return submitting || + instance.enableHcaptcha && !hCaptchaResponse || + instance.enableRecaptcha && !reCaptchaResponse || + instance.enableTurnstile && !turnstileResponse || + instance.emailRequiredForSignup && emailState !== 'ok' || + usernameState !== 'ok' || + passwordRetypeState !== 'match'; +}); + +function onChangeUsername(): void { + if (username === '') { + usernameState = null; + return; + } + + { + const err = + !username.match(/^[a-zA-Z0-9_]+$/) ? 'invalid-format' : + username.length < 1 ? 'min-range' : + username.length > 20 ? 'max-range' : + null; + + if (err) { + usernameState = err; + return; + } + } + + if (usernameAbortController != null) { + usernameAbortController.abort(); + } + usernameState = 'wait'; + usernameAbortController = new AbortController(); + + os.api('username/available', { + username, + }, undefined, usernameAbortController.signal).then(result => { + usernameState = result.available ? 'ok' : 'unavailable'; + }).catch((err) => { + if (err.name !== 'AbortError') { + usernameState = 'error'; + } + }); +} + +function onChangeEmail(): void { + if (email === '') { + emailState = null; + return; + } + + if (emailAbortController != null) { + emailAbortController.abort(); + } + emailState = 'wait'; + emailAbortController = new AbortController(); + + os.api('email-address/available', { + emailAddress: email, + }, undefined, emailAbortController.signal).then(result => { + emailState = result.available ? 'ok' : + result.reason === 'used' ? 'unavailable:used' : + result.reason === 'format' ? 'unavailable:format' : + result.reason === 'disposable' ? 'unavailable:disposable' : + result.reason === 'mx' ? 'unavailable:mx' : + result.reason === 'smtp' ? 'unavailable:smtp' : + 'unavailable'; + }).catch((err) => { + if (err.name !== 'AbortError') { + emailState = 'error'; + } + }); +} + +function onChangePassword(): void { + if (password === '') { + passwordStrength = ''; + return; + } + + const strength = getPasswordStrength(password); + passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low'; +} + +function onChangePasswordRetype(): void { + if (retypedPassword === '') { + passwordRetypeState = null; + return; + } + + passwordRetypeState = password === retypedPassword ? 'match' : 'not-match'; +} + +async function onSubmit(): Promise<void> { + if (submitting) return; + submitting = true; + + try { + await os.api('signup', { + username, + password, + emailAddress: email, + invitationCode, + 'hcaptcha-response': hCaptchaResponse, + 'g-recaptcha-response': reCaptchaResponse, + 'turnstile-response': turnstileResponse, + }); + if (instance.emailRequiredForSignup) { + os.alert({ + type: 'success', + title: i18n.ts._signup.almostThere, + text: i18n.t('_signup.emailSent', { email }), + }); + emit('signupEmailPending'); + } else { + const res = await os.api('signin', { + username, + password, + }); + emit('signup', res); + + if (props.autoSet) { + return login(res.i); + } + } + } catch { + submitting = false; + hcaptcha?.reset?.(); + recaptcha?.reset?.(); + turnstile?.reset?.(); + + os.alert({ + type: 'error', + text: i18n.ts.somethingHappened, + }); + } +} +</script> + +<style lang="scss" module> +.banner { + padding: 16px; + text-align: center; + font-size: 26px; + background-color: var(--accentedBg); + color: var(--accent); +} + +.captcha { + margin: 16px 0; +} +</style> diff --git a/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts new file mode 100644 index 0000000000000000000000000000000000000000..2d9545573002d5b0fb06eb4cf8eda35b8f124c8a --- /dev/null +++ b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts @@ -0,0 +1,94 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { expect } from '@storybook/jest'; +import { userEvent, waitFor, within } from '@storybook/testing-library'; +import { StoryObj } from '@storybook/vue3'; +import { onBeforeUnmount } from 'vue'; +import MkSignupServerRules from './MkSignupDialog.rules.vue'; +import { i18n } from '@/i18n'; +import { instance } from '@/instance'; +export const Empty = { + render(args) { + return { + components: { + MkSignupServerRules, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkSignupServerRules v-bind="props" />', + }; + }, + async play({ canvasElement }) { + const canvas = within(canvasElement); + const groups = await canvas.findAllByRole('group'); + const buttons = await canvas.findAllByRole('button'); + for (const group of groups) { + if (group.ariaExpanded === 'true') { + continue; + } + const button = await within(group).findByRole('button'); + userEvent.click(button); + await waitFor(() => expect(group).toHaveAttribute('aria-expanded', 'true')); + } + const labels = await canvas.findAllByText(i18n.ts.agree); + for (const label of labels) { + expect(buttons.at(-1)).toBeDisabled(); + await waitFor(() => userEvent.click(label)); + } + expect(buttons.at(-1)).toBeEnabled(); + }, + args: { + serverRules: [], + tosUrl: null, + }, + decorators: [ + (_, context) => ({ + setup() { + instance.serverRules = context.args.serverRules; + instance.tosUrl = context.args.tosUrl; + onBeforeUnmount(() => { + // FIXME: 呼ã³å‡ºã•ã‚Œãªã„ + instance.serverRules = []; + instance.tosUrl = null; + }); + }, + template: '<story/>', + }), + ], + parameters: { + layout: 'centered', + }, +} satisfies StoryObj<typeof MkSignupServerRules>; +export const ServerRulesOnly = { + ...Empty, + args: { + ...Empty.args, + serverRules: [ + 'ルール', + ], + }, +} satisfies StoryObj<typeof MkSignupServerRules>; +export const TOSOnly = { + ...Empty, + args: { + ...Empty.args, + tosUrl: 'https://example.com/tos', + }, +} satisfies StoryObj<typeof MkSignupServerRules>; +export const ServerRulesAndTOS = { + ...Empty, + args: { + ...Empty.args, + serverRules: ServerRulesOnly.args.serverRules, + tosUrl: TOSOnly.args.tosUrl, + }, +} satisfies StoryObj<typeof MkSignupServerRules>; diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue new file mode 100644 index 0000000000000000000000000000000000000000..6da81c3bcb081f65b6af6fb0bf4a1776c443efa7 --- /dev/null +++ b/packages/frontend/src/components/MkSignupDialog.rules.vue @@ -0,0 +1,124 @@ +<template> +<div> + <div :class="$style.banner"> + <i class="ti ti-checklist"></i> + </div> + <MkSpacer :margin-min="20" :margin-max="28"> + <div class="_gaps_m"> + <div v-if="instance.disableRegistration"> + <MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo> + </div> + + <div style="text-align: center;">{{ i18n.ts.pleaseConfirmBelowBeforeSignup }}</div> + + <MkFolder v-if="availableServerRules" :default-open="true"> + <template #label>{{ i18n.ts.serverRules }}</template> + <template #suffix><i v-if="agreeServerRules" class="ti ti-check" style="color: var(--success)"></i></template> + + <ol class="_gaps_s" :class="$style.rules"> + <li v-for="item in instance.serverRules" :class="$style.rule"><div :class="$style.ruleText" v-html="item"></div></li> + </ol> + + <MkSwitch v-model="agreeServerRules" style="margin-top: 16px;">{{ i18n.ts.agree }}</MkSwitch> + </MkFolder> + + <MkFolder v-if="availableTos" :default-open="true"> + <template #label>{{ i18n.ts.termsOfService }}</template> + <template #suffix><i v-if="agreeTos" class="ti ti-check" style="color: var(--success)"></i></template> + + <a :href="instance.tosUrl" class="_link" target="_blank">{{ i18n.ts.termsOfService }} <i class="ti ti-external-link"></i></a> + + <MkSwitch v-model="agreeTos" style="margin-top: 16px;">{{ i18n.ts.agree }}</MkSwitch> + </MkFolder> + + <MkFolder :default-open="true"> + <template #label>{{ i18n.ts.basicNotesBeforeCreateAccount }}</template> + <template #suffix><i v-if="agreeNote" class="ti ti-check" style="color: var(--success)"></i></template> + + <a href="https://misskey-hub.net/docs/notes.html" class="_link" target="_blank">{{ i18n.ts.basicNotesBeforeCreateAccount }} <i class="ti ti-external-link"></i></a> + + <MkSwitch v-model="agreeNote" style="margin-top: 16px;" data-cy-signup-rules-notes-agree>{{ i18n.ts.agree }}</MkSwitch> + </MkFolder> + + <div v-if="!agreed" style="text-align: center;">{{ i18n.ts.pleaseAgreeAllToContinue }}</div> + + <div class="_buttonsCenter"> + <MkButton inline rounded @click="emit('cancel')">{{ i18n.ts.cancel }}</MkButton> + <MkButton inline primary rounded gradate :disabled="!agreed" data-cy-signup-rules-continue @click="emit('done')">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + </div> + </div> + </MkSpacer> +</div> +</template> + +<script lang="ts" setup> +import { computed, ref } from 'vue'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; +import MkButton from '@/components/MkButton.vue'; +import MkFolder from '@/components/MkFolder.vue'; +import MkSwitch from '@/components/MkSwitch.vue'; +import MkInfo from '@/components/MkInfo.vue'; + +const availableServerRules = instance.serverRules.length > 0; +const availableTos = instance.tosUrl != null; + +const agreeServerRules = ref(false); +const agreeTos = ref(false); +const agreeNote = ref(false); + +const agreed = computed(() => { + return (!availableServerRules || agreeServerRules.value) && (!availableTos || agreeTos.value) && agreeNote.value; +}); + +const emit = defineEmits<{ + (ev: 'cancel'): void; + (ev: 'done'): void; +}>(); +</script> + +<style lang="scss" module> +.banner { + padding: 16px; + text-align: center; + font-size: 26px; + background-color: var(--accentedBg); + color: var(--accent); +} + +.rules { + counter-reset: item; + list-style: none; + padding: 0; + margin: 0; +} + +.rule { + display: flex; + gap: 8px; + word-break: break-word; + + &::before { + flex-shrink: 0; + display: flex; + position: sticky; + top: calc(var(--stickyTop, 0px) + 8px); + counter-increment: item; + content: counter(item); + width: 32px; + height: 32px; + line-height: 32px; + background-color: var(--accentedBg); + color: var(--accent); + font-size: 13px; + font-weight: bold; + align-items: center; + justify-content: center; + border-radius: 999px; + } +} + +.ruleText { + padding-top: 6px; +} +</style> diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue index 790c1e94df43d7ad5f9d0321952e935b3aa6a2b4..17f8b864254a7e7782ee32953153fabf90376bba 100644 --- a/packages/frontend/src/components/MkSignupDialog.vue +++ b/packages/frontend/src/components/MkSignupDialog.vue @@ -1,24 +1,40 @@ <template> <MkModalWindow ref="dialog" - :width="366" - :height="500" + :width="500" + :height="600" @close="dialog.close()" @closed="$emit('closed')" > <template #header>{{ i18n.ts.signup }}</template> - <MkSpacer :margin-min="20" :margin-max="28"> - <XSignup :auto-set="autoSet" @signup="onSignup" @signup-email-pending="onSignupEmailPending"/> - </MkSpacer> + <div style="overflow-x: clip;"> + <Transition + mode="out-in" + :enter-active-class="$style.transition_x_enterActive" + :leave-active-class="$style.transition_x_leaveActive" + :enter-from-class="$style.transition_x_enterFrom" + :leave-to-class="$style.transition_x_leaveTo" + > + <template v-if="!isAcceptedServerRule"> + <XServerRules @done="isAcceptedServerRule = true" @cancel="dialog.close()"/> + </template> + <template v-else> + <XSignup :auto-set="autoSet" @signup="onSignup" @signup-email-pending="onSignupEmailPending"/> + </template> + </Transition> + </div> </MkModalWindow> </template> <script lang="ts" setup> import { } from 'vue'; -import XSignup from '@/components/MkSignup.vue'; +import { $ref } from 'vue/macros'; +import XSignup from '@/components/MkSignupDialog.form.vue'; +import XServerRules from '@/components/MkSignupDialog.rules.vue'; import MkModalWindow from '@/components/MkModalWindow.vue'; import { i18n } from '@/i18n'; +import { instance } from '@/instance'; const props = withDefaults(defineProps<{ autoSet?: boolean; @@ -33,6 +49,8 @@ const emit = defineEmits<{ const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>(); +const isAcceptedServerRule = $ref(false); + function onSignup(res) { emit('done', res); dialog.close(); @@ -42,3 +60,18 @@ function onSignupEmailPending() { dialog.close(); } </script> + +<style lang="scss" 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); +} +</style> diff --git a/packages/frontend/src/components/MkSwitch.vue b/packages/frontend/src/components/MkSwitch.vue index 8bb8637dda7e4c3e1c734cfab6f17e0d7c0d01a3..d9f6716f92d6039a2f2c8ce2dee06d3f916f8701 100644 --- a/packages/frontend/src/components/MkSwitch.vue +++ b/packages/frontend/src/components/MkSwitch.vue @@ -9,7 +9,7 @@ :disabled="disabled" @keydown.enter="toggle" > - <span ref="button" v-tooltip="checked ? i18n.ts.itsOn : i18n.ts.itsOff" class="button" @click.prevent="toggle"> + <span ref="button" v-tooltip="checked ? i18n.ts.itsOn : i18n.ts.itsOff" class="button" data-cy-switch-toggle @click.prevent="toggle"> <div class="knob"></div> </span> <span class="label"> diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue index 5086c1b319502bd72da15e6a1c00c12e94d968a4..6349ada65a376e4c3f64ffc4bf3808f9c49a39b8 100644 --- a/packages/frontend/src/components/MkUserInfo.vue +++ b/packages/frontend/src/components/MkUserInfo.vue @@ -1,30 +1,30 @@ <template> -<div class="_panel vjnjpkug"> - <div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div> - <MkAvatar class="avatar" :user="user" indicator/> - <div class="title"> - <MkA class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA> - <p class="username"><MkAcct :user="user"/></p> +<div class="_panel" :class="$style.root"> + <div :class="$style.banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div> + <MkAvatar :class="$style.avatar" :user="user" indicator/> + <div :class="$style.title"> + <MkA :class="$style.name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA> + <p :class="$style.username"><MkAcct :user="user"/></p> </div> - <span v-if="$i && $i.id !== user.id && user.isFollowed" class="followed">{{ i18n.ts.followsYou }}</span> - <div class="description"> + <span v-if="$i && $i.id !== user.id && user.isFollowed" :class="$style.followed">{{ i18n.ts.followsYou }}</span> + <div :class="$style.description"> <div v-if="user.description" class="mfm"> <Mfm :text="user.description" :author="user" :i="$i"/> </div> <span v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</span> </div> - <div class="status"> - <div> - <p>{{ i18n.ts.notes }}</p><span>{{ user.notesCount }}</span> + <div :class="$style.status"> + <div :class="$style.statusItem"> + <p :class="$style.statusItemLabel">{{ i18n.ts.notes }}</p><span :class="$style.statusItemValue">{{ user.notesCount }}</span> </div> - <div> - <p>{{ i18n.ts.following }}</p><span>{{ user.followingCount }}</span> + <div :class="$style.statusItem"> + <p :class="$style.statusItemLabel">{{ i18n.ts.following }}</p><span :class="$style.statusItemValue">{{ user.followingCount }}</span> </div> - <div> - <p>{{ i18n.ts.followers }}</p><span>{{ user.followersCount }}</span> + <div :class="$style.statusItem"> + <p :class="$style.statusItemLabel">{{ i18n.ts.followers }}</p><span :class="$style.statusItemValue">{{ user.followersCount }}</span> </div> </div> - <MkFollowButton v-if="$i && user.id != $i.id" class="koudoku-button" :user="user" mini/> + <MkFollowButton v-if="$i && user.id != $i.id" :class="$style.follow" :user="user" mini/> </div> </template> @@ -40,99 +40,99 @@ defineProps<{ }>(); </script> -<style lang="scss" scoped> -.vjnjpkug { +<style lang="scss" module> +.root { position: relative; +} + +.banner { + height: 84px; + background-color: rgba(0, 0, 0, 0.1); + background-size: cover; + background-position: center; +} + +.avatar { + display: block; + position: absolute; + top: 62px; + left: 13px; + z-index: 2; + width: 58px; + height: 58px; + border: solid 4px var(--panel); +} + +.title { + display: block; + padding: 10px 0 10px 88px; +} + +.name { + display: inline-block; + margin: 0; + font-weight: bold; + line-height: 16px; + word-break: break-all; +} + +.username { + display: block; + margin: 0; + line-height: 16px; + font-size: 0.8em; + color: var(--fg); + opacity: 0.7; +} + +.followed { + position: absolute; + top: 12px; + left: 12px; + padding: 4px 8px; + color: #fff; + background: rgba(0, 0, 0, 0.7); + font-size: 0.7em; + border-radius: 6px; +} + +.description { + padding: 16px; + font-size: 0.8em; + border-top: solid 0.5px var(--divider); +} + +.mfm { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.status { + padding: 10px 16px; + border-top: solid 0.5px var(--divider); +} + +.statusItem { + display: inline-block; + width: 33%; +} + +.statusItemLabel { + margin: 0; + font-size: 0.7em; + color: var(--fg); +} + +.statusItemValue { + font-size: 1em; + color: var(--accent); +} - > .banner { - height: 84px; - background-color: rgba(0, 0, 0, 0.1); - background-size: cover; - background-position: center; - } - - > .avatar { - display: block; - position: absolute; - top: 62px; - left: 13px; - z-index: 2; - width: 58px; - height: 58px; - border: solid 4px var(--panel); - } - - > .title { - display: block; - padding: 10px 0 10px 88px; - - > .name { - display: inline-block; - margin: 0; - font-weight: bold; - line-height: 16px; - word-break: break-all; - } - - > .username { - display: block; - margin: 0; - line-height: 16px; - font-size: 0.8em; - color: var(--fg); - opacity: 0.7; - } - } - - > .followed { - position: absolute; - top: 12px; - left: 12px; - padding: 4px 8px; - color: #fff; - background: rgba(0, 0, 0, 0.7); - font-size: 0.7em; - border-radius: 6px; - } - - > .description { - padding: 16px; - font-size: 0.8em; - border-top: solid 0.5px var(--divider); - - > .mfm { - display: -webkit-box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; - overflow: hidden; - } - } - - > .status { - padding: 10px 16px; - border-top: solid 0.5px var(--divider); - - > div { - display: inline-block; - width: 33%; - - > p { - margin: 0; - font-size: 0.7em; - color: var(--fg); - } - - > span { - font-size: 1em; - color: var(--accent); - } - } - } - - > .koudoku-button { - position: absolute; - top: 8px; - right: 8px; - } +.follow { + position: absolute; + top: 8px; + right: 8px; } </style> diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue index 51eb426e97d2d704a18044252c1a126b7e081a8e..3571ca84d9c27f5f41bf57961dcf762d7273cd1f 100644 --- a/packages/frontend/src/components/MkUserList.vue +++ b/packages/frontend/src/components/MkUserList.vue @@ -8,7 +8,7 @@ </template> <template #default="{ items }"> - <div class="efvhhmdq"> + <div :class="$style.root"> <MkUserInfo v-for="item in items" :key="item.id" class="user" :user="extractor(item)"/> </div> </template> @@ -29,8 +29,8 @@ const props = withDefaults(defineProps<{ }); </script> -<style lang="scss" scoped> -.efvhhmdq { +<style lang="scss" module> +.root { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); grid-gap: var(--margin); diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts new file mode 100644 index 0000000000000000000000000000000000000000..7d5a65f41a2f3a652ed92701a692527486819e83 --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts @@ -0,0 +1,51 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { rest } from 'msw'; +import { commonHandlers } from '../../.storybook/mocks'; +import { userDetailed } from '../../.storybook/fakes'; +import MkUserSetupDialog_Follow from './MkUserSetupDialog.Follow.vue'; +export const Default = { + render(args) { + return { + components: { + MkUserSetupDialog_Follow, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkUserSetupDialog_Follow v-bind="props" />', + }; + }, + args: { + + }, + parameters: { + layout: 'centered', + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/users', (req, res, ctx) => { + return res(ctx.json([ + userDetailed('44'), + userDetailed('49'), + ])); + }), + rest.post('/api/pinned-users', (req, res, ctx) => { + return res(ctx.json([ + userDetailed('44'), + userDetailed('49'), + ])); + }), + ], + }, + }, +} satisfies StoryObj<typeof MkUserSetupDialog_Follow>; diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue new file mode 100644 index 0000000000000000000000000000000000000000..b89e3e4c9d076fed5078ae2a62cb6e3f9737af66 --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue @@ -0,0 +1,63 @@ +<template> +<div class="_gaps"> + <div style="text-align: center;">{{ i18n.ts._initialAccountSetting.followUsers }}</div> + + <MkFolder :default-open="true"> + <template #label>{{ i18n.ts.recommended }}</template> + + <MkPagination :pagination="pinnedUsers"> + <template #default="{ items }"> + <div :class="$style.users"> + <XUser v-for="item in items" :key="item.id" :user="item"/> + </div> + </template> + </MkPagination> + </MkFolder> + + <MkFolder :default-open="true"> + <template #label>{{ i18n.ts.popularUsers }}</template> + + <MkPagination :pagination="popularUsers"> + <template #default="{ items }"> + <div :class="$style.users"> + <XUser v-for="item in items" :key="item.id" :user="item"/> + </div> + </template> + </MkPagination> + </MkFolder> +</div> +</template> + +<script lang="ts" setup> +import { computed, ref, watch } from 'vue'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; +import MkButton from '@/components/MkButton.vue'; +import MkFolder from '@/components/MkFolder.vue'; +import XUser from '@/components/MkUserSetupDialog.User.vue'; +import MkInfo from '@/components/MkInfo.vue'; +import * as os from '@/os'; +import { $i } from '@/account'; +import MkPagination from '@/components/MkPagination.vue'; + +const emit = defineEmits<{ + (ev: 'done'): void; +}>(); + +const pinnedUsers = { endpoint: 'pinned-users', noPaging: true }; + +const popularUsers = { endpoint: 'users', limit: 10, noPaging: true, params: { + state: 'alive', + origin: 'local', + sort: '+follower', +} }; +</script> + +<style lang="scss" module> +.users { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); + grid-gap: var(--margin); + justify-content: center; +} +</style> diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts new file mode 100644 index 0000000000000000000000000000000000000000..f4930aa26b0ba2adf535c31986df9b223daef9fc --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import MkUserSetupDialog_Profile from './MkUserSetupDialog.Profile.vue'; +export const Default = { + render(args) { + return { + components: { + MkUserSetupDialog_Profile, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkUserSetupDialog_Profile v-bind="props" />', + }; + }, + args: { + + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj<typeof MkUserSetupDialog_Profile>; diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue new file mode 100644 index 0000000000000000000000000000000000000000..adb8d4334965f1be82dbcc245ef797980a596130 --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue @@ -0,0 +1,101 @@ +<template> +<div class="_gaps"> + <MkInfo>{{ i18n.ts._initialAccountSetting.theseSettingsCanEditLater }}</MkInfo> + + <FormSlot> + <template #label>{{ i18n.ts.avatar }}</template> + <div v-adaptive-bg :class="$style.avatarSection" class="_panel"> + <MkAvatar :class="$style.avatar" :user="$i" @click="setAvatar"/> + <div style="margin-top: 16px;"> + <MkButton primary rounded inline @click="setAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton> + </div> + </div> + </FormSlot> + + <MkInput v-model="name" :max="30" manual-save data-cy-user-setup-user-name> + <template #label>{{ i18n.ts._profile.name }}</template> + </MkInput> + + <MkTextarea v-model="description" :max="500" tall manual-save data-cy-user-setup-user-description> + <template #label>{{ i18n.ts._profile.description }}</template> + </MkTextarea> + + <MkInfo>{{ i18n.ts._initialAccountSetting.youCanEditMoreSettingsInSettingsPageLater }}</MkInfo> +</div> +</template> + +<script lang="ts" setup> +import { computed, ref, watch } from 'vue'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; +import MkButton from '@/components/MkButton.vue'; +import MkInput from '@/components/MkInput.vue'; +import MkTextarea from '@/components/MkTextarea.vue'; +import FormSlot from '@/components/form/slot.vue'; +import MkInfo from '@/components/MkInfo.vue'; +import { chooseFileFromPc } from '@/scripts/select-file'; +import * as os from '@/os'; +import { $i } from '@/account'; + +const emit = defineEmits<{ + (ev: 'done'): void; +}>(); + +const name = ref(''); +const description = ref(''); + +watch(name, () => { + os.apiWithDialog('i/update', { + // 空文å—列をnullã«ã—ãŸã„ã®ã§??ã¯ä½¿ã†ãª + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + name: name.value || null, + }); +}); + +watch(description, () => { + os.apiWithDialog('i/update', { + // 空文å—列をnullã«ã—ãŸã„ã®ã§??ã¯ä½¿ã†ãª + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + description: description.value || null, + }); +}); + +function setAvatar(ev) { + chooseFileFromPc(false).then(async (files) => { + const file = files[0]; + + let originalOrCropped = file; + + const { canceled } = await os.confirm({ + type: 'question', + text: i18n.t('cropImageAsk'), + okText: i18n.ts.cropYes, + cancelText: i18n.ts.cropNo, + }); + + if (!canceled) { + originalOrCropped = await os.cropImage(file, { + aspectRatio: 1, + }); + } + + const i = await os.apiWithDialog('i/update', { + avatarId: originalOrCropped.id, + }); + $i.avatarId = i.avatarId; + $i.avatarUrl = i.avatarUrl; + }); +} +</script> + +<style lang="scss" module> +.avatarSection { + text-align: center; + padding: 20px; +} + +.avatar { + width: 100px; + height: 100px; +} +</style> diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts new file mode 100644 index 0000000000000000000000000000000000000000..7413f4884bd5d6c17c3a52d04f1c5db6c6da8ea9 --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts @@ -0,0 +1,32 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { userDetailed } from '../../.storybook/fakes'; +import MkUserSetupDialog_User from './MkUserSetupDialog.User.vue'; +export const Default = { + render(args) { + return { + components: { + MkUserSetupDialog_User, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkUserSetupDialog_User v-bind="props" />', + }; + }, + args: { + user: userDetailed(), + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj<typeof MkUserSetupDialog_User>; diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.vue b/packages/frontend/src/components/MkUserSetupDialog.User.vue new file mode 100644 index 0000000000000000000000000000000000000000..d66f34f1650b015bb78ddb8757ea4ad785a8f902 --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.User.vue @@ -0,0 +1,101 @@ +<template> +<div v-adaptive-bg class="_panel" style="position: relative;"> + <div :class="$style.banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div> + <MkAvatar :class="$style.avatar" :user="user" indicator/> + <div :class="$style.title"> + <div :class="$style.name"><MkUserName :user="user" :nowrap="false"/></div> + <p :class="$style.username"><MkAcct :user="user"/></p> + </div> + <div :class="$style.description"> + <div v-if="user.description" :class="$style.mfm"> + <Mfm :text="user.description" :author="user" :i="$i"/> + </div> + <span v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</span> + </div> + <div :class="$style.footer"> + <MkButton v-if="!isFollowing" primary gradate rounded full @click="follow"><i class="ti ti-plus"></i> {{ i18n.ts.follow }}</MkButton> + <div v-else style="opacity: 0.7; text-align: center;">{{ i18n.ts.youFollowing }} <i class="ti ti-check"></i></div> + </div> +</div> +</template> + +<script lang="ts" setup> +import * as misskey from 'misskey-js'; +import { ref } from 'vue'; +import MkButton from '@/components/MkButton.vue'; +import { i18n } from '@/i18n'; +import { $i } from '@/account'; +import * as os from '@/os'; + +const props = defineProps<{ + user: misskey.entities.UserDetailed; +}>(); + +const isFollowing = ref(false); + +async function follow() { + isFollowing.value = true; + os.api('following/create', { + userId: props.user.id, + }); +} +</script> + +<style lang="scss" module> +.banner { + height: 60px; + background-color: rgba(0, 0, 0, 0.1); + background-size: cover; + background-position: center; +} + +.avatar { + display: block; + position: absolute; + top: 30px; + left: 13px; + z-index: 2; + width: 58px; + height: 58px; + border: solid 4px var(--panel); +} + +.title { + display: block; + padding: 10px 0 10px 88px; +} + +.name { + display: inline-block; + margin: 0; + font-weight: bold; + line-height: 16px; + word-break: break-all; +} + +.username { + display: block; + margin: 0; + line-height: 16px; + font-size: 0.8em; + color: var(--fg); + opacity: 0.7; +} + +.description { + padding: 0 16px 16px 88px; + font-size: 0.9em; +} + +.mfm { + display: -webkit-box; + -webkit-line-clamp: 5; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.footer { + border-top: solid 0.5px var(--divider); + padding: 16px; +} +</style> diff --git a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts new file mode 100644 index 0000000000000000000000000000000000000000..55790602d567923b82c792afc73a243c836e3977 --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts @@ -0,0 +1,51 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { rest } from 'msw'; +import { commonHandlers } from '../../.storybook/mocks'; +import { userDetailed } from '../../.storybook/fakes'; +import MkUserSetupDialog from './MkUserSetupDialog.vue'; +export const Default = { + render(args) { + return { + components: { + MkUserSetupDialog, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkUserSetupDialog v-bind="props" />', + }; + }, + args: { + + }, + parameters: { + layout: 'centered', + msw: { + handlers: [ + ...commonHandlers, + rest.post('/api/users', (req, res, ctx) => { + return res(ctx.json([ + userDetailed('44'), + userDetailed('49'), + ])); + }), + rest.post('/api/pinned-users', (req, res, ctx) => { + return res(ctx.json([ + userDetailed('44'), + userDetailed('49'), + ])); + }), + ], + }, + }, +} satisfies StoryObj<typeof MkUserSetupDialog>; diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue new file mode 100644 index 0000000000000000000000000000000000000000..096b88c309a58df6487450aef90f777a9e0eeca3 --- /dev/null +++ b/packages/frontend/src/components/MkUserSetupDialog.vue @@ -0,0 +1,145 @@ +<template> +<MkModalWindow + ref="dialog" + :width="500" + :height="550" + data-cy-user-setup + @close="close(true)" + @closed="emit('closed')" +> + <template #header>{{ i18n.ts.initialAccountSetting }}</template> + + <div style="overflow-x: clip;"> + <Transition + mode="out-in" + :enter-active-class="$style.transition_x_enterActive" + :leave-active-class="$style.transition_x_leaveActive" + :enter-from-class="$style.transition_x_enterFrom" + :leave-to-class="$style.transition_x_leaveTo" + > + <template v-if="page === 0"> + <div :class="$style.centerPage"> + <MkSpacer :margin-min="20" :margin-max="28"> + <div class="_gaps" style="text-align: center;"> + <i class="ti ti-confetti" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i> + <div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.accountCreated }}</div> + <div>{{ i18n.ts._initialAccountSetting.letsStartAccountSetup }}</div> + <MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts._initialAccountSetting.profileSetting }} <i class="ti ti-arrow-right"></i></MkButton> + </div> + </MkSpacer> + </div> + </template> + <template v-else-if="page === 1"> + <div style="height: 100cqh; overflow: auto;"> + <MkSpacer :margin-min="20" :margin-max="28"> + <XProfile/> + <MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + </MkSpacer> + </div> + </template> + <template v-else-if="page === 2"> + <div style="height: 100cqh; overflow: auto;"> + <MkSpacer :margin-min="20" :margin-max="28"> + <XFollow/> + <MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + </MkSpacer> + </div> + </template> + <template v-else-if="page === 3"> + <div :class="$style.centerPage"> + <MkSpacer :margin-min="20" :margin-max="28"> + <div class="_gaps" style="text-align: center;"> + <i class="ti ti-bell-ringing-2" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i> + <div style="font-size: 120%;">{{ i18n.ts.pushNotification }}</div> + <div style="padding: 0 16px;">{{ i18n.t('_initialAccountSetting.pushNotificationDescription', { name: instance.name ?? host }) }}</div> + <MkPushNotificationAllowButton primary show-only-to-register style="margin: 0 auto;"/> + <MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + </div> + </MkSpacer> + </div> + </template> + <template v-else-if="page === 4"> + <div :class="$style.centerPage"> + <MkSpacer :margin-min="20" :margin-max="28"> + <div class="_gaps" style="text-align: center;"> + <i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i> + <div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.initialAccountSettingCompleted }}</div> + <I18n :src="i18n.ts._initialAccountSetting.ifYouNeedLearnMore" tag="div" style="padding: 0 16px;"> + <template #name>{{ instance.name ?? host }}</template> + <template #link> + <a href="https://misskey-hub.net/help.html" target="_blank" class="_link">{{ i18n.ts.help }}</a> + </template> + </I18n> + <div>{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}</div> + <MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="close(false)">{{ i18n.ts.close }}</MkButton> + </div> + </MkSpacer> + </div> + </template> + </Transition> + </div> +</MkModalWindow> +</template> + +<script lang="ts" setup> +import { ref, shallowRef, watch } from 'vue'; +import MkModalWindow from '@/components/MkModalWindow.vue'; +import MkButton from '@/components/MkButton.vue'; +import XProfile from '@/components/MkUserSetupDialog.Profile.vue'; +import XFollow from '@/components/MkUserSetupDialog.Follow.vue'; +import { i18n } from '@/i18n'; +import { instance } from '@/instance'; +import { host } from '@/config'; +import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue'; +import { defaultStore } from '@/store'; +import * as os from '@/os'; + +const emit = defineEmits<{ + (ev: 'closed'): void; +}>(); + +const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); + +const page = ref(defaultStore.state.accountSetupWizard); + +watch(page, () => { + defaultStore.set('accountSetupWizard', page.value); +}); + +async function close(skip: boolean) { + if (skip) { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.ts._initialAccountSetting.skipAreYouSure, + }); + if (canceled) return; + } + + dialog.value.close(); + defaultStore.set('accountSetupWizard', -1); +} +</script> + +<style lang="scss" 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); +} + +.centerPage { + display: flex; + justify-content: center; + align-items: center; + height: 100cqh; + padding-bottom: 30px; + box-sizing: border-box; +} +</style> diff --git a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue new file mode 100644 index 0000000000000000000000000000000000000000..fb705786cf7305452eac9fe1a3e7a387b906de91 --- /dev/null +++ b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue @@ -0,0 +1,157 @@ +<template> +<div> + <MkLoading v-if="fetching"/> + <div v-show="!fetching" :class="$style.root"> + <canvas ref="chartEl"></canvas> + </div> +</div> +</template> + +<script lang="ts" setup> +import { onMounted } from 'vue'; +import { Chart } from 'chart.js'; +import gradient from 'chartjs-plugin-gradient'; +import tinycolor from 'tinycolor2'; +import * as os from '@/os'; +import { defaultStore } from '@/store'; +import { useChartTooltip } from '@/scripts/use-chart-tooltip'; +import { chartVLine } from '@/scripts/chart-vline'; +import { initChart } from '@/scripts/init-chart'; + +initChart(); + +const chartEl = $shallowRef<HTMLCanvasElement>(null); +const now = new Date(); +let chartInstance: Chart = null; +const chartLimit = 30; +let fetching = $ref(true); + +const { handler: externalTooltipHandler } = useChartTooltip(); + +async function renderChart() { + if (chartInstance) { + chartInstance.destroy(); + } + + const getDate = (ago: number) => { + const y = now.getFullYear(); + const m = now.getMonth(); + const d = now.getDate(); + + return new Date(y, m, d - ago); + }; + + const format = (arr) => { + return arr.map((v, i) => ({ + x: getDate(i).getTime(), + y: v, + })); + }; + + const raw = await os.api('charts/active-users', { limit: chartLimit, span: 'day' }); + + const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; + + const computedStyle = getComputedStyle(document.documentElement); + const accent = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString(); + + const colorRead = accent; + const colorWrite = '#2ecc71'; + + const max = Math.max(...raw.read); + + chartInstance = new Chart(chartEl, { + type: 'bar', + data: { + datasets: [{ + parsing: false, + label: 'Read', + data: format(raw.read).slice().reverse(), + pointRadius: 0, + borderWidth: 0, + borderJoinStyle: 'round', + borderRadius: 4, + backgroundColor: colorRead, + barPercentage: 0.5, + categoryPercentage: 1, + fill: true, + }], + }, + options: { + aspectRatio: 2.5, + layout: { + padding: { + left: 0, + right: 8, + top: 0, + bottom: 0, + }, + }, + scales: { + x: { + type: 'time', + offset: true, + time: { + stepSize: 1, + unit: 'day', + displayFormats: { + day: 'M/d', + month: 'Y/M', + }, + }, + grid: { + display: false, + }, + ticks: { + display: true, + maxRotation: 0, + autoSkipPadding: 8, + }, + }, + y: { + position: 'left', + suggestedMax: 10, + grid: { + display: true, + }, + ticks: { + display: true, + //mirror: true, + }, + }, + }, + interaction: { + intersect: false, + mode: 'index', + }, + plugins: { + legend: { + display: false, + }, + tooltip: { + enabled: false, + mode: 'index', + animation: { + duration: 0, + }, + external: externalTooltipHandler, + }, + gradient, + }, + }, + plugins: [chartVLine(vLineColor)], + }); + + fetching = false; +} + +onMounted(async () => { + renderChart(); +}); +</script> + +<style lang="scss" module> +.root { + padding: 20px; +} +</style> diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue new file mode 100644 index 0000000000000000000000000000000000000000..62267681273b140c08ca00bb7a1a2c5afe8449b0 --- /dev/null +++ b/packages/frontend/src/components/MkVisitorDashboard.vue @@ -0,0 +1,227 @@ +<template> +<div v-if="meta" :class="$style.root"> + <div :class="[$style.main, $style.panel]"> + <img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.mainIcon"/> + <button class="_button _acrylic" :class="$style.mainMenu" @click="showMenu"><i class="ti ti-dots"></i></button> + <div :class="$style.mainFg"> + <h1 :class="$style.mainTitle"> + <!-- 背景色ã«ã‚ˆã£ã¦ã¯ãƒã‚´ãŒè¦‹ãˆãªããªã‚‹ã®ã§ã¨ã‚Šã‚ãˆãšç„¡åŠ¹ã« --> + <!-- <img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span> --> + <span>{{ instanceName }}</span> + </h1> + <div :class="$style.mainAbout"> + <!-- eslint-disable-next-line vue/no-v-html --> + <div v-html="meta.description || i18n.ts.headlineMisskey"></div> + </div> + <div v-if="instance.disableRegistration" :class="$style.mainWarn"> + <MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo> + </div> + <div class="_gaps_s" :class="$style.mainActions"> + <MkButton :class="$style.mainAction" full rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.joinThisServer }}</MkButton> + <MkButton :class="$style.mainAction" full rounded @click="exploreOtherServers()">{{ i18n.ts.exploreOtherServers }}</MkButton> + <MkButton :class="$style.mainAction" full rounded data-cy-signin @click="signin()">{{ i18n.ts.login }}</MkButton> + </div> + </div> + </div> + <div v-if="stats" :class="$style.stats"> + <div :class="[$style.statsItem, $style.panel]"> + <div :class="$style.statsItemLabel">{{ i18n.ts.users }}</div> + <div :class="$style.statsItemCount"><MkNumber :value="stats.originalUsersCount"/></div> + </div> + <div :class="[$style.statsItem, $style.panel]"> + <div :class="$style.statsItemLabel">{{ i18n.ts.notes }}</div> + <div :class="$style.statsItemCount"><MkNumber :value="stats.originalNotesCount"/></div> + </div> + </div> + <div v-if="instance.policies.ltlAvailable" :class="[$style.tl, $style.panel]"> + <div :class="$style.tlHeader">{{ i18n.ts.letsLookAtTimeline }}</div> + <div :class="$style.tlBody"> + <MkTimeline src="local"/> + </div> + </div> + <div :class="[$style.activeUsersChart, $style.panel]"> + <XActiveUsersChart/> + </div> +</div> +</template> + +<script lang="ts" setup> +import { } from 'vue'; +import { Instance } from 'misskey-js/built/entities'; +import XTimeline from './welcome.timeline.vue'; +import XSigninDialog from '@/components/MkSigninDialog.vue'; +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'; +import * as os from '@/os'; +import { i18n } from '@/i18n'; +import { instance } from '@/instance'; +import number from '@/filters/number'; +import MkNumber from '@/components/MkNumber.vue'; +import XActiveUsersChart from '@/components/MkVisitorDashboard.ActiveUsersChart.vue'; + +let meta = $ref<Instance>(); +let stats = $ref(null); + +os.api('meta', { detail: true }).then(_meta => { + meta = _meta; +}); + +os.api('stats', { +}).then((res) => { + stats = res; +}); + +function signin() { + os.popup(XSigninDialog, { + autoSet: true, + }, {}, 'closed'); +} + +function signup() { + os.popup(XSignupDialog, { + autoSet: true, + }, {}, 'closed'); +} + +function showMenu(ev) { + os.popupMenu([{ + text: i18n.ts.instanceInfo, + icon: 'ti ti-info-circle', + action: () => { + os.pageWindow('/about'); + }, + }, { + text: i18n.ts.aboutMisskey, + icon: 'ti ti-info-circle', + action: () => { + os.pageWindow('/about-misskey'); + }, + }, null, { + text: i18n.ts.help, + icon: 'ti ti-help-circle', + action: () => { + window.open('https://misskey-hub.net/help.md', '_blank'); + }, + }], ev.currentTarget ?? ev.target); +} + +function exploreOtherServers() { + // TODO: 言語をよã—ãªã« + window.open('https://join.misskey.page/ja-JP/instances', '_blank'); +} +</script> + +<style lang="scss" module> +.root { + position: relative; + display: flex; + flex-direction: column; + gap: 16px; + padding: 32px 0 0 0; +} + +.panel { + position: relative; + background: var(--panel); + border-radius: var(--radius); + box-shadow: 0 12px 32px rgb(0 0 0 / 25%); +} + +.main { + text-align: center; +} + +.mainIcon { + width: 85px; + margin-top: -47px; + vertical-align: bottom; + filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.5)); +} + +.mainMenu { + position: absolute; + top: 16px; + right: 16px; + width: 32px; + height: 32px; + border-radius: 8px; + font-size: 18px; +} + +.mainFg { + position: relative; + z-index: 1; +} + +.mainTitle { + display: block; + margin: 0; + padding: 16px 32px 24px 32px; + font-size: 1.4em; +} + +.mainLogo { + vertical-align: bottom; + max-height: 120px; + max-width: min(100%, 300px); +} + +.mainAbout { + padding: 0 32px; +} + +.mainWarn { + padding: 32px 32px 0 32px; +} + +.mainActions { + padding: 32px; +} + +.mainAction { + line-height: 28px; +} + +.stats { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 16px; +} + +.statsItem { + overflow: clip; + padding: 16px 20px; +} + +.statsItemLabel { + color: var(--fgTransparentWeak); + font-size: 0.9em; +} + +.statsItemCount { + font-weight: bold; + font-size: 1.2em; + color: var(--accent); +} + +.tl { + overflow: clip; +} + +.tlHeader { + padding: 12px 16px; + border-bottom: solid 1px var(--divider); +} + +.tlBody { + height: 350px; + overflow: auto; +} + +.activeUsersChart { + +} +</style> diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue index d074fdd1506bd5468558423fed18f17e2ec20cab..33e594acd80be4309248157774f2e8fe0e2f06ba 100644 --- a/packages/frontend/src/components/MkWidgets.vue +++ b/packages/frontend/src/components/MkWidgets.vue @@ -2,11 +2,11 @@ <div :class="$style.root"> <template v-if="edit"> <header :class="$style['edit-header']"> - <MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" class="mk-widget-select"> + <MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" data-cy-widget-select> <template #label>{{ i18n.ts.selectWidget }}</template> <option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.t(`_widgets.${widget}`) }}</option> </MkSelect> - <MkButton inline primary class="mk-widget-add" @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> + <MkButton inline primary data-cy-widget-add @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> <MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton> </header> <Sortable diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue index 687abed632e501c1d1960c19bc57bceb705da890..b662479b2a28ba032a2656f9890807fa6ce4d6de 100644 --- a/packages/frontend/src/components/MkWindow.vue +++ b/packages/frontend/src/components/MkWindow.vue @@ -29,7 +29,7 @@ <button v-if="closeButton" v-tooltip="i18n.ts.close" class="_button" :class="$style.headerButton" @click="close()"><i class="ti ti-x"></i></button> </span> </div> - <div v-container :class="$style.content"> + <div :class="$style.content"> <slot></slot> </div> </div> @@ -541,7 +541,7 @@ defineExpose({ flex: 1; overflow: auto; background: var(--panel); - container-type: inline-size; + container-type: size; } $handleSize: 8px; diff --git a/packages/frontend/src/components/global/MkAcct.stories.impl.ts b/packages/frontend/src/components/global/MkAcct.stories.impl.ts index d5e3fc3568294c6d4495e79ca67138ac4b790ea1..9d5fd3947dc5222020a05062cf037f5c826d2787 100644 --- a/packages/frontend/src/components/global/MkAcct.stories.impl.ts +++ b/packages/frontend/src/components/global/MkAcct.stories.impl.ts @@ -41,3 +41,35 @@ export const Detail = { detail: true, }, } satisfies StoryObj<typeof MkAcct>; +export const Long = { + ...Default, + args: { + ...Default.args, + user: { + ...userDetailed(), + username: 'the_quick_brown_fox_jumped_over_the_lazy_dog', + host: 'misskey.example', + }, + }, + decorators: [ + () => ({ + template: '<div style="width: 360px;"><story/></div>', + }), + ], +} satisfies StoryObj<typeof MkAcct>; +export const VeryLong = { + ...Default, + args: { + ...Default.args, + user: { + ...userDetailed(), + username: '2c7cc62a697ea3a7826521f3fd34f0cb273693cbe5e9310f35449f43622a5cdc', + host: 'the.quick.brown.fox.jumped.over.the.lazy.dog.very.long.hostname.nostr.example', + }, + }, + decorators: [ + () => ({ + template: '<div style="width: 360px;"><story/></div>', + }), + ], +} satisfies StoryObj<typeof MkAcct>; diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue index 2b9f892fc6d06c1797d8719eebc3e7b7b7f90c6c..59358aef70c0aa3dc9a70d9b5bc40c3f1ee77abe 100644 --- a/packages/frontend/src/components/global/MkAcct.vue +++ b/packages/frontend/src/components/global/MkAcct.vue @@ -1,5 +1,9 @@ <template> -<span> +<MkCondensedLine v-if="defaultStore.state.enableCondensedLineForAcct" :min-scale="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>@{{ user.username }}</span> <span v-if="user.host || detail || defaultStore.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span> </span> @@ -8,6 +12,7 @@ <script lang="ts" setup> import * as misskey from 'misskey-js'; import { toUnicode } from 'punycode/'; +import MkCondensedLine from './MkCondensedLine.vue'; import { host as hostRaw } from '@/config'; import { defaultStore } from '@/store'; diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue index 8497b8443b2ee6c8652b02d3da97d1fafc3d9275..ad36dcabe45e1ff37b7657950e37fa45fea021b8 100644 --- a/packages/frontend/src/components/global/MkAvatar.vue +++ b/packages/frontend/src/components/global/MkAvatar.vue @@ -222,7 +222,7 @@ watch(() => props.user.avatarBlurhash, () => { transform: rotate(37.5deg) skew(30deg); &, &::after { - border-radius: 0 75% 75%; + border-radius: 25% 75% 75%; } > .layer { @@ -251,7 +251,7 @@ watch(() => props.user.avatarBlurhash, () => { transform: rotate(-37.5deg) skew(-30deg); &, &::after { - border-radius: 75% 0 75% 75%; + border-radius: 75% 25% 75% 75%; } > .layer { diff --git a/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts new file mode 100644 index 0000000000000000000000000000000000000000..ce985bc59f779cb78a55b6e63244a4393fb1ba90 --- /dev/null +++ b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts @@ -0,0 +1,39 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import MkCondensedLine from './MkCondensedLine.vue'; +export const Default = { + render(args) { + return { + components: { + MkCondensedLine, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkCondensedLine>{{ props.text }}</MkCondensedLine>', + }; + }, + args: { + text: 'This is a condensed line.', + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj<typeof MkCondensedLine>; +export const ContainerIs100px = { + ...Default, + decorators: [ + () => ({ + template: '<div style="width: 100px;"><story/></div>', + }), + ], +} satisfies StoryObj<typeof MkCondensedLine>; diff --git a/packages/frontend/src/components/global/MkCondensedLine.vue b/packages/frontend/src/components/global/MkCondensedLine.vue new file mode 100644 index 0000000000000000000000000000000000000000..1d46ff1ec9f355e0a2110ade6ae2382fe3f5df4b --- /dev/null +++ b/packages/frontend/src/components/global/MkCondensedLine.vue @@ -0,0 +1,65 @@ +<template> +<span :class="$style.container"> + <span ref="content" :class="$style.content"> + <slot/> + </span> +</span> +</template> + +<script lang="ts"> +interface Props { + readonly minScale?: number; +} + +const contentSymbol = Symbol(); +const observer = new ResizeObserver((entries) => { + for (const entry of entries) { + const content = (entry.target[contentSymbol] ? entry.target : entry.target.firstElementChild) as HTMLSpanElement; + const props: Required<Props> = content[contentSymbol]; + const container = content.parentElement as HTMLSpanElement; + const contentWidth = content.getBoundingClientRect().width; + const containerWidth = container.getBoundingClientRect().width; + container.style.transform = `scaleX(${Math.max(props.minScale, Math.min(1, containerWidth / contentWidth))})`; + } +}); +</script> + +<script setup lang="ts"> +import { ref, watch } from 'vue'; + +const props = withDefaults(defineProps<Props>(), { + minScale: 0, +}); + +const content = ref<HTMLSpanElement>(); + +watch(content, (value, oldValue) => { + if (oldValue) { + delete oldValue[contentSymbol]; + observer.unobserve(oldValue); + if (oldValue.parentElement) { + observer.unobserve(oldValue.parentElement); + } + } + if (value) { + value[contentSymbol] = props; + observer.observe(value); + if (value.parentElement) { + observer.observe(value.parentElement); + } + } +}); +</script> + +<style module lang="scss"> +.container { + display: inline-block; + max-width: 100%; + transform-origin: 0; +} + +.content { + display: inline-block; + white-space: nowrap; +} +</style> diff --git a/packages/frontend/src/components/global/MkError.stories.impl.ts b/packages/frontend/src/components/global/MkError.stories.impl.ts index 60ac5c91ad72b87909d49dd3b73d512b5fff7266..8252a4d76eb9cc28c3b68fe23ce1114ac98c6eef 100644 --- a/packages/frontend/src/components/global/MkError.stories.impl.ts +++ b/packages/frontend/src/components/global/MkError.stories.impl.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { action } from '@storybook/addon-actions'; import { expect } from '@storybook/jest'; import { waitFor } from '@storybook/testing-library'; import { StoryObj } from '@storybook/vue3'; @@ -20,14 +21,21 @@ export const Default = { ...this.args, }; }, + events() { + return { + retry: action('retry'), + }; + }, }, - template: '<MkError v-bind="props" />', + template: '<MkError v-bind="props" v-on="events" />', }; }, async play({ canvasElement }) { await expect(canvasElement.firstElementChild).not.toBeNull(); await waitFor(async () => expect(canvasElement.firstElementChild?.classList).not.toContain('_transition_zoom-enter-active')); }, + args: { + }, parameters: { layout: 'centered', }, diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index 710edd797ae5e8615338e4af78df85ccc83ee393..b91d378b17ee76d64c1b792d47fbc365bf018b05 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -156,7 +156,7 @@ onUnmounted(() => { } &.thin { - --height: 42px; + --height: 40px; > .buttons { > .button { diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue index 99169512dbd74316fc93735f5ab669f247377b25..261cc0ee18ca2f93c7ac927d1aa2cb5f4af8d0ea 100644 --- a/packages/frontend/src/components/global/MkTime.vue +++ b/packages/frontend/src/components/global/MkTime.vue @@ -8,6 +8,7 @@ </template> <script lang="ts" setup> +import isChromatic from 'chromatic/isChromatic'; import { onUnmounted } from 'vue'; import { i18n } from '@/i18n'; import { dateTimeFormat } from '@/scripts/intl-const'; @@ -17,7 +18,7 @@ const props = withDefaults(defineProps<{ origin?: Date | null; mode?: 'relative' | 'absolute' | 'detail'; }>(), { - origin: null, + origin: isChromatic() ? new Date('2023-04-01T00:00:00Z') : null, mode: 'relative', }); diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index 63e8fc225c9834b1c6685759feeb138cab725871..4ef8111da910dea910c82d6ce5e9ebf31d143848 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -5,6 +5,7 @@ import MkA from './global/MkA.vue'; import MkAcct from './global/MkAcct.vue'; import MkAvatar from './global/MkAvatar.vue'; import MkEmoji from './global/MkEmoji.vue'; +import MkCondensedLine from './global/MkCondensedLine.vue'; import MkCustomEmoji from './global/MkCustomEmoji.vue'; import MkUserName from './global/MkUserName.vue'; import MkEllipsis from './global/MkEllipsis.vue'; @@ -33,6 +34,7 @@ export const components = { MkAcct: MkAcct, MkAvatar: MkAvatar, MkEmoji: MkEmoji, + MkCondensedLine: MkCondensedLine, MkCustomEmoji: MkCustomEmoji, MkUserName: MkUserName, MkEllipsis: MkEllipsis, @@ -55,6 +57,7 @@ declare module '@vue/runtime-core' { MkAcct: typeof MkAcct; MkAvatar: typeof MkAvatar; MkEmoji: typeof MkEmoji; + MkCondensedLine: typeof MkCondensedLine; MkCustomEmoji: typeof MkCustomEmoji; MkUserName: typeof MkUserName; MkEllipsis: typeof MkEllipsis; diff --git a/packages/frontend/src/config.ts b/packages/frontend/src/config.ts index 073b21a0ae2249fa3a93f3379ddb59176f6552df..24f60910d1cfc6646ba9488e371c6628be97faf4 100644 --- a/packages/frontend/src/config.ts +++ b/packages/frontend/src/config.ts @@ -1,21 +1,22 @@ -import { miLocalStorage } from "./local-storage"; +import { miLocalStorage } from './local-storage'; const address = new URL(location.href); -const siteName = (document.querySelector('meta[property="og:site_name"]') as HTMLMetaElement)?.content; +const siteName = document.querySelector<HTMLMetaElement>('meta[property="og:site_name"]')?.content; export const host = address.host; export const hostname = address.hostname; export const url = address.origin; export const apiUrl = url + '/api'; export const wsUrl = url.replace('http://', 'ws://').replace('https://', 'wss://') + '/streaming'; -export const lang = miLocalStorage.getItem('lang'); +export const lang = miLocalStorage.getItem('lang') ?? 'en-US'; export const langs = _LANGS_; -export let locale = JSON.parse(miLocalStorage.getItem('locale')); +const preParseLocale = miLocalStorage.getItem('locale'); +export let locale = preParseLocale ? JSON.parse(preParseLocale) : null; export const version = _VERSION_; export const instanceName = siteName === 'Misskey' ? host : siteName; export const ui = miLocalStorage.getItem('ui'); export const debug = miLocalStorage.getItem('debug') === 'true'; -export function updateLocale(newLocale) { +export function updateLocale(newLocale): void { locale = newLocale; } diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts index 1d1b8fcea4f4fe6f25ace20b686641d79cb4d4c8..aaa3d10302546867a6e64d66f97156e910f99cbd 100644 --- a/packages/frontend/src/const.ts +++ b/packages/frontend/src/const.ts @@ -35,6 +35,11 @@ export const FILE_TYPE_BROWSERSAFE = [ 'audio/webm', 'audio/aac', + + // see https://github.com/misskey-dev/misskey/pull/10686 + 'audio/flac', + 'audio/wav', + // backward compatibility 'audio/x-flac', 'audio/vnd.wave', ]; @@ -56,6 +61,7 @@ export const ROLE_POLICIES = [ 'canSearchNotes', 'canHideAds', 'driveCapacityMb', + 'alwaysMarkNsfw', 'pinLimit', 'antennaLimit', 'wordMuteLimit', diff --git a/packages/frontend/src/directives/container.ts b/packages/frontend/src/directives/container.ts deleted file mode 100644 index a8a93eb9be4c00d7eb3d31488a12fd1141e044d4..0000000000000000000000000000000000000000 --- a/packages/frontend/src/directives/container.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Directive } from 'vue'; - -const map = new WeakMap<HTMLElement, ResizeObserver>(); - -export default { - mounted(el: HTMLElement, binding, vn) { - const ro = new ResizeObserver((entries, observer) => { - el.style.setProperty('--containerHeight', el.offsetHeight + 'px'); - }); - ro.observe(el); - map.set(el, ro); - }, - - unmounted(el, binding, vn) { - const ro = map.get(el); - if (ro) { - ro.disconnect(); - map.delete(el); - } - }, -} as Directive; diff --git a/packages/frontend/src/directives/index.ts b/packages/frontend/src/directives/index.ts index 064ee4f64b40ef8ca17b5f380e6ef7d2ca512665..7847d661d41f3a1c683c3e45d882a5b72d58167a 100644 --- a/packages/frontend/src/directives/index.ts +++ b/packages/frontend/src/directives/index.ts @@ -11,7 +11,6 @@ import clickAnime from './click-anime'; import panel from './panel'; import adaptiveBorder from './adaptive-border'; import adaptiveBg from './adaptive-bg'; -import container from './container'; export default function(app: App) { for (const [key, value] of Object.entries(directives)) { @@ -32,5 +31,4 @@ export const directives = { 'panel': panel, 'adaptive-border': adaptiveBorder, 'adaptive-bg': adaptiveBg, - 'container': container, }; diff --git a/packages/frontend/src/init.ts b/packages/frontend/src/init.ts index 5b3e7ec9325c4fb72b72f34f4f80abd66a18ce96..49e7bb4008545a4032f60b342f8096fe55151427 100644 --- a/packages/frontend/src/init.ts +++ b/packages/frontend/src/init.ts @@ -6,18 +6,6 @@ import 'vite/modulepreload-polyfill'; import '@/style.scss'; -//#region account indexedDB migration -import { set } from '@/scripts/idb-proxy'; - -{ - const accounts = miLocalStorage.getItem('accounts'); - if (accounts) { - set('accounts', JSON.parse(accounts)); - miLocalStorage.removeItem('accounts'); - } -} -//#endregion - import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue'; import { compareVersions } from 'compare-versions'; import JSON5 from 'json5'; @@ -42,11 +30,11 @@ import { reloadChannel } from '@/scripts/unison-reload'; import { reactionPicker } from '@/scripts/reaction-picker'; import { getUrlWithoutLoginId } from '@/scripts/login-id'; import { getAccountFromId } from '@/scripts/get-account-from-id'; -import { deckStore } from './ui/deck/deck-store'; -import { miLocalStorage } from './local-storage'; -import { claimAchievement, claimedAchievements } from './scripts/achievements'; -import { fetchCustomEmojis } from './custom-emojis'; -import { mainRouter } from './router'; +import { deckStore } from '@/ui/deck/deck-store'; +import { miLocalStorage } from '@/local-storage'; +import { claimAchievement, claimedAchievements } from '@/scripts/achievements'; +import { fetchCustomEmojis } from '@/custom-emojis'; +import { mainRouter } from '@/router'; console.info(`Misskey v${version}`); @@ -55,7 +43,9 @@ if (_DEV_) { console.info(`vue ${vueVersion}`); + // eslint-disable-next-line @typescript-eslint/no-explicit-any (window as any).$i = $i; + // eslint-disable-next-line @typescript-eslint/no-explicit-any (window as any).$store = defaultStore; window.addEventListener('error', event => { @@ -184,7 +174,7 @@ fetchInstanceMetaPromise.then(() => { try { await fetchCustomEmojis(); -} catch (err) {} +} catch (err) { /* empty */ } const app = createApp( new URLSearchParams(window.location.search).has('zen') ? defineAsyncComponent(() => import('@/ui/zen.vue')) : @@ -212,20 +202,20 @@ await deckStore.ready; // https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210 // ãªãœã‹init.tsã®å†…容ãŒ2回実行ã•ã‚Œã‚‹ã“ã¨ãŒã‚ã‚‹ãŸã‚ã€mountã™ã‚‹divã‚’1ã¤ã«åˆ¶é™ã™ã‚‹ -const rootEl = (() => { +const rootEl = ((): HTMLElement => { const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; - const currentEl = document.getElementById(MISSKEY_MOUNT_DIV_ID); + const currentRoot = document.getElementById(MISSKEY_MOUNT_DIV_ID); - if (currentEl) { + if (currentRoot) { console.warn('multiple import detected'); - return currentEl; + return currentRoot; } - const rootEl = document.createElement('div'); - rootEl.id = MISSKEY_MOUNT_DIV_ID; - document.body.appendChild(rootEl); - return rootEl; + const root = document.createElement('div'); + root.id = MISSKEY_MOUNT_DIV_ID; + document.body.appendChild(root); + return root; })(); app.mount(rootEl); @@ -256,8 +246,7 @@ if (lastVersion !== version) { popup(defineAsyncComponent(() => import('@/components/MkUpdated.vue')), {}, {}, 'closed'); } } - } catch (err) { - } + } catch (err) { /* empty */ } } await defaultStore.ready; @@ -354,6 +343,16 @@ if ($i) { // only add post shortcuts if logged in hotkeys['p|n'] = post; + if (defaultStore.state.accountSetupWizard !== -1) { + // ã“ã®ã‚¦ã‚£ã‚¶ãƒ¼ãƒ‰ãŒå®Ÿè£…ã•ã‚Œã‚‹å‰ã«ç™»éŒ²ã—ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã«ã¯è¡¨ç¤ºã•ã›ãªã„ãŸã‚ + // TODO: ãã®ã†ã¡æ¶ˆã™ + if (Date.now() - new Date($i.createdAt).getTime() < 1000 * 60 * 60 * 24) { + popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, {}, 'closed'); + } else { + defaultStore.set('accountSetupWizard', -1); + } + } + if ($i.isDeleted) { alert({ type: 'warning', @@ -442,6 +441,10 @@ if ($i) { claimAchievement('client30min'); }, 1000 * 60 * 30); + window.setTimeout(() => { + claimAchievement('client60min'); + }, 1000 * 60 * 60); + const lastUsed = miLocalStorage.getItem('lastUsed'); if (lastUsed) { const lastUsedDate = parseInt(lastUsed, 10); @@ -456,7 +459,7 @@ if ($i) { const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt'); const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo'); - if (neverShowDonationInfo !== 'true' && (new Date($i.createdAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3)))) { + if (neverShowDonationInfo !== 'true' && (new Date($i.createdAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) { if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) { popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {}, 'closed'); } diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts index 9a288f264c598376c6c6a3a8fbdc22ed8fb4028c..441a35747a3be50a9684c5547e6d234adc4542f2 100644 --- a/packages/frontend/src/local-storage.ts +++ b/packages/frontend/src/local-storage.ts @@ -24,6 +24,7 @@ type Keys = 'customCss' | 'message_drafts' | 'scratchpad' | + 'debug' | `miux:${string}` | `ui:folder:${string}` | `themes:${string}` | @@ -32,7 +33,7 @@ type Keys = 'emojis' // DEPRECATED, stored in indexeddb (13.9.0~); export const miLocalStorage = { - getItem: (key: Keys) => window.localStorage.getItem(key), - setItem: (key: Keys, value: string) => window.localStorage.setItem(key, value), - removeItem: (key: Keys) => window.localStorage.removeItem(key), + 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), }; diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index 962f9cdd98d96369a301832468380cf087990b4b..c4f9d47d7d35ba639516b877bd838e14e9888b6a 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -18,6 +18,8 @@ import MkPopupMenu from '@/components/MkPopupMenu.vue'; import MkContextMenu from '@/components/MkContextMenu.vue'; import { MenuItem } from '@/types/menu'; import copyToClipboard from './scripts/copy-to-clipboard'; +import { showMovedDialog } from './scripts/show-moved-dialog'; +import { DriveFile } from 'misskey-js/built/entities'; export const openingWindowsCount = ref(0); @@ -55,6 +57,12 @@ export const apiWithDialog = (( } else if (err.code === 'RATE_LIMIT_EXCEEDED') { title = i18n.ts.cannotPerformTemporary; text = i18n.ts.cannotPerformTemporaryDescription; + } else if (err.code === 'INVALID_PARAM') { + title = i18n.ts.invalidParamError; + text = i18n.ts.invalidParamErrorDescription; + } else if (err.code === 'ROLE_PERMISSION_DENIED') { + title = i18n.ts.permissionDeniedError; + text = i18n.ts.permissionDeniedErrorDescription; } else if (err.code.startsWith('TOO_MANY')) { title = i18n.ts.youCannotCreateAnymore; text = `${i18n.ts.error}: ${err.id}`; @@ -413,7 +421,7 @@ export async function selectUser(opts: { includeSelf?: boolean } = {}) { }); } -export async function selectDriveFile(multiple: boolean) { +export async function selectDriveFile(multiple: boolean): Promise<DriveFile[]> { return new Promise((resolve, reject) => { popup(defineAsyncComponent(() => import('@/components/MkDriveSelectDialog.vue')), { type: 'file', @@ -421,7 +429,7 @@ export async function selectDriveFile(multiple: boolean) { }, { done: files => { if (files) { - resolve(multiple ? files : files[0]); + resolve(files); } }, }, 'closed'); @@ -572,6 +580,8 @@ export function contextMenu(items: MenuItem[] | Ref<MenuItem[]>, ev: MouseEvent) } export function post(props: Record<string, any> = {}): Promise<void> { + showMovedDialog(); + return new Promise((resolve, reject) => { // NOTE: MkPostFormDialogã‚’dynamic importã™ã‚‹ã¨iOSã§ãƒ†ã‚ストエリアã«è‡ªå‹•ãƒ•ã‚©ãƒ¼ã‚«ã‚¹ã§ããªã„ // NOTE: ãŸã ã€dynamic importã—ãªã„å ´åˆã€MkPostFormDialogインスタンスãŒä½¿ã„ã¾ã‚ã•ã‚Œã€ diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index bca4d17784b0fde760a384141ee6fd0dac584bb5..e592c629ce629f839c82d771095fee31b360071b 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -132,6 +132,18 @@ const patronsWithIcon = [{ }, { name: 'mollinaca', icon: 'https://misskey-hub.net/patrons/ceb36b8f66e549bdadb3b90d5da62314.jpg', +}, { + name: 'å‚本é¾', + icon: 'https://misskey-hub.net/patrons/a631cf8b490145cf8dbbe4e7508cfbc2.jpg', +}, { + name: 'takke', + icon: 'https://misskey-hub.net/patrons/6c3327e626c046f2914fbcd9f7557935.jpg', +}, { + name: 'ãºã‚“ãŽã‚“', + icon: 'https://misskey-hub.net/patrons/6a652e0534ff4cb1836e7ce4968d76a7.jpg', +}, { + name: 'ã‹ã¿ã‚‰ãˆã£ã¨', + icon: 'https://misskey-hub.net/patrons/be1326bda7d940a482f3758ffd9ffaf6.jpg', }]; const patrons = [ @@ -219,6 +231,13 @@ const patrons = [ '巣黒るã„@リスケモ男ã®å¨˜VTuber!', 'ãµã‡ã„ã½ã‚€', 'ä¾å¤ç”°ã‚¤ã‚³', + '戸塚ã“ã ã¾', + 'ã™ãƒ¼ã€‚', + '秋雨/Slime-hatena.jp', + 'ã‘ã', + 'ãšã‚‚', + 'binvinyl', + '渡志郎', ]; let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure')); diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue index d4614302344632309adb5119071f71414b983748..2d82fcf2771371242922f76672559cd1366f6f59 100644 --- a/packages/frontend/src/pages/about.emojis.vue +++ b/packages/frontend/src/pages/about.emojis.vue @@ -53,7 +53,15 @@ function search() { } if (selectedTags.size === 0) { - searchEmojis = customEmojis.value.filter(emoji => emoji.name.includes(q) || emoji.aliases.includes(q)); + const queryarry = q.match(/\:([a-z0-9_]*)\:/g); + + if (queryarry) { + searchEmojis = customEmojis.value.filter(emoji => + queryarry.includes(`:${emoji.name}:`) + ); + } else { + searchEmojis = customEmojis.value.filter(emoji => emoji.name.includes(q) || emoji.aliases.includes(q)); + } } else { searchEmojis = customEmojis.value.filter(emoji => (emoji.name.includes(q) || emoji.aliases.includes(q)) && [...selectedTags].every(t => emoji.aliases.includes(t))); } diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue index d54d93eaee57fc9370226c73c6c3729c44e73c3e..8e29990426fe3eaf0a136a15661418c49461c36e 100644 --- a/packages/frontend/src/pages/about.vue +++ b/packages/frontend/src/pages/about.vue @@ -3,10 +3,10 @@ <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer v-if="tab === 'overview'" :content-max="600" :margin-min="20"> <div class="_gaps_m"> - <div class="fwhjspax" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"> - <div class="content"> - <img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" class="icon"/> - <div class="name"> + <div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"> + <div style="overflow: clip;"> + <img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/> + <div :class="$style.bannerName"> <b>{{ instance.name ?? host }}</b> </div> </div> @@ -41,7 +41,14 @@ <template #value>{{ instance.maintainerEmail }}</template> </MkKeyValue> </FormSplit> - <FormLink v-if="instance.tosUrl" :to="instance.tosUrl" external>{{ i18n.ts.tos }}</FormLink> + <MkFolder v-if="instance.serverRules.length > 0"> + <template #label>{{ i18n.ts.serverRules }}</template> + + <ol class="_gaps_s" :class="$style.rules"> + <li v-for="item in instance.serverRules" :class="$style.rule"><div :class="$style.ruleText" v-html="item"></div></li> + </ol> + </MkFolder> + <FormLink v-if="instance.tosUrl" :to="instance.tosUrl" external>{{ i18n.ts.termsOfService }}</FormLink> </div> </FormSection> @@ -94,6 +101,7 @@ import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import FormSuspense from '@/components/form/suspense.vue'; import FormSplit from '@/components/form/split.vue'; +import MkFolder from '@/components/MkFolder.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; import MkInstanceStats from '@/components/MkInstanceStats.vue'; import * as os from '@/os'; @@ -148,31 +156,63 @@ definePageMetadata(computed(() => ({ }))); </script> -<style lang="scss" scoped> -.fwhjspax { +<style lang="scss" module> +.banner { text-align: center; border-radius: 10px; overflow: clip; background-size: cover; background-position: center center; +} + +.bannerIcon { + display: block; + margin: 16px auto 0 auto; + height: 64px; + border-radius: 8px; +} + +.bannerName { + display: block; + padding: 16px; + color: #fff; + text-shadow: 0 0 8px #000; + background: linear-gradient(transparent, rgba(0, 0, 0, 0.7)); +} - > .content { - overflow: hidden; - - > .icon { - display: block; - margin: 16px auto 0 auto; - height: 64px; - border-radius: 8px; - } - - > .name { - display: block; - padding: 16px; - color: #fff; - text-shadow: 0 0 8px #000; - background: linear-gradient(transparent, rgba(0, 0, 0, 0.7)); - } +.rules { + counter-reset: item; + list-style: none; + padding: 0; + margin: 0; +} + +.rule { + display: flex; + gap: 8px; + word-break: break-word; + + &::before { + flex-shrink: 0; + display: flex; + position: sticky; + top: calc(var(--stickyTop, 0px) + 8px); + counter-increment: item; + content: counter(item); + width: 32px; + height: 32px; + line-height: 32px; + background-color: var(--accentedBg); + color: var(--accent); + font-size: 13px; + font-weight: bold; + align-items: center; + justify-content: center; + border-radius: 999px; } } + +.ruleText { + padding-top: 6px; +} </style> diff --git a/packages/frontend/src/pages/admin/email-settings.vue b/packages/frontend/src/pages/admin/email-settings.vue index b742132af6b70163d2cdc8eeb920f6a9040dd20f..d51bf6230a9a02d47332d04dd3eea943a92980a2 100644 --- a/packages/frontend/src/pages/admin/email-settings.vue +++ b/packages/frontend/src/pages/admin/email-settings.vue @@ -96,7 +96,9 @@ async function testEmail() { const { canceled, result: destination } = await os.inputText({ title: i18n.ts.destination, type: 'email', - placeholder: instance.maintainerEmail, + default: instance.maintainerEmail ?? '', + placeholder: 'test@example.com', + minLength: 1, }); if (canceled) return; os.apiWithDialog('admin/send-email', { diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue index ebe1a8ade098cc6847b54249545fee1f3ca1c5b3..ffd3b6e233c605ada252a02ac7a577b97c2bc983 100644 --- a/packages/frontend/src/pages/admin/moderation.vue +++ b/packages/frontend/src/pages/admin/moderation.vue @@ -5,14 +5,30 @@ <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> <div class="_gaps_m"> - <FormSection first> - <div class="_gaps_m"> - <MkTextarea v-model="sensitiveWords"> - <template #label>{{ i18n.ts.sensitiveWords }}</template> - <template #caption>{{ i18n.ts.sensitiveWordsDescription }}</template> - </MkTextarea> - </div> - </FormSection> + <MkSwitch v-model="enableRegistration"> + <template #label>{{ i18n.ts.enableRegistration }}</template> + </MkSwitch> + + <MkSwitch v-model="emailRequiredForSignup"> + <template #label>{{ i18n.ts.emailRequiredForSignup }}</template> + </MkSwitch> + + <FormLink to="/admin/server-rules">{{ i18n.ts.serverRules }}</FormLink> + + <MkInput v-model="tosUrl"> + <template #prefix><i class="ti ti-link"></i></template> + <template #label>{{ i18n.ts.tosUrl }}</template> + </MkInput> + + <MkTextarea v-model="preservedUsernames"> + <template #label>{{ i18n.ts.preservedUsernames }}</template> + <template #caption>{{ i18n.ts.preservedUsernamesDescription }}</template> + </MkTextarea> + + <MkTextarea v-model="sensitiveWords"> + <template #label>{{ i18n.ts.sensitiveWords }}</template> + <template #caption>{{ i18n.ts.sensitiveWordsDescription }}</template> + </MkTextarea> </div> </FormSuspense> </MkSpacer> @@ -41,17 +57,30 @@ import { fetchInstance } from '@/instance'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import MkButton from '@/components/MkButton.vue'; +import FormLink from '@/components/form/link.vue'; +let enableRegistration: boolean = $ref(false); +let emailRequiredForSignup: boolean = $ref(false); let sensitiveWords: string = $ref(''); +let preservedUsernames: string = $ref(''); +let tosUrl: string | null = $ref(null); async function init() { const meta = await os.api('admin/meta'); + enableRegistration = !meta.disableRegistration; + emailRequiredForSignup = meta.emailRequiredForSignup; sensitiveWords = meta.sensitiveWords.join('\n'); + preservedUsernames = meta.preservedUsernames.join('\n'); + tosUrl = meta.tosUrl; } function save() { os.apiWithDialog('admin/update-meta', { + disableRegistration: !enableRegistration, + emailRequiredForSignup, + tosUrl, sensitiveWords: sensitiveWords.split('\n'), + preservedUsernames: preservedUsernames.split('\n'), }).then(() => { fetchInstance(); }); diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue index b1aa03f1f74f23a366a8b03d297f114d4c46cfc1..c211ef2f055f1f73f79c2c2596066c16121d1baf 100644 --- a/packages/frontend/src/pages/admin/roles.edit.vue +++ b/packages/frontend/src/pages/admin/roles.edit.vue @@ -54,6 +54,7 @@ if (props.id) { target: 'manual', condFormula: { id: uuid(), type: 'isRemote' }, isPublic: false, + isExplorable: false, asBadge: false, canEditMembersByModerator: false, displayOrder: 0, diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 873ff02feb2222f764a94dec0c38294eefa35d27..49942c87ce58aec4531441e5b90674f294768530 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -8,10 +8,9 @@ <template #label>{{ i18n.ts._role.description }}</template> </MkTextarea> - <MkInput v-model="role.color"> + <MkColorInput v-model="role.color"> <template #label>{{ i18n.ts.color }}</template> - <template #caption>#RRGGBB</template> - </MkInput> + </MkColorInput> <MkInput v-model="role.iconUrl"> <template #label>{{ i18n.ts._role.iconUrl }}</template> @@ -59,6 +58,11 @@ <template #caption>{{ i18n.ts._role.descriptionOfAsBadge }}</template> </MkSwitch> + <MkSwitch v-model="role.isExplorable" :readonly="readonly"> + <template #label>{{ i18n.ts._role.isExplorable }}</template> + <template #caption>{{ i18n.ts._role.descriptionOfIsExplorable }}</template> + </MkSwitch> + <FormSlot> <template #label><i class="ti ti-license"></i> {{ i18n.ts._role.policies }}</template> <div class="_gaps_s"> @@ -206,7 +210,7 @@ </MkRange> </div> </MkFolder> - + <MkFolder v-if="matchQuery([i18n.ts._role._options.driveCapacity, 'driveCapacityMb'])"> <template #label>{{ i18n.ts._role._options.driveCapacity }}</template> <template #suffix> @@ -227,6 +231,26 @@ </div> </MkFolder> + <MkFolder v-if="matchQuery([i18n.ts._role._options.alwaysMarkNsfw, 'alwaysMarkNsfw'])"> + <template #label>{{ i18n.ts._role._options.alwaysMarkNsfw }}</template> + <template #suffix> + <span v-if="role.policies.alwaysMarkNsfw.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span> + <span v-else>{{ role.policies.alwaysMarkNsfw.value ? i18n.ts.yes : i18n.ts.no }}</span> + <span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.alwaysMarkNsfw)"></i></span> + </template> + <div class="_gaps"> + <MkSwitch v-model="role.policies.alwaysMarkNsfw.useDefault" :readonly="readonly"> + <template #label>{{ i18n.ts._role.useBaseValue }}</template> + </MkSwitch> + <MkSwitch v-model="role.policies.alwaysMarkNsfw.value" :disabled="role.policies.alwaysMarkNsfw.useDefault" :readonly="readonly"> + <template #label>{{ i18n.ts.enable }}</template> + </MkSwitch> + <MkRange v-model="role.policies.alwaysMarkNsfw.priority" :min="0" :max="2" :step="1" easing :text-converter="(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.pinMax, 'pinLimit'])"> <template #label>{{ i18n.ts._role._options.pinMax }}</template> <template #suffix> @@ -409,6 +433,7 @@ import { watch } from 'vue'; import { throttle } from 'throttle-debounce'; import RolesEditorFormula from './RolesEditorFormula.vue'; import MkInput from '@/components/MkInput.vue'; +import MkColorInput from '@/components/MkColorInput.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkFolder from '@/components/MkFolder.vue'; @@ -475,6 +500,7 @@ const save = throttle(100, () => { isAdministrator: role.isAdministrator, isModerator: role.isModerator, isPublic: role.isPublic, + isExplorable: role.isExplorable, asBadge: role.asBadge, canEditMembersByModerator: role.canEditMembersByModerator, policies: role.policies, diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index a1e467edbdf08f628d6973d9c63ad85429de8924..e8dbe1c5f0ed6c16fb3d557266c899d227c2fd91 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -75,6 +75,14 @@ </MkInput> </MkFolder> + <MkFolder v-if="matchQuery([i18n.ts._role._options.alwaysMarkNsfw, 'alwaysMarkNsfw'])"> + <template #label>{{ i18n.ts._role._options.alwaysMarkNsfw }}</template> + <template #suffix>{{ policies.alwaysMarkNsfw ? i18n.ts.yes : i18n.ts.no }}</template> + <MkSwitch v-model="policies.alwaysMarkNsfw"> + <template #label>{{ i18n.ts.enable }}</template> + </MkSwitch> + </MkFolder> + <MkFolder v-if="matchQuery([i18n.ts._role._options.pinMax, 'pinLimit'])"> <template #label>{{ i18n.ts._role._options.pinMax }}</template> <template #suffix>{{ policies.pinLimit }}</template> diff --git a/packages/frontend/src/pages/admin/server-rules.vue b/packages/frontend/src/pages/admin/server-rules.vue new file mode 100644 index 0000000000000000000000000000000000000000..85781c0bd00cd128a503f2b0d922dd7d964fa398 --- /dev/null +++ b/packages/frontend/src/pages/admin/server-rules.vue @@ -0,0 +1,128 @@ +<template> +<div> + <MkStickyContainer> + <template #header><XHeader :tabs="headerTabs"/></template> + <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> + <div class="_gaps_m"> + <div>{{ i18n.ts._serverRules.description }}</div> + <Sortable + v-model="serverRules" + class="_gaps_m" + :item-key="(_, i) => i" + :animation="150" + :handle="'.' + $style.itemHandle" + @start="e => e.item.classList.add('active')" + @end="e => e.item.classList.remove('active')" + > + <template #item="{element,index}"> + <div :class="$style.item"> + <div :class="$style.itemHeader"> + <div :class="$style.itemNumber" v-text="String(index + 1)"/> + <span :class="$style.itemHandle"><i class="ti ti-menu"/></span> + <button class="_button" :class="$style.itemRemove" @click="remove(index)"><i class="ti ti-x"></i></button> + </div> + <MkInput v-model="serverRules[index]"/> + </div> + </template> + </Sortable> + <div :class="$style.commands"> + <MkButton rounded @click="serverRules.push('')"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> + <MkButton primary rounded :class="$style.buttonSave" @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> + </div> + </div> + </MkSpacer> + </MkStickyContainer> +</div> +</template> + +<script lang="ts" setup> +import { defineAsyncComponent } from 'vue'; +import XHeader from './_header_.vue'; +import * as os from '@/os'; +import { fetchInstance, instance } from '@/instance'; +import { i18n } from '@/i18n'; +import { definePageMetadata } from '@/scripts/page-metadata'; +import MkButton from '@/components/MkButton.vue'; +import MkInput from '@/components/MkInput.vue'; + +const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); + +let serverRules: string[] = $ref(instance.serverRules); + +const save = async () => { + await os.apiWithDialog('admin/update-meta', { + serverRules, + }); + fetchInstance(); +}; + +const remove = (index: number): void => { + serverRules.splice(index, 1); +}; + +const headerTabs = $computed(() => []); + +definePageMetadata({ + title: i18n.ts.serverRules, + icon: 'ti ti-checkbox', +}); +</script> + +<style lang="scss" module> +.item { + display: block; + color: var(--navFg); +} + +.itemHeader { + display: flex; + margin-bottom: 8px; + align-items: center; +} + +.itemHandle { + display: flex; + width: 40px; + height: 40px; + align-items: center; + justify-content: center; + cursor: move; +} + +.itemNumber { + display: flex; + background-color: var(--accentedBg); + color: var(--accent); + font-size: 14px; + font-weight: bold; + width: 28px; + height: 28px; + align-items: center; + justify-content: center; + border-radius: 999px; + margin-right: 8px; +} + +.itemEdit { + width: 100%; + max-width: 100%; + min-width: 100%; +} + +.itemRemove { + width: 40px; + height: 40px; + color: var(--error); + margin-left: auto; + border-radius: 6px; + + &:hover { + background: var(--X5); + } +} + +.commands { + display: flex; + gap: 16px; +} +</style> diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index 65e64930d5261792f1f3b03d7db98c1d50c88e72..7ec3c381f3a33815a24a4330b0d0c8d2db093c6f 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -13,11 +13,6 @@ <template #label>{{ i18n.ts.instanceDescription }}</template> </MkTextarea> - <MkInput v-model="tosUrl"> - <template #prefix><i class="ti ti-link"></i></template> - <template #label>{{ i18n.ts.tosUrl }}</template> - </MkInput> - <FormSplit :min-width="300"> <MkInput v-model="maintainerName"> <template #label>{{ i18n.ts.maintainerName }}</template> @@ -36,14 +31,6 @@ <FormSection> <div class="_gaps_s"> - <MkSwitch v-model="enableRegistration"> - <template #label>{{ i18n.ts.enableRegistration }}</template> - </MkSwitch> - - <MkSwitch v-model="emailRequiredForSignup"> - <template #label>{{ i18n.ts.emailRequiredForSignup }}</template> - </MkSwitch> - <MkSwitch v-model="enableChartsForRemoteUser"> <template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template> </MkSwitch> @@ -73,11 +60,9 @@ <template #label>{{ i18n.ts.backgroundImageUrl }}</template> </MkInput> - <MkInput v-model="themeColor"> - <template #prefix><i class="ti ti-palette"></i></template> + <MkColorInput v-model="themeColor"> <template #label>{{ i18n.ts.themeColor }}</template> - <template #caption>#RRGGBB</template> - </MkInput> + </MkColorInput> <MkTextarea v-model="defaultLightTheme"> <template #label>{{ i18n.ts.instanceDefaultLightTheme }}</template> @@ -166,10 +151,10 @@ import { fetchInstance } from '@/instance'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import MkButton from '@/components/MkButton.vue'; +import MkColorInput from '@/components/MkColorInput.vue'; let name: string | null = $ref(null); let description: string | null = $ref(null); -let tosUrl: string | null = $ref(null); let maintainerName: string | null = $ref(null); let maintainerEmail: string | null = $ref(null); let iconUrl: string | null = $ref(null); @@ -180,8 +165,6 @@ let defaultLightTheme: any = $ref(null); let defaultDarkTheme: any = $ref(null); let pinnedUsers: string = $ref(''); let cacheRemoteFiles: boolean = $ref(false); -let enableRegistration: boolean = $ref(false); -let emailRequiredForSignup: boolean = $ref(false); let enableServiceWorker: boolean = $ref(false); let enableChartsForRemoteUser: boolean = $ref(false); let enableChartsForFederatedInstances: boolean = $ref(false); @@ -194,7 +177,6 @@ async function init() { const meta = await os.api('admin/meta'); name = meta.name; description = meta.description; - tosUrl = meta.tosUrl; iconUrl = meta.iconUrl; bannerUrl = meta.bannerUrl; backgroundImageUrl = meta.backgroundImageUrl; @@ -205,8 +187,6 @@ async function init() { maintainerEmail = meta.maintainerEmail; pinnedUsers = meta.pinnedUsers.join('\n'); cacheRemoteFiles = meta.cacheRemoteFiles; - enableRegistration = !meta.disableRegistration; - emailRequiredForSignup = meta.emailRequiredForSignup; enableServiceWorker = meta.enableServiceWorker; enableChartsForRemoteUser = meta.enableChartsForRemoteUser; enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances; @@ -220,7 +200,6 @@ function save() { os.apiWithDialog('admin/update-meta', { name, description, - tosUrl, iconUrl, bannerUrl, backgroundImageUrl, @@ -231,8 +210,6 @@ function save() { maintainerEmail, pinnedUsers: pinnedUsers.split('\n'), cacheRemoteFiles, - disableRegistration: !enableRegistration, - emailRequiredForSignup, enableServiceWorker, enableChartsForRemoteUser, enableChartsForFederatedInstances, diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue index 9cb440d2bb5c5846aab4a5aa812ade64a358b298..a74ab40473a5b6e05be491db1a85fbcf0c28ef8d 100644 --- a/packages/frontend/src/pages/channel-editor.vue +++ b/packages/frontend/src/pages/channel-editor.vue @@ -11,6 +11,10 @@ <template #label>{{ i18n.ts.description }}</template> </MkTextarea> + <MkColorInput v-model="color"> + <template #label>{{ i18n.ts.color }}</template> + </MkColorInput> + <div> <MkButton v-if="bannerId == null" @click="setBannerImage"><i class="ti ti-plus"></i> {{ i18n.ts._channel.setBanner }}</MkButton> <div v-else-if="bannerUrl"> @@ -42,8 +46,9 @@ </div> </MkFolder> - <div> + <div class="_buttons"> <MkButton primary @click="save()"><i class="ti ti-device-floppy"></i> {{ channelId ? i18n.ts.save : i18n.ts.create }}</MkButton> + <MkButton v-if="channelId" danger @click="archive()"><i class="ti ti-trash"></i> {{ i18n.ts.archive }}</MkButton> </div> </div> </MkSpacer> @@ -55,6 +60,7 @@ import { computed, ref, watch, defineAsyncComponent } from 'vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; +import MkColorInput from '@/components/MkColorInput.vue'; import { selectFile } from '@/scripts/select-file'; import * as os from '@/os'; import { useRouter } from '@/router'; @@ -75,6 +81,7 @@ let name = $ref(null); let description = $ref(null); let bannerUrl = $ref<string | null>(null); let bannerId = $ref<string | null>(null); +let color = $ref('#000'); const pinnedNotes = ref([]); watch(() => bannerId, async () => { @@ -101,6 +108,7 @@ async function fetchChannel() { pinnedNotes.value = channel.pinnedNoteIds.map(id => ({ id, })); + color = channel.color; } fetchChannel(); @@ -128,6 +136,7 @@ function save() { description: description, bannerId: bannerId, pinnedNoteIds: pinnedNotes.value.map(x => x.id), + color: color, }; if (props.channelId) { @@ -143,6 +152,23 @@ function save() { } } +async function archive() { + const { canceled } = await os.confirm({ + type: 'warning', + title: i18n.t('channelArchiveConfirmTitle', { name: name }), + text: i18n.ts.channelArchiveConfirmDescription, + }); + + if (canceled) return; + + os.api('channels/update', { + channelId: props.channelId, + isArchived: true, + }).then(() => { + os.success(); + }); +} + function setBannerImage(evt) { selectFile(evt.currentTarget ?? evt.target, null).then(file => { bannerId = file.id; diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 437c1fae31b9bd4532cf300df8b10873037f225d..af1b4d2056eec41e6d89833c7d7b9f34ace6028c 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -28,6 +28,8 @@ </MkFoldableSection> </div> <div v-if="channel && tab === 'timeline'" class="_gaps"> + <MkInfo v-if="channel.isArchived" warn>{{ i18n.ts.thisChannelArchived }}</MkInfo> + <!-- スマホ・タブレットã®å ´åˆã€ã‚ーボードãŒè¡¨ç¤ºã•ã‚Œã‚‹ã¨æŠ•ç¨¿ãŒè¦‹ã¥ã‚‰ããªã‚‹ã®ã§ã€ãƒ‡ã‚¹ã‚¯ãƒˆãƒƒãƒ—å ´åˆã®ã¿è‡ªå‹•ã§ãƒ•ã‚©ãƒ¼ã‚«ã‚¹ã‚’当ã¦ã‚‹ --> <MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/> @@ -36,6 +38,17 @@ <div v-else-if="tab === 'featured'"> <MkNotes :pagination="featuredPagination"/> </div> + <div v-else-if="tab === 'search'"> + <div class="_gaps"> + <div> + <MkInput v-model="searchQuery"> + <template #prefix><i class="ti ti-search"></i></template> + </MkInput> + <MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton> + </div> + <MkNotes v-if="searchPagination" :key="searchQuery" :pagination="searchPagination"/> + </div> + </div> </MkSpacer> <template #footer> <div :class="$style.footer"> @@ -63,8 +76,10 @@ import { deviceKind } from '@/scripts/device-kind'; import MkNotes from '@/components/MkNotes.vue'; import { url } from '@/config'; import MkButton from '@/components/MkButton.vue'; +import MkInput from '@/components/MkInput.vue'; import { defaultStore } from '@/store'; import MkNote from '@/components/MkNote.vue'; +import MkInfo from '@/components/MkInfo.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; const router = useRouter(); @@ -76,6 +91,8 @@ const props = defineProps<{ let tab = $ref('timeline'); let channel = $ref(null); let favorited = $ref(false); +let searchQuery = $ref(''); +let searchPagination = $ref(); const featuredPagination = $computed(() => ({ endpoint: 'notes/featured' as const, limit: 10, @@ -123,6 +140,21 @@ async function unfavorite() { }); } +async function search() { + const query = searchQuery.toString().trim(); + + if (query == null) return; + + searchPagination = { + endpoint: 'notes/search', + limit: 10, + params: { + query: searchQuery, + channelId: channel.id, + }, + }; +} + const headerActions = $computed(() => { if (channel && channel.userId) { const share = { @@ -160,6 +192,10 @@ const headerTabs = $computed(() => [{ key: 'featured', title: i18n.ts.featured, icon: 'ti ti-bolt', +}, { + key: 'search', + title: i18n.ts.search, + icon: 'ti ti-search', }]); definePageMetadata(computed(() => channel ? { @@ -170,7 +206,7 @@ definePageMetadata(computed(() => channel ? { <style lang="scss" module> .main { - min-height: calc(var(--containerHeight) - (var(--stickyTop, 0px) + var(--stickyBottom, 0px))); + min-height: calc(100cqh - (var(--stickyTop, 0px) + var(--stickyBottom, 0px))); } .footer { diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue index 70e7705d1d3ed10e2889610ca6b29b59135649be..e670cdd864787e89bffa4121d0e1b42c62e7a77f 100644 --- a/packages/frontend/src/pages/channels.vue +++ b/packages/frontend/src/pages/channels.vue @@ -96,7 +96,7 @@ const ownedPagination = { async function search() { const query = searchQuery.toString().trim(); - if (query == null || query === '') return; + if (query == null) return; const type = searchType.toString().trim(); diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue index 59cb3262b785b7bae2aea6c6592bf4927e7b5a56..3f13f0787d6a0d17ee8b2bea9aff448b156eb277 100644 --- a/packages/frontend/src/pages/custom-emojis-manager.vue +++ b/packages/frontend/src/pages/custom-emojis-manager.vue @@ -15,9 +15,10 @@ <div v-if="selectMode" class="_buttons"> <MkButton inline @click="selectAll">Select all</MkButton> <MkButton inline @click="setCategoryBulk">Set category</MkButton> + <MkButton inline @click="setTagBulk">Set tag</MkButton> <MkButton inline @click="addTagBulk">Add tag</MkButton> <MkButton inline @click="removeTagBulk">Remove tag</MkButton> - <MkButton inline @click="setTagBulk">Set tag</MkButton> + <MkButton inline @click="setLisenceBulk">Set Lisence</MkButton> <MkButton inline danger @click="delBulk">Delete</MkButton> </div> <MkPagination ref="emojisPaginationComponent" :pagination="pagination"> @@ -221,6 +222,18 @@ const setCategoryBulk = async () => { emojisPaginationComponent.value.reload(); }; +const setLisenceBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'License', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/set-license-bulk', { + ids: selectedEmojis.value, + license: result, + }); + emojisPaginationComponent.value.reload(); +}; + const addTagBulk = async () => { const { canceled, result } = await os.inputText({ title: 'Tag', diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue index 35edcc7cdad0af2bd30ed3f9dcd9cbf2db56f4f5..816825e5b6e5649d7062cb20e2939196664a8b87 100644 --- a/packages/frontend/src/pages/flash/flash-edit.vue +++ b/packages/frontend/src/pages/flash/flash-edit.vue @@ -33,7 +33,7 @@ import MkTextarea from '@/components/MkTextarea.vue'; import MkInput from '@/components/MkInput.vue'; import { useRouter } from '@/router'; -const PRESET_DEFAULT = `/// @ 0.13.1 +const PRESET_DEFAULT = `/// @ 0.13.2 var name = "" @@ -51,7 +51,7 @@ Ui:render([ ]) `; -const PRESET_OMIKUJI = `/// @ 0.13.1 +const PRESET_OMIKUJI = `/// @ 0.13.2 // ユーザーã”ã¨ã«æ—¥æ›¿ã‚ã‚Šã®ãŠã¿ãã˜ã®ãƒ—リセット // é¸æŠžè‚¢ @@ -94,7 +94,7 @@ Ui:render([ ]) `; -const PRESET_SHUFFLE = `/// @ 0.13.1 +const PRESET_SHUFFLE = `/// @ 0.13.2 // å·»ã戻ã—å¯èƒ½ãªæ–‡å—シャッフルã®ãƒ—リセット let string = "ペペãƒãƒ³ãƒãƒ¼ãƒŽ" @@ -173,7 +173,7 @@ var cursor = 0 do() `; -const PRESET_QUIZ = `/// @ 0.13.1 +const PRESET_QUIZ = `/// @ 0.13.2 let title = '地ç†ã‚¯ã‚¤ã‚º' let qas = [{ @@ -286,7 +286,7 @@ qaEls.push(Ui:C:container({ Ui:render(qaEls) `; -const PRESET_TIMELINE = `/// @ 0.13.1 +const PRESET_TIMELINE = `/// @ 0.13.2 // APIリクエストを行ã„ãƒãƒ¼ã‚«ãƒ«ã‚¿ã‚¤ãƒ ラインを表示ã™ã‚‹ãƒ—リセット @fetch() { @@ -305,6 +305,11 @@ const PRESET_TIMELINE = `/// @ 0.13.1 // ãã‚Œãžã‚Œã®ãƒŽãƒ¼ãƒˆã”ã¨ã«UIè¦ç´ ä½œæˆ let noteEls = [] each (let note, notes) { + // 表示åã‚’è¨å®šã—ã¦ã„ãªã„アカウントã¯idを表示 + let userName = if Core:type(note.user.name) == "str" note.user.name else note.user.username + // リノートもã—ãã¯ãƒ¡ãƒ‡ã‚£ã‚¢ãƒ»æŠ•ç¥¨ã®ã¿ã§æœ¬æ–‡ãŒç„¡ã„ノートã«ä»£æ›¿è¡¨ç¤ºæ–‡ã‚’è¨å®š + let noteText = if Core:type(note.text) == "str" note.text else "(リノートもã—ãã¯ãƒ¡ãƒ‡ã‚£ã‚¢ãƒ»æŠ•ç¥¨ã®ã¿ã®ãƒŽãƒ¼ãƒˆï¼‰" + let el = Ui:C:container({ bgColor: "#444" fgColor: "#fff" @@ -312,11 +317,11 @@ const PRESET_TIMELINE = `/// @ 0.13.1 rounded: true children: [ Ui:C:mfm({ - text: note.user.name + text: userName bold: true }) Ui:C:mfm({ - text: note.text + text: noteText }) ] }) diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue index 1fae7686e5f95fbbc8976f110eac81a98bbdea21..cafcee0c33981e76337557dab26c8a8f45b760d8 100644 --- a/packages/frontend/src/pages/gallery/edit.vue +++ b/packages/frontend/src/pages/gallery/edit.vue @@ -2,7 +2,7 @@ <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :content-max="800" :margin-min="16" :margin-max="32"> - <FormSuspense :p="init"> + <FormSuspense :p="init" class="_gaps"> <MkInput v-model="title"> <template #label>{{ i18n.ts.title }}</template> </MkInput> @@ -11,7 +11,7 @@ <template #label>{{ i18n.ts.description }}</template> </MkTextarea> - <div class=""> + <div class="_gaps_s"> <div v-for="file in files" :key="file.id" class="wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }"> <div class="name">{{ file.name }}</div> <button v-tooltip="i18n.ts.remove" class="remove _button" @click="remove(file)"><i class="ti ti-x"></i></button> @@ -21,10 +21,12 @@ <MkSwitch v-model="isSensitive">{{ i18n.ts.markAsSensitive }}</MkSwitch> - <MkButton v-if="postId" primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> - <MkButton v-else primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.publish }}</MkButton> + <div class="_buttons"> + <MkButton v-if="postId" primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> + <MkButton v-else primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.publish }}</MkButton> - <MkButton v-if="postId" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + <MkButton v-if="postId" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + </div> </FormSuspense> </MkSpacer> </MkStickyContainer> diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue index 768a48746c95c3dd965f7d9472a5a6c347da5e24..86201e8e0c87748b8e97c0d69fdeac9f7375c90b 100644 --- a/packages/frontend/src/pages/my-lists/list.vue +++ b/packages/frontend/src/pages/my-lists/list.vue @@ -131,7 +131,7 @@ definePageMetadata(computed(() => list ? { <style lang="scss" module> .main { - min-height: calc(var(--containerHeight) - (var(--stickyTop, 0px) + var(--stickyBottom, 0px))); + min-height: calc(100cqh - (var(--stickyTop, 0px) + var(--stickyBottom, 0px))); } .userItem { diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue index ffeb8ba285c8c544727fdc4ac825fbc8d161b2ec..e97a4b07f121d8cc97592d0732fd2855e866efbf 100644 --- a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue +++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue @@ -33,8 +33,8 @@ const emit = defineEmits<{ let file: any = $ref(null); async function choose() { - os.selectDriveFile(false).then((fileResponse: any) => { - file = fileResponse; + os.selectDriveFile(false).then((fileResponse) => { + file = fileResponse[0]; emit('update:modelValue', { ...props.modelValue, fileId: fileResponse.id, diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue index f2645394a2aab6df82db846b283f66ea96b64ce1..fe39c594ba868b10ac348e49607a70718fa4f2e4 100644 --- a/packages/frontend/src/pages/role.vue +++ b/packages/frontend/src/pages/role.vue @@ -1,8 +1,16 @@ <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template> - - <MkSpacer v-if="tab === 'users'" :content-max="1200"> + <MKSpacer v-if="!(typeof error === 'undefined')" :content-max="1200"> + <div :class="$style.root"> + <img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> + <p :class="$style.text"> + <i class="ti ti-alert-triangle"></i> + {{ error }} + </p> + </div> + </MKSpacer> + <MkSpacer v-else-if="tab === 'users'" :content-max="1200"> <div class="_gaps_s"> <div v-if="role">{{ role.description }}</div> <MkUserList :pagination="users" :extractor="(item) => item.user"/> @@ -13,7 +21,6 @@ </MkSpacer> </MkStickyContainer> </template> - <script lang="ts" setup> import { computed, watch } from 'vue'; import * as os from '@/os'; @@ -21,6 +28,7 @@ import MkUserList from '@/components/MkUserList.vue'; import { definePageMetadata } from '@/scripts/page-metadata'; import { i18n } from '@/i18n'; import MkTimeline from '@/components/MkTimeline.vue'; +import { instanceName } from '@/config'; const props = withDefaults(defineProps<{ role: string; @@ -31,12 +39,21 @@ const props = withDefaults(defineProps<{ let tab = $ref(props.initialTab); let role = $ref(); +let error = $ref(); watch(() => props.role, () => { os.api('roles/show', { roleId: props.role, }).then(res => { role = res; + document.title = `${role?.name} | ${instanceName}`; + }).catch((err) => { + if (err.code === 'NO_SUCH_ROLE') { + error = i18n.ts.noRole; + } else { + error = i18n.ts.somethingHappened; + } + document.title = `${error} | ${instanceName}`; }); }, { immediate: true }); @@ -63,4 +80,23 @@ definePageMetadata(computed(() => ({ icon: 'ti ti-badge', }))); </script> +<style lang="scss" module> +.root { + padding: 32px; + text-align: center; + align-items: center; +} + +.text { + margin: 0 0 8px 0; +} + +.img { + vertical-align: bottom; + width: 128px; + height: 128px; + margin-bottom: 16px; + border-radius: 16px; +} +</style> diff --git a/packages/frontend/src/pages/settings/account-info.vue b/packages/frontend/src/pages/settings/account-stats.vue similarity index 94% rename from packages/frontend/src/pages/settings/account-info.vue rename to packages/frontend/src/pages/settings/account-stats.vue index 584808b0b4cdcadeebd0b91848f55810c4f6788d..a0f1541b401682b30311085d7bad7bbabff9560e 100644 --- a/packages/frontend/src/pages/settings/account-info.vue +++ b/packages/frontend/src/pages/settings/account-stats.vue @@ -1,18 +1,6 @@ <template> <div class="_gaps_m"> - <MkKeyValue> - <template #key>ID</template> - <template #value><span class="_monospace">{{ $i.id }}</span></template> - </MkKeyValue> - - <FormSection> - <MkKeyValue> - <template #key>{{ i18n.ts.registeredDate }}</template> - <template #value><MkTime :time="$i.createdAt" mode="detail"/></template> - </MkKeyValue> - </FormSection> - - <FormSection v-if="stats"> + <FormSection v-if="stats" first> <template #label>{{ i18n.ts.statistics }}</template> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ i18n.ts.notesCount }}</template> diff --git a/packages/frontend/src/pages/settings/delete-account.vue b/packages/frontend/src/pages/settings/delete-account.vue deleted file mode 100644 index c6e79165c5aee970f9fad58d32fe3cb241e6e5ac..0000000000000000000000000000000000000000 --- a/packages/frontend/src/pages/settings/delete-account.vue +++ /dev/null @@ -1,52 +0,0 @@ -<template> -<div class="_gaps_m"> - <FormInfo warn>{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo> - <FormInfo>{{ i18n.ts._accountDelete.sendEmail }}</FormInfo> - <MkButton v-if="!$i.isDeleted" danger @click="deleteAccount">{{ i18n.ts._accountDelete.requestAccountDelete }}</MkButton> - <MkButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</MkButton> -</div> -</template> - -<script lang="ts" setup> -import FormInfo from '@/components/MkInfo.vue'; -import MkButton from '@/components/MkButton.vue'; -import * as os from '@/os'; -import { signout, $i } from '@/account'; -import { i18n } from '@/i18n'; -import { definePageMetadata } from '@/scripts/page-metadata'; - -async function deleteAccount() { - { - const { canceled } = await os.confirm({ - type: 'warning', - text: i18n.ts.deleteAccountConfirm, - }); - if (canceled) return; - } - - const { canceled, result: password } = await os.inputText({ - title: i18n.ts.password, - type: 'password', - }); - if (canceled) return; - - await os.apiWithDialog('i/delete-account', { - password: password, - }); - - await os.alert({ - title: i18n.ts._accountDelete.started, - }); - - await signout(); -} - -const headerActions = $computed(() => []); - -const headerTabs = $computed(() => []); - -definePageMetadata({ - title: i18n.ts._accountDelete.accountDelete, - icon: 'ti ti-alert-triangle', -}); -</script> diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue index d3fb422e01d41bc5c8039b7f46eac4f9a818a8cd..73c2b2e6040e44c6b2c7a6418f47e0539131bd33 100644 --- a/packages/frontend/src/pages/settings/drive.vue +++ b/packages/frontend/src/pages/settings/drive.vue @@ -119,6 +119,13 @@ function saveProfile() { os.api('i/update', { alwaysMarkNsfw: !!alwaysMarkNsfw, autoSensitive: !!autoSensitive, + }).catch(err => { + os.alert({ + type: 'error', + title: i18n.ts.error, + text: err.message, + }); + alwaysMarkNsfw = true; }); } diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index 904fd3f95291fbcc8bf2422d5d2ded54d78e41ee..ba0f3274fc827826b7efac411bb189c1134d715a 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -20,24 +20,71 @@ <option value="desktop"><i class="ti ti-device-desktop"/> {{ i18n.ts.desktop }}</option> </MkRadios> - <MkSwitch v-model="showFixedPostForm">{{ i18n.ts.showFixedPostForm }}</MkSwitch> - <MkSwitch v-model="showFixedPostFormInChannel">{{ i18n.ts.showFixedPostFormInChannel }}</MkSwitch> + <FormSection> + <div class="_gaps_s"> + <MkSwitch v-model="showFixedPostForm">{{ i18n.ts.showFixedPostForm }}</MkSwitch> + <MkSwitch v-model="showFixedPostFormInChannel">{{ i18n.ts.showFixedPostFormInChannel }}</MkSwitch> + </div> + </FormSection> <FormSection> - <template #label>{{ i18n.ts.behavior }}</template> + <template #label>{{ i18n.ts.displayOfNote }}</template> <div class="_gaps_m"> <div class="_gaps_s"> - <MkSwitch v-model="imageNewTab">{{ i18n.ts.openImageInNewTab }}</MkSwitch> - <MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch> + <MkSwitch v-model="showNoteActionsOnlyHover">{{ i18n.ts.showNoteActionsOnlyHover }}</MkSwitch> + <MkSwitch v-model="showClipButtonInNoteFooter">{{ i18n.ts.showClipButtonInNoteFooter }}</MkSwitch> + <MkSwitch v-model="largeNoteReactions">{{ i18n.ts.largeNoteReactions }}</MkSwitch> + <MkSwitch v-model="collapseRenotes">{{ i18n.ts.collapseRenotes }}</MkSwitch> + <MkSwitch v-model="advancedMfm">{{ i18n.ts.enableAdvancedMfm }}</MkSwitch> + <MkSwitch v-if="advancedMfm" v-model="animatedMfm">{{ i18n.ts.enableAnimatedMfm }}</MkSwitch> + <MkSwitch v-model="showGapBetweenNotesInTimeline">{{ i18n.ts.showGapBetweenNotesInTimeline }}</MkSwitch> + <MkSwitch v-model="loadRawImages">{{ i18n.ts.loadRawImages }}</MkSwitch> <MkSwitch v-model="useReactionPickerForContextMenu">{{ i18n.ts.useReactionPickerForContextMenu }}</MkSwitch> </div> - <MkSelect v-model="serverDisconnectedBehavior"> - <template #label>{{ i18n.ts.whenServerDisconnected }}</template> - <option value="reload">{{ i18n.ts._serverDisconnectedBehavior.reload }}</option> - <option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option> - <option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option> + + <MkSelect v-model="instanceTicker"> + <template #label>{{ i18n.ts.instanceTicker }}</template> + <option value="none">{{ i18n.ts._instanceTicker.none }}</option> + <option value="remote">{{ i18n.ts._instanceTicker.remote }}</option> + <option value="always">{{ i18n.ts._instanceTicker.always }}</option> </MkSelect> + + <MkSelect v-model="nsfw"> + <template #label>{{ i18n.ts.nsfw }}</template> + <option value="respect">{{ i18n.ts._nsfw.respect }}</option> + <option value="ignore">{{ i18n.ts._nsfw.ignore }}</option> + <option value="force">{{ i18n.ts._nsfw.force }}</option> + </MkSelect> + <!-- + <MkRadios v-model="mediaListWithOneImageAppearance"> + <template #label>{{ i18n.ts.mediaListWithOneImageAppearance }}</template> + <option value="expand">{{ i18n.ts.default }}</option> + <option value="16_9">{{ i18n.t('limitTo', { x: '16:9' }) }}</option> + <option value="1_1">{{ i18n.t('limitTo', { x: '1:1' }) }}</option> + <option value="2_3">{{ i18n.t('limitTo', { x: '2:3' }) }}</option> + </MkRadios> + --> + </div> + </FormSection> + + <FormSection> + <template #label>{{ i18n.ts.notificationDisplay }}</template> + + <div class="_gaps_m"> + <MkRadios v-model="notificationPosition"> + <template #label>{{ i18n.ts.position }}</template> + <option value="leftTop"><i class="ti ti-align-box-left-top"></i> {{ i18n.ts.leftTop }}</option> + <option value="rightTop"><i class="ti ti-align-box-right-top"></i> {{ i18n.ts.rightTop }}</option> + <option value="leftBottom"><i class="ti ti-align-box-left-bottom"></i> {{ i18n.ts.leftBottom }}</option> + <option value="rightBottom"><i class="ti ti-align-box-right-bottom"></i> {{ i18n.ts.rightBottom }}</option> + </MkRadios> + + <MkRadios v-model="notificationStackAxis"> + <template #label>{{ i18n.ts.stackAxis }}</template> + <option value="vertical"><i class="ti ti-carousel-vertical"></i> {{ i18n.ts.vertical }}</option> + <option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option> + </MkRadios> </div> </FormSection> @@ -46,22 +93,15 @@ <div class="_gaps_m"> <div class="_gaps_s"> - <MkSwitch v-model="showNoteActionsOnlyHover">{{ i18n.ts.showNoteActionsOnlyHover }}</MkSwitch> - <MkSwitch v-model="showClipButtonInNoteFooter">{{ i18n.ts.showClipButtonInNoteFooter }}</MkSwitch> - <MkSwitch v-model="largeNoteReactions">{{ i18n.ts.largeNoteReactions }}</MkSwitch> - <MkSwitch v-model="collapseRenotes">{{ i18n.ts.collapseRenotes }}</MkSwitch> - <MkSwitch v-model="advancedMfm">{{ i18n.ts.enableAdvancedMfm }}</MkSwitch> - <MkSwitch v-if="advancedMfm" v-model="animatedMfm">{{ i18n.ts.enableAnimatedMfm }}</MkSwitch> <MkSwitch v-model="reduceAnimation">{{ i18n.ts.reduceUiAnimation }}</MkSwitch> <MkSwitch v-model="useBlurEffect">{{ i18n.ts.useBlurEffect }}</MkSwitch> <MkSwitch v-model="useBlurEffectForModal">{{ i18n.ts.useBlurEffectForModal }}</MkSwitch> - <MkSwitch v-model="showGapBetweenNotesInTimeline">{{ i18n.ts.showGapBetweenNotesInTimeline }}</MkSwitch> - <MkSwitch v-model="loadRawImages">{{ i18n.ts.loadRawImages }}</MkSwitch> <MkSwitch v-model="disableShowingAnimatedImages">{{ i18n.ts.disableShowingAnimatedImages }}</MkSwitch> <MkSwitch v-model="squareAvatars">{{ i18n.ts.squareAvatars }}</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="enableDataSaverMode">{{ i18n.ts.dataSaver }}</MkSwitch> </div> <div> <MkRadios v-model="emojiStyle"> @@ -84,27 +124,29 @@ </FormSection> <FormSection> - <MkSwitch v-model="aiChanMode">{{ i18n.ts.aiChanMode }}</MkSwitch> - </FormSection> - - <MkSelect v-model="instanceTicker"> - <template #label>{{ i18n.ts.instanceTicker }}</template> - <option value="none">{{ i18n.ts._instanceTicker.none }}</option> - <option value="remote">{{ i18n.ts._instanceTicker.remote }}</option> - <option value="always">{{ i18n.ts._instanceTicker.always }}</option> - </MkSelect> + <template #label>{{ i18n.ts.behavior }}</template> - <MkSelect v-model="nsfw"> - <template #label>{{ i18n.ts.nsfw }}</template> - <option value="respect">{{ i18n.ts._nsfw.respect }}</option> - <option value="ignore">{{ i18n.ts._nsfw.ignore }}</option> - <option value="force">{{ i18n.ts._nsfw.force }}</option> - </MkSelect> + <div class="_gaps_m"> + <div class="_gaps_s"> + <MkSwitch v-model="imageNewTab">{{ i18n.ts.openImageInNewTab }}</MkSwitch> + <MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch> + </div> + <MkSelect v-model="serverDisconnectedBehavior"> + <template #label>{{ i18n.ts.whenServerDisconnected }}</template> + <option value="reload">{{ i18n.ts._serverDisconnectedBehavior.reload }}</option> + <option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option> + <option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option> + </MkSelect> + <MkRange v-model="numberOfPageCache" :min="1" :max="10" :step="1" easing> + <template #label>{{ i18n.ts.numberOfPageCache }}</template> + <template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template> + </MkRange> + </div> + </FormSection> - <MkRange v-model="numberOfPageCache" :min="1" :max="10" :step="1" easing> - <template #label>{{ i18n.ts.numberOfPageCache }}</template> - <template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template> - </MkRange> + <FormSection> + <MkSwitch v-model="aiChanMode">{{ i18n.ts.aiChanMode }}</MkSwitch> + </FormSection> <FormLink to="/settings/deck">{{ i18n.ts.deck }}</FormLink> @@ -160,6 +202,7 @@ const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer')); const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages')); const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds')); const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages')); +const enableDataSaverMode = computed(defaultStore.makeGetterSetter('enableDataSaverMode')); const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab')); const nsfw = computed(defaultStore.makeGetterSetter('nsfw')); const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm')); @@ -170,6 +213,9 @@ const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfin const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu')); const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars')); const aiChanMode = computed(defaultStore.makeGetterSetter('aiChanMode')); +const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('mediaListWithOneImageAppearance')); +const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition')); +const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis')); watch(lang, () => { miLocalStorage.setItem('lang', lang.value as string); diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue index a8274f560139a0055b8a26e99d005a54848b5380..89b4104020f2d3b38585cb7f1006321ab44aae53 100644 --- a/packages/frontend/src/pages/settings/import-export.vue +++ b/packages/frontend/src/pages/settings/import-export.vue @@ -32,7 +32,7 @@ <MkButton primary :class="$style.button" inline @click="exportFollowing()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> </div> </MkFolder> - <MkFolder> + <MkFolder v-if="$i && !$i.movedTo"> <template #label>{{ i18n.ts.import }}</template> <template #icon><i class="ti ti-upload"></i></template> <MkButton primary :class="$style.button" inline @click="importFollowing($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton> @@ -47,7 +47,7 @@ <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> + <MkFolder v-if="$i && !$i.movedTo"> <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> @@ -62,7 +62,7 @@ <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> + <MkFolder v-if="$i && !$i.movedTo"> <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> @@ -77,13 +77,28 @@ <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> + <MkFolder v-if="$i && !$i.movedTo"> <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> </MkFolder> </div> </FormSection> + <FormSection> + <template #label><i class="ti ti-antenna"></i> {{ i18n.ts.antennas }}</template> + <div class="_gaps_s"> + <MkFolder> + <template #label>{{ i18n.ts.export }}</template> + <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"> + <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> + </MkFolder> + </div> + </FormSection> </div> </template> @@ -97,6 +112,7 @@ import * as os from '@/os'; import { selectFile } from '@/scripts/select-file'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { $i } from '@/account'; const excludeMutingUsers = ref(false); const excludeInactiveUsers = ref(false); @@ -150,6 +166,10 @@ const exportMuting = () => { os.api('i/export-mute', {}).then(onExportSuccess).catch(onError); }; +const exportAntennas = () => { + os.api('i/export-antennas', {}).then(onExportSuccess).catch(onError); +}; + const importFollowing = async (ev) => { const file = await selectFile(ev.currentTarget ?? ev.target); os.api('i/import-following', { fileId: file.id }).then(onImportSuccess).catch(onError); @@ -170,6 +190,11 @@ const importBlocking = async (ev) => { os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError); }; +const importAntennas = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + os.api('i/import-antennas', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; + const headerActions = $computed(() => []); const headerTabs = $computed(() => []); diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue index 17af7417fda47e72548bc9f1a6d17267775e36ca..34a962ef4cf86849170c2a56ffa67065df8ba1f5 100644 --- a/packages/frontend/src/pages/settings/index.vue +++ b/packages/frontend/src/pages/settings/index.vue @@ -164,12 +164,12 @@ const menuDef = computed(() => [{ text: i18n.ts.importAndExport, to: '/settings/import-export', active: currentPage?.route.name === 'import-export', - }, /*{ + }, { icon: 'ti ti-plane', - text: i18n.ts.accountMigration, + text: `${i18n.ts.accountMigration} (${i18n.ts.experimental})`, to: '/settings/migration', active: currentPage?.route.name === 'migration', - },*/ { + }, { icon: 'ti ti-dots', text: i18n.ts.other, to: '/settings/other', diff --git a/packages/frontend/src/pages/settings/migration.vue b/packages/frontend/src/pages/settings/migration.vue index 2ef8af7481d1b9b86c3fe51a3f4de2f7b07d6c7d..541992875e5bd009cb753d2fb415aa6f0ffb0af1 100644 --- a/packages/frontend/src/pages/settings/migration.vue +++ b/packages/frontend/src/pages/settings/migration.vue @@ -1,63 +1,121 @@ <template> <div class="_gaps_m"> - <FormSection first> + <FormInfo warn> + {{ i18n.ts.thisIsExperimentalFeature }} + </FormInfo> + <MkFolder :default-open="true"> + <template #icon><i class="ti ti-plane-arrival"></i></template> + <template #label>{{ i18n.ts._accountMigration.moveFrom }}</template> + <template #caption>{{ i18n.ts._accountMigration.moveFromSub }}</template> + + <div class="_gaps_m"> + <FormInfo> + {{ i18n.ts._accountMigration.moveFromDescription }} + </FormInfo> + <div> + <MkButton :disabled="accountAliases.length >= 10" inline style="margin-right: 8px;" @click="add"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> + <MkButton inline primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> + </div> + <div class="_gaps"> + <MkInput v-for="(_, i) in accountAliases" v-model="accountAliases[i]"> + <template #prefix><i class="ti ti-plane-arrival"></i></template> + <template #label>{{ i18n.t('_accountMigration.moveFromLabel', { n: i + 1 }) }}</template> + </MkInput> + </div> + </div> + </MkFolder> + + <MkFolder :default-open="!!$i?.movedTo"> + <template #icon><i class="ti ti-plane-departure"></i></template> <template #label>{{ i18n.ts._accountMigration.moveTo }}</template> - <MkInput v-model="moveToAccount" manual-save> - <template #prefix><i class="ti ti-plane-departure"></i></template> - <template #label>{{ i18n.ts._accountMigration.moveToLabel }}</template> - </MkInput> - </FormSection> - <FormInfo warn>{{ i18n.ts._accountMigration.moveAccountDescription }}</FormInfo> - <FormSection> - <template #label>{{ i18n.ts._accountMigration.moveFrom }}</template> - <MkInput v-model="accountAlias" manual-save> - <template #prefix><i class="ti ti-plane-arrival"></i></template> - <template #label>{{ i18n.ts._accountMigration.moveFromLabel }}</template> - </MkInput> - </FormSection> - <FormInfo warn>{{ i18n.ts._accountMigration.moveFromDescription }}</FormInfo> + <div class="_gaps_m"> + <FormInfo>{{ i18n.ts._accountMigration.moveAccountDescription }}</FormInfo> + + <template v-if="$i && !$i.movedTo"> + <FormInfo>{{ i18n.ts._accountMigration.moveAccountHowTo }}</FormInfo> + <FormInfo warn>{{ i18n.ts._accountMigration.moveCannotBeUndone }}</FormInfo> + + <MkInput v-model="moveToAccount"> + <template #prefix><i class="ti ti-plane-departure"></i></template> + <template #label>{{ i18n.ts._accountMigration.moveToLabel }}</template> + </MkInput> + <MkButton inline danger :disabled="!moveToAccount" @click="move"> + <i class="ti ti-check"></i> {{ i18n.ts._accountMigration.startMigration }} + </MkButton> + </template> + <template v-else-if="$i"> + <FormInfo>{{ i18n.ts._accountMigration.postMigrationNote }}</FormInfo> + <FormInfo warn>{{ i18n.ts._accountMigration.movedAndCannotBeUndone }}</FormInfo> + <div>{{ i18n.ts._accountMigration.movedTo }}</div> + <MkUserInfo v-if="movedTo" :user="movedTo" class="_panel _shadow" /> + </template> + </div> + </MkFolder> </div> </template> <script lang="ts" setup> -import { ref, watch } from 'vue'; -import FormSection from '@/components/form/section.vue'; +import { ref } from 'vue'; import FormInfo from '@/components/MkInfo.vue'; import MkInput from '@/components/MkInput.vue'; +import MkButton from '@/components/MkButton.vue'; +import MkFolder from '@/components/MkFolder.vue'; +import MkUserInfo from '@/components/MkUserInfo.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { $i } from '@/account'; +import { toString } from 'misskey-js/built/acct'; +import { UserDetailed } from 'misskey-js/built/entities'; +import { unisonReload } from '@/scripts/unison-reload'; const moveToAccount = ref(''); -const accountAlias = ref(''); +const movedTo = ref<UserDetailed>(); +const accountAliases = ref(['']); + +async function init() { + if ($i?.movedTo) { + movedTo.value = await os.api('users/show', { userId: $i.movedTo }); + } else { + moveToAccount.value = ''; + } + + if ($i?.alsoKnownAs && $i.alsoKnownAs.length > 0) { + const alsoKnownAs = await os.api('users/show', { userIds: $i.alsoKnownAs }); + accountAliases.value = (alsoKnownAs && alsoKnownAs.length > 0) ? alsoKnownAs.map(user => `@${toString(user)}`) : ['']; + } else { + accountAliases.value = ['']; + } +} async function move(): Promise<void> { const account = moveToAccount.value; const confirm = await os.confirm({ type: 'warning', - text: i18n.t('migrationConfirm', { account: account.toString() }), + text: i18n.t('_accountMigration.migrationConfirm', { account }), }); if (confirm.canceled) return; - os.apiWithDialog('i/move', { + await os.apiWithDialog('i/move', { moveToAccount: account, }); + unisonReload(); +} + +function add(): void { + accountAliases.value.push(''); } async function save(): Promise<void> { - const account = accountAlias.value; - os.apiWithDialog('i/known-as', { - alsoKnownAs: account, + const alsoKnownAs = accountAliases.value.map(alias => alias.trim()).filter(alias => alias !== ''); + const i = await os.apiWithDialog('i/update', { + alsoKnownAs, }); + $i.alsoKnownAs = i.alsoKnownAs; + init(); } -watch(accountAlias, async () => { - await save(); -}); - -watch(moveToAccount, async () => { - await move(); -}); +init(); definePageMetadata({ title: i18n.ts.accountMigration, diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue index 892ea61e75d02f4134e7912302159eae5394dea2..776305d723c087986b38d0f644384847e48e138d 100644 --- a/packages/frontend/src/pages/settings/other.vue +++ b/packages/frontend/src/pages/settings/other.vue @@ -1,32 +1,85 @@ <template> <div class="_gaps_m"> + <!-- <MkSwitch v-model="$i.injectFeaturedNote" @update:model-value="onChangeInjectFeaturedNote"> - {{ i18n.ts.showFeaturedNotesInTimeline }} + <template #label>{{ i18n.ts.showFeaturedNotesInTimeline }}</template> </MkSwitch> + --> <!-- <MkSwitch v-model="reportError">{{ i18n.ts.sendErrorReports }}<template #caption>{{ i18n.ts.sendErrorReportsDescription }}</template></MkSwitch> --> - <FormLink to="/settings/account-info">{{ i18n.ts.accountInfo }}</FormLink> + <FormSection first> + <div class="_gaps_s"> + <MkFolder> + <template #icon><i class="ti ti-info-circle"></i></template> + <template #label>{{ i18n.ts.accountInfo }}</template> + + <div class="_gaps_m"> + <MkKeyValue> + <template #key>ID</template> + <template #value><span class="_monospace">{{ $i.id }}</span></template> + </MkKeyValue> + + <MkKeyValue> + <template #key>{{ i18n.ts.registeredDate }}</template> + <template #value><MkTime :time="$i.createdAt" mode="detail"/></template> + </MkKeyValue> + + <FormLink to="/settings/account-stats"><template #icon><i class="ti ti-info-circle"></i></template>{{ i18n.ts.statistics }}</FormLink> + </div> + </MkFolder> + + <MkFolder> + <template #icon><i class="ti ti-alert-triangle"></i></template> + <template #label>{{ i18n.ts.closeAccount }}</template> - <FormLink to="/registry"><template #icon><i class="ti ti-adjustments"></i></template>{{ i18n.ts.registry }}</FormLink> + <div class="_gaps_m"> + <FormInfo warn>{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo> + <FormInfo>{{ i18n.ts._accountDelete.sendEmail }}</FormInfo> + <MkButton v-if="!$i.isDeleted" danger @click="deleteAccount">{{ i18n.ts._accountDelete.requestAccountDelete }}</MkButton> + <MkButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</MkButton> + </div> + </MkFolder> - <FormLink to="/settings/delete-account"><template #icon><i class="ti ti-alert-triangle"></i></template>{{ i18n.ts.closeAccount }}</FormLink> + <MkFolder> + <template #icon><i class="ti ti-flask"></i></template> + <template #label>{{ i18n.ts.experimentalFeatures }}</template> + + <div class="_gaps_m"> + <MkSwitch v-model="enableCondensedLineForAcct"> + <template #label>Enable condensed line for acct</template> + </MkSwitch> + </div> + </MkFolder> + </div> + </FormSection> + + <FormSection> + <FormLink to="/registry"><template #icon><i class="ti ti-adjustments"></i></template>{{ i18n.ts.registry }}</FormLink> + </FormSection> </div> </template> <script lang="ts" setup> -import { computed } from 'vue'; +import { computed, watch } from 'vue'; import MkSwitch from '@/components/MkSwitch.vue'; import FormLink from '@/components/form/link.vue'; +import MkFolder from '@/components/MkFolder.vue'; +import FormInfo from '@/components/MkInfo.vue'; +import MkKeyValue from '@/components/MkKeyValue.vue'; +import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; import { defaultStore } from '@/store'; -import { $i } from '@/account'; +import { signout, $i } from '@/account'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { unisonReload } from '@/scripts/unison-reload'; +import FormSection from '@/components/form/section.vue'; const reportError = computed(defaultStore.makeGetterSetter('reportError')); +const enableCondensedLineForAcct = computed(defaultStore.makeGetterSetter('enableCondensedLineForAcct')); function onChangeInjectFeaturedNote(v) { os.api('i/update', { @@ -36,6 +89,48 @@ function onChangeInjectFeaturedNote(v) { }); } +async function deleteAccount() { + { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.ts.deleteAccountConfirm, + }); + if (canceled) return; + } + + const { canceled, result: password } = await os.inputText({ + title: i18n.ts.password, + type: 'password', + }); + if (canceled) return; + + await os.apiWithDialog('i/delete-account', { + password: password, + }); + + await os.alert({ + title: i18n.ts._accountDelete.started, + }); + + await signout(); +} + +async function reloadAsk() { + const { canceled } = await os.confirm({ + type: 'info', + text: i18n.ts.reloadToApplySetting, + }); + if (canceled) return; + + unisonReload(); +} + +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 092b9a9cc8eddb7a8a117f7b6d0c407c18f6062e..6613ce4c1d8639758668f1276e4d48c1c11c001c 100644 --- a/packages/frontend/src/pages/settings/preferences-backups.vue +++ b/packages/frontend/src/pages/settings/preferences-backups.vue @@ -88,6 +88,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [ 'squareAvatars', 'numberOfPageCache', 'aiChanMode', + 'mediaListWithOneImageAppearance', ]; const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [ 'lightTheme', diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index a5f6c11f89d52819423f12f9d6ec595416b72320..6ffd6826108839dbe650f317d9f0f62c96ba0d2b 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -37,19 +37,40 @@ <template #icon><i class="ti ti-list"></i></template> <template #label>{{ i18n.ts._profile.metadataEdit }}</template> - <div class="_gaps_m"> - <FormSplit v-for="(record, i) in fields" :min-width="250"> - <MkInput v-model="record.name" small> - <template #label>{{ i18n.ts._profile.metadataLabel }} #{{ i + 1 }}</template> - </MkInput> - <MkInput v-model="record.value" small> - <template #label>{{ i18n.ts._profile.metadataContent }} #{{ i + 1 }}</template> - </MkInput> - </FormSplit> - <div> + <div :class="$style.metadataRoot"> + <div :class="$style.metadataMargin"> <MkButton :disabled="fields.length >= 16" inline style="margin-right: 8px;" @click="addField"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> + <MkButton v-if="!fieldEditMode" :disabled="fields.length <= 1" inline danger style="margin-right: 8px;" @click="fieldEditMode = !fieldEditMode"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + <MkButton v-else inline style="margin-right: 8px;" @click="fieldEditMode = !fieldEditMode"><i class="ti ti-arrows-sort"></i> {{ i18n.ts.rearrange }}</MkButton> <MkButton inline primary @click="saveFields"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> </div> + + <Sortable + v-model="fields" + class="_gaps_s" + item-key="id" + :animation="150" + :handle="'.' + $style.dragItemHandle" + @start="e => e.item.classList.add('active')" + @end="e => e.item.classList.remove('active')" + > + <template #item="{element, index}"> + <div :class="$style.fieldDragItem"> + <button v-if="!fieldEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button> + <button v-if="fieldEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteField(index)"><i class="ti ti-x"></i></button> + <div :class="$style.dragItemForm"> + <FormSplit :min-width="200"> + <MkInput v-model="element.name" small> + <template #label>{{ i18n.ts._profile.metadataLabel }}</template> + </MkInput> + <MkInput v-model="element.value" small> + <template #label>{{ i18n.ts._profile.metadataContent }}</template> + </MkInput> + </FormSplit> + </div> + </div> + </template> + </Sortable> </div> </MkFolder> <template #caption>{{ i18n.ts._profile.metadataDescription }}</template> @@ -76,7 +97,7 @@ </template> <script lang="ts" setup> -import { computed, reactive, watch } from 'vue'; +import { computed, reactive, ref, watch, defineAsyncComponent, onMounted, onUnmounted } from 'vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkTextarea from '@/components/MkTextarea.vue'; @@ -94,6 +115,8 @@ import { definePageMetadata } from '@/scripts/page-metadata'; import { claimAchievement } from '@/scripts/achievements'; import { defaultStore } from '@/store'; +const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); + const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance')); const profile = reactive({ @@ -113,22 +136,28 @@ watch(() => profile, () => { deep: true, }); -const fields = reactive($i.fields.map(field => ({ name: field.name, value: field.value }))); +const fields = ref($i?.fields.map(field => ({ id: Math.random().toString(), name: field.name, value: field.value })) ?? []); +const fieldEditMode = ref(false); function addField() { - fields.push({ + fields.value.push({ + id: Math.random().toString(), name: '', value: '', }); } -while (fields.length < 4) { +while (fields.value.length < 4) { addField(); } +function deleteField(index: number) { + fields.value.splice(index, 1); +} + function saveFields() { os.apiWithDialog('i/update', { - fields: fields.filter(field => field.name !== '' && field.value !== ''), + fields: fields.value.filter(field => field.name !== '' && field.value !== '').map(field => ({ name: field.name, value: field.value })), }); } @@ -248,3 +277,60 @@ definePageMetadata({ } } </style> +<style lang="scss" module> +.metadataRoot { + container-type: inline-size; +} + +.metadataMargin { + margin-bottom: 1.5em; +} + +.fieldDragItem { + display: flex; + padding-bottom: .75em; + align-items: flex-end; + border-bottom: solid 0.5px var(--divider); + + &:last-child { + border-bottom: 0; + } + + /* (drag button) 32px + (drag button margin) 8px + (input width) 200px * 2 + (input gap) 12px = 452px */ + @container (max-width: 452px) { + align-items: center; + } +} + +.dragItemHandle { + cursor: grab; + width: 32px; + height: 32px; + margin: 0 8px 0 0; + opacity: 0.5; + flex-shrink: 0; + + &:active { + cursor: grabbing; + } +} + +.dragItemRemove { + @extend .dragItemHandle; + + color: #ff2a2a; + opacity: 1; + cursor: pointer; + + &:hover, &:focus { + opacity: .7; + } + &:active { + cursor: pointer; + } +} + +.dragItemForm { + flex-grow: 1; +} +</style> diff --git a/packages/frontend/src/pages/timeline.tutorial.vue b/packages/frontend/src/pages/timeline.tutorial.vue index 0d0c932a5c00e94220387c763bff4891964cc791..32228d28f41c808b4f5575fc83c615c4a5d039d5 100644 --- a/packages/frontend/src/pages/timeline.tutorial.vue +++ b/packages/frontend/src/pages/timeline.tutorial.vue @@ -1,7 +1,7 @@ <template> <div :class="$style.container"> <div :class="$style.title"> - <div :class="$style.titleText"><i class="ti ti-info-circle"></i> {{ i18n.ts._tutorial.title }}</div> + <div :class="$style.titleText"><i class="ti ti-info-circle"></i> {{ i18n.ts._timelineTutorial.title }}</div> <div :class="$style.step"> <button class="_button" :class="$style.stepArrow" :disabled="tutorial === 0" @click="tutorial--"> <i class="ti ti-chevron-left"></i> @@ -12,66 +12,30 @@ </button> </div> </div> + <div v-if="tutorial === 0" :class="$style.body"> - <div>{{ i18n.ts._tutorial.step1_1 }}</div> - <div>{{ i18n.ts._tutorial.step1_2 }}</div> - <div>{{ i18n.ts._tutorial.step1_3 }}</div> + <div>{{ i18n.t('_timelineTutorial.step1_1', { name: instance.name ?? host }) }}</div> + <div>{{ i18n.t('_timelineTutorial.step1_2', { name: instance.name ?? host }) }}</div> </div> <div v-else-if="tutorial === 1" :class="$style.body"> - <div>{{ i18n.ts._tutorial.step2_1 }}</div> - <div>{{ i18n.ts._tutorial.step2_2 }}</div> - <MkA class="_link" to="/settings/profile">{{ i18n.ts.editProfile }}</MkA> + <div>{{ i18n.ts._timelineTutorial.step2_1 }}</div> + <div>{{ i18n.t('_timelineTutorial.step2_2', { name: instance.name ?? host }) }}</div> </div> <div v-else-if="tutorial === 2" :class="$style.body"> - <div>{{ i18n.ts._tutorial.step3_1 }}</div> - <div>{{ i18n.ts._tutorial.step3_2 }}</div> - <div>{{ i18n.ts._tutorial.step3_3 }}</div> - <small :class="$style.small">{{ i18n.ts._tutorial.step3_4 }}</small> + <div>{{ i18n.ts._timelineTutorial.step3_1 }}</div> + <div>{{ i18n.ts._timelineTutorial.step3_2 }}</div> </div> <div v-else-if="tutorial === 3" :class="$style.body"> - <div>{{ i18n.ts._tutorial.step4_1 }}</div> - <div>{{ i18n.ts._tutorial.step4_2 }}</div> - </div> - <div v-else-if="tutorial === 4" :class="$style.body"> - <div>{{ i18n.ts._tutorial.step5_1 }}</div> - <I18n :src="i18n.ts._tutorial.step5_2" tag="div"> - <template #featured> - <MkA class="_link" to="/explore">{{ i18n.ts.featured }}</MkA> - </template> - <template #explore> - <MkA class="_link" to="/explore#users">{{ i18n.ts.explore }}</MkA> - </template> - </I18n> - <div>{{ i18n.ts._tutorial.step5_3 }}</div> - <small :class="$style.small">{{ i18n.ts._tutorial.step5_4 }}</small> - </div> - <div v-else-if="tutorial === 5" :class="$style.body"> - <div>{{ i18n.ts._tutorial.step6_1 }}</div> - <div>{{ i18n.ts._tutorial.step6_2 }}</div> - <div>{{ i18n.ts._tutorial.step6_3 }}</div> - </div> - <div v-else-if="tutorial === 6" :class="$style.body"> - <div>{{ i18n.ts._tutorial.step7_1 }}</div> - <I18n :src="i18n.ts._tutorial.step7_2" tag="div"> - <template #help> - <a href="https://misskey-hub.net/help.html" target="_blank" class="_link">{{ i18n.ts.help }}</a> - </template> - </I18n> - <div>{{ i18n.ts._tutorial.step7_3 }}</div> - </div> - <div v-else-if="tutorial === 7" :class="$style.body"> - <div>{{ i18n.ts._tutorial.step8_1 }}</div> - <div>{{ i18n.ts._tutorial.step8_2 }}</div> - <small :class="$style.small">{{ i18n.ts._tutorial.step8_3 }}</small> + <div>{{ i18n.ts._timelineTutorial.step4_1 }}</div> + <div>{{ i18n.ts._timelineTutorial.step4_2 }}</div> </div> <div :class="$style.footer"> <template v-if="tutorial === tutorialsNumber - 1"> - <MkPushNotificationAllowButton :class="$style.footerItem" primary show-only-to-register @click="tutorial = -1"/> - <MkButton :class="$style.footerItem" :primary="false" @click="tutorial = -1">{{ i18n.ts.noThankYou }}</MkButton> + <MkButton :class="$style.footerItem" primary rounded gradate @click="tutorial = -1">{{ i18n.ts.done }} <i class="ti ti-check"></i></MkButton> </template> <template v-else> - <MkButton :class="$style.footerItem" primary @click="tutorial++"><i class="ti ti-check"></i> {{ i18n.ts.next }}</MkButton> + <MkButton :class="$style.footerItem" primary rounded gradate @click="tutorial++">{{ i18n.ts.next }} <i class="ti ti-arrow-right"></i></MkButton> </template> </div> </div> @@ -80,15 +44,16 @@ <script lang="ts" setup> import { computed } from 'vue'; import MkButton from '@/components/MkButton.vue'; -import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; +import { instance } from '@/instance'; +import { host } from '@/config'; -const tutorialsNumber = 8; +const tutorialsNumber = 4; const tutorial = computed({ - get() { return defaultStore.reactiveState.tutorial.value || 0; }, - set(value) { defaultStore.set('tutorial', value); }, + get() { return defaultStore.reactiveState.timelineTutorial.value || 0; }, + set(value) { defaultStore.set('timelineTutorial', value); }, }); </script> diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 9f13f7a1dd33c717fa6835e5d299912a21d5f985..1bf4cdc99ae4ee1ae6222fff1b65133a3c8f5b48 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -3,7 +3,7 @@ <template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :display-my-avatar="true"/></template> <MkSpacer :content-max="800"> <div ref="rootEl" v-hotkey.global="keymap"> - <XTutorial v-if="$i && defaultStore.reactiveState.tutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/> + <XTutorial v-if="$i && defaultStore.reactiveState.timelineTutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/> <MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/> <div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 8c3478d8f2260fc36248fb482ae2259cfba41365..5bc157826891eb6705f7992d3f12bda4fb3ef263 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -7,7 +7,7 @@ <!-- <div class="punished" v-if="user.isSilenced"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i> {{ i18n.ts.userSilenced }}</div> --> <div class="profile _gaps"> - <MkAccountMoved v-if="user.movedToUri" :host="user.movedToUri.host" :acct="user.movedToUri.username"/> + <MkAccountMoved v-if="user.movedTo" :moved-to="user.movedTo"/> <MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/> <div :key="user.id" class="main _panel"> @@ -21,6 +21,9 @@ <span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--badge);"><i class="ti ti-shield"></i></span> <span v-if="user.isLocked" :title="i18n.ts.isLocked"><i class="ti ti-lock"></i></span> <span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ti ti-robot"></i></span> + <button v-if="!isEditingMemo && !memoDraft" class="_button add-note-button" @click="showMemoTextarea"> + <i class="ti ti-edit"/> {{ i18n.ts.addMemo }} + </button> </div> </div> <span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ i18n.ts.followsYou }}</span> @@ -45,6 +48,22 @@ {{ role.name }} </span> </div> + <div v-if="iAmModerator" class="moderationNote"> + <MkTextarea v-model="moderationNote" manual-save> + <template #label>Moderation note</template> + </MkTextarea> + </div> + <div v-if="isEditingMemo || memoDraft" class="memo" :class="{'no-memo': !memoDraft}"> + <div class="heading" v-text="i18n.ts.memo"/> + <textarea + ref="memoTextareaEl" + v-model="memoDraft" + rows="1" + @focus="isEditingMemo = true" + @blur="updateMemo" + @input="adjustMemoTextarea" + /> + </div> <div class="description"> <MkOmit> <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i"/> @@ -113,13 +132,14 @@ </template> <script lang="ts" setup> -import { defineAsyncComponent, computed, onMounted, onUnmounted } from 'vue'; +import { defineAsyncComponent, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'; import calcAge from 's-age'; import * as misskey from 'misskey-js'; import MkNote from '@/components/MkNote.vue'; import MkFollowButton from '@/components/MkFollowButton.vue'; import MkAccountMoved from '@/components/MkAccountMoved.vue'; import MkRemoteCaution from '@/components/MkRemoteCaution.vue'; +import MkTextarea from '@/components/MkTextarea.vue'; import MkOmit from '@/components/MkOmit.vue'; import MkInfo from '@/components/MkInfo.vue'; import { getScrollPosition } from '@/scripts/scroll'; @@ -129,10 +149,11 @@ import { userPage } from '@/filters/user'; import * as os from '@/os'; import { useRouter } from '@/router'; import { i18n } from '@/i18n'; -import { $i } from '@/account'; +import { $i, iAmModerator } from '@/account'; import { dateString } from '@/filters/date'; import { confetti } from '@/scripts/confetti'; import MkNotes from '@/components/MkNotes.vue'; +import { api } from '@/os'; const XPhotos = defineAsyncComponent(() => import('./index.photos.vue')); const XActivity = defineAsyncComponent(() => import('./index.activity.vue')); @@ -151,6 +172,14 @@ let parallaxAnimationId = $ref<null | number>(null); let narrow = $ref<null | boolean>(null); let rootEl = $ref<null | HTMLElement>(null); let bannerEl = $ref<null | HTMLElement>(null); +let memoTextareaEl = $ref<null | HTMLElement>(null); +let memoDraft = $ref(props.user.memo); +let isEditingMemo = $ref(false); +let moderationNote = $ref(props.user.moderationNote); + +watch($$(moderationNote), async () => { + await os.api('admin/update-user-note', { userId: props.user.id, text: moderationNote }); +}); const pagination = { endpoint: 'users/notes' as const, @@ -193,6 +222,31 @@ function parallax() { banner.style.backgroundPosition = `center calc(50% - ${pos}px)`; } +function showMemoTextarea() { + isEditingMemo = true; + nextTick(() => { + memoTextareaEl?.focus(); + }); +} + +function adjustMemoTextarea() { + if (!memoTextareaEl) return; + memoTextareaEl.style.height = '0px'; + memoTextareaEl.style.height = `${memoTextareaEl.scrollHeight}px`; +} + +async function updateMemo() { + await api('users/update-memo', { + memo: memoDraft, + userId: props.user.id, + }); + isEditingMemo = false; +} + +watch([props.user], () => { + memoDraft = props.user.memo; +}); + onMounted(() => { window.requestAnimationFrame(parallaxLoop); narrow = rootEl!.clientWidth < 1000; @@ -208,6 +262,9 @@ onMounted(() => { }); } } + nextTick(() => { + adjustMemoTextarea(); + }); }); onUnmounted(() => { @@ -323,6 +380,16 @@ onUnmounted(() => { font-weight: bold; } } + + > .add-note-button { + background: rgba(0, 0, 0, 0.2); + color: #fff; + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); + border-radius: 24px; + padding: 4px 8px; + font-size: 80%; + } } } } @@ -369,6 +436,43 @@ onUnmounted(() => { } } + > .moderationNote { + margin: 12px 24px 0 154px; + } + + > .memo { + margin: 12px 24px 0 154px; + background: transparent; + color: var(--fg); + border: 1px solid var(--divider); + border-radius: 8px; + padding: 8px; + line-height: 0; + + > .heading { + text-align: left; + color: var(--fgTransparent); + line-height: 1.5; + font-size: 85%; + } + + textarea { + margin: 0; + padding: 0; + resize: none; + border: none; + outline: none; + width: 100%; + height: auto; + min-height: 0; + line-height: 1.5; + color: var(--fg); + overflow: hidden; + background: transparent; + font-family: inherit; + } + } + > .description { padding: 24px 24px 24px 154px; font-size: 0.95em; @@ -504,6 +608,14 @@ onUnmounted(() => { justify-content: center; } + > .moderationNote { + margin: 16px 16px 0 16px; + } + + > .memo { + margin: 16px 16px 0 16px; + } + > .description { padding: 16px; text-align: center; diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue index 4d8d76db18f30e0c6899cb14759792c83642d649..929152bd5abcf86f8e0f960c03f6003864b96ea4 100644 --- a/packages/frontend/src/pages/welcome.entrance.a.vue +++ b/packages/frontend/src/pages/welcome.entrance.a.vue @@ -13,35 +13,7 @@ <MkEmoji :normal="true" :no-style="true" emoji="ðŸ®"/> </div> <div class="contents"> - <div class="main"> - <img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/> - <button class="_button _acrylic menu" @click="showMenu"><i class="ti ti-dots"></i></button> - <div class="fg"> - <h1> - <!-- 背景色ã«ã‚ˆã£ã¦ã¯ãƒã‚´ãŒè¦‹ãˆãªããªã‚‹ã®ã§ã¨ã‚Šã‚ãˆãšç„¡åŠ¹ã« --> - <!-- <img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span> --> - <span class="text">{{ instanceName }}</span> - </h1> - <div class="about"> - <!-- eslint-disable-next-line vue/no-v-html --> - <div class="desc" v-html="meta.description || i18n.ts.headlineMisskey"></div> - </div> - <div v-if="instance.disableRegistration" class="warn"> - <MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo> - </div> - <div class="action _gaps_s"> - <MkButton full rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.joinThisServer }}</MkButton> - <MkButton full rounded @click="exploreOtherServers()">{{ i18n.ts.exploreOtherServers }}</MkButton> - <MkButton full rounded data-cy-signin @click="signin()">{{ i18n.ts.login }}</MkButton> - </div> - </div> - </div> - <div v-if="instance.policies.ltlAvailable" class="tl"> - <div class="title">{{ i18n.ts.letsLookAtTimeline }}</div> - <div class="body"> - <MkTimeline src="local"/> - </div> - </div> + <MkVisitorDashboard/> </div> <div v-if="instances && instances.length > 0" class="federation"> <MarqueeText :duration="40"> @@ -60,16 +32,15 @@ import { } from 'vue'; import { Instance } from 'misskey-js/built/entities'; import XTimeline from './welcome.timeline.vue'; import MarqueeText from '@/components/MkMarquee.vue'; -import XSigninDialog from '@/components/MkSigninDialog.vue'; -import XSignupDialog from '@/components/MkSignupDialog.vue'; -import MkButton from '@/components/MkButton.vue'; import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue'; -import MkTimeline from '@/components/MkTimeline.vue'; import MkInfo from '@/components/MkInfo.vue'; import { instanceName } from '@/config'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { instance } from '@/instance'; +import number from '@/filters/number'; +import MkNumber from '@/components/MkNumber.vue'; +import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue'; let meta = $ref<Instance>(); let instances = $ref<any[]>(); @@ -84,45 +55,6 @@ os.apiGet('federation/instances', { }).then(_instances => { instances = _instances; }); - -function signin() { - os.popup(XSigninDialog, { - autoSet: true, - }, {}, 'closed'); -} - -function signup() { - os.popup(XSignupDialog, { - autoSet: true, - }, {}, 'closed'); -} - -function showMenu(ev) { - os.popupMenu([{ - text: i18n.ts.instanceInfo, - icon: 'ti ti-info-circle', - action: () => { - os.pageWindow('/about'); - }, - }, { - text: i18n.ts.aboutMisskey, - icon: 'ti ti-info-circle', - action: () => { - os.pageWindow('/about-misskey'); - }, - }, null, { - text: i18n.ts.help, - icon: 'ti ti-question-circle', - action: () => { - window.open('https://misskey-hub.net/help.md', '_blank'); - }, - }], ev.currentTarget ?? ev.target); -} - -function exploreOtherServers() { - // TODO: 言語をよã—ãªã« - window.open('https://join.misskey.page/ja-JP/instances', '_blank'); -} </script> <style lang="scss" scoped> @@ -202,89 +134,11 @@ function exploreOtherServers() { position: relative; width: min(430px, calc(100% - 32px)); margin-left: 128px; - padding: 150px 0 100px 0; + padding: 100px 0 100px 0; @media (max-width: 1200px) { margin: auto; } - - > .main { - position: relative; - background: var(--panel); - border-radius: var(--radius); - box-shadow: 0 12px 32px rgb(0 0 0 / 25%); - text-align: center; - - > .icon { - width: 85px; - margin-top: -47px; - border-radius: 100%; - vertical-align: bottom; - } - - > .menu { - position: absolute; - top: 16px; - right: 16px; - width: 32px; - height: 32px; - border-radius: 8px; - font-size: 18px; - } - - > .fg { - position: relative; - z-index: 1; - - > h1 { - display: block; - margin: 0; - padding: 16px 32px 24px 32px; - font-size: 1.4em; - - > .logo { - vertical-align: bottom; - max-height: 120px; - max-width: min(100%, 300px); - } - } - - > .about { - padding: 0 32px; - } - - > .warn { - padding: 32px 32px 0 32px; - } - - > .action { - padding: 32px; - - > * { - line-height: 28px; - } - } - } - } - - > .tl { - position: relative; - background: var(--panel); - border-radius: var(--radius); - overflow: clip; - box-shadow: 0 12px 32px rgb(0 0 0 / 25%); - margin-top: 16px; - - > .title { - padding: 12px 16px; - border-bottom: solid 1px var(--divider); - } - - > .body { - height: 350px; - overflow: auto; - } - } } > .federation { diff --git a/packages/frontend/src/pages/welcome.entrance.b.vue b/packages/frontend/src/pages/welcome.entrance.b.vue deleted file mode 100644 index 03bf174710005a0ed7e2b8a2ccbf899eabc2927f..0000000000000000000000000000000000000000 --- a/packages/frontend/src/pages/welcome.entrance.b.vue +++ /dev/null @@ -1,239 +0,0 @@ -<template> -<div v-if="meta" class="rsqzvsbo"> - <div class="top"> - <MkFeaturedPhotos class="bg"/> - <XTimeline class="tl"/> - <div class="shape"></div> - <div class="main"> - <h1> - <img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span> - </h1> - <div class="about"> - <!-- eslint-disable-next-line vue/no-v-html --> - <div class="desc" v-html="meta.description || i18n.ts.headlineMisskey"></div> - </div> - <div class="action"> - <MkButton class="signup" inline gradate @click="signup()">{{ i18n.ts.signup }}</MkButton> - <MkButton class="signin" inline @click="signin()">{{ i18n.ts.login }}</MkButton> - </div> - <div v-if="onlineUsersCount && stats" class="status"> - <div> - <I18n :src="i18n.ts.nUsers" text-tag="span" class="users"> - <template #n><b>{{ number(stats.originalUsersCount) }}</b></template> - </I18n> - <I18n :src="i18n.ts.nNotes" text-tag="span" class="notes"> - <template #n><b>{{ number(stats.originalNotesCount) }}</b></template> - </I18n> - </div> - <I18n :src="i18n.ts.onlineUsersCount" text-tag="span" class="online"> - <template #n><b>{{ onlineUsersCount }}</b></template> - </I18n> - </div> - </div> - <img src="/client-assets/misskey.svg" class="misskey"/> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import { toUnicode } from 'punycode/'; -import XTimeline from './welcome.timeline.vue'; -import XSigninDialog from '@/components/MkSigninDialog.vue'; -import XSignupDialog from '@/components/MkSignupDialog.vue'; -import MkButton from '@/components/MkButton.vue'; -import MkNote from '@/components/MkNote.vue'; -import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue'; -import { host, instanceName } from '@/config'; -import * as os from '@/os'; -import number from '@/filters/number'; -import { i18n } from '@/i18n'; - -export default defineComponent({ - components: { - MkButton, - MkNote, - XTimeline, - MkFeaturedPhotos, - }, - - data() { - return { - host: toUnicode(host), - instanceName, - meta: null, - stats: null, - tags: [], - onlineUsersCount: null, - i18n, - }; - }, - - created() { - os.api('meta', { detail: true }).then(meta => { - this.meta = meta; - }); - - os.api('stats').then(stats => { - this.stats = stats; - }); - - os.api('get-online-users-count').then(res => { - this.onlineUsersCount = res.count; - }); - - os.api('hashtags/list', { - sort: '+mentionedLocalUsers', - limit: 8, - }).then(tags => { - this.tags = tags; - }); - }, - - methods: { - signin() { - os.popup(XSigninDialog, { - autoSet: true, - }, {}, 'closed'); - }, - - signup() { - os.popup(XSignupDialog, { - autoSet: true, - }, {}, 'closed'); - }, - - showMenu(ev) { - os.popupMenu([{ - text: i18n.t('aboutX', { x: instanceName }), - icon: 'ti ti-info-circle', - action: () => { - os.pageWindow('/about'); - }, - }, { - text: i18n.ts.aboutMisskey, - icon: 'ti ti-info-circle', - action: () => { - os.pageWindow('/about-misskey'); - }, - }, null, { - text: i18n.ts.help, - icon: 'ti ti-question-circle', - action: () => { - window.open('https://misskey-hub.net/help.md', '_blank'); - }, - }], ev.currentTarget ?? ev.target); - }, - - number, - }, -}); -</script> - -<style lang="scss" scoped> -.rsqzvsbo { - > .top { - min-height: 100vh; - box-sizing: border-box; - - > .bg { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - } - - > .tl { - position: absolute; - top: 0; - bottom: 0; - right: 64px; - margin: auto; - width: 500px; - height: calc(100% - 128px); - overflow: hidden; - -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%); - mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%); - } - - > .shape { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: var(--accent); - clip-path: polygon(0% 0%, 40% 0%, 22% 100%, 0% 100%); - } - - > .misskey { - position: absolute; - bottom: 64px; - left: 64px; - width: 160px; - } - - > .main { - position: relative; - width: min(450px, 100%); - padding: 64px; - color: #fff; - font-size: 1.1em; - - @media (max-width: 1200px) { - margin: auto; - } - - > h1 { - display: block; - margin: 0 0 32px 0; - padding: 0; - - > .logo { - vertical-align: bottom; - max-height: 100px; - } - } - - > .about { - padding: 0; - } - - > .action { - margin: 32px 0; - - > * { - line-height: 32px; - } - - > .signup { - background: var(--panel); - color: var(--fg); - } - - > .signin { - background: var(--accent); - color: inherit; - } - } - - > .status { - margin: 32px 0; - border-top: solid 1px rgba(255, 255, 255, 0.5); - font-size: 90%; - - > div { - padding: 16px 0; - - > span:not(:last-child) { - padding-right: 1em; - margin-right: 1em; - border-right: solid 1px rgba(255, 255, 255, 0.5); - } - } - } - } - } -} -</style> diff --git a/packages/frontend/src/pages/welcome.entrance.c.vue b/packages/frontend/src/pages/welcome.entrance.c.vue deleted file mode 100644 index eca4e5764dd5e8d7aca956c4ff7b839822c56472..0000000000000000000000000000000000000000 --- a/packages/frontend/src/pages/welcome.entrance.c.vue +++ /dev/null @@ -1,308 +0,0 @@ -<template> -<div v-if="meta" class="rsqzvsbo"> - <div class="top"> - <MkFeaturedPhotos class="bg"/> - <div class="fade"></div> - <div class="emojis"> - <MkEmoji :normal="true" :no-style="true" emoji="ðŸ‘"/> - <MkEmoji :normal="true" :no-style="true" emoji="â¤"/> - <MkEmoji :normal="true" :no-style="true" emoji="😆"/> - <MkEmoji :normal="true" :no-style="true" emoji="🎉"/> - <MkEmoji :normal="true" :no-style="true" emoji="ðŸ®"/> - </div> - <div class="main"> - <img src="/client-assets/misskey.svg" class="misskey"/> - <div class="form _panel"> - <div class="bg"> - <div class="fade"></div> - </div> - <div class="fg"> - <h1> - <img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span> - </h1> - <div class="about"> - <!-- eslint-disable-next-line vue/no-v-html --> - <div class="desc" v-html="meta.description || i18n.ts.headlineMisskey"></div> - </div> - <div class="action"> - <MkButton inline gradate @click="signup()">{{ i18n.ts.signup }}</MkButton> - <MkButton inline @click="signin()">{{ i18n.ts.login }}</MkButton> - </div> - <div v-if="onlineUsersCount && stats" class="status"> - <div> - <I18n :src="i18n.ts.nUsers" text-tag="span" class="users"> - <template #n><b>{{ number(stats.originalUsersCount) }}</b></template> - </I18n> - <I18n :src="i18n.ts.nNotes" text-tag="span" class="notes"> - <template #n><b>{{ number(stats.originalNotesCount) }}</b></template> - </I18n> - </div> - <I18n :src="i18n.ts.onlineUsersCount" text-tag="span" class="online"> - <template #n><b>{{ onlineUsersCount }}</b></template> - </I18n> - </div> - <button class="_button _acrylic menu" @click="showMenu"><i class="ti ti-dots"></i></button> - </div> - </div> - <nav class="nav"> - <MkA to="/announcements">{{ i18n.ts.announcements }}</MkA> - <MkA to="/explore">{{ i18n.ts.explore }}</MkA> - <MkA to="/channels">{{ i18n.ts.channel }}</MkA> - <MkA to="/featured">{{ i18n.ts.featured }}</MkA> - </nav> - </div> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import { toUnicode } from 'punycode/'; -import XTimeline from './welcome.timeline.vue'; -import XSigninDialog from '@/components/MkSigninDialog.vue'; -import XSignupDialog from '@/components/MkSignupDialog.vue'; -import MkButton from '@/components/MkButton.vue'; -import MkNote from '@/components/MkNote.vue'; -import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue'; -import { host, instanceName } from '@/config'; -import * as os from '@/os'; -import number from '@/filters/number'; -import { i18n } from '@/i18n'; - -export default defineComponent({ - components: { - MkButton, - MkNote, - MkFeaturedPhotos, - XTimeline, - }, - - data() { - return { - host: toUnicode(host), - instanceName, - meta: null, - stats: null, - tags: [], - onlineUsersCount: null, - i18n, - }; - }, - - created() { - os.api('meta', { detail: true }).then(meta => { - this.meta = meta; - }); - - os.api('stats').then(stats => { - this.stats = stats; - }); - - os.api('get-online-users-count').then(res => { - this.onlineUsersCount = res.count; - }); - - os.api('hashtags/list', { - sort: '+mentionedLocalUsers', - limit: 8, - }).then(tags => { - this.tags = tags; - }); - }, - - methods: { - signin() { - os.popup(XSigninDialog, { - autoSet: true, - }, {}, 'closed'); - }, - - signup() { - os.popup(XSignupDialog, { - autoSet: true, - }, {}, 'closed'); - }, - - showMenu(ev) { - os.popupMenu([{ - text: i18n.t('aboutX', { x: instanceName }), - icon: 'ti ti-info-circle', - action: () => { - os.pageWindow('/about'); - }, - }, { - text: i18n.ts.aboutMisskey, - icon: 'ti ti-info-circle', - action: () => { - os.pageWindow('/about-misskey'); - }, - }, null, { - text: i18n.ts.help, - icon: 'ti ti-question-circle', - action: () => { - window.open('https://misskey-hub.net/help.md', '_blank'); - }, - }], ev.currentTarget ?? ev.target); - }, - - number, - }, -}); -</script> - -<style lang="scss" scoped> -.rsqzvsbo { - > .top { - display: flex; - text-align: center; - min-height: 100vh; - box-sizing: border-box; - padding: 16px; - - > .bg { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - } - - > .fade { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.25); - } - - > .emojis { - position: absolute; - bottom: 32px; - left: 35px; - - > * { - margin-right: 8px; - } - - @media (max-width: 1200px) { - display: none; - } - } - - > .main { - position: relative; - width: min(460px, 100%); - margin: auto; - - > .misskey { - width: 150px; - margin-bottom: 16px; - - @media (max-width: 450px) { - width: 130px; - } - } - - > .form { - position: relative; - box-shadow: 0 12px 32px rgb(0 0 0 / 25%); - - > .bg { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 128px; - background-position: center; - background-size: cover; - opacity: 0.75; - - > .fade { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 128px; - background: linear-gradient(0deg, var(--panel), var(--X15)); - } - } - - > .fg { - position: relative; - z-index: 1; - - > h1 { - display: block; - margin: 0; - padding: 32px 32px 24px 32px; - - > .logo { - vertical-align: bottom; - max-height: 120px; - } - } - - > .about { - padding: 0 32px; - } - - > .action { - padding: 32px; - - > * { - line-height: 28px; - } - } - - > .status { - border-top: solid 0.5px var(--divider); - padding: 32px; - font-size: 90%; - - > div { - > span:not(:last-child) { - padding-right: 1em; - margin-right: 1em; - border-right: solid 0.5px var(--divider); - } - } - - > .online { - ::v-deep(b) { - color: #41b781; - } - - ::v-deep(span) { - opacity: 0.7; - } - } - } - - > .menu { - position: absolute; - top: 16px; - right: 16px; - width: 32px; - height: 32px; - border-radius: 8px; - } - } - } - - > .nav { - position: relative; - z-index: 2; - margin-top: 20px; - color: #fff; - text-shadow: 0 0 8px black; - font-size: 0.9em; - - > *:not(:last-child) { - margin-right: 1.5em; - } - } - } - } -} -</style> diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue index 212d156a8349adf5a5eff611c82ae72d2c25f10c..7728d97a6554f348bf763994b521ceaeb0d429a6 100644 --- a/packages/frontend/src/pages/welcome.setup.vue +++ b/packages/frontend/src/pages/welcome.setup.vue @@ -1,8 +1,11 @@ <template> -<form class="mk-setup" @submit.prevent="submit()"> - <h1>Welcome to Misskey!</h1> - <div class="_gaps_m"> - <p>{{ i18n.ts.intro }}</p> +<form :class="$style.root" class="_panel" @submit.prevent="submit()"> + <div :class="$style.title"> + <div>Welcome to Misskey!</div> + <div :class="$style.version">v{{ version }}</div> + </div> + <div class="_gaps_m" style="padding: 32px;"> + <div>{{ i18n.ts.intro }}</div> <MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-admin-username> <template #label>{{ i18n.ts.username }}</template> <template #prefix>@</template> @@ -12,8 +15,8 @@ <template #label>{{ i18n.ts.password }}</template> <template #prefix><i class="ti ti-lock"></i></template> </MkInput> - <div class="bottom"> - <MkButton gradate type="submit" :disabled="submitting" data-cy-admin-ok> + <div> + <MkButton gradate large rounded type="submit" :disabled="submitting" data-cy-admin-ok style="margin: 0 auto;"> {{ submitting ? i18n.ts.processing : i18n.ts.done }}<MkEllipsis v-if="submitting"/> </MkButton> </div> @@ -25,7 +28,7 @@ import { } from 'vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; -import { host } from '@/config'; +import { host, version } from '@/config'; import * as os from '@/os'; import { login } from '@/account'; import { i18n } from '@/i18n'; @@ -54,36 +57,28 @@ function submit() { } </script> -<style lang="scss" scoped> -.mk-setup { +<style lang="scss" module> +.root { border-radius: var(--radius); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); overflow: hidden; max-width: 500px; margin: 32px auto; +} - > h1 { - margin: 0; - font-size: 1.5em; - text-align: center; - padding: 32px; - background: var(--accent); - color: #fff; - } - - > div { - padding: 32px; - background: var(--panel); - - > p { - margin-top: 0; - } +.title { + margin: 0; + font-size: 1.5em; + text-align: center; + padding: 32px; + background: var(--accentedBg); + color: var(--accent); + font-weight: bold; +} - > .bottom { - > * { - margin: 0 auto; - } - } - } +.version { + font-size: 70%; + font-weight: normal; + opacity: 0.7; } </style> diff --git a/packages/frontend/src/pages/welcome.timeline.vue b/packages/frontend/src/pages/welcome.timeline.vue index 6a507ee1ed44eb78c658a6fbedd18c9ffd1c2600..6ec6e3f8633ef977c137b7fcb0c5ecb35aec95bd 100644 --- a/packages/frontend/src/pages/welcome.timeline.vue +++ b/packages/frontend/src/pages/welcome.timeline.vue @@ -33,7 +33,7 @@ import { $i } from '@/account'; let notes = $ref<Note[]>([]); let isScrolling = $ref(false); -let scrollEl = $ref<HTMLElement>(); +let scrollEl = $shallowRef<HTMLElement>(); os.apiGet('notes/featured').then(_notes => { notes = _notes; diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 0769ec2614b59833d087ea2090f46dea6b9ae1d2..e46c1eeb77ebd70fdedc64cc68a463cfbfb6df22 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -164,7 +164,7 @@ export const routes = [{ }, { path: '/migration', name: 'migration', - component: page(() => import('./pages/settings/migration.vue')) + component: page(() => import('./pages/settings/migration.vue')), }, { path: '/custom-css', name: 'general', @@ -174,13 +174,9 @@ export const routes = [{ name: 'profile', component: page(() => import('./pages/settings/accounts.vue')), }, { - path: '/account-info', + path: '/account-stats', name: 'other', - component: page(() => import('./pages/settings/account-info.vue')), - }, { - path: '/delete-account', - name: 'other', - component: page(() => import('./pages/settings/delete-account.vue')), + component: page(() => import('./pages/settings/account-stats.vue')), }, { path: '/other', name: 'other', @@ -427,6 +423,10 @@ export const routes = [{ path: '/other-settings', name: 'other-settings', component: page(() => import('./pages/admin/other-settings.vue')), + }, { + path: '/server-rules', + name: 'server-rules', + component: page(() => import('./pages/admin/server-rules.vue')), }, { path: '/', component: page(() => import('./pages/_empty_.vue')), diff --git a/packages/frontend/src/scripts/achievements.ts b/packages/frontend/src/scripts/achievements.ts index 25e8b71a127827bc19abf17ae69ebd1feb76867a..fbca0057690d9e9d3e194dd8143dca18cf286fc3 100644 --- a/packages/frontend/src/scripts/achievements.ts +++ b/packages/frontend/src/scripts/achievements.ts @@ -60,6 +60,7 @@ export const ACHIEVEMENT_TYPES = [ 'iLoveMisskey', 'foundTreasure', 'client30min', + 'client60min', 'noteDeletedWithin1min', 'postedAtLateNight', 'postedAt0min0sec', @@ -343,6 +344,11 @@ export const ACHIEVEMENT_BADGES = { bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))', frame: 'bronze', }, + 'client60min': { + img: '/fluent-emoji/1f552.png', + bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))', + frame: 'silver', + }, 'noteDeletedWithin1min': { img: '/fluent-emoji/1f5d1.png', bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))', @@ -458,6 +464,7 @@ const claimingQueue = new Set<string>(); export async function claimAchievement(type: typeof ACHIEVEMENT_TYPES[number]) { if ($i == null) return; + if ($i.movedTo) return; if (claimedAchievements.includes(type)) return; claimingQueue.add(type); claimedAchievements.push(type); diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts index 1b47eaa42001aa0169ac811fdc92ff3d85b2e035..b6b7445b6721287b324b59f9bb603e5a205e8157 100644 --- a/packages/frontend/src/scripts/aiscript/api.ts +++ b/packages/frontend/src/scripts/aiscript/api.ts @@ -17,6 +17,7 @@ export function createAiScriptEnv(opts) { title: title.value, text: text.value, }); + return values.NULL; }), 'Mk:confirm': values.FN_NATIVE(async ([title, text, type]) => { const confirm = await os.confirm({ diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/scripts/get-drive-file-menu.ts index 52e610e437cb74f6c3ac18ccf6ac1de17021575a..ed01b490547c186e44abfabf7cabaee1107d3d32 100644 --- a/packages/frontend/src/scripts/get-drive-file-menu.ts +++ b/packages/frontend/src/scripts/get-drive-file-menu.ts @@ -36,6 +36,12 @@ function toggleSensitive(file: Misskey.entities.DriveFile) { os.api('drive/files/update', { fileId: file.id, isSensitive: !file.isSensitive, + }).catch(err => { + os.alert({ + type: 'error', + title: i18n.ts.error, + text: err.message, + }); }); } @@ -74,6 +80,12 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile) { icon: 'ti ti-text-caption', action: () => describe(file), }, null, { + text: i18n.ts.createNoteFromTheFile, + icon: 'ti ti-pencil', + action: () => os.post({ + initialFiles: [file], + }), + }, { text: i18n.ts.copyUrl, icon: 'ti ti-link', action: () => copyUrl(file), diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index d91f0b0eb65662466c7b1e52096b962edd4696fe..c8a610025385a3cebb940abbeefc9a07885a5962 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -211,6 +211,12 @@ export function getNoteMenu(props: { }, {}, 'closed'); } + function showRenotes(): void { + os.popup(defineAsyncComponent(() => import('@/components/MkRenotedUsersDialog.vue')), { + noteId: appearNote.id, + }, {}, 'closed'); + } + async function translate(): Promise<void> { if (props.translation.value != null) return; props.translating.value = true; @@ -241,8 +247,12 @@ export function getNoteMenu(props: { text: i18n.ts.details, action: openDetail, }, { - icon: 'ti ti-users', - text: i18n.ts.reactions, + icon: 'ti ti-repeat', + text: i18n.ts.renotesList, + action: showRenotes, + }, { + icon: 'ti ti-icons', + text: i18n.ts.reactionsList, action: showReactions, }, { icon: 'ti ti-copy', diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts index fe941c77b27fa1fb9735420894a892f1545b4f46..6ff9fb63f1d92ab1717da4461be98aaeca9f3bc2 100644 --- a/packages/frontend/src/scripts/get-user-menu.ts +++ b/packages/frontend/src/scripts/get-user-menu.ts @@ -98,6 +98,27 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router }); } + async function editMemo(): Promise<void> { + const userDetailed = await os.api('users/show', { + userId: user.id, + }); + const { canceled, result } = await os.form(i18n.ts.editMemo, { + memo: { + type: 'string', + required: true, + multiline: true, + label: i18n.ts.memo, + default: userDetailed.memo, + }, + }); + if (canceled) return; + + os.apiWithDialog('users/update-memo', { + memo: result.memo, + userId: user.id, + }); + } + let menu = [{ icon: 'ti ti-at', text: i18n.ts.copyUsername, @@ -123,6 +144,12 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router os.post({ specified: user, initialText: `@${user.username} ` }); }, }, null, { + icon: 'ti ti-pencil', + text: i18n.ts.editMemo, + action: () => { + editMemo(); + }, + }, { type: 'parent', icon: 'ti ti-list', text: i18n.ts.addToList, diff --git a/packages/frontend/src/scripts/please-login.ts b/packages/frontend/src/scripts/please-login.ts index b8fb853cc1a0a661b0028a1dad86b4d157d47070..c101a127f3d4257ea2baef753bac70f5e25fe48b 100644 --- a/packages/frontend/src/scripts/please-login.ts +++ b/packages/frontend/src/scripts/please-login.ts @@ -17,5 +17,5 @@ export function pleaseLogin(path?: string) { }, }, 'closed'); - if (!path) throw new Error('signin required'); + throw new Error('signin required'); } diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/scripts/select-file.ts index ec5f8f65e91faeba628499849fe6510b524c81cc..fe9f0a2447ae22626a81b72c4f9fd18b50d1beb2 100644 --- a/packages/frontend/src/scripts/select-file.ts +++ b/packages/frontend/src/scripts/select-file.ts @@ -6,70 +6,76 @@ import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; import { uploadFile } from '@/scripts/upload'; -function select(src: any, label: string | null, multiple: boolean): Promise<DriveFile | DriveFile[]> { +export function chooseFileFromPc(multiple: boolean, keepOriginal = false): Promise<DriveFile[]> { return new Promise((res, rej) => { - const keepOriginal = ref(defaultStore.state.keepOriginalUploading); - - const chooseFileFromPc = () => { - const input = document.createElement('input'); - input.type = 'file'; - input.multiple = multiple; - input.onchange = () => { - const promises = Array.from(input.files).map(file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal.value)); + const input = document.createElement('input'); + input.type = 'file'; + input.multiple = multiple; + input.onchange = () => { + const promises = Array.from(input.files).map(file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal)); - Promise.all(promises).then(driveFiles => { - res(multiple ? driveFiles : driveFiles[0]); - }).catch(err => { - // アップãƒãƒ¼ãƒ‰ã®ã‚¨ãƒ©ãƒ¼ã¯ uploadFile 内ã§ãƒãƒ³ãƒ‰ãƒªãƒ³ã‚°ã•ã‚Œã¦ã„ã‚‹ãŸã‚アラートダイアãƒã‚°ã‚’出ã—ãŸã‚Šã¯ã—ã¦ã¯ã„ã‘ãªã„ - }); + Promise.all(promises).then(driveFiles => { + res(driveFiles); + }).catch(err => { + // アップãƒãƒ¼ãƒ‰ã®ã‚¨ãƒ©ãƒ¼ã¯ uploadFile 内ã§ãƒãƒ³ãƒ‰ãƒªãƒ³ã‚°ã•ã‚Œã¦ã„ã‚‹ãŸã‚アラートダイアãƒã‚°ã‚’出ã—ãŸã‚Šã¯ã—ã¦ã¯ã„ã‘ãªã„ + }); - // 一応廃棄 - (window as any).__misskey_input_ref__ = null; - }; + // 一応廃棄 + (window as any).__misskey_input_ref__ = null; + }; - // https://qiita.com/fukasawah/items/b9dc732d95d99551013d - // iOS Safari ã§æ£å¸¸ã«å‹•ã‹ã™ç‚ºã®ãŠã¾ã˜ãªã„ - (window as any).__misskey_input_ref__ = input; + // https://qiita.com/fukasawah/items/b9dc732d95d99551013d + // iOS Safari ã§æ£å¸¸ã«å‹•ã‹ã™ç‚ºã®ãŠã¾ã˜ãªã„ + (window as any).__misskey_input_ref__ = input; - input.click(); - }; + input.click(); + }); +} - const chooseFileFromDrive = () => { - os.selectDriveFile(multiple).then(files => { - res(files); - }); - }; +export function chooseFileFromDrive(multiple: boolean): Promise<DriveFile[]> { + return new Promise((res, rej) => { + os.selectDriveFile(multiple).then(files => { + res(files); + }); + }); +} - const chooseFileFromUrl = () => { - os.inputText({ - title: i18n.ts.uploadFromUrl, - type: 'url', - placeholder: i18n.ts.uploadFromUrlDescription, - }).then(({ canceled, result: url }) => { - if (canceled) return; +export function chooseFileFromUrl(): Promise<DriveFile> { + return new Promise((res, rej) => { + os.inputText({ + title: i18n.ts.uploadFromUrl, + type: 'url', + placeholder: i18n.ts.uploadFromUrlDescription, + }).then(({ canceled, result: url }) => { + if (canceled) return; - const marker = Math.random().toString(); // TODO: UUIDã¨ã‹ä½¿ã† + const marker = Math.random().toString(); // TODO: UUIDã¨ã‹ä½¿ã† - const connection = stream.useChannel('main'); - connection.on('urlUploadFinished', urlResponse => { - if (urlResponse.marker === marker) { - res(multiple ? [urlResponse.file] : urlResponse.file); - connection.dispose(); - } - }); + const connection = stream.useChannel('main'); + connection.on('urlUploadFinished', urlResponse => { + if (urlResponse.marker === marker) { + res(urlResponse.file); + connection.dispose(); + } + }); - os.api('drive/files/upload-from-url', { - url: url, - folderId: defaultStore.state.uploadFolder, - marker, - }); + os.api('drive/files/upload-from-url', { + url: url, + folderId: defaultStore.state.uploadFolder, + marker, + }); - os.alert({ - title: i18n.ts.uploadFromUrlRequested, - text: i18n.ts.uploadFromUrlMayTakeTime, - }); + os.alert({ + title: i18n.ts.uploadFromUrlRequested, + text: i18n.ts.uploadFromUrlMayTakeTime, }); - }; + }); + }); +} + +function select(src: any, label: string | null, multiple: boolean): Promise<DriveFile[]> { + return new Promise((res, rej) => { + const keepOriginal = ref(defaultStore.state.keepOriginalUploading); os.popupMenu([label ? { text: label, @@ -81,23 +87,23 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv }, { text: i18n.ts.upload, icon: 'ti ti-upload', - action: chooseFileFromPc, + action: () => chooseFileFromPc(multiple, keepOriginal.value).then(files => res(files)), }, { text: i18n.ts.fromDrive, icon: 'ti ti-cloud', - action: chooseFileFromDrive, + action: () => chooseFileFromDrive(multiple).then(files => res(files)), }, { text: i18n.ts.fromUrl, icon: 'ti ti-link', - action: chooseFileFromUrl, + action: () => chooseFileFromUrl().then(file => res([file])), }], src); }); } export function selectFile(src: any, label: string | null = null): Promise<DriveFile> { - return select(src, label, false) as Promise<DriveFile>; + return select(src, label, false).then(files => files[0]); } export function selectFiles(src: any, label: string | null = null): Promise<DriveFile[]> { - return select(src, label, true) as Promise<DriveFile[]>; + return select(src, label, true); } diff --git a/packages/frontend/src/scripts/show-moved-dialog.ts b/packages/frontend/src/scripts/show-moved-dialog.ts new file mode 100644 index 0000000000000000000000000000000000000000..acb26c36e239c07cf049e8dfece0acb3a372debc --- /dev/null +++ b/packages/frontend/src/scripts/show-moved-dialog.ts @@ -0,0 +1,16 @@ +import * as os from '@/os'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; + +export function showMovedDialog() { + if (!$i) return; + if (!$i.movedTo) return; + + os.alert({ + type: 'error', + title: i18n.ts.accountMovedShort, + text: i18n.ts.operationForbidden, + }); + + throw new Error('account moved'); +} diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/scripts/upload.ts index 9a39652ef504c57b4836f2587eb8376c5acd176e..2dd11c9fa2783928f63e20d49bff357d8dbd5f2c 100644 --- a/packages/frontend/src/scripts/upload.ts +++ b/packages/frontend/src/scripts/upload.ts @@ -83,7 +83,13 @@ export function uploadFile( // TODO: 消ã™ã®ã§ã¯ãªãã¦(ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯çš„ãªã‚¨ãƒ©ãƒ¼ãªã‚‰)å†é€ã§ãるよã†ã«ã—ãŸã„ uploads.value = uploads.value.filter(x => x.id !== id); - if (ev.target?.response) { + if (xhr.status === 413) { + alert({ + type: 'error', + title: i18n.ts.failedToUpload, + text: i18n.ts.cannotUploadBecauseExceedsFileSizeLimit, + }); + } else if (ev.target?.response) { const res = JSON.parse(ev.target.response); if (res.error?.id === 'bec5bd69-fba3-43c9-b4fb-2894b66ad5d2') { alert({ diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index a9350932403d29f0b684a9486f6868396c0ad2e0..245bcbefe11f40277bd9afae24ebb84be74481f2 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -38,7 +38,11 @@ export const pageViewInterruptors: PageViewInterruptor[] = []; // TODO: ãã‚Œãžã‚Œã„ã¡ã„ã¡whereã¨ã‹defaultã¨ã„ã†ã‚ーを付ã‘ãªãゃã„ã‘ãªã„ã®å†—é•·ãªã®ã§ãªã‚“ã¨ã‹ã™ã‚‹(ãŸã 型定義ãŒé¢å€’ã«ãªã‚Šãã†) // ã‚ã¨ã€ç¾è¡Œã®å®šç¾©ã®ä»•æ–¹ãªã‚‰ã€ŒwhereãŒä½•ã§ã‚ã‚‹ã‹ã«é–¢ã‚らãšã‚ーåã®é‡è¤‡ä¸å¯ã€ã¨ã„ã†åˆ¶ç´„を付ã‘られるメリットもã‚ã‚‹ã‹ã‚‰ãã®ãƒ¡ãƒªãƒƒãƒˆã‚’引ã継ã方法も考ãˆãªã„ã¨ã„ã‘ãªã„ export const defaultStore = markRaw(new Storage('base', { - tutorial: { + accountSetupWizard: { + where: 'account', + default: 0, + }, + timelineTutorial: { where: 'account', default: 0, }, @@ -164,7 +168,7 @@ export const defaultStore = markRaw(new Storage('base', { }, animation: { where: 'device', - default: !matchMedia('(prefers-reduced-motion)').matches, + default: !window.matchMedia('(prefers-reduced-motion)').matches, }, animatedMfm: { where: 'device', @@ -182,9 +186,13 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: false, }, + enableDataSaverMode: { + where: 'device', + default: false, + }, disableShowingAnimatedImages: { where: 'device', - default: matchMedia('(prefers-reduced-motion)').matches, + default: window.matchMedia('(prefers-reduced-motion)').matches, }, emojiStyle: { where: 'device', @@ -306,6 +314,22 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: false, }, + mediaListWithOneImageAppearance: { + where: 'device', + default: 'expand' as 'expand' | '16_9' | '1_1' | '2_3', + }, + notificationPosition: { + where: 'device', + default: 'rightBottom' as 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom', + }, + notificationStackAxis: { + where: 'device', + default: 'horizontal' as 'vertical' | 'horizontal', + }, + enableCondensedLineForAcct: { + where: 'device', + default: true, + }, })); // TODO: ä»–ã®ã‚¿ãƒ–ã¨æ°¸ç¶šåŒ–ã•ã‚ŒãŸstateã‚’åŒæœŸ diff --git a/packages/frontend/src/themes/d-botanical.json5 b/packages/frontend/src/themes/d-botanical.json5 index c03b95e2d75854f8afd08d9ec805f72c255137d5..33cf7aa8174502b1226b9645d036bb56b2db6fca 100644 --- a/packages/frontend/src/themes/d-botanical.json5 +++ b/packages/frontend/src/themes/d-botanical.json5 @@ -13,8 +13,7 @@ fgHighlighted: '#fff', divider: 'rgba(255, 255, 255, 0.14)', panel: 'rgb(47, 47, 44)', - panelHeaderBg: '@panel', - panelHeaderDivider: '@divider', + panelHeaderDivider: 'rgba(0, 0, 0, 0)', header: ':alpha<0.7<@panel', navBg: '#363636', renote: '@accent', diff --git a/packages/frontend/src/themes/d-dark.json5 b/packages/frontend/src/themes/d-dark.json5 index d24ce4df6952aa8d7c6fd377297d3baff13896f7..63144e88ea26f36cd9b551acda87b6e2ef292df6 100644 --- a/packages/frontend/src/themes/d-dark.json5 +++ b/packages/frontend/src/themes/d-dark.json5 @@ -13,8 +13,7 @@ fgHighlighted: '#fff', divider: 'rgba(255, 255, 255, 0.14)', panel: '#2d2d2d', - panelHeaderBg: '@panel', - panelHeaderDivider: '@divider', + panelHeaderDivider: 'rgba(0, 0, 0, 0)', header: ':alpha<0.7<@panel', navBg: '#363636', renote: '@accent', diff --git a/packages/frontend/src/themes/d-future.json5 b/packages/frontend/src/themes/d-future.json5 index b6fa1ab0c18d43129f1a806d965e8629f2b18ead..0962a124110a4f655b7e1a312ea0ddd87e91f72a 100644 --- a/packages/frontend/src/themes/d-future.json5 +++ b/packages/frontend/src/themes/d-future.json5 @@ -14,8 +14,7 @@ fgOnAccent: '#000', divider: 'rgba(255, 255, 255, 0.1)', panel: '#18181c', - panelHeaderBg: '@panel', - panelHeaderDivider: '@divider', + panelHeaderDivider: 'rgba(0, 0, 0, 0)', renote: '@accent', mention: '#f2c97d', mentionMe: '@accent', diff --git a/packages/frontend/src/themes/d-green-lime.json5 b/packages/frontend/src/themes/d-green-lime.json5 index a6983b9ac21a6db7dad2925bb82352a0fca0bf7d..9522f534a41b41fc4ff8f8db6009dc1dcef32726 100644 --- a/packages/frontend/src/themes/d-green-lime.json5 +++ b/packages/frontend/src/themes/d-green-lime.json5 @@ -14,8 +14,7 @@ fgOnAccent: '#192320', divider: '#e7fffb24', panel: '#192320', - panelHeaderBg: '@panel', - panelHeaderDivider: '@divider', + panelHeaderDivider: 'rgba(0, 0, 0, 0)', popup: '#293330', renote: '@accent', mentionMe: '#ffaa00', diff --git a/packages/frontend/src/themes/d-green-orange.json5 b/packages/frontend/src/themes/d-green-orange.json5 index 62adc39e29cb9708d6959944352c7562951ca887..e542782c6651ac5870c7e7b6d9c91f5eb88a726e 100644 --- a/packages/frontend/src/themes/d-green-orange.json5 +++ b/packages/frontend/src/themes/d-green-orange.json5 @@ -14,8 +14,7 @@ fgOnAccent: '#192320', divider: '#e7fffb24', panel: '#192320', - panelHeaderBg: '@panel', - panelHeaderDivider: '@divider', + panelHeaderDivider: 'rgba(0, 0, 0, 0)', popup: '#293330', renote: '@accent', mentionMe: '#b4e900', diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts index eae4f0091c783cc5fcf574e091b26eafe1e308cd..53042a4ce74ae43111a5d015377ca211a9d22b3b 100644 --- a/packages/frontend/src/ui/_common_/common.ts +++ b/packages/frontend/src/ui/_common_/common.ts @@ -76,7 +76,7 @@ export function openInstanceMenu(ev: MouseEvent) { } : undefined], }, null, { text: i18n.ts.help, - icon: 'ti ti-question-circle', + icon: 'ti ti-help-circle', action: () => { window.open('https://misskey-hub.net/help.html', '_blank'); }, diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue index 5a32c076a41694e14e9f69338788f049ea4f753e..71a4285e9db12c95c580258c0ad4bb3849b6981b 100644 --- a/packages/frontend/src/ui/_common_/common.vue +++ b/packages/frontend/src/ui/_common_/common.vue @@ -10,14 +10,16 @@ <XUpload v-if="uploads.length > 0"/> <TransitionGroup - tag="div" :class="$style.notifications" + tag="div" :class="[$style.notifications, $style[`notificationsPosition-${defaultStore.state.notificationPosition}`], $style[`notificationsStackAxis-${defaultStore.state.notificationStackAxis}`]]" :move-class="defaultStore.state.animation ? $style.transition_notification_move : ''" :enter-active-class="defaultStore.state.animation ? $style.transition_notification_enterActive : ''" :leave-active-class="defaultStore.state.animation ? $style.transition_notification_leaveActive : ''" :enter-from-class="defaultStore.state.animation ? $style.transition_notification_enterFrom : ''" :leave-to-class="defaultStore.state.animation ? $style.transition_notification_leaveTo : ''" > - <XNotification v-for="notification in notifications" :key="notification.id" :notification="notification" :class="$style.notification"/> + <div v-for="notification in notifications" :key="notification.id" :class="$style.notification"> + <XNotification :notification="notification"/> + </div> </TransitionGroup> <XStreamIndicator/> @@ -30,7 +32,7 @@ </template> <script lang="ts" setup> -import { defineAsyncComponent } from 'vue'; +import { defineAsyncComponent, ref } from 'vue'; import * as misskey from 'misskey-js'; import { swInject } from './sw-inject'; import XNotification from './notification.vue'; @@ -85,7 +87,10 @@ if ($i) { .transition_notification_leaveActive { transition: opacity 0.3s, transform 0.3s !important; } -.transition_notification_enterFrom, +.transition_notification_enterFrom { + opacity: 0; + transform: translateX(250px); +} .transition_notification_leaveTo { opacity: 0; transform: translateX(-250px); @@ -94,35 +99,90 @@ if ($i) { .notifications { position: fixed; z-index: 3900000; - left: 0; - width: 250px; - top: 32px; - padding: 0 32px; + padding: 0 var(--margin); pointer-events: none; - container-type: inline-size; -} + display: flex; -.notification { - & + .notification { - margin-top: 8px; + &.notificationsPosition-leftTop { + top: var(--margin); + left: 0; + } + + &.notificationsPosition-rightTop { + top: var(--margin); + right: 0; + } + + &.notificationsPosition-leftBottom { + bottom: calc(var(--minBottomSpacing) + var(--margin)); + left: 0; } -} -@media (max-width: 500px) { - .notifications { - top: initial; + &.notificationsPosition-rightBottom { bottom: calc(var(--minBottomSpacing) + var(--margin)); - padding: 0 var(--margin); - display: flex; - flex-direction: column-reverse; + right: 0; } - .notification { - & + .notification { - margin-top: 0; - margin-bottom: 8px; + &.notificationsStackAxis-vertical { + width: 250px; + + &.notificationsPosition-leftTop, + &.notificationsPosition-rightTop { + flex-direction: column; + + .notification { + & + .notification { + margin-top: 8px; + } + } + } + + &.notificationsPosition-leftBottom, + &.notificationsPosition-rightBottom { + flex-direction: column-reverse; + + .notification { + & + .notification { + margin-bottom: 8px; + } + } } } + + &.notificationsStackAxis-horizontal { + width: 100%; + + &.notificationsPosition-leftTop, + &.notificationsPosition-leftBottom { + flex-direction: row; + + .notification { + & + .notification { + margin-left: 8px; + } + } + } + + &.notificationsPosition-rightTop, + &.notificationsPosition-rightBottom { + flex-direction: row-reverse; + + .notification { + & + .notification { + margin-right: 8px; + } + } + } + + .notification { + width: 250px; + flex-shrink: 0; + } + } +} + +.notification { + container-type: inline-size; } </style> diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue index ff0cba33ac65d6fd6b411de4dd2095ff5eb72ac6..9605d1b22ee6e029efb5d0f9c2302402210d1e87 100644 --- a/packages/frontend/src/ui/deck/channel-column.vue +++ b/packages/frontend/src/ui/deck/channel-column.vue @@ -40,7 +40,7 @@ if (props.column.channelId == null) { } async function setChannel() { - const channels = await os.api('channels/followed', { + const channels = await os.api('channels/my-favorites', { limit: 100, }); const { canceled, result: channel } = await os.select({ diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue index e895847bd96f60e6093676fbdc3b0fcdf888c3e0..402bbe035260e6a2e6fded26c9300310a6e5167f 100644 --- a/packages/frontend/src/ui/deck/column.vue +++ b/packages/frontend/src/ui/deck/column.vue @@ -22,7 +22,7 @@ <span :class="$style.title"><slot name="header"></slot></span> <button v-tooltip="i18n.ts.settings" :class="$style.menu" class="_button" @click.stop="showSettingsMenu"><i class="ti ti-dots"></i></button> </header> - <div v-show="active" ref="body" v-container :class="$style.body"> + <div v-show="active" ref="body" :class="$style.body"> <slot></slot> </div> </section> @@ -243,7 +243,7 @@ function onDrop(ev) { <style lang="scss" module> .root { --root-margin: 10px; - --deckColumnHeaderHeight: 40px; + --deckColumnHeaderHeight: 38px; height: 100%; overflow: clip; @@ -318,10 +318,7 @@ function onDrop(ev) { background: var(--panelHeaderBg); box-shadow: 0 1px 0 0 var(--panelHeaderDivider); cursor: pointer; - - &, * { - user-select: none; - } + user-select: none; } .title { @@ -365,7 +362,7 @@ function onDrop(ev) { overflow-x: clip; -webkit-overflow-scrolling: touch; box-sizing: border-box; - container-type: inline-size; + container-type: size; background-color: var(--bg); } </style> diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue index 24629675156dc7ab42b9acee3f4953667ed0552e..27d0c26ac4a1147ab338f54a8fd91e4a33fd9c1b 100644 --- a/packages/frontend/src/ui/universal.vue +++ b/packages/frontend/src/ui/universal.vue @@ -2,7 +2,7 @@ <div :class="[$style.root, { [$style.withWallpaper]: wallpaper }]"> <XSidebar v-if="!isMobile" :class="$style.sidebar"/> - <MkStickyContainer v-container :class="$style.contents"> + <MkStickyContainer :class="$style.contents"> <template #header><XStatusBars :class="$style.statusbars"/></template> <main style="min-width: 0;" :style="{ background: pageMetadata?.value?.bg }" @contextmenu.stop="onContextmenu"> <div :class="$style.content" style="container-type: inline-size;"> diff --git a/packages/frontend/src/ui/universal.widgets.vue b/packages/frontend/src/ui/universal.widgets.vue index d11649c603eeb8efc69c3db80a565d5f2ad61313..3e0c38bb831bd624cd14402b676346c467cc4b45 100644 --- a/packages/frontend/src/ui/universal.widgets.vue +++ b/packages/frontend/src/ui/universal.widgets.vue @@ -3,7 +3,7 @@ <XWidgets :class="$style.widgets" :edit="editMode" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> <button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="ti ti-check"></i> {{ i18n.ts.editWidgetsExit }}</button> - <button v-else class="_textButton mk-widget-edit" :class="$style.edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ i18n.ts.editWidgets }}</button> + <button v-else class="_textButton" data-cy-widget-edit :class="$style.edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ i18n.ts.editWidgets }}</button> </div> </template> diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue index 6c96440ebd94820fe994850418caea8222781749..623abbda39dbdea2a11f5990ace54fc6689a2e14 100644 --- a/packages/frontend/src/ui/visitor.vue +++ b/packages/frontend/src/ui/visitor.vue @@ -1,19 +1,286 @@ <template> -<DesignB/> +<div class="mk-app"> + <a v-if="root" href="https://github.com/misskey-dev/misskey" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--panel); color:var(--fg); position: fixed; z-index: 10; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a> + + <div v-if="!narrow && !root" class="side"> + <div class="banner" :style="{ backgroundImage: instance.backgroundImageUrl ? `url(${ instance.backgroundImageUrl })` : 'none' }"></div> + <div class="dashboard"> + <MkVisitorDashboard/> + </div> + </div> + + <div class="main"> + <div v-if="!root" class="header"> + <div v-if="narrow === false" class="wide"> + <MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i> {{ i18n.ts.home }}</MkA> + <MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i> {{ i18n.ts.timeline }}</MkA> + <MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i> {{ i18n.ts.explore }}</MkA> + <MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i> {{ i18n.ts.channel }}</MkA> + </div> + <div v-else-if="narrow === true" class="narrow"> + <button class="menu _button" @click="showMenu = true"> + <i class="ti ti-menu-2 icon"></i> + </button> + </div> + </div> + <div class="contents"> + <main v-if="!root" style="container-type: inline-size;"> + <RouterView/> + </main> + <main v-else> + <RouterView/> + </main> + </div> + </div> + + <Transition :name="'tray-back'"> + <div + v-if="showMenu" + class="menu-back _modalBg" + @click="showMenu = false" + @touchstart.passive="showMenu = false" + ></div> + </Transition> + + <Transition :name="'tray'"> + <div v-if="showMenu" class="menu"> + <MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ i18n.ts.home }}</MkA> + <MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i>{{ i18n.ts.timeline }}</MkA> + <MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ i18n.ts.explore }}</MkA> + <MkA to="/announcements" class="link" active-class="active"><i class="ti ti-speakerphone icon"></i>{{ i18n.ts.announcements }}</MkA> + <MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ i18n.ts.channel }}</MkA> + <div class="divider"></div> + <MkA to="/pages" class="link" active-class="active"><i class="ti ti-news icon"></i>{{ i18n.ts.pages }}</MkA> + <MkA to="/play" class="link" active-class="active"><i class="ti ti-player-play icon"></i>Play</MkA> + <MkA to="/gallery" class="link" active-class="active"><i class="ti ti-icons icon"></i>{{ i18n.ts.gallery }}</MkA> + <div class="action"> + <button class="_buttonPrimary" @click="signup()">{{ i18n.ts.signup }}</button> + <button class="_button" @click="signin()">{{ i18n.ts.login }}</button> + </div> + </div> + </Transition> +</div> <XCommon/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -//import DesignA from './visitor/a.vue'; -import DesignB from './visitor/b.vue'; +<script lang="ts" setup> +import { ComputedRef, onMounted, provide } from 'vue'; import XCommon from './_common_/common.vue'; +import { host, instanceName } from '@/config'; +import * as os from '@/os'; +import { instance } from '@/instance'; +import XSigninDialog from '@/components/MkSigninDialog.vue'; +import XSignupDialog from '@/components/MkSignupDialog.vue'; +import { ColdDeviceStorage, defaultStore } from '@/store'; +import { mainRouter } from '@/router'; +import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; +import { i18n } from '@/i18n'; +import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue'; + +const DESKTOP_THRESHOLD = 1100; + +let pageMetadata = $ref<null | ComputedRef<PageMetadata>>(); + +provide('router', mainRouter); +provideMetadataReceiver((info) => { + pageMetadata = info; + if (pageMetadata.value) { + document.title = `${pageMetadata.value.title} | ${instanceName}`; + } +}); + +const announcements = { + endpoint: 'announcements', + limit: 10, +}; + +const isTimelineAvailable = $ref(instance.policies?.ltlAvailable || instance.policies?.gtlAvailable); + +let showMenu = $ref(false); +let isDesktop = $ref(window.innerWidth >= DESKTOP_THRESHOLD); +let narrow = $ref(window.innerWidth < 1280); +let meta = $ref(); -export default defineComponent({ - components: { - XCommon, - //DesignA, - DesignB, - }, +const keymap = $computed(() => { + return { + 'd': () => { + if (ColdDeviceStorage.get('syncDeviceDarkMode')) return; + defaultStore.set('darkMode', !defaultStore.state.darkMode); + }, + 's': () => { + mainRouter.push('/search'); + }, + }; +}); + +const root = $computed(() => mainRouter.currentRoute.value.name === 'index'); + +os.api('meta', { detail: true }).then(res => { + meta = res; +}); + +function signin() { + os.popup(XSigninDialog, { + autoSet: true, + }, {}, 'closed'); +} + +function signup() { + os.popup(XSignupDialog, { + autoSet: true, + }, {}, 'closed'); +} + +onMounted(() => { + if (!isDesktop) { + window.addEventListener('resize', () => { + if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop = true; + }, { passive: true }); + } +}); + +defineExpose({ + showMenu: $$(showMenu), }); </script> + +<style> +.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}} +</style> + +<style lang="scss" scoped> +.tray-enter-active, +.tray-leave-active { + opacity: 1; + transform: translateX(0); + transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1); +} +.tray-enter-from, +.tray-leave-active { + opacity: 0; + transform: translateX(-240px); +} + +.tray-back-enter-active, +.tray-back-leave-active { + opacity: 1; + transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1); +} +.tray-back-enter-from, +.tray-back-leave-active { + opacity: 0; +} + +.mk-app { + display: flex; + min-height: 100vh; + + > .side { + position: sticky; + top: 0; + left: 0; + width: 500px; + height: 100vh; + background: var(--accent); + + > .banner { + position: absolute; + top: 0; + left: 0; + width: 100%; + aspect-ratio: 1.5; + background-position: center; + background-size: cover; + -webkit-mask-image: linear-gradient(rgba(0, 0, 0, 1.0), transparent); + mask-image: linear-gradient(rgba(0, 0, 0, 1.0), transparent); + } + + > .dashboard { + position: relative; + padding: 32px; + box-sizing: border-box; + max-height: 100%; + overflow: auto; + } + } + + > .main { + flex: 1; + min-width: 0; + + > .header { + background: var(--panel); + + > .wide { + line-height: 50px; + padding: 0 16px; + + > .link { + padding: 0 16px; + } + } + + > .narrow { + > .menu { + padding: 16px; + } + } + } + } + + > .menu-back { + position: fixed; + z-index: 1001; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + } + + > .menu { + position: fixed; + z-index: 1001; + top: 0; + left: 0; + width: 240px; + height: 100vh; + background: var(--panel); + + > .link { + display: block; + padding: 16px; + + > .icon { + margin-right: 1em; + } + } + + > .divider { + margin: 8px auto; + width: calc(100% - 32px); + border-top: solid 0.5px var(--divider); + } + + > .action { + padding: 16px; + + > button { + display: block; + width: 100%; + padding: 10px; + box-sizing: border-box; + text-align: center; + border-radius: 999px; + + &._button { + background: var(--panel); + } + + &:first-child { + margin-bottom: 16px; + } + } + } + } +} +</style> diff --git a/packages/frontend/src/ui/visitor/a.vue b/packages/frontend/src/ui/visitor/a.vue deleted file mode 100644 index 4761036075b15dd92d76afa25cd8ca0cc719ec7d..0000000000000000000000000000000000000000 --- a/packages/frontend/src/ui/visitor/a.vue +++ /dev/null @@ -1,263 +0,0 @@ -<template> -<div class="mk-app"> - <div v-if="mainRouter.currentRoute?.name === 'index'" class="banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"> - <div> - <h1 v-if="meta"><img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span></h1> - <div v-if="meta" class="about"> - <!-- eslint-disable-next-line vue/no-v-html --> - <div class="desc" v-html="meta.description || i18n.ts.introMisskey"></div> - </div> - <div class="action"> - <button class="_button primary" @click="signup()">{{ i18n.ts.signup }}</button> - <button class="_button" @click="signin()">{{ i18n.ts.login }}</button> - </div> - </div> - </div> - <div v-else class="banner-mini" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"> - <div> - <h1 v-if="meta"><img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span></h1> - </div> - </div> - - <div class="main"> - <div ref="contents" class="contents" :class="{ wallpaper }"> - <header v-show="mainRouter.currentRoute?.name !== 'index'" ref="header" class="header"> - <XHeader :info="pageInfo"/> - </header> - <main ref="main" style="container-type: inline-size;"> - <RouterView/> - </main> - <div class="powered-by"> - <b><MkA to="/">{{ host }}</MkA></b> - <small>Powered by <a href="https://github.com/misskey-dev/misskey" target="_blank">Misskey</a></small> - </div> - </div> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import XHeader from './header.vue'; -import { host, instanceName } from '@/config'; -import * as os from '@/os'; -import MkButton from '@/components/MkButton.vue'; -import { defaultStore, ColdDeviceStorage } from '@/store'; -import { mainRouter } from '@/router'; -import { instance } from '@/instance'; -import { i18n } from '@/i18n'; - -const DESKTOP_THRESHOLD = 1100; - -export default defineComponent({ - components: { - XHeader, - MkButton, - }, - - data() { - return { - host, - instanceName, - pageInfo: null, - meta: null, - narrow: window.innerWidth < 1280, - announcements: { - endpoint: 'announcements', - limit: 10, - }, - mainRouter, - isDesktop: window.innerWidth >= DESKTOP_THRESHOLD, - defaultStore, - instance, - i18n, - }; - }, - - computed: { - keymap(): any { - return { - 'd': () => { - if (ColdDeviceStorage.get('syncDeviceDarkMode')) return; - this.defaultStore.set('darkMode', !this.defaultStore.state.darkMode); - }, - 's': () => { - mainRouter.push('/search'); - }, - 'h|/': this.help, - }; - }, - }, - - created() { - document.documentElement.style.overflowY = 'scroll'; - - os.api('meta', { detail: true }).then(meta => { - this.meta = meta; - }); - }, - - mounted() { - if (!this.isDesktop) { - window.addEventListener('resize', () => { - if (window.innerWidth >= DESKTOP_THRESHOLD) this.isDesktop = true; - }, { passive: true }); - } - }, - - methods: { - // @ThatOneCalculator: Are these methods even used? - // I can't find references to them anywhere else in the code... - - // setParallax(el) { - // new simpleParallax(el); - // }, - - changePage(page) { - if (page == null) return; - // eslint-disable-next-line no-undef - if (page[symbols.PAGE_INFO]) { - // eslint-disable-next-line no-undef - this.pageInfo = page[symbols.PAGE_INFO]; - } - }, - - top() { - window.scroll({ top: 0, behavior: 'smooth' }); - }, - - help() { - window.open('https://misskey-hub.net/docs/keyboard-shortcut.md', '_blank'); - }, - }, -}); -</script> - -<style lang="scss" scoped> -.mk-app { - min-height: 100vh; - - > .banner { - position: relative; - width: 100%; - text-align: center; - background-position: center; - background-size: cover; - - > div { - height: 100%; - background: rgba(0, 0, 0, 0.3); - - * { - color: #fff; - } - - > h1 { - margin: 0; - padding: 96px 32px 0 32px; - text-shadow: 0 0 8px black; - - > .logo { - vertical-align: bottom; - max-height: 150px; - } - } - - > .about { - padding: 32px; - max-width: 580px; - margin: 0 auto; - box-sizing: border-box; - text-shadow: 0 0 8px black; - } - - > .action { - padding-bottom: 64px; - - > button { - display: inline-block; - padding: 10px 20px; - box-sizing: border-box; - text-align: center; - border-radius: 999px; - background: var(--panel); - color: var(--fg); - - &.primary { - background: var(--accent); - color: #fff; - } - - &:first-child { - margin-right: 16px; - } - } - } - } - } - - > .banner-mini { - position: relative; - width: 100%; - text-align: center; - background-position: center; - background-size: cover; - - > div { - position: relative; - z-index: 1; - height: 100%; - background: rgba(0, 0, 0, 0.3); - - * { - color: #fff !important; - } - - > header { - - } - - > h1 { - margin: 0; - padding: 32px; - text-shadow: 0 0 8px black; - - > .logo { - vertical-align: bottom; - max-height: 100px; - } - } - } - } - - > .main { - > .contents { - position: relative; - z-index: 1; - - > .header { - position: sticky; - top: 0; - left: 0; - z-index: 1000; - } - - > .powered-by { - padding: 28px; - font-size: 14px; - text-align: center; - border-top: 1px solid var(--divider); - - > small { - display: block; - margin-top: 8px; - opacity: 0.5; - } - } - } - } -} -</style> - -<style lang="scss"> -</style> diff --git a/packages/frontend/src/ui/visitor/b.vue b/packages/frontend/src/ui/visitor/b.vue deleted file mode 100644 index 5287a670c51c503e97c0bcba064be201d658bc62..0000000000000000000000000000000000000000 --- a/packages/frontend/src/ui/visitor/b.vue +++ /dev/null @@ -1,266 +0,0 @@ -<template> -<div class="mk-app"> - <a v-if="root" href="https://github.com/misskey-dev/misskey" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--panel); color:var(--fg); position: fixed; z-index: 10; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a> - - <div v-if="!narrow && !root" class="side"> - <XKanban class="kanban" full/> - </div> - - <div class="main"> - <XKanban v-if="narrow && !root" class="banner" :powered-by="root"/> - - <div class="contents"> - <XHeader v-if="!root" class="header"/> - <main v-if="!root" style="container-type: inline-size;"> - <RouterView/> - </main> - <main v-else> - <RouterView/> - </main> - <div v-if="!root" class="powered-by"> - <b><MkA to="/">{{ host }}</MkA></b> - <small>Powered by <a href="https://github.com/misskey-dev/misskey" target="_blank">Misskey</a></small> - </div> - </div> - </div> - - <Transition :name="'tray-back'"> - <div - v-if="showMenu" - class="menu-back _modalBg" - @click="showMenu = false" - @touchstart.passive="showMenu = false" - ></div> - </Transition> - - <Transition :name="'tray'"> - <div v-if="showMenu" class="menu"> - <MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ i18n.ts.home }}</MkA> - <MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i>{{ i18n.ts.timeline }}</MkA> - <MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ i18n.ts.explore }}</MkA> - <MkA to="/announcements" class="link" active-class="active"><i class="ti ti-speakerphone icon"></i>{{ i18n.ts.announcements }}</MkA> - <MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ i18n.ts.channel }}</MkA> - <div class="divider"></div> - <MkA to="/pages" class="link" active-class="active"><i class="ti ti-news icon"></i>{{ i18n.ts.pages }}</MkA> - <MkA to="/play" class="link" active-class="active"><i class="ti ti-player-play icon"></i>Play</MkA> - <MkA to="/gallery" class="link" active-class="active"><i class="ti ti-icons icon"></i>{{ i18n.ts.gallery }}</MkA> - <div class="action"> - <button class="_buttonPrimary" @click="signup()">{{ i18n.ts.signup }}</button> - <button class="_button" @click="signin()">{{ i18n.ts.login }}</button> - </div> - </div> - </Transition> -</div> -</template> - -<script lang="ts" setup> -import { ComputedRef, onMounted, provide } from 'vue'; -import XHeader from './header.vue'; -import XKanban from './kanban.vue'; -import { host, instanceName } from '@/config'; -import * as os from '@/os'; -import { instance } from '@/instance'; -import XSigninDialog from '@/components/MkSigninDialog.vue'; -import XSignupDialog from '@/components/MkSignupDialog.vue'; -import { ColdDeviceStorage, defaultStore } from '@/store'; -import { mainRouter } from '@/router'; -import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; -import { i18n } from '@/i18n'; - -const DESKTOP_THRESHOLD = 1100; - -let pageMetadata = $ref<null | ComputedRef<PageMetadata>>(); - -provide('router', mainRouter); -provideMetadataReceiver((info) => { - pageMetadata = info; - if (pageMetadata.value) { - document.title = `${pageMetadata.value.title} | ${instanceName}`; - } -}); - -const announcements = { - endpoint: 'announcements', - limit: 10, -}; - -const isTimelineAvailable = $ref(instance.policies?.ltlAvailable || instance.policies?.gtlAvailable); - -let showMenu = $ref(false); -let isDesktop = $ref(window.innerWidth >= DESKTOP_THRESHOLD); -let narrow = $ref(window.innerWidth < 1280); -let meta = $ref(); - -const keymap = $computed(() => { - return { - 'd': () => { - if (ColdDeviceStorage.get('syncDeviceDarkMode')) return; - defaultStore.set('darkMode', !defaultStore.state.darkMode); - }, - 's': () => { - mainRouter.push('/search'); - }, - }; -}); - -const root = $computed(() => mainRouter.currentRoute.value.name === 'index'); - -os.api('meta', { detail: true }).then(res => { - meta = res; -}); - -function signin() { - os.popup(XSigninDialog, { - autoSet: true, - }, {}, 'closed'); -} - -function signup() { - os.popup(XSignupDialog, { - autoSet: true, - }, {}, 'closed'); -} - -onMounted(() => { - if (!isDesktop) { - window.addEventListener('resize', () => { - if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop = true; - }, { passive: true }); - } -}); - -defineExpose({ - showMenu: $$(showMenu), -}); -</script> - -<style> -.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}} -</style> - -<style lang="scss" scoped> -.tray-enter-active, -.tray-leave-active { - opacity: 1; - transform: translateX(0); - transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1); -} -.tray-enter-from, -.tray-leave-active { - opacity: 0; - transform: translateX(-240px); -} - -.tray-back-enter-active, -.tray-back-leave-active { - opacity: 1; - transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1); -} -.tray-back-enter-from, -.tray-back-leave-active { - opacity: 0; -} - -.mk-app { - display: flex; - min-height: 100vh; - background-position: center; - background-size: cover; - background-attachment: fixed; - - > .side { - width: 500px; - height: 100vh; - - > .kanban { - position: fixed; - top: 0; - left: 0; - width: 500px; - height: 100vh; - overflow: auto; - } - } - - > .main { - flex: 1; - min-width: 0; - - > .banner { - } - - > .contents { - position: relative; - z-index: 1; - - > .powered-by { - padding: 28px; - font-size: 14px; - text-align: center; - border-top: 1px solid var(--divider); - - > small { - display: block; - margin-top: 8px; - opacity: 0.5; - } - } - } - } - - > .menu-back { - position: fixed; - z-index: 1001; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - } - - > .menu { - position: fixed; - z-index: 1001; - top: 0; - left: 0; - width: 240px; - height: 100vh; - background: var(--panel); - - > .link { - display: block; - padding: 16px; - - > .icon { - margin-right: 1em; - } - } - - > .divider { - margin: 8px auto; - width: calc(100% - 32px); - border-top: solid 0.5px var(--divider); - } - - > .action { - padding: 16px; - - > button { - display: block; - width: 100%; - padding: 10px; - box-sizing: border-box; - text-align: center; - border-radius: 999px; - - &._button { - background: var(--panel); - } - - &:first-child { - margin-bottom: 16px; - } - } - } - } -} -</style> diff --git a/packages/frontend/src/ui/visitor/header.vue b/packages/frontend/src/ui/visitor/header.vue deleted file mode 100644 index 7de81f6431aae9c4b9a89438845205ad895fe8a6..0000000000000000000000000000000000000000 --- a/packages/frontend/src/ui/visitor/header.vue +++ /dev/null @@ -1,211 +0,0 @@ -<template> -<div class="sqxihjet"> - <div v-if="narrow === false" class="wide"> - <div class="content"> - <MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ i18n.ts.home }}</MkA> - <MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i>{{ i18n.ts.timeline }}</MkA> - <MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ i18n.ts.explore }}</MkA> - <MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ i18n.ts.channel }}</MkA> - <div class="right"> - <button class="_button search" @click="search()"><i class="ti ti-search icon"></i><span>{{ i18n.ts.search }}</span></button> - <button class="_buttonPrimary signup" @click="signup()">{{ i18n.ts.signup }}</button> - <button class="_button login" @click="signin()">{{ i18n.ts.login }}</button> - </div> - </div> - </div> - <div v-else-if="narrow === true" class="narrow"> - <button class="menu _button" @click="$parent.showMenu = true"> - <i class="ti ti-menu-2 icon"></i> - </button> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import XSigninDialog from '@/components/MkSigninDialog.vue'; -import XSignupDialog from '@/components/MkSignupDialog.vue'; -import * as os from '@/os'; -import { instance } from '@/instance'; -import { mainRouter } from '@/router'; -import { i18n } from '@/i18n'; - -export default defineComponent({ - data() { - return { - narrow: null, - showMenu: false, - isTimelineAvailable: instance.policies.ltlAvailable || instance.policies.gtlAvailable, - i18n, - }; - }, - - mounted() { - this.narrow = this.$el.clientWidth < 1300; - }, - - methods: { - signin() { - os.popup(XSigninDialog, { - autoSet: true, - }, {}, 'closed'); - }, - - signup() { - os.popup(XSignupDialog, { - autoSet: true, - }, {}, 'closed'); - }, - - search() { - mainRouter.push('/search'); - }, - }, -}); -</script> - -<style lang="scss" scoped> -.sqxihjet { - $height: 50px; - position: sticky; - width: 50px; - top: 0; - left: 0; - z-index: 1000; - line-height: $height; - -webkit-backdrop-filter: var(--blur, blur(32px)); - backdrop-filter: var(--blur, blur(32px)); - background-color: var(--X16); - - > .wide { - > .content { - max-width: 1400px; - margin: 0 auto; - display: flex; - align-items: center; - - > .link { - $line: 3px; - display: inline-block; - padding: 0 16px; - line-height: $height - ($line * 2); - border-top: solid $line transparent; - border-bottom: solid $line transparent; - - > .icon { - margin-right: 0.5em; - } - - &.page { - border-bottom-color: var(--accent); - } - } - - > .page { - > .title { - display: inline-block; - vertical-align: bottom; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - position: relative; - - > .icon + .text { - margin-left: 8px; - } - - > .avatar { - $size: 32px; - display: inline-block; - width: $size; - height: $size; - vertical-align: middle; - margin-right: 8px; - pointer-events: none; - } - - &._button { - &:hover { - color: var(--fgHighlighted); - } - } - - &.selected { - box-shadow: 0 -2px 0 0 var(--accent) inset; - color: var(--fgHighlighted); - } - } - - > .action { - padding: 0 0 0 16px; - } - } - - > .right { - margin-left: auto; - - > .search { - background: var(--bg); - border-radius: 999px; - width: 230px; - line-height: $height - 20px; - margin-right: 16px; - text-align: left; - - > * { - opacity: 0.7; - } - - > .icon { - padding: 0 16px; - } - } - - > .signup { - border-radius: 999px; - padding: 0 24px; - line-height: $height - 20px; - } - - > .login { - padding: 0 16px; - } - } - } - } - - > .narrow { - display: flex; - - > .menu, - > .action { - width: $height; - height: $height; - font-size: 20px; - } - - > .title { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - position: relative; - text-align: center; - - > .icon + .text { - margin-left: 8px; - } - - > .avatar { - $size: 32px; - display: inline-block; - width: $size; - height: $size; - vertical-align: middle; - margin-right: 8px; - pointer-events: none; - } - } - } -} -</style> diff --git a/packages/frontend/src/ui/visitor/kanban.vue b/packages/frontend/src/ui/visitor/kanban.vue deleted file mode 100644 index ce7fcfe94468993562d6efe62fac79eecc6876c5..0000000000000000000000000000000000000000 --- a/packages/frontend/src/ui/visitor/kanban.vue +++ /dev/null @@ -1,261 +0,0 @@ -<!-- eslint-disable vue/no-v-html --> -<template> -<div class="rwqkcmrc" :style="{ backgroundImage: transparent ? 'none' : `url(${ instance.backgroundImageUrl })` }"> - <div class="back" :class="{ transparent }"></div> - <div class="contents"> - <div class="wrapper"> - <h1 v-if="meta" :class="{ full }"> - <MkA to="/" class="link"><img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl" alt="logo"><span v-else class="text">{{ instanceName }}</span></MkA> - </h1> - <template v-if="full"> - <div v-if="meta" class="about"> - <div class="desc" v-html="meta.description || i18n.ts.introMisskey"></div> - </div> - <div class="action"> - <button class="_buttonPrimary" @click="signup()">{{ i18n.ts.signup }}</button> - <button class="_button" @click="signin()">{{ i18n.ts.login }}</button> - </div> - <div class="announcements panel"> - <header>{{ i18n.ts.announcements }}</header> - <MkPagination v-slot="{items}" :pagination="announcements" class="list"> - <section v-for="announcement in items" :key="announcement.id" class="item"> - <div class="title">{{ announcement.title }}</div> - <div class="content"> - <Mfm :text="announcement.text"/> - <img v-if="announcement.imageUrl" :src="announcement.imageUrl" alt="announcement image"/> - </div> - </section> - </MkPagination> - </div> - <div v-if="poweredBy" class="powered-by"> - <b><MkA to="/">{{ host }}</MkA></b> - <small>Powered by <a href="https://github.com/misskey-dev/misskey" target="_blank">Misskey</a></small> - </div> - </template> - </div> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import { host, instanceName } from '@/config'; -import * as os from '@/os'; -import MkPagination from '@/components/MkPagination.vue'; -import XSigninDialog from '@/components/MkSigninDialog.vue'; -import XSignupDialog from '@/components/MkSignupDialog.vue'; -import MkButton from '@/components/MkButton.vue'; -import { instance } from '@/instance'; -import { i18n } from '@/i18n'; - -export default defineComponent({ - components: { - MkPagination, - MkButton, - }, - - props: { - full: { - type: Boolean, - required: false, - default: false, - }, - transparent: { - type: Boolean, - required: false, - default: false, - }, - poweredBy: { - type: Boolean, - required: false, - default: false, - }, - }, - - data() { - return { - host, - instanceName, - pageInfo: null, - meta: null, - narrow: window.innerWidth < 1280, - announcements: { - endpoint: 'announcements', - limit: 10, - }, - instance, - i18n, - }; - }, - - created() { - os.api('meta', { detail: true }).then(meta => { - this.meta = meta; - }); - }, - - methods: { - signin() { - os.popup(XSigninDialog, { - autoSet: true, - }, {}, 'closed'); - }, - - signup() { - os.popup(XSignupDialog, { - autoSet: true, - }, {}, 'closed'); - }, - }, -}); -</script> - -<style lang="scss" scoped> -.rwqkcmrc { - position: relative; - text-align: center; - background-position: center; - background-size: cover; - // TODO: パララックスã«ã—ãŸã„ - - > .back { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.3); - - &.transparent { - -webkit-backdrop-filter: var(--blur, blur(12px)); - backdrop-filter: var(--blur, blur(12px)); - } - } - - > .contents { - position: relative; - z-index: 1; - height: inherit; - overflow: auto; - - > .wrapper { - max-width: 380px; - padding: 0 16px; - box-sizing: border-box; - margin: 0 auto; - - > .panel { - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); - background: rgba(0, 0, 0, 0.5); - border-radius: var(--radius); - - &, * { - color: #fff !important; - } - } - - > h1 { - display: block; - margin: 0; - padding: 32px 0 32px 0; - color: #fff; - - &.full { - padding: 64px 0 0 0; - - > .link { - > ::v-deep(.logo) { - max-height: 130px; - } - } - } - - > .link { - display: block; - - > ::v-deep(.logo) { - vertical-align: bottom; - max-height: 100px; - } - } - } - - > .about { - display: block; - margin: 24px 0; - text-align: center; - box-sizing: border-box; - text-shadow: 0 0 8px black; - color: #fff; - } - - > .action { - > button { - display: block; - width: 100%; - padding: 10px; - box-sizing: border-box; - text-align: center; - border-radius: 999px; - - &._button { - background: var(--panel); - } - - &:first-child { - margin-bottom: 16px; - } - } - } - - > .announcements { - margin: 32px 0; - text-align: left; - - > header { - padding: 12px 16px; - border-bottom: solid 1px rgba(255, 255, 255, 0.5); - } - - > .list { - max-height: 300px; - overflow: auto; - - > .item { - padding: 12px 16px; - - & + .item { - border-top: solid 1px rgba(255, 255, 255, 0.5); - } - - > .title { - font-weight: bold; - } - - > .content { - > img { - max-width: 100%; - } - } - } - } - } - - > .powered-by { - padding: 28px; - font-size: 14px; - text-align: center; - border-top: 1px solid rgba(255, 255, 255, 0.5); - color: #fff; - - > small { - display: block; - margin-top: 8px; - opacity: 0.5; - } - } - } - } -} -</style> diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json index 4d582daa3c33aeebd05babd758466d405339520b..514b304246dfd2ada8edb8d6dfe1ebb1191b8359 100644 --- a/packages/frontend/tsconfig.json +++ b/packages/frontend/tsconfig.json @@ -27,10 +27,12 @@ }, "typeRoots": [ "node_modules/@types", + "node_modules/@vue-macros", "@types", ], "types": [ "vite/client", + "reactivity-transform/macros-global" ], "lib": [ "esnext", diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 425f3aa45d93273a8ae6b3188b66d578f940d694..fad0dd0177a48088b8ec1562cd8afe636a751edb 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -1,6 +1,9 @@ import path from 'path'; +import pluginReplace from '@rollup/plugin-replace'; import pluginVue from '@vitejs/plugin-vue'; import { type UserConfig, defineConfig } from 'vite'; +// @ts-expect-error https://github.com/sxzz/unplugin-vue-macros/issues/257#issuecomment-1410752890 +import ReactivityTransform from '@vue-macros/reactivity-transform/vite'; import locales from '../../locales'; import meta from '../../package.json'; @@ -41,11 +44,26 @@ export function getConfig(): UserConfig { return { base: '/vite/', + server: { + port: 5173, + }, + plugins: [ pluginVue({ reactivityTransform: true, }), + ReactivityTransform(), pluginJson5(), + ...process.env.NODE_ENV === 'production' + ? [ + pluginReplace({ + preventAssignment: true, + values: { + 'isChromatic()': JSON.stringify(false), + }, + }), + ] + : [], ], resolve: { diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 67d12000b865fdaed087e3af1bf9e73a4e899f3d..19e5b7544394ec112a55800ee9ccf660bf0f5f09 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -1357,10 +1357,6 @@ export type Endpoints = { req: TODO; res: TODO; }; - 'i/known-as': { - req: TODO; - res: TODO; - }; 'i/notifications': { req: { limit?: number; @@ -1511,6 +1507,7 @@ export type Endpoints = { mutedWords?: string[][]; mutingNotificationTypes?: Notification_2['type'][]; emailNotificationTypes?: string[]; + alsoKnownAs?: string[]; }; res: MeDetailed; }; @@ -2348,6 +2345,7 @@ type LiteInstanceMetadata = { imageUrl: string; }[]; translatorAvailable: boolean; + serverRules: string[]; }; // @public (undocumented) @@ -2633,6 +2631,7 @@ type User = UserLite | UserDetailed; // @public (undocumented) type UserDetailed = UserLite & { + alsoKnownAs: string[]; bannerBlurhash: string | null; bannerColor: string | null; bannerUrl: string | null; @@ -2663,6 +2662,7 @@ type UserDetailed = UserLite & { lang: string | null; lastFetchedAt?: DateString; location: string | null; + movedTo: string; notesCount: number; pinnedNoteIds: ID[]; pinnedNotes: Note[]; @@ -2696,8 +2696,6 @@ type UserLite = { onlineStatus: 'online' | 'active' | 'offline' | 'unknown'; avatarUrl: string; avatarBlurhash: string; - alsoKnownAs: string[]; - movedToUri: any; emojis: { name: string; url: string; diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 76e0452444e2041b48faaa9316f5e0acbdc7c035..2e9bc760593ed7853d58da263c8a041fa17ff22a 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -20,27 +20,27 @@ "url": "git+https://github.com/misskey-dev/misskey.js.git" }, "devDependencies": { - "@microsoft/api-extractor": "7.34.4", - "@swc/jest": "0.2.24", - "@types/jest": "29.5.0", - "@types/node": "18.15.11", - "@typescript-eslint/eslint-plugin": "5.57.1", - "@typescript-eslint/parser": "5.57.1", - "eslint": "8.37.0", + "@microsoft/api-extractor": "7.34.7", + "@swc/jest": "0.2.26", + "@types/jest": "29.5.1", + "@types/node": "18.16.3", + "@typescript-eslint/eslint-plugin": "5.59.2", + "@typescript-eslint/parser": "5.59.2", + "eslint": "8.39.0", "jest": "29.5.0", "jest-fetch-mock": "3.0.3", "jest-websocket-mock": "2.4.0", "mock-socket": "9.2.1", "tsd": "0.28.1", - "typescript": "5.0.3" + "typescript": "5.0.4" }, "files": [ "built" ], "dependencies": { "@swc/cli": "0.1.62", - "@swc/core": "1.3.46", - "eventemitter3": "5.0.0", + "@swc/core": "1.3.56", + "eventemitter3": "5.0.1", "reconnecting-websocket": "4.4.0" } } diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts index aed9f5bf84296a40108af70fa9ccf54ca31422bd..cc88c4b1a401eacd418f3d0a75191f4936e6adda 100644 --- a/packages/misskey-js/src/api.types.ts +++ b/packages/misskey-js/src/api.types.ts @@ -363,7 +363,6 @@ export type Endpoints = { 'i/import-following': { req: TODO; res: TODO; }; 'i/import-user-lists': { req: TODO; res: TODO; }; 'i/move': { req: TODO; res: TODO; }; - 'i/known-as': { req: TODO; res: TODO; }; 'i/notifications': { req: { limit?: number; sinceId?: Notification['id']; @@ -421,6 +420,7 @@ export type Endpoints = { mutedWords?: string[][]; mutingNotificationTypes?: Notification['type'][]; emailNotificationTypes?: string[]; + alsoKnownAs?: string[]; }; res: MeDetailed; }; 'i/user-group-invites': { req: TODO; res: TODO; }; 'i/2fa/done': { req: TODO; res: TODO; }; diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index 0c90e44494e229ff63409e6d03098b983a187788..04065c51c9ccb5956e8a497889920a5c085783d8 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -14,8 +14,6 @@ export type UserLite = { onlineStatus: 'online' | 'active' | 'offline' | 'unknown'; avatarUrl: string; avatarBlurhash: string; - alsoKnownAs: string[]; - movedToUri: any; emojis: { name: string; url: string; @@ -31,6 +29,7 @@ export type UserLite = { }; export type UserDetailed = UserLite & { + alsoKnownAs: string[]; bannerBlurhash: string | null; bannerColor: string | null; bannerUrl: string | null; @@ -58,6 +57,7 @@ export type UserDetailed = UserLite & { lang: string | null; lastFetchedAt?: DateString; location: string | null; + movedTo: string; notesCount: number; pinnedNoteIds: ID[]; pinnedNotes: Note[]; @@ -315,6 +315,7 @@ export type LiteInstanceMetadata = { imageUrl: string; }[]; translatorAvailable: boolean; + serverRules: string[]; }; export type DetailedInstanceMetadata = LiteInstanceMetadata & { diff --git a/packages/sw/package.json b/packages/sw/package.json index d7a4a2a4b6fe092154915e4e5b828f1988c2b249..75bb40ccb10b1c6c19c14c07db51b68b0cd22a35 100644 --- a/packages/sw/package.json +++ b/packages/sw/package.json @@ -9,15 +9,15 @@ "lint": "pnpm typecheck && pnpm eslint" }, "dependencies": { - "esbuild": "0.17.15", + "esbuild": "0.17.18", "idb-keyval": "6.2.0", "misskey-js": "workspace:*" }, "devDependencies": { - "@typescript-eslint/parser": "5.57.1", + "@typescript-eslint/parser": "5.59.2", "@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67", - "eslint": "8.37.0", + "eslint": "8.39.0", "eslint-plugin-import": "2.27.5", - "typescript": "5.0.3" + "typescript": "5.0.4" } } diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts index 9e0d9f0d1e888e89f0be72e42a43ba00e7e8286d..2783f2bfb5f8db9506599c44905b7a7dad9b57b5 100644 --- a/packages/sw/src/sw.ts +++ b/packages/sw/src/sw.ts @@ -56,7 +56,7 @@ globalThis.addEventListener('push', ev => { return createNotification(data); case 'readAllNotifications': await globalThis.registration.getNotifications() - .then(notifications => notifications.forEach(n => n.close())); + .then(notifications => notifications.forEach(n => n.tag !== 'read_notification' && n.close())); break; } @@ -133,7 +133,7 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv switch (action) { case 'markAllAsRead': await globalThis.registration.getNotifications() - .then(notifications => notifications.forEach(n => n.close())); + .then(notifications => notifications.forEach(n => n.tag !== 'read_notification' && n.close())); await get('accounts').then(accounts => { return Promise.all(accounts.map(async account => { await swos.sendMarkAllAsRead(account.id); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7325659f8030047fed6d1b484e823ae99a8910e3..04d2fcf36b12eb2865ed5249339f8d0b5a1046bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,12 +30,12 @@ importers: specifier: 4.1.0 version: 4.1.0 typescript: - specifier: 5.0.3 - version: 5.0.3 + specifier: 5.0.4 + version: 5.0.4 optionalDependencies: '@tensorflow/tfjs-core': - specifier: 4.2.0 - version: 4.2.0 + specifier: 4.4.0 + version: 4.4.0 devDependencies: '@types/gulp': specifier: 4.0.10 @@ -44,20 +44,20 @@ importers: specifier: 2.0.1 version: 2.0.1 '@typescript-eslint/eslint-plugin': - specifier: 5.57.1 - version: 5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.37.0)(typescript@5.0.3) + specifier: 5.59.2 + version: 5.59.2(@typescript-eslint/parser@5.59.2)(eslint@8.39.0)(typescript@5.0.4) '@typescript-eslint/parser': - specifier: 5.57.1 - version: 5.57.1(eslint@8.37.0)(typescript@5.0.3) + specifier: 5.59.2 + version: 5.59.2(eslint@8.39.0)(typescript@5.0.4) cross-env: specifier: 7.0.3 version: 7.0.3 cypress: - specifier: 12.9.0 - version: 12.9.0 + specifier: 12.11.0 + version: 12.11.0 eslint: - specifier: 8.37.0 - version: 8.37.0 + specifier: 8.39.0 + version: 8.39.0 start-server-and-test: specifier: 2.0.0 version: 2.0.0 @@ -65,23 +65,23 @@ importers: packages/backend: dependencies: '@aws-sdk/client-s3': - specifier: 3.306.0 - version: 3.306.0 + specifier: 3.321.1 + version: 3.321.1 '@aws-sdk/lib-storage': - specifier: 3.306.0 - version: 3.306.0(@aws-sdk/abort-controller@3.306.0)(@aws-sdk/client-s3@3.306.0) + specifier: 3.321.1 + version: 3.321.1(@aws-sdk/abort-controller@3.310.0)(@aws-sdk/client-s3@3.321.1) '@aws-sdk/node-http-handler': - specifier: 3.306.0 - version: 3.306.0 + specifier: 3.321.1 + version: 3.321.1 '@bull-board/api': - specifier: 5.0.0 - version: 5.0.0 + specifier: 5.1.2 + version: 5.1.2(@bull-board/ui@5.1.2) '@bull-board/fastify': - specifier: 5.0.0 - version: 5.0.0 + specifier: 5.1.2 + version: 5.1.2 '@bull-board/ui': - specifier: 5.0.0 - version: 5.0.0 + specifier: 5.1.2 + version: 5.1.2 '@discordapp/twemoji': specifier: 14.1.2 version: 14.1.2 @@ -98,20 +98,20 @@ importers: specifier: 9.0.0 version: 9.0.0 '@fastify/multipart': - specifier: 7.5.0 - version: 7.5.0 + specifier: 7.6.0 + version: 7.6.0 '@fastify/static': - specifier: 6.10.0 - version: 6.10.0 + specifier: 6.10.1 + version: 6.10.1 '@fastify/view': specifier: 7.4.1 version: 7.4.1 '@nestjs/common': specifier: 9.4.0 - version: 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.0) + version: 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/core': specifier: 9.4.0 - version: 9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.0) + version: 9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/testing': specifier: 9.4.0 version: 9.4.0(@nestjs/common@9.4.0)(@nestjs/core@9.4.0) @@ -123,10 +123,10 @@ importers: version: 10.0.2 '@swc/cli': specifier: 0.1.62 - version: 0.1.62(@swc/core@1.3.46)(chokidar@3.5.3) + version: 0.1.62(@swc/core@1.3.56)(chokidar@3.5.3) '@swc/core': - specifier: 1.3.46 - version: 1.3.46(@swc/helpers@0.5.0) + specifier: 1.3.56 + version: 1.3.56 accepts: specifier: 1.3.8 version: 1.3.8 @@ -173,8 +173,8 @@ importers: specifier: 0.5.4 version: 0.5.4 date-fns: - specifier: 2.29.3 - version: 2.29.3 + specifier: 2.30.0 + version: 2.30.0 deep-email-validator: specifier: 0.1.21 version: 0.1.21 @@ -182,14 +182,14 @@ importers: specifier: 0.0.1 version: 0.0.1 fastify: - specifier: 4.15.0 - version: 4.15.0 + specifier: 4.17.0 + version: 4.17.0 feed: specifier: 4.2.2 version: 4.2.2 file-type: - specifier: 18.2.1 - version: 18.2.1 + specifier: 18.3.0 + version: 18.3.0 fluent-ffmpeg: specifier: 2.1.2 version: 2.1.2 @@ -200,14 +200,14 @@ importers: specifier: 12.6.0 version: 12.6.0 happy-dom: - specifier: 8.9.0 - version: 8.9.0 + specifier: 9.10.2 + version: 9.10.2 hpagent: specifier: 1.2.0 version: 1.2.0 ioredis: - specifier: 4.28.5 - version: 4.28.5 + specifier: 5.3.2 + version: 5.3.2 ip-cidr: specifier: 3.1.0 version: 3.1.0 @@ -227,8 +227,11 @@ importers: specifier: 8.1.1 version: 8.1.1 jsrsasign: - specifier: 10.7.0 - version: 10.7.0 + specifier: 10.8.6 + version: 10.8.6 + meilisearch: + specifier: 0.32.3 + version: 0.32.3 mfm-js: specifier: 0.23.3 version: 0.23.3 @@ -252,7 +255,7 @@ importers: version: 6.9.1 nsfwjs: specifier: 2.4.2 - version: 2.4.2(@tensorflow/tfjs@4.2.0) + version: 2.4.2(@tensorflow/tfjs@4.4.0) oauth: specifier: 0.10.0 version: 0.10.0 @@ -287,8 +290,8 @@ importers: specifier: 0.3.17 version: 0.3.17 qrcode: - specifier: 1.5.1 - version: 1.5.1 + specifier: 1.5.3 + version: 1.5.3 random-seed: specifier: 0.3.0 version: 0.3.0 @@ -311,11 +314,11 @@ importers: specifier: 1.0.0 version: 1.0.0 rss-parser: - specifier: 3.12.0 - version: 3.12.0 + specifier: 3.13.0 + version: 3.13.0 rxjs: - specifier: 7.8.0 - version: 7.8.0 + specifier: 7.8.1 + version: 7.8.1 s-age: specifier: 1.1.2 version: 1.1.2 @@ -326,14 +329,17 @@ importers: specifier: 3.0.5 version: 3.0.5 semver: - specifier: 7.3.8 - version: 7.3.8 + specifier: 7.5.0 + version: 7.5.0 sharp: - specifier: 0.32.0 - version: 0.32.0 + specifier: 0.32.1 + version: 0.32.1 sharp-read-bmp: specifier: github:misskey-dev/sharp-read-bmp version: github.com/misskey-dev/sharp-read-bmp/02d9dc189fa7df0c4bea09330be26741772dac01 + slacc: + specifier: 0.0.7 + version: 0.0.7 strict-event-emitter-types: specifier: 2.0.0 version: 2.0.0 @@ -342,7 +348,7 @@ importers: version: 2.1.0 summaly: specifier: github:misskey-dev/summaly - version: github.com/misskey-dev/summaly/1bab7afee616429b8bbf7a7cbcbb8ebcef66d992 + version: github.com/misskey-dev/summaly/2d63e2a0066f89871e777cc81d43c1ade8c97517 systeminformation: specifier: 5.17.12 version: 5.17.12 @@ -353,8 +359,8 @@ importers: specifier: 0.2.1 version: 0.2.1 tsc-alias: - specifier: 1.8.5 - version: 1.8.5 + specifier: 1.8.6 + version: 1.8.6 tsconfig-paths: specifier: 4.2.0 version: 4.2.0 @@ -362,11 +368,11 @@ importers: specifier: 14.0.0 version: 14.0.0 typeorm: - specifier: 0.3.13 - version: 0.3.13(ioredis@4.28.5)(pg@8.10.0) + specifier: 0.3.15 + version: 0.3.15(ioredis@5.3.2)(pg@8.10.0) typescript: - specifier: 5.0.3 - version: 5.0.3 + specifier: 5.0.4 + version: 5.0.4 ulid: specifier: 2.3.0 version: 2.3.0 @@ -380,8 +386,8 @@ importers: specifier: 1.1.2 version: 1.1.2 web-push: - specifier: 3.5.0 - version: 3.5.0 + specifier: 3.6.1 + version: 3.6.1 websocket: specifier: 1.0.34 version: 1.0.34 @@ -396,48 +402,81 @@ importers: specifier: 1.3.11 version: 1.3.11 '@swc/core-darwin-arm64': - specifier: 1.3.46 - version: 1.3.46 + specifier: 1.3.56 + version: 1.3.56 '@swc/core-darwin-x64': - specifier: 1.3.46 - version: 1.3.46 + specifier: 1.3.56 + version: 1.3.56 '@swc/core-linux-arm-gnueabihf': - specifier: 1.3.46 - version: 1.3.46 + specifier: 1.3.56 + version: 1.3.56 '@swc/core-linux-arm64-gnu': - specifier: 1.3.46 - version: 1.3.46 + specifier: 1.3.56 + version: 1.3.56 '@swc/core-linux-arm64-musl': - specifier: 1.3.46 - version: 1.3.46 + specifier: 1.3.56 + version: 1.3.56 '@swc/core-linux-x64-gnu': - specifier: 1.3.46 - version: 1.3.46 + specifier: 1.3.56 + version: 1.3.56 '@swc/core-linux-x64-musl': - specifier: 1.3.46 - version: 1.3.46 + specifier: 1.3.56 + version: 1.3.56 '@swc/core-win32-arm64-msvc': - specifier: 1.3.46 - version: 1.3.46 + specifier: 1.3.56 + version: 1.3.56 '@swc/core-win32-ia32-msvc': - specifier: 1.3.46 - version: 1.3.46 + specifier: 1.3.56 + version: 1.3.56 '@swc/core-win32-x64-msvc': - specifier: 1.3.46 - version: 1.3.46 + specifier: 1.3.56 + version: 1.3.56 '@tensorflow/tfjs': - specifier: 4.2.0 - version: 4.2.0(seedrandom@3.0.5) + specifier: 4.4.0 + version: 4.4.0(seedrandom@3.0.5) '@tensorflow/tfjs-node': - specifier: 4.2.0 - version: 4.2.0(seedrandom@3.0.5) + specifier: 4.4.0 + version: 4.4.0(seedrandom@3.0.5) + slacc-android-arm-eabi: + specifier: 0.0.7 + version: 0.0.7 + slacc-android-arm64: + specifier: 0.0.7 + version: 0.0.7 + slacc-darwin-arm64: + specifier: 0.0.7 + version: 0.0.7 + slacc-darwin-universal: + specifier: 0.0.7 + version: 0.0.7 + slacc-darwin-x64: + specifier: 0.0.7 + version: 0.0.7 + slacc-linux-arm-gnueabihf: + specifier: 0.0.7 + version: 0.0.7 + slacc-linux-arm64-gnu: + specifier: 0.0.7 + version: 0.0.7 + slacc-linux-arm64-musl: + specifier: 0.0.7 + version: 0.0.7 + slacc-linux-x64-gnu: + specifier: 0.0.7 + version: 0.0.7 + slacc-win32-arm64-msvc: + specifier: 0.0.7 + version: 0.0.7 + slacc-win32-x64-msvc: + specifier: 0.0.7 + version: 0.0.7 devDependencies: '@jest/globals': specifier: 29.5.0 version: 29.5.0 '@swc/jest': - specifier: 0.2.24 - version: 0.2.24(@swc/core@1.3.46) + specifier: 0.2.26 + version: 0.2.26(@swc/core@1.3.56) '@types/accepts': specifier: 1.3.5 version: 1.3.5 @@ -465,12 +504,9 @@ importers: '@types/fluent-ffmpeg': specifier: 2.1.21 version: 2.1.21 - '@types/ioredis': - specifier: 4.28.10 - version: 4.28.10 '@types/jest': - specifier: 29.5.0 - version: 29.5.0 + specifier: 29.5.1 + version: 29.5.1 '@types/js-yaml': specifier: 4.0.5 version: 4.0.5 @@ -487,8 +523,8 @@ importers: specifier: 2.1.1 version: 2.1.1 '@types/node': - specifier: 18.15.11 - version: 18.15.11 + specifier: 18.16.3 + version: 18.16.3 '@types/node-fetch': specifier: 3.0.3 version: 3.0.3 @@ -529,8 +565,8 @@ importers: specifier: 7.3.13 version: 7.3.13 '@types/sharp': - specifier: 0.31.1 - version: 0.31.1 + specifier: 0.32.0 + version: 0.32.0 '@types/sinonjs__fake-timers': specifier: 8.1.2 version: 8.1.2 @@ -559,11 +595,11 @@ importers: specifier: 8.5.4 version: 8.5.4 '@typescript-eslint/eslint-plugin': - specifier: 5.57.1 - version: 5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.37.0)(typescript@5.0.3) + specifier: 5.59.2 + version: 5.59.2(@typescript-eslint/parser@5.59.2)(eslint@8.39.0)(typescript@5.0.4) '@typescript-eslint/parser': - specifier: 5.57.1 - version: 5.57.1(eslint@8.37.0)(typescript@5.0.3) + specifier: 5.59.2 + version: 5.59.2(eslint@8.39.0)(typescript@5.0.4) aws-sdk-client-mock: specifier: ^2.1.1 version: 2.1.1 @@ -571,17 +607,17 @@ importers: specifier: 7.0.3 version: 7.0.3 eslint: - specifier: 8.37.0 - version: 8.37.0 + specifier: 8.39.0 + version: 8.39.0 eslint-plugin-import: specifier: 2.27.5 - version: 2.27.5(@typescript-eslint/parser@5.57.1)(eslint@8.37.0) + version: 2.27.5(@typescript-eslint/parser@5.59.2)(eslint@8.39.0) execa: specifier: 6.1.0 version: 6.1.0 jest: specifier: 29.5.0 - version: 29.5.0(@types/node@18.15.11) + version: 29.5.0(@types/node@18.16.3) jest-mock: specifier: 29.5.0 version: 29.5.0 @@ -592,23 +628,29 @@ importers: specifier: 14.1.2 version: 14.1.2 '@rollup/plugin-alias': - specifier: 4.0.3 - version: 4.0.3(rollup@3.20.2) + specifier: 5.0.0 + version: 5.0.0(rollup@3.21.3) '@rollup/plugin-json': specifier: 6.0.0 - version: 6.0.0(rollup@3.20.2) + version: 6.0.0(rollup@3.21.3) + '@rollup/plugin-replace': + specifier: ^5.0.2 + version: 5.0.2(rollup@3.21.3) '@rollup/pluginutils': specifier: 5.0.2 - version: 5.0.2(rollup@3.20.2) + version: 5.0.2(rollup@3.21.3) '@syuilo/aiscript': - specifier: 0.13.1 - version: 0.13.1 + specifier: 0.13.2 + version: 0.13.2 '@tabler/icons-webfont': - specifier: 2.12.0 - version: 2.12.0 + specifier: 2.17.0 + version: 2.17.0 '@vitejs/plugin-vue': - specifier: 4.1.0 - version: 4.1.0(vite@4.2.1)(vue@3.2.47) + specifier: 4.2.1 + version: 4.2.1(vite@4.3.4)(vue@3.2.47) + '@vue-macros/reactivity-transform': + specifier: ^0.3.5 + version: 0.3.5(rollup@3.21.3)(vue@3.2.47) '@vue/compiler-sfc': specifier: 3.2.47 version: 3.2.47 @@ -628,20 +670,23 @@ importers: specifier: 1.6.0 version: 1.6.0 chart.js: - specifier: 4.2.1 - version: 4.2.1 + specifier: 4.3.0 + version: 4.3.0 chartjs-adapter-date-fns: specifier: 3.0.0 - version: 3.0.0(chart.js@4.2.1)(date-fns@2.29.3) + version: 3.0.0(chart.js@4.3.0)(date-fns@2.30.0) chartjs-chart-matrix: specifier: 2.0.1 - version: 2.0.1(chart.js@4.2.1) + version: 2.0.1(chart.js@4.3.0) chartjs-plugin-gradient: specifier: 0.6.1 - version: 0.6.1(chart.js@4.2.1) + version: 0.6.1(chart.js@4.3.0) chartjs-plugin-zoom: specifier: 2.0.1 - version: 2.0.1(chart.js@4.2.1) + version: 2.0.1(chart.js@4.3.0) + chromatic: + specifier: 6.17.3 + version: 6.17.3 compare-versions: specifier: 5.0.1 version: 5.0.1 @@ -649,14 +694,14 @@ importers: specifier: 2.0.0-beta.2 version: 2.0.0-beta.2 date-fns: - specifier: 2.29.3 - version: 2.29.3 + specifier: 2.30.0 + version: 2.30.0 escape-regexp: specifier: 0.0.1 version: 0.0.1 eventemitter3: - specifier: 5.0.0 - version: 5.0.0 + specifier: 5.0.1 + version: 5.0.1 gsap: specifier: 3.11.5 version: 3.11.5 @@ -697,8 +742,8 @@ importers: specifier: 1.0.0 version: 1.0.0 rollup: - specifier: 3.20.2 - version: 3.20.2 + specifier: 3.21.3 + version: 3.21.3 s-age: specifier: 1.1.2 version: 1.1.2 @@ -706,8 +751,8 @@ importers: specifier: 2.10.0 version: 2.10.0 sass: - specifier: 1.60.0 - version: 1.60.0 + specifier: 1.62.1 + version: 1.62.1 seedrandom: specifier: 3.0.5 version: 3.0.5 @@ -730,8 +775,8 @@ importers: specifier: 1.6.0 version: 1.6.0 tsc-alias: - specifier: 1.8.5 - version: 1.8.5 + specifier: 1.8.6 + version: 1.8.6 tsconfig-paths: specifier: 4.2.0 version: 4.2.0 @@ -739,8 +784,8 @@ importers: specifier: 14.0.0 version: 14.0.0 typescript: - specifier: 5.0.3 - version: 5.0.3 + specifier: 5.0.4 + version: 5.0.4 uuid: specifier: 9.0.0 version: 9.0.0 @@ -748,8 +793,8 @@ importers: specifier: 1.8.0 version: 1.8.0 vite: - specifier: 4.2.1 - version: 4.2.1(@types/node@18.15.11)(sass@1.60.0) + specifier: 4.3.4 + version: 4.3.4(@types/node@18.16.3)(sass@1.62.1) vue: specifier: 3.2.47 version: 3.2.47 @@ -763,57 +808,60 @@ importers: specifier: next version: 4.1.0(vue@3.2.47) devDependencies: + '@storybook/addon-actions': + specifier: 7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-essentials': - specifier: 7.0.2 - version: 7.0.2(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-interactions': - specifier: 7.0.2 - version: 7.0.2(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-links': - specifier: 7.0.2 - version: 7.0.2(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-storysource': - specifier: 7.0.2 - version: 7.0.2(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0) '@storybook/addons': - specifier: 7.0.2 - version: 7.0.2(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0) '@storybook/blocks': - specifier: 7.0.2 - version: 7.0.2(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0) '@storybook/core-events': - specifier: 7.0.2 - version: 7.0.2 + specifier: 7.0.7 + version: 7.0.7 '@storybook/jest': specifier: 0.1.0 version: 0.1.0 '@storybook/manager-api': - specifier: 7.0.2 - version: 7.0.2(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0) '@storybook/preview-api': - specifier: 7.0.2 - version: 7.0.2 + specifier: 7.0.7 + version: 7.0.7 '@storybook/react': - specifier: 7.0.2 - version: 7.0.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.3) + specifier: 7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4) '@storybook/react-vite': - specifier: 7.0.2 - version: 7.0.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.3)(vite@4.2.1) + specifier: 7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.4) '@storybook/testing-library': - specifier: 0.0.14-next.1 - version: 0.0.14-next.1 + specifier: 0.1.0 + version: 0.1.0 '@storybook/theming': - specifier: 7.0.2 - version: 7.0.2(react-dom@18.2.0)(react@18.2.0) + specifier: 7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0) '@storybook/types': - specifier: 7.0.2 - version: 7.0.2 + specifier: 7.0.7 + version: 7.0.7 '@storybook/vue3': - specifier: 7.0.2 - version: 7.0.2(vue@3.2.47) + specifier: 7.0.7 + version: 7.0.7(vue@3.2.47) '@storybook/vue3-vite': - specifier: 7.0.2 - version: 7.0.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.3)(vite@4.2.1)(vue@3.2.47) + specifier: 7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.4)(vue@3.2.47) '@testing-library/jest-dom': specifier: 5.16.5 version: 5.16.5 @@ -824,8 +872,8 @@ importers: specifier: 0.0.1 version: 0.0.1 '@types/estree': - specifier: 1.0.0 - version: 1.0.0 + specifier: 1.0.1 + version: 1.0.1 '@types/gulp': specifier: 4.0.10 version: 4.0.10 @@ -836,11 +884,11 @@ importers: specifier: 0.18.2 version: 0.18.2 '@types/micromatch': - specifier: 3.1.1 - version: 3.1.1 + specifier: 4.0.2 + version: 4.0.2 '@types/node': - specifier: 18.15.11 - version: 18.15.11 + specifier: 18.16.3 + version: 18.16.3 '@types/punycode': specifier: 2.1.0 version: 2.1.0 @@ -869,14 +917,14 @@ importers: specifier: 8.5.4 version: 8.5.4 '@typescript-eslint/eslint-plugin': - specifier: 5.57.1 - version: 5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.37.0)(typescript@5.0.3) + specifier: 5.59.2 + version: 5.59.2(@typescript-eslint/parser@5.59.2)(eslint@8.39.0)(typescript@5.0.4) '@typescript-eslint/parser': - specifier: 5.57.1 - version: 5.57.1(eslint@8.37.0)(typescript@5.0.3) + specifier: 5.59.2 + version: 5.59.2(eslint@8.39.0)(typescript@5.0.4) '@vitest/coverage-c8': - specifier: ^0.29.8 - version: 0.29.8(vitest@0.29.8) + specifier: 0.30.1 + version: 0.30.1(vitest@0.30.1) '@vue/runtime-core': specifier: 3.2.47 version: 3.2.47 @@ -886,42 +934,39 @@ importers: chokidar-cli: specifier: 3.0.0 version: 3.0.0 - chromatic: - specifier: 6.17.3 - version: 6.17.3 cross-env: specifier: 7.0.3 version: 7.0.3 cypress: - specifier: 12.9.0 - version: 12.9.0 + specifier: 12.11.0 + version: 12.11.0 eslint: - specifier: 8.37.0 - version: 8.37.0 + specifier: 8.39.0 + version: 8.39.0 eslint-plugin-import: specifier: 2.27.5 - version: 2.27.5(@typescript-eslint/parser@5.57.1)(eslint@8.37.0) + version: 2.27.5(@typescript-eslint/parser@5.59.2)(eslint@8.39.0) eslint-plugin-vue: - specifier: 9.10.0 - version: 9.10.0(eslint@8.37.0) + specifier: 9.11.0 + version: 9.11.0(eslint@8.39.0) fast-glob: specifier: 3.2.12 version: 3.2.12 happy-dom: - specifier: 8.9.0 - version: 8.9.0 + specifier: 9.10.2 + version: 9.10.2 micromatch: specifier: 3.1.10 version: 3.1.10 msw: specifier: 1.2.1 - version: 1.2.1(typescript@5.0.3) + version: 1.2.1(typescript@5.0.4) msw-storybook-addon: specifier: 1.8.0 version: 1.8.0(msw@1.2.1) prettier: - specifier: 2.8.7 - version: 2.8.7 + specifier: 2.8.8 + version: 2.8.8 react: specifier: 18.2.0 version: 18.2.0 @@ -932,69 +977,69 @@ importers: specifier: 2.0.0 version: 2.0.0 storybook: - specifier: 7.0.2 - version: 7.0.2 + specifier: 7.0.7 + version: 7.0.7 storybook-addon-misskey-theme: specifier: github:misskey-dev/storybook-addon-misskey-theme - version: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.2)(@storybook/components@7.0.2)(@storybook/core-events@7.0.2)(@storybook/manager-api@7.0.2)(@storybook/preview-api@7.0.2)(@storybook/theming@7.0.2)(@storybook/types@7.0.2)(react-dom@18.2.0)(react@18.2.0) + version: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.7)(@storybook/components@7.0.7)(@storybook/core-events@7.0.7)(@storybook/manager-api@7.0.7)(@storybook/preview-api@7.0.7)(@storybook/theming@7.0.7)(@storybook/types@7.0.7)(react-dom@18.2.0)(react@18.2.0) summaly: specifier: github:misskey-dev/summaly - version: github.com/misskey-dev/summaly/1bab7afee616429b8bbf7a7cbcbb8ebcef66d992 + version: github.com/misskey-dev/summaly/2d63e2a0066f89871e777cc81d43c1ade8c97517 vite-plugin-turbosnap: - specifier: ^1.0.1 - version: 1.0.1 + specifier: 1.0.2 + version: 1.0.2 vitest: - specifier: 0.29.8 - version: 0.29.8(happy-dom@8.9.0)(sass@1.60.0) + specifier: 0.30.1 + version: 0.30.1(happy-dom@9.10.2)(sass@1.62.1) vitest-fetch-mock: specifier: 0.2.2 - version: 0.2.2(vitest@0.29.8) + version: 0.2.2(vitest@0.30.1) vue-eslint-parser: specifier: 9.1.1 - version: 9.1.1(eslint@8.37.0) + version: 9.1.1(eslint@8.39.0) vue-tsc: - specifier: 1.2.0 - version: 1.2.0(typescript@5.0.3) + specifier: 1.6.3 + version: 1.6.3(typescript@5.0.4) packages/misskey-js: dependencies: '@swc/cli': specifier: 0.1.62 - version: 0.1.62(@swc/core@1.3.46)(chokidar@3.5.3) + version: 0.1.62(@swc/core@1.3.56)(chokidar@3.5.3) '@swc/core': - specifier: 1.3.46 - version: 1.3.46(@swc/helpers@0.5.0) + specifier: 1.3.56 + version: 1.3.56 eventemitter3: - specifier: 5.0.0 - version: 5.0.0 + specifier: 5.0.1 + version: 5.0.1 reconnecting-websocket: specifier: 4.4.0 version: 4.4.0 devDependencies: '@microsoft/api-extractor': - specifier: 7.34.4 - version: 7.34.4(@types/node@18.15.11) + specifier: 7.34.7 + version: 7.34.7(@types/node@18.16.3) '@swc/jest': - specifier: 0.2.24 - version: 0.2.24(@swc/core@1.3.46) + specifier: 0.2.26 + version: 0.2.26(@swc/core@1.3.56) '@types/jest': - specifier: 29.5.0 - version: 29.5.0 + specifier: 29.5.1 + version: 29.5.1 '@types/node': - specifier: 18.15.11 - version: 18.15.11 + specifier: 18.16.3 + version: 18.16.3 '@typescript-eslint/eslint-plugin': - specifier: 5.57.1 - version: 5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.37.0)(typescript@5.0.3) + specifier: 5.59.2 + version: 5.59.2(@typescript-eslint/parser@5.59.2)(eslint@8.39.0)(typescript@5.0.4) '@typescript-eslint/parser': - specifier: 5.57.1 - version: 5.57.1(eslint@8.37.0)(typescript@5.0.3) + specifier: 5.59.2 + version: 5.59.2(eslint@8.39.0)(typescript@5.0.4) eslint: - specifier: 8.37.0 - version: 8.37.0 + specifier: 8.39.0 + version: 8.39.0 jest: specifier: 29.5.0 - version: 29.5.0(@types/node@18.15.11) + version: 29.5.0(@types/node@18.16.3) jest-fetch-mock: specifier: 3.0.3 version: 3.0.3 @@ -1008,14 +1053,14 @@ importers: specifier: 0.28.1 version: 0.28.1 typescript: - specifier: 5.0.3 - version: 5.0.3 + specifier: 5.0.4 + version: 5.0.4 packages/sw: dependencies: esbuild: - specifier: 0.17.15 - version: 0.17.15 + specifier: 0.17.18 + version: 0.17.18 idb-keyval: specifier: 6.2.0 version: 6.2.0 @@ -1024,20 +1069,20 @@ importers: version: link:../misskey-js devDependencies: '@typescript-eslint/parser': - specifier: 5.57.1 - version: 5.57.1(eslint@8.37.0)(typescript@5.0.3) + specifier: 5.59.2 + version: 5.59.2(eslint@8.39.0)(typescript@5.0.4) '@typescript/lib-webworker': specifier: npm:@types/serviceworker@0.0.67 version: /@types/serviceworker@0.0.67 eslint: - specifier: 8.37.0 - version: 8.37.0 + specifier: 8.39.0 + version: 8.39.0 eslint-plugin-import: specifier: 2.27.5 - version: 2.27.5(@typescript-eslint/parser@5.57.1)(eslint@8.37.0) + version: 2.27.5(@typescript-eslint/parser@5.59.2)(eslint@8.39.0) typescript: - specifier: 5.0.3 - version: 5.0.3 + specifier: 5.0.4 + version: 5.0.4 packages: @@ -1057,7 +1102,7 @@ packages: resolution: {integrity: sha512-xguP2WR2Dv0gQ7Ykbdb7BNCnPnIPB94uTi0Z2NvkRBEnhbwjOQ7QyQKJXrVQg4qDpiD9hA5l5cCwy/z2OXgc3w==} dependencies: grapheme-splitter: 1.0.4 - dev: true + dev: false /@aw-web-design/x-default-browser@1.4.88: resolution: {integrity: sha512-AkEmF0wcwYC2QkhK703Y83fxWARttIWXDmQN8+cof8FmFZ5BRhnNXGymeb1S73bOCLfWjYELxtujL56idCN/XA==} @@ -1070,7 +1115,7 @@ packages: resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==} dependencies: '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 tslib: 1.14.1 dev: false @@ -1078,7 +1123,7 @@ packages: resolution: {integrity: sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==} dependencies: '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 tslib: 1.14.1 dev: false @@ -1094,7 +1139,7 @@ packages: '@aws-crypto/ie11-detection': 3.0.0 '@aws-crypto/supports-web-crypto': 3.0.0 '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 '@aws-sdk/util-locate-window': 3.208.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 @@ -1107,7 +1152,7 @@ packages: '@aws-crypto/sha256-js': 3.0.0 '@aws-crypto/supports-web-crypto': 3.0.0 '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 '@aws-sdk/util-locate-window': 3.208.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 @@ -1117,7 +1162,7 @@ packages: resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==} dependencies: '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 tslib: 1.14.1 dev: false @@ -1130,81 +1175,81 @@ packages: /@aws-crypto/util@3.0.0: resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} dependencies: - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 dev: false - /@aws-sdk/abort-controller@3.306.0: - resolution: {integrity: sha512-ewCvdUrMJMlnkNaqXdG7L2H6O7CDI036y6lkTU8gQqa2lCzZvqBkzz6R5NbWqb8TJPi69Z7lXEITgk2b0+pl6w==} + /@aws-sdk/abort-controller@3.310.0: + resolution: {integrity: sha512-v1zrRQxDLA1MdPim159Vx/CPHqsB4uybSxRi1CnfHO5ZjHryx3a5htW2gdGAykVCul40+yJXvfpufMrELVxH+g==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/chunked-blob-reader@3.303.0: - resolution: {integrity: sha512-Cofcujz08TTKA7CtsIcWyFTyfe84KBa72kTjsXA9GQgn0cuUHZgGoFpBzoyDT8Ff3TPMKmAGDXmllRGFZw4mQw==} + /@aws-sdk/chunked-blob-reader@3.310.0: + resolution: {integrity: sha512-CrJS3exo4mWaLnWxfCH+w88Ou0IcAZSIkk4QbmxiHl/5Dq705OLoxf4385MVyExpqpeVJYOYQ2WaD8i/pQZ2fg==} dependencies: tslib: 2.5.0 dev: false - /@aws-sdk/client-s3@3.306.0: - resolution: {integrity: sha512-UkR5jFomspBAXSeAX/1T+YdUMusjg6xDHNvngL9JcW6qbtVv2zDuaGQ65474n2eFr4iJfgVL+IhKy+GNTZ03HA==} + /@aws-sdk/client-s3@3.321.1: + resolution: {integrity: sha512-SndPRdeofP2j1kPDLoPbJL8DzzjSciFb1S+Tda3UljOy9gQl68OAruwKloXHJE8GRkLJnYowlwLu36H1MvADJg==} engines: {node: '>=14.0.0'} dependencies: '@aws-crypto/sha1-browser': 3.0.0 '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.306.0 - '@aws-sdk/config-resolver': 3.306.0 - '@aws-sdk/credential-provider-node': 3.306.0 - '@aws-sdk/eventstream-serde-browser': 3.306.0 - '@aws-sdk/eventstream-serde-config-resolver': 3.306.0 - '@aws-sdk/eventstream-serde-node': 3.306.0 - '@aws-sdk/fetch-http-handler': 3.306.0 - '@aws-sdk/hash-blob-browser': 3.306.0 - '@aws-sdk/hash-node': 3.306.0 - '@aws-sdk/hash-stream-node': 3.306.0 - '@aws-sdk/invalid-dependency': 3.306.0 - '@aws-sdk/md5-js': 3.306.0 - '@aws-sdk/middleware-bucket-endpoint': 3.306.0 - '@aws-sdk/middleware-content-length': 3.306.0 - '@aws-sdk/middleware-endpoint': 3.306.0 - '@aws-sdk/middleware-expect-continue': 3.306.0 - '@aws-sdk/middleware-flexible-checksums': 3.306.0 - '@aws-sdk/middleware-host-header': 3.306.0 - '@aws-sdk/middleware-location-constraint': 3.306.0 - '@aws-sdk/middleware-logger': 3.306.0 - '@aws-sdk/middleware-recursion-detection': 3.306.0 - '@aws-sdk/middleware-retry': 3.306.0 - '@aws-sdk/middleware-sdk-s3': 3.306.0 - '@aws-sdk/middleware-serde': 3.306.0 - '@aws-sdk/middleware-signing': 3.306.0 - '@aws-sdk/middleware-ssec': 3.306.0 - '@aws-sdk/middleware-stack': 3.306.0 - '@aws-sdk/middleware-user-agent': 3.306.0 - '@aws-sdk/node-config-provider': 3.306.0 - '@aws-sdk/node-http-handler': 3.306.0 - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/signature-v4-multi-region': 3.306.0 - '@aws-sdk/smithy-client': 3.306.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/url-parser': 3.306.0 - '@aws-sdk/util-base64': 3.303.0 - '@aws-sdk/util-body-length-browser': 3.303.0 - '@aws-sdk/util-body-length-node': 3.303.0 - '@aws-sdk/util-defaults-mode-browser': 3.306.0 - '@aws-sdk/util-defaults-mode-node': 3.306.0 - '@aws-sdk/util-endpoints': 3.306.0 - '@aws-sdk/util-retry': 3.306.0 - '@aws-sdk/util-stream-browser': 3.306.0 - '@aws-sdk/util-stream-node': 3.306.0 - '@aws-sdk/util-user-agent-browser': 3.306.0 - '@aws-sdk/util-user-agent-node': 3.306.0 - '@aws-sdk/util-utf8': 3.303.0 - '@aws-sdk/util-waiter': 3.306.0 - '@aws-sdk/xml-builder': 3.303.0 + '@aws-sdk/client-sts': 3.321.1 + '@aws-sdk/config-resolver': 3.310.0 + '@aws-sdk/credential-provider-node': 3.321.1 + '@aws-sdk/eventstream-serde-browser': 3.310.0 + '@aws-sdk/eventstream-serde-config-resolver': 3.310.0 + '@aws-sdk/eventstream-serde-node': 3.310.0 + '@aws-sdk/fetch-http-handler': 3.310.0 + '@aws-sdk/hash-blob-browser': 3.310.0 + '@aws-sdk/hash-node': 3.310.0 + '@aws-sdk/hash-stream-node': 3.310.0 + '@aws-sdk/invalid-dependency': 3.310.0 + '@aws-sdk/md5-js': 3.310.0 + '@aws-sdk/middleware-bucket-endpoint': 3.310.0 + '@aws-sdk/middleware-content-length': 3.310.0 + '@aws-sdk/middleware-endpoint': 3.310.0 + '@aws-sdk/middleware-expect-continue': 3.310.0 + '@aws-sdk/middleware-flexible-checksums': 3.310.0 + '@aws-sdk/middleware-host-header': 3.310.0 + '@aws-sdk/middleware-location-constraint': 3.310.0 + '@aws-sdk/middleware-logger': 3.310.0 + '@aws-sdk/middleware-recursion-detection': 3.310.0 + '@aws-sdk/middleware-retry': 3.310.0 + '@aws-sdk/middleware-sdk-s3': 3.310.0 + '@aws-sdk/middleware-serde': 3.310.0 + '@aws-sdk/middleware-signing': 3.310.0 + '@aws-sdk/middleware-ssec': 3.310.0 + '@aws-sdk/middleware-stack': 3.310.0 + '@aws-sdk/middleware-user-agent': 3.319.0 + '@aws-sdk/node-config-provider': 3.310.0 + '@aws-sdk/node-http-handler': 3.321.1 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/signature-v4-multi-region': 3.310.0 + '@aws-sdk/smithy-client': 3.316.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/url-parser': 3.310.0 + '@aws-sdk/util-base64': 3.310.0 + '@aws-sdk/util-body-length-browser': 3.310.0 + '@aws-sdk/util-body-length-node': 3.310.0 + '@aws-sdk/util-defaults-mode-browser': 3.316.0 + '@aws-sdk/util-defaults-mode-node': 3.316.0 + '@aws-sdk/util-endpoints': 3.319.0 + '@aws-sdk/util-retry': 3.310.0 + '@aws-sdk/util-stream-browser': 3.310.0 + '@aws-sdk/util-stream-node': 3.321.1 + '@aws-sdk/util-user-agent-browser': 3.310.0 + '@aws-sdk/util-user-agent-node': 3.310.0 + '@aws-sdk/util-utf8': 3.310.0 + '@aws-sdk/util-waiter': 3.310.0 + '@aws-sdk/xml-builder': 3.310.0 fast-xml-parser: 4.1.2 tslib: 2.5.0 transitivePeerDependencies: @@ -1212,581 +1257,581 @@ packages: - aws-crt dev: false - /@aws-sdk/client-sso-oidc@3.306.0: - resolution: {integrity: sha512-O27yrApCkbC0/uPRb1aHkENpFSqrkPbXRi76NF/8T97qC8bngRpy6yeafcQRrp9NGQSF/m9xbPWYsQuiurqedw==} + /@aws-sdk/client-sso-oidc@3.321.1: + resolution: {integrity: sha512-PBVfHQbyrsfzbnO6u9d9Sik8JlXGLhHj3zLd87iBkYXBdHwD5NuvwWu7OtjUtrHjP4SfzodVwfjmTbDAFqbtzw==} engines: {node: '>=14.0.0'} dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/config-resolver': 3.306.0 - '@aws-sdk/fetch-http-handler': 3.306.0 - '@aws-sdk/hash-node': 3.306.0 - '@aws-sdk/invalid-dependency': 3.306.0 - '@aws-sdk/middleware-content-length': 3.306.0 - '@aws-sdk/middleware-endpoint': 3.306.0 - '@aws-sdk/middleware-host-header': 3.306.0 - '@aws-sdk/middleware-logger': 3.306.0 - '@aws-sdk/middleware-recursion-detection': 3.306.0 - '@aws-sdk/middleware-retry': 3.306.0 - '@aws-sdk/middleware-serde': 3.306.0 - '@aws-sdk/middleware-stack': 3.306.0 - '@aws-sdk/middleware-user-agent': 3.306.0 - '@aws-sdk/node-config-provider': 3.306.0 - '@aws-sdk/node-http-handler': 3.306.0 - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/smithy-client': 3.306.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/url-parser': 3.306.0 - '@aws-sdk/util-base64': 3.303.0 - '@aws-sdk/util-body-length-browser': 3.303.0 - '@aws-sdk/util-body-length-node': 3.303.0 - '@aws-sdk/util-defaults-mode-browser': 3.306.0 - '@aws-sdk/util-defaults-mode-node': 3.306.0 - '@aws-sdk/util-endpoints': 3.306.0 - '@aws-sdk/util-retry': 3.306.0 - '@aws-sdk/util-user-agent-browser': 3.306.0 - '@aws-sdk/util-user-agent-node': 3.306.0 - '@aws-sdk/util-utf8': 3.303.0 + '@aws-sdk/config-resolver': 3.310.0 + '@aws-sdk/fetch-http-handler': 3.310.0 + '@aws-sdk/hash-node': 3.310.0 + '@aws-sdk/invalid-dependency': 3.310.0 + '@aws-sdk/middleware-content-length': 3.310.0 + '@aws-sdk/middleware-endpoint': 3.310.0 + '@aws-sdk/middleware-host-header': 3.310.0 + '@aws-sdk/middleware-logger': 3.310.0 + '@aws-sdk/middleware-recursion-detection': 3.310.0 + '@aws-sdk/middleware-retry': 3.310.0 + '@aws-sdk/middleware-serde': 3.310.0 + '@aws-sdk/middleware-stack': 3.310.0 + '@aws-sdk/middleware-user-agent': 3.319.0 + '@aws-sdk/node-config-provider': 3.310.0 + '@aws-sdk/node-http-handler': 3.321.1 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/smithy-client': 3.316.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/url-parser': 3.310.0 + '@aws-sdk/util-base64': 3.310.0 + '@aws-sdk/util-body-length-browser': 3.310.0 + '@aws-sdk/util-body-length-node': 3.310.0 + '@aws-sdk/util-defaults-mode-browser': 3.316.0 + '@aws-sdk/util-defaults-mode-node': 3.316.0 + '@aws-sdk/util-endpoints': 3.319.0 + '@aws-sdk/util-retry': 3.310.0 + '@aws-sdk/util-user-agent-browser': 3.310.0 + '@aws-sdk/util-user-agent-node': 3.310.0 + '@aws-sdk/util-utf8': 3.310.0 tslib: 2.5.0 transitivePeerDependencies: - aws-crt dev: false - /@aws-sdk/client-sso@3.306.0: - resolution: {integrity: sha512-uqfLUOP9LlBoqXe3P250TPX3fGrabfRt9Q9rlLFK0fVBI7HPIQ/wsPplLoPrMeT04qQmTI03UnVKMNza3GqyIg==} + /@aws-sdk/client-sso@3.321.1: + resolution: {integrity: sha512-ecoT4tBGtRJR5G7oLBTMXZmgZZlff1amhSdKPEtkWxv6kWc8VPb5rRuRgVPsDR9HuesI6ZVlODptvGtnfkIJwA==} engines: {node: '>=14.0.0'} dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/config-resolver': 3.306.0 - '@aws-sdk/fetch-http-handler': 3.306.0 - '@aws-sdk/hash-node': 3.306.0 - '@aws-sdk/invalid-dependency': 3.306.0 - '@aws-sdk/middleware-content-length': 3.306.0 - '@aws-sdk/middleware-endpoint': 3.306.0 - '@aws-sdk/middleware-host-header': 3.306.0 - '@aws-sdk/middleware-logger': 3.306.0 - '@aws-sdk/middleware-recursion-detection': 3.306.0 - '@aws-sdk/middleware-retry': 3.306.0 - '@aws-sdk/middleware-serde': 3.306.0 - '@aws-sdk/middleware-stack': 3.306.0 - '@aws-sdk/middleware-user-agent': 3.306.0 - '@aws-sdk/node-config-provider': 3.306.0 - '@aws-sdk/node-http-handler': 3.306.0 - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/smithy-client': 3.306.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/url-parser': 3.306.0 - '@aws-sdk/util-base64': 3.303.0 - '@aws-sdk/util-body-length-browser': 3.303.0 - '@aws-sdk/util-body-length-node': 3.303.0 - '@aws-sdk/util-defaults-mode-browser': 3.306.0 - '@aws-sdk/util-defaults-mode-node': 3.306.0 - '@aws-sdk/util-endpoints': 3.306.0 - '@aws-sdk/util-retry': 3.306.0 - '@aws-sdk/util-user-agent-browser': 3.306.0 - '@aws-sdk/util-user-agent-node': 3.306.0 - '@aws-sdk/util-utf8': 3.303.0 + '@aws-sdk/config-resolver': 3.310.0 + '@aws-sdk/fetch-http-handler': 3.310.0 + '@aws-sdk/hash-node': 3.310.0 + '@aws-sdk/invalid-dependency': 3.310.0 + '@aws-sdk/middleware-content-length': 3.310.0 + '@aws-sdk/middleware-endpoint': 3.310.0 + '@aws-sdk/middleware-host-header': 3.310.0 + '@aws-sdk/middleware-logger': 3.310.0 + '@aws-sdk/middleware-recursion-detection': 3.310.0 + '@aws-sdk/middleware-retry': 3.310.0 + '@aws-sdk/middleware-serde': 3.310.0 + '@aws-sdk/middleware-stack': 3.310.0 + '@aws-sdk/middleware-user-agent': 3.319.0 + '@aws-sdk/node-config-provider': 3.310.0 + '@aws-sdk/node-http-handler': 3.321.1 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/smithy-client': 3.316.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/url-parser': 3.310.0 + '@aws-sdk/util-base64': 3.310.0 + '@aws-sdk/util-body-length-browser': 3.310.0 + '@aws-sdk/util-body-length-node': 3.310.0 + '@aws-sdk/util-defaults-mode-browser': 3.316.0 + '@aws-sdk/util-defaults-mode-node': 3.316.0 + '@aws-sdk/util-endpoints': 3.319.0 + '@aws-sdk/util-retry': 3.310.0 + '@aws-sdk/util-user-agent-browser': 3.310.0 + '@aws-sdk/util-user-agent-node': 3.310.0 + '@aws-sdk/util-utf8': 3.310.0 tslib: 2.5.0 transitivePeerDependencies: - aws-crt dev: false - /@aws-sdk/client-sts@3.306.0: - resolution: {integrity: sha512-LivDrH0OnAZDC3EB6hVrrl25itlMLn/C/epwDjpnH2Qdq+gjbZ0ElVNu8XOX4qaXoo0zyV5pztnzwD/A76mX2g==} + /@aws-sdk/client-sts@3.321.1: + resolution: {integrity: sha512-AB+N4a1TVEKl9Sd5O2TxTprEZp7Va6zPZLMraFAYMdmJVBmCmmwyBs7ygju685DpQ1dos5PRsKCRcossyY5pDQ==} engines: {node: '>=14.0.0'} dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/config-resolver': 3.306.0 - '@aws-sdk/credential-provider-node': 3.306.0 - '@aws-sdk/fetch-http-handler': 3.306.0 - '@aws-sdk/hash-node': 3.306.0 - '@aws-sdk/invalid-dependency': 3.306.0 - '@aws-sdk/middleware-content-length': 3.306.0 - '@aws-sdk/middleware-endpoint': 3.306.0 - '@aws-sdk/middleware-host-header': 3.306.0 - '@aws-sdk/middleware-logger': 3.306.0 - '@aws-sdk/middleware-recursion-detection': 3.306.0 - '@aws-sdk/middleware-retry': 3.306.0 - '@aws-sdk/middleware-sdk-sts': 3.306.0 - '@aws-sdk/middleware-serde': 3.306.0 - '@aws-sdk/middleware-signing': 3.306.0 - '@aws-sdk/middleware-stack': 3.306.0 - '@aws-sdk/middleware-user-agent': 3.306.0 - '@aws-sdk/node-config-provider': 3.306.0 - '@aws-sdk/node-http-handler': 3.306.0 - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/smithy-client': 3.306.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/url-parser': 3.306.0 - '@aws-sdk/util-base64': 3.303.0 - '@aws-sdk/util-body-length-browser': 3.303.0 - '@aws-sdk/util-body-length-node': 3.303.0 - '@aws-sdk/util-defaults-mode-browser': 3.306.0 - '@aws-sdk/util-defaults-mode-node': 3.306.0 - '@aws-sdk/util-endpoints': 3.306.0 - '@aws-sdk/util-retry': 3.306.0 - '@aws-sdk/util-user-agent-browser': 3.306.0 - '@aws-sdk/util-user-agent-node': 3.306.0 - '@aws-sdk/util-utf8': 3.303.0 + '@aws-sdk/config-resolver': 3.310.0 + '@aws-sdk/credential-provider-node': 3.321.1 + '@aws-sdk/fetch-http-handler': 3.310.0 + '@aws-sdk/hash-node': 3.310.0 + '@aws-sdk/invalid-dependency': 3.310.0 + '@aws-sdk/middleware-content-length': 3.310.0 + '@aws-sdk/middleware-endpoint': 3.310.0 + '@aws-sdk/middleware-host-header': 3.310.0 + '@aws-sdk/middleware-logger': 3.310.0 + '@aws-sdk/middleware-recursion-detection': 3.310.0 + '@aws-sdk/middleware-retry': 3.310.0 + '@aws-sdk/middleware-sdk-sts': 3.310.0 + '@aws-sdk/middleware-serde': 3.310.0 + '@aws-sdk/middleware-signing': 3.310.0 + '@aws-sdk/middleware-stack': 3.310.0 + '@aws-sdk/middleware-user-agent': 3.319.0 + '@aws-sdk/node-config-provider': 3.310.0 + '@aws-sdk/node-http-handler': 3.321.1 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/smithy-client': 3.316.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/url-parser': 3.310.0 + '@aws-sdk/util-base64': 3.310.0 + '@aws-sdk/util-body-length-browser': 3.310.0 + '@aws-sdk/util-body-length-node': 3.310.0 + '@aws-sdk/util-defaults-mode-browser': 3.316.0 + '@aws-sdk/util-defaults-mode-node': 3.316.0 + '@aws-sdk/util-endpoints': 3.319.0 + '@aws-sdk/util-retry': 3.310.0 + '@aws-sdk/util-user-agent-browser': 3.310.0 + '@aws-sdk/util-user-agent-node': 3.310.0 + '@aws-sdk/util-utf8': 3.310.0 fast-xml-parser: 4.1.2 tslib: 2.5.0 transitivePeerDependencies: - aws-crt dev: false - /@aws-sdk/config-resolver@3.306.0: - resolution: {integrity: sha512-kpqHu6LvNMYxullm+tLCsY6KQ2mZUxZTdyWJKTYLZCTxj4HcGJxf4Jxj9dwFAZVl/clcVPGWcHJaQJjyjwzBzw==} + /@aws-sdk/config-resolver@3.310.0: + resolution: {integrity: sha512-8vsT+/50lOqfDxka9m/rRt6oxv1WuGZoP8oPMk0Dt+TxXMbAzf4+rejBgiB96wshI1k3gLokYRjSQZn+dDtT8g==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-config-provider': 3.295.0 - '@aws-sdk/util-middleware': 3.306.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-config-provider': 3.310.0 + '@aws-sdk/util-middleware': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/credential-provider-env@3.306.0: - resolution: {integrity: sha512-DTH+aMvMu+LAoWW+yfPkWzFXt/CPNFQ7+/4xiMnc7FWf+tjt+HZIrPECAV2rBVppNCkh7PC+xDSN61PFvBYOsw==} + /@aws-sdk/credential-provider-env@3.310.0: + resolution: {integrity: sha512-vvIPQpI16fj95xwS7M3D48F7QhZJBnnCgB5lR+b7So+vsG9ibm1mZRVGzVpdxCvgyOhHFbvrby9aalNJmmIP1A==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/property-provider': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/property-provider': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/credential-provider-imds@3.306.0: - resolution: {integrity: sha512-WdrNhq2MwvjZk2I8Of+bZ/qWHG2hREQpwlBiG3tMeEkuywx7M1x3Rt0eHgiR1sTcm05kxNn0rB4OeWOeek37cA==} + /@aws-sdk/credential-provider-imds@3.310.0: + resolution: {integrity: sha512-baxK7Zp6dai5AGW01FIW27xS2KAaPUmKLIXv5SvFYsUgXXvNW55im4uG3b+2gA0F7V+hXvVBH08OEqmwW6we5w==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/node-config-provider': 3.306.0 - '@aws-sdk/property-provider': 3.306.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/url-parser': 3.306.0 + '@aws-sdk/node-config-provider': 3.310.0 + '@aws-sdk/property-provider': 3.310.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/url-parser': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/credential-provider-ini@3.306.0: - resolution: {integrity: sha512-6VvP0YmXVd+pCnlD2iTDhNvO2Ikzyk9Ade/t5R1eZ4Vf1gKhDiNA2/AgDt9XlzQHk7iw1okTmYCeQsK1j+7+NQ==} + /@aws-sdk/credential-provider-ini@3.321.1: + resolution: {integrity: sha512-prndSVQhiikNaI40bYnM2Q8PkC35FCwhbQnBk6KXNvdtfo9RqatMC639F+6oryb3BuMy++Ij4Yoi8WnPBs5Sww==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/credential-provider-env': 3.306.0 - '@aws-sdk/credential-provider-imds': 3.306.0 - '@aws-sdk/credential-provider-process': 3.306.0 - '@aws-sdk/credential-provider-sso': 3.306.0 - '@aws-sdk/credential-provider-web-identity': 3.306.0 - '@aws-sdk/property-provider': 3.306.0 - '@aws-sdk/shared-ini-file-loader': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/credential-provider-env': 3.310.0 + '@aws-sdk/credential-provider-imds': 3.310.0 + '@aws-sdk/credential-provider-process': 3.310.0 + '@aws-sdk/credential-provider-sso': 3.321.1 + '@aws-sdk/credential-provider-web-identity': 3.310.0 + '@aws-sdk/property-provider': 3.310.0 + '@aws-sdk/shared-ini-file-loader': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 transitivePeerDependencies: - aws-crt dev: false - /@aws-sdk/credential-provider-node@3.306.0: - resolution: {integrity: sha512-HYuMmABRzbVWo03CElRUa+T+yenyUmLkwNCVAAvIRmbr9TnLT/bJbplXpUSzgSCS6T3TgwbQ9zf9xY9tX+gHzA==} + /@aws-sdk/credential-provider-node@3.321.1: + resolution: {integrity: sha512-5B1waOwSvY2JMLGRebo7IUqnTaGoCnby9cRbG/dhi7Ke97M3V8380S9THDJ/bktjL8zHEVfBVZy7HhXHzhSjEg==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/credential-provider-env': 3.306.0 - '@aws-sdk/credential-provider-imds': 3.306.0 - '@aws-sdk/credential-provider-ini': 3.306.0 - '@aws-sdk/credential-provider-process': 3.306.0 - '@aws-sdk/credential-provider-sso': 3.306.0 - '@aws-sdk/credential-provider-web-identity': 3.306.0 - '@aws-sdk/property-provider': 3.306.0 - '@aws-sdk/shared-ini-file-loader': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/credential-provider-env': 3.310.0 + '@aws-sdk/credential-provider-imds': 3.310.0 + '@aws-sdk/credential-provider-ini': 3.321.1 + '@aws-sdk/credential-provider-process': 3.310.0 + '@aws-sdk/credential-provider-sso': 3.321.1 + '@aws-sdk/credential-provider-web-identity': 3.310.0 + '@aws-sdk/property-provider': 3.310.0 + '@aws-sdk/shared-ini-file-loader': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 transitivePeerDependencies: - aws-crt dev: false - /@aws-sdk/credential-provider-process@3.306.0: - resolution: {integrity: sha512-2RezGskHqJeHtGbK7CqhGNAoqXgQJb7FfPFqwUQ9oVDZS8f145jVwajjHcc7Qn3IwGoqylMF3uXIljUv89uDzA==} + /@aws-sdk/credential-provider-process@3.310.0: + resolution: {integrity: sha512-h73sg6GPMUWC+3zMCbA1nZ2O03nNJt7G96JdmnantiXBwHpRKWW8nBTLzx5uhXn6hTuTaoQRP/P+oxQJKYdMmA==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/property-provider': 3.306.0 - '@aws-sdk/shared-ini-file-loader': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/property-provider': 3.310.0 + '@aws-sdk/shared-ini-file-loader': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/credential-provider-sso@3.306.0: - resolution: {integrity: sha512-6msBUisMdOzk0ywJQNunZIb0rVMaA6GTx7ek8aCuWInX+lJm0oEPPVp+b3ewwVheih1rRC2bgNk8eAjfC9YcKw==} + /@aws-sdk/credential-provider-sso@3.321.1: + resolution: {integrity: sha512-kg0rc1OacJFgAvmZj0TOu+BSc+yRdnC5dO/RAag3XU6+hlQI5/C080RQp9Qj6V7ga0HtAJMRwJcUlCPA3RJPug==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/client-sso': 3.306.0 - '@aws-sdk/property-provider': 3.306.0 - '@aws-sdk/shared-ini-file-loader': 3.306.0 - '@aws-sdk/token-providers': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/client-sso': 3.321.1 + '@aws-sdk/property-provider': 3.310.0 + '@aws-sdk/shared-ini-file-loader': 3.310.0 + '@aws-sdk/token-providers': 3.321.1 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 transitivePeerDependencies: - aws-crt dev: false - /@aws-sdk/credential-provider-web-identity@3.306.0: - resolution: {integrity: sha512-MOQGQaOtdo4zLQZ1bRjD2n1PUzfNty+sKe+1wlm5bIqTN93UX3S8f0QznucZr7uJxI4Z14ZLwuYeAUV4Tgchlw==} + /@aws-sdk/credential-provider-web-identity@3.310.0: + resolution: {integrity: sha512-H4SzuZXILNhK6/IR1uVvsUDZvzc051hem7GLyYghBCu8mU+tq28YhKE8MfSroi6eL2e5Vujloij1OM2EQQkPkw==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/property-provider': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/property-provider': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/eventstream-codec@3.306.0: - resolution: {integrity: sha512-tQCo0tl/NdD3rVt9hDkGO3SLPXdohlRx6j9IMor5HpM7tCFcB4WNpDHPHyClxRoprLT04BZksJZcbVUybAeTcA==} + /@aws-sdk/eventstream-codec@3.310.0: + resolution: {integrity: sha512-clIeSgWbZbxwtsxZ/yoedNM0/kJFSIjjHPikuDGhxhqc+vP6TN3oYyVMFrYwFaTFhk2+S5wZcWYMw8Op1pWo+A==} dependencies: '@aws-crypto/crc32': 3.0.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-hex-encoding': 3.295.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-hex-encoding': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/eventstream-serde-browser@3.306.0: - resolution: {integrity: sha512-Vbx0KQzZ5YAeYwC3tbCGsJZ2nSqKQlQFbuwhXe/Btz/dh1HaJPMxoIC8Dr0b6Z7S8l2Pq4ucqdNaUzLeJJbezw==} + /@aws-sdk/eventstream-serde-browser@3.310.0: + resolution: {integrity: sha512-3S6ziuQVALgEyz0TANGtYDVeG8ArK4Y05mcgrs8qUTmsvlDIXX37cR/DvmVbNB76M4IrsZeSAIajL9644CywkA==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/eventstream-serde-universal': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/eventstream-serde-universal': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/eventstream-serde-config-resolver@3.306.0: - resolution: {integrity: sha512-MBNJspHTbP3+bZeDgfH+FcAxBodKH3NnXmSCyUosMJMaNQ8JtqsaW+y6FfvC6aVht3FyC6qKWBjuKfEeG2jLCA==} + /@aws-sdk/eventstream-serde-config-resolver@3.310.0: + resolution: {integrity: sha512-8s1Qdn9STj+sV75nUp9yt0W6fHS4BZ2jTm4Z/1Pcbvh2Gqs0WjH5n2StS+pDW5Y9J/HSGBl0ogmUr5lC5bXFHg==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/eventstream-serde-node@3.306.0: - resolution: {integrity: sha512-Fq6n8vUMxMSqWFD2xSjh/X7Wyr+CggGHjsVgIFfXIXEK14iTOnOK7/yS86mDR6GBRupIfrw/Q+Jf0A7b8o2Wlg==} + /@aws-sdk/eventstream-serde-node@3.310.0: + resolution: {integrity: sha512-kSnRomCgW43K9TmQYuwN9+AoYPnhyOKroanUMyZEzJk7rpCPMj4OzaUpXfDYOvznFNYn7NLaH6nHLJAr0VPlJA==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/eventstream-serde-universal': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/eventstream-serde-universal': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/eventstream-serde-universal@3.306.0: - resolution: {integrity: sha512-Xp2JCRkZm3dyVv2O/EJnOP1vwUwEmXXminbX1MgnuVIMifSbbjeo6lGqCsjrIkXyymkx4TvrmPmSWlwzPCnyVw==} + /@aws-sdk/eventstream-serde-universal@3.310.0: + resolution: {integrity: sha512-Qyjt5k/waV5cDukpgT824ISZAz5U0pwzLz5ztR409u85AGNkF/9n7MS+LSyBUBSb0WJ5pUeSD47WBk+nLq9Nhw==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/eventstream-codec': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/eventstream-codec': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/fetch-http-handler@3.306.0: - resolution: {integrity: sha512-T8OODOnPpDqkXS+XSMIkd6hf90h833JLN93wq3ibbyD/WvGveufFFHsbsNyccE9+CSv/BjEuN5QbHqTKTp3BlA==} + /@aws-sdk/fetch-http-handler@3.310.0: + resolution: {integrity: sha512-Bi9vIwzdkw1zMcvi/zGzlWS9KfIEnAq4NNhsnCxbQ4OoIRU9wvU+WGZdBBhxg0ZxZmpp1j1aZhU53lLjA07MHw==} dependencies: - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/querystring-builder': 3.306.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-base64': 3.303.0 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/querystring-builder': 3.310.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-base64': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/hash-blob-browser@3.306.0: - resolution: {integrity: sha512-OK3xQ60Vz1VTDgMGk8PjFESQlDjXCLR/q1xc9ODyXBO2/A+ntUxMBcGfu7KYjtGI69g91H/BGhPAy+W5Gg+rmw==} + /@aws-sdk/hash-blob-browser@3.310.0: + resolution: {integrity: sha512-OoR8p0cbypToysLT0v3o2oyjy6+DKrY7GNCAzHOHJK9xmqXCt+DsjKoPeiY7o1sWX2aN6Plmvubj/zWxMKEn/A==} dependencies: - '@aws-sdk/chunked-blob-reader': 3.303.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/chunked-blob-reader': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/hash-node@3.306.0: - resolution: {integrity: sha512-EcSLd6gKoDEEBPZqEv+Ky9gIyefwyyrAJGILGKoYBmcOIY7Y0xKId0hxCa9/1xvWTaVC1u+rA06DGgksZOa78w==} + /@aws-sdk/hash-node@3.310.0: + resolution: {integrity: sha512-NvE2fhRc8GRwCXBfDehxVAWCmVwVMILliAKVPAEr4yz2CkYs0tqU51S48x23dtna07H4qHtgpeNqVTthcIQOEQ==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-buffer-from': 3.303.0 - '@aws-sdk/util-utf8': 3.303.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-buffer-from': 3.310.0 + '@aws-sdk/util-utf8': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/hash-stream-node@3.306.0: - resolution: {integrity: sha512-9owhr/kmCRwRZWAeGnf3k+L5tD/DcLAEMfbIuH8uolEigI+YGRMXnJB0PpyzIyd1N8TM/bm6WW3uvQiFHy7Hxw==} + /@aws-sdk/hash-stream-node@3.310.0: + resolution: {integrity: sha512-ZoXdybNgvMz1Hl6k/e32xVL3jmG5p2IEk5mTtLfFEuskTJ74Z+VMYKkkF1whyy7KQfH83H+TQGnsGtlRCchQKw==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-utf8': 3.303.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-utf8': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/invalid-dependency@3.306.0: - resolution: {integrity: sha512-9Mkcr+qG7QR4R5bJcA8bBNd8E2x6WaZStsQ3QeFbdQr3V3Tunvra/KlCFsEL55GgU8BZt5isOaHqq7uxs5ILtQ==} + /@aws-sdk/invalid-dependency@3.310.0: + resolution: {integrity: sha512-1s5RG5rSPXoa/aZ/Kqr5U/7lqpx+Ry81GprQ2bxWqJvWQIJ0IRUwo5pk8XFxbKVr/2a+4lZT/c3OGoBOM1yRRA==} dependencies: - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/is-array-buffer@3.303.0: - resolution: {integrity: sha512-IitBTr+pou7v5BrYLFH/SbIf3g1LIgMhcI3bDXBq2FjzmDftj4bW8BOmg05b9YKf2TrrggvJ4yk/jH+yYFXoJQ==} + /@aws-sdk/is-array-buffer@3.310.0: + resolution: {integrity: sha512-urnbcCR+h9NWUnmOtet/s4ghvzsidFmspfhYaHAmSRdy9yDjdjBJMFjjsn85A1ODUktztm+cVncXjQ38WCMjMQ==} engines: {node: '>=14.0.0'} dependencies: tslib: 2.5.0 dev: false - /@aws-sdk/lib-storage@3.306.0(@aws-sdk/abort-controller@3.306.0)(@aws-sdk/client-s3@3.306.0): - resolution: {integrity: sha512-/XPTLZ39gxaQGpcZhYW+o/cKpkELt94Exurs/2YSjNv0BiTueDLoY8pPMkXeuspOKlcPLQneX2HnjbvaQXjUhQ==} + /@aws-sdk/lib-storage@3.321.1(@aws-sdk/abort-controller@3.310.0)(@aws-sdk/client-s3@3.321.1): + resolution: {integrity: sha512-a9JZRuq1sc64zgoIIMipVovDgdCuiCaTRB0pY4rjVIvhQVgdyg734rlZbvdDOduiKYz7QvYkqMAdsp0ASV14Gg==} engines: {node: '>=14.0.0'} peerDependencies: '@aws-sdk/abort-controller': ^3.0.0 '@aws-sdk/client-s3': ^3.0.0 dependencies: - '@aws-sdk/abort-controller': 3.306.0 - '@aws-sdk/client-s3': 3.306.0 - '@aws-sdk/middleware-endpoint': 3.306.0 - '@aws-sdk/smithy-client': 3.306.0 + '@aws-sdk/abort-controller': 3.310.0 + '@aws-sdk/client-s3': 3.321.1 + '@aws-sdk/middleware-endpoint': 3.310.0 + '@aws-sdk/smithy-client': 3.316.0 buffer: 5.6.0 events: 3.3.0 stream-browserify: 3.0.0 tslib: 2.5.0 dev: false - /@aws-sdk/md5-js@3.306.0: - resolution: {integrity: sha512-QCv5hfFL3AsSQPZvO8YOaBoNlRu6+B2lVSVZHLaWrortlaRGQi5+fYgYPhpTho/2cw0qeogB4pQIho3UgCAmfg==} + /@aws-sdk/md5-js@3.310.0: + resolution: {integrity: sha512-x5sRBUrEfLWAS1EhwbbDQ7cXq6uvBxh3qR2XAsnGvFFceTeAadk7cVogWxlk3PC+OCeeym7c3/6Bv2HQ2f1YyQ==} dependencies: - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-utf8': 3.303.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-utf8': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/middleware-bucket-endpoint@3.306.0: - resolution: {integrity: sha512-DL8Q146s0nJ7MSHxt8sr6MDR4tIh+q7z9Yki7o1soCMNGTaYCUC20nSffs7IhQAUo1/q3pRh8LoJNtIpa7Q3pA==} + /@aws-sdk/middleware-bucket-endpoint@3.310.0: + resolution: {integrity: sha512-uJJfHI7v4AgbJZRLtyI8ap2QRWkBokGc3iyUoQ+dVNT3/CE2ZCu694A6W+H0dRqg79dIE+f9CRNdtLGa/Ehhvg==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-arn-parser': 3.295.0 - '@aws-sdk/util-config-provider': 3.295.0 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-arn-parser': 3.310.0 + '@aws-sdk/util-config-provider': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/middleware-content-length@3.306.0: - resolution: {integrity: sha512-JbONf2Ms+/DVRcpFNsKGdOQU94Js56KV+AhlPJmCwLxfyWvQjTt0KxFC1Dd+cjeNEXUduvBarrehgsqFlWnoHQ==} + /@aws-sdk/middleware-content-length@3.310.0: + resolution: {integrity: sha512-P8tQZxgDt6CAh1wd/W6WPzjc+uWPJwQkm+F7rAwRlM+k9q17HrhnksGDKcpuuLyIhPQYdmOMIkpKVgXGa4avhQ==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/middleware-endpoint@3.306.0: - resolution: {integrity: sha512-i3QRiwgkcsuVN55O7l8I/QGwCypGRZXdYkPjU56LI2w2oiZ82f/nVMNXVc+ZFm2YH7WbCE+5jguw2J7HXdOlyQ==} + /@aws-sdk/middleware-endpoint@3.310.0: + resolution: {integrity: sha512-Z+N2vOL8K354/lstkClxLLsr6hCpVRh+0tCMXrVj66/NtKysCEZ/0b9LmqOwD9pWHNiI2mJqXwY0gxNlKAroUg==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/middleware-serde': 3.306.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/url-parser': 3.306.0 - '@aws-sdk/util-middleware': 3.306.0 + '@aws-sdk/middleware-serde': 3.310.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/url-parser': 3.310.0 + '@aws-sdk/util-middleware': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/middleware-expect-continue@3.306.0: - resolution: {integrity: sha512-k+UgyQpTqFaizClQRdVTpcGJduRXKS0GCDDV/pdz1NVx09YHCIxVpkpdkYj+2T1Ufp3bK7ju2FLaXJwoO6/r+Q==} + /@aws-sdk/middleware-expect-continue@3.310.0: + resolution: {integrity: sha512-l3d1z2gt+gINJDnPSyu84IxfzjzPfCQrqC1sunw2cZGo/sXtEiq698Q3SiTcO2PGP4LBQAy2RHb5wVBJP708CQ==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/middleware-flexible-checksums@3.306.0: - resolution: {integrity: sha512-0Z3eKIVdP9son45Id18pDWwfcqoYTzDsSfePMD//HByBQQusL8pS1D1mlWqVpnV++ZfNmPcnph+v6cIfHYVM4Q==} + /@aws-sdk/middleware-flexible-checksums@3.310.0: + resolution: {integrity: sha512-5ndnLgzgGVpWkmHBAiYkagHqiSuow8q62J4J6E2PzaQ77+fm8W3nfdy7hK5trHokEyouCZdxT/XK/IRhgj/4PA==} engines: {node: '>=14.0.0'} dependencies: '@aws-crypto/crc32': 3.0.0 '@aws-crypto/crc32c': 3.0.0 - '@aws-sdk/is-array-buffer': 3.303.0 - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-utf8': 3.303.0 + '@aws-sdk/is-array-buffer': 3.310.0 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-utf8': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/middleware-host-header@3.306.0: - resolution: {integrity: sha512-mHDHK9E+c7HwMlrCJ+VFSB6tkq8oJVkYEHCvPkdrnzN/g9P/d/UhPIeGapZXMbAIZEaLpEGqs536mYzeRKZG8A==} + /@aws-sdk/middleware-host-header@3.310.0: + resolution: {integrity: sha512-QWSA+46/hXorXyWa61ic2K7qZzwHTiwfk2e9mRRjeIRepUgI3qxFjsYqrWtrOGBjmFmq0pYIY8Bb/DCJuQqcoA==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/middleware-location-constraint@3.306.0: - resolution: {integrity: sha512-TfSPLDcl/QTs2OHplJhbJMzdWe3/du5wrNn0LECligzX4f7Ygn2zV/UtqHet36tv206ssDm6b9g8KvRvG3FT2g==} + /@aws-sdk/middleware-location-constraint@3.310.0: + resolution: {integrity: sha512-LFm0JTQWwTPWL/tZU2wsQTl8J5PpDEkXjEhaXVKamtyH0xhysRqd+0n92n65dc8oztAuQkb9xUbErGn5b6gsew==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/middleware-logger@3.306.0: - resolution: {integrity: sha512-1FRHp/QB0Lb+CgP+c9CYW6BZh+q+5pnuOKo/Rd6hjYiM+kT1G/cWdXnMJQBR4rbTCTixbqCnObNJ1EyP/ofQhQ==} + /@aws-sdk/middleware-logger@3.310.0: + resolution: {integrity: sha512-Lurm8XofrASBRnAVtiSNuDSRsRqPNg27RIFLLsLp/pqog9nFJ0vz0kgdb9S5Z+zw83Mm+UlqOe6D8NTUNp4fVg==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/middleware-recursion-detection@3.306.0: - resolution: {integrity: sha512-Hpj42ZLmwCy/CtVxi57NTeOEPoUJlivF3VIgowZ9JhaF61cakVKyrJ+f3jwXciDUtuYrdKm5Wf6prW6apWo0YA==} + /@aws-sdk/middleware-recursion-detection@3.310.0: + resolution: {integrity: sha512-SuB75/xk/gyue24gkriTwO2jFd7YcUGZDClQYuRejgbXSa3CO0lWyawQtfLcSSEBp9izrEVXuFH24K1eAft5nQ==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/middleware-retry@3.306.0: - resolution: {integrity: sha512-eMyfr/aeurXXDz4x+WVrvLI8fVDP6klJOjziBEWZ/MUNP/hTFhkiQsMVbvT6O4Pspp7+FgCSdcUPG6Os2gK+CQ==} + /@aws-sdk/middleware-retry@3.310.0: + resolution: {integrity: sha512-oTPsRy2W4s+dfxbJPW7Km+hHtv/OMsNsVfThAq8DDYKC13qlr1aAyOqGLD+dpBy2aKe7ss517Sy2HcHtHqm7/g==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/service-error-classification': 3.306.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-middleware': 3.306.0 - '@aws-sdk/util-retry': 3.306.0 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/service-error-classification': 3.310.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-middleware': 3.310.0 + '@aws-sdk/util-retry': 3.310.0 tslib: 2.5.0 uuid: 8.3.2 dev: false - /@aws-sdk/middleware-sdk-s3@3.306.0: - resolution: {integrity: sha512-x1V4NKpY8waDBTUjg2TLcqLZjD6OoED0NMFvKPxul/l2rzInvcNK6L2ifxxhYb7YG9N4MtLnCvAUqEW5h3eLYw==} + /@aws-sdk/middleware-sdk-s3@3.310.0: + resolution: {integrity: sha512-QK9x9g2ksg0hOjjYgqddeFcn5ctUEGdxJVu4OumPXceulefMcSO2jyH2qTybYSA93nqNQFdFmg5wQfvIRUWFCQ==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-arn-parser': 3.295.0 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-arn-parser': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/middleware-sdk-sts@3.306.0: - resolution: {integrity: sha512-2rSAR3nc5faYuEnh1KxQMCMCkEkJyaDfA3zwWLqZ+/TBCH0PlPkBv+Z9yXmteEki0vI5Hr+e+atTutJZoyG13g==} + /@aws-sdk/middleware-sdk-sts@3.310.0: + resolution: {integrity: sha512-+5PFwlYNLvLLIfw0ASAoWV/iIF8Zv6R6QGtyP0CclhRSvNjgbQDVnV0g95MC5qvh+GB/Yjlkt8qAjLSPjHfsrQ==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/middleware-signing': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/middleware-signing': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/middleware-serde@3.306.0: - resolution: {integrity: sha512-M3gyPLPduZXMvdgt4XEpVO+3t0ZVPdgeQQwG6JnXv0dgyUizshYs4lrVOAb1KwF6StsmkrAgSN+I273elLiKjA==} + /@aws-sdk/middleware-serde@3.310.0: + resolution: {integrity: sha512-RNeeTVWSLTaentUeCgQKZhAl+C6hxtwD78cQWS10UymWpQFwbaxztzKUu4UQS5xA2j6PxwPRRUjqa4jcFjfLsg==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/middleware-signing@3.306.0: - resolution: {integrity: sha512-JhpSriN4xa4a/p5gAPL0OWFKJF4eWYU3K+LLlXBNGMbxg/qNL4skgT4dMFe3ii9EW8kI+r6tpvSgC+lP7/Tyng==} + /@aws-sdk/middleware-signing@3.310.0: + resolution: {integrity: sha512-f9mKq+XMdW207Af3hKjdTnpNhdtwqWuvFs/ZyXoOkp/g1MY1O6L23Jy6i52m29LxbT4AuNRG1oKODfXM0vYVjQ==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/property-provider': 3.306.0 - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/signature-v4': 3.306.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-middleware': 3.306.0 + '@aws-sdk/property-provider': 3.310.0 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/signature-v4': 3.310.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-middleware': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/middleware-ssec@3.306.0: - resolution: {integrity: sha512-4oXDBsQSPWxyr5K9OjQA1Y+WChGYLD85mvcycd3WqKn9BKsQ9F4enE3XHmRg0qlgXV9CJ/fG1axLC3R9Aa6u/g==} + /@aws-sdk/middleware-ssec@3.310.0: + resolution: {integrity: sha512-CnEwNKVpd5bXnrCKPaePF8mWTA9ET21OMBb54y9b0fd8K02zoOcdBz4DWfh1SjFD4HkgCdja4egd8l2ivyvqmw==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/middleware-stack@3.306.0: - resolution: {integrity: sha512-G//a6MVSxyFVpOMZ+dzT3+w7XblOd2tRJ5g+/okjn3pNBLbo5o9Hu33K/bz0SQjT/m5mU2F9m0wcdCPYbRPysg==} + /@aws-sdk/middleware-stack@3.310.0: + resolution: {integrity: sha512-010O1PD+UAcZVKRvqEusE1KJqN96wwrf6QsqbRM0ywsKQ21NDweaHvEDlds2VHpgmofxkRLRu/IDrlPkKRQrRg==} engines: {node: '>=14.0.0'} dependencies: tslib: 2.5.0 dev: false - /@aws-sdk/middleware-user-agent@3.306.0: - resolution: {integrity: sha512-tP6I+Lbs68muPfdMA6Rfc+8fYo49nEn9A3RMiOU2COClWsmiZatpbK9UYlqIOxeGB/s2jI7hXmQq6tT2LStLSg==} + /@aws-sdk/middleware-user-agent@3.319.0: + resolution: {integrity: sha512-ytaLx2dlR5AdMSne6FuDCISVg8hjyKj+cHU20b2CRA/E/z+XXrLrssp4JrCgizRKPPUep0psMIa22Zd6osTT5Q==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-endpoints': 3.306.0 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-endpoints': 3.319.0 tslib: 2.5.0 dev: false - /@aws-sdk/node-config-provider@3.306.0: - resolution: {integrity: sha512-+m+ALxNx5E1zLPPijO1pAbT5tnofLzZFWlnSYBEiOIwzaRU44rLYDqAhgXJkMMbOECkffDrv6ym0oWJIwJI+DA==} + /@aws-sdk/node-config-provider@3.310.0: + resolution: {integrity: sha512-T/Pp6htc6hq/Cq+MLNDSyiwWCMVF6GqbBbXKVlO5L8rdHx4sq9xPdoPveZhGWrxvkanjA6eCwUp6E0riBOSVng==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/property-provider': 3.306.0 - '@aws-sdk/shared-ini-file-loader': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/property-provider': 3.310.0 + '@aws-sdk/shared-ini-file-loader': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/node-http-handler@3.306.0: - resolution: {integrity: sha512-qvNSIVdGf0pnWEXsAulIqXk7LML25Zc1yxbujxoAj8oX5y+mDhzQdHKrMgc0FuI4RKoEd9px4DYoUbmTWrrxwA==} + /@aws-sdk/node-http-handler@3.321.1: + resolution: {integrity: sha512-DdQBrtFFDNtzphJIN3s93Vf+qd9LHSzH6WTQRrWoXhTDMHDzSI2Cn+c5KWfk89Nggp/n3+OTwUPQeCiBT5EBuw==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/abort-controller': 3.306.0 - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/querystring-builder': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/abort-controller': 3.310.0 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/querystring-builder': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/property-provider@3.306.0: - resolution: {integrity: sha512-37PnbjpANjHys0Y+DVmKUz1JbSGZ/mAndZeplTUsFDUtbNwJRw/fDyWUvGC82JWB4gNSP5muWscFvetZnK2l8A==} + /@aws-sdk/property-provider@3.310.0: + resolution: {integrity: sha512-3lxDb0akV6BBzmFe4nLPaoliQbAifyWJhuvuDOu7e8NzouvpQXs0275w9LePhhcgjKAEVXUIse05ZW2DLbxo/g==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/protocol-http@3.306.0: - resolution: {integrity: sha512-6Z8bqB8Ydz/qG7+lJzjwsjIca2w2zp4nZ2HjxMoUm0NBbVXGDx7H9qy9eOUqEiCbdXbsfK2BmVQreLhFLt056Q==} + /@aws-sdk/protocol-http@3.310.0: + resolution: {integrity: sha512-fgZ1aw/irQtnrsR58pS8ThKOWo57Py3xX6giRvwSgZDEcxHfVzuQjy9yPuV++v04fdmdtgpbGf8WfvAAJ11yXQ==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/querystring-builder@3.306.0: - resolution: {integrity: sha512-kvz6fLwE4KojTxbphuo9JPwKKuhau2mmSurnqhtf77t9+0cOh2uzyYhIUtOFewpLj+qGoh4b2EODlJqczc7IKg==} + /@aws-sdk/querystring-builder@3.310.0: + resolution: {integrity: sha512-ZHH8GV/80+pWGo7DzsvwvXR5xVxUHXUvPJPFAkhr6nCf78igdoF8gR10ScFoEKbtEapoNTaZlKHPXxpD8aPG7A==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-uri-escape': 3.303.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-uri-escape': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/querystring-parser@3.306.0: - resolution: {integrity: sha512-YjOdLcyS/8sNkFPgnxyUx+cM/P2XFGCA2WjQ0e9AXX8xFFkmnY6U5w2EknQ5zyvKy+R/KAV0KAMJBUB+ofjg0A==} + /@aws-sdk/querystring-parser@3.310.0: + resolution: {integrity: sha512-YkIznoP6lsiIUHinx++/lbb3tlMURGGqMpo0Pnn32zYzGrJXA6eC3D0as2EcMjo55onTfuLcIiX4qzXes2MYOA==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/service-error-classification@3.306.0: - resolution: {integrity: sha512-lmXIVHWU5J60GmmTgyj79kupWYg5ntyNrUPt1P9FYTsXz+tdk4YYH7/2IxZ1XjBr4jEsN56gfSI0cfT07ztQJA==} + /@aws-sdk/service-error-classification@3.310.0: + resolution: {integrity: sha512-PuyC7k3qfIKeH2LCnDwbttMOKq3qAx4buvg0yfnJtQOz6t1AR8gsnAq0CjKXXyfkXwNKWTqCpE6lVNUIkXgsMw==} engines: {node: '>=14.0.0'} dev: false - /@aws-sdk/shared-ini-file-loader@3.306.0: - resolution: {integrity: sha512-mDmBRN+Y0+EBD5megId97UIJGV/rmRsAds22qy0mmVdD3X7qlxn974btXVgfZyda6qw/pX6hgi8X99Qj6Wjb0w==} + /@aws-sdk/shared-ini-file-loader@3.310.0: + resolution: {integrity: sha512-N0q9pG0xSjQwc690YQND5bofm+4nfUviQ/Ppgan2kU6aU0WUq8KwgHJBto/YEEI+VlrME30jZJnxtOvcZJc2XA==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/signature-v4-multi-region@3.306.0: - resolution: {integrity: sha512-c+J/7oYZf1MWgEqHyzW6EM25FZfqHer+pMWwyaDCEQFsAuuNeqbvr5MWFrjYpSeF8MpfVbFsEfho5XmjHMq6jw==} + /@aws-sdk/signature-v4-multi-region@3.310.0: + resolution: {integrity: sha512-q8W+RIomTS/q85Ntgks/CoDElwqkC9+4OCicee5YznNHjQ4gtNWhUkYIyIRWRmXa/qx/AUreW9DM8FAecCOdng==} engines: {node: '>=14.0.0'} peerDependencies: '@aws-sdk/signature-v4-crt': ^3.118.0 @@ -1794,137 +1839,137 @@ packages: '@aws-sdk/signature-v4-crt': optional: true dependencies: - '@aws-sdk/protocol-http': 3.306.0 - '@aws-sdk/signature-v4': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/protocol-http': 3.310.0 + '@aws-sdk/signature-v4': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/signature-v4@3.306.0: - resolution: {integrity: sha512-yoQTo6wLirKHg34Zhm8tKmfEaK8fOn+psVdMtRs2vGq3uzKLb+YW5zywnujoVwBvygQTWxiDMwRxDduWAisccA==} + /@aws-sdk/signature-v4@3.310.0: + resolution: {integrity: sha512-1M60P1ZBNAjCFv9sYW29OF6okktaeibWyW3lMXqzoHF70lHBZh+838iUchznXUA5FLabfn4jBFWMRxlAXJUY2Q==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/is-array-buffer': 3.303.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-hex-encoding': 3.295.0 - '@aws-sdk/util-middleware': 3.306.0 - '@aws-sdk/util-uri-escape': 3.303.0 - '@aws-sdk/util-utf8': 3.303.0 + '@aws-sdk/is-array-buffer': 3.310.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-hex-encoding': 3.310.0 + '@aws-sdk/util-middleware': 3.310.0 + '@aws-sdk/util-uri-escape': 3.310.0 + '@aws-sdk/util-utf8': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/smithy-client@3.306.0: - resolution: {integrity: sha512-AFdNkto0Md6laio9t70WtvocoZqVcAydbY5csimXQh+lhKVmy/C+ZcKarDvaa0JD6PjSHb4snYzcINFpHW5LJQ==} + /@aws-sdk/smithy-client@3.316.0: + resolution: {integrity: sha512-6YXOKbRnXeS8r8RWzuL6JMBolDYM5Wa4fD/VY6x/wK78i2xErHOvqzHgyyeLI1MMw4uqyd4wRNJNWC9TMPduXw==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/middleware-stack': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/middleware-stack': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/token-providers@3.306.0: - resolution: {integrity: sha512-GQlUx9u+fHLjOJedudLM//j7RSZAip57n59bjn/I3TRVjDs065opNu2xSWMPm1n46kPx6VA5z+DktvuFeAblxQ==} + /@aws-sdk/token-providers@3.321.1: + resolution: {integrity: sha512-I1sXS4qXirSvgvrOIPf+e1D7GvC83DdeyMxHZvuhHgeMCqDAzToS8OLxOX0enN9xZRHWAQYja8xyeGbDL2I0Zw==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/client-sso-oidc': 3.306.0 - '@aws-sdk/property-provider': 3.306.0 - '@aws-sdk/shared-ini-file-loader': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/client-sso-oidc': 3.321.1 + '@aws-sdk/property-provider': 3.310.0 + '@aws-sdk/shared-ini-file-loader': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 transitivePeerDependencies: - aws-crt dev: false - /@aws-sdk/types@3.306.0: - resolution: {integrity: sha512-RnyknWWpQcRmNH7AsNr89sdhOoltCU/4YEwBMw34Eh+/36l7HfA5PdEKbsOkO7MO4+2g5qmmm/AHcnHRvymApg==} + /@aws-sdk/types@3.310.0: + resolution: {integrity: sha512-j8eamQJ7YcIhw7fneUfs8LYl3t01k4uHi4ZDmNRgtbmbmTTG3FZc2MotStZnp3nZB6vLiPF1o5aoJxWVvkzS6A==} engines: {node: '>=14.0.0'} dependencies: tslib: 2.5.0 dev: false - /@aws-sdk/url-parser@3.306.0: - resolution: {integrity: sha512-mhyOjtycZgxKYo2CoDhDQONuRd5TLfEwmyGWVgFrfubF0LejQ3rkBRLC5zT9TBZ8RJHNlqU2oGdsZCy3JV6Rlw==} + /@aws-sdk/url-parser@3.310.0: + resolution: {integrity: sha512-mCLnCaSB9rQvAgx33u0DujLvr4d5yEm/W5r789GblwwQnlNXedVu50QRizMLTpltYWyAUoXjJgQnJHmJMaKXhw==} dependencies: - '@aws-sdk/querystring-parser': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/querystring-parser': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/util-arn-parser@3.295.0: - resolution: {integrity: sha512-kSSVymcbjyQQHvCZaTt1teKKW4MSSMPRdPNxSNO1aLsVwxrWdnAggDrpHwFjvPCRUcKtpThepATOz75PfUm9Bg==} + /@aws-sdk/util-arn-parser@3.310.0: + resolution: {integrity: sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==} engines: {node: '>=14.0.0'} dependencies: tslib: 2.5.0 dev: false - /@aws-sdk/util-base64@3.303.0: - resolution: {integrity: sha512-oj+p/GHHPcZEKjiiOHU/CyNQeh8i+8dfMMzU+VGdoK5jHaVG8h2b+V7GPf7I4wDkG2ySCK5b5Jw5NUHwdTJ13Q==} + /@aws-sdk/util-base64@3.310.0: + resolution: {integrity: sha512-v3+HBKQvqgdzcbL+pFswlx5HQsd9L6ZTlyPVL2LS9nNXnCcR3XgGz9jRskikRUuUvUXtkSG1J88GAOnJ/apTPg==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/util-buffer-from': 3.303.0 + '@aws-sdk/util-buffer-from': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/util-body-length-browser@3.303.0: - resolution: {integrity: sha512-T643m0pKzgjAvPFy4W8zL+aszG3T22U8hb6stlMvT0z++Smv8QfIvkIkXjWyH2KlOt5GKliHwdOv8SAi0FSMJQ==} + /@aws-sdk/util-body-length-browser@3.310.0: + resolution: {integrity: sha512-sxsC3lPBGfpHtNTUoGXMQXLwjmR0zVpx0rSvzTPAuoVILVsp5AU/w5FphNPxD5OVIjNbZv9KsKTuvNTiZjDp9g==} dependencies: tslib: 2.5.0 dev: false - /@aws-sdk/util-body-length-node@3.303.0: - resolution: {integrity: sha512-/hS8z6e18Le60hJr2TUIFoUjUiAsnQsuDn6DxX74GXhMOHeSwZDJ9jHF39quYkNMmAE37GrVH4MI9vE0pN27qw==} + /@aws-sdk/util-body-length-node@3.310.0: + resolution: {integrity: sha512-2tqGXdyKhyA6w4zz7UPoS8Ip+7sayOg9BwHNidiGm2ikbDxm1YrCfYXvCBdwaJxa4hJfRVz+aL9e+d3GqPI9pQ==} engines: {node: '>=14.0.0'} dependencies: tslib: 2.5.0 dev: false - /@aws-sdk/util-buffer-from@3.303.0: - resolution: {integrity: sha512-hUU+NW+SW6RNojtAKnnmz+tDShVKlEx2YsS4a5fSfrKRUes+zWz10cxVX0RQfysd3R6tdSHhbjsSj8eCIybheg==} + /@aws-sdk/util-buffer-from@3.310.0: + resolution: {integrity: sha512-i6LVeXFtGih5Zs8enLrt+ExXY92QV25jtEnTKHsmlFqFAuL3VBeod6boeMXkN2p9lbSVVQ1sAOOYZOHYbYkntw==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/is-array-buffer': 3.303.0 + '@aws-sdk/is-array-buffer': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/util-config-provider@3.295.0: - resolution: {integrity: sha512-/5Dl1aV2yI8YQjqwmg4RTnl/E9NmNsx7HIwBZt+dTcOrM0LMUwczQBFFcLyqCj/qv5y+VsvLoAAA/OiBT7hb3w==} + /@aws-sdk/util-config-provider@3.310.0: + resolution: {integrity: sha512-xIBaYo8dwiojCw8vnUcIL4Z5tyfb1v3yjqyJKJWV/dqKUFOOS0U591plmXbM+M/QkXyML3ypon1f8+BoaDExrg==} engines: {node: '>=14.0.0'} dependencies: tslib: 2.5.0 dev: false - /@aws-sdk/util-defaults-mode-browser@3.306.0: - resolution: {integrity: sha512-XczPC/klGngMNDcNvThloyeKoPoG61ts1tZVcDbyRaOqmoMH80fn+c6Ah4A/BPzbo8wm1MIA9kqeJI0ypps6qQ==} + /@aws-sdk/util-defaults-mode-browser@3.316.0: + resolution: {integrity: sha512-6FSqLhYmaihtH2n1s4b2rlLW0ABU8N6VZIfzLfe2ING4PF0MzfaMMhnTFUHVXfKCVGoR8yP6iyFTRCyHGVEL1w==} engines: {node: '>= 10.0.0'} dependencies: - '@aws-sdk/property-provider': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/property-provider': 3.310.0 + '@aws-sdk/types': 3.310.0 bowser: 2.11.0 tslib: 2.5.0 dev: false - /@aws-sdk/util-defaults-mode-node@3.306.0: - resolution: {integrity: sha512-0hs/cS7Pu4sEO78n0Uv7ybBEFq5j23TOu3QNH+YMzF8n4yuQtaMwNM8DI2s03/pVGXYsPzO7036jREGcu+enXw==} + /@aws-sdk/util-defaults-mode-node@3.316.0: + resolution: {integrity: sha512-dkYy10hdjPSScXXvnjGpZpnJxllkb6ICHgLMwZ4JczLHhPM12T/4PQ758YN8HS+muiYDGX1Bl2z1jd/bMcewBQ==} engines: {node: '>= 10.0.0'} dependencies: - '@aws-sdk/config-resolver': 3.306.0 - '@aws-sdk/credential-provider-imds': 3.306.0 - '@aws-sdk/node-config-provider': 3.306.0 - '@aws-sdk/property-provider': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/config-resolver': 3.310.0 + '@aws-sdk/credential-provider-imds': 3.310.0 + '@aws-sdk/node-config-provider': 3.310.0 + '@aws-sdk/property-provider': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/util-endpoints@3.306.0: - resolution: {integrity: sha512-aPTqU4VGhec8LDhKZrfA3/sBHTYRa0favKEo8aEa/vIZJTNBAFlUhvr5z7peAr8gBOtZZcElzX8PiK3jjn3ILw==} + /@aws-sdk/util-endpoints@3.319.0: + resolution: {integrity: sha512-3I64UMoYA2e2++oOUJXRcFtYLpLylnZFRltWfPo1B3dLlf+MIWat9djT+mMus+hW1ntLsvAIVu1hLVePJC0gvw==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/util-hex-encoding@3.295.0: - resolution: {integrity: sha512-XJcoVo41kHzhe28PBm/rqt5mdCp8R6abwiW9ug1dA6FOoPUO8kBUxDv6xaOmA2hfRvd2ocFfBXaUCBqUowkGcQ==} + /@aws-sdk/util-hex-encoding@3.310.0: + resolution: {integrity: sha512-sVN7mcCCDSJ67pI1ZMtk84SKGqyix6/0A1Ab163YKn+lFBQRMKexleZzpYzNGxYzmQS6VanP/cfU7NiLQOaSfA==} engines: {node: '>=14.0.0'} dependencies: tslib: 2.5.0 @@ -1937,59 +1982,59 @@ packages: tslib: 2.5.0 dev: false - /@aws-sdk/util-middleware@3.306.0: - resolution: {integrity: sha512-14CSm1mTrfSNBGbkZu8vSjXYg7DUMfZc74IinOajcFtTswa/6SyiyhU9DK0a837qqwxSfFGpnE2thVeJIF/7FA==} + /@aws-sdk/util-middleware@3.310.0: + resolution: {integrity: sha512-FTSUKL/eRb9X6uEZClrTe27QFXUNNp7fxYrPndZwk1hlaOP5ix+MIHBcI7pIiiY/JPfOUmPyZOu+HetlFXjWog==} engines: {node: '>=14.0.0'} dependencies: tslib: 2.5.0 dev: false - /@aws-sdk/util-retry@3.306.0: - resolution: {integrity: sha512-zcgTEIehQAIAm4vBNWfXZpDNbIrDM095vZmpbozQwK/pfDqMGvq7j3r9atKuEGTtoomoGoYwj3x/KEhO6JXJLg==} + /@aws-sdk/util-retry@3.310.0: + resolution: {integrity: sha512-FwWGhCBLfoivTMUHu1LIn4NjrN9JLJ/aX5aZmbcPIOhZVFJj638j0qDgZXyfvVqBuBZh7M8kGq0Oahy3dp69OA==} engines: {node: '>= 14.0.0'} dependencies: - '@aws-sdk/service-error-classification': 3.306.0 + '@aws-sdk/service-error-classification': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/util-stream-browser@3.306.0: - resolution: {integrity: sha512-g/UdUlVtMrdctzu8Yk6UKIlN9OtqGyTDC3bCzzr6dNmfdRMuilL5LZCAgusEEzH2MZGTNtw5BbYZBN//UNhxwQ==} + /@aws-sdk/util-stream-browser@3.310.0: + resolution: {integrity: sha512-bysXZHwFwvbqOTCScCdCnoLk1K3GCo0HRIYEZuL7O7MHrQmfaYRXcaft/p22+GUv9VeFXS/eJJZ5r4u32az94w==} dependencies: - '@aws-sdk/fetch-http-handler': 3.306.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-base64': 3.303.0 - '@aws-sdk/util-hex-encoding': 3.295.0 - '@aws-sdk/util-utf8': 3.303.0 + '@aws-sdk/fetch-http-handler': 3.310.0 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-base64': 3.310.0 + '@aws-sdk/util-hex-encoding': 3.310.0 + '@aws-sdk/util-utf8': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/util-stream-node@3.306.0: - resolution: {integrity: sha512-hF8P52E3borUp6Z4K8cQOGm+bJgkbkE/VSl0EiO29E563ZiPnT/VBYVICX7MIDNPgHKDN5n/OSVfe1egjhOigQ==} + /@aws-sdk/util-stream-node@3.321.1: + resolution: {integrity: sha512-jvfff1zeA8q16hQWSC0BGwcHJPCwoh+bwiuAjihfl9q1tFLYuqaTzJzzkL1bntUsbW+y/ac5DO7fWcYPq0jWkw==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/node-http-handler': 3.306.0 - '@aws-sdk/types': 3.306.0 - '@aws-sdk/util-buffer-from': 3.303.0 + '@aws-sdk/node-http-handler': 3.321.1 + '@aws-sdk/types': 3.310.0 + '@aws-sdk/util-buffer-from': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/util-uri-escape@3.303.0: - resolution: {integrity: sha512-N3ULNuHCL3QzAlCTY+XRRkRQTYCTU8RRuzFCJX0pDpz9t2K+tLT7DbxqupWGNFGl5Xlulf1Is14J3BP/Dx91rA==} + /@aws-sdk/util-uri-escape@3.310.0: + resolution: {integrity: sha512-drzt+aB2qo2LgtDoiy/3sVG8w63cgLkqFIa2NFlGpUgHFWTXkqtbgf4L5QdjRGKWhmZsnqkbtL7vkSWEcYDJ4Q==} engines: {node: '>=14.0.0'} dependencies: tslib: 2.5.0 dev: false - /@aws-sdk/util-user-agent-browser@3.306.0: - resolution: {integrity: sha512-uZAtpvCasUdWRlB/nEjN0gf6G7810hT50VyWjpd6mQW78myV8M5fu/R03UFAZ+D8fhqqIdzR/IXDY1QUGp8bCA==} + /@aws-sdk/util-user-agent-browser@3.310.0: + resolution: {integrity: sha512-yU/4QnHHuQ5z3vsUqMQVfYLbZGYwpYblPiuZx4Zo9+x0PBkNjYMqctdDcrpoH9Z2xZiDN16AmQGK1tix117ZKw==} dependencies: - '@aws-sdk/types': 3.306.0 + '@aws-sdk/types': 3.310.0 bowser: 2.11.0 tslib: 2.5.0 dev: false - /@aws-sdk/util-user-agent-node@3.306.0: - resolution: {integrity: sha512-zLp9wIx7FZ0qFLimYW3lJ1uJM5gqxmmcQjNimUaUq/4a1caDkaiF/QeyyMFva+wIjyHRv22P5abUBjIEZrs5WA==} + /@aws-sdk/util-user-agent-node@3.310.0: + resolution: {integrity: sha512-Ra3pEl+Gn2BpeE7KiDGpi4zj7WJXZA5GXnGo3mjbi9+Y3zrbuhJAbdZO3mO/o7xDgMC6ph4xCTbaSGzU6b6EDg==} engines: {node: '>=14.0.0'} peerDependencies: aws-crt: '>=1.0.0' @@ -1997,8 +2042,8 @@ packages: aws-crt: optional: true dependencies: - '@aws-sdk/node-config-provider': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/node-config-provider': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false @@ -2008,25 +2053,25 @@ packages: tslib: 2.5.0 dev: false - /@aws-sdk/util-utf8@3.303.0: - resolution: {integrity: sha512-tZXVuMOIONPOuOGBs/XRdzxv6jUvTM620dRFFIHZwlGiW8bo0x0LlonrzDAJZA4e9ZwmxJIj8Ji13WVRBGvZWg==} + /@aws-sdk/util-utf8@3.310.0: + resolution: {integrity: sha512-DnLfFT8uCO22uOJc0pt0DsSNau1GTisngBCDw8jQuWT5CqogMJu4b/uXmwEqfj8B3GX6Xsz8zOd6JpRlPftQoA==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/util-buffer-from': 3.303.0 + '@aws-sdk/util-buffer-from': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/util-waiter@3.306.0: - resolution: {integrity: sha512-/cCmEaxGJOVKHuuzm4zM3aY2Un7pJGyewcd9WWvLjZIoF9jCCqyjmsxM+OXdCjs7NOdo41cValYhILYI+nD8Tg==} + /@aws-sdk/util-waiter@3.310.0: + resolution: {integrity: sha512-AV5j3guH/Y4REu+Qh3eXQU9igljHuU4XjX2sADAgf54C0kkhcCCkkiuzk3IsX089nyJCqIcj5idbjdvpnH88Vw==} engines: {node: '>=14.0.0'} dependencies: - '@aws-sdk/abort-controller': 3.306.0 - '@aws-sdk/types': 3.306.0 + '@aws-sdk/abort-controller': 3.310.0 + '@aws-sdk/types': 3.310.0 tslib: 2.5.0 dev: false - /@aws-sdk/xml-builder@3.303.0: - resolution: {integrity: sha512-Oht8XdmCkLhwZx2WTjOOLN8rt9000zJS4Hehv9NG7+kKfaA6sKFGIculmumaS+h8hAwWFndtgpOTlKC95zHSWQ==} + /@aws-sdk/xml-builder@3.310.0: + resolution: {integrity: sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==} engines: {node: '>=14.0.0'} dependencies: tslib: 2.5.0 @@ -2039,11 +2084,6 @@ packages: '@babel/highlight': 7.18.6 dev: true - /@babel/compat-data@7.20.10: - resolution: {integrity: sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==} - engines: {node: '>=6.9.0'} - dev: true - /@babel/compat-data@7.21.4: resolution: {integrity: sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==} engines: {node: '>=6.9.0'} @@ -2056,13 +2096,13 @@ packages: '@ampproject/remapping': 2.2.0 '@babel/code-frame': 7.18.6 '@babel/generator': 7.21.3 - '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.21.3) + '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3) '@babel/helper-module-transforms': 7.21.2 '@babel/helpers': 7.21.0 - '@babel/parser': 7.21.3 + '@babel/parser': 7.21.4 '@babel/template': 7.20.7 '@babel/traverse': 7.21.3 - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 convert-source-map: 1.9.0 debug: 4.3.4(supports-color@8.1.1) gensync: 1.0.0-beta.2 @@ -2076,7 +2116,7 @@ packages: resolution: {integrity: sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 '@jridgewell/gen-mapping': 0.3.2 '@jridgewell/trace-mapping': 0.3.17 jsesc: 2.5.2 @@ -2097,20 +2137,6 @@ packages: '@babel/types': 7.21.4 dev: true - /@babel/helper-compilation-targets@7.20.7(@babel/core@7.21.3): - resolution: {integrity: sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.20.10 - '@babel/core': 7.21.3 - '@babel/helper-validator-option': 7.21.0 - browserslist: 4.21.5 - lru-cache: 5.1.1 - semver: 6.3.0 - dev: true - /@babel/helper-compilation-targets@7.21.4(@babel/core@7.21.3): resolution: {integrity: sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==} engines: {node: '>=6.9.0'} @@ -2342,12 +2368,12 @@ packages: dependencies: '@babel/types': 7.21.4 - /@babel/parser@7.21.3: - resolution: {integrity: sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==} + /@babel/parser@7.21.4: + resolution: {integrity: sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} @@ -3052,7 +3078,7 @@ packages: '@babel/helper-module-imports': 7.18.6 '@babel/helper-plugin-utils': 7.20.2 '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.21.3) - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 dev: true /@babel/plugin-transform-regenerator@7.20.5(@babel/core@7.21.3): @@ -3163,92 +3189,6 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/preset-env@7.20.2(@babel/core@7.21.3): - resolution: {integrity: sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/compat-data': 7.20.10 - '@babel/core': 7.21.3 - '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.21.3) - '@babel/helper-plugin-utils': 7.20.2 - '@babel/helper-validator-option': 7.21.0 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.20.7(@babel/core@7.21.3) - '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.21.3) - '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-proposal-class-static-block': 7.21.0(@babel/core@7.21.3) - '@babel/plugin-proposal-dynamic-import': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-proposal-export-namespace-from': 7.18.9(@babel/core@7.21.3) - '@babel/plugin-proposal-json-strings': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.21.3) - '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.21.3) - '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.21.3) - '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-proposal-private-property-in-object': 7.21.0(@babel/core@7.21.3) - '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.21.3) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.21.3) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.21.3) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.21.3) - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.21.3) - '@babel/plugin-syntax-import-assertions': 7.20.0(@babel/core@7.21.3) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.21.3) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.21.3) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.21.3) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.21.3) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.21.3) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.21.3) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.21.3) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.21.3) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.21.3) - '@babel/plugin-transform-arrow-functions': 7.20.7(@babel/core@7.21.3) - '@babel/plugin-transform-async-to-generator': 7.20.7(@babel/core@7.21.3) - '@babel/plugin-transform-block-scoped-functions': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-transform-block-scoping': 7.21.0(@babel/core@7.21.3) - '@babel/plugin-transform-classes': 7.21.0(@babel/core@7.21.3) - '@babel/plugin-transform-computed-properties': 7.20.7(@babel/core@7.21.3) - '@babel/plugin-transform-destructuring': 7.21.3(@babel/core@7.21.3) - '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-transform-duplicate-keys': 7.18.9(@babel/core@7.21.3) - '@babel/plugin-transform-exponentiation-operator': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-transform-for-of': 7.21.0(@babel/core@7.21.3) - '@babel/plugin-transform-function-name': 7.18.9(@babel/core@7.21.3) - '@babel/plugin-transform-literals': 7.18.9(@babel/core@7.21.3) - '@babel/plugin-transform-member-expression-literals': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-transform-modules-amd': 7.20.11(@babel/core@7.21.3) - '@babel/plugin-transform-modules-commonjs': 7.21.2(@babel/core@7.21.3) - '@babel/plugin-transform-modules-systemjs': 7.20.11(@babel/core@7.21.3) - '@babel/plugin-transform-modules-umd': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-transform-named-capturing-groups-regex': 7.20.5(@babel/core@7.21.3) - '@babel/plugin-transform-new-target': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-transform-object-super': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-transform-parameters': 7.21.3(@babel/core@7.21.3) - '@babel/plugin-transform-property-literals': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-transform-regenerator': 7.20.5(@babel/core@7.21.3) - '@babel/plugin-transform-reserved-words': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-transform-shorthand-properties': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-transform-spread': 7.20.7(@babel/core@7.21.3) - '@babel/plugin-transform-sticky-regex': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-transform-template-literals': 7.18.9(@babel/core@7.21.3) - '@babel/plugin-transform-typeof-symbol': 7.18.9(@babel/core@7.21.3) - '@babel/plugin-transform-unicode-escapes': 7.18.10(@babel/core@7.21.3) - '@babel/plugin-transform-unicode-regex': 7.18.6(@babel/core@7.21.3) - '@babel/preset-modules': 0.1.5(@babel/core@7.21.3) - '@babel/types': 7.21.3 - babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.21.3) - babel-plugin-polyfill-corejs3: 0.6.0(@babel/core@7.21.3) - babel-plugin-polyfill-regenerator: 0.4.1(@babel/core@7.21.3) - core-js-compat: 3.29.1 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/preset-env@7.21.4(@babel/core@7.21.3): resolution: {integrity: sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw==} engines: {node: '>=6.9.0'} @@ -3403,14 +3343,13 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 - dev: true /@babel/template@7.20.7: resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.18.6 - '@babel/parser': 7.21.3 + '@babel/parser': 7.21.4 '@babel/types': 7.21.4 dev: true @@ -3424,7 +3363,7 @@ packages: '@babel/helper-function-name': 7.21.0 '@babel/helper-hoist-variables': 7.18.6 '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.21.3 + '@babel/parser': 7.21.4 '@babel/types': 7.21.4 debug: 4.3.4(supports-color@8.1.1) globals: 11.12.0 @@ -3432,14 +3371,6 @@ packages: - supports-color dev: true - /@babel/types@7.21.3: - resolution: {integrity: sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.19.4 - '@babel/helper-validator-identifier': 7.19.1 - to-fast-properties: 2.0.0 - /@babel/types@7.21.4: resolution: {integrity: sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==} engines: {node: '>=6.9.0'} @@ -3456,26 +3387,29 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true - /@bull-board/api@5.0.0: - resolution: {integrity: sha512-nOB8TUSeX8h2wgMxmPZwy0p3XqRX/DotR71M5FT5OS/bZCZIKRpEc36TIeKPimZli6voFyX+rsEqVhGenpIKUQ==} + /@bull-board/api@5.1.2(@bull-board/ui@5.1.2): + resolution: {integrity: sha512-NLV88eDnOMd0XuYUNBcaYL6UdxbGYY91kP+P5Jled6CGcYmXLXv/mzAopxmgctqmP07cjC2EXN/4Oc7dyalDqA==} + peerDependencies: + '@bull-board/ui': 5.1.2 dependencies: + '@bull-board/ui': 5.1.2 redis-info: 3.1.0 dev: false - /@bull-board/fastify@5.0.0: - resolution: {integrity: sha512-g7eLQhap9HiRjWv3NkgbVrdiG8RDUwyA3qDW2jES1dRcpWPBD9/3rxESwzuIoxa2h0OJ34VLY9hsgpu0vRyhZg==} + /@bull-board/fastify@5.1.2: + resolution: {integrity: sha512-qiURhcqMfER5hp4RtgepMDbPj5H4ZKNOgK+7RIG3bd3d0tBoLjXzooXFryxzd6w130pXU9/crUMtcMP+Ulaj6g==} dependencies: - '@bull-board/api': 5.0.0 - '@bull-board/ui': 5.0.0 - '@fastify/static': 6.10.0 + '@bull-board/api': 5.1.2(@bull-board/ui@5.1.2) + '@bull-board/ui': 5.1.2 + '@fastify/static': 6.10.1 '@fastify/view': 7.4.1 ejs: 3.1.8 dev: false - /@bull-board/ui@5.0.0: - resolution: {integrity: sha512-WznLX8dGthUWimZDqN5Zft3Axp6SStlI244BhiQYXB7xXZRMgaihNZxh7vgpwg0Osmw/gPFvAI+NFVGc3tvj9g==} + /@bull-board/ui@5.1.2: + resolution: {integrity: sha512-DXXbKA4NLo5D19Vssrg4pPFaFjXVzjFN0ht4GVuoJQejy7t/RVrWzZCjdyVuSiOFTlG3SyB39zW5a95Q5EXUTg==} dependencies: - '@bull-board/api': 5.0.0 + '@bull-board/api': 5.1.2(@bull-board/ui@5.1.2) dev: false /@canvas/image-data@1.0.0: @@ -3637,7 +3571,6 @@ packages: /@discoveryjs/json-ext@0.5.7: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} - dev: true /@emotion/use-insertion-effect-with-fallbacks@1.0.0(react@18.2.0): resolution: {integrity: sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==} @@ -3647,365 +3580,189 @@ packages: react: 18.2.0 dev: true - /@esbuild/android-arm64@0.17.14: - resolution: {integrity: sha512-eLOpPO1RvtsP71afiFTvS7tVFShJBCT0txiv/xjFBo5a7R7Gjw7X0IgIaFoLKhqXYAXhahoXm7qAmRXhY4guJg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - optional: true - - /@esbuild/android-arm64@0.17.15: - resolution: {integrity: sha512-0kOB6Y7Br3KDVgHeg8PRcvfLkq+AccreK///B4Z6fNZGr/tNHX0z2VywCc7PTeWp+bPvjA5WMvNXltHw5QjAIA==} + /@esbuild/android-arm64@0.17.18: + resolution: {integrity: sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw==} engines: {node: '>=12'} cpu: [arm64] os: [android] requiresBuild: true optional: true - /@esbuild/android-arm@0.17.14: - resolution: {integrity: sha512-0CnlwnjDU8cks0yJLXfkaU/uoLyRf9VZJs4p1PskBr2AlAHeEsFEwJEo0of/Z3g+ilw5mpyDwThlxzNEIxOE4g==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true - optional: true - - /@esbuild/android-arm@0.17.15: - resolution: {integrity: sha512-sRSOVlLawAktpMvDyJIkdLI/c/kdRTOqo8t6ImVxg8yT7LQDUYV5Rp2FKeEosLr6ZCja9UjYAzyRSxGteSJPYg==} + /@esbuild/android-arm@0.17.18: + resolution: {integrity: sha512-EmwL+vUBZJ7mhFCs5lA4ZimpUH3WMAoqvOIYhVQwdIgSpHC8ImHdsRyhHAVxpDYUSm0lWvd63z0XH1IlImS2Qw==} engines: {node: '>=12'} cpu: [arm] os: [android] requiresBuild: true optional: true - /@esbuild/android-x64@0.17.14: - resolution: {integrity: sha512-nrfQYWBfLGfSGLvRVlt6xi63B5IbfHm3tZCdu/82zuFPQ7zez4XjmRtF/wIRYbJQ/DsZrxJdEvYFE67avYXyng==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - optional: true - - /@esbuild/android-x64@0.17.15: - resolution: {integrity: sha512-MzDqnNajQZ63YkaUWVl9uuhcWyEyh69HGpMIrf+acR4otMkfLJ4sUCxqwbCyPGicE9dVlrysI3lMcDBjGiBBcQ==} + /@esbuild/android-x64@0.17.18: + resolution: {integrity: sha512-x+0efYNBF3NPW2Xc5bFOSFW7tTXdAcpfEg2nXmxegm4mJuVeS+i109m/7HMiOQ6M12aVGGFlqJX3RhNdYM2lWg==} engines: {node: '>=12'} cpu: [x64] os: [android] requiresBuild: true optional: true - /@esbuild/darwin-arm64@0.17.14: - resolution: {integrity: sha512-eoSjEuDsU1ROwgBH/c+fZzuSyJUVXQTOIN9xuLs9dE/9HbV/A5IqdXHU1p2OfIMwBwOYJ9SFVGGldxeRCUJFyw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - optional: true - - /@esbuild/darwin-arm64@0.17.15: - resolution: {integrity: sha512-7siLjBc88Z4+6qkMDxPT2juf2e8SJxmsbNVKFY2ifWCDT72v5YJz9arlvBw5oB4W/e61H1+HDB/jnu8nNg0rLA==} + /@esbuild/darwin-arm64@0.17.18: + resolution: {integrity: sha512-6tY+djEAdF48M1ONWnQb1C+6LiXrKjmqjzPNPWXhu/GzOHTHX2nh8Mo2ZAmBFg0kIodHhciEgUBtcYCAIjGbjQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] requiresBuild: true optional: true - /@esbuild/darwin-x64@0.17.14: - resolution: {integrity: sha512-zN0U8RWfrDttdFNkHqFYZtOH8hdi22z0pFm0aIJPsNC4QQZv7je8DWCX5iA4Zx6tRhS0CCc0XC2m7wKsbWEo5g==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - optional: true - - /@esbuild/darwin-x64@0.17.15: - resolution: {integrity: sha512-NbImBas2rXwYI52BOKTW342Tm3LTeVlaOQ4QPZ7XuWNKiO226DisFk/RyPk3T0CKZkKMuU69yOvlapJEmax7cg==} + /@esbuild/darwin-x64@0.17.18: + resolution: {integrity: sha512-Qq84ykvLvya3dO49wVC9FFCNUfSrQJLbxhoQk/TE1r6MjHo3sFF2tlJCwMjhkBVq3/ahUisj7+EpRSz0/+8+9A==} engines: {node: '>=12'} cpu: [x64] os: [darwin] requiresBuild: true optional: true - /@esbuild/freebsd-arm64@0.17.14: - resolution: {integrity: sha512-z0VcD4ibeZWVQCW1O7szaLxGsx54gcCnajEJMdYoYjLiq4g1jrP2lMq6pk71dbS5+7op/L2Aod+erw+EUr28/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - optional: true - - /@esbuild/freebsd-arm64@0.17.15: - resolution: {integrity: sha512-Xk9xMDjBVG6CfgoqlVczHAdJnCs0/oeFOspFap5NkYAmRCT2qTn1vJWA2f419iMtsHSLm+O8B6SLV/HlY5cYKg==} + /@esbuild/freebsd-arm64@0.17.18: + resolution: {integrity: sha512-fw/ZfxfAzuHfaQeMDhbzxp9mc+mHn1Y94VDHFHjGvt2Uxl10mT4CDavHm+/L9KG441t1QdABqkVYwakMUeyLRA==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] requiresBuild: true optional: true - /@esbuild/freebsd-x64@0.17.14: - resolution: {integrity: sha512-hd9mPcxfTgJlolrPlcXkQk9BMwNBvNBsVaUe5eNUqXut6weDQH8whcNaKNF2RO8NbpT6GY8rHOK2A9y++s+ehw==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - optional: true - - /@esbuild/freebsd-x64@0.17.15: - resolution: {integrity: sha512-3TWAnnEOdclvb2pnfsTWtdwthPfOz7qAfcwDLcfZyGJwm1SRZIMOeB5FODVhnM93mFSPsHB9b/PmxNNbSnd0RQ==} + /@esbuild/freebsd-x64@0.17.18: + resolution: {integrity: sha512-FQFbRtTaEi8ZBi/A6kxOC0V0E9B/97vPdYjY9NdawyLd4Qk5VD5g2pbWN2VR1c0xhzcJm74HWpObPszWC+qTew==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] requiresBuild: true optional: true - /@esbuild/linux-arm64@0.17.14: - resolution: {integrity: sha512-FhAMNYOq3Iblcj9i+K0l1Fp/MHt+zBeRu/Qkf0LtrcFu3T45jcwB6A1iMsemQ42vR3GBhjNZJZTaCe3VFPbn9g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - optional: true - - /@esbuild/linux-arm64@0.17.15: - resolution: {integrity: sha512-T0MVnYw9KT6b83/SqyznTs/3Jg2ODWrZfNccg11XjDehIved2oQfrX/wVuev9N936BpMRaTR9I1J0tdGgUgpJA==} + /@esbuild/linux-arm64@0.17.18: + resolution: {integrity: sha512-R7pZvQZFOY2sxUG8P6A21eq6q+eBv7JPQYIybHVf1XkQYC+lT7nDBdC7wWKTrbvMXKRaGudp/dzZCwL/863mZQ==} engines: {node: '>=12'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-arm@0.17.14: - resolution: {integrity: sha512-BNTl+wSJ1omsH8s3TkQmIIIQHwvwJrU9u1ggb9XU2KTVM4TmthRIVyxSp2qxROJHhZuW/r8fht46/QE8hU8Qvg==} + /@esbuild/linux-arm@0.17.18: + resolution: {integrity: sha512-jW+UCM40LzHcouIaqv3e/oRs0JM76JfhHjCavPxMUti7VAPh8CaGSlS7cmyrdpzSk7A+8f0hiedHqr/LMnfijg==} engines: {node: '>=12'} cpu: [arm] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-arm@0.17.15: - resolution: {integrity: sha512-MLTgiXWEMAMr8nmS9Gigx43zPRmEfeBfGCwxFQEMgJ5MC53QKajaclW6XDPjwJvhbebv+RzK05TQjvH3/aM4Xw==} + /@esbuild/linux-ia32@0.17.18: + resolution: {integrity: sha512-ygIMc3I7wxgXIxk6j3V00VlABIjq260i967Cp9BNAk5pOOpIXmd1RFQJQX9Io7KRsthDrQYrtcx7QCof4o3ZoQ==} engines: {node: '>=12'} - cpu: [arm] + cpu: [ia32] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-ia32@0.17.14: - resolution: {integrity: sha512-91OK/lQ5y2v7AsmnFT+0EyxdPTNhov3y2CWMdizyMfxSxRqHazXdzgBKtlmkU2KYIc+9ZK3Vwp2KyXogEATYxQ==} + /@esbuild/linux-loong64@0.17.18: + resolution: {integrity: sha512-bvPG+MyFs5ZlwYclCG1D744oHk1Pv7j8psF5TfYx7otCVmcJsEXgFEhQkbhNW8otDHL1a2KDINW20cfCgnzgMQ==} engines: {node: '>=12'} - cpu: [ia32] + cpu: [loong64] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-ia32@0.17.15: - resolution: {integrity: sha512-wp02sHs015T23zsQtU4Cj57WiteiuASHlD7rXjKUyAGYzlOKDAjqK6bk5dMi2QEl/KVOcsjwL36kD+WW7vJt8Q==} + /@esbuild/linux-mips64el@0.17.18: + resolution: {integrity: sha512-oVqckATOAGuiUOa6wr8TXaVPSa+6IwVJrGidmNZS1cZVx0HqkTMkqFGD2HIx9H1RvOwFeWYdaYbdY6B89KUMxA==} engines: {node: '>=12'} - cpu: [ia32] + cpu: [mips64el] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-loong64@0.17.14: - resolution: {integrity: sha512-vp15H+5NR6hubNgMluqqKza85HcGJgq7t6rMH7O3Y6ApiOWPkvW2AJfNojUQimfTp6OUrACUXfR4hmpcENXoMQ==} + /@esbuild/linux-ppc64@0.17.18: + resolution: {integrity: sha512-3dLlQO+b/LnQNxgH4l9rqa2/IwRJVN9u/bK63FhOPB4xqiRqlQAU0qDU3JJuf0BmaH0yytTBdoSBHrb2jqc5qQ==} engines: {node: '>=12'} - cpu: [loong64] + cpu: [ppc64] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-loong64@0.17.15: - resolution: {integrity: sha512-k7FsUJjGGSxwnBmMh8d7IbObWu+sF/qbwc+xKZkBe/lTAF16RqxRCnNHA7QTd3oS2AfGBAnHlXL67shV5bBThQ==} + /@esbuild/linux-riscv64@0.17.18: + resolution: {integrity: sha512-/x7leOyDPjZV3TcsdfrSI107zItVnsX1q2nho7hbbQoKnmoeUWjs+08rKKt4AUXju7+3aRZSsKrJtaRmsdL1xA==} engines: {node: '>=12'} - cpu: [loong64] + cpu: [riscv64] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-mips64el@0.17.14: - resolution: {integrity: sha512-90TOdFV7N+fgi6c2+GO9ochEkmm9kBAKnuD5e08GQMgMINOdOFHuYLPQ91RYVrnWwQ5683sJKuLi9l4SsbJ7Hg==} + /@esbuild/linux-s390x@0.17.18: + resolution: {integrity: sha512-cX0I8Q9xQkL/6F5zWdYmVf5JSQt+ZfZD2bJudZrWD+4mnUvoZ3TDDXtDX2mUaq6upMFv9FlfIh4Gfun0tbGzuw==} engines: {node: '>=12'} - cpu: [mips64el] + cpu: [s390x] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-mips64el@0.17.15: - resolution: {integrity: sha512-ZLWk6czDdog+Q9kE/Jfbilu24vEe/iW/Sj2d8EVsmiixQ1rM2RKH2n36qfxK4e8tVcaXkvuV3mU5zTZviE+NVQ==} + /@esbuild/linux-x64@0.17.18: + resolution: {integrity: sha512-66RmRsPlYy4jFl0vG80GcNRdirx4nVWAzJmXkevgphP1qf4dsLQCpSKGM3DUQCojwU1hnepI63gNZdrr02wHUA==} engines: {node: '>=12'} - cpu: [mips64el] + cpu: [x64] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-ppc64@0.17.14: - resolution: {integrity: sha512-NnBGeoqKkTugpBOBZZoktQQ1Yqb7aHKmHxsw43NddPB2YWLAlpb7THZIzsRsTr0Xw3nqiPxbA1H31ZMOG+VVPQ==} + /@esbuild/netbsd-x64@0.17.18: + resolution: {integrity: sha512-95IRY7mI2yrkLlTLb1gpDxdC5WLC5mZDi+kA9dmM5XAGxCME0F8i4bYH4jZreaJ6lIZ0B8hTrweqG1fUyW7jbg==} engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] + cpu: [x64] + os: [netbsd] requiresBuild: true optional: true - /@esbuild/linux-ppc64@0.17.15: - resolution: {integrity: sha512-mY6dPkIRAiFHRsGfOYZC8Q9rmr8vOBZBme0/j15zFUKM99d4ILY4WpOC7i/LqoY+RE7KaMaSfvY8CqjJtuO4xg==} + /@esbuild/openbsd-x64@0.17.18: + resolution: {integrity: sha512-WevVOgcng+8hSZ4Q3BKL3n1xTv5H6Nb53cBrtzzEjDbbnOmucEVcZeGCsCOi9bAOcDYEeBZbD2SJNBxlfP3qiA==} engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] + cpu: [x64] + os: [openbsd] requiresBuild: true optional: true - /@esbuild/linux-riscv64@0.17.14: - resolution: {integrity: sha512-0qdlKScLXA8MGVy21JUKvMzCYWovctuP8KKqhtE5A6IVPq4onxXhSuhwDd2g5sRCzNDlDjitc5sX31BzDoL5Fw==} + /@esbuild/sunos-x64@0.17.18: + resolution: {integrity: sha512-Rzf4QfQagnwhQXVBS3BYUlxmEbcV7MY+BH5vfDZekU5eYpcffHSyjU8T0xucKVuOcdCsMo+Ur5wmgQJH2GfNrg==} engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] + cpu: [x64] + os: [sunos] requiresBuild: true optional: true - /@esbuild/linux-riscv64@0.17.15: - resolution: {integrity: sha512-EcyUtxffdDtWjjwIH8sKzpDRLcVtqANooMNASO59y+xmqqRYBBM7xVLQhqF7nksIbm2yHABptoioS9RAbVMWVA==} + /@esbuild/win32-arm64@0.17.18: + resolution: {integrity: sha512-Kb3Ko/KKaWhjeAm2YoT/cNZaHaD1Yk/pa3FTsmqo9uFh1D1Rfco7BBLIPdDOozrObj2sahslFuAQGvWbgWldAg==} engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] + cpu: [arm64] + os: [win32] requiresBuild: true optional: true - /@esbuild/linux-s390x@0.17.14: - resolution: {integrity: sha512-Hdm2Jo1yaaOro4v3+6/zJk6ygCqIZuSDJHdHaf8nVH/tfOuoEX5Riv03Ka15LmQBYJObUTNS1UdyoMk0WUn9Ww==} + /@esbuild/win32-ia32@0.17.18: + resolution: {integrity: sha512-0/xUMIdkVHwkvxfbd5+lfG7mHOf2FRrxNbPiKWg9C4fFrB8H0guClmaM3BFiRUYrznVoyxTIyC/Ou2B7QQSwmw==} engines: {node: '>=12'} - cpu: [s390x] - os: [linux] + cpu: [ia32] + os: [win32] requiresBuild: true optional: true - /@esbuild/linux-s390x@0.17.15: - resolution: {integrity: sha512-BuS6Jx/ezxFuHxgsfvz7T4g4YlVrmCmg7UAwboeyNNg0OzNzKsIZXpr3Sb/ZREDXWgt48RO4UQRDBxJN3B9Rbg==} + /@esbuild/win32-x64@0.17.18: + resolution: {integrity: sha512-qU25Ma1I3NqTSHJUOKi9sAH1/Mzuvlke0ioMJRthLXKm7JiSKVwFghlGbDLOO2sARECGhja4xYfRAZNPAkooYg==} engines: {node: '>=12'} - cpu: [s390x] - os: [linux] + cpu: [x64] + os: [win32] requiresBuild: true optional: true - /@esbuild/linux-x64@0.17.14: - resolution: {integrity: sha512-8KHF17OstlK4DuzeF/KmSgzrTWQrkWj5boluiiq7kvJCiQVzUrmSkaBvcLB2UgHpKENO2i6BthPkmUhNDaJsVw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - optional: true - - /@esbuild/linux-x64@0.17.15: - resolution: {integrity: sha512-JsdS0EgEViwuKsw5tiJQo9UdQdUJYuB+Mf6HxtJSPN35vez1hlrNb1KajvKWF5Sa35j17+rW1ECEO9iNrIXbNg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - optional: true - - /@esbuild/netbsd-x64@0.17.14: - resolution: {integrity: sha512-nVwpqvb3yyXztxIT2+VsxJhB5GCgzPdk1n0HHSnchRAcxqKO6ghXwHhJnr0j/B+5FSyEqSxF4q03rbA2fKXtUQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - optional: true - - /@esbuild/netbsd-x64@0.17.15: - resolution: {integrity: sha512-R6fKjtUysYGym6uXf6qyNephVUQAGtf3n2RCsOST/neIwPqRWcnc3ogcielOd6pT+J0RDR1RGcy0ZY7d3uHVLA==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - optional: true - - /@esbuild/openbsd-x64@0.17.14: - resolution: {integrity: sha512-1RZ7uQQ9zcy/GSAJL1xPdN7NDdOOtNEGiJalg/MOzeakZeTrgH/DoCkbq7TaPDiPhWqnDF+4bnydxRqQD7il6g==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - optional: true - - /@esbuild/openbsd-x64@0.17.15: - resolution: {integrity: sha512-mVD4PGc26b8PI60QaPUltYKeSX0wxuy0AltC+WCTFwvKCq2+OgLP4+fFd+hZXzO2xW1HPKcytZBdjqL6FQFa7w==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - optional: true - - /@esbuild/sunos-x64@0.17.14: - resolution: {integrity: sha512-nqMjDsFwv7vp7msrwWRysnM38Sd44PKmW8EzV01YzDBTcTWUpczQg6mGao9VLicXSgW/iookNK6AxeogNVNDZA==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - optional: true - - /@esbuild/sunos-x64@0.17.15: - resolution: {integrity: sha512-U6tYPovOkw3459t2CBwGcFYfFRjivcJJc1WC8Q3funIwX8x4fP+R6xL/QuTPNGOblbq/EUDxj9GU+dWKX0oWlQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - optional: true - - /@esbuild/win32-arm64@0.17.14: - resolution: {integrity: sha512-xrD0mccTKRBBIotrITV7WVQAwNJ5+1va6L0H9zN92v2yEdjfAN7864cUaZwJS7JPEs53bDTzKFbfqVlG2HhyKQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - optional: true - - /@esbuild/win32-arm64@0.17.15: - resolution: {integrity: sha512-W+Z5F++wgKAleDABemiyXVnzXgvRFs+GVKThSI+mGgleLWluv0D7Diz4oQpgdpNzh4i2nNDzQtWbjJiqutRp6Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - optional: true - - /@esbuild/win32-ia32@0.17.14: - resolution: {integrity: sha512-nXpkz9bbJrLLyUTYtRotSS3t5b+FOuljg8LgLdINWFs3FfqZMtbnBCZFUmBzQPyxqU87F8Av+3Nco/M3hEcu1w==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - optional: true - - /@esbuild/win32-ia32@0.17.15: - resolution: {integrity: sha512-Muz/+uGgheShKGqSVS1KsHtCyEzcdOn/W/Xbh6H91Etm+wiIfwZaBn1W58MeGtfI8WA961YMHFYTthBdQs4t+w==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - optional: true - - /@esbuild/win32-x64@0.17.14: - resolution: {integrity: sha512-gPQmsi2DKTaEgG14hc3CHXHp62k8g6qr0Pas+I4lUxRMugGSATh/Bi8Dgusoz9IQ0IfdrvLpco6kujEIBoaogA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - optional: true - - /@esbuild/win32-x64@0.17.15: - resolution: {integrity: sha512-DjDa9ywLUUmjhV2Y9wUTIF+1XsmuFGvZoCmOWkli1XcNAh5t25cc7fgsCx4Zi/Uurep3TTLyDiKATgGEg61pkA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - optional: true - - /@eslint-community/eslint-utils@4.4.0(eslint@8.37.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.39.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.37.0 + eslint: 8.39.0 eslint-visitor-keys: 3.4.0 dev: true @@ -4031,8 +3788,8 @@ packages: - supports-color dev: true - /@eslint/js@8.37.0: - resolution: {integrity: sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==} + /@eslint/js@8.39.0: + resolution: {integrity: sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -4089,10 +3846,10 @@ packages: resolution: {integrity: sha512-KAfcLa+CnknwVi5fWogrLXgidLic+GXnLjijXdpl8pvkvbXU5BGa37iZO9FGvsh9ZL4y+oFi5cbHBm5UOG+dmQ==} dev: false - /@fastify/fast-json-stringify-compiler@4.2.0: - resolution: {integrity: sha512-ypZynRvXA3dibfPykQN3RB5wBdEUgSGgny8Qc6k163wYPLD4mEGEDkACp+00YmqkGvIm8D/xYoHajwyEdWD/eg==} + /@fastify/fast-json-stringify-compiler@4.3.0: + resolution: {integrity: sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==} dependencies: - fast-json-stringify: 5.5.0 + fast-json-stringify: 5.7.0 dev: false /@fastify/http-proxy@9.0.0: @@ -4106,8 +3863,8 @@ packages: - utf-8-validate dev: false - /@fastify/multipart@7.5.0: - resolution: {integrity: sha512-cAQzz+vVRaNomTppActeqA3xZstF18WK9gjwCMAkoU6xKFljsnKewdGw2RkcYAaiWgFzGBQUD8owSKPFgzhJAA==} + /@fastify/multipart@7.6.0: + resolution: {integrity: sha512-mQoz0SMxk1WAYLYvQypoj3GYhCbmhnFl2LdleTzKQ4HgPfhVcMT014uln2wgzRh2y+gL74b/+j0mMVgvOKpqpQ==} dependencies: '@fastify/busboy': 1.1.0 '@fastify/deepmerge': 1.3.0 @@ -4141,8 +3898,8 @@ packages: mime: 3.0.0 dev: false - /@fastify/static@6.10.0: - resolution: {integrity: sha512-TGruNm6ZabkQz2oRNoarPnY2BvS9i9DNf8Nn1aDcZp+WjOQRPCq0Wy2ko78yGB5JHytdCWoHpprc128QtLl8hw==} + /@fastify/static@6.10.1: + resolution: {integrity: sha512-DNnG+5QenQcTQw37qk0/191STThnN6SbU+2XMpWtpYR3gQUfUvMax14jTT/jqNINNbCkQJaKMnPtpFPKo4/68g==} dependencies: '@fastify/accept-negotiator': 1.0.0 '@fastify/send': 2.0.1 @@ -4218,7 +3975,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.16.3 chalk: 4.1.2 jest-message-util: 29.5.0 jest-util: 29.5.0 @@ -4239,14 +3996,14 @@ packages: '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.16.3 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.7.1 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.5.0 - jest-config: 29.5.0(@types/node@18.15.11) + jest-config: 29.5.0(@types/node@18.16.3) jest-haste-map: 29.5.0 jest-message-util: 29.5.0 jest-regex-util: 29.4.3 @@ -4280,7 +4037,7 @@ packages: dependencies: '@jest/fake-timers': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.16.3 jest-mock: 29.5.0 dev: true @@ -4307,7 +4064,7 @@ packages: dependencies: '@jest/types': 29.5.0 '@sinonjs/fake-timers': 10.0.2 - '@types/node': 18.15.11 + '@types/node': 18.16.3 jest-message-util: 29.5.0 jest-mock: 29.5.0 jest-util: 29.5.0 @@ -4340,7 +4097,7 @@ packages: '@jest/transform': 29.5.0 '@jest/types': 29.5.0 '@jridgewell/trace-mapping': 0.3.17 - '@types/node': 18.15.11 + '@types/node': 18.16.3 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -4434,7 +4191,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.15.11 + '@types/node': 18.16.3 '@types/yargs': 16.0.5 chalk: 4.1.2 dev: true @@ -4446,12 +4203,12 @@ packages: '@jest/schemas': 29.4.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.15.11 + '@types/node': 18.16.3 '@types/yargs': 17.0.19 chalk: 4.1.2 dev: true - /@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.0.3)(vite@4.2.1): + /@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.0.4)(vite@4.3.4): resolution: {integrity: sha512-ou4ZJSXMMWHqGS4g8uNRbC5TiTWxAgQZiVucoUrOCWuPrTbkpJbmVyIi9jU72SBry7gQtuMEDp4YR8EEXAg7VQ==} peerDependencies: typescript: '>= 4.3.x' @@ -4463,9 +4220,9 @@ packages: 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.0.3) - typescript: 5.0.3 - vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0) + react-docgen-typescript: 2.2.2(typescript@5.0.4) + typescript: 5.0.4 + vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1) dev: true /@jridgewell/gen-mapping@0.1.1: @@ -4528,6 +4285,7 @@ packages: /@mapbox/node-pre-gyp@1.0.9: resolution: {integrity: sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==} + hasBin: true dependencies: detect-libc: 2.0.1 https-proxy-agent: 5.0.1 @@ -4536,7 +4294,7 @@ packages: nopt: 5.0.0 npmlog: 5.0.1 rimraf: 3.0.2 - semver: 7.3.8 + semver: 7.5.0 tar: 6.1.13 transitivePeerDependencies: - encoding @@ -4554,24 +4312,24 @@ packages: react: 18.2.0 dev: true - /@microsoft/api-extractor-model@7.26.4(@types/node@18.15.11): - resolution: {integrity: sha512-PDCgCzXDo+SLY5bsfl4bS7hxaeEtnXj7XtuzEE+BtALp7B5mK/NrS2kHWU69pohgsRmEALycQdaQPXoyT2i5MQ==} + /@microsoft/api-extractor-model@7.26.7(@types/node@18.16.3): + resolution: {integrity: sha512-rx3Tq632VG3ddR74kPuPbv1qmUgO2IuCvn1z16hbNWNS5RhnTQqNPWIm7NVoi6lCh2E7uxzfmdnWXIXiJhM5IQ==} dependencies: '@microsoft/tsdoc': 0.14.2 '@microsoft/tsdoc-config': 0.16.2 - '@rushstack/node-core-library': 3.55.2(@types/node@18.15.11) + '@rushstack/node-core-library': 3.58.0(@types/node@18.16.3) transitivePeerDependencies: - '@types/node' dev: true - /@microsoft/api-extractor@7.34.4(@types/node@18.15.11): - resolution: {integrity: sha512-HOdcci2nT40ejhwPC3Xja9G+WSJmWhCUKKryRfQYsmE9cD+pxmBaKBKCbuS9jUcl6bLLb4Gz+h7xEN5r0QiXnQ==} + /@microsoft/api-extractor@7.34.7(@types/node@18.16.3): + resolution: {integrity: sha512-8CrrYyOBWqc4XFviR1KSuHSlmJjlnC5CVpPkcFB8HXiLABUVaVWFTsOzJIwUU6z8mc4BZPZ8tSGAg/mwFDU31Q==} hasBin: true dependencies: - '@microsoft/api-extractor-model': 7.26.4(@types/node@18.15.11) + '@microsoft/api-extractor-model': 7.26.7(@types/node@18.16.3) '@microsoft/tsdoc': 0.14.2 '@microsoft/tsdoc-config': 0.16.2 - '@rushstack/node-core-library': 3.55.2(@types/node@18.15.11) + '@rushstack/node-core-library': 3.58.0(@types/node@18.16.3) '@rushstack/rig-package': 0.3.18 '@rushstack/ts-command-line': 4.13.2 colors: 1.2.5 @@ -4685,7 +4443,7 @@ packages: tar-fs: 2.1.1 dev: true - /@nestjs/common@9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.0): + /@nestjs/common@9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1): resolution: {integrity: sha512-RUcVAQsEF4WPrmzFXEOUfZnPwrLTe1UVlzXTlSyfqfqbdWDPKDGlIPVelBLfc5/+RRUQ0I5iE4+CQvpCmkqldw==} peerDependencies: cache-manager: <=5 @@ -4703,12 +4461,12 @@ packages: dependencies: iterare: 1.2.1 reflect-metadata: 0.1.13 - rxjs: 7.8.0 + rxjs: 7.8.1 tslib: 2.5.0 uid: 2.0.2 dev: false - /@nestjs/core@9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.0): + /@nestjs/core@9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.1): resolution: {integrity: sha512-yTLryCgFD0462wPe4HIzhyTcDgibt8Stfwb5YzcX7Ma0NM4m8uBIpcPG109KBubp8ZmV85e5mw4rl20qLQQVsQ==} requiresBuild: true peerDependencies: @@ -4726,13 +4484,13 @@ packages: '@nestjs/websockets': optional: true dependencies: - '@nestjs/common': 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.0) + '@nestjs/common': 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nuxtjs/opencollective': 0.3.2 fast-safe-stringify: 2.1.1 iterare: 1.2.1 path-to-regexp: 3.2.0 reflect-metadata: 0.1.13 - rxjs: 7.8.0 + rxjs: 7.8.1 tslib: 2.5.0 uid: 2.0.2 transitivePeerDependencies: @@ -4752,8 +4510,8 @@ packages: '@nestjs/platform-express': optional: true dependencies: - '@nestjs/common': 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.0) - '@nestjs/core': 9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.0) + '@nestjs/common': 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/core': 9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) tslib: 2.5.0 dev: false @@ -4780,7 +4538,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} dependencies: '@gar/promisify': 1.1.3 - semver: 7.3.8 + semver: 7.5.0 dev: false /@npmcli/move-file@2.0.1: @@ -4883,8 +4641,8 @@ packages: '@redis/client': 1.4.2 dev: true - /@rollup/plugin-alias@4.0.3(rollup@3.20.2): - resolution: {integrity: sha512-ZuDWE1q4PQDhvm/zc5Prun8sBpLJy41DMptYrS6MhAy9s9kL/doN1613BWfEchGVfKxzliJ3BjbOPizXX38DbQ==} + /@rollup/plugin-alias@5.0.0(rollup@3.21.3): + resolution: {integrity: sha512-l9hY5chSCjuFRPsnRm16twWBiSApl2uYFLsepQYwtBuAxNMQ/1dJqADld40P0Jkqm65GRTLy/AC6hnpVebtLsA==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0 @@ -4892,11 +4650,11 @@ packages: rollup: optional: true dependencies: - rollup: 3.20.2 + rollup: 3.21.3 slash: 4.0.0 dev: false - /@rollup/plugin-json@6.0.0(rollup@3.20.2): + /@rollup/plugin-json@6.0.0(rollup@3.21.3): resolution: {integrity: sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==} engines: {node: '>=14.0.0'} peerDependencies: @@ -4905,8 +4663,22 @@ packages: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.2(rollup@3.20.2) - rollup: 3.20.2 + '@rollup/pluginutils': 5.0.2(rollup@3.21.3) + rollup: 3.21.3 + dev: false + + /@rollup/plugin-replace@5.0.2(rollup@3.21.3): + resolution: {integrity: sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.0.2(rollup@3.21.3) + magic-string: 0.27.0 + rollup: 3.21.3 dev: false /@rollup/pluginutils@4.2.1: @@ -4917,7 +4689,7 @@ packages: picomatch: 2.3.1 dev: true - /@rollup/pluginutils@5.0.2(rollup@3.20.2): + /@rollup/pluginutils@5.0.2(rollup@3.21.3): resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} engines: {node: '>=14.0.0'} peerDependencies: @@ -4926,21 +4698,21 @@ packages: rollup: optional: true dependencies: - '@types/estree': 1.0.0 + '@types/estree': 1.0.1 estree-walker: 2.0.2 picomatch: 2.3.1 - rollup: 3.20.2 + rollup: 3.21.3 dev: false - /@rushstack/node-core-library@3.55.2(@types/node@18.15.11): - resolution: {integrity: sha512-SaLe/x/Q/uBVdNFK5V1xXvsVps0y7h1sN7aSJllQyFbugyOaxhNRF25bwEDnicARNEjJw0pk0lYnJQ9Kr6ev0A==} + /@rushstack/node-core-library@3.58.0(@types/node@18.16.3): + resolution: {integrity: sha512-DHAZ3LTOEq2/EGURznpTJDnB3SNE2CKMDXuviQ6afhru6RykE3QoqXkeyjbpLb5ib5cpIRCPE/wykNe0xmQj3w==} peerDependencies: '@types/node': '*' peerDependenciesMeta: '@types/node': optional: true dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 colors: 1.2.5 fs-extra: 7.0.1 import-lazy: 4.0.0 @@ -4991,6 +4763,7 @@ packages: /@sindresorhus/is@4.6.0: resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} + dev: false /@sindresorhus/is@5.3.0: resolution: {integrity: sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==} @@ -5051,9 +4824,9 @@ packages: lodash.values: 4.3.0 object-hash: 3.0.0 packageurl-js: 1.0.1 - semver: 7.3.8 + semver: 7.5.0 tslib: 2.5.0 - dev: true + dev: false /@snyk/graphlib@2.1.9-patch.3: resolution: {integrity: sha512-bBY9b9ulfLj0v2Eer0yFYa3syVeIxVKl2EpxSrsVeT4mjA0CltZyHsF0JjoaGXP27nItTdJS5uVsj1NA+3aE+Q==} @@ -5073,14 +4846,14 @@ packages: lodash.transform: 4.6.0 lodash.union: 4.6.0 lodash.values: 4.3.0 - dev: true + dev: false /@sqltools/formatter@1.2.5: resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} dev: false - /@storybook/addon-actions@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-rcj39u9MrmzsrDWYt1zsoVxrogZ1Amrv9xkEofEY/QKUr2R3xpHhTALveY9BKIlG1GoE8zLlLoP2k4nz3sNNwQ==} + /@storybook/addon-actions@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-WxsnSjAvdf6NhUfTqcwV+FJmsJV56gh2cY4QnGfqfwO5zoBWTUYnhz57TgxSMhJY0kspyX9Q1Kc//r1d5lt1qA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5090,14 +4863,14 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.2 - '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.0.2 + '@storybook/client-logger': 7.0.7 + '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.7 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.2 - '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.2 + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.7 + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 dequal: 2.0.3 lodash: 4.17.21 polished: 4.2.2 @@ -5107,11 +4880,11 @@ packages: react-inspector: 6.0.1(react@18.2.0) telejson: 7.0.4 ts-dedent: 2.2.0 - uuid-browser: 3.1.0 + uuid: 9.0.0 dev: true - /@storybook/addon-backgrounds@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-yRNHQ4PPRJ+HIORQPhDGxn5xolw1xW0ByQZoNRpMD+AMEyfUNFdWbCsRQAOWjNhawxVMHM7EeA2Exrb41zhEjA==} + /@storybook/addon-backgrounds@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-DhT32K1+ti7MXY9oqt36b9jlg7iY68IP0ZQbR3gjShcsIXZpFqh18TQo0vwDY1ldqnBvkTk6Jd5vcxA8tfyshw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5121,22 +4894,22 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.2 - '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.0.2 + '@storybook/client-logger': 7.0.7 + '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.7 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.2 - '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.2 + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.7 + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 memoizerific: 1.11.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) ts-dedent: 2.2.0 dev: true - /@storybook/addon-controls@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-dMpRtj5cmfC9vEMve5ncvbWCEC+WD9YuzJ+grdc48E/Hd//p+O2FE6klSkrz5FAjrc+rHINixdyssekpEL6nYQ==} + /@storybook/addon-controls@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-/QEzleKoWRQ3i7KB32QvqDGcGMw4kG2BxEf0d+ymxd2SjoeL6kX2eHE0b4OxFPXiWUyTfXBFwmcI2Re3fRUJnQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5146,15 +4919,15 @@ packages: react-dom: optional: true dependencies: - '@storybook/blocks': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/client-logger': 7.0.2 - '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-common': 7.0.2 - '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/node-logger': 7.0.2 - '@storybook/preview-api': 7.0.2 - '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.2 + '@storybook/blocks': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/client-logger': 7.0.7 + '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-common': 7.0.7 + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/node-logger': 7.0.7 + '@storybook/preview-api': 7.0.7 + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 lodash: 4.17.21 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -5163,33 +4936,29 @@ packages: - supports-color dev: true - /@storybook/addon-docs@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-q3rDWoZEym6Lkmhqc/HBNfLDAmTY8l0WINGUZo/nF98eP5iu4B7Nk7V6BRGYGQt6Y6ZyIQ8WKH0e/eJww2zIog==} + /@storybook/addon-docs@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-5PT7aiTD6QPH+4CZLcv4PiUgWucD9JNGHVMRbQMEyFW6qbs87dHmu1m1uXIvx3BF5h3mTo4FHNAf8IQIq5HH9w==} peerDependencies: - '@storybook/mdx1-csf': '>=1.0.0-0' react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@storybook/mdx1-csf': - optional: true dependencies: '@babel/core': 7.21.3 '@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.21.3) '@jest/transform': 29.5.0 '@mdx-js/react': 2.3.0(react@18.2.0) - '@storybook/blocks': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/client-logger': 7.0.2 - '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/csf-plugin': 7.0.2 - '@storybook/csf-tools': 7.0.2 + '@storybook/blocks': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/client-logger': 7.0.7 + '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/csf-plugin': 7.0.7 + '@storybook/csf-tools': 7.0.7 '@storybook/global': 5.0.0 '@storybook/mdx2-csf': 1.0.0 - '@storybook/node-logger': 7.0.2 - '@storybook/postinstall': 7.0.2 - '@storybook/preview-api': 7.0.2 - '@storybook/react-dom-shim': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.2 + '@storybook/node-logger': 7.0.7 + '@storybook/postinstall': 7.0.7 + '@storybook/preview-api': 7.0.7 + '@storybook/react-dom-shim': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 fs-extra: 11.1.0 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -5200,43 +4969,42 @@ packages: - supports-color dev: true - /@storybook/addon-essentials@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-LAsWsXa/Pp2B4Ve2WVgc990FtsiHpFDRsq7S3V7xRrZP8DYRbtJIVdszPMDS5uKC+yzbswFEXz08lqbGvq8zgQ==} + /@storybook/addon-essentials@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-uNx0BvN1XP7cNnk/L4oiFQlEB/KABqOeIyI8/mhfIyTvvwo9uAYIQAyiwWuz9MFmofCNm7CgLNOUaEwNDkM4CA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/addon-actions': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-backgrounds': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-controls': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-docs': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-highlight': 7.0.2 - '@storybook/addon-measure': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-outline': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-toolbars': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-viewport': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-common': 7.0.2 - '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/node-logger': 7.0.2 - '@storybook/preview-api': 7.0.2 + '@storybook/addon-actions': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-backgrounds': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-controls': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-docs': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-highlight': 7.0.7 + '@storybook/addon-measure': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-outline': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-toolbars': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-viewport': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-common': 7.0.7 + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/node-logger': 7.0.7 + '@storybook/preview-api': 7.0.7 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) ts-dedent: 2.2.0 transitivePeerDependencies: - - '@storybook/mdx1-csf' - supports-color dev: true - /@storybook/addon-highlight@7.0.2: - resolution: {integrity: sha512-9BkL1OOanguuy73S6nLK0isUb045tOkFONd/PQldOJ0PV3agCvKxKHyzlBz7Hsba8KZhY5jQs+nVW2NiREyGYg==} + /@storybook/addon-highlight@7.0.7: + resolution: {integrity: sha512-expme2GzzCXX7/lL7UjCDi1Tfj+4LeNsAdWiurVLH7glK7yKPPeXXkIldbLP/XjJv4NKlqCwnNRHQx0vDLlE6g==} dependencies: - '@storybook/core-events': 7.0.2 + '@storybook/core-events': 7.0.7 '@storybook/global': 5.0.0 - '@storybook/preview-api': 7.0.2 + '@storybook/preview-api': 7.0.7 dev: true - /@storybook/addon-interactions@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-vPWnyGND4s9nVp+U21N/jE00dCRsHcKU68SoL4OiIZioTTRbLvrTG9eAdBkZXsVPpFHq8gndma3nXfplOSSckg==} + /@storybook/addon-interactions@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-jBl6O5sSbix0X1G9dFuWvvu4qefgLP9dAB/utVdDadZxlbPfa5B2C2q2YIqjcKZoX8DS8Fh8SUhlX1mdW5tu5w==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5246,16 +5014,16 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.2 - '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-common': 7.0.2 - '@storybook/core-events': 7.0.2 + '@storybook/client-logger': 7.0.7 + '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-common': 7.0.7 + '@storybook/core-events': 7.0.7 '@storybook/global': 5.0.0 - '@storybook/instrumenter': 7.0.2 - '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.2 - '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.2 + '@storybook/instrumenter': 7.0.7 + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.7 + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 jest-mock: 27.5.1 polished: 4.2.2 react: 18.2.0 @@ -5265,8 +5033,8 @@ packages: - supports-color dev: true - /@storybook/addon-links@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-lPtfy2MqrcI9YjupBM2eRKGPdFKVPCz7WgO/JQQakGugORJTEGCyJrNJNtWY9jDenv8ynLZ40OxtPBZi54Sr6Q==} + /@storybook/addon-links@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-DEjDxjHb3mT8Sdnx4In5Ev9gJ/XdjlHOq4iuy0wnMyrCV4wnzTQnIeSCx8nkrXFb314zc33JPnCcrb5pQoD5GQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5276,22 +5044,22 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.2 - '@storybook/core-events': 7.0.2 + '@storybook/client-logger': 7.0.7 + '@storybook/core-events': 7.0.7 '@storybook/csf': 0.1.0 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.2 - '@storybook/router': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.2 + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.7 + '@storybook/router': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) ts-dedent: 2.2.0 dev: true - /@storybook/addon-measure@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-cf/d5MXpHAjyUiDIVfc8pLn79CPHgnryDmNNlSiP2zEFKcivrRWiu8Rmrad8pGqLkuAh+PXLKCGn9uiqDvg7QQ==} + /@storybook/addon-measure@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-lb4wEIvIVF+ePx1sC+n9rDI0+49sRa6MWbcvZ+BhbAoCeGcX7uACQFdW6HyXolmBuZASsTnzVQ4KqzzvY1dSWw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5301,19 +5069,19 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.2 - '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.0.2 + '@storybook/client-logger': 7.0.7 + '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.7 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.2 - '@storybook/types': 7.0.2 + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.7 + '@storybook/types': 7.0.7 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/addon-outline@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-thVISO4NM22xlETisBvAPvz2yFD3qLGOjgzBmj8l8r9Rv0IEdwdPrwm5j0WTv8OtbhC4A8lPpvMsn5FhY5mDXg==} + /@storybook/addon-outline@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-AxbNZ4N1fXBTeMYM9tFudfW+Gzq7UikCjPxn5ax3Pde+zZjaEMppUxv5EMz4g5GIJupLYRmKH5pN0YcYoRLY6w==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5323,20 +5091,20 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.2 - '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.0.2 + '@storybook/client-logger': 7.0.7 + '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.7 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.2 - '@storybook/types': 7.0.2 + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.7 + '@storybook/types': 7.0.7 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) ts-dedent: 2.2.0 dev: true - /@storybook/addon-storysource@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-0F47HIc97bNKSDTNnUy0M4ch/tNziWwnw+EDRCNoE6kOMJ9uF6caiTk4QCahCRoA/iNbBWCn91mnU4ZRp22Wiw==} + /@storybook/addon-storysource@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-gRdnWN8H5BhQF3rkcfjEIzEsZ/kdlgjiA6YA3GRKlcRCkzWHHXtHDdT3BSUFPb/8mT8DftFZNP2PKWeKd1rZvQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5346,13 +5114,13 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.2 - '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.2 - '@storybook/router': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/source-loader': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0) + '@storybook/client-logger': 7.0.7 + '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.7 + '@storybook/router': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/source-loader': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) estraverse: 5.3.0 prop-types: 15.8.1 react: 18.2.0 @@ -5360,8 +5128,8 @@ packages: react-syntax-highlighter: 15.5.0(react@18.2.0) dev: true - /@storybook/addon-toolbars@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-tAxZ2+nUYsJdT1sx3BrmoMAZFM19+OzWJY6qSnbEq5zoRgvGZaXGR6tLMKydDoHQBU9Ta9YHGo7N7u7h1C23yg==} + /@storybook/addon-toolbars@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-/NkYHhU1VAz5lXjWuV8+ADWB84HzktvZv4jfiKX7Zzu6JVzrBu7FotQSWh3pDqqVwCB50RClUGtcHmSSac9CAQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5371,17 +5139,17 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.2 - '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.2 - '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0) + '@storybook/client-logger': 7.0.7 + '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.7 + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/addon-viewport@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-TaHJWIIazPM/TerRbka9RqjMPNpwaRsGRdVRBtVoVosy1FzsEjAdQSO7RBMe4G03m5CacSqdsDiJCblI2AXaew==} + /@storybook/addon-viewport@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-znqhd8JFEFoXcAdwYhz1CwrCpVAzhuSyUVBUNDsDs+mgBEfGth4D4abIdWWGcfP6+CmI5ebFHtk443cExZebag==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5391,49 +5159,49 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.2 - '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.0.2 + '@storybook/client-logger': 7.0.7 + '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.7 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.2 - '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0) + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.7 + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) memoizerific: 1.11.3 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/addons@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-2+amBRcmJ5kD2ZNeftiyp9Or9Kd7oVjwycZZX5SBB4EFQvRNKyM0w4GVgwbeJAdx5uuw7tJFtQgnakb39mxAgA==} + /@storybook/addons@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-it8NWXsdm3dhjc237d9jj7dGJf6eHDfuDv12nirV64J1dzWrnW+lONeZMPMgxxdLlgYfxH52fLgjcw/dAC/E+Q==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.2 - '@storybook/types': 7.0.2 + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.7 + '@storybook/types': 7.0.7 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/blocks@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-JzHmU8jZLzeQ6bunzci8j/2Ji18GBTyhrPFLk5RjEbMNGWpGjvER/yR127tZOdbPguVNr4iVbRfGzd1wGHlrzA==} + /@storybook/blocks@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-ehR0hAFWNHHqmrmbwYPKhLpgbIBKtyMbeoGClTRSnrVBGONciYJdmxegkCTReUklCY+HBJjtlwNowT+7+5sSaw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/channels': 7.0.2 - '@storybook/client-logger': 7.0.2 - '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.0.2 + '@storybook/channels': 7.0.7 + '@storybook/client-logger': 7.0.7 + '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.7 '@storybook/csf': 0.1.0 - '@storybook/docs-tools': 7.0.2 + '@storybook/docs-tools': 7.0.7 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.2 - '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.2 + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.7 + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 '@types/lodash': 4.14.191 color-convert: 2.0.1 dequal: 2.0.3 @@ -5451,19 +5219,19 @@ packages: - supports-color dev: true - /@storybook/builder-manager@7.0.2: - resolution: {integrity: sha512-Oej/n8D7eaWgmWF7nN2hXLRM53lcYOdh6umSN8Mh/LcYUfxB+dvUBFzUjoLE0xjhW6xRinrKrENT5LcP/f/HBQ==} + /@storybook/builder-manager@7.0.7: + resolution: {integrity: sha512-VI/0iEjAlzQDt1yKu8GXugNIz7t46IHIKgMNmltQ05KPypMgInUoMmbfP5AYOVddjLdSqjMLO7EK58pBLOInpw==} dependencies: '@fal-works/esbuild-plugin-global-externals': 2.1.2 - '@storybook/core-common': 7.0.2 - '@storybook/manager': 7.0.2 - '@storybook/node-logger': 7.0.2 + '@storybook/core-common': 7.0.7 + '@storybook/manager': 7.0.7 + '@storybook/node-logger': 7.0.7 '@types/ejs': 3.1.2 '@types/find-cache-dir': 3.2.1 - '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.17.15) + '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.17.18) browser-assert: 1.2.1 ejs: 3.1.8 - esbuild: 0.17.15 + esbuild: 0.17.18 esbuild-plugin-alias: 0.2.1 express: 4.18.2 find-cache-dir: 3.3.2 @@ -5474,34 +5242,31 @@ packages: - supports-color dev: true - /@storybook/builder-vite@7.0.2(typescript@5.0.3)(vite@4.2.1): - resolution: {integrity: sha512-G6CD2Gf2zwzRslvNvqgz4FeADVEA9XA4Mw6+NM6Twc+Wy/Ah482dvHS9ApSgirtGyBKjOfdHn1xQT4Z+kzbJnw==} + /@storybook/builder-vite@7.0.7(typescript@5.0.4)(vite@4.3.4): + resolution: {integrity: sha512-2wL6fsFWzij+R155urOLc7EjZtlVWf4FLfaSlLGAuZwRQU40N04YdMaHMp9tjd9Vdr5fxEDwTB51PnVWJMlsEw==} peerDependencies: '@preact/preset-vite': '*' - '@storybook/mdx1-csf': '>=1.0.0-next.1' typescript: '>= 4.3.x' vite: ^3.0.0 || ^4.0.0 vite-plugin-glimmerx: '*' peerDependenciesMeta: '@preact/preset-vite': optional: true - '@storybook/mdx1-csf': - optional: true typescript: optional: true vite-plugin-glimmerx: optional: true dependencies: - '@storybook/channel-postmessage': 7.0.2 - '@storybook/channel-websocket': 7.0.2 - '@storybook/client-logger': 7.0.2 - '@storybook/core-common': 7.0.2 - '@storybook/csf-plugin': 7.0.2 + '@storybook/channel-postmessage': 7.0.7 + '@storybook/channel-websocket': 7.0.7 + '@storybook/client-logger': 7.0.7 + '@storybook/core-common': 7.0.7 + '@storybook/csf-plugin': 7.0.7 '@storybook/mdx2-csf': 1.0.0 - '@storybook/node-logger': 7.0.2 - '@storybook/preview': 7.0.2 - '@storybook/preview-api': 7.0.2 - '@storybook/types': 7.0.2 + '@storybook/node-logger': 7.0.7 + '@storybook/preview': 7.0.7 + '@storybook/preview-api': 7.0.7 + '@storybook/types': 7.0.7 browser-assert: 1.2.1 es-module-lexer: 0.9.3 express: 4.18.2 @@ -5511,66 +5276,81 @@ packages: magic-string: 0.27.0 remark-external-links: 8.0.0 remark-slug: 6.1.0 - rollup: 3.20.2 - typescript: 5.0.3 - vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0) + rollup: 3.21.3 + typescript: 5.0.4 + vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1) transitivePeerDependencies: - supports-color dev: true - /@storybook/channel-postmessage@7.0.0: - resolution: {integrity: sha512-Sy3oHL/xDRjUiHnM0ncnkbOE5pK3O72MjOoiLJX4FCI90w03KM4+F/N0eU2cXl6yXHuCyI5eJisEzQxTNsaJiw==} + /@storybook/channel-postmessage@7.0.2: + resolution: {integrity: sha512-SZ/KqnZcx10W9hJbrzBKcP9dmgaeTaXugUhcgw1IkmjKWdsKazqFZCPwQWZZKAmhO4wYbyYOhkz3wfSIeB4mFw==} dependencies: - '@storybook/channels': 7.0.0 - '@storybook/client-logger': 7.0.0 - '@storybook/core-events': 7.0.0 + '@storybook/channels': 7.0.2 + '@storybook/client-logger': 7.0.2 + '@storybook/core-events': 7.0.2 '@storybook/global': 5.0.0 qs: 6.11.1 telejson: 7.0.4 dev: true - /@storybook/channel-postmessage@7.0.2: - resolution: {integrity: sha512-SZ/KqnZcx10W9hJbrzBKcP9dmgaeTaXugUhcgw1IkmjKWdsKazqFZCPwQWZZKAmhO4wYbyYOhkz3wfSIeB4mFw==} + /@storybook/channel-postmessage@7.0.6: + resolution: {integrity: sha512-xBsh/+85GS4bJ08r7z1iRn26EI6hGmMgNpjpFztRigMhsq5SkD9FJb+Nh9bbaHm+yPOCqJcaHQ2aQpuJNT8dHA==} dependencies: - '@storybook/channels': 7.0.2 - '@storybook/client-logger': 7.0.2 - '@storybook/core-events': 7.0.2 + '@storybook/channels': 7.0.6 + '@storybook/client-logger': 7.0.6 + '@storybook/core-events': 7.0.6 '@storybook/global': 5.0.0 qs: 6.11.1 telejson: 7.0.4 dev: true - /@storybook/channel-websocket@7.0.2: - resolution: {integrity: sha512-YU3lFId6Nsi75ddA+3qfbnLfNUPswboYyx+SALhaLuXqz7zqfzX4ezMgxeS/h0gRlUJ7nf2/yJ5qie/kZaizjw==} + /@storybook/channel-postmessage@7.0.7: + resolution: {integrity: sha512-XMtYfcaE0UoY/V7K1cTu9PcWETD4iyWb/Yswc4F9VrPw0Ui4UwGS1j4iaAu8DC06yyoJs4XvxYFBMlCQmKja6A==} dependencies: - '@storybook/channels': 7.0.2 - '@storybook/client-logger': 7.0.2 + '@storybook/channels': 7.0.7 + '@storybook/client-logger': 7.0.7 + '@storybook/core-events': 7.0.7 '@storybook/global': 5.0.0 + qs: 6.11.1 telejson: 7.0.4 dev: true - /@storybook/channels@7.0.0: - resolution: {integrity: sha512-adPIkvL4q37dGTWCpSzV8ETLdkxsg7BAgzeT9pustZJjRIZqAHGUAm7krDtGT7jbV4dU0Zw0VpUrnmyfxIkOKQ==} + /@storybook/channel-websocket@7.0.7: + resolution: {integrity: sha512-KDbLiQts4/dCow3qk5WJSPA6SlaX3iP9RhF0Fjj03hoG2TRskrvo+AkUiJr8gF6dpkPndfuCYUCRsO2Ml8B+AA==} + dependencies: + '@storybook/channels': 7.0.7 + '@storybook/client-logger': 7.0.7 + '@storybook/global': 5.0.0 + telejson: 7.0.4 dev: true /@storybook/channels@7.0.2: resolution: {integrity: sha512-qkI8mFy9c8mxN2f01etayKhCaauL6RAsxRzbX1/pKj6UqhHWqqUbtHwymrv4hG5qDYjV1e9pd7ae5eNF8Kui0g==} dev: true - /@storybook/cli@7.0.2: - resolution: {integrity: sha512-xMM2QdXNGg09wuXzAGroKrbsnaHSFPmtmefX1XGALhHuKVwxOoC2apWMpek6gY/9vh5EIRTog2Dvfd2BzNrT6Q==} + /@storybook/channels@7.0.6: + resolution: {integrity: sha512-+34cVmrXZ3lb1s5tDK+OWd5HLtEPSUMas0VKFJ0k9LBpFlVl9aiCZBJRvSYmWL7beauUfa+HSmJgjlD6228ChQ==} + dev: true + + /@storybook/channels@7.0.7: + resolution: {integrity: sha512-Om4ovBLNw8pVrBu83MpOKgAuGO9Dpr1Coh2qp8t64WRPkejX1mxOY9IgH723//zH3igx8LCkf9rvBvcrsyaScQ==} + dev: true + + /@storybook/cli@7.0.7: + resolution: {integrity: sha512-koTkWr7wlaHF14T5moRP/tYM44+Jf4GEzQ/rqx/Jfn7EbNlVUOibdLJj4JnseMGRc7ZP6tKYku2n+B8g7hJX4w==} hasBin: true dependencies: '@babel/core': 7.21.3 - '@babel/preset-env': 7.20.2(@babel/core@7.21.3) + '@babel/preset-env': 7.21.4(@babel/core@7.21.3) '@ndelangen/get-tarball': 3.0.7 - '@storybook/codemod': 7.0.2 - '@storybook/core-common': 7.0.2 - '@storybook/core-server': 7.0.2 - '@storybook/csf-tools': 7.0.2 - '@storybook/node-logger': 7.0.2 - '@storybook/telemetry': 7.0.2 - '@storybook/types': 7.0.2 + '@storybook/codemod': 7.0.7 + '@storybook/core-common': 7.0.7 + '@storybook/core-server': 7.0.7 + '@storybook/csf-tools': 7.0.7 + '@storybook/node-logger': 7.0.7 + '@storybook/telemetry': 7.0.7 + '@storybook/types': 7.0.7 '@types/semver': 7.3.13 boxen: 5.1.2 chalk: 4.1.2 @@ -5586,13 +5366,13 @@ packages: get-port: 5.1.1 giget: 1.1.2 globby: 11.1.0 - jscodeshift: 0.14.0(@babel/preset-env@7.20.2) + jscodeshift: 0.14.0(@babel/preset-env@7.21.4) leven: 3.1.0 - prettier: 2.8.7 + prettier: 2.8.8 prompts: 2.4.2 puppeteer-core: 2.1.1 read-pkg-up: 7.0.1 - semver: 7.3.8 + semver: 7.5.0 shelljs: 0.8.5 simple-update-notifier: 1.1.0 strip-json-comments: 3.1.1 @@ -5606,49 +5386,55 @@ packages: - utf-8-validate dev: true - /@storybook/client-logger@7.0.0: - resolution: {integrity: sha512-wRZZiPta37DFc8SVZ8Q3ZqyTrs5qgO6bcCuVDRLQAcO0Oz4xKEVPEVfVVxSPZU/+p2ypqdBBCP2pdL/Jy86AJg==} + /@storybook/client-logger@7.0.2: + resolution: {integrity: sha512-rv7W2BhzIQHbFpUM5/CP/acS6T5lTmaxT0MbZ9n+9h++9QQU/cFOdkZgSUbLVAb1AeUGoLsk0HYzcqPpV35Xsw==} dependencies: '@storybook/global': 5.0.0 dev: true - /@storybook/client-logger@7.0.2: - resolution: {integrity: sha512-rv7W2BhzIQHbFpUM5/CP/acS6T5lTmaxT0MbZ9n+9h++9QQU/cFOdkZgSUbLVAb1AeUGoLsk0HYzcqPpV35Xsw==} + /@storybook/client-logger@7.0.6: + resolution: {integrity: sha512-TC/E5BBkY+WNldNw5p5Ffr9x4UgMe48GmC50ikBpQFk6og1B7XpFGMMbj40EBB0R5cpZkQNEVQh4OvunEygNzg==} dependencies: '@storybook/global': 5.0.0 dev: true - /@storybook/codemod@7.0.2: - resolution: {integrity: sha512-D9PdByxJlFiaDJcLkM+RN1DHCj4VfQIlSZkADOcNtI4o9H064oiMloWDGZiR1i1FCYMSXuWmW6tMsuCVebA+Nw==} + /@storybook/client-logger@7.0.7: + resolution: {integrity: sha512-EclHjDs5HwHMKB4X2orn/KKA0DTIDmp4AXAUJGRfxb5ArpKEb7tXLHsgrRBlaoz1j5LAwKTmEyZOONh9G3etjg==} + dependencies: + '@storybook/global': 5.0.0 + dev: true + + /@storybook/codemod@7.0.7: + resolution: {integrity: sha512-VlkDlkvfbzLe+NOmzs5zGrGb4jnaeAFZqpvIkXxevr6aGcOwgeelNv8gTmgBAcy+xbGW4Pp0XA2BlMweIvKEKA==} dependencies: '@babel/core': 7.21.3 '@babel/preset-env': 7.21.4(@babel/core@7.21.3) - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 '@storybook/csf': 0.1.0 - '@storybook/csf-tools': 7.0.2 - '@storybook/node-logger': 7.0.2 - '@storybook/types': 7.0.2 + '@storybook/csf-tools': 7.0.7 + '@storybook/node-logger': 7.0.7 + '@storybook/types': 7.0.7 cross-spawn: 7.0.3 globby: 11.1.0 jscodeshift: 0.14.0(@babel/preset-env@7.21.4) lodash: 4.17.21 - prettier: 2.8.7 + prettier: 2.8.8 recast: 0.23.1 transitivePeerDependencies: - supports-color dev: true - /@storybook/components@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Ee9pY6WlpricPUdYiyR0Ov8zgHkUt541yl1CZ6Ytaom2TA12cAnRjKewbLAgVPPhIE1LsMRhOPFYql0JMtnN4Q==} + /@storybook/components@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-6PLs9LMkBuhH/w4bSJ72tYgICMbOOIHuoB/fQdVlzhsdnXL2fM/v4RVW2N7v+Oz3lYXp/JtV8V9Ub8h6eDQKXg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/client-logger': 7.0.2 + '@storybook/client-logger': 7.0.7 '@storybook/csf': 0.1.0 '@storybook/global': 5.0.0 - '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.2 + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 memoizerific: 1.11.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -5656,23 +5442,23 @@ packages: util-deprecate: 1.0.2 dev: true - /@storybook/core-client@7.0.2: - resolution: {integrity: sha512-tr6Uv41YD2O0xiUrtgujiY1QxuznhbyUI0BRsSh49e8cx3QoW7FgPy7IVZHgb17DXKZ/wY/hgdyTTB87H6IbLA==} + /@storybook/core-client@7.0.7: + resolution: {integrity: sha512-eydcpR28qV3A3BwR5V6wsixoI1BRLA0SzFiwH/1ajrgX13inv+gV97gHv47Ojf/+YAZ3HqdVaUKFsUfMKwKieA==} dependencies: - '@storybook/client-logger': 7.0.2 - '@storybook/preview-api': 7.0.2 + '@storybook/client-logger': 7.0.7 + '@storybook/preview-api': 7.0.7 dev: true - /@storybook/core-common@7.0.2: - resolution: {integrity: sha512-DayFPTCj695tnEKLuDlogclBim8mzdrbj9U1xzFm23BUReheGSGdLl2zrb3mP1l9Zj4xJ/Ctst1KN9SFbW84vw==} + /@storybook/core-common@7.0.7: + resolution: {integrity: sha512-c8T24wex9bnCYdZVZFNX4VV+wfhrp47OLzVONZDqxMhq6G//Bgv5zH4Awcx5UfWf/05VcP7KGF1VKj8ebRyEEA==} dependencies: - '@storybook/node-logger': 7.0.2 - '@storybook/types': 7.0.2 + '@storybook/node-logger': 7.0.7 + '@storybook/types': 7.0.7 '@types/node': 16.18.16 '@types/pretty-hrtime': 1.0.1 chalk: 4.1.2 - esbuild: 0.17.15 - esbuild-register: 3.4.2(esbuild@0.17.15) + esbuild: 0.17.18 + esbuild-register: 3.4.2(esbuild@0.17.18) file-system-cache: 2.0.2 find-up: 5.0.0 fs-extra: 11.1.0 @@ -5689,31 +5475,35 @@ packages: - supports-color dev: true - /@storybook/core-events@7.0.0: - resolution: {integrity: sha512-pxzNmgEI1p90bHyAYABHDDtB2XM5pffq6CqIHboK6aSCux7Cdc16IjOYq6BJIhCKaaI+qQHaFLR4JfaFAsxwQQ==} - dev: true - /@storybook/core-events@7.0.2: resolution: {integrity: sha512-1DCHCwHRL3+rlvnVVc/BCfReP31XaT2WYgcLeGTmkX1E43Po1MkgcM7PnJPSaa9POvSqZ+6YLZv5Bs1SXbufow==} dev: true - /@storybook/core-server@7.0.2: - resolution: {integrity: sha512-7ipGws8YffVaiwkc+D0+MfZc/Sy52aKenG3nDJdK4Ajmp5LPAlelb/sxIhfRvoHDbDsy2FQNz++Mb55Yh03KkA==} + /@storybook/core-events@7.0.6: + resolution: {integrity: sha512-kGrtjlYtjd4iTVk+Phb4CymZaVkB+MGscKAgcO8gfgJ/Q/gq8HQLVZSIzeoCDcDSHOGlBzbg2WVtdHIHhCKlOQ==} + dev: true + + /@storybook/core-events@7.0.7: + resolution: {integrity: sha512-XNsR2RgaL2vBwuqsu+KA1DzGmB1UFfrAhpxhmyWTKDCniwtTLlaXgfKbqwcrOrPu/o1YswgIup/9UHepRHaf4A==} + dev: true + + /@storybook/core-server@7.0.7: + resolution: {integrity: sha512-PB4zoClH7aKG4XeJhxx43iK9n/C9gctXubNN5DSN6thPm4UITOas+/q4N7AHbCPyRbcMyoW7M31KtpzZu4Fjew==} dependencies: '@aw-web-design/x-default-browser': 1.4.88 '@discoveryjs/json-ext': 0.5.7 - '@storybook/builder-manager': 7.0.2 - '@storybook/core-common': 7.0.2 - '@storybook/core-events': 7.0.2 + '@storybook/builder-manager': 7.0.7 + '@storybook/core-common': 7.0.7 + '@storybook/core-events': 7.0.7 '@storybook/csf': 0.1.0 - '@storybook/csf-tools': 7.0.2 + '@storybook/csf-tools': 7.0.7 '@storybook/docs-mdx': 0.1.0 '@storybook/global': 5.0.0 - '@storybook/manager': 7.0.2 - '@storybook/node-logger': 7.0.2 - '@storybook/preview-api': 7.0.2 - '@storybook/telemetry': 7.0.2 - '@storybook/types': 7.0.2 + '@storybook/manager': 7.0.7 + '@storybook/node-logger': 7.0.7 + '@storybook/preview-api': 7.0.7 + '@storybook/telemetry': 7.0.7 + '@storybook/types': 7.0.7 '@types/detect-port': 1.3.2 '@types/node': 16.18.16 '@types/node-fetch': 2.6.2 @@ -5735,7 +5525,7 @@ packages: pretty-hrtime: 1.0.3 prompts: 2.4.2 read-pkg-up: 7.0.1 - semver: 7.3.8 + semver: 7.5.0 serve-favicon: 2.5.0 telejson: 7.0.4 ts-dedent: 2.2.0 @@ -5749,24 +5539,24 @@ packages: - utf-8-validate dev: true - /@storybook/csf-plugin@7.0.2: - resolution: {integrity: sha512-aGuo+G6G5IwSGkmc+OUA796sOfvJMaQj8QS/Zh5F0nL4ZlQvghHpXON8cRHHvmXHQqUo07KLiy7CZh2I2oq4iQ==} + /@storybook/csf-plugin@7.0.7: + resolution: {integrity: sha512-uhf2g077gXA6ZEMXIPQ0RnX+IoOTBJbj+6+VQfT7K5tvJeop1z0Fvk0FoknNXcUe7aUA0nzA/cUQ1v4vXqbY3Q==} dependencies: - '@storybook/csf-tools': 7.0.2 + '@storybook/csf-tools': 7.0.7 unplugin: 0.10.2 transitivePeerDependencies: - supports-color dev: true - /@storybook/csf-tools@7.0.2: - resolution: {integrity: sha512-sOp355yQSpYiMqNSopmFYWZkPPRJdGgy4tpxGGLxpOZMygK3j1wQ/WQtl2Z0h61KP0S0dl6hrs0pHQz3A/eVrw==} + /@storybook/csf-tools@7.0.7: + resolution: {integrity: sha512-KbO5K2RS0oFm94eR49bAPvoyXY3Q6+ozvBek/F05RP7iAV790icQc59Xci9YDM1ONgb3afS+gSJGFBsE0h4pmg==} dependencies: '@babel/generator': 7.21.3 - '@babel/parser': 7.21.3 + '@babel/parser': 7.21.4 '@babel/traverse': 7.21.3 - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 '@storybook/csf': 0.1.0 - '@storybook/types': 7.0.2 + '@storybook/types': 7.0.7 fs-extra: 11.1.0 recast: 0.23.1 ts-dedent: 2.2.0 @@ -5774,12 +5564,6 @@ packages: - supports-color dev: true - /@storybook/csf@0.0.2-next.11: - resolution: {integrity: sha512-xGt0YSVxZb43sKmEf1GIQD8xEbo+c+S6khDEL7Qu/pYA0gh5z3WUuhOlovnelYj/YJod+XRsfVvk23AaRfUJ4Q==} - dependencies: - type-fest: 2.19.0 - dev: true - /@storybook/csf@0.1.0: resolution: {integrity: sha512-uk+jMXCZ8t38jSTHk2o5btI+aV2Ksbvl6DoOv3r6VaCM1KZqeuMwtwywIQdflkA8/6q/dKT8z8L+g8hC4GC3VQ==} dependencies: @@ -5790,13 +5574,13 @@ packages: resolution: {integrity: sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==} dev: true - /@storybook/docs-tools@7.0.2: - resolution: {integrity: sha512-w4D5BURrYjLbLGG9VKAaKU2dSdukszxRE3HWkJyhQU9R1JHvS3n8ntcMqYPqRfoHCOeBLBxP0edDYcAfzGNDYQ==} + /@storybook/docs-tools@7.0.7: + resolution: {integrity: sha512-VB4Qa33DYNxXALWcXyDid246r9Q6SGM+Q+pGWOuEJsxRxDmrUspXHaHG0CO1NIjMWfbqpOoz61vquZO0GZoAAg==} dependencies: '@babel/core': 7.21.3 - '@storybook/core-common': 7.0.2 - '@storybook/preview-api': 7.0.2 - '@storybook/types': 7.0.2 + '@storybook/core-common': 7.0.7 + '@storybook/preview-api': 7.0.7 + '@storybook/types': 7.0.7 '@types/doctrine': 0.0.3 doctrine: 3.0.0 lodash: 4.17.21 @@ -5807,23 +5591,13 @@ packages: /@storybook/expect@27.5.2-0: resolution: {integrity: sha512-cP99mhWN/JeCp7VSIiymvj5tmuMY050iFohvp8Zq+kewKsBSZ6/qpTJAGCCZk6pneTcp4S0Fm5BSqyxzbyJ3gw==} dependencies: - '@types/jest': 29.5.0 + '@types/jest': 29.5.1 dev: true /@storybook/global@5.0.0: resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} dev: true - /@storybook/instrumenter@7.0.0: - resolution: {integrity: sha512-A7jBrV7VM3OxRgall8rpjagy3VC78A/OV1g1aYVVLpAF/+Odj+MeHHF179+fR6JBLnBgukNfsG7/ZHHGs0gL5Q==} - dependencies: - '@storybook/channels': 7.0.0 - '@storybook/client-logger': 7.0.0 - '@storybook/core-events': 7.0.0 - '@storybook/global': 5.0.0 - '@storybook/preview-api': 7.0.0 - dev: true - /@storybook/instrumenter@7.0.2: resolution: {integrity: sha512-zr9/fuaYtGVUtcL8XgjA4Iq5jtzdcqQyOSH4XLXtz6JtSad3lkRagbJo2Vzbw7dO/4vzjfTMxEzvWjUuPxLOhA==} dependencies: @@ -5834,50 +5608,70 @@ packages: '@storybook/preview-api': 7.0.2 dev: true + /@storybook/instrumenter@7.0.6: + resolution: {integrity: sha512-JUcDas1cYCE+ZMVOw5CKc5g6PxDe3HH+IGdh/W9wL5vmdOUvAs858m7NLxkjkQGufof+Ohbmf/Yz5gyXaZ5+Yg==} + dependencies: + '@storybook/channels': 7.0.6 + '@storybook/client-logger': 7.0.6 + '@storybook/core-events': 7.0.6 + '@storybook/global': 5.0.0 + '@storybook/preview-api': 7.0.6 + dev: true + + /@storybook/instrumenter@7.0.7: + resolution: {integrity: sha512-0zE5lM3laKvCT4GW/XKKw8kakvI4catqK8PObZolRhfxbtGufW4VJZ2E8vXLtgA/+K3zikypjuWE6d45NLbh9w==} + dependencies: + '@storybook/channels': 7.0.7 + '@storybook/client-logger': 7.0.7 + '@storybook/core-events': 7.0.7 + '@storybook/global': 5.0.0 + '@storybook/preview-api': 7.0.7 + dev: true + /@storybook/jest@0.1.0: resolution: {integrity: sha512-TmybnEXlv5Fu2/Hq4nRj7alS9mw4CasLR0RDwaAzS+Vpvu1TC4+j9rh+b1BHtmWebbJh0JMT6mgzPqOyJdgtQA==} dependencies: '@storybook/expect': 27.5.2-0 - '@storybook/instrumenter': 7.0.0 + '@storybook/instrumenter': 7.0.2 '@testing-library/jest-dom': 5.16.5 jest-mock: 27.5.1 dev: true - /@storybook/manager-api@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-PbLj9Rc5uCMPfMdaXv1wE3koA3+d0rmZ3BJI8jeq+mfZEvpvfI4OOpRioT1q04CkkVomFOVFTyO0Q/o6Rb5N7g==} + /@storybook/manager-api@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-QTd/P72peAhofKqK+8yzIO9iWAEfPn8WUGGveV2KGaTlSlgbr87RLHEKilcXMZcYhBWC9izFRmjKum9ROdskrQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/channels': 7.0.2 - '@storybook/client-logger': 7.0.2 - '@storybook/core-events': 7.0.2 + '@storybook/channels': 7.0.7 + '@storybook/client-logger': 7.0.7 + '@storybook/core-events': 7.0.7 '@storybook/csf': 0.1.0 '@storybook/global': 5.0.0 - '@storybook/router': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.2 + '@storybook/router': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 dequal: 2.0.3 lodash: 4.17.21 memoizerific: 1.11.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - semver: 7.3.8 + semver: 7.5.0 store2: 2.14.2 telejson: 7.0.4 ts-dedent: 2.2.0 dev: true - /@storybook/manager@7.0.2: - resolution: {integrity: sha512-jsFsFKG0rPNYfuRm/WSXGMBy8vnALyFWU330ObDmfU0JID3SeLlVqAOZT1GlwI6vupYpWodsN6qPZKRmC8onRw==} + /@storybook/manager@7.0.7: + resolution: {integrity: sha512-FhquwUpUOHsjZROf6E6kzUnJ6EmMeJ9b+HYg6yYPyIMYVMjAhnkRKbIj0phGx2lhgKFlmxik+3pgchK5SLdcZA==} dev: true /@storybook/mdx2-csf@1.0.0: resolution: {integrity: sha512-dBAnEL4HfxxJmv7LdEYUoZlQbWj9APZNIbOaq0tgF8XkxiIbzqvgB0jhL/9UOrysSDbQWBiCRTu2wOVxedGfmw==} dev: true - /@storybook/node-logger@7.0.2: - resolution: {integrity: sha512-UENpXxB1yDqP7JXaODJo+pbGt5y3NFBNurBr4+pI4bMAC4ARjpgRE4wp6fxUKFPu9MAR10oCdcLEHkaVUAjYRg==} + /@storybook/node-logger@7.0.7: + resolution: {integrity: sha512-5Y4LLgKeCStq1ktCKZ5eNPzQQSQ+CYZAlkEdzQ3Pp//0KXaZvVxEvGtaYhAymP2HatLpI8Oneo4lHrJioRfgww==} dependencies: '@types/npmlog': 4.1.4 chalk: 4.1.2 @@ -5885,20 +5679,20 @@ packages: pretty-hrtime: 1.0.3 dev: true - /@storybook/postinstall@7.0.2: - resolution: {integrity: sha512-Hhiu3+N3ZDcbrhOCBJTDJbn/mC4l0v3ziyAP3yalq/2ZR9R5kfsEHHakKmswsKKV+ey0gNGijFTy3soU5oSs+A==} + /@storybook/postinstall@7.0.7: + resolution: {integrity: sha512-APcZ2KaR7z1aJje3pID4Ywmt1/aVcP3Sc4ltzNdH9mCkEsuq0fZHHQrYSa9Ya1IPRmSeLZ5/23q1iyqmGU3zoQ==} dev: true - /@storybook/preview-api@7.0.0: - resolution: {integrity: sha512-Q0IYYH1gOmx42ClYlQfQPjuERBWM3Ey+3DFsLQaraKXDdgZ9wN7jPNuS7wxuUNylT0oa/3WjxT7qNfiGw8JtBw==} + /@storybook/preview-api@7.0.2: + resolution: {integrity: sha512-QAlJM/r92+dQe/kB7MTTR9b/1mt9UJjxNjazGdEWipA/nw23kOF3o/hBcvKwBYkit4zGYsX70H+vuzW8hCo/lA==} dependencies: - '@storybook/channel-postmessage': 7.0.0 - '@storybook/channels': 7.0.0 - '@storybook/client-logger': 7.0.0 - '@storybook/core-events': 7.0.0 - '@storybook/csf': 0.0.2-next.11 + '@storybook/channel-postmessage': 7.0.2 + '@storybook/channels': 7.0.2 + '@storybook/client-logger': 7.0.2 + '@storybook/core-events': 7.0.2 + '@storybook/csf': 0.1.0 '@storybook/global': 5.0.0 - '@storybook/types': 7.0.0 + '@storybook/types': 7.0.2 '@types/qs': 6.9.7 dequal: 2.0.3 lodash: 4.17.21 @@ -5909,16 +5703,16 @@ packages: util-deprecate: 1.0.2 dev: true - /@storybook/preview-api@7.0.2: - resolution: {integrity: sha512-QAlJM/r92+dQe/kB7MTTR9b/1mt9UJjxNjazGdEWipA/nw23kOF3o/hBcvKwBYkit4zGYsX70H+vuzW8hCo/lA==} + /@storybook/preview-api@7.0.6: + resolution: {integrity: sha512-uNsedNyiEccBV2EDUC/xcKTbmiNCYuVHbgOoWTmBz0ZqFo9bX0jxkpyYWHEhJM79qqVqmrpiQ5jbS8QKn8TIxQ==} dependencies: - '@storybook/channel-postmessage': 7.0.2 - '@storybook/channels': 7.0.2 - '@storybook/client-logger': 7.0.2 - '@storybook/core-events': 7.0.2 + '@storybook/channel-postmessage': 7.0.6 + '@storybook/channels': 7.0.6 + '@storybook/client-logger': 7.0.6 + '@storybook/core-events': 7.0.6 '@storybook/csf': 0.1.0 '@storybook/global': 5.0.0 - '@storybook/types': 7.0.2 + '@storybook/types': 7.0.6 '@types/qs': 6.9.7 dequal: 2.0.3 lodash: 4.17.21 @@ -5929,12 +5723,32 @@ packages: util-deprecate: 1.0.2 dev: true - /@storybook/preview@7.0.2: - resolution: {integrity: sha512-U7MZkDT9bBq7HggLAXmTO9gI4eqhYs26fZS0L6iTE/PCX4Wg2TJBJSq2X8jhDXRqJFOt8SrQ756+V5Vtwrh4Og==} + /@storybook/preview-api@7.0.7: + resolution: {integrity: sha512-R5pmGTodpu6hbwEg2RM2ulWtW3d426YzsisHrZJ+FT9lecWauN1y9xHCz7HdNzEFhT8r4YOa24L9ZS3mosZ7hA==} + dependencies: + '@storybook/channel-postmessage': 7.0.7 + '@storybook/channels': 7.0.7 + '@storybook/client-logger': 7.0.7 + '@storybook/core-events': 7.0.7 + '@storybook/csf': 0.1.0 + '@storybook/global': 5.0.0 + '@storybook/types': 7.0.7 + '@types/qs': 6.9.7 + dequal: 2.0.3 + lodash: 4.17.21 + memoizerific: 1.11.3 + qs: 6.11.1 + synchronous-promise: 2.0.17 + ts-dedent: 2.2.0 + util-deprecate: 1.0.2 + dev: true + + /@storybook/preview@7.0.7: + resolution: {integrity: sha512-uL3ZcFao6UvxiSxCIcXKFakxEr9Nn0lvu0zzC2yQCVepzA7a+GDr1cK5VbZ6Mez38CnOvBmb5pkCbgRqSf/oug==} dev: true - /@storybook/react-dom-shim@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-fMl0aV7mJ3wyQKvt6z+rZuiIiSd9YinS77IJ1ETHqVZ4SxWriOS0GFKP6sZflrlpShoZBh+zl1lDPG7ZZdrQGw==} + /@storybook/react-dom-shim@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-INGwFeu9M+RzpvktSKuwy8Rk/70mXGqxxsb9lPtq7phmETvfpNX7GnLJqiVazTaQiB1DkB0iAPUsK2MNbBu+Kw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5943,35 +5757,34 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/react-vite@7.0.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.3)(vite@4.2.1): - resolution: {integrity: sha512-1bDrmGo6imxBzZKJJ+SEHPuDn474JY3Yatm0cPaNVtlYhbnbiTPa3PxhI4U3233l4Qsc6DXNLKvi++j/knXDCw==} + /@storybook/react-vite@7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.4): + resolution: {integrity: sha512-RuWfP/kiLpuHdcF9dWUUp9SOGMmO0FJ0HGV5yAOhGmi8KmTzvc8zjC+hJjj+sSgn2n71BO8pG/zqGl16FwfwVQ==} engines: {node: '>=16'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 vite: ^3.0.0 || ^4.0.0 dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.0.3)(vite@4.2.1) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.0.4)(vite@4.3.4) '@rollup/pluginutils': 4.2.1 - '@storybook/builder-vite': 7.0.2(typescript@5.0.3)(vite@4.2.1) - '@storybook/react': 7.0.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.3) - '@vitejs/plugin-react': 3.1.0(vite@4.2.1) + '@storybook/builder-vite': 7.0.7(typescript@5.0.4)(vite@4.3.4) + '@storybook/react': 7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4) + '@vitejs/plugin-react': 3.1.0(vite@4.3.4) ast-types: 0.14.2 magic-string: 0.27.0 react: 18.2.0 react-docgen: 6.0.0-alpha.3 react-dom: 18.2.0(react@18.2.0) - vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0) + vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1) transitivePeerDependencies: - '@preact/preset-vite' - - '@storybook/mdx1-csf' - supports-color - typescript - vite-plugin-glimmerx dev: true - /@storybook/react@7.0.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.3): - resolution: {integrity: sha512-2P7Oju1XKWMyn75dO0vjL4gthzBL/lLiCBRyAHKXZJ1H2eNdWjXkOOtH1HxnbRcXjWSU4tW96dqKY8m0iR9zAA==} + /@storybook/react@7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4): + resolution: {integrity: sha512-eEsIfAGumzo7KRi/WKFpn/PGFhwLv72oiEM/8l5MMX/6poIkiekunqJLfx2BoL4cCtiS4g7OYzOdWjN01DwVCg==} engines: {node: '>=16.0.0'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5981,13 +5794,13 @@ packages: typescript: optional: true dependencies: - '@storybook/client-logger': 7.0.2 - '@storybook/core-client': 7.0.2 - '@storybook/docs-tools': 7.0.2 + '@storybook/client-logger': 7.0.7 + '@storybook/core-client': 7.0.7 + '@storybook/docs-tools': 7.0.7 '@storybook/global': 5.0.0 - '@storybook/preview-api': 7.0.2 - '@storybook/react-dom-shim': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.2 + '@storybook/preview-api': 7.0.7 + '@storybook/react-dom-shim': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 '@types/escodegen': 0.0.6 '@types/estree': 0.0.51 '@types/node': 16.18.16 @@ -6003,119 +5816,127 @@ packages: react-element-to-jsx-string: 15.0.0(react-dom@18.2.0)(react@18.2.0) ts-dedent: 2.2.0 type-fest: 2.19.0 - typescript: 5.0.3 + typescript: 5.0.4 util-deprecate: 1.0.2 transitivePeerDependencies: - supports-color dev: true - /@storybook/router@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-ZB2vucfayZUrMLBlXju4v6CNOQQb0YKDLw5RoojdBxOsUFtnp5UiPOE+I8PQR63EBwnRjozeibV1XSM+GlQb5w==} + /@storybook/router@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-/lM8/NHQKeshfnC3ayFuO8Y9TCSHnCAPRhIsVxvanBzcj+ILbCIyZ+TspvB3hT4MbX/Ez+JR8VrMbjXIGwmH8w==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/client-logger': 7.0.2 + '@storybook/client-logger': 7.0.7 memoizerific: 1.11.3 qs: 6.11.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/source-loader@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-YIEluNmRHxDZByhwnwTwo+NXKuPrxlCDjdtkMHqvlLQW60KV4l8zaBaBP4//Jq4ycYI1Qt85UKzeZPYzJVT50g==} + /@storybook/source-loader@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-vAURuD0WmafHe8XGbkwcOn1dCLcU4ociL5xakCw0upgW0cUczp4BMRfwmP0Az5YDhfoQRjBVmUaFfjONhTDawg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: '@storybook/csf': 0.1.0 - '@storybook/types': 7.0.2 + '@storybook/types': 7.0.7 estraverse: 5.3.0 lodash: 4.17.21 - prettier: 2.8.7 + prettier: 2.8.8 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/telemetry@7.0.2: - resolution: {integrity: sha512-s2PIwI9nVYQBf3h40EFHLynYUfdqzRJMXyaCWJdVQuvdQfRkAn3CLXaubK+VdjC869z3ZfW20EMu3Mbgzcc0HA==} + /@storybook/telemetry@7.0.7: + resolution: {integrity: sha512-Ka6pwWr3sWs3A/6WQ0wsoSYzXx3Mhr7eByNZZKuuCu9jnw3I8AbIOqQX2iOVzaQBLZsvXEeqvYY8iZ+GuRbbGQ==} dependencies: - '@storybook/client-logger': 7.0.2 - '@storybook/core-common': 7.0.2 + '@storybook/client-logger': 7.0.7 + '@storybook/core-common': 7.0.7 chalk: 4.1.2 detect-package-manager: 2.0.1 fetch-retry: 5.0.4 fs-extra: 11.1.0 isomorphic-unfetch: 3.1.0 - nanoid: 3.3.4 + nanoid: 3.3.6 read-pkg-up: 7.0.1 transitivePeerDependencies: - encoding - supports-color dev: true - /@storybook/testing-library@0.0.14-next.1: - resolution: {integrity: sha512-1CAl40IKIhcPaCC4pYCG0b9IiYNymktfV/jTrX7ctquRY3akaN7f4A1SippVHosksft0M+rQTFE0ccfWW581fw==} + /@storybook/testing-library@0.1.0: + resolution: {integrity: sha512-g947f4LJZw3IluBhysMKLJXByAFiSxnGuooENqU+ZPt/GTrz1I9GDBlhmoTJahuFkVbwHvziAl/8riY2Re921g==} dependencies: - '@storybook/client-logger': 7.0.2 - '@storybook/instrumenter': 7.0.2 + '@storybook/client-logger': 7.0.6 + '@storybook/instrumenter': 7.0.6 '@testing-library/dom': 8.20.0 '@testing-library/user-event': 13.5.0(@testing-library/dom@8.20.0) ts-dedent: 2.2.0 dev: true - /@storybook/theming@7.0.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-c9sE+QAZNbopPvLiJ6BMxBERfTaq1ATyIri97FBvTucuSotNXw7X5q+ip5/nrCOPZuvK2f5wF4DRyD2HnB/rIQ==} + /@storybook/theming@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-InTZe+Sgco1NsxgiG+cyUKWQe3GsjlIyU/o5qDdtOTXcZ64HzyBuAZlAequSddqfDeMDqxRFPc2w1J28MAUHxA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) - '@storybook/client-logger': 7.0.2 + '@storybook/client-logger': 7.0.7 '@storybook/global': 5.0.0 memoizerific: 1.11.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/types@7.0.0: - resolution: {integrity: sha512-eCMW/xTVMswgD/58itibw8s8f2hZ7tciT3saRdGCymg9tPUhMC/9eGIIUSr/C+xfnCJEZm6J6DgEUo1xlifonw==} + /@storybook/types@7.0.2: + resolution: {integrity: sha512-0OCt/kAexa8MCcljxA+yZxGMn0n2U2Ync0KxotItqNbKBKVkaLQUls0+IXTWSCpC/QJvNZ049jxUHHanNi/96w==} dependencies: - '@storybook/channels': 7.0.0 + '@storybook/channels': 7.0.2 '@types/babel__core': 7.20.0 '@types/express': 4.17.17 file-system-cache: 2.0.2 dev: true - /@storybook/types@7.0.2: - resolution: {integrity: sha512-0OCt/kAexa8MCcljxA+yZxGMn0n2U2Ync0KxotItqNbKBKVkaLQUls0+IXTWSCpC/QJvNZ049jxUHHanNi/96w==} + /@storybook/types@7.0.6: + resolution: {integrity: sha512-dFASQxzvldU2Nx/eJG+oL4wCchUWAKOmOSYJYhKgtGpx99oXOiWUyC0SgCpTveBJ7AppoiseyasQ9Gd/Ccycdw==} dependencies: - '@storybook/channels': 7.0.2 + '@storybook/channels': 7.0.6 '@types/babel__core': 7.20.0 '@types/express': 4.17.17 file-system-cache: 2.0.2 dev: true - /@storybook/vue3-vite@7.0.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.3)(vite@4.2.1)(vue@3.2.47): - resolution: {integrity: sha512-lmxnHA9wHkgbNq+oW6dVnXbe9QOFjOz4Ejkl1AAjjg0blJ+VGautVa3mSeYM99szx5EigSfQjFAkv/TAJVC80Q==} + /@storybook/types@7.0.7: + resolution: {integrity: sha512-v9piuwp8FvTiHXIOOi5lEyTEJKhnbcbhVxgJ3VFhhXYFd0DTz6Bst0XIIgkgs21ITb3xhkfPbCRUueMcbXO1MA==} + dependencies: + '@storybook/channels': 7.0.7 + '@types/babel__core': 7.20.0 + '@types/express': 4.17.17 + file-system-cache: 2.0.2 + dev: true + + /@storybook/vue3-vite@7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.4)(vue@3.2.47): + resolution: {integrity: sha512-ePAOd5TBYq6WJTHgSP6B/k3PXzv8JU5sm956FMmpnDXvttZGKlp+a1qjabA8IbKD8SKfmjQmV7719Uh/ZPyQ6Q==} engines: {node: ^14.18 || >=16} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 vite: ^3.0.0 || ^4.0.0 dependencies: - '@storybook/builder-vite': 7.0.2(typescript@5.0.3)(vite@4.2.1) - '@storybook/core-server': 7.0.2 - '@storybook/vue3': 7.0.2(vue@3.2.47) - '@vitejs/plugin-vue': 4.1.0(vite@4.2.1)(vue@3.2.47) + '@storybook/builder-vite': 7.0.7(typescript@5.0.4)(vite@4.3.4) + '@storybook/core-server': 7.0.7 + '@storybook/vue3': 7.0.7(vue@3.2.47) + '@vitejs/plugin-vue': 4.2.1(vite@4.3.4)(vue@3.2.47) magic-string: 0.27.0 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0) + vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1) vue-docgen-api: 4.64.1(vue@3.2.47) transitivePeerDependencies: - '@preact/preset-vite' - - '@storybook/mdx1-csf' - bufferutil - encoding - supports-color @@ -6125,17 +5946,17 @@ packages: - vue dev: true - /@storybook/vue3@7.0.2(vue@3.2.47): - resolution: {integrity: sha512-0KDwhDYg9TQTB9awEcm8IeBMkxD6ZQFHuSHfE7TEWSx83H9pGb6CkVkAgjZlmfMiSY6gI160G4mJt2/tgTxbiA==} + /@storybook/vue3@7.0.7(vue@3.2.47): + resolution: {integrity: sha512-SPZYm46zVkL9julVrQsUpPY02M/XzaDVsODlC75dXgL1jBskmbgW6WkjFEOoNr1RtEblb+t+pqxLoK4/LA09qw==} engines: {node: '>=16.0.0'} peerDependencies: vue: ^3.0.0 dependencies: - '@storybook/core-client': 7.0.2 - '@storybook/docs-tools': 7.0.2 + '@storybook/core-client': 7.0.7 + '@storybook/docs-tools': 7.0.7 '@storybook/global': 5.0.0 - '@storybook/preview-api': 7.0.2 - '@storybook/types': 7.0.2 + '@storybook/preview-api': 7.0.7 + '@storybook/types': 7.0.7 ts-dedent: 2.2.0 type-fest: 2.19.0 vue: 3.2.47 @@ -6143,7 +5964,7 @@ packages: - supports-color dev: true - /@swc/cli@0.1.62(@swc/core@1.3.46)(chokidar@3.5.3): + /@swc/cli@0.1.62(@swc/core@1.3.56)(chokidar@3.5.3): resolution: {integrity: sha512-kOFLjKY3XH1DWLfXL1/B5MizeNorHR8wHKEi92S/Zi9Md/AK17KSqR8MgyRJ6C1fhKHvbBCl8wboyKAFXStkYw==} engines: {node: '>= 12.13'} hasBin: true @@ -6155,11 +5976,11 @@ packages: optional: true dependencies: '@mole-inc/bin-wrapper': 8.0.1 - '@swc/core': 1.3.46(@swc/helpers@0.5.0) + '@swc/core': 1.3.56 chokidar: 3.5.3 commander: 7.2.0 fast-glob: 3.2.12 - semver: 7.3.8 + semver: 7.5.0 slash: 3.0.0 source-map: 0.7.4 dev: false @@ -6175,119 +5996,115 @@ packages: dev: false optional: true - /@swc/core-darwin-arm64@1.3.46: - resolution: {integrity: sha512-kY4ASe7SsntDw2B1T70H9K1CFmK8POi+LyIpeCyC96EB9wbH2Sax+ploBB/wZALbYzr/dMJzOCU8QXzdmVS4Rg==} + /@swc/core-darwin-arm64@1.3.56: + resolution: {integrity: sha512-DZcu7BzDaLEdWHabz9DRTP0yEBLqkrWmskFcD5BX0lGAvoIvE4duMnAqi5F2B3X7630QioHRCYFoRw2WkeE3Cw==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] requiresBuild: true optional: true - /@swc/core-darwin-x64@1.3.46: - resolution: {integrity: sha512-kE3PMk8xW+2BZ3oZiTxxsUU/GzrGwM+qS4frOBz9TYHZe+W1dTtj4F9vBit4PFJ+tv4O6DPt9neGobzdq0UmRw==} + /@swc/core-darwin-x64@1.3.56: + resolution: {integrity: sha512-VH5saqYFasdRXJy6RAT+MXm0+IjkMZvOkohJwUei+oA65cKJofQwrJ1jZro8yOJFYvUSI3jgNRGsdBkmo/4hMw==} engines: {node: '>=10'} cpu: [x64] os: [darwin] requiresBuild: true optional: true - /@swc/core-linux-arm-gnueabihf@1.3.46: - resolution: {integrity: sha512-7TbiUr9MYxT+mC7sVrayag/isFoaZUG/ogkEK8B/ouA1pnIYqWh3N5ifqCzfcSRiOURt+vVqPyoO1puSiNzVuQ==} + /@swc/core-linux-arm-gnueabihf@1.3.56: + resolution: {integrity: sha512-LWwPo6NnJkH01+ukqvkoNIOpMdw+Zundm4vBeicwyVrkP+mC3kwVfi03TUFpQUz3kRKdw/QEnxGTj+MouCPbtw==} engines: {node: '>=10'} cpu: [arm] os: [linux] requiresBuild: true optional: true - /@swc/core-linux-arm64-gnu@1.3.46: - resolution: {integrity: sha512-Ycw4LU/wsUK9R+Y/2qFOPQseZDfM5D5gbWGrrYj5RoTm57FbnUsSsO26QeZxUNvams1oAQDkZDuerCc9qBRzIQ==} + /@swc/core-linux-arm64-gnu@1.3.56: + resolution: {integrity: sha512-GzsUy/4egJ4cMlxbM+Ub7AMi5CKAc+pxBxrh8MUPQbyStW8jGgnQsJouTnGy0LHawtdEnsCOl6PcO6OgvktXuQ==} engines: {node: '>=10'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /@swc/core-linux-arm64-musl@1.3.46: - resolution: {integrity: sha512-cBclyr6IW1PBr8l9D4FkebgbqlkiIYnSJCbY84J/6PfTzQlD6w9a1TAoYxdGZpJ7SGHdmB0oDiZS1rhxCSCV/Q==} + /@swc/core-linux-arm64-musl@1.3.56: + resolution: {integrity: sha512-9gxL09BIiAv8zY0DjfnFf19bo8+P4T9tdhzPwcm+1yPJcY5yr1+YFWLNFzz01agtOj6VlZ2/wUJTaOfdjjtc+A==} engines: {node: '>=10'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /@swc/core-linux-x64-gnu@1.3.46: - resolution: {integrity: sha512-amqMhTA2CXB6t11hVAZSSPKq4DZ9/sWbW3wYYQHxzqrMJML0726OJs4pt0XnlU7FzdP/9M9j2B/gWCRaCMxXVA==} + /@swc/core-linux-x64-gnu@1.3.56: + resolution: {integrity: sha512-n0ORNknl50vMRkll3BDO1E4WOqY6iISlPV1ZQCRLWQ6YQ2q8/WAryBxc2OAybcGHBUFkxyACpJukeU1QZ/9tNw==} engines: {node: '>=10'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /@swc/core-linux-x64-musl@1.3.46: - resolution: {integrity: sha512-WOQZTIkJ9khIj5Z2unf6OTrWV9k8br+HZ93RvnamEmJBlLPUuT9IjB+agNhjaDgOpz9/ZldSGqV7vzl5FGQl1Q==} + /@swc/core-linux-x64-musl@1.3.56: + resolution: {integrity: sha512-r+D34WLAOAlJtfw1gaVWpHRwCncU9nzW9i7w9kSw4HpWYnHJOz54jLGSEmNsrhdTCz1VK2ar+V2ktFUsrlGlDA==} engines: {node: '>=10'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /@swc/core-win32-arm64-msvc@1.3.46: - resolution: {integrity: sha512-4JSREbqaTRQ6QO0EeoiB6G5vuFT8zI8aTOLu5At7Cvlw+X7bOGNO+wJ3Tqw7O+68OL+0bPHzHGTXKL9kUccY1A==} + /@swc/core-win32-arm64-msvc@1.3.56: + resolution: {integrity: sha512-29Yt75Is6X24z3x8h/xZC1HnDPkPpyLH9mDQiM6Cuc0I9mVr1XSriPEUB2N/awf5IE4SA8c+3IVq1DtKWbkJIw==} engines: {node: '>=10'} cpu: [arm64] os: [win32] requiresBuild: true optional: true - /@swc/core-win32-ia32-msvc@1.3.46: - resolution: {integrity: sha512-kC8dIDzcArm1e85yHJsEZFxcNq5NztLkrqkP1nVOQ+9QXD9DKhjbZtWy2gnpclinii6KEGng8SieWiJiOA0CBQ==} + /@swc/core-win32-ia32-msvc@1.3.56: + resolution: {integrity: sha512-mplp0zbYDrcHtfvkniXlXdB04e2qIjz2Gq/XHKr4Rnc6xVORJjjXF91IemXKpavx2oZYJws+LNJL7UFQ8jyCdQ==} engines: {node: '>=10'} cpu: [ia32] os: [win32] requiresBuild: true optional: true - /@swc/core-win32-x64-msvc@1.3.46: - resolution: {integrity: sha512-rrSAfq+DvpJioBxUsnuH+sKl0eXid1DwkwNzkVGHEreN9GoP7GospWtFq7VDcO6DrS/s3HtR4/TzoIYFEBCRIg==} + /@swc/core-win32-x64-msvc@1.3.56: + resolution: {integrity: sha512-zp8MBnrw/bjdLenO/ifYzHrImSjKunqL0C2IF4LXYNRfcbYFh2NwobsVQMZ20IT0474lKRdlP8Oxdt+bHuXrzA==} engines: {node: '>=10'} cpu: [x64] os: [win32] requiresBuild: true optional: true - /@swc/core@1.3.46(@swc/helpers@0.5.0): - resolution: {integrity: sha512-WxzgJMWUBVJ95HsvEqlWzM3Qxp2FQrPa4QdAkQQuuvCMnfdctGUbhX/c3LiSRlWrl2LIkYAi4bLansTOol4QcQ==} + /@swc/core@1.3.56: + resolution: {integrity: sha512-yz/EeXT+PMZucUNrYceRUaTfuNS4IIu5EDZSOlvCEvm4jAmZi7CYH1B/kvzEzoAOzr7zkQiDPNJftcQXLkjbjA==} engines: {node: '>=10'} requiresBuild: true peerDependencies: '@swc/helpers': ^0.5.0 - dependencies: - '@swc/helpers': 0.5.0 + peerDependenciesMeta: + '@swc/helpers': + optional: true optionalDependencies: - '@swc/core-darwin-arm64': 1.3.46 - '@swc/core-darwin-x64': 1.3.46 - '@swc/core-linux-arm-gnueabihf': 1.3.46 - '@swc/core-linux-arm64-gnu': 1.3.46 - '@swc/core-linux-arm64-musl': 1.3.46 - '@swc/core-linux-x64-gnu': 1.3.46 - '@swc/core-linux-x64-musl': 1.3.46 - '@swc/core-win32-arm64-msvc': 1.3.46 - '@swc/core-win32-ia32-msvc': 1.3.46 - '@swc/core-win32-x64-msvc': 1.3.46 - - /@swc/helpers@0.5.0: - resolution: {integrity: sha512-SjY/p4MmECVVEWspzSRpQEM3sjR17sP8PbGxELWrT+YZMBfiUyt1MRUNjMV23zohwlG2HYtCQOsCwsTHguXkyg==} - dependencies: - tslib: 2.5.0 - - /@swc/jest@0.2.24(@swc/core@1.3.46): - resolution: {integrity: sha512-fwgxQbM1wXzyKzl1+IW0aGrRvAA8k0Y3NxFhKigbPjOJ4mCKnWEcNX9HQS3gshflcxq8YKhadabGUVfdwjCr6Q==} + '@swc/core-darwin-arm64': 1.3.56 + '@swc/core-darwin-x64': 1.3.56 + '@swc/core-linux-arm-gnueabihf': 1.3.56 + '@swc/core-linux-arm64-gnu': 1.3.56 + '@swc/core-linux-arm64-musl': 1.3.56 + '@swc/core-linux-x64-gnu': 1.3.56 + '@swc/core-linux-x64-musl': 1.3.56 + '@swc/core-win32-arm64-msvc': 1.3.56 + '@swc/core-win32-ia32-msvc': 1.3.56 + '@swc/core-win32-x64-msvc': 1.3.56 + + /@swc/jest@0.2.26(@swc/core@1.3.56): + resolution: {integrity: sha512-7lAi7q7ShTO3E5Gt1Xqf3pIhRbERxR1DUxvtVa9WKzIB+HGQ7wZP5sYx86zqnaEoKKGhmOoZ7gyW0IRu8Br5+A==} engines: {npm: '>= 7.0.0'} peerDependencies: '@swc/core': '*' dependencies: '@jest/create-cache-key-function': 27.5.1 - '@swc/core': 1.3.46(@swc/helpers@0.5.0) + '@swc/core': 1.3.56 jsonc-parser: 3.2.0 dev: true @@ -6297,8 +6114,8 @@ packages: dev: false optional: true - /@syuilo/aiscript@0.13.1: - resolution: {integrity: sha512-WJduqlsm7pq8r1oYbPSAf5cqR6Pve6ipGoPUUSgti51dMytMnxE2dn7d7i8759RF3yftyoclZbL/DC7IjPsTng==} + /@syuilo/aiscript@0.13.2: + resolution: {integrity: sha512-1aqQSH6U+vV01UDUotXUEjIwJKcZZPASJyIJ9msxXRpSInPGJJ/q1kGkZMgSpVhzYFT7/QBxo0UC1ZVEOsrDTw==} dependencies: autobind-decorator: 2.4.0 seedrandom: 3.0.5 @@ -6311,6 +6128,7 @@ packages: engines: {node: '>=10'} dependencies: defer-to-connect: 2.0.1 + dev: false /@szmarczak/http-timer@5.0.1: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} @@ -6318,59 +6136,58 @@ packages: dependencies: defer-to-connect: 2.0.1 - /@tabler/icons-webfont@2.12.0: - resolution: {integrity: sha512-5I4jub4zwBo4WdnZhRRmfjPutY7JL/ZbdGjH6MJ56x9RuYeJu62geJIAr8eHDdOSTWguUzZI3lQMygXoz63grw==} + /@tabler/icons-webfont@2.17.0: + resolution: {integrity: sha512-jgRZWiWCaG++jFTIU/dbOT+JmSgoFlALwBUUS31mt1b5py7B0YWelnfxf5s3ctE+0dlnoIS+r7rDOeDSAWx8SA==} dependencies: - '@tabler/icons': 2.12.0 + '@tabler/icons': 2.17.0 dev: false - /@tabler/icons@2.12.0: - resolution: {integrity: sha512-PiYD2zUOewNAtfJKU9tBZfyv3NeFlTlUe5LSGTTplvBcM0iuHrNVnd30+HIcUGJQhi3Uf0Ng9FRi/kPqm/q6ZA==} + /@tabler/icons@2.17.0: + resolution: {integrity: sha512-UeJaylOGNRhQKyDlgZfrQ3UPSGlfVQuXcmCsTYeXioKKepibW6VZ3H36Lo1jvBTBkQD2e9m+k2NxwkztOTXwrA==} dev: false - /@tensorflow/tfjs-backend-cpu@4.2.0(@tensorflow/tfjs-core@4.2.0): - resolution: {integrity: sha512-8HWg9J69m0Ovc6w8TVhhixMOcwA3t/NPXLblOA/sgJ+/JD5gsbpLWJk4QISQyb1RnpSVzw6PX3BSMTJU7hWVOg==} + /@tensorflow/tfjs-backend-cpu@4.4.0(@tensorflow/tfjs-core@4.4.0): + resolution: {integrity: sha512-d4eln500/qNym78z9IrUUzF0ITBoJGLrxV8xd92kLVoXhg35Mm+zqUXShjFcrH8joOHOFuST0qZ0TbDDqcPzPA==} engines: {yarn: '>= 1.3.2'} peerDependencies: - '@tensorflow/tfjs-core': 4.2.0 + '@tensorflow/tfjs-core': 4.4.0 dependencies: - '@tensorflow/tfjs-core': 4.2.0 + '@tensorflow/tfjs-core': 4.4.0 '@types/seedrandom': 2.4.30 seedrandom: 3.0.5 dev: false - /@tensorflow/tfjs-backend-webgl@4.2.0(@tensorflow/tfjs-core@4.2.0): - resolution: {integrity: sha512-Qvf+hD5pSh+xi48kChSGzcDKJemkc4EKfoVVjuxl4k25ZUPwuEd7zZUAtinkLu1dzgHNyvePZY8k+9rVm59HJA==} + /@tensorflow/tfjs-backend-webgl@4.4.0(@tensorflow/tfjs-core@4.4.0): + resolution: {integrity: sha512-TzQKvfAPgGt9cMG+5bVoTckoG1xr/PVJM/uODkPvzcMqi3j97kuWDXwkYJIgXldStmfiKkU7f5CmyD3Cq3E6BA==} engines: {yarn: '>= 1.3.2'} peerDependencies: - '@tensorflow/tfjs-core': 4.2.0 + '@tensorflow/tfjs-core': 4.4.0 dependencies: - '@tensorflow/tfjs-backend-cpu': 4.2.0(@tensorflow/tfjs-core@4.2.0) - '@tensorflow/tfjs-core': 4.2.0 + '@tensorflow/tfjs-backend-cpu': 4.4.0(@tensorflow/tfjs-core@4.4.0) + '@tensorflow/tfjs-core': 4.4.0 '@types/offscreencanvas': 2019.3.0 '@types/seedrandom': 2.4.30 '@types/webgl-ext': 0.0.30 seedrandom: 3.0.5 dev: false - /@tensorflow/tfjs-converter@4.2.0(@tensorflow/tfjs-core@4.2.0): - resolution: {integrity: sha512-m+E2KJM6yGQdi8ElzWpChdD/JaqhWMCi9yK70v/ndkOaCL2q2UN48nYP2T5S15vkDvMIgzAQyZfh7hxQsMuvRQ==} + /@tensorflow/tfjs-converter@4.4.0(@tensorflow/tfjs-core@4.4.0): + resolution: {integrity: sha512-JUjpRStrAuw37tgPd5UENu0UjQVuJT09yF7KpOur4BriJ0uQqrbEZHMPHmvUtr5nYzkqlXJTuXIyxvEY/olNpg==} peerDependencies: - '@tensorflow/tfjs-core': 4.2.0 + '@tensorflow/tfjs-core': 4.4.0 dependencies: - '@tensorflow/tfjs-core': 4.2.0 + '@tensorflow/tfjs-core': 4.4.0 dev: false - /@tensorflow/tfjs-core@4.2.0: - resolution: {integrity: sha512-uuHkiWVC8b00ngFbHvAV7J7haRlN/9PEdeenCi0CzBjgKd7aN25wPWaoN0TSQcU+GT4FJ8mofMZ9VBYZ/s/WLg==} + /@tensorflow/tfjs-core@4.4.0: + resolution: {integrity: sha512-Anxpc7cAOA0Q7EUXdTbQKMg3reFvrdkgDlaYzH9ZfkMq2CgLV4Au6E/s6HmbYn/VrAtWy9mLY5c/lLJqh4764g==} engines: {yarn: '>= 1.3.2'} - requiresBuild: true dependencies: '@types/long': 4.0.2 '@types/offscreencanvas': 2019.7.0 '@types/seedrandom': 2.4.30 '@types/webgl-ext': 0.0.30 - '@webgpu/types': 0.1.21 + '@webgpu/types': 0.1.30 long: 4.0.0 node-fetch: 2.6.7 seedrandom: 3.0.5 @@ -6378,13 +6195,13 @@ packages: - encoding dev: false - /@tensorflow/tfjs-data@4.2.0(@tensorflow/tfjs-core@4.2.0)(seedrandom@3.0.5): - resolution: {integrity: sha512-11t7Q+ikseduJgkd9iSeRrtor1aA3o5PVCFhC5yYvR3JLO55ic1+4Ryo0EJfhRoismS6zBUJrpzX4K0zlLbIfw==} + /@tensorflow/tfjs-data@4.4.0(@tensorflow/tfjs-core@4.4.0)(seedrandom@3.0.5): + resolution: {integrity: sha512-aY4eq4cgrsrXeBU6ABZAAN3tV0fG4YcHd0z+cYuNXnCo+VEQLJnPmhn+xymZ4VQZQH4GXbVS4dV9pXMclFNRFw==} peerDependencies: - '@tensorflow/tfjs-core': 4.2.0 + '@tensorflow/tfjs-core': 4.4.0 seedrandom: ^3.0.5 dependencies: - '@tensorflow/tfjs-core': 4.2.0 + '@tensorflow/tfjs-core': 4.4.0 '@types/node-fetch': 2.6.2 node-fetch: 2.6.7 seedrandom: 3.0.5 @@ -6393,21 +6210,21 @@ packages: - encoding dev: false - /@tensorflow/tfjs-layers@4.2.0(@tensorflow/tfjs-core@4.2.0): - resolution: {integrity: sha512-SO0KTmCFOjrW+PlP9nKYXz07XGFq6uE7am9yH2bRaRPWpEeaKT/+k0C9vFMxI/GzRwY8AK4sLe4U+jE1mhYxGw==} + /@tensorflow/tfjs-layers@4.4.0(@tensorflow/tfjs-core@4.4.0): + resolution: {integrity: sha512-OGC7shfiD9Gc698hINHK4y9slOJvu5m54tVNm4xf+WSNrw/avvgpar6yyoL5bakYIZNQvFNK75Yr8VRPR7oPeQ==} peerDependencies: - '@tensorflow/tfjs-core': 4.2.0 + '@tensorflow/tfjs-core': 4.4.0 dependencies: - '@tensorflow/tfjs-core': 4.2.0 + '@tensorflow/tfjs-core': 4.4.0 dev: false - /@tensorflow/tfjs-node@4.2.0(seedrandom@3.0.5): - resolution: {integrity: sha512-dkyExfqTjMVDpKLBzHH2b7JWzGK+QORtYswvoNHbeHwx7kvYYRAQeiHxp+xrgry6LdbWfZs88IWCwNGoOiZ21w==} + /@tensorflow/tfjs-node@4.4.0(seedrandom@3.0.5): + resolution: {integrity: sha512-+JSAddsupjSQUDZeb7QGOFkL3Tty3kjPHx8ethiYFzwTZJHCMvM7wZJd0Fqnjxym6A0KpsmB7SPZgwRRXVIlPA==} engines: {node: '>=8.11.0'} requiresBuild: true dependencies: '@mapbox/node-pre-gyp': 1.0.9 - '@tensorflow/tfjs': 4.2.0(seedrandom@3.0.5) + '@tensorflow/tfjs': 4.4.0(seedrandom@3.0.5) adm-zip: 0.5.10 google-protobuf: 3.21.2 https-proxy-agent: 2.2.4 @@ -6421,18 +6238,19 @@ packages: dev: false optional: true - /@tensorflow/tfjs@4.2.0(seedrandom@3.0.5): - resolution: {integrity: sha512-iZmtyGC9IJkx+TpFnkgDol8BHv2BU3zJ01HyNcuvnm1w1EqoNe+1n8bwvLzI/sxHMcHTqzuu7VugMaphryxE+A==} + /@tensorflow/tfjs@4.4.0(seedrandom@3.0.5): + resolution: {integrity: sha512-EmCsnzdvawyk4b+4JKaLLuicHcJQRZtL1zSy9AWJLiiHTbDDseYgLxfaCEfLk8v2bUe7SBXwl3n3B7OjgvH11Q==} + hasBin: true dependencies: - '@tensorflow/tfjs-backend-cpu': 4.2.0(@tensorflow/tfjs-core@4.2.0) - '@tensorflow/tfjs-backend-webgl': 4.2.0(@tensorflow/tfjs-core@4.2.0) - '@tensorflow/tfjs-converter': 4.2.0(@tensorflow/tfjs-core@4.2.0) - '@tensorflow/tfjs-core': 4.2.0 - '@tensorflow/tfjs-data': 4.2.0(@tensorflow/tfjs-core@4.2.0)(seedrandom@3.0.5) - '@tensorflow/tfjs-layers': 4.2.0(@tensorflow/tfjs-core@4.2.0) + '@tensorflow/tfjs-backend-cpu': 4.4.0(@tensorflow/tfjs-core@4.4.0) + '@tensorflow/tfjs-backend-webgl': 4.4.0(@tensorflow/tfjs-core@4.4.0) + '@tensorflow/tfjs-converter': 4.4.0(@tensorflow/tfjs-core@4.4.0) + '@tensorflow/tfjs-core': 4.4.0 + '@tensorflow/tfjs-data': 4.4.0(@tensorflow/tfjs-core@4.4.0)(seedrandom@3.0.5) + '@tensorflow/tfjs-layers': 4.4.0(@tensorflow/tfjs-core@4.4.0) argparse: 1.0.10 chalk: 4.1.2 - core-js: 3.27.1 + core-js: 3.29.1 regenerator-runtime: 0.13.11 yargs: 16.2.0 transitivePeerDependencies: @@ -6523,7 +6341,7 @@ packages: /@types/accepts@1.3.5: resolution: {integrity: sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/archiver@5.3.2: @@ -6543,8 +6361,8 @@ packages: /@types/babel__core@7.20.0: resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==} dependencies: - '@babel/parser': 7.21.3 - '@babel/types': 7.21.3 + '@babel/parser': 7.21.4 + '@babel/types': 7.21.4 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.18.3 @@ -6553,20 +6371,20 @@ packages: /@types/babel__generator@7.6.4: resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 dev: true /@types/babel__template@7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: - '@babel/parser': 7.21.3 - '@babel/types': 7.21.3 + '@babel/parser': 7.21.4 + '@babel/types': 7.21.4 dev: true /@types/babel__traverse@7.18.3: resolution: {integrity: sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 dev: true /@types/bcryptjs@2.4.2: @@ -6577,7 +6395,7 @@ packages: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.35 - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/braces@3.0.1: @@ -6597,8 +6415,9 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.4 - '@types/node': 18.15.11 + '@types/node': 18.16.3 '@types/responselike': 1.0.0 + dev: false /@types/cbor@6.0.0: resolution: {integrity: sha512-mGQ1lbYOwVti5Xlarn1bTeBZqgY0kstsdjnkoEovgohYKdBjGejHyNGXHdMBeqyQazIv32Jjp33+5pBEaSRy2w==} @@ -6629,7 +6448,7 @@ packages: /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/content-disposition@0.5.5: @@ -6664,7 +6483,7 @@ packages: /@types/emscripten@1.39.6: resolution: {integrity: sha512-H90aoynNhhkQP6DRweEjJp5vfUVdIj7tdPLsu7pq89vODD/lcugKfZOsfgwpvM6XUewEp2N5dCg1Uf3Qe55Dcg==} - dev: true + dev: false /@types/escape-regexp@0.0.1: resolution: {integrity: sha512-ogj/ZTIdeFkiuxDwawYuZSIgC6suFGgBeZPr6Xs5lHEcvIXTjXGtH+/n8f1XhZhespaUwJ5LIGRICPji972FLw==} @@ -6677,7 +6496,7 @@ packages: /@types/eslint@7.29.0: resolution: {integrity: sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==} dependencies: - '@types/estree': 1.0.0 + '@types/estree': 1.0.1 '@types/json-schema': 7.0.11 dev: true @@ -6685,8 +6504,8 @@ packages: resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} dev: true - /@types/estree@1.0.0: - resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} + /@types/estree@1.0.1: + resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} /@types/expect@1.20.4: resolution: {integrity: sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==} @@ -6694,7 +6513,7 @@ packages: /@types/express-serve-static-core@4.17.33: resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 dev: true @@ -6715,34 +6534,34 @@ packages: /@types/fluent-ffmpeg@2.1.21: resolution: {integrity: sha512-+n3dy/Tegt6n+YwGZUiGq6i8Jrnt8+MoyPiW1L6J5EWUl7GSt18a/VyReecfCsvTTNBXNMIKOMHDstiQM8nJLA==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/glob-stream@6.1.1: resolution: {integrity: sha512-AGOUTsTdbPkRS0qDeyeS+6KypmfVpbT5j23SN8UPG63qjKXNKjXn6V9wZUr8Fin0m9l8oGYaPK8b2WUMF8xI1A==} dependencies: '@types/glob': 8.1.0 - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/glob@7.2.0: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/glob@8.1.0: resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/graceful-fs@4.1.6: resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/gulp-rename@2.0.1: @@ -6769,12 +6588,6 @@ packages: /@types/http-cache-semantics@4.0.1: resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} - /@types/ioredis@4.28.10: - resolution: {integrity: sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==} - dependencies: - '@types/node': 18.15.11 - dev: true - /@types/istanbul-lib-coverage@2.0.4: resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} dev: true @@ -6791,8 +6604,8 @@ packages: '@types/istanbul-lib-report': 3.0.0 dev: true - /@types/jest@29.5.0: - resolution: {integrity: sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==} + /@types/jest@29.5.1: + resolution: {integrity: sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ==} dependencies: expect: 29.5.0 pretty-format: 29.5.0 @@ -6809,7 +6622,7 @@ packages: /@types/jsdom@21.1.1: resolution: {integrity: sha512-cZFuoVLtzKP3gmq9eNosUL1R50U+USkbLtUQ1bYVgl/lKp0FZM7Cq4aIHAL8oIvQ17uSHi7jXPtfDOdjPwBE7A==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 '@types/tough-cookie': 4.0.2 parse5: 7.1.2 dev: true @@ -6833,7 +6646,8 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 + dev: false /@types/lodash@4.14.191: resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==} @@ -6851,8 +6665,8 @@ packages: resolution: {integrity: sha512-IgHxcT3RC8LzFLhKwP3gbMPeaK7BM9eBH46OdapPA7yvuIUJ8H6zHZV53J8hGZcTSnt95jANt+rTBNUUc22ACQ==} dev: true - /@types/micromatch@3.1.1: - resolution: {integrity: sha512-Wr5y4uv3r7JP4jEUqv7rZeYiMBGRHcbojDVsl11wq6gw1v/ZZQvJexd9rtvVx3EIVqw8dwtcRjSs8m2DV9qHjQ==} + /@types/micromatch@4.0.2: + resolution: {integrity: sha512-oqXqVb0ci19GtH0vOA/U2TmHTcRY9kuZl4mqUxe0QmJAlIW13kzhuK5pi1i9+ngav8FjpSb9FVS/GE00GLX1VA==} dependencies: '@types/braces': 3.0.1 dev: true @@ -6880,7 +6694,7 @@ packages: /@types/node-fetch@2.6.2: resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 form-data: 3.0.1 /@types/node-fetch@3.0.3: @@ -6891,7 +6705,7 @@ packages: /@types/node@13.13.52: resolution: {integrity: sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==} - dev: true + dev: false /@types/node@14.18.36: resolution: {integrity: sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==} @@ -6904,13 +6718,13 @@ packages: /@types/node@18.11.18: resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} - /@types/node@18.15.11: - resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==} + /@types/node@18.16.3: + resolution: {integrity: sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==} /@types/nodemailer@6.4.7: resolution: {integrity: sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/normalize-package-data@2.4.1: @@ -6924,7 +6738,7 @@ packages: /@types/oauth@0.9.1: resolution: {integrity: sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/offscreencanvas@2019.3.0: @@ -6938,7 +6752,7 @@ packages: /@types/pg@8.6.6: resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 pg-protocol: 1.5.0 pg-types: 2.2.0 dev: true @@ -6966,7 +6780,7 @@ packages: /@types/qrcode@1.5.0: resolution: {integrity: sha512-x5ilHXRxUPIMfjtM+1vf/GPTRWZ81nqscursm5gMznJeK9M0YnZ1c3bEvRLQ0zSSgedLx1J6MGL231ObQGGhaA==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/qs@6.9.7: @@ -6996,7 +6810,7 @@ packages: /@types/readdir-glob@1.1.1: resolution: {integrity: sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/redis@4.0.11: @@ -7012,7 +6826,8 @@ packages: /@types/responselike@1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 + dev: false /@types/sanitize-html@2.9.0: resolution: {integrity: sha512-4fP/kEcKNj2u39IzrxWYuf/FnCCwwQCpif6wwY6ROUS1EPRIfWJjGkY3HIowY1EX/VbX5e86yq8AAE7UPMgATg==} @@ -7034,13 +6849,12 @@ packages: /@types/semver@7.3.13: resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} - dev: true /@types/serve-static@1.15.1: resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==} dependencies: '@types/mime': 3.0.1 - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/serviceworker@0.0.67: @@ -7050,13 +6864,14 @@ packages: /@types/set-cookie-parser@2.4.2: resolution: {integrity: sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true - /@types/sharp@0.31.1: - resolution: {integrity: sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag==} + /@types/sharp@0.32.0: + resolution: {integrity: sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw==} + deprecated: This is a stub types definition. sharp provides its own type definitions, so you do not need this installed. dependencies: - '@types/node': 18.15.11 + sharp: 0.32.1 dev: true /@types/sinon@10.0.13: @@ -7084,7 +6899,7 @@ packages: /@types/testing-library__jest-dom@5.14.5: resolution: {integrity: sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==} dependencies: - '@types/jest': 29.5.0 + '@types/jest': 29.5.1 dev: true /@types/throttle-debounce@5.0.0: @@ -7105,7 +6920,7 @@ packages: /@types/treeify@1.0.0: resolution: {integrity: sha512-ONpcZAEYlbPx4EtJwfTyCDQJGUpKf4sEcuySdCVjK5Fj/3vHp5HII1fqa1/+qrsLnpYELCQTfVW/awsGJePoIg==} - dev: true + dev: false /@types/undertaker-registry@1.0.1: resolution: {integrity: sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ==} @@ -7114,7 +6929,7 @@ packages: /@types/undertaker@1.2.8: resolution: {integrity: sha512-gW3PRqCHYpo45XFQHJBhch7L6hytPsIe0QeLujlnFsjHPnXLhJcPdN6a9368d7aIQgH2I/dUTPFBlGeSNA3qOg==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 '@types/undertaker-registry': 1.0.1 async-done: 1.3.2 dev: true @@ -7126,7 +6941,7 @@ packages: /@types/unzipper@0.10.5: resolution: {integrity: sha512-NrLJb29AdnBARpg9S/4ktfPEisbJ0AvaaAr3j7Q1tg8AgcEUsq2HqbNzvgLRoWyRtjzeLEv7vuL39u1mrNIyNA==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/uuid@9.0.1: @@ -7136,14 +6951,14 @@ packages: /@types/vary@1.1.0: resolution: {integrity: sha512-LQWqrIa0dvEOOH37lGksMEXbypRLUFqu6Gx0pmX7zIUisD2I/qaVgEX/vJ/PSVSW0Hk6yz1BNkFpqg6dZm3Wug==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/vinyl-fs@2.4.12: resolution: {integrity: sha512-LgBpYIWuuGsihnlF+OOWWz4ovwCYlT03gd3DuLwex50cYZLmX3yrW+sFF9ndtmh7zcZpS6Ri47PrIu+fV+sbXw==} dependencies: '@types/glob-stream': 6.1.1 - '@types/node': 18.15.11 + '@types/node': 18.16.3 '@types/vinyl': 2.0.7 dev: true @@ -7151,12 +6966,12 @@ packages: resolution: {integrity: sha512-4UqPv+2567NhMQuMLdKAyK4yzrfCqwaTt6bLhHEs8PFcxbHILsrxaY63n4wgE/BRLDWDQeI+WcTmkXKExh9hQg==} dependencies: '@types/expect': 1.20.4 - '@types/node': 18.15.11 + '@types/node': 18.16.3 /@types/web-push@3.3.2: resolution: {integrity: sha512-JxWGVL/m7mWTIg4mRYO+A6s0jPmBkr4iJr39DqJpRJAc+jrPiEe1/asmkwerzRon8ZZDxaZJpsxpv0Z18Wo9gw==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/webgl-ext@0.0.30: @@ -7165,18 +6980,18 @@ packages: /@types/webpack-env@1.18.0: resolution: {integrity: sha512-56/MAlX5WMsPVbOg7tAxnYvNYMMWr/QJiIp6BxVSW3JJXUVzzOn64qW8TzQyMSqSUFM2+PVI4aUHcHOzIz/1tg==} - dev: true + dev: false /@types/websocket@1.0.5: resolution: {integrity: sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/ws@8.5.4: resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /@types/yargs-parser@21.0.0: @@ -7199,12 +7014,12 @@ packages: resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} requiresBuild: true dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true optional: true - /@typescript-eslint/eslint-plugin@5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.37.0)(typescript@5.0.3): - resolution: {integrity: sha512-1MeobQkQ9tztuleT3v72XmY0XuKXVXusAhryoLuU5YZ+mXoYKZP9SQ7Flulh1NX4DTjpGTc2b/eMu4u7M7dhnQ==} + /@typescript-eslint/eslint-plugin@5.59.2(@typescript-eslint/parser@5.59.2)(eslint@8.39.0)(typescript@5.0.4): + resolution: {integrity: sha512-yVrXupeHjRxLDcPKL10sGQ/QlVrA8J5IYOEWVqk0lJaSZP7X5DfnP7Ns3cc74/blmbipQ1htFNVGsHX6wsYm0A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: '@typescript-eslint/parser': ^5.0.0 @@ -7215,24 +7030,24 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.5.0 - '@typescript-eslint/parser': 5.57.1(eslint@8.37.0)(typescript@5.0.3) - '@typescript-eslint/scope-manager': 5.57.1 - '@typescript-eslint/type-utils': 5.57.1(eslint@8.37.0)(typescript@5.0.3) - '@typescript-eslint/utils': 5.57.1(eslint@8.37.0)(typescript@5.0.3) + '@typescript-eslint/parser': 5.59.2(eslint@8.39.0)(typescript@5.0.4) + '@typescript-eslint/scope-manager': 5.59.2 + '@typescript-eslint/type-utils': 5.59.2(eslint@8.39.0)(typescript@5.0.4) + '@typescript-eslint/utils': 5.59.2(eslint@8.39.0)(typescript@5.0.4) debug: 4.3.4(supports-color@8.1.1) - eslint: 8.37.0 + eslint: 8.39.0 grapheme-splitter: 1.0.4 ignore: 5.2.4 natural-compare-lite: 1.4.0 - semver: 7.3.8 - tsutils: 3.21.0(typescript@5.0.3) - typescript: 5.0.3 + semver: 7.5.0 + tsutils: 3.21.0(typescript@5.0.4) + typescript: 5.0.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@5.57.1(eslint@8.37.0)(typescript@5.0.3): - resolution: {integrity: sha512-hlA0BLeVSA/wBPKdPGxoVr9Pp6GutGoY380FEhbVi0Ph4WNe8kLvqIRx76RSQt1lynZKfrXKs0/XeEk4zZycuA==} + /@typescript-eslint/parser@5.59.2(eslint@8.39.0)(typescript@5.0.4): + resolution: {integrity: sha512-uq0sKyw6ao1iFOZZGk9F8Nro/8+gfB5ezl1cA06SrqbgJAt0SRoFhb9pXaHvkrxUpZaoLxt8KlovHNk8Gp6/HQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -7241,26 +7056,26 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.57.1 - '@typescript-eslint/types': 5.57.1 - '@typescript-eslint/typescript-estree': 5.57.1(typescript@5.0.3) + '@typescript-eslint/scope-manager': 5.59.2 + '@typescript-eslint/types': 5.59.2 + '@typescript-eslint/typescript-estree': 5.59.2(typescript@5.0.4) debug: 4.3.4(supports-color@8.1.1) - eslint: 8.37.0 - typescript: 5.0.3 + eslint: 8.39.0 + typescript: 5.0.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager@5.57.1: - resolution: {integrity: sha512-N/RrBwEUKMIYxSKl0oDK5sFVHd6VI7p9K5MyUlVYAY6dyNb/wHUqndkTd3XhpGlXgnQsBkRZuu4f9kAHghvgPw==} + /@typescript-eslint/scope-manager@5.59.2: + resolution: {integrity: sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.57.1 - '@typescript-eslint/visitor-keys': 5.57.1 + '@typescript-eslint/types': 5.59.2 + '@typescript-eslint/visitor-keys': 5.59.2 dev: true - /@typescript-eslint/type-utils@5.57.1(eslint@8.37.0)(typescript@5.0.3): - resolution: {integrity: sha512-/RIPQyx60Pt6ga86hKXesXkJ2WOS4UemFrmmq/7eOyiYjYv/MUSHPlkhU6k9T9W1ytnTJueqASW+wOmW4KrViw==} + /@typescript-eslint/type-utils@5.59.2(eslint@8.39.0)(typescript@5.0.4): + resolution: {integrity: sha512-b1LS2phBOsEy/T381bxkkywfQXkV1dWda/z0PhnIy3bC5+rQWQDS7fk9CSpcXBccPY27Z6vBEuaPBCKCgYezyQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -7269,23 +7084,23 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.57.1(typescript@5.0.3) - '@typescript-eslint/utils': 5.57.1(eslint@8.37.0)(typescript@5.0.3) + '@typescript-eslint/typescript-estree': 5.59.2(typescript@5.0.4) + '@typescript-eslint/utils': 5.59.2(eslint@8.39.0)(typescript@5.0.4) debug: 4.3.4(supports-color@8.1.1) - eslint: 8.37.0 - tsutils: 3.21.0(typescript@5.0.3) - typescript: 5.0.3 + eslint: 8.39.0 + tsutils: 3.21.0(typescript@5.0.4) + typescript: 5.0.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@5.57.1: - resolution: {integrity: sha512-bSs4LOgyV3bJ08F5RDqO2KXqg3WAdwHCu06zOqcQ6vqbTJizyBhuh1o1ImC69X4bV2g1OJxbH71PJqiO7Y1RuA==} + /@typescript-eslint/types@5.59.2: + resolution: {integrity: sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree@5.57.1(typescript@5.0.3): - resolution: {integrity: sha512-A2MZqD8gNT0qHKbk2wRspg7cHbCDCk2tcqt6ScCFLr5Ru8cn+TCfM786DjPhqwseiS+PrYwcXht5ztpEQ6TFTw==} + /@typescript-eslint/typescript-estree@5.59.2(typescript@5.0.4): + resolution: {integrity: sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -7293,47 +7108,47 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.57.1 - '@typescript-eslint/visitor-keys': 5.57.1 + '@typescript-eslint/types': 5.59.2 + '@typescript-eslint/visitor-keys': 5.59.2 debug: 4.3.4(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.3.8 - tsutils: 3.21.0(typescript@5.0.3) - typescript: 5.0.3 + semver: 7.5.0 + tsutils: 3.21.0(typescript@5.0.4) + typescript: 5.0.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@5.57.1(eslint@8.37.0)(typescript@5.0.3): - resolution: {integrity: sha512-kN6vzzf9NkEtawECqze6v99LtmDiUJCVpvieTFA1uL7/jDghiJGubGZ5csicYHU1Xoqb3oH/R5cN5df6W41Nfg==} + /@typescript-eslint/utils@5.59.2(eslint@8.39.0)(typescript@5.0.4): + resolution: {integrity: sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.37.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.39.0) '@types/json-schema': 7.0.11 '@types/semver': 7.3.13 - '@typescript-eslint/scope-manager': 5.57.1 - '@typescript-eslint/types': 5.57.1 - '@typescript-eslint/typescript-estree': 5.57.1(typescript@5.0.3) - eslint: 8.37.0 + '@typescript-eslint/scope-manager': 5.59.2 + '@typescript-eslint/types': 5.59.2 + '@typescript-eslint/typescript-estree': 5.59.2(typescript@5.0.4) + eslint: 8.39.0 eslint-scope: 5.1.1 - semver: 7.3.8 + semver: 7.5.0 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys@5.57.1: - resolution: {integrity: sha512-RjQrAniDU0CEk5r7iphkm731zKlFiUjvcBS2yHAg8WWqFMCaCrD0rKEVOMUyMMcbGPZ0bPp56srkGWrgfZqLRA==} + /@typescript-eslint/visitor-keys@5.59.2: + resolution: {integrity: sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.57.1 + '@typescript-eslint/types': 5.59.2 eslint-visitor-keys: 3.4.0 dev: true - /@vitejs/plugin-react@3.1.0(vite@4.2.1): + /@vitejs/plugin-react@3.1.0(vite@4.3.4): resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -7344,121 +7159,203 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.3) magic-string: 0.27.0 react-refresh: 0.14.0 - vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0) + vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1) transitivePeerDependencies: - supports-color dev: true - /@vitejs/plugin-vue@4.1.0(vite@4.2.1)(vue@3.2.47): - resolution: {integrity: sha512-++9JOAFdcXI3lyer9UKUV4rfoQ3T1RN8yDqoCLar86s0xQct5yblxAE+yWgRnU5/0FOlVCpTZpYSBV/bGWrSrQ==} + /@vitejs/plugin-vue@4.2.1(vite@4.3.4)(vue@3.2.47): + resolution: {integrity: sha512-ZTZjzo7bmxTRTkb8GSTwkPOYDIP7pwuyV+RV53c9PYUouwcbkIZIvWvNWlX2b1dYZqtOv7D6iUAnJLVNGcLrSw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.0.0 vue: ^3.2.25 dependencies: - vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0) + vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1) vue: 3.2.47 - /@vitest/coverage-c8@0.29.8(vitest@0.29.8): - resolution: {integrity: sha512-y+sEMQMctWokjnSqm3FCQEYFkjLrYaznsxEZHxcx8z2aftpYg3A5tvI1S5himfdEFo7o+OeHzh40bPSWZHW4oQ==} + /@vitest/coverage-c8@0.30.1(vitest@0.30.1): + resolution: {integrity: sha512-/Wa3dtSuckpdngAmiCwowaEXXgJkqPrtfvrs9HTB9QoEfNbZWPu4E4cjEn4lJZb4qcGf4fxFtUA2f9DnDNAzBA==} peerDependencies: - vitest: '>=0.29.0 <1' + vitest: '>=0.30.0 <1' dependencies: c8: 7.13.0 picocolors: 1.0.0 std-env: 3.3.2 - vitest: 0.29.8(happy-dom@8.9.0)(sass@1.60.0) + vitest: 0.30.1(happy-dom@9.10.2)(sass@1.62.1) dev: true - /@vitest/expect@0.29.8: - resolution: {integrity: sha512-xlcVXn5I5oTq6NiZSY3ykyWixBxr5mG8HYtjvpgg6KaqHm0mvhX18xuwl5YGxIRNt/A5jidd7CWcNHrSvgaQqQ==} + /@vitest/expect@0.30.1: + resolution: {integrity: sha512-c3kbEtN8XXJSeN81iDGq29bUzSjQhjES2WR3aColsS4lPGbivwLtas4DNUe0jD9gg/FYGIteqOenfU95EFituw==} dependencies: - '@vitest/spy': 0.29.8 - '@vitest/utils': 0.29.8 + '@vitest/spy': 0.30.1 + '@vitest/utils': 0.30.1 chai: 4.3.7 dev: true - /@vitest/runner@0.29.8: - resolution: {integrity: sha512-FzdhnRDwEr/A3Oo1jtIk/B952BBvP32n1ObMEb23oEJNO+qO5cBet6M2XWIDQmA7BDKGKvmhUf2naXyp/2JEwQ==} + /@vitest/runner@0.30.1: + resolution: {integrity: sha512-W62kT/8i0TF1UBCNMRtRMOBWJKRnNyv9RrjIgdUryEe0wNpGZvvwPDLuzYdxvgSckzjp54DSpv1xUbv4BQ0qVA==} dependencies: - '@vitest/utils': 0.29.8 + '@vitest/utils': 0.30.1 + concordance: 5.0.4 p-limit: 4.0.0 pathe: 1.1.0 dev: true - /@vitest/spy@0.29.8: - resolution: {integrity: sha512-VdjBe9w34vOMl5I5mYEzNX8inTxrZ+tYUVk9jxaZJmHFwmDFC/GV3KBFTA/JKswr3XHvZL+FE/yq5EVhb6pSAw==} + /@vitest/snapshot@0.30.1: + resolution: {integrity: sha512-fJZqKrE99zo27uoZA/azgWyWbFvM1rw2APS05yB0JaLwUIg9aUtvvnBf4q7JWhEcAHmSwbrxKFgyBUga6tq9Tw==} + dependencies: + magic-string: 0.30.0 + pathe: 1.1.0 + pretty-format: 27.5.1 + dev: true + + /@vitest/spy@0.30.1: + resolution: {integrity: sha512-YfJeIf37GvTZe04ZKxzJfnNNuNSmTEGnla2OdL60C8od16f3zOfv9q9K0nNii0NfjDJRt/CVN/POuY5/zTS+BA==} dependencies: - tinyspy: 1.1.1 + tinyspy: 2.1.0 dev: true - /@vitest/utils@0.29.8: - resolution: {integrity: sha512-qGzuf3vrTbnoY+RjjVVIBYfuWMjn3UMUqyQtdGNZ6ZIIyte7B37exj6LaVkrZiUTvzSadVvO/tJm8AEgbGCBPg==} + /@vitest/utils@0.30.1: + resolution: {integrity: sha512-/c8Xv2zUVc+rnNt84QF0Y0zkfxnaGhp87K2dYJMLtLOIckPzuxLVzAtFCicGFdB4NeBHNzTRr1tNn7rCtQcWFA==} dependencies: - cli-truncate: 3.1.0 - diff: 5.1.0 + concordance: 5.0.4 loupe: 2.3.6 pretty-format: 27.5.1 dev: true - /@volar/language-core@1.3.0-alpha.0: - resolution: {integrity: sha512-W3uMzecHPcbwddPu4SJpUcPakRBK/y/BP+U0U6NiPpUX1tONLC4yCawt+QBJqtgJ+sfD6ztf5PyvPL3hQRqfOA==} + /@volar/language-core@1.4.1: + resolution: {integrity: sha512-EIY+Swv+TjsWpxOxujjMf1ZXqOjg9MT2VMXZ+1dKva0wD8W0L6EtptFFcCJdBbcKmGMFkr57Qzz9VNMWhs3jXQ==} dependencies: - '@volar/source-map': 1.3.0-alpha.0 + '@volar/source-map': 1.4.1 dev: true - /@volar/source-map@1.3.0-alpha.0: - resolution: {integrity: sha512-jSdizxWFvDTvkPYZnO6ew3sBZUnS0abKCbuopkc0JrIlFbznWC/fPH3iPFIMS8/IIkRxq1Jh9VVG60SmtsdaMQ==} + /@volar/source-map@1.4.1: + resolution: {integrity: sha512-bZ46ad72dsbzuOWPUtJjBXkzSQzzSejuR3CT81+GvTEI2E994D8JPXzM3tl98zyCNnjgs4OkRyliImL1dvJ5BA==} dependencies: muggle-string: 0.2.2 dev: true - /@volar/typescript@1.3.0-alpha.0: - resolution: {integrity: sha512-5UItyW2cdH2mBLu4RrECRNJRgtvvzKrSCn2y3v/D61QwIDkGx4aeil6x8RFuUL5TFtV6QvVHXnsOHxNgd+sCow==} + /@volar/typescript@1.4.1(typescript@5.0.4): + resolution: {integrity: sha512-phTy6p9yG6bgMIKQWEeDOi/aeT0njZsb1a/G1mrEuDsLmAn24Le4gDwSsGNhea6Uhu+3gdpUZn2PmZXa+WG2iQ==} + peerDependencies: + typescript: '*' dependencies: - '@volar/language-core': 1.3.0-alpha.0 + '@volar/language-core': 1.4.1 + typescript: 5.0.4 dev: true - /@volar/vue-language-core@1.2.0: - resolution: {integrity: sha512-w7yEiaITh2WzKe6u8ZdeLKCUz43wdmY/OqAmsB/PGDvvhTcVhCJ6f0W/RprZL1IhqH8wALoWiwEh/Wer7ZviMQ==} + /@volar/vue-language-core@1.6.3: + resolution: {integrity: sha512-e9OTDCPa8Wuh0ORhD4z++qTIcrsrqcI9waspr93YcQCq6j+Q+JTFuy7HBSQgyezSAsP6x1WWokKVk4fWWDJQOw==} dependencies: - '@volar/language-core': 1.3.0-alpha.0 - '@volar/source-map': 1.3.0-alpha.0 - '@vue/compiler-dom': 3.2.47 - '@vue/compiler-sfc': 3.2.47 - '@vue/reactivity': 3.2.47 - '@vue/shared': 3.2.47 - minimatch: 6.2.0 + '@volar/language-core': 1.4.1 + '@volar/source-map': 1.4.1 + '@vue/compiler-dom': 3.3.0-beta.3 + '@vue/compiler-sfc': 3.3.0-beta.3 + '@vue/reactivity': 3.3.0-beta.3 + '@vue/shared': 3.3.0-beta.3 + minimatch: 9.0.0 muggle-string: 0.2.2 vue-template-compiler: 2.7.14 dev: true - /@volar/vue-typescript@1.2.0: - resolution: {integrity: sha512-zjmRi9y3J1EkG+pfuHp8IbHmibihrKK485cfzsHjiuvJMGrpkWvlO5WVEk8oslMxxeGC5XwBFE9AOlvh378EPA==} + /@volar/vue-typescript@1.6.3(typescript@5.0.4): + resolution: {integrity: sha512-Dz29Qym33P1MSZDTZJ6PPLN1TLQfkX+g2pRnHqLCsFdSUu4yWYnElBURCn5WJkekxV/v+k2T43aur2RCSY3Ovg==} + peerDependencies: + typescript: '*' dependencies: - '@volar/typescript': 1.3.0-alpha.0 - '@volar/vue-language-core': 1.2.0 + '@volar/typescript': 1.4.1(typescript@5.0.4) + '@volar/vue-language-core': 1.6.3 + typescript: 5.0.4 dev: true + /@vue-macros/common@1.3.0(rollup@3.21.3)(vue@3.2.47): + resolution: {integrity: sha512-oRK9vdKryXtJbfucRla8XdnQiWVVNHEBid0waacdfMJn+LOunWeU/3k8VoZZc328HmmZj69MGkUoMWixsHCHGg==} + engines: {node: '>=14.19.0'} + peerDependencies: + vue: ^2.7.0 || ^3.2.25 + peerDependenciesMeta: + vue: + optional: true + dependencies: + '@babel/types': 7.21.4 + '@rollup/pluginutils': 5.0.2(rollup@3.21.3) + '@vue/compiler-sfc': 3.3.0-beta.2 + local-pkg: 0.4.3 + magic-string-ast: 0.1.2 + vue: 3.2.47 + transitivePeerDependencies: + - rollup + dev: false + + /@vue-macros/reactivity-transform@0.3.5(rollup@3.21.3)(vue@3.2.47): + resolution: {integrity: sha512-HDWPMytAp32uC4aXuLITsBkxGI8yppmthGSSYJENXPvovnIctGV7q6mMNkr9cJMjyr6pjE1rv0y0Vc7SUhx/Xw==} + engines: {node: '>=14.19.0'} + peerDependencies: + vue: ^2.7.0 || ^3.2.25 + dependencies: + '@babel/parser': 7.21.4 + '@vue-macros/common': 1.3.0(rollup@3.21.3)(vue@3.2.47) + '@vue/compiler-core': 3.3.0-beta.2 + '@vue/shared': 3.3.0-beta.2 + magic-string: 0.30.0 + unplugin: 1.3.1 + vue: 3.2.47 + transitivePeerDependencies: + - rollup + dev: false + /@vue/compiler-core@3.2.47: resolution: {integrity: sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==} dependencies: - '@babel/parser': 7.21.3 + '@babel/parser': 7.21.4 '@vue/shared': 3.2.47 estree-walker: 2.0.2 source-map: 0.6.1 + /@vue/compiler-core@3.3.0-beta.2: + resolution: {integrity: sha512-Z2VZCL9Rr1gVgyALHIRP+lNFjgfs/K4aTxvJYQ2vhgEAaI0/L6wtG5sr/gOP+MgxwGQV0PvA+iDG3Y3PC7rTEg==} + dependencies: + '@babel/parser': 7.21.4 + '@vue/shared': 3.3.0-beta.2 + estree-walker: 2.0.2 + source-map-js: 1.0.2 + dev: false + + /@vue/compiler-core@3.3.0-beta.3: + resolution: {integrity: sha512-mv2rPo4JHou6ebm7+U/wO1HpA6W1zDfTqbt4fqjoXrMwU4DWNgRcLKTXG6G3cXV4mOe+2YgWspfxEzo7fPTMKg==} + dependencies: + '@babel/parser': 7.21.4 + '@vue/shared': 3.3.0-beta.3 + estree-walker: 2.0.2 + source-map-js: 1.0.2 + dev: true + /@vue/compiler-dom@3.2.47: resolution: {integrity: sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==} dependencies: '@vue/compiler-core': 3.2.47 '@vue/shared': 3.2.47 + /@vue/compiler-dom@3.3.0-beta.2: + resolution: {integrity: sha512-9LPRdCj66OwmUiPa9nuKiaoyKxlFT56j+io8nK/aW5OLl1UkY//Lj661fmDkTY20oLmArt73fAuHD913w4hRqA==} + dependencies: + '@vue/compiler-core': 3.3.0-beta.2 + '@vue/shared': 3.3.0-beta.2 + dev: false + + /@vue/compiler-dom@3.3.0-beta.3: + resolution: {integrity: sha512-e7VpjN9wYiuJdJos6Uoe501CzdMkfaEr/27Ks4Ss7Irtcj5YA/S1OROZ35Xl2Pc3ctx6beq5RpcOvnMqh0hcaA==} + dependencies: + '@vue/compiler-core': 3.3.0-beta.3 + '@vue/shared': 3.3.0-beta.3 + dev: true + /@vue/compiler-sfc@2.7.14: resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==} dependencies: - '@babel/parser': 7.21.3 - postcss: 8.4.21 + '@babel/parser': 7.21.4 + postcss: 8.4.23 source-map: 0.6.1 dev: false @@ -7476,26 +7373,96 @@ packages: postcss: 8.4.21 source-map: 0.6.1 + /@vue/compiler-sfc@3.3.0-beta.2: + resolution: {integrity: sha512-5FmcQ5LIpM/Y22dTxnxWPD04jC2gr6XSVVqQNY0y776F1P9x4f06fIpMibL58aKU07Th2z4Ab3oPg/Cg1QNVmA==} + dependencies: + '@babel/parser': 7.21.4 + '@vue/compiler-core': 3.3.0-beta.2 + '@vue/compiler-dom': 3.3.0-beta.2 + '@vue/compiler-ssr': 3.3.0-beta.2 + '@vue/reactivity-transform': 3.3.0-beta.2 + '@vue/shared': 3.3.0-beta.2 + estree-walker: 2.0.2 + magic-string: 0.30.0 + postcss: 8.4.21 + source-map-js: 1.0.2 + dev: false + + /@vue/compiler-sfc@3.3.0-beta.3: + resolution: {integrity: sha512-6shZNooetShjSMHJvgVoE0EM8pOMV5vnrzsHoCU06stzV+kqRJQpbN7xf2s9wK2fgHMIBSMINrM9AuZiQnNCJg==} + dependencies: + '@babel/parser': 7.21.4 + '@vue/compiler-core': 3.3.0-beta.3 + '@vue/compiler-dom': 3.3.0-beta.3 + '@vue/compiler-ssr': 3.3.0-beta.3 + '@vue/reactivity-transform': 3.3.0-beta.3 + '@vue/shared': 3.3.0-beta.3 + estree-walker: 2.0.2 + magic-string: 0.30.0 + postcss: 8.4.23 + source-map-js: 1.0.2 + dev: true + /@vue/compiler-ssr@3.2.47: resolution: {integrity: sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==} dependencies: '@vue/compiler-dom': 3.2.47 '@vue/shared': 3.2.47 + /@vue/compiler-ssr@3.3.0-beta.2: + resolution: {integrity: sha512-Xg9od6GvHwfEpnTxMQR+KlKG1nbOHWRLHCiSA0FENiSDTjCDHh0ClzZLhIZUZJD75miyE9ia5ZQF6vpw680rCw==} + dependencies: + '@vue/compiler-dom': 3.3.0-beta.2 + '@vue/shared': 3.3.0-beta.2 + dev: false + + /@vue/compiler-ssr@3.3.0-beta.3: + resolution: {integrity: sha512-egJ0lEVAod3Hpnw96cJ/0a9qv5f5h5/VCBpKYT8scqkzoMsikh8AJant2omokBCL/Ut5UAMLVQlA5b66+2Ys/g==} + dependencies: + '@vue/compiler-dom': 3.3.0-beta.3 + '@vue/shared': 3.3.0-beta.3 + dev: true + /@vue/reactivity-transform@3.2.47: resolution: {integrity: sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==} dependencies: - '@babel/parser': 7.21.3 + '@babel/parser': 7.21.4 '@vue/compiler-core': 3.2.47 '@vue/shared': 3.2.47 estree-walker: 2.0.2 magic-string: 0.25.9 + /@vue/reactivity-transform@3.3.0-beta.2: + resolution: {integrity: sha512-EUL53/rsd+hrqhCa/SrhXQ6PzMZJfLQt39xQlzr0Sxsdv/bg5lqbcK9YtGkjYohRuSp1QneFU78LEsQ9j4B2Dw==} + dependencies: + '@babel/parser': 7.21.4 + '@vue/compiler-core': 3.3.0-beta.2 + '@vue/shared': 3.3.0-beta.2 + estree-walker: 2.0.2 + magic-string: 0.30.0 + dev: false + + /@vue/reactivity-transform@3.3.0-beta.3: + resolution: {integrity: sha512-aM3TgBca9QMMu/9B9ASRVvckeZpAdJO9nmQh5UCznhoDYjVxQPS+sCQvH6TLOjPB1MDQMVQYg4ZiPqfVVo7NbA==} + dependencies: + '@babel/parser': 7.21.4 + '@vue/compiler-core': 3.3.0-beta.3 + '@vue/shared': 3.3.0-beta.3 + estree-walker: 2.0.2 + magic-string: 0.30.0 + dev: true + /@vue/reactivity@3.2.47: resolution: {integrity: sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==} dependencies: '@vue/shared': 3.2.47 + /@vue/reactivity@3.3.0-beta.3: + resolution: {integrity: sha512-9VjWfWgZJ18YXEkfnDfZr33RyLBa6zc0RARLkMqMApWvM26eusZAZ4hhyxlgODBU/mEFk4XOGIAtwwSQedA0MQ==} + dependencies: + '@vue/shared': 3.3.0-beta.3 + dev: true + /@vue/runtime-core@3.2.47: resolution: {integrity: sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==} dependencies: @@ -7521,6 +7488,14 @@ packages: /@vue/shared@3.2.47: resolution: {integrity: sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==} + /@vue/shared@3.3.0-beta.2: + resolution: {integrity: sha512-AsHYKYiYUnL/LHog6iV/G9tctFZYOsaxHDbSnfeyip94rjndO46XSDbHek7wDlcj3NHGaf8jAQQKfva/7mypjA==} + dev: false + + /@vue/shared@3.3.0-beta.3: + resolution: {integrity: sha512-st1SnB/Bkbb9TsieeI4TRX9TqHYIR5wvIma3ZtEben55EYSWa1q5u2BhTNgABSdH+rv3Xwfrvpwh5PmCw6Y53g==} + dev: true + /@vue/test-utils@2.3.2(vue@3.2.47): resolution: {integrity: sha512-hJnVaYhbrIm0yBS0+e1Y0Sj85cMyAi+PAbK4JHqMRUZ6S622Goa+G7QzkRSyvCteG8wop7tipuEbHoZo26wsSA==} peerDependencies: @@ -7533,8 +7508,8 @@ packages: '@vue/server-renderer': 3.2.47(vue@3.2.47) dev: true - /@webgpu/types@0.1.21: - resolution: {integrity: sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==} + /@webgpu/types@0.1.30: + resolution: {integrity: sha512-9AXJSmL3MzY8ZL//JjudA//q+2kBRGhLBFpkdGksWIuxrMy81nFrCzj2Am+mbh8WoU6rXmv7cY5E3rdlyru2Qg==} dev: false /@xmldom/xmldom@0.8.6: @@ -7571,21 +7546,21 @@ packages: p-limit: 2.3.0 pluralize: 7.0.0 pretty-bytes: 5.6.0 - semver: 7.3.8 + semver: 7.5.0 stream-to-promise: 2.2.0 tar-stream: 2.2.0 treeify: 1.1.0 tslib: 1.14.1 tunnel: 0.0.6 - dev: true + dev: false - /@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.17.15): + /@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.17.18): resolution: {integrity: sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==} engines: {node: '>=14.15.0'} peerDependencies: esbuild: '>=0.10.0' dependencies: - esbuild: 0.17.15 + esbuild: 0.17.18 tslib: 2.5.0 dev: true @@ -7595,7 +7570,7 @@ packages: dependencies: '@yarnpkg/libzip': 2.3.0 tslib: 1.14.1 - dev: true + dev: false /@yarnpkg/json-proxy@2.1.1: resolution: {integrity: sha512-meUiCAgCYpXTH1qJfqfz+dX013ohW9p2dKfwIzUYAFutH+lsz1eHPBIk72cuCV84adh9gX6j66ekBKH/bIhCQw==} @@ -7603,7 +7578,7 @@ packages: dependencies: '@yarnpkg/fslib': 2.10.2 tslib: 1.14.1 - dev: true + dev: false /@yarnpkg/libzip@2.3.0: resolution: {integrity: sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==} @@ -7611,11 +7586,11 @@ packages: dependencies: '@types/emscripten': 1.39.6 tslib: 1.14.1 - dev: true + dev: false /@yarnpkg/lockfile@1.1.0: resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} - dev: true + dev: false /@yarnpkg/parsers@2.5.1: resolution: {integrity: sha512-KtYN6Ez3x753vPF9rETxNTPnPjeaHY11Exlpqb4eTII7WRlnGiZ5rvvQBau4R20Ik5KBv+vS3EJEcHyCunwzzw==} @@ -7623,7 +7598,7 @@ packages: dependencies: js-yaml: 3.14.1 tslib: 1.14.1 - dev: true + dev: false /@yarnpkg/pnp@2.3.2: resolution: {integrity: sha512-JdwHu1WBCISqJEhIwx6Hbpe8MYsYbkGMxoxolkDiAeJ9IGEe08mQcbX1YmUDV1ozSWlm9JZE90nMylcDsXRFpA==} @@ -7632,7 +7607,7 @@ packages: '@types/node': 13.13.52 '@yarnpkg/fslib': 2.10.2 tslib: 1.14.1 - dev: true + dev: false /@yarnpkg/shell@2.4.1: resolution: {integrity: sha512-oNNJkH8ZI5uwu0dMkJf737yMSY1WXn9gp55DqSA5wAOhKvV5DJTXFETxkVgBQhO6Bow9tMGSpvowTMD/oAW/9g==} @@ -7647,7 +7622,7 @@ packages: micromatch: 4.0.5 stream-buffers: 3.0.2 tslib: 1.14.1 - dev: true + dev: false /@zxing/text-encoding@0.9.0: resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==} @@ -7850,11 +7825,6 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - /ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} - engines: {node: '>=12'} - dev: true - /ansi-styles@2.2.1: resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} engines: {node: '>=0.10.0'} @@ -7878,11 +7848,6 @@ packages: engines: {node: '>=10'} dev: true - /ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - dev: true - /ansi-wrap@0.1.0: resolution: {integrity: sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==} engines: {node: '>=0.10.0'} @@ -7890,6 +7855,7 @@ packages: /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: false /anymatch@2.0.0: resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==} @@ -8480,7 +8446,7 @@ packages: engines: {node: '>=12'} dependencies: bin-version: 6.0.0 - semver: 7.3.8 + semver: 7.5.0 semver-truncate: 2.0.0 dev: false @@ -8511,7 +8477,7 @@ packages: /binjumper@0.1.4: resolution: {integrity: sha512-Gdxhj+U295tIM6cO4bJO1jsvSjBVHNpj2o/OwW7pqDEtaqF6KdOxjtbo93jMMKAkP7+u09+bV8DhSqjIv4qR3w==} engines: {node: '>=10.12.0'} - dev: true + dev: false /bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -8532,6 +8498,10 @@ packages: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} dev: true + /blueimp-md5@2.19.0: + resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} + dev: true + /blurhash@2.0.5: resolution: {integrity: sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==} dev: false @@ -8733,10 +8703,10 @@ packages: cron-parser: 4.7.1 debuglog: 1.0.1 get-port: 5.1.1 - ioredis: 5.2.4 + ioredis: 5.3.2 lodash: 4.17.21 msgpackr: 1.8.1 - semver: 7.3.8 + semver: 7.5.0 uuid: 8.3.2 transitivePeerDependencies: - supports-color @@ -8825,6 +8795,7 @@ packages: /cacheable-lookup@5.0.4: resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} engines: {node: '>=10.6.0'} + dev: false /cacheable-lookup@6.1.0: resolution: {integrity: sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==} @@ -8858,6 +8829,7 @@ packages: lowercase-keys: 2.0.0 normalize-url: 6.1.0 responselike: 2.0.1 + dev: false /cachedir@2.3.0: resolution: {integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==} @@ -8984,7 +8956,6 @@ packages: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - dev: true /chalk@4.1.1: resolution: {integrity: sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==} @@ -9031,45 +9002,45 @@ packages: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true - /chart.js@4.2.1: - resolution: {integrity: sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==} - engines: {pnpm: ^7.0.0} + /chart.js@4.3.0: + resolution: {integrity: sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==} + engines: {pnpm: '>=7'} dependencies: '@kurkle/color': 0.3.2 dev: false - /chartjs-adapter-date-fns@3.0.0(chart.js@4.2.1)(date-fns@2.29.3): + /chartjs-adapter-date-fns@3.0.0(chart.js@4.3.0)(date-fns@2.30.0): resolution: {integrity: sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==} peerDependencies: chart.js: '>=2.8.0' date-fns: '>=2.0.0' dependencies: - chart.js: 4.2.1 - date-fns: 2.29.3 + chart.js: 4.3.0 + date-fns: 2.30.0 dev: false - /chartjs-chart-matrix@2.0.1(chart.js@4.2.1): + /chartjs-chart-matrix@2.0.1(chart.js@4.3.0): resolution: {integrity: sha512-BGfeY+/PHnITyDlc7WfnKJ1RyOfgOzIqWp/gxzzl7pUjyoGzHDcw51qd2xJF9gdT9Def7ZwOnOMm8GJUXDxI0w==} peerDependencies: chart.js: '>=3.0.0' dependencies: - chart.js: 4.2.1 + chart.js: 4.3.0 dev: false - /chartjs-plugin-gradient@0.6.1(chart.js@4.2.1): + /chartjs-plugin-gradient@0.6.1(chart.js@4.3.0): resolution: {integrity: sha512-TGHNIh8KqQMLdb+UfY80cBHYRyOC47eeokmgkeajRdKGbFt462lJiyiq4ZJ25fiM7BGsmzoBLhmVyEw4B3gQxw==} peerDependencies: chart.js: '>=2.6.0' dependencies: - chart.js: 4.2.1 + chart.js: 4.3.0 dev: false - /chartjs-plugin-zoom@2.0.1(chart.js@4.2.1): + /chartjs-plugin-zoom@2.0.1(chart.js@4.3.0): resolution: {integrity: sha512-ogOmLu6e+Q7E1XWOCOz9YwybMslz9qNfGV2a+qjfmqJYpsw5ZMoRHZBUyW+NGhkpQ5PwwPA/+rikHpBZb7PZuA==} peerDependencies: chart.js: '>=3.2.0' dependencies: - chart.js: 4.2.1 + chart.js: 4.3.0 hammerjs: 2.0.8 dev: false @@ -9145,11 +9116,11 @@ packages: snyk-nodejs-lockfile-parser: 1.48.2 transitivePeerDependencies: - supports-color - dev: true + dev: false /ci-info@2.0.0: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} - dev: true + dev: false /ci-info@3.7.1: resolution: {integrity: sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==} @@ -9226,14 +9197,6 @@ packages: string-width: 4.2.3 dev: true - /cli-truncate@3.1.0: - resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - slice-ansi: 5.0.0 - string-width: 5.1.2 - dev: true - /cli-width@3.0.0: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} @@ -9241,7 +9204,7 @@ packages: /clipanion@2.6.2: resolution: {integrity: sha512-0tOHJNMF9+4R3qcbBL+4IxLErpaYSYvzs10aXuECDbZdJOuJHdagJMAqvLdeaUQTI/o2uSCDRpet6ywDiKOAYw==} - dev: true + dev: false /cliui@3.2.0: resolution: {integrity: sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==} @@ -9300,6 +9263,7 @@ packages: resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} dependencies: mimic-response: 1.0.1 + dev: false /clone-stats@1.0.0: resolution: {integrity: sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==} @@ -9396,7 +9360,6 @@ packages: dependencies: color-name: 1.1.4 simple-swizzle: 0.2.2 - dev: false /color-support@1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} @@ -9416,7 +9379,6 @@ packages: dependencies: color-convert: 2.0.1 color-string: 1.9.1 - dev: false /colorette@2.0.19: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} @@ -9453,11 +9415,6 @@ packages: /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - /commander@5.1.0: - resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} - engines: {node: '>= 6'} - dev: true - /commander@6.2.1: resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} engines: {node: '>= 6'} @@ -9532,6 +9489,20 @@ packages: readable-stream: 2.3.7 typedarray: 0.0.6 + /concordance@5.0.4: + resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==} + engines: {node: '>=10.18.0 <11 || >=12.14.0 <13 || >=14'} + dependencies: + date-time: 3.1.0 + esutils: 2.0.3 + fast-diff: 1.2.0 + js-string-escape: 1.0.1 + lodash: 4.17.21 + md5-hex: 3.0.1 + semver: 7.5.0 + well-known-symbols: 2.0.0 + dev: true + /config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} dependencies: @@ -9549,7 +9520,7 @@ packages: /constantinople@4.0.1: resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==} dependencies: - '@babel/parser': 7.21.3 + '@babel/parser': 7.21.4 '@babel/types': 7.21.4 /content-disposition@0.5.4: @@ -9600,11 +9571,6 @@ packages: browserslist: 4.21.5 dev: true - /core-js@3.27.1: - resolution: {integrity: sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==} - requiresBuild: true - dev: false - /core-js@3.29.1: resolution: {integrity: sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==} requiresBuild: true @@ -9655,7 +9621,6 @@ packages: node-fetch: 2.6.7 transitivePeerDependencies: - encoding - dev: true /cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} @@ -9772,8 +9737,8 @@ packages: uniq: 1.0.1 dev: false - /cypress@12.9.0: - resolution: {integrity: sha512-Ofe09LbHKgSqX89Iy1xen2WvpgbvNxDzsWx3mgU1mfILouELeXYGwIib3ItCwoRrRifoQwcBFmY54Vs0zw7QCg==} + /cypress@12.11.0: + resolution: {integrity: sha512-TJE+CCWI26Hwr5Msb9GpQhFLubdYooW0fmlPwTsfiyxmngqc7+SZGLPeIkj2dTSSZSEtpQVzOzvcnzH0o8G7Vw==} engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0} hasBin: true requiresBuild: true @@ -9792,7 +9757,7 @@ packages: check-more-types: 2.24.0 cli-cursor: 3.1.0 cli-table3: 0.6.3 - commander: 5.1.0 + commander: 6.2.1 common-tags: 1.8.2 dayjs: 1.11.7 debug: 4.3.4(supports-color@8.1.1) @@ -9810,12 +9775,12 @@ packages: listr2: 3.14.0(enquirer@2.3.6) lodash: 4.17.21 log-symbols: 4.1.0 - minimist: 1.2.7 + minimist: 1.2.8 ospath: 1.2.2 pretty-bytes: 5.6.0 proxy-from-env: 1.0.0 request-progress: 3.0.0 - semver: 7.3.8 + semver: 7.5.0 supports-color: 8.1.1 tmp: 0.2.1 untildify: 4.0.0 @@ -9852,11 +9817,20 @@ packages: whatwg-url: 12.0.1 dev: false - /date-fns@2.29.3: - resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==} + /date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} + dependencies: + '@babel/runtime': 7.21.0 dev: false + /date-time@3.1.0: + resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==} + engines: {node: '>=6'} + dependencies: + time-zone: 1.0.0 + dev: true + /dayjs@1.11.7: resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==} dev: true @@ -9991,7 +9965,6 @@ packages: /deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} - dev: false /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -10090,11 +10063,6 @@ packages: /delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - /denque@1.5.1: - resolution: {integrity: sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==} - engines: {node: '>=0.10'} - dev: false - /denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} @@ -10131,7 +10099,6 @@ packages: /detect-libc@2.0.1: resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} engines: {node: '>=8'} - dev: false /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} @@ -10168,7 +10135,7 @@ packages: /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} - dev: true + dev: false /diff@5.1.0: resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} @@ -10274,10 +10241,6 @@ packages: object.defaults: 1.1.0 dev: false - /eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true - /ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} dependencies: @@ -10326,10 +10289,6 @@ packages: /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - /emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: true - /encode-utf8@1.0.3: resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==} dev: false @@ -10351,7 +10310,7 @@ packages: resolution: {integrity: sha512-EoulkdKF/1xa92q25PbjuDcgJ9RDHYU2Rs3SCIvs2/dSQ3BpmxneNHmA/M7fe60M3PrV7nNGTTNbkK62l6vXiQ==} dependencies: once: 1.3.3 - dev: true + dev: false /end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} @@ -10510,74 +10469,45 @@ packages: resolution: {integrity: sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==} dev: true - /esbuild-register@3.4.2(esbuild@0.17.15): + /esbuild-register@3.4.2(esbuild@0.17.18): resolution: {integrity: sha512-kG/XyTDyz6+YDuyfB9ZoSIOOmgyFCH+xPRtsCa8W85HLRV5Csp+o3jWVbOSHgSLfyLc5DmP+KFDNwty4mEjC+Q==} peerDependencies: esbuild: '>=0.12 <1' dependencies: debug: 4.3.4(supports-color@8.1.1) - esbuild: 0.17.15 + esbuild: 0.17.18 transitivePeerDependencies: - supports-color dev: true - /esbuild@0.17.14: - resolution: {integrity: sha512-vOO5XhmVj/1XQR9NQ1UPq6qvMYL7QFJU57J5fKBKBKxp17uDt5PgxFDb4A2nEiXhr1qQs4x0F5+66hVVw4ruNw==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/android-arm': 0.17.14 - '@esbuild/android-arm64': 0.17.14 - '@esbuild/android-x64': 0.17.14 - '@esbuild/darwin-arm64': 0.17.14 - '@esbuild/darwin-x64': 0.17.14 - '@esbuild/freebsd-arm64': 0.17.14 - '@esbuild/freebsd-x64': 0.17.14 - '@esbuild/linux-arm': 0.17.14 - '@esbuild/linux-arm64': 0.17.14 - '@esbuild/linux-ia32': 0.17.14 - '@esbuild/linux-loong64': 0.17.14 - '@esbuild/linux-mips64el': 0.17.14 - '@esbuild/linux-ppc64': 0.17.14 - '@esbuild/linux-riscv64': 0.17.14 - '@esbuild/linux-s390x': 0.17.14 - '@esbuild/linux-x64': 0.17.14 - '@esbuild/netbsd-x64': 0.17.14 - '@esbuild/openbsd-x64': 0.17.14 - '@esbuild/sunos-x64': 0.17.14 - '@esbuild/win32-arm64': 0.17.14 - '@esbuild/win32-ia32': 0.17.14 - '@esbuild/win32-x64': 0.17.14 - - /esbuild@0.17.15: - resolution: {integrity: sha512-LBUV2VsUIc/iD9ME75qhT4aJj0r75abCVS0jakhFzOtR7TQsqQA5w0tZ+KTKnwl3kXE0MhskNdHDh/I5aCR1Zw==} + /esbuild@0.17.18: + resolution: {integrity: sha512-z1lix43jBs6UKjcZVKOw2xx69ffE2aG0PygLL5qJ9OS/gy0Ewd1gW/PUQIOIQGXBHWNywSc0floSKoMFF8aK2w==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.17.15 - '@esbuild/android-arm64': 0.17.15 - '@esbuild/android-x64': 0.17.15 - '@esbuild/darwin-arm64': 0.17.15 - '@esbuild/darwin-x64': 0.17.15 - '@esbuild/freebsd-arm64': 0.17.15 - '@esbuild/freebsd-x64': 0.17.15 - '@esbuild/linux-arm': 0.17.15 - '@esbuild/linux-arm64': 0.17.15 - '@esbuild/linux-ia32': 0.17.15 - '@esbuild/linux-loong64': 0.17.15 - '@esbuild/linux-mips64el': 0.17.15 - '@esbuild/linux-ppc64': 0.17.15 - '@esbuild/linux-riscv64': 0.17.15 - '@esbuild/linux-s390x': 0.17.15 - '@esbuild/linux-x64': 0.17.15 - '@esbuild/netbsd-x64': 0.17.15 - '@esbuild/openbsd-x64': 0.17.15 - '@esbuild/sunos-x64': 0.17.15 - '@esbuild/win32-arm64': 0.17.15 - '@esbuild/win32-ia32': 0.17.15 - '@esbuild/win32-x64': 0.17.15 + '@esbuild/android-arm': 0.17.18 + '@esbuild/android-arm64': 0.17.18 + '@esbuild/android-x64': 0.17.18 + '@esbuild/darwin-arm64': 0.17.18 + '@esbuild/darwin-x64': 0.17.18 + '@esbuild/freebsd-arm64': 0.17.18 + '@esbuild/freebsd-x64': 0.17.18 + '@esbuild/linux-arm': 0.17.18 + '@esbuild/linux-arm64': 0.17.18 + '@esbuild/linux-ia32': 0.17.18 + '@esbuild/linux-loong64': 0.17.18 + '@esbuild/linux-mips64el': 0.17.18 + '@esbuild/linux-ppc64': 0.17.18 + '@esbuild/linux-riscv64': 0.17.18 + '@esbuild/linux-s390x': 0.17.18 + '@esbuild/linux-x64': 0.17.18 + '@esbuild/netbsd-x64': 0.17.18 + '@esbuild/openbsd-x64': 0.17.18 + '@esbuild/sunos-x64': 0.17.18 + '@esbuild/win32-arm64': 0.17.18 + '@esbuild/win32-ia32': 0.17.18 + '@esbuild/win32-x64': 0.17.18 /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -10643,7 +10573,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.7.4(@typescript-eslint/parser@5.57.1)(eslint-import-resolver-node@0.3.7)(eslint@8.37.0): + /eslint-module-utils@2.7.4(@typescript-eslint/parser@5.59.2)(eslint-import-resolver-node@0.3.7)(eslint@8.39.0): resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} engines: {node: '>=4'} peerDependencies: @@ -10664,15 +10594,15 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.57.1(eslint@8.37.0)(typescript@5.0.3) + '@typescript-eslint/parser': 5.59.2(eslint@8.39.0)(typescript@5.0.4) debug: 3.2.7(supports-color@8.1.1) - eslint: 8.37.0 + eslint: 8.39.0 eslint-import-resolver-node: 0.3.7 transitivePeerDependencies: - supports-color dev: true - /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.57.1)(eslint@8.37.0): + /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.59.2)(eslint@8.39.0): resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} engines: {node: '>=4'} peerDependencies: @@ -10682,15 +10612,15 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.57.1(eslint@8.37.0)(typescript@5.0.3) + '@typescript-eslint/parser': 5.59.2(eslint@8.39.0)(typescript@5.0.4) array-includes: 3.1.6 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 - eslint: 8.37.0 + eslint: 8.39.0 eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.57.1)(eslint-import-resolver-node@0.3.7)(eslint@8.37.0) + eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.59.2)(eslint-import-resolver-node@0.3.7)(eslint@8.39.0) has: 1.0.3 is-core-module: 2.11.0 is-glob: 4.0.3 @@ -10705,19 +10635,19 @@ packages: - supports-color dev: true - /eslint-plugin-vue@9.10.0(eslint@8.37.0): - resolution: {integrity: sha512-2MgP31OBf8YilUvtakdVMc8xVbcMp7z7/iQj8LHVpXrSXHPXSJRUIGSPFI6b6pyCx/buKaFJ45ycqfHvQRiW2g==} + /eslint-plugin-vue@9.11.0(eslint@8.39.0): + resolution: {integrity: sha512-bBCJAZnkBV7ATH4Z1E7CvN3nmtS4H7QUU3UBxPdo8WohRU+yHjnQRALpTbxMVcz0e4Mx3IyxIdP5HYODMxK9cQ==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.37.0) - eslint: 8.37.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.39.0) + eslint: 8.39.0 natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.0.11 - semver: 7.3.8 - vue-eslint-parser: 9.1.1(eslint@8.37.0) + semver: 7.5.0 + vue-eslint-parser: 9.1.1(eslint@8.39.0) xml-name-validator: 4.0.0 transitivePeerDependencies: - supports-color @@ -10743,20 +10673,28 @@ packages: estraverse: 5.3.0 dev: true + /eslint-scope@7.2.0: + resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + /eslint-visitor-keys@3.4.0: resolution: {integrity: sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.37.0: - resolution: {integrity: sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==} + /eslint@8.39.0: + resolution: {integrity: sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.37.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.39.0) '@eslint-community/regexpp': 4.5.0 '@eslint/eslintrc': 2.0.2 - '@eslint/js': 8.37.0 + '@eslint/js': 8.39.0 '@humanwhocodes/config-array': 0.11.8 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -10766,7 +10704,7 @@ packages: debug: 4.3.4(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.1.1 + eslint-scope: 7.2.0 eslint-visitor-keys: 3.4.0 espree: 9.5.1 esquery: 1.4.2 @@ -10845,7 +10783,7 @@ packages: engines: {node: '>=8.3.0'} dependencies: '@babel/traverse': 7.21.3 - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 c8: 7.13.0 transitivePeerDependencies: - supports-color @@ -10867,7 +10805,7 @@ packages: resolution: {integrity: sha512-KB44sV4Mv7uLIkJHJ5qhiZe5um6th2g57nHQL/uqnPHKP2IswoTRWUteEXTJQL4gW++1zqWUni+H2hGkP51c9w==} dependencies: tslib: 2.5.0 - dev: true + dev: false /event-stream@3.3.4: resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} @@ -10894,8 +10832,8 @@ packages: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} dev: false - /eventemitter3@5.0.0: - resolution: {integrity: sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg==} + /eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} dev: false /events@3.3.0: @@ -10987,7 +10925,6 @@ packages: /expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} - dev: false /expand-tilde@2.0.2: resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} @@ -11158,6 +11095,10 @@ packages: /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + /fast-diff@1.2.0: + resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} + dev: true + /fast-glob@3.2.12: resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} engines: {node: '>=8.6.0'} @@ -11171,8 +11112,8 @@ packages: /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - /fast-json-stringify@5.5.0: - resolution: {integrity: sha512-rmw2Z8/mLkND8zI+3KTYIkNPEoF5v6GqDP/o+g7H3vjdWjBwuKpgAYFHIzL6ORRB+iqDjjtJnLIW9Mzxn5szOA==} + /fast-json-stringify@5.7.0: + resolution: {integrity: sha512-sBVPTgnAZseLu1Qgj6lUbQ0HfjFhZWXAmpZ5AaSGkyLh5gAXBga/uPJjQPHpDFjC9adWIpdOcCLSDTgrZ7snoQ==} dependencies: '@fastify/deepmerge': 1.3.0 ajv: 8.12.0 @@ -11225,15 +11166,16 @@ packages: resolution: {integrity: sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==} dev: false - /fastify@4.15.0: - resolution: {integrity: sha512-m/CaRN8nf5uyYdrDe2qqq+0z3oGyE+A++qlKQoLJTI4WI0nWK9D6R3FxXQ3MVwt/md977GMR4F43pE9oqrS2zw==} + /fastify@4.17.0: + resolution: {integrity: sha512-tzuY1tgWJo2Y6qEKwmLhFvACUmr68Io2pqP/sDKU71KRM6A6R3DrCDqLGqANbeLZcKUfdfY58ut35CGqemcTgg==} dependencies: '@fastify/ajv-compiler': 3.5.0 '@fastify/error': 3.2.0 - '@fastify/fast-json-stringify-compiler': 4.2.0 + '@fastify/fast-json-stringify-compiler': 4.3.0 abstract-logging: 2.0.1 avvio: 8.2.0 fast-content-type-parse: 1.0.0 + fast-json-stringify: 5.7.0 find-my-way: 7.6.0 light-my-request: 5.8.0 pino: 8.8.0 @@ -11241,8 +11183,8 @@ packages: proxy-addr: 2.0.7 rfdc: 1.3.0 secure-json-parse: 2.7.0 - semver: 7.3.8 - tiny-lru: 10.0.1 + semver: 7.5.0 + tiny-lru: 11.0.1 transitivePeerDependencies: - supports-color dev: false @@ -11318,8 +11260,8 @@ packages: token-types: 5.0.1 dev: false - /file-type@18.2.1: - resolution: {integrity: sha512-Yw5MtnMv7vgD2/6Bjmmuegc8bQEVA9GmAyaR18bMYWKqsWDG9wgYZ1j4I6gNMF5Y5JBDcUcjRQqNQx7Y8uotcg==} + /file-type@18.3.0: + resolution: {integrity: sha512-pkPZ5OGIq0TYb37b8bHDLNeQSe1H2KlaQ2ySGpJkkr2KZdaWsO4QhPzHA0mQcsUW2cSqJk+4gM/UyLz/UFbXdQ==} engines: {node: '>=14.16'} dependencies: readable-web-to-node-stream: 3.0.2 @@ -11860,7 +11802,6 @@ packages: /github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - dev: false /github-slugger@1.5.0: resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==} @@ -12044,6 +11985,7 @@ packages: lowercase-keys: 2.0.0 p-cancelable: 2.1.1 responselike: 2.0.1 + dev: false /got@12.6.0: resolution: {integrity: sha512-WTcaQ963xV97MN3x0/CbAriXFZcXCfgxVp91I+Ze6pawQOa7SgzwSx2zIJJsX+kTajMnVs0xcFD1TxZKFqhdnQ==} @@ -12070,7 +12012,6 @@ packages: /grapheme-splitter@1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} - dev: true /graphql@16.6.0: resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==} @@ -12184,7 +12125,7 @@ packages: engines: {node: '>=0.4.7'} hasBin: true dependencies: - minimist: 1.2.7 + minimist: 1.2.8 neo-async: 2.6.2 source-map: 0.6.1 wordwrap: 1.0.0 @@ -12192,18 +12133,15 @@ packages: uglify-js: 3.17.4 dev: true - /happy-dom@8.9.0: - resolution: {integrity: sha512-JZwJuGdR7ko8L61136YzmrLv7LgTh5b8XaEM3P709mLjyQuXJ3zHTDXvUtBBahRjGlcYW0zGjIiEWizoTUGKfA==} + /happy-dom@9.10.2: + resolution: {integrity: sha512-PZ+UzoU7S8/has8Zw7tr8WGPAAr1QVrAc9o2Llhtuf6aewnR4DDmhPU1Z8KzWwSjXOE+d+b38qNfwHU35PSMIw==} dependencies: css.escape: 1.5.1 he: 1.2.0 iconv-lite: 0.6.3 - node-fetch: 2.6.7 webidl-conversions: 7.0.0 whatwg-encoding: 2.0.0 whatwg-mimetype: 3.0.0 - transitivePeerDependencies: - - encoding /har-schema@2.0.0: resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} @@ -12438,6 +12376,7 @@ packages: dependencies: quick-lru: 5.1.1 resolve-alpn: 1.2.1 + dev: false /http2-wrapper@2.2.0: resolution: {integrity: sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==} @@ -12600,7 +12539,7 @@ packages: mute-stream: 0.0.8 ora: 5.4.1 run-async: 2.4.1 - rxjs: 7.8.0 + rxjs: 7.8.1 string-width: 4.2.3 strip-ansi: 6.0.1 through: 2.3.8 @@ -12633,27 +12572,8 @@ packages: engines: {node: '>=0.10.0'} dev: false - /ioredis@4.28.5: - resolution: {integrity: sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==} - engines: {node: '>=6'} - dependencies: - cluster-key-slot: 1.1.2 - debug: 4.3.4(supports-color@8.1.1) - denque: 1.5.1 - lodash.defaults: 4.2.0 - lodash.flatten: 4.4.0 - lodash.isarguments: 3.1.0 - p-map: 2.1.0 - redis-commands: 1.7.0 - redis-errors: 1.2.0 - redis-parser: 3.0.0 - standard-as-callback: 2.1.0 - transitivePeerDependencies: - - supports-color - dev: false - - /ioredis@5.2.4: - resolution: {integrity: sha512-qIpuAEt32lZJQ0XyrloCRdlEdUUNGG9i0UOk6zgzK6igyudNWqEBxfH6OlbnOOoBBvr1WB02mm8fR55CnikRng==} + /ioredis@5.3.2: + resolution: {integrity: sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==} engines: {node: '>=12.22.0'} dependencies: '@ioredis/commands': 1.2.0 @@ -12775,7 +12695,6 @@ packages: /is-arrayish@0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - dev: false /is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} @@ -12803,7 +12722,6 @@ packages: /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - dev: true /is-ci@3.0.1: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} @@ -12906,11 +12824,6 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - /is-fullwidth-code-point@4.0.0: - resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} - engines: {node: '>=12'} - dev: true - /is-generator-fn@2.1.0: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} @@ -13182,7 +13095,7 @@ packages: /is@3.3.0: resolution: {integrity: sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==} - dev: true + dev: false /isarray@0.0.1: resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} @@ -13229,7 +13142,7 @@ packages: engines: {node: '>=8'} dependencies: '@babel/core': 7.21.3 - '@babel/parser': 7.21.3 + '@babel/parser': 7.21.4 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 6.3.0 @@ -13304,7 +13217,7 @@ packages: '@jest/expect': 29.5.0 '@jest/test-result': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.16.3 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 @@ -13324,7 +13237,7 @@ packages: - supports-color dev: true - /jest-cli@29.5.0(@types/node@18.15.11): + /jest-cli@29.5.0(@types/node@18.16.3): resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -13341,7 +13254,7 @@ packages: exit: 0.1.2 graceful-fs: 4.2.11 import-local: 3.1.0 - jest-config: 29.5.0(@types/node@18.15.11) + jest-config: 29.5.0(@types/node@18.16.3) jest-util: 29.5.0 jest-validate: 29.5.0 prompts: 2.4.2 @@ -13352,7 +13265,7 @@ packages: - ts-node dev: true - /jest-config@29.5.0(@types/node@18.15.11): + /jest-config@29.5.0(@types/node@18.16.3): resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -13367,7 +13280,7 @@ packages: '@babel/core': 7.21.3 '@jest/test-sequencer': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.16.3 babel-jest: 29.5.0(@babel/core@7.21.3) chalk: 4.1.2 ci-info: 3.7.1 @@ -13436,7 +13349,7 @@ packages: '@jest/environment': 29.5.0 '@jest/fake-timers': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.16.3 jest-mock: 29.5.0 jest-util: 29.5.0 dev: true @@ -13466,7 +13379,7 @@ packages: dependencies: '@jest/types': 29.5.0 '@types/graceful-fs': 4.1.6 - '@types/node': 18.15.11 + '@types/node': 18.16.3 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -13517,7 +13430,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 18.15.11 + '@types/node': 18.16.3 dev: true /jest-mock@29.5.0: @@ -13525,7 +13438,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.16.3 jest-util: 29.5.0 dev: true @@ -13580,7 +13493,7 @@ packages: '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.16.3 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -13611,7 +13524,7 @@ packages: '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.16.3 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 @@ -13656,7 +13569,7 @@ packages: jest-util: 29.5.0 natural-compare: 1.4.0 pretty-format: 29.5.0 - semver: 7.3.8 + semver: 7.5.0 transitivePeerDependencies: - supports-color dev: true @@ -13666,7 +13579,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.16.3 chalk: 4.1.2 ci-info: 3.7.1 graceful-fs: 4.2.11 @@ -13691,7 +13604,7 @@ packages: dependencies: '@jest/test-result': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.15.11 + '@types/node': 18.16.3 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -13710,13 +13623,13 @@ packages: resolution: {integrity: sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 18.15.11 + '@types/node': 18.16.3 jest-util: 29.5.0 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true - /jest@29.5.0(@types/node@18.15.11): + /jest@29.5.0(@types/node@18.16.3): resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -13729,7 +13642,7 @@ packages: '@jest/core': 29.5.0 '@jest/types': 29.5.0 import-local: 3.1.0 - jest-cli: 29.5.0(@types/node@18.15.11) + jest-cli: 29.5.0(@types/node@18.16.3) transitivePeerDependencies: - '@types/node' - supports-color @@ -13782,6 +13695,11 @@ packages: resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==} dev: true + /js-string-escape@1.0.1: + resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} + engines: {node: '>= 0.8'} + dev: true + /js-stringify@1.0.2: resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==} @@ -13795,7 +13713,6 @@ packages: dependencies: argparse: 1.0.10 esprima: 4.0.1 - dev: true /js-yaml@3.7.0: resolution: {integrity: sha512-eIlkGty7HGmntbV6P/ZlAsoncFLGsNoM27lkTzS+oneY/EiNhj+geqD9ezg/ip+SW6Var0BJU2JtV0vEUZpWVQ==} @@ -13821,36 +13738,6 @@ packages: resolution: {integrity: sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ==} engines: {node: '>=0.1.90'} - /jscodeshift@0.14.0(@babel/preset-env@7.20.2): - resolution: {integrity: sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==} - hasBin: true - peerDependencies: - '@babel/preset-env': ^7.1.6 - dependencies: - '@babel/core': 7.21.3 - '@babel/parser': 7.21.3 - '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.21.3) - '@babel/plugin-transform-modules-commonjs': 7.21.2(@babel/core@7.21.3) - '@babel/preset-env': 7.20.2(@babel/core@7.21.3) - '@babel/preset-flow': 7.18.6(@babel/core@7.21.3) - '@babel/preset-typescript': 7.21.0(@babel/core@7.21.3) - '@babel/register': 7.21.0(@babel/core@7.21.3) - babel-core: 7.0.0-bridge.0(@babel/core@7.21.3) - chalk: 4.1.2 - flow-parser: 0.202.0 - graceful-fs: 4.2.11 - micromatch: 4.0.5 - neo-async: 2.6.2 - node-dir: 0.1.17 - recast: 0.21.5 - temp: 0.8.4 - write-file-atomic: 2.4.3 - transitivePeerDependencies: - - supports-color - dev: true - /jscodeshift@0.14.0(@babel/preset-env@7.21.4): resolution: {integrity: sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==} hasBin: true @@ -13858,7 +13745,7 @@ packages: '@babel/preset-env': ^7.1.6 dependencies: '@babel/core': 7.21.3 - '@babel/parser': 7.21.3 + '@babel/parser': 7.21.4 '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.21.3) '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.21.3) '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.21.3) @@ -13945,7 +13832,7 @@ packages: object.assign: 4.1.4 promiseback: 2.0.3 safer-buffer: 2.1.2 - dev: true + dev: false /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} @@ -13971,7 +13858,7 @@ packages: resolution: {integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==} hasBin: true dependencies: - minimist: 1.2.7 + minimist: 1.2.8 dev: true /json5@2.2.3: @@ -14035,8 +13922,8 @@ packages: verror: 1.10.0 dev: true - /jsrsasign@10.7.0: - resolution: {integrity: sha512-D5V2gGpYGtwbAtQHoglTVrpYf7QJuNoPEhaLOsTFONS2jXUl3qyR1hnYrNpASAybqQeiDYA3zGthR0ubgPRoQA==} + /jsrsasign@10.8.6: + resolution: {integrity: sha512-bQmbVtsfbgaKBTWCKiDCPlUPbdlRIK/FzSwT3BzIgZl/cU6TqXu6pZJsCI/dJVrZ9Gir5GC4woqw9shH/v7MBw==} dev: false /jssha@3.3.0: @@ -14232,7 +14119,7 @@ packages: log-update: 4.0.0 p-map: 4.0.0 rfdc: 1.3.0 - rxjs: 7.8.0 + rxjs: 7.8.1 through: 2.3.8 wrap-ansi: 7.0.0 dev: true @@ -14255,7 +14142,6 @@ packages: /local-pkg@0.4.3: resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} engines: {node: '>=14'} - dev: true /locate-path@3.0.0: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} @@ -14280,15 +14166,15 @@ packages: /lodash.clone@4.5.0: resolution: {integrity: sha512-GhrVeweiTD6uTmmn5hV/lzgCQhccwReIVRLHp7LT4SopOjqEZ5BbX8b5WWEtAKasjmy8hR7ZPwsYlxRCku5odg==} - dev: true + dev: false /lodash.clonedeep@4.5.0: resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} - dev: true + dev: false /lodash.constant@3.0.0: resolution: {integrity: sha512-X5XMrB+SdI1mFa81162NSTo/YNd23SLdLOLzcXTwS4inDZ5YCL8X67UFzZJAH4CqIa6R8cr56CShfA5K5MFiYQ==} - dev: true + dev: false /lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} @@ -14303,11 +14189,11 @@ packages: /lodash.filter@4.6.0: resolution: {integrity: sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==} - dev: true + dev: false /lodash.flatmap@4.5.0: resolution: {integrity: sha512-/OcpcAGWlrZyoHGeHh3cAoa6nGdX6QYtmzNP84Jqol6UEQQ2gIaU3H+0eICcjcKGl0/XF8LWOujNn9lffsnaOg==} - dev: true + dev: false /lodash.flatten@4.4.0: resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} @@ -14315,7 +14201,7 @@ packages: /lodash.foreach@4.5.0: resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==} - dev: true + dev: false /lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} @@ -14323,22 +14209,21 @@ packages: /lodash.has@4.5.2: resolution: {integrity: sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g==} - dev: true + dev: false /lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} /lodash.isempty@4.4.0: resolution: {integrity: sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==} - dev: true + dev: false /lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} - dev: true /lodash.isfunction@3.0.9: resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} - dev: true + dev: false /lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} @@ -14346,15 +14231,15 @@ packages: /lodash.isundefined@3.0.1: resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} - dev: true + dev: false /lodash.keys@4.2.0: resolution: {integrity: sha512-J79MkJcp7Df5mizHiVNpjoHXLi4HLjh9VLS/M7lQSGoQ+0oQ+lWEigREkqKyizPB1IawvQLLKY8mzEcm1tkyxQ==} - dev: true + dev: false /lodash.map@4.6.0: resolution: {integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==} - dev: true + dev: false /lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} @@ -14369,11 +14254,11 @@ packages: /lodash.reduce@4.6.0: resolution: {integrity: sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==} - dev: true + dev: false /lodash.size@4.2.0: resolution: {integrity: sha512-wbu3SF1XC5ijqm0piNxw59yCbuUf2kaShumYBLWUrcCvwh6C8odz6SY/wGVzCWTQTFL/1Ygbvqg2eLtspUVVAQ==} - dev: true + dev: false /lodash.throttle@4.1.1: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} @@ -14381,14 +14266,15 @@ packages: /lodash.topairs@4.3.0: resolution: {integrity: sha512-qrRMbykBSEGdOgQLJJqVSdPWMD7Q+GJJ5jMRfQYb+LTLsw3tYVIabnCzRqTJb2WTo17PG5gNzXuFaZgYH/9SAQ==} - dev: true + dev: false /lodash.transform@4.6.0: resolution: {integrity: sha512-LO37ZnhmBVx0GvOU/caQuipEh4GN82TcWv3yHlebGDgOxbxiwwzW5Pcx2AcvpIv2WmvmSMoC492yQFNhy/l/UQ==} - dev: true + dev: false /lodash.union@4.6.0: resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + dev: false /lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} @@ -14396,7 +14282,7 @@ packages: /lodash.values@4.3.0: resolution: {integrity: sha512-r0RwvdCv8id9TUblb/O7rYPwVy6lerCbcawrfdo9iC/1t1wsNMJknO79WNBgwkH0hIeJ08jmvvESbFpNb4jH0Q==} - dev: true + dev: false /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -14439,6 +14325,7 @@ packages: /lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} + dev: false /lowercase-keys@3.0.0: resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} @@ -14488,6 +14375,13 @@ packages: hasBin: true dev: true + /magic-string-ast@0.1.2: + resolution: {integrity: sha512-P53AZrzq7hclCU6HWj88xNZHmP15DKjMmK/vBytO1qnpYP3ul4IEZlyCE0aU3JRnmgWmZPmoTKj4Bls7v0pMyA==} + engines: {node: '>=14.19.0'} + dependencies: + magic-string: 0.30.0 + dev: false + /magic-string@0.25.9: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} dependencies: @@ -14498,7 +14392,12 @@ packages: engines: {node: '>=12'} dependencies: '@jridgewell/sourcemap-codec': 1.4.14 - dev: true + + /magic-string@0.30.0: + resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.14 /mailcheck@1.1.1: resolution: {integrity: sha512-3WjL8+ZDouZwKlyJBMp/4LeziLFXgleOdsYu87piGcMLqhBzCsy2QFdbtAwv757TFC/rtqd738fgJw1tFQCSgA==} @@ -14613,6 +14512,13 @@ packages: resolution: {integrity: sha512-v2huwvQGOHTGOkMqtHd2hercCG3f6QAObTisPPHg8TZqq2lz7eIY/5i/5YUV8Ibf3mEioFEmwibcPUF2/fnKKQ==} dev: false + /md5-hex@3.0.1: + resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} + engines: {node: '>=8'} + dependencies: + blueimp-md5: 2.19.0 + dev: true + /mdast-util-definitions@4.0.0: resolution: {integrity: sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==} dependencies: @@ -14628,6 +14534,14 @@ packages: engines: {node: '>= 0.6'} dev: true + /meilisearch@0.32.3: + resolution: {integrity: sha512-EOgfBuRE5SiIPIpEDYe2HO0D7a4z5bexIgaAdJFma/dH5hx1kwO+u/qb2g3qKyjG+iA3l8MlmTj/Xd72uahaAw==} + dependencies: + cross-fetch: 3.1.5 + transitivePeerDependencies: + - encoding + dev: false + /memoizerific@1.11.3: resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} dependencies: @@ -14741,6 +14655,7 @@ packages: /mimic-response@1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} + dev: false /mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} @@ -14770,9 +14685,9 @@ packages: dependencies: brace-expansion: 2.0.1 - /minimatch@6.2.0: - resolution: {integrity: sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==} - engines: {node: '>=10'} + /minimatch@9.0.0: + resolution: {integrity: sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==} + engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 dev: true @@ -14788,6 +14703,10 @@ packages: /minimist@1.2.7: resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} + dev: false + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} /minipass-collect@1.0.2: resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} @@ -14874,7 +14793,7 @@ packages: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true dependencies: - minimist: 1.2.7 + minimist: 1.2.8 /mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} @@ -14887,8 +14806,8 @@ packages: hasBin: true dev: false - /mlly@1.1.1: - resolution: {integrity: sha512-Jnlh4W/aI4GySPo6+DyTN17Q75KKbLTyFK8BrGhjNP4rxuUjbRWhE6gHg3bs33URWAF44FRm7gdQA348i3XxRw==} + /mlly@1.2.0: + resolution: {integrity: sha512-+c7A3CV0KGdKcylsI6khWyts/CYrGTrRVo4R/I7u/cUsy0Conxa6LUhiEzVKIw14lc2L5aiO4+SeVe4TeGRKww==} dependencies: acorn: 8.8.2 pathe: 1.1.0 @@ -14955,10 +14874,10 @@ packages: msw: '>=0.35.0 <2.0.0' dependencies: is-node-process: 1.0.1 - msw: 1.2.1(typescript@5.0.3) + msw: 1.2.1(typescript@5.0.4) dev: true - /msw@1.2.1(typescript@5.0.3): + /msw@1.2.1(typescript@5.0.4): resolution: {integrity: sha512-bF7qWJQSmKn6bwGYVPXOxhexTCGD5oJSZg8yt8IBClxvo3Dx/1W0zqE1nX9BSWmzRsCKWfeGWcB/vpqV6aclpw==} engines: {node: '>=14'} hasBin: true @@ -14987,7 +14906,7 @@ packages: path-to-regexp: 6.2.1 strict-event-emitter: 0.4.6 type-fest: 2.19.0 - typescript: 5.0.3 + typescript: 5.0.4 yargs: 17.6.2 transitivePeerDependencies: - encoding @@ -15033,6 +14952,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + /nanoid@3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + /nanomatch@1.2.13: resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} engines: {node: '>=0.10.0'} @@ -15053,7 +14977,6 @@ packages: /napi-build-utils@1.0.2: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} - dev: false /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} @@ -15135,16 +15058,14 @@ packages: resolution: {integrity: sha512-eSKV6s+APenqVh8ubJyiu/YhZgxQpGP66ntzUb3lY1xB9ukSRaGnx0AIxI+IM+1+IVYC1oWobgG5L3Lt9ARykQ==} engines: {node: '>=10'} dependencies: - semver: 7.3.8 - dev: false + semver: 7.5.0 /node-addon-api@5.0.0: resolution: {integrity: sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==} dev: false - /node-addon-api@6.0.0: - resolution: {integrity: sha512-GyHvgPvUXBvAkXa0YvYnhilSB1A+FRYMpIVggKzPZqdaZfevZOuzfWzyvgzOwRLHBeo/MMswmJFsrNF4Nw1pmA==} - dev: false + /node-addon-api@6.1.0: + resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} /node-bitmap@0.0.1: resolution: {integrity: sha512-Jx5lPaaLdIaOsj2mVLWMWulXF6GQVdyLvNSxmiYCvZ8Ma2hfKX0POoR2kgKOqz+oFsRreq0yYZjQ2wjE9VNzCA==} @@ -15204,7 +15125,7 @@ packages: nopt: 6.0.0 npmlog: 6.0.2 rimraf: 3.0.2 - semver: 7.3.8 + semver: 7.5.0 tar: 6.1.13 which: 2.0.2 transitivePeerDependencies: @@ -15226,7 +15147,7 @@ packages: dependencies: has: 1.0.3 is: 3.3.0 - dev: true + dev: false /nodemailer@6.9.1: resolution: {integrity: sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==} @@ -15267,7 +15188,7 @@ packages: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.11.0 - semver: 7.3.8 + semver: 7.5.0 validate-npm-package-license: 3.0.4 dev: true @@ -15300,6 +15221,7 @@ packages: /normalize-url@6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} + dev: false /normalize-url@8.0.0: resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==} @@ -15350,13 +15272,13 @@ packages: set-blocking: 2.0.0 dev: false - /nsfwjs@2.4.2(@tensorflow/tfjs@4.2.0): + /nsfwjs@2.4.2(@tensorflow/tfjs@4.4.0): resolution: {integrity: sha512-i4Pp2yt59qPQgeZFyg3wXFBX52uSeu/hkDoqdZfe+sILRxNBUu0VDogj7Lmqak0GlrXviS/wLiVeIx40IDUu7A==} peerDependencies: '@tensorflow/tfjs': ^3.18.0 dependencies: '@nsfw-filter/gif-frames': 1.0.2 - '@tensorflow/tfjs': 4.2.0(seedrandom@3.0.5) + '@tensorflow/tfjs': 4.4.0(seedrandom@3.0.5) dev: false /nth-check@2.1.1: @@ -15404,7 +15326,7 @@ packages: /object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - dev: true + dev: false /object-inspect@1.12.2: resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} @@ -15510,7 +15432,7 @@ packages: resolution: {integrity: sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==} dependencies: wrappy: 1.0.2 - dev: true + dev: false /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -15635,6 +15557,7 @@ packages: /p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} + dev: false /p-cancelable@3.0.0: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} @@ -15684,11 +15607,6 @@ packages: p-limit: 3.1.0 dev: true - /p-map@2.1.0: - resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} - engines: {node: '>=6'} - dev: false - /p-map@4.0.0: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} @@ -15716,7 +15634,7 @@ packages: /packageurl-js@1.0.1: resolution: {integrity: sha512-EtXC0kgLjy/C7S4SN3Kk1SDRWLzIn/LUK0gXlz3gsxDdpI0k7q8C3SASpV0pc+v0yADBTt5rWewq/flGdGxtoQ==} - dev: true + dev: false /packet-reader@1.0.0: resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==} @@ -16091,7 +16009,7 @@ packages: resolution: {integrity: sha512-hM58GKXOcj8WTqUXnsQyJYXdeAPbythQgEF3nTcEo+nkD49chjQ9IKm/QJy9xf6JakXptz86h7ecP2024rrLaQ==} dependencies: jsonc-parser: 3.2.0 - mlly: 1.1.1 + mlly: 1.2.0 pathe: 1.1.0 dev: true @@ -16121,7 +16039,7 @@ packages: /pluralize@7.0.0: resolution: {integrity: sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==} engines: {node: '>=4'} - dev: true + dev: false /pngjs-nozlib@1.0.0: resolution: {integrity: sha512-N1PggqLp9xDqwAoKvGohmZ3m4/N9xpY0nDZivFqQLcpLHmliHnCp9BuNCsOeqHWMuEEgFjpEaq9dZq6RZyy0fA==} @@ -16376,6 +16294,14 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /postcss@8.4.23: + resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.6 + picocolors: 1.0.0 + source-map-js: 1.0.2 + /postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -16402,7 +16328,7 @@ packages: detect-libc: 2.0.1 expand-template: 2.0.3 github-from-package: 0.0.0 - minimist: 1.2.7 + minimist: 1.2.8 mkdirp-classic: 0.5.3 napi-build-utils: 1.0.2 node-abi: 3.31.0 @@ -16411,7 +16337,6 @@ packages: simple-get: 4.0.1 tar-fs: 2.1.1 tunnel-agent: 0.6.0 - dev: false /prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} @@ -16427,8 +16352,8 @@ packages: engines: {node: '>=0.10.0'} dev: false - /prettier@2.8.7: - resolution: {integrity: sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==} + /prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true dev: true @@ -16436,7 +16361,6 @@ packages: /pretty-bytes@5.6.0: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} engines: {node: '>=6'} - dev: true /pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} @@ -16527,7 +16451,7 @@ packages: engines: {node: '>= 0.4'} dependencies: promise: 7.3.1 - dev: true + dev: false /promise-inflight@1.0.1: resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} @@ -16565,7 +16489,7 @@ packages: dependencies: is-callable: 1.2.7 promise-deferred: 2.0.3 - dev: true + dev: false /prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} @@ -16758,9 +16682,10 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} dev: false - /qrcode@1.5.1: - resolution: {integrity: sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==} + /qrcode@1.5.3: + resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==} engines: {node: '>=10.13.0'} + hasBin: true dependencies: dijkstrajs: 1.0.2 encode-utf8: 1.0.3 @@ -16875,9 +16800,8 @@ packages: dependencies: deep-extend: 0.6.0 ini: 1.3.8 - minimist: 1.2.7 + minimist: 1.2.8 strip-json-comments: 2.0.1 - dev: false /rdf-canonize@3.3.0: resolution: {integrity: sha512-gfSNkMua/VWC1eYbSkVaL/9LQhFeOh0QULwv7Or0f+po8pMgQ1blYQFe1r9Mv2GJZXw88Cz/drnAnB9UlNnHfQ==} @@ -16908,12 +16832,12 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /react-docgen-typescript@2.2.2(typescript@5.0.3): + /react-docgen-typescript@2.2.2(typescript@5.0.4): resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==} peerDependencies: typescript: '>= 4.3.x' dependencies: - typescript: 5.0.3 + typescript: 5.0.4 dev: true /react-docgen@6.0.0-alpha.3: @@ -17155,10 +17079,6 @@ packages: strip-indent: 3.0.0 dev: true - /redis-commands@1.7.0: - resolution: {integrity: sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==} - dev: false - /redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} engines: {node: '>=4'} @@ -17464,6 +17384,7 @@ packages: resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} dependencies: lowercase-keys: 2.0.0 + dev: false /responselike@3.0.0: resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} @@ -17526,8 +17447,8 @@ packages: seedrandom: 2.4.2 dev: false - /rollup@3.20.2: - resolution: {integrity: sha512-3zwkBQl7Ai7MFYQE0y1MeQ15+9jsi7XxfrqwTb/9EK8D9C9+//EBR4M+CuA1KODRaNbFez/lWxA5vhEGZp4MUg==} + /rollup@3.21.3: + resolution: {integrity: sha512-VnPfEG51nIv2xPLnZaekkuN06q9ZbnyDcLkaBdJa/W7UddyhOfMP2yOPziYQfeY7k++fZM8FdQIummFN5y14kA==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: @@ -17537,11 +17458,11 @@ packages: resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} dev: false - /rss-parser@3.12.0: - resolution: {integrity: sha512-aqD3E8iavcCdkhVxNDIdg1nkBI17jgqF+9OqPS1orwNaOgySdpvq6B+DoONLhzjzwV8mWg37sb60e4bmLK117A==} + /rss-parser@3.13.0: + resolution: {integrity: sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==} dependencies: entities: 2.2.0 - xml2js: 0.4.23 + xml2js: 0.5.0 dev: false /run-async@2.4.1: @@ -17554,8 +17475,8 @@ packages: dependencies: queue-microtask: 1.2.3 - /rxjs@7.8.0: - resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==} + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: tslib: 2.5.0 @@ -17615,9 +17536,9 @@ packages: postcss: 8.4.21 dev: false - /sass@1.60.0: - resolution: {integrity: sha512-updbwW6fNb5gGm8qMXzVO7V4sWf7LMXnMly/JEyfbfERbVH46Fn6q02BX7/eHTdKpE7d+oTkMMQpFWNUMfFbgQ==} - engines: {node: '>=12.0.0'} + /sass@1.62.1: + resolution: {integrity: sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==} + engines: {node: '>=14.0.0'} hasBin: true dependencies: chokidar: 3.5.3 @@ -17691,6 +17612,14 @@ packages: hasBin: true dependencies: lru-cache: 6.0.0 + dev: true + + /semver@7.5.0: + resolution: {integrity: sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 /send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} @@ -17782,26 +17711,25 @@ packages: detect-libc: 2.0.1 node-addon-api: 5.0.0 prebuild-install: 7.1.1 - semver: 7.3.8 + semver: 7.5.0 simple-get: 4.0.1 tar-fs: 2.1.1 tunnel-agent: 0.6.0 dev: false - /sharp@0.32.0: - resolution: {integrity: sha512-yLAypVcqj1toSAqRSwbs86nEzfyZVDYqjuUX8grhFpeij0DDNagKJXELS/auegDBRDg1XBtELdOGfo2X1cCpeA==} + /sharp@0.32.1: + resolution: {integrity: sha512-kQTFtj7ldpUqSe8kDxoGLZc1rnMFU0AO2pqbX6pLy3b7Oj8ivJIdoKNwxHVQG2HN6XpHPJqCSM2nsma2gOXvOg==} engines: {node: '>=14.15.0'} requiresBuild: true dependencies: color: 4.2.3 detect-libc: 2.0.1 - node-addon-api: 6.0.0 + node-addon-api: 6.1.0 prebuild-install: 7.1.1 - semver: 7.3.8 + semver: 7.5.0 simple-get: 4.0.1 tar-fs: 2.1.1 tunnel-agent: 0.6.0 - dev: false /shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} @@ -17856,7 +17784,6 @@ packages: /simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - dev: false /simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} @@ -17864,13 +17791,11 @@ packages: decompress-response: 6.0.0 once: 1.4.0 simple-concat: 1.0.1 - dev: false /simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} dependencies: is-arrayish: 0.3.2 - dev: false /simple-update-notifier@1.1.0: resolution: {integrity: sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==} @@ -17894,6 +17819,121 @@ packages: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} dev: true + /slacc-android-arm-eabi@0.0.7: + resolution: {integrity: sha512-6TikZlR1jsQscxwphhrf0U4xbsRy6zKJ0zmEULopTzbohgo5OLdZ7L3tQazkYlaaFe3YjGnVLW3FfGhhrajVog==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + + /slacc-android-arm64@0.0.7: + resolution: {integrity: sha512-aol/9Rg0Hfqu81hpK+HXcx9sGYu4qqYU+djBCgLtb8I6ZMdWUdE0dp8ACBoTOmYn34hYGcUu4FlJUZ8r7Utucg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /slacc-darwin-arm64@0.0.7: + resolution: {integrity: sha512-PkV7rO/c9AImNYDacP+kxtOjVuxjy06IIOAxbWerIWvoeqsCNRtiF/dh+OqIACRFBuHIDe0oAyUCEMGUTnzjyQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /slacc-darwin-universal@0.0.7: + resolution: {integrity: sha512-Y9zXpL40m4Yq3dE5vdnAgfmn0Fxc0Bf0ixC9TSl96gKeIZEd6drkjfpHFdsIDNImzOksIAUo0HHiDdbEfE7zdQ==} + engines: {node: '>= 10'} + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /slacc-darwin-x64@0.0.7: + resolution: {integrity: sha512-yKaGjX2YJl1QHe4NgqQVsY83jees3hjFxEUPoKpuZEQzWbMNn0XSyceFRGXIk1oDqiKU40UcsdcCedjYjSEd0Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /slacc-linux-arm-gnueabihf@0.0.7: + resolution: {integrity: sha512-pdWMdQeX6uA9JfSoWo9EHH0yRiwXKMbaKoS9gflDSyt/hjeR3Qx/KK7Wihd7HeXx7njlNdpr9ycTRmm5NgapQQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /slacc-linux-arm64-gnu@0.0.7: + resolution: {integrity: sha512-hz9TK/w6fxeNZXyFzuLq5cJD/XRyJbo6BaIdW+VrKKnb9nkLnWlqDQtdtJk7Fw7zHjdY3Uqufjwm0iT6qBVpUQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /slacc-linux-arm64-musl@0.0.7: + resolution: {integrity: sha512-wCDAYL7e+lh3XL7g87Ui/Bb2Ap9GcBqeJuj2yHIx6MYC8ontwFSXhqRTmd2zmPLmZA5Nc11aKGN11YNu0Pnwlw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /slacc-linux-x64-gnu@0.0.7: + resolution: {integrity: sha512-E5+2cveizpfHXCk/Hu5VfslWFeDVw47nywODiJ8CsofT2l5ITfYPMFEBXm9ORY25mGBTgsO6lJYiF9Hz4FlS9Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /slacc-win32-arm64-msvc@0.0.7: + resolution: {integrity: sha512-3a+qnkZbP+Pr5RZuzd0Vi1uCal137QiJajRAWT4r7qwu+Zidd50x2oikQ4rAegqZVTm8qTwVmWA+WmH8WHI7iw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /slacc-win32-x64-msvc@0.0.7: + resolution: {integrity: sha512-ydFdZ7wEXQPsw2Tg+yG9uJdCGTehyPtrWBVUMa7fojr3j1gbtThXS2l9Ad/6fYYi2VwdaYPLWbwV3GYElPGL8g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /slacc@0.0.7: + resolution: {integrity: sha512-rwi2F3oJaGPST9JdCoUd5fnSZaoZFgTL00GFKhKufT48uwtUEAHlOL0t8gEVmon71X+53f9nEdsGWhwtOutJTQ==} + engines: {node: '>= 10'} + optionalDependencies: + slacc-android-arm-eabi: 0.0.7 + slacc-android-arm64: 0.0.7 + slacc-darwin-arm64: 0.0.7 + slacc-darwin-universal: 0.0.7 + slacc-darwin-x64: 0.0.7 + slacc-linux-arm-gnueabihf: 0.0.7 + slacc-linux-arm64-gnu: 0.0.7 + slacc-linux-arm64-musl: 0.0.7 + slacc-linux-x64-gnu: 0.0.7 + slacc-win32-arm64-msvc: 0.0.7 + slacc-win32-x64-msvc: 0.0.7 + dev: false + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -17921,14 +17961,6 @@ packages: is-fullwidth-code-point: 3.0.0 dev: true - /slice-ansi@5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} - dependencies: - ansi-styles: 6.2.1 - is-fullwidth-code-point: 4.0.0 - dev: true - /smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -17969,10 +18001,10 @@ packages: async: 3.2.4 debug: 4.3.4(supports-color@8.1.1) lodash.merge: 4.6.2 - minimist: 1.2.7 + minimist: 1.2.8 transitivePeerDependencies: - supports-color - dev: true + dev: false /snyk-nodejs-lockfile-parser@1.48.2: resolution: {integrity: sha512-CiuKigz4Ed/prR61T2hDDNnvqCr9JA4zTWE9xD4x+emt2zUVRHKF2RBKq2s82RA8jcts1OOXnREr1v1OvNxIpg==} @@ -17990,13 +18022,13 @@ packages: lodash.isempty: 4.4.0 lodash.topairs: 4.3.0 micromatch: 4.0.5 - semver: 7.3.8 + semver: 7.5.0 snyk-config: 5.1.0 tslib: 1.14.1 uuid: 8.3.2 transitivePeerDependencies: - supports-color - dev: true + dev: false /socks-proxy-agent@7.0.0: resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} @@ -18221,11 +18253,11 @@ packages: resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} dev: true - /storybook@7.0.2: - resolution: {integrity: sha512-/XBLhT9Vb14yNBcA9rlW15y+C6IsCA3kx5PKvK9kL10sKCi8invcY94UfCSisXe8HqsO3u6peumo2xpYucKMjw==} + /storybook@7.0.7: + resolution: {integrity: sha512-MaFAhpPm/KsaoIQfGzapnRyXNh1VbS8l38BNZR5ZD97ejGkLukJ7TO4fFS87Hyy6whAXo6tTdtqeCByMQ9gRFA==} hasBin: true dependencies: - '@storybook/cli': 7.0.2 + '@storybook/cli': 7.0.7 transitivePeerDependencies: - bufferutil - encoding @@ -18243,7 +18275,7 @@ packages: /stream-buffers@3.0.2: resolution: {integrity: sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==} engines: {node: '>= 0.10.0'} - dev: true + dev: false /stream-combiner@0.0.4: resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} @@ -18269,7 +18301,7 @@ packages: resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} dependencies: any-promise: 1.3.0 - dev: true + dev: false /stream-to-promise@2.2.0: resolution: {integrity: sha512-HAGUASw8NT0k8JvIVutB2Y/9iBk7gpgEyAudXwNJmZERdMITGdajOa4VJfD/kNiA3TppQpTP4J+CtcHwdzKBAw==} @@ -18277,7 +18309,7 @@ packages: any-promise: 1.3.0 end-of-stream: 1.1.0 stream-to-array: 2.3.0 - dev: true + dev: false /stream-wormhole@1.1.0: resolution: {integrity: sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==} @@ -18347,15 +18379,6 @@ packages: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - /string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.0.1 - dev: true - /string.prototype.trimend@1.0.6: resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} dependencies: @@ -18412,13 +18435,6 @@ packages: dependencies: ansi-regex: 5.0.1 - /strip-ansi@7.0.1: - resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} - engines: {node: '>=12'} - dependencies: - ansi-regex: 6.0.1 - dev: true - /strip-bom@2.0.0: resolution: {integrity: sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==} engines: {node: '>=0.10.0'} @@ -18459,7 +18475,6 @@ packages: /strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} - dev: false /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} @@ -18742,11 +18757,21 @@ packages: engines: {node: '>=0.10.0'} dev: false + /time-zone@1.0.0: + resolution: {integrity: sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==} + engines: {node: '>=4'} + dev: true + /tiny-lru@10.0.1: resolution: {integrity: sha512-Vst+6kEsWvb17Zpz14sRJV/f8bUWKhqm6Dc+v08iShmIJ/WxqWytHzCTd6m88pS33rE2zpX34TRmOpAJPloNCA==} engines: {node: '>=6'} dev: false + /tiny-lru@11.0.1: + resolution: {integrity: sha512-iNgFugVuQgBKrqeO/mpiTTgmBsTP0WL6yeuLfLs/Ctf0pI/ixGqIRm8sDCwMcXGe9WWvt2sGXI5mNqZbValmJg==} + engines: {node: '>=12'} + dev: false + /tinybench@2.4.0: resolution: {integrity: sha512-iyziEiyFxX4kyxSp+MtY1oCH/lvjH3PxFN8PGCDeqcZWAJ/i+9y+nL85w99PxVzrIvew/GSkSbDYtiGVa85Afg==} dev: true @@ -18760,8 +18785,8 @@ packages: engines: {node: '>=14.0.0'} dev: true - /tinyspy@1.1.1: - resolution: {integrity: sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==} + /tinyspy@2.1.0: + resolution: {integrity: sha512-7eORpyqImoOvkQJCSkL0d0mB4NHHIFAy4b1u8PHdDa7SjGS2njzl6/lyGoZLm+eyYEtlUmFGE0rFj66SWxZgQQ==} engines: {node: '>=14.0.0'} dev: true @@ -18885,7 +18910,7 @@ packages: /treeify@1.1.0: resolution: {integrity: sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==} engines: {node: '>=0.6'} - dev: true + dev: false /trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} @@ -18908,8 +18933,8 @@ packages: resolution: {integrity: sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w==} dev: true - /tsc-alias@1.8.5: - resolution: {integrity: sha512-Y3ka0olwSRdbHPyX5kXhYY2aoBKuT53DFdeY+PpQUR4hg5M/b8eIRmC8dL4FBdd0wT366iWc6iDUUGe6QwI7mg==} + /tsc-alias@1.8.6: + resolution: {integrity: sha512-vq+i6VpE83IeMsSJVcFN03ZBofADhr8/gIJXjxpbnTRfN/MFXy0+SBaKG2o7p95QqXBGkeG98HYz3IkOOveFbg==} hasBin: true dependencies: chokidar: 3.5.3 @@ -18925,7 +18950,7 @@ packages: dependencies: '@types/json5': 0.0.29 json5: 1.0.1 - minimist: 1.2.7 + minimist: 1.2.8 strip-bom: 3.0.0 dev: true @@ -18958,14 +18983,14 @@ packages: /tslib@2.5.0: resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} - /tsutils@3.21.0(typescript@5.0.3): + /tsutils@3.21.0(typescript@5.0.4): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.0.3 + typescript: 5.0.4 dev: true /tunnel-agent@0.6.0: @@ -18976,7 +19001,7 @@ packages: /tunnel@0.0.6: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} - dev: true + dev: false /tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} @@ -19062,8 +19087,8 @@ packages: /typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - /typeorm@0.3.13(ioredis@4.28.5)(pg@8.10.0): - resolution: {integrity: sha512-AqyKG4xtYST2zpgX/LdAleP8WAjwyO7EemRtG67EmmbFpWsPBk9BVr87wkN59huBned3qPEehVOSUQ81pNuH8A==} + /typeorm@0.3.15(ioredis@5.3.2)(pg@8.10.0): + resolution: {integrity: sha512-R4JSw8QjDP1W+ypeRz/XrCXIqubrLSnNAzJAp9EQSQIPHTv+YmUHZis8g08lOwFpuhqL9m8jkPSz8GWEKlU/ow==} engines: {node: '>= 12.9.0'} hasBin: true peerDependencies: @@ -19072,7 +19097,7 @@ packages: better-sqlite3: ^7.1.2 || ^8.0.0 hdb-pool: ^0.1.6 ioredis: ^5.0.4 - mongodb: ^3.6.0 + mongodb: ^5.2.0 mssql: ^9.1.1 mysql2: ^2.2.5 || ^3.0.1 oracledb: ^5.1.0 @@ -19128,15 +19153,13 @@ packages: debug: 4.3.4(supports-color@8.1.1) dotenv: 16.0.3 glob: 8.1.0 - ioredis: 4.28.5 - js-yaml: 4.1.0 + ioredis: 5.3.2 mkdirp: 2.1.6 pg: 8.10.0 reflect-metadata: 0.1.13 sha.js: 2.4.11 tslib: 2.5.0 uuid: 9.0.0 - xml2js: 0.4.23 yargs: 17.6.2 transitivePeerDependencies: - supports-color @@ -19148,8 +19171,8 @@ packages: hasBin: true dev: true - /typescript@5.0.3: - resolution: {integrity: sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==} + /typescript@5.0.4: + resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} engines: {node: '>=12.20'} hasBin: true @@ -19348,6 +19371,15 @@ packages: webpack-virtual-modules: 0.4.6 dev: true + /unplugin@1.3.1: + resolution: {integrity: sha512-h4uUTIvFBQRxUKS2Wjys6ivoeofGhxzTe2sRWlooyjHXVttcVfV/JiavNd3d4+jty0SVV0dxGw9AkY9MwiaCEw==} + dependencies: + acorn: 8.8.2 + chokidar: 3.5.3 + webpack-sources: 3.2.3 + webpack-virtual-modules: 0.5.0 + dev: false + /unset-value@1.0.0: resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==} engines: {node: '>=0.10.0'} @@ -19451,10 +19483,6 @@ packages: engines: {node: '>= 0.4.0'} dev: true - /uuid-browser@3.1.0: - resolution: {integrity: sha512-dsNgbLaTrd6l3MMxTtouOCFw4CBFc/3a+GgYA2YyrJvyQ1u6q4pcu3ktLoUZ/VN/Aw9WsauazbgsgdfVWgAKQg==} - dev: true - /uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. @@ -19467,7 +19495,6 @@ packages: /uuid@9.0.0: resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} - dev: false /v8-to-istanbul@9.0.1: resolution: {integrity: sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==} @@ -19575,17 +19602,17 @@ packages: replace-ext: 1.0.1 dev: false - /vite-node@0.29.8(@types/node@18.15.11)(sass@1.60.0): - resolution: {integrity: sha512-b6OtCXfk65L6SElVM20q5G546yu10/kNrhg08afEoWlFRJXFq9/6glsvSVY+aI6YeC1tu2TtAqI2jHEQmOmsFw==} - engines: {node: '>=v14.16.0'} + /vite-node@0.30.1(@types/node@18.16.3)(sass@1.62.1): + resolution: {integrity: sha512-vTikpU/J7e6LU/8iM3dzBo8ZhEiKZEKRznEMm+mJh95XhWaPrJQraT/QsT2NWmuEf+zgAoMe64PKT7hfZ1Njmg==} + engines: {node: '>=v14.18.0'} hasBin: true dependencies: cac: 6.7.14 debug: 4.3.4(supports-color@8.1.1) - mlly: 1.1.1 + mlly: 1.2.0 pathe: 1.1.0 picocolors: 1.0.0 - vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0) + vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1) transitivePeerDependencies: - '@types/node' - less @@ -19596,12 +19623,12 @@ packages: - terser dev: true - /vite-plugin-turbosnap@1.0.1: - resolution: {integrity: sha512-isVvISdXZyflIsXYrpTMBnyrtZq92ftohL8/xHi1H0kUwXIFDegqedX1kCKIQ04tjUkphB0cFbGzuvOGVwVTnQ==} + /vite-plugin-turbosnap@1.0.2: + resolution: {integrity: sha512-irjKcKXRn7v5bPAg4mAbsS6DgibpP1VUFL9tlgxU6lloK6V9yw9qCZkS+s2PtbkZpWNzr3TN3zVJAc6J7gJZmA==} dev: true - /vite@4.2.1(@types/node@18.15.11)(sass@1.60.0): - resolution: {integrity: sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==} + /vite@4.3.4(@types/node@18.16.3)(sass@1.62.1): + resolution: {integrity: sha512-f90aqGBoxSFxWph2b39ae2uHAxm5jFBBdnfueNxZAT1FTpM13ccFQExCaKbR2xFW5atowjleRniQ7onjJ22QEg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -19625,30 +19652,29 @@ packages: terser: optional: true dependencies: - '@types/node': 18.15.11 - esbuild: 0.17.14 - postcss: 8.4.21 - resolve: 1.22.1 - rollup: 3.20.2 - sass: 1.60.0 + '@types/node': 18.16.3 + esbuild: 0.17.18 + postcss: 8.4.23 + rollup: 3.21.3 + sass: 1.62.1 optionalDependencies: fsevents: 2.3.2 - /vitest-fetch-mock@0.2.2(vitest@0.29.8): + /vitest-fetch-mock@0.2.2(vitest@0.30.1): resolution: {integrity: sha512-XmH6QgTSjCWrqXoPREIdbj40T7i1xnGmAsTAgfckoO75W1IEHKR8hcPCQ7SO16RsdW1t85oUm6pcQRLeBgjVYQ==} engines: {node: '>=14.14.0'} peerDependencies: vitest: '>=0.16.0' dependencies: cross-fetch: 3.1.5 - vitest: 0.29.8(happy-dom@8.9.0)(sass@1.60.0) + vitest: 0.30.1(happy-dom@9.10.2)(sass@1.62.1) transitivePeerDependencies: - encoding dev: true - /vitest@0.29.8(happy-dom@8.9.0)(sass@1.60.0): - resolution: {integrity: sha512-JIAVi2GK5cvA6awGpH0HvH/gEG9PZ0a/WoxdiV3PmqK+3CjQMf8c+J/Vhv4mdZ2nRyXFw66sAg6qz7VNkaHfDQ==} - engines: {node: '>=v14.16.0'} + /vitest@0.30.1(happy-dom@9.10.2)(sass@1.62.1): + resolution: {integrity: sha512-y35WTrSTlTxfMLttgQk4rHcaDkbHQwDP++SNwPb+7H8yb13Q3cu2EixrtHzF27iZ8v0XCciSsLg00RkPAzB/aA==} + engines: {node: '>=v14.18.0'} hasBin: true peerDependencies: '@edge-runtime/vm': '*' @@ -19679,18 +19705,21 @@ packages: dependencies: '@types/chai': 4.3.4 '@types/chai-subset': 1.3.3 - '@types/node': 18.15.11 - '@vitest/expect': 0.29.8 - '@vitest/runner': 0.29.8 - '@vitest/spy': 0.29.8 - '@vitest/utils': 0.29.8 + '@types/node': 18.16.3 + '@vitest/expect': 0.30.1 + '@vitest/runner': 0.30.1 + '@vitest/snapshot': 0.30.1 + '@vitest/spy': 0.30.1 + '@vitest/utils': 0.30.1 acorn: 8.8.2 acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.7 + concordance: 5.0.4 debug: 4.3.4(supports-color@8.1.1) - happy-dom: 8.9.0 + happy-dom: 9.10.2 local-pkg: 0.4.3 + magic-string: 0.30.0 pathe: 1.1.0 picocolors: 1.0.0 source-map: 0.6.1 @@ -19698,9 +19727,8 @@ packages: strip-literal: 1.0.1 tinybench: 2.4.0 tinypool: 0.4.0 - tinyspy: 1.1.1 - vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0) - vite-node: 0.29.8(@types/node@18.15.11)(sass@1.60.0) + vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1) + vite-node: 0.30.1(@types/node@18.16.3)(sass@1.62.1) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -19718,8 +19746,8 @@ packages: /vue-docgen-api@4.64.1(vue@3.2.47): resolution: {integrity: sha512-jbOf7ByE3Zvtuk+429Jorl+eIeh2aB2Fx1GUo3xJd1aByJWE8KDlSEa6b11PB1ze8f0sRUBraRDinICCk0KY7g==} dependencies: - '@babel/parser': 7.21.3 - '@babel/types': 7.21.3 + '@babel/parser': 7.21.4 + '@babel/types': 7.21.4 '@vue/compiler-dom': 3.2.47 '@vue/compiler-sfc': 3.2.47 ast-types: 0.14.2 @@ -19733,20 +19761,20 @@ packages: - vue dev: true - /vue-eslint-parser@9.1.1(eslint@8.37.0): + /vue-eslint-parser@9.1.1(eslint@8.39.0): resolution: {integrity: sha512-C2aI/r85Q6tYcz4dpgvrs4wH/MqVrRAVIdpYedrxnATDHHkb+TroeRcDpKWGZCx/OcECMWfz7tVwQ8e+Opy6rA==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: eslint: '>=6.0.0' dependencies: debug: 4.3.4(supports-color@8.1.1) - eslint: 8.37.0 + eslint: 8.39.0 eslint-scope: 7.1.1 eslint-visitor-keys: 3.4.0 espree: 9.5.1 esquery: 1.4.2 lodash: 4.17.21 - semver: 7.3.8 + semver: 7.5.0 transitivePeerDependencies: - supports-color dev: true @@ -19782,15 +19810,16 @@ packages: he: 1.2.0 dev: true - /vue-tsc@1.2.0(typescript@5.0.3): - resolution: {integrity: sha512-rIlzqdrhyPYyLG9zxsVRa+JEseeS9s8F2BbVVVWRRsTZvJO2BbhLEb2HW3MY+DFma0378tnIqs+vfTzbcQtRFw==} + /vue-tsc@1.6.3(typescript@5.0.4): + resolution: {integrity: sha512-q7l27j0eSJgyGat0khetrvoeaAHieRZFnf8WAJyKvB3eF0AxmLqfs4ahwZhaojBJjZ/lAXZa+Xt8EX54KzQ34w==} hasBin: true peerDependencies: typescript: '*' dependencies: - '@volar/vue-language-core': 1.2.0 - '@volar/vue-typescript': 1.2.0 - typescript: 5.0.3 + '@volar/vue-language-core': 1.6.3 + '@volar/vue-typescript': 1.6.3(typescript@5.0.4) + semver: 7.5.0 + typescript: 5.0.4 dev: true /vue@2.7.14: @@ -19833,8 +19862,8 @@ packages: axios: 0.27.2(debug@4.3.4) joi: 17.7.0 lodash: 4.17.21 - minimist: 1.2.7 - rxjs: 7.8.0 + minimist: 1.2.8 + rxjs: 7.8.1 transitivePeerDependencies: - debug dev: true @@ -19867,15 +19896,16 @@ packages: '@zxing/text-encoding': 0.9.0 dev: true - /web-push@3.5.0: - resolution: {integrity: sha512-JC0V9hzKTqlDYJ+LTZUXtW7B175qwwaqzbbMSWDxHWxZvd3xY0C2rcotMGDavub2nAAFw+sXTsqR65/KY2A5AQ==} + /web-push@3.6.1: + resolution: {integrity: sha512-+eN2/4Ybu+nkRgXqlPXUzqeA5BgQ7J4StYJM/e2uJpsSIGhkZJFrTlCx+ow9sD0mn+4jgbo2FkFoMBKftl49ZQ==} engines: {node: '>= 6'} + hasBin: true dependencies: asn1.js: 5.4.1 http_ece: 1.1.0 https-proxy-agent: 5.0.1 jws: 4.0.0 - minimist: 1.2.7 + minimist: 1.2.8 urlsafe-base64: 1.0.0 transitivePeerDependencies: - supports-color @@ -19895,12 +19925,15 @@ packages: /webpack-sources@3.2.3: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} engines: {node: '>=10.13.0'} - dev: true /webpack-virtual-modules@0.4.6: resolution: {integrity: sha512-5tyDlKLqPfMqjT3Q9TAqf2YqjwmnUleZwzJi1A5qXnlBCdj2AtOJ6wAWdglTIDOPgOiOrXeBeFcsQ8+aGQ6QbA==} dev: true + /webpack-virtual-modules@0.5.0: + resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==} + dev: false + /websocket@1.0.34: resolution: {integrity: sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==} engines: {node: '>=4.0.0'} @@ -19915,6 +19948,11 @@ packages: - supports-color dev: false + /well-known-symbols@2.0.0: + resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} + engines: {node: '>=6'} + dev: true + /whatwg-encoding@2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} @@ -20021,7 +20059,7 @@ packages: resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==} engines: {node: '>= 10.0.0'} dependencies: - '@babel/parser': 7.21.3 + '@babel/parser': 7.21.4 '@babel/types': 7.21.4 assert-never: 1.2.1 babel-walk: 3.0.0-canary-5 @@ -20126,8 +20164,8 @@ packages: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} - /xml2js@0.4.23: - resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==} + /xml2js@0.5.0: + resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} engines: {node: '>=4.0.0'} dependencies: sax: 1.2.4 @@ -20329,7 +20367,7 @@ packages: sharp: 0.31.3 dev: false - github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.2)(@storybook/components@7.0.2)(@storybook/core-events@7.0.2)(@storybook/manager-api@7.0.2)(@storybook/preview-api@7.0.2)(@storybook/theming@7.0.2)(@storybook/types@7.0.2)(react-dom@18.2.0)(react@18.2.0): + github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.7)(@storybook/components@7.0.7)(@storybook/core-events@7.0.7)(@storybook/manager-api@7.0.7)(@storybook/preview-api@7.0.7)(@storybook/theming@7.0.7)(@storybook/types@7.0.7)(react-dom@18.2.0)(react@18.2.0): resolution: {tarball: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640} id: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640 name: storybook-addon-misskey-theme @@ -20350,21 +20388,21 @@ packages: react-dom: optional: true dependencies: - '@storybook/blocks': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.0.2 - '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.2 - '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.2 + '@storybook/blocks': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.7 + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.7 + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true - github.com/misskey-dev/summaly/1bab7afee616429b8bbf7a7cbcbb8ebcef66d992: - resolution: {tarball: https://codeload.github.com/misskey-dev/summaly/tar.gz/1bab7afee616429b8bbf7a7cbcbb8ebcef66d992} + github.com/misskey-dev/summaly/2d63e2a0066f89871e777cc81d43c1ade8c97517: + resolution: {tarball: https://codeload.github.com/misskey-dev/summaly/tar.gz/2d63e2a0066f89871e777cc81d43c1ade8c97517} name: summaly - version: 4.0.1 + version: 4.0.2 dependencies: cheerio: 1.0.0-rc.12 escape-regexp: 0.0.1