diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 07d9af12f7..90eb268dda 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -75,7 +75,7 @@ jobs: - run: corepack enable - run: pnpm i --frozen-lockfile - name: Restore eslint cache - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.1.0 with: path: ${{ env.eslint-cache-path }} key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 405ee7c10a..143b63f7b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,25 @@ +## 2024.10.1 +### Note +- 悪質なユーザからサーバを守る措置の一環として、モデレータ権限を持つユーザの最終アクティブ日時を確認し、 +7日間活動していない場合は自動的に招待制へと移行(コントロールパネル -> モデレーション -> "誰でも新規登録できるようにする"をオフに変更)するようになりました。 +詳細な経緯は https://github.com/misskey-dev/misskey/issues/13437 をご確認ください。 + +### Client +- Enhance: l10nの更新 +- Fix: メールアドレス不要でCaptchaが有効な場合にアカウント登録完了後自動でのログインに失敗する問題を修正 + +### Server +- Feat: モデレータ権限を持つユーザが全員7日間活動しなかった場合は自動的に招待制へと移行するように ( #13437 ) +- Fix: `admin/emoji/update`エンドポイントのidのみ指定した時不正なエラーが発生するバグを修正 + +### Server +- Fix: キューのエラーログを簡略化するように + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/649) + ## 2024.10.0 ### Note -- サーバー初期設定時に使用する初期パスワードを設定できるようになりました。今後Misskeyサーバーを新たに設置する際には、初回の起動前にコンフィグファイルの`setupPassword`をコメントアウトし、初期パスワードを設定することをおすすめします。(すでに初期設定を完了しているサーバーについては、この変更に伴い対応する必要はありません) +- セキュリティ向上のため、サーバー初期設定時に使用する初期パスワードを設定できるようになりました。今後Misskeyサーバーを新たに設置する際には、初回の起動前にコンフィグファイルの`setupPassword`をコメントアウトし、初期パスワードを設定することをおすすめします。(すでに初期設定を完了しているサーバーについては、この変更に伴い対応する必要はありません) - ホスティングサービスを運営している場合は、コンフィグファイルを構築する際に`setupPassword`をランダムな値に設定し、ユーザーに通知するようにシステムを更新することをおすすめします。 - なお、初期パスワードが設定されていない場合でも初期設定を行うことが可能です(UI上で初期パスワードの入力欄を空欄にすると続行できます)。 - ユーザーデータを読み込む際の型が一部変更されました。 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f61311f1e5..3a4dc7b918 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -578,18 +578,18 @@ ESMではディレクトリインポートは廃止されているのと、デ ### Lighten CSS vars ``` css -color: hsl(from var(--accent) h s calc(l + 10)); +color: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); ``` ### Darken CSS vars ``` css -color: hsl(from var(--accent) h s calc(l - 10)); +color: hsl(from var(--MI_THEME-accent) h s calc(l - 10)); ``` ### Add alpha to CSS vars ``` css -color: color(from var(--accent) srgb r g b / 0.5); +color: color(from var(--MI_THEME-accent) srgb r g b / 0.5); ``` diff --git a/idea/MkDisableSection.vue b/idea/MkDisableSection.vue index d177886569..360705071b 100644 --- a/idea/MkDisableSection.vue +++ b/idea/MkDisableSection.vue @@ -34,7 +34,7 @@ defineProps<{ width: 100%; height: 100%; cursor: not-allowed; - --color: color(from var(--error) srgb r g b / 0.25); + --color: color(from var(--MI_THEME-error) srgb r g b / 0.25); background-size: auto auto; background-image: repeating-linear-gradient(135deg, transparent, transparent 10px, var(--color) 4px, var(--color) 14px); } diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index d95600cb1f..de24ad4bb9 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -1252,7 +1252,6 @@ _theme: buttonBg: "خلفية الأزرار" buttonHoverBg: "خلفية الأزرار (عند التمرير فوقها)" inputBorder: "حواف حقل الإدخال" - listItemHoverBg: "خلفية عناصر القائمة (عند التمرير فوقها)" driveFolderBg: "خلفية مجلد قرص التخزين" messageBg: "خلفية المحادثة" _sfx: diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index ab0ee74bb4..0e761b0743 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -1017,7 +1017,6 @@ _theme: buttonBg: "বাটনের পটভূমি" buttonHoverBg: "বাটনের পটভূমি (হভার)" inputBorder: "ইনপুট ফিল্ডের বর্ডার" - listItemHoverBg: "লিস্ট আইটেমের পটভূমি (হোভার)" driveFolderBg: "ড্রাইভ ফোল্ডারের পটভূমি" wallpaperOverlay: "ওয়ালপেপার ওভারলে" badge: "ব্যাজ" diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index ad5fde37bc..7b668e5ce9 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -453,6 +453,7 @@ totpDescription: "Escriu una contrasenya d'un sol us fent servir l'aplicació d' moderator: "Moderador/a" moderation: "Moderació" moderationNote: "Nota de moderació " +moderationNoteDescription: "Pots escriure notes que es compartiran entre els moderadors." addModerationNote: "Afegir una nota de moderació " moderationLogs: "Registre de moderació " nUsersMentioned: "{n} usuaris mencionats" @@ -1284,6 +1285,14 @@ unknownWebAuthnKey: "Passkey desconeguda" passkeyVerificationFailed: "La verificació a fallat" passkeyVerificationSucceededButPasswordlessLoginDisabled: "La verificació de la passkey a estat correcta, però s'ha deshabilitat l'inici de sessió sense contrasenya." messageToFollower: "Missatge als meus seguidors" +target: "Assumpte " +_abuseUserReport: + forward: "Reenviar " + forwardDescription: "Reenvia l'informe a una altra instància com un compte del sistema anònima." + resolve: "Solució " + accept: "Acceptar " + reject: "Rebutjar" + resolveTutorial: "Si l'informe és legítim selecciona \"Acceptar\" per resoldre'l positivament. Però si l'informe no és legítim selecciona \"Rebutjar\" per resoldre'l negativament." _delivery: status: "Estat d'entrega " stop: "Suspés" @@ -1974,7 +1983,6 @@ _theme: buttonBg: "Fons botó " buttonHoverBg: "Fons botó (en passar-hi per sobre)" inputBorder: "Contorn del cap d'introducció " - listItemHoverBg: "Fons dels elements d'una llista" driveFolderBg: "Fons de la carpeta Disc" wallpaperOverlay: "Superposició del fons de pantalla " badge: "Insígnia " @@ -2520,6 +2528,8 @@ _moderationLogTypes: markSensitiveDriveFile: "Fitxer marcat com a sensible" unmarkSensitiveDriveFile: "S'ha tret la marca de sensible del fitxer" resolveAbuseReport: "Informe resolt" + forwardAbuseReport: "Informe reenviat" + updateAbuseReportNote: "Nota de moderació d'un informe actualitzat" createInvitation: "Crear codi d'invitació " createAd: "Anunci creat" deleteAd: "Anunci esborrat" diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index 4233a68f17..caf6d6e163 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -1629,7 +1629,6 @@ _theme: buttonBg: "Pozadí tlačítka" buttonHoverBg: "Pozadí tlačítka (Hover)" inputBorder: "Ohraničení vstupního pole" - listItemHoverBg: "Pozadí položky seznamu (Hover)" driveFolderBg: "Pozadí složky disku" wallpaperOverlay: "Překrytí tapety" badge: "Odznak" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 35a04b453c..4e2bd06934 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -1784,7 +1784,6 @@ _theme: buttonBg: "Hintergrund von Schaltflächen" buttonHoverBg: "Hintergrund von Schaltflächen (Mouseover)" inputBorder: "Rahmen von Eingabefeldern" - listItemHoverBg: "Hintergrund von Listeneinträgen (Mouseover)" driveFolderBg: "Hintergrund von Drive-Ordnern" wallpaperOverlay: "Hintergrundbild-Overlay" badge: "Wappen" diff --git a/locales/en-US.yml b/locales/en-US.yml index 7b275c990c..6ea7fb4f8d 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -112,7 +112,7 @@ enterEmoji: "Enter an emoji" renote: "Renote" unrenote: "Remove renote" renoted: "Renoted." -renotedToX: "Renote to {name}." +renotedToX: "Renoted to {name}." cantRenote: "This post can't be renoted." cantReRenote: "A renote can't be renoted." quote: "Quote" @@ -454,6 +454,7 @@ totpDescription: "Use an authenticator app to enter one-time passwords" moderator: "Moderator" moderation: "Moderation" moderationNote: "Moderation note" +moderationNoteDescription: "You can fill in notes that will be shared only among moderators." addModerationNote: "Add moderation note" moderationLogs: "Moderation logs" nUsersMentioned: "Mentioned by {n} users" @@ -921,6 +922,7 @@ followersVisibility: "Visibility of followers" continueThread: "View thread continuation" deleteAccountConfirm: "This will irreversibly delete your account. Proceed?" incorrectPassword: "Incorrect password." +incorrectTotp: "The one-time password is incorrect or has expired." voteConfirm: "Confirm your vote for \"{choice}\"?" hide: "Hide" useDrawerReactionPickerForMobile: "Display reaction picker as drawer on mobile" @@ -1284,6 +1286,14 @@ unknownWebAuthnKey: "Unknown Passkey" passkeyVerificationFailed: "Passkey verification has failed." passkeyVerificationSucceededButPasswordlessLoginDisabled: "Passkey verification has succeeded but password-less login is disabled." messageToFollower: "Message to followers" +target: "Target" +_abuseUserReport: + forward: "Forward" + forwardDescription: "Forward the report to a remote server as an anonymous system account." + resolve: "Resolve" + accept: "Accept" + reject: "Reject" + resolveTutorial: "If the report is legitimate in content, select \"Accept\" to mark the case as resolved in the affirmative.\nIf the content of the report is not legitimate, select \"Reject\" to mark the case as resolved in the negative." _delivery: status: "Delivery status" stop: "Suspended" @@ -1737,7 +1747,7 @@ _role: canManageAvatarDecorations: "Manage avatar decorations" driveCapacity: "Drive capacity" alwaysMarkNsfw: "Always mark files as NSFW" - canUpdateBioMedia: "Allow to edit an icon or a banner image" + canUpdateBioMedia: "Can edit an icon or a banner image" pinMax: "Maximum number of pinned notes" antennaMax: "Maximum number of antennas" wordMuteMax: "Maximum number of characters allowed in word mutes" @@ -1974,7 +1984,6 @@ _theme: buttonBg: "Button background" buttonHoverBg: "Button background (Hover)" inputBorder: "Input field border" - listItemHoverBg: "List item background (Hover)" driveFolderBg: "Drive folder background" wallpaperOverlay: "Wallpaper overlay" badge: "Badge" @@ -2473,22 +2482,22 @@ _webhookSettings: reaction: "When receiving a reaction" mention: "When being mentioned" _systemEvents: - abuseReport: "When received a new abuse report" - abuseReportResolved: "When resolved abuse report" + abuseReport: "When received a new report" + abuseReportResolved: "When resolved report" userCreated: "When user is created" deleteConfirm: "Are you sure you want to delete the Webhook?" testRemarks: "Click the button to the right of the switch to send a test Webhook with dummy data." _abuseReport: _notificationRecipient: - createRecipient: "Add a recipient for abuse reports" - modifyRecipient: "Edit a recipient for abuse reports" + createRecipient: "Add a recipient for reports" + modifyRecipient: "Edit a recipient for reports" recipientType: "Notification type" _recipientType: mail: "Email" webhook: "Webhook" _captions: - mail: "Send the email to moderators' email addresses when you receive abuse." - webhook: "Send a notification to SystemWebhook when you receive or resolve abuse." + mail: "Send the email to moderators' email addresses when you receive reports." + webhook: "Send a notification to System Webhook when you receive or resolve reports." keywords: "Keywords" notifiedUser: "Users to notify" notifiedWebhook: "Webhook to use" @@ -2521,6 +2530,8 @@ _moderationLogTypes: markSensitiveDriveFile: "File marked as sensitive" unmarkSensitiveDriveFile: "File unmarked as sensitive" resolveAbuseReport: "Report resolved" + forwardAbuseReport: "Report forwarded" + updateAbuseReportNote: "Moderation note of a report updated" createInvitation: "Invite generated" createAd: "Ad created" deleteAd: "Ad deleted" @@ -2528,18 +2539,18 @@ _moderationLogTypes: createAvatarDecoration: "Avatar decoration created" updateAvatarDecoration: "Avatar decoration updated" deleteAvatarDecoration: "Avatar decoration deleted" - unsetUserAvatar: "Unset this user's avatar" - unsetUserBanner: "Unset this user's banner" - createSystemWebhook: "Create SystemWebhook" - updateSystemWebhook: "Update SystemWebhook" - deleteSystemWebhook: "Delete SystemWebhook" - createAbuseReportNotificationRecipient: "Create a recipient for abuse reports" - updateAbuseReportNotificationRecipient: "Update recipients for abuse reports" - deleteAbuseReportNotificationRecipient: "Delete a recipient for abuse reports" - deleteAccount: "Delete the account" - deletePage: "Delete the page" - deleteFlash: "Delete Play" - deleteGalleryPost: "Delete the gallery post" + unsetUserAvatar: "User avatar unset" + unsetUserBanner: "User banner unset" + createSystemWebhook: "System Webhook created" + updateSystemWebhook: "System Webhook updated" + deleteSystemWebhook: "System Webhook deleted" + createAbuseReportNotificationRecipient: "Recipient for reports created" + updateAbuseReportNotificationRecipient: "Recipient for reports updated" + deleteAbuseReportNotificationRecipient: "Recipient for reports deleted" + deleteAccount: "Account deleted" + deletePage: "Page deleted" + deleteFlash: "Play deleted" + deleteGalleryPost: "Gallery post deleted" _fileViewer: title: "File details" type: "File type" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index de9ea0c32a..d574999e40 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -1915,7 +1915,6 @@ _theme: buttonBg: "Fondo de botón" buttonHoverBg: "Fondo de botón (hover)" inputBorder: "Borde de los campos de entrada" - listItemHoverBg: "Fondo de elemento de listas (hover)" driveFolderBg: "Fondo de capeta del drive" wallpaperOverlay: "Transparencia del fondo de pantalla" badge: "Medalla" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 7dfc64d63f..a7060c06fc 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -1701,7 +1701,6 @@ _theme: buttonBg: "Arrière-plan du bouton" buttonHoverBg: "Arrière-plan du bouton (survolé)" inputBorder: "Cadre de la zone de texte" - listItemHoverBg: "Arrière-plan d'item de liste (survolé)" driveFolderBg: "Arrière-plan du dossier de disque" wallpaperOverlay: "Superposition de fond d'écran" badge: "Badge" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index fbfedb89e3..ce3958b167 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -1924,7 +1924,6 @@ _theme: buttonBg: "Latar belakang tombol" buttonHoverBg: "Latar belakang tombol (Mengambang)" inputBorder: "Batas bidang masukan" - listItemHoverBg: "Latar belakang daftar item (Mengambang)" driveFolderBg: "Latar belakang folder drive" wallpaperOverlay: "Lapisan wallpaper" badge: "Lencana" diff --git a/locales/index.d.ts b/locales/index.d.ts index 77d857bdb1..6fb1228067 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5174,6 +5174,10 @@ export interface Locale extends ILocale { * 対象 */ "target": string; + /** + * CAPTCHAのテストを目的とした機能です。本番環境で使用しないでください。 + */ + "testCaptchaWarning": string; "_abuseUserReport": { /** * 転送 @@ -5704,6 +5708,10 @@ export interface Locale extends ILocale { * サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。 */ "inquiryUrlDescription": string; + /** + * 一定期間モデレーターのアクティビティが検出されなかった場合、スパム防止のためこの設定は自動でオフになります。 + */ + "thisSettingWillAutomaticallyOffWhenModeratorsInactive": string; }; "_accountMigration": { /** @@ -7725,10 +7733,6 @@ export interface Locale extends ILocale { * 入力ボックスの縁取り */ "inputBorder": string; - /** - * リスト項目の背景 (ホバー) - */ - "listItemHoverBg": string; /** * ドライブフォルダーの背景 */ diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 004bb6e9fd..d42fff326c 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -1975,7 +1975,6 @@ _theme: buttonBg: "Sfondo del pulsante" buttonHoverBg: "Sfondo del pulsante (sorvolato)" inputBorder: "Inquadra casella di testo" - listItemHoverBg: "Sfondo della voce di elenco (sorvolato)" driveFolderBg: "Sfondo della cartella di disco" wallpaperOverlay: "Sovrapposizione dello sfondo" badge: "Distintivo" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index fe1149b4c2..aef8795d74 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1289,6 +1289,7 @@ passkeyVerificationFailed: "パスキーの検証に失敗しました。" passkeyVerificationSucceededButPasswordlessLoginDisabled: "パスキーの検証に成功しましたが、パスワードレスログインが無効になっています。" messageToFollower: "フォロワーへのメッセージ" target: "対象" +testCaptchaWarning: "CAPTCHAのテストを目的とした機能です。本番環境で使用しないでください。" _abuseUserReport: forward: "転送" @@ -1442,6 +1443,7 @@ _serverSettings: reactionsBufferingDescription: "有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。" inquiryUrl: "問い合わせ先URL" inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。" + thisSettingWillAutomaticallyOffWhenModeratorsInactive: "一定期間モデレーターのアクティビティが検出されなかった場合、スパム防止のためこの設定は自動でオフになります。" _accountMigration: moveFrom: "別のアカウントからこのアカウントに移行" @@ -2023,7 +2025,6 @@ _theme: buttonBg: "ボタンの背景" buttonHoverBg: "ボタンの背景 (ホバー)" inputBorder: "入力ボックスの縁取り" - listItemHoverBg: "リスト項目の背景 (ホバー)" driveFolderBg: "ドライブフォルダーの背景" wallpaperOverlay: "壁紙のオーバーレイ" badge: "バッジ" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 52a8f41380..0a8b3828f2 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -1943,7 +1943,6 @@ _theme: buttonBg: "ボタンの背景" buttonHoverBg: "ボタンの背景 (ホバー)" inputBorder: "入力ボックスの縁取り" - listItemHoverBg: "リスト項目の背景 (ホバー)" driveFolderBg: "ドライブフォルダーの背景" wallpaperOverlay: "壁紙のオーバーレイ" badge: "バッジ" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 757afe53f9..973140dca2 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1984,7 +1984,6 @@ _theme: buttonBg: "버튼 배경" buttonHoverBg: "버튼 배경 (호버)" inputBorder: "입력 필드 테두리" - listItemHoverBg: "리스트 항목 배경 (호버)" driveFolderBg: "드라이브 폴더 배경" wallpaperOverlay: "배경화면 오버레이" badge: "배지" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 117434ad32..d7afd57760 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -1205,7 +1205,6 @@ _theme: buttonBg: "Tło przycisku" buttonHoverBg: "Tło przycisku (po najechaniu)" inputBorder: "Obramowanie pola wejścia" - listItemHoverBg: "Tło elementu listy (po najechaniu)" driveFolderBg: "Tło folderu na dysku" wallpaperOverlay: "Nakładka tapety" badge: "Odznaka" diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index dac4abbe64..9039fd2141 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -1,5 +1,5 @@ --- -_lang_: "日本語" +_lang_: "Português" headlineMisskey: "Uma rede ligada por notas" introMisskey: "Bem-vindo! O Misskey é um serviço de microblog descentralizado de código aberto.\nCrie \"notas\" para compartilhar o que está acontecendo agora ou para se expressar com todos à sua volta 📡\nVocê também pode adicionar rapidamente reações às notas de outras pessoas usando a função \"Reações\" 👍\nVamos explorar um novo mundo 🚀" poweredByMisskeyDescription: "{name} é uma instância da plataforma de código aberto Misskey." @@ -25,7 +25,7 @@ basicSettings: "Configurações básicas" otherSettings: "Outras configurações" openInWindow: "Abrir em um janela" profile: "Perfil" -timeline: "Cronologia" +timeline: "Linha do tempo" noAccountDescription: "Este usuário não tem uma descrição." login: "Iniciar sessão" loggingIn: "Iniciando sessão…" @@ -1058,7 +1058,7 @@ resetPasswordConfirm: "Deseja realmente mudar a sua senha?" sensitiveWords: "Palavras sensíveis" sensitiveWordsDescription: "A visibilidade de todas as notas contendo as palavras configuradas será colocadas como \"Início\" automaticamente. Você pode listar várias delas separando-as por linha." sensitiveWordsDescription2: "Utilizar espaços irá criar expressões aditivas (AND) e cercar palavras-chave com barras irá transformá-las em expressões regulares (RegEx)" -prohibitedWords: "Palavras proibídas" +prohibitedWords: "Palavras proibidas" prohibitedWordsDescription: "Habilita um erro ao tentar publicar uma nota contendo as palavras escolhidas. Várias palavras podem ser escolhidas, separando-as por linha." prohibitedWordsDescription2: "Utilizar espaços irá criar expressões aditivas (AND) e cercar palavras-chave com barras irá transformá-las em expressões regulares (RegEx)" hiddenTags: "Hashtags escondidas" @@ -1416,7 +1416,7 @@ _achievements: _types: _notes1: title: "Configurando o meu misskey" - description: "Post uma nota pela primeira vez" + description: "Poste uma nota pela primeira vez" flavor: "Divirta-se com o Misskey!" _notes10: title: "Algumas notas" @@ -1944,7 +1944,6 @@ _theme: buttonBg: "Plano de fundo de botão" buttonHoverBg: "Plano de fundo de botão (Selecionado)" inputBorder: "Borda de campo digitável" - listItemHoverBg: "Plano de fundo do item de uma lista (Selecionado)" driveFolderBg: "Plano de fundo da pasta no Drive" wallpaperOverlay: "Sobreposição do papel de parede." badge: "Emblema" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index befb537105..70178ec2fd 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -1694,7 +1694,6 @@ _theme: buttonBg: "Фон кнопки" buttonHoverBg: "Текст кнопки" inputBorder: "Рамка поля ввода" - listItemHoverBg: "Фон пункта списка (под указателем)" driveFolderBg: "Фон папки «Диска»" wallpaperOverlay: "Слой обоев" badge: "Значок" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index 8cb73e1303..60ce45a6b9 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -1108,7 +1108,6 @@ _theme: buttonBg: "Pozadie tlačidla" buttonHoverBg: "Pozadie tlačidla (pod kurzorom)" inputBorder: "Okraj vstupného poľa" - listItemHoverBg: "Pozadie položky zoznamu (pod kurzorom)" driveFolderBg: "Pozadie priečinu disku" wallpaperOverlay: "Vrstvenie pozadia" badge: "Odznak" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 31eee2bccc..c70d448e2b 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -1943,7 +1943,6 @@ _theme: buttonBg: "ปุ่มพื้นหลัง" buttonHoverBg: "ปุ่มพื้นหลัง (โฮเวอร์)" inputBorder: "เส้นขอบของช่องป้อนข้อมูล" - listItemHoverBg: "รายการไอเทมพื้นหลัง (โฮเวอร์)" driveFolderBg: "พื้นหลังโฟลเดอร์ไดรฟ์" wallpaperOverlay: "วอลล์เปเปอร์ซ้อนทับ" badge: "ตรา" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 974508b3a7..f2262cd71f 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -1302,7 +1302,6 @@ _theme: buttonBg: "Фон кнопки" buttonHoverBg: "Фон кнопки (при наведенні)" inputBorder: "Край поля вводу" - listItemHoverBg: "Фон елементу в списку (при наведенні)" driveFolderBg: "Фон папки на диску" wallpaperOverlay: "Накладання шпалер" badge: "Значок" diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 6cf9b3f278..235497d844 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -1546,7 +1546,6 @@ _theme: buttonBg: "Nền nút" buttonHoverBg: "Nền nút (Chạm)" inputBorder: "Đường viền khung soạn thảo" - listItemHoverBg: "Nền mục liệt kê (Chạm)" driveFolderBg: "Nền thư mục Ổ đĩa" wallpaperOverlay: "Lớp phủ hình nền" badge: "Huy hiệu" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index c4bfe972fe..8b681efb13 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1199,10 +1199,10 @@ followingOrFollower: "关注中或关注者" fileAttachedOnly: "仅限媒体" showRepliesToOthersInTimeline: "在时间线中包含给别人的回复" hideRepliesToOthersInTimeline: "在时间线中隐藏给别人的回复" -showRepliesToOthersInTimelineAll: "在时间线中包含现在关注的所有人的回复" -hideRepliesToOthersInTimelineAll: "在时间线中隐藏现在关注的所有人的回复" -confirmShowRepliesAll: "此操作不可撤销。确认要在时间线中包含现在关注的所有人的回复吗?" -confirmHideRepliesAll: "此操作不可撤销。确认要在时间线中隐藏现在关注的所有人的回复吗?" +showRepliesToOthersInTimelineAll: "在时间线中显示所有现在关注的人的回复" +hideRepliesToOthersInTimelineAll: "在时间线中隐藏所有现在关注的人的回复" +confirmShowRepliesAll: "此操作不可撤销。确认要在时间线中显示所有现在关注的人的回复吗?" +confirmHideRepliesAll: "此操作不可撤销。确认要在时间线中隐藏所有现在关注的人的回复吗?" externalServices: "外部服务" sourceCode: "源代码" sourceCodeIsNotYetProvided: "还未提供源代码。要解决此问题请联系管理员。" @@ -1290,6 +1290,10 @@ target: "对象" _abuseUserReport: forward: "转发" forwardDescription: "目标是匿名系统账户,将把举报转发给远程服务器。" + resolve: "解决" + accept: "确认" + reject: "拒绝" + resolveTutorial: "如果举报内容有理且已解决,选择「确认」将案件以肯定的态度标记为已解决。\n如果举报内容站不住脚,选择「拒绝」将案件以否定的态度标记为已解决。" _delivery: status: "投递状态" stop: "停止投递" @@ -1626,7 +1630,7 @@ _achievements: _postedAt0min0sec: title: "报时" description: "在 0 点发布一篇帖子" - flavor: "报时信号最后一响,零点整" + flavor: "嘟 · 嘟 · 嘟 · 哔——" _selfQuote: title: "自我引用" description: "引用了自己的帖子" @@ -1980,7 +1984,6 @@ _theme: buttonBg: "按钮背景" buttonHoverBg: "按钮背景(悬停)" inputBorder: "输入框边框" - listItemHoverBg: "下拉列表项目背景(悬停)" driveFolderBg: "网盘的文件夹背景" wallpaperOverlay: "壁纸叠加层" badge: "徽章" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 5e8a5d8f8d..55b504e8fb 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1975,7 +1975,6 @@ _theme: buttonBg: "按鈕背景" buttonHoverBg: "按鈕背景 (漂浮)" inputBorder: "輸入框邊框" - listItemHoverBg: "列表物品背景 (漂浮)" driveFolderBg: "雲端硬碟文件夾背景" wallpaperOverlay: "壁紙覆蓋層" badge: "徽章" diff --git a/package.json b/package.json index 8cb783d883..2c84c55303 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2024.10.0-beta.6", + "version": "2024.10.1-beta.3", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/backend/migration/1728550878802-testcaptcha.js b/packages/backend/migration/1728550878802-testcaptcha.js new file mode 100644 index 0000000000..d8d987c0c1 --- /dev/null +++ b/packages/backend/migration/1728550878802-testcaptcha.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class Testcaptcha1728550878802 { + name = 'Testcaptcha1728550878802' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "enableTestcaptcha" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableTestcaptcha"`); + } +} diff --git a/packages/backend/src/core/AbuseReportNotificationService.ts b/packages/backend/src/core/AbuseReportNotificationService.ts index fb7c7bd2c3..7d030f2f16 100644 --- a/packages/backend/src/core/AbuseReportNotificationService.ts +++ b/packages/backend/src/core/AbuseReportNotificationService.ts @@ -61,7 +61,10 @@ export class AbuseReportNotificationService implements OnApplicationShutdown { return; } - const moderatorIds = await this.roleService.getModeratorIds(true, true); + const moderatorIds = await this.roleService.getModeratorIds({ + includeAdmins: true, + excludeExpire: true, + }); for (const moderatorId of moderatorIds) { for (const abuseReport of abuseReports) { @@ -370,7 +373,10 @@ export class AbuseReportNotificationService implements OnApplicationShutdown { } // モデレータ権限の有無で通知先設定を振り分ける - const authorizedUserIds = await this.roleService.getModeratorIds(true, true); + const authorizedUserIds = await this.roleService.getModeratorIds({ + includeAdmins: true, + excludeExpire: true, + }); const authorizedUserRecipients = Array.of(); const unauthorizedUserRecipients = Array.of(); for (const recipient of userRecipients) { diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index f6b7955cd2..206d0dbe0a 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -119,5 +119,18 @@ export class CaptchaService { throw new Error(`turnstile-failed: ${errorCodes}`); } } + + @bindThis + public async verifyTestcaptcha(response: string | null | undefined): Promise { + if (response == null) { + throw new Error('testcaptcha-failed: no response provided'); + } + + const success = response === 'testcaptcha-passed'; + + if (!success) { + throw new Error('testcaptcha-failed'); + } + } } diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 5db3c5b980..4566113449 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -103,19 +103,33 @@ export class CustomEmojiService implements OnApplicationShutdown { } @bindThis - public async update(id: MiEmoji['id'], data: { + public async update(data: ( + { id: MiEmoji['id'], name?: string; } | { name: string; id?: MiEmoji['id'], } + ) & { driveFile?: MiDriveFile; - name?: string; category?: string | null; aliases?: string[]; license?: string | null; isSensitive?: boolean; localOnly?: boolean; roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][]; - }, moderator?: MiUser): Promise { - const emoji = await this.emojisRepository.findOneByOrFail({ id: id }); - const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() }); - if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists'); + }, moderator?: MiUser): Promise< + null + | 'NO_SUCH_EMOJI' + | 'SAME_NAME_EMOJI_EXISTS' + > { + const emoji = data.id + ? await this.getEmojiById(data.id) + : await this.getEmojiByName(data.name!); + if (emoji === null) return 'NO_SUCH_EMOJI'; + const id = emoji.id; + + // IDと絵文字名が両方指定されている場合は絵文字名の変更を行うため重複チェックが必要 + const doNameUpdate = data.id && data.name && (data.name !== emoji.name); + if (doNameUpdate) { + const isDuplicate = await this.checkDuplicate(data.name!); + if (isDuplicate) return 'SAME_NAME_EMOJI_EXISTS'; + } await this.emojisRepository.update(emoji.id, { updatedAt: new Date(), @@ -135,7 +149,7 @@ export class CustomEmojiService implements OnApplicationShutdown { const packed = await this.emojiEntityService.packDetailed(emoji.id); - if (emoji.name === data.name) { + if (!doNameUpdate) { this.globalEventService.publishBroadcastStream('emojiUpdated', { emojis: [packed], }); @@ -157,6 +171,7 @@ export class CustomEmojiService implements OnApplicationShutdown { after: updated, }); } + return null; } @bindThis diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index f35e456556..37028026cc 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -93,6 +93,13 @@ export class QueueService { repeat: { pattern: '0 0 * * *' }, removeOnComplete: true, }); + + this.systemQueue.add('checkModeratorsActivity', { + }, { + // 毎時30分に起動 + repeat: { pattern: '30 * * * *' }, + removeOnComplete: true, + }); } @bindThis diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 56b7fc6fc2..6f88f277e5 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -103,6 +103,7 @@ export const DEFAULT_POLICIES: RolePolicies = { @Injectable() export class RoleService implements OnApplicationShutdown, OnModuleInit { + private rootUserIdCache: MemorySingleCache; private rolesCache: MemorySingleCache; private roleAssignmentByUserIdCache: MemoryKVCache; private notificationService: NotificationService; @@ -138,6 +139,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { private moderationLogService: ModerationLogService, private fanoutTimelineService: FanoutTimelineService, ) { + this.rootUserIdCache = new MemorySingleCache(1000 * 60 * 60 * 24 * 7); // 1week. rootユーザのIDは不変なので長めに this.rolesCache = new MemorySingleCache(1000 * 60 * 60); // 1h this.roleAssignmentByUserIdCache = new MemoryKVCache(1000 * 60 * 5); // 5m @@ -423,49 +425,78 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { } @bindThis - public async isExplorable(role: { id: MiRole['id']} | null): Promise { + public async isExplorable(role: { id: MiRole['id'] } | null): Promise { if (role == null) return false; const check = await this.rolesRepository.findOneBy({ id: role.id }); if (check == null) return false; return check.isExplorable; } + /** + * モデレーター権限のロールが割り当てられているユーザID一覧を取得する. + * + * @param opts.includeAdmins 管理者権限も含めるか(デフォルト: true) + * @param opts.includeRoot rootユーザも含めるか(デフォルト: false) + * @param opts.excludeExpire 期限切れのロールを除外するか(デフォルト: false) + */ @bindThis - public async getModeratorIds(includeAdmins = true, excludeExpire = false): Promise { + public async getModeratorIds(opts?: { + includeAdmins?: boolean, + includeRoot?: boolean, + excludeExpire?: boolean, + }): Promise { + const includeAdmins = opts?.includeAdmins ?? true; + const includeRoot = opts?.includeRoot ?? false; + const excludeExpire = opts?.excludeExpire ?? false; + const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({})); const moderatorRoles = includeAdmins ? roles.filter(r => r.isModerator || r.isAdministrator) : roles.filter(r => r.isModerator); - // TODO: isRootなアカウントも含める const assigns = moderatorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({ roleId: In(moderatorRoles.map(r => r.id)) }) : []; + // Setを経由して重複を除去(ユーザIDは重複する可能性があるので) const now = Date.now(); - const result = [ - // Setを経由して重複を除去(ユーザIDは重複する可能性があるので) - ...new Set( - assigns - .filter(it => - (excludeExpire) - ? (it.expiresAt == null || it.expiresAt.getTime() > now) - : true, - ) - .map(a => a.userId), - ), - ]; + const resultSet = new Set( + assigns + .filter(it => + (excludeExpire) + ? (it.expiresAt == null || it.expiresAt.getTime() > now) + : true, + ) + .map(a => a.userId), + ); - return result.sort((x, y) => x.localeCompare(y)); + if (includeRoot) { + const rootUserId = await this.rootUserIdCache.fetch(async () => { + const it = await this.usersRepository.createQueryBuilder('users') + .select('id') + .where({ isRoot: true }) + .getRawOne<{ id: string }>(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return it!.id; + }); + resultSet.add(rootUserId); + } + + return [...resultSet].sort((x, y) => x.localeCompare(y)); } @bindThis - public async getModerators(includeAdmins = true): Promise { - const ids = await this.getModeratorIds(includeAdmins); - const users = ids.length > 0 ? await this.usersRepository.findBy({ - id: In(ids), - }) : []; - return users; + public async getModerators(opts?: { + includeAdmins?: boolean, + includeRoot?: boolean, + excludeExpire?: boolean, + }): Promise { + const ids = await this.getModeratorIds(opts); + return ids.length > 0 + ? await this.usersRepository.findBy({ + id: In(ids), + }) + : []; } @bindThis diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts index 0cdcf3310a..7b0150f5b6 100644 --- a/packages/backend/src/core/entities/FlashEntityService.ts +++ b/packages/backend/src/core/entities/FlashEntityService.ts @@ -40,7 +40,7 @@ export class FlashEntityService { // { schema: 'UserDetailed' } すると無限ループするので注意 const user = hint?.packedUser ?? await this.userEntityService.pack(flash.user ?? flash.userId, me); - let isLiked = false; + let isLiked = undefined; if (meId) { isLiked = hint?.likedFlashIds ? hint.likedFlashIds.includes(flash.id) diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index 70c8cd32d7..9212aa1e6d 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -96,6 +96,7 @@ export class MetaEntityService { recaptchaSiteKey: instance.recaptchaSiteKey, enableTurnstile: instance.enableTurnstile, turnstileSiteKey: instance.turnstileSiteKey, + enableTestcaptcha: instance.enableTestcaptcha, swPublickey: instance.swPublicKey, themeColor: instance.themeColor, mascotImageUrl: instance.mascotImageUrl ?? '/assets/ai.png', diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 99c3d2442a..37e5b2bb2d 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -258,6 +258,11 @@ export class MiMeta { }) public turnstileSecretKey: string | null; + @Column('boolean', { + default: false, + }) + public enableTestcaptcha: boolean; + // chaptcha系を追加した際にはnodeinfoのレスポンスに追加するのを忘れないようにすること @Column('enum', { diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index df0853b5c7..1e0d723fdf 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -115,6 +115,10 @@ export const packedMetaLiteSchema = { type: 'string', optional: false, nullable: true, }, + enableTestcaptcha: { + type: 'boolean', + optional: false, nullable: false, + }, swPublickey: { type: 'string', optional: false, nullable: true, diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts index 0027b5ef3d..9044285bf6 100644 --- a/packages/backend/src/queue/QueueProcessorModule.ts +++ b/packages/backend/src/queue/QueueProcessorModule.ts @@ -6,6 +6,7 @@ import { Module } from '@nestjs/common'; import { CoreModule } from '@/core/CoreModule.js'; import { GlobalModule } from '@/GlobalModule.js'; +import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; import { QueueLoggerService } from './QueueLoggerService.js'; import { QueueProcessorService } from './QueueProcessorService.js'; import { DeliverProcessorService } from './processors/DeliverProcessorService.js'; @@ -80,6 +81,8 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor DeliverProcessorService, InboxProcessorService, AggregateRetentionProcessorService, + CheckExpiredMutingsProcessorService, + CheckModeratorsActivityProcessorService, QueueProcessorService, ], exports: [ diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index e9e1c45224..6940e1c188 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -10,6 +10,7 @@ import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; +import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js'; import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js'; import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js'; @@ -66,7 +67,7 @@ function getJobInfo(job: Bull.Job | undefined, increment = false): string { // onActiveとかonCompletedのattemptsMadeがなぜか0始まりなのでインクリメントする const currentAttempts = job.attemptsMade + (increment ? 1 : 0); - const maxAttempts = job.opts ? job.opts.attempts : 0; + const maxAttempts = job.opts.attempts ?? 0; return `id=${job.id} attempts=${currentAttempts}/${maxAttempts} age=${formated}`; } @@ -120,24 +121,35 @@ export class QueueProcessorService implements OnApplicationShutdown { private aggregateRetentionProcessorService: AggregateRetentionProcessorService, private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService, private bakeBufferedReactionsProcessorService: BakeBufferedReactionsProcessorService, + private checkModeratorsActivityProcessorService: CheckModeratorsActivityProcessorService, private cleanProcessorService: CleanProcessorService, ) { this.logger = this.queueLoggerService.logger; - function renderError(e: Error): any { - if (e) { // 何故かeがundefinedで来ることがある - return { - stack: e.stack, - message: e.message, - name: e.name, - }; - } else { - return { - stack: '?', - message: '?', - name: '?', - }; + function renderError(e?: Error) { + // 何故かeがundefinedで来ることがある + if (!e) return '?'; + + if (e instanceof Bull.UnrecoverableError || e.name === 'AbortError') { + return `${e.name}: ${e.message}`; } + + return { + stack: e.stack, + message: e.message, + name: e.name, + }; + } + + function renderJob(job?: Bull.Job) { + if (!job) return '?'; + + return { + name: job.name || undefined, + info: getJobInfo(job), + failedReason: job.failedReason || undefined, + data: job.data, + }; } //#region system @@ -150,6 +162,7 @@ export class QueueProcessorService implements OnApplicationShutdown { case 'aggregateRetention': return this.aggregateRetentionProcessorService.process(); case 'checkExpiredMutings': return this.checkExpiredMutingsProcessorService.process(); case 'bakeBufferedReactions': return this.bakeBufferedReactionsProcessorService.process(); + case 'checkModeratorsActivity': return this.checkModeratorsActivityProcessorService.process(); case 'clean': return this.cleanProcessorService.process(); default: throw new Error(`unrecognized job type ${job.name} for system`); } @@ -172,15 +185,15 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('active', (job) => logger.debug(`active id=${job.id}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('failed', (job, err: Error) => { - logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }); + logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) }); if (config.sentryForBackend) { - Sentry.captureMessage(`Queue: System: ${job?.name ?? '?'}: ${err.message}`, { + Sentry.captureMessage(`Queue: System: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, }); } }) - .on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) + .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) })) .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); } //#endregion @@ -229,15 +242,15 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('active', (job) => logger.debug(`active id=${job.id}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('failed', (job, err) => { - logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }); + logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) }); if (config.sentryForBackend) { - Sentry.captureMessage(`Queue: DB: ${job?.name ?? '?'}: ${err.message}`, { + Sentry.captureMessage(`Queue: DB: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, }); } }) - .on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) + .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) })) .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); } //#endregion @@ -269,15 +282,15 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('active', (job) => logger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) .on('failed', (job, err) => { - logger.error(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`); + logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`); if (config.sentryForBackend) { - Sentry.captureMessage(`Queue: Deliver: ${err.message}`, { + Sentry.captureMessage(`Queue: Deliver: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, }); } }) - .on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) + .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) })) .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); } //#endregion @@ -309,15 +322,15 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('active', (job) => logger.debug(`active ${getJobInfo(job, true)}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)}`)) .on('failed', (job, err) => { - logger.error(`failed(${err.stack}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) }); + logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job: renderJob(job), e: renderError(err) }); if (config.sentryForBackend) { - Sentry.captureMessage(`Queue: Inbox: ${err.message}`, { + Sentry.captureMessage(`Queue: Inbox: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, }); } }) - .on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) + .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) })) .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); } //#endregion @@ -349,15 +362,15 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('active', (job) => logger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) .on('failed', (job, err) => { - logger.error(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`); + logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`); if (config.sentryForBackend) { - Sentry.captureMessage(`Queue: UserWebhookDeliver: ${err.message}`, { + Sentry.captureMessage(`Queue: UserWebhookDeliver: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, }); } }) - .on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) + .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) })) .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); } //#endregion @@ -389,15 +402,15 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('active', (job) => logger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) .on('failed', (job, err) => { - logger.error(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`); + logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`); if (config.sentryForBackend) { - Sentry.captureMessage(`Queue: SystemWebhookDeliver: ${err.message}`, { + Sentry.captureMessage(`Queue: SystemWebhookDeliver: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, }); } }) - .on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) + .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) })) .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); } //#endregion @@ -436,15 +449,15 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('active', (job) => logger.debug(`active id=${job.id}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('failed', (job, err) => { - logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }); + logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) }); if (config.sentryForBackend) { - Sentry.captureMessage(`Queue: Relationship: ${job?.name ?? '?'}: ${err.message}`, { + Sentry.captureMessage(`Queue: Relationship: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, }); } }) - .on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) + .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) })) .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); } //#endregion @@ -477,15 +490,15 @@ export class QueueProcessorService implements OnApplicationShutdown { .on('active', (job) => logger.debug(`active id=${job.id}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('failed', (job, err) => { - logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }); + logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) }); if (config.sentryForBackend) { - Sentry.captureMessage(`Queue: ObjectStorage: ${job?.name ?? '?'}: ${err.message}`, { + Sentry.captureMessage(`Queue: ObjectStorage: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, { level: 'error', extra: { job, err }, }); } }) - .on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) })) + .on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) })) .on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`)); } //#endregion diff --git a/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts new file mode 100644 index 0000000000..f2677f8e5c --- /dev/null +++ b/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts @@ -0,0 +1,127 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; +import { MetaService } from '@/core/MetaService.js'; +import { RoleService } from '@/core/RoleService.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; + +// モデレーターが不在と判断する日付の閾値 +const MODERATOR_INACTIVITY_LIMIT_DAYS = 7; +const ONE_DAY_MILLI_SEC = 1000 * 60 * 60 * 24; + +@Injectable() +export class CheckModeratorsActivityProcessorService { + private logger: Logger; + + constructor( + private metaService: MetaService, + private roleService: RoleService, + private queueLoggerService: QueueLoggerService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('check-moderators-activity'); + } + + @bindThis + public async process(): Promise { + this.logger.info('start.'); + + const meta = await this.metaService.fetch(false); + if (!meta.disableRegistration) { + await this.processImpl(); + } else { + this.logger.info('is already invitation only.'); + } + + this.logger.succ('finish.'); + } + + @bindThis + private async processImpl() { + const { isModeratorsInactive, inactivityLimitCountdown } = await this.evaluateModeratorsInactiveDays(); + if (isModeratorsInactive) { + this.logger.warn(`The moderator has been inactive for ${MODERATOR_INACTIVITY_LIMIT_DAYS} days. We will move to invitation only.`); + await this.changeToInvitationOnly(); + + // TODO: モデレータに通知メール+Misskey通知 + // TODO: SystemWebhook通知 + } else { + if (inactivityLimitCountdown <= 2) { + this.logger.warn(`A moderator has been inactive for a period of time. If you are inactive for an additional ${inactivityLimitCountdown} days, it will switch to invitation only.`); + + // TODO: 警告メール + } + } + } + + /** + * モデレーターが不在であるかどうかを確認する。trueの場合はモデレーターが不在である。 + * isModerator, isAdministrator, isRootのいずれかがtrueのユーザを対象に、 + * {@link MiUser.lastActiveDate}の値が実行日時の{@link MODERATOR_INACTIVITY_LIMIT_DAYS}日前よりも古いユーザがいるかどうかを確認する。 + * {@link MiUser.lastActiveDate}がnullの場合は、そのユーザは確認の対象外とする。 + * + * ----- + * + * ### サンプルパターン + * - 実行日時: 2022-01-30 12:00:00 + * - 判定基準: 2022-01-23 12:00:00(実行日時の{@link MODERATOR_INACTIVITY_LIMIT_DAYS}日前) + * + * #### パターン① + * - モデレータA: lastActiveDate = 2022-01-20 00:00:00 ※アウト + * - モデレータB: lastActiveDate = 2022-01-23 12:00:00 ※セーフ(判定基準と同値なのでギリギリ残り0日) + * - モデレータC: lastActiveDate = 2022-01-23 11:59:59 ※アウト(残り-1日) + * - モデレータD: lastActiveDate = null + * + * この場合、モデレータBのアクティビティのみ判定基準日よりも古くないため、モデレーターが在席と判断される。 + * + * #### パターン② + * - モデレータA: lastActiveDate = 2022-01-20 00:00:00 ※アウト + * - モデレータB: lastActiveDate = 2022-01-22 12:00:00 ※アウト(残り-1日) + * - モデレータC: lastActiveDate = 2022-01-23 11:59:59 ※アウト(残り-1日) + * - モデレータD: lastActiveDate = null + * + * この場合、モデレータA, B, Cのアクティビティは判定基準日よりも古いため、モデレーターが不在と判断される。 + */ + @bindThis + public async evaluateModeratorsInactiveDays() { + const today = new Date(); + const inactivePeriod = new Date(today); + inactivePeriod.setDate(today.getDate() - MODERATOR_INACTIVITY_LIMIT_DAYS); + + const moderators = await this.fetchModerators() + .then(it => it.filter(it => it.lastActiveDate != null)); + const inactiveModerators = moderators + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + .filter(it => it.lastActiveDate!.getTime() < inactivePeriod.getTime()); + + // 残りの猶予を示したいので、最終アクティブ日時が一番若いモデレータの日数を基準に猶予を計算する + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const newestLastActiveDate = new Date(Math.max(...moderators.map(it => it.lastActiveDate!.getTime()))); + const inactivityLimitCountdown = Math.floor((newestLastActiveDate.getTime() - inactivePeriod.getTime()) / ONE_DAY_MILLI_SEC); + + return { + isModeratorsInactive: inactiveModerators.length === moderators.length, + inactiveModerators, + inactivityLimitCountdown, + }; + } + + @bindThis + private async changeToInvitationOnly() { + await this.metaService.update({ disableRegistration: true }); + } + + @bindThis + private async fetchModerators() { + // TODO: モデレーター以外にも特別な権限を持つユーザーがいる場合は考慮する + return this.roleService.getModerators({ + includeAdmins: true, + includeRoot: true, + excludeExpire: true, + }); + } +} diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index be63635efe..3a8cb19f01 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -119,6 +119,7 @@ export class ApiServerService { 'g-recaptcha-response'?: string; 'turnstile-response'?: string; 'm-captcha-response'?: string; + 'testcaptcha-response'?: string; } }>('/signup', (request, reply) => this.signupApiService.signup(request, reply)); @@ -132,6 +133,7 @@ export class ApiServerService { 'g-recaptcha-response'?: string; 'turnstile-response'?: string; 'm-captcha-response'?: string; + 'testcaptcha-response'?: string; }; }>('/signin-flow', (request, reply) => this.signinApiService.signin(request, reply)); diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 0d24ffa56a..1d983ca4bc 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -71,6 +71,7 @@ export class SigninApiService { 'g-recaptcha-response'?: string; 'turnstile-response'?: string; 'm-captcha-response'?: string; + 'testcaptcha-response'?: string; }; }>, reply: FastifyReply, @@ -194,6 +195,12 @@ export class SigninApiService { throw new FastifyReplyError(400, err); }); } + + if (this.meta.enableTestcaptcha) { + await this.captchaService.verifyTestcaptcha(body['testcaptcha-response']).catch(err => { + throw new FastifyReplyError(400, err); + }); + } } if (same) { diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index c499638018..3ec5e5d3e6 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -67,6 +67,7 @@ export class SignupApiService { 'g-recaptcha-response'?: string; 'turnstile-response'?: string; 'm-captcha-response'?: string; + 'testcaptcha-response'?: string; } }>, reply: FastifyReply, @@ -99,6 +100,12 @@ export class SignupApiService { throw new FastifyReplyError(400, err); }); } + + if (this.meta.enableTestcaptcha) { + await this.captchaService.verifyTestcaptcha(body['testcaptcha-response']).catch(err => { + throw new FastifyReplyError(400, err); + }); + } } const username = body['username']; diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index 22609a16a3..212cba5c5d 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; -import type { DriveFilesRepository } from '@/models/_.js'; +import type { DriveFilesRepository, MiEmoji } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -78,25 +78,14 @@ export default class extends Endpoint { // eslint- if (driveFile == null) throw new ApiError(meta.errors.noSuchFile); } - let emojiId; - if (ps.id) { - emojiId = ps.id; - const emoji = await this.customEmojiService.getEmojiById(ps.id); - if (!emoji) throw new ApiError(meta.errors.noSuchEmoji); - if (ps.name && (ps.name !== emoji.name)) { - const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name); - if (isDuplicate) throw new ApiError(meta.errors.sameNameEmojiExists); - } - } else { - if (!ps.name) throw new Error('Invalid Params unexpectedly passed. This is a BUG. Please report it to the development team.'); - const emoji = await this.customEmojiService.getEmojiByName(ps.name); - if (!emoji) throw new ApiError(meta.errors.noSuchEmoji); - emojiId = emoji.id; - } + // JSON schemeのanyOfの型変換がうまくいっていないらしい + const required = { id: ps.id, name: ps.name } as + | { id: MiEmoji['id']; name?: string } + | { id?: MiEmoji['id']; name: string }; - await this.customEmojiService.update(emojiId, { + const error = await this.customEmojiService.update({ + ...required, driveFile, - name: ps.name, category: ps.category, aliases: ps.aliases, license: ps.license, @@ -104,6 +93,14 @@ export default class extends Endpoint { // eslint- localOnly: ps.localOnly, roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction, }, me); + + switch (error) { + case null: return; + case 'NO_SUCH_EMOJI': throw new ApiError(meta.errors.noSuchEmoji); + case 'SAME_NAME_EMOJI_EXISTS': throw new ApiError(meta.errors.sameNameEmojiExists); + } + // 網羅性チェック + const mustBeNever: never = error; }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index e09ecf389e..4cca18a7bc 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -69,6 +69,10 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + enableTestcaptcha: { + type: 'boolean', + optional: false, nullable: false, + }, swPublickey: { type: 'string', optional: false, nullable: true, @@ -559,6 +563,7 @@ export default class extends Endpoint { // eslint- recaptchaSiteKey: instance.recaptchaSiteKey, enableTurnstile: instance.enableTurnstile, turnstileSiteKey: instance.turnstileSiteKey, + enableTestcaptcha: instance.enableTestcaptcha, swPublickey: instance.swPublicKey, themeColor: instance.themeColor, mascotImageUrl: instance.mascotImageUrl, diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 2fef9abbf9..2b2c8c60ab 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -71,13 +71,13 @@ export default class extends Endpoint { // eslint- break; } case 'moderator': { - const moderatorIds = await this.roleService.getModeratorIds(false); + const moderatorIds = await this.roleService.getModeratorIds({ includeAdmins: false }); if (moderatorIds.length === 0) return []; query.where('user.id IN (:...moderatorIds)', { moderatorIds: moderatorIds }); break; } case 'adminOrModerator': { - const adminOrModeratorIds = await this.roleService.getModeratorIds(); + const adminOrModeratorIds = await this.roleService.getModeratorIds({ includeAdmins: true }); if (adminOrModeratorIds.length === 0) return []; query.where('user.id IN (:...adminOrModeratorIds)', { adminOrModeratorIds: adminOrModeratorIds }); break; diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 868269d1a6..d151a4d20c 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -78,6 +78,7 @@ export const paramDef = { enableTurnstile: { type: 'boolean' }, turnstileSiteKey: { type: 'string', nullable: true }, turnstileSecretKey: { type: 'string', nullable: true }, + enableTestcaptcha: { type: 'boolean' }, sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] }, sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] }, setSensitiveFlagAutomatically: { type: 'boolean' }, @@ -370,6 +371,10 @@ export default class extends Endpoint { // eslint- set.turnstileSecretKey = ps.turnstileSecretKey; } + if (ps.enableTestcaptcha !== undefined) { + set.enableTestcaptcha = ps.enableTestcaptcha; + } + if (ps.sensitiveMediaDetection !== undefined) { set.sensitiveMediaDetection = ps.sensitiveMediaDetection; } diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index 7c6a533429..a04640d993 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -98,7 +98,7 @@ const theme = localStorage.getItem('theme'); if (theme) { for (const [k, v] of Object.entries(JSON.parse(theme))) { - document.documentElement.style.setProperty(`--${k}`, v.toString()); + document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); // HTMLの theme-color 適用 if (k === 'htmlThemeColor') { diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css index dbcc8f537c..5d81f2bed0 100644 --- a/packages/backend/src/server/web/style.css +++ b/packages/backend/src/server/web/style.css @@ -5,8 +5,8 @@ */ html { - background-color: var(--bg); - color: var(--fg); + background-color: var(--MI_THEME-bg); + color: var(--MI_THEME-fg); } #splash { @@ -17,7 +17,7 @@ html { width: 100vw; height: 100vh; cursor: wait; - background-color: var(--bg); + background-color: var(--MI_THEME-bg); opacity: 1; transition: opacity 0.5s ease; } @@ -45,7 +45,7 @@ html { width: 28px; height: 28px; transform: translateY(70px); - color: var(--accent); + color: var(--MI_THEME-accent); } #splashSpinner > .spinner { diff --git a/packages/backend/src/server/web/style.embed.css b/packages/backend/src/server/web/style.embed.css index a7b110d80a..5e8786cc4e 100644 --- a/packages/backend/src/server/web/style.embed.css +++ b/packages/backend/src/server/web/style.embed.css @@ -5,8 +5,8 @@ */ html { - background-color: var(--bg); - color: var(--fg); + background-color: var(--MI_THEME-bg); + color: var(--MI_THEME-fg); } html.embed { @@ -24,7 +24,7 @@ html.embed { width: 100vw; height: 100vh; cursor: wait; - background-color: var(--bg); + background-color: var(--MI_THEME-bg); opacity: 1; transition: opacity 0.5s ease; } @@ -33,7 +33,7 @@ html.embed #splash { box-sizing: border-box; min-height: 300px; border-radius: var(--radius, 12px); - border: 1px solid var(--divider, #e8e8e8); + border: 1px solid var(--MI_THEME-divider, #e8e8e8); } html.embed.norounded #splash { @@ -67,7 +67,7 @@ html.embed.noborder #splash { width: 28px; height: 28px; transform: translateY(70px); - color: var(--accent); + color: var(--MI_THEME-accent); } #splashSpinner > .spinner { diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts index ef80d25f81..9c1b1008d6 100644 --- a/packages/backend/test/unit/RoleService.ts +++ b/packages/backend/test/unit/RoleService.ts @@ -10,6 +10,8 @@ import { jest } from '@jest/globals'; import { ModuleMocker } from 'jest-mock'; import { Test } from '@nestjs/testing'; import * as lolex from '@sinonjs/fake-timers'; +import type { TestingModule } from '@nestjs/testing'; +import type { MockFunctionMetadata } from 'jest-mock'; import { GlobalModule } from '@/GlobalModule.js'; import { RoleService } from '@/core/RoleService.js'; import { @@ -31,8 +33,6 @@ import { secureRndstr } from '@/misc/secure-rndstr.js'; import { NotificationService } from '@/core/NotificationService.js'; import { RoleCondFormulaValue } from '@/models/Role.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import type { TestingModule } from '@nestjs/testing'; -import type { MockFunctionMetadata } from 'jest-mock'; const moduleMocker = new ModuleMocker(global); @@ -277,9 +277,9 @@ describe('RoleService', () => { }); describe('getModeratorIds', () => { - test('includeAdmins = false, excludeExpire = false', async () => { - const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([ - createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), + test('includeAdmins = false, includeRoot = false, excludeExpire = false', async () => { + const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }), ]); const role1 = await createRole({ name: 'admin', isAdministrator: true }); @@ -295,13 +295,17 @@ describe('RoleService', () => { assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), ]); - const result = await roleService.getModeratorIds(false, false); + const result = await roleService.getModeratorIds({ + includeAdmins: false, + includeRoot: false, + excludeExpire: false, + }); expect(result).toEqual([modeUser1.id, modeUser2.id]); }); - test('includeAdmins = false, excludeExpire = true', async () => { - const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([ - createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), + test('includeAdmins = false, includeRoot = false, excludeExpire = true', async () => { + const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }), ]); const role1 = await createRole({ name: 'admin', isAdministrator: true }); @@ -317,13 +321,17 @@ describe('RoleService', () => { assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), ]); - const result = await roleService.getModeratorIds(false, true); + const result = await roleService.getModeratorIds({ + includeAdmins: false, + includeRoot: false, + excludeExpire: true, + }); expect(result).toEqual([modeUser1.id]); }); - test('includeAdmins = true, excludeExpire = false', async () => { - const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([ - createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), + test('includeAdmins = true, includeRoot = false, excludeExpire = false', async () => { + const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }), ]); const role1 = await createRole({ name: 'admin', isAdministrator: true }); @@ -339,13 +347,17 @@ describe('RoleService', () => { assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), ]); - const result = await roleService.getModeratorIds(true, false); + const result = await roleService.getModeratorIds({ + includeAdmins: true, + includeRoot: false, + excludeExpire: false, + }); expect(result).toEqual([adminUser1.id, adminUser2.id, modeUser1.id, modeUser2.id]); }); - test('includeAdmins = true, excludeExpire = true', async () => { - const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([ - createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), + test('includeAdmins = true, includeRoot = false, excludeExpire = true', async () => { + const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }), ]); const role1 = await createRole({ name: 'admin', isAdministrator: true }); @@ -361,9 +373,111 @@ describe('RoleService', () => { assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), ]); - const result = await roleService.getModeratorIds(true, true); + const result = await roleService.getModeratorIds({ + includeAdmins: true, + includeRoot: false, + excludeExpire: true, + }); expect(result).toEqual([adminUser1.id, modeUser1.id]); }); + + test('includeAdmins = false, includeRoot = true, excludeExpire = false', async () => { + const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }), + ]); + + const role1 = await createRole({ name: 'admin', isAdministrator: true }); + const role2 = await createRole({ name: 'moderator', isModerator: true }); + const role3 = await createRole({ name: 'normal' }); + + await Promise.all([ + assignRole({ userId: adminUser1.id, roleId: role1.id }), + assignRole({ userId: adminUser2.id, roleId: role1.id, expiresAt: new Date(Date.now() - 1000) }), + assignRole({ userId: modeUser1.id, roleId: role2.id }), + assignRole({ userId: modeUser2.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }), + assignRole({ userId: normalUser1.id, roleId: role3.id }), + assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), + ]); + + const result = await roleService.getModeratorIds({ + includeAdmins: false, + includeRoot: true, + excludeExpire: false, + }); + expect(result).toEqual([modeUser1.id, modeUser2.id, rootUser.id]); + }); + + test('root has moderator role', async () => { + const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser({ isRoot: true }), + ]); + + const role1 = await createRole({ name: 'admin', isAdministrator: true }); + const role2 = await createRole({ name: 'moderator', isModerator: true }); + const role3 = await createRole({ name: 'normal' }); + + await Promise.all([ + assignRole({ userId: adminUser1.id, roleId: role1.id }), + assignRole({ userId: modeUser1.id, roleId: role2.id }), + assignRole({ userId: rootUser.id, roleId: role2.id }), + assignRole({ userId: normalUser1.id, roleId: role3.id }), + ]); + + const result = await roleService.getModeratorIds({ + includeAdmins: false, + includeRoot: true, + excludeExpire: false, + }); + expect(result).toEqual([modeUser1.id, rootUser.id]); + }); + + test('root has administrator role', async () => { + const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser({ isRoot: true }), + ]); + + const role1 = await createRole({ name: 'admin', isAdministrator: true }); + const role2 = await createRole({ name: 'moderator', isModerator: true }); + const role3 = await createRole({ name: 'normal' }); + + await Promise.all([ + assignRole({ userId: adminUser1.id, roleId: role1.id }), + assignRole({ userId: rootUser.id, roleId: role1.id }), + assignRole({ userId: modeUser1.id, roleId: role2.id }), + assignRole({ userId: normalUser1.id, roleId: role3.id }), + ]); + + const result = await roleService.getModeratorIds({ + includeAdmins: true, + includeRoot: true, + excludeExpire: false, + }); + expect(result).toEqual([adminUser1.id, modeUser1.id, rootUser.id]); + }); + + test('root has moderator role(expire)', async () => { + const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser({ isRoot: true }), + ]); + + const role1 = await createRole({ name: 'admin', isAdministrator: true }); + const role2 = await createRole({ name: 'moderator', isModerator: true }); + const role3 = await createRole({ name: 'normal' }); + + await Promise.all([ + assignRole({ userId: adminUser1.id, roleId: role1.id }), + assignRole({ userId: modeUser1.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }), + assignRole({ userId: rootUser.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }), + assignRole({ userId: normalUser1.id, roleId: role3.id }), + ]); + + const result = await roleService.getModeratorIds({ + includeAdmins: false, + includeRoot: true, + excludeExpire: true, + }); + expect(result).toEqual([rootUser.id]); + }); }); describe('conditional role', () => { diff --git a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts new file mode 100644 index 0000000000..b783320aa0 --- /dev/null +++ b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts @@ -0,0 +1,235 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { jest } from '@jest/globals'; +import { Test, TestingModule } from '@nestjs/testing'; +import * as lolex from '@sinonjs/fake-timers'; +import { addHours, addSeconds, subDays, subHours, subSeconds } from 'date-fns'; +import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; +import { MiUser, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import { IdService } from '@/core/IdService.js'; +import { RoleService } from '@/core/RoleService.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { MetaService } from '@/core/MetaService.js'; +import { DI } from '@/di-symbols.js'; +import { QueueLoggerService } from '@/queue/QueueLoggerService.js'; + +const baseDate = new Date(Date.UTC(2000, 11, 15, 12, 0, 0)); + +describe('CheckModeratorsActivityProcessorService', () => { + let app: TestingModule; + let clock: lolex.InstalledClock; + let service: CheckModeratorsActivityProcessorService; + + // -------------------------------------------------------------------------------------- + + let usersRepository: UsersRepository; + let userProfilesRepository: UserProfilesRepository; + let idService: IdService; + let roleService: jest.Mocked; + + // -------------------------------------------------------------------------------------- + + async function createUser(data: Partial = {}) { + const id = idService.gen(); + const user = await usersRepository + .insert({ + id: id, + username: `user_${id}`, + usernameLower: `user_${id}`.toLowerCase(), + ...data, + }) + .then(x => usersRepository.findOneByOrFail(x.identifiers[0])); + + await userProfilesRepository.insert({ + userId: user.id, + }); + + return user; + } + + function mockModeratorRole(users: MiUser[]) { + roleService.getModerators.mockReset(); + roleService.getModerators.mockResolvedValue(users); + } + + // -------------------------------------------------------------------------------------- + + beforeAll(async () => { + app = await Test + .createTestingModule({ + imports: [ + GlobalModule, + ], + providers: [ + CheckModeratorsActivityProcessorService, + IdService, + { + provide: RoleService, useFactory: () => ({ getModerators: jest.fn() }), + }, + { + provide: MetaService, useFactory: () => ({ fetch: jest.fn() }), + }, + { + provide: QueueLoggerService, useFactory: () => ({ + logger: ({ + createSubLogger: () => ({ + info: jest.fn(), + warn: jest.fn(), + succ: jest.fn(), + }), + }), + }), + }, + ], + }) + .compile(); + + usersRepository = app.get(DI.usersRepository); + userProfilesRepository = app.get(DI.userProfilesRepository); + + service = app.get(CheckModeratorsActivityProcessorService); + idService = app.get(IdService); + roleService = app.get(RoleService) as jest.Mocked; + + app.enableShutdownHooks(); + }); + + beforeEach(async () => { + clock = lolex.install({ + now: new Date(baseDate), + shouldClearNativeTimers: true, + }); + }); + + afterEach(async () => { + clock.uninstall(); + await usersRepository.delete({}); + await userProfilesRepository.delete({}); + roleService.getModerators.mockReset(); + }); + + afterAll(async () => { + await app.close(); + }); + + // -------------------------------------------------------------------------------------- + + describe('evaluateModeratorsInactiveDays', () => { + test('[isModeratorsInactive] inactiveなモデレーターがいても他のモデレーターがアクティブなら"運営が非アクティブ"としてみなされない', async () => { + const [user1, user2, user3, user4] = await Promise.all([ + // 期限よりも1秒新しいタイミングでアクティブ化(セーフ) + createUser({ lastActiveDate: subDays(addSeconds(baseDate, 1), 7) }), + // 期限ちょうどにアクティブ化(セーフ) + createUser({ lastActiveDate: subDays(baseDate, 7) }), + // 期限よりも1秒古いタイミングでアクティブ化(アウト) + createUser({ lastActiveDate: subDays(subSeconds(baseDate, 1), 7) }), + // 対象外 + createUser({ lastActiveDate: null }), + ]); + + mockModeratorRole([user1, user2, user3, user4]); + + const result = await service.evaluateModeratorsInactiveDays(); + expect(result.isModeratorsInactive).toBe(false); + expect(result.inactiveModerators).toEqual([user3]); + }); + + test('[isModeratorsInactive] 全員非アクティブなら"運営が非アクティブ"としてみなされる', async () => { + const [user1, user2] = await Promise.all([ + // 期限よりも1秒古いタイミングでアクティブ化(アウト) + createUser({ lastActiveDate: subDays(subSeconds(baseDate, 1), 7) }), + // 対象外 + createUser({ lastActiveDate: null }), + ]); + + mockModeratorRole([user1, user2]); + + const result = await service.evaluateModeratorsInactiveDays(); + expect(result.isModeratorsInactive).toBe(true); + expect(result.inactiveModerators).toEqual([user1]); + }); + + test('[countdown] 猶予まで24時間ある場合、猶予1日として計算される', async () => { + const [user1, user2] = await Promise.all([ + createUser({ lastActiveDate: subDays(baseDate, 8) }), + // 猶予はこのユーザ基準で計算される想定。 + // 期限まで残り24時間->猶予1日として計算されるはずである + createUser({ lastActiveDate: subDays(baseDate, 6) }), + ]); + + mockModeratorRole([user1, user2]); + + const result = await service.evaluateModeratorsInactiveDays(); + expect(result.isModeratorsInactive).toBe(false); + expect(result.inactiveModerators).toEqual([user1]); + expect(result.inactivityLimitCountdown).toBe(1); + }); + + test('[countdown] 猶予まで25時間ある場合、猶予1日として計算される', async () => { + const [user1, user2] = await Promise.all([ + createUser({ lastActiveDate: subDays(baseDate, 8) }), + // 猶予はこのユーザ基準で計算される想定。 + // 期限まで残り25時間->猶予1日として計算されるはずである + createUser({ lastActiveDate: subDays(addHours(baseDate, 1), 6) }), + ]); + + mockModeratorRole([user1, user2]); + + const result = await service.evaluateModeratorsInactiveDays(); + expect(result.isModeratorsInactive).toBe(false); + expect(result.inactiveModerators).toEqual([user1]); + expect(result.inactivityLimitCountdown).toBe(1); + }); + + test('[countdown] 猶予まで23時間ある場合、猶予0日として計算される', async () => { + const [user1, user2] = await Promise.all([ + createUser({ lastActiveDate: subDays(baseDate, 8) }), + // 猶予はこのユーザ基準で計算される想定。 + // 期限まで残り23時間->猶予0日として計算されるはずである + createUser({ lastActiveDate: subDays(subHours(baseDate, 1), 6) }), + ]); + + mockModeratorRole([user1, user2]); + + const result = await service.evaluateModeratorsInactiveDays(); + expect(result.isModeratorsInactive).toBe(false); + expect(result.inactiveModerators).toEqual([user1]); + expect(result.inactivityLimitCountdown).toBe(0); + }); + + test('[countdown] 期限ちょうどの場合、猶予0日として計算される', async () => { + const [user1, user2] = await Promise.all([ + createUser({ lastActiveDate: subDays(baseDate, 8) }), + // 猶予はこのユーザ基準で計算される想定。 + // 期限ちょうど->猶予0日として計算されるはずである + createUser({ lastActiveDate: subDays(baseDate, 7) }), + ]); + + mockModeratorRole([user1, user2]); + + const result = await service.evaluateModeratorsInactiveDays(); + expect(result.isModeratorsInactive).toBe(false); + expect(result.inactiveModerators).toEqual([user1]); + expect(result.inactivityLimitCountdown).toBe(0); + }); + + test('[countdown] 期限より1時間超過している場合、猶予-1日として計算される', async () => { + const [user1, user2] = await Promise.all([ + createUser({ lastActiveDate: subDays(baseDate, 8) }), + // 猶予はこのユーザ基準で計算される想定。 + // 期限より1時間超過->猶予-1日として計算されるはずである + createUser({ lastActiveDate: subDays(subHours(baseDate, 1), 7) }), + ]); + + mockModeratorRole([user1, user2]); + + const result = await service.evaluateModeratorsInactiveDays(); + expect(result.isModeratorsInactive).toBe(true); + expect(result.inactiveModerators).toEqual([user1, user2]); + expect(result.inactivityLimitCountdown).toBe(-1); + }); + }); +}); diff --git a/packages/frontend-embed/src/components/EmLoading.vue b/packages/frontend-embed/src/components/EmLoading.vue index 49d8ace37b..47d797606b 100644 --- a/packages/frontend-embed/src/components/EmLoading.vue +++ b/packages/frontend-embed/src/components/EmLoading.vue @@ -56,7 +56,7 @@ const props = withDefaults(defineProps<{ --size: 38px; &.colored { - color: var(--accent); + color: var(--MI_THEME-accent); } &.inline { diff --git a/packages/frontend-embed/src/components/EmMediaBanner.vue b/packages/frontend-embed/src/components/EmMediaBanner.vue index 435da238a4..cf4a4c53b5 100644 --- a/packages/frontend-embed/src/components/EmMediaBanner.vue +++ b/packages/frontend-embed/src/components/EmMediaBanner.vue @@ -31,17 +31,17 @@ defineProps<{ display: flex; align-items: center; width: 100%; - padding: var(--margin); + padding: var(--MI-margin); margin-top: 4px; - border: 1px solid var(--inputBorder); - border-radius: var(--radius); - background-color: var(--panel); + border: 1px solid var(--MI_THEME-inputBorder); + border-radius: var(--MI-radius); + background-color: var(--MI_THEME-panel); transition: background-color .1s, border-color .1s; &:hover { text-decoration: none; - border-color: var(--inputBorderHover); - background-color: var(--buttonHoverBg); + border-color: var(--MI_THEME-inputBorderHover); + background-color: var(--MI_THEME-buttonHoverBg); } } diff --git a/packages/frontend-embed/src/components/EmMediaImage.vue b/packages/frontend-embed/src/components/EmMediaImage.vue index 470352469b..d711020a74 100644 --- a/packages/frontend-embed/src/components/EmMediaImage.vue +++ b/packages/frontend-embed/src/components/EmMediaImage.vue @@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
GIF
ALT
-
+
@@ -94,8 +94,8 @@ async function onclick(ev: MouseEvent) { display: block; position: absolute; border-radius: 6px; - background-color: var(--fg); - color: var(--accentLighten); + background-color: var(--MI_THEME-fg); + color: var(--MI_THEME-accentLighten); font-size: 12px; opacity: .5; padding: 5px 8px; @@ -114,19 +114,19 @@ async function onclick(ev: MouseEvent) { .visible { position: relative; - //box-shadow: 0 0 0 1px var(--divider) inset; - background: var(--bg); + //box-shadow: 0 0 0 1px var(--MI_THEME-divider) inset; + background: var(--MI_THEME-bg); background-size: 16px 16px; } html[data-color-scheme=dark] .visible { --c: rgb(255 255 255 / 2%); - background-image: linear-gradient(45deg, var(--c) 16.67%, var(--bg) 16.67%, var(--bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--bg) 66.67%, var(--bg) 100%); + background-image: linear-gradient(45deg, var(--c) 16.67%, var(--MI_THEME-bg) 16.67%, var(--MI_THEME-bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--MI_THEME-bg) 66.67%, var(--MI_THEME-bg) 100%); } html[data-color-scheme=light] .visible { --c: rgb(0 0 0 / 2%); - background-image: linear-gradient(45deg, var(--c) 16.67%, var(--bg) 16.67%, var(--bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--bg) 66.67%, var(--bg) 100%); + background-image: linear-gradient(45deg, var(--c) 16.67%, var(--MI_THEME-bg) 16.67%, var(--MI_THEME-bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--MI_THEME-bg) 66.67%, var(--MI_THEME-bg) 100%); } .imageContainer { @@ -150,10 +150,10 @@ html[data-color-scheme=light] .visible { } .indicator { - /* Hardcode to black because either --bg or --fg makes it hard to read in dark/light mode */ + /* Hardcode to black because either --MI_THEME-bg or --MI_THEME-fg makes it hard to read in dark/light mode */ background-color: black; border-radius: 6px; - color: var(--accentLighten); + color: var(--MI_THEME-accentLighten); display: inline-block; font-weight: bold; font-size: 0.8em; diff --git a/packages/frontend-embed/src/components/EmMediaVideo.vue b/packages/frontend-embed/src/components/EmMediaVideo.vue index ce751f9acd..e2779bdee4 100644 --- a/packages/frontend-embed/src/components/EmMediaVideo.vue +++ b/packages/frontend-embed/src/components/EmMediaVideo.vue @@ -29,9 +29,9 @@ defineProps<{ width: 100%; height: auto; aspect-ratio: 16 / 9; - padding: var(--margin); - border: 1px solid var(--divider); - border-radius: var(--radius); + padding: var(--MI-margin); + border: 1px solid var(--MI_THEME-divider); + border-radius: var(--MI-radius); background-color: #000; &:hover { @@ -49,7 +49,7 @@ defineProps<{ } .videoOverlayPlayButton { - background: var(--accent); + background: var(--MI_THEME-accent); color: #fff; padding: 1rem; border-radius: 99rem; diff --git a/packages/frontend-embed/src/components/EmMention.vue b/packages/frontend-embed/src/components/EmMention.vue index a631783507..a71364237d 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('--mention')); +const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-mention')); bg.setAlpha(0.1); const bgCss = bg.toRgbString(); @@ -37,7 +37,7 @@ const bgCss = bg.toRgbString(); display: inline-block; padding: 4px 8px 4px 4px; border-radius: 999px; - color: var(--mention); + color: var(--MI_THEME-mention); } .host { diff --git a/packages/frontend-embed/src/components/EmMfm.ts b/packages/frontend-embed/src/components/EmMfm.ts index 59f0d495e6..cae2feb8fb 100644 --- a/packages/frontend-embed/src/components/EmMfm.ts +++ b/packages/frontend-embed/src/components/EmMfm.ts @@ -26,8 +26,8 @@ const QUOTE_STYLE = ` display: block; margin: 8px; padding: 6px 0 6px 12px; -color: var(--fg); -border-left: solid 3px var(--fg); +color: var(--MI_THEME-fg); +border-left: solid 3px var(--MI_THEME-fg); opacity: 0.7; `.split('\n').join(' '); @@ -251,7 +251,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext .collapsedLabel { - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); } } .collapsedLabel { display: inline-block; - background: var(--panel); + background: var(--MI_THEME-panel); padding: 6px 10px; font-size: 0.8em; border-radius: 999px; @@ -424,13 +424,13 @@ const isDeleted = ref(false); } .replyIcon { - color: var(--accent); + color: var(--MI_THEME-accent); margin-right: 0.5em; } .translation { - border: solid 0.5px var(--divider); - border-radius: var(--radius); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius); padding: 12px; margin-top: 8px; } @@ -449,7 +449,7 @@ const isDeleted = ref(false); .quoteNote { padding: 16px; - border: dashed 1px var(--renote); + border: dashed 1px var(--MI_THEME-renote); border-radius: 8px; overflow: clip; } @@ -473,7 +473,7 @@ const isDeleted = ref(false); } &:hover { - color: var(--fgHighlighted); + color: var(--MI_THEME-fgHighlighted); } } @@ -550,7 +550,7 @@ const isDeleted = ref(false); margin: 0 10px 0 0; width: 46px; height: 46px; - top: calc(14px + var(--stickyTop, 0px)); + top: calc(14px + var(--MI-stickyTop, 0px)); } } diff --git a/packages/frontend-embed/src/components/EmNoteDetailed.vue b/packages/frontend-embed/src/components/EmNoteDetailed.vue index 360de31864..b39b47c065 100644 --- a/packages/frontend-embed/src/components/EmNoteDetailed.vue +++ b/packages/frontend-embed/src/components/EmNoteDetailed.vue @@ -195,7 +195,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong); padding: 16px 32px 8px 32px; line-height: 28px; white-space: pre; - color: var(--renote); + color: var(--MI_THEME-renote); } .renoteAvatar { @@ -281,7 +281,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong); padding: 4px 6px; font-size: 80%; line-height: 1; - border: solid 0.5px var(--divider); + border: solid 0.5px var(--MI_THEME-divider); border-radius: 4px; } @@ -323,14 +323,14 @@ const collapsed = ref(appearNote.value.cw == null && isLong); } .noteReplyTarget { - color: var(--accent); + color: var(--MI_THEME-accent); margin-right: 0.5em; } .rn { margin-left: 4px; font-style: oblique; - color: var(--renote); + color: var(--MI_THEME-renote); } .reactionOmitted { @@ -350,7 +350,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong); .quoteNote { padding: 16px; - border: dashed 1px var(--renote); + border: dashed 1px var(--MI_THEME-renote); border-radius: 8px; overflow: clip; } @@ -364,12 +364,12 @@ const collapsed = ref(appearNote.value.cw == null && isLong); width: 100%; margin-top: 14px; position: sticky; - bottom: calc(var(--stickyBottom, 0px) + 14px); + bottom: calc(var(--MI-stickyBottom, 0px) + 14px); } .showLessLabel { display: inline-block; - background: var(--popup); + background: var(--MI_THEME-popup); padding: 6px 10px; font-size: 0.8em; border-radius: 999px; @@ -390,16 +390,16 @@ const collapsed = ref(appearNote.value.cw == null && isLong); z-index: 2; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), var(--X15)); + background: linear-gradient(0deg, var(--MI_THEME-panel), var(--MI_THEME-X15)); &:hover > .collapsedLabel { - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); } } .collapsedLabel { display: inline-block; - background: var(--panel); + background: var(--MI_THEME-panel); padding: 6px 10px; font-size: 0.8em; border-radius: 999px; @@ -422,7 +422,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong); } &:hover { - color: var(--fgHighlighted); + color: var(--MI_THEME-fgHighlighted); } } @@ -438,7 +438,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong); opacity: 0.7; &.reacted { - color: var(--accent); + color: var(--MI_THEME-accent); } } diff --git a/packages/frontend-embed/src/components/EmNoteHeader.vue b/packages/frontend-embed/src/components/EmNoteHeader.vue index 7d0b9bacad..85b4aac071 100644 --- a/packages/frontend-embed/src/components/EmNoteHeader.vue +++ b/packages/frontend-embed/src/components/EmNoteHeader.vue @@ -72,7 +72,7 @@ defineProps<{ margin: 0 .5em 0 0; padding: 1px 6px; font-size: 80%; - border: solid 0.5px var(--divider); + border: solid 0.5px var(--MI_THEME-divider); border-radius: 3px; } diff --git a/packages/frontend-embed/src/components/EmNoteSimple.vue b/packages/frontend-embed/src/components/EmNoteSimple.vue index 704a876e59..b9aaf3fa4a 100644 --- a/packages/frontend-embed/src/components/EmNoteSimple.vue +++ b/packages/frontend-embed/src/components/EmNoteSimple.vue @@ -53,7 +53,7 @@ const showContent = ref(false); height: 34px; border-radius: 8px; position: sticky !important; - top: calc(16px + var(--stickyTop, 0px)); + top: calc(16px + var(--MI-stickyTop, 0px)); left: 0; } diff --git a/packages/frontend-embed/src/components/EmNoteSub.vue b/packages/frontend-embed/src/components/EmNoteSub.vue index f60aea3e7e..59be8608e0 100644 --- a/packages/frontend-embed/src/components/EmNoteSub.vue +++ b/packages/frontend-embed/src/components/EmNoteSub.vue @@ -123,7 +123,7 @@ if (props.detail) { } .reply, .more { - border-left: solid 0.5px var(--divider); + border-left: solid 0.5px var(--MI_THEME-divider); margin-top: 10px; } @@ -144,7 +144,7 @@ if (props.detail) { .muted { text-align: center; padding: 8px !important; - border: 1px solid var(--divider); + border: 1px solid var(--MI_THEME-divider); margin: 8px 8px 0 8px; border-radius: 8px; } diff --git a/packages/frontend-embed/src/components/EmNotes.vue b/packages/frontend-embed/src/components/EmNotes.vue index 3418d97f77..4211261e19 100644 --- a/packages/frontend-embed/src/components/EmNotes.vue +++ b/packages/frontend-embed/src/components/EmNotes.vue @@ -43,10 +43,10 @@ defineExpose({ diff --git a/packages/frontend-embed/src/components/EmPoll.vue b/packages/frontend-embed/src/components/EmPoll.vue index a2b1203449..d197e094c6 100644 --- a/packages/frontend-embed/src/components/EmPoll.vue +++ b/packages/frontend-embed/src/components/EmPoll.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
  • - + ({{ i18n.tsx._poll.votesCount({ n: choice.votes }) }}) @@ -52,8 +52,8 @@ const total = computed(() => sum(props.poll.choices.map(x => x.votes))); position: relative; margin: 4px 0; padding: 4px; - //border: solid 0.5px var(--divider); - background: var(--accentedBg); + //border: solid 0.5px var(--MI_THEME-divider); + background: var(--MI_THEME-accentedBg); border-radius: 4px; overflow: clip; } @@ -63,8 +63,8 @@ const total = computed(() => sum(props.poll.choices.map(x => x.votes))); top: 0; left: 0; height: 100%; - background: var(--accent); - background: linear-gradient(90deg,var(--buttonGradateA),var(--buttonGradateB)); + background: var(--MI_THEME-accent); + background: linear-gradient(90deg,var(--MI_THEME-buttonGradateA),var(--MI_THEME-buttonGradateB)); transition: width 1s ease; } @@ -72,11 +72,11 @@ const total = computed(() => sum(props.poll.choices.map(x => x.votes))); position: relative; display: inline-block; padding: 3px 5px; - background: var(--panel); + background: var(--MI_THEME-panel); border-radius: 3px; } .info { - color: var(--fg); + color: var(--MI_THEME-fg); } diff --git a/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue b/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue index 2e43eb8d17..2ebff489fd 100644 --- a/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue +++ b/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue @@ -38,7 +38,7 @@ const props = defineProps<{ justify-content: center; &.canToggle { - background: var(--buttonBg); + background: var(--MI_THEME-buttonBg); &:hover { background: rgba(0, 0, 0, 0.1); @@ -72,12 +72,12 @@ const props = defineProps<{ } &.reacted, &.reacted:hover { - background: var(--accentedBg); - color: var(--accent); - box-shadow: 0 0 0 1px var(--accent) inset; + background: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); + box-shadow: 0 0 0 1px var(--MI_THEME-accent) inset; > .count { - color: var(--accent); + color: var(--MI_THEME-accent); } > .icon { diff --git a/packages/frontend-embed/src/components/EmSubNoteContent.vue b/packages/frontend-embed/src/components/EmSubNoteContent.vue index db2666a45f..61815ddfd8 100644 --- a/packages/frontend-embed/src/components/EmSubNoteContent.vue +++ b/packages/frontend-embed/src/components/EmSubNoteContent.vue @@ -65,11 +65,11 @@ const collapsed = ref(isLong); left: 0; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0)); > .fadeLabel { display: inline-block; - background: var(--panel); + background: var(--MI_THEME-panel); padding: 6px 10px; font-size: 0.8em; border-radius: 999px; @@ -78,7 +78,7 @@ const collapsed = ref(isLong); &:hover { > .fadeLabel { - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); } } } @@ -87,25 +87,25 @@ const collapsed = ref(isLong); .reply { margin-right: 6px; - color: var(--accent); + color: var(--MI_THEME-accent); } .rp { margin-left: 4px; font-style: oblique; - color: var(--renote); + color: var(--MI_THEME-renote); } .showLess { width: 100%; margin-top: 14px; position: sticky; - bottom: calc(var(--stickyBottom, 0px) + 14px); + bottom: calc(var(--MI-stickyBottom, 0px) + 14px); } .showLessLabel { display: inline-block; - background: var(--popup); + background: var(--MI_THEME-popup); padding: 6px 10px; font-size: 0.8em; border-radius: 999px; diff --git a/packages/frontend-embed/src/components/EmTime.vue b/packages/frontend-embed/src/components/EmTime.vue index c3986f7d70..7902e18483 100644 --- a/packages/frontend-embed/src/components/EmTime.vue +++ b/packages/frontend-embed/src/components/EmTime.vue @@ -98,10 +98,10 @@ if (!invalid && props.origin === null && (props.mode === 'relative' || props.mod diff --git a/packages/frontend-embed/src/components/EmTimelineContainer.vue b/packages/frontend-embed/src/components/EmTimelineContainer.vue index 6c30b1102d..60fd67ced9 100644 --- a/packages/frontend-embed/src/components/EmTimelineContainer.vue +++ b/packages/frontend-embed/src/components/EmTimelineContainer.vue @@ -20,7 +20,7 @@ withDefaults(defineProps<{ diff --git a/packages/frontend-embed/src/pages/tag.vue b/packages/frontend-embed/src/pages/tag.vue index b481b3ebe5..4b00ae7c2d 100644 --- a/packages/frontend-embed/src/pages/tag.vue +++ b/packages/frontend-embed/src/pages/tag.vue @@ -83,7 +83,7 @@ function top(ev: MouseEvent) { display: flex; min-width: 0; align-items: center; - gap: var(--margin); + gap: var(--MI-margin); overflow: hidden; .headerClipIconRoot { @@ -93,8 +93,8 @@ function top(ev: MouseEvent) { line-height: 32px; font-size: 14px; text-align: center; - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); border-radius: 50%; } diff --git a/packages/frontend-embed/src/pages/user-timeline.vue b/packages/frontend-embed/src/pages/user-timeline.vue index 85e6f52d50..348b1a7622 100644 --- a/packages/frontend-embed/src/pages/user-timeline.vue +++ b/packages/frontend-embed/src/pages/user-timeline.vue @@ -117,7 +117,7 @@ function top(ev: MouseEvent) { display: flex; min-width: 0; align-items: center; - gap: var(--margin); + gap: var(--MI-margin); overflow: hidden; .avatarLink { diff --git a/packages/frontend-embed/src/style.scss b/packages/frontend-embed/src/style.scss index 02008ddbd0..2e43cfd20a 100644 --- a/packages/frontend-embed/src/style.scss +++ b/packages/frontend-embed/src/style.scss @@ -7,18 +7,18 @@ */ :root { - --radius: 12px; - --marginFull: 14px; - --marginHalf: 10px; + --MI-radius: 12px; + --MI-marginFull: 14px; + --MI-marginHalf: 10px; - --margin: var(--marginFull); + --MI-margin: var(--MI-marginFull); } html { background-color: transparent; color-scheme: light dark; - color: var(--fg); - accent-color: var(--accent); + color: var(--MI_THEME-fg); + accent-color: var(--MI_THEME-accent); overflow: clip; overflow-wrap: break-word; font-family: 'Hiragino Maru Gothic Pro', "BIZ UDGothic", Roboto, HelveticaNeue, Arial, sans-serif; @@ -29,7 +29,7 @@ html { -webkit-text-size-adjust: 100%; &, * { - scrollbar-color: var(--scrollbarHandle) transparent; + scrollbar-color: var(--MI_THEME-scrollbarHandle) transparent; scrollbar-width: thin; &::-webkit-scrollbar { @@ -42,14 +42,14 @@ html { } &::-webkit-scrollbar-thumb { - background: var(--scrollbarHandle); + background: var(--MI_THEME-scrollbarHandle); &:hover { - background: var(--scrollbarHandleHover); + background: var(--MI_THEME-scrollbarHandleHover); } &:active { - background: var(--accent); + background: var(--MI_THEME-accent); } } } @@ -93,7 +93,7 @@ rt { } :focus-visible { - outline: var(--focus) solid 2px; + outline: var(--MI_THEME-focus) solid 2px; outline-offset: -2px; &:hover { @@ -151,38 +151,38 @@ rt { ._buttonGray { @extend ._button; - background: var(--buttonBg); + background: var(--MI_THEME-buttonBg); &:not(:disabled):hover { - background: var(--buttonHoverBg); + background: var(--MI_THEME-buttonHoverBg); } } ._buttonPrimary { @extend ._button; - color: var(--fgOnAccent); - background: var(--accent); + color: var(--MI_THEME-fgOnAccent); + background: var(--MI_THEME-accent); &:not(:disabled):hover { - background: hsl(from var(--accent) h s calc(l + 5)); + background: hsl(from var(--MI_THEME-accent) h s calc(l + 5)); } &:not(:disabled):active { - background: hsl(from var(--accent) h s calc(l - 5)); + background: hsl(from var(--MI_THEME-accent) h s calc(l - 5)); } } ._buttonGradate { @extend ._buttonPrimary; - color: var(--fgOnAccent); - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + color: var(--MI_THEME-fgOnAccent); + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); &:not(:disabled):hover { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); } &:not(:disabled):active { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); } } @@ -199,13 +199,13 @@ rt { } ._help { - color: var(--accent); + color: var(--MI_THEME-accent); cursor: help; } ._textButton { @extend ._button; - color: var(--accent); + color: var(--MI_THEME-accent); &:focus-visible { outline-offset: 2px; @@ -217,13 +217,13 @@ rt { } ._panel { - background: var(--panel); - border-radius: var(--radius); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius); overflow: clip; } ._margin { - margin: var(--margin) 0; + margin: var(--MI-margin) 0; } ._gaps_m { @@ -241,7 +241,7 @@ rt { ._gaps { display: flex; flex-direction: column; - gap: var(--margin); + gap: var(--MI-margin); } ._buttons { @@ -263,24 +263,24 @@ rt { padding: 10px; box-sizing: border-box; text-align: center; - border: solid 0.5px var(--divider); - border-radius: var(--radius); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius); &:active { - border-color: var(--accent); + border-color: var(--MI_THEME-accent); } } ._popup { - background: var(--popup); - border-radius: var(--radius); + background: var(--MI_THEME-popup); + border-radius: var(--MI-radius); contain: content; } ._acrylic { - background: var(--acrylicPanel); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + background: var(--MI_THEME-acrylicPanel); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } ._fullinfo { @@ -296,7 +296,7 @@ rt { } ._link { - color: var(--link); + color: var(--MI_THEME-link); } ._caption { diff --git a/packages/frontend-embed/src/theme.ts b/packages/frontend-embed/src/theme.ts index 23e70cd0d3..4664ad4880 100644 --- a/packages/frontend-embed/src/theme.ts +++ b/packages/frontend-embed/src/theme.ts @@ -61,7 +61,7 @@ export function applyTheme(theme: Theme, persist = true) { } for (const [k, v] of Object.entries(props)) { - document.documentElement.style.setProperty(`--${k}`, v.toString()); + 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 8da5f46a96..4ba5968a91 100644 --- a/packages/frontend-embed/src/ui.vue +++ b/packages/frontend-embed/src/ui.vue @@ -88,14 +88,14 @@ onUnmounted(() => { diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue index b50a7fea5c..e52ab5ccad 100644 --- a/packages/frontend/src/components/MkAsUi.vue +++ b/packages/frontend/src/components/MkAsUi.vue @@ -106,7 +106,7 @@ const containerStyle = computed(() => { const border = isBordered ? { borderWidth: c.borderWidth ?? '1px', - borderColor: c.borderColor ?? 'var(--divider)', + borderColor: c.borderColor ?? 'var(--MI_THEME-divider)', borderStyle: c.borderStyle ?? 'solid', } : undefined; @@ -165,7 +165,7 @@ function openPostForm() { } .postForm { - background: var(--bg); + background: var(--MI_THEME-bg); border-radius: 8px; } diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index f547991369..0ea4566d4e 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -407,16 +407,16 @@ onBeforeUnmount(() => { text-overflow: ellipsis; &:hover { - background: var(--X3); + background: var(--MI_THEME-X3); } &[data-selected='true'] { - background: var(--accent); + background: var(--MI_THEME-accent); color: #fff !important; } &:active { - background: var(--accentDarken); + background: var(--MI_THEME-accentDarken); color: #fff !important; } } diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index 1156b3f2b8..311facb4aa 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -129,7 +129,7 @@ function onMousedown(evt: MouseEvent): void { font-size: 95%; box-shadow: none; text-decoration: none; - background: var(--buttonBg); + background: var(--MI_THEME-buttonBg); border-radius: 5px; overflow: clip; box-sizing: border-box; @@ -140,11 +140,11 @@ function onMousedown(evt: MouseEvent): void { } &:not(:disabled):hover { - background: var(--buttonHoverBg); + background: var(--MI_THEME-buttonHoverBg); } &:not(:disabled):active { - background: var(--buttonHoverBg); + background: var(--MI_THEME-buttonHoverBg); } &.small { @@ -167,15 +167,15 @@ function onMousedown(evt: MouseEvent): void { &.primary { font-weight: bold; - color: var(--fgOnAccent) !important; - background: var(--accent); + color: var(--MI_THEME-fgOnAccent) !important; + background: var(--MI_THEME-accent); &:not(:disabled):hover { - background: hsl(from var(--accent) h s calc(l + 5)); + background: hsl(from var(--MI_THEME-accent) h s calc(l + 5)); } &:not(:disabled):active { - background: hsl(from var(--accent) h s calc(l + 5)); + background: hsl(from var(--MI_THEME-accent) h s calc(l + 5)); } } @@ -216,15 +216,15 @@ function onMousedown(evt: MouseEvent): void { &.gradate { font-weight: bold; - color: var(--fgOnAccent) !important; - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + color: var(--MI_THEME-fgOnAccent) !important; + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); &:not(:disabled):hover { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); } &:not(:disabled):active { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); } } diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue index c5b6e0caed..82fc89e51c 100644 --- a/packages/frontend/src/components/MkCaptcha.vue +++ b/packages/frontend/src/components/MkCaptcha.vue @@ -10,6 +10,17 @@ SPDX-License-Identifier: AGPL-3.0-only
    +
    + +
    +
    Test captcha passed!
    +
    +
    +
    Type "ai-chan-kawaii" to pass captcha
    + + +
    +
    @@ -29,7 +40,7 @@ export type Captcha = { getResponse(id: string): string; }; -export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile' | 'mcaptcha'; +export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile' | 'mcaptcha' | 'testcaptcha'; type CaptchaContainer = { readonly [_ in CaptchaProvider]?: Captcha; @@ -54,12 +65,16 @@ const available = ref(false); const captchaEl = shallowRef(); +const testcaptchaInput = ref(''); +const testcaptchaPassed = ref(false); + const variable = computed(() => { switch (props.provider) { case 'hcaptcha': return 'hcaptcha'; case 'recaptcha': return 'grecaptcha'; case 'turnstile': return 'turnstile'; case 'mcaptcha': return 'mcaptcha'; + case 'testcaptcha': return 'testcaptcha'; } }); @@ -71,6 +86,7 @@ const src = computed(() => { case 'recaptcha': return 'https://www.recaptcha.net/recaptcha/api.js?render=explicit'; case 'turnstile': return 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'; case 'mcaptcha': return null; + case 'testcaptcha': return null; } }); @@ -78,7 +94,7 @@ const scriptId = computed(() => `script-${props.provider}`); const captcha = computed(() => window[variable.value] || {} as unknown as Captcha); -if (loaded || props.provider === 'mcaptcha') { +if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha') { available.value = true; } else if (src.value !== null) { (document.getElementById(scriptId.value) ?? document.head.appendChild(Object.assign(document.createElement('script'), { @@ -91,6 +107,8 @@ if (loaded || props.provider === 'mcaptcha') { function reset() { if (captcha.value.reset) captcha.value.reset(); + testcaptchaPassed.value = false; + testcaptchaInput.value = ''; } async function requestRender() { @@ -127,6 +145,12 @@ function onReceivedMessage(message: MessageEvent) { } } +function testcaptchaSubmit() { + testcaptchaPassed.value = testcaptchaInput.value === 'ai-chan-kawaii'; + callback(testcaptchaPassed.value ? 'testcaptcha-passed' : undefined); + if (!testcaptchaPassed.value) testcaptchaInput.value = ''; +} + onMounted(() => { if (available.value) { window.addEventListener('message', onReceivedMessage); diff --git a/packages/frontend/src/components/MkChannelFollowButton.vue b/packages/frontend/src/components/MkChannelFollowButton.vue index 35dc3ad4bf..d4e4f6179a 100644 --- a/packages/frontend/src/components/MkChannelFollowButton.vue +++ b/packages/frontend/src/components/MkChannelFollowButton.vue @@ -68,9 +68,9 @@ async function onClick() { position: relative; display: inline-block; font-weight: bold; - color: var(--accent); + color: var(--MI_THEME-accent); background: transparent; - border: solid 1px var(--accent); + border: solid 1px var(--MI_THEME-accent); padding: 0; height: 31px; font-size: 16px; @@ -99,17 +99,17 @@ async function onClick() { } &.active { - color: var(--fgOnAccent); - background: var(--accent); + color: var(--MI_THEME-fgOnAccent); + background: var(--MI_THEME-accent); &:hover { - background: var(--accentLighten); - border-color: var(--accentLighten); + background: var(--MI_THEME-accentLighten); + border-color: var(--MI_THEME-accentLighten); } &:active { - background: var(--accentDarken); - border-color: var(--accentDarken); + background: var(--MI_THEME-accentDarken); + border-color: var(--MI_THEME-accentDarken); } } diff --git a/packages/frontend/src/components/MkChannelPreview.vue b/packages/frontend/src/components/MkChannelPreview.vue index 3c0874a1eb..99580df5e2 100644 --- a/packages/frontend/src/components/MkChannelPreview.vue +++ b/packages/frontend/src/components/MkChannelPreview.vue @@ -100,7 +100,7 @@ const bannerStyle = computed(() => { height: 100%; border-radius: inherit; pointer-events: none; - box-shadow: inset 0 0 0 2px var(--focus); + box-shadow: inset 0 0 0 2px var(--MI_THEME-focus); } } @@ -117,7 +117,7 @@ const bannerStyle = computed(() => { left: 0; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0)); } > .name { @@ -148,7 +148,7 @@ const bannerStyle = computed(() => { bottom: 16px; left: 16px; background: rgba(0, 0, 0, 0.7); - color: var(--warn); + color: var(--MI_THEME-warn); border-radius: 6px; font-weight: bold; font-size: 1em; @@ -167,7 +167,7 @@ const bannerStyle = computed(() => { > footer { padding: 12px 16px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); > span { opacity: 0.7; @@ -213,8 +213,8 @@ const bannerStyle = computed(() => { top: 0; right: 0; transform: translate(25%, -25%); - background-color: var(--accent); - border: solid var(--bg) 4px; + background-color: var(--MI_THEME-accent); + border: solid var(--MI_THEME-bg) 4px; border-radius: 100%; width: 1.5rem; height: 1.5rem; diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue index 57d325b11a..d05f4921f6 100644 --- a/packages/frontend/src/components/MkChart.vue +++ b/packages/frontend/src/components/MkChart.vue @@ -863,8 +863,8 @@ onMounted(() => { left: 0; width: 100%; height: 100%; - -webkit-backdrop-filter: var(--blur, blur(12px)); - backdrop-filter: var(--blur, blur(12px)); + -webkit-backdrop-filter: var(--MI-blur, blur(12px)); + backdrop-filter: var(--MI-blur, blur(12px)); display: flex; justify-content: center; align-items: center; diff --git a/packages/frontend/src/components/MkChartLegend.vue b/packages/frontend/src/components/MkChartLegend.vue index 6eb2009784..574cde9da4 100644 --- a/packages/frontend/src/components/MkChartLegend.vue +++ b/packages/frontend/src/components/MkChartLegend.vue @@ -53,11 +53,11 @@ defineExpose({ > .item { font-size: 85%; padding: 4px 12px 4px 8px; - border: solid 1px var(--divider); + border: solid 1px var(--MI_THEME-divider); border-radius: 999px; &:hover { - border-color: var(--inputBorderHover); + border-color: var(--MI_THEME-inputBorderHover); } &.disabled { diff --git a/packages/frontend/src/components/MkClipPreview.vue b/packages/frontend/src/components/MkClipPreview.vue index dd550733cb..5b09ec90dd 100644 --- a/packages/frontend/src/components/MkClipPreview.vue +++ b/packages/frontend/src/components/MkClipPreview.vue @@ -49,13 +49,13 @@ const remaining = computed(() => { outline: none; .root { - box-shadow: inset 0 0 0 2px var(--focus); + box-shadow: inset 0 0 0 2px var(--MI_THEME-focus); } } &:hover { text-decoration: none; - color: var(--accent); + color: var(--MI_THEME-accent); } } @@ -65,7 +65,7 @@ const remaining = computed(() => { .divider { height: 1px; - background: var(--divider); + background: var(--MI_THEME-divider); } .description { diff --git a/packages/frontend/src/components/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue index c0e7df5dac..0d7a67eaec 100644 --- a/packages/frontend/src/components/MkCode.core.vue +++ b/packages/frontend/src/components/MkCode.core.vue @@ -77,7 +77,7 @@ watch(() => props.lang, (to) => { margin: .5em 0; overflow: auto; border-radius: 8px; - border: 1px solid var(--divider); + border: 1px solid var(--MI_THEME-divider); font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; color: var(--shiki-fallback); diff --git a/packages/frontend/src/components/MkCode.vue b/packages/frontend/src/components/MkCode.vue index 716dd92678..cb82bfd98b 100644 --- a/packages/frontend/src/components/MkCode.vue +++ b/packages/frontend/src/components/MkCode.vue @@ -71,7 +71,7 @@ function copy() { .codeBlockFallbackRoot { display: block; overflow-wrap: anywhere; - background: var(--bg); + background: var(--MI_THEME-bg); padding: 1em; margin: .5em 0; overflow: auto; @@ -94,8 +94,8 @@ function copy() { border-radius: 8px; padding: 24px; margin-top: 4px; - color: var(--fg); - background: var(--bg); + color: var(--MI_THEME-fg); + background: var(--MI_THEME-bg); } .codePlaceholderContainer { diff --git a/packages/frontend/src/components/MkCodeEditor.vue b/packages/frontend/src/components/MkCodeEditor.vue index afd9132a12..5bf2301e72 100644 --- a/packages/frontend/src/components/MkCodeEditor.vue +++ b/packages/frontend/src/components/MkCodeEditor.vue @@ -140,7 +140,7 @@ watch(v, newValue => { .caption { font-size: 0.85em; padding: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); &:empty { display: none; @@ -160,17 +160,17 @@ watch(v, newValue => { margin: 0; border-radius: 6px; padding: 0; - color: var(--fg); - border: solid 1px var(--panel); + color: var(--MI_THEME-fg); + border: solid 1px var(--MI_THEME-panel); transition: border-color 0.1s ease-out; font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; &:hover { - border-color: var(--inputBorderHover) !important; + border-color: var(--MI_THEME-inputBorderHover) !important; } } .focused.codeEditorRoot { - border-color: var(--accent) !important; + border-color: var(--MI_THEME-accent) !important; border-radius: 6px; } @@ -196,7 +196,7 @@ watch(v, newValue => { resize: none; text-align: left; color: transparent; - caret-color: var(--fg); + caret-color: var(--MI_THEME-fg); background-color: transparent; border: 0; border-radius: 6px; @@ -211,6 +211,6 @@ watch(v, newValue => { } .textarea::selection { - color: var(--bg); + color: var(--MI_THEME-bg); } diff --git a/packages/frontend/src/components/MkCodeInline.vue b/packages/frontend/src/components/MkCodeInline.vue index 6add80d1bc..04b6e54108 100644 --- a/packages/frontend/src/components/MkCodeInline.vue +++ b/packages/frontend/src/components/MkCodeInline.vue @@ -18,7 +18,7 @@ const props = defineProps<{ display: inline-block; font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; overflow-wrap: anywhere; - background: var(--bg); + background: var(--MI_THEME-bg); padding: .1em; border-radius: .3em; } diff --git a/packages/frontend/src/components/MkColorInput.vue b/packages/frontend/src/components/MkColorInput.vue index f5c580789b..55a32664de 100644 --- a/packages/frontend/src/components/MkColorInput.vue +++ b/packages/frontend/src/components/MkColorInput.vue @@ -60,7 +60,7 @@ const onInput = () => { .caption { font-size: 0.85em; padding: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); &:empty { display: none; @@ -72,8 +72,8 @@ const onInput = () => { &.focused { > .inputCore { - border-color: var(--accent) !important; - //box-shadow: 0 0 0 4px var(--focus); + border-color: var(--MI_THEME-accent) !important; + //box-shadow: 0 0 0 4px var(--MI_THEME-focus); } } @@ -98,9 +98,9 @@ const onInput = () => { font: inherit; font-weight: normal; font-size: 1em; - color: var(--fg); - background: var(--panel); - border: solid 1px var(--panel); + color: var(--MI_THEME-fg); + background: var(--MI_THEME-panel); + border: solid 1px var(--MI_THEME-panel); border-radius: 6px; outline: none; box-shadow: none; @@ -108,7 +108,7 @@ const onInput = () => { transition: border-color 0.1s ease-out; &:hover { - border-color: var(--inputBorderHover) !important; + border-color: var(--MI_THEME-inputBorderHover) !important; } } diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue index 8ad653a0bf..8ab01d7db8 100644 --- a/packages/frontend/src/components/MkContainer.vue +++ b/packages/frontend/src/components/MkContainer.vue @@ -165,11 +165,11 @@ onUnmounted(() => { .header { position: sticky; - top: var(--stickyTop, 0px); + top: var(--MI-stickyTop, 0px); left: 0; - color: var(--panelHeaderFg); - background: var(--panelHeaderBg); - border-bottom: solid 0.5px var(--panelHeaderDivider); + color: var(--MI_THEME-panelHeaderFg); + background: var(--MI_THEME-panelHeaderBg); + border-bottom: solid 0.5px var(--MI_THEME-panelHeaderDivider); z-index: 2; line-height: 1.4em; } @@ -201,7 +201,7 @@ onUnmounted(() => { } .content { - --stickyTop: 0px; + --MI-stickyTop: 0px; &.omitted { position: relative; @@ -216,11 +216,11 @@ onUnmounted(() => { left: 0; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0)); > .fadeLabel { display: inline-block; - background: var(--panel); + background: var(--MI_THEME-panel); padding: 6px 10px; font-size: 0.8em; border-radius: 999px; @@ -229,7 +229,7 @@ onUnmounted(() => { &:hover { > .fadeLabel { - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); } } } diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index 2e1e92cbdf..c2a1aaf29a 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -125,7 +125,7 @@ onMounted(() => { const computedStyle = getComputedStyle(document.documentElement); const selection = cropper.getCropperSelection()!; - selection.themeColor = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString(); + selection.themeColor = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(); selection.aspectRatio = props.aspectRatio; selection.initialAspectRatio = props.aspectRatio; selection.outlined = true; @@ -170,8 +170,8 @@ onMounted(() => { display: flex; align-items: center; justify-content: center; - -webkit-backdrop-filter: var(--blur, blur(10px)); - backdrop-filter: var(--blur, blur(10px)); + -webkit-backdrop-filter: var(--MI-blur, blur(10px)); + backdrop-filter: var(--MI-blur, blur(10px)); background: rgba(0, 0, 0, 0.5); } diff --git a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue index c7f1288729..949adc6a8e 100644 --- a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue +++ b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue @@ -85,8 +85,8 @@ function cancel() { .emojiImgWrapper { max-width: 100%; height: 40cqh; - background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--X5) 8px, var(--X5) 14px); - border-radius: var(--radius); + background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--MI_THEME-X5) 8px, var(--MI_THEME-X5) 14px); + border-radius: var(--MI-radius); margin: auto; overflow-y: hidden; } @@ -101,8 +101,8 @@ function cancel() { display: inline-block; word-break: break-all; padding: 3px 10px; - background-color: var(--X5); - border: solid 1px var(--divider); - border-radius: var(--radius); + background-color: var(--MI_THEME-X5); + border: solid 1px var(--MI_THEME-divider); + border-radius: var(--MI-radius); } diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue index 4b94bef4b6..5976aa02f5 100644 --- a/packages/frontend/src/components/MkDateSeparatedList.vue +++ b/packages/frontend/src/components/MkDateSeparatedList.vue @@ -100,10 +100,12 @@ export default defineComponent({ return [el, separator]; } else { if (props.ad && item._shouldInsertAd_) { - return [h(MkAd, { + return [h('div', { key: item.id + ':ad', + style: 'padding: 8px; background-size: auto auto; background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--MI_THEME-bg) 8px, var(--MI_THEME-bg) 14px );', + }, [h(MkAd, { prefer: ['horizontal', 'horizontal-big'], - }), el]; + })]), el]; } else { return el; } @@ -182,7 +184,7 @@ export default defineComponent({ } &:not(.date-separated-list-nogap) > *:not(:last-child) { - margin-bottom: var(--margin); + margin-bottom: var(--MI-margin); } } @@ -194,7 +196,7 @@ export default defineComponent({ box-shadow: none; &:not(:last-child) { - border-bottom: solid 0.5px var(--divider); + border-bottom: solid 0.5px var(--MI_THEME-divider); } } } @@ -235,7 +237,7 @@ export default defineComponent({ line-height: 32px; text-align: center; font-size: 12px; - color: var(--dateLabelFg); + color: var(--MI_THEME-dateLabelFg); } .date-1 { diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue index 16cf5b1b75..22130d4fab 100644 --- a/packages/frontend/src/components/MkDialog.vue +++ b/packages/frontend/src/components/MkDialog.vue @@ -184,7 +184,7 @@ function onInputKeydown(evt: KeyboardEvent) { max-width: 480px; box-sizing: border-box; text-align: center; - background: var(--panel); + background: var(--MI_THEME-panel); border-radius: 16px; } @@ -206,15 +206,15 @@ function onInputKeydown(evt: KeyboardEvent) { } .type_success { - color: var(--success); + color: var(--MI_THEME-success); } .type_error { - color: var(--error); + color: var(--MI_THEME-error); } .type_warning { - color: var(--warn); + color: var(--MI_THEME-warn); } .title { diff --git a/packages/frontend/src/components/MkDivider.vue b/packages/frontend/src/components/MkDivider.vue index e4e3af99e4..f72f091383 100644 --- a/packages/frontend/src/components/MkDivider.vue +++ b/packages/frontend/src/components/MkDivider.vue @@ -27,6 +27,6 @@ defineProps<{ diff --git a/packages/frontend/src/components/MkDonation.vue b/packages/frontend/src/components/MkDonation.vue index 098be07a8c..0e0da64750 100644 --- a/packages/frontend/src/components/MkDonation.vue +++ b/packages/frontend/src/components/MkDonation.vue @@ -65,12 +65,12 @@ function neverShow() { .root { position: fixed; z-index: v-bind(zIndex); - bottom: var(--margin); + bottom: var(--MI-margin); left: 0; right: 0; margin: auto; box-sizing: border-box; - width: calc(100% - (var(--margin) * 2)); + width: calc(100% - (var(--MI-margin) * 2)); max-width: 500px; display: flex; } @@ -79,7 +79,7 @@ function neverShow() { text-align: center; padding-top: 25px; width: 100px; - color: var(--accent); + color: var(--MI_THEME-accent); } @media (max-width: 500px) { .icon { diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue index 90284890a5..e45c3bd9ce 100644 --- a/packages/frontend/src/components/MkDrive.file.vue +++ b/packages/frontend/src/components/MkDrive.file.vue @@ -148,14 +148,14 @@ function onDragend() { } &.isSelected { - background: var(--accent); + background: var(--MI_THEME-accent); &:hover { - background: var(--accentLighten); + background: var(--MI_THEME-accentLighten); } &:active { - background: var(--accentDarken); + background: var(--MI_THEME-accentDarken); } > .label { @@ -244,7 +244,7 @@ function onDragend() { font-size: 0.8em; text-align: center; word-break: break-all; - color: var(--fg); + color: var(--MI_THEME-fg); overflow: hidden; } diff --git a/packages/frontend/src/components/MkDrive.folder.vue b/packages/frontend/src/components/MkDrive.folder.vue index 92b3a23662..44e3b59ade 100644 --- a/packages/frontend/src/components/MkDrive.folder.vue +++ b/packages/frontend/src/components/MkDrive.folder.vue @@ -36,13 +36,13 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -144,15 +144,15 @@ onMounted(() => { width: 100%; box-sizing: border-box; padding: 9px 12px 9px 12px; - background: var(--folderHeaderBg); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + background: var(--MI_THEME-folderHeaderBg); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); border-radius: 6px; transition: border-radius 0.3s; &:hover { text-decoration: none; - background: var(--folderHeaderHoverBg); + background: var(--MI_THEME-folderHeaderHoverBg); } &:focus-within { @@ -160,8 +160,8 @@ onMounted(() => { } &.active { - color: var(--accent); - background: var(--folderHeaderHoverBg); + color: var(--MI_THEME-accent); + background: var(--MI_THEME-folderHeaderHoverBg); } &.opened { @@ -175,7 +175,7 @@ onMounted(() => { } .headerLower { - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); font-size: .85em; padding-left: 4px; } @@ -209,13 +209,13 @@ onMounted(() => { } .headerTextSub { - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); font-size: .85em; } .headerRight { margin-left: auto; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); white-space: nowrap; } @@ -224,26 +224,26 @@ onMounted(() => { } .body { - background: var(--panel); + background: var(--MI_THEME-panel); border-radius: 0 0 6px 6px; container-type: inline-size; &.bgSame { - background: var(--bg); + background: var(--MI_THEME-bg); } } .footer { position: sticky !important; z-index: 1; - bottom: var(--stickyBottom, 0px); + bottom: var(--MI-stickyBottom, 0px); left: 0; padding: 12px; - background: var(--acrylicBg); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + background: var(--MI_THEME-acrylicBg); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); background-size: auto auto; - background-image: repeating-linear-gradient(135deg, transparent, transparent 5px, var(--panel) 5px, var(--panel) 10px); + background-image: repeating-linear-gradient(135deg, transparent, transparent 5px, var(--MI_THEME-panel) 5px, var(--MI_THEME-panel) 10px); border-radius: 0 0 6px 6px; } diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index 0de52906ed..ccea7cd453 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -165,8 +165,8 @@ onBeforeUnmount(() => { position: relative; display: inline-block; font-weight: bold; - color: var(--fgOnWhite); - border: solid 1px var(--accent); + color: var(--MI_THEME-fgOnWhite); + border: solid 1px var(--MI_THEME-accent); padding: 0; height: 31px; font-size: 16px; @@ -201,17 +201,17 @@ onBeforeUnmount(() => { } &.active { - color: var(--fgOnAccent); - background: var(--accent); + color: var(--MI_THEME-fgOnAccent); + background: var(--MI_THEME-accent); &:hover { - background: var(--accentLighten); - border-color: var(--accentLighten); + background: var(--MI_THEME-accentLighten); + border-color: var(--MI_THEME-accentLighten); } &:active { - background: var(--accentDarken); - border-color: var(--accentDarken); + background: var(--MI_THEME-accentDarken); + border-color: var(--MI_THEME-accentDarken); } } diff --git a/packages/frontend/src/components/MkFormDialog.file.vue b/packages/frontend/src/components/MkFormDialog.file.vue index 9360594236..ecb6cf882b 100644 --- a/packages/frontend/src/components/MkFormDialog.file.vue +++ b/packages/frontend/src/components/MkFormDialog.file.vue @@ -66,6 +66,6 @@ function selectButton(ev: MouseEvent) { diff --git a/packages/frontend/src/components/MkFormFooter.vue b/packages/frontend/src/components/MkFormFooter.vue index 1e88d59d8e..f409f6ce50 100644 --- a/packages/frontend/src/components/MkFormFooter.vue +++ b/packages/frontend/src/components/MkFormFooter.vue @@ -36,7 +36,7 @@ const props = defineProps<{ } .text { - color: var(--warn); + color: var(--MI_THEME-warn); font-size: 90%; animation: modified-blink 2s infinite; } diff --git a/packages/frontend/src/components/MkFukidashi.vue b/packages/frontend/src/components/MkFukidashi.vue index 09825487bf..8b1c56fca4 100644 --- a/packages/frontend/src/components/MkFukidashi.vue +++ b/packages/frontend/src/components/MkFukidashi.vue @@ -39,8 +39,8 @@ withDefaults(defineProps<{ diff --git a/packages/frontend/src/components/MkMediaRange.vue b/packages/frontend/src/components/MkMediaRange.vue index 86ed8ba2cf..df7505b0c3 100644 --- a/packages/frontend/src/components/MkMediaRange.vue +++ b/packages/frontend/src/components/MkMediaRange.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -304,8 +320,8 @@ async function onSubmit(): Promise { padding: 16px; text-align: center; font-size: 26px; - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); } .captcha { diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue index 59a3651cd4..e2a06dd91f 100644 --- a/packages/frontend/src/components/MkSignupDialog.rules.vue +++ b/packages/frontend/src/components/MkSignupDialog.rules.vue @@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
    1. @@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
      @@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + {{ i18n.ts.basicNotesBeforeCreateAccount }} @@ -150,8 +150,8 @@ async function updateAgreeNote(v: boolean) { padding: 16px; text-align: center; font-size: 26px; - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); } .rules { @@ -170,14 +170,14 @@ async function updateAgreeNote(v: boolean) { flex-shrink: 0; display: flex; position: sticky; - top: calc(var(--stickyTop, 0px) + 8px); + top: calc(var(--MI-stickyTop, 0px) + 8px); counter-increment: item; content: counter(item); width: 32px; height: 32px; line-height: 32px; - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); font-size: 13px; font-weight: bold; align-items: center; diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue index 4cccd99492..f240e6dc46 100644 --- a/packages/frontend/src/components/MkSignupDialog.vue +++ b/packages/frontend/src/components/MkSignupDialog.vue @@ -47,7 +47,7 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (ev: 'done', res: Misskey.entities.SigninFlowResponse): void; + (ev: 'done', res: Misskey.entities.SignupResponse): void; (ev: 'closed'): void; }>(); @@ -55,7 +55,7 @@ const dialog = shallowRef>(); const isAcceptedServerRule = ref(false); -function onSignup(res: Misskey.entities.SigninFlowResponse) { +function onSignup(res: Misskey.entities.SignupResponse) { emit('done', res); dialog.value?.close(); } diff --git a/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue b/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue index 1845b01b69..4c197ed43e 100644 --- a/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue +++ b/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue @@ -63,12 +63,12 @@ function close() { .root { position: fixed; z-index: v-bind(zIndex); - bottom: var(--margin); + bottom: var(--MI-margin); left: 0; right: 0; margin: auto; box-sizing: border-box; - width: calc(100% - (var(--margin) * 2)); + width: calc(100% - (var(--MI-margin) * 2)); max-width: 500px; display: flex; } @@ -77,7 +77,7 @@ function close() { text-align: center; padding-top: 25px; width: 100px; - color: var(--accent); + color: var(--MI_THEME-accent); } @media (max-width: 500px) { .icon { diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index 3bbb163f0f..9e02884b8c 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -62,11 +62,11 @@ const collapsed = ref(isLong); left: 0; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0)); > .fadeLabel { display: inline-block; - background: var(--panel); + background: var(--MI_THEME-panel); padding: 6px 10px; font-size: 0.8em; border-radius: 999px; @@ -75,7 +75,7 @@ const collapsed = ref(isLong); &:hover { > .fadeLabel { - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); } } } @@ -84,25 +84,25 @@ const collapsed = ref(isLong); .reply { margin-right: 6px; - color: var(--accent); + color: var(--MI_THEME-accent); } .rp { margin-left: 4px; font-style: oblique; - color: var(--renote); + color: var(--MI_THEME-renote); } .showLess { width: 100%; margin-top: 14px; position: sticky; - bottom: calc(var(--stickyBottom, 0px) + 14px); + bottom: calc(var(--MI-stickyBottom, 0px) + 14px); } .showLessLabel { display: inline-block; - background: var(--popup); + background: var(--MI_THEME-popup); padding: 6px 10px; font-size: 0.8em; border-radius: 999px; diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index 3746ffd8f3..6e7a875dec 100644 --- a/packages/frontend/src/components/MkSuperMenu.vue +++ b/packages/frontend/src/components/MkSuperMenu.vue @@ -43,7 +43,7 @@ defineProps<{ & + .group { margin-top: 16px; padding-top: 16px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } > .title { @@ -64,7 +64,7 @@ defineProps<{ &:hover { text-decoration: none; - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); } &:focus-visible { @@ -72,12 +72,12 @@ defineProps<{ } &.active { - color: var(--accent); - background: var(--accentedBg); + color: var(--MI_THEME-accent); + background: var(--MI_THEME-accentedBg); } &.danger { - color: var(--error); + color: var(--MI_THEME-error); } > .icon { @@ -128,10 +128,10 @@ defineProps<{ &:hover { text-decoration: none; background: none; - color: var(--accent); + color: var(--MI_THEME-accent); > .icon { - background: var(--accentedBg); + background: var(--MI_THEME-accentedBg); } } @@ -144,7 +144,7 @@ defineProps<{ width: 60px; height: 60px; aspect-ratio: 1; - background: var(--panel); + background: var(--MI_THEME-panel); border-radius: 100%; } diff --git a/packages/frontend/src/components/MkSwitch.button.vue b/packages/frontend/src/components/MkSwitch.button.vue index 226908e221..fd8ed0992e 100644 --- a/packages/frontend/src/components/MkSwitch.button.vue +++ b/packages/frontend/src/components/MkSwitch.button.vue @@ -51,9 +51,9 @@ const toggle = () => { width: calc(var(--height) * 1.6); height: calc(var(--height) + 2px); // 枠線 outline: none; - background: var(--switchOffBg); + background: var(--MI_THEME-switchOffBg); background-clip: content-box; - border: solid 1px var(--switchOffBg); + border: solid 1px var(--MI_THEME-switchOffBg); border-radius: 999px; cursor: pointer; transition: inherit; @@ -61,8 +61,8 @@ const toggle = () => { } .buttonChecked { - background-color: var(--switchOnBg) !important; - border-color: var(--switchOnBg) !important; + background-color: var(--MI_THEME-switchOnBg) !important; + border-color: var(--MI_THEME-switchOnBg) !important; } .buttonDisabled { @@ -80,12 +80,12 @@ const toggle = () => { &:not(.knobChecked) { left: 3px; - background: var(--switchOffFg); + background: var(--MI_THEME-switchOffFg); } } .knobChecked { left: calc(calc(100% - var(--height)) + 3px); - background: var(--switchOnFg); + background: var(--MI_THEME-switchOnFg); } diff --git a/packages/frontend/src/components/MkSwitch.vue b/packages/frontend/src/components/MkSwitch.vue index a0994d9cc9..5e6029ee40 100644 --- a/packages/frontend/src/components/MkSwitch.vue +++ b/packages/frontend/src/components/MkSwitch.vue @@ -59,7 +59,7 @@ const toggle = () => { &:hover { > .button { - border-color: var(--inputBorderHover) !important; + border-color: var(--MI_THEME-inputBorderHover) !important; } } @@ -77,7 +77,7 @@ const toggle = () => { margin: 0; &:focus-visible ~ .toggle { - outline: 2px solid var(--focus); + outline: 2px solid var(--MI_THEME-focus); outline-offset: 2px; } } @@ -87,7 +87,7 @@ const toggle = () => { margin-top: 2px; display: block; transition: inherit; - color: var(--fg); + color: var(--MI_THEME-fg); } .label { @@ -99,7 +99,7 @@ const toggle = () => { .caption { margin: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); font-size: 0.85em; &:empty { diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.vue b/packages/frontend/src/components/MkSystemWebhookEditor.vue index ec3b1c90ca..a00cf0d9d3 100644 --- a/packages/frontend/src/components/MkSystemWebhookEditor.vue +++ b/packages/frontend/src/components/MkSystemWebhookEditor.vue @@ -261,10 +261,10 @@ onMounted(async () => { bottom: 0; left: 0; padding: 12px; - border-top: solid 0.5px var(--divider); - background: var(--acrylicBg); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + border-top: solid 0.5px var(--MI_THEME-divider); + background: var(--MI_THEME-acrylicBg); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } .switchBox { @@ -289,6 +289,6 @@ onMounted(async () => { .description { font-size: 0.85em; padding: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); } diff --git a/packages/frontend/src/components/MkTab.vue b/packages/frontend/src/components/MkTab.vue index f2d0c95013..f557ffa5dc 100644 --- a/packages/frontend/src/components/MkTab.vue +++ b/packages/frontend/src/components/MkTab.vue @@ -47,13 +47,13 @@ export default defineComponent({ } &.active { - color: var(--accent); - background: var(--accentedBg); + color: var(--MI_THEME-accent); + background: var(--MI_THEME-accentedBg); } &:not(.active):hover { - color: var(--fgHighlighted); - background: var(--panelHighlight); + color: var(--MI_THEME-fgHighlighted); + background: var(--MI_THEME-panelHighlight); } &:not(:first-child) { diff --git a/packages/frontend/src/components/MkTagCloud.vue b/packages/frontend/src/components/MkTagCloud.vue index 6b9c181597..87aa046963 100644 --- a/packages/frontend/src/components/MkTagCloud.vue +++ b/packages/frontend/src/components/MkTagCloud.vue @@ -33,7 +33,7 @@ watch(available, () => { try { window.TagCanvas.Start(idForCanvas, idForTags, { textColour: '#ffffff', - outlineColour: tinycolor(computedStyle.getPropertyValue('--accent')).toHexString(), + outlineColour: tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(), outlineRadius: 10, initial: [-0.030, -0.010], frontSelect: true, diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue index 59490c552a..d1a6e1ebbf 100644 --- a/packages/frontend/src/components/MkTextarea.vue +++ b/packages/frontend/src/components/MkTextarea.vue @@ -159,7 +159,7 @@ onUnmounted(() => { .caption { font-size: 0.85em; padding: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); &:empty { display: none; @@ -179,9 +179,9 @@ onUnmounted(() => { font: inherit; font-weight: normal; font-size: 1em; - color: var(--fg); - background: var(--panel); - border: solid 1px var(--panel); + color: var(--MI_THEME-fg); + background: var(--MI_THEME-panel); + border: solid 1px var(--MI_THEME-panel); border-radius: 6px; outline: none; box-shadow: none; @@ -189,13 +189,13 @@ onUnmounted(() => { transition: border-color 0.1s ease-out; &:hover { - border-color: var(--inputBorderHover) !important; + border-color: var(--MI_THEME-inputBorderHover) !important; } } .focused { > .textarea { - border-color: var(--accent) !important; + border-color: var(--MI_THEME-accent) !important; } } @@ -226,7 +226,7 @@ onUnmounted(() => { .mfmPreview { padding: 12px; - border-radius: var(--radius); + border-radius: var(--MI-radius); box-sizing: border-box; min-height: 130px; pointer-events: none; diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue index b32066c950..a7bc3f37f1 100644 --- a/packages/frontend/src/components/MkTokenGenerateWindow.vue +++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue @@ -136,15 +136,15 @@ function enableAll(): void { .adminPermissions { margin: 8px -6px 0; padding: 24px 6px 6px; - border: 2px solid var(--error); - border-radius: calc(var(--radius) / 2); + border: 2px solid var(--MI_THEME-error); + border-radius: calc(var(--MI-radius) / 2); } .adminPermissionsHeader { margin: -34px 0 6px 12px; padding: 0 4px; width: fit-content; - color: var(--error); - background: var(--panel); + color: var(--MI_THEME-error); + background: var(--MI_THEME-panel); } diff --git a/packages/frontend/src/components/MkTooltip.vue b/packages/frontend/src/components/MkTooltip.vue index a3620aab68..10365d29b1 100644 --- a/packages/frontend/src/components/MkTooltip.vue +++ b/packages/frontend/src/components/MkTooltip.vue @@ -110,7 +110,7 @@ onUnmounted(() => { box-sizing: border-box; text-align: center; border-radius: 4px; - border: solid 0.5px var(--divider); + border: solid 0.5px var(--MI_THEME-divider); pointer-events: none; transform-origin: center center; } diff --git a/packages/frontend/src/components/MkTutorialDialog.Note.vue b/packages/frontend/src/components/MkTutorialDialog.Note.vue index 0a04393452..f39b2a48df 100644 --- a/packages/frontend/src/components/MkTutorialDialog.Note.vue +++ b/packages/frontend/src/components/MkTutorialDialog.Note.vue @@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
      {{ i18n.ts._initialTutorial._reaction.description }}
      {{ i18n.ts._initialTutorial._reaction.letsTryReacting }}
      -
      {{ i18n.ts._initialTutorial.wellDone }} {{ i18n.ts._initialTutorial._reaction.reactNotification }}
      {{ i18n.ts._initialTutorial._reaction.reactDone }}
      +
      {{ i18n.ts._initialTutorial.wellDone }} {{ i18n.ts._initialTutorial._reaction.reactNotification }}
      {{ i18n.ts._initialTutorial._reaction.reactDone }}
      @@ -106,13 +106,13 @@ function removeReaction(emoji) { diff --git a/packages/frontend/src/components/MkTutorialDialog.PostNote.vue b/packages/frontend/src/components/MkTutorialDialog.PostNote.vue index 27483cc7c2..6559307a94 100644 --- a/packages/frontend/src/components/MkTutorialDialog.PostNote.vue +++ b/packages/frontend/src/components/MkTutorialDialog.PostNote.vue @@ -81,14 +81,14 @@ const exampleCWNote = reactive({ diff --git a/packages/frontend/src/components/MkUserCardMini.vue b/packages/frontend/src/components/MkUserCardMini.vue index d3e77b2818..7a2e878931 100644 --- a/packages/frontend/src/components/MkUserCardMini.vue +++ b/packages/frontend/src/components/MkUserCardMini.vue @@ -23,7 +23,7 @@ import { acct } from '@/filters/user.js'; const props = withDefaults(defineProps<{ user: Misskey.entities.User; - withChart: boolean; + withChart?: boolean; }>(), { withChart: true, }); @@ -49,7 +49,7 @@ $bodyInfoHieght: 16px; display: flex; align-items: center; padding: 16px; - background: var(--panel); + background: var(--MI_THEME-panel); border-radius: 8px; } @@ -64,7 +64,7 @@ $bodyInfoHieght: 16px; flex: 1; overflow: hidden; font-size: 0.9em; - color: var(--fg); + color: var(--MI_THEME-fg); padding-right: 8px; } diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue index f0b9606590..0164515a8a 100644 --- a/packages/frontend/src/components/MkUserInfo.vue +++ b/packages/frontend/src/components/MkUserInfo.vue @@ -69,7 +69,7 @@ defineProps<{ z-index: 2; width: 58px; height: 58px; - border: solid 4px var(--panel); + border: solid 4px var(--MI_THEME-panel); } .title { @@ -90,7 +90,7 @@ defineProps<{ margin: 0; line-height: 16px; font-size: 0.8em; - color: var(--fg); + color: var(--MI_THEME-fg); opacity: 0.7; } @@ -108,7 +108,7 @@ defineProps<{ .description { padding: 16px; font-size: 0.8em; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } .mfm { @@ -120,7 +120,7 @@ defineProps<{ .status { padding: 10px 16px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } .statusItem { @@ -131,12 +131,12 @@ defineProps<{ .statusItemLabel { margin: 0; font-size: 0.7em; - color: var(--fg); + color: var(--MI_THEME-fg); } .statusItemValue { font-size: 1em; - color: var(--accent); + color: var(--MI_THEME-accent); } .follow { diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue index 17a9254d01..8b4afd7994 100644 --- a/packages/frontend/src/components/MkUserList.vue +++ b/packages/frontend/src/components/MkUserList.vue @@ -39,6 +39,6 @@ const props = withDefaults(defineProps<{ .root { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - grid-gap: var(--margin); + grid-gap: var(--MI-margin); } diff --git a/packages/frontend/src/components/MkUserOnlineIndicator.vue b/packages/frontend/src/components/MkUserOnlineIndicator.vue index c39a900bcf..5cebeea2f4 100644 --- a/packages/frontend/src/components/MkUserOnlineIndicator.vue +++ b/packages/frontend/src/components/MkUserOnlineIndicator.vue @@ -36,7 +36,7 @@ const text = computed(() => { diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.vue b/packages/frontend/src/components/MkUserSetupDialog.User.vue index bb9af676e2..004edab630 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.User.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.User.vue @@ -61,7 +61,7 @@ async function follow() { z-index: 2; width: 58px; height: 58px; - border: solid 4px var(--panel); + border: solid 4px var(--MI_THEME-panel); } .title { @@ -82,7 +82,7 @@ async function follow() { margin: 0; line-height: 16px; font-size: 0.8em; - color: var(--fg); + color: var(--MI_THEME-fg); opacity: 0.7; } @@ -99,7 +99,7 @@ async function follow() { } .footer { - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); padding: 16px; } diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue index 1fb1eda039..b7261129ef 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.vue @@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
      - +
      {{ i18n.ts._initialAccountSetting.accountCreated }}
      {{ i18n.ts._initialAccountSetting.letsStartAccountSetup }}
      {{ i18n.ts._initialAccountSetting.profileSetting }} @@ -91,7 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only
      - +
      {{ i18n.ts.pushNotification }}
      {{ i18n.tsx._initialAccountSetting.pushNotificationDescription({ name: instance.name ?? host }) }}
      @@ -108,7 +108,7 @@ SPDX-License-Identifier: AGPL-3.0-only
      - +
      {{ i18n.ts._initialAccountSetting.initialAccountSettingCompleted }}
      {{ i18n.tsx._initialAccountSetting.youCanContinueTutorial({ name: instance.name ?? host }) }}
      @@ -223,7 +223,7 @@ async function later(later: boolean) { .progressBarValue { height: 100%; - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); transition: all 0.5s cubic-bezier(0,.5,.5,1); } @@ -252,7 +252,7 @@ async function later(later: boolean) { left: 0; flex-shrink: 0; padding: 12px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); -webkit-backdrop-filter: blur(15px); backdrop-filter: blur(15px); } diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue index 75066bbc32..650e639c4f 100644 --- a/packages/frontend/src/components/MkVisibilityPicker.vue +++ b/packages/frontend/src/components/MkVisibilityPicker.vue @@ -124,7 +124,7 @@ function choose(visibility: typeof Misskey.noteVisibilities[number]): void { } &.active { - color: var(--accent); + color: var(--MI_THEME-accent); } } diff --git a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue index cab42cd59d..d098dad9a1 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue @@ -62,7 +62,7 @@ async function renderChart() { const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; const computedStyle = getComputedStyle(document.documentElement); - const accent = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString(); + const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(); const colorRead = accent; const colorWrite = '#2ecc71'; diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue index a6c8baeaaa..97c765d81c 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.vue @@ -106,8 +106,8 @@ function showMenu(ev: MouseEvent) { .panel { position: relative; - background: var(--panel); - border-radius: var(--radius); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius); box-shadow: 0 12px 32px rgb(0 0 0 / 25%); } @@ -178,14 +178,14 @@ function showMenu(ev: MouseEvent) { } .statsItemLabel { - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); font-size: 0.9em; } .statsItemCount { font-weight: bold; font-size: 1.2em; - color: var(--accent); + color: var(--MI_THEME-accent); } .tl { @@ -194,7 +194,7 @@ function showMenu(ev: MouseEvent) { .tlHeader { padding: 12px 16px; - border-bottom: solid 1px var(--divider); + border-bottom: solid 1px var(--MI_THEME-divider); } .tlBody { diff --git a/packages/frontend/src/components/MkWaitingDialog.vue b/packages/frontend/src/components/MkWaitingDialog.vue index 60b75b6d30..34fa6b0723 100644 --- a/packages/frontend/src/components/MkWaitingDialog.vue +++ b/packages/frontend/src/components/MkWaitingDialog.vue @@ -47,8 +47,8 @@ watch(() => props.showing, () => { padding: 32px; box-sizing: border-box; text-align: center; - background: var(--panel); - border-radius: var(--radius); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius); width: 250px; &.iconOnly { @@ -65,7 +65,7 @@ watch(() => props.showing, () => { font-size: 32px; &.success { - color: var(--accent); + color: var(--MI_THEME-accent); } &.waiting { diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue index 0c51cfa9ce..492dd4cdc0 100644 --- a/packages/frontend/src/components/MkWidgets.vue +++ b/packages/frontend/src/components/MkWidgets.vue @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only