diff --git a/CHANGELOG.md b/CHANGELOG.md index 71273f705a..ea76178d11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,8 @@ ### General - ノートを削除した際、関連するノートが同時に削除されないようになりました - APIで、「replyIdが存在しているのにreplyがnull」や「renoteIdが存在しているのにrenoteがnull」であるという、今までにはなかったパターンが表れることになります -- 定期的に参照されていない古いリモートの投稿を削除する機能が実装されました(コントロールパネル→パフォーマンス→Remote Notes Cleaning) - - 既存のサーバーでは**デフォルトでオフ**、新規サーバーでは**デフォルトでオン**になります +- 定期的に古いリモートの投稿を削除する機能が実装されました + - コントロールパネル→パフォーマンス→Remote Notes Cleaning で有効化できます - データベースの肥大化を防止することが可能です - 既存のサーバーで当機能を有効化した場合は、処理量が多くなるため、一時的にストレージ使用量が増加する可能性があります。 - 増加量を抑えるには、最大処理継続時間をデフォルトより短くしてください。 @@ -15,7 +15,8 @@ - サーバーの初期設定が完了するまでは連合がオンにならないようになりました - 日本語における公開範囲名称の「ダイレクト」が「指名」に改称されました - 実際の動作に即した名称になり、馴染みのない人でも理解しやすくなりました - - 他サービスにおける「ダイレクトメッセージ」に相当するMisskeyの機能は「チャット」ですが、「ダイレクト投稿」という名称の機能が存在するとそちらがダイレクトメッセージ機能であるような誤解を生んでいました + - 他サービスにおける「ダイレクトメッセージ」に相当するMisskeyの機能は「チャット」ですが(過去のバージョンのMisskeyでも、当該機能は「チャット」ではなく「ダイレクトメッセージ」でした)、「ダイレクト投稿」という名称の機能が存在するとそちらがダイレクトメッセージ機能であるような誤解を生んでいました + - 今後、「チャット」の名称を「ダイレクトメッセージ」に戻す可能性があります - mfm.jsをアップデートしました - Enhance: Unicode 15.1 および 16.0 に収録されている絵文字に対応 - Enhance: acctに `.` が入っているユーザーのメンションに対応 @@ -27,6 +28,7 @@ - プラグインは1.xに対応したものが必要です - Playはそのまま動作しますが、新規に作られるプリセットは1.xになります - 以前のバージョンから無効化されていた note_view_interruptor が有効になりました + - ハンドラは同期的である必要があります - Feat: セーフモード - プラグイン・テーマ・カスタムCSSの使用でクライアントの起動に問題が発生した際に、これらを無効にして起動できます - 以下の方法でセーフモードを起動できます @@ -57,6 +59,7 @@ - Fix: 照会ダイアログでap/showでローカルユーザーを解決した際@username@nullに飛ばされる問題を修正 - Fix: アイコンのデコレーションを付ける際にデコレーションが表示されなくなる問題を修正 - Fix: 管理中アカウント一覧で正しい表示が行われない問題を修正 +- Fix: lookupページでリモートURLを指定した際に正しく動作しない問題を修正 ### Server - Feat: サーバー管理コマンド @@ -71,6 +74,7 @@ - Fix: SystemWebhook設定でsecretを空に出来ない問題を修正 - Fix: 削除されたユーザーがチャットメッセージにリアクションしている場合`chat/history`などでエラーになる問題を修正 - Fix: Pageのアイキャッチ画像をドライブから消してもPageごと消えないように +- Fix: タイムラインAPIの withRenotes: false 時のレスポンスを修正 ## 2025.7.0 diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index f1a97bb63b..8180ee0bd8 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -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" @@ -3120,6 +3120,7 @@ _serverSetupWizard: youCanConfigureMoreFederationSettingsLater: "Les configuracions avançades, com especificar els servidors amb els quals es pot federar, es poden fer més tard." remoteContentsCleaning: "Neteja automàtica del contingut rebut" remoteContentsCleaning_description: "Quan es comença a federar es rep un munt de contingut, quan s'activa la neteja automàtica el contingut antic que no es consulta serà eliminat del servidor, el que permet estalviar espai d'emmagatzematge." + remoteContentsCleaning_description2: "Alguns mètodes de referència, com els enllaços, no poden ser detectats pel sistema." adminInfo: "Informació de l'administrador " adminInfo_description: "Estableix la informació de l'administrador que es farà servir per rebre consultes." adminInfo_mustBeFilled: "Aquesta informació ha de ser omplerta si el servidor té els registres oberts o la federació es troba activada." diff --git a/locales/en-US.yml b/locales/en-US.yml index b8a9fdbc44..ffa81744a8 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" @@ -3120,6 +3120,7 @@ _serverSetupWizard: youCanConfigureMoreFederationSettingsLater: "Advanced settings such as specifying federated servers can be configured later." remoteContentsCleaning: "Automatic cleanup of received contents" remoteContentsCleaning_description: "Federation may result in a continuous inflow of content. Enabling automatic cleanup will remove outdated and unreferenced content from the server to save storage." + remoteContentsCleaning_description2: "Hyperlinks to remote content within the local server may become broken." adminInfo: "Administrator information" adminInfo_description: "Sets the administrator information used to receive inquiries." adminInfo_mustBeFilled: "Must be entered if public server or federation is on." diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 5d01c20e05..c8b17dee71 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" @@ -3120,6 +3120,7 @@ _serverSetupWizard: youCanConfigureMoreFederationSettingsLater: "Los ajustes avanzados, como la especificación de servidores federados, pueden configurarse más adelante." remoteContentsCleaning: "Limpieza automática de los contenidos recibidos" remoteContentsCleaning_description: "La federación puede dar lugar a un flujo continuo de contenido. Al habilitar la limpieza automática, se eliminará del servidor el contenido obsoleto y sin referencias para ahorrar espacio de almacenamiento." + remoteContentsCleaning_description2: "Ciertos métodos de referencia, como los hipervínculos, no pueden ser detectados por el sistema." adminInfo: "Información del administrador" adminInfo_description: "Establece la información del administrador para recibir consultas." adminInfo_mustBeFilled: "Esta información debe ser introducida en el caso de registros abiertos o la federación esté activada." diff --git a/locales/index.d.ts b/locales/index.d.ts index 05443ab7d6..2b910df793 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -6531,7 +6531,7 @@ export interface Locale extends ILocale { */ "remoteNotesCleaning": string; /** - * 有効にすると、参照されていない古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。 + * 有効にすると、一定期間経過したリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。 */ "remoteNotesCleaning_description": string; /** @@ -12032,17 +12032,13 @@ export interface Locale extends ILocale { */ "youCanConfigureMoreFederationSettingsLater": string; /** - * 受信コンテンツの自動クリーニング + * リモートコンテンツの自動クリーニング */ "remoteContentsCleaning": string; /** - * 連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。 + * 連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、一定期間経過したリモートコンテンツを自動でサーバーから削除し、ストレージを節約できます。 */ "remoteContentsCleaning_description": string; - /** - * ハイパーリンクなど、一部の参照方法はシステム上で検知できません。 - */ - "remoteContentsCleaning_description2": string; /** * 管理者情報 */ diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 9d91e558f9..410e9b3aae 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -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" @@ -3120,6 +3120,7 @@ _serverSetupWizard: youCanConfigureMoreFederationSettingsLater: "Puoi svolgere la configurazione avanzata anche dopo. Ad esempio specificando quali server possono federarsi." remoteContentsCleaning: "Pulizia automatica dei contenuti in arrivo" remoteContentsCleaning_description: "Con la federazione funzionante, riceverai sempre più contenuti. Abilitando la pulizia automatica, i contenuti non referenziati e obsoleti verranno rimossi automaticamente dai tuoi server, risparmiando spazio di archiviazione." + remoteContentsCleaning_description2: "Alcuni metodi di riferimento, come i collegamenti ipertestuali, non possono essere rilevati sul sistema." adminInfo: "Informazioni sull'amministratore" adminInfo_description: "Imposta le informazioni dell'amministratore utilizzate per accettare le richieste." adminInfo_mustBeFilled: "Questa operazione è necessaria su un server aperto o se è attiva la federazione." diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1bc6df7103..3de5ea60df 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1660,7 +1660,7 @@ _serverSettings: fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。" reactionsBufferingDescription: "有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。" remoteNotesCleaning: "リモート投稿の自動クリーニング" - remoteNotesCleaning_description: "有効にすると、参照されていない古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。" + remoteNotesCleaning_description: "有効にすると、一定期間経過したリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。" remoteNotesCleaningMaxProcessingDuration: "最大クリーニング処理継続時間" remoteNotesCleaningExpiryDaysForEachNotes: "最低ノート保持日数" inquiryUrl: "問い合わせ先URL" @@ -3216,9 +3216,8 @@ _serverSetupWizard: doYouConnectToFediverse_description1: "分散型サーバーで構成されるネットワーク(Fediverse)に接続すると、他のサーバーと相互にコンテンツのやり取りが可能です。" doYouConnectToFediverse_description2: "Fediverseと接続することは「連合」とも呼ばれます。" youCanConfigureMoreFederationSettingsLater: "連合可能なサーバーの指定など、高度な設定も後ほど可能です。" - remoteContentsCleaning: "受信コンテンツの自動クリーニング" - remoteContentsCleaning_description: "連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。" - remoteContentsCleaning_description2: "ハイパーリンクなど、一部の参照方法はシステム上で検知できません。" + remoteContentsCleaning: "リモートコンテンツの自動クリーニング" + remoteContentsCleaning_description: "連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、一定期間経過したリモートコンテンツを自動でサーバーから削除し、ストレージを節約できます。" adminInfo: "管理者情報" adminInfo_description: "問い合わせを受け付けるために使用される管理者情報を設定します。" adminInfo_mustBeFilled: "オープンサーバー、または連合がオンの場合は必ず入力が必要です。" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index bcd927e758..7e8adeb9e4 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: "로컬 콘텐츠만 공개하고 리모트 콘텐츠는 비공개" @@ -3120,6 +3120,7 @@ _serverSetupWizard: youCanConfigureMoreFederationSettingsLater: "나중에 연합 가능한 서버의 지정 등 고급 설정을 할 수 있습니다." remoteContentsCleaning: "리모트 콘텐츠 자동 정리" remoteContentsCleaning_description: "연합 중인 서버가 있는 경우, 리모트 서버에서 대단히 많은 콘텐츠를 받아오게 됩니다. 자동 정리 기능을 활성화하면, 오래되고 서버에서 더 이상 조회되지 않는 콘텐츠를 자동으로 서버에서 삭제하여, 스토리지를 절약할 수 있습니다." + remoteContentsCleaning_description2: "로컬 내 원격 콘텐츠로의 하이퍼링크는 깨진 링크로 됩니다." adminInfo: "관리자 정보" adminInfo_description: "문의 접수를 위해 사용되는 관리자 정보를 설정합니다." adminInfo_mustBeFilled: "오픈 서버 혹은 연합이 켜져 있는 경우 반드시 입력해야 합니다." diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml index 2efb51f08f..bde027ba69 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -1668,7 +1668,6 @@ _serverSettings: restartServerSetupWizardConfirm_text: "Bazı mevcut ayarlar sıfırlanacaktır." entrancePageStyle: "Giriş sayfası stili" showTimelineForVisitor: "Panoyu göster" - showActivityiesForVisitor: "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." diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 08f3fda096..3c203ce36b 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: "仅公开本地内容,隐藏远程内容" @@ -3120,6 +3120,7 @@ _serverSetupWizard: youCanConfigureMoreFederationSettingsLater: "可在之后进行如哪些服务器可以进行联合等高级设置。" remoteContentsCleaning: "自动清理传入内容" remoteContentsCleaning_description: "加入联合后,服务器将持续接收大量内容。打开自动清理后,将自动删除无法找到的旧内容,可节省存储空间。" + remoteContentsCleaning_description2: "如超链接之类的某些引用方法无法被系统检测到。" adminInfo: "管理员信息" adminInfo_description: "设置用于接受询问的管理员信息。" adminInfo_mustBeFilled: "开放服务器或开启了联合的情况下必须输入。" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 1656896133..80566a01c8 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" @@ -3120,6 +3120,7 @@ _serverSetupWizard: youCanConfigureMoreFederationSettingsLater: "您可以在稍後進行更高級的設定,例如指定可以聯繫的伺服器等。\n" remoteContentsCleaning: "自動清理接收的內容" remoteContentsCleaning_description: "進行聯邦後,會持續接收大量內容。啟用自動清理功能後,系統會自動從伺服器中刪除未被參照的過時內容,以節省儲存空間。" + remoteContentsCleaning_description2: "有些引用方式系統上無法檢測到,例如超連結。" adminInfo: "管理員資訊" adminInfo_description: "設定用於接收查詢的管理者資訊。\n" adminInfo_mustBeFilled: "當設置為開放伺服器或啟用聯邦時,必須填寫此資訊。\n" diff --git a/package.json b/package.json index efac6ec621..1f814da3f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2025.8.0-beta.4", + "version": "2025.8.0-beta.6", "codename": "nasubi", "repository": { "type": "git", 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/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 dca92e1037..ed7d5bfc3a 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -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'; @@ -134,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/json-schema/user-webhook.ts b/packages/backend/src/models/json-schema/user-webhook.ts new file mode 100644 index 0000000000..8ea0991716 --- /dev/null +++ b/packages/backend/src/models/json-schema/user-webhook.ts @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { webhookEventTypes } from '@/models/Webhook.js'; + +export const packedUserWebhookSchema = { + type: 'object', + properties: { + id: { + type: 'string', + format: 'id', + optional: false, nullable: false, + }, + userId: { + type: 'string', + format: 'id', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + on: { + type: 'array', + items: { + type: 'string', + optional: false, nullable: false, + enum: webhookEventTypes, + }, + }, + url: { + type: 'string', + optional: false, nullable: false, + }, + secret: { + type: 'string', + optional: false, nullable: false, + }, + active: { + type: 'boolean', + optional: false, nullable: false, + }, + latestSentAt: { + type: 'string', + format: 'date-time', + optional: false, nullable: true, + }, + latestStatus: { + type: 'integer', + optional: false, nullable: true, + }, + }, +} as const; diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 2b5f706ff9..c507d8d5c6 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -65,7 +65,7 @@ export const packedUserLiteSchema = { avatarUrl: { type: 'string', format: 'url', - nullable: true, optional: false, + nullable: false, optional: false, }, avatarBlurhash: { type: 'string', @@ -465,11 +465,11 @@ export const packedMeDetailedOnlySchema = { }, isModerator: { type: 'boolean', - nullable: true, optional: false, + nullable: false, optional: false, }, isAdmin: { type: 'boolean', - nullable: true, optional: false, + nullable: false, optional: false, }, injectFeaturedNote: { type: 'boolean', @@ -591,7 +591,7 @@ export const packedMeDetailedOnlySchema = { }, mutedInstances: { type: 'array', - nullable: true, optional: false, + nullable: false, optional: false, items: { type: 'string', nullable: false, optional: false, diff --git a/packages/backend/src/server/api/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..b367de7800 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -18,9 +18,14 @@ 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 type { Config } from '@/config.js'; import { ApiError } from '../../error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js'; +import * as Acct from '@/misc/acct.js'; +import { IsNull } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { NotesRepository, UsersRepository } from '@/models/_.js'; export const meta = { tags: ['federation'], @@ -109,6 +114,12 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + private utilityService: UtilityService, private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, @@ -137,7 +148,7 @@ export default class extends Endpoint { // eslint- } let local = await this.mergePack(me, ...await Promise.all([ - this.apDbResolverService.getUserFromApId(uri), + this.apDbResolverService.getUserFromApId(uri).then(async x => x ?? await this.getUserFromProfileUrl(uri)), this.apDbResolverService.getNoteFromApId(uri), ])); if (local != null) return local; @@ -200,6 +211,32 @@ export default class extends Endpoint { // eslint- ); } + // To keep consistency with ap/show from external server, + // this function will handle `https://local.instance/@user` + // profile url to resolve user. + @bindThis + private async getUserFromProfileUrl(url: string): Promise { + if (!this.utilityService.isUriLocal(url)) { + return null; + } + + const uri = new URL(url); + const pathComponents = uri.pathname.split('/').filter(Boolean); + if (pathComponents.length === 1 && pathComponents[0].startsWith('@')) { + const acct = Acct.parse(pathComponents[0]); + // normalize acct host + if (acct.host != null && this.utilityService.toPuny(acct.host) === this.utilityService.toPuny(this.config.host)) acct.host = null; + + return await this.usersRepository.findOneBy({ + usernameLower: acct.username.toLowerCase(), + host: acct.host ?? IsNull(), + isSuspended: false, + }); + } + + return null; + } + @bindThis private async mergePack(me: MiLocalUser | null | undefined, user: MiUser | null | undefined, note: MiNote | null | undefined): Promise | null> { if (user != null) { diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index 055b5cc061..523d81ac73 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -46,6 +46,14 @@ export const meta = { type: 'string', }, }, + iconUrl: { + type: 'string', + optional: true, nullable: true, + }, + description: { + type: 'string', + optional: true, nullable: true, + }, }, }, }, @@ -88,6 +96,8 @@ export default class extends Endpoint { // eslint- createdAt: this.idService.parse(token.id).date.toISOString(), lastUsedAt: token.lastUsedAt?.toISOString(), permission: token.app ? token.app.permission : token.permission, + iconUrl: token.iconUrl, + description: token.description ?? token.app?.description ?? null, }))); }); } diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts index 394c178f2a..8a3ba9e026 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts @@ -21,29 +21,7 @@ export const meta = { type: 'array', items: { type: 'object', - properties: { - id: { - type: 'string', - format: 'misskey:id', - }, - userId: { - type: 'string', - format: 'misskey:id', - }, - name: { type: 'string' }, - on: { - type: 'array', - items: { - type: 'string', - enum: webhookEventTypes, - }, - }, - url: { type: 'string' }, - secret: { type: 'string' }, - active: { type: 'boolean' }, - latestSentAt: { type: 'string', format: 'date-time', nullable: true }, - latestStatus: { type: 'integer', nullable: true }, - }, + ref: 'UserWebhook', }, }, } as const; @@ -65,19 +43,17 @@ export default class extends Endpoint { // eslint- userId: me.id, }); - return webhooks.map(webhook => ( - { - id: webhook.id, - userId: webhook.userId, - name: webhook.name, - on: webhook.on, - url: webhook.url, - secret: webhook.secret, - active: webhook.active, - latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null, - latestStatus: webhook.latestStatus, - } - )); + return webhooks.map(webhook => ({ + id: webhook.id, + userId: webhook.userId, + name: webhook.name, + on: webhook.on, + url: webhook.url, + secret: webhook.secret, + active: webhook.active, + latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null, + latestStatus: webhook.latestStatus, + })); }); } } diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts index 4a0c09ff0c..1c19081c98 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts @@ -28,29 +28,7 @@ export const meta = { res: { type: 'object', - properties: { - id: { - type: 'string', - format: 'misskey:id', - }, - userId: { - type: 'string', - format: 'misskey:id', - }, - name: { type: 'string' }, - on: { - type: 'array', - items: { - type: 'string', - enum: webhookEventTypes, - }, - }, - url: { type: 'string' }, - secret: { type: 'string' }, - active: { type: 'boolean' }, - latestSentAt: { type: 'string', format: 'date-time', nullable: true }, - latestStatus: { type: 'integer', nullable: true }, - }, + ref: 'UserWebhook', }, } as const; diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 1c73edf08e..7fa8004209 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -91,6 +91,7 @@ export default class extends Endpoint { // eslint- qb.orWhere(new Brackets(qb => { qb.where('note.text IS NOT NULL'); qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); })); })); } diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 1f3631ae3d..eeeb797efc 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -242,6 +242,7 @@ export default class extends Endpoint { // eslint- qb.orWhere(new Brackets(qb => { qb.orWhere('note.text IS NOT NULL'); qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); })); })); } diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 614cd9204d..42e80c6ae1 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -223,6 +223,7 @@ export default class extends Endpoint { // eslint- qb.orWhere(new Brackets(qb => { qb.orWhere('note.text IS NOT NULL'); qb.orWhere('note.fileIds != \'{}\''); + qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)'); })); })); } diff --git a/packages/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/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/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue index bf39c1e983..c786e9fe9f 100644 --- a/packages/frontend/src/components/MkAchievements.vue +++ b/packages/frontend/src/components/MkAchievements.vue @@ -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, diff --git a/packages/frontend/src/components/MkChannelList.stories.impl.ts b/packages/frontend/src/components/MkChannelList.stories.impl.ts index 867526ea5e..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/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/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue index 83169496ff..9f1364aec4 100644 --- a/packages/frontend/src/components/MkDrive.vue +++ b/packages/frontend/src/components/MkDrive.vue @@ -152,11 +152,12 @@ import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js'; import { Paginator } from '@/utility/paginator.js'; const props = withDefaults(defineProps<{ - initialFolder?: Misskey.entities.DriveFolder['id'] | null; + initialFolder?: Misskey.entities.DriveFolder | Misskey.entities.DriveFolder['id'] | null; type?: string; multiple?: boolean; select?: 'file' | 'folder' | null; }>(), { + initialFolder: null, multiple: false, select: null, }); diff --git a/packages/frontend/src/components/MkDriveFolderSelectDialog.vue b/packages/frontend/src/components/MkDriveFolderSelectDialog.vue index 2ebab1088f..d5b6b0cbec 100644 --- a/packages/frontend/src/components/MkDriveFolderSelectDialog.vue +++ b/packages/frontend/src/components/MkDriveFolderSelectDialog.vue @@ -39,13 +39,13 @@ withDefaults(defineProps<{ }); const emit = defineEmits<{ - (ev: 'done', r?: Misskey.entities.DriveFolder[]): void; + (ev: 'done', r?: (Misskey.entities.DriveFolder | null)[]): void; (ev: 'closed'): void; }>(); const dialog = useTemplateRef('dialog'); -const selected = ref([]); +const selected = ref<(Misskey.entities.DriveFolder | null)[]>([]); function ok() { emit('done', selected.value); @@ -57,7 +57,7 @@ function cancel() { dialog.value?.close(); } -function onChangeSelection(v: Misskey.entities.DriveFolder[]) { +function onChangeSelection(v: (Misskey.entities.DriveFolder | null)[]) { selected.value = v; } diff --git a/packages/frontend/src/components/MkFlashPreview.vue b/packages/frontend/src/components/MkFlashPreview.vue index f8f6143a7c..b7278ac742 100644 --- a/packages/frontend/src/components/MkFlashPreview.vue +++ b/packages/frontend/src/components/MkFlashPreview.vue @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only

