diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml index 6117e69c03..76a0bae8cc 100644 --- a/.github/workflows/api-misskey-js.yml +++ b/.github/workflows/api-misskey-js.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Setup pnpm uses: pnpm/action-setup@v4.1.0 diff --git a/.github/workflows/changelog-check.yml b/.github/workflows/changelog-check.yml index 5ca27749bb..f4abedd960 100644 --- a/.github/workflows/changelog-check.yml +++ b/.github/workflows/changelog-check.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Checkout head - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Setup Node.js uses: actions/setup-node@v4.4.0 with: diff --git a/.github/workflows/check-misskey-js-autogen.yml b/.github/workflows/check-misskey-js-autogen.yml index 22d500c306..05034ea0f4 100644 --- a/.github/workflows/check-misskey-js-autogen.yml +++ b/.github/workflows/check-misskey-js-autogen.yml @@ -18,7 +18,7 @@ jobs: if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }} steps: - name: checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 with: submodules: true persist-credentials: false @@ -66,7 +66,7 @@ jobs: if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }} steps: - name: checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 with: submodules: true persist-credentials: false diff --git a/.github/workflows/check-misskey-js-version.yml b/.github/workflows/check-misskey-js-version.yml index 2b15cbee53..0e336a1551 100644 --- a/.github/workflows/check-misskey-js-version.yml +++ b/.github/workflows/check-misskey-js-version.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Check version run: | if [ "$(jq -r '.version' package.json)" != "$(jq -r '.version' packages/misskey-js/package.json)" ]; then diff --git a/.github/workflows/check-spdx-license-id.yml b/.github/workflows/check-spdx-license-id.yml index cf1fd6007d..d0b1be4991 100644 --- a/.github/workflows/check-spdx-license-id.yml +++ b/.github/workflows/check-spdx-license-id.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Check run: | counter=0 diff --git a/.github/workflows/check_copyright_year.yml b/.github/workflows/check_copyright_year.yml index eaf922d4bc..d891a538c6 100644 --- a/.github/workflows/check_copyright_year.yml +++ b/.github/workflows/check_copyright_year.yml @@ -10,7 +10,7 @@ jobs: check_copyright_year: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 - run: | if [ "$(grep Copyright COPYING | sed -e 's/.*2014-\([0-9]*\) .*/\1/g')" -ne "$(date +%Y)" ]; then echo "Please change copyright year!" diff --git a/.github/workflows/deploy-test-environment.yml b/.github/workflows/deploy-test-environment.yml index 46baf7421b..d838bc35e5 100644 --- a/.github/workflows/deploy-test-environment.yml +++ b/.github/workflows/deploy-test-environment.yml @@ -28,7 +28,7 @@ jobs: wait_time: ${{ steps.get-wait-time.outputs.wait_time }} steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Check allowed users id: check-allowed-users diff --git a/.github/workflows/docker-develop.yml b/.github/workflows/docker-develop.yml index 56dedf273d..e24ef00d78 100644 --- a/.github/workflows/docker-develop.yml +++ b/.github/workflows/docker-develop.yml @@ -27,7 +27,7 @@ jobs: platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Check out the repo - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index eb98273ba0..991fb85d85 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -32,7 +32,7 @@ jobs: platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Check out the repo - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Docker meta diff --git a/.github/workflows/dockle.yml b/.github/workflows/dockle.yml index f006a45ea4..1c0863e274 100644 --- a/.github/workflows/dockle.yml +++ b/.github/workflows/dockle.yml @@ -15,7 +15,7 @@ jobs: DOCKER_CONTENT_TRUST: 1 DOCKLE_VERSION: 0.4.14 steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 - name: Download and install dockle v${{ env.DOCKLE_VERSION }} run: | curl -L -o dockle.deb "https://github.com/goodwithtech/dockle/releases/download/v${DOCKLE_VERSION}/dockle_${DOCKLE_VERSION}_Linux-64bit.deb" diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml index 933404dfa5..1cd1ef0ebd 100644 --- a/.github/workflows/get-api-diff.yml +++ b/.github/workflows/get-api-diff.yml @@ -25,7 +25,7 @@ jobs: ref: refs/pull/${{ github.event.number }}/merge steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: ref: ${{ matrix.ref }} submodules: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 550438e308..91813cebc3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,7 +36,7 @@ jobs: pnpm_install: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: fetch-depth: 0 submodules: true @@ -69,7 +69,7 @@ jobs: eslint-cache-version: v1 eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }} steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: fetch-depth: 0 submodules: true @@ -81,7 +81,7 @@ jobs: cache: 'pnpm' - run: pnpm i --frozen-lockfile - name: Restore eslint cache - uses: actions/cache@v4.2.3 + uses: actions/cache@v4.2.4 with: path: ${{ env.eslint-cache-path }} key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }} @@ -99,7 +99,7 @@ jobs: - sw - misskey-js steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: fetch-depth: 0 submodules: true diff --git a/.github/workflows/locale.yml b/.github/workflows/locale.yml index 68e45fdf61..63702c8bc7 100644 --- a/.github/workflows/locale.yml +++ b/.github/workflows/locale.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: fetch-depth: 0 submodules: true diff --git a/.github/workflows/on-release-created.yml b/.github/workflows/on-release-created.yml index 7787d6055b..f36eb0037d 100644 --- a/.github/workflows/on-release-created.yml +++ b/.github/workflows/on-release-created.yml @@ -16,7 +16,7 @@ jobs: id-token: write steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: submodules: true - name: Setup pnpm diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index b1d95c1b33..7f964ef1d7 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -22,12 +22,12 @@ jobs: NODE_OPTIONS: "--max_old_space_size=7168" steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 if: github.event_name != 'pull_request_target' with: fetch-depth: 0 submodules: true - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 if: github.event_name == 'pull_request_target' with: fetch-depth: 0 diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index 5358df3dc4..e9791ac95e 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -50,7 +50,7 @@ jobs: - 56312:6379 steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: submodules: true - name: Setup pnpm @@ -129,7 +129,7 @@ jobs: - 56312:6379 steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: submodules: true - name: Setup pnpm @@ -173,7 +173,7 @@ jobs: POSTGRES_HOST_AUTH_METHOD: trust steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: submodules: true - name: Setup pnpm diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index 94e43cf91e..da3c9255f2 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: submodules: true - name: Setup pnpm @@ -76,7 +76,7 @@ jobs: - 56312:6379 steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: submodules: true # https://github.com/cypress-io/cypress-docker-images/issues/150 diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml index f6d16bbd76..0fad57f8da 100644 --- a/.github/workflows/test-misskey-js.yml +++ b/.github/workflows/test-misskey-js.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v4.3.0 - name: Setup pnpm uses: pnpm/action-setup@v4.1.0 diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml index 751c374608..08289dfd3b 100644 --- a/.github/workflows/test-production.yml +++ b/.github/workflows/test-production.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: submodules: true - name: Setup pnpm diff --git a/.github/workflows/validate-api-json.yml b/.github/workflows/validate-api-json.yml index edff7dbecb..25353e4d29 100644 --- a/.github/workflows/validate-api-json.yml +++ b/.github/workflows/validate-api-json.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v4.3.0 with: submodules: true - name: Setup pnpm diff --git a/CHANGELOG.md b/CHANGELOG.md index 59c3e7481b..3b6a1f6f66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## 2025.9.0 + +### Client +- Enhance: AiScriptAppウィジェットで構文エラーを検知してもダイアログではなくウィジェット内にエラーを表示するように +- Enhance: /flushページでサイトキャッシュをクリアできるようになりました +- Enhance: クリップ/リスト/アンテナ/ロール追加系メニュー項目において、表示件数を拡張 +- Enhance: 「キャッシュを削除」ボタンでブラウザの内部キャッシュの削除も行えるように +- Enhance: Ctrlキー(Commandキー)を押下しながらリンクをクリックすると新しいタブで開くように +- Fix: プッシュ通知を有効にできない問題を修正 +- Fix: RSSティッカーウィジェットが正しく動作しない問題を修正 +- Fix: プロファイルを復元後アカウントの切り替えができない問題を修正 +- Fix: エラー画像が横に引き伸ばされてしまう問題に対応 + +### Server +- Fix: webpなどの画像に対してセンシティブなメディアの検出が適用されていなかった問題を修正 + ## 2025.8.0 ### Note @@ -6,8 +22,8 @@ ### General - ノートを削除した際、関連するノートが同時に削除されないようになりました - APIで、「replyIdが存在しているのにreplyがnull」や「renoteIdが存在しているのにrenoteがnull」であるという、今までにはなかったパターンが表れることになります -- 定期的に参照されていない古いリモートの投稿を削除する機能が実装されました(コントロールパネル→パフォーマンス→Remote Notes Cleaning) - - 既存のサーバーでは**デフォルトでオフ**、新規サーバーでは**デフォルトでオン**になります +- 定期的に古いリモートの投稿を削除する機能が実装されました + - コントロールパネル→パフォーマンス→Remote Notes Cleaning で有効化できます - データベースの肥大化を防止することが可能です - 既存のサーバーで当機能を有効化した場合は、処理量が多くなるため、一時的にストレージ使用量が増加する可能性があります。 - 増加量を抑えるには、最大処理継続時間をデフォルトより短くしてください。 @@ -15,7 +31,8 @@ - サーバーの初期設定が完了するまでは連合がオンにならないようになりました - 日本語における公開範囲名称の「ダイレクト」が「指名」に改称されました - 実際の動作に即した名称になり、馴染みのない人でも理解しやすくなりました - - 他サービスにおける「ダイレクトメッセージ」に相当するMisskeyの機能は「チャット」ですが、「ダイレクト投稿」という名称の機能が存在するとそちらがダイレクトメッセージ機能であるような誤解を生んでいました + - 他サービスにおける「ダイレクトメッセージ」に相当するMisskeyの機能は「チャット」ですが(過去のバージョンのMisskeyでも、当該機能は「チャット」ではなく「ダイレクトメッセージ」でした)、「ダイレクト投稿」という名称の機能が存在するとそちらがダイレクトメッセージ機能であるような誤解を生んでいました + - 今後、「チャット」の名称を「ダイレクトメッセージ」に戻す可能性があります - mfm.jsをアップデートしました - Enhance: Unicode 15.1 および 16.0 に収録されている絵文字に対応 - Enhance: acctに `.` が入っているユーザーのメンションに対応 @@ -27,6 +44,7 @@ - プラグインは1.xに対応したものが必要です - Playはそのまま動作しますが、新規に作られるプリセットは1.xになります - 以前のバージョンから無効化されていた note_view_interruptor が有効になりました + - ハンドラは同期的である必要があります - Feat: セーフモード - プラグイン・テーマ・カスタムCSSの使用でクライアントの起動に問題が発生した際に、これらを無効にして起動できます - 以下の方法でセーフモードを起動できます @@ -37,14 +55,16 @@ - コントロールパネル→ブランディング→エントランスページのスタイル - Feat: ページのタブバーを下部に表示できるように - Feat: (実験的)iOSでの触覚フィードバックを有効にできるように +- Feat: コントロールパネルを検索できるように - Enhance: 「自動でもっと見る」オプションが有効になり、安定性が向上しました -- Enhance: コントロールパネルを検索できるように - Enhance: トルコ語 (tr-TR) に対応 - Enhance: 不必要な翻訳データを読み込まなくなり、パフォーマンスが向上しました - Enhance: 画像エフェクトのパラメータ名の多言語対応 -- Enhance: 依存ソフトウェアの更新 - Enhance: ノートを非表示にする相対期間を1ヶ月単位で自由に指定できるように - Enhance: メールアドレス確認画面のUIを改善 +- Enhance: アイコンのスクロール追従を無効化する際の適用範囲を強化 +- Enhance: レンダリングパフォーマンスの向上 +- Enhance: 依存ソフトウェアの更新 - Fix: 投稿フォームでファイルのアップロードが中止または失敗した際のハンドリングを修正 - Fix: 一部の設定検索結果が存在しないパスになる問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1171) @@ -53,6 +73,12 @@ - Fix: カラムの名前が正しくリスト/チャンネルの名前にならない問題を修正 - Fix: 複数のメンションを1行に記述した場合に、サジェストが正しく表示されない問題を修正 - Fix: メンションとしての条件を満たしていても、特定の条件(`-`が含まれる場合など)で正しくサジェストされない問題を一部修正 +- Fix: ユーザーの前後ノートを閲覧する機能が動作しない問題を修正 +- Fix: 照会ダイアログでap/showでローカルユーザーを解決した際@username@nullに飛ばされる問題を修正 +- Fix: アイコンのデコレーションを付ける際にデコレーションが表示されなくなる問題を修正 +- Fix: タッチ操作時にマウスホバー時のユーザープレビューが開くことがある問題を修正 +- Fix: 管理中アカウント一覧で正しい表示が行われない問題を修正 +- Fix: lookupページでリモートURLを指定した際に正しく動作しない問題を修正 ### Server - Feat: サーバー管理コマンド @@ -65,6 +91,9 @@ - Enhance: `clips/list` APIがページネーションに対応しました - Fix: `notes/mentions` で場合によっては並び順が正しく返されない問題を修正 - Fix: SystemWebhook設定でsecretを空に出来ない問題を修正 +- Fix: 削除されたユーザーがチャットメッセージにリアクションしている場合`chat/history`などでエラーになる問題を修正 +- Fix: Pageのアイキャッチ画像をドライブから消してもPageごと消えないように +- Fix: タイムラインAPIの withRenotes: false 時のレスポンスを修正 ## 2025.7.0 diff --git a/idea/MkAbuseReport.stories.impl.ts b/idea/MkAbuseReport.stories.impl.ts index 717bceb23d..138e5cd0cd 100644 --- a/idea/MkAbuseReport.stories.impl.ts +++ b/idea/MkAbuseReport.stories.impl.ts @@ -4,8 +4,8 @@ */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { action } from '@storybook/addon-actions'; -import { StoryObj } from '@storybook/vue3'; +import { action } from 'storybook/actions'; +import type { StoryObj } from '@storybook/vue3'; import { HttpResponse, http } from 'msw'; import { abuseUserReport } from '../packages/frontend/.storybook/fakes.js'; import { commonHandlers } from '../packages/frontend/.storybook/mocks.js'; diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index ae9de0f173..63878bf1b7 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -1245,7 +1245,7 @@ releaseToRefresh: "Deixar anar per actualitzar" refreshing: "Recarregant..." pullDownToRefresh: "Llisca cap a baix per recarregar" useGroupedNotifications: "Mostrar les notificacions agrupades " -signupPendingError: "Hi ha hagut un problema verificant l'adreça de correu electrònic. L'enllaç pot haver caducat." +emailVerificationFailedError: "Hem tingut un problema en verificar la teva adreça de correu electrònic. És probable que l'enllaç estigui caducat." cwNotationRequired: "Si està activat \"Amagar contingut\" s'ha d'escriure una descripció " doReaction: "Afegeix una reacció " code: "Codi" @@ -1644,7 +1644,7 @@ _serverSettings: reactionsBufferingDescription: "Quan s'activa aquesta opció millora bastant el rendiment en recuperar les línies de temps reduint la càrrega de la base. Com a contrapunt, augmentarà l'ús de memòria de Redís. Desactiva aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes d'inestabilitat." remoteNotesCleaning: "Neteja automàtica de notes remotes" remoteNotesCleaning_description: "Quan activis aquesta opció, periòdicament es netejaran les notes remotes que no es consultin, això evitarà que la base de dades se" - remoteNotesCleaningMaxProcessingDuration: "D'oració màxima del temps de funcionament del procés de neteja" + remoteNotesCleaningMaxProcessingDuration: "Duració màxima del temps de funcionament del procés de neteja" remoteNotesCleaningExpiryDaysForEachNotes: "Duració mínima de conservació de les notes" inquiryUrl: "URL de consulta " inquiryUrlDescription: "Escriu adreça URL per al formulari de consulta per al mantenidor del servidor o una pàgina web amb el contacte d'informació." @@ -1668,7 +1668,7 @@ _serverSettings: restartServerSetupWizardConfirm_text: "Algunes configuracions actuals seran restablertes." entrancePageStyle: "Estil de la pàgina d'inici" showTimelineForVisitor: "Mostrar la línia de temps" - showActivityiesForVisitor: "Mostrar les activitats" + showActivitiesForVisitor: "Mostrar activitat" _userGeneratedContentsVisibilityForVisitor: all: "Tot obert al públic " localOnly: "Només es publiquen els continguts locals, el contingut remot es manté privat" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 51ae0642aa..2d7d41bfdd 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -1243,7 +1243,6 @@ releaseToRefresh: "Zum Aktualisieren loslassen" refreshing: "Wird aktualisiert..." pullDownToRefresh: "Zum Aktualisieren ziehen" useGroupedNotifications: "Benachrichtigungen gruppieren" -signupPendingError: "Beim Überprüfen der Mailadresse ist etwas schiefgelaufen. Der Link könnte abgelaufen sein." cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Beschreibung gegeben werden." doReaction: "Reagieren" code: "Code" diff --git a/locales/en-US.yml b/locales/en-US.yml index 4a797e0fc2..9c02e83021 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1218,8 +1218,8 @@ showRepliesToOthersInTimeline: "Show replies to others in timeline" hideRepliesToOthersInTimeline: "Hide replies to others from timeline" showRepliesToOthersInTimelineAll: "Show replies to others from everyone you follow in timeline" hideRepliesToOthersInTimelineAll: "Hide replies to others from everyone you follow in timeline" -confirmShowRepliesAll: "This operation is irreversible. Would you really like to show replies to others from everyone you follow in your timeline?" -confirmHideRepliesAll: "This operation is irreversible. Would you really like to hide replies to others from everyone you follow in your timeline?" +confirmShowRepliesAll: "Are you sure you want to show replies from everyone you follow in your timeline? This action is irreversible." +confirmHideRepliesAll: "Are you sure you want to hide replies from everyone you follow in your timeline? This action is irreversible." externalServices: "External Services" sourceCode: "Source code" sourceCodeIsNotYetProvided: "Source code is not yet available. Contact the administrator to fix this problem." @@ -1245,7 +1245,7 @@ releaseToRefresh: "Release to refresh" refreshing: "Refreshing..." pullDownToRefresh: "Pull down to refresh" useGroupedNotifications: "Display grouped notifications" -signupPendingError: "There was a problem verifying the email address. The link may have expired." +emailVerificationFailedError: "A problem occurred while verifying your email address. The link may have expired." cwNotationRequired: "If \"Hide content\" is enabled, a description must be provided." doReaction: "Add reaction" code: "Code" @@ -1376,6 +1376,7 @@ safeModeEnabled: "Safe mode is enabled" pluginsAreDisabledBecauseSafeMode: "All plugins are disabled because safe mode is enabled." customCssIsDisabledBecauseSafeMode: "Custom CSS is not applied because safe mode is enabled." themeIsDefaultBecauseSafeMode: "While safe mode is active, the default theme is used. Disabling safe mode will revert these changes." +thankYouForTestingBeta: "Thank you for helping us test the beta version!" _order: newest: "Newest First" oldest: "Oldest First" @@ -1665,6 +1666,9 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor_description2: "Unconditionally publishing all content on the server to the Internet, including remote content received by the server is risky. This is especially important for guests who are unaware of the distributed nature of the content, as they may mistakenly believe that even remote content is content created by users on the server." restartServerSetupWizardConfirm_title: "Restart server setup wizard?" restartServerSetupWizardConfirm_text: "Some current settings will be reset." + entrancePageStyle: "Entrance page style" + showTimelineForVisitor: "Show timeline" + showActivitiesForVisitor: "Show activities" _userGeneratedContentsVisibilityForVisitor: all: "Everything is public" localOnly: "Only local content is published, remote content is kept private" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 51b377d49f..8a1d2c458b 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -1054,6 +1054,7 @@ permissionDeniedError: "Operación denegada" permissionDeniedErrorDescription: "Esta cuenta no tiene permisos para hacer esa acción." preset: "Predefinido" selectFromPresets: "Escoger desde predefinidos" +custom: "Personalizado" achievements: "Logros" gotInvalidResponseError: "Respuesta del servidor inválida" gotInvalidResponseErrorDescription: "Puede que el servidor esté caído o en mantenimiento. Favor de intentar más tarde" @@ -1244,7 +1245,7 @@ releaseToRefresh: "Soltar para recargar" refreshing: "Recargando..." pullDownToRefresh: "Tira hacia abajo para recargar" useGroupedNotifications: "Mostrar notificaciones agrupadas" -signupPendingError: "Ha habido un problema al verificar tu dirección de correo electrónico. Es posible que el enlace haya caducado." +emailVerificationFailedError: "Se ha producido un error al confirmar tu dirección de correo electrónico. Es posible que el enlace haya caducado." cwNotationRequired: "Si se ha activado \"ocultar contenido\", es necesario proporcionar una descripción." doReaction: "Añadir reacción" code: "Código" @@ -1375,6 +1376,7 @@ safeModeEnabled: "El modo seguro está activado" pluginsAreDisabledBecauseSafeMode: "El modo seguro está activado, por lo que todos los plugins están desactivados." customCssIsDisabledBecauseSafeMode: "El modo seguro está activado, por lo que no se aplica el CSS personalizado." themeIsDefaultBecauseSafeMode: "Mientras el modo seguro esté activado, se utilizará el tema predeterminado. Cuando se desactive el modo seguro, se volverá al tema original." +thankYouForTestingBeta: "¡Gracias por tu colaboración en la prueba de la versión beta!" _order: newest: "Los más recientes primero" oldest: "Los más antiguos primero" @@ -1664,6 +1666,9 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor_description2: "Publicar incondicionalmente todo el contenido del servidor en Internet, incluido el contenido remoto recibido por el servidor, es arriesgado. Esto es especialmente importante para los invitados que desconocen la naturaleza distribuida del contenido, ya que pueden creer erróneamente que incluso el contenido remoto es contenido creado por usuarios en el servidor." restartServerSetupWizardConfirm_title: "¿Reiniciar el asistente de configuración del servidor?" restartServerSetupWizardConfirm_text: "Algunas configuraciones actuales se restablecerán" + entrancePageStyle: "Estilo de la página de inicio" + showTimelineForVisitor: "Mostrar la línea de tiempo" + showActivitiesForVisitor: "Mostrar actividades" _userGeneratedContentsVisibilityForVisitor: all: "Todo es público." localOnly: "Sólo se publica el contenido local, el remoto se mantiene privado" @@ -2132,7 +2137,7 @@ _aboutMisskey: _displayOfSensitiveMedia: respect: "Esconder medios marcados como sensibles" ignore: "Mostrar medios marcados como sensibles" - force: "Esconder todala multimedia" + force: "Esconder toda la multimedia" _instanceTicker: none: "No mostrar" remote: "Mostrar a usuarios remotos" @@ -2273,6 +2278,7 @@ _time: minute: "Minutos" hour: "Horas" day: "Días" + month: "Mes(es)" _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 f47a350369..8ea21bdb46 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -1208,7 +1208,6 @@ releaseToRefresh: "Relâcher pour rafraîchir" refreshing: "Rafraîchissement..." pullDownToRefresh: "Tirer vers le bas pour rafraîchir" useGroupedNotifications: "Grouper les notifications" -signupPendingError: "Un problème est survenu lors de la vérification de votre adresse e-mail. Le lien a peut-être expiré." cwNotationRequired: "Si « Masquer le contenu » est activé, une description doit être fournie." doReaction: "Réagir" code: "Code" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 93683508b5..ac6aefa4b4 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -1212,7 +1212,6 @@ releaseToRefresh: "Lepaskan untuk memuat ulang" refreshing: "Sedang memuat ulang..." pullDownToRefresh: "Tarik ke bawah untuk memuat ulang" useGroupedNotifications: "Tampilkan notifikasi secara dikelompokkan" -signupPendingError: "Terdapat masalah ketika memverifikasi alamat surel. Tautan kemungkinan telah kedaluwarsa." cwNotationRequired: "Jika \"Sembunyikan konten\" diaktifkan, deskripsi harus disediakan." doReaction: "Tambahkan reaksi" code: "Kode" diff --git a/locales/index.d.ts b/locales/index.d.ts index c31a3f4e83..0cee5b27e5 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -6531,7 +6531,7 @@ export interface Locale extends ILocale { */ "remoteNotesCleaning": string; /** - * 有効にすると、参照されていない古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。 + * 有効にすると、一定期間経過したリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。 */ "remoteNotesCleaning_description": string; /** @@ -6633,7 +6633,7 @@ export interface Locale extends ILocale { /** * アクティビティを表示する */ - "showActivityiesForVisitor": string; + "showActivitiesForVisitor": string; "_userGeneratedContentsVisibilityForVisitor": { /** * 全て公開 @@ -12032,11 +12032,11 @@ export interface Locale extends ILocale { */ "youCanConfigureMoreFederationSettingsLater": string; /** - * 受信コンテンツの自動クリーニング + * リモートコンテンツの自動クリーニング */ "remoteContentsCleaning": string; /** - * 連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。 + * 連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、一定期間経過したリモートコンテンツを自動でサーバーから削除し、ストレージを節約できます。 */ "remoteContentsCleaning_description": string; /** diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 5d1b9dc924..8ea11f81c9 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -139,7 +139,7 @@ overwriteFromPinnedEmojis: "Sovrascrivi con le impostazioni globali" reactionSettingDescription2: "Trascina per riorganizzare, clicca per cancellare, usa il pulsante \"+\" per aggiungere." rememberNoteVisibility: "Ricordare le impostazioni di visibilità delle note" attachCancel: "Rimuovi allegato" -deleteFile: "File da Drive eliminato" +deleteFile: "Elimina un file dal Drive" markAsSensitive: "Segna come esplicito" unmarkAsSensitive: "Non segnare come esplicito " enterFileName: "Nome del file" @@ -1245,7 +1245,7 @@ releaseToRefresh: "Rilascia per aggiornare" refreshing: "Aggiornamento..." pullDownToRefresh: "Trascinare per aggiornare" useGroupedNotifications: "Mostra le notifiche raggruppate" -signupPendingError: "Si è verificato un problema durante la verifica del tuo indirizzo email. Potrebbe essere scaduto il collegamento temporaneo." +emailVerificationFailedError: "La verifica dell'indirizzo e-mail non è andata a buon fine. Il link potrebbe essere scaduto." cwNotationRequired: "Devi indicare perché il contenuto è indicato come esplicito." doReaction: "Reagisci" code: "Codice" @@ -1668,7 +1668,7 @@ _serverSettings: restartServerSetupWizardConfirm_text: "Verranno ripristinate alcune tue impostazioni personalizzate." entrancePageStyle: "Stile della pagina di ingresso" showTimelineForVisitor: "Mostra la Timeline a visitatori non autenticati" - showActivityiesForVisitor: "Mostra le attività a visitatori non autenticati" + showActivitiesForVisitor: "Mostrare la propria attività" _userGeneratedContentsVisibilityForVisitor: all: "Tutto pubblico" localOnly: "Pubblica solo contenuti locali, mantieni privati i contenuti remoti" @@ -2222,7 +2222,7 @@ _theme: hashtag: "Hashtag" mention: "Menzioni" mentionMe: "Menzioni (di me)" - renote: "Renota" + renote: "Rinota" modalBg: "Sfondo modale." divider: "Interruzione di linea" scrollbarHandle: "Maniglie della barra di scorrimento" @@ -2663,7 +2663,7 @@ _notification: createToken: "È stato creato un token di accesso" createTokenDescription: "In caso contrario, eliminare il token di accesso tramite ({text})." _types: - all: "Tutto" + all: "Tutte" note: "Nuove Note" follow: "Follower" mention: "Menzioni" @@ -2671,7 +2671,7 @@ _notification: renote: "Rinota" quote: "Cita" reaction: "Reazioni" - pollEnded: "Sondaggio chiuso." + pollEnded: "Sondaggio terminato" receiveFollowRequest: "Richieste di follow in arrivo" followRequestAccepted: "Richieste di follow accettate" roleAssigned: "Ruolo concesso" @@ -2679,7 +2679,7 @@ _notification: achievementEarned: "Risultato raggiunto" exportCompleted: "Esportazione completata" login: "Accessi" - createToken: "Creare un token di accesso" + createToken: "Aggiunto un token di accesso" test: "Notifiche di test" app: "Notifiche da applicazioni" _actions: @@ -2771,56 +2771,56 @@ _abuseReport: notifiedWebhook: "Webhook da usare" deleteConfirm: "Vuoi davvero rimuovere il destinatario della notifica?" _moderationLogTypes: - createRole: "Ruolo creato" - deleteRole: "Ruolo eliminato" - updateRole: "Ruolo aggiornato" - assignRole: "Ruolo assegnato" - unassignRole: "Ruolo disassegnato" - suspend: "Sospensione" - unsuspend: "Sospensione rimossa" - addCustomEmoji: "Emoji personalizzata aggiunta" - updateCustomEmoji: "Emoji personalizzata aggiornata" - deleteCustomEmoji: "Emoji personalizzata eliminata" - updateServerSettings: "Impostazioni del server aggiornate" - updateUserNote: "Promemoria di moderazione aggiornato" - deleteDriveFile: "File da Drive eliminato" - deleteNote: "Nota eliminata" - createGlobalAnnouncement: "Annuncio globale creato" - createUserAnnouncement: "Annuncio ai profili iscritti creato" - updateGlobalAnnouncement: "Annuncio globale aggiornato" - updateUserAnnouncement: "Annuncio ai profili iscritti aggiornato" - deleteGlobalAnnouncement: "Annuncio globale eliminato" - deleteUserAnnouncement: "Annuncio ai profili iscritti eliminato" - resetPassword: "Password azzerata" - suspendRemoteInstance: "Istanza remota sospesa" - unsuspendRemoteInstance: "Istanza remota riattivata" - updateRemoteInstanceNote: "Aggiornamento del promemoria di moderazione per il server remoto" - markSensitiveDriveFile: "File nel Drive segnato come esplicito" - unmarkSensitiveDriveFile: "File nel Drive segnato come non esplicito" - resolveAbuseReport: "Segnalazione risolta" - forwardAbuseReport: "Segnalazione inoltrata" - updateAbuseReportNote: "Ha aggiornato la segnalazione" - createInvitation: "Genera codice di invito" - createAd: "Banner creato" - deleteAd: "Banner eliminato" - updateAd: "Banner aggiornato" - createAvatarDecoration: "Creazione decorazione della foto profilo" - updateAvatarDecoration: "Aggiornamento decorazione foto profilo" - deleteAvatarDecoration: "Eliminazione decorazione della foto profilo" - unsetUserAvatar: "Rimossa foto profilo" - unsetUserBanner: "Rimossa intestazione profilo" - createSystemWebhook: "Crea un SystemWebhook" - updateSystemWebhook: "Modifica SystemWebhook" - deleteSystemWebhook: "Elimina SystemWebhook" + createRole: "Crea un Ruolo" + deleteRole: "Elimina un Ruolo" + updateRole: "Modifica un ruolo" + assignRole: "Assegna un Ruolo" + unassignRole: "Toglie un Ruolo al Profilo" + suspend: "Sospende" + unsuspend: "Solleva la sospensione" + addCustomEmoji: "Aggiunge Emoji personalizzata" + updateCustomEmoji: "Modifica Emoji personalizzata" + deleteCustomEmoji: "Elimina Emoji personalizzata" + updateServerSettings: "Modifica le impostazioni del server" + updateUserNote: "Modifica un promemoria di moderazione" + deleteDriveFile: "Elimina un file dal Drive" + deleteNote: "Elimina una Nota" + createGlobalAnnouncement: "Crea un annuncio globale" + createUserAnnouncement: "Crea un annuncio ai profili già iscritti" + updateGlobalAnnouncement: "Modifica un annuncio globale" + updateUserAnnouncement: "Modifica un annuncio ai profili già iscritti" + deleteGlobalAnnouncement: "Elimina un annuncio globale" + deleteUserAnnouncement: "Elimina un annuncio ai profili già iscritti" + resetPassword: "Azzera la password" + suspendRemoteInstance: "Sospende una istanza remota" + unsuspendRemoteInstance: "Riattiva una istanza remota" + updateRemoteInstanceNote: "Modifica il promemoria di moderazione per il server remoto" + markSensitiveDriveFile: "Aggiunge NSFW a un file nel Drive" + unmarkSensitiveDriveFile: "Toglie NSFW da un file nel Drive" + resolveAbuseReport: "Risolve una segnalazione" + forwardAbuseReport: "Inoltra una segnalazione" + updateAbuseReportNote: "Modifica una segnalazione" + createInvitation: "Genera un codice di invito" + createAd: "Aggiunge un Banner" + deleteAd: "Elimina un Banner" + updateAd: "Modifica un Banner" + createAvatarDecoration: "Crea una decorazione della foto profilo" + updateAvatarDecoration: "Modifica una decorazione della foto profilo" + deleteAvatarDecoration: "Elimina una decorazione della foto profilo" + unsetUserAvatar: "Toglie una foto profilo" + unsetUserBanner: "Toglie una immagine di intestazione profilo" + createSystemWebhook: "Aggiunge un System Webhook" + updateSystemWebhook: "Modifica un System Webhook" + deleteSystemWebhook: "Elimina un System Webhook" createAbuseReportNotificationRecipient: "Crea destinatario per le notifiche di segnalazioni" - updateAbuseReportNotificationRecipient: "Aggiorna destinatario notifiche di segnalazioni" - deleteAbuseReportNotificationRecipient: "Elimina destinatario notifiche di segnalazioni" - deleteAccount: "Quando viene eliminato un profilo" - deletePage: "Pagina eliminata" - deleteFlash: "Play eliminato" - deleteGalleryPost: "Eliminazione pubblicazione nella Galleria" - deleteChatRoom: "Elimina chat" - updateProxyAccountDescription: "Aggiornata la descrizione del profilo proxy" + updateAbuseReportNotificationRecipient: "Modifica un destinatario per le notifiche di segnalazioni" + deleteAbuseReportNotificationRecipient: "Elimina un destinatario per le notifiche di segnalazioni" + deleteAccount: "Elimina un profilo" + deletePage: "Elimina una Pagina" + deleteFlash: "Elimina un Play" + deleteGalleryPost: "Elimina pubblicazione nella Galleria" + deleteChatRoom: "Elimina una Chat" + updateProxyAccountDescription: "Aggiorna la descrizione del profilo proxy" _fileViewer: title: "Dettagli del file" type: "Tipo di file" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 522f53ce4d..3cb8248948 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1660,7 +1660,7 @@ _serverSettings: fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。" reactionsBufferingDescription: "有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。" remoteNotesCleaning: "リモート投稿の自動クリーニング" - remoteNotesCleaning_description: "有効にすると、参照されていない古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。" + remoteNotesCleaning_description: "有効にすると、一定期間経過したリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。" remoteNotesCleaningMaxProcessingDuration: "最大クリーニング処理継続時間" remoteNotesCleaningExpiryDaysForEachNotes: "最低ノート保持日数" inquiryUrl: "問い合わせ先URL" @@ -1685,7 +1685,7 @@ _serverSettings: restartServerSetupWizardConfirm_text: "現在の一部の設定はリセットされます。" entrancePageStyle: "エントランスページのスタイル" showTimelineForVisitor: "タイムラインを表示する" - showActivityiesForVisitor: "アクティビティを表示する" + showActivitiesForVisitor: "アクティビティを表示する" _userGeneratedContentsVisibilityForVisitor: all: "全て公開" @@ -3216,8 +3216,8 @@ _serverSetupWizard: doYouConnectToFediverse_description1: "分散型サーバーで構成されるネットワーク(Fediverse)に接続すると、他のサーバーと相互にコンテンツのやり取りが可能です。" doYouConnectToFediverse_description2: "Fediverseと接続することは「連合」とも呼ばれます。" youCanConfigureMoreFederationSettingsLater: "連合可能なサーバーの指定など、高度な設定も後ほど可能です。" - remoteContentsCleaning: "受信コンテンツの自動クリーニング" - remoteContentsCleaning_description: "連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。" + remoteContentsCleaning: "リモートコンテンツの自動クリーニング" + remoteContentsCleaning_description: "連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、一定期間経過したリモートコンテンツを自動でサーバーから削除し、ストレージを節約できます。" adminInfo: "管理者情報" adminInfo_description: "問い合わせを受け付けるために使用される管理者情報を設定します。" adminInfo_mustBeFilled: "オープンサーバー、または連合がオンの場合は必ず入力が必要です。" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 05113500a3..d44ed1c3fc 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -1239,7 +1239,6 @@ releaseToRefresh: "離したらリロード" refreshing: "リロードしとる" pullDownToRefresh: "引っ張ってリロードするで" useGroupedNotifications: "通知をグループ分けして出すで" -signupPendingError: "メアド確認してたらなんか変なことなったわ。リンクの期限切れてるかもしれん。" cwNotationRequired: "「内容を隠す」んやったら注釈書かなアカンで。" doReaction: "ツッコむで" code: "コード" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 382a506726..8809d3b4d3 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1245,7 +1245,7 @@ releaseToRefresh: "놓아서 새로고침" refreshing: "새로고침 중" pullDownToRefresh: "아래로 내려서 새로고침" useGroupedNotifications: "알림을 그룹화하고 표시" -signupPendingError: "메일 주소 확인중에 문제가 발생했습니다. 링크의 유효기간이 지났을 가능성이 있습니다." +emailVerificationFailedError: "메일 주소 확인에 실패했습니다. 확인에 필요한 URL의 유효기간이 지났을 가능성이 있습니다." cwNotationRequired: "'내용을 숨기기'를 체크한 경우 주석을 써야 합니다." doReaction: "리액션 추가" code: "문자열" @@ -1668,7 +1668,7 @@ _serverSettings: restartServerSetupWizardConfirm_text: "현재 일부 설정은 리셋됩니다." entrancePageStyle: "입구 페이지의 스타일" showTimelineForVisitor: "타임라인 표시" - showActivityiesForVisitor: "활동 표시" + showActivitiesForVisitor: "액티비티 표시하기" _userGeneratedContentsVisibilityForVisitor: all: "모두 공개" localOnly: "로컬 콘텐츠만 공개하고 리모트 콘텐츠는 비공개" diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index 64fec3957e..013d2ef549 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -1243,7 +1243,6 @@ releaseToRefresh: "Solte para atualizar" refreshing: "Atualizando..." pullDownToRefresh: "Puxe para baixo para atualizar" useGroupedNotifications: "Agrupar notificações" -signupPendingError: "Houve um problema ao verificar o endereço de email. O link pode ter expirado." cwNotationRequired: "Se \"Esconder conteúdo\" está habilitado, uma descrição deve ser adicionada." doReaction: "Adicionar reação" code: "Código" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 3d3ecca531..f633e1488f 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -1215,12 +1215,12 @@ privacyPolicyUrl: "Ссылка на Политику Конфиденциаль tosAndPrivacyPolicy: "Условия использования и политика конфиденциальности" avatarDecorations: "Украшения для аватара" attach: "Прикрепить" +detachAll: "Убрать всё" angle: "Угол" flip: "Переворот" showAvatarDecorations: "Показать украшения для аватара" pullDownToRefresh: "Опустите что бы обновить" useGroupedNotifications: "Отображать уведомления сгруппировано" -signupPendingError: "Возникла проблема с подтверждением вашего адреса электронной почты. Возможно, срок действия ссылки истёк." cwNotationRequired: "Если включена опция «Скрыть содержимое», необходимо написать аннотацию." doReaction: "Добавить реакцию" code: "Код" @@ -1254,7 +1254,7 @@ clipNoteLimitExceeded: "К этому клипу больше нельзя до performance: "Производительность" modified: "Изменено" signinWithPasskey: "Войдите в систему, используя свой пароль" -unknownWebAuthnKey: "Не известный ключ " +unknownWebAuthnKey: "Неизвестный ключ" passkeyVerificationFailed: "Ошибка проверка ключа доступа " messageToFollower: "Сообщение подписчикам" testCaptchaWarning: "Эта функция предназначена для тестирования CAPTCHA. Не использовать это в рабочей среде" @@ -1269,8 +1269,11 @@ availableRoles: "Доступные роли" federationDisabled: "Федерация отключена для этого сервера. Вы не можете взаимодействовать с пользователями на других серверах." draft: "Черновик" markAsSensitiveConfirm: "Отметить контент как чувствительный?" +preferences: "Основное" resetToDefaultValue: "Сбросить настройки до стандартных" +syncBetweenDevices: "Синхронизировать между устройствами" postForm: "Форма отправки" +textCount: "Количество символов" information: "Описание" inMinutes: "мин" inDays: "сут" @@ -1282,6 +1285,11 @@ _chat: send: "Отправить" _settings: webhook: "Вебхук" + preferencesBanner: "Вы можете настроить общее поведение клиента по вашим предпочтениям" + timelineAndNote: "Лента и заметки" + _chat: + showSenderName: "Показывать имя отправителя" + sendOnEnter: "Использовать Enter для отправки" _delivery: stop: "Заморожено" _type: @@ -1558,6 +1566,12 @@ _achievements: title: "Brain Diver" description: "Опубликована ссылка на песню «Brain Diver»" flavor: "Мисски-Мисски Ла-Ту-Ма" + _bubbleGameExplodingHead: + title: "🤯" + description: "Самый большой объект в Bubble game" + _bubbleGameDoubleExplodingHead: + title: "Двойной🤯" + description: "Два самых больших объекта в Bubble game одновременно!" _role: new: "Новая роль" edit: "Изменить роль" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 0835902642..f70b0d5be8 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -1245,7 +1245,6 @@ releaseToRefresh: "ปล่อยเพื่อรีเฟรช" refreshing: "กำลังรีเฟรช..." pullDownToRefresh: "ดึงลงเพื่อรีเฟรช" useGroupedNotifications: "แสดงผลการแจ้งเตือนแบบกลุ่มแล้ว" -signupPendingError: "มีปัญหาในการตรวจสอบที่อยู่อีเมลลิงก์อาจหมดอายุแล้ว" cwNotationRequired: "หากเปิดใช้งาน “ซ่อนเนื้อหา” จะต้องระบุคำอธิบาย" doReaction: "เพิ่มรีแอคชั่น" code: "โค้ด" diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml index cabd80e6e7..bde027ba69 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -31,7 +31,7 @@ openInWindow: "Pencerede aç" profile: "Profil" timeline: "Pano" noAccountDescription: "Bu kullanıcı henüz biyografisini yazmamış." -login: "Giriş Yap" +login: "Oturum Aç" loggingIn: "Giriş Yapılıyor..." logout: "Çıkış Yap" signup: "Kaydol" @@ -310,7 +310,7 @@ agreeBelow: "Aşağıdakileri kabul ediyorum" basicNotesBeforeCreateAccount: "Önemli notlar" termsOfService: "Hizmet Şartları" start: "Başla" -home: "Ana sayfa" +home: "Pano" remoteUserCaution: "Bu kullanıcı uzak bir sunucudan geldiği için, gösterilen bilgiler eksik olabilir." activity: "Etkinlik" images: "Görseller" @@ -1054,6 +1054,7 @@ permissionDeniedError: "İşlem reddedildi" permissionDeniedErrorDescription: "Bu hesap bu işlemi gerçekleştirmek için gerekli izne sahip değildir." preset: "Ön ayar" selectFromPresets: "Ön ayarlardan seçim yapın" +custom: "Özel" achievements: "Başarılar" gotInvalidResponseError: "Geçersiz sunucu yanıtı" gotInvalidResponseErrorDescription: "Sunucu erişilemez durumda olabilir veya bakım çalışması yapılmaktadır. Lütfen daha sonra tekrar dene." @@ -1066,8 +1067,8 @@ collapseRenotesDescription: "Zaten yanıtladığın veya renote aldığın notla internalServerError: "İç Sunucu Hatası" internalServerErrorDescription: "Sunucu beklenmedik bir hatayla karşılaştı." copyErrorInfo: "Hata ayrıntılarını kopyala" -joinThisServer: "Bu sunucuda kaydolun" -exploreOtherServers: "Başka bir sunucu arayın" +joinThisServer: "Kaydol" +exploreOtherServers: "Diğer sunucuları keşfet" letsLookAtTimeline: "Pano'ya bir göz atın" disableFederationConfirm: "Federasyonu cidden devre dışı bırakmak istiyor musun?" disableFederationConfirmWarn: "Federasyondan ayrılsa bile, aksi belirtilmedikçe gönderiler herkese açık olmaya devam edecek. Genellikle bunu yapmanız gerekmez." @@ -1244,7 +1245,7 @@ releaseToRefresh: "Yenilemek için serbest bırak" refreshing: "Yenileniyor..." pullDownToRefresh: "Yenilemek için aşağı çekin" useGroupedNotifications: "Gruplandırılmış bildirimleri göster" -signupPendingError: "E-posta adresini doğrulamada bir sorun oluştu. Bağlantının süresi dolmuş olabilir." +emailVerificationFailedError: "E-posta adresi doğrulanırken bir sorun oluştu. Bağlantının geçerlilik süresi dolmuş olabilir." cwNotationRequired: "“İçeriği gizle” seçeneği etkinleştirilirse, bir açıklama sağlanmalı." doReaction: "Tepki ekle" code: "Kod" @@ -1375,6 +1376,7 @@ safeModeEnabled: "Güvenli mod etkinleştirildi" pluginsAreDisabledBecauseSafeMode: "Güvenli mod etkinleştirildiği için tüm eklentiler devre dışı bırakılmıştır." customCssIsDisabledBecauseSafeMode: "Güvenli mod etkin olduğu için özel CSS uygulanmıyor." themeIsDefaultBecauseSafeMode: "Güvenli mod etkinken, varsayılan tema kullanılır. Güvenli modu devre dışı bırakmak bu değişiklikleri geri alır." +thankYouForTestingBeta: "Beta sürümünü test ettiğin için teşekkür ederiz!" _order: newest: "Önce yeni" oldest: "Önce eski" @@ -1623,7 +1625,7 @@ _initialTutorial: _timelineDescription: home: "Ana Pano'da, takip ettiğin hesapların notlarını görebilirsin." local: "Yerel Pano'da, bu sunucudaki tüm kullanıcıların notlarını görebilirsin." - social: "Sosyal Pano, Ana Sayfa ve Yerel Pano'dan gelen notları görüntüler." + social: "Pano, Sosyal Pano ve Yerel Pano'dan gelen notları görüntüler." global: "Global Pano'da, bağlı tüm sunuculardan gelen notları görebilirsin." _serverRules: description: "Kayıt öncesinde gösterilecek bir dizi kural. Hizmet Şartlarının özetini belirlemen önerilir." @@ -1664,6 +1666,8 @@ _serverSettings: userGeneratedContentsVisibilityForVisitor_description2: "Sunucu tarafından alınan uzak içerik dahil olmak üzere sunucudaki tüm içeriği koşulsuz olarak İnternet'e yayınlamak risklidir. Bu, içeriğin dağıtılmış yapısından haberdar olmayan misafirler için özellikle önemlidir, çünkü onlar yanlışlıkla uzak içeriğin bile sunucudaki kullanıcılar tarafından oluşturulan içerik olduğunu düşünebilirler." restartServerSetupWizardConfirm_title: "Sunucu kurulum sihirbazını yeniden başlatmak ister misin?" restartServerSetupWizardConfirm_text: "Bazı mevcut ayarlar sıfırlanacaktır." + entrancePageStyle: "Giriş sayfası stili" + showTimelineForVisitor: "Panoyu göster" _userGeneratedContentsVisibilityForVisitor: all: "Her şey halka açıktır." localOnly: "Yalnızca yerel içerik yayınlanır, uzak içerik gizli tutulur." @@ -2273,6 +2277,7 @@ _time: minute: "Dakika(lar)" hour: "Saat(ler)" day: "Gün(ler)" + month: "Ay" _2fa: alreadyRegistered: "2fa kimlik doğrulama cihazını zaten kaydettin." registerTOTP: "Kimlik doğrulama uygulamasını kaydet" @@ -2478,7 +2483,7 @@ _poll: _visibility: public: "Halka açık" publicDescription: "Notunuz tüm kullanıcılar tarafından görülebilir olacaktır." - home: "Ana sayfa" + home: "Pano" homeDescription: "Yalnızca ana zaman çizelgesine gönder" followers: "Takipçiler" followersDescription: "Sadece takipçilerine görünür hale getir" @@ -2554,7 +2559,7 @@ _instanceCharts: files: "Dosya sayısındaki fark" filesTotal: "Toplam dosya sayısı" _timelines: - home: "Ana Sayfa" + home: "Pano" local: "Yerel" social: "Sosyal" global: "Global" @@ -2672,7 +2677,7 @@ _notification: chatRoomInvitationReceived: "Sohbet odasına davet edildi" achievementEarned: "Başarı kilidi açıldı" exportCompleted: "İhracat işlemi tamamlandı." - login: "Giriş Yap" + login: "Oturum Aç" createToken: "Erişim jetonu oluştur" test: "Bildirim testi" app: "Bağlı uygulamalardan gelen bildirimler" @@ -2709,7 +2714,7 @@ _deck: main: "Ana" widgets: "Widget'lar" notifications: "Bildirimler" - tl: "Ana Sayfa" + tl: "Pano" antenna: "Antenler" list: "Liste" channel: "Kanal" diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 8b4f1bfd5e..cb2f37bed7 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -1196,7 +1196,6 @@ showAvatarDecorations: "Hiển thị trang trí ảnh đại diện" releaseToRefresh: "Thả để làm mới" refreshing: "Đang làm mới" pullDownToRefresh: "Kéo xuống để làm mới" -signupPendingError: "Đã xảy ra sự cố khi xác minh địa chỉ email của bạn. Liên kết có thể đã hết hạn." cwNotationRequired: "Nếu \"Ẩn nội dung\" được bật thì cần phải có chú thích." decorate: "Trang trí" lastNDays: "{n} ngày trước" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 7fbb907783..6c62c80f0d 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1245,7 +1245,7 @@ releaseToRefresh: "松开以刷新" refreshing: "刷新中" pullDownToRefresh: "下拉以刷新" useGroupedNotifications: "分组显示通知" -signupPendingError: "确认电子邮件时出现错误。链接可能已过期。" +emailVerificationFailedError: "确认电子邮件时出现错误。链接可能已过期。" cwNotationRequired: "在启用「隐藏内容」时必须输入注释" doReaction: "回应" code: "代码" @@ -1668,7 +1668,7 @@ _serverSettings: restartServerSetupWizardConfirm_text: "现有的部分设定将重置。" entrancePageStyle: "入口页面样式" showTimelineForVisitor: "显示时间线" - showActivityiesForVisitor: "显示活动" + showActivitiesForVisitor: "显示活动" _userGeneratedContentsVisibilityForVisitor: all: "全部公开" localOnly: "仅公开本地内容,隐藏远程内容" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index ead814b7bb..65b7f9bfba 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1245,7 +1245,7 @@ releaseToRefresh: "放開以更新內容" refreshing: "載入更新中" pullDownToRefresh: "往下拉來更新內容" useGroupedNotifications: "分組顯示通知訊息" -signupPendingError: "驗證您的電子郵件地址時出現問題。連結可能已過期。" +emailVerificationFailedError: "驗證您的電子郵件地址時出現問題。連結可能已過期。" cwNotationRequired: "如果開啟「隱藏內容」,則需要註解說明。" doReaction: "做出反應" code: "程式碼" @@ -1668,7 +1668,7 @@ _serverSettings: restartServerSetupWizardConfirm_text: "當前的部分設定將會被重設。" entrancePageStyle: "入口頁面的樣式" showTimelineForVisitor: "顯示時間軸" - showActivityiesForVisitor: "顯示活動" + showActivitiesForVisitor: "顯示活動" _userGeneratedContentsVisibilityForVisitor: all: "全部公開\n" localOnly: "僅公開本地內容,遠端內容則不公開\n" diff --git a/package.json b/package.json index 85d3137563..1e7c8507cc 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "misskey", - "version": "2025.8.0-beta.2", + "version": "2025.9.0-alpha.2", "codename": "nasubi", "repository": { "type": "git", "url": "https://github.com/misskey-dev/misskey.git" }, - "packageManager": "pnpm@10.14.0", + "packageManager": "pnpm@10.15.0", "workspaces": [ "packages/frontend-shared", "packages/frontend", @@ -53,8 +53,8 @@ "lodash": "4.17.21" }, "dependencies": { - "cssnano": "7.1.0", - "esbuild": "0.25.8", + "cssnano": "7.1.1", + "esbuild": "0.25.9", "execa": "9.6.0", "fast-glob": "3.3.3", "glob": "11.0.3", @@ -67,15 +67,16 @@ }, "devDependencies": { "@misskey-dev/eslint-plugin": "2.1.0", - "@types/node": "22.17.1", - "@typescript-eslint/eslint-plugin": "8.39.0", - "@typescript-eslint/parser": "8.39.0", + "@types/js-yaml": "4.0.9", + "@types/node": "22.17.2", + "@typescript-eslint/eslint-plugin": "8.40.0", + "@typescript-eslint/parser": "8.40.0", "cross-env": "7.0.3", "cypress": "14.5.4", - "eslint": "9.33.0", + "eslint": "9.34.0", "globals": "16.3.0", "ncp": "2.0.0", - "pnpm": "10.14.0", + "pnpm": "10.15.0", "start-server-and-test": "2.0.13" }, "optionalDependencies": { diff --git a/packages/backend/migration/1756062689648-NonCascadingPageEyeCatching.js b/packages/backend/migration/1756062689648-NonCascadingPageEyeCatching.js new file mode 100644 index 0000000000..8554cc4304 --- /dev/null +++ b/packages/backend/migration/1756062689648-NonCascadingPageEyeCatching.js @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class NonCascadingPageEyeCatching1756062689648 { + name = 'NonCascadingPageEyeCatching1756062689648' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "page" DROP CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa"`); + await queryRunner.query(`ALTER TABLE "page" ADD CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa" FOREIGN KEY ("eyeCatchingImageId") REFERENCES "drive_file"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "page" DROP CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa"`); + await queryRunner.query(`ALTER TABLE "page" ADD CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa" FOREIGN KEY ("eyeCatchingImageId") REFERENCES "drive_file"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 39ea07125d..89e04f4da4 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -39,17 +39,17 @@ }, "optionalDependencies": { "@swc/core-android-arm64": "1.3.11", - "@swc/core-darwin-arm64": "1.13.3", - "@swc/core-darwin-x64": "1.13.3", + "@swc/core-darwin-arm64": "1.13.4", + "@swc/core-darwin-x64": "1.13.4", "@swc/core-freebsd-x64": "1.3.11", - "@swc/core-linux-arm-gnueabihf": "1.13.3", - "@swc/core-linux-arm64-gnu": "1.13.3", - "@swc/core-linux-arm64-musl": "1.13.3", - "@swc/core-linux-x64-gnu": "1.13.3", - "@swc/core-linux-x64-musl": "1.13.3", - "@swc/core-win32-arm64-msvc": "1.13.3", - "@swc/core-win32-ia32-msvc": "1.13.3", - "@swc/core-win32-x64-msvc": "1.13.3", + "@swc/core-linux-arm-gnueabihf": "1.13.4", + "@swc/core-linux-arm64-gnu": "1.13.4", + "@swc/core-linux-arm64-musl": "1.13.4", + "@swc/core-linux-x64-gnu": "1.13.4", + "@swc/core-linux-x64-musl": "1.13.4", + "@swc/core-win32-arm64-msvc": "1.13.4", + "@swc/core-win32-ia32-msvc": "1.13.4", + "@swc/core-win32-x64-msvc": "1.13.4", "@tensorflow/tfjs": "4.22.0", "@tensorflow/tfjs-node": "4.22.0", "bufferutil": "4.0.9", @@ -69,8 +69,8 @@ "utf-8-validate": "6.0.5" }, "dependencies": { - "@aws-sdk/client-s3": "3.864.0", - "@aws-sdk/lib-storage": "3.864.0", + "@aws-sdk/client-s3": "3.873.0", + "@aws-sdk/lib-storage": "3.873.0", "@discordapp/twemoji": "16.0.1", "@fastify/accepts": "5.0.2", "@fastify/cookie": "11.0.2", @@ -93,7 +93,7 @@ "@sinonjs/fake-timers": "11.3.1", "@smithy/node-http-handler": "2.5.0", "@swc/cli": "0.7.8", - "@swc/core": "1.13.3", + "@swc/core": "1.13.4", "@twemoji/parser": "16.0.0", "@types/redis-info": "3.0.3", "accepts": "1.3.8", @@ -103,10 +103,10 @@ "bcryptjs": "2.4.3", "blurhash": "2.0.5", "body-parser": "1.20.3", - "bullmq": "5.56.9", + "bullmq": "5.58.1", "cacheable-lookup": "7.0.0", "cbor": "9.0.2", - "chalk": "5.5.0", + "chalk": "5.6.0", "chalk-template": "1.1.0", "chokidar": "4.0.3", "cli-highlight": "2.1.11", @@ -114,7 +114,7 @@ "content-disposition": "0.5.4", "date-fns": "2.30.0", "deep-email-validator": "0.1.21", - "fastify": "5.4.0", + "fastify": "5.5.0", "fastify-raw-body": "5.0.0", "feed": "4.2.2", "file-type": "19.6.0", @@ -135,7 +135,7 @@ "jsonld": "8.3.3", "jsrsasign": "11.1.0", "juice": "11.0.1", - "meilisearch": "0.51.0", + "meilisearch": "0.52.0", "mfm-js": "0.25.0", "microformats-parser": "2.0.4", "mime-types": "2.1.35", @@ -151,7 +151,7 @@ "oauth2orize": "1.12.0", "oauth2orize-pkce": "0.1.2", "os-utils": "0.0.14", - "otpauth": "9.4.0", + "otpauth": "9.4.1", "parse5": "7.3.0", "pg": "8.16.3", "pkce-challenge": "4.1.0", @@ -177,10 +177,10 @@ "stringz": "2.1.0", "systeminformation": "5.27.7", "tinycolor2": "1.6.0", - "tmp": "0.2.4", + "tmp": "0.2.5", "tsc-alias": "1.8.16", "tsconfig-paths": "4.2.0", - "typeorm": "0.3.25", + "typeorm": "0.3.26", "typescript": "5.9.2", "ulid": "2.4.0", "vary": "1.1.2", @@ -191,7 +191,7 @@ "devDependencies": { "@jest/globals": "29.7.0", "@nestjs/platform-express": "10.4.20", - "@sentry/vue": "9.45.0", + "@sentry/vue": "9.46.0", "@simplewebauthn/types": "12.0.0", "@swc/jest": "0.2.39", "@types/accepts": "1.3.7", @@ -210,8 +210,8 @@ "@types/jsrsasign": "10.5.15", "@types/mime-types": "2.1.4", "@types/ms": "0.7.34", - "@types/node": "22.17.1", - "@types/nodemailer": "6.4.17", + "@types/node": "22.17.2", + "@types/nodemailer": "6.4.19", "@types/oauth": "0.9.6", "@types/oauth2orize": "1.11.5", "@types/oauth2orize-pkce": "0.1.2", @@ -231,8 +231,8 @@ "@types/vary": "1.1.3", "@types/web-push": "3.6.4", "@types/ws": "8.18.1", - "@typescript-eslint/eslint-plugin": "8.39.0", - "@typescript-eslint/parser": "8.39.0", + "@typescript-eslint/eslint-plugin": "8.40.0", + "@typescript-eslint/parser": "8.40.0", "aws-sdk-client-mock": "4.1.0", "cross-env": "7.0.3", "eslint-plugin-import": "2.32.0", diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts index 248a9b8979..23ab8082ed 100644 --- a/packages/backend/src/core/AiService.ts +++ b/packages/backend/src/core/AiService.ts @@ -29,7 +29,7 @@ export class AiService { } @bindThis - public async detectSensitive(path: string): Promise { + public async detectSensitive(source: string | Buffer): Promise { try { if (isSupportedCpu === undefined) { isSupportedCpu = await this.computeIsSupportedCpu(); @@ -51,7 +51,7 @@ export class AiService { }); } - const buffer = await fs.promises.readFile(path); + const buffer = source instanceof Buffer ? source : await fs.promises.readFile(source); const image = await tf.node.decodeImage(buffer, 3) as any; try { const predictions = await this.model.classify(image); diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts index 6250d4d3a1..62a7d24afb 100644 --- a/packages/backend/src/core/FileInfoService.ts +++ b/packages/backend/src/core/FileInfoService.ts @@ -21,6 +21,7 @@ import { LoggerService } from '@/core/LoggerService.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import type { PredictionType } from 'nsfwjs'; +import { isMimeImage } from '@/misc/is-mime-image.js'; export type FileInfo = { size: number; @@ -204,16 +205,7 @@ export class FileInfoService { return [sensitive, porn]; } - if ([ - 'image/jpeg', - 'image/png', - 'image/webp', - ].includes(mime)) { - const result = await this.aiService.detectSensitive(source); - if (result) { - [sensitive, porn] = judgePrediction(result); - } - } else if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) { + if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) { const [outDir, disposeOutDir] = await createTempDir(); try { const command = FFmpeg() @@ -281,6 +273,23 @@ export class FileInfoService { } finally { disposeOutDir(); } + } else if (isMimeImage(mime, 'sharp-convertible-image-with-bmp')) { + /* + * tfjs-node は限られた画像形式しか受け付けないため、sharp で PNG に変換する + * せっかくなので内部処理で使われる最大サイズの299x299に事前にリサイズする + */ + const png = await (await sharpBmp(source, mime)) + .resize(299, 299, { + withoutEnlargement: false, + }) + .rotate() + .flatten({ background: { r: 119, g: 119, b: 119 } }) // 透過部分を18%グレーで塗りつぶす + .png() + .toBuffer(); + const result = await this.aiService.detectSensitive(png); + if (result) { + [sensitive, porn] = judgePrediction(result); + } } return [sensitive, porn]; diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 3df7ee69ee..7dc07ef4dd 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -31,6 +31,7 @@ import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import { NotificationService } from '@/core/NotificationService.js'; import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common'; +// misskey-js の rolePolicies と同期すべし export type RolePolicies = { gtlAvailable: boolean; ltlAvailable: boolean; diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts index 907b5ea6be..6714bda9a9 100644 --- a/packages/backend/src/core/WebhookTestService.ts +++ b/packages/backend/src/core/WebhookTestService.ts @@ -244,7 +244,6 @@ export class WebhookTestService { case 'reaction': return; default: { - // eslint-disable-next-line @typescript-eslint/no-unused-vars const _exhaustiveAssertion: never = params.type; return; } @@ -327,7 +326,6 @@ export class WebhookTestService { break; } default: { - // eslint-disable-next-line @typescript-eslint/no-unused-vars const _exhaustiveAssertion: never = params.type; return; } @@ -412,7 +410,7 @@ export class WebhookTestService { name: user.name, username: user.username, host: user.host, - avatarUrl: user.avatarId == null ? null : user.avatarUrl, + avatarUrl: (user.avatarId == null ? null : user.avatarUrl) ?? '', avatarBlurhash: user.avatarId == null ? null : user.avatarBlurhash, avatarDecorations: user.avatarDecorations.map(it => ({ id: it.id, diff --git a/packages/backend/src/core/entities/ChatEntityService.ts b/packages/backend/src/core/entities/ChatEntityService.ts index 6bce2413fd..cfa983e766 100644 --- a/packages/backend/src/core/entities/ChatEntityService.ts +++ b/packages/backend/src/core/entities/ChatEntityService.ts @@ -54,12 +54,13 @@ export class ChatEntityService { const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src }); - const reactions: { user: Packed<'UserLite'>; reaction: string; }[] = []; + // userは削除されている可能性があるのでnull許容 + const reactions: { user: Packed<'UserLite'> | null; reaction: string; }[] = []; for (const record of message.reactions) { const [userId, reaction] = record.split('/'); reactions.push({ - user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId), + user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId).catch(() => null), reaction, }); } @@ -76,7 +77,7 @@ export class ChatEntityService { toRoom: message.toRoomId ? (packedRooms?.get(message.toRoomId) ?? await this.packRoom(message.toRoom ?? message.toRoomId, me)) : undefined, fileId: message.fileId, file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null, - reactions, + reactions: reactions.filter((r): r is { user: Packed<'UserLite'>; reaction: string; } => r.user != null), }; } @@ -108,6 +109,7 @@ export class ChatEntityService { } } + // TODO: packedUsersに削除されたユーザーもnullとして含める const [packedUsers, packedFiles, packedRooms] = await Promise.all([ this.userEntityService.packMany(users, me) .then(users => new Map(users.map(u => [u.id, u]))), @@ -183,12 +185,13 @@ export class ChatEntityService { const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src }); - const reactions: { user: Packed<'UserLite'>; reaction: string; }[] = []; + // userは削除されている可能性があるのでnull許容 + const reactions: { user: Packed<'UserLite'> | null; reaction: string; }[] = []; for (const record of message.reactions) { const [userId, reaction] = record.split('/'); reactions.push({ - user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId), + user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId).catch(() => null), reaction, }); } @@ -202,7 +205,7 @@ export class ChatEntityService { toRoomId: message.toRoomId!, fileId: message.fileId, file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null, - reactions, + reactions: reactions.filter((r): r is { user: Packed<'UserLite'>; reaction: string; } => r.user != null), }; } diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts index 46ec13704c..54ce4d472a 100644 --- a/packages/backend/src/core/entities/NoteReactionEntityService.ts +++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts @@ -49,15 +49,12 @@ export class NoteReactionEntityService implements OnModuleInit { public async pack( src: MiNoteReaction['id'] | MiNoteReaction, me?: { id: MiUser['id'] } | null | undefined, - options?: { - withNote: boolean; - }, + options?: object, hints?: { packedUser?: Packed<'UserLite'> }, ): Promise> { const opts = Object.assign({ - withNote: false, }, options); const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src }); @@ -67,9 +64,6 @@ export class NoteReactionEntityService implements OnModuleInit { createdAt: this.idService.parse(reaction.id).date.toISOString(), user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me), type: this.reactionService.convertLegacyReaction(reaction.reaction), - ...(opts.withNote ? { - note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me), - } : {}), }; } @@ -77,16 +71,50 @@ export class NoteReactionEntityService implements OnModuleInit { public async packMany( reactions: MiNoteReaction[], me?: { id: MiUser['id'] } | null | undefined, - options?: { - withNote: boolean; - }, + options?: object, ): Promise[]> { const opts = Object.assign({ - withNote: false, }, options); const _users = reactions.map(({ user, userId }) => user ?? userId); const _userMap = await this.userEntityService.packMany(_users, me) .then(users => new Map(users.map(u => [u.id, u]))); return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) }))); } + + @bindThis + public async packWithNote( + src: MiNoteReaction['id'] | MiNoteReaction, + me?: { id: MiUser['id'] } | null | undefined, + options?: object, + hints?: { + packedUser?: Packed<'UserLite'> + }, + ): Promise> { + const opts = Object.assign({ + }, options); + + const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src }); + + return { + id: reaction.id, + createdAt: this.idService.parse(reaction.id).date.toISOString(), + user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me), + type: this.reactionService.convertLegacyReaction(reaction.reaction), + note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me), + }; + } + + @bindThis + public async packManyWithNote( + reactions: MiNoteReaction[], + me?: { id: MiUser['id'] } | null | undefined, + options?: object, + ): Promise[]> { + const opts = Object.assign({ + }, options); + const _users = reactions.map(({ user, userId }) => user ?? userId); + const _userMap = await this.userEntityService.packMany(_users, me) + .then(users => new Map(users.map(u => [u.id, u]))); + return Promise.all(reactions.map(reaction => this.packWithNote(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) }))); + } } diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index d4769d24d4..47021359e1 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -471,8 +471,8 @@ export class UserEntityService implements OnModuleInit { (profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount : null; - const isModerator = isMe && isDetailed ? this.roleService.isModerator(user) : null; - const isAdmin = isMe && isDetailed ? this.roleService.isAdministrator(user) : null; + const isModerator = isMe && isDetailed ? this.roleService.isModerator(user) : undefined; + const isAdmin = isMe && isDetailed ? this.roleService.isAdministrator(user) : undefined; const unreadAnnouncements = isMe && isDetailed ? (await this.announcementService.getUnreadAnnouncements(user)).map((announcement) => ({ createdAt: this.idService.parse(announcement.id).date.toISOString(), @@ -481,6 +481,7 @@ export class UserEntityService implements OnModuleInit { const notificationsInfo = isMe && isDetailed ? await this.getNotificationsInfo(user.id) : null; + // TODO: 例えば avatarUrl: true など間違った型を設定しても型エラーにならないのをどうにかする(ジェネリクス使わない方法で実装するしかなさそう?) const packed = { id: user.id, name: user.name, diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index ed47edff9b..ed7d5bfc3a 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -22,7 +22,7 @@ import { packedFollowingSchema } from '@/models/json-schema/following.js'; import { packedMutingSchema } from '@/models/json-schema/muting.js'; import { packedRenoteMutingSchema } from '@/models/json-schema/renote-muting.js'; import { packedBlockingSchema } from '@/models/json-schema/blocking.js'; -import { packedNoteReactionSchema } from '@/models/json-schema/note-reaction.js'; +import { packedNoteReactionSchema, packedNoteReactionWithNoteSchema } from '@/models/json-schema/note-reaction.js'; import { packedHashtagSchema } from '@/models/json-schema/hashtag.js'; import { packedInviteCodeSchema } from '@/models/json-schema/invite-code.js'; import { packedPageBlockSchema, packedPageSchema } from '@/models/json-schema/page.js'; @@ -65,6 +65,7 @@ import { packedMetaDetailedSchema, packedMetaLiteSchema, } from '@/models/json-schema/meta.js'; +import { packedUserWebhookSchema } from '@/models/json-schema/user-webhook.js'; import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js'; import { packedAbuseReportNotificationRecipientSchema } from '@/models/json-schema/abuse-report-notification-recipient.js'; import { packedChatMessageSchema, packedChatMessageLiteSchema, packedChatMessageLiteForRoomSchema, packedChatMessageLiteFor1on1Schema } from '@/models/json-schema/chat-message.js'; @@ -92,6 +93,7 @@ export const refs = { Note: packedNoteSchema, NoteDraft: packedNoteDraftSchema, NoteReaction: packedNoteReactionSchema, + NoteReactionWithNote: packedNoteReactionWithNoteSchema, NoteFavorite: packedNoteFavoriteSchema, Notification: packedNotificationSchema, DriveFile: packedDriveFileSchema, @@ -133,6 +135,7 @@ export const refs = { MetaLite: packedMetaLiteSchema, MetaDetailedOnly: packedMetaDetailedOnlySchema, MetaDetailed: packedMetaDetailedSchema, + UserWebhook: packedUserWebhookSchema, SystemWebhook: packedSystemWebhookSchema, AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema, ChatMessage: packedChatMessageSchema, diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts index 5764a307b0..0b4eeb3455 100644 --- a/packages/backend/src/models/Notification.ts +++ b/packages/backend/src/models/Notification.ts @@ -10,6 +10,7 @@ import { MiAccessToken } from './AccessToken.js'; import { MiRole } from './Role.js'; import { MiDriveFile } from './DriveFile.js'; +// misskey-js の notificationTypes と同期すべし export type MiNotification = { type: 'note'; id: string; diff --git a/packages/backend/src/models/Page.ts b/packages/backend/src/models/Page.ts index 0b59e7a92c..d112a66c04 100644 --- a/packages/backend/src/models/Page.ts +++ b/packages/backend/src/models/Page.ts @@ -69,7 +69,7 @@ export class MiPage { public eyeCatchingImageId: MiDriveFile['id'] | null; @ManyToOne(type => MiDriveFile, { - onDelete: 'CASCADE', + onDelete: 'SET NULL', }) @JoinColumn() public eyeCatchingImage: MiDriveFile | null; diff --git a/packages/backend/src/models/json-schema/note-reaction.ts b/packages/backend/src/models/json-schema/note-reaction.ts index 95658ace1f..04c9f34232 100644 --- a/packages/backend/src/models/json-schema/note-reaction.ts +++ b/packages/backend/src/models/json-schema/note-reaction.ts @@ -10,7 +10,6 @@ export const packedNoteReactionSchema = { type: 'string', optional: false, nullable: false, format: 'id', - example: 'xxxxxxxxxx', }, createdAt: { type: 'string', @@ -28,3 +27,33 @@ export const packedNoteReactionSchema = { }, }, } as const; + +export const packedNoteReactionWithNoteSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + user: { + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', + }, + type: { + type: 'string', + optional: false, nullable: false, + }, + note: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', + }, + }, +} as const; diff --git a/packages/backend/src/models/json-schema/user-webhook.ts b/packages/backend/src/models/json-schema/user-webhook.ts new file mode 100644 index 0000000000..8ea0991716 --- /dev/null +++ b/packages/backend/src/models/json-schema/user-webhook.ts @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { webhookEventTypes } from '@/models/Webhook.js'; + +export const packedUserWebhookSchema = { + type: 'object', + properties: { + id: { + type: 'string', + format: 'id', + optional: false, nullable: false, + }, + userId: { + type: 'string', + format: 'id', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + on: { + type: 'array', + items: { + type: 'string', + optional: false, nullable: false, + enum: webhookEventTypes, + }, + }, + url: { + type: 'string', + optional: false, nullable: false, + }, + secret: { + type: 'string', + optional: false, nullable: false, + }, + active: { + type: 'boolean', + optional: false, nullable: false, + }, + latestSentAt: { + type: 'string', + format: 'date-time', + optional: false, nullable: true, + }, + latestStatus: { + type: 'integer', + optional: false, nullable: true, + }, + }, +} as const; diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 2b5f706ff9..c507d8d5c6 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -65,7 +65,7 @@ export const packedUserLiteSchema = { avatarUrl: { type: 'string', format: 'url', - nullable: true, optional: false, + nullable: false, optional: false, }, avatarBlurhash: { type: 'string', @@ -465,11 +465,11 @@ export const packedMeDetailedOnlySchema = { }, isModerator: { type: 'boolean', - nullable: true, optional: false, + nullable: false, optional: false, }, isAdmin: { type: 'boolean', - nullable: true, optional: false, + nullable: false, optional: false, }, injectFeaturedNote: { type: 'boolean', @@ -591,7 +591,7 @@ export const packedMeDetailedOnlySchema = { }, mutedInstances: { type: 'array', - nullable: true, optional: false, + nullable: false, optional: false, items: { type: 'string', nullable: false, optional: false, diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index 32818003ad..57d74ef2b1 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -176,6 +176,17 @@ export class ApiServerService { } }); + fastify.all('/clear-browser-cache', (request, reply) => { + if (['GET', 'POST'].includes(request.method)) { + reply.header('Clear-Site-Data', '"cache", "prefetchCache", "prerenderCache", "executionContexts"'); + reply.code(204); + reply.send(); + } else { + reply.code(405); + reply.send(); + } + }); + // Make sure any unknown path under /api returns HTTP 404 Not Found, // because otherwise ClientServerService will return the base client HTML // page with HTTP 200. diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 81a788de2b..804bd5d9b9 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -49,6 +49,34 @@ export const meta = { type: 'string', optional: false, nullable: false, }, + icon: { + type: 'string', + optional: false, nullable: true, + }, + display: { + type: 'string', + optional: false, nullable: false, + }, + isActive: { + type: 'boolean', + optional: false, nullable: false, + }, + forExistingUsers: { + type: 'boolean', + optional: false, nullable: false, + }, + silence: { + type: 'boolean', + optional: false, nullable: false, + }, + needConfirmationToRead: { + type: 'boolean', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: true, + }, imageUrl: { type: 'string', optional: false, nullable: true, diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index b84a5c73f9..e7a70d0762 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -157,6 +157,22 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + maybeSensitive: { + type: 'boolean', + optional: false, nullable: false, + }, + maybePorn: { + type: 'boolean', + optional: false, nullable: false, + }, + requestIp: { + type: 'string', + optional: false, nullable: true, + }, + requestHeaders: { + type: 'object', + optional: false, nullable: true, + }, }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 6ec908d5bf..21099c0a8c 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -223,10 +223,12 @@ export const meta = { sensitiveMediaDetection: { type: 'string', optional: false, nullable: false, + enum: ['none', 'all', 'local', 'remote'], }, sensitiveMediaDetectionSensitivity: { type: 'string', optional: false, nullable: false, + enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'], }, setSensitiveFlagAutomatically: { type: 'boolean', @@ -473,6 +475,10 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + feedbackUrl: { + type: 'string', + optional: false, nullable: true, + }, summalyProxy: { type: 'string', optional: false, nullable: true, diff --git a/packages/backend/src/server/api/endpoints/flash/update.ts b/packages/backend/src/server/api/endpoints/flash/update.ts index e378669f0a..8696c6f6e8 100644 --- a/packages/backend/src/server/api/endpoints/flash/update.ts +++ b/packages/backend/src/server/api/endpoints/flash/update.ts @@ -73,8 +73,8 @@ export default class extends Endpoint { // eslint- updatedAt: new Date(), ...Object.fromEntries( Object.entries(ps).filter( - ([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key) - ) + ([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key), + ), ), }); }); diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index 055b5cc061..523d81ac73 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -46,6 +46,14 @@ export const meta = { type: 'string', }, }, + iconUrl: { + type: 'string', + optional: true, nullable: true, + }, + description: { + type: 'string', + optional: true, nullable: true, + }, }, }, }, @@ -88,6 +96,8 @@ export default class extends Endpoint { // eslint- createdAt: this.idService.parse(token.id).date.toISOString(), lastUsedAt: token.lastUsedAt?.toISOString(), permission: token.app ? token.app.permission : token.permission, + iconUrl: token.iconUrl, + description: token.description ?? token.app?.description ?? null, }))); }); } diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts index 394c178f2a..8a3ba9e026 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts @@ -21,29 +21,7 @@ export const meta = { type: 'array', items: { type: 'object', - properties: { - id: { - type: 'string', - format: 'misskey:id', - }, - userId: { - type: 'string', - format: 'misskey:id', - }, - name: { type: 'string' }, - on: { - type: 'array', - items: { - type: 'string', - enum: webhookEventTypes, - }, - }, - url: { type: 'string' }, - secret: { type: 'string' }, - active: { type: 'boolean' }, - latestSentAt: { type: 'string', format: 'date-time', nullable: true }, - latestStatus: { type: 'integer', nullable: true }, - }, + ref: 'UserWebhook', }, }, } as const; @@ -65,19 +43,17 @@ export default class extends Endpoint { // eslint- userId: me.id, }); - return webhooks.map(webhook => ( - { - id: webhook.id, - userId: webhook.userId, - name: webhook.name, - on: webhook.on, - url: webhook.url, - secret: webhook.secret, - active: webhook.active, - latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null, - latestStatus: webhook.latestStatus, - } - )); + return webhooks.map(webhook => ({ + id: webhook.id, + userId: webhook.userId, + name: webhook.name, + on: webhook.on, + url: webhook.url, + secret: webhook.secret, + active: webhook.active, + latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null, + latestStatus: webhook.latestStatus, + })); }); } } diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts index 4a0c09ff0c..1c19081c98 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts @@ -28,29 +28,7 @@ export const meta = { res: { type: 'object', - properties: { - id: { - type: 'string', - format: 'misskey:id', - }, - userId: { - type: 'string', - format: 'misskey:id', - }, - name: { type: 'string' }, - on: { - type: 'array', - items: { - type: 'string', - enum: webhookEventTypes, - }, - }, - url: { type: 'string' }, - secret: { type: 'string' }, - active: { type: 'boolean' }, - latestSentAt: { type: 'string', format: 'date-time', nullable: true }, - latestStatus: { type: 'integer', nullable: true }, - }, + ref: 'UserWebhook', }, } as const; diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 1c73edf08e..7fa8004209 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -91,6 +91,7 @@ export default class extends Endpoint { // eslint- qb.orWhere(new Brackets(qb => { qb.where('note.text IS NOT NULL'); qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); })); })); } diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 1f3631ae3d..eeeb797efc 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -242,6 +242,7 @@ export default class extends Endpoint { // eslint- qb.orWhere(new Brackets(qb => { qb.orWhere('note.text IS NOT NULL'); qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); })); })); } diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 614cd9204d..42e80c6ae1 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -223,6 +223,7 @@ export default class extends Endpoint { // eslint- qb.orWhere(new Brackets(qb => { qb.orWhere('note.text IS NOT NULL'); qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); })); })); } diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index 8756801fe4..ed5952d4c5 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -23,6 +23,16 @@ export const meta = { type: 'object', optional: false, nullable: false, ref: 'UserList', + properties: { + likedCount: { + type: 'number', + optional: true, nullable: false, + }, + isLiked: { + type: 'boolean', + optional: true, nullable: false, + }, + }, }, errors: { diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index d6f1ecd8ed..d84a191f7a 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -28,7 +28,7 @@ export const meta = { items: { type: 'object', optional: false, nullable: false, - ref: 'NoteReaction', + ref: 'NoteReactionWithNote', }, }, @@ -120,7 +120,7 @@ export default class extends Endpoint { // eslint- return true; }); - return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true }); + return await this.noteReactionEntityService.packManyWithNote(reactions, me); }); } } diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index b515a0c0c8..3cd83efa1a 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -201,6 +201,8 @@ export class ClientServerService { @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { + const configUrl = new URL(this.config.url); + fastify.register(fastifyView, { root: _dirname + '/views', engine: { @@ -239,7 +241,6 @@ export class ClientServerService { done(); }); } else { - const configUrl = new URL(this.config.url); const urlOriginWithoutPort = configUrl.origin.replace(/:\d+$/, ''); const port = (process.env.VITE_PORT ?? '5173'); @@ -887,6 +888,22 @@ export class ClientServerService { [, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); fastify.get('/flush', async (request, reply) => { + let sendHeader = true; + + if (request.headers['origin']) { + const originURL = new URL(request.headers['origin']); + if (originURL.protocol !== 'https:') { // Clear-Site-Data only supports https + sendHeader = false; + } + if (originURL.host !== configUrl.host) { + sendHeader = false; + } + } + + if (sendHeader) { + reply.header('Clear-Site-Data', '"*"'); + } + reply.header('Set-Cookie', 'http-flush-failed=1; Path=/flush; Max-Age=60'); return await reply.view('flush'); }); diff --git a/packages/backend/src/server/web/views/flush.pug b/packages/backend/src/server/web/views/flush.pug index a73a45212f..7884495d08 100644 --- a/packages/backend/src/server/web/views/flush.pug +++ b/packages/backend/src/server/web/views/flush.pug @@ -6,41 +6,45 @@ html const msg = document.getElementById('msg'); const successText = `\nSuccess Flush! Back to Misskey\n成功しました。Misskeyを開き直してください。`; - message('Start flushing.'); + if (!document.cookie) { + message('Your site data is fully cleared by your browser.'); + message(successText); + } else { + message('Your browser does not support Clear-Site-Data header. Start opportunistic flushing.'); + (async function() { + try { + localStorage.clear(); + message('localStorage cleared.'); - (async function() { - try { - localStorage.clear(); - message('localStorage cleared.'); + const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => { + const delidb = indexedDB.deleteDatabase(name); + delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`)); + delidb.onerror = e => rej(e) + })); - const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => { - const delidb = indexedDB.deleteDatabase(name); - delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`)); - delidb.onerror = e => rej(e) - })); + await Promise.all(idbPromises); - await Promise.all(idbPromises); + if (navigator.serviceWorker.controller) { + navigator.serviceWorker.controller.postMessage('clear'); + await navigator.serviceWorker.getRegistrations() + .then(registrations => { + return Promise.all(registrations.map(registration => registration.unregister())); + }) + .catch(e => { throw new Error(e) }); + } - if (navigator.serviceWorker.controller) { - navigator.serviceWorker.controller.postMessage('clear'); - await navigator.serviceWorker.getRegistrations() - .then(registrations => { - return Promise.all(registrations.map(registration => registration.unregister())); - }) - .catch(e => { throw new Error(e) }); + message(successText); + } catch (e) { + message(`\n${e}\n\nFlush Failed. Please retry.\n失敗しました。もう一度試してみてください。`); + message(`\nIf you retry more than 3 times, try manually clearing the browser cache or contact to instance admin.\n3回以上試しても失敗する場合、ブラウザのキャッシュを手動で消去し、それでもだめならインスタンス管理者に連絡してみてください。\n`) + + console.error(e); + setTimeout(() => { + location = '/'; + }, 10000) } - - message(successText); - } catch (e) { - message(`\n${e}\n\nFlush Failed. Please retry.\n失敗しました。もう一度試してみてください。`); - message(`\nIf you retry more than 3 times, clear the browser cache or contact to instance admin.\n3回以上試しても失敗する場合、ブラウザのキャッシュを消去し、それでもだめならインスタンス管理者に連絡してみてください。\n`) - - console.error(e); - setTimeout(() => { - location = '/'; - }, 10000) - } - })(); + })(); + } function message(text) { msg.insertAdjacentHTML('beforeend', `[${(new Date()).toString()}] ${text.replace(/\n/g,'')}`) diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json index f9d1330ae5..73bcd798f0 100644 --- a/packages/frontend-embed/package.json +++ b/packages/frontend-embed/package.json @@ -16,7 +16,7 @@ "@rollup/pluginutils": "5.2.0", "@twemoji/parser": "16.0.0", "@vitejs/plugin-vue": "6.0.1", - "@vue/compiler-sfc": "3.5.18", + "@vue/compiler-sfc": "3.5.19", "astring": "1.9.0", "buraha": "0.0.1", "estree-walker": "3.0.3", @@ -26,16 +26,16 @@ "mfm-js": "0.25.0", "misskey-js": "workspace:*", "punycode.js": "2.3.1", - "rollup": "4.46.2", - "sass": "1.89.2", - "shiki": "3.9.1", + "rollup": "4.48.0", + "sass": "1.90.0", + "shiki": "3.11.0", "tinycolor2": "1.6.0", "tsc-alias": "1.8.16", "tsconfig-paths": "4.2.0", "typescript": "5.9.2", "uuid": "11.1.0", - "vite": "7.0.6", - "vue": "3.5.18" + "vite": "7.1.3", + "vue": "3.5.19" }, "devDependencies": { "@misskey-dev/summaly": "5.2.3", @@ -43,14 +43,14 @@ "@testing-library/vue": "8.1.0", "@types/estree": "1.0.8", "@types/micromatch": "4.0.9", - "@types/node": "22.17.0", + "@types/node": "22.17.2", "@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/tinycolor2": "1.4.6", "@types/ws": "8.18.1", - "@typescript-eslint/eslint-plugin": "8.38.0", - "@typescript-eslint/parser": "8.38.0", + "@typescript-eslint/eslint-plugin": "8.40.0", + "@typescript-eslint/parser": "8.40.0", "@vitest/coverage-v8": "3.2.4", - "@vue/runtime-core": "3.5.18", + "@vue/runtime-core": "3.5.19", "acorn": "8.15.0", "cross-env": "10.0.0", "eslint-plugin-import": "2.32.0", @@ -59,14 +59,14 @@ "happy-dom": "18.0.1", "intersection-observer": "0.12.2", "micromatch": "4.0.8", - "msw": "2.10.4", + "msw": "2.10.5", "nodemon": "3.1.10", "prettier": "3.6.2", - "start-server-and-test": "2.0.12", - "tsx": "4.20.3", + "start-server-and-test": "2.0.13", + "tsx": "4.20.4", "vite-plugin-turbosnap": "1.0.3", - "vue-component-type-helpers": "3.0.5", + "vue-component-type-helpers": "3.0.6", "vue-eslint-parser": "10.2.0", - "vue-tsc": "3.0.5" + "vue-tsc": "3.0.6" } } diff --git a/packages/frontend-embed/src/theme.ts b/packages/frontend-embed/src/theme.ts index 680ab80167..c9b1c0d0c6 100644 --- a/packages/frontend-embed/src/theme.ts +++ b/packages/frontend-embed/src/theme.ts @@ -3,6 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +// TODO: (可能な部分を)sharedに抽出して frontend と共通化 + import tinycolor from 'tinycolor2'; import lightTheme from '@@/themes/_light.json5'; import darkTheme from '@@/themes/_dark.json5'; diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts index 5c33c38f44..c8c437afe9 100644 --- a/packages/frontend-shared/js/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -54,67 +54,6 @@ https://github.com/sindresorhus/file-type/blob/main/core.js https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers */ -export const notificationTypes = [ - 'note', - 'follow', - 'mention', - 'reply', - 'renote', - 'quote', - 'reaction', - 'pollEnded', - 'receiveFollowRequest', - 'followRequestAccepted', - 'roleAssigned', - 'chatRoomInvitationReceived', - 'achievementEarned', - 'exportCompleted', - 'login', - 'createToken', - 'test', - 'app', -] as const; -export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const; - -export const ROLE_POLICIES = [ - 'gtlAvailable', - 'ltlAvailable', - 'canPublicNote', - 'mentionLimit', - 'canInvite', - 'inviteLimit', - 'inviteLimitCycle', - 'inviteExpirationTime', - 'canManageCustomEmojis', - 'canManageAvatarDecorations', - 'canSearchNotes', - 'canUseTranslator', - 'canHideAds', - 'driveCapacityMb', - 'maxFileSizeMb', - 'alwaysMarkNsfw', - 'canUpdateBioMedia', - 'pinLimit', - 'antennaLimit', - 'wordMuteLimit', - 'webhookLimit', - 'clipLimit', - 'noteEachClipsLimit', - 'userListLimit', - 'userEachUserListsLimit', - 'rateLimitFactor', - 'avatarDecorationLimit', - 'canImportAntennas', - 'canImportBlocking', - 'canImportFollowing', - 'canImportMuting', - 'canImportUserLists', - 'chatAvailability', - 'uploadableFileTypes', - 'noteDraftLimit', - 'watermarkAvailable', -] as const; - export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime']; export const MFM_PARAMS: Record = { tada: ['speed=', 'delay='], diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json index 7452ecc918..ea2d66a7c7 100644 --- a/packages/frontend-shared/package.json +++ b/packages/frontend-shared/package.json @@ -21,10 +21,10 @@ "lint": "pnpm typecheck && pnpm eslint" }, "devDependencies": { - "@types/node": "22.17.0", - "@typescript-eslint/eslint-plugin": "8.38.0", - "@typescript-eslint/parser": "8.38.0", - "esbuild": "0.25.8", + "@types/node": "22.17.2", + "@typescript-eslint/eslint-plugin": "8.40.0", + "@typescript-eslint/parser": "8.40.0", + "esbuild": "0.25.9", "eslint-plugin-vue": "10.4.0", "nodemon": "3.1.10", "typescript": "5.9.2", @@ -35,6 +35,6 @@ ], "dependencies": { "misskey-js": "workspace:*", - "vue": "3.5.18" + "vue": "3.5.19" } } diff --git a/packages/frontend/.storybook/charts.ts b/packages/frontend/.storybook/charts.ts index 31bb9e51c5..93e1287d69 100644 --- a/packages/frontend/.storybook/charts.ts +++ b/packages/frontend/.storybook/charts.ts @@ -6,7 +6,7 @@ import { HttpResponse, http } from 'msw'; import type { DefaultBodyType, HttpResponseResolver, JsonBodyType, PathParams } from 'msw'; import seedrandom from 'seedrandom'; -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; function getChartArray(seed: string, limit: number, option?: { accumulate?: boolean, mul?: number }): number[] { const rng = seedrandom(seed); diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts index 91ef41eedf..ed29c63471 100644 --- a/packages/frontend/.storybook/fakes.ts +++ b/packages/frontend/.storybook/fakes.ts @@ -127,7 +127,7 @@ export function galleryPost(isSensitive = false) { } } -export function file(isSensitive = false) { +export function file(isSensitive = false): entities.DriveFile { return { id: 'somefileid', createdAt: '2016-12-28T22:49:51.000Z', @@ -207,6 +207,7 @@ export function federationInstance(): entities.FederationInstance { isSuspended: false, suspensionState: 'none', isBlocked: false, + isMediaSilenced: false, softwareName: 'misskey', softwareVersion: '2024.5.0', openRegistrations: false, @@ -311,6 +312,8 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host: enti alsoKnownAs: null, notify: 'none', memo: null, + canChat: true, + chatScope: 'everyone', }; } @@ -378,6 +381,7 @@ export function role(params: { asBadge: params.asBadge ?? true, canEditMembersByModerator: params.canEditMembersByModerator ?? false, usersCount: params.usersCount ?? 10, + preserveAssignmentOnMoveAccount: false, condFormula: { id: '', type: 'or', diff --git a/packages/frontend/.vscode/storybook.code-snippets b/packages/frontend/.vscode/storybook.code-snippets index 785d0a1608..d7063f7200 100644 --- a/packages/frontend/.vscode/storybook.code-snippets +++ b/packages/frontend/.vscode/storybook.code-snippets @@ -42,7 +42,7 @@ "prefix": "storyimplevent", "body": [ "/* eslint-disable @typescript-eslint/explicit-function-return-type */", - "import { action } from '@storybook/addon-actions';", + "import { action } from 'storybook/actions';", "import { StoryObj } from '@storybook/vue3';", "import $1 from './$1.vue';", "export const Default = {", diff --git a/packages/frontend/lib/vite-plugin-create-search-index.ts b/packages/frontend/lib/vite-plugin-create-search-index.ts index 4e20828909..f17b43b0e3 100644 --- a/packages/frontend/lib/vite-plugin-create-search-index.ts +++ b/packages/frontend/lib/vite-plugin-create-search-index.ts @@ -8,7 +8,7 @@ import { parse as vueSfcParse } from 'vue/compiler-sfc'; import { createLogger, - EnvironmentModuleGraph, + type EnvironmentModuleGraph, type LogErrorOptions, type LogOptions, normalizePath, diff --git a/packages/frontend/package.json b/packages/frontend/package.json index fe2f47ad1f..108fae7305 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -24,14 +24,14 @@ "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "6.0.2", "@rollup/pluginutils": "5.2.0", - "@sentry/vue": "10.0.0", + "@sentry/vue": "10.5.0", "@syuilo/aiscript": "1.1.0", "@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0", "@twemoji/parser": "16.0.0", "@vitejs/plugin-vue": "6.0.1", - "@vue/compiler-sfc": "3.5.18", + "@vue/compiler-sfc": "3.5.19", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", - "analytics": "0.8.16", + "analytics": "0.8.19", "astring": "1.9.0", "broadcast-channel": "7.1.0", "buraha": "0.0.1", @@ -55,7 +55,7 @@ "ios-haptics": "0.1.0", "is-file-animated": "1.0.2", "json5": "2.2.3", - "magic-string": "0.30.17", + "magic-string": "0.30.18", "matter-js": "0.20.0", "mfm-js": "0.25.0", "misskey-bubble-game": "workspace:*", @@ -63,10 +63,10 @@ "misskey-reversi": "workspace:*", "photoswipe": "5.4.4", "punycode.js": "2.3.1", - "rollup": "4.46.2", + "rollup": "4.48.0", "sanitize-html": "2.17.0", - "sass": "1.89.2", - "shiki": "3.9.1", + "sass": "1.90.0", + "shiki": "3.11.0", "strict-event-emitter-types": "2.0.0", "textarea-caret": "3.1.0", "three": "0.179.1", @@ -76,17 +76,16 @@ "tsconfig-paths": "4.2.0", "typescript": "5.9.2", "v-code-diff": "1.13.1", - "vite": "7.0.6", - "vue": "3.5.18", + "vite": "7.1.3", + "vue": "3.5.19", "vuedraggable": "next", "wanakana": "5.3.1" }, "devDependencies": { "@misskey-dev/summaly": "5.2.3", - "@storybook/addon-actions": "9.0.8", "@storybook/addon-essentials": "8.6.14", "@storybook/addon-interactions": "8.6.14", - "@storybook/addon-links": "9.1.0", + "@storybook/addon-links": "9.1.3", "@storybook/addon-mdx-gfm": "8.6.14", "@storybook/addon-storysource": "8.6.14", "@storybook/blocks": "8.6.14", @@ -94,34 +93,34 @@ "@storybook/core-events": "8.6.14", "@storybook/manager-api": "8.6.14", "@storybook/preview-api": "8.6.14", - "@storybook/react": "9.1.0", - "@storybook/react-vite": "9.1.0", + "@storybook/react": "9.1.3", + "@storybook/react-vite": "9.1.3", "@storybook/test": "8.6.14", "@storybook/theming": "8.6.14", "@storybook/types": "8.6.14", - "@storybook/vue3": "9.1.0", - "@storybook/vue3-vite": "9.1.0", + "@storybook/vue3": "9.1.3", + "@storybook/vue3-vite": "9.1.3", "@tabler/icons-webfont": "3.34.1", "@testing-library/vue": "8.1.0", "@types/canvas-confetti": "1.9.0", "@types/estree": "1.0.8", - "@types/matter-js": "0.19.8", + "@types/matter-js": "0.20.0", "@types/micromatch": "4.0.9", - "@types/node": "22.17.0", + "@types/node": "22.17.2", "@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/sanitize-html": "2.16.0", "@types/seedrandom": "3.0.8", "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/ws": "8.18.1", - "@typescript-eslint/eslint-plugin": "8.38.0", - "@typescript-eslint/parser": "8.38.0", + "@typescript-eslint/eslint-plugin": "8.40.0", + "@typescript-eslint/parser": "8.40.0", "@vitest/coverage-v8": "3.2.4", - "@vue/compiler-core": "3.5.18", - "@vue/runtime-core": "3.5.18", + "@vue/compiler-core": "3.5.19", + "@vue/runtime-core": "3.5.19", "acorn": "8.15.0", "cross-env": "10.0.0", - "cypress": "14.5.3", + "cypress": "14.5.4", "eslint-plugin-import": "2.32.0", "eslint-plugin-vue": "10.4.0", "fast-glob": "3.3.3", @@ -129,22 +128,22 @@ "intersection-observer": "0.12.2", "micromatch": "4.0.8", "minimatch": "10.0.3", - "msw": "2.10.4", + "msw": "2.10.5", "msw-storybook-addon": "2.0.5", "nodemon": "3.1.10", "prettier": "3.6.2", "react": "19.1.1", "react-dom": "19.1.1", "seedrandom": "3.0.5", - "start-server-and-test": "2.0.12", - "storybook": "9.1.0", + "start-server-and-test": "2.0.13", + "storybook": "9.1.3", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", - "tsx": "4.20.3", + "tsx": "4.20.4", "vite-plugin-turbosnap": "1.0.3", "vitest": "3.2.4", "vitest-fetch-mock": "0.4.5", - "vue-component-type-helpers": "3.0.5", + "vue-component-type-helpers": "3.0.6", "vue-eslint-parser": "10.2.0", - "vue-tsc": "3.0.5" + "vue-tsc": "3.0.6" } } diff --git a/packages/frontend/src/accounts.ts b/packages/frontend/src/accounts.ts index 3693ac3308..60f7cd0b4b 100644 --- a/packages/frontend/src/accounts.ts +++ b/packages/frontend/src/accounts.ts @@ -23,7 +23,7 @@ export async function getAccounts(): Promise<{ host: string; id: Misskey.entities.User['id']; username: Misskey.entities.User['username']; - user?: Misskey.entities.User | null; + user?: Misskey.entities.MeDetailed | null; token: string | null; }[]> { const tokens = store.s.accountTokens; @@ -38,7 +38,7 @@ export async function getAccounts(): Promise<{ })); } -async function addAccount(host: string, user: Misskey.entities.User, token: AccountWithToken['token']) { +async function addAccount(host: string, user: Misskey.entities.MeDetailed, token: AccountWithToken['token']) { if (!prefer.s.accounts.some(x => x[0] === host && x[1].id === user.id)) { store.set('accountTokens', { ...store.s.accountTokens, [host + '/' + user.id]: token }); store.set('accountInfos', { ...store.s.accountInfos, [host + '/' + user.id]: user }); @@ -149,9 +149,10 @@ export function updateCurrentAccountPartial(accountData: Partial { if (reason === isAccountDeleted) { - removeAccount(host, $i.id); + removeAccount(host, me.id); if (Object.keys(store.s.accountTokens).length > 0) { login(Object.values(store.s.accountTokens)[0]); } else { @@ -214,46 +215,75 @@ export async function openAccountMenu(opts: { includeCurrentAccount?: boolean; withExtraOperation: boolean; active?: Misskey.entities.User['id']; - onChoose?: (account: Misskey.entities.User) => void; + onChoose?: (account: Misskey.entities.MeDetailed) => void; }, ev: MouseEvent) { if (!$i) return; + const me = $i; - function createItem(host: string, id: Misskey.entities.User['id'], username: Misskey.entities.User['username'], account: Misskey.entities.User | null | undefined, token: string): MenuItem { + const callback = opts.onChoose; + + function createItem(host: string, id: Misskey.entities.User['id'], username: Misskey.entities.User['username'], account: Misskey.entities.MeDetailed | null | undefined, token: string | null): MenuItem { if (account) { return { type: 'user' as const, user: account, active: opts.active != null ? opts.active === id : false, action: async () => { - if (opts.onChoose) { - opts.onChoose(account); + if (callback) { + callback(account); } else { switchAccount(host, id); } }, }; - } else { + } else if (token != null) { return { type: 'button' as const, text: username, active: opts.active != null ? opts.active === id : false, action: async () => { - if (opts.onChoose) { + if (callback) { fetchAccount(token, id).then(account => { - opts.onChoose(account); + callback(account); }); } else { switchAccount(host, id); } }, }; + } else { // プロファイルを復元した場合などはアカウントのトークンや詳細情報はstoreにキャッシュされていない + return { + type: 'button' as const, + text: username, + active: opts.active != null ? opts.active === id : false, + action: async () => { + const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), { + initialUsername: username, + }, { + done: async (res: Misskey.entities.SigninFlowResponse & { finished: true }) => { + store.set('accountTokens', { ...store.s.accountTokens, [host + '/' + res.id]: res.i }); + + if (callback) { + fetchAccount(res.i, id).then(account => { + callback(account); + }); + } else { + switchAccount(host, id); + } + }, + closed: () => { + dispose(); + }, + }); + }, + }; } } const menuItems: MenuItem[] = []; // TODO: $iのホストも比較したいけど通常null - const accountItems = (await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id))).map(a => createItem(a.host, a.id, a.username, a.user, a.token)); + const accountItems = (await getAccounts().then(accounts => accounts.filter(x => x.id !== me.id))).map(a => createItem(a.host, a.id, a.username, a.user, a.token)); if (opts.withExtraOperation) { menuItems.push({ diff --git a/packages/frontend/src/aiscript/api.ts b/packages/frontend/src/aiscript/api.ts index a876e94ee8..0549ab76a0 100644 --- a/packages/frontend/src/aiscript/api.ts +++ b/packages/frontend/src/aiscript/api.ts @@ -86,7 +86,7 @@ export function createAiScriptEnv(opts: { storageKey: string, token?: string }) throw new errors.AiScriptRuntimeError('expected param'); } utils.assertObject(param); - return misskeyApi(ep.value, utils.valToJs(param) as object, actualToken).then(res => { + return misskeyApi(ep.value as keyof Misskey.Endpoints, utils.valToJs(param) as object, actualToken).then(res => { return utils.jsToVal(res); }, err => { return values.ERROR('request_failed', utils.jsToVal(err)); diff --git a/packages/frontend/src/aiscript/ui.ts b/packages/frontend/src/aiscript/ui.ts index a27ece512e..9c330da3c5 100644 --- a/packages/frontend/src/aiscript/ui.ts +++ b/packages/frontend/src/aiscript/ui.ts @@ -4,11 +4,11 @@ */ import { utils, values } from '@syuilo/aiscript'; -import { genId } from '@/utility/id.js'; import { ref } from 'vue'; -import type { Ref } from 'vue'; import * as Misskey from 'misskey-js'; import { assertStringAndIsIn } from './common.js'; +import type { Ref } from 'vue'; +import { genId } from '@/utility/id.js'; const ALIGNS = ['left', 'center', 'right'] as const; const FONTS = ['serif', 'sans-serif', 'monospace'] as const; @@ -21,16 +21,15 @@ type BorderStyle = (typeof BORDER_STYLES)[number]; export type AsUiComponentBase = { id: string; hidden?: boolean; + children?: AsUiComponent['id'][]; }; export type AsUiRoot = AsUiComponentBase & { type: 'root'; - children: AsUiComponent['id'][]; }; export type AsUiContainer = AsUiComponentBase & { type: 'container'; - children?: AsUiComponent['id'][]; align?: Align; bgColor?: string; fgColor?: string; @@ -123,7 +122,6 @@ export type AsUiSelect = AsUiComponentBase & { export type AsUiFolder = AsUiComponentBase & { type: 'folder'; - children?: AsUiComponent['id'][]; title?: string; opened?: boolean; }; diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 6ae8379801..18817d3f79 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -368,11 +368,6 @@ export async function mainBoot() { }); }); - main.on('unreadAntenna', () => { - updateCurrentAccountPartial({ hasUnreadAntenna: true }); - sound.playMisskeySfx('antenna'); - }); - main.on('newChatMessage', () => { updateCurrentAccountPartial({ hasUnreadChatMessages: true }); sound.playMisskeySfx('chatMessage'); diff --git a/packages/frontend/src/cache.ts b/packages/frontend/src/cache.ts index 70078b410d..39cf73feb8 100644 --- a/packages/frontend/src/cache.ts +++ b/packages/frontend/src/cache.ts @@ -7,8 +7,8 @@ import * as Misskey from 'misskey-js'; import { Cache } from '@/utility/cache.js'; import { misskeyApi } from '@/utility/misskey-api.js'; -export const clipsCache = new Cache(1000 * 60 * 30, () => misskeyApi('clips/list')); -export const rolesCache = new Cache(1000 * 60 * 30, () => misskeyApi('admin/roles/list')); +export const clipsCache = new Cache(1000 * 60 * 30, () => misskeyApi('clips/list', { limit: 30 })); +export const rolesCache = new Cache(1000 * 60 * 30, () => misskeyApi('admin/roles/list', { limit: 30 })); export const userListsCache = new Cache(1000 * 60 * 30, () => misskeyApi('users/lists/list')); -export const antennasCache = new Cache(1000 * 60 * 30, () => misskeyApi('antennas/list')); +export const antennasCache = new Cache(1000 * 60 * 30, () => misskeyApi('antennas/list', { limit: 30 })); export const favoritedChannelsCache = new Cache(1000 * 60 * 30, () => misskeyApi('channels/my-favorites', { limit: 100 })); diff --git a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts index b62096bbe9..2eb17b1b9e 100644 --- a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts +++ b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts @@ -2,14 +2,13 @@ * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ - -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { action } from '@storybook/addon-actions'; -import type { StoryObj } from '@storybook/vue3'; + +import { action } from 'storybook/actions'; import { HttpResponse, http } from 'msw'; import { userDetailed } from '../../.storybook/fakes.js'; import { commonHandlers } from '../../.storybook/mocks.js'; import MkAbuseReportWindow from './MkAbuseReportWindow.vue'; +import type { StoryObj } from '@storybook/vue3'; export const Default = { render(args) { return { diff --git a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts index b907b5b25a..fff29262f1 100644 --- a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts +++ b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts @@ -4,7 +4,7 @@ */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import type { StoryObj } from '@storybook/vue3'; import { HttpResponse, http } from 'msw'; import { commonHandlers } from '../../.storybook/mocks.js'; diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue index 70766634ce..c786e9fe9f 100644 --- a/packages/frontend/src/components/MkAchievements.vue +++ b/packages/frontend/src/components/MkAchievements.vue @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only [$style.iconFrame_platinum]: ACHIEVEMENT_BADGES[achievement.name].frame === 'platinum', }]" > - + @@ -61,8 +61,8 @@ import { ACHIEVEMENT_TYPES, ACHIEVEMENT_BADGES, claimAchievement } from '@/utili const props = withDefaults(defineProps<{ user: Misskey.entities.User; - withLocked: boolean; - withDescription: boolean; + withLocked?: boolean; + withDescription?: boolean; }>(), { withLocked: true, withDescription: true, @@ -71,7 +71,7 @@ const props = withDefaults(defineProps<{ const achievements = ref(null); const lockedAchievements = computed(() => ACHIEVEMENT_TYPES.filter(x => !(achievements.value ?? []).some(a => a.name === x))); -function fetch() { +function _fetch_() { misskeyApi('users/achievements', { userId: props.user.id }).then(res => { achievements.value = []; for (const t of ACHIEVEMENT_TYPES) { @@ -84,11 +84,11 @@ function fetch() { function clickHere() { claimAchievement('clickedClickHere'); - fetch(); + _fetch_(); } onMounted(() => { - fetch(); + _fetch_(); }); diff --git a/packages/frontend/src/components/MkAnimBg.vue b/packages/frontend/src/components/MkAnimBg.vue index e57fbcdee3..19a21f6e24 100644 --- a/packages/frontend/src/components/MkAnimBg.vue +++ b/packages/frontend/src/components/MkAnimBg.vue @@ -44,7 +44,7 @@ function initShaderProgram(gl: WebGLRenderingContext, vsSource: string, fsSource const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource); const shaderProgram = gl.createProgram(); - if (shaderProgram == null || vertexShader == null || fragmentShader == null) return null; + if (vertexShader == null || fragmentShader == null) return null; gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); @@ -71,8 +71,10 @@ onMounted(() => { canvas.width = width; canvas.height = height; - const gl = canvas.getContext('webgl', { premultipliedAlpha: true }); - if (gl == null) return; + const maybeGl = canvas.getContext('webgl', { premultipliedAlpha: true }); + if (maybeGl == null) return; + + const gl = maybeGl; gl.clearColor(0.0, 0.0, 0.0, 0.0); gl.clear(gl.COLOR_BUFFER_BIT); @@ -229,8 +231,8 @@ onMounted(() => { gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.DYNAMIC_DRAW); if (isChromatic()) { - gl!.uniform1f(u_time, 0); - gl!.drawArrays(gl!.TRIANGLE_STRIP, 0, 4); + gl.uniform1f(u_time, 0); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } else { function render(timeStamp: number) { let sizeChanged = false; @@ -249,8 +251,8 @@ onMounted(() => { gl.viewport(0, 0, width, height); } - gl!.uniform1f(u_time, timeStamp); - gl!.drawArrays(gl!.TRIANGLE_STRIP, 0, 4); + gl.uniform1f(u_time, timeStamp); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); handle = window.requestAnimationFrame(render); } @@ -263,6 +265,8 @@ onUnmounted(() => { if (handle) { window.cancelAnimationFrame(handle); } + + // TODO: WebGLリソースの解放 }); diff --git a/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts index 627cb0c4ff..743bdda032 100644 --- a/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts +++ b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts @@ -4,7 +4,7 @@ */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import type { StoryObj } from '@storybook/vue3'; import { HttpResponse, http } from 'msw'; import { commonHandlers } from '../../.storybook/mocks.js'; diff --git a/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts b/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts index 4d921a4c48..5c4b05481a 100644 --- a/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts +++ b/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts @@ -4,7 +4,7 @@ */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import type { StoryObj } from '@storybook/vue3'; import { HttpResponse, http } from 'msw'; import { commonHandlers } from '../../.storybook/mocks.js'; diff --git a/packages/frontend/src/components/MkAntennaEditorDialog.stories.impl.ts b/packages/frontend/src/components/MkAntennaEditorDialog.stories.impl.ts index 5878b52fb9..1a70cb745c 100644 --- a/packages/frontend/src/components/MkAntennaEditorDialog.stories.impl.ts +++ b/packages/frontend/src/components/MkAntennaEditorDialog.stories.impl.ts @@ -4,7 +4,7 @@ */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import type { StoryObj } from '@storybook/vue3'; import { HttpResponse, http } from 'msw'; import { commonHandlers } from '../../.storybook/mocks.js'; diff --git a/packages/frontend/src/components/MkAuthConfirm.vue b/packages/frontend/src/components/MkAuthConfirm.vue index b3331d742b..8744b50926 100644 --- a/packages/frontend/src/components/MkAuthConfirm.vue +++ b/packages/frontend/src/components/MkAuthConfirm.vue @@ -167,9 +167,13 @@ async function init() { for (const user of usersRes) { if (users.value.has(user.id)) continue; + const account = accounts.find(a => a.id === user.id); + + if (!account || account.token == null) continue; + users.value.set(user.id, { ...user, - token: accounts.find(a => a.id === user.id)!.token, + token: account.token, }); } } diff --git a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts index 64ccb708aa..15aab8daed 100644 --- a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts +++ b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts @@ -4,7 +4,7 @@ */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import { expect, userEvent, waitFor, within } from '@storybook/test'; import type { StoryObj } from '@storybook/vue3'; import { HttpResponse, http } from 'msw'; diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index e5b9533cd7..cf5d95e11b 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + ({{ emoji.aliasOf }}) @@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + {{ param }} @@ -194,6 +194,11 @@ const mfmParams = ref([]); const select = ref(-1); const zIndex = os.claimZIndex('high'); +function completeMfmParam(param: string) { + if (props.type !== 'mfmParam') throw new Error('Invalid type'); + complete('mfmParam', props.q.params.toSpliced(-1, 1, param).join(',')); +} + function complete(type: T, value: CompleteInfo[T]['payload']) { emit('done', { type, value }); emit('closed'); diff --git a/packages/frontend/src/components/MkButton.stories.impl.ts b/packages/frontend/src/components/MkButton.stories.impl.ts index 0a569b3beb..4420cc4f05 100644 --- a/packages/frontend/src/components/MkButton.stories.impl.ts +++ b/packages/frontend/src/components/MkButton.stories.impl.ts @@ -5,7 +5,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable import/no-default-export */ -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import type { StoryObj } from '@storybook/vue3'; import MkButton from './MkButton.vue'; export const Default = { diff --git a/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts b/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts index 4304c2e2b7..095805ba95 100644 --- a/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts +++ b/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts @@ -2,9 +2,9 @@ * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ - + import { HttpResponse, http } from 'msw'; -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import { expect, userEvent, within } from '@storybook/test'; import { channel } from '../../.storybook/fakes.js'; import { commonHandlers } from '../../.storybook/mocks.js'; diff --git a/packages/frontend/src/components/MkChannelList.stories.impl.ts b/packages/frontend/src/components/MkChannelList.stories.impl.ts index 47ca864dc0..33a61c8f7a 100644 --- a/packages/frontend/src/components/MkChannelList.stories.impl.ts +++ b/packages/frontend/src/components/MkChannelList.stories.impl.ts @@ -3,14 +3,13 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable import/no-default-export */ -import type { StoryObj } from '@storybook/vue3'; import { HttpResponse, http } from 'msw'; -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import { channel } from '../../.storybook/fakes.js'; import { commonHandlers } from '../../.storybook/mocks.js'; import MkChannelList from './MkChannelList.vue'; +import type { StoryObj } from '@storybook/vue3'; +import { Paginator } from '@/utility/paginator.js'; export const Default = { render(args) { return { @@ -33,10 +32,7 @@ export const Default = { }; }, args: { - pagination: { - endpoint: 'channels/search', - limit: 10, - }, + paginator: new Paginator('channels/search', {}), }, parameters: { chromatic: { diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue index 4d67bba70d..c54081ad42 100644 --- a/packages/frontend/src/components/MkChart.vue +++ b/packages/frontend/src/components/MkChart.vue @@ -589,7 +589,10 @@ const fetchDriveFilesChart = async (): Promise => { }; const fetchInstanceRequestsChart = async (): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'In', @@ -611,7 +614,10 @@ const fetchInstanceRequestsChart = async (): Promise => { }; const fetchInstanceUsersChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Users', @@ -626,7 +632,10 @@ const fetchInstanceUsersChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Notes', @@ -641,7 +650,10 @@ const fetchInstanceNotesChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Following', @@ -664,7 +676,10 @@ const fetchInstanceFfChart = async (total: boolean): Promise = }; const fetchInstanceDriveUsageChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { bytes: true, series: [{ @@ -680,7 +695,10 @@ const fetchInstanceDriveUsageChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Drive files', @@ -695,7 +713,10 @@ const fetchInstanceDriveFilesChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/user/notes', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/notes', { userId: userId, limit: props.limit, span: props.span }); return { series: [...(props.args?.withoutAll ? [] : [{ name: 'All', @@ -727,7 +748,10 @@ const fetchPerUserNotesChart = async (): Promise => { }; const fetchPerUserPvChart = async (): Promise => { - const raw = await misskeyApiGet('charts/user/pv', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/pv', { userId: userId, limit: props.limit, span: props.span }); return { series: [{ name: 'Unique PV (user)', @@ -754,7 +778,10 @@ const fetchPerUserPvChart = async (): Promise => { }; const fetchPerUserFollowingChart = async (): Promise => { - const raw = await misskeyApiGet('charts/user/following', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/following', { userId: userId, limit: props.limit, span: props.span }); return { series: [{ name: 'Local', @@ -769,7 +796,10 @@ const fetchPerUserFollowingChart = async (): Promise => { }; const fetchPerUserFollowersChart = async (): Promise => { - const raw = await misskeyApiGet('charts/user/following', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/following', { userId: userId, limit: props.limit, span: props.span }); return { series: [{ name: 'Local', @@ -784,7 +814,10 @@ const fetchPerUserFollowersChart = async (): Promise => { }; const fetchPerUserDriveChart = async (): Promise => { - const raw = await misskeyApiGet('charts/user/drive', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/drive', { userId: userId, limit: props.limit, span: props.span }); return { bytes: true, series: [{ diff --git a/packages/frontend/src/components/MkChartTooltip.vue b/packages/frontend/src/components/MkChartTooltip.vue index 51081ede23..b9d2c8219a 100644 --- a/packages/frontend/src/components/MkChartTooltip.vue +++ b/packages/frontend/src/components/MkChartTooltip.vue @@ -25,12 +25,12 @@ defineProps<{ showing: boolean; x: number; y: number; - title?: string; + title?: string | null; series?: { backgroundColor: string; borderColor: string; text: string; - }[]; + }[] | null; }>(); const emit = defineEmits<{ diff --git a/packages/frontend/src/components/MkChatHistories.stories.impl.ts b/packages/frontend/src/components/MkChatHistories.stories.impl.ts index 8268adc36f..74fdff6fdd 100644 --- a/packages/frontend/src/components/MkChatHistories.stories.impl.ts +++ b/packages/frontend/src/components/MkChatHistories.stories.impl.ts @@ -4,7 +4,7 @@ */ import { http, HttpResponse } from 'msw'; -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import { chatMessage } from '../../.storybook/fakes'; import MkChatHistories from './MkChatHistories.vue'; import type { StoryObj } from '@storybook/vue3'; diff --git a/packages/frontend/src/components/MkClickerGame.stories.impl.ts b/packages/frontend/src/components/MkClickerGame.stories.impl.ts index 6e1eb13d61..f9012742cb 100644 --- a/packages/frontend/src/components/MkClickerGame.stories.impl.ts +++ b/packages/frontend/src/components/MkClickerGame.stories.impl.ts @@ -2,9 +2,9 @@ * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ - + import { HttpResponse, http } from 'msw'; -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import { expect, userEvent, within } from '@storybook/test'; import { commonHandlers } from '../../.storybook/mocks.js'; import MkClickerGame from './MkClickerGame.vue'; diff --git a/packages/frontend/src/components/MkCodeEditor.stories.impl.ts b/packages/frontend/src/components/MkCodeEditor.stories.impl.ts index c76b6fd08e..24b8e9119b 100644 --- a/packages/frontend/src/components/MkCodeEditor.stories.impl.ts +++ b/packages/frontend/src/components/MkCodeEditor.stories.impl.ts @@ -6,7 +6,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable import/no-default-export */ import type { StoryObj } from '@storybook/vue3'; -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import MkCodeEditor from './MkCodeEditor.vue'; const code = `for (let i, 100) { <: if (i % 15 == 0) "FizzBuzz" diff --git a/packages/frontend/src/components/MkColorInput.stories.impl.ts b/packages/frontend/src/components/MkColorInput.stories.impl.ts index 3df92ca858..f8ec58bbcc 100644 --- a/packages/frontend/src/components/MkColorInput.stories.impl.ts +++ b/packages/frontend/src/components/MkColorInput.stories.impl.ts @@ -6,7 +6,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable import/no-default-export */ import type { StoryObj } from '@storybook/vue3'; -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import MkColorInput from './MkColorInput.vue'; export const Default = { render(args) { diff --git a/packages/frontend/src/components/MkCropperDialog.stories.impl.ts b/packages/frontend/src/components/MkCropperDialog.stories.impl.ts index 78cb4120de..7ac3e2a2cd 100644 --- a/packages/frontend/src/components/MkCropperDialog.stories.impl.ts +++ b/packages/frontend/src/components/MkCropperDialog.stories.impl.ts @@ -4,7 +4,7 @@ */ import { HttpResponse, http } from 'msw'; -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import { file } from '../../.storybook/fakes.js'; import { commonHandlers } from '../../.storybook/mocks.js'; import MkCropperDialog from './MkCropperDialog.vue'; @@ -38,7 +38,7 @@ export const Default = { }; }, args: { - file: file(), + imageFile: file(), aspectRatio: NaN, }, parameters: { diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index 7f592fba79..6c07eac47a 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/global/StackingRouterView.vue b/packages/frontend/src/components/global/StackingRouterView.vue index 9e47517244..4c56767608 100644 --- a/packages/frontend/src/components/global/StackingRouterView.vue +++ b/packages/frontend/src/components/global/StackingRouterView.vue @@ -87,7 +87,7 @@ router.useListener('change', ({ resolved }) => { const fullPath = router.getCurrentFullPath(); if (tabs.value.some(tab => tab.routePath === routePath && deepEqual(resolved.props, tab.props))) { - const newTabs = []; + const newTabs = [] as typeof tabs.value; for (const tab of tabs.value) { newTabs.push(tab); diff --git a/packages/frontend/src/components/grid/MkCellTooltip.vue b/packages/frontend/src/components/grid/MkCellTooltip.vue index fd289c6cd9..6cd4f9ec1c 100644 --- a/packages/frontend/src/components/grid/MkCellTooltip.vue +++ b/packages/frontend/src/components/grid/MkCellTooltip.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> - + {{ content }} @@ -18,7 +18,7 @@ import MkTooltip from '@/components/MkTooltip.vue'; defineProps<{ showing: boolean; content: string; - targetElement: HTMLElement; + anchorElement: HTMLElement; }>(); const emit = defineEmits<{ diff --git a/packages/frontend/src/components/grid/MkDataCell.vue b/packages/frontend/src/components/grid/MkDataCell.vue index 444509e6b3..6f1dae8398 100644 --- a/packages/frontend/src/components/grid/MkDataCell.vue +++ b/packages/frontend/src/components/grid/MkDataCell.vue @@ -48,6 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only { const result = os.popup(defineAsyncComponent(() => import('@/components/grid/MkCellTooltip.vue')), { showing, content, - targetElement: rootEl.value!, + anchorElement: rootEl.value!, }, { closed: () => { result.dispose(); diff --git a/packages/frontend/src/components/grid/MkGrid.stories.impl.ts b/packages/frontend/src/components/grid/MkGrid.stories.impl.ts index f85bf146e8..5ed8465299 100644 --- a/packages/frontend/src/components/grid/MkGrid.stories.impl.ts +++ b/packages/frontend/src/components/grid/MkGrid.stories.impl.ts @@ -4,7 +4,7 @@ */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { action } from '@storybook/addon-actions'; +import { action } from 'storybook/actions'; import type { StoryObj } from '@storybook/vue3'; import { ref } from 'vue'; import { commonHandlers } from '../../../.storybook/mocks.js'; diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index 6b1b80695f..eadf88ebd9 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -20,6 +20,7 @@ import NestedRouterView from './global/NestedRouterView.vue'; import StackingRouterView from './global/StackingRouterView.vue'; import MkLoading from './global/MkLoading.vue'; import MkError from './global/MkError.vue'; +import MkSuspense from './global/MkSuspense.vue'; import MkAd from './global/MkAd.vue'; import MkPageHeader from './global/MkPageHeader.vue'; import MkStickyContainer from './global/MkStickyContainer.vue'; @@ -60,6 +61,7 @@ export const components = { MkUrl: MkUrl, MkLoading: MkLoading, MkError: MkError, + MkSuspense: MkSuspense, MkAd: MkAd, MkPageHeader: MkPageHeader, MkStickyContainer: MkStickyContainer, @@ -94,6 +96,7 @@ declare module '@vue/runtime-core' { MkUrl: typeof MkUrl; MkLoading: typeof MkLoading; MkError: typeof MkError; + MkSuspense: typeof MkSuspense; MkAd: typeof MkAd; MkPageHeader: typeof MkPageHeader; MkStickyContainer: typeof MkStickyContainer; diff --git a/packages/frontend/src/components/page/page.image.vue b/packages/frontend/src/components/page/page.image.vue index 69443ce7dd..7e8d8f7bfb 100644 --- a/packages/frontend/src/components/page/page.image.vue +++ b/packages/frontend/src/components/page/page.image.vue @@ -15,7 +15,7 @@ import * as Misskey from 'misskey-js'; import MkMediaList from '@/components/MkMediaList.vue'; const props = defineProps<{ - block: Misskey.entities.PageBlock, + block: Extract, page: Misskey.entities.Page, }>(); diff --git a/packages/frontend/src/components/page/page.note.vue b/packages/frontend/src/components/page/page.note.vue index df26874c17..bae05b9765 100644 --- a/packages/frontend/src/components/page/page.note.vue +++ b/packages/frontend/src/components/page/page.note.vue @@ -18,7 +18,7 @@ import MkNoteDetailed from '@/components/MkNoteDetailed.vue'; import { misskeyApi } from '@/utility/misskey-api.js'; const props = defineProps<{ - block: Misskey.entities.PageBlock, + block: Extract, page: Misskey.entities.Page, }>(); diff --git a/packages/frontend/src/components/page/page.section.vue b/packages/frontend/src/components/page/page.section.vue index e3d26d924f..05c12b3b83 100644 --- a/packages/frontend/src/components/page/page.section.vue +++ b/packages/frontend/src/components/page/page.section.vue @@ -29,7 +29,7 @@ import * as Misskey from 'misskey-js'; const XBlock = defineAsyncComponent(() => import('./page.block.vue')); defineProps<{ - block: Misskey.entities.PageBlock, + block: Extract, h: number, page: Misskey.entities.Page, }>(); diff --git a/packages/frontend/src/components/page/page.text.vue b/packages/frontend/src/components/page/page.text.vue index a00eb0b5ca..792f6529d8 100644 --- a/packages/frontend/src/components/page/page.text.vue +++ b/packages/frontend/src/components/page/page.text.vue @@ -22,7 +22,7 @@ import { isEnabledUrlPreview } from '@/utility/url-preview.js'; const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue')); const props = defineProps<{ - block: Misskey.entities.PageBlock, + block: Extract, page: Misskey.entities.Page, }>(); diff --git a/packages/frontend/src/directives/tooltip.ts b/packages/frontend/src/directives/tooltip.ts index 750acd0588..62aecbc87c 100644 --- a/packages/frontend/src/directives/tooltip.ts +++ b/packages/frontend/src/directives/tooltip.ts @@ -57,7 +57,7 @@ export default { text: self.text, asMfm: binding.modifiers.mfm, direction: binding.modifiers.left ? 'left' : binding.modifiers.right ? 'right' : binding.modifiers.top ? 'top' : binding.modifiers.bottom ? 'bottom' : 'top', - targetElement: el, + anchorElement: el, }, { closed: () => dispose(), }); diff --git a/packages/frontend/src/directives/user-preview.ts b/packages/frontend/src/directives/user-preview.ts index 94deea82c7..b11ef8f088 100644 --- a/packages/frontend/src/directives/user-preview.ts +++ b/packages/frontend/src/directives/user-preview.ts @@ -6,6 +6,7 @@ import { defineAsyncComponent, ref } from 'vue'; import type { Directive } from 'vue'; import { popup } from '@/os.js'; +import { isTouchUsing } from '@/utility/touch.js'; export class UserPreview { private el; @@ -107,6 +108,7 @@ export class UserPreview { export default { mounted(el: HTMLElement, binding, vn) { if (binding.value == null) return; + if (isTouchUsing) return; // TODO: 新たにプロパティを作るのをやめMapを使う // ただメモリ的には↓の方が省メモリかもしれないので検討中 diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts index a5397f0c0d..c9d83a4dbe 100644 --- a/packages/frontend/src/instance.ts +++ b/packages/frontend/src/instance.ts @@ -51,3 +51,9 @@ export async function fetchInstance(force = false): Promise['$emit'] だとインターセクション型が返ってきて -// 使い物にならないので、代わりに ['$props'] から色々省くことで emit の型を生成する -// FIXME: 何故か *.ts ファイルからだと型がうまく取れない?ことがあるのをなんとかしたい -type ComponentEmit = T extends new () => { $props: infer Props } - ? [keyof Pick>] extends [never] - ? Record // *.ts ファイルから型がうまく取れないとき用(これがないと {} になって型エラーがうるさい) - : EmitsExtractor - : T extends (...args: any) => any - ? ReturnType extends { [x: string]: any; __ctx?: { [x: string]: any; props: infer Props } } - ? [keyof Pick>] extends [never] - ? Record - : EmitsExtractor - : never - : never; - // props に ref を許可するようにする type ComponentProps = { [K in keyof CP]: CP[K] | Ref[K]> }; -type EmitsExtractor = { - [K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize : K extends string ? never : K]: T[K]; -}; - export function popup( component: T, props: ComponentProps, @@ -703,7 +684,7 @@ export async function cropImageFile(imageFile: File | Blob, options: { }); } -export function popupMenu(items: MenuItem[], anchorElement?: HTMLElement | EventTarget | null, options?: { +export function popupMenu(items: (MenuItem | null)[], anchorElement?: HTMLElement | EventTarget | null, options?: { align?: string; width?: number; onClosing?: () => void; @@ -715,7 +696,7 @@ export function popupMenu(items: MenuItem[], anchorElement?: HTMLElement | Event let returnFocusTo = getHTMLElementOrNull(anchorElement) ?? getHTMLElementOrNull(window.document.activeElement); return new Promise(resolve => nextTick(() => { const { dispose } = popup(MkPopupMenu, { - items, + items: items.filter(x => x != null), anchorElement, width: options?.width, align: options?.align, diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue index b166dfd940..7e514c5a73 100644 --- a/packages/frontend/src/pages/about.emojis.vue +++ b/packages/frontend/src/pages/about.emojis.vue @@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + {{ category || i18n.ts.other }} @@ -48,7 +48,7 @@ import { $i } from '@/i.js'; const customEmojiTags = getCustomEmojiTags(); const q = ref(''); -const searchEmojis = ref(null); +const searchEmojis = ref(null); const selectedTags = ref(new Set()); function search() { diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue index 47ec675d57..fd5e061d52 100644 --- a/packages/frontend/src/pages/about.federation.vue +++ b/packages/frontend/src/pages/about.federation.vue @@ -22,20 +22,46 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.blocked }} {{ i18n.ts.notResponding }} - + {{ i18n.ts.sort }} - {{ i18n.ts.pubSub }} ({{ i18n.ts.descendingOrder }}) - {{ i18n.ts.pubSub }} ({{ i18n.ts.ascendingOrder }}) - {{ i18n.ts.notes }} ({{ i18n.ts.descendingOrder }}) - {{ i18n.ts.notes }} ({{ i18n.ts.ascendingOrder }}) - {{ i18n.ts.users }} ({{ i18n.ts.descendingOrder }}) - {{ i18n.ts.users }} ({{ i18n.ts.ascendingOrder }}) - {{ i18n.ts.following }} ({{ i18n.ts.descendingOrder }}) - {{ i18n.ts.following }} ({{ i18n.ts.ascendingOrder }}) - {{ i18n.ts.followers }} ({{ i18n.ts.descendingOrder }}) - {{ i18n.ts.followers }} ({{ i18n.ts.ascendingOrder }}) - {{ i18n.ts.registeredAt }} ({{ i18n.ts.descendingOrder }}) - {{ i18n.ts.registeredAt }} ({{ i18n.ts.ascendingOrder }}) @@ -52,6 +78,7 @@ SPDX-License-Identifier: AGPL-3.0-only + + diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue index 7a49ba542f..608a77c06b 100644 --- a/packages/frontend/src/pages/admin-file.vue +++ b/packages/frontend/src/pages/admin-file.vue @@ -4,197 +4,37 @@ SPDX-License-Identifier: AGPL-3.0-only --> - - - - - - - - - MIME Type - {{ file.type }} - - - Size - {{ bytes(file.size) }} - - - ID - {{ file.id }} - - - MD5 - {{ file.md5 }} - - - {{ i18n.ts.createdAt }} - - - - - - - - - {{ i18n.ts.sensitive }} - - - - {{ i18n.ts.delete }} - - - - - - - - - {{ i18n.ts.requireAdminForView }} - - IP - {{ info.requestIp }} - - - Headers - - {{ k }} - {{ v }} - - - - - - - - - + file = result.file"> + + - - diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index a194b9a94f..38e3c7a11b 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -6,58 +6,57 @@ SPDX-License-Identifier: AGPL-3.0-only - - - - - - - @{{ acct(user) }} - - Suspended - Silenced - Moderator - - + + + + + + @{{ acct(user) }} + + Suspended + Silenced + Moderator + + - {{ i18n.ts.isSystemAccount }} + {{ i18n.ts.isSystemAccount }} - {{ i18n.ts.instanceInfo }} + {{ i18n.ts.instanceInfo }} - - - ID - {{ user.id }} - - - - - {{ i18n.ts.createdAt }} - - - - {{ i18n.ts.lastActiveDate }} - - - - {{ i18n.ts.email }} - {{ info.email }} - - - + + + {{ i18n.ts.createdAt }} + + + + {{ i18n.ts.lastActiveDate }} + + + + {{ i18n.ts.email }} + {{ info.email }} + + + - - {{ i18n.ts.moderationNote }} - {{ i18n.ts.moderationNoteDescription }} - + + {{ i18n.ts.moderationNote }} + {{ i18n.ts.moderationNoteDescription }} + - - - - {{ i18n.ts.suspend }} + + + {{ i18n.ts.suspend }} - - {{ i18n.ts.resetPassword }} - - - - - {{ i18n.ts._role.policies }} - - - {{ policy }} ... {{ info.policies[policy] }} - - - - - - - IP - {{ i18n.ts.requireAdminForView }} - The date is the IP address was first acknowledged. - - - {{ record.createdAt }} - {{ record.ip }} - - - - - - {{ i18n.ts.unsetUserAvatar }} - {{ i18n.ts.unsetUserBanner }} - - {{ i18n.ts.deleteAccount }} + + {{ i18n.ts.resetPassword }} - - - - {{ i18n.ts.assign }} - - - - - - - - - - Assigned: - Period: {{ new Date(info.roleAssigns.find(a => a.roleId === role.id).expiresAt).toLocaleString() }} - Period: {{ i18n.ts.indefinitely }} - - - - - - {{ i18n.ts.new }} - - - {{ i18n.ts.filter }} - {{ i18n.ts.active }} - {{ i18n.ts.archived }} - - - - - - - - - - - - - {{ announcement.title }} - {{ i18n.ts.messageRead }} + + + {{ i18n.ts._role.policies }} + + + {{ policy }} ... {{ info.policies[policy] }} - - - + - - - + + + IP + {{ i18n.ts.requireAdminForView }} + The date is the IP address was first acknowledged. + + + {{ record.createdAt }} + {{ record.ip }} + + + - - - - - {{ i18n.ts.notes }} - - - - {{ i18n.tsx.recentNHours({ n: 90 }) }} - - {{ i18n.tsx.recentNDays({ n: 90 }) }} - + + {{ i18n.ts.unsetUserAvatar }} + {{ i18n.ts.unsetUserBanner }} + {{ i18n.ts.deleteAccount }} + + + + + + {{ i18n.ts.assign }} + + + + + + + + + + Assigned: + Period: {{ new Date(info.roleAssigns.find(a => a.roleId === role.id)!.expiresAt!).toLocaleString() }} + Period: {{ i18n.ts.indefinitely }} + - - - + + {{ i18n.ts.new }} - - + + {{ i18n.ts.filter }} + {{ i18n.ts.active }} + {{ i18n.ts.archived }} + + + + + + + + + + + + + {{ announcement.title }} + {{ i18n.ts.messageRead }} + + + + + + + + + + + + + + + {{ i18n.ts.notes }} + + + + {{ i18n.tsx.recentNHours({ n: 90 }) }} + + {{ i18n.tsx.recentNDays({ n: 90 }) }} + + - + + + + + + + + + @@ -224,7 +222,6 @@ import MkButton from '@/components/MkButton.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; import MkSelect from '@/components/MkSelect.vue'; -import FormSuspense from '@/components/form/suspense.vue'; import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue'; import MkInfo from '@/components/MkInfo.vue'; import * as os from '@/os.js'; @@ -232,11 +229,13 @@ import { misskeyApi } from '@/utility/misskey-api.js'; import { acct } from '@/filters/user.js'; import { definePage } from '@/page.js'; import { i18n } from '@/i18n.js'; -import { iAmAdmin, $i, iAmModerator } from '@/i.js'; +import { ensureSignin, iAmAdmin, iAmModerator } from '@/i.js'; import MkRolePreview from '@/components/MkRolePreview.vue'; import MkPagination from '@/components/MkPagination.vue'; import { Paginator } from '@/utility/paginator.js'; +const $i = ensureSignin(); + const props = withDefaults(defineProps<{ userId: string; initialTab?: string; @@ -244,18 +243,19 @@ const props = withDefaults(defineProps<{ initialTab: 'overview', }); +const result = await _fetch_(); + const tab = ref(props.initialTab); const chartSrc = ref('per-user-notes'); -const user = ref(); -const init = ref>(); -const info = ref(); -const ips = ref(null); +const user = ref(result.user); +const info = ref(result.info); +const ips = ref(result.ips); const ap = ref(null); -const moderator = ref(false); -const silenced = ref(false); -const suspended = ref(false); -const isSystem = ref(false); -const moderationNote = ref(''); +const moderator = ref(info.value.isModerator); +const silenced = ref(info.value.isSilenced); +const suspended = ref(info.value.isSuspended); +const isSystem = ref(user.value.host == null && user.value.username.includes('.')); +const moderationNote = ref(info.value.moderationNote); const filesPaginator = markRaw(new Paginator('admin/drive/files', { limit: 10, computedParams: computed(() => ({ @@ -272,34 +272,37 @@ const announcementsPaginator = markRaw(new Paginator('admin/announcements/list', status: announcementsStatus.value, })), })); -const expandedRoles = ref([]); +const expandedRoleIds = ref<(typeof info.value.roles[number]['id'])[]>([]); -function createFetcher() { - return () => Promise.all([misskeyApi('users/show', { +function _fetch_() { + return Promise.all([misskeyApi('users/show', { userId: props.userId, }), misskeyApi('admin/show-user', { userId: props.userId, }), iAmAdmin ? misskeyApi('admin/get-user-ips', { userId: props.userId, - }) : Promise.resolve(null)]).then(([_user, _info, _ips]) => { - user.value = _user; - info.value = _info; - ips.value = _ips; - moderator.value = info.value.isModerator; - silenced.value = info.value.isSilenced; - suspended.value = info.value.isSuspended; - moderationNote.value = info.value.moderationNote; - isSystem.value = user.value.host == null && user.value.username.includes('.'); - - watch(moderationNote, async () => { - await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value }); - await refreshUser(); - }); - }); + }) : Promise.resolve(null)]).then(([_user, _info, _ips]) => ({ + user: _user, + info: _info, + ips: _ips, + })); } -function refreshUser() { - init.value = createFetcher(); +watch(moderationNote, async () => { + await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value }); + await refreshUser(); +}); + +async function refreshUser() { + const result = await _fetch_(); + user.value = result.user; + info.value = result.info; + ips.value = result.ips; + moderator.value = info.value.isModerator; + silenced.value = info.value.isSilenced; + suspended.value = info.value.isSuspended; + isSystem.value = user.value.host == null && user.value.username.includes('.'); + moderationNote.value = info.value.moderationNote; } async function updateRemoteUser() { @@ -456,7 +459,7 @@ async function assignRole() { refreshUser(); } -async function unassignRole(role, ev) { +async function unassignRole(role: typeof info.value.roles[number], ev: MouseEvent) { os.popupMenu([{ text: i18n.ts.unassign, icon: 'ti ti-x', @@ -468,11 +471,11 @@ async function unassignRole(role, ev) { }], ev.currentTarget ?? ev.target); } -function toggleRoleItem(role) { - if (expandedRoles.value.includes(role.id)) { - expandedRoles.value = expandedRoles.value.filter(x => x !== role.id); +function toggleRoleItem(role: typeof info.value.roles[number]) { + if (expandedRoleIds.value.includes(role.id)) { + expandedRoleIds.value = expandedRoleIds.value.filter(x => x !== role.id); } else { - expandedRoles.value.push(role.id); + expandedRoleIds.value.push(role.id); } } @@ -493,12 +496,6 @@ async function editAnnouncement(announcement) { }); } -watch(() => props.userId, () => { - init.value = createFetcher(); -}, { - immediate: true, -}); - watch(user, () => { misskeyApi('ap/get', { uri: user.value.uri ?? `${url}/users/${user.value.id}`, diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue index c5baeda7b0..06a28db088 100644 --- a/packages/frontend/src/pages/admin/ads.vue +++ b/packages/frontend/src/pages/admin/ads.vue @@ -140,15 +140,15 @@ function toggleDayOfWeek(ad, index) { function add() { ads.value.unshift({ - id: null, + id: '', memo: '', place: 'square', priority: 'middle', ratio: 1, url: '', - imageUrl: null, - expiresAt: null, - startsAt: null, + imageUrl: '', + expiresAt: new Date().toISOString(), + startsAt: new Date().toISOString(), dayOfWeek: 0, }); } @@ -160,7 +160,7 @@ function remove(ad) { }).then(({ canceled }) => { if (canceled) return; ads.value = ads.value.filter(x => x !== ad); - if (ad.id == null) return; + if (ad.id === '') return; os.apiWithDialog('admin/ad/delete', { id: ad.id, }).then(() => { @@ -170,7 +170,7 @@ function remove(ad) { } function save(ad) { - if (ad.id == null) { + if (ad.id === '') { misskeyApi('admin/ad/create', { ...ad, expiresAt: new Date(ad.expiresAt).getTime(), @@ -207,7 +207,7 @@ function save(ad) { } function more() { - misskeyApi('admin/ad/list', { untilId: ads.value.reduce((acc, ad) => ad.id != null ? ad : acc).id, publishing: publishing }).then(adsResponse => { + misskeyApi('admin/ad/list', { untilId: ads.value.reduce((acc, ad) => ad.id !== '' ? ad : acc).id, publishing: publishing }).then(adsResponse => { if (adsResponse == null) return; ads.value = ads.value.concat(adsResponse.map(r => { const exdate = new Date(r.expiresAt); diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue index ac7797becc..e5e0f087e1 100644 --- a/packages/frontend/src/pages/admin/branding.vue +++ b/packages/frontend/src/pages/admin/branding.vue @@ -23,8 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-only - - {{ i18n.ts._serverSettings.showActivityiesForVisitor }} + + {{ i18n.ts._serverSettings.showActivitiesForVisitor }} @@ -152,6 +152,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref, computed } from 'vue'; import JSON5 from 'json5'; import { host } from '@@/js/config.js'; +import type { ClientOptions } from '@/instance.js'; import MkInput from '@/components/MkInput.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import * as os from '@/os.js'; @@ -166,9 +167,13 @@ import MkSwitch from '@/components/MkSwitch.vue'; const meta = await misskeyApi('admin/meta'); -const entrancePageStyle = ref(meta.clientOptions.entrancePageStyle ?? 'classic'); -const showTimelineForVisitor = ref(meta.clientOptions.showTimelineForVisitor ?? true); -const showActivityiesForVisitor = ref(meta.clientOptions.showActivityiesForVisitor ?? true); +// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition +const entrancePageStyle = ref(meta.clientOptions.entrancePageStyle ?? 'classic'); +// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition +const showTimelineForVisitor = ref(meta.clientOptions.showTimelineForVisitor ?? true); +// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition +const showActivitiesForVisitor = ref(meta.clientOptions.showActivitiesForVisitor ?? true); + const iconUrl = ref(meta.iconUrl); const app192IconUrl = ref(meta.app192IconUrl); const app512IconUrl = ref(meta.app512IconUrl); @@ -186,11 +191,11 @@ const manifestJsonOverride = ref(meta.manifestJsonOverride === '' ? '{}' : JSON. function save() { os.apiWithDialog('admin/update-meta', { - clientOptions: { + clientOptions: ({ entrancePageStyle: entrancePageStyle.value, showTimelineForVisitor: showTimelineForVisitor.value, - showActivityiesForVisitor: showActivityiesForVisitor.value, - }, + showActivitiesForVisitor: showActivitiesForVisitor.value, + } as ClientOptions) as any, iconUrl: iconUrl.value, app192IconUrl: app192IconUrl.value, app512IconUrl: app512IconUrl.value, diff --git a/packages/frontend/src/pages/admin/database.vue b/packages/frontend/src/pages/admin/database.vue index d51f43c098..f41967d3bd 100644 --- a/packages/frontend/src/pages/admin/database.vue +++ b/packages/frontend/src/pages/admin/database.vue @@ -6,19 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only - + {{ table[0] }} {{ bytes(table[1].size) }} ({{ number(table[1].count) }} recs) - + diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index bc957ff38a..c1a8b992b7 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -48,8 +48,8 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.tsx.lastNDays({ n: 7 }) }} {{ i18n.ts.ranking }} ({{ gameMode.toUpperCase() }}) - - + + {{ r.score.toLocaleString() }} {{ getScoreUnit(gameMode) }} @@ -87,6 +87,7 @@ SPDX-License-Identifier: AGPL-3.0-only + diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue index cf0d700962..12d1a37390 100644 --- a/packages/frontend/src/pages/gallery/edit.vue +++ b/packages/frontend/src/pages/gallery/edit.vue @@ -4,162 +4,35 @@ SPDX-License-Identifier: AGPL-3.0-only --> - - - - - {{ i18n.ts.title }} - - - - {{ i18n.ts.description }} - - - - - {{ file.name }} - - - {{ i18n.ts.attachFile }} - - - {{ i18n.ts.markAsSensitive }} - - - {{ i18n.ts.save }} - {{ i18n.ts.publish }} - - {{ i18n.ts.delete }} - - - - + + + - - diff --git a/packages/frontend/src/pages/install-extensions.vue b/packages/frontend/src/pages/install-extensions.vue index 1b3c6616cc..cad3b2a00a 100644 --- a/packages/frontend/src/pages/install-extensions.vue +++ b/packages/frontend/src/pages/install-extensions.vue @@ -80,7 +80,7 @@ function close_(): void { } } -async function fetch() { +async function _fetch_() { if (!url.value || !hash.value) { errorKV.value = { title: i18n.ts._externalResourceInstaller._errors._invalidParams.title, @@ -161,7 +161,7 @@ async function fetch() { }, raw: res.data, }; - } catch (err) { + } catch (err: any) { switch (err.message.toLowerCase()) { case 'this theme is already installed': errorKV.value = { @@ -229,7 +229,7 @@ async function install() { const urlParams = new URLSearchParams(window.location.search); url.value = urlParams.get('url'); hash.value = urlParams.get('hash'); -fetch(); +_fetch_(); definePage(() => ({ title: i18n.ts._externalResourceInstaller.title, diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 4be5fa447d..473207fe6e 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -198,7 +198,7 @@ if (iAmModerator) { }); } -async function fetch(): Promise { +async function _fetch_(): Promise { if (iAmAdmin) { meta.value = await misskeyApi('admin/meta'); } @@ -276,7 +276,7 @@ function refreshMetadata(): void { }); } -fetch(); +_fetch_(); const headerActions = computed(() => [{ text: `https://${props.host}`, diff --git a/packages/frontend/src/pages/list.vue b/packages/frontend/src/pages/list.vue index 1261428c1c..a52b562c7f 100644 --- a/packages/frontend/src/pages/list.vue +++ b/packages/frontend/src/pages/list.vue @@ -103,6 +103,7 @@ definePage(() => ({ icon: 'ti ti-list', })); +
[${(new Date()).toString()}] ${text.replace(/\n/g,'')}