diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e52f5cc8..3b6a1f6f66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,18 @@ -## Unreleased - -### General -- +## 2025.9.0 ### Client +- Enhance: AiScriptAppウィジェットで構文エラーを検知してもダイアログではなくウィジェット内にエラーを表示するように +- Enhance: /flushページでサイトキャッシュをクリアできるようになりました +- Enhance: クリップ/リスト/アンテナ/ロール追加系メニュー項目において、表示件数を拡張 +- Enhance: 「キャッシュを削除」ボタンでブラウザの内部キャッシュの削除も行えるように +- Enhance: Ctrlキー(Commandキー)を押下しながらリンクをクリックすると新しいタブで開くように +- Fix: プッシュ通知を有効にできない問題を修正 - Fix: RSSティッカーウィジェットが正しく動作しない問題を修正 +- Fix: プロファイルを復元後アカウントの切り替えができない問題を修正 - Fix: エラー画像が横に引き伸ばされてしまう問題に対応 ### Server -- - +- Fix: webpなどの画像に対してセンシティブなメディアの検出が適用されていなかった問題を修正 ## 2025.8.0 diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 4b6c8b97c3..63878bf1b7 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -1644,7 +1644,7 @@ _serverSettings: reactionsBufferingDescription: "Quan s'activa aquesta opció millora bastant el rendiment en recuperar les línies de temps reduint la càrrega de la base. Com a contrapunt, augmentarà l'ús de memòria de Redís. Desactiva aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes d'inestabilitat." remoteNotesCleaning: "Neteja automàtica de notes remotes" remoteNotesCleaning_description: "Quan activis aquesta opció, periòdicament es netejaran les notes remotes que no es consultin, això evitarà que la base de dades se" - remoteNotesCleaningMaxProcessingDuration: "D'oració màxima del temps de funcionament del procés de neteja" + remoteNotesCleaningMaxProcessingDuration: "Duració màxima del temps de funcionament del procés de neteja" remoteNotesCleaningExpiryDaysForEachNotes: "Duració mínima de conservació de les notes" inquiryUrl: "URL de consulta " inquiryUrlDescription: "Escriu adreça URL per al formulari de consulta per al mantenidor del servidor o una pàgina web amb el contacte d'informació." diff --git a/locales/es-ES.yml b/locales/es-ES.yml index ac983aae37..8a1d2c458b 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -2137,7 +2137,7 @@ _aboutMisskey: _displayOfSensitiveMedia: respect: "Esconder medios marcados como sensibles" ignore: "Mostrar medios marcados como sensibles" - force: "Esconder todala multimedia" + force: "Esconder toda la multimedia" _instanceTicker: none: "No mostrar" remote: "Mostrar a usuarios remotos" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index aea69f6f24..5e52143f83 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -1215,6 +1215,7 @@ privacyPolicyUrl: "Ссылка на Политику Конфиденциаль tosAndPrivacyPolicy: "Условия использования и политика конфиденциальности" avatarDecorations: "Украшения для аватара" attach: "Прикрепить" +detachAll: "Убрать всё" angle: "Угол" flip: "Переворот" showAvatarDecorations: "Показать украшения для аватара" @@ -1253,7 +1254,7 @@ clipNoteLimitExceeded: "К этому клипу больше нельзя до performance: "Производительность" modified: "Изменено" signinWithPasskey: "Войдите в систему, используя свой пароль" -unknownWebAuthnKey: "Не известный ключ " +unknownWebAuthnKey: "Неизвестный ключ" passkeyVerificationFailed: "Ошибка проверка ключа доступа " messageToFollower: "Сообщение подписчикам" testCaptchaWarning: "Эта функция предназначена для тестирования CAPTCHA. Не использовать это в рабочей среде" @@ -1268,8 +1269,11 @@ availableRoles: "Доступные роли" federationDisabled: "Федерация отключена для этого сервера. Вы не можете взаимодействовать с пользователями на других серверах." draft: "Черновик" markAsSensitiveConfirm: "Отметить контент как чувствительный?" +preferences: "Основное" resetToDefaultValue: "Сбросить настройки до стандартных" +syncBetweenDevices: "Синхронизировать между устройствами" postForm: "Форма отправки" +textCount: "Количество символов" information: "Описание" inMinutes: "мин" inDays: "сут" @@ -1281,6 +1285,11 @@ _chat: send: "Отправить" _settings: webhook: "Вебхук" + preferencesBanner: "Вы можете настроить общее поведение клиента по вашим предпочтениям" + timelineAndNote: "Лента и заметки" + _chat: + showSenderName: "Показывать имя отправителя" + sendOnEnter: "Использовать Enter для отправки" _delivery: stop: "Заморожено" _type: @@ -1529,7 +1538,7 @@ _achievements: description: "Нажато здесь" _justPlainLucky: title: "Чистая удача" - description: "Может достаться с вероятностью 0,01% каждые 10 секунд." + description: "Может достаться с вероятностью 0,005% каждые 10 секунд." _setNameToSyuilo: title: "Комплекс бога" description: "Установлено «syuilo» в качестве имени" @@ -1557,6 +1566,12 @@ _achievements: title: "Brain Diver" description: "Опубликована ссылка на песню «Brain Diver»" flavor: "Мисски-Мисски Ла-Ту-Ма" + _bubbleGameExplodingHead: + title: "🤯" + description: "Самый большой объект в Bubble game" + _bubbleGameDoubleExplodingHead: + title: "Двойной🤯" + description: "Два самых больших объекта в Bubble game одновременно!" _role: new: "Новая роль" edit: "Изменить роль" diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml index bde027ba69..5ca2b18fac 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -360,7 +360,7 @@ whenServerDisconnected: "Sunucu ile bağlantı kesildiğinde" disconnectedFromServer: "Sunucu bağlantısı kesildi" reload: "Yenile" doNothing: "Yoksay" -reloadConfirm: "Zaman çizelgesini yenilemek ister misin?" +reloadConfirm: "Panoyu yenilemek ister misin?" watch: "İzle" unwatch: "İzlemeyi bırak" accept: "Kabul et" @@ -573,9 +573,9 @@ objectStorageSetPublicRead: "Yükleme sırasında \"genel-okuma\" ayarını yap s3ForcePathStyleDesc: "s3ForcePathStyle etkinleştirilirse, kova adı URL'nin ana bilgisayar adı yerine URL yoluna eklenmelidir. Kendi kendine barındırılan bir Minio örneği gibi hizmetleri kullanırken bu ayarı etkinleştirmen gerekebilir." serverLogs: "Sunucu log kayıtları" deleteAll: "Tümünü sil" -showFixedPostForm: "Gönderi formunu zaman çizelgesinin en üstünde görüntüle" -showFixedPostFormInChannel: "Gönderi formunu zaman çizelgesinin en üstünde görüntüle (Kanallar)" -withRepliesByDefaultForNewlyFollowed: "Yeni takip edilen kullanıcıların yanıtlarını varsayılan olarak zaman çizelgesine dahil et" +showFixedPostForm: "Gönderi formunu pano üstünde görüntüle" +showFixedPostFormInChannel: "Gönderi formunu pano üstünde görüntüle (Kanallar)" +withRepliesByDefaultForNewlyFollowed: "Yeni takip edilen kullanıcıların yanıtlarını varsayılan olarak panoya dahil et" newNoteRecived: "Yeni Not'lar var" newNote: "Yeni Not" sounds: "Sesler" @@ -1059,7 +1059,7 @@ achievements: "Başarılar" gotInvalidResponseError: "Geçersiz sunucu yanıtı" gotInvalidResponseErrorDescription: "Sunucu erişilemez durumda olabilir veya bakım çalışması yapılmaktadır. Lütfen daha sonra tekrar dene." thisPostMayBeAnnoying: "Bu not başkalarını rahatsız edebilir." -thisPostMayBeAnnoyingHome: "Ana zaman çizelgesine gönder" +thisPostMayBeAnnoyingHome: "Ana panoya gönder" thisPostMayBeAnnoyingCancel: "İptal" thisPostMayBeAnnoyingIgnore: "Yine de gönder" collapseRenotes: "Daha önce görüntülenen Renote'lari kısaltılmış olarak göster" @@ -1218,8 +1218,8 @@ showRepliesToOthersInTimeline: "Pano'da diğer kişilere verilen yanıtları gö hideRepliesToOthersInTimeline: "Pano'dan diğer kişilerin yanıtlarını gizle" showRepliesToOthersInTimelineAll: "Pano'da takip ettiğin herkesin diğerlerine verdiği yanıtları göster" hideRepliesToOthersInTimelineAll: "Pano'da takip ettiğin herkesten diğer kişilere verilen yanıtları gizle" -confirmShowRepliesAll: "Bu işlem geri alınamaz. Takip ettiğin herkesin yanıtlarını zaman çizelgende diğer kullanıcılara göstermek istiyor musun?" -confirmHideRepliesAll: "Bu işlem geri alınamaz. Şu anda takip ettiğin tüm kullanıcıların yanıtlarını zaman tünelinde cidden göstermeyecek misin?" +confirmShowRepliesAll: "Bu işlem geri alınamaz. Takip ettiğin herkesin yanıtlarını panoda diğer kullanıcılara göstermek istiyor musun?" +confirmHideRepliesAll: "Bu işlem geri alınamaz. Şu anda takip ettiğin tüm kullanıcıların yanıtlarını panoda cidden göstermeyecek misin?" externalServices: "Dış Hizmetler" sourceCode: "Kaynak kodu" sourceCodeIsNotYetProvided: "Kaynak kodu henüz mevcut değildir. Bu sorunu gidermek için yöneticiyle iletişime geçin." @@ -1570,9 +1570,9 @@ _initialTutorial: description: "Burada, Misskey'i kullanmanın temellerini ve özelliklerini öğrenebilirsin." _note: title: "Not nedir?" - description: "Misskey'deki gönderiler “Notlar” olarak adlandırılır. Notlar zaman çizelgesinde kronolojik olarak düzenlenir ve gerçek zamanlı olarak güncellenir." + description: "Misskey'deki gönderiler “Notlar” olarak adlandırılır. Notlar panoda kronolojik olarak düzenlenir ve gerçek zamanlı olarak güncellenir." reply: "Bir mesaja yanıt vermek için bu düğmeye tıklayın. Yanıtlara yanıt vermek de mümkündür, böylece konuşma bir konu başlığı gibi devam eder." - renote: "Bu notu kendi zaman çizelgende paylaşabilirsiniz. Ayrıca yorumlarınızla birlikte alıntı da yapabilirsin." + renote: "Bu notu kendi panonda paylaşabilirsin. Ayrıca yorumlarınla birlikte alıntı da yapabilirsin." reaction: "Not'a tepkiler ekleyebilirsin. Daha fazla ayrıntı bir sonraki sayfada açıklanacak." menu: "Not ayrıntılarını görüntüleyebilir, bağlantıları kopyalayabilir ve çeşitli diğer işlemleri gerçekleştirebilirsin." _reaction: @@ -1640,7 +1640,7 @@ _serverSettings: shortNameDescription: "Resmi adın uzun olması durumunda görüntülenebilen, örneğin adının kısaltması." fanoutTimelineDescription: "Etkinleştirildiğinde Pano alma performansını büyük ölçüde artırır ve veritabanı yükünü azaltır. Bunun karşılığında Redis'in bellek kullanımı artacaktır. Sunucu belleği düşükse veya sunucu kararsızsa bunu devre dışı bırakmayı düşün." fanoutTimelineDbFallback: "Veritabanına geri dön" - fanoutTimelineDbFallbackDescription: "Etkinleştirildiğinde, Pano önbelleğe alınmamışsa ek sorgular için veritabanına geri döner. Bu özelliği devre dışı bırakmak, geri dönüş sürecini ortadan kaldırarak sunucu yükünü daha da azaltır, ancak alınabilecek zaman çizelgelerinin aralığını sınırlar." + fanoutTimelineDbFallbackDescription: "Etkinleştirildiğinde, Pano önbelleğe alınmamışsa ek sorgular için veritabanına geri döner. Bu özelliği devre dışı bırakmak, geri dönüş sürecini ortadan kaldırarak sunucu yükünü daha da azaltır, ancak alınabilecek panoların aralığını sınırlar." reactionsBufferingDescription: "Etkinleştirildiğinde, reaksiyon oluşturma sırasında performans büyük ölçüde artacak ve veritabanı üzerindeki yük azalacaktır. Ancak, Redis bellek kullanımı artacakt." remoteNotesCleaning: "Uzak notların otomatik olarak temizlenmesi" remoteNotesCleaning_description: "Etkinleştirildiğinde, kullanılmayan ve güncelliğini yitirmiş uzak notlar, veritabanının şişmesini önlemek için periyodik olarak temizlenecek." @@ -1668,6 +1668,7 @@ _serverSettings: restartServerSetupWizardConfirm_text: "Bazı mevcut ayarlar sıfırlanacaktır." entrancePageStyle: "Giriş sayfası stili" showTimelineForVisitor: "Panoyu göster" + showActivitiesForVisitor: "Aktiviteleri göster" _userGeneratedContentsVisibilityForVisitor: all: "Her şey halka açıktır." localOnly: "Yalnızca yerel içerik yayınlanır, uzak içerik gizli tutulur." @@ -1876,7 +1877,7 @@ _achievements: title: "Öz Referans" description: "Kendi notunuzu alıntı yapın" _htl20npm: - title: "Akış Zaman Çizelgesi" + title: "Akış Panosu" description: "Ev zaman çizelgenizin hızı 20 npm'yi (dakika başına not sayısı) aşıyor mu?" _viewInstanceChart: title: "Analist" @@ -1965,7 +1966,7 @@ _role: asBadge: "Rozet olarak göster" descriptionOfAsBadge: "This role's icon will be displayed next to the username of users with this role if turned on." isExplorable: "Rolü keşfedilebilir hale getir" - descriptionOfIsExplorable: "Bu rolün zaman çizelgesi ve bu role sahip kullanıcıların listesi, etkinleştirilirse kamuya açık hale getirilecek." + descriptionOfIsExplorable: "Bu rolün panosu ve bu role sahip kullanıcıların listesi, etkinleştirilirse kamuya açık hale getirilecek." displayOrder: "Pozisyon" descriptionOfDisplayOrder: "Sayı ne kadar yüksekse, UI pozisyonu da o kadar yüksek olur." preserveAssignmentOnMoveAccount: "Geçiş sırasında rol atamalarını koruyun" @@ -1979,7 +1980,7 @@ _role: high: "Yüksek" _options: gtlAvailable: "Global Pano'yu görüntüleyebilir" - ltlAvailable: "Yerel zaman çizelgesini görüntüleyebilir" + ltlAvailable: "Yerel panoyu görüntüleyebilir" canPublicNote: "Halka açık notlar gönderebilir" mentionMax: "Bir notta maksimum bahsetme sayısı" canInvite: "Sunucu davet kodları oluşturabilir" @@ -2484,7 +2485,7 @@ _visibility: public: "Halka açık" publicDescription: "Notunuz tüm kullanıcılar tarafından görülebilir olacaktır." home: "Pano" - homeDescription: "Yalnızca ana zaman çizelgesine gönder" + homeDescription: "Yalnızca ana panoya gönder" followers: "Takipçiler" followersDescription: "Sadece takipçilerine görünür hale getir" specified: "Doğrudan" @@ -2531,7 +2532,7 @@ _exportOrImport: userLists: "Kullanıcı listeleri" excludeMutingUsers: "Sessize alınan kullanıcıları hariç tut" excludeInactiveUsers: "Etkin olmayan kullanıcıları hariç tut" - withReplies: "İçe aktarılan kullanıcıların yanıtlarını zaman çizelgesine dahil edin" + withReplies: "İçe aktarılan kullanıcıların yanıtlarını panoya dahil edin" _charts: federation: "Federasyon" apRequest: "Talepler" @@ -2925,7 +2926,7 @@ _reversi: freeMatch: "Ücretsiz Eşleştirme" lookingForPlayer: "Rakip aranıyor..." gameCanceled: "Oyun iptal edildi." - shareToTlTheGameWhenStart: "Oyun başlatıldığında zaman çizelgesinde paylaş" + shareToTlTheGameWhenStart: "Oyun başlatıldığında panoda paylaş" iStartedAGame: "Oyun başladı! #MisskeyReversi" opponentHasSettingsChanged: "Rakip ayarlarını değiştirmiş." allowIrregularRules: "Düzensiz kurallar (tamamen ücretsiz)" @@ -3153,7 +3154,7 @@ _clientPerformanceIssueTip: _clip: tip: "Klip, notları gruplandırmanıza olanak tanıyan bir özelliktir." _userLists: - tip: "Listeler, oluşturulurken belirttiğin herhangi bir kullanıcıyı içerebilir. Oluşturulan liste, yalnızca belirtilen kullanıcıları gösteren bir zaman çizelgesi olarak görüntülenebilir." + tip: "Listeler, oluşturulurken belirttiğin herhangi bir kullanıcıyı içerebilir. Oluşturulan liste, yalnızca belirtilen kullanıcıları gösteren bir pano olarak görüntülenebilir." watermark: "Filigran" defaultPreset: "Varsayılan Ön Ayar" _watermarkEditor: diff --git a/package.json b/package.json index 328397e710..80148d75f4 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "misskey", - "version": "2025.8.0", + "version": "2025.9.0-beta.0", "codename": "nasubi", "repository": { "type": "git", "url": "https://github.com/misskey-dev/misskey.git" }, - "packageManager": "pnpm@10.15.0", + "packageManager": "pnpm@10.15.1", "workspaces": [ "packages/frontend-shared", "packages/frontend", @@ -62,22 +62,22 @@ "js-yaml": "4.1.0", "postcss": "8.5.6", "tar": "7.4.3", - "terser": "5.43.1", + "terser": "5.44.0", "typescript": "5.9.2" }, "devDependencies": { "@misskey-dev/eslint-plugin": "2.1.0", "@types/js-yaml": "4.0.9", - "@types/node": "22.17.2", - "@typescript-eslint/eslint-plugin": "8.40.0", - "@typescript-eslint/parser": "8.40.0", + "@types/node": "22.18.1", + "@typescript-eslint/eslint-plugin": "8.42.0", + "@typescript-eslint/parser": "8.42.0", "cross-env": "7.0.3", "cypress": "14.5.4", - "eslint": "9.34.0", + "eslint": "9.35.0", "globals": "16.3.0", "ncp": "2.0.0", - "pnpm": "10.15.0", - "start-server-and-test": "2.0.13" + "pnpm": "10.15.1", + "start-server-and-test": "2.1.0" }, "optionalDependencies": { "@tensorflow/tfjs-core": "4.22.0" diff --git a/packages/backend/package.json b/packages/backend/package.json index 89e04f4da4..5114912769 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -39,17 +39,17 @@ }, "optionalDependencies": { "@swc/core-android-arm64": "1.3.11", - "@swc/core-darwin-arm64": "1.13.4", - "@swc/core-darwin-x64": "1.13.4", + "@swc/core-darwin-arm64": "1.13.5", + "@swc/core-darwin-x64": "1.13.5", "@swc/core-freebsd-x64": "1.3.11", - "@swc/core-linux-arm-gnueabihf": "1.13.4", - "@swc/core-linux-arm64-gnu": "1.13.4", - "@swc/core-linux-arm64-musl": "1.13.4", - "@swc/core-linux-x64-gnu": "1.13.4", - "@swc/core-linux-x64-musl": "1.13.4", - "@swc/core-win32-arm64-msvc": "1.13.4", - "@swc/core-win32-ia32-msvc": "1.13.4", - "@swc/core-win32-x64-msvc": "1.13.4", + "@swc/core-linux-arm-gnueabihf": "1.13.5", + "@swc/core-linux-arm64-gnu": "1.13.5", + "@swc/core-linux-arm64-musl": "1.13.5", + "@swc/core-linux-x64-gnu": "1.13.5", + "@swc/core-linux-x64-musl": "1.13.5", + "@swc/core-win32-arm64-msvc": "1.13.5", + "@swc/core-win32-ia32-msvc": "1.13.5", + "@swc/core-win32-x64-msvc": "1.13.5", "@tensorflow/tfjs": "4.22.0", "@tensorflow/tfjs-node": "4.22.0", "bufferutil": "4.0.9", @@ -69,20 +69,20 @@ "utf-8-validate": "6.0.5" }, "dependencies": { - "@aws-sdk/client-s3": "3.873.0", - "@aws-sdk/lib-storage": "3.873.0", + "@aws-sdk/client-s3": "3.883.0", + "@aws-sdk/lib-storage": "3.883.0", "@discordapp/twemoji": "16.0.1", "@fastify/accepts": "5.0.2", "@fastify/cookie": "11.0.2", "@fastify/cors": "10.1.0", "@fastify/express": "4.0.2", "@fastify/http-proxy": "10.0.2", - "@fastify/multipart": "9.0.3", + "@fastify/multipart": "9.2.1", "@fastify/static": "8.2.0", "@fastify/view": "10.0.2", "@misskey-dev/sharp-read-bmp": "1.2.0", "@misskey-dev/summaly": "5.2.3", - "@napi-rs/canvas": "0.1.77", + "@napi-rs/canvas": "0.1.79", "@nestjs/common": "11.1.6", "@nestjs/core": "11.1.6", "@nestjs/testing": "11.1.6", @@ -93,7 +93,7 @@ "@sinonjs/fake-timers": "11.3.1", "@smithy/node-http-handler": "2.5.0", "@swc/cli": "0.7.8", - "@swc/core": "1.13.4", + "@swc/core": "1.13.5", "@twemoji/parser": "16.0.0", "@types/redis-info": "3.0.3", "accepts": "1.3.8", @@ -103,7 +103,7 @@ "bcryptjs": "2.4.3", "blurhash": "2.0.5", "body-parser": "1.20.3", - "bullmq": "5.58.1", + "bullmq": "5.58.5", "cacheable-lookup": "7.0.0", "cbor": "9.0.2", "chalk": "5.6.0", @@ -114,13 +114,13 @@ "content-disposition": "0.5.4", "date-fns": "2.30.0", "deep-email-validator": "0.1.21", - "fastify": "5.5.0", + "fastify": "5.6.0", "fastify-raw-body": "5.0.0", "feed": "4.2.2", "file-type": "19.6.0", "fluent-ffmpeg": "2.1.3", "form-data": "4.0.4", - "got": "14.4.7", + "got": "14.4.8", "happy-dom": "16.8.1", "hpagent": "1.2.0", "htmlescape": "1.1.1", @@ -141,7 +141,7 @@ "mime-types": "2.1.35", "misskey-js": "workspace:*", "misskey-reversi": "workspace:*", - "ms": "3.0.0-canary.1", + "ms": "3.0.0-canary.202508261828", "nanoid": "5.1.5", "nested-property": "4.0.0", "node-fetch": "3.3.2", @@ -175,7 +175,7 @@ "slacc": "0.0.10", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", - "systeminformation": "5.27.7", + "systeminformation": "5.27.8", "tinycolor2": "1.6.0", "tmp": "0.2.5", "tsc-alias": "1.8.16", @@ -210,7 +210,7 @@ "@types/jsrsasign": "10.5.15", "@types/mime-types": "2.1.4", "@types/ms": "0.7.34", - "@types/node": "22.17.2", + "@types/node": "22.18.1", "@types/nodemailer": "6.4.19", "@types/oauth": "0.9.6", "@types/oauth2orize": "1.11.5", @@ -222,7 +222,7 @@ "@types/ratelimiter": "3.4.6", "@types/rename": "1.0.7", "@types/sanitize-html": "2.16.0", - "@types/semver": "7.7.0", + "@types/semver": "7.7.1", "@types/simple-oauth2": "5.0.7", "@types/sinonjs__fake-timers": "8.1.5", "@types/supertest": "6.0.3", @@ -231,8 +231,8 @@ "@types/vary": "1.1.3", "@types/web-push": "3.6.4", "@types/ws": "8.18.1", - "@typescript-eslint/eslint-plugin": "8.40.0", - "@typescript-eslint/parser": "8.40.0", + "@typescript-eslint/eslint-plugin": "8.42.0", + "@typescript-eslint/parser": "8.42.0", "aws-sdk-client-mock": "4.1.0", "cross-env": "7.0.3", "eslint-plugin-import": "2.32.0", diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts index 248a9b8979..23ab8082ed 100644 --- a/packages/backend/src/core/AiService.ts +++ b/packages/backend/src/core/AiService.ts @@ -29,7 +29,7 @@ export class AiService { } @bindThis - public async detectSensitive(path: string): Promise { + public async detectSensitive(source: string | Buffer): Promise { try { if (isSupportedCpu === undefined) { isSupportedCpu = await this.computeIsSupportedCpu(); @@ -51,7 +51,7 @@ export class AiService { }); } - const buffer = await fs.promises.readFile(path); + const buffer = source instanceof Buffer ? source : await fs.promises.readFile(source); const image = await tf.node.decodeImage(buffer, 3) as any; try { const predictions = await this.model.classify(image); diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts index 6250d4d3a1..62a7d24afb 100644 --- a/packages/backend/src/core/FileInfoService.ts +++ b/packages/backend/src/core/FileInfoService.ts @@ -21,6 +21,7 @@ import { LoggerService } from '@/core/LoggerService.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import type { PredictionType } from 'nsfwjs'; +import { isMimeImage } from '@/misc/is-mime-image.js'; export type FileInfo = { size: number; @@ -204,16 +205,7 @@ export class FileInfoService { return [sensitive, porn]; } - if ([ - 'image/jpeg', - 'image/png', - 'image/webp', - ].includes(mime)) { - const result = await this.aiService.detectSensitive(source); - if (result) { - [sensitive, porn] = judgePrediction(result); - } - } else if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) { + if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) { const [outDir, disposeOutDir] = await createTempDir(); try { const command = FFmpeg() @@ -281,6 +273,23 @@ export class FileInfoService { } finally { disposeOutDir(); } + } else if (isMimeImage(mime, 'sharp-convertible-image-with-bmp')) { + /* + * tfjs-node は限られた画像形式しか受け付けないため、sharp で PNG に変換する + * せっかくなので内部処理で使われる最大サイズの299x299に事前にリサイズする + */ + const png = await (await sharpBmp(source, mime)) + .resize(299, 299, { + withoutEnlargement: false, + }) + .rotate() + .flatten({ background: { r: 119, g: 119, b: 119 } }) // 透過部分を18%グレーで塗りつぶす + .png() + .toBuffer(); + const result = await this.aiService.detectSensitive(png); + if (result) { + [sensitive, porn] = judgePrediction(result); + } } return [sensitive, porn]; diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 0f225a8242..2d0e7b5d83 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -756,8 +756,8 @@ export class QueueService { @bindThis public async queueRetryJob(queueType: typeof QUEUE_TYPES[number], jobId: string) { const queue = this.getQueue(queueType); - const job: Bull.Job | null = await queue.getJob(jobId); - if (job) { + const job = await queue.getJob(jobId); + if (job != null) { if (job.finishedOn != null) { await job.retry(); } else { @@ -769,8 +769,8 @@ export class QueueService { @bindThis public async queueRemoveJob(queueType: typeof QUEUE_TYPES[number], jobId: string) { const queue = this.getQueue(queueType); - const job: Bull.Job | null = await queue.getJob(jobId); - if (job) { + const job = await queue.getJob(jobId); + if (job != null) { await job.remove(); } } @@ -803,8 +803,8 @@ export class QueueService { @bindThis public async queueGetJob(queueType: typeof QUEUE_TYPES[number], jobId: string) { const queue = this.getQueue(queueType); - const job: Bull.Job | null = await queue.getJob(jobId); - if (job) { + const job = await queue.getJob(jobId); + if (job != null) { return this.packJobData(job); } else { throw new Error(`Job not found: ${jobId}`); diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index 32818003ad..57d74ef2b1 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -176,6 +176,17 @@ export class ApiServerService { } }); + fastify.all('/clear-browser-cache', (request, reply) => { + if (['GET', 'POST'].includes(request.method)) { + reply.header('Clear-Site-Data', '"cache", "prefetchCache", "prerenderCache", "executionContexts"'); + reply.code(204); + reply.send(); + } else { + reply.code(405); + reply.send(); + } + }); + // Make sure any unknown path under /api returns HTTP 404 Not Found, // because otherwise ClientServerService will return the base client HTML // page with HTTP 200. diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index b515a0c0c8..3cd83efa1a 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -201,6 +201,8 @@ export class ClientServerService { @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { + const configUrl = new URL(this.config.url); + fastify.register(fastifyView, { root: _dirname + '/views', engine: { @@ -239,7 +241,6 @@ export class ClientServerService { done(); }); } else { - const configUrl = new URL(this.config.url); const urlOriginWithoutPort = configUrl.origin.replace(/:\d+$/, ''); const port = (process.env.VITE_PORT ?? '5173'); @@ -887,6 +888,22 @@ export class ClientServerService { [, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); fastify.get('/flush', async (request, reply) => { + let sendHeader = true; + + if (request.headers['origin']) { + const originURL = new URL(request.headers['origin']); + if (originURL.protocol !== 'https:') { // Clear-Site-Data only supports https + sendHeader = false; + } + if (originURL.host !== configUrl.host) { + sendHeader = false; + } + } + + if (sendHeader) { + reply.header('Clear-Site-Data', '"*"'); + } + reply.header('Set-Cookie', 'http-flush-failed=1; Path=/flush; Max-Age=60'); return await reply.view('flush'); }); diff --git a/packages/backend/src/server/web/views/flush.pug b/packages/backend/src/server/web/views/flush.pug index a73a45212f..7884495d08 100644 --- a/packages/backend/src/server/web/views/flush.pug +++ b/packages/backend/src/server/web/views/flush.pug @@ -6,41 +6,45 @@ html const msg = document.getElementById('msg'); const successText = `\nSuccess Flush! Back to Misskey\n成功しました。Misskeyを開き直してください。`; - message('Start flushing.'); + if (!document.cookie) { + message('Your site data is fully cleared by your browser.'); + message(successText); + } else { + message('Your browser does not support Clear-Site-Data header. Start opportunistic flushing.'); + (async function() { + try { + localStorage.clear(); + message('localStorage cleared.'); - (async function() { - try { - localStorage.clear(); - message('localStorage cleared.'); + const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => { + const delidb = indexedDB.deleteDatabase(name); + delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`)); + delidb.onerror = e => rej(e) + })); - const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => { - const delidb = indexedDB.deleteDatabase(name); - delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`)); - delidb.onerror = e => rej(e) - })); + await Promise.all(idbPromises); - await Promise.all(idbPromises); + if (navigator.serviceWorker.controller) { + navigator.serviceWorker.controller.postMessage('clear'); + await navigator.serviceWorker.getRegistrations() + .then(registrations => { + return Promise.all(registrations.map(registration => registration.unregister())); + }) + .catch(e => { throw new Error(e) }); + } - if (navigator.serviceWorker.controller) { - navigator.serviceWorker.controller.postMessage('clear'); - await navigator.serviceWorker.getRegistrations() - .then(registrations => { - return Promise.all(registrations.map(registration => registration.unregister())); - }) - .catch(e => { throw new Error(e) }); + message(successText); + } catch (e) { + message(`\n${e}\n\nFlush Failed. Please retry.\n失敗しました。もう一度試してみてください。`); + message(`\nIf you retry more than 3 times, try manually clearing the browser cache or contact to instance admin.\n3回以上試しても失敗する場合、ブラウザのキャッシュを手動で消去し、それでもだめならインスタンス管理者に連絡してみてください。\n`) + + console.error(e); + setTimeout(() => { + location = '/'; + }, 10000) } - - message(successText); - } catch (e) { - message(`\n${e}\n\nFlush Failed. Please retry.\n失敗しました。もう一度試してみてください。`); - message(`\nIf you retry more than 3 times, clear the browser cache or contact to instance admin.\n3回以上試しても失敗する場合、ブラウザのキャッシュを消去し、それでもだめならインスタンス管理者に連絡してみてください。\n`) - - console.error(e); - setTimeout(() => { - location = '/'; - }, 10000) - } - })(); + })(); + } function message(text) { msg.insertAdjacentHTML('beforeend', `

