diff --git a/.config/example.yml b/.config/example.yml index c127eaae22..489cceec34 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -105,6 +105,16 @@ port: 3000 # socket: /path/to/misskey.sock # chmodSocket: '777' +# Proxy trust settings +# +# Changes how the server interpret the origin IP of the request. +# +# Any format supported by Fastify is accepted. +# Default: trust all proxies (i.e. trustProxy: true) +# See: https://fastify.dev/docs/latest/reference/server/#trustproxy +# +# trustProxy: 1 + # ┌──────────────────────────┐ #───┘ PostgreSQL configuration └──────────────────────────────── diff --git a/CHANGELOG.md b/CHANGELOG.md index b1fde74a35..da2116bf54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,41 @@ +## 2025.9.1 + +### NOTE +- pnpm 10.16.0 が必要です + +### General +- Enhance: 広告ごとにセンシティブフラグを設定できるようになりました + +### Client +- Feat: アカウントのQRコードを表示・読み取りできるようになりました +- Feat: 動画を圧縮してアップロードできるようになりました +- Enhance: チャットの日本語名称がダイレクトメッセージに戻るとともに、ベータ版機能ではなくなりました +- Enhance: 画像編集にマスクエフェクト(塗りつぶし、ぼかし)を追加 +- Enhance: ウォーターマークにアカウントのQRコードを追加できるように +- Enhance: 絵文字ピッカーのサイズをより大きくできるように +- Enhance: 時刻計算のための基準値を一か所で管理するようにし、パフォーマンスを向上 +- Fix: iOSで、デバイスがダークモードだと初回読み込み時にエラーになる問題を修正 +- Fix: 設定データの移行が完了しているクライアントでは、設定データ移行用のプログラムの読み込みをスキップするように + +### Server +- Enhance: ユーザーIPを確実に取得できるために設定ファイルにFastifyOptions.trustProxyを追加しました + +## 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 +44,8 @@ ### General - ノートを削除した際、関連するノートが同時に削除されないようになりました - APIで、「replyIdが存在しているのにreplyがnull」や「renoteIdが存在しているのにrenoteがnull」であるという、今までにはなかったパターンが表れることになります -- 定期的に参照されていない古いリモートの投稿を削除する機能が実装されました(コントロールパネル→パフォーマンス→Remote Notes Cleaning) - - 既存のサーバーでは**デフォルトでオフ**、新規サーバーでは**デフォルトでオン**になります +- 定期的に古いリモートの投稿を削除する機能が実装されました + - コントロールパネル→パフォーマンス→Remote Notes Cleaning で有効化できます - データベースの肥大化を防止することが可能です - 既存のサーバーで当機能を有効化した場合は、処理量が多くなるため、一時的にストレージ使用量が増加する可能性があります。 - 増加量を抑えるには、最大処理継続時間をデフォルトより短くしてください。 @@ -15,7 +53,8 @@ - サーバーの初期設定が完了するまでは連合がオンにならないようになりました - 日本語における公開範囲名称の「ダイレクト」が「指名」に改称されました - 実際の動作に即した名称になり、馴染みのない人でも理解しやすくなりました - - 他サービスにおける「ダイレクトメッセージ」に相当するMisskeyの機能は「チャット」ですが、「ダイレクト投稿」という名称の機能が存在するとそちらがダイレクトメッセージ機能であるような誤解を生んでいました + - 他サービスにおける「ダイレクトメッセージ」に相当するMisskeyの機能は「チャット」ですが(過去のバージョンのMisskeyでも、当該機能は「チャット」ではなく「ダイレクトメッセージ」でした)、「ダイレクト投稿」という名称の機能が存在するとそちらがダイレクトメッセージ機能であるような誤解を生んでいました + - 今後、「チャット」の名称を「ダイレクトメッセージ」に戻す可能性があります - mfm.jsをアップデートしました - Enhance: Unicode 15.1 および 16.0 に収録されている絵文字に対応 - Enhance: acctに `.` が入っているユーザーのメンションに対応 @@ -27,6 +66,7 @@ - プラグインは1.xに対応したものが必要です - Playはそのまま動作しますが、新規に作られるプリセットは1.xになります - 以前のバージョンから無効化されていた note_view_interruptor が有効になりました + - ハンドラは同期的である必要があります - Feat: セーフモード - プラグイン・テーマ・カスタムCSSの使用でクライアントの起動に問題が発生した際に、これらを無効にして起動できます - 以下の方法でセーフモードを起動できます @@ -42,9 +82,11 @@ - 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) @@ -54,7 +96,11 @@ - Fix: 複数のメンションを1行に記述した場合に、サジェストが正しく表示されない問題を修正 - Fix: メンションとしての条件を満たしていても、特定の条件(`-`が含まれる場合など)で正しくサジェストされない問題を一部修正 - Fix: ユーザーの前後ノートを閲覧する機能が動作しない問題を修正 -- Fix: 設定データの移行が完了しているクライアントでは、設定データ移行用のプログラムの読み込みをスキップするように +- Fix: 照会ダイアログでap/showでローカルユーザーを解決した際@username@nullに飛ばされる問題を修正 +- Fix: アイコンのデコレーションを付ける際にデコレーションが表示されなくなる問題を修正 +- Fix: タッチ操作時にマウスホバー時のユーザープレビューが開くことがある問題を修正 +- Fix: 管理中アカウント一覧で正しい表示が行われない問題を修正 +- Fix: lookupページでリモートURLを指定した際に正しく動作しない問題を修正 ### Server - Feat: サーバー管理コマンド @@ -68,6 +114,8 @@ - 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 f1a97bb63b..63878bf1b7 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -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/en-US.yml b/locales/en-US.yml index b8a9fdbc44..faa7f5e59e 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." @@ -1668,7 +1668,7 @@ _serverSettings: restartServerSetupWizardConfirm_text: "Some current settings will be reset." entrancePageStyle: "Entrance page style" showTimelineForVisitor: "Show timeline" - showActivityiesForVisitor: "Show activities" + showActivitiesForVisitor: "Show activities" _userGeneratedContentsVisibilityForVisitor: all: "Everything is public" localOnly: "Only local content is published, remote content is kept private" @@ -3194,6 +3194,7 @@ _imageEffector: mirror: "Mirror" invert: "Invert Colors" grayscale: "Grayscale" + blur: "Blur" colorAdjust: "Color Correction" colorClamp: "Color Compression" colorClampAdvanced: "Color Compression (Advanced)" @@ -3209,6 +3210,8 @@ _imageEffector: angle: "Angle" scale: "Size" size: "Size" + radius: "Radius" + samples: "Samples" color: "Color" opacity: "Opacity" normalize: "Normalize" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 5d01c20e05..8a1d2c458b 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -1668,7 +1668,7 @@ _serverSettings: restartServerSetupWizardConfirm_text: "Algunas configuraciones actuales se restablecerán" entrancePageStyle: "Estilo de la página de inicio" showTimelineForVisitor: "Mostrar la línea de tiempo" - showActivityiesForVisitor: "Mostrar actividades" + showActivitiesForVisitor: "Mostrar actividades" _userGeneratedContentsVisibilityForVisitor: all: "Todo es público." localOnly: "Sólo se publica el contenido local, el remoto se mantiene privado" @@ -2137,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" diff --git a/locales/index.d.ts b/locales/index.d.ts index c31a3f4e83..4071d5c373 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1030,6 +1030,10 @@ export interface Locale extends ILocale { * 処理中 */ "processing": string; + /** + * 準備中 + */ + "preprocessing": string; /** * プレビュー */ @@ -1227,7 +1231,7 @@ export interface Locale extends ILocale { */ "noMoreHistory": string; /** - * チャットを始める + * メッセージを送る */ "startChat": string; /** @@ -1927,7 +1931,7 @@ export interface Locale extends ILocale { */ "markAsReadAllUnreadNotes": string; /** - * すべてのチャットを既読にする + * すべてのダイレクトメッセージを既読にする */ "markAsReadAllTalkMessages": string; /** @@ -5390,6 +5394,14 @@ export interface Locale extends ILocale { * チャット */ "chat": string; + /** + * ダイレクトメッセージ + */ + "directMessage": string; + /** + * メッセージ + */ + "directMessage_short": string; /** * 旧設定情報を移行 */ @@ -5501,6 +5513,14 @@ export interface Locale extends ILocale { * 低くすると画質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、画質は低下します。 */ "defaultImageCompressionLevel_description": string; + /** + * デフォルトの圧縮度 + */ + "defaultCompressionLevel": string; + /** + * 低くすると品質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、品質は低下します。 + */ + "defaultCompressionLevel_description": string; /** * 分 */ @@ -5529,6 +5549,40 @@ export interface Locale extends ILocale { * ベータ版の検証にご協力いただきありがとうございます! */ "thankYouForTestingBeta": string; + /** + * ユーザー指定ノートを作成 + */ + "createUserSpecifiedNote": string; + "_compression": { + "_quality": { + /** + * 高品質 + */ + "high": string; + /** + * 中品質 + */ + "medium": string; + /** + * 低品質 + */ + "low": string; + }; + "_size": { + /** + * サイズ大 + */ + "large": string; + /** + * サイズ中 + */ + "medium": string; + /** + * サイズ小 + */ + "small": string; + }; + }; "_order": { /** * 新しい順 @@ -5540,6 +5594,10 @@ export interface Locale extends ILocale { "oldest": string; }; "_chat": { + /** + * メッセージ + */ + "messages": string; /** * まだメッセージはありません */ @@ -5549,36 +5607,36 @@ export interface Locale extends ILocale { */ "newMessage": string; /** - * 個人チャット + * 個別 */ "individualChat": string; /** - * 特定ユーザーとの一対一のチャットができます。 + * 特定ユーザーと個別にメッセージのやりとりができます。 */ "individualChat_description": string; /** - * ルームチャット + * グループ */ "roomChat": string; /** - * 複数人でのチャットができます。 - * また、個人チャットを許可していないユーザーとでも、相手が受け入れればチャットができます。 + * 複数人でメッセージのやりとりができます。 + * また、個別のメッセージを許可していないユーザーとでも、相手が受け入れればやりとりできます。 */ "roomChat_description": string; /** - * ルームを作成 + * グループを作成 */ "createRoom": string; /** - * ユーザーを招待してチャットを始めましょう + * ユーザーを招待してメッセージを送信しましょう */ "inviteUserToChat": string; /** - * 作成したルーム + * 作成したグループ */ "yourRooms": string; /** - * 参加中のルーム + * 参加中のグループ */ "joiningRooms": string; /** @@ -5598,7 +5656,7 @@ export interface Locale extends ILocale { */ "noHistory": string; /** - * ルームはありません + * グループはありません */ "noRooms": string; /** @@ -5618,7 +5676,7 @@ export interface Locale extends ILocale { */ "ignore": string; /** - * ルームから退出 + * グループから退出 */ "leave": string; /** @@ -5642,35 +5700,35 @@ export interface Locale extends ILocale { */ "newline": string; /** - * このルームをミュート + * このグループをミュート */ "muteThisRoom": string; /** - * ルームを削除 + * グループを削除 */ "deleteRoom": string; /** - * このサーバー、またはこのアカウントでチャットは有効化されていません。 + * このサーバー、またはこのアカウントでダイレクトメッセージは有効化されていません。 */ "chatNotAvailableForThisAccountOrServer": string; /** - * このサーバー、またはこのアカウントでチャットは読み取り専用となっています。新たに書き込んだり、チャットルームを作成・参加したりすることはできません。 + * このサーバー、またはこのアカウントでダイレクトメッセージは読み取り専用となっています。新たに書き込んだり、グループを作成・参加したりすることはできません。 */ "chatIsReadOnlyForThisAccountOrServer": string; /** - * 相手のアカウントでチャット機能が使えない状態になっています。 + * 相手のアカウントでダイレクトメッセージが使えない状態になっています。 */ "chatNotAvailableInOtherAccount": string; /** - * このユーザーとのチャットを開始できません + * このユーザーとのダイレクトメッセージを開始できません */ "cannotChatWithTheUser": string; /** - * チャットが使えない状態になっているか、相手がチャットを開放していません。 + * ダイレクトメッセージが使えない状態になっているか、相手がダイレクトメッセージを開放していません。 */ "cannotChatWithTheUser_description": string; /** - * あなたはこのルームの参加者ではありませんが、招待が届いています。参加するには、招待を承認してください。 + * あなたはこのグループの参加者ではありませんが、招待が届いています。参加するには、招待を承認してください。 */ "youAreNotAMemberOfThisRoomButInvited": string; /** @@ -5678,31 +5736,31 @@ export interface Locale extends ILocale { */ "doYouAcceptInvitation": string; /** - * チャットする + * ダイレクトメッセージ */ "chatWithThisUser": string; /** - * このユーザーはフォロワーからのみチャットを受け付けています。 + * このユーザーはフォロワーからのみメッセージを受け付けています。 */ "thisUserAllowsChatOnlyFromFollowers": string; /** - * このユーザーは、このユーザーがフォローしているユーザーからのみチャットを受け付けています。 + * このユーザーは、このユーザーがフォローしているユーザーからのみメッセージを受け付けています。 */ "thisUserAllowsChatOnlyFromFollowing": string; /** - * このユーザーは相互フォローのユーザーからのみチャットを受け付けています。 + * このユーザーは相互フォローのユーザーからのみメッセージを受け付けています。 */ "thisUserAllowsChatOnlyFromMutualFollowing": string; /** - * このユーザーは誰からもチャットを受け付けていません。 + * このユーザーは誰からもメッセージを受け付けていません。 */ "thisUserNotAllowedChatAnyone": string; /** - * チャットを許可する相手 + * メッセージを許可する相手 */ "chatAllowedUsers": string; /** - * 自分からチャットメッセージを送った相手とはこの設定に関わらずチャットが可能です。 + * 自分からメッセージを送った相手とはこの設定に関わらずメッセージの送受信が可能です。 */ "chatAllowedUsers_note": string; "_chatAllowedUsers": { @@ -6531,7 +6589,7 @@ export interface Locale extends ILocale { */ "remoteNotesCleaning": string; /** - * 有効にすると、参照されていない古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。 + * 有効にすると、一定期間経過したリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。 */ "remoteNotesCleaning_description": string; /** @@ -6633,7 +6691,7 @@ export interface Locale extends ILocale { /** * アクティビティを表示する */ - "showActivityiesForVisitor": string; + "showActivitiesForVisitor": string; "_userGeneratedContentsVisibilityForVisitor": { /** * 全て公開 @@ -7856,7 +7914,7 @@ export interface Locale extends ILocale { */ "canImportUserLists": string; /** - * チャットを許可 + * ダイレクトメッセージを許可 */ "chatAvailability": string; /** @@ -8706,7 +8764,7 @@ export interface Locale extends ILocale { */ "badge": string; /** - * チャットの背景 + * メッセージの背景 */ "messageBg": string; /** @@ -8733,7 +8791,7 @@ export interface Locale extends ILocale { */ "reaction": string; /** - * チャットのメッセージ + * ダイレクトメッセージ */ "chatMessage": string; }; @@ -9017,11 +9075,11 @@ export interface Locale extends ILocale { */ "write:following": string; /** - * チャットを見る + * ダイレクトメッセージを見る */ "read:messaging": string; /** - * チャットを操作する + * ダイレクトメッセージを操作する */ "write:messaging": string; /** @@ -9313,11 +9371,11 @@ export interface Locale extends ILocale { */ "write:report-abuse": string; /** - * チャットを操作する + * ダイレクトメッセージを操作する */ "write:chat": string; /** - * チャットを閲覧する + * ダイレクトメッセージを閲覧する */ "read:chat": string; }; @@ -9543,7 +9601,7 @@ export interface Locale extends ILocale { */ "birthdayFollowings": string; /** - * チャット + * ダイレクトメッセージ */ "chat": string; }; @@ -10283,7 +10341,7 @@ export interface Locale extends ILocale { */ "roleAssigned": string; /** - * チャットルームへ招待されました + * ダイレクトメッセージのグループへ招待されました */ "chatRoomInvitationReceived": string; /** @@ -10396,7 +10454,7 @@ export interface Locale extends ILocale { */ "roleAssigned": string; /** - * チャットルームへ招待された + * ダイレクトメッセージのグループへ招待された */ "chatRoomInvitationReceived": string; /** @@ -10578,7 +10636,7 @@ export interface Locale extends ILocale { */ "roleTimeline": string; /** - * チャット + * ダイレクトメッセージ */ "chat": string; }; @@ -10945,7 +11003,7 @@ export interface Locale extends ILocale { */ "deleteGalleryPost": string; /** - * チャットルームを削除 + * ダイレクトメッセージのグループを削除 */ "deleteChatRoom": string; /** @@ -12032,11 +12090,11 @@ export interface Locale extends ILocale { */ "youCanConfigureMoreFederationSettingsLater": string; /** - * 受信コンテンツの自動クリーニング + * リモートコンテンツの自動クリーニング */ "remoteContentsCleaning": string; /** - * 連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。 + * 連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、一定期間経過したリモートコンテンツを自動でサーバーから削除し、ストレージを節約できます。 */ "remoteContentsCleaning_description": string; /** @@ -12219,10 +12277,18 @@ export interface Locale extends ILocale { * テキスト */ "text": string; + /** + * 二次元コード + */ + "qr": string; /** * 位置 */ "position": string; + /** + * マージン + */ + "margin": string; /** * タイプ */ @@ -12279,6 +12345,10 @@ export interface Locale extends ILocale { * サブドットの数 */ "polkadotSubDotDivisions": string; + /** + * 空欄にするとアカウントのURLになります + */ + "leaveBlankToAccountUrl": string; }; "_imageEffector": { /** @@ -12318,6 +12388,10 @@ export interface Locale extends ILocale { * 白黒 */ "grayscale": string; + /** + * ぼかし + */ + "blur": string; /** * 色調補正 */ @@ -12362,6 +12436,10 @@ export interface Locale extends ILocale { * ティアリング */ "tearing": string; + /** + * 塗りつぶし + */ + "fill": string; }; "_fxProps": { /** @@ -12376,6 +12454,18 @@ export interface Locale extends ILocale { * サイズ */ "size": string; + /** + * 半径 + */ + "radius": string; + /** + * サンプル数 + */ + "samples": string; + /** + * 位置 + */ + "offset": string; /** * 色 */ @@ -12488,6 +12578,10 @@ export interface Locale extends ILocale { * 黒色にする */ "zoomLinesBlack": string; + /** + * 円形 + */ + "circle": string; }; }; /** @@ -12548,6 +12642,68 @@ export interface Locale extends ILocale { */ "listDrafts": string; }; + /** + * 二次元コード + */ + "qr": string; + "_qr": { + /** + * 表示 + */ + "showTabTitle": string; + /** + * 読み取る + */ + "readTabTitle": string; + /** + * {name} {acct} + */ + "shareTitle": ParameterizedString<"name" | "acct">; + /** + * Fediverseで私をフォローしてください! + */ + "shareText": string; + /** + * カメラを選択 + */ + "chooseCamera": string; + /** + * ライト選択不可 + */ + "cannotToggleFlash": string; + /** + * ライトをオンにする + */ + "turnOnFlash": string; + /** + * ライトをオフにする + */ + "turnOffFlash": string; + /** + * コードリーダーを再開 + */ + "startQr": string; + /** + * コードリーダーを停止 + */ + "stopQr": string; + /** + * QRコードが見つかりません + */ + "noQrCodeFound": string; + /** + * 端末の画像をスキャン + */ + "scanFile": string; + /** + * テキスト + */ + "raw": string; + /** + * MFM + */ + "mfm": string; + }; } declare const locales: { [lang: string]: Locale; diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 9d91e558f9..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" @@ -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..c0e598ef7b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -253,6 +253,7 @@ noteDeleteConfirm: "このノートを削除しますか?" pinLimitExceeded: "これ以上ピン留めできません" done: "完了" processing: "処理中" +preprocessing: "準備中" preview: "プレビュー" default: "デフォルト" defaultValueIs: "デフォルト: {value}" @@ -302,7 +303,7 @@ uploadNFiles: "{n}個のファイルをアップロード" explore: "みつける" messageRead: "既読" noMoreHistory: "これより過去の履歴はありません" -startChat: "チャットを始める" +startChat: "メッセージを送る" nUsersRead: "{n}人が読みました" agreeTo: "{0}に同意" agree: "同意する" @@ -477,7 +478,7 @@ notFoundDescription: "指定されたURLに該当するページはありませ uploadFolder: "既定アップロード先" markAsReadAllNotifications: "すべての通知を既読にする" markAsReadAllUnreadNotes: "すべての投稿を既読にする" -markAsReadAllTalkMessages: "すべてのチャットを既読にする" +markAsReadAllTalkMessages: "すべてのダイレクトメッセージを既読にする" help: "ヘルプ" inputMessageHere: "ここにメッセージを入力" close: "閉じる" @@ -1343,6 +1344,8 @@ postForm: "投稿フォーム" textCount: "文字数" information: "情報" chat: "チャット" +directMessage: "ダイレクトメッセージ" +directMessage_short: "メッセージ" migrateOldSettings: "旧設定情報を移行" migrateOldSettings_description: "通常これは自動で行われていますが、何らかの理由により上手く移行されなかった場合は手動で移行処理をトリガーできます。現在の設定情報は上書きされます。" compress: "圧縮" @@ -1370,6 +1373,8 @@ redisplayAllTips: "全ての「ヒントとコツ」を再表示" hideAllTips: "全ての「ヒントとコツ」を非表示" defaultImageCompressionLevel: "デフォルトの画像圧縮度" defaultImageCompressionLevel_description: "低くすると画質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、画質は低下します。" +defaultCompressionLevel: "デフォルトの圧縮度" +defaultCompressionLevel_description: "低くすると品質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、品質は低下します。" inMinutes: "分" inDays: "日" safeModeEnabled: "セーフモードが有効です" @@ -1377,53 +1382,65 @@ pluginsAreDisabledBecauseSafeMode: "セーフモードが有効なため、プ customCssIsDisabledBecauseSafeMode: "セーフモードが有効なため、カスタムCSSは適用されていません。" themeIsDefaultBecauseSafeMode: "セーフモードが有効な間はデフォルトのテーマが使用されます。セーフモードをオフにすると元に戻ります。" thankYouForTestingBeta: "ベータ版の検証にご協力いただきありがとうございます!" +createUserSpecifiedNote: "ユーザー指定ノートを作成" + +_compression: + _quality: + high: "高品質" + medium: "中品質" + low: "低品質" + _size: + large: "サイズ大" + medium: "サイズ中" + small: "サイズ小" _order: newest: "新しい順" oldest: "古い順" _chat: + messages: "メッセージ" noMessagesYet: "まだメッセージはありません" newMessage: "新しいメッセージ" - individualChat: "個人チャット" - individualChat_description: "特定ユーザーとの一対一のチャットができます。" - roomChat: "ルームチャット" - roomChat_description: "複数人でのチャットができます。\nまた、個人チャットを許可していないユーザーとでも、相手が受け入れればチャットができます。" - createRoom: "ルームを作成" - inviteUserToChat: "ユーザーを招待してチャットを始めましょう" - yourRooms: "作成したルーム" - joiningRooms: "参加中のルーム" + individualChat: "個別" + individualChat_description: "特定ユーザーと個別にメッセージのやりとりができます。" + roomChat: "グループ" + roomChat_description: "複数人でメッセージのやりとりができます。\nまた、個別のメッセージを許可していないユーザーとでも、相手が受け入れればやりとりできます。" + createRoom: "グループを作成" + inviteUserToChat: "ユーザーを招待してメッセージを送信しましょう" + yourRooms: "作成したグループ" + joiningRooms: "参加中のグループ" invitations: "招待" noInvitations: "招待はありません" history: "履歴" noHistory: "履歴はありません" - noRooms: "ルームはありません" + noRooms: "グループはありません" inviteUser: "ユーザーを招待" sentInvitations: "送信した招待" join: "参加" ignore: "無視" - leave: "ルームから退出" + leave: "グループから退出" members: "メンバー" searchMessages: "メッセージを検索" home: "ホーム" send: "送信" newline: "改行" - muteThisRoom: "このルームをミュート" - deleteRoom: "ルームを削除" - chatNotAvailableForThisAccountOrServer: "このサーバー、またはこのアカウントでチャットは有効化されていません。" - chatIsReadOnlyForThisAccountOrServer: "このサーバー、またはこのアカウントでチャットは読み取り専用となっています。新たに書き込んだり、チャットルームを作成・参加したりすることはできません。" - chatNotAvailableInOtherAccount: "相手のアカウントでチャット機能が使えない状態になっています。" - cannotChatWithTheUser: "このユーザーとのチャットを開始できません" - cannotChatWithTheUser_description: "チャットが使えない状態になっているか、相手がチャットを開放していません。" - youAreNotAMemberOfThisRoomButInvited: "あなたはこのルームの参加者ではありませんが、招待が届いています。参加するには、招待を承認してください。" + muteThisRoom: "このグループをミュート" + deleteRoom: "グループを削除" + chatNotAvailableForThisAccountOrServer: "このサーバー、またはこのアカウントでダイレクトメッセージは有効化されていません。" + chatIsReadOnlyForThisAccountOrServer: "このサーバー、またはこのアカウントでダイレクトメッセージは読み取り専用となっています。新たに書き込んだり、グループを作成・参加したりすることはできません。" + chatNotAvailableInOtherAccount: "相手のアカウントでダイレクトメッセージが使えない状態になっています。" + cannotChatWithTheUser: "このユーザーとのダイレクトメッセージを開始できません" + cannotChatWithTheUser_description: "ダイレクトメッセージが使えない状態になっているか、相手がダイレクトメッセージを開放していません。" + youAreNotAMemberOfThisRoomButInvited: "あなたはこのグループの参加者ではありませんが、招待が届いています。参加するには、招待を承認してください。" doYouAcceptInvitation: "招待を承認しますか?" - chatWithThisUser: "チャットする" - thisUserAllowsChatOnlyFromFollowers: "このユーザーはフォロワーからのみチャットを受け付けています。" - thisUserAllowsChatOnlyFromFollowing: "このユーザーは、このユーザーがフォローしているユーザーからのみチャットを受け付けています。" - thisUserAllowsChatOnlyFromMutualFollowing: "このユーザーは相互フォローのユーザーからのみチャットを受け付けています。" - thisUserNotAllowedChatAnyone: "このユーザーは誰からもチャットを受け付けていません。" - chatAllowedUsers: "チャットを許可する相手" - chatAllowedUsers_note: "自分からチャットメッセージを送った相手とはこの設定に関わらずチャットが可能です。" + chatWithThisUser: "ダイレクトメッセージ" + thisUserAllowsChatOnlyFromFollowers: "このユーザーはフォロワーからのみメッセージを受け付けています。" + thisUserAllowsChatOnlyFromFollowing: "このユーザーは、このユーザーがフォローしているユーザーからのみメッセージを受け付けています。" + thisUserAllowsChatOnlyFromMutualFollowing: "このユーザーは相互フォローのユーザーからのみメッセージを受け付けています。" + thisUserNotAllowedChatAnyone: "このユーザーは誰からもメッセージを受け付けていません。" + chatAllowedUsers: "メッセージを許可する相手" + chatAllowedUsers_note: "自分からメッセージを送った相手とはこの設定に関わらずメッセージの送受信が可能です。" _chatAllowedUsers: everyone: "誰でも" followers: "自分のフォロワーのみ" @@ -1660,7 +1677,7 @@ _serverSettings: fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。" reactionsBufferingDescription: "有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。" remoteNotesCleaning: "リモート投稿の自動クリーニング" - remoteNotesCleaning_description: "有効にすると、参照されていない古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。" + remoteNotesCleaning_description: "有効にすると、一定期間経過したリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。" remoteNotesCleaningMaxProcessingDuration: "最大クリーニング処理継続時間" remoteNotesCleaningExpiryDaysForEachNotes: "最低ノート保持日数" inquiryUrl: "問い合わせ先URL" @@ -1685,7 +1702,7 @@ _serverSettings: restartServerSetupWizardConfirm_text: "現在の一部の設定はリセットされます。" entrancePageStyle: "エントランスページのスタイル" showTimelineForVisitor: "タイムラインを表示する" - showActivityiesForVisitor: "アクティビティを表示する" + showActivitiesForVisitor: "アクティビティを表示する" _userGeneratedContentsVisibilityForVisitor: all: "全て公開" @@ -2034,7 +2051,7 @@ _role: canImportFollowing: "フォローのインポートを許可" canImportMuting: "ミュートのインポートを許可" canImportUserLists: "リストのインポートを許可" - chatAvailability: "チャットを許可" + chatAvailability: "ダイレクトメッセージを許可" uploadableFileTypes: "アップロード可能なファイル種別" uploadableFileTypes_caption: "MIMEタイプを指定します。改行で区切って複数指定できるほか、アスタリスク(*)でワイルドカード指定できます。(例: image/*)" uploadableFileTypes_caption2: "ファイルによっては種別を判定できないことがあります。そのようなファイルを許可する場合は {x} を指定に追加してください。" @@ -2281,7 +2298,7 @@ _theme: buttonHoverBg: "ボタンの背景 (ホバー)" inputBorder: "入力ボックスの縁取り" badge: "バッジ" - messageBg: "チャットの背景" + messageBg: "メッセージの背景" fgHighlighted: "強調された文字" _sfx: @@ -2289,7 +2306,7 @@ _sfx: noteMy: "ノート(自分)" notification: "通知" reaction: "リアクション選択時" - chatMessage: "チャットのメッセージ" + chatMessage: "ダイレクトメッセージ" _soundSettings: driveFile: "ドライブの音声を使用" @@ -2369,8 +2386,8 @@ _permissions: "write:favorites": "お気に入りを操作する" "read:following": "フォローの情報を見る" "write:following": "フォロー・フォロー解除する" - "read:messaging": "チャットを見る" - "write:messaging": "チャットを操作する" + "read:messaging": "ダイレクトメッセージを見る" + "write:messaging": "ダイレクトメッセージを操作する" "read:mutes": "ミュートを見る" "write:mutes": "ミュートを操作する" "write:notes": "ノートを作成・削除する" @@ -2443,8 +2460,8 @@ _permissions: "read:clip-favorite": "クリップのいいねを見る" "read:federation": "連合に関する情報を取得する" "write:report-abuse": "違反を報告する" - "write:chat": "チャットを操作する" - "read:chat": "チャットを閲覧する" + "write:chat": "ダイレクトメッセージを操作する" + "read:chat": "ダイレクトメッセージを閲覧する" _auth: shareAccessTitle: "アプリへのアクセス許可" @@ -2507,7 +2524,7 @@ _widgets: chooseList: "リストを選択" clicker: "クリッカー" birthdayFollowings: "今日誕生日のユーザー" - chat: "チャット" + chat: "ダイレクトメッセージ" _cw: hide: "隠す" @@ -2714,7 +2731,7 @@ _notification: newNote: "新しい投稿" unreadAntennaNote: "アンテナ {name}" roleAssigned: "ロールが付与されました" - chatRoomInvitationReceived: "チャットルームへ招待されました" + chatRoomInvitationReceived: "ダイレクトメッセージのグループへ招待されました" emptyPushNotificationMessage: "プッシュ通知の更新をしました" achievementEarned: "実績を獲得" testNotification: "通知テスト" @@ -2744,7 +2761,7 @@ _notification: receiveFollowRequest: "フォロー申請を受け取った" followRequestAccepted: "フォローが受理された" roleAssigned: "ロールが付与された" - chatRoomInvitationReceived: "チャットルームへ招待された" + chatRoomInvitationReceived: "ダイレクトメッセージのグループへ招待された" achievementEarned: "実績の獲得" exportCompleted: "エクスポートが完了した" login: "ログイン" @@ -2794,7 +2811,7 @@ _deck: mentions: "メンション" direct: "指名" roleTimeline: "ロールタイムライン" - chat: "チャット" + chat: "ダイレクトメッセージ" _dialog: charactersExceeded: "最大文字数を超えています! 現在 {current} / 制限 {max}" @@ -2897,7 +2914,7 @@ _moderationLogTypes: deletePage: "ページを削除" deleteFlash: "Playを削除" deleteGalleryPost: "ギャラリーの投稿を削除" - deleteChatRoom: "チャットルームを削除" + deleteChatRoom: "ダイレクトメッセージのグループを削除" updateProxyAccountDescription: "プロキシアカウントの説明を更新" _fileViewer: @@ -3216,8 +3233,8 @@ _serverSetupWizard: doYouConnectToFediverse_description1: "分散型サーバーで構成されるネットワーク(Fediverse)に接続すると、他のサーバーと相互にコンテンツのやり取りが可能です。" doYouConnectToFediverse_description2: "Fediverseと接続することは「連合」とも呼ばれます。" youCanConfigureMoreFederationSettingsLater: "連合可能なサーバーの指定など、高度な設定も後ほど可能です。" - remoteContentsCleaning: "受信コンテンツの自動クリーニング" - remoteContentsCleaning_description: "連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。" + remoteContentsCleaning: "リモートコンテンツの自動クリーニング" + remoteContentsCleaning_description: "連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、一定期間経過したリモートコンテンツを自動でサーバーから削除し、ストレージを節約できます。" adminInfo: "管理者情報" adminInfo_description: "問い合わせを受け付けるために使用される管理者情報を設定します。" adminInfo_mustBeFilled: "オープンサーバー、または連合がオンの場合は必ず入力が必要です。" @@ -3271,7 +3288,9 @@ _watermarkEditor: opacity: "不透明度" scale: "サイズ" text: "テキスト" + qr: "二次元コード" position: "位置" + margin: "マージン" type: "タイプ" image: "画像" advanced: "高度" @@ -3286,6 +3305,7 @@ _watermarkEditor: polkadotSubDotOpacity: "サブドットの不透明度" polkadotSubDotRadius: "サブドットの大きさ" polkadotSubDotDivisions: "サブドットの数" + leaveBlankToAccountUrl: "空欄にするとアカウントのURLになります" _imageEffector: title: "エフェクト" @@ -3299,6 +3319,7 @@ _imageEffector: mirror: "ミラー" invert: "色の反転" grayscale: "白黒" + blur: "ぼかし" colorAdjust: "色調補正" colorClamp: "色の圧縮" colorClampAdvanced: "色の圧縮(高度)" @@ -3310,11 +3331,15 @@ _imageEffector: checker: "チェッカー" blockNoise: "ブロックノイズ" tearing: "ティアリング" + fill: "塗りつぶし" _fxProps: angle: "角度" scale: "サイズ" size: "サイズ" + radius: "半径" + samples: "サンプル数" + offset: "位置" color: "色" opacity: "不透明度" normalize: "正規化" @@ -3343,6 +3368,7 @@ _imageEffector: zoomLinesThreshold: "集中線の幅" zoomLinesMaskSize: "中心径" zoomLinesBlack: "黒色にする" + circle: "円形" drafts: "下書き" _drafts: @@ -3359,3 +3385,20 @@ _drafts: restoreFromDraft: "下書きから復元" restore: "復元" listDrafts: "下書き一覧" + +qr: "二次元コード" +_qr: + showTabTitle: "表示" + readTabTitle: "読み取る" + shareTitle: "{name} {acct}" + shareText: "Fediverseで私をフォローしてください!" + chooseCamera: "カメラを選択" + cannotToggleFlash: "ライト選択不可" + turnOnFlash: "ライトをオンにする" + turnOffFlash: "ライトをオフにする" + startQr: "コードリーダーを再開" + stopQr: "コードリーダーを停止" + noQrCodeFound: "QRコードが見つかりません" + scanFile: "端末の画像をスキャン" + raw: "テキスト" + mfm: "MFM" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index bcd927e758..8809d3b4d3 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1668,7 +1668,7 @@ _serverSettings: restartServerSetupWizardConfirm_text: "현재 일부 설정은 리셋됩니다." entrancePageStyle: "입구 페이지의 스타일" showTimelineForVisitor: "타임라인 표시" - showActivityiesForVisitor: "활동 표시" + showActivitiesForVisitor: "액티비티 표시하기" _userGeneratedContentsVisibilityForVisitor: all: "모두 공개" localOnly: "로컬 콘텐츠만 공개하고 리모트 콘텐츠는 비공개" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index aea69f6f24..5e52143f83 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -1215,6 +1215,7 @@ privacyPolicyUrl: "Ссылка на Политику Конфиденциаль tosAndPrivacyPolicy: "Условия использования и политика конфиденциальности" avatarDecorations: "Украшения для аватара" attach: "Прикрепить" +detachAll: "Убрать всё" angle: "Угол" flip: "Переворот" showAvatarDecorations: "Показать украшения для аватара" @@ -1253,7 +1254,7 @@ clipNoteLimitExceeded: "К этому клипу больше нельзя до performance: "Производительность" modified: "Изменено" signinWithPasskey: "Войдите в систему, используя свой пароль" -unknownWebAuthnKey: "Не известный ключ " +unknownWebAuthnKey: "Неизвестный ключ" passkeyVerificationFailed: "Ошибка проверка ключа доступа " messageToFollower: "Сообщение подписчикам" testCaptchaWarning: "Эта функция предназначена для тестирования CAPTCHA. Не использовать это в рабочей среде" @@ -1268,8 +1269,11 @@ availableRoles: "Доступные роли" federationDisabled: "Федерация отключена для этого сервера. Вы не можете взаимодействовать с пользователями на других серверах." draft: "Черновик" markAsSensitiveConfirm: "Отметить контент как чувствительный?" +preferences: "Основное" resetToDefaultValue: "Сбросить настройки до стандартных" +syncBetweenDevices: "Синхронизировать между устройствами" postForm: "Форма отправки" +textCount: "Количество символов" information: "Описание" inMinutes: "мин" inDays: "сут" @@ -1281,6 +1285,11 @@ _chat: send: "Отправить" _settings: webhook: "Вебхук" + preferencesBanner: "Вы можете настроить общее поведение клиента по вашим предпочтениям" + timelineAndNote: "Лента и заметки" + _chat: + showSenderName: "Показывать имя отправителя" + sendOnEnter: "Использовать Enter для отправки" _delivery: stop: "Заморожено" _type: @@ -1529,7 +1538,7 @@ _achievements: description: "Нажато здесь" _justPlainLucky: title: "Чистая удача" - description: "Может достаться с вероятностью 0,01% каждые 10 секунд." + description: "Может достаться с вероятностью 0,005% каждые 10 секунд." _setNameToSyuilo: title: "Комплекс бога" description: "Установлено «syuilo» в качестве имени" @@ -1557,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/tr-TR.yml b/locales/tr-TR.yml index 2efb51f08f..5ca2b18fac 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -360,7 +360,7 @@ whenServerDisconnected: "Sunucu ile bağlantı kesildiğinde" disconnectedFromServer: "Sunucu bağlantısı kesildi" reload: "Yenile" doNothing: "Yoksay" -reloadConfirm: "Zaman çizelgesini yenilemek ister misin?" +reloadConfirm: "Panoyu yenilemek ister misin?" watch: "İzle" unwatch: "İzlemeyi bırak" accept: "Kabul et" @@ -573,9 +573,9 @@ objectStorageSetPublicRead: "Yükleme sırasında \"genel-okuma\" ayarını yap s3ForcePathStyleDesc: "s3ForcePathStyle etkinleştirilirse, kova adı URL'nin ana bilgisayar adı yerine URL yoluna eklenmelidir. Kendi kendine barındırılan bir Minio örneği gibi hizmetleri kullanırken bu ayarı etkinleştirmen gerekebilir." serverLogs: "Sunucu log kayıtları" deleteAll: "Tümünü sil" -showFixedPostForm: "Gönderi formunu zaman çizelgesinin en üstünde görüntüle" -showFixedPostFormInChannel: "Gönderi formunu zaman çizelgesinin en üstünde görüntüle (Kanallar)" -withRepliesByDefaultForNewlyFollowed: "Yeni takip edilen kullanıcıların yanıtlarını varsayılan olarak zaman çizelgesine dahil et" +showFixedPostForm: "Gönderi formunu pano üstünde görüntüle" +showFixedPostFormInChannel: "Gönderi formunu pano üstünde görüntüle (Kanallar)" +withRepliesByDefaultForNewlyFollowed: "Yeni takip edilen kullanıcıların yanıtlarını varsayılan olarak panoya dahil et" newNoteRecived: "Yeni Not'lar var" newNote: "Yeni Not" sounds: "Sesler" @@ -1059,7 +1059,7 @@ 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." thisPostMayBeAnnoying: "Bu not başkalarını rahatsız edebilir." -thisPostMayBeAnnoyingHome: "Ana zaman çizelgesine gönder" +thisPostMayBeAnnoyingHome: "Ana panoya gönder" thisPostMayBeAnnoyingCancel: "İptal" thisPostMayBeAnnoyingIgnore: "Yine de gönder" collapseRenotes: "Daha önce görüntülenen Renote'lari kısaltılmış olarak göster" @@ -1218,8 +1218,8 @@ showRepliesToOthersInTimeline: "Pano'da diğer kişilere verilen yanıtları gö hideRepliesToOthersInTimeline: "Pano'dan diğer kişilerin yanıtlarını gizle" showRepliesToOthersInTimelineAll: "Pano'da takip ettiğin herkesin diğerlerine verdiği yanıtları göster" hideRepliesToOthersInTimelineAll: "Pano'da takip ettiğin herkesten diğer kişilere verilen yanıtları gizle" -confirmShowRepliesAll: "Bu işlem geri alınamaz. Takip ettiğin herkesin yanıtlarını zaman çizelgende diğer kullanıcılara göstermek istiyor musun?" -confirmHideRepliesAll: "Bu işlem geri alınamaz. Şu anda takip ettiğin tüm kullanıcıların yanıtlarını zaman tünelinde cidden göstermeyecek misin?" +confirmShowRepliesAll: "Bu işlem geri alınamaz. Takip ettiğin herkesin yanıtlarını panoda diğer kullanıcılara göstermek istiyor musun?" +confirmHideRepliesAll: "Bu işlem geri alınamaz. Şu anda takip ettiğin tüm kullanıcıların yanıtlarını panoda cidden göstermeyecek misin?" externalServices: "Dış Hizmetler" sourceCode: "Kaynak kodu" sourceCodeIsNotYetProvided: "Kaynak kodu henüz mevcut değildir. Bu sorunu gidermek için yöneticiyle iletişime geçin." @@ -1570,9 +1570,9 @@ _initialTutorial: description: "Burada, Misskey'i kullanmanın temellerini ve özelliklerini öğrenebilirsin." _note: title: "Not nedir?" - description: "Misskey'deki gönderiler “Notlar” olarak adlandırılır. Notlar zaman çizelgesinde kronolojik olarak düzenlenir ve gerçek zamanlı olarak güncellenir." + description: "Misskey'deki gönderiler “Notlar” olarak adlandırılır. Notlar panoda kronolojik olarak düzenlenir ve gerçek zamanlı olarak güncellenir." reply: "Bir mesaja yanıt vermek için bu düğmeye tıklayın. Yanıtlara yanıt vermek de mümkündür, böylece konuşma bir konu başlığı gibi devam eder." - renote: "Bu notu kendi zaman çizelgende paylaşabilirsiniz. Ayrıca yorumlarınızla birlikte alıntı da yapabilirsin." + renote: "Bu notu kendi panonda paylaşabilirsin. Ayrıca yorumlarınla birlikte alıntı da yapabilirsin." reaction: "Not'a tepkiler ekleyebilirsin. Daha fazla ayrıntı bir sonraki sayfada açıklanacak." menu: "Not ayrıntılarını görüntüleyebilir, bağlantıları kopyalayabilir ve çeşitli diğer işlemleri gerçekleştirebilirsin." _reaction: @@ -1640,7 +1640,7 @@ _serverSettings: shortNameDescription: "Resmi adın uzun olması durumunda görüntülenebilen, örneğin adının kısaltması." fanoutTimelineDescription: "Etkinleştirildiğinde Pano alma performansını büyük ölçüde artırır ve veritabanı yükünü azaltır. Bunun karşılığında Redis'in bellek kullanımı artacaktır. Sunucu belleği düşükse veya sunucu kararsızsa bunu devre dışı bırakmayı düşün." fanoutTimelineDbFallback: "Veritabanına geri dön" - fanoutTimelineDbFallbackDescription: "Etkinleştirildiğinde, Pano önbelleğe alınmamışsa ek sorgular için veritabanına geri döner. Bu özelliği devre dışı bırakmak, geri dönüş sürecini ortadan kaldırarak sunucu yükünü daha da azaltır, ancak alınabilecek zaman çizelgelerinin aralığını sınırlar." + fanoutTimelineDbFallbackDescription: "Etkinleştirildiğinde, Pano önbelleğe alınmamışsa ek sorgular için veritabanına geri döner. Bu özelliği devre dışı bırakmak, geri dönüş sürecini ortadan kaldırarak sunucu yükünü daha da azaltır, ancak alınabilecek panoların aralığını sınırlar." reactionsBufferingDescription: "Etkinleştirildiğinde, reaksiyon oluşturma sırasında performans büyük ölçüde artacak ve veritabanı üzerindeki yük azalacaktır. Ancak, Redis bellek kullanımı artacakt." remoteNotesCleaning: "Uzak notların otomatik olarak temizlenmesi" remoteNotesCleaning_description: "Etkinleştirildiğinde, kullanılmayan ve güncelliğini yitirmiş uzak notlar, veritabanının şişmesini önlemek için periyodik olarak temizlenecek." @@ -1668,7 +1668,7 @@ _serverSettings: restartServerSetupWizardConfirm_text: "Bazı mevcut ayarlar sıfırlanacaktır." entrancePageStyle: "Giriş sayfası stili" showTimelineForVisitor: "Panoyu göster" - showActivityiesForVisitor: "Aktiviteleri göster" + showActivitiesForVisitor: "Aktiviteleri göster" _userGeneratedContentsVisibilityForVisitor: all: "Her şey halka açıktır." localOnly: "Yalnızca yerel içerik yayınlanır, uzak içerik gizli tutulur." @@ -1877,7 +1877,7 @@ _achievements: title: "Öz Referans" description: "Kendi notunuzu alıntı yapın" _htl20npm: - title: "Akış Zaman Çizelgesi" + title: "Akış Panosu" description: "Ev zaman çizelgenizin hızı 20 npm'yi (dakika başına not sayısı) aşıyor mu?" _viewInstanceChart: title: "Analist" @@ -1966,7 +1966,7 @@ _role: asBadge: "Rozet olarak göster" descriptionOfAsBadge: "This role's icon will be displayed next to the username of users with this role if turned on." isExplorable: "Rolü keşfedilebilir hale getir" - descriptionOfIsExplorable: "Bu rolün zaman çizelgesi ve bu role sahip kullanıcıların listesi, etkinleştirilirse kamuya açık hale getirilecek." + descriptionOfIsExplorable: "Bu rolün panosu ve bu role sahip kullanıcıların listesi, etkinleştirilirse kamuya açık hale getirilecek." displayOrder: "Pozisyon" descriptionOfDisplayOrder: "Sayı ne kadar yüksekse, UI pozisyonu da o kadar yüksek olur." preserveAssignmentOnMoveAccount: "Geçiş sırasında rol atamalarını koruyun" @@ -1980,7 +1980,7 @@ _role: high: "Yüksek" _options: gtlAvailable: "Global Pano'yu görüntüleyebilir" - ltlAvailable: "Yerel zaman çizelgesini görüntüleyebilir" + ltlAvailable: "Yerel panoyu görüntüleyebilir" canPublicNote: "Halka açık notlar gönderebilir" mentionMax: "Bir notta maksimum bahsetme sayısı" canInvite: "Sunucu davet kodları oluşturabilir" @@ -2485,7 +2485,7 @@ _visibility: public: "Halka açık" publicDescription: "Notunuz tüm kullanıcılar tarafından görülebilir olacaktır." home: "Pano" - homeDescription: "Yalnızca ana zaman çizelgesine gönder" + homeDescription: "Yalnızca ana panoya gönder" followers: "Takipçiler" followersDescription: "Sadece takipçilerine görünür hale getir" specified: "Doğrudan" @@ -2532,7 +2532,7 @@ _exportOrImport: userLists: "Kullanıcı listeleri" excludeMutingUsers: "Sessize alınan kullanıcıları hariç tut" excludeInactiveUsers: "Etkin olmayan kullanıcıları hariç tut" - withReplies: "İçe aktarılan kullanıcıların yanıtlarını zaman çizelgesine dahil edin" + withReplies: "İçe aktarılan kullanıcıların yanıtlarını panoya dahil edin" _charts: federation: "Federasyon" apRequest: "Talepler" @@ -2926,7 +2926,7 @@ _reversi: freeMatch: "Ücretsiz Eşleştirme" lookingForPlayer: "Rakip aranıyor..." gameCanceled: "Oyun iptal edildi." - shareToTlTheGameWhenStart: "Oyun başlatıldığında zaman çizelgesinde paylaş" + shareToTlTheGameWhenStart: "Oyun başlatıldığında panoda paylaş" iStartedAGame: "Oyun başladı! #MisskeyReversi" opponentHasSettingsChanged: "Rakip ayarlarını değiştirmiş." allowIrregularRules: "Düzensiz kurallar (tamamen ücretsiz)" @@ -3154,7 +3154,7 @@ _clientPerformanceIssueTip: _clip: tip: "Klip, notları gruplandırmanıza olanak tanıyan bir özelliktir." _userLists: - tip: "Listeler, oluşturulurken belirttiğin herhangi bir kullanıcıyı içerebilir. Oluşturulan liste, yalnızca belirtilen kullanıcıları gösteren bir zaman çizelgesi olarak görüntülenebilir." + tip: "Listeler, oluşturulurken belirttiğin herhangi bir kullanıcıyı içerebilir. Oluşturulan liste, yalnızca belirtilen kullanıcıları gösteren bir pano olarak görüntülenebilir." watermark: "Filigran" defaultPreset: "Varsayılan Ön Ayar" _watermarkEditor: diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 08f3fda096..6c62c80f0d 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -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 1656896133..65b7f9bfba 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -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 027d832494..faec340213 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "misskey", - "version": "2025.8.0-beta.3", + "version": "2025.9.1-alpha.1", "codename": "nasubi", "repository": { "type": "git", "url": "https://github.com/misskey-dev/misskey.git" }, - "packageManager": "pnpm@10.15.0", + "packageManager": "pnpm@10.16.0", "workspaces": [ "packages/frontend-shared", "packages/frontend", @@ -62,21 +62,22 @@ "js-yaml": "4.1.0", "postcss": "8.5.6", "tar": "7.4.3", - "terser": "5.43.1", + "terser": "5.44.0", "typescript": "5.9.2" }, "devDependencies": { "@misskey-dev/eslint-plugin": "2.1.0", - "@types/node": "22.17.2", - "@typescript-eslint/eslint-plugin": "8.40.0", - "@typescript-eslint/parser": "8.40.0", + "@types/js-yaml": "4.0.9", + "@types/node": "22.18.1", + "@typescript-eslint/eslint-plugin": "8.42.0", + "@typescript-eslint/parser": "8.42.0", "cross-env": "7.0.3", "cypress": "14.5.4", - "eslint": "9.34.0", + "eslint": "9.35.0", "globals": "16.3.0", "ncp": "2.0.0", - "pnpm": "10.15.0", - "start-server-and-test": "2.0.13" + "pnpm": "10.16.0", + "start-server-and-test": "2.1.0" }, "optionalDependencies": { "@tensorflow/tfjs-core": "4.22.0" 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/migration/1757823175259-sensitive-ad.js b/packages/backend/migration/1757823175259-sensitive-ad.js new file mode 100644 index 0000000000..46f0f270ab --- /dev/null +++ b/packages/backend/migration/1757823175259-sensitive-ad.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class SensitiveAd1757823175259 { + name = 'SensitiveAd1757823175259' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "ad" ADD "isSensitive" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "ad" DROP COLUMN "isSensitive"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 39ea07125d..5114912769 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.5", + "@swc/core-darwin-x64": "1.13.5", "@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.5", + "@swc/core-linux-arm64-gnu": "1.13.5", + "@swc/core-linux-arm64-musl": "1.13.5", + "@swc/core-linux-x64-gnu": "1.13.5", + "@swc/core-linux-x64-musl": "1.13.5", + "@swc/core-win32-arm64-msvc": "1.13.5", + "@swc/core-win32-ia32-msvc": "1.13.5", + "@swc/core-win32-x64-msvc": "1.13.5", "@tensorflow/tfjs": "4.22.0", "@tensorflow/tfjs-node": "4.22.0", "bufferutil": "4.0.9", @@ -69,20 +69,20 @@ "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.883.0", + "@aws-sdk/lib-storage": "3.883.0", "@discordapp/twemoji": "16.0.1", "@fastify/accepts": "5.0.2", "@fastify/cookie": "11.0.2", "@fastify/cors": "10.1.0", "@fastify/express": "4.0.2", "@fastify/http-proxy": "10.0.2", - "@fastify/multipart": "9.0.3", + "@fastify/multipart": "9.2.1", "@fastify/static": "8.2.0", "@fastify/view": "10.0.2", "@misskey-dev/sharp-read-bmp": "1.2.0", "@misskey-dev/summaly": "5.2.3", - "@napi-rs/canvas": "0.1.77", + "@napi-rs/canvas": "0.1.79", "@nestjs/common": "11.1.6", "@nestjs/core": "11.1.6", "@nestjs/testing": "11.1.6", @@ -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.5", "@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.5", "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,13 +114,13 @@ "content-disposition": "0.5.4", "date-fns": "2.30.0", "deep-email-validator": "0.1.21", - "fastify": "5.4.0", + "fastify": "5.6.0", "fastify-raw-body": "5.0.0", "feed": "4.2.2", "file-type": "19.6.0", "fluent-ffmpeg": "2.1.3", "form-data": "4.0.4", - "got": "14.4.7", + "got": "14.4.8", "happy-dom": "16.8.1", "hpagent": "1.2.0", "htmlescape": "1.1.1", @@ -135,13 +135,13 @@ "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", "misskey-js": "workspace:*", "misskey-reversi": "workspace:*", - "ms": "3.0.0-canary.1", + "ms": "3.0.0-canary.202508261828", "nanoid": "5.1.5", "nested-property": "4.0.0", "node-fetch": "3.3.2", @@ -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", @@ -175,12 +175,12 @@ "slacc": "0.0.10", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", - "systeminformation": "5.27.7", + "systeminformation": "5.27.8", "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.18.1", + "@types/nodemailer": "6.4.19", "@types/oauth": "0.9.6", "@types/oauth2orize": "1.11.5", "@types/oauth2orize-pkce": "0.1.2", @@ -222,7 +222,7 @@ "@types/ratelimiter": "3.4.6", "@types/rename": "1.0.7", "@types/sanitize-html": "2.16.0", - "@types/semver": "7.7.0", + "@types/semver": "7.7.1", "@types/simple-oauth2": "5.0.7", "@types/sinonjs__fake-timers": "8.1.5", "@types/supertest": "6.0.3", @@ -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.42.0", + "@typescript-eslint/parser": "8.42.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/config.ts b/packages/backend/src/config.ts index f71f1d7e34..fdf6fe18e2 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -7,6 +7,7 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; import * as yaml from 'js-yaml'; +import { type FastifyServerOptions } from 'fastify'; import type * as Sentry from '@sentry/node'; import type * as SentryVue from '@sentry/vue'; import type { RedisOptions } from 'ioredis'; @@ -27,6 +28,7 @@ type Source = { url?: string; port?: number; socket?: string; + trustProxy?: FastifyServerOptions['trustProxy']; chmodSocket?: string; disableHsts?: boolean; db: { @@ -118,6 +120,7 @@ export type Config = { url: string; port: number; socket: string | undefined; + trustProxy: FastifyServerOptions['trustProxy']; chmodSocket: string | undefined; disableHsts: boolean | undefined; db: { @@ -266,6 +269,7 @@ export function loadConfig(): Config { url: url.origin, port: config.port ?? parseInt(process.env.PORT ?? '', 10), socket: config.socket, + trustProxy: config.trustProxy, chmodSocket: config.chmodSocket, disableHsts: config.disableHsts, host, 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/QueueService.ts b/packages/backend/src/core/QueueService.ts index 0f225a8242..2d0e7b5d83 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -756,8 +756,8 @@ export class QueueService { @bindThis public async queueRetryJob(queueType: typeof QUEUE_TYPES[number], jobId: string) { const queue = this.getQueue(queueType); - const job: Bull.Job | null = await queue.getJob(jobId); - if (job) { + const job = await queue.getJob(jobId); + if (job != null) { if (job.finishedOn != null) { await job.retry(); } else { @@ -769,8 +769,8 @@ export class QueueService { @bindThis public async queueRemoveJob(queueType: typeof QUEUE_TYPES[number], jobId: string) { const queue = this.getQueue(queueType); - const job: Bull.Job | null = await queue.getJob(jobId); - if (job) { + const job = await queue.getJob(jobId); + if (job != null) { await job.remove(); } } @@ -803,8 +803,8 @@ export class QueueService { @bindThis public async queueGetJob(queueType: typeof QUEUE_TYPES[number], jobId: string) { const queue = this.getQueue(queueType); - const job: Bull.Job | null = await queue.getJob(jobId); - if (job) { + const job = await queue.getJob(jobId); + if (job != null) { return this.packJobData(job); } else { throw new Error(`Job not found: ${jobId}`); diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 3df7ee69ee..9e1d9cc370 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; @@ -100,14 +101,15 @@ export const DEFAULT_POLICIES: RolePolicies = { userEachUserListsLimit: 50, rateLimitFactor: 1, avatarDecorationLimit: 1, - canImportAntennas: true, - canImportBlocking: true, - canImportFollowing: true, - canImportMuting: true, - canImportUserLists: true, + canImportAntennas: false, + canImportBlocking: false, + canImportFollowing: false, + canImportMuting: false, + canImportUserLists: false, chatAvailability: 'available', uploadableFileTypes: [ 'text/plain', + 'text/csv', 'application/json', 'image/*', 'video/*', 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/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index f8abfb2f98..2da614a120 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -117,6 +117,7 @@ export class MetaEntityService { ratio: ad.ratio, imageUrl: ad.imageUrl, dayOfWeek: ad.dayOfWeek, + isSensitive: ad.isSensitive ? true : undefined, })), notesPerOneAd: instance.notesPerOneAd, enableEmail: instance.enableEmail, 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/Ad.ts b/packages/backend/src/models/Ad.ts index 108e991c70..0d402fcbe8 100644 --- a/packages/backend/src/models/Ad.ts +++ b/packages/backend/src/models/Ad.ts @@ -54,10 +54,17 @@ export class MiAd { length: 8192, nullable: false, }) public memo: string; + @Column('integer', { default: 0, nullable: false, }) public dayOfWeek: number; + + @Column('boolean', { + default: false, + }) + public isSensitive: boolean; + constructor(data: Partial) { if (data == null) return; 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/ad.ts b/packages/backend/src/models/json-schema/ad.ts index b01b39a38b..d88ac23894 100644 --- a/packages/backend/src/models/json-schema/ad.ts +++ b/packages/backend/src/models/json-schema/ad.ts @@ -60,5 +60,10 @@ export const packedAdSchema = { optional: false, nullable: false, }, + isSensitive: { + type: 'boolean', + optional: false, + nullable: false, + }, }, } as const; diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index 357ff26041..a0e7d490b3 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -195,6 +195,10 @@ export const packedMetaLiteSchema = { type: 'integer', optional: false, nullable: false, }, + isSensitive: { + type: 'boolean', + optional: true, nullable: false, + }, }, }, }, 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/ServerService.ts b/packages/backend/src/server/ServerService.ts index 7325c53df0..1286b4dad6 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -75,7 +75,7 @@ export class ServerService implements OnApplicationShutdown { @bindThis public async launch(): Promise { const fastify = Fastify({ - trustProxy: true, + trustProxy: this.config.trustProxy ?? true, logger: false, }); this.#fastify = fastify; 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/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index 06047b58a6..6606202118 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -34,13 +34,22 @@ export const meta = { res: { type: 'object', optional: false, nullable: false, - ref: 'MeDetailed', - properties: { - token: { - type: 'string', - optional: false, nullable: false, + allOf: [ + { + type: 'object', + ref: 'MeDetailed', }, - }, + { + type: 'object', + optional: false, nullable: false, + properties: { + token: { + type: 'string', + optional: false, nullable: false, + }, + }, + } + ], }, } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts index 955154f4fb..01697ae185 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -36,6 +36,7 @@ export const paramDef = { startsAt: { type: 'integer' }, imageUrl: { type: 'string', minLength: 1 }, dayOfWeek: { type: 'integer' }, + isSensitive: { type: 'boolean' }, }, required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'imageUrl', 'dayOfWeek'], } as const; @@ -55,6 +56,7 @@ export default class extends Endpoint { // eslint- expiresAt: new Date(ps.expiresAt), startsAt: new Date(ps.startsAt), dayOfWeek: ps.dayOfWeek, + isSensitive: ps.isSensitive, url: ps.url, imageUrl: ps.imageUrl, priority: ps.priority, @@ -73,6 +75,7 @@ export default class extends Endpoint { // eslint- expiresAt: ad.expiresAt.toISOString(), startsAt: ad.startsAt.toISOString(), dayOfWeek: ad.dayOfWeek, + isSensitive: ad.isSensitive, url: ad.url, imageUrl: ad.imageUrl, priority: ad.priority, diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts index 4f897d98e4..f67cad5bd2 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts @@ -63,6 +63,7 @@ export default class extends Endpoint { // eslint- expiresAt: ad.expiresAt.toISOString(), startsAt: ad.startsAt.toISOString(), dayOfWeek: ad.dayOfWeek, + isSensitive: ad.isSensitive, url: ad.url, imageUrl: ad.imageUrl, memo: ad.memo, diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts index 4e3d731aca..a3d9aaddc6 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -39,6 +39,7 @@ export const paramDef = { expiresAt: { type: 'integer' }, startsAt: { type: 'integer' }, dayOfWeek: { type: 'integer' }, + isSensitive: { type: 'boolean' }, }, required: ['id'], } as const; @@ -66,6 +67,7 @@ export default class extends Endpoint { // eslint- expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : undefined, startsAt: ps.startsAt ? new Date(ps.startsAt) : undefined, dayOfWeek: ps.dayOfWeek, + isSensitive: ps.isSensitive, }); const updatedAd = await this.adsRepository.findOneByOrFail({ id: ad.id }); 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/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 4afed7dc5c..fe48e7497a 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -18,9 +18,9 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; -import { ApiError } from '../../error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['federation'], 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/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index cae0e752da..a41de25ddf 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -29,10 +29,16 @@ export const meta = { id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d', }, - signinRequired: { - message: 'Signin required.', - code: 'SIGNIN_REQUIRED', - id: '8e75455b-738c-471d-9f80-62693f33372e', + contentRestrictedByUser: { + message: 'Content restricted by user. Please sign in to view.', + code: 'CONTENT_RESTRICTED_BY_USER', + id: 'fbcc002d-37d9-4944-a6b0-d9e29f2d33ab', + }, + + contentRestrictedByServer: { + message: 'Content restricted by server settings. Please sign in to view.', + code: 'CONTENT_RESTRICTED_BY_SERVER', + id: '145f88d2-b03d-4087-8143-a78928883c4b', }, }, } as const; @@ -61,15 +67,15 @@ export default class extends Endpoint { // eslint- }); if (note.user!.requireSigninToViewContents && me == null) { - throw new ApiError(meta.errors.signinRequired); + throw new ApiError(meta.errors.contentRestrictedByUser); } if (this.serverSettings.ugcVisibilityForVisitor === 'none' && me == null) { - throw new ApiError(meta.errors.signinRequired); + throw new ApiError(meta.errors.contentRestrictedByServer); } if (this.serverSettings.ugcVisibilityForVisitor === 'local' && note.userHost != null && me == null) { - throw new ApiError(meta.errors.signinRequired); + throw new ApiError(meta.errors.contentRestrictedByServer); } return await this.noteEntityService.pack(note, me, { 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..c6d477a92f 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -22,7 +22,26 @@ export const meta = { res: { type: 'object', optional: false, nullable: false, - ref: 'UserList', + allOf: [ + { + type: 'object', + ref: 'UserList', + }, + { + type: 'object', + optional: false, nullable: false, + 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/boot.js b/packages/backend/src/server/web/boot.js index 0c0b46f82b..ab4b158287 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -13,7 +13,7 @@ }; window.onunhandledrejection = (e) => { console.error(e); - renderError('SOMETHING_HAPPENED_IN_PROMISE', e); + renderError('SOMETHING_HAPPENED_IN_PROMISE', e.reason || e); }; let forceError = localStorage.getItem('forceError'); 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/backend/test-federation/test/utils.ts b/packages/backend/test-federation/test/utils.ts index 7e24bb7904..056a16ba15 100644 --- a/packages/backend/test-federation/test/utils.ts +++ b/packages/backend/test-federation/test/utils.ts @@ -68,7 +68,6 @@ async function createAdmin(host: Host): Promise { ADMIN_CACHE.set(host, { id: res.id, - // @ts-expect-error FIXME: openapi-typescript generates incorrect response type for this endpoint, so ignore this i: res.token, }); return res as Misskey.entities.SignupResponse; diff --git a/packages/frontend-builder/package.json b/packages/frontend-builder/package.json index 5fdd25b32d..f03efac6d0 100644 --- a/packages/frontend-builder/package.json +++ b/packages/frontend-builder/package.json @@ -20,6 +20,6 @@ "dependencies": { "estree-walker": "3.0.3", "magic-string": "0.30.17", - "vite": "7.0.6" + "vite": "7.0.7" } } diff --git a/packages/frontend-embed/eslint.config.js b/packages/frontend-embed/eslint.config.js index 179d811e77..46247e40d5 100644 --- a/packages/frontend-embed/eslint.config.js +++ b/packages/frontend-embed/eslint.config.js @@ -46,9 +46,71 @@ export default [ allowSingleExtends: true, }], 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], - // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため - // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため - 'id-denylist': ['error', 'window', 'e'], + // window ... グローバルスコープと衝突し、予期せぬ結果を招くため + // e ... error や event など、複数のキーワードの頭文字であり分かりにくいため + // close ... window.closeと衝突 or 紛らわしい + // open ... window.openと衝突 or 紛らわしい + // fetch ... window.fetchと衝突 or 紛らわしい + // location ... window.locationと衝突 or 紛らわしい + // document ... window.documentと衝突 or 紛らわしい + // history ... window.historyと衝突 or 紛らわしい + // scroll ... window.scrollと衝突 or 紛らわしい + // setTimeout ... window.setTimeoutと衝突 or 紛らわしい + // setInterval ... window.setIntervalと衝突 or 紛らわしい + // clearTimeout ... window.clearTimeoutと衝突 or 紛らわしい + // clearInterval ... window.clearIntervalと衝突 or 紛らわしい + 'id-denylist': ['error', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history', 'scroll', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'], + 'no-restricted-globals': [ + 'error', + { + 'name': 'open', + 'message': 'Use `window.open`.', + }, + { + 'name': 'close', + 'message': 'Use `window.close`.', + }, + { + 'name': 'fetch', + 'message': 'Use `window.fetch`.', + }, + { + 'name': 'location', + 'message': 'Use `window.location`.', + }, + { + 'name': 'document', + 'message': 'Use `window.document`.', + }, + { + 'name': 'history', + 'message': 'Use `window.history`.', + }, + { + 'name': 'scroll', + 'message': 'Use `window.scroll`.', + }, + { + 'name': 'setTimeout', + 'message': 'Use `window.setTimeout`.', + }, + { + 'name': 'setInterval', + 'message': 'Use `window.setInterval`.', + }, + { + 'name': 'clearTimeout', + 'message': 'Use `window.clearTimeout`.', + }, + { + 'name': 'clearInterval', + 'message': 'Use `window.clearInterval`.', + }, + { + 'name': 'name', + 'message': 'Use `window.name`. もしくは name という変数名を定義し忘れている', + }, + ], 'no-shadow': ['warn'], 'vue/attributes-order': ['error', { alphabetical: false, diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json index 73bcd798f0..6a3392c021 100644 --- a/packages/frontend-embed/package.json +++ b/packages/frontend-embed/package.json @@ -13,10 +13,10 @@ "@discordapp/twemoji": "16.0.1", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "6.0.2", - "@rollup/pluginutils": "5.2.0", + "@rollup/pluginutils": "5.3.0", "@twemoji/parser": "16.0.0", "@vitejs/plugin-vue": "6.0.1", - "@vue/compiler-sfc": "3.5.19", + "@vue/compiler-sfc": "3.5.21", "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.48.0", - "sass": "1.90.0", - "shiki": "3.11.0", + "rollup": "4.50.1", + "sass": "1.92.1", + "shiki": "3.12.2", "tinycolor2": "1.6.0", "tsc-alias": "1.8.16", "tsconfig-paths": "4.2.0", "typescript": "5.9.2", "uuid": "11.1.0", - "vite": "7.1.3", - "vue": "3.5.19" + "vite": "7.1.5", + "vue": "3.5.21" }, "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.2", + "@types/node": "22.18.1", "@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/tinycolor2": "1.4.6", "@types/ws": "8.18.1", - "@typescript-eslint/eslint-plugin": "8.40.0", - "@typescript-eslint/parser": "8.40.0", + "@typescript-eslint/eslint-plugin": "8.42.0", + "@typescript-eslint/parser": "8.42.0", "@vitest/coverage-v8": "3.2.4", - "@vue/runtime-core": "3.5.19", + "@vue/runtime-core": "3.5.21", "acorn": "8.15.0", "cross-env": "10.0.0", "eslint-plugin-import": "2.32.0", @@ -59,11 +59,11 @@ "happy-dom": "18.0.1", "intersection-observer": "0.12.2", "micromatch": "4.0.8", - "msw": "2.10.5", + "msw": "2.11.1", "nodemon": "3.1.10", "prettier": "3.6.2", - "start-server-and-test": "2.0.13", - "tsx": "4.20.4", + "start-server-and-test": "2.1.0", + "tsx": "4.20.5", "vite-plugin-turbosnap": "1.0.3", "vue-component-type-helpers": "3.0.6", "vue-eslint-parser": "10.2.0", diff --git a/packages/frontend-embed/src/boot.ts b/packages/frontend-embed/src/boot.ts index 9d69437c30..961cbcef66 100644 --- a/packages/frontend-embed/src/boot.ts +++ b/packages/frontend-embed/src/boot.ts @@ -33,7 +33,7 @@ import type { Theme } from '@/theme.js'; console.log('Misskey Embed'); //#region Embedパラメータの取得・パース -const params = new URLSearchParams(location.search); +const params = new URLSearchParams(window.location.search); const embedParams = parseEmbedParams(params); if (_DEV_) console.log(embedParams); //#endregion @@ -81,7 +81,7 @@ storeBootloaderErrors({ ...i18n.ts._bootErrors, reload: i18n.ts.reload }); //#endregion // サイズの制限 -document.documentElement.style.maxWidth = '500px'; +window.document.documentElement.style.maxWidth = '500px'; // iframeIdの設定 function setIframeIdHandler(event: MessageEvent) { @@ -114,16 +114,16 @@ app.provide(DI.embedParams, embedParams); const rootEl = ((): HTMLElement => { const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; - const currentRoot = document.getElementById(MISSKEY_MOUNT_DIV_ID); + const currentRoot = window.document.getElementById(MISSKEY_MOUNT_DIV_ID); if (currentRoot) { console.warn('multiple import detected'); return currentRoot; } - const root = document.createElement('div'); + const root = window.document.createElement('div'); root.id = MISSKEY_MOUNT_DIV_ID; - document.body.appendChild(root); + window.document.body.appendChild(root); return root; })(); @@ -159,7 +159,7 @@ console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hu //#endregion function removeSplash() { - const splash = document.getElementById('splash'); + const splash = window.document.getElementById('splash'); if (splash) { splash.style.opacity = '0'; splash.style.pointerEvents = 'none'; diff --git a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue index 0bff048ce4..71f0ee9294 100644 --- a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue +++ b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue @@ -19,7 +19,7 @@ import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurha const canvasPromise = new Promise(resolve => { // テスト環境で Web Worker インスタンスは作成できない if (import.meta.env.MODE === 'test') { - const canvas = document.createElement('canvas'); + const canvas = window.document.createElement('canvas'); canvas.width = 64; canvas.height = 64; resolve(canvas); @@ -34,7 +34,7 @@ const canvasPromise = new Promise(resol ); resolve(workers); } else { - const canvas = document.createElement('canvas'); + const canvas = window.document.createElement('canvas'); canvas.width = 64; canvas.height = 64; resolve(canvas); diff --git a/packages/frontend-embed/src/components/EmInstanceTicker.vue b/packages/frontend-embed/src/components/EmInstanceTicker.vue index 4a116e317a..7add3bb53f 100644 --- a/packages/frontend-embed/src/components/EmInstanceTicker.vue +++ b/packages/frontend-embed/src/components/EmInstanceTicker.vue @@ -29,7 +29,7 @@ const props = defineProps<{ // if no instance data is given, this is for the local instance const instance = props.instance ?? { name: serverMetadata.name, - themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content, + themeColor: (window.document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content, }; const faviconUrl = computed(() => props.instance ? mediaProxy.getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : mediaProxy.getProxiedImageUrlNullable(serverMetadata.iconUrl, 'preview') ?? '/favicon.ico'); diff --git a/packages/frontend-embed/src/components/EmMention.vue b/packages/frontend-embed/src/components/EmMention.vue index b5aaa95894..0a8ac9c05a 100644 --- a/packages/frontend-embed/src/components/EmMention.vue +++ b/packages/frontend-embed/src/components/EmMention.vue @@ -27,7 +27,7 @@ const canonical = props.host === localHost ? `@${props.username}` : `@${props.us const url = `/${canonical}`; -const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-mention')); +const bg = tinycolor(getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-mention')); bg.setAlpha(0.1); const bgCss = bg.toRgbString(); diff --git a/packages/frontend-embed/src/components/EmPagination.vue b/packages/frontend-embed/src/components/EmPagination.vue index 94a91305f4..bd49d127a9 100644 --- a/packages/frontend-embed/src/components/EmPagination.vue +++ b/packages/frontend-embed/src/components/EmPagination.vue @@ -134,7 +134,7 @@ const isBackTop = ref(false); const empty = computed(() => items.value.size === 0); const error = ref(false); -const scrollableElement = computed(() => rootEl.value ? getScrollContainer(rootEl.value) : document.body); +const scrollableElement = computed(() => rootEl.value ? getScrollContainer(rootEl.value) : window.document.body); const visibility = useDocumentVisibility(); @@ -353,7 +353,7 @@ watch(visibility, () => { BACKGROUND_PAUSE_WAIT_SEC * 1000); } else { // 'visible' if (timerForSetPause) { - clearTimeout(timerForSetPause); + window.clearTimeout(timerForSetPause); timerForSetPause = null; } else { isPausingUpdate = false; @@ -447,11 +447,11 @@ onBeforeMount(() => { init().then(() => { if (props.pagination.reversed) { nextTick(() => { - setTimeout(toBottom, 800); + window.setTimeout(toBottom, 800); // scrollToBottomでmoreFetchingボタンが画面外まで出るまで // more = trueを遅らせる - setTimeout(() => { + window.setTimeout(() => { moreFetching.value = false; }, 2000); }); @@ -461,11 +461,11 @@ onBeforeMount(() => { onBeforeUnmount(() => { if (timerForSetPause) { - clearTimeout(timerForSetPause); + window.clearTimeout(timerForSetPause); timerForSetPause = null; } if (preventAppearFetchMoreTimer.value) { - clearTimeout(preventAppearFetchMoreTimer.value); + window.clearTimeout(preventAppearFetchMoreTimer.value); preventAppearFetchMoreTimer.value = null; } scrollObserver.value?.disconnect(); diff --git a/packages/frontend-embed/src/server-context.ts b/packages/frontend-embed/src/server-context.ts index a84a1a726a..c061d5a6f1 100644 --- a/packages/frontend-embed/src/server-context.ts +++ b/packages/frontend-embed/src/server-context.ts @@ -4,7 +4,7 @@ */ import * as Misskey from 'misskey-js'; -const providedContextEl = document.getElementById('misskey_embedCtx'); +const providedContextEl = window.document.getElementById('misskey_embedCtx'); export type ServerContext = { clip?: Misskey.entities.Clip; diff --git a/packages/frontend-embed/src/server-metadata.ts b/packages/frontend-embed/src/server-metadata.ts index 6c94aacd48..ad9b5a1a91 100644 --- a/packages/frontend-embed/src/server-metadata.ts +++ b/packages/frontend-embed/src/server-metadata.ts @@ -6,7 +6,7 @@ import * as Misskey from 'misskey-js'; import { misskeyApi } from '@/misskey-api.js'; -const providedMetaEl = document.getElementById('misskey_meta'); +const providedMetaEl = window.document.getElementById('misskey_meta'); const _serverMetadata: Misskey.entities.MetaDetailed | null = (providedMetaEl && providedMetaEl.textContent) ? JSON.parse(providedMetaEl.textContent) : null; diff --git a/packages/frontend-embed/src/theme.ts b/packages/frontend-embed/src/theme.ts index 680ab80167..c7bc5df85d 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'; @@ -33,15 +35,15 @@ export function assertIsTheme(theme: Record): theme is Theme { export function applyTheme(theme: Theme, persist = true) { if (timeout) window.clearTimeout(timeout); - document.documentElement.classList.add('_themeChanging_'); + window.document.documentElement.classList.add('_themeChanging_'); timeout = window.setTimeout(() => { - document.documentElement.classList.remove('_themeChanging_'); + window.document.documentElement.classList.remove('_themeChanging_'); }, 1000); const colorScheme = theme.base === 'dark' ? 'dark' : 'light'; - document.documentElement.dataset.colorScheme = colorScheme; + window.document.documentElement.dataset.colorScheme = colorScheme; // Deep copy const _theme = JSON.parse(JSON.stringify(theme)); @@ -53,7 +55,7 @@ export function applyTheme(theme: Theme, persist = true) { const props = compile(_theme); - for (const tag of document.head.children) { + for (const tag of window.document.head.children) { if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { tag.setAttribute('content', props['htmlThemeColor']); break; @@ -61,7 +63,7 @@ export function applyTheme(theme: Theme, persist = true) { } for (const [k, v] of Object.entries(props)) { - document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); + window.document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); } // iframeを正常に透過させるために、cssのcolor-schemeは `light dark;` 固定にしてある。style.scss参照 diff --git a/packages/frontend-embed/src/ui.vue b/packages/frontend-embed/src/ui.vue index 4ba5968a91..711d0eae6d 100644 --- a/packages/frontend-embed/src/ui.vue +++ b/packages/frontend-embed/src/ui.vue @@ -52,8 +52,8 @@ function safeURIDecode(str: string): string { } } -const page = location.pathname.split('/')[2]; -const contentId = safeURIDecode(location.pathname.split('/')[3]); +const page = window.location.pathname.split('/')[2]; +const contentId = safeURIDecode(window.location.pathname.split('/')[3]); if (_DEV_) console.log(page, contentId); const embedParams = inject(DI.embedParams, defaultEmbedParams); diff --git a/packages/frontend-shared/eslint.config.js b/packages/frontend-shared/eslint.config.js index 6453be0042..b972cfdb27 100644 --- a/packages/frontend-shared/eslint.config.js +++ b/packages/frontend-shared/eslint.config.js @@ -51,9 +51,71 @@ export default [ allowSingleExtends: true, }], 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], - // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため - // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため - 'id-denylist': ['error', 'window', 'e'], + // window ... グローバルスコープと衝突し、予期せぬ結果を招くため + // e ... error や event など、複数のキーワードの頭文字であり分かりにくいため + // close ... window.closeと衝突 or 紛らわしい + // open ... window.openと衝突 or 紛らわしい + // fetch ... window.fetchと衝突 or 紛らわしい + // location ... window.locationと衝突 or 紛らわしい + // document ... window.documentと衝突 or 紛らわしい + // history ... window.historyと衝突 or 紛らわしい + // scroll ... window.scrollと衝突 or 紛らわしい + // setTimeout ... window.setTimeoutと衝突 or 紛らわしい + // setInterval ... window.setIntervalと衝突 or 紛らわしい + // clearTimeout ... window.clearTimeoutと衝突 or 紛らわしい + // clearInterval ... window.clearIntervalと衝突 or 紛らわしい + 'id-denylist': ['error', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history', 'scroll', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'], + 'no-restricted-globals': [ + 'error', + { + 'name': 'open', + 'message': 'Use `window.open`.', + }, + { + 'name': 'close', + 'message': 'Use `window.close`.', + }, + { + 'name': 'fetch', + 'message': 'Use `window.fetch`.', + }, + { + 'name': 'location', + 'message': 'Use `window.location`.', + }, + { + 'name': 'document', + 'message': 'Use `window.document`.', + }, + { + 'name': 'history', + 'message': 'Use `window.history`.', + }, + { + 'name': 'scroll', + 'message': 'Use `window.scroll`.', + }, + { + 'name': 'setTimeout', + 'message': 'Use `window.setTimeout`.', + }, + { + 'name': 'setInterval', + 'message': 'Use `window.setInterval`.', + }, + { + 'name': 'clearTimeout', + 'message': 'Use `window.clearTimeout`.', + }, + { + 'name': 'clearInterval', + 'message': 'Use `window.clearInterval`.', + }, + { + 'name': 'name', + 'message': 'Use `window.name`. もしくは name という変数名を定義し忘れている', + }, + ], 'no-shadow': ['warn'], 'vue/attributes-order': ['error', { alphabetical: false, diff --git a/packages/frontend-shared/js/config.ts b/packages/frontend-shared/js/config.ts index ac5c5629f3..6272d3f6b9 100644 --- a/packages/frontend-shared/js/config.ts +++ b/packages/frontend-shared/js/config.ts @@ -4,15 +4,15 @@ */ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -const address = new URL(document.querySelector('meta[property="instance_url"]')?.content || location.href); -const siteName = document.querySelector('meta[property="og:site_name"]')?.content; +const address = new URL(window.document.querySelector('meta[property="instance_url"]')?.content || window.location.href); +const siteName = window.document.querySelector('meta[property="og:site_name"]')?.content; export const host = address.host; export const hostname = address.hostname; export const url = address.origin; export const port = address.port; -export const apiUrl = location.origin + '/api'; -export const wsOrigin = location.origin; +export const apiUrl = window.location.origin + '/api'; +export const wsOrigin = window.location.origin; export const lang = localStorage.getItem('lang') ?? 'en-US'; export const langs = _LANGS_; export const version = _VERSION_; diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts index b2d83fff8b..c8c437afe9 100644 --- a/packages/frontend-shared/js/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -54,68 +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', - 'canSearchUsers', - '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/js/scroll.ts b/packages/frontend-shared/js/scroll.ts index 9057b896c6..5578cffdec 100644 --- a/packages/frontend-shared/js/scroll.ts +++ b/packages/frontend-shared/js/scroll.ts @@ -51,7 +51,7 @@ export function onScrollTop(el: HTMLElement, cb: (topVisible: boolean) => unknow // - toleranceの範囲内に収まる程度の微量なスクロールが発生した let prevTopVisible = firstTopVisible; const onScroll = () => { - if (!document.body.contains(el)) return; + if (!window.document.body.contains(el)) return; const topVisible = isHeadVisible(el, tolerance); if (topVisible !== prevTopVisible) { @@ -78,7 +78,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1 const containerOrWindow = container ?? window; const onScroll = () => { - if (!document.body.contains(el)) return; + if (!window.document.body.contains(el)) return; if (isTailVisible(el, 1, container)) { cb(); if (once) removeListener(); @@ -145,8 +145,8 @@ export function isTailVisible(el: HTMLElement, tolerance = 1, container = getScr // https://ja.javascript.info/size-and-scroll-window#ref-932 export function getBodyScrollHeight() { return Math.max( - document.body.scrollHeight, document.documentElement.scrollHeight, - document.body.offsetHeight, document.documentElement.offsetHeight, - document.body.clientHeight, document.documentElement.clientHeight, + window.document.body.scrollHeight, window.document.documentElement.scrollHeight, + window.document.body.offsetHeight, window.document.documentElement.offsetHeight, + window.document.body.clientHeight, window.document.documentElement.clientHeight, ); } diff --git a/packages/frontend-shared/js/use-document-visibility.ts b/packages/frontend-shared/js/use-document-visibility.ts index b1197e68da..a87c1f1bab 100644 --- a/packages/frontend-shared/js/use-document-visibility.ts +++ b/packages/frontend-shared/js/use-document-visibility.ts @@ -7,18 +7,18 @@ import { onMounted, onUnmounted, ref } from 'vue'; import type { Ref } from 'vue'; export function useDocumentVisibility(): Ref { - const visibility = ref(document.visibilityState); + const visibility = ref(window.document.visibilityState); const onChange = (): void => { - visibility.value = document.visibilityState; + visibility.value = window.document.visibilityState; }; onMounted(() => { - document.addEventListener('visibilitychange', onChange); + window.document.addEventListener('visibilitychange', onChange); }); onUnmounted(() => { - document.removeEventListener('visibilitychange', onChange); + window.document.removeEventListener('visibilitychange', onChange); }); return visibility; diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json index ea2d66a7c7..aebc418e3c 100644 --- a/packages/frontend-shared/package.json +++ b/packages/frontend-shared/package.json @@ -21,9 +21,9 @@ "lint": "pnpm typecheck && pnpm eslint" }, "devDependencies": { - "@types/node": "22.17.2", - "@typescript-eslint/eslint-plugin": "8.40.0", - "@typescript-eslint/parser": "8.40.0", + "@types/node": "22.18.1", + "@typescript-eslint/eslint-plugin": "8.42.0", + "@typescript-eslint/parser": "8.42.0", "esbuild": "0.25.9", "eslint-plugin-vue": "10.4.0", "nodemon": "3.1.10", @@ -35,6 +35,6 @@ ], "dependencies": { "misskey-js": "workspace:*", - "vue": "3.5.19" + "vue": "3.5.21" } } 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 952af75d97..104ec42a18 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -23,13 +23,13 @@ "@misskey-dev/browser-image-resizer": "2024.1.0", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "6.0.2", - "@rollup/pluginutils": "5.2.0", - "@sentry/vue": "10.5.0", + "@rollup/pluginutils": "5.3.0", + "@sentry/vue": "10.10.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.19", + "@vue/compiler-sfc": "3.5.21", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", "analytics": "0.8.19", "astring": "1.9.0", @@ -41,7 +41,7 @@ "chartjs-chart-matrix": "3.0.0", "chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-zoom": "2.2.0", - "chromatic": "13.1.3", + "chromatic": "13.1.4", "compare-versions": "6.1.1", "cropperjs": "2.0.1", "date-fns": "4.1.0", @@ -57,36 +57,38 @@ "json5": "2.2.3", "magic-string": "0.30.18", "matter-js": "0.20.0", + "mediabunny": "1.15.1", "mfm-js": "0.25.0", "misskey-bubble-game": "workspace:*", "misskey-js": "workspace:*", "misskey-reversi": "workspace:*", "photoswipe": "5.4.4", "punycode.js": "2.3.1", - "rollup": "4.48.0", + "qr-code-styling": "1.9.2", + "qr-scanner": "1.4.2", + "rollup": "4.50.1", "sanitize-html": "2.17.0", - "sass": "1.90.0", - "shiki": "3.11.0", + "sass": "1.92.1", + "shiki": "3.12.2", "strict-event-emitter-types": "2.0.0", "textarea-caret": "3.1.0", - "three": "0.179.1", + "three": "0.180.0", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", "tsc-alias": "1.8.16", "tsconfig-paths": "4.2.0", "typescript": "5.9.2", "v-code-diff": "1.13.1", - "vite": "7.1.3", - "vue": "3.5.19", + "vite": "7.1.5", + "vue": "3.5.21", "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.3", + "@storybook/addon-links": "9.1.5", "@storybook/addon-mdx-gfm": "8.6.14", "@storybook/addon-storysource": "8.6.14", "@storybook/blocks": "8.6.14", @@ -94,31 +96,31 @@ "@storybook/core-events": "8.6.14", "@storybook/manager-api": "8.6.14", "@storybook/preview-api": "8.6.14", - "@storybook/react": "9.1.3", - "@storybook/react-vite": "9.1.3", + "@storybook/react": "9.1.5", + "@storybook/react-vite": "9.1.5", "@storybook/test": "8.6.14", "@storybook/theming": "8.6.14", "@storybook/types": "8.6.14", - "@storybook/vue3": "9.1.3", - "@storybook/vue3-vite": "9.1.3", + "@storybook/vue3": "9.1.5", + "@storybook/vue3-vite": "9.1.5", "@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.20.0", "@types/micromatch": "4.0.9", - "@types/node": "22.17.2", + "@types/node": "22.18.1", "@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.40.0", - "@typescript-eslint/parser": "8.40.0", + "@typescript-eslint/eslint-plugin": "8.42.0", + "@typescript-eslint/parser": "8.42.0", "@vitest/coverage-v8": "3.2.4", - "@vue/compiler-core": "3.5.19", - "@vue/runtime-core": "3.5.19", + "@vue/compiler-core": "3.5.21", + "@vue/runtime-core": "3.5.21", "acorn": "8.15.0", "cross-env": "10.0.0", "cypress": "14.5.4", @@ -129,17 +131,17 @@ "intersection-observer": "0.12.2", "micromatch": "4.0.8", "minimatch": "10.0.3", - "msw": "2.10.5", + "msw": "2.11.1", "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.13", - "storybook": "9.1.3", + "start-server-and-test": "2.1.0", + "storybook": "9.1.5", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", - "tsx": "4.20.4", + "tsx": "4.20.5", "vite-plugin-turbosnap": "1.0.3", "vitest": "3.2.4", "vitest-fetch-mock": "0.4.5", 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/common.ts b/packages/frontend/src/boot/common.ts index 574012ff78..4becf32ab5 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -151,7 +151,21 @@ export async function common(createVue: () => Promise>) { } //#endregion + //#region Sync dark mode + if (prefer.s.syncDeviceDarkMode) { + store.set('darkMode', isDeviceDarkmode()); + } + + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (mql) => { + if (prefer.s.syncDeviceDarkMode) { + store.set('darkMode', mql.matches); + } + }); + //#endregion + // NOTE: この処理は必ずクライアント更新チェック処理より後に来ること(テーマ再構築のため) + // NOTE: この処理は必ずダークモード判定処理より後に来ること(初回のテーマ適用のため) + // see: https://github.com/misskey-dev/misskey/issues/16562 watch(store.r.darkMode, (darkMode) => { const theme = (() => { if (darkMode) { @@ -183,18 +197,6 @@ export async function common(createVue: () => Promise>) { }); } - //#region Sync dark mode - if (prefer.s.syncDeviceDarkMode) { - store.set('darkMode', isDeviceDarkmode()); - } - - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (mql) => { - if (prefer.s.syncDeviceDarkMode) { - store.set('darkMode', mql.matches); - } - }); - //#endregion - if (!isSafeMode) { if (prefer.s.darkTheme && store.s.darkMode) { if (miLocalStorage.getItem('themeId') !== prefer.s.darkTheme.id) applyTheme(prefer.s.darkTheme); diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index b1966d32c1..6fea9122ad 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -369,11 +369,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..0e1018dcbf 100644 --- a/packages/frontend/src/components/MkAnimBg.vue +++ b/packages/frontend/src/components/MkAnimBg.vue @@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only 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/MkAntennaEditor.vue b/packages/frontend/src/components/MkAntennaEditor.vue index e2febf7225..a41fdbc45d 100644 --- a/packages/frontend/src/components/MkAntennaEditor.vue +++ b/packages/frontend/src/components/MkAntennaEditor.vue @@ -10,17 +10,11 @@ SPDX-License-Identifier: AGPL-3.0-only - + - - - - - - + - @@ -52,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only 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/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue index 359ee08812..76c65397ae 100644 --- a/packages/frontend/src/components/MkPoll.vue +++ b/packages/frontend/src/components/MkPoll.vue @@ -27,16 +27,16 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue index 1f7796bd83..ba8d3a7210 100644 --- a/packages/frontend/src/components/MkPostFormDialog.vue +++ b/packages/frontend/src/components/MkPostFormDialog.vue @@ -54,10 +54,11 @@ function onPosted() { async function _close() { const canClose = await form.value?.canClose(); if (!canClose) return; + form.value?.abortUploader(); modal.value?.close(); } -function onEsc(ev: KeyboardEvent) { +function onEsc() { _close(); } diff --git a/packages/frontend/src/components/MkPushNotificationAllowButton.vue b/packages/frontend/src/components/MkPushNotificationAllowButton.vue index 9c37eb5e72..697346020c 100644 --- a/packages/frontend/src/components/MkPushNotificationAllowButton.vue +++ b/packages/frontend/src/components/MkPushNotificationAllowButton.vue @@ -90,7 +90,7 @@ function subscribe() { publickey: encode(subscription.getKey('p256dh')), }); }, async err => { // When subscribe failed - // 通知が許可されていなかったとき + // 通知が許可されていなかったとき if (err?.name === 'NotAllowedError') { console.info('User denied the notification permission request.'); return; @@ -114,14 +114,13 @@ async function unsubscribe() { if ($i && accounts.length >= 2) { apiWithDialog('sw/unregister', { - i: $i.token, endpoint, - }); + }, $i.token); } else { pushSubscription.value.unsubscribe(); apiWithDialog('sw/unregister', { endpoint, - }); + }, null); pushSubscription.value = null; } } @@ -134,7 +133,7 @@ function encode(buffer: ArrayBuffer | null) { * Convert the URL safe base64 string to a Uint8Array * @param base64String base64 string */ -function urlBase64ToUint8Array(base64String: string): Uint8Array { +function urlBase64ToUint8Array(base64String: string): BufferSource { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/-/g, '+') diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue index 67a9094cad..c0acfa8c60 100644 --- a/packages/frontend/src/components/MkRange.vue +++ b/packages/frontend/src/components/MkRange.vue @@ -167,7 +167,7 @@ function onMouseenter() { text: computed(() => { return props.textConverter(finalValue.value); }), - targetElement: thumbEl.value ?? undefined, + anchorElement: thumbEl.value ?? undefined, }, { closed: () => dispose(), }); @@ -191,7 +191,7 @@ function onMousedown(ev: MouseEvent | TouchEvent) { text: computed(() => { return props.textConverter(finalValue.value); }), - targetElement: thumbEl.value ?? undefined, + anchorElement: thumbEl.value ?? undefined, }, { closed: () => dispose(), }); diff --git a/packages/frontend/src/components/MkReactionIcon.vue b/packages/frontend/src/components/MkReactionIcon.vue index 7d62456e03..2bfdfa7599 100644 --- a/packages/frontend/src/components/MkReactionIcon.vue +++ b/packages/frontend/src/components/MkReactionIcon.vue @@ -28,7 +28,7 @@ if (props.withTooltip) { const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkReactionTooltip.vue')), { showing, reaction: props.reaction.replace(/^:(\w+):$/, ':$1@.:'), - targetElement: elRef.value.$el, + anchorElement: elRef.value.$el, }, { closed: () => dispose(), }); diff --git a/packages/frontend/src/components/MkReactionTooltip.vue b/packages/frontend/src/components/MkReactionTooltip.vue index 77ca841ad0..971ebc060b 100644 --- a/packages/frontend/src/components/MkReactionTooltip.vue +++ b/packages/frontend/src/components/MkReactionTooltip.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only -->