- +

{{ userName(flash.user) }}

diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue index 584afff55c..d8725ade0b 100644 --- a/packages/frontend/src/components/MkLaunchPad.vue +++ b/packages/frontend/src/components/MkLaunchPad.vue @@ -34,9 +34,10 @@ import { deviceKind } from '@/utility/device-kind.js'; import { prefer } from '@/preferences.js'; const props = withDefaults(defineProps<{ - anchorElement?: HTMLElement; + anchorElement?: HTMLElement | null; anchor?: { x: string; y: string; }; }>(), { + anchorElement: null, anchor: () => ({ x: 'right', y: 'center' }), }); diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index 4a1100c324..bfc8179e13 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -94,6 +94,8 @@ async function calcAspectRatio() { onMounted(() => { calcAspectRatio(); + if (gallery.value == null) return; // TSを黙らすため + lightbox = new PhotoSwipeLightbox({ dataSource: props.mediaList .filter(media => { diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue index cf60c1ca3e..d21e09a984 100644 --- a/packages/frontend/src/components/MkPageWindow.vue +++ b/packages/frontend/src/components/MkPageWindow.vue @@ -23,8 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-only
- - + +
@@ -58,20 +58,15 @@ const windowRouter = createRouter(props.initialPath); const pageMetadata = ref(null); const windowEl = useTemplateRef('windowEl'); -const history = ref<{ path: string; }[]>([{ +const _history_ = ref<{ path: string; }[]>([{ path: windowRouter.getCurrentFullPath(), }]); const buttonsLeft = computed(() => { - const buttons: Record[] = []; - - if (history.value.length > 1) { - buttons.push({ - icon: 'ti ti-arrow-left', - onClick: back, - }); - } - - return buttons; + return _history_.value.length > 1 ? [{ + icon: 'ti ti-arrow-left', + title: i18n.ts.goBack, + onClick: back, + }] : []; }); const buttonsRight = computed(() => { const buttons = [{ @@ -97,12 +92,12 @@ function getSearchMarker(path: string) { const searchMarkerId = ref(getSearchMarker(props.initialPath)); windowRouter.addListener('push', ctx => { - history.value.push({ path: ctx.fullPath }); + _history_.value.push({ path: ctx.fullPath }); }); windowRouter.addListener('replace', ctx => { - history.value.pop(); - history.value.push({ path: ctx.fullPath }); + _history_.value.pop(); + _history_.value.push({ path: ctx.fullPath }); }); windowRouter.addListener('change', ctx => { @@ -150,8 +145,8 @@ const contextmenu = computed(() => ([{ }])); function back() { - history.value.pop(); - windowRouter.replaceByPath(history.value.at(-1)!.path); + _history_.value.pop(); + windowRouter.replaceByPath(_history_.value.at(-1)!.path); } function reload() { diff --git a/packages/frontend/src/components/MkPushNotificationAllowButton.vue b/packages/frontend/src/components/MkPushNotificationAllowButton.vue index 9c37eb5e72..c651d3a3f5 100644 --- a/packages/frontend/src/components/MkPushNotificationAllowButton.vue +++ b/packages/frontend/src/components/MkPushNotificationAllowButton.vue @@ -78,7 +78,7 @@ function subscribe() { // SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters return promiseDialog(registration.value.pushManager.subscribe({ userVisibleOnly: true, - applicationServerKey: urlBase64ToUint8Array(instance.swPublickey), + applicationServerKey: urlBase64ToBase64(instance.swPublickey), }) .then(async subscription => { pushSubscription.value = subscription; @@ -131,22 +131,16 @@ function encode(buffer: ArrayBuffer | null) { } /** - * Convert the URL safe base64 string to a Uint8Array + * Convert the URL safe base64 string to a base64 string * @param base64String base64 string */ -function urlBase64ToUint8Array(base64String: string): Uint8Array { +function urlBase64ToBase64(base64String: string): string { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/-/g, '+') .replace(/_/g, '/'); - const rawData = window.atob(base64); - const outputArray = new Uint8Array(rawData.length); - - for (let i = 0; i < rawData.length; ++i) { - outputArray[i] = rawData.charCodeAt(i); - } - return outputArray; + return base64; } if (navigator.serviceWorker == null) { diff --git a/packages/frontend/src/components/MkRoleSelectDialog.vue b/packages/frontend/src/components/MkRoleSelectDialog.vue index fc7ba50fb3..f1cc98def4 100644 --- a/packages/frontend/src/components/MkRoleSelectDialog.vue +++ b/packages/frontend/src/components/MkRoleSelectDialog.vue @@ -105,9 +105,7 @@ async function addRole() { .map(r => ({ text: r.name, value: r })); const { canceled, result: role } = await os.select({ items }); - if (canceled) { - return; - } + if (canceled || role == null) return; selectedRoleIds.value.push(role.id); } diff --git a/packages/frontend/src/components/MkServerSetupWizard.vue b/packages/frontend/src/components/MkServerSetupWizard.vue index e6c75d09f0..1d2dfed297 100644 --- a/packages/frontend/src/components/MkServerSetupWizard.vue +++ b/packages/frontend/src/components/MkServerSetupWizard.vue @@ -66,7 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + diff --git a/packages/frontend/src/components/MkSignin.password.vue b/packages/frontend/src/components/MkSignin.password.vue index cd003a39df..3b3b6a0b73 100644 --- a/packages/frontend/src/components/MkSignin.password.vue +++ b/packages/frontend/src/components/MkSignin.password.vue @@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + {{ i18n.ts.continue }} diff --git a/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue b/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue index 93ffee8d98..15e8e2105f 100644 --- a/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue +++ b/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue @@ -59,7 +59,7 @@ import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-sep import { Paginator } from '@/utility/paginator.js'; const props = defineProps<{ - excludeTypes?: typeof notificationTypes[number][]; + excludeTypes?: typeof notificationTypes[number][] | null; }>(); const rootEl = useTemplateRef('rootEl'); diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue index f606b0b001..08a018ea9b 100644 --- a/packages/frontend/src/components/MkWidgets.vue +++ b/packages/frontend/src/components/MkWidgets.vue @@ -51,6 +51,7 @@ export type DefaultStoredWidget = { diff --git a/packages/frontend/src/components/global/StackingRouterView.vue b/packages/frontend/src/components/global/StackingRouterView.vue index 9e47517244..4c56767608 100644 --- a/packages/frontend/src/components/global/StackingRouterView.vue +++ b/packages/frontend/src/components/global/StackingRouterView.vue @@ -87,7 +87,7 @@ router.useListener('change', ({ resolved }) => { const fullPath = router.getCurrentFullPath(); if (tabs.value.some(tab => tab.routePath === routePath && deepEqual(resolved.props, tab.props))) { - const newTabs = []; + const newTabs = [] as typeof tabs.value; for (const tab of tabs.value) { newTabs.push(tab); diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue index 43133bb573..7e514c5a73 100644 --- a/packages/frontend/src/pages/about.emojis.vue +++ b/packages/frontend/src/pages/about.emojis.vue @@ -48,7 +48,7 @@ import { $i } from '@/i.js'; const customEmojiTags = getCustomEmojiTags(); const q = ref(''); -const searchEmojis = ref(null); +const searchEmojis = ref(null); const selectedTags = ref(new Set()); function search() { diff --git a/packages/frontend/src/pages/achievements.vue b/packages/frontend/src/pages/achievements.vue index 1560403b70..f4de2df6d5 100644 --- a/packages/frontend/src/pages/achievements.vue +++ b/packages/frontend/src/pages/achievements.vue @@ -16,9 +16,11 @@ import { onActivated, onDeactivated, onMounted, onUnmounted } from 'vue'; import MkAchievements from '@/components/MkAchievements.vue'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; -import { $i } from '@/i.js'; +import { ensureSignin } from '@/i.js'; import { claimAchievement } from '@/utility/achievements.js'; +const $i = ensureSignin(); + let timer: number | null; function viewAchievements3min() { diff --git a/packages/frontend/src/pages/admin/modlog.vue b/packages/frontend/src/pages/admin/modlog.vue index b7e5d6b219..08bdc8d254 100644 --- a/packages/frontend/src/pages/admin/modlog.vue +++ b/packages/frontend/src/pages/admin/modlog.vue @@ -78,7 +78,7 @@ const timeline = computed(() => { return paginator.items.value.map(x => ({ id: x.id, timestamp: new Date(x.createdAt).getTime(), - data: x, + data: x as Misskey.entities.ModerationLog, })); }); diff --git a/packages/frontend/src/pages/api-console.vue b/packages/frontend/src/pages/api-console.vue index 7571696b84..f436fc72fa 100644 --- a/packages/frontend/src/pages/api-console.vue +++ b/packages/frontend/src/pages/api-console.vue @@ -68,6 +68,11 @@ function send() { function onEndpointChange() { misskeyApi('endpoint', { endpoint: endpoint.value }, withCredential.value ? undefined : null).then(resp => { + if (resp == null) { + body.value = '{}'; + return; + } + const endpointBody = {}; for (const p of resp.params) { endpointBody[p.name] = diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue index 324e0c573a..1e7301d06d 100644 --- a/packages/frontend/src/pages/channels.vue +++ b/packages/frontend/src/pages/channels.vue @@ -110,6 +110,11 @@ async function search() { const type = searchType.value.toString().trim(); + if (type !== 'nameAndDescription' && type !== 'nameOnly') { + console.error(`Unrecognized search type: ${type}`); + return; + } + channelPaginator.value = markRaw(new Paginator('channels/search', { limit: 10, params: { diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue index b4fc4a46d9..201ce003f0 100644 --- a/packages/frontend/src/pages/emoji-edit-dialog.vue +++ b/packages/frontend/src/pages/emoji-edit-dialog.vue @@ -156,12 +156,9 @@ async function done() { isSensitive: isSensitive.value, localOnly: localOnly.value, roleIdsThatCanBeUsedThisEmojiAsReaction: rolesThatCanBeUsedThisEmojiAsReaction.value.map(x => x.id), + fileId: file.value ? file.value.id : undefined, }; - if (file.value) { - params.fileId = file.value.id; - } - if (props.emoji) { await os.apiWithDialog('admin/emoji/update', { id: props.emoji.id, @@ -177,7 +174,12 @@ async function done() { windowEl.value?.close(); } else { - const created = await os.apiWithDialog('admin/emoji/add', params); + if (params.fileId == null) return; + + const created = await os.apiWithDialog('admin/emoji/add', { + ...params, + fileId: params.fileId, // TSを黙らすため + }); emit('done', { created: created, diff --git a/packages/frontend/src/pages/explore.users.vue b/packages/frontend/src/pages/explore.users.vue index 72f2a6813c..08f9f5e582 100644 --- a/packages/frontend/src/pages/explore.users.vue +++ b/packages/frontend/src/pages/explore.users.vue @@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- +
@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- + @@ -78,22 +78,17 @@ const props = defineProps<{ }>(); const origin = ref('local'); -const tagsEl = useTemplateRef('tagsEl'); const tagsLocal = ref([]); const tagsRemote = ref([]); -watch(() => props.tag, () => { - if (tagsEl.value) tagsEl.value.toggleContent(props.tag == null); -}); - -const tagUsersPaginator = markRaw(new Paginator('hashtags/users', { +const tagUsersPaginator = props.tag != null ? markRaw(new Paginator('hashtags/users', { limit: 30, params: { tag: props.tag, origin: 'combined', sort: '+follower', }, -})); +})) : null; const pinnedUsersPaginator = markRaw(new Paginator('pinned-users', { noPaging: true, diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index 560d2a46ea..f318a9f817 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -196,13 +196,13 @@ async function run() { const isLegacy = !flash.value.script.replaceAll(' ', '').startsWith('///@1.0.0'); - const { Interpreter, Parser, values } = isLegacy ? await import('@syuilo/aiscript-0-19-0') : await import('@syuilo/aiscript'); + const { Interpreter, Parser, values } = isLegacy ? (await import('@syuilo/aiscript-0-19-0') as any) : await import('@syuilo/aiscript'); const parser = new Parser(); components.value = []; - aiscript.value = new Interpreter({ + const interpreter = new Interpreter({ ...createAiScriptEnv({ storageKey: 'flash:' + flash.value.id, }), @@ -221,6 +221,8 @@ async function run() { }, }); + aiscript.value = interpreter; + let ast; try { ast = parser.parse(flash.value.script); @@ -232,8 +234,8 @@ async function run() { return; } try { - await aiscript.value.exec(ast); - } catch (err) { + await interpreter.exec(ast); + } catch (err: any) { os.alert({ type: 'error', title: 'AiScript Error', diff --git a/packages/frontend/src/pages/lookup.vue b/packages/frontend/src/pages/lookup.vue index 8a1e952d85..182b2f703d 100644 --- a/packages/frontend/src/pages/lookup.vue +++ b/packages/frontend/src/pages/lookup.vue @@ -48,7 +48,7 @@ function _fetch_() { if (res.type === 'User') { mainRouter.replace('/@:acct/:page?', { params: { - acct: res.host != null ? `${res.object.username}@${res.object.host}` : res.object.username, + acct: res.object.host != null ? `${res.object.username}@${res.object.host}` : res.object.username, }, }); } else if (res.type === 'Note') { diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue index 751a67190a..d73363d058 100644 --- a/packages/frontend/src/pages/scratchpad.vue +++ b/packages/frontend/src/pages/scratchpad.vue @@ -82,10 +82,10 @@ const logs = ref<{ text: string; print: boolean; }[]>([]); -const root = ref(); +const root = ref(); const components = ref[]>([]); const uiKey = ref(0); -const uiInspectorOpenedComponents = ref(new Map); +const uiInspectorOpenedComponents = ref(new WeakMap, boolean>); const saved = miLocalStorage.getItem('scratchpad'); if (saved) { @@ -186,11 +186,13 @@ const headerActions = computed(() => []); const headerTabs = computed(() => []); const showns = computed(() => { + if (root.value == null) return new Set(); const result = new Set(); (function addChildrenToResult(c: AsUiComponent) { result.add(c.id); - if (c.children) { - const childComponents = components.value.filter(v => c.children.includes(v.value.id)); + const children = c.children; + if (children) { + const childComponents = components.value.filter(v => children.includes(v.value.id)); for (const child of childComponents) { addChildrenToResult(child.value); } diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue index 09c1199d0c..f7c634b42e 100644 --- a/packages/frontend/src/pages/settings/navbar.vue +++ b/packages/frontend/src/pages/settings/navbar.vue @@ -91,7 +91,7 @@ async function addItem() { value: '-', text: i18n.ts.divider, }], }); - if (canceled) return; + if (canceled || item == null) return; items.value = [...items.value, { id: genId(), type: item, diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue index 2f2b57bdaf..84ecc23e84 100644 --- a/packages/frontend/src/pages/settings/notifications.vue +++ b/packages/frontend/src/pages/settings/notifications.vue @@ -55,11 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
diff --git a/packages/frontend/src/pages/settings/plugin.install.vue b/packages/frontend/src/pages/settings/plugin.install.vue index 22b53b4b96..8ed7f2a7b6 100644 --- a/packages/frontend/src/pages/settings/plugin.install.vue +++ b/packages/frontend/src/pages/settings/plugin.install.vue @@ -40,7 +40,7 @@ async function install() { code.value = null; router.push('/settings/plugin'); - } catch (err) { + } catch (err: any) { os.alert({ type: 'error', title: 'Install failed', diff --git a/packages/frontend/src/pages/settings/webhook.new.vue b/packages/frontend/src/pages/settings/webhook.new.vue index 6c4dff5551..5f36e6bcfc 100644 --- a/packages/frontend/src/pages/settings/webhook.new.vue +++ b/packages/frontend/src/pages/settings/webhook.new.vue @@ -40,6 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only