diff --git a/.github/ISSUE_TEMPLATE/01_bug-report.md b/.github/ISSUE_TEMPLATE/01_bug-report.md index 46ce16f073b6e30523d353c4b1ac2885274c782d..25e7fc8b201e8ed2d04d9d8688c7380b7a16a790 100644 --- a/.github/ISSUE_TEMPLATE/01_bug-report.md +++ b/.github/ISSUE_TEMPLATE/01_bug-report.md @@ -46,8 +46,10 @@ Please include errors from the developer console and/or server log files if you <!-- Example: Chrome 113.0.5672.126 --> * Server URL: <!-- Example: misskey.io --> +* Misskey: + 13.x.x -### 🛰 Backend (for instance admin) +### 🛰 Backend (for server admin) <!-- If you are using a managed service, put that after the version. --> * Installation Method or Hosting Service: <!-- Example: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment --> diff --git a/CHANGELOG.md b/CHANGELOG.md index d735ec71edbf56356e17fd4b3b821c1a6bb87757..45dc0e3c905c00374666075ec9db53ef7d5b4f66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,19 @@ --> +## 13.13.2 + +### General +- ã‚¨ãƒ©ãƒ¼æ™‚ã‚„é …ç›®ãŒå˜åœ¨ã—ãªã„ã¨ããªã©ã®ã‚¢ã‚¤ã‚³ãƒ³ç”»åƒã‚’サーãƒãƒ¼ç®¡ç†è€…ãŒè¨å®šã§ãるよã†ã« +- ãƒãƒ¼ãƒ«ãŒä»˜ä¸Žã•ã‚Œã¦ã„るユーザーリストをéžå…¬é–‹ã«ã§ãるよã†ã« +- サーãƒãƒ¼ã®è² è·ãŒéžå¸¸ã«é«˜ã„ãŸã‚ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼çµ±è¨ˆè¡¨ç¤ºæ©Ÿèƒ½ã‚’削除ã—ã¾ã—㟠+ +### Client +- Fix: タブãŒãƒãƒƒã‚¯ã‚°ãƒ©ã‚¦ãƒ³ãƒ‰ã§ã‚‚streamãŒåˆ‡æ–ã•ã‚Œãªã„よã†ã« + +### Server +- Fix: ã‚ャッシュãŒæºœã¾ã‚Šç¶šã‘ãªã„よã†ã« + ## 13.13.1 ### Client @@ -96,11 +109,12 @@ Meilisearchã®è¨å®šã«`index`ãŒå¿…è¦ã«ãªã‚Šã¾ã—ãŸã€‚値ã¯Misskeyサー ## 13.12.0 ### NOTE -- Node.js 18.6.0以上ãŒå¿…è¦ã«ãªã‚Šã¾ã—㟠+- Node.js 18.16.0以上ãŒå¿…è¦ã«ãªã‚Šã¾ã—㟠### General - アカウントã®å¼•ã£è¶Šã—(フォãƒãƒ¯ãƒ¼å¼•ã継ãŽï¼‰ã«å¯¾å¿œ - Meilisearchを全文検索ã«ä½¿ç”¨ã§ãるよã†ã«ãªã‚Šã¾ã—㟠+ * 「フォãƒãƒ¯ãƒ¼ã®ã¿ã€ã®æŠ•ç¨¿ã¯æ¤œç´¢çµæžœã«è¡¨ç¤ºã•ã‚Œã¾ã›ã‚“。 - æ–°è¦ç™»éŒ²å‰ã«ç°¡æ½”ãªãƒ«ãƒ¼ãƒ«ã‚’ユーザーã«è¡¨ç¤ºã§ãã‚‹ã€ã‚µãƒ¼ãƒãƒ¼ãƒ«ãƒ¼ãƒ«æ©Ÿèƒ½ã‚’è¿½åŠ - ユーザーã¸ã®è‡ªåˆ†ç”¨ãƒ¡ãƒ¢æ©Ÿèƒ½ * ユーザーã«å¯¾ã—ã¦ã€è‡ªåˆ†ã ã‘ãŒè¦‹ã‚‰ã‚Œã‚‹ãƒ¡ãƒ¢ã‚’è¿½åŠ ã§ãるよã†ã«ãªã‚Šã¾ã—ãŸã€‚ diff --git a/locales/de-DE.yml b/locales/de-DE.yml index c4c12cb1aa8cc4c5ab8c097859426da743ce1213..9b52a5069746e3637f94067955f860111d5aab0e 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -991,7 +991,7 @@ postToTheChannel: "In Kanal senden" cannotBeChangedLater: "Kann später nicht mehr geändert werden." reactionAcceptance: "Reaktionsannahme" likeOnly: "Nur \"Gefällt mir\"" -likeOnlyForRemote: "Nur \"Gefällt mir\" für fremde Instanzen" +likeOnlyForRemote: "Alle (Nur \"Gefällt mir\" für fremde Instanzen)" nonSensitiveOnly: "Keine Sensitiven" nonSensitiveOnlyForLocalLikeOnlyForRemote: "Keine Sensitiven (Nur \"Gefällt mir\" von fremden Instanzen)" rolesAssignedToMe: "Mir zugewiesene Rollen" @@ -1062,6 +1062,7 @@ later: "Später" goToMisskey: "Zu Misskey" additionalEmojiDictionary: "Zusätzliche Emoji-Wörterbücher" installed: "Installiert" +branding: "Branding" _initialAccountSetting: accountCreated: "Dein Konto wurde erfolgreich erstellt!" letsStartAccountSetup: "Lass uns nun dein Konto einrichten." @@ -1093,7 +1094,7 @@ _accountMigration: migrationConfirm: "Dieses Konto wirklich zu {account} umziehen? Sobald der Umzug beginnt, kann er nicht rückgängig gemacht werden, und dieses Konto nicht wieder im ursprünglichen Zustand verwendet werden." movedAndCannotBeUndone: "\nDieses Konto wurde migriert.\nDiese Aktion ist unwiderruflich." postMigrationNote: "Dieses Konto wird 24 Stunden nach Abschluss der Migration allen Konten, denen es derzeit folgt, nicht mehr folgen.\n\nSowohl die Anzahl der Follower als auch die der Konten, denen dieses Konto folgt, wird dann auf Null gesetzt. Um zu vermeiden, dass Follower dieses Kontos dessen Beiträge, welche nur für Follower bestimmt sind, nicht mehr sehen können, werden sie diesem Konto jedoch weiterhin folgen." - movedTo: "Umzugsziel:" + movedTo: "Neues Konto:" _achievements: earnedAt: "Freigeschaltet am" _types: @@ -1347,7 +1348,7 @@ _role: condition: "Bedingung" isConditionalRole: "Dies ist eine konditionale Rolle." isPublic: "Öffentliche Rolle" - descriptionOfIsPublic: "Ist dies aktiviert, so kann jeder die Liste der Benutzer, die dieser Rolle zugewiesen sind, einsehen. Zusätzlich wird diese Rolle im Profil zugewiesener Benutzer angezeigt." + descriptionOfIsPublic: "Diese Rolle wird im Profil zugewiesener Benutzer angezeigt." options: "Optionen" policies: "Richtlinien" baseRole: "Rollenvorlage" @@ -1356,8 +1357,8 @@ _role: iconUrl: "Icon-URL" asBadge: "Als Abzeichen anzeigen" descriptionOfAsBadge: "Ist dies aktiviert, so wird das Icon dieser Rolle an der Seite der Namen von Benutzern mit dieser Rolle angezeigt." - isExplorable: "Rollenchronik veröffentlichen" - descriptionOfIsExplorable: "Ist dies aktiviert, so ist die Rollenchronik dieser Rolle frei zugänglich. Die Chronik von Rollen, welche nicht öffentlich sind, wird auch bei Aktivierung nicht veröffentlicht." + isExplorable: "Benutzerliste veröffentlichen" + descriptionOfIsExplorable: "Ist dies aktiviert, so ist die Chronik dieser Rolle, sowie eine Liste der Benutzer mit dieser Rolle, frei zugänglich." displayOrder: "Position" descriptionOfDisplayOrder: "Je höher die Nummer, desto höher die UI-Position." canEditMembersByModerator: "Moderatoren können Benutzern diese Rolle zuweisen" diff --git a/locales/en-US.yml b/locales/en-US.yml index 0f1c7c89fee6c9bfa3a4ff4d89173d83e4f1602a..8938208574e6b5fc941371e54b78ef9f956acdb7 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -991,7 +991,7 @@ postToTheChannel: "Post to channel" cannotBeChangedLater: "This cannot be changed later." reactionAcceptance: "Reaction Acceptance" likeOnly: "Only likes" -likeOnlyForRemote: "Only likes for remote instances" +likeOnlyForRemote: "All (Only likes for remote instances)" nonSensitiveOnly: "Non-sensitive only" nonSensitiveOnlyForLocalLikeOnlyForRemote: "Non-sensitive only (Only likes from remote)" rolesAssignedToMe: "Roles assigned to me" @@ -1062,6 +1062,7 @@ later: "Later" goToMisskey: "To Misskey" additionalEmojiDictionary: "Additional emoji dictionaries" installed: "Installed" +branding: "Branding" _initialAccountSetting: accountCreated: "Your account was successfully created!" letsStartAccountSetup: "For starters, let's set up your profile." @@ -1093,7 +1094,7 @@ _accountMigration: migrationConfirm: "Really migrate this account to {account}? Once started, this process cannot be stopped or taken back, and you will not be able to use this account in its original state anymore." movedAndCannotBeUndone: "\nThis account has been migrated.\nMigration cannot be reversed." postMigrationNote: "This account will unfollow all accounts it is currently following 24 hours after migration finishes.\nBoth the number of follows and followers will then become zero. To avoid your followers from being unable to see followers only posts of this account, they will however continue following this account." - movedTo: "Account to move to:" + movedTo: "New account:" _achievements: earnedAt: "Unlocked at" _types: @@ -1347,7 +1348,7 @@ _role: condition: "Condition" isConditionalRole: "This is a conditional role." isPublic: "Public role" - descriptionOfIsPublic: "Anyone will be able to view a list of users assigned to this role. In addition, this role will be displayed in the profiles of assigned users." + descriptionOfIsPublic: "This role will be displayed in the profiles of assigned users." options: "Options" policies: "Policies" baseRole: "Role template" @@ -1356,8 +1357,8 @@ _role: iconUrl: "Icon URL" asBadge: "Show as badge" descriptionOfAsBadge: "This role's icon will be displayed next to the username of users with this role if turned on." - isExplorable: "Role timeline is public" - descriptionOfIsExplorable: "This role's timeline will become publicly accessible if enabled. Timelines of non-public roles will not be made public even if set." + isExplorable: "Make role explorable" + descriptionOfIsExplorable: "This role's timeline and the list of users with this will be made public if enabled." displayOrder: "Position" descriptionOfDisplayOrder: "The higher the number, the higher its UI position." canEditMembersByModerator: "Allow moderators to edit the list of members for this role" diff --git a/locales/index.d.ts b/locales/index.d.ts index 7047f42eff4983c2b7ddee224368490920081fa6..eed29f408c0b0d668f8babdd5334f52f1d1491bc 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1065,6 +1065,7 @@ export interface Locale { "goToMisskey": string; "additionalEmojiDictionary": string; "installed": string; + "branding": string; "_initialAccountSetting": { "accountCreated": string; "letsStartAccountSetup": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index fcba3fb8223f5a5a909bd60e26a972b48cbb6f01..8004e53575f67563a1bf84ed96500f5e148090a5 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1062,6 +1062,7 @@ later: "ã‚ã¨ã§" goToMisskey: "Misskeyã¸" additionalEmojiDictionary: "絵文å—ã®è¿½åŠ 辞書" installed: "インストール済ã¿" +branding: "ブランディング" _initialAccountSetting: accountCreated: "アカウントã®ä½œæˆãŒå®Œäº†ã—ã¾ã—ãŸï¼" @@ -1351,8 +1352,8 @@ _role: conditional: "コンディショナル" condition: "æ¡ä»¶" isConditionalRole: "ã“ã‚Œã¯ã‚³ãƒ³ãƒ‡ã‚£ã‚·ãƒ§ãƒŠãƒ«ãƒãƒ¼ãƒ«ã§ã™ã€‚" - isPublic: "ãƒãƒ¼ãƒ«ã‚’公開" - descriptionOfIsPublic: "ãƒãƒ¼ãƒ«ã«ã‚¢ã‚µã‚¤ãƒ³ã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’誰ã§ã‚‚見るã“ã¨ãŒã§ãã¾ã™ã€‚ã¾ãŸã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ—ãƒãƒ•ã‚£ãƒ¼ãƒ«ã§ã“ã®ãƒãƒ¼ãƒ«ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚" + isPublic: "公開ãƒãƒ¼ãƒ«" + descriptionOfIsPublic: "ユーザーã®ãƒ—ãƒãƒ•ã‚£ãƒ¼ãƒ«ã§ã“ã®ãƒãƒ¼ãƒ«ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚" options: "オプション" policies: "ãƒãƒªã‚·ãƒ¼" baseRole: "ベースãƒãƒ¼ãƒ«" @@ -1361,8 +1362,8 @@ _role: iconUrl: "アイコン画åƒã®URL" asBadge: "ãƒãƒƒã‚¸ã¨ã—ã¦è¡¨ç¤º" descriptionOfAsBadge: "オンã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼åã®æ¨ªã«ãƒãƒ¼ãƒ«ã®ã‚¢ã‚¤ã‚³ãƒ³ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚" - isExplorable: "ãƒãƒ¼ãƒ«ã‚¿ã‚¤ãƒ ラインを公開" - descriptionOfIsExplorable: "オンã«ã™ã‚‹ã¨ã€ãƒãƒ¼ãƒ«ã®ã‚¿ã‚¤ãƒ ラインを公開ã—ã¾ã™ã€‚ãƒãƒ¼ãƒ«ã®å…¬é–‹ãŒã‚ªãƒ•ã®å ´åˆã€ã‚¿ã‚¤ãƒ ラインã®å…¬é–‹ã¯ã•ã‚Œã¾ã›ã‚“。" + isExplorable: "ユーザーを見ã¤ã‘ã‚„ã™ãã™ã‚‹" + descriptionOfIsExplorable: "オンã«ã™ã‚‹ã¨ã€ã€Œã¿ã¤ã‘ã‚‹ã€ã§ãƒ¡ãƒ³ãƒãƒ¼ä¸€è¦§ãŒå…¬é–‹ã•ã‚Œã‚‹ã»ã‹ã€ãƒãƒ¼ãƒ«ã®ã‚¿ã‚¤ãƒ ラインãŒåˆ©ç”¨å¯èƒ½ã«ãªã‚Šã¾ã™ã€‚" displayOrder: "è¡¨ç¤ºé †" descriptionOfDisplayOrder: "数値ãŒå¤§ãã„ã»ã©UI上ã§å…ˆé ã«è¡¨ç¤ºã•ã‚Œã¾ã™ã€‚" canEditMembersByModerator: "モデレーターã®ãƒ¡ãƒ³ãƒãƒ¼ç·¨é›†ã‚’許å¯" diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml index 7bd8188a48f5be6cd209a619c07481d1fe35fca3..cc402eec484ba8aac15b1d4c306c8252ac568d28 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -1,6 +1,7 @@ --- _lang_: "Türkçe" introMisskey: "Açık kaynaklı bir dağıtılmış mikroblog hizmeti olan Misskey'e hoÅŸ geldiniz.\nMisskey, neler olup bittiÄŸini paylaÅŸmak ve herkese sizden bahsetmek için \"notlar\" oluÅŸturmanıza olanak tanıyan, açık kaynaklı, dağıtılmış bir mikroblog hizmetidir.\nHerkesin notlarına kendi tepkilerinizi hızlıca eklemek için \"Tepkiler\" özelliÄŸini de kullanabilirsinizðŸ‘.\nYeni bir dünyayı keÅŸfedin🚀." +poweredByMisskeyDescription: "name}Açık kaynak bir platform\n<b>Misskey</b>Dünya'nın en sunucularında biri。" monthAndDay: "{month}Ay {day}Gün" search: "Arama" notifications: "Bildirim" @@ -13,7 +14,9 @@ cancel: "Ä°ptal" enterUsername: "Kullanıcı adınızı giriniz" noNotes: "Notlar mevcut deÄŸil." noNotifications: "Bildirim bulunmuyor" +instance: "Sunucu" settings: "Ayarlar" +notificationSettings: "Bildirim Ayarları" basicSettings: "Temel Ayarlar" otherSettings: "DiÄŸer Ayarlar" openInWindow: "Bir pencere ile aç" @@ -21,9 +24,11 @@ profile: "Profil" timeline: "Zaman çizelgesi" noAccountDescription: "Bu kullanıcı henüz biyografisini yazmadı" login: "GiriÅŸ Yap " +loggingIn: "Oturum aç" logout: "Çıkış Yap" signup: "Kayıt Ol" uploading: "Yükleniyor" +save: "Kaydet" users: "Kullanıcı" addUser: "Kullanıcı Ekle" favorite: "Favoriler" @@ -31,6 +36,7 @@ favorites: "Favoriler" unfavorite: "Favorilerden Kaldır" favorited: "Favorilerime eklendi." alreadyFavorited: "Zaten favorilerinizde kayıtlı." +cantFavorite: "Favorilere kayıt yapılamadı" pin: "SabitlenmiÅŸ" unpin: "Sabitlemeyi kaldır" copyContent: "İçeriÄŸi kopyala" @@ -40,23 +46,88 @@ deleteAndEdit: "Sil ve yeniden düzenle" deleteAndEditConfirm: "Bu notu silip yeniden düzenlemek istiyor musunuz? Bu nota iliÅŸkin tüm Tepkiler, Yeniden Notlar ve Yanıtlar da silinecektir." addToList: "Listeye ekle" sendMessage: "Mesaj Gönder" +copyRSS: "RSSKopyala" copyUsername: "Kullanıcı Adını Kopyala" +copyUserId: "KullanıcıyıKopyala" +copyNoteId: "Kimlik notunu kopyala" searchUser: "Kullanıcıları ara" +reply: "yanıt" +loadMore: "Devamını yükle" +showMore: "Devamını yükle" +lists: "Listeler" +noLists: "Liste yok" +note: "not" +notes: "notlar" +following: "takipçi" +followers: "takipçi" +followsYou: "seni takip ediyor" +createList: "Liste oluÅŸtur" +manageLists: "Yönetici Listeleri" +error: "hata" +follow: "takipçi" +followRequest: "Takip isteÄŸi" +followRequests: "Takip istekleri" +unfollow: "takip etmeyi bırak" +followRequestPending: "Bekleyen Takip Etme Talebi" +enterEmoji: "Emoji Giriniz" +renote: "vazgeçme" +unrenote: "not alma" +renoted: "yeniden adlandırılmış" +cantRenote: "Ayrılamama" +cantReRenote: "not alabilirmiyim" +quote: "alıntı" +pinnedNote: "Sabitlenen" pinned: "SabitlenmiÅŸ" +you: "sen" +unmute: "sesi aç" +renoteMute: "sesi kapat" +renoteUnmute: "sesi açmayı iptal et" +block: "engelle" +unblock: "engellemeyi kaldır" +suspend: "askıya al" +unsuspend: "askıya alma" +blockConfirm: "Onayı engelle" +unblockConfirm: "engellemeyi kaldır onayla" +selectChannel: "Kanal seç" +flagAsBot: "Bot olarak iÅŸaretle" +instances: "Sunucu" remove: "Sil" +pinnedNotes: "Sabitlenen" +userList: "Listeler" smtpUser: "Kullanıcı Adı" smtpPass: "Åžifre" user: "Kullanıcı" searchByGoogle: "Arama" +_theme: + keys: + renote: "vazgeçme" _sfx: + note: "notlar" notification: "Bildirim" _widgets: profile: "Profil" notifications: "Bildirim" timeline: "Zaman çizelgesi" +_cw: + show: "Devamını yükle" +_visibility: + followers: "takipçi" _profile: username: "Kullanıcı Adı" +_exportOrImport: + followingList: "takipçi" + blockingList: "engelle" + userLists: "Listeler" +_notification: + _types: + follow: "takipçi" + renote: "vazgeçme" + quote: "alıntı" + _actions: + reply: "yanıt" + renote: "vazgeçme" _deck: _columns: notifications: "Bildirim" tl: "Zaman çizelgesi" + list: "Listeler" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 9c278ea751af9e0adac24cbf26310123a2cfecdb..313c254c79665ce2076b6281237c21c400dd3851 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1060,6 +1060,7 @@ cancelReactionConfirm: "è¦å–消回应å—?" changeReactionConfirm: "è¦æ›´æ”¹å›žåº”å—?" later: "一会å†è¯´" goToMisskey: "去往Misskey" +additionalEmojiDictionary: "表情符å·è¿½åŠ å—å…¸" installed: "已安装" _initialAccountSetting: accountCreated: "账户创建完æˆäº†ï¼" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index ef0baeef500e80f7c3fc363aea60e8683a8396b3..8017018507ce76aee90619faef0672c2b6a106cd 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1062,6 +1062,7 @@ later: "ç¨å¾Œå†èªª" goToMisskey: "å¾€Misskey" additionalEmojiDictionary: "è¡¨æƒ…ç¬¦è™Ÿçš„é™„åŠ è¾å…¸" installed: "已安è£" +branding: "å“牌宣傳" _initialAccountSetting: accountCreated: "帳戶已建立完æˆï¼" letsStartAccountSetup: "來進行帳戶的åˆå§‹è¨å®šå§ã€‚" diff --git a/package.json b/package.json index eff0bf2dc2b420d267f8f1c5f58408263de2c171..dd0c1d57e7a786cece19d7c1e404464193991bd2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "13.13.1", + "version": "13.13.2", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/backend/migration/1685973839966-errorImageUrl.js b/packages/backend/migration/1685973839966-errorImageUrl.js new file mode 100644 index 0000000000000000000000000000000000000000..fd5d467162a7be2308098f9ee54046cf21ac328f --- /dev/null +++ b/packages/backend/migration/1685973839966-errorImageUrl.js @@ -0,0 +1,17 @@ +export class ErrorImageUrl1685973839966 { + name = 'ErrorImageUrl1685973839966' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "errorImageUrl"`); + await queryRunner.query(`ALTER TABLE "meta" ADD "serverErrorImageUrl" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "notFoundImageUrl" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "infoImageUrl" character varying(1024)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "infoImageUrl"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "notFoundImageUrl"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "serverErrorImageUrl"`); + await queryRunner.query(`ALTER TABLE "meta" ADD "errorImageUrl" character varying(1024) DEFAULT 'https://xn--931a.moe/aiart/yubitun.png'`); + } +} diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts index de33e4c243de2357cc239393cc10762cc95390fa..2b7f9a48dabdfc3c5c534fee36454f350419cb73 100644 --- a/packages/backend/src/core/CacheService.ts +++ b/packages/backend/src/core/CacheService.ts @@ -168,6 +168,17 @@ export class CacheService implements OnApplicationShutdown { @bindThis public dispose(): void { this.redisForSub.off('message', this.onMessage); + this.userByIdCache.dispose(); + this.localUserByNativeTokenCache.dispose(); + this.localUserByIdCache.dispose(); + this.uriPersonCache.dispose(); + this.userProfileCache.dispose(); + this.userMutingsCache.dispose(); + this.userBlockingCache.dispose(); + this.userBlockedCache.dispose(); + this.renoteMutingsCache.dispose(); + this.userFollowingsCache.dispose(); + this.userFollowingChannelsCache.dispose(); } @bindThis diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 3499df38b763521cce5c7085888ed98d4ea9e7a1..5f2ced77eb61a0c94d9b6b2947a4b5afb8cb7191 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { DataSource, In, IsNull } from 'typeorm'; import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; @@ -18,7 +18,7 @@ import type { Serialized } from '@/server/api/stream/types.js'; const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/; @Injectable() -export class CustomEmojiService { +export class CustomEmojiService implements OnApplicationShutdown { private cache: MemoryKVCache<Emoji | null>; public localEmojisCache: RedisSingleCache<Map<string, Emoji>>; @@ -349,4 +349,14 @@ export class CustomEmojiService { this.cache.set(`${emoji.name} ${emoji.host}`, emoji); } } + + @bindThis + public dispose(): void { + this.cache.dispose(); + } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index 8b9a87a380ac6a3363e26d256d3386283ea4c12d..3603d59dcc2e263c1eefa6b19249e4af143807d2 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import * as Redis from 'ioredis'; import type { InstancesRepository } from '@/models/index.js'; import type { Instance } from '@/models/entities/Instance.js'; @@ -9,7 +9,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; @Injectable() -export class FederatedInstanceService { +export class FederatedInstanceService implements OnApplicationShutdown { public federatedInstanceCache: RedisKVCache<Instance | null>; constructor( @@ -77,4 +77,14 @@ export class FederatedInstanceService { this.federatedInstanceCache.set(result.host, result); } + + @bindThis + public dispose(): void { + this.federatedInstanceCache.dispose(); + } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index a4c569bdec60d95873c4fcc7de34455f5e3ed5a9..15a1d74878d4686a3644dbcea7de07dbf8abb2c7 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import push from 'web-push'; import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; @@ -42,7 +42,7 @@ function truncateBody<T extends keyof PushNotificationsTypes>(type: T, body: Pus } @Injectable() -export class PushNotificationService { +export class PushNotificationService implements OnApplicationShutdown { private subscriptionsCache: RedisKVCache<SwSubscription[]>; constructor( @@ -115,4 +115,14 @@ export class PushNotificationService { }); } } + + @bindThis + public dispose(): void { + this.subscriptionsCache.dispose(); + } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 40ae106662710270e54dc23ea8360368deac3270..79922d0a87eadded3ec974779f820a359c160eba 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -435,6 +435,7 @@ export class RoleService implements OnApplicationShutdown { @bindThis public dispose(): void { this.redisForSub.off('message', this.onMessage); + this.roleAssignmentByUserIdCache.dispose(); } @bindThis diff --git a/packages/backend/src/core/UserKeypairService.ts b/packages/backend/src/core/UserKeypairService.ts index 72c35c529c2752cff574090eea9ce54ea9972d3a..d768f086503dacf11183a3569f0d8d26ae38d9c2 100644 --- a/packages/backend/src/core/UserKeypairService.ts +++ b/packages/backend/src/core/UserKeypairService.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import * as Redis from 'ioredis'; import type { User } from '@/models/entities/User.js'; import type { UserKeypairsRepository } from '@/models/index.js'; @@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; @Injectable() -export class UserKeypairService { +export class UserKeypairService implements OnApplicationShutdown { private cache: RedisKVCache<UserKeypair>; constructor( @@ -31,4 +31,14 @@ export class UserKeypairService { public async getUserKeypair(userId: User['id']): Promise<UserKeypair> { return await this.cache.fetch(userId); } + + @bindThis + public dispose(): void { + this.cache.dispose(); + } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index 2b404ebecaeb64d7ff3a8323a4200436f77b8739..2d9e7a14ee28f751f4231babf3c848edeffd7cd4 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import escapeRegexp from 'escape-regexp'; import { DI } from '@/di-symbols.js'; import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; @@ -30,7 +30,7 @@ export type UriParseResult = { }; @Injectable() -export class ApDbResolverService { +export class ApDbResolverService implements OnApplicationShutdown { private publicKeyCache: MemoryKVCache<UserPublickey | null>; private publicKeyByUserIdCache: MemoryKVCache<UserPublickey | null>; @@ -162,4 +162,15 @@ export class ApDbResolverService { key, }; } + + @bindThis + public dispose(): void { + this.publicKeyCache.dispose(); + this.publicKeyByUserIdCache.dispose(); + } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index 5610929648b147341cb67d3529abb3c2667e8929..f130a7db8b88f05fa8721b51663a98fdd5cc87c4 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -83,6 +83,16 @@ export class RedisKVCache<T> { // TODO: イベント発行ã—ã¦ä»–プãƒã‚»ã‚¹ã®ãƒ¡ãƒ¢ãƒªã‚ャッシュも更新ã§ãるよã†ã«ã™ã‚‹ } + + @bindThis + public gc() { + this.memoryCache.gc(); + } + + @bindThis + public dispose() { + this.memoryCache.dispose(); + } } export class RedisSingleCache<T> { @@ -174,10 +184,15 @@ export class RedisSingleCache<T> { export class MemoryKVCache<T> { public cache: Map<string, { date: number; value: T; }>; private lifetime: number; + private gcIntervalHandle: NodeJS.Timer; constructor(lifetime: MemoryKVCache<never>['lifetime']) { this.cache = new Map(); this.lifetime = lifetime; + + this.gcIntervalHandle = setInterval(() => { + this.gc(); + }, 1000 * 60 * 3); } @bindThis @@ -200,7 +215,7 @@ export class MemoryKVCache<T> { } @bindThis - public delete(key: string) { + public delete(key: string): void { this.cache.delete(key); } @@ -255,6 +270,21 @@ export class MemoryKVCache<T> { } return value; } + + @bindThis + public gc(): void { + const now = Date.now(); + for (const [key, { date }] of this.cache.entries()) { + if ((now - date) > this.lifetime) { + this.cache.delete(key); + } + } + } + + @bindThis + public dispose(): void { + clearInterval(this.gcIntervalHandle); + } } export class MemorySingleCache<T> { diff --git a/packages/backend/src/models/entities/Meta.ts b/packages/backend/src/models/entities/Meta.ts index 6d44e4edc7664c2964cb7f3119301a48e2d12df7..f799551f30837cfb3ed8c13c66a483e8efbff5df 100644 --- a/packages/backend/src/models/entities/Meta.ts +++ b/packages/backend/src/models/entities/Meta.ts @@ -101,13 +101,25 @@ export class Meta { length: 1024, nullable: true, }) - public errorImageUrl: string | null; + public iconUrl: string | null; @Column('varchar', { length: 1024, nullable: true, }) - public iconUrl: string | null; + public serverErrorImageUrl: string | null; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public notFoundImageUrl: string | null; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public infoImageUrl: string | null; @Column('boolean', { default: true, diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts index e23591d8765dfb0e9dd8c3720b5f97a3f2cfd4ec..4ad0197d87d895b72409b3bea4c55fda62e2115c 100644 --- a/packages/backend/src/server/api/AuthenticateService.ts +++ b/packages/backend/src/server/api/AuthenticateService.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; import type { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/index.js'; import type { LocalUser } from '@/models/entities/User.js'; @@ -17,7 +17,7 @@ export class AuthenticationError extends Error { } @Injectable() -export class AuthenticateService { +export class AuthenticateService implements OnApplicationShutdown { private appCache: MemoryKVCache<App>; constructor( @@ -85,4 +85,14 @@ export class AuthenticateService { } } } + + @bindThis + public dispose(): void { + this.appCache.dispose(); + } + + @bindThis + public onApplicationShutdown(signal?: string | undefined): void { + this.dispose(); + } } diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 1e32e9988dfb269316556234547412ee33613c94..d1ff3fe9255b7d4fa7e3c2c20b418c670e9677a6 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -333,7 +333,6 @@ import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js'; import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js'; import * as ep___users_search from './endpoints/users/search.js'; import * as ep___users_show from './endpoints/users/show.js'; -import * as ep___users_stats from './endpoints/users/stats.js'; import * as ep___users_achievements from './endpoints/users/achievements.js'; import * as ep___users_updateMemo from './endpoints/users/update-memo.js'; import * as ep___fetchRss from './endpoints/fetch-rss.js'; @@ -674,7 +673,6 @@ const $users_reportAbuse: Provider = { provide: 'ep:users/report-abuse', useClas const $users_searchByUsernameAndHost: Provider = { provide: 'ep:users/search-by-username-and-host', useClass: ep___users_searchByUsernameAndHost.default }; const $users_search: Provider = { provide: 'ep:users/search', useClass: ep___users_search.default }; const $users_show: Provider = { provide: 'ep:users/show', useClass: ep___users_show.default }; -const $users_stats: Provider = { provide: 'ep:users/stats', useClass: ep___users_stats.default }; const $users_achievements: Provider = { provide: 'ep:users/achievements', useClass: ep___users_achievements.default }; const $users_updateMemo: Provider = { provide: 'ep:users/update-memo', useClass: ep___users_updateMemo.default }; const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.default }; @@ -1019,7 +1017,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $users_searchByUsernameAndHost, $users_search, $users_show, - $users_stats, $users_achievements, $users_updateMemo, $fetchRss, @@ -1356,7 +1353,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $users_searchByUsernameAndHost, $users_search, $users_show, - $users_stats, $users_achievements, $users_updateMemo, $fetchRss, diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index 893dfe956e5057917f7d8c258552e1a9fb649f79..d1394d6d761e38073cbd888f1b31ce0c06e87a70 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -128,26 +128,27 @@ export class StreamingApiServerService { ev.removeAllListeners(); stream.dispose(); this.redisForSub.off('message', onRedisMessage); + this.#connections.delete(connection); if (userUpdateIntervalId) clearInterval(userUpdateIntervalId); }); - connection.on('message', async (data) => { + connection.on('pong', () => { this.#connections.set(connection, Date.now()); - if (data.toString() === 'ping') { - connection.send('pong'); - } }); }); + // 一定期間通信ãŒç„¡ã„コãƒã‚¯ã‚·ãƒ§ãƒ³ã¯å®Ÿéš›ã«ã¯åˆ‡æ–ã•ã‚Œã¦ã„ã‚‹å¯èƒ½æ€§ãŒã‚ã‚‹ãŸã‚定期的ã«terminateã™ã‚‹ this.#cleanConnectionsIntervalId = setInterval(() => { const now = Date.now(); for (const [connection, lastActive] of this.#connections.entries()) { - if (now - lastActive > 1000 * 60 * 5) { + if (now - lastActive > 1000 * 60 * 2) { connection.terminate(); this.#connections.delete(connection); + } else { + connection.ping(); } } - }, 1000 * 60 * 5); + }, 1000 * 60); } @bindThis diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 7e678a64040728ea8eb09f4dc2fd3579fb1d5425..94206ef870decae8b2b97845505708157f07a6e5 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -333,7 +333,6 @@ import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js'; import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js'; import * as ep___users_search from './endpoints/users/search.js'; import * as ep___users_show from './endpoints/users/show.js'; -import * as ep___users_stats from './endpoints/users/stats.js'; import * as ep___users_achievements from './endpoints/users/achievements.js'; import * as ep___users_updateMemo from './endpoints/users/update-memo.js'; import * as ep___fetchRss from './endpoints/fetch-rss.js'; @@ -672,7 +671,6 @@ const eps = [ ['users/search-by-username-and-host', ep___users_searchByUsernameAndHost], ['users/search', ep___users_search], ['users/show', ep___users_show], - ['users/stats', ep___users_stats], ['users/achievements', ep___users_achievements], ['users/update-memo', ep___users_updateMemo], ['fetch-rss', ep___fetchRss], diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 87a2d22ac2d58c285ac72eda0ceeee4a5b6b414c..4cc1b6011f247732d48d6adec5ca310f55b9ca72 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -61,10 +61,17 @@ export const meta = { type: 'string', optional: false, nullable: true, }, - errorImageUrl: { + serverErrorImageUrl: { + type: 'string', + optional: false, nullable: true, + }, + infoImageUrl: { + type: 'string', + optional: false, nullable: true, + }, + notFoundImageUrl: { type: 'string', optional: false, nullable: true, - default: 'https://xn--931a.moe/aiart/yubitun.png', }, iconUrl: { type: 'string', @@ -305,7 +312,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { themeColor: instance.themeColor, mascotImageUrl: instance.mascotImageUrl, bannerUrl: instance.bannerUrl, - errorImageUrl: instance.errorImageUrl, + serverErrorImageUrl: instance.serverErrorImageUrl, + notFoundImageUrl: instance.notFoundImageUrl, + infoImageUrl: instance.infoImageUrl, iconUrl: instance.iconUrl, backgroundImageUrl: instance.backgroundImageUrl, logoImageUrl: instance.logoImageUrl, 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 0e94f56cfd3426ede515683fcadc19c9842e9ab1..1de5e9efd3fb9d0fbd06fd8686f8956e5d5f4481 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -32,7 +32,9 @@ export const paramDef = { themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' }, mascotImageUrl: { type: 'string', nullable: true }, bannerUrl: { type: 'string', nullable: true }, - errorImageUrl: { type: 'string', nullable: true }, + serverErrorImageUrl: { type: 'string', nullable: true }, + infoImageUrl: { type: 'string', nullable: true }, + notFoundImageUrl: { type: 'string', nullable: true }, iconUrl: { type: 'string', nullable: true }, backgroundImageUrl: { type: 'string', nullable: true }, logoImageUrl: { type: 'string', nullable: true }, @@ -149,6 +151,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { set.iconUrl = ps.iconUrl; } + if (ps.serverErrorImageUrl !== undefined) { + set.serverErrorImageUrl = ps.serverErrorImageUrl; + } + + if (ps.infoImageUrl !== undefined) { + set.infoImageUrl = ps.infoImageUrl; + } + + if (ps.notFoundImageUrl !== undefined) { + set.notFoundImageUrl = ps.notFoundImageUrl; + } + if (ps.backgroundImageUrl !== undefined) { set.backgroundImageUrl = ps.backgroundImageUrl; } @@ -281,10 +295,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { set.smtpPass = ps.smtpPass; } - if (ps.errorImageUrl !== undefined) { - set.errorImageUrl = ps.errorImageUrl; - } - if (ps.enableServiceWorker !== undefined) { set.enableServiceWorker = ps.enableServiceWorker; } diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index fe68467a64d0bc425b78d444dc56b3ccec3cc978..3b3c5caa003b6ba68e1a282770d09945666f8e76 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -124,10 +124,17 @@ export const meta = { type: 'string', optional: false, nullable: false, }, - errorImageUrl: { + serverErrorImageUrl: { type: 'string', - optional: false, nullable: false, - default: 'https://xn--931a.moe/aiart/yubitun.png', + optional: false, nullable: true, + }, + infoImageUrl: { + type: 'string', + optional: false, nullable: true, + }, + notFoundImageUrl: { + type: 'string', + optional: false, nullable: true, }, iconUrl: { type: 'string', @@ -288,7 +295,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { themeColor: instance.themeColor, mascotImageUrl: instance.mascotImageUrl, bannerUrl: instance.bannerUrl, - errorImageUrl: instance.errorImageUrl, + infoImageUrl: instance.infoImageUrl, + serverErrorImageUrl: instance.serverErrorImageUrl, + notFoundImageUrl: instance.notFoundImageUrl, iconUrl: instance.iconUrl, backgroundImageUrl: instance.backgroundImageUrl, logoImageUrl: instance.logoImageUrl, diff --git a/packages/backend/src/server/api/endpoints/roles/list.ts b/packages/backend/src/server/api/endpoints/roles/list.ts index d61c6b8dc6a522413afdb7f9c740d56290940b4e..5ad29839c2c29dfb926d1575da5083c5a2940402 100644 --- a/packages/backend/src/server/api/endpoints/roles/list.ts +++ b/packages/backend/src/server/api/endpoints/roles/list.ts @@ -30,6 +30,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { super(meta, paramDef, async (ps, me) => { const roles = await this.rolesRepository.findBy({ isPublic: true, + isExplorable: true, }); return await this.roleEntityService.packMany(roles, me); }); diff --git a/packages/backend/src/server/api/endpoints/roles/users.ts b/packages/backend/src/server/api/endpoints/roles/users.ts index 607dc24206954c6f5dc267368ccbafbf97cf2fff..b2cb8b42a85a27c6990df9b02089589c86334a94 100644 --- a/packages/backend/src/server/api/endpoints/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/roles/users.ts @@ -49,6 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { const role = await this.rolesRepository.findOneBy({ id: ps.roleId, isPublic: true, + isExplorable: true, }); if (role == null) { diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts deleted file mode 100644 index 7479793afea752722255f80eda8e359c15326ba4..0000000000000000000000000000000000000000 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { awaitAll } from '@/misc/prelude/await-all.js'; -import { Endpoint } from '@/server/api/endpoint-base.js'; -import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; -import { DI } from '@/di-symbols.js'; -import type { UsersRepository, NotesRepository, FollowingsRepository, DriveFilesRepository, NoteReactionsRepository, PageLikesRepository, NoteFavoritesRepository, PollVotesRepository } from '@/models/index.js'; -import { ApiError } from '../../error.js'; - -export const meta = { - tags: ['users'], - - requireCredential: false, - - description: 'Show statistics about a user.', - - errors: { - noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '9e638e45-3b25-4ef7-8f95-07e8498f1819', - }, - }, - - res: { - type: 'object', - optional: false, nullable: false, - properties: { - notesCount: { - type: 'integer', - optional: false, nullable: false, - }, - repliesCount: { - type: 'integer', - optional: false, nullable: false, - }, - renotesCount: { - type: 'integer', - optional: false, nullable: false, - }, - repliedCount: { - type: 'integer', - optional: false, nullable: false, - }, - renotedCount: { - type: 'integer', - optional: false, nullable: false, - }, - pollVotesCount: { - type: 'integer', - optional: false, nullable: false, - }, - pollVotedCount: { - type: 'integer', - optional: false, nullable: false, - }, - localFollowingCount: { - type: 'integer', - optional: false, nullable: false, - }, - remoteFollowingCount: { - type: 'integer', - optional: false, nullable: false, - }, - localFollowersCount: { - type: 'integer', - optional: false, nullable: false, - }, - remoteFollowersCount: { - type: 'integer', - optional: false, nullable: false, - }, - followingCount: { - type: 'integer', - optional: false, nullable: false, - }, - followersCount: { - type: 'integer', - optional: false, nullable: false, - }, - sentReactionsCount: { - type: 'integer', - optional: false, nullable: false, - }, - receivedReactionsCount: { - type: 'integer', - optional: false, nullable: false, - }, - noteFavoritesCount: { - type: 'integer', - optional: false, nullable: false, - }, - pageLikesCount: { - type: 'integer', - optional: false, nullable: false, - }, - pageLikedCount: { - type: 'integer', - optional: false, nullable: false, - }, - driveFilesCount: { - type: 'integer', - optional: false, nullable: false, - }, - driveUsage: { - type: 'integer', - optional: false, nullable: false, - description: 'Drive usage in bytes', - }, - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - userId: { type: 'string', format: 'misskey:id' }, - }, - required: ['userId'], -} as const; - -// eslint-disable-next-line import/no-default-export -@Injectable() -export default class extends Endpoint<typeof meta, typeof paramDef> { - constructor( - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, - - @Inject(DI.followingsRepository) - private followingsRepository: FollowingsRepository, - - @Inject(DI.driveFilesRepository) - private driveFilesRepository: DriveFilesRepository, - - @Inject(DI.noteReactionsRepository) - private noteReactionsRepository: NoteReactionsRepository, - - @Inject(DI.pageLikesRepository) - private pageLikesRepository: PageLikesRepository, - - @Inject(DI.noteFavoritesRepository) - private noteFavoritesRepository: NoteFavoritesRepository, - - @Inject(DI.pollVotesRepository) - private pollVotesRepository: PollVotesRepository, - - private driveFileEntityService: DriveFileEntityService, - ) { - super(meta, paramDef, async (ps, me) => { - const user = await this.usersRepository.findOneBy({ id: ps.userId }); - if (user == null) { - throw new ApiError(meta.errors.noSuchUser); - } - - const result = await awaitAll({ - notesCount: this.notesRepository.createQueryBuilder('note') - .where('note.userId = :userId', { userId: user.id }) - .getCount(), - repliesCount: this.notesRepository.createQueryBuilder('note') - .where('note.userId = :userId', { userId: user.id }) - .andWhere('note.replyId IS NOT NULL') - .getCount(), - renotesCount: this.notesRepository.createQueryBuilder('note') - .where('note.userId = :userId', { userId: user.id }) - .andWhere('note.renoteId IS NOT NULL') - .getCount(), - repliedCount: this.notesRepository.createQueryBuilder('note') - .where('note.replyUserId = :userId', { userId: user.id }) - .getCount(), - renotedCount: this.notesRepository.createQueryBuilder('note') - .where('note.renoteUserId = :userId', { userId: user.id }) - .getCount(), - pollVotesCount: this.pollVotesRepository.createQueryBuilder('vote') - .where('vote.userId = :userId', { userId: user.id }) - .getCount(), - pollVotedCount: this.pollVotesRepository.createQueryBuilder('vote') - .innerJoin('vote.note', 'note') - .where('note.userId = :userId', { userId: user.id }) - .getCount(), - localFollowingCount: this.followingsRepository.createQueryBuilder('following') - .where('following.followerId = :userId', { userId: user.id }) - .andWhere('following.followeeHost IS NULL') - .getCount(), - remoteFollowingCount: this.followingsRepository.createQueryBuilder('following') - .where('following.followerId = :userId', { userId: user.id }) - .andWhere('following.followeeHost IS NOT NULL') - .getCount(), - localFollowersCount: this.followingsRepository.createQueryBuilder('following') - .where('following.followeeId = :userId', { userId: user.id }) - .andWhere('following.followerHost IS NULL') - .getCount(), - remoteFollowersCount: this.followingsRepository.createQueryBuilder('following') - .where('following.followeeId = :userId', { userId: user.id }) - .andWhere('following.followerHost IS NOT NULL') - .getCount(), - sentReactionsCount: this.noteReactionsRepository.createQueryBuilder('reaction') - .where('reaction.userId = :userId', { userId: user.id }) - .getCount(), - receivedReactionsCount: this.noteReactionsRepository.createQueryBuilder('reaction') - .innerJoin('reaction.note', 'note') - .where('note.userId = :userId', { userId: user.id }) - .getCount(), - noteFavoritesCount: this.noteFavoritesRepository.createQueryBuilder('favorite') - .where('favorite.userId = :userId', { userId: user.id }) - .getCount(), - pageLikesCount: this.pageLikesRepository.createQueryBuilder('like') - .where('like.userId = :userId', { userId: user.id }) - .getCount(), - pageLikedCount: this.pageLikesRepository.createQueryBuilder('like') - .innerJoin('like.page', 'page') - .where('page.userId = :userId', { userId: user.id }) - .getCount(), - driveFilesCount: this.driveFilesRepository.createQueryBuilder('file') - .where('file.userId = :userId', { userId: user.id }) - .getCount(), - driveUsage: this.driveFileEntityService.calcDriveUsageOf(user), - }); - - return { - ...result, - followingCount: result.localFollowingCount + result.remoteFollowingCount, - followersCount: result.localFollowersCount + result.remoteFollowersCount, - }; - }); - } -} diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index f780280c1f58a49c22e02cc515b3151a61a18f91..07ba2731c3f2386dacd9c03370339767a4c1c2e1 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -26,7 +26,7 @@ import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; -import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, Meta, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type Logger from '@/logger.js'; import { deepClone } from '@/misc/clone.js'; import { bindThis } from '@/decorators.js'; @@ -117,6 +117,18 @@ export class ClientServerService { return (res); } + @bindThis + private generateCommonPugData(meta: Meta) { + return { + instanceName: meta.name ?? 'Misskey', + icon: meta.iconUrl, + themeColor: meta.themeColor, + serverErrorImageUrl: meta.serverErrorImageUrl ?? 'https://xn--931a.moe/assets/error.jpg', + infoImageUrl: meta.infoImageUrl ?? 'https://xn--931a.moe/assets/info.jpg', + notFoundImageUrl: meta.notFoundImageUrl ?? 'https://xn--931a.moe/assets/not-found.jpg', + }; + } + @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { fastify.register(fastifyCookie, {}); @@ -341,12 +353,10 @@ export class ClientServerService { reply.header('Cache-Control', 'public, max-age=30'); return await reply.view('base', { img: meta.bannerUrl, - title: meta.name ?? 'Misskey', - instanceName: meta.name ?? 'Misskey', url: this.config.url, + title: meta.name ?? 'Misskey', desc: meta.description, - icon: meta.iconUrl, - themeColor: meta.themeColor, + ...this.generateCommonPugData(meta), }); }; @@ -431,9 +441,7 @@ export class ClientServerService { user, profile, me, avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user), sub: request.params.sub, - instanceName: meta.name ?? 'Misskey', - icon: meta.iconUrl, - themeColor: meta.themeColor, + ...this.generateCommonPugData(meta), }); } else { // リモートユーザーãªã®ã§ @@ -481,9 +489,7 @@ export class ClientServerService { avatarUrl: _note.user.avatarUrl, // TODO: Let locale changeable by instance setting summary: getNoteSummary(_note), - instanceName: meta.name ?? 'Misskey', - icon: meta.iconUrl, - themeColor: meta.themeColor, + ...this.generateCommonPugData(meta), }); } else { return await renderBase(reply); @@ -522,9 +528,7 @@ export class ClientServerService { page: _page, profile, avatarUrl: _page.user.avatarUrl, - instanceName: meta.name ?? 'Misskey', - icon: meta.iconUrl, - themeColor: meta.themeColor, + ...this.generateCommonPugData(meta), }); } else { return await renderBase(reply); @@ -550,9 +554,7 @@ export class ClientServerService { flash: _flash, profile, avatarUrl: _flash.user.avatarUrl, - instanceName: meta.name ?? 'Misskey', - icon: meta.iconUrl, - themeColor: meta.themeColor, + ...this.generateCommonPugData(meta), }); } else { return await renderBase(reply); @@ -578,9 +580,7 @@ export class ClientServerService { clip: _clip, profile, avatarUrl: _clip.user.avatarUrl, - instanceName: meta.name ?? 'Misskey', - icon: meta.iconUrl, - themeColor: meta.themeColor, + ...this.generateCommonPugData(meta), }); } else { return await renderBase(reply); @@ -604,9 +604,7 @@ export class ClientServerService { post: _post, profile, avatarUrl: _post.user.avatarUrl, - instanceName: meta.name ?? 'Misskey', - icon: meta.iconUrl, - themeColor: meta.themeColor, + ...this.generateCommonPugData(meta), }); } else { return await renderBase(reply); @@ -625,9 +623,7 @@ export class ClientServerService { reply.header('Cache-Control', 'public, max-age=15'); return await reply.view('channel', { channel: _channel, - instanceName: meta.name ?? 'Misskey', - icon: meta.iconUrl, - themeColor: meta.themeColor, + ...this.generateCommonPugData(meta), }); } else { return await renderBase(reply); diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 69b3f68e0524b34ead1077a00050d3921e126e0d..1216fc73f797db0214879c926ca3fe1e011de3c9 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -31,9 +31,9 @@ html link(rel='apple-touch-icon' href= icon || '/apple-touch-icon.png') link(rel='manifest' href='/manifest.json') link(rel='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${url}/opensearch.xml`) - link(rel='prefetch' href='https://xn--931a.moe/assets/info.jpg') - link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg') - link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg') + link(rel='prefetch' href=serverErrorImageUrl) + link(rel='prefetch' href=infoImageUrl) + link(rel='prefetch' href=notFoundImageUrl) //- https://github.com/misskey-dev/misskey/issues/9842 link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.21.0') link(rel='modulepreload' href=`/vite/${clientEntry.file}`) diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug index 874c48c60285c760f9c674af8232916e7f682b2d..ea0917a80e1ade98729aca2bf1397f2439c3dd5b 100644 --- a/packages/backend/src/server/web/views/note.pug +++ b/packages/backend/src/server/web/views/note.pug @@ -5,8 +5,8 @@ block vars - const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`; - const url = `${config.url}/notes/${note.id}`; - const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null; - - const image = (note.files || []).find(file => file.type.startsWith('image/') && !file.type.isSensitive) - - const video = (note.files || []).find(file => file.type.startsWith('video/') && !file.type.isSensitive) + - const image = (note.files || []).find(file => file.type.startsWith('image/') && !file.isSensitive) + - const video = (note.files || []).find(file => file.type.startsWith('video/') && !file.isSensitive) block title = `${title} | ${instanceName}` diff --git a/packages/frontend/src/components/MkChannelList.vue b/packages/frontend/src/components/MkChannelList.vue index 4050520eb9bedd7c542c8be4768df20a7d61eb4f..2d3ea8d177952388ae32612054eddb1fd5caea9a 100644 --- a/packages/frontend/src/components/MkChannelList.vue +++ b/packages/frontend/src/components/MkChannelList.vue @@ -2,7 +2,7 @@ <MkPagination :pagination="pagination"> <template #empty> <div class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.notFound }}</div> </div> </template> @@ -17,6 +17,7 @@ import MkChannelPreview from '@/components/MkChannelPreview.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue'; import { i18n } from '@/i18n'; +import { infoImageUrl } from '@/instance'; const props = withDefaults(defineProps<{ pagination: Paging; diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue index 9cc2b7a967a32b5a5cb68bd84b5a2758d5e15516..b49c8fa8b7be9f6bec9e354b7c68a7f09f6296b7 100644 --- a/packages/frontend/src/components/MkNotes.vue +++ b/packages/frontend/src/components/MkNotes.vue @@ -2,7 +2,7 @@ <MkPagination ref="pagingComponent" :pagination="pagination"> <template #empty> <div class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.noNotes }}</div> </div> </template> @@ -32,6 +32,7 @@ import MkNote from '@/components/MkNote.vue'; import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue'; import { i18n } from '@/i18n'; +import { infoImageUrl } from '@/instance'; const props = defineProps<{ pagination: Paging; diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue index 70224bffa130d50c50e7be91fcbf9e965f38b300..d4a30d1916d0cd3e04ec854dffbe9fd82cd63b34 100644 --- a/packages/frontend/src/components/MkNotifications.vue +++ b/packages/frontend/src/components/MkNotifications.vue @@ -2,7 +2,7 @@ <MkPagination ref="pagingComponent" :pagination="pagination"> <template #empty> <div class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.noNotifications }}</div> </div> </template> @@ -26,6 +26,7 @@ import { useStream } from '@/stream'; import { $i } from '@/account'; import { i18n } from '@/i18n'; import { notificationTypes } from '@/const'; +import { infoImageUrl } from '@/instance'; const props = defineProps<{ includeTypes?: typeof notificationTypes[number][]; diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index 740094b1130e0944e646b11d9ed7b126e9fac961..598529bf5870dbc90b919fe50901357bc3038458 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -13,7 +13,7 @@ <div v-else-if="empty" key="_empty_" class="empty"> <slot name="empty"> <div class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.nothing }}</div> </div> </slot> @@ -73,6 +73,8 @@ export type Paging<E extends keyof misskey.Endpoints = keyof misskey.Endpoints> }; </script> <script lang="ts" setup> +import { infoImageUrl } from '@/instance'; + const props = withDefaults(defineProps<{ pagination: Paging; disableAutoLoad?: boolean; diff --git a/packages/frontend/src/components/MkReactedUsersDialog.vue b/packages/frontend/src/components/MkReactedUsersDialog.vue index cd2a359d5c14afa35c03ba8be08d11309c328f06..0a858a8965fc2a0b6fed66665fbb552fc6b23cea 100644 --- a/packages/frontend/src/components/MkReactedUsersDialog.vue +++ b/packages/frontend/src/components/MkReactedUsersDialog.vue @@ -11,7 +11,7 @@ <MkSpacer :marginMin="20" :marginMax="28"> <div v-if="note" class="_gaps"> <div v-if="reactions.length === 0" class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.nothing }}</div> </div> <template v-else> @@ -42,6 +42,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue'; import { userPage } from '@/filters/user'; import { i18n } from '@/i18n'; import * as os from '@/os'; +import { infoImageUrl } from '@/instance'; const emit = defineEmits<{ (ev: 'closed'): void, diff --git a/packages/frontend/src/components/MkRenotedUsersDialog.vue b/packages/frontend/src/components/MkRenotedUsersDialog.vue index 814a68d4daf0e3b52711c35aeaa47c0ba9cea843..484cb2f9a716248fbcf05909f82ad016c55b6d27 100644 --- a/packages/frontend/src/components/MkRenotedUsersDialog.vue +++ b/packages/frontend/src/components/MkRenotedUsersDialog.vue @@ -11,7 +11,7 @@ <MkSpacer :marginMin="20" :marginMax="28"> <div v-if="renotes" class="_gaps"> <div v-if="renotes.length === 0" class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.nothing }}</div> </div> <template v-else> @@ -35,6 +35,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue'; import { userPage } from '@/filters/user'; import { i18n } from '@/i18n'; import * as os from '@/os'; +import { infoImageUrl } from '@/instance'; const emit = defineEmits<{ (ev: 'closed'): void, diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue index 3571ca84d9c27f5f41bf57961dcf762d7273cd1f..2a23f3e70dc2a7bb2bde34060c14c1c80c4327e9 100644 --- a/packages/frontend/src/components/MkUserList.vue +++ b/packages/frontend/src/components/MkUserList.vue @@ -2,7 +2,7 @@ <MkPagination :pagination="pagination"> <template #empty> <div class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.noUsers }}</div> </div> </template> @@ -19,6 +19,7 @@ import MkUserInfo from '@/components/MkUserInfo.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue'; import { i18n } from '@/i18n'; +import { infoImageUrl } from '@/instance'; const props = withDefaults(defineProps<{ pagination: Paging; diff --git a/packages/frontend/src/components/global/MkError.vue b/packages/frontend/src/components/global/MkError.vue index 513ef21d35b5f0120563be8ec47ba0db0b07942e..503e00387c38cd10af84b9f7baeeaf244bcfeff5 100644 --- a/packages/frontend/src/components/global/MkError.vue +++ b/packages/frontend/src/components/global/MkError.vue @@ -1,7 +1,7 @@ <template> <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear> <div :class="$style.root"> - <img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> + <img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/> <p :class="$style.text"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</p> <MkButton :class="$style.button" @click="() => emit('retry')">{{ i18n.ts.retry }}</MkButton> </div> @@ -12,6 +12,7 @@ import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; +import { serverErrorImageUrl } from '@/instance'; const emit = defineEmits<{ (ev: 'retry'): void; diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts index aaa3d10302546867a6e64d66f97156e910f99cbd..ad7fa372e97cbe0a7c3c43bd45a971e80402359c 100644 --- a/packages/frontend/src/const.ts +++ b/packages/frontend/src/const.ts @@ -78,3 +78,7 @@ export const ROLE_POLICIES = [ //export const CURRENT_STICKY_BOTTOM = Symbol('CURRENT_STICKY_BOTTOM'); export const CURRENT_STICKY_TOP = 'CURRENT_STICKY_TOP'; export const CURRENT_STICKY_BOTTOM = 'CURRENT_STICKY_BOTTOM'; + +export const DEFAULT_SERVER_ERROR_IMAGE_URL = 'https://xn--931a.moe/assets/error.jpg'; +export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://xn--931a.moe/assets/not-found.jpg'; +export const DEFAULT_INFO_IMAGE_URL = 'https://xn--931a.moe/assets/info.jpg'; diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts index f4c1988704caa4f7f7f2bebdcf57a39bf385662f..9cfcbcbc3f297431aed244b1ce9d61a0d3c77522 100644 --- a/packages/frontend/src/instance.ts +++ b/packages/frontend/src/instance.ts @@ -1,7 +1,8 @@ -import { reactive } from 'vue'; +import { computed, reactive } from 'vue'; import * as Misskey from 'misskey-js'; import { api } from './os'; import { miLocalStorage } from './local-storage'; +import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@/const'; // TODO: ä»–ã®ã‚¿ãƒ–ã¨æ°¸ç¶šåŒ–ã•ã‚ŒãŸstateã‚’åŒæœŸ @@ -13,6 +14,12 @@ export const instance: Misskey.entities.InstanceMetadata = reactive(cached ? JSO // TODO: set default values }); +export const serverErrorImageUrl = computed(() => instance.serverErrorImageUrl ?? DEFAULT_SERVER_ERROR_IMAGE_URL); + +export const infoImageUrl = computed(() => instance.infoImageUrl ?? DEFAULT_INFO_IMAGE_URL); + +export const notFoundImageUrl = computed(() => instance.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL); + export async function fetchInstance() { const meta = await api('meta', { detail: false, diff --git a/packages/frontend/src/pages/_error_.vue b/packages/frontend/src/pages/_error_.vue index f27d2df336f88b4582866a6da7dbad4d46ce13e5..eee661bd8a87aba50bcc19586d2a827d86e5d37d 100644 --- a/packages/frontend/src/pages/_error_.vue +++ b/packages/frontend/src/pages/_error_.vue @@ -2,7 +2,7 @@ <MkLoading v-if="!loaded"/> <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear> <div v-show="loaded" :class="$style.root"> - <img src="https://xn--931a.moe/assets/error.jpg" class="_ghost" :class="$style.img"/> + <img :src="serverErrorImageUrl" class="_ghost" :class="$style.img"/> <div class="_gaps"> <p><b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.pageLoadError }}</b></p> <p v-if="meta && (version === meta.version)">{{ i18n.ts.pageLoadErrorDescription }}</p> @@ -30,6 +30,7 @@ import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import { miLocalStorage } from '@/local-storage'; import { defaultStore } from '@/store'; +import { serverErrorImageUrl } from '@/instance'; const props = withDefaults(defineProps<{ error?: Error; diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue new file mode 100644 index 0000000000000000000000000000000000000000..65ce9e00683b4c267ebc2ac86a36150410eaeb0a --- /dev/null +++ b/packages/frontend/src/pages/admin/branding.vue @@ -0,0 +1,133 @@ +<template> +<div> + <MkStickyContainer> + <template #header><XHeader :tabs="headerTabs"/></template> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> + <FormSuspense :p="init"> + <div class="_gaps_m"> + <MkInput v-model="iconUrl"> + <template #prefix><i class="ti ti-link"></i></template> + <template #label>{{ i18n.ts.iconUrl }}</template> + </MkInput> + + <MkInput v-model="bannerUrl"> + <template #prefix><i class="ti ti-link"></i></template> + <template #label>{{ i18n.ts.bannerUrl }}</template> + </MkInput> + + <MkInput v-model="backgroundImageUrl"> + <template #prefix><i class="ti ti-link"></i></template> + <template #label>{{ i18n.ts.backgroundImageUrl }}</template> + </MkInput> + + <MkInput v-model="notFoundImageUrl"> + <template #prefix><i class="ti ti-link"></i></template> + <template #label>{{ i18n.ts.notFoundDescription }}</template> + </MkInput> + + <MkInput v-model="infoImageUrl"> + <template #prefix><i class="ti ti-link"></i></template> + <template #label>{{ i18n.ts.nothing }}</template> + </MkInput> + + <MkInput v-model="serverErrorImageUrl"> + <template #prefix><i class="ti ti-link"></i></template> + <template #label>{{ i18n.ts.somethingHappened }}</template> + </MkInput> + + <MkColorInput v-model="themeColor"> + <template #label>{{ i18n.ts.themeColor }}</template> + </MkColorInput> + + <MkTextarea v-model="defaultLightTheme"> + <template #label>{{ i18n.ts.instanceDefaultLightTheme }}</template> + <template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template> + </MkTextarea> + + <MkTextarea v-model="defaultDarkTheme"> + <template #label>{{ i18n.ts.instanceDefaultDarkTheme }}</template> + <template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template> + </MkTextarea> + </div> + </FormSuspense> + </MkSpacer> + <template #footer> + <div :class="$style.footer"> + <MkSpacer :contentMax="700" :marginMin="16" :marginMax="16"> + <MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> + </MkSpacer> + </div> + </template> + </MkStickyContainer> +</div> +</template> + +<script lang="ts" setup> +import { } from 'vue'; +import XHeader from './_header_.vue'; +import MkSwitch from '@/components/MkSwitch.vue'; +import MkInput from '@/components/MkInput.vue'; +import MkTextarea from '@/components/MkTextarea.vue'; +import FormSection from '@/components/form/section.vue'; +import FormSplit from '@/components/form/split.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import * as os from '@/os'; +import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; +import { definePageMetadata } from '@/scripts/page-metadata'; +import MkButton from '@/components/MkButton.vue'; +import MkColorInput from '@/components/MkColorInput.vue'; + +let iconUrl: string | null = $ref(null); +let bannerUrl: string | null = $ref(null); +let backgroundImageUrl: string | null = $ref(null); +let themeColor: any = $ref(null); +let defaultLightTheme: any = $ref(null); +let defaultDarkTheme: any = $ref(null); +let serverErrorImageUrl: string | null = $ref(null); +let infoImageUrl: string | null = $ref(null); +let notFoundImageUrl: string | null = $ref(null); + +async function init() { + const meta = await os.api('admin/meta'); + iconUrl = meta.iconUrl; + bannerUrl = meta.bannerUrl; + backgroundImageUrl = meta.backgroundImageUrl; + themeColor = meta.themeColor; + defaultLightTheme = meta.defaultLightTheme; + defaultDarkTheme = meta.defaultDarkTheme; + serverErrorImageUrl = meta.serverErrorImageUrl; + infoImageUrl = meta.infoImageUrl; + notFoundImageUrl = meta.notFoundImageUrl; +} + +function save() { + os.apiWithDialog('admin/update-meta', { + iconUrl, + bannerUrl, + backgroundImageUrl, + themeColor: themeColor === '' ? null : themeColor, + defaultLightTheme: defaultLightTheme === '' ? null : defaultLightTheme, + defaultDarkTheme: defaultDarkTheme === '' ? null : defaultDarkTheme, + infoImageUrl, + notFoundImageUrl, + serverErrorImageUrl, + }).then(() => { + fetchInstance(); + }); +} + +const headerTabs = $computed(() => []); + +definePageMetadata({ + title: i18n.ts.branding, + icon: 'ti ti-paint', +}); +</script> + +<style lang="scss" module> +.footer { + -webkit-backdrop-filter: var(--blur, blur(15px)); + backdrop-filter: var(--blur, blur(15px)); +} +</style> diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index 5cbbcaa44cdc19323a0d23a0e8ff9e41de90f64a..8b083bc89654e08642baba45d7daa7460b0af5d2 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -143,6 +143,11 @@ const menuDef = $computed(() => [{ text: i18n.ts.general, to: '/admin/settings', active: currentPage?.route.name === 'settings', + }, { + icon: 'ti ti-paint', + text: i18n.ts.branding, + to: '/admin/branding', + active: currentPage?.route.name === 'branding', }, { icon: 'ti ti-shield', text: i18n.ts.moderation, diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue index 4ed6abf200a04c602d6b73ac9aaf1c95cb9b7396..6cbe7ae658de85d04b301c658f470f76cd93cf8c 100644 --- a/packages/frontend/src/pages/admin/roles.role.vue +++ b/packages/frontend/src/pages/admin/roles.role.vue @@ -23,7 +23,7 @@ <MkPagination :pagination="usersPagination"> <template #empty> <div class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.noUsers }}</div> </div> </template> @@ -69,6 +69,7 @@ import MkButton from '@/components/MkButton.vue'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkInfo from '@/components/MkInfo.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue'; +import { infoImageUrl } from '@/instance'; const router = useRouter(); diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index 39d5ae8607546420c08350f4ebb1415deb90a144..4c2fe46f284b1b1fadb4c5c3b14d243317a2a8df 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -29,41 +29,6 @@ <template #caption>{{ i18n.ts.pinnedUsersDescription }}</template> </MkTextarea> - <FormSection> - <template #label>{{ i18n.ts.theme }}</template> - - <div class="_gaps_m"> - <MkInput v-model="iconUrl"> - <template #prefix><i class="ti ti-link"></i></template> - <template #label>{{ i18n.ts.iconUrl }}</template> - </MkInput> - - <MkInput v-model="bannerUrl"> - <template #prefix><i class="ti ti-link"></i></template> - <template #label>{{ i18n.ts.bannerUrl }}</template> - </MkInput> - - <MkInput v-model="backgroundImageUrl"> - <template #prefix><i class="ti ti-link"></i></template> - <template #label>{{ i18n.ts.backgroundImageUrl }}</template> - </MkInput> - - <MkColorInput v-model="themeColor"> - <template #label>{{ i18n.ts.themeColor }}</template> - </MkColorInput> - - <MkTextarea v-model="defaultLightTheme"> - <template #label>{{ i18n.ts.instanceDefaultLightTheme }}</template> - <template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template> - </MkTextarea> - - <MkTextarea v-model="defaultDarkTheme"> - <template #label>{{ i18n.ts.instanceDefaultDarkTheme }}</template> - <template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template> - </MkTextarea> - </div> - </FormSection> - <FormSection> <template #label>{{ i18n.ts.files }}</template> @@ -145,12 +110,6 @@ let name: string | null = $ref(null); let description: string | null = $ref(null); let maintainerName: string | null = $ref(null); let maintainerEmail: string | null = $ref(null); -let iconUrl: string | null = $ref(null); -let bannerUrl: string | null = $ref(null); -let backgroundImageUrl: string | null = $ref(null); -let themeColor: any = $ref(null); -let defaultLightTheme: any = $ref(null); -let defaultDarkTheme: any = $ref(null); let pinnedUsers: string = $ref(''); let cacheRemoteFiles: boolean = $ref(false); let enableServiceWorker: boolean = $ref(false); @@ -163,12 +122,6 @@ async function init() { const meta = await os.api('admin/meta'); name = meta.name; description = meta.description; - iconUrl = meta.iconUrl; - bannerUrl = meta.bannerUrl; - backgroundImageUrl = meta.backgroundImageUrl; - themeColor = meta.themeColor; - defaultLightTheme = meta.defaultLightTheme; - defaultDarkTheme = meta.defaultDarkTheme; maintainerName = meta.maintainerName; maintainerEmail = meta.maintainerEmail; pinnedUsers = meta.pinnedUsers.join('\n'); @@ -184,12 +137,6 @@ function save() { os.apiWithDialog('admin/update-meta', { name, description, - iconUrl, - bannerUrl, - backgroundImageUrl, - themeColor: themeColor === '' ? null : themeColor, - defaultLightTheme: defaultLightTheme === '' ? null : defaultLightTheme, - defaultDarkTheme: defaultDarkTheme === '' ? null : defaultDarkTheme, maintainerName, maintainerEmail, pinnedUsers: pinnedUsers.split('\n'), diff --git a/packages/frontend/src/pages/favorites.vue b/packages/frontend/src/pages/favorites.vue index 460bf65d1ea522b965cb22d255898548ccfc0c8b..21c306148b9d3a8ae05bc22a84b0cd37c358cf02 100644 --- a/packages/frontend/src/pages/favorites.vue +++ b/packages/frontend/src/pages/favorites.vue @@ -5,7 +5,7 @@ <MkPagination :pagination="pagination"> <template #empty> <div class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.noNotes }}</div> </div> </template> @@ -26,6 +26,7 @@ import MkNote from '@/components/MkNote.vue'; import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { infoImageUrl } from '@/instance'; const pagination = { endpoint: 'i/favorites' as const, diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue index 1452942a1e549d85d45064c33d590cdfa8081efd..a70a4894a51576a7e8dd65ce0b818bdb08db2942 100644 --- a/packages/frontend/src/pages/follow-requests.vue +++ b/packages/frontend/src/pages/follow-requests.vue @@ -5,7 +5,7 @@ <MkPagination ref="paginationComponent" :pagination="pagination"> <template #empty> <div class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.noFollowRequests }}</div> </div> </template> @@ -39,6 +39,7 @@ import { userPage, acct } from '@/filters/user'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { infoImageUrl } from '@/instance'; const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>(); diff --git a/packages/frontend/src/pages/list.vue b/packages/frontend/src/pages/list.vue index f92c06d1c53b4e6f72aeedbe3677244c5572b3e7..40934fb71d5d590abe7d0a1c96b214cf70f56d47 100644 --- a/packages/frontend/src/pages/list.vue +++ b/packages/frontend/src/pages/list.vue @@ -3,7 +3,7 @@ <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200"> <div :class="$style.root"> - <img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> + <img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/> <p :class="$style.text"> <i class="ti ti-alert-triangle"></i> {{ i18n.ts.nothing }} @@ -36,6 +36,7 @@ import { i18n } from '@/i18n'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkButton from '@/components/MkButton.vue'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { serverErrorImageUrl } from '@/instance'; const props = defineProps<{ listId: string; diff --git a/packages/frontend/src/pages/not-found.vue b/packages/frontend/src/pages/not-found.vue index 2c9d949017ede433fda55cb6719a37f08fbaff8e..43dc41e7cc0572bf35346dcea5a5b270d1b20566 100644 --- a/packages/frontend/src/pages/not-found.vue +++ b/packages/frontend/src/pages/not-found.vue @@ -1,7 +1,7 @@ <template> <div> <div class="_fullinfo"> - <img src="https://xn--931a.moe/assets/not-found.jpg" class="_ghost"/> + <img :src="notFoundImageUrl" class="_ghost"/> <div>{{ i18n.ts.notFoundDescription }}</div> </div> </div> @@ -10,6 +10,7 @@ <script lang="ts" setup> import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { notFoundImageUrl } from '@/instance'; const headerActions = $computed(() => []); diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue index e85ab0917a8434bd90136ea77e92ca9aa38d4d2d..fc04468d5a768acbf047f4a97564fd9a621bbfa1 100644 --- a/packages/frontend/src/pages/role.vue +++ b/packages/frontend/src/pages/role.vue @@ -3,7 +3,7 @@ <template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template> <MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200"> <div :class="$style.root"> - <img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> + <img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/> <p :class="$style.text"> <i class="ti ti-alert-triangle"></i> {{ error }} @@ -30,6 +30,7 @@ import { definePageMetadata } from '@/scripts/page-metadata'; import { i18n } from '@/i18n'; import MkTimeline from '@/components/MkTimeline.vue'; import { instanceName } from '@/config'; +import { serverErrorImageUrl } from '@/instance'; const props = withDefaults(defineProps<{ role: string; diff --git a/packages/frontend/src/pages/settings/account-stats.vue b/packages/frontend/src/pages/settings/account-stats.vue deleted file mode 100644 index a0f1541b401682b30311085d7bad7bbabff9560e..0000000000000000000000000000000000000000 --- a/packages/frontend/src/pages/settings/account-stats.vue +++ /dev/null @@ -1,146 +0,0 @@ -<template> -<div class="_gaps_m"> - <FormSection v-if="stats" first> - <template #label>{{ i18n.ts.statistics }}</template> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.notesCount }}</template> - <template #value>{{ number(stats.notesCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.repliesCount }}</template> - <template #value>{{ number(stats.repliesCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.renotesCount }}</template> - <template #value>{{ number(stats.renotesCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.repliedCount }}</template> - <template #value>{{ number(stats.repliedCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.renotedCount }}</template> - <template #value>{{ number(stats.renotedCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.pollVotesCount }}</template> - <template #value>{{ number(stats.pollVotesCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.pollVotedCount }}</template> - <template #value>{{ number(stats.pollVotedCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.sentReactionsCount }}</template> - <template #value>{{ number(stats.sentReactionsCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.receivedReactionsCount }}</template> - <template #value>{{ number(stats.receivedReactionsCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.noteFavoritesCount }}</template> - <template #value>{{ number(stats.noteFavoritesCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.followingCount }}</template> - <template #value>{{ number(stats.followingCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.followingCount }} ({{ i18n.ts.local }})</template> - <template #value>{{ number(stats.localFollowingCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.followingCount }} ({{ i18n.ts.remote }})</template> - <template #value>{{ number(stats.remoteFollowingCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.followersCount }}</template> - <template #value>{{ number(stats.followersCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.followersCount }} ({{ i18n.ts.local }})</template> - <template #value>{{ number(stats.localFollowersCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.followersCount }} ({{ i18n.ts.remote }})</template> - <template #value>{{ number(stats.remoteFollowersCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.pageLikesCount }}</template> - <template #value>{{ number(stats.pageLikesCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.pageLikedCount }}</template> - <template #value>{{ number(stats.pageLikedCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.driveFilesCount }}</template> - <template #value>{{ number(stats.driveFilesCount) }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ i18n.ts.driveUsage }}</template> - <template #value>{{ bytes(stats.driveUsage) }}</template> - </MkKeyValue> - </FormSection> - - <FormSection> - <template #label>{{ i18n.ts.other }}</template> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>emailVerified</template> - <template #value>{{ $i.emailVerified ? i18n.ts.yes : i18n.ts.no }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>twoFactorEnabled</template> - <template #value>{{ $i.twoFactorEnabled ? i18n.ts.yes : i18n.ts.no }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>securityKeys</template> - <template #value>{{ $i.securityKeys ? i18n.ts.yes : i18n.ts.no }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>usePasswordLessLogin</template> - <template #value>{{ $i.usePasswordLessLogin ? i18n.ts.yes : i18n.ts.no }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>isModerator</template> - <template #value>{{ $i.isModerator ? i18n.ts.yes : i18n.ts.no }}</template> - </MkKeyValue> - <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>isAdmin</template> - <template #value>{{ $i.isAdmin ? i18n.ts.yes : i18n.ts.no }}</template> - </MkKeyValue> - </FormSection> -</div> -</template> - -<script lang="ts" setup> -import { onMounted, ref } from 'vue'; -import FormSection from '@/components/form/section.vue'; -import MkKeyValue from '@/components/MkKeyValue.vue'; -import * as os from '@/os'; -import number from '@/filters/number'; -import bytes from '@/filters/bytes'; -import { $i } from '@/account'; -import { i18n } from '@/i18n'; -import { definePageMetadata } from '@/scripts/page-metadata'; - -const stats = ref<any>({}); - -onMounted(() => { - os.api('users/stats', { - userId: $i!.id, - }).then(response => { - stats.value = response; - }); -}); - -const headerActions = $computed(() => []); - -const headerTabs = $computed(() => []); - -definePageMetadata({ - title: i18n.ts.accountInfo, - icon: 'ti ti-info-circle', -}); -</script> diff --git a/packages/frontend/src/pages/settings/apps.vue b/packages/frontend/src/pages/settings/apps.vue index fbb78200d46238a523644006878c39be78b8fff1..cadce49230825e544abd5cab2508340addeb5172 100644 --- a/packages/frontend/src/pages/settings/apps.vue +++ b/packages/frontend/src/pages/settings/apps.vue @@ -3,7 +3,7 @@ <FormPagination ref="list" :pagination="pagination"> <template #empty> <div class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.nothing }}</div> </div> </template> @@ -47,6 +47,7 @@ import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import MkKeyValue from '@/components/MkKeyValue.vue'; import MkButton from '@/components/MkButton.vue'; +import { infoImageUrl } from '@/instance'; const list = ref<any>(null); diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue index 3d0463f7080eba87ce524570850bbe73a2a9077d..e0785ab9fe66a8ec7cf94336e0031c9160441ed1 100644 --- a/packages/frontend/src/pages/settings/mute-block.vue +++ b/packages/frontend/src/pages/settings/mute-block.vue @@ -10,7 +10,7 @@ <MkPagination :pagination="renoteMutingPagination"> <template #empty> <div class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.noUsers }}</div> </div> </template> @@ -38,7 +38,7 @@ <MkPagination :pagination="mutingPagination"> <template #empty> <div class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.noUsers }}</div> </div> </template> @@ -68,7 +68,7 @@ <MkPagination :pagination="blockingPagination"> <template #empty> <div class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.noUsers }}</div> </div> </template> @@ -107,6 +107,7 @@ import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; import * as os from '@/os'; +import { infoImageUrl } from '@/instance'; let tab = $ref('renoteMute'); diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue index 0b73780a8bd7ec05e9a07278a85d5ef8899cbc6a..3d8bb59277a8fe023f241a31fe96861e126ee67c 100644 --- a/packages/frontend/src/pages/settings/other.vue +++ b/packages/frontend/src/pages/settings/other.vue @@ -26,8 +26,6 @@ <template #key>{{ i18n.ts.registeredDate }}</template> <template #value><MkTime :time="$i.createdAt" mode="detail"/></template> </MkKeyValue> - - <FormLink to="/settings/account-stats"><template #icon><i class="ti ti-info-circle"></i></template>{{ i18n.ts.statistics }}</FormLink> </div> </MkFolder> diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index 58217d047546dc8c71536c17442bfb1a67d51efb..81f0518a06e0f87703047fc66f3d663e44679b7a 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -127,7 +127,6 @@ const profile = reactive({ lang: $i.lang, isBot: $i.isBot, isCat: $i.isCat, - showTimelineReplies: $i.showTimelineReplies, }); watch(() => profile, () => { @@ -151,7 +150,7 @@ while (fields.value.length < 4) { addField(); } -function deleteField(index: number) { +function deleteField(index: number) { fields.value.splice(index, 1); } @@ -176,7 +175,6 @@ function save() { lang: profile.lang || null, isBot: !!profile.isBot, isCat: !!profile.isCat, - showTimelineReplies: !!profile.showTimelineReplies, }); claimAchievement('profileFilled'); if (profile.name === 'syuilo' || profile.name === 'ã—ã‚…ã„ã‚') { diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 6b11137d799158792be486f02a08ad087168ab92..fe9bc5938ea1086ee76c304fa16670df79d4d81c 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -177,10 +177,6 @@ export const routes = [{ path: '/accounts', name: 'profile', component: page(() => import('./pages/settings/accounts.vue')), - }, { - path: '/account-stats', - name: 'other', - component: page(() => import('./pages/settings/account-stats.vue')), }, { path: '/other', name: 'other', @@ -392,6 +388,10 @@ export const routes = [{ path: '/settings', name: 'settings', component: page(() => import('./pages/admin/settings.vue')), + }, { + path: '/branding', + name: 'branding', + component: page(() => import('./pages/admin/branding.vue')), }, { path: '/moderation', name: 'moderation', diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index c82873177334c738439c3086abc7d5583206489c..bd5d5beb840c9cb1e004046b54af9683cb25f7c6 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -254,6 +254,28 @@ async function deleteProfile() { } </script> +<style> +html, +body { + width: 100%; + height: 100%; + overflow: clip; + position: fixed; + top: 0; + left: 0; + overscroll-behavior: none; +} + +#misskey_app { + width: 100%; + height: 100%; + overflow: clip; + position: absolute; + top: 0; + left: 0; +} +</style> + <style lang="scss" module> .transition_menuDrawerBg_enterActive, .transition_menuDrawerBg_leaveActive { diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue index c376eb2b471d382f374b8cb54c0c0d2044f6f1e2..c8d6744a37b98f8ddd29e55ba201e52588fe9fdb 100644 --- a/packages/frontend/src/ui/deck/column.vue +++ b/packages/frontend/src/ui/deck/column.vue @@ -313,6 +313,7 @@ function onDrop(ev) { > .body { background: var(--bg) !important; + overflow-y: scroll !important; &::-webkit-scrollbar-track { background: inherit; diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue index c0da59a57d9d9be90e80e8419f6565fe89e15127..8abb20300ff2c5b5051ed78633fb8804d043d573 100644 --- a/packages/frontend/src/ui/universal.vue +++ b/packages/frontend/src/ui/universal.vue @@ -215,6 +215,28 @@ watch($$(navFooter), () => { }); </script> +<style> +html, +body { + width: 100%; + height: 100%; + overflow: clip; + position: fixed; + top: 0; + left: 0; + overscroll-behavior: none; +} + +#misskey_app { + width: 100%; + height: 100%; + overflow: clip; + position: absolute; + top: 0; + left: 0; +} +</style> + <style lang="scss" module> $ui-font-size: 1em; // TODO: ã©ã“ã‹ã«é›†ç´„ã—ãŸã„ $widgets-hide-threshold: 1090px; diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue index 1be882c66dfac5a6573a288a1b79f65811bab0cc..953ce2908dbcb75eb51ea4a509361206fc212e01 100644 --- a/packages/frontend/src/widgets/WidgetRss.vue +++ b/packages/frontend/src/widgets/WidgetRss.vue @@ -7,7 +7,7 @@ <div class="ekmkgxbj"> <MkLoading v-if="fetching"/> <div v-else-if="(!items || items.length === 0) && widgetProps.showHeader" class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.nothing }}</div> </div> <div v-else :class="$style.feed"> @@ -25,6 +25,7 @@ import MkContainer from '@/components/MkContainer.vue'; import { url as base } from '@/config'; import { i18n } from '@/i18n'; import { useInterval } from '@/scripts/use-interval'; +import { infoImageUrl } from '@/instance'; const name = 'rss'; diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 396187a4392e5e837fde88e4f5bf107f86d2daf2..ca8eee01a1e13788bbe6d72e50f36cb4b2eee9fd 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2147,10 +2147,6 @@ export type Endpoints = { }; }; }; - 'users/stats': { - req: TODO; - res: TODO; - }; }; declare namespace entities { @@ -2324,7 +2320,9 @@ type LiteInstanceMetadata = { themeColor: string | null; mascotImageUrl: string | null; bannerUrl: string | null; - errorImageUrl: string | null; + serverErrorImageUrl: string | null; + infoImageUrl: string | null; + notFoundImageUrl: string | null; iconUrl: string | null; backgroundImageUrl: string | null; logoImageUrl: string | null; diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts index cc88c4b1a401eacd418f3d0a75191f4936e6adda..b8c59e7b156ea43c0b191016d49b3a4406403714 100644 --- a/packages/misskey-js/src/api.types.ts +++ b/packages/misskey-js/src/api.types.ts @@ -602,5 +602,4 @@ export type Endpoints = { $default: UserDetailed; }; }; }; - 'users/stats': { req: TODO; res: TODO; }; }; diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index 04065c51c9ccb5956e8a497889920a5c085783d8..383b17f0b9735d49045897674bafc5c39e0c3054 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -294,7 +294,9 @@ export type LiteInstanceMetadata = { themeColor: string | null; mascotImageUrl: string | null; bannerUrl: string | null; - errorImageUrl: string | null; + serverErrorImageUrl: string | null; + infoImageUrl: string | null; + notFoundImageUrl: string | null; iconUrl: string | null; backgroundImageUrl: string | null; logoImageUrl: string | null;