[${(new Date()).toString()}] ${text.replace(/\n/g,'
')}

`) diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json index 73bcd798f0..2f5a3fc369 100644 --- a/packages/frontend-embed/package.json +++ b/packages/frontend-embed/package.json @@ -13,10 +13,10 @@ "@discordapp/twemoji": "16.0.1", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "6.0.2", - "@rollup/pluginutils": "5.2.0", + "@rollup/pluginutils": "5.3.0", "@twemoji/parser": "16.0.0", "@vitejs/plugin-vue": "6.0.1", - "@vue/compiler-sfc": "3.5.19", + "@vue/compiler-sfc": "3.5.21", "astring": "1.9.0", "buraha": "0.0.1", "estree-walker": "3.0.3", @@ -26,16 +26,16 @@ "mfm-js": "0.25.0", "misskey-js": "workspace:*", "punycode.js": "2.3.1", - "rollup": "4.48.0", - "sass": "1.90.0", - "shiki": "3.11.0", + "rollup": "4.50.1", + "sass": "1.92.1", + "shiki": "3.12.2", "tinycolor2": "1.6.0", "tsc-alias": "1.8.16", "tsconfig-paths": "4.2.0", "typescript": "5.9.2", "uuid": "11.1.0", - "vite": "7.1.3", - "vue": "3.5.19" + "vite": "7.1.4", + "vue": "3.5.21" }, "devDependencies": { "@misskey-dev/summaly": "5.2.3", @@ -43,14 +43,14 @@ "@testing-library/vue": "8.1.0", "@types/estree": "1.0.8", "@types/micromatch": "4.0.9", - "@types/node": "22.17.2", + "@types/node": "22.18.1", "@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/tinycolor2": "1.4.6", "@types/ws": "8.18.1", - "@typescript-eslint/eslint-plugin": "8.40.0", - "@typescript-eslint/parser": "8.40.0", + "@typescript-eslint/eslint-plugin": "8.42.0", + "@typescript-eslint/parser": "8.42.0", "@vitest/coverage-v8": "3.2.4", - "@vue/runtime-core": "3.5.19", + "@vue/runtime-core": "3.5.21", "acorn": "8.15.0", "cross-env": "10.0.0", "eslint-plugin-import": "2.32.0", @@ -59,11 +59,11 @@ "happy-dom": "18.0.1", "intersection-observer": "0.12.2", "micromatch": "4.0.8", - "msw": "2.10.5", + "msw": "2.11.1", "nodemon": "3.1.10", "prettier": "3.6.2", - "start-server-and-test": "2.0.13", - "tsx": "4.20.4", + "start-server-and-test": "2.1.0", + "tsx": "4.20.5", "vite-plugin-turbosnap": "1.0.3", "vue-component-type-helpers": "3.0.6", "vue-eslint-parser": "10.2.0", diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json index ea2d66a7c7..aebc418e3c 100644 --- a/packages/frontend-shared/package.json +++ b/packages/frontend-shared/package.json @@ -21,9 +21,9 @@ "lint": "pnpm typecheck && pnpm eslint" }, "devDependencies": { - "@types/node": "22.17.2", - "@typescript-eslint/eslint-plugin": "8.40.0", - "@typescript-eslint/parser": "8.40.0", + "@types/node": "22.18.1", + "@typescript-eslint/eslint-plugin": "8.42.0", + "@typescript-eslint/parser": "8.42.0", "esbuild": "0.25.9", "eslint-plugin-vue": "10.4.0", "nodemon": "3.1.10", @@ -35,6 +35,6 @@ ], "dependencies": { "misskey-js": "workspace:*", - "vue": "3.5.19" + "vue": "3.5.21" } } diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 8789c65fa0..07831f8bb3 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -23,13 +23,13 @@ "@misskey-dev/browser-image-resizer": "2024.1.0", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "6.0.2", - "@rollup/pluginutils": "5.2.0", - "@sentry/vue": "10.5.0", + "@rollup/pluginutils": "5.3.0", + "@sentry/vue": "10.10.0", "@syuilo/aiscript": "1.1.0", "@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0", "@twemoji/parser": "16.0.0", "@vitejs/plugin-vue": "6.0.1", - "@vue/compiler-sfc": "3.5.19", + "@vue/compiler-sfc": "3.5.21", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", "analytics": "0.8.19", "astring": "1.9.0", @@ -41,7 +41,7 @@ "chartjs-chart-matrix": "3.0.0", "chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-zoom": "2.2.0", - "chromatic": "13.1.3", + "chromatic": "13.1.4", "compare-versions": "6.1.1", "cropperjs": "2.0.1", "date-fns": "4.1.0", @@ -65,21 +65,21 @@ "punycode.js": "2.3.1", "qr-code-styling": "1.9.2", "qr-scanner": "1.4.2", - "rollup": "4.48.0", + "rollup": "4.50.1", "sanitize-html": "2.17.0", - "sass": "1.90.0", - "shiki": "3.11.0", + "sass": "1.92.1", + "shiki": "3.12.2", "strict-event-emitter-types": "2.0.0", "textarea-caret": "3.1.0", - "three": "0.179.1", + "three": "0.180.0", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", "tsc-alias": "1.8.16", "tsconfig-paths": "4.2.0", "typescript": "5.9.2", "v-code-diff": "1.13.1", - "vite": "7.1.3", - "vue": "3.5.19", + "vite": "7.1.4", + "vue": "3.5.21", "vuedraggable": "next", "wanakana": "5.3.1" }, @@ -87,7 +87,7 @@ "@misskey-dev/summaly": "5.2.3", "@storybook/addon-essentials": "8.6.14", "@storybook/addon-interactions": "8.6.14", - "@storybook/addon-links": "9.1.3", + "@storybook/addon-links": "9.1.5", "@storybook/addon-mdx-gfm": "8.6.14", "@storybook/addon-storysource": "8.6.14", "@storybook/blocks": "8.6.14", @@ -95,31 +95,31 @@ "@storybook/core-events": "8.6.14", "@storybook/manager-api": "8.6.14", "@storybook/preview-api": "8.6.14", - "@storybook/react": "9.1.3", - "@storybook/react-vite": "9.1.3", + "@storybook/react": "9.1.5", + "@storybook/react-vite": "9.1.5", "@storybook/test": "8.6.14", "@storybook/theming": "8.6.14", "@storybook/types": "8.6.14", - "@storybook/vue3": "9.1.3", - "@storybook/vue3-vite": "9.1.3", + "@storybook/vue3": "9.1.5", + "@storybook/vue3-vite": "9.1.5", "@tabler/icons-webfont": "3.34.1", "@testing-library/vue": "8.1.0", "@types/canvas-confetti": "1.9.0", "@types/estree": "1.0.8", "@types/matter-js": "0.20.0", "@types/micromatch": "4.0.9", - "@types/node": "22.17.2", + "@types/node": "22.18.1", "@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/sanitize-html": "2.16.0", "@types/seedrandom": "3.0.8", "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/ws": "8.18.1", - "@typescript-eslint/eslint-plugin": "8.40.0", - "@typescript-eslint/parser": "8.40.0", + "@typescript-eslint/eslint-plugin": "8.42.0", + "@typescript-eslint/parser": "8.42.0", "@vitest/coverage-v8": "3.2.4", - "@vue/compiler-core": "3.5.19", - "@vue/runtime-core": "3.5.19", + "@vue/compiler-core": "3.5.21", + "@vue/runtime-core": "3.5.21", "acorn": "8.15.0", "cross-env": "10.0.0", "cypress": "14.5.4", @@ -130,17 +130,17 @@ "intersection-observer": "0.12.2", "micromatch": "4.0.8", "minimatch": "10.0.3", - "msw": "2.10.5", + "msw": "2.11.1", "msw-storybook-addon": "2.0.5", "nodemon": "3.1.10", "prettier": "3.6.2", "react": "19.1.1", "react-dom": "19.1.1", "seedrandom": "3.0.5", - "start-server-and-test": "2.0.13", - "storybook": "9.1.3", + "start-server-and-test": "2.1.0", + "storybook": "9.1.5", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", - "tsx": "4.20.4", + "tsx": "4.20.5", "vite-plugin-turbosnap": "1.0.3", "vitest": "3.2.4", "vitest-fetch-mock": "0.4.5", diff --git a/packages/frontend/src/accounts.ts b/packages/frontend/src/accounts.ts index afa2ecb911..60f7cd0b4b 100644 --- a/packages/frontend/src/accounts.ts +++ b/packages/frontend/src/accounts.ts @@ -251,13 +251,30 @@ export async function openAccountMenu(opts: { } }, }; - } else { + } else { // プロファイルを復元した場合などはアカウントのトークンや詳細情報はstoreにキャッシュされていない return { type: 'button' as const, text: username, active: opts.active != null ? opts.active === id : false, action: async () => { - // TODO + const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), { + initialUsername: username, + }, { + done: async (res: Misskey.entities.SigninFlowResponse & { finished: true }) => { + store.set('accountTokens', { ...store.s.accountTokens, [host + '/' + res.id]: res.i }); + + if (callback) { + fetchAccount(res.i, id).then(account => { + callback(account); + }); + } else { + switchAccount(host, id); + } + }, + closed: () => { + dispose(); + }, + }); }, }; } diff --git a/packages/frontend/src/cache.ts b/packages/frontend/src/cache.ts index 70078b410d..39cf73feb8 100644 --- a/packages/frontend/src/cache.ts +++ b/packages/frontend/src/cache.ts @@ -7,8 +7,8 @@ import * as Misskey from 'misskey-js'; import { Cache } from '@/utility/cache.js'; import { misskeyApi } from '@/utility/misskey-api.js'; -export const clipsCache = new Cache(1000 * 60 * 30, () => misskeyApi('clips/list')); -export const rolesCache = new Cache(1000 * 60 * 30, () => misskeyApi('admin/roles/list')); +export const clipsCache = new Cache(1000 * 60 * 30, () => misskeyApi('clips/list', { limit: 30 })); +export const rolesCache = new Cache(1000 * 60 * 30, () => misskeyApi('admin/roles/list', { limit: 30 })); export const userListsCache = new Cache(1000 * 60 * 30, () => misskeyApi('users/lists/list')); -export const antennasCache = new Cache(1000 * 60 * 30, () => misskeyApi('antennas/list')); +export const antennasCache = new Cache(1000 * 60 * 30, () => misskeyApi('antennas/list', { limit: 30 })); export const favoritedChannelsCache = new Cache(1000 * 60 * 30, () => misskeyApi('channels/my-favorites', { limit: 100 })); diff --git a/packages/frontend/src/components/MkPushNotificationAllowButton.vue b/packages/frontend/src/components/MkPushNotificationAllowButton.vue index c651d3a3f5..9c37eb5e72 100644 --- a/packages/frontend/src/components/MkPushNotificationAllowButton.vue +++ b/packages/frontend/src/components/MkPushNotificationAllowButton.vue @@ -78,7 +78,7 @@ function subscribe() { // SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters return promiseDialog(registration.value.pushManager.subscribe({ userVisibleOnly: true, - applicationServerKey: urlBase64ToBase64(instance.swPublickey), + applicationServerKey: urlBase64ToUint8Array(instance.swPublickey), }) .then(async subscription => { pushSubscription.value = subscription; @@ -131,16 +131,22 @@ function encode(buffer: ArrayBuffer | null) { } /** - * Convert the URL safe base64 string to a base64 string + * Convert the URL safe base64 string to a Uint8Array * @param base64String base64 string */ -function urlBase64ToBase64(base64String: string): string { +function urlBase64ToUint8Array(base64String: string): Uint8Array { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/-/g, '+') .replace(/_/g, '/'); - return base64; + const rawData = window.atob(base64); + const outputArray = new Uint8Array(rawData.length); + + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i); + } + return outputArray; } if (navigator.serviceWorker == null) { diff --git a/packages/frontend/src/components/MkSignin.input.vue b/packages/frontend/src/components/MkSignin.input.vue index aacd1eae2a..4c73eab3f5 100644 --- a/packages/frontend/src/components/MkSignin.input.vue +++ b/packages/frontend/src/components/MkSignin.input.vue @@ -69,9 +69,11 @@ import MkInfo from '@/components/MkInfo.vue'; const props = withDefaults(defineProps<{ message?: string, openOnRemote?: OpenOnRemoteOptions, + initialUsername?: string; }>(), { message: '', openOnRemote: undefined, + initialUsername: undefined, }); const emit = defineEmits<{ @@ -81,7 +83,7 @@ const emit = defineEmits<{ const host = toUnicode(configHost); -const username = ref(''); +const username = ref(props.initialUsername ?? ''); //#region Open on remote function openRemote(options: OpenOnRemoteOptions, targetHost?: string): void { diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index b0fbe3c490..93a29b0b54 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -20,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only key="input" :message="message" :openOnRemote="openOnRemote" + :initialUsername="initialUsername" @usernameSubmitted="onUsernameSubmitted" @passkeyClick="onPasskeyLogin" @@ -89,10 +90,12 @@ const props = withDefaults(defineProps<{ autoSet?: boolean; message?: string, openOnRemote?: OpenOnRemoteOptions, + initialUsername?: string; }>(), { autoSet: false, message: '', openOnRemote: undefined, + initialUsername: undefined, }); const page = ref<'input' | 'password' | 'totp' | 'passkey'>('input'); diff --git a/packages/frontend/src/components/MkSigninDialog.vue b/packages/frontend/src/components/MkSigninDialog.vue index 60c99880cd..77141f1714 100644 --- a/packages/frontend/src/components/MkSigninDialog.vue +++ b/packages/frontend/src/components/MkSigninDialog.vue @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- +
@@ -34,10 +34,12 @@ withDefaults(defineProps<{ autoSet?: boolean; message?: string, openOnRemote?: OpenOnRemoteOptions, + initialUsername?: string; }>(), { autoSet: false, message: '', openOnRemote: undefined, + initialUsername: undefined, }); const emit = defineEmits<{ diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue index ae1b4549ec..99693a4c00 100644 --- a/packages/frontend/src/components/global/MkA.vue +++ b/packages/frontend/src/components/global/MkA.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> @@ -86,6 +86,11 @@ function openWindow() { } function nav(ev: MouseEvent) { + // 制御キーとの組み合わせは無視(shiftを除く) + if (ev.metaKey || ev.altKey || ev.ctrlKey) return; + + ev.preventDefault(); + if (behavior === 'browser') { window.location.href = props.to; return; diff --git a/packages/frontend/src/components/global/MkSuspense.vue b/packages/frontend/src/components/global/MkSuspense.vue index 4a79cce552..8bfb602e23 100644 --- a/packages/frontend/src/components/global/MkSuspense.vue +++ b/packages/frontend/src/components/global/MkSuspense.vue @@ -1,4 +1,3 @@ -@ -1,70 +0,0 @@