Merge branch 'develop' into refactor-mkselect
This commit is contained in:
commit
94b2b21ddd
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -1,20 +1,30 @@
|
||||||
## 2025.9.0
|
## Unreleased
|
||||||
|
|
||||||
### General
|
### General
|
||||||
-
|
-
|
||||||
|
|
||||||
|
### Client
|
||||||
|
- Enhance: 時刻計算のための基準値を一か所で管理するようにし、パフォーマンスを向上
|
||||||
|
|
||||||
|
### Server
|
||||||
|
-
|
||||||
|
|
||||||
|
|
||||||
|
## 2025.9.0
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Enhance: AiScriptAppウィジェットで構文エラーを検知してもダイアログではなくウィジェット内にエラーを表示するように
|
- Enhance: AiScriptAppウィジェットで構文エラーを検知してもダイアログではなくウィジェット内にエラーを表示するように
|
||||||
- Enhance: /flushページでサイトキャッシュをクリアできるようになりました
|
- Enhance: /flushページでサイトキャッシュをクリアできるようになりました
|
||||||
- Enhance: クリップ/リスト/アンテナ/ロール追加系メニュー項目において、表示件数を拡張
|
- Enhance: クリップ/リスト/アンテナ/ロール追加系メニュー項目において、表示件数を拡張
|
||||||
|
- Enhance: 「キャッシュを削除」ボタンでブラウザの内部キャッシュの削除も行えるように
|
||||||
|
- Enhance: Ctrlキー(Commandキー)を押下しながらリンクをクリックすると新しいタブで開くように
|
||||||
- Fix: プッシュ通知を有効にできない問題を修正
|
- Fix: プッシュ通知を有効にできない問題を修正
|
||||||
- Fix: RSSティッカーウィジェットが正しく動作しない問題を修正
|
- Fix: RSSティッカーウィジェットが正しく動作しない問題を修正
|
||||||
- Fix: プロファイルを復元後アカウントの切り替えができない問題を修正
|
- Fix: プロファイルを復元後アカウントの切り替えができない問題を修正
|
||||||
- Fix: エラー画像が横に引き伸ばされてしまう問題に対応
|
- Fix: エラー画像が横に引き伸ばされてしまう問題に対応
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
-
|
- Fix: webpなどの画像に対してセンシティブなメディアの検出が適用されていなかった問題を修正
|
||||||
|
|
||||||
|
|
||||||
## 2025.8.0
|
## 2025.8.0
|
||||||
|
|
||||||
|
|
|
@ -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."
|
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: "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"
|
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"
|
remoteNotesCleaningExpiryDaysForEachNotes: "Duració mínima de conservació de les notes"
|
||||||
inquiryUrl: "URL de consulta "
|
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ó."
|
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ó."
|
||||||
|
|
|
@ -1215,6 +1215,7 @@ privacyPolicyUrl: "Ссылка на Политику Конфиденциаль
|
||||||
tosAndPrivacyPolicy: "Условия использования и политика конфиденциальности"
|
tosAndPrivacyPolicy: "Условия использования и политика конфиденциальности"
|
||||||
avatarDecorations: "Украшения для аватара"
|
avatarDecorations: "Украшения для аватара"
|
||||||
attach: "Прикрепить"
|
attach: "Прикрепить"
|
||||||
|
detachAll: "Убрать всё"
|
||||||
angle: "Угол"
|
angle: "Угол"
|
||||||
flip: "Переворот"
|
flip: "Переворот"
|
||||||
showAvatarDecorations: "Показать украшения для аватара"
|
showAvatarDecorations: "Показать украшения для аватара"
|
||||||
|
@ -1268,8 +1269,11 @@ availableRoles: "Доступные роли"
|
||||||
federationDisabled: "Федерация отключена для этого сервера. Вы не можете взаимодействовать с пользователями на других серверах."
|
federationDisabled: "Федерация отключена для этого сервера. Вы не можете взаимодействовать с пользователями на других серверах."
|
||||||
draft: "Черновик"
|
draft: "Черновик"
|
||||||
markAsSensitiveConfirm: "Отметить контент как чувствительный?"
|
markAsSensitiveConfirm: "Отметить контент как чувствительный?"
|
||||||
|
preferences: "Основное"
|
||||||
resetToDefaultValue: "Сбросить настройки до стандартных"
|
resetToDefaultValue: "Сбросить настройки до стандартных"
|
||||||
|
syncBetweenDevices: "Синхронизировать между устройствами"
|
||||||
postForm: "Форма отправки"
|
postForm: "Форма отправки"
|
||||||
|
textCount: "Количество символов"
|
||||||
information: "Описание"
|
information: "Описание"
|
||||||
inMinutes: "мин"
|
inMinutes: "мин"
|
||||||
inDays: "сут"
|
inDays: "сут"
|
||||||
|
@ -1281,6 +1285,11 @@ _chat:
|
||||||
send: "Отправить"
|
send: "Отправить"
|
||||||
_settings:
|
_settings:
|
||||||
webhook: "Вебхук"
|
webhook: "Вебхук"
|
||||||
|
preferencesBanner: "Вы можете настроить общее поведение клиента по вашим предпочтениям"
|
||||||
|
timelineAndNote: "Лента и заметки"
|
||||||
|
_chat:
|
||||||
|
showSenderName: "Показывать имя отправителя"
|
||||||
|
sendOnEnter: "Использовать Enter для отправки"
|
||||||
_delivery:
|
_delivery:
|
||||||
stop: "Заморожено"
|
stop: "Заморожено"
|
||||||
_type:
|
_type:
|
||||||
|
@ -1529,7 +1538,7 @@ _achievements:
|
||||||
description: "Нажато здесь"
|
description: "Нажато здесь"
|
||||||
_justPlainLucky:
|
_justPlainLucky:
|
||||||
title: "Чистая удача"
|
title: "Чистая удача"
|
||||||
description: "Может достаться с вероятностью 0,01% каждые 10 секунд."
|
description: "Может достаться с вероятностью 0,005% каждые 10 секунд."
|
||||||
_setNameToSyuilo:
|
_setNameToSyuilo:
|
||||||
title: "Комплекс бога"
|
title: "Комплекс бога"
|
||||||
description: "Установлено «syuilo» в качестве имени"
|
description: "Установлено «syuilo» в качестве имени"
|
||||||
|
@ -1557,6 +1566,12 @@ _achievements:
|
||||||
title: "Brain Diver"
|
title: "Brain Diver"
|
||||||
description: "Опубликована ссылка на песню «Brain Diver»"
|
description: "Опубликована ссылка на песню «Brain Diver»"
|
||||||
flavor: "Мисски-Мисски Ла-Ту-Ма"
|
flavor: "Мисски-Мисски Ла-Ту-Ма"
|
||||||
|
_bubbleGameExplodingHead:
|
||||||
|
title: "🤯"
|
||||||
|
description: "Самый большой объект в Bubble game"
|
||||||
|
_bubbleGameDoubleExplodingHead:
|
||||||
|
title: "Двойной🤯"
|
||||||
|
description: "Два самых больших объекта в Bubble game одновременно!"
|
||||||
_role:
|
_role:
|
||||||
new: "Новая роль"
|
new: "Новая роль"
|
||||||
edit: "Изменить роль"
|
edit: "Изменить роль"
|
||||||
|
|
|
@ -360,7 +360,7 @@ whenServerDisconnected: "Sunucu ile bağlantı kesildiğinde"
|
||||||
disconnectedFromServer: "Sunucu bağlantısı kesildi"
|
disconnectedFromServer: "Sunucu bağlantısı kesildi"
|
||||||
reload: "Yenile"
|
reload: "Yenile"
|
||||||
doNothing: "Yoksay"
|
doNothing: "Yoksay"
|
||||||
reloadConfirm: "Zaman çizelgesini yenilemek ister misin?"
|
reloadConfirm: "Panoyu yenilemek ister misin?"
|
||||||
watch: "İzle"
|
watch: "İzle"
|
||||||
unwatch: "İzlemeyi bırak"
|
unwatch: "İzlemeyi bırak"
|
||||||
accept: "Kabul et"
|
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."
|
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ı"
|
serverLogs: "Sunucu log kayıtları"
|
||||||
deleteAll: "Tümünü sil"
|
deleteAll: "Tümünü sil"
|
||||||
showFixedPostForm: "Gönderi formunu zaman çizelgesinin en üstünde görüntüle"
|
showFixedPostForm: "Gönderi formunu pano üstünde görüntüle"
|
||||||
showFixedPostFormInChannel: "Gönderi formunu zaman çizelgesinin en üstünde görüntüle (Kanallar)"
|
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 zaman çizelgesine dahil et"
|
withRepliesByDefaultForNewlyFollowed: "Yeni takip edilen kullanıcıların yanıtlarını varsayılan olarak panoya dahil et"
|
||||||
newNoteRecived: "Yeni Not'lar var"
|
newNoteRecived: "Yeni Not'lar var"
|
||||||
newNote: "Yeni Not"
|
newNote: "Yeni Not"
|
||||||
sounds: "Sesler"
|
sounds: "Sesler"
|
||||||
|
@ -1059,7 +1059,7 @@ achievements: "Başarılar"
|
||||||
gotInvalidResponseError: "Geçersiz sunucu yanıtı"
|
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."
|
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."
|
thisPostMayBeAnnoying: "Bu not başkalarını rahatsız edebilir."
|
||||||
thisPostMayBeAnnoyingHome: "Ana zaman çizelgesine gönder"
|
thisPostMayBeAnnoyingHome: "Ana panoya gönder"
|
||||||
thisPostMayBeAnnoyingCancel: "İptal"
|
thisPostMayBeAnnoyingCancel: "İptal"
|
||||||
thisPostMayBeAnnoyingIgnore: "Yine de gönder"
|
thisPostMayBeAnnoyingIgnore: "Yine de gönder"
|
||||||
collapseRenotes: "Daha önce görüntülenen Renote'lari kısaltılmış olarak göster"
|
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"
|
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"
|
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"
|
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?"
|
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ı zaman tünelinde cidden göstermeyecek misin?"
|
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"
|
externalServices: "Dış Hizmetler"
|
||||||
sourceCode: "Kaynak kodu"
|
sourceCode: "Kaynak kodu"
|
||||||
sourceCodeIsNotYetProvided: "Kaynak kodu henüz mevcut değildir. Bu sorunu gidermek için yöneticiyle iletişime geçin."
|
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."
|
description: "Burada, Misskey'i kullanmanın temellerini ve özelliklerini öğrenebilirsin."
|
||||||
_note:
|
_note:
|
||||||
title: "Not nedir?"
|
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."
|
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."
|
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."
|
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:
|
_reaction:
|
||||||
|
@ -1640,7 +1640,7 @@ _serverSettings:
|
||||||
shortNameDescription: "Resmi adın uzun olması durumunda görüntülenebilen, örneğin adının kısaltması."
|
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."
|
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"
|
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."
|
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: "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."
|
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."
|
restartServerSetupWizardConfirm_text: "Bazı mevcut ayarlar sıfırlanacaktır."
|
||||||
entrancePageStyle: "Giriş sayfası stili"
|
entrancePageStyle: "Giriş sayfası stili"
|
||||||
showTimelineForVisitor: "Panoyu göster"
|
showTimelineForVisitor: "Panoyu göster"
|
||||||
|
showActivitiesForVisitor: "Aktiviteleri göster"
|
||||||
_userGeneratedContentsVisibilityForVisitor:
|
_userGeneratedContentsVisibilityForVisitor:
|
||||||
all: "Her şey halka açıktır."
|
all: "Her şey halka açıktır."
|
||||||
localOnly: "Yalnızca yerel içerik yayınlanır, uzak içerik gizli tutulur."
|
localOnly: "Yalnızca yerel içerik yayınlanır, uzak içerik gizli tutulur."
|
||||||
|
@ -1876,7 +1877,7 @@ _achievements:
|
||||||
title: "Öz Referans"
|
title: "Öz Referans"
|
||||||
description: "Kendi notunuzu alıntı yapın"
|
description: "Kendi notunuzu alıntı yapın"
|
||||||
_htl20npm:
|
_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?"
|
description: "Ev zaman çizelgenizin hızı 20 npm'yi (dakika başına not sayısı) aşıyor mu?"
|
||||||
_viewInstanceChart:
|
_viewInstanceChart:
|
||||||
title: "Analist"
|
title: "Analist"
|
||||||
|
@ -1965,7 +1966,7 @@ _role:
|
||||||
asBadge: "Rozet olarak göster"
|
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."
|
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"
|
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"
|
displayOrder: "Pozisyon"
|
||||||
descriptionOfDisplayOrder: "Sayı ne kadar yüksekse, UI pozisyonu da o kadar yüksek olur."
|
descriptionOfDisplayOrder: "Sayı ne kadar yüksekse, UI pozisyonu da o kadar yüksek olur."
|
||||||
preserveAssignmentOnMoveAccount: "Geçiş sırasında rol atamalarını koruyun"
|
preserveAssignmentOnMoveAccount: "Geçiş sırasında rol atamalarını koruyun"
|
||||||
|
@ -1979,7 +1980,7 @@ _role:
|
||||||
high: "Yüksek"
|
high: "Yüksek"
|
||||||
_options:
|
_options:
|
||||||
gtlAvailable: "Global Pano'yu görüntüleyebilir"
|
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"
|
canPublicNote: "Halka açık notlar gönderebilir"
|
||||||
mentionMax: "Bir notta maksimum bahsetme sayısı"
|
mentionMax: "Bir notta maksimum bahsetme sayısı"
|
||||||
canInvite: "Sunucu davet kodları oluşturabilir"
|
canInvite: "Sunucu davet kodları oluşturabilir"
|
||||||
|
@ -2484,7 +2485,7 @@ _visibility:
|
||||||
public: "Halka açık"
|
public: "Halka açık"
|
||||||
publicDescription: "Notunuz tüm kullanıcılar tarafından görülebilir olacaktır."
|
publicDescription: "Notunuz tüm kullanıcılar tarafından görülebilir olacaktır."
|
||||||
home: "Pano"
|
home: "Pano"
|
||||||
homeDescription: "Yalnızca ana zaman çizelgesine gönder"
|
homeDescription: "Yalnızca ana panoya gönder"
|
||||||
followers: "Takipçiler"
|
followers: "Takipçiler"
|
||||||
followersDescription: "Sadece takipçilerine görünür hale getir"
|
followersDescription: "Sadece takipçilerine görünür hale getir"
|
||||||
specified: "Doğrudan"
|
specified: "Doğrudan"
|
||||||
|
@ -2531,7 +2532,7 @@ _exportOrImport:
|
||||||
userLists: "Kullanıcı listeleri"
|
userLists: "Kullanıcı listeleri"
|
||||||
excludeMutingUsers: "Sessize alınan kullanıcıları hariç tut"
|
excludeMutingUsers: "Sessize alınan kullanıcıları hariç tut"
|
||||||
excludeInactiveUsers: "Etkin olmayan 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:
|
_charts:
|
||||||
federation: "Federasyon"
|
federation: "Federasyon"
|
||||||
apRequest: "Talepler"
|
apRequest: "Talepler"
|
||||||
|
@ -2925,7 +2926,7 @@ _reversi:
|
||||||
freeMatch: "Ücretsiz Eşleştirme"
|
freeMatch: "Ücretsiz Eşleştirme"
|
||||||
lookingForPlayer: "Rakip aranıyor..."
|
lookingForPlayer: "Rakip aranıyor..."
|
||||||
gameCanceled: "Oyun iptal edildi."
|
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"
|
iStartedAGame: "Oyun başladı! #MisskeyReversi"
|
||||||
opponentHasSettingsChanged: "Rakip ayarlarını değiştirmiş."
|
opponentHasSettingsChanged: "Rakip ayarlarını değiştirmiş."
|
||||||
allowIrregularRules: "Düzensiz kurallar (tamamen ücretsiz)"
|
allowIrregularRules: "Düzensiz kurallar (tamamen ücretsiz)"
|
||||||
|
@ -3153,7 +3154,7 @@ _clientPerformanceIssueTip:
|
||||||
_clip:
|
_clip:
|
||||||
tip: "Klip, notları gruplandırmanıza olanak tanıyan bir özelliktir."
|
tip: "Klip, notları gruplandırmanıza olanak tanıyan bir özelliktir."
|
||||||
_userLists:
|
_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"
|
watermark: "Filigran"
|
||||||
defaultPreset: "Varsayılan Ön Ayar"
|
defaultPreset: "Varsayılan Ön Ayar"
|
||||||
_watermarkEditor:
|
_watermarkEditor:
|
||||||
|
|
18
package.json
18
package.json
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2025.9.0-alpha.1",
|
"version": "2025.9.0",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/misskey-dev/misskey.git"
|
"url": "https://github.com/misskey-dev/misskey.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.15.0",
|
"packageManager": "pnpm@10.15.1",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/frontend-shared",
|
"packages/frontend-shared",
|
||||||
"packages/frontend",
|
"packages/frontend",
|
||||||
|
@ -62,22 +62,22 @@
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
"tar": "7.4.3",
|
"tar": "7.4.3",
|
||||||
"terser": "5.43.1",
|
"terser": "5.44.0",
|
||||||
"typescript": "5.9.2"
|
"typescript": "5.9.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "2.1.0",
|
"@misskey-dev/eslint-plugin": "2.1.0",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/node": "22.17.2",
|
"@types/node": "22.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
"@typescript-eslint/eslint-plugin": "8.42.0",
|
||||||
"@typescript-eslint/parser": "8.40.0",
|
"@typescript-eslint/parser": "8.42.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "14.5.4",
|
"cypress": "14.5.4",
|
||||||
"eslint": "9.34.0",
|
"eslint": "9.35.0",
|
||||||
"globals": "16.3.0",
|
"globals": "16.3.0",
|
||||||
"ncp": "2.0.0",
|
"ncp": "2.0.0",
|
||||||
"pnpm": "10.15.0",
|
"pnpm": "10.15.1",
|
||||||
"start-server-and-test": "2.0.13"
|
"start-server-and-test": "2.1.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@tensorflow/tfjs-core": "4.22.0"
|
"@tensorflow/tfjs-core": "4.22.0"
|
||||||
|
|
|
@ -39,17 +39,17 @@
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@swc/core-android-arm64": "1.3.11",
|
"@swc/core-android-arm64": "1.3.11",
|
||||||
"@swc/core-darwin-arm64": "1.13.4",
|
"@swc/core-darwin-arm64": "1.13.5",
|
||||||
"@swc/core-darwin-x64": "1.13.4",
|
"@swc/core-darwin-x64": "1.13.5",
|
||||||
"@swc/core-freebsd-x64": "1.3.11",
|
"@swc/core-freebsd-x64": "1.3.11",
|
||||||
"@swc/core-linux-arm-gnueabihf": "1.13.4",
|
"@swc/core-linux-arm-gnueabihf": "1.13.5",
|
||||||
"@swc/core-linux-arm64-gnu": "1.13.4",
|
"@swc/core-linux-arm64-gnu": "1.13.5",
|
||||||
"@swc/core-linux-arm64-musl": "1.13.4",
|
"@swc/core-linux-arm64-musl": "1.13.5",
|
||||||
"@swc/core-linux-x64-gnu": "1.13.4",
|
"@swc/core-linux-x64-gnu": "1.13.5",
|
||||||
"@swc/core-linux-x64-musl": "1.13.4",
|
"@swc/core-linux-x64-musl": "1.13.5",
|
||||||
"@swc/core-win32-arm64-msvc": "1.13.4",
|
"@swc/core-win32-arm64-msvc": "1.13.5",
|
||||||
"@swc/core-win32-ia32-msvc": "1.13.4",
|
"@swc/core-win32-ia32-msvc": "1.13.5",
|
||||||
"@swc/core-win32-x64-msvc": "1.13.4",
|
"@swc/core-win32-x64-msvc": "1.13.5",
|
||||||
"@tensorflow/tfjs": "4.22.0",
|
"@tensorflow/tfjs": "4.22.0",
|
||||||
"@tensorflow/tfjs-node": "4.22.0",
|
"@tensorflow/tfjs-node": "4.22.0",
|
||||||
"bufferutil": "4.0.9",
|
"bufferutil": "4.0.9",
|
||||||
|
@ -69,20 +69,20 @@
|
||||||
"utf-8-validate": "6.0.5"
|
"utf-8-validate": "6.0.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.873.0",
|
"@aws-sdk/client-s3": "3.883.0",
|
||||||
"@aws-sdk/lib-storage": "3.873.0",
|
"@aws-sdk/lib-storage": "3.883.0",
|
||||||
"@discordapp/twemoji": "16.0.1",
|
"@discordapp/twemoji": "16.0.1",
|
||||||
"@fastify/accepts": "5.0.2",
|
"@fastify/accepts": "5.0.2",
|
||||||
"@fastify/cookie": "11.0.2",
|
"@fastify/cookie": "11.0.2",
|
||||||
"@fastify/cors": "10.1.0",
|
"@fastify/cors": "10.1.0",
|
||||||
"@fastify/express": "4.0.2",
|
"@fastify/express": "4.0.2",
|
||||||
"@fastify/http-proxy": "10.0.2",
|
"@fastify/http-proxy": "10.0.2",
|
||||||
"@fastify/multipart": "9.0.3",
|
"@fastify/multipart": "9.2.1",
|
||||||
"@fastify/static": "8.2.0",
|
"@fastify/static": "8.2.0",
|
||||||
"@fastify/view": "10.0.2",
|
"@fastify/view": "10.0.2",
|
||||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||||
"@misskey-dev/summaly": "5.2.3",
|
"@misskey-dev/summaly": "5.2.3",
|
||||||
"@napi-rs/canvas": "0.1.77",
|
"@napi-rs/canvas": "0.1.79",
|
||||||
"@nestjs/common": "11.1.6",
|
"@nestjs/common": "11.1.6",
|
||||||
"@nestjs/core": "11.1.6",
|
"@nestjs/core": "11.1.6",
|
||||||
"@nestjs/testing": "11.1.6",
|
"@nestjs/testing": "11.1.6",
|
||||||
|
@ -93,7 +93,7 @@
|
||||||
"@sinonjs/fake-timers": "11.3.1",
|
"@sinonjs/fake-timers": "11.3.1",
|
||||||
"@smithy/node-http-handler": "2.5.0",
|
"@smithy/node-http-handler": "2.5.0",
|
||||||
"@swc/cli": "0.7.8",
|
"@swc/cli": "0.7.8",
|
||||||
"@swc/core": "1.13.4",
|
"@swc/core": "1.13.5",
|
||||||
"@twemoji/parser": "16.0.0",
|
"@twemoji/parser": "16.0.0",
|
||||||
"@types/redis-info": "3.0.3",
|
"@types/redis-info": "3.0.3",
|
||||||
"accepts": "1.3.8",
|
"accepts": "1.3.8",
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"body-parser": "1.20.3",
|
"body-parser": "1.20.3",
|
||||||
"bullmq": "5.58.1",
|
"bullmq": "5.58.5",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"cbor": "9.0.2",
|
"cbor": "9.0.2",
|
||||||
"chalk": "5.6.0",
|
"chalk": "5.6.0",
|
||||||
|
@ -114,13 +114,13 @@
|
||||||
"content-disposition": "0.5.4",
|
"content-disposition": "0.5.4",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
"deep-email-validator": "0.1.21",
|
"deep-email-validator": "0.1.21",
|
||||||
"fastify": "5.5.0",
|
"fastify": "5.6.0",
|
||||||
"fastify-raw-body": "5.0.0",
|
"fastify-raw-body": "5.0.0",
|
||||||
"feed": "4.2.2",
|
"feed": "4.2.2",
|
||||||
"file-type": "19.6.0",
|
"file-type": "19.6.0",
|
||||||
"fluent-ffmpeg": "2.1.3",
|
"fluent-ffmpeg": "2.1.3",
|
||||||
"form-data": "4.0.4",
|
"form-data": "4.0.4",
|
||||||
"got": "14.4.7",
|
"got": "14.4.8",
|
||||||
"happy-dom": "16.8.1",
|
"happy-dom": "16.8.1",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"htmlescape": "1.1.1",
|
"htmlescape": "1.1.1",
|
||||||
|
@ -141,7 +141,7 @@
|
||||||
"mime-types": "2.1.35",
|
"mime-types": "2.1.35",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"misskey-reversi": "workspace:*",
|
"misskey-reversi": "workspace:*",
|
||||||
"ms": "3.0.0-canary.1",
|
"ms": "3.0.0-canary.202508261828",
|
||||||
"nanoid": "5.1.5",
|
"nanoid": "5.1.5",
|
||||||
"nested-property": "4.0.0",
|
"nested-property": "4.0.0",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
|
@ -175,7 +175,7 @@
|
||||||
"slacc": "0.0.10",
|
"slacc": "0.0.10",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
"systeminformation": "5.27.7",
|
"systeminformation": "5.27.8",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tmp": "0.2.5",
|
"tmp": "0.2.5",
|
||||||
"tsc-alias": "1.8.16",
|
"tsc-alias": "1.8.16",
|
||||||
|
@ -210,7 +210,7 @@
|
||||||
"@types/jsrsasign": "10.5.15",
|
"@types/jsrsasign": "10.5.15",
|
||||||
"@types/mime-types": "2.1.4",
|
"@types/mime-types": "2.1.4",
|
||||||
"@types/ms": "0.7.34",
|
"@types/ms": "0.7.34",
|
||||||
"@types/node": "22.17.2",
|
"@types/node": "22.18.1",
|
||||||
"@types/nodemailer": "6.4.19",
|
"@types/nodemailer": "6.4.19",
|
||||||
"@types/oauth": "0.9.6",
|
"@types/oauth": "0.9.6",
|
||||||
"@types/oauth2orize": "1.11.5",
|
"@types/oauth2orize": "1.11.5",
|
||||||
|
@ -222,7 +222,7 @@
|
||||||
"@types/ratelimiter": "3.4.6",
|
"@types/ratelimiter": "3.4.6",
|
||||||
"@types/rename": "1.0.7",
|
"@types/rename": "1.0.7",
|
||||||
"@types/sanitize-html": "2.16.0",
|
"@types/sanitize-html": "2.16.0",
|
||||||
"@types/semver": "7.7.0",
|
"@types/semver": "7.7.1",
|
||||||
"@types/simple-oauth2": "5.0.7",
|
"@types/simple-oauth2": "5.0.7",
|
||||||
"@types/sinonjs__fake-timers": "8.1.5",
|
"@types/sinonjs__fake-timers": "8.1.5",
|
||||||
"@types/supertest": "6.0.3",
|
"@types/supertest": "6.0.3",
|
||||||
|
@ -231,8 +231,8 @@
|
||||||
"@types/vary": "1.1.3",
|
"@types/vary": "1.1.3",
|
||||||
"@types/web-push": "3.6.4",
|
"@types/web-push": "3.6.4",
|
||||||
"@types/ws": "8.18.1",
|
"@types/ws": "8.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
"@typescript-eslint/eslint-plugin": "8.42.0",
|
||||||
"@typescript-eslint/parser": "8.40.0",
|
"@typescript-eslint/parser": "8.42.0",
|
||||||
"aws-sdk-client-mock": "4.1.0",
|
"aws-sdk-client-mock": "4.1.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint-plugin-import": "2.32.0",
|
"eslint-plugin-import": "2.32.0",
|
||||||
|
|
|
@ -29,7 +29,7 @@ export class AiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async detectSensitive(path: string): Promise<nsfw.PredictionType[] | null> {
|
public async detectSensitive(source: string | Buffer): Promise<nsfw.PredictionType[] | null> {
|
||||||
try {
|
try {
|
||||||
if (isSupportedCpu === undefined) {
|
if (isSupportedCpu === undefined) {
|
||||||
isSupportedCpu = await this.computeIsSupportedCpu();
|
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;
|
const image = await tf.node.decodeImage(buffer, 3) as any;
|
||||||
try {
|
try {
|
||||||
const predictions = await this.model.classify(image);
|
const predictions = await this.model.classify(image);
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { PredictionType } from 'nsfwjs';
|
import type { PredictionType } from 'nsfwjs';
|
||||||
|
import { isMimeImage } from '@/misc/is-mime-image.js';
|
||||||
|
|
||||||
export type FileInfo = {
|
export type FileInfo = {
|
||||||
size: number;
|
size: number;
|
||||||
|
@ -204,16 +205,7 @@ export class FileInfoService {
|
||||||
return [sensitive, porn];
|
return [sensitive, porn];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([
|
if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) {
|
||||||
'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/'))) {
|
|
||||||
const [outDir, disposeOutDir] = await createTempDir();
|
const [outDir, disposeOutDir] = await createTempDir();
|
||||||
try {
|
try {
|
||||||
const command = FFmpeg()
|
const command = FFmpeg()
|
||||||
|
@ -281,6 +273,23 @@ export class FileInfoService {
|
||||||
} finally {
|
} finally {
|
||||||
disposeOutDir();
|
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];
|
return [sensitive, porn];
|
||||||
|
|
|
@ -756,8 +756,8 @@ export class QueueService {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async queueRetryJob(queueType: typeof QUEUE_TYPES[number], jobId: string) {
|
public async queueRetryJob(queueType: typeof QUEUE_TYPES[number], jobId: string) {
|
||||||
const queue = this.getQueue(queueType);
|
const queue = this.getQueue(queueType);
|
||||||
const job: Bull.Job | null = await queue.getJob(jobId);
|
const job = await queue.getJob(jobId);
|
||||||
if (job) {
|
if (job != null) {
|
||||||
if (job.finishedOn != null) {
|
if (job.finishedOn != null) {
|
||||||
await job.retry();
|
await job.retry();
|
||||||
} else {
|
} else {
|
||||||
|
@ -769,8 +769,8 @@ export class QueueService {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async queueRemoveJob(queueType: typeof QUEUE_TYPES[number], jobId: string) {
|
public async queueRemoveJob(queueType: typeof QUEUE_TYPES[number], jobId: string) {
|
||||||
const queue = this.getQueue(queueType);
|
const queue = this.getQueue(queueType);
|
||||||
const job: Bull.Job | null = await queue.getJob(jobId);
|
const job = await queue.getJob(jobId);
|
||||||
if (job) {
|
if (job != null) {
|
||||||
await job.remove();
|
await job.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -803,8 +803,8 @@ export class QueueService {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async queueGetJob(queueType: typeof QUEUE_TYPES[number], jobId: string) {
|
public async queueGetJob(queueType: typeof QUEUE_TYPES[number], jobId: string) {
|
||||||
const queue = this.getQueue(queueType);
|
const queue = this.getQueue(queueType);
|
||||||
const job: Bull.Job | null = await queue.getJob(jobId);
|
const job = await queue.getJob(jobId);
|
||||||
if (job) {
|
if (job != null) {
|
||||||
return this.packJobData(job);
|
return this.packJobData(job);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Job not found: ${jobId}`);
|
throw new Error(`Job not found: ${jobId}`);
|
||||||
|
|
|
@ -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,
|
// Make sure any unknown path under /api returns HTTP 404 Not Found,
|
||||||
// because otherwise ClientServerService will return the base client HTML
|
// because otherwise ClientServerService will return the base client HTML
|
||||||
// page with HTTP 200.
|
// page with HTTP 200.
|
||||||
|
|
|
@ -34,13 +34,22 @@ export const meta = {
|
||||||
res: {
|
res: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
allOf: [
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
ref: 'MeDetailed',
|
ref: 'MeDetailed',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
properties: {
|
properties: {
|
||||||
token: {
|
token: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,14 @@ export const meta = {
|
||||||
res: {
|
res: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
allOf: [
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
ref: 'UserList',
|
ref: 'UserList',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
properties: {
|
properties: {
|
||||||
likedCount: {
|
likedCount: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
@ -34,6 +41,8 @@ export const meta = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
errors: {
|
errors: {
|
||||||
noSuchList: {
|
noSuchList: {
|
||||||
|
|
|
@ -68,7 +68,6 @@ async function createAdmin(host: Host): Promise<Misskey.entities.SignupResponse
|
||||||
return await client.request('admin/accounts/create', ADMIN_PARAMS).then(res => {
|
return await client.request('admin/accounts/create', ADMIN_PARAMS).then(res => {
|
||||||
ADMIN_CACHE.set(host, {
|
ADMIN_CACHE.set(host, {
|
||||||
id: res.id,
|
id: res.id,
|
||||||
// @ts-expect-error FIXME: openapi-typescript generates incorrect response type for this endpoint, so ignore this
|
|
||||||
i: res.token,
|
i: res.token,
|
||||||
});
|
});
|
||||||
return res as Misskey.entities.SignupResponse;
|
return res as Misskey.entities.SignupResponse;
|
||||||
|
|
|
@ -20,6 +20,6 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"estree-walker": "3.0.3",
|
"estree-walker": "3.0.3",
|
||||||
"magic-string": "0.30.17",
|
"magic-string": "0.30.17",
|
||||||
"vite": "7.0.6"
|
"vite": "7.0.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,9 +46,71 @@ export default [
|
||||||
allowSingleExtends: true,
|
allowSingleExtends: true,
|
||||||
}],
|
}],
|
||||||
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
|
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
|
||||||
// window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
|
// window ... グローバルスコープと衝突し、予期せぬ結果を招くため
|
||||||
// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
|
// e ... error や event など、複数のキーワードの頭文字であり分かりにくいため
|
||||||
'id-denylist': ['error', 'window', 'e'],
|
// close ... window.closeと衝突 or 紛らわしい
|
||||||
|
// open ... window.openと衝突 or 紛らわしい
|
||||||
|
// fetch ... window.fetchと衝突 or 紛らわしい
|
||||||
|
// location ... window.locationと衝突 or 紛らわしい
|
||||||
|
// document ... window.documentと衝突 or 紛らわしい
|
||||||
|
// history ... window.historyと衝突 or 紛らわしい
|
||||||
|
// scroll ... window.scrollと衝突 or 紛らわしい
|
||||||
|
// setTimeout ... window.setTimeoutと衝突 or 紛らわしい
|
||||||
|
// setInterval ... window.setIntervalと衝突 or 紛らわしい
|
||||||
|
// clearTimeout ... window.clearTimeoutと衝突 or 紛らわしい
|
||||||
|
// clearInterval ... window.clearIntervalと衝突 or 紛らわしい
|
||||||
|
'id-denylist': ['error', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history', 'scroll', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'],
|
||||||
|
'no-restricted-globals': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'name': 'open',
|
||||||
|
'message': 'Use `window.open`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'close',
|
||||||
|
'message': 'Use `window.close`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'fetch',
|
||||||
|
'message': 'Use `window.fetch`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'location',
|
||||||
|
'message': 'Use `window.location`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'document',
|
||||||
|
'message': 'Use `window.document`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'history',
|
||||||
|
'message': 'Use `window.history`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'scroll',
|
||||||
|
'message': 'Use `window.scroll`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'setTimeout',
|
||||||
|
'message': 'Use `window.setTimeout`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'setInterval',
|
||||||
|
'message': 'Use `window.setInterval`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'clearTimeout',
|
||||||
|
'message': 'Use `window.clearTimeout`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'clearInterval',
|
||||||
|
'message': 'Use `window.clearInterval`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'name',
|
||||||
|
'message': 'Use `window.name`. もしくは name という変数名を定義し忘れている',
|
||||||
|
},
|
||||||
|
],
|
||||||
'no-shadow': ['warn'],
|
'no-shadow': ['warn'],
|
||||||
'vue/attributes-order': ['error', {
|
'vue/attributes-order': ['error', {
|
||||||
alphabetical: false,
|
alphabetical: false,
|
||||||
|
|
|
@ -13,10 +13,10 @@
|
||||||
"@discordapp/twemoji": "16.0.1",
|
"@discordapp/twemoji": "16.0.1",
|
||||||
"@rollup/plugin-json": "6.1.0",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-replace": "6.0.2",
|
"@rollup/plugin-replace": "6.0.2",
|
||||||
"@rollup/pluginutils": "5.2.0",
|
"@rollup/pluginutils": "5.3.0",
|
||||||
"@twemoji/parser": "16.0.0",
|
"@twemoji/parser": "16.0.0",
|
||||||
"@vitejs/plugin-vue": "6.0.1",
|
"@vitejs/plugin-vue": "6.0.1",
|
||||||
"@vue/compiler-sfc": "3.5.19",
|
"@vue/compiler-sfc": "3.5.21",
|
||||||
"astring": "1.9.0",
|
"astring": "1.9.0",
|
||||||
"buraha": "0.0.1",
|
"buraha": "0.0.1",
|
||||||
"estree-walker": "3.0.3",
|
"estree-walker": "3.0.3",
|
||||||
|
@ -26,16 +26,16 @@
|
||||||
"mfm-js": "0.25.0",
|
"mfm-js": "0.25.0",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"rollup": "4.48.0",
|
"rollup": "4.50.1",
|
||||||
"sass": "1.90.0",
|
"sass": "1.92.1",
|
||||||
"shiki": "3.11.0",
|
"shiki": "3.12.2",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tsc-alias": "1.8.16",
|
"tsc-alias": "1.8.16",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.9.2",
|
"typescript": "5.9.2",
|
||||||
"uuid": "11.1.0",
|
"uuid": "11.1.0",
|
||||||
"vite": "7.1.3",
|
"vite": "7.1.5",
|
||||||
"vue": "3.5.19"
|
"vue": "3.5.21"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/summaly": "5.2.3",
|
"@misskey-dev/summaly": "5.2.3",
|
||||||
|
@ -43,14 +43,14 @@
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/estree": "1.0.8",
|
"@types/estree": "1.0.8",
|
||||||
"@types/micromatch": "4.0.9",
|
"@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/punycode.js": "npm:@types/punycode@2.1.4",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/ws": "8.18.1",
|
"@types/ws": "8.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
"@typescript-eslint/eslint-plugin": "8.42.0",
|
||||||
"@typescript-eslint/parser": "8.40.0",
|
"@typescript-eslint/parser": "8.42.0",
|
||||||
"@vitest/coverage-v8": "3.2.4",
|
"@vitest/coverage-v8": "3.2.4",
|
||||||
"@vue/runtime-core": "3.5.19",
|
"@vue/runtime-core": "3.5.21",
|
||||||
"acorn": "8.15.0",
|
"acorn": "8.15.0",
|
||||||
"cross-env": "10.0.0",
|
"cross-env": "10.0.0",
|
||||||
"eslint-plugin-import": "2.32.0",
|
"eslint-plugin-import": "2.32.0",
|
||||||
|
@ -59,11 +59,11 @@
|
||||||
"happy-dom": "18.0.1",
|
"happy-dom": "18.0.1",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.8",
|
"micromatch": "4.0.8",
|
||||||
"msw": "2.10.5",
|
"msw": "2.11.1",
|
||||||
"nodemon": "3.1.10",
|
"nodemon": "3.1.10",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"start-server-and-test": "2.0.13",
|
"start-server-and-test": "2.1.0",
|
||||||
"tsx": "4.20.4",
|
"tsx": "4.20.5",
|
||||||
"vite-plugin-turbosnap": "1.0.3",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vue-component-type-helpers": "3.0.6",
|
"vue-component-type-helpers": "3.0.6",
|
||||||
"vue-eslint-parser": "10.2.0",
|
"vue-eslint-parser": "10.2.0",
|
||||||
|
|
|
@ -33,7 +33,7 @@ import type { Theme } from '@/theme.js';
|
||||||
console.log('Misskey Embed');
|
console.log('Misskey Embed');
|
||||||
|
|
||||||
//#region Embedパラメータの取得・パース
|
//#region Embedパラメータの取得・パース
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const embedParams = parseEmbedParams(params);
|
const embedParams = parseEmbedParams(params);
|
||||||
if (_DEV_) console.log(embedParams);
|
if (_DEV_) console.log(embedParams);
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -81,7 +81,7 @@ storeBootloaderErrors({ ...i18n.ts._bootErrors, reload: i18n.ts.reload });
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
// サイズの制限
|
// サイズの制限
|
||||||
document.documentElement.style.maxWidth = '500px';
|
window.document.documentElement.style.maxWidth = '500px';
|
||||||
|
|
||||||
// iframeIdの設定
|
// iframeIdの設定
|
||||||
function setIframeIdHandler(event: MessageEvent) {
|
function setIframeIdHandler(event: MessageEvent) {
|
||||||
|
@ -114,16 +114,16 @@ app.provide(DI.embedParams, embedParams);
|
||||||
const rootEl = ((): HTMLElement => {
|
const rootEl = ((): HTMLElement => {
|
||||||
const MISSKEY_MOUNT_DIV_ID = 'misskey_app';
|
const MISSKEY_MOUNT_DIV_ID = 'misskey_app';
|
||||||
|
|
||||||
const currentRoot = document.getElementById(MISSKEY_MOUNT_DIV_ID);
|
const currentRoot = window.document.getElementById(MISSKEY_MOUNT_DIV_ID);
|
||||||
|
|
||||||
if (currentRoot) {
|
if (currentRoot) {
|
||||||
console.warn('multiple import detected');
|
console.warn('multiple import detected');
|
||||||
return currentRoot;
|
return currentRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
const root = document.createElement('div');
|
const root = window.document.createElement('div');
|
||||||
root.id = MISSKEY_MOUNT_DIV_ID;
|
root.id = MISSKEY_MOUNT_DIV_ID;
|
||||||
document.body.appendChild(root);
|
window.document.body.appendChild(root);
|
||||||
return root;
|
return root;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hu
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
function removeSplash() {
|
function removeSplash() {
|
||||||
const splash = document.getElementById('splash');
|
const splash = window.document.getElementById('splash');
|
||||||
if (splash) {
|
if (splash) {
|
||||||
splash.style.opacity = '0';
|
splash.style.opacity = '0';
|
||||||
splash.style.pointerEvents = 'none';
|
splash.style.pointerEvents = 'none';
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurha
|
||||||
const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resolve => {
|
const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resolve => {
|
||||||
// テスト環境で Web Worker インスタンスは作成できない
|
// テスト環境で Web Worker インスタンスは作成できない
|
||||||
if (import.meta.env.MODE === 'test') {
|
if (import.meta.env.MODE === 'test') {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = window.document.createElement('canvas');
|
||||||
canvas.width = 64;
|
canvas.width = 64;
|
||||||
canvas.height = 64;
|
canvas.height = 64;
|
||||||
resolve(canvas);
|
resolve(canvas);
|
||||||
|
@ -34,7 +34,7 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
|
||||||
);
|
);
|
||||||
resolve(workers);
|
resolve(workers);
|
||||||
} else {
|
} else {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = window.document.createElement('canvas');
|
||||||
canvas.width = 64;
|
canvas.width = 64;
|
||||||
canvas.height = 64;
|
canvas.height = 64;
|
||||||
resolve(canvas);
|
resolve(canvas);
|
||||||
|
|
|
@ -29,7 +29,7 @@ const props = defineProps<{
|
||||||
// if no instance data is given, this is for the local instance
|
// if no instance data is given, this is for the local instance
|
||||||
const instance = props.instance ?? {
|
const instance = props.instance ?? {
|
||||||
name: serverMetadata.name,
|
name: serverMetadata.name,
|
||||||
themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content,
|
themeColor: (window.document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content,
|
||||||
};
|
};
|
||||||
|
|
||||||
const faviconUrl = computed(() => props.instance ? mediaProxy.getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : mediaProxy.getProxiedImageUrlNullable(serverMetadata.iconUrl, 'preview') ?? '/favicon.ico');
|
const faviconUrl = computed(() => props.instance ? mediaProxy.getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : mediaProxy.getProxiedImageUrlNullable(serverMetadata.iconUrl, 'preview') ?? '/favicon.ico');
|
||||||
|
|
|
@ -27,7 +27,7 @@ const canonical = props.host === localHost ? `@${props.username}` : `@${props.us
|
||||||
|
|
||||||
const url = `/${canonical}`;
|
const url = `/${canonical}`;
|
||||||
|
|
||||||
const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-mention'));
|
const bg = tinycolor(getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-mention'));
|
||||||
bg.setAlpha(0.1);
|
bg.setAlpha(0.1);
|
||||||
const bgCss = bg.toRgbString();
|
const bgCss = bg.toRgbString();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -134,7 +134,7 @@ const isBackTop = ref(false);
|
||||||
const empty = computed(() => items.value.size === 0);
|
const empty = computed(() => items.value.size === 0);
|
||||||
const error = ref(false);
|
const error = ref(false);
|
||||||
|
|
||||||
const scrollableElement = computed(() => rootEl.value ? getScrollContainer(rootEl.value) : document.body);
|
const scrollableElement = computed(() => rootEl.value ? getScrollContainer(rootEl.value) : window.document.body);
|
||||||
|
|
||||||
const visibility = useDocumentVisibility();
|
const visibility = useDocumentVisibility();
|
||||||
|
|
||||||
|
@ -353,7 +353,7 @@ watch(visibility, () => {
|
||||||
BACKGROUND_PAUSE_WAIT_SEC * 1000);
|
BACKGROUND_PAUSE_WAIT_SEC * 1000);
|
||||||
} else { // 'visible'
|
} else { // 'visible'
|
||||||
if (timerForSetPause) {
|
if (timerForSetPause) {
|
||||||
clearTimeout(timerForSetPause);
|
window.clearTimeout(timerForSetPause);
|
||||||
timerForSetPause = null;
|
timerForSetPause = null;
|
||||||
} else {
|
} else {
|
||||||
isPausingUpdate = false;
|
isPausingUpdate = false;
|
||||||
|
@ -447,11 +447,11 @@ onBeforeMount(() => {
|
||||||
init().then(() => {
|
init().then(() => {
|
||||||
if (props.pagination.reversed) {
|
if (props.pagination.reversed) {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
setTimeout(toBottom, 800);
|
window.setTimeout(toBottom, 800);
|
||||||
|
|
||||||
// scrollToBottomでmoreFetchingボタンが画面外まで出るまで
|
// scrollToBottomでmoreFetchingボタンが画面外まで出るまで
|
||||||
// more = trueを遅らせる
|
// more = trueを遅らせる
|
||||||
setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
moreFetching.value = false;
|
moreFetching.value = false;
|
||||||
}, 2000);
|
}, 2000);
|
||||||
});
|
});
|
||||||
|
@ -461,11 +461,11 @@ onBeforeMount(() => {
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (timerForSetPause) {
|
if (timerForSetPause) {
|
||||||
clearTimeout(timerForSetPause);
|
window.clearTimeout(timerForSetPause);
|
||||||
timerForSetPause = null;
|
timerForSetPause = null;
|
||||||
}
|
}
|
||||||
if (preventAppearFetchMoreTimer.value) {
|
if (preventAppearFetchMoreTimer.value) {
|
||||||
clearTimeout(preventAppearFetchMoreTimer.value);
|
window.clearTimeout(preventAppearFetchMoreTimer.value);
|
||||||
preventAppearFetchMoreTimer.value = null;
|
preventAppearFetchMoreTimer.value = null;
|
||||||
}
|
}
|
||||||
scrollObserver.value?.disconnect();
|
scrollObserver.value?.disconnect();
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
|
||||||
const providedContextEl = document.getElementById('misskey_embedCtx');
|
const providedContextEl = window.document.getElementById('misskey_embedCtx');
|
||||||
|
|
||||||
export type ServerContext = {
|
export type ServerContext = {
|
||||||
clip?: Misskey.entities.Clip;
|
clip?: Misskey.entities.Clip;
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { misskeyApi } from '@/misskey-api.js';
|
import { misskeyApi } from '@/misskey-api.js';
|
||||||
|
|
||||||
const providedMetaEl = document.getElementById('misskey_meta');
|
const providedMetaEl = window.document.getElementById('misskey_meta');
|
||||||
|
|
||||||
const _serverMetadata: Misskey.entities.MetaDetailed | null = (providedMetaEl && providedMetaEl.textContent) ? JSON.parse(providedMetaEl.textContent) : null;
|
const _serverMetadata: Misskey.entities.MetaDetailed | null = (providedMetaEl && providedMetaEl.textContent) ? JSON.parse(providedMetaEl.textContent) : null;
|
||||||
|
|
||||||
|
|
|
@ -35,15 +35,15 @@ export function assertIsTheme(theme: Record<string, unknown>): theme is Theme {
|
||||||
export function applyTheme(theme: Theme, persist = true) {
|
export function applyTheme(theme: Theme, persist = true) {
|
||||||
if (timeout) window.clearTimeout(timeout);
|
if (timeout) window.clearTimeout(timeout);
|
||||||
|
|
||||||
document.documentElement.classList.add('_themeChanging_');
|
window.document.documentElement.classList.add('_themeChanging_');
|
||||||
|
|
||||||
timeout = window.setTimeout(() => {
|
timeout = window.setTimeout(() => {
|
||||||
document.documentElement.classList.remove('_themeChanging_');
|
window.document.documentElement.classList.remove('_themeChanging_');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
const colorScheme = theme.base === 'dark' ? 'dark' : 'light';
|
const colorScheme = theme.base === 'dark' ? 'dark' : 'light';
|
||||||
|
|
||||||
document.documentElement.dataset.colorScheme = colorScheme;
|
window.document.documentElement.dataset.colorScheme = colorScheme;
|
||||||
|
|
||||||
// Deep copy
|
// Deep copy
|
||||||
const _theme = JSON.parse(JSON.stringify(theme));
|
const _theme = JSON.parse(JSON.stringify(theme));
|
||||||
|
@ -55,7 +55,7 @@ export function applyTheme(theme: Theme, persist = true) {
|
||||||
|
|
||||||
const props = compile(_theme);
|
const props = compile(_theme);
|
||||||
|
|
||||||
for (const tag of document.head.children) {
|
for (const tag of window.document.head.children) {
|
||||||
if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
|
if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
|
||||||
tag.setAttribute('content', props['htmlThemeColor']);
|
tag.setAttribute('content', props['htmlThemeColor']);
|
||||||
break;
|
break;
|
||||||
|
@ -63,7 +63,7 @@ export function applyTheme(theme: Theme, persist = true) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [k, v] of Object.entries(props)) {
|
for (const [k, v] of Object.entries(props)) {
|
||||||
document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
|
window.document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// iframeを正常に透過させるために、cssのcolor-schemeは `light dark;` 固定にしてある。style.scss参照
|
// iframeを正常に透過させるために、cssのcolor-schemeは `light dark;` 固定にしてある。style.scss参照
|
||||||
|
|
|
@ -52,8 +52,8 @@ function safeURIDecode(str: string): string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const page = location.pathname.split('/')[2];
|
const page = window.location.pathname.split('/')[2];
|
||||||
const contentId = safeURIDecode(location.pathname.split('/')[3]);
|
const contentId = safeURIDecode(window.location.pathname.split('/')[3]);
|
||||||
if (_DEV_) console.log(page, contentId);
|
if (_DEV_) console.log(page, contentId);
|
||||||
|
|
||||||
const embedParams = inject(DI.embedParams, defaultEmbedParams);
|
const embedParams = inject(DI.embedParams, defaultEmbedParams);
|
||||||
|
|
|
@ -51,9 +51,71 @@ export default [
|
||||||
allowSingleExtends: true,
|
allowSingleExtends: true,
|
||||||
}],
|
}],
|
||||||
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
|
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
|
||||||
// window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
|
// window ... グローバルスコープと衝突し、予期せぬ結果を招くため
|
||||||
// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
|
// e ... error や event など、複数のキーワードの頭文字であり分かりにくいため
|
||||||
'id-denylist': ['error', 'window', 'e'],
|
// close ... window.closeと衝突 or 紛らわしい
|
||||||
|
// open ... window.openと衝突 or 紛らわしい
|
||||||
|
// fetch ... window.fetchと衝突 or 紛らわしい
|
||||||
|
// location ... window.locationと衝突 or 紛らわしい
|
||||||
|
// document ... window.documentと衝突 or 紛らわしい
|
||||||
|
// history ... window.historyと衝突 or 紛らわしい
|
||||||
|
// scroll ... window.scrollと衝突 or 紛らわしい
|
||||||
|
// setTimeout ... window.setTimeoutと衝突 or 紛らわしい
|
||||||
|
// setInterval ... window.setIntervalと衝突 or 紛らわしい
|
||||||
|
// clearTimeout ... window.clearTimeoutと衝突 or 紛らわしい
|
||||||
|
// clearInterval ... window.clearIntervalと衝突 or 紛らわしい
|
||||||
|
'id-denylist': ['error', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history', 'scroll', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'],
|
||||||
|
'no-restricted-globals': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'name': 'open',
|
||||||
|
'message': 'Use `window.open`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'close',
|
||||||
|
'message': 'Use `window.close`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'fetch',
|
||||||
|
'message': 'Use `window.fetch`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'location',
|
||||||
|
'message': 'Use `window.location`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'document',
|
||||||
|
'message': 'Use `window.document`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'history',
|
||||||
|
'message': 'Use `window.history`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'scroll',
|
||||||
|
'message': 'Use `window.scroll`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'setTimeout',
|
||||||
|
'message': 'Use `window.setTimeout`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'setInterval',
|
||||||
|
'message': 'Use `window.setInterval`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'clearTimeout',
|
||||||
|
'message': 'Use `window.clearTimeout`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'clearInterval',
|
||||||
|
'message': 'Use `window.clearInterval`.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'name',
|
||||||
|
'message': 'Use `window.name`. もしくは name という変数名を定義し忘れている',
|
||||||
|
},
|
||||||
|
],
|
||||||
'no-shadow': ['warn'],
|
'no-shadow': ['warn'],
|
||||||
'vue/attributes-order': ['error', {
|
'vue/attributes-order': ['error', {
|
||||||
alphabetical: false,
|
alphabetical: false,
|
||||||
|
|
|
@ -4,15 +4,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||||
const address = new URL(document.querySelector<HTMLMetaElement>('meta[property="instance_url"]')?.content || location.href);
|
const address = new URL(window.document.querySelector<HTMLMetaElement>('meta[property="instance_url"]')?.content || window.location.href);
|
||||||
const siteName = document.querySelector<HTMLMetaElement>('meta[property="og:site_name"]')?.content;
|
const siteName = window.document.querySelector<HTMLMetaElement>('meta[property="og:site_name"]')?.content;
|
||||||
|
|
||||||
export const host = address.host;
|
export const host = address.host;
|
||||||
export const hostname = address.hostname;
|
export const hostname = address.hostname;
|
||||||
export const url = address.origin;
|
export const url = address.origin;
|
||||||
export const port = address.port;
|
export const port = address.port;
|
||||||
export const apiUrl = location.origin + '/api';
|
export const apiUrl = window.location.origin + '/api';
|
||||||
export const wsOrigin = location.origin;
|
export const wsOrigin = window.location.origin;
|
||||||
export const lang = localStorage.getItem('lang') ?? 'en-US';
|
export const lang = localStorage.getItem('lang') ?? 'en-US';
|
||||||
export const langs = _LANGS_;
|
export const langs = _LANGS_;
|
||||||
export const version = _VERSION_;
|
export const version = _VERSION_;
|
||||||
|
|
|
@ -51,7 +51,7 @@ export function onScrollTop(el: HTMLElement, cb: (topVisible: boolean) => unknow
|
||||||
// - toleranceの範囲内に収まる程度の微量なスクロールが発生した
|
// - toleranceの範囲内に収まる程度の微量なスクロールが発生した
|
||||||
let prevTopVisible = firstTopVisible;
|
let prevTopVisible = firstTopVisible;
|
||||||
const onScroll = () => {
|
const onScroll = () => {
|
||||||
if (!document.body.contains(el)) return;
|
if (!window.document.body.contains(el)) return;
|
||||||
|
|
||||||
const topVisible = isHeadVisible(el, tolerance);
|
const topVisible = isHeadVisible(el, tolerance);
|
||||||
if (topVisible !== prevTopVisible) {
|
if (topVisible !== prevTopVisible) {
|
||||||
|
@ -78,7 +78,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1
|
||||||
|
|
||||||
const containerOrWindow = container ?? window;
|
const containerOrWindow = container ?? window;
|
||||||
const onScroll = () => {
|
const onScroll = () => {
|
||||||
if (!document.body.contains(el)) return;
|
if (!window.document.body.contains(el)) return;
|
||||||
if (isTailVisible(el, 1, container)) {
|
if (isTailVisible(el, 1, container)) {
|
||||||
cb();
|
cb();
|
||||||
if (once) removeListener();
|
if (once) removeListener();
|
||||||
|
@ -145,8 +145,8 @@ export function isTailVisible(el: HTMLElement, tolerance = 1, container = getScr
|
||||||
// https://ja.javascript.info/size-and-scroll-window#ref-932
|
// https://ja.javascript.info/size-and-scroll-window#ref-932
|
||||||
export function getBodyScrollHeight() {
|
export function getBodyScrollHeight() {
|
||||||
return Math.max(
|
return Math.max(
|
||||||
document.body.scrollHeight, document.documentElement.scrollHeight,
|
window.document.body.scrollHeight, window.document.documentElement.scrollHeight,
|
||||||
document.body.offsetHeight, document.documentElement.offsetHeight,
|
window.document.body.offsetHeight, window.document.documentElement.offsetHeight,
|
||||||
document.body.clientHeight, document.documentElement.clientHeight,
|
window.document.body.clientHeight, window.document.documentElement.clientHeight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,18 +7,18 @@ import { onMounted, onUnmounted, ref } from 'vue';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
export function useDocumentVisibility(): Ref<DocumentVisibilityState> {
|
export function useDocumentVisibility(): Ref<DocumentVisibilityState> {
|
||||||
const visibility = ref(document.visibilityState);
|
const visibility = ref(window.document.visibilityState);
|
||||||
|
|
||||||
const onChange = (): void => {
|
const onChange = (): void => {
|
||||||
visibility.value = document.visibilityState;
|
visibility.value = window.document.visibilityState;
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('visibilitychange', onChange);
|
window.document.addEventListener('visibilitychange', onChange);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('visibilitychange', onChange);
|
window.document.removeEventListener('visibilitychange', onChange);
|
||||||
});
|
});
|
||||||
|
|
||||||
return visibility;
|
return visibility;
|
||||||
|
|
|
@ -21,9 +21,9 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "22.17.2",
|
"@types/node": "22.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
"@typescript-eslint/eslint-plugin": "8.42.0",
|
||||||
"@typescript-eslint/parser": "8.40.0",
|
"@typescript-eslint/parser": "8.42.0",
|
||||||
"esbuild": "0.25.9",
|
"esbuild": "0.25.9",
|
||||||
"eslint-plugin-vue": "10.4.0",
|
"eslint-plugin-vue": "10.4.0",
|
||||||
"nodemon": "3.1.10",
|
"nodemon": "3.1.10",
|
||||||
|
@ -35,6 +35,6 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"vue": "3.5.19"
|
"vue": "3.5.21"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,13 +23,13 @@
|
||||||
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
||||||
"@rollup/plugin-json": "6.1.0",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-replace": "6.0.2",
|
"@rollup/plugin-replace": "6.0.2",
|
||||||
"@rollup/pluginutils": "5.2.0",
|
"@rollup/pluginutils": "5.3.0",
|
||||||
"@sentry/vue": "10.5.0",
|
"@sentry/vue": "10.10.0",
|
||||||
"@syuilo/aiscript": "1.1.0",
|
"@syuilo/aiscript": "1.1.0",
|
||||||
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
|
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
|
||||||
"@twemoji/parser": "16.0.0",
|
"@twemoji/parser": "16.0.0",
|
||||||
"@vitejs/plugin-vue": "6.0.1",
|
"@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",
|
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
|
||||||
"analytics": "0.8.19",
|
"analytics": "0.8.19",
|
||||||
"astring": "1.9.0",
|
"astring": "1.9.0",
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
"chartjs-chart-matrix": "3.0.0",
|
"chartjs-chart-matrix": "3.0.0",
|
||||||
"chartjs-plugin-gradient": "0.6.1",
|
"chartjs-plugin-gradient": "0.6.1",
|
||||||
"chartjs-plugin-zoom": "2.2.0",
|
"chartjs-plugin-zoom": "2.2.0",
|
||||||
"chromatic": "13.1.3",
|
"chromatic": "13.1.4",
|
||||||
"compare-versions": "6.1.1",
|
"compare-versions": "6.1.1",
|
||||||
"cropperjs": "2.0.1",
|
"cropperjs": "2.0.1",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
|
@ -63,21 +63,21 @@
|
||||||
"misskey-reversi": "workspace:*",
|
"misskey-reversi": "workspace:*",
|
||||||
"photoswipe": "5.4.4",
|
"photoswipe": "5.4.4",
|
||||||
"punycode.js": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"rollup": "4.48.0",
|
"rollup": "4.50.1",
|
||||||
"sanitize-html": "2.17.0",
|
"sanitize-html": "2.17.0",
|
||||||
"sass": "1.90.0",
|
"sass": "1.92.1",
|
||||||
"shiki": "3.11.0",
|
"shiki": "3.12.2",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"three": "0.179.1",
|
"three": "0.180.0",
|
||||||
"throttle-debounce": "5.0.2",
|
"throttle-debounce": "5.0.2",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tsc-alias": "1.8.16",
|
"tsc-alias": "1.8.16",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.9.2",
|
"typescript": "5.9.2",
|
||||||
"v-code-diff": "1.13.1",
|
"v-code-diff": "1.13.1",
|
||||||
"vite": "7.1.3",
|
"vite": "7.1.5",
|
||||||
"vue": "3.5.19",
|
"vue": "3.5.21",
|
||||||
"vuedraggable": "next",
|
"vuedraggable": "next",
|
||||||
"wanakana": "5.3.1"
|
"wanakana": "5.3.1"
|
||||||
},
|
},
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
"@misskey-dev/summaly": "5.2.3",
|
"@misskey-dev/summaly": "5.2.3",
|
||||||
"@storybook/addon-essentials": "8.6.14",
|
"@storybook/addon-essentials": "8.6.14",
|
||||||
"@storybook/addon-interactions": "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-mdx-gfm": "8.6.14",
|
||||||
"@storybook/addon-storysource": "8.6.14",
|
"@storybook/addon-storysource": "8.6.14",
|
||||||
"@storybook/blocks": "8.6.14",
|
"@storybook/blocks": "8.6.14",
|
||||||
|
@ -93,31 +93,31 @@
|
||||||
"@storybook/core-events": "8.6.14",
|
"@storybook/core-events": "8.6.14",
|
||||||
"@storybook/manager-api": "8.6.14",
|
"@storybook/manager-api": "8.6.14",
|
||||||
"@storybook/preview-api": "8.6.14",
|
"@storybook/preview-api": "8.6.14",
|
||||||
"@storybook/react": "9.1.3",
|
"@storybook/react": "9.1.5",
|
||||||
"@storybook/react-vite": "9.1.3",
|
"@storybook/react-vite": "9.1.5",
|
||||||
"@storybook/test": "8.6.14",
|
"@storybook/test": "8.6.14",
|
||||||
"@storybook/theming": "8.6.14",
|
"@storybook/theming": "8.6.14",
|
||||||
"@storybook/types": "8.6.14",
|
"@storybook/types": "8.6.14",
|
||||||
"@storybook/vue3": "9.1.3",
|
"@storybook/vue3": "9.1.5",
|
||||||
"@storybook/vue3-vite": "9.1.3",
|
"@storybook/vue3-vite": "9.1.5",
|
||||||
"@tabler/icons-webfont": "3.34.1",
|
"@tabler/icons-webfont": "3.34.1",
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/canvas-confetti": "1.9.0",
|
"@types/canvas-confetti": "1.9.0",
|
||||||
"@types/estree": "1.0.8",
|
"@types/estree": "1.0.8",
|
||||||
"@types/matter-js": "0.20.0",
|
"@types/matter-js": "0.20.0",
|
||||||
"@types/micromatch": "4.0.9",
|
"@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/punycode.js": "npm:@types/punycode@2.1.4",
|
||||||
"@types/sanitize-html": "2.16.0",
|
"@types/sanitize-html": "2.16.0",
|
||||||
"@types/seedrandom": "3.0.8",
|
"@types/seedrandom": "3.0.8",
|
||||||
"@types/throttle-debounce": "5.0.2",
|
"@types/throttle-debounce": "5.0.2",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/ws": "8.18.1",
|
"@types/ws": "8.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
"@typescript-eslint/eslint-plugin": "8.42.0",
|
||||||
"@typescript-eslint/parser": "8.40.0",
|
"@typescript-eslint/parser": "8.42.0",
|
||||||
"@vitest/coverage-v8": "3.2.4",
|
"@vitest/coverage-v8": "3.2.4",
|
||||||
"@vue/compiler-core": "3.5.19",
|
"@vue/compiler-core": "3.5.21",
|
||||||
"@vue/runtime-core": "3.5.19",
|
"@vue/runtime-core": "3.5.21",
|
||||||
"acorn": "8.15.0",
|
"acorn": "8.15.0",
|
||||||
"cross-env": "10.0.0",
|
"cross-env": "10.0.0",
|
||||||
"cypress": "14.5.4",
|
"cypress": "14.5.4",
|
||||||
|
@ -128,17 +128,17 @@
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.8",
|
"micromatch": "4.0.8",
|
||||||
"minimatch": "10.0.3",
|
"minimatch": "10.0.3",
|
||||||
"msw": "2.10.5",
|
"msw": "2.11.1",
|
||||||
"msw-storybook-addon": "2.0.5",
|
"msw-storybook-addon": "2.0.5",
|
||||||
"nodemon": "3.1.10",
|
"nodemon": "3.1.10",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "19.1.1",
|
||||||
"seedrandom": "3.0.5",
|
"seedrandom": "3.0.5",
|
||||||
"start-server-and-test": "2.0.13",
|
"start-server-and-test": "2.1.0",
|
||||||
"storybook": "9.1.3",
|
"storybook": "9.1.5",
|
||||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
"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",
|
"vite-plugin-turbosnap": "1.0.3",
|
||||||
"vitest": "3.2.4",
|
"vitest": "3.2.4",
|
||||||
"vitest-fetch-mock": "0.4.5",
|
"vitest-fetch-mock": "0.4.5",
|
||||||
|
|
|
@ -29,6 +29,6 @@ const users = ref<Misskey.entities.UserLite[]>([]);
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
users.value = await misskeyApi('users/show', {
|
users.value = await misskeyApi('users/show', {
|
||||||
userIds: props.userIds,
|
userIds: props.userIds,
|
||||||
}) as unknown as Misskey.entities.UserLite[];
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -27,16 +27,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { host } from '@@/js/config.js';
|
import { host } from '@@/js/config.js';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
|
||||||
import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
|
import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
|
||||||
import { sum } from '@/utility/array.js';
|
import { sum } from '@/utility/array.js';
|
||||||
import { pleaseLogin } from '@/utility/please-login.js';
|
import { pleaseLogin } from '@/utility/please-login.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { useLowresTime } from '@/composables/use-lowres-time.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
noteId: string;
|
noteId: string;
|
||||||
|
@ -48,7 +48,21 @@ const props = defineProps<{
|
||||||
author?: Misskey.entities.UserLite;
|
author?: Misskey.entities.UserLite;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const remaining = ref(-1);
|
const now = useLowresTime();
|
||||||
|
|
||||||
|
const expiresAtTime = computed(() => props.expiresAt ? new Date(props.expiresAt).getTime() : null);
|
||||||
|
|
||||||
|
const remaining = computed(() => {
|
||||||
|
if (expiresAtTime.value == null) return -1;
|
||||||
|
return Math.floor(Math.max(expiresAtTime.value - now.value, 0) / 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
const remainingWatchStop = watch(remaining, (to) => {
|
||||||
|
if (to <= 0) {
|
||||||
|
showResult.value = true;
|
||||||
|
remainingWatchStop();
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
const total = computed(() => sum(props.choices.map(x => x.votes)));
|
const total = computed(() => sum(props.choices.map(x => x.votes)));
|
||||||
const closed = computed(() => remaining.value === 0);
|
const closed = computed(() => remaining.value === 0);
|
||||||
|
@ -71,22 +85,7 @@ const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
||||||
url: `https://${host}/notes/${props.noteId}`,
|
url: `https://${host}/notes/${props.noteId}`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 期限付きアンケート
|
const vote = async (id: number) => {
|
||||||
if (props.expiresAt) {
|
|
||||||
const tick = () => {
|
|
||||||
remaining.value = Math.floor(Math.max(new Date(props.expiresAt!).getTime() - Date.now(), 0) / 1000);
|
|
||||||
if (remaining.value === 0) {
|
|
||||||
showResult.value = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useInterval(tick, 3000, {
|
|
||||||
immediate: true,
|
|
||||||
afterMounted: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const vote = async (id) => {
|
|
||||||
if (props.readOnly || closed.value || isVoted.value) return;
|
if (props.readOnly || closed.value || isVoted.value) return;
|
||||||
|
|
||||||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||||
|
|
|
@ -823,17 +823,15 @@ async function saveServerDraft(clearLocal = false) {
|
||||||
return await os.apiWithDialog(serverDraftId.value == null ? 'notes/drafts/create' : 'notes/drafts/update', {
|
return await os.apiWithDialog(serverDraftId.value == null ? 'notes/drafts/create' : 'notes/drafts/update', {
|
||||||
...(serverDraftId.value == null ? {} : { draftId: serverDraftId.value }),
|
...(serverDraftId.value == null ? {} : { draftId: serverDraftId.value }),
|
||||||
text: text.value,
|
text: text.value,
|
||||||
useCw: useCw.value,
|
cw: useCw.value ? cw.value || null : null,
|
||||||
cw: cw.value,
|
|
||||||
visibility: visibility.value,
|
visibility: visibility.value,
|
||||||
localOnly: localOnly.value,
|
localOnly: localOnly.value,
|
||||||
hashtag: hashtags.value,
|
hashtag: hashtags.value,
|
||||||
...(files.value.length > 0 ? { fileIds: files.value.map(f => f.id) } : {}),
|
...(files.value.length > 0 ? { fileIds: files.value.map(f => f.id) } : {}),
|
||||||
poll: poll.value,
|
poll: poll.value,
|
||||||
...(visibleUsers.value.length > 0 ? { visibleUserIds: visibleUsers.value.map(x => x.id) } : {}),
|
...(visibleUsers.value.length > 0 ? { visibleUserIds: visibleUsers.value.map(x => x.id) } : {}),
|
||||||
renoteId: renoteTargetNote.value ? renoteTargetNote.value.id : undefined,
|
renoteId: renoteTargetNote.value ? renoteTargetNote.value.id : quoteId.value ? quoteId.value : undefined,
|
||||||
replyId: replyTargetNote.value ? replyTargetNote.value.id : undefined,
|
replyId: replyTargetNote.value ? replyTargetNote.value.id : undefined,
|
||||||
quoteId: quoteId.value,
|
|
||||||
channelId: targetChannel.value ? targetChannel.value.id : undefined,
|
channelId: targetChannel.value ? targetChannel.value.id : undefined,
|
||||||
reactionAcceptance: reactionAcceptance.value,
|
reactionAcceptance: reactionAcceptance.value,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
|
|
@ -114,14 +114,13 @@ async function unsubscribe() {
|
||||||
|
|
||||||
if ($i && accounts.length >= 2) {
|
if ($i && accounts.length >= 2) {
|
||||||
apiWithDialog('sw/unregister', {
|
apiWithDialog('sw/unregister', {
|
||||||
i: $i.token,
|
|
||||||
endpoint,
|
endpoint,
|
||||||
});
|
}, $i.token);
|
||||||
} else {
|
} else {
|
||||||
pushSubscription.value.unsubscribe();
|
pushSubscription.value.unsubscribe();
|
||||||
apiWithDialog('sw/unregister', {
|
apiWithDialog('sw/unregister', {
|
||||||
endpoint,
|
endpoint,
|
||||||
});
|
}, null);
|
||||||
pushSubscription.value = null;
|
pushSubscription.value = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,7 +133,7 @@ function encode(buffer: ArrayBuffer | null) {
|
||||||
* Convert the URL safe base64 string to a Uint8Array
|
* Convert the URL safe base64 string to a Uint8Array
|
||||||
* @param base64String base64 string
|
* @param base64String base64 string
|
||||||
*/
|
*/
|
||||||
function urlBase64ToUint8Array(base64String: string): Uint8Array {
|
function urlBase64ToUint8Array(base64String: string): BufferSource {
|
||||||
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
||||||
const base64 = (base64String + padding)
|
const base64 = (base64String + padding)
|
||||||
.replace(/-/g, '+')
|
.replace(/-/g, '+')
|
||||||
|
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<MkA :to="forModeration ? `/admin/roles/${role.id}` : `/roles/${role.id}`" :class="$style.root" tabindex="-1" :style="{ '--color': role.color }">
|
<MkA :to="forModeration ? `/admin/roles/${role.id}` : `/roles/${role.id}`" :class="$style.root" tabindex="-1" :style="{ '--color': role.color }">
|
||||||
<template v-if="forModeration">
|
<template v-if="forModeration">
|
||||||
<i v-if="role.isPublic" class="ti ti-world" :class="$style.icon" style="color: var(--MI_THEME-success)"></i>
|
<i v-if="'isPublic' in role && role.isPublic" class="ti ti-world" :class="$style.icon" style="color: var(--MI_THEME-success)"></i>
|
||||||
<i v-else class="ti ti-lock" :class="$style.icon" style="color: var(--MI_THEME-warn)"></i>
|
<i v-else class="ti ti-lock" :class="$style.icon" style="color: var(--MI_THEME-warn)"></i>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<span :class="$style.bodyName">{{ role.name }}</span>
|
<span :class="$style.bodyName">{{ role.name }}</span>
|
||||||
<template v-if="detailed">
|
<template v-if="detailed && 'target' in role && 'usersCount' in role">
|
||||||
<span v-if="role.target === 'manual'" :class="$style.bodyUsers">{{ role.usersCount }} users</span>
|
<span v-if="role.target === 'manual'" :class="$style.bodyUsers">{{ role.usersCount }} users</span>
|
||||||
<span v-else-if="role.target === 'conditional'" :class="$style.bodyUsers">? users</span>
|
<span v-else-if="role.target === 'conditional'" :class="$style.bodyUsers">? users</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -39,7 +39,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
role: Misskey.entities.Role;
|
role: Misskey.entities.Role | Misskey.entities.IResponse['roles'][number];
|
||||||
forModeration: boolean;
|
forModeration: boolean;
|
||||||
detailed?: boolean;
|
detailed?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
|
|
|
@ -72,7 +72,7 @@ import { getStaticImageUrl } from '@/utility/media-proxy.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
showing: boolean;
|
showing: boolean;
|
||||||
q: string;
|
q: string | Misskey.entities.UserDetailed;
|
||||||
source: HTMLElement;
|
source: HTMLElement;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -99,10 +99,11 @@ async function fetchUser() {
|
||||||
user.value = props.q;
|
user.value = props.q;
|
||||||
error.value = false;
|
error.value = false;
|
||||||
} else {
|
} else {
|
||||||
const query: Omit<Misskey.entities.UsersShowRequest, 'userIds'> = props.q.startsWith('@') ?
|
const query: Misskey.entities.UsersShowRequest = props.q.startsWith('@') ?
|
||||||
Misskey.acct.parse(props.q.substring(1)) :
|
Misskey.acct.parse(props.q.substring(1)) :
|
||||||
{ userId: props.q };
|
{ userId: props.q };
|
||||||
|
|
||||||
|
// @ts-expect-error payloadの引数側の型が正常に解決されない
|
||||||
misskeyApi('users/show', query).then(res => {
|
misskeyApi('users/show', query).then(res => {
|
||||||
if (!props.showing) return;
|
if (!props.showing) return;
|
||||||
user.value = res;
|
user.value = res;
|
||||||
|
|
|
@ -4,31 +4,39 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="[$style.root, { [$style.inline]: inline }]">
|
<component
|
||||||
<a v-if="external" :class="$style.main" class="_button" :href="to" target="_blank">
|
:is="to ? 'div' : 'button'"
|
||||||
|
:class="[
|
||||||
|
$style.root,
|
||||||
|
{
|
||||||
|
[$style.inline]: inline,
|
||||||
|
'_button': !to,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="to ? (external ? 'a' : 'MkA') : 'div'"
|
||||||
|
:class="[$style.main, { [$style.active]: active }]"
|
||||||
|
class="_button"
|
||||||
|
v-bind="to ? (external ? { href: to, target: '_blank' } : { to, behavior }) : {}"
|
||||||
|
>
|
||||||
<span :class="$style.icon"><slot name="icon"></slot></span>
|
<span :class="$style.icon"><slot name="icon"></slot></span>
|
||||||
<span :class="$style.text"><slot></slot></span>
|
<div :class="$style.headerText">
|
||||||
<span :class="$style.suffix">
|
<div>
|
||||||
<span :class="$style.suffixText"><slot name="suffix"></slot></span>
|
<MkCondensedLine :minScale="2 / 3"><slot></slot></MkCondensedLine>
|
||||||
<i class="ti ti-external-link"></i>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<MkA v-else :class="[$style.main, { [$style.active]: active }]" class="_button" :to="to" :behavior="behavior">
|
|
||||||
<span :class="$style.icon"><slot name="icon"></slot></span>
|
|
||||||
<span :class="$style.text"><slot></slot></span>
|
|
||||||
<span :class="$style.suffix">
|
|
||||||
<span :class="$style.suffixText"><slot name="suffix"></slot></span>
|
|
||||||
<i class="ti ti-chevron-right"></i>
|
|
||||||
</span>
|
|
||||||
</MkA>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<span :class="$style.suffix">
|
||||||
|
<span :class="$style.suffixText"><slot name="suffix"></slot></span>
|
||||||
|
<i :class="to && external ? 'ti ti-external-link' : 'ti ti-chevron-right'"></i>
|
||||||
|
</span>
|
||||||
|
</component>
|
||||||
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { } from 'vue';
|
defineProps<{
|
||||||
|
to?: string;
|
||||||
const props = defineProps<{
|
|
||||||
to: string;
|
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
external?: boolean;
|
external?: boolean;
|
||||||
behavior?: null | 'window' | 'browser';
|
behavior?: null | 'window' | 'browser';
|
||||||
|
@ -75,17 +83,18 @@ const props = defineProps<{
|
||||||
&:empty {
|
&:empty {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
& + .text {
|
& + .headerText {
|
||||||
padding-left: 4px;
|
padding-left: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
.headerText {
|
||||||
flex-shrink: 1;
|
white-space: nowrap;
|
||||||
white-space: normal;
|
text-overflow: ellipsis;
|
||||||
|
text-align: start;
|
||||||
|
overflow: hidden;
|
||||||
padding-right: 12px;
|
padding-right: 12px;
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.suffix {
|
.suffix {
|
||||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a ref="el" :href="to" :class="active ? activeClass : null" @click.prevent="nav" @contextmenu.prevent.stop="onContextmenu">
|
<a ref="el" :href="to" :class="active ? activeClass : null" @click="nav" @contextmenu.prevent.stop="onContextmenu">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
@ -86,6 +86,11 @@ function openWindow() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function nav(ev: MouseEvent) {
|
function nav(ev: MouseEvent) {
|
||||||
|
// 制御キーとの組み合わせは無視(shiftを除く)
|
||||||
|
if (ev.metaKey || ev.altKey || ev.ctrlKey) return;
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
if (behavior === 'browser') {
|
if (behavior === 'browser') {
|
||||||
window.location.href = props.to;
|
window.location.href = props.to;
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
@ -1,70 +0,0 @@
|
|
||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
|
@ -14,9 +14,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import isChromatic from 'chromatic/isChromatic';
|
import isChromatic from 'chromatic/isChromatic';
|
||||||
import { onMounted, onUnmounted, ref, computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { dateTimeFormat } from '@@/js/intl-const.js';
|
import { dateTimeFormat } from '@@/js/intl-const.js';
|
||||||
|
import { useLowresTime } from '@/composables/use-lowres-time.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
time: Date | string | number | null;
|
time: Date | string | number | null;
|
||||||
|
@ -46,8 +47,10 @@ const _time = props.time == null ? NaN : getDateSafe(props.time).getTime();
|
||||||
const invalid = Number.isNaN(_time);
|
const invalid = Number.isNaN(_time);
|
||||||
const absolute = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid;
|
const absolute = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid;
|
||||||
|
|
||||||
|
const actualNow = useLowresTime();
|
||||||
|
const now = computed(() => (props.origin ? props.origin.getTime() : actualNow.value));
|
||||||
|
|
||||||
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
|
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
|
||||||
const now = ref(props.origin?.getTime() ?? Date.now());
|
|
||||||
const ago = computed(() => (now.value - _time) / 1000/*ms*/);
|
const ago = computed(() => (now.value - _time) / 1000/*ms*/);
|
||||||
|
|
||||||
const relative = computed<string>(() => {
|
const relative = computed<string>(() => {
|
||||||
|
@ -72,29 +75,6 @@ const relative = computed<string>(() => {
|
||||||
i18n.tsx._timeIn.seconds({ n: (~~(-ago.value % 60)).toString() })
|
i18n.tsx._timeIn.seconds({ n: (~~(-ago.value % 60)).toString() })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let tickId: number;
|
|
||||||
let currentInterval: number;
|
|
||||||
|
|
||||||
function tick() {
|
|
||||||
now.value = Date.now();
|
|
||||||
const nextInterval = ago.value < 60 ? 10000 : ago.value < 3600 ? 60000 : 180000;
|
|
||||||
|
|
||||||
if (currentInterval !== nextInterval) {
|
|
||||||
if (tickId) window.clearInterval(tickId);
|
|
||||||
currentInterval = nextInterval;
|
|
||||||
tickId = window.setInterval(tick, nextInterval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!invalid && props.origin === null && (props.mode === 'relative' || props.mode === 'detail')) {
|
|
||||||
onMounted(() => {
|
|
||||||
tick();
|
|
||||||
});
|
|
||||||
onUnmounted(() => {
|
|
||||||
if (tickId) window.clearInterval(tickId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkPageHeader v-else v-model:tab="tab" v-bind="pageHeaderProps"/>
|
<MkPageHeader v-else v-model:tab="tab" v-bind="pageHeaderProps"/>
|
||||||
</template>
|
</template>
|
||||||
<div :class="$style.body">
|
<div :class="$style.body">
|
||||||
<MkSwiper v-if="prefer.s.enableHorizontalSwipe && swipable && (props.tabs?.length ?? 1) > 1" v-model:tab="tab" :class="$style.swiper" :tabs="props.tabs">
|
<MkSwiper v-if="prefer.s.enableHorizontalSwipe && swipable && (props.tabs?.length ?? 1) > 1" v-model:tab="tab" :class="$style.swiper" :tabs="props.tabs ?? []">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</MkSwiper>
|
</MkSwiper>
|
||||||
<slot v-else></slot>
|
<slot v-else></slot>
|
||||||
|
@ -45,7 +45,7 @@ const props = withDefaults(defineProps<PageHeaderProps & {
|
||||||
});
|
});
|
||||||
|
|
||||||
const pageHeaderProps = computed(() => {
|
const pageHeaderProps = computed(() => {
|
||||||
const { reversed, ...rest } = props;
|
const { reversed, tab, ...rest } = props;
|
||||||
return rest;
|
return rest;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -75,10 +75,6 @@ defineExpose({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.root {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.body, .swiper {
|
.body, .swiper {
|
||||||
min-height: calc(100cqh - (var(--MI-stickyTop, 0px) + var(--MI-stickyBottom, 0px)));
|
min-height: calc(100cqh - (var(--MI-stickyTop, 0px) + var(--MI-stickyBottom, 0px)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,5 +65,12 @@ router.useListener('change', ({ resolved }) => {
|
||||||
.root {
|
.root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: var(--MI_THEME-bg);
|
background-color: var(--MI_THEME-bg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIXME: Safari 26 で contain: layout を指定するとバグるので、hotfixとして _pageContainer の content: strict を上書き
|
||||||
|
* https://github.com/misskey-dev/misskey/issues/16204#issuecomment-3265404776
|
||||||
|
* https://bugs.webkit.org/show_bug.cgi?id=297186
|
||||||
|
*/
|
||||||
|
contain: size style paint !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ref, readonly, computed } from 'vue';
|
||||||
|
|
||||||
|
const time = ref(Date.now());
|
||||||
|
|
||||||
|
export const TIME_UPDATE_INTERVAL = 10000; // 10秒
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 精度が求められないが定期的に更新しないといけない時計で使用(10秒に一度更新)。
|
||||||
|
* tickを各コンポーネントで行うのではなく、ここで一括して行うことでパフォーマンスを改善する。
|
||||||
|
*
|
||||||
|
* ※ マウント前の時刻を返す可能性があるため、通常は`useLowresTime`を使用する
|
||||||
|
*/
|
||||||
|
export const lowresTime = readonly(time);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 精度が求められないが定期的に更新しないといけない時計で使用(10秒に一度更新)。
|
||||||
|
* tickを各コンポーネントで行うのではなく、ここで一括して行うことでパフォーマンスを改善する。
|
||||||
|
*
|
||||||
|
* 必ず現在時刻以降を返すことを保証するコンポーサブル
|
||||||
|
*/
|
||||||
|
export function useLowresTime() {
|
||||||
|
// lowresTime自体はマウント前の時刻を返す可能性があるため、必ず現在時刻以降を返すことを保証する
|
||||||
|
const now = Date.now();
|
||||||
|
return computed(() => Math.max(time.value, now));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setInterval(() => {
|
||||||
|
time.value = Date.now();
|
||||||
|
}, TIME_UPDATE_INTERVAL);
|
|
@ -24,7 +24,7 @@ export const globalEvents = new EventEmitter<Events>();
|
||||||
|
|
||||||
export function useGlobalEvent<T extends keyof Events>(
|
export function useGlobalEvent<T extends keyof Events>(
|
||||||
event: T,
|
event: T,
|
||||||
callback: Events[T],
|
callback: EventEmitter.EventListener<Events, T>,
|
||||||
): void {
|
): void {
|
||||||
globalEvents.on(event, callback);
|
globalEvents.on(event, callback);
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
|
|
@ -94,7 +94,7 @@ export class Pizzax<T extends StateDef> {
|
||||||
|
|
||||||
private mergeState<X>(value: X, def: X): X {
|
private mergeState<X>(value: X, def: X): X {
|
||||||
if (this.isPureObject(value) && this.isPureObject(def)) {
|
if (this.isPureObject(value) && this.isPureObject(def)) {
|
||||||
const merged = deepMerge(value, def);
|
const merged = deepMerge<Record<PropertyKey, unknown>>(value, def);
|
||||||
|
|
||||||
if (_DEV_) console.log('Merging state. Incoming: ', value, ' Default: ', def, ' Result: ', merged);
|
if (_DEV_) console.log('Merging state. Incoming: ', value, ' Default: ', def, ' Result: ', merged);
|
||||||
|
|
||||||
|
|
|
@ -36,9 +36,9 @@ import { focusParent } from '@/utility/focus.js';
|
||||||
export const openingWindowsCount = ref(0);
|
export const openingWindowsCount = ref(0);
|
||||||
|
|
||||||
export type ApiWithDialogCustomErrors = Record<string, { title?: string; text: string; }>;
|
export type ApiWithDialogCustomErrors = Record<string, { title?: string; text: string; }>;
|
||||||
export const apiWithDialog = (<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req']>(
|
export const apiWithDialog = (<E extends keyof Misskey.Endpoints>(
|
||||||
endpoint: E,
|
endpoint: E,
|
||||||
data: P,
|
data: Misskey.Endpoints[E]['req'],
|
||||||
token?: string | null | undefined,
|
token?: string | null | undefined,
|
||||||
customErrors?: ApiWithDialogCustomErrors,
|
customErrors?: ApiWithDialogCustomErrors,
|
||||||
) => {
|
) => {
|
||||||
|
|
|
@ -11,12 +11,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkInput v-model="q" class="" :placeholder="i18n.ts.search" autocapitalize="off">
|
<MkInput v-model="q" class="" :placeholder="i18n.ts.search" autocapitalize="off">
|
||||||
<template #prefix><i class="ti ti-search"></i></template>
|
<template #prefix><i class="ti ti-search"></i></template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
<!-- たくさんあると邪魔
|
|
||||||
<div class="tags">
|
|
||||||
<span class="tag _button" v-for="tag in customEmojiTags" :class="{ active: selectedTags.has(tag) }" @click="toggleTag(tag)">{{ tag }}</span>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkFoldableSection v-if="searchEmojis">
|
<MkFoldableSection v-if="searchEmojis">
|
||||||
|
@ -42,22 +36,19 @@ import XEmoji from './emojis.emoji.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
import { customEmojis, customEmojiCategories, getCustomEmojiTags } from '@/custom-emojis.js';
|
import { customEmojis, customEmojiCategories } from '@/custom-emojis.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
|
|
||||||
const customEmojiTags = getCustomEmojiTags();
|
|
||||||
const q = ref('');
|
const q = ref('');
|
||||||
const searchEmojis = ref<Misskey.entities.EmojiSimple[] | null>(null);
|
const searchEmojis = ref<Misskey.entities.EmojiSimple[] | null>(null);
|
||||||
const selectedTags = ref(new Set());
|
|
||||||
|
|
||||||
function search() {
|
function search() {
|
||||||
if ((q.value === '' || q.value == null) && selectedTags.value.size === 0) {
|
if (q.value === '' || q.value == null) {
|
||||||
searchEmojis.value = null;
|
searchEmojis.value = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedTags.value.size === 0) {
|
|
||||||
const queryarry = q.value.match(/\:([a-z0-9_]*)\:/g);
|
const queryarry = q.value.match(/\:([a-z0-9_]*)\:/g);
|
||||||
|
|
||||||
if (queryarry) {
|
if (queryarry) {
|
||||||
|
@ -67,26 +58,11 @@ function search() {
|
||||||
} else {
|
} else {
|
||||||
searchEmojis.value = customEmojis.value.filter(emoji => emoji.name.includes(q.value) || emoji.aliases.includes(q.value));
|
searchEmojis.value = customEmojis.value.filter(emoji => emoji.name.includes(q.value) || emoji.aliases.includes(q.value));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
searchEmojis.value = customEmojis.value.filter(emoji => (emoji.name.includes(q.value) || emoji.aliases.includes(q.value)) && [...selectedTags.value].every(t => emoji.aliases.includes(t)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleTag(tag) {
|
|
||||||
if (selectedTags.value.has(tag)) {
|
|
||||||
selectedTags.value.delete(tag);
|
|
||||||
} else {
|
|
||||||
selectedTags.value.add(tag);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(q, () => {
|
watch(q, () => {
|
||||||
search();
|
search();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(selectedTags, () => {
|
|
||||||
search();
|
|
||||||
}, { deep: true });
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -231,6 +231,7 @@ import { ensureSignin, iAmAdmin, iAmModerator } from '@/i.js';
|
||||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { Paginator } from '@/utility/paginator.js';
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
import type { ChartSrc } from '@/components/MkChart.vue';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
|
|
|
@ -307,8 +307,8 @@ async function onFileSelectClicked() {
|
||||||
const driveFiles = await chooseFileFromPcAndUpload({
|
const driveFiles = await chooseFileFromPcAndUpload({
|
||||||
multiple: true,
|
multiple: true,
|
||||||
folderId: selectedFolderId.value,
|
folderId: selectedFolderId.value,
|
||||||
// 拡張子は消す
|
// // 拡張子は消す
|
||||||
nameConverter: (file) => file.name.replace(/\.[a-zA-Z0-9]+$/, ''),
|
// nameConverter: (file) => file.name.replace(/\.[a-zA-Z0-9]+$/, ''),
|
||||||
});
|
});
|
||||||
|
|
||||||
gridItems.value.push(...driveFiles.map(fromDriveFile));
|
gridItems.value.push(...driveFiles.map(fromDriveFile));
|
||||||
|
|
|
@ -26,10 +26,10 @@ const chartEl = useTemplateRef('chartEl');
|
||||||
|
|
||||||
const { handler: externalTooltipHandler } = useChartTooltip();
|
const { handler: externalTooltipHandler } = useChartTooltip();
|
||||||
|
|
||||||
let chartInstance: Chart;
|
let chartInstance: Chart | null = null;
|
||||||
|
|
||||||
function setData(values) {
|
function setData(values) {
|
||||||
if (chartInstance == null) return;
|
if (chartInstance == null || chartInstance.data.labels == null) return;
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
chartInstance.data.labels.push('');
|
chartInstance.data.labels.push('');
|
||||||
chartInstance.data.datasets[0].data.push(value);
|
chartInstance.data.datasets[0].data.push(value);
|
||||||
|
@ -42,7 +42,7 @@ function setData(values) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function pushData(value) {
|
function pushData(value) {
|
||||||
if (chartInstance == null) return;
|
if (chartInstance == null || chartInstance.data.labels == null) return;
|
||||||
chartInstance.data.labels.push('');
|
chartInstance.data.labels.push('');
|
||||||
chartInstance.data.datasets[0].data.push(value);
|
chartInstance.data.datasets[0].data.push(value);
|
||||||
if (chartInstance.data.datasets[0].data.length > 200) {
|
if (chartInstance.data.datasets[0].data.length > 200) {
|
||||||
|
@ -69,6 +69,8 @@ const color =
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||||
|
|
||||||
|
if (chartEl.value == null) return;
|
||||||
|
|
||||||
chartInstance = new Chart(chartEl.value, {
|
chartInstance = new Chart(chartEl.value, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -210,6 +210,7 @@ async function fetchCurrentQueue() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchJobs() {
|
async function fetchJobs() {
|
||||||
|
if (tab.value === '-') return;
|
||||||
jobsFetching.value = true;
|
jobsFetching.value = true;
|
||||||
const state = jobState.value;
|
const state = jobState.value;
|
||||||
jobs.value = await misskeyApi('admin/queue/jobs', {
|
jobs.value = await misskeyApi('admin/queue/jobs', {
|
||||||
|
@ -307,6 +308,7 @@ async function removeJobs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshJob(jobId: string) {
|
async function refreshJob(jobId: string) {
|
||||||
|
if (tab.value === '-') return;
|
||||||
const newJob = await misskeyApi('admin/queue/show-job', { queue: tab.value, jobId });
|
const newJob = await misskeyApi('admin/queue/show-job', { queue: tab.value, jobId });
|
||||||
const index = jobs.value.findIndex((job) => job.id === jobId);
|
const index = jobs.value.findIndex((job) => job.id === jobId);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ initChart();
|
||||||
|
|
||||||
const chartEl = useTemplateRef('chartEl');
|
const chartEl = useTemplateRef('chartEl');
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
let chartInstance: Chart = null;
|
let chartInstance: Chart | null = null;
|
||||||
const chartLimit = 7;
|
const chartLimit = 7;
|
||||||
const fetching = ref(true);
|
const fetching = ref(true);
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="item _panel sub">
|
<div class="item _panel sub">
|
||||||
<div class="icon"><i class="ti ti-world-download"></i></div>
|
<div class="icon"><i class="ti ti-world-download"></i></div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="value">
|
<div v-if="federationSubActive != null" class="value">
|
||||||
{{ number(federationSubActive) }}
|
{{ number(federationSubActive) }}
|
||||||
<MkNumberDiff v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="federationSubActiveDiff"></MkNumberDiff>
|
<MkNumberDiff v-if="federationSubActiveDiff != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="federationSubActiveDiff"></MkNumberDiff>
|
||||||
</div>
|
</div>
|
||||||
<div class="label">Sub</div>
|
<div class="label">Sub</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -33,9 +33,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="item _panel pub">
|
<div class="item _panel pub">
|
||||||
<div class="icon"><i class="ti ti-world-upload"></i></div>
|
<div class="icon"><i class="ti ti-world-upload"></i></div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="value">
|
<div v-if="federationPubActive != null" class="value">
|
||||||
{{ number(federationPubActive) }}
|
{{ number(federationPubActive) }}
|
||||||
<MkNumberDiff v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="federationPubActiveDiff"></MkNumberDiff>
|
<MkNumberDiff v-if="federationPubActiveDiff != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="federationPubActiveDiff"></MkNumberDiff>
|
||||||
</div>
|
</div>
|
||||||
<div class="label">Pub</div>
|
<div class="label">Pub</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,7 +12,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
|
||||||
import MkHeatmap from '@/components/MkHeatmap.vue';
|
import MkHeatmap from '@/components/MkHeatmap.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import { useMkSelect } from '@/composables/use-mkselect.js';
|
import { useMkSelect } from '@/composables/use-mkselect.js';
|
||||||
|
|
|
@ -32,15 +32,17 @@ const { handler: externalTooltipHandler } = useChartTooltip({
|
||||||
position: 'middle',
|
position: 'middle',
|
||||||
});
|
});
|
||||||
|
|
||||||
let chartInstance: Chart;
|
let chartInstance: Chart | null = null;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
if (chartEl.value == null) return;
|
||||||
|
|
||||||
chartInstance = new Chart(chartEl.value, {
|
chartInstance = new Chart(chartEl.value, {
|
||||||
type: 'doughnut',
|
type: 'doughnut',
|
||||||
data: {
|
data: {
|
||||||
labels: props.data.map(x => x.name),
|
labels: props.data.map(x => x.name),
|
||||||
datasets: [{
|
datasets: [{
|
||||||
backgroundColor: props.data.map(x => x.color),
|
backgroundColor: props.data.map(x => x.color ?? '#000'),
|
||||||
borderColor: getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-panel'),
|
borderColor: getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-panel'),
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
hoverOffset: 0,
|
hoverOffset: 0,
|
||||||
|
@ -57,9 +59,10 @@ onMounted(() => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
onClick: (ev) => {
|
onClick: (ev) => {
|
||||||
const hit = chartInstance.getElementsAtEventForMode(ev, 'nearest', { intersect: true }, false)[0];
|
if (ev.native == null) return;
|
||||||
if (hit && props.data[hit.index].onClick) {
|
const hit = chartInstance!.getElementsAtEventForMode(ev.native, 'nearest', { intersect: true }, false)[0];
|
||||||
props.data[hit.index].onClick();
|
if (hit && props.data[hit.index].onClick != null) {
|
||||||
|
props.data[hit.index].onClick!();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
|
|
|
@ -26,10 +26,10 @@ const chartEl = useTemplateRef('chartEl');
|
||||||
|
|
||||||
const { handler: externalTooltipHandler } = useChartTooltip();
|
const { handler: externalTooltipHandler } = useChartTooltip();
|
||||||
|
|
||||||
let chartInstance: Chart;
|
let chartInstance: Chart | null = null;
|
||||||
|
|
||||||
function setData(values) {
|
function setData(values: number[]) {
|
||||||
if (chartInstance == null) return;
|
if (chartInstance == null || chartInstance.data.labels == null) return;
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
chartInstance.data.labels.push('');
|
chartInstance.data.labels.push('');
|
||||||
chartInstance.data.datasets[0].data.push(value);
|
chartInstance.data.datasets[0].data.push(value);
|
||||||
|
@ -41,8 +41,8 @@ function setData(values) {
|
||||||
chartInstance.update();
|
chartInstance.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
function pushData(value) {
|
function pushData(value: number) {
|
||||||
if (chartInstance == null) return;
|
if (chartInstance == null || chartInstance.data.labels == null) return;
|
||||||
chartInstance.data.labels.push('');
|
chartInstance.data.labels.push('');
|
||||||
chartInstance.data.datasets[0].data.push(value);
|
chartInstance.data.datasets[0].data.push(value);
|
||||||
if (chartInstance.data.datasets[0].data.length > 100) {
|
if (chartInstance.data.datasets[0].data.length > 100) {
|
||||||
|
@ -67,6 +67,8 @@ const color =
|
||||||
'?' as never;
|
'?' as never;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
if (chartEl.value == null) return;
|
||||||
|
|
||||||
const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||||
|
|
||||||
chartInstance = new Chart(chartEl.value, {
|
chartInstance = new Chart(chartEl.value, {
|
||||||
|
|
|
@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { markRaw, onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
|
import { markRaw, onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import XChart from './overview.queue.chart.vue';
|
import XChart from './overview.queue.chart.vue';
|
||||||
import type { ApQueueDomain } from '@/pages/admin/queue.vue';
|
import type { ApQueueDomain } from '@/pages/admin/federation-job-queue.vue';
|
||||||
import number from '@/filters/number.js';
|
import number from '@/filters/number.js';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
import { genId } from '@/utility/id.js';
|
import { genId } from '@/utility/id.js';
|
||||||
|
@ -64,10 +64,10 @@ function onStats(stats: Misskey.entities.QueueStats) {
|
||||||
delayed.value = stats[props.domain].delayed;
|
delayed.value = stats[props.domain].delayed;
|
||||||
waiting.value = stats[props.domain].waiting;
|
waiting.value = stats[props.domain].waiting;
|
||||||
|
|
||||||
chartProcess.value.pushData(stats[props.domain].activeSincePrevTick);
|
chartProcess.value?.pushData(stats[props.domain].activeSincePrevTick);
|
||||||
chartActive.value.pushData(stats[props.domain].active);
|
chartActive.value?.pushData(stats[props.domain].active);
|
||||||
chartDelayed.value.pushData(stats[props.domain].delayed);
|
chartDelayed.value?.pushData(stats[props.domain].delayed);
|
||||||
chartWaiting.value.pushData(stats[props.domain].waiting);
|
chartWaiting.value?.pushData(stats[props.domain].waiting);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) {
|
function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) {
|
||||||
|
@ -83,10 +83,10 @@ function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) {
|
||||||
dataWaiting.push(stats[props.domain].waiting);
|
dataWaiting.push(stats[props.domain].waiting);
|
||||||
}
|
}
|
||||||
|
|
||||||
chartProcess.value.setData(dataProcess);
|
chartProcess.value?.setData(dataProcess);
|
||||||
chartActive.value.setData(dataActive);
|
chartActive.value?.setData(dataActive);
|
||||||
chartDelayed.value.setData(dataDelayed);
|
chartDelayed.value?.setData(dataDelayed);
|
||||||
chartWaiting.value.setData(dataWaiting);
|
chartWaiting.value?.setData(dataWaiting);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -7,13 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div>
|
<div>
|
||||||
<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" mode="out-in">
|
<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||||
<MkLoading v-if="fetching"/>
|
<MkLoading v-if="fetching"/>
|
||||||
<div v-else :class="$style.root">
|
<div v-else-if="stats != null" :class="$style.root">
|
||||||
<div class="item _panel users">
|
<div class="item _panel users">
|
||||||
<div class="icon"><i class="ti ti-users"></i></div>
|
<div class="icon"><i class="ti ti-users"></i></div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="value">
|
<div class="value">
|
||||||
<MkNumber :value="stats.originalUsersCount" style="margin-right: 0.5em;"/>
|
<MkNumber :value="stats.originalUsersCount" style="margin-right: 0.5em;"/>
|
||||||
<MkNumberDiff v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="usersComparedToThePrevDay"></MkNumberDiff>
|
<MkNumberDiff v-if="usersComparedToThePrevDay != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="usersComparedToThePrevDay"></MkNumberDiff>
|
||||||
</div>
|
</div>
|
||||||
<div class="label">Users</div>
|
<div class="label">Users</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="value">
|
<div class="value">
|
||||||
<MkNumber :value="stats.originalNotesCount" style="margin-right: 0.5em;"/>
|
<MkNumber :value="stats.originalNotesCount" style="margin-right: 0.5em;"/>
|
||||||
<MkNumberDiff v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="notesComparedToThePrevDay"></MkNumberDiff>
|
<MkNumberDiff v-if="notesComparedToThePrevDay != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="notesComparedToThePrevDay"></MkNumberDiff>
|
||||||
</div>
|
</div>
|
||||||
<div class="label">Notes</div>
|
<div class="label">Notes</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,6 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<MkError v-else/>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -71,8 +72,8 @@ import { customEmojis } from '@/custom-emojis.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
|
||||||
const stats = ref<Misskey.entities.StatsResponse | null>(null);
|
const stats = ref<Misskey.entities.StatsResponse | null>(null);
|
||||||
const usersComparedToThePrevDay = ref<number>();
|
const usersComparedToThePrevDay = ref<number | null>(null);
|
||||||
const notesComparedToThePrevDay = ref<number>();
|
const notesComparedToThePrevDay = ref<number | null>(null);
|
||||||
const onlineUsersCount = ref(0);
|
const onlineUsersCount = ref(0);
|
||||||
const fetching = ref(true);
|
const fetching = ref(true);
|
||||||
|
|
||||||
|
@ -85,11 +86,11 @@ onMounted(async () => {
|
||||||
onlineUsersCount.value = _onlineUsersCount;
|
onlineUsersCount.value = _onlineUsersCount;
|
||||||
|
|
||||||
misskeyApiGet('charts/users', { limit: 2, span: 'day' }).then(chart => {
|
misskeyApiGet('charts/users', { limit: 2, span: 'day' }).then(chart => {
|
||||||
usersComparedToThePrevDay.value = stats.value.originalUsersCount - chart.local.total[1];
|
usersComparedToThePrevDay.value = _stats.originalUsersCount - chart.local.total[1];
|
||||||
});
|
});
|
||||||
|
|
||||||
misskeyApiGet('charts/notes', { limit: 2, span: 'day' }).then(chart => {
|
misskeyApiGet('charts/notes', { limit: 2, span: 'day' }).then(chart => {
|
||||||
notesComparedToThePrevDay.value = stats.value.originalNotesCount - chart.local.total[1];
|
notesComparedToThePrevDay.value = _stats.originalNotesCount - chart.local.total[1];
|
||||||
});
|
});
|
||||||
|
|
||||||
fetching.value = false;
|
fetching.value = false;
|
||||||
|
|
|
@ -95,7 +95,7 @@ const federationPubActiveDiff = ref<number | null>(null);
|
||||||
const federationSubActive = ref<number | null>(null);
|
const federationSubActive = ref<number | null>(null);
|
||||||
const federationSubActiveDiff = ref<number | null>(null);
|
const federationSubActiveDiff = ref<number | null>(null);
|
||||||
const newUsers = ref<Misskey.entities.UserDetailed[] | null>(null);
|
const newUsers = ref<Misskey.entities.UserDetailed[] | null>(null);
|
||||||
const activeInstances = shallowRef<Misskey.entities.FederationInstance | null>(null);
|
const activeInstances = shallowRef<Misskey.entities.FederationInstancesResponse | null>(null);
|
||||||
const queueStatsConnection = markRaw(useStream().useChannel('queueStats'));
|
const queueStatsConnection = markRaw(useStream().useChannel('queueStats'));
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const filesPagination = {
|
const filesPagination = {
|
||||||
|
|
|
@ -831,7 +831,6 @@ import { watch, ref, computed } from 'vue';
|
||||||
import { throttle } from 'throttle-debounce';
|
import { throttle } from 'throttle-debounce';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import RolesEditorFormula from './RolesEditorFormula.vue';
|
import RolesEditorFormula from './RolesEditorFormula.vue';
|
||||||
import type { GetMkSelectValueTypesFromDef, MkSelectItem } from '@/components/MkSelect.vue';
|
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkColorInput from '@/components/MkColorInput.vue';
|
import MkColorInput from '@/components/MkColorInput.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
|
|
|
@ -71,7 +71,7 @@ import { Paginator } from '@/utility/paginator.js';
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
id?: string;
|
id: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const usersPaginator = markRaw(new Paginator('admin/roles/users', {
|
const usersPaginator = markRaw(new Paginator('admin/roles/users', {
|
||||||
|
|
|
@ -350,6 +350,7 @@ import { definePage } from '@/page.js';
|
||||||
import { instance, fetchInstance } from '@/instance.js';
|
import { instance, fetchInstance } from '@/instance.js';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
|
import { deepClone } from '@/utility/clone.js';
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -357,10 +358,7 @@ const baseRoleQ = ref('');
|
||||||
|
|
||||||
const roles = await misskeyApi('admin/roles/list');
|
const roles = await misskeyApi('admin/roles/list');
|
||||||
|
|
||||||
const policies = reactive<Record<typeof Misskey.rolePolicies[number], any>>({});
|
const policies = reactive(deepClone(instance.policies));
|
||||||
for (const ROLE_POLICY of Misskey.rolePolicies) {
|
|
||||||
policies[ROLE_POLICY] = instance.policies[ROLE_POLICY];
|
|
||||||
}
|
|
||||||
|
|
||||||
const avatarDecorationLimit = computed({
|
const avatarDecorationLimit = computed({
|
||||||
get: () => Math.min(16, Math.max(0, policies.avatarDecorationLimit)),
|
get: () => Math.min(16, Math.max(0, policies.avatarDecorationLimit)),
|
||||||
|
@ -380,6 +378,7 @@ function matchQuery(keywords: string[]): boolean {
|
||||||
|
|
||||||
async function updateBaseRole() {
|
async function updateBaseRole() {
|
||||||
await os.apiWithDialog('admin/roles/update-default-policies', {
|
await os.apiWithDialog('admin/roles/update-default-policies', {
|
||||||
|
//@ts-expect-error misskey-js側の型定義が不十分
|
||||||
policies,
|
policies,
|
||||||
});
|
});
|
||||||
fetchInstance(true);
|
fetchInstance(true);
|
||||||
|
|
|
@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="title">{{ post.title }}</div>
|
<div class="title">{{ post.title }}</div>
|
||||||
<div class="description"><Mfm :text="post.description"/></div>
|
<div class="description"><Mfm v-if="post.description != null" :text="post.description"/></div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<i class="ti ti-clock"></i> <MkTime :time="post.createdAt" mode="detail"/>
|
<i class="ti ti-clock"></i> <MkTime :time="post.createdAt" mode="detail"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -93,7 +93,7 @@ const error = ref<any>(null);
|
||||||
const otherPostsPaginator = markRaw(new Paginator('users/gallery/posts', {
|
const otherPostsPaginator = markRaw(new Paginator('users/gallery/posts', {
|
||||||
limit: 6,
|
limit: 6,
|
||||||
computedParams: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
userId: post.value.user.id,
|
userId: post.value!.user.id,
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -109,33 +109,38 @@ function fetchPost() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyLink() {
|
function copyLink() {
|
||||||
|
if (!post.value) return;
|
||||||
copyToClipboard(`${url}/gallery/${post.value.id}`);
|
copyToClipboard(`${url}/gallery/${post.value.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function share() {
|
function share() {
|
||||||
|
if (!post.value) return;
|
||||||
navigator.share({
|
navigator.share({
|
||||||
title: post.value.title,
|
title: post.value.title,
|
||||||
text: post.value.description,
|
text: post.value.description ?? undefined,
|
||||||
url: `${url}/gallery/${post.value.id}`,
|
url: `${url}/gallery/${post.value.id}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function shareWithNote() {
|
function shareWithNote() {
|
||||||
|
if (!post.value) return;
|
||||||
os.post({
|
os.post({
|
||||||
initialText: `${post.value.title} ${url}/gallery/${post.value.id}`,
|
initialText: `${post.value.title} ${url}/gallery/${post.value.id}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function like() {
|
function like() {
|
||||||
|
if (!post.value) return;
|
||||||
os.apiWithDialog('gallery/posts/like', {
|
os.apiWithDialog('gallery/posts/like', {
|
||||||
postId: props.postId,
|
postId: props.postId,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
post.value.isLiked = true;
|
post.value!.isLiked = true;
|
||||||
post.value.likedCount++;
|
post.value!.likedCount++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unlike() {
|
async function unlike() {
|
||||||
|
if (!post.value) return;
|
||||||
const confirm = await os.confirm({
|
const confirm = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.ts.unlikeConfirm,
|
text: i18n.ts.unlikeConfirm,
|
||||||
|
@ -144,8 +149,8 @@ async function unlike() {
|
||||||
os.apiWithDialog('gallery/posts/unlike', {
|
os.apiWithDialog('gallery/posts/unlike', {
|
||||||
postId: props.postId,
|
postId: props.postId,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
post.value.isLiked = false;
|
post.value!.isLiked = false;
|
||||||
post.value.likedCount--;
|
post.value!.likedCount--;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkButton v-if="list.isLiked" v-tooltip="i18n.ts.unlike" inline :class="$style.button" asLike primary @click="unlike()"><i class="ti ti-heart-off"></i><span v-if="list.likedCount > 0" class="count">{{ list.likedCount }}</span></MkButton>
|
<MkButton v-if="list.isLiked" v-tooltip="i18n.ts.unlike" inline :class="$style.button" asLike primary @click="unlike()"><i class="ti ti-heart-off"></i><span v-if="list.likedCount != null && list.likedCount > 0" class="count">{{ list.likedCount }}</span></MkButton>
|
||||||
<MkButton v-if="!list.isLiked" v-tooltip="i18n.ts.like" inline :class="$style.button" asLike @click="like()"><i class="ti ti-heart"></i><span v-if="1 > 0" class="count">{{ list.likedCount }}</span></MkButton>
|
<MkButton v-if="!list.isLiked" v-tooltip="i18n.ts.like" inline :class="$style.button" asLike @click="like()"><i class="ti ti-heart"></i><span v-if="1 > 0" class="count">{{ list.likedCount }}</span></MkButton>
|
||||||
<MkButton inline @click="create()"><i class="ti ti-download" :class="$style.import"></i>{{ i18n.ts.import }}</MkButton>
|
<MkButton inline @click="create()"><i class="ti ti-download" :class="$style.import"></i>{{ i18n.ts.import }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,7 +41,7 @@ const props = defineProps<{
|
||||||
listId: string;
|
listId: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const list = ref<Misskey.entities.UserList | null>(null);
|
const list = ref<Misskey.entities.UsersListsShowResponse | null>(null);
|
||||||
const error = ref<unknown | null>(null);
|
const error = ref<unknown | null>(null);
|
||||||
const users = ref<Misskey.entities.UserDetailed[]>([]);
|
const users = ref<Misskey.entities.UserDetailed[]>([]);
|
||||||
|
|
||||||
|
@ -51,8 +51,9 @@ function fetchList(): void {
|
||||||
forPublic: true,
|
forPublic: true,
|
||||||
}).then(_list => {
|
}).then(_list => {
|
||||||
list.value = _list;
|
list.value = _list;
|
||||||
|
if (_list.userIds == null || _list.userIds.length === 0) return;
|
||||||
misskeyApi('users/show', {
|
misskeyApi('users/show', {
|
||||||
userIds: list.value.userIds,
|
userIds: _list.userIds,
|
||||||
}).then(_users => {
|
}).then(_users => {
|
||||||
users.value = _users;
|
users.value = _users;
|
||||||
});
|
});
|
||||||
|
@ -68,7 +69,7 @@ function like() {
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
if (list.value == null) return;
|
if (list.value == null) return;
|
||||||
list.value.isLiked = true;
|
list.value.isLiked = true;
|
||||||
list.value.likedCount++;
|
list.value.likedCount = (list.value.likedCount != null ? list.value.likedCount + 1 : 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +80,7 @@ function unlike() {
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
if (list.value == null) return;
|
if (list.value == null) return;
|
||||||
list.value.isLiked = false;
|
list.value.isLiked = false;
|
||||||
list.value.likedCount--;
|
list.value.likedCount = (list.value.likedCount != null ? Math.max(0, list.value.likedCount - 1) : 0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +89,7 @@ async function create() {
|
||||||
const { canceled, result: name } = await os.inputText({
|
const { canceled, result: name } = await os.inputText({
|
||||||
title: i18n.ts.enterListName,
|
title: i18n.ts.enterListName,
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled || name == null) return;
|
||||||
await os.apiWithDialog('users/lists/create-from-public', { name: name, listId: list.value.id });
|
await os.apiWithDialog('users/lists/create-from-public', { name: name, listId: list.value.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ const props = defineProps<{
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'note' }): void;
|
(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'note' }): void;
|
||||||
|
(ev: 'remove'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const id = ref(props.modelValue.note);
|
const id = ref(props.modelValue.note);
|
||||||
|
|
|
@ -27,6 +27,7 @@ const props = defineProps<{
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'text' }): void;
|
(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'text' }): void;
|
||||||
|
(ev: 'remove'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let autocomplete: Autocomplete;
|
let autocomplete: Autocomplete;
|
||||||
|
@ -42,6 +43,7 @@ watch(text, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
if (inputEl.value == null) return;
|
||||||
autocomplete = new Autocomplete(inputEl.value, text);
|
autocomplete = new Autocomplete(inputEl.value, text);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
|
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
||||||
<div class="jqqmcavi">
|
<div class="jqqmcavi">
|
||||||
<MkButton v-if="pageId" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="ti ti-external-link"></i> {{ i18n.ts._pages.viewPage }}</MkButton>
|
<MkButton v-if="pageId && author != null" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="ti ti-external-link"></i> {{ i18n.ts._pages.viewPage }}</MkButton>
|
||||||
<MkButton v-if="!readonly" inline primary class="button" @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
<MkButton v-if="!readonly" inline primary class="button" @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||||
<MkButton v-if="pageId" inline class="button" @click="duplicate"><i class="ti ti-copy"></i> {{ i18n.ts.duplicate }}</MkButton>
|
<MkButton v-if="pageId" inline class="button" @click="duplicate"><i class="ti ti-copy"></i> {{ i18n.ts.duplicate }}</MkButton>
|
||||||
<MkButton v-if="pageId && !readonly" inline class="button" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
<MkButton v-if="pageId && !readonly" inline class="button" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||||
|
@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
<MkInput v-model="name">
|
<MkInput v-model="name">
|
||||||
<template #prefix>{{ url }}/@{{ author.username }}/pages/</template>
|
<template #prefix>{{ url }}/@{{ author?.username ?? '???' }}/pages/</template>
|
||||||
<template #label>{{ i18n.ts._pages.url }}</template>
|
<template #label>{{ i18n.ts._pages.url }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tab = ref('settings');
|
const tab = ref('settings');
|
||||||
const author = ref($i);
|
const author = ref<Misskey.entities.User | null>($i);
|
||||||
const readonly = ref(false);
|
const readonly = ref(false);
|
||||||
const page = ref<Misskey.entities.Page | null>(null);
|
const page = ref<Misskey.entities.Page | null>(null);
|
||||||
const pageId = ref<string | null>(null);
|
const pageId = ref<string | null>(null);
|
||||||
|
|
|
@ -164,7 +164,7 @@ const $i = ensureSignin();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
game: Misskey.entities.ReversiGameDetailed;
|
game: Misskey.entities.ReversiGameDetailed;
|
||||||
connection?: Misskey.ChannelConnection<Misskey.Channels['reversiGame']> | null;
|
connection?: Misskey.IChannelConnection<Misskey.Channels['reversiGame']> | null;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const showBoardLabels = ref<boolean>(false);
|
const showBoardLabels = ref<boolean>(false);
|
||||||
|
|
|
@ -132,7 +132,7 @@ const mapCategories = Array.from(new Set(Object.values(Reversi.maps).map(x => x.
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
game: Misskey.entities.ReversiGameDetailed;
|
game: Misskey.entities.ReversiGameDetailed;
|
||||||
connection: Misskey.ChannelConnection<Misskey.Channels['reversiGame']>;
|
connection: Misskey.IChannelConnection<Misskey.Channels['reversiGame']>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const shareWhenStart = defineModel<boolean>('shareWhenStart', { default: false });
|
const shareWhenStart = defineModel<boolean>('shareWhenStart', { default: false });
|
||||||
|
|
|
@ -33,7 +33,7 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const game = shallowRef<Misskey.entities.ReversiGameDetailed | null>(null);
|
const game = shallowRef<Misskey.entities.ReversiGameDetailed | null>(null);
|
||||||
const connection = shallowRef<Misskey.ChannelConnection | null>(null);
|
const connection = shallowRef<Misskey.IChannelConnection<Misskey.Channels['reversiGame']> | null>(null);
|
||||||
const shareWhenStart = ref(false);
|
const shareWhenStart = ref(false);
|
||||||
|
|
||||||
watch(() => props.gameId, () => {
|
watch(() => props.gameId, () => {
|
||||||
|
|
|
@ -196,6 +196,7 @@ async function addSecurityKey() {
|
||||||
if (auth.canceled) return;
|
if (auth.canceled) return;
|
||||||
|
|
||||||
const registrationOptions = parseCreationOptionsFromJSON({
|
const registrationOptions = parseCreationOptionsFromJSON({
|
||||||
|
// @ts-expect-error misskey-js側に型がない
|
||||||
publicKey: await os.apiWithDialog('i/2fa/register-key', {
|
publicKey: await os.apiWithDialog('i/2fa/register-key', {
|
||||||
password: auth.result.password,
|
password: auth.result.password,
|
||||||
token: auth.result.token,
|
token: auth.result.token,
|
||||||
|
@ -226,6 +227,7 @@ async function addSecurityKey() {
|
||||||
password: auth.result.password,
|
password: auth.result.password,
|
||||||
token: auth.result.token,
|
token: auth.result.token,
|
||||||
name: name.result,
|
name: name.result,
|
||||||
|
// @ts-expect-error misskey-js側に型がない
|
||||||
credential: credential.toJSON(),
|
credential: credential.toJSON(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,6 +131,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
<MkButton @click="forceCloudBackup">Force cloud backup</MkButton>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
<template v-if="$i.policies.chatAvailability !== 'unavailable'">
|
<template v-if="$i.policies.chatAvailability !== 'unavailable'">
|
||||||
<MkButton @click="readAllChatMessages">Read all chat messages</MkButton>
|
<MkButton @click="readAllChatMessages">Read all chat messages</MkButton>
|
||||||
|
|
||||||
|
@ -167,6 +171,7 @@ import { signout } from '@/signout.js';
|
||||||
import { migrateOldSettings } from '@/pref-migrate.js';
|
import { migrateOldSettings } from '@/pref-migrate.js';
|
||||||
import { hideAllTips as _hideAllTips, resetAllTips as _resetAllTips } from '@/tips.js';
|
import { hideAllTips as _hideAllTips, resetAllTips as _resetAllTips } from '@/tips.js';
|
||||||
import { suggestReload } from '@/utility/reload-suggest.js';
|
import { suggestReload } from '@/utility/reload-suggest.js';
|
||||||
|
import { cloudBackup } from '@/preferences/utility.js';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
|
@ -224,6 +229,11 @@ function readAllChatMessages() {
|
||||||
os.apiWithDialog('chat/read-all', {});
|
os.apiWithDialog('chat/read-all', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function forceCloudBackup() {
|
||||||
|
await cloudBackup();
|
||||||
|
os.success();
|
||||||
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
|
@ -41,12 +41,10 @@ import { useMkSelect } from '@/composables/use-mkselect.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { playMisskeySfxFile, soundsTypes, getSoundDuration } from '@/utility/sound.js';
|
import { playMisskeySfxFile, soundsTypes, getSoundDuration } from '@/utility/sound.js';
|
||||||
import { selectFile } from '@/utility/drive.js';
|
import { selectFile } from '@/utility/drive.js';
|
||||||
|
import type { SoundStore } from '@/preferences/def.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
type: SoundType;
|
def: SoundStore;
|
||||||
fileId?: string;
|
|
||||||
fileUrl?: string;
|
|
||||||
volume: number;
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -61,14 +59,14 @@ const {
|
||||||
label: getSoundTypeName(x),
|
label: getSoundTypeName(x),
|
||||||
value: x,
|
value: x,
|
||||||
})),
|
})),
|
||||||
initialValue: props.type,
|
initialValue: props.def.type,
|
||||||
});
|
});
|
||||||
const fileId = ref(props.fileId);
|
const fileId = ref('fileId' in props.def ? props.def.fileId : undefined);
|
||||||
const fileUrl = ref(props.fileUrl);
|
const fileUrl = ref('fileUrl' in props.def ? props.def.fileUrl : undefined);
|
||||||
const fileName = ref<string>('');
|
const fileName = ref<string>('');
|
||||||
const driveFileError = ref(false);
|
const driveFileError = ref(false);
|
||||||
const hasChanged = ref(false);
|
const hasChanged = ref(false);
|
||||||
const volume = ref(props.volume);
|
const volume = ref(props.def.volume);
|
||||||
|
|
||||||
if (type.value === '_driveFile_' && fileId.value) {
|
if (type.value === '_driveFile_' && fileId.value) {
|
||||||
await misskeyApi('drive/files/show', {
|
await misskeyApi('drive/files/show', {
|
||||||
|
|
|
@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #suffix>{{ getSoundTypeName(sounds[type].type) }}</template>
|
<template #suffix>{{ getSoundTypeName(sounds[type].type) }}</template>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<template #default>
|
<template #default>
|
||||||
<XSound :type="sounds[type].type" :volume="sounds[type].volume" :fileId="sounds[type].fileId" :fileUrl="sounds[type].fileUrl" @update="(res) => updated(type, res)"/>
|
<XSound :def="sounds[type]" @update="(res) => updated(type, res)"/>
|
||||||
</template>
|
</template>
|
||||||
<template #fallback>
|
<template #fallback>
|
||||||
<MkLoading/>
|
<MkLoading/>
|
||||||
|
|
|
@ -112,8 +112,7 @@ async function init() {
|
||||||
...(visibleUserIds ? visibleUserIds.split(',').map(userId => ({ userId })) : []),
|
...(visibleUserIds ? visibleUserIds.split(',').map(userId => ({ userId })) : []),
|
||||||
...(visibleAccts ? visibleAccts.split(',').map(Misskey.acct.parse) : []),
|
...(visibleAccts ? visibleAccts.split(',').map(Misskey.acct.parse) : []),
|
||||||
]
|
]
|
||||||
// TypeScriptの指示通りに変換する
|
// @ts-expect-error payloadの引数側の型が正常に解決されない
|
||||||
.map(q => 'username' in q ? { username: q.username, host: q.host === null ? undefined : q.host } : q)
|
|
||||||
.map(q => misskeyApi('users/show', q)
|
.map(q => misskeyApi('users/show', q)
|
||||||
.then(user => {
|
.then(user => {
|
||||||
visibleUsers.value.push(user);
|
visibleUsers.value.push(user);
|
||||||
|
|
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkPagination v-slot="{items}" :paginator="paginator" withControl>
|
<MkPagination v-slot="{items}" :paginator="paginator" withControl>
|
||||||
<MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/list/${ list.id }`">
|
<MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/list/${ list.id }`">
|
||||||
<div>{{ list.name }}</div>
|
<div>{{ list.name }}</div>
|
||||||
<MkAvatars :userIds="list.userIds"/>
|
<MkAvatars v-if="list.userIds != null" :userIds="list.userIds"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { ref } from 'vue';
|
||||||
import { compareVersions } from 'compare-versions';
|
import { compareVersions } from 'compare-versions';
|
||||||
import { isSafeMode } from '@@/js/config.js';
|
import { isSafeMode } from '@@/js/config.js';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import type { Parser, Interpreter, values } from '@syuilo/aiscript';
|
import type { Parser, Interpreter, values, utils as utils_TypeReferenceOnly } from '@syuilo/aiscript';
|
||||||
import type { FormWithDefault } from '@/utility/form.js';
|
import type { FormWithDefault } from '@/utility/form.js';
|
||||||
import { genId } from '@/utility/id.js';
|
import { genId } from '@/utility/id.js';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
|
@ -82,22 +82,23 @@ export async function parsePluginMeta(code: string): Promise<AiScriptPluginMeta>
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata = meta.get(null);
|
const metadata = meta.get(null);
|
||||||
if (metadata == null) {
|
if (metadata == null || typeof metadata !== 'object' || Array.isArray(metadata)) {
|
||||||
throw new Error('Metadata not found');
|
throw new Error('Metadata not found or invalid');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, version, author, description, permissions, config } = metadata;
|
const { name, version, author, description, permissions, config } = metadata;
|
||||||
|
|
||||||
if (name == null || version == null || author == null) {
|
if (name == null || version == null || author == null) {
|
||||||
throw new Error('Required property not found');
|
throw new Error('Required property not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
name: name as string,
|
||||||
version,
|
version: version as string,
|
||||||
author,
|
author: author as string,
|
||||||
description,
|
description: description as string | undefined,
|
||||||
permissions,
|
permissions: permissions as string[] | undefined,
|
||||||
config,
|
config: config as Record<string, any> | undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +111,7 @@ export async function authorizePlugin(plugin: Plugin) {
|
||||||
title: i18n.ts.tokenRequested,
|
title: i18n.ts.tokenRequested,
|
||||||
information: i18n.ts.pluginTokenRequestedDescription,
|
information: i18n.ts.pluginTokenRequestedDescription,
|
||||||
initialName: plugin.name,
|
initialName: plugin.name,
|
||||||
initialPermissions: plugin.permissions,
|
initialPermissions: plugin.permissions as typeof Misskey.permissions[number][],
|
||||||
}, {
|
}, {
|
||||||
done: async result => {
|
done: async result => {
|
||||||
const { name, permissions } = result;
|
const { name, permissions } = result;
|
||||||
|
@ -149,6 +150,7 @@ export async function installPlugin(code: string, meta?: AiScriptPluginMeta) {
|
||||||
|
|
||||||
const plugin = {
|
const plugin = {
|
||||||
...realMeta,
|
...realMeta,
|
||||||
|
config: realMeta.config ?? {},
|
||||||
installId,
|
installId,
|
||||||
active: true,
|
active: true,
|
||||||
configData: {},
|
configData: {},
|
||||||
|
@ -205,7 +207,7 @@ type HandlerDef = {
|
||||||
handler: (note: Misskey.entities.Note) => void;
|
handler: (note: Misskey.entities.Note) => void;
|
||||||
};
|
};
|
||||||
note_view_interruptor: {
|
note_view_interruptor: {
|
||||||
handler: (note: Misskey.entities.Note) => Misskey.entities.Note;
|
handler: (note: Misskey.entities.Note) => Misskey.entities.Note | null;
|
||||||
};
|
};
|
||||||
note_post_interruptor: {
|
note_post_interruptor: {
|
||||||
handler: (note: FIXME) => unknown;
|
handler: (note: FIXME) => unknown;
|
||||||
|
@ -353,7 +355,9 @@ export function changePluginActive(plugin: Plugin, active: boolean) {
|
||||||
async function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Promise<Record<string, values.Value>> {
|
async function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Promise<Record<string, values.Value>> {
|
||||||
const id = opts.plugin.installId;
|
const id = opts.plugin.installId;
|
||||||
|
|
||||||
const { utils, values } = await import('@syuilo/aiscript');
|
const ais = await import('@syuilo/aiscript');
|
||||||
|
const values = ais.values;
|
||||||
|
const utils: typeof utils_TypeReferenceOnly = ais.utils;
|
||||||
const { createAiScriptEnv } = await import('@/aiscript/api.js');
|
const { createAiScriptEnv } = await import('@/aiscript/api.js');
|
||||||
|
|
||||||
const config = new Map<string, values.Value>();
|
const config = new Map<string, values.Value>();
|
||||||
|
@ -375,7 +379,7 @@ async function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Pr
|
||||||
utils.assertFunction(handler);
|
utils.assertFunction(handler);
|
||||||
addPluginHandler(id, 'post_form_action', {
|
addPluginHandler(id, 'post_form_action', {
|
||||||
title: title.value,
|
title: title.value,
|
||||||
handler: withContext(ctx => (form, update) => {
|
handler: (form, update) => withContext(ctx => {
|
||||||
ctx.execFn(handler, [utils.jsToVal(form), values.FN_NATIVE(([key, value]) => {
|
ctx.execFn(handler, [utils.jsToVal(form), values.FN_NATIVE(([key, value]) => {
|
||||||
if (!key || !value) {
|
if (!key || !value) {
|
||||||
return;
|
return;
|
||||||
|
@ -391,7 +395,7 @@ async function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Pr
|
||||||
utils.assertFunction(handler);
|
utils.assertFunction(handler);
|
||||||
addPluginHandler(id, 'user_action', {
|
addPluginHandler(id, 'user_action', {
|
||||||
title: title.value,
|
title: title.value,
|
||||||
handler: withContext(ctx => (user) => {
|
handler: (user) => withContext(ctx => {
|
||||||
ctx.execFn(handler, [utils.jsToVal(user)]);
|
ctx.execFn(handler, [utils.jsToVal(user)]);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
@ -402,7 +406,7 @@ async function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Pr
|
||||||
utils.assertFunction(handler);
|
utils.assertFunction(handler);
|
||||||
addPluginHandler(id, 'note_action', {
|
addPluginHandler(id, 'note_action', {
|
||||||
title: title.value,
|
title: title.value,
|
||||||
handler: withContext(ctx => (note) => {
|
handler: (note) => withContext(ctx => {
|
||||||
ctx.execFn(handler, [utils.jsToVal(note)]);
|
ctx.execFn(handler, [utils.jsToVal(note)]);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
@ -411,8 +415,8 @@ async function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Pr
|
||||||
'Plugin:register:note_view_interruptor': values.FN_NATIVE(([handler]) => {
|
'Plugin:register:note_view_interruptor': values.FN_NATIVE(([handler]) => {
|
||||||
utils.assertFunction(handler);
|
utils.assertFunction(handler);
|
||||||
addPluginHandler(id, 'note_view_interruptor', {
|
addPluginHandler(id, 'note_view_interruptor', {
|
||||||
handler: withContext(ctx => (note) => {
|
handler: (note) => withContext(ctx => {
|
||||||
return utils.valToJs(ctx.execFnSync(handler, [utils.jsToVal(note)]));
|
return utils.valToJs(ctx.execFnSync(handler, [utils.jsToVal(note)])) as Misskey.entities.Note | null;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
@ -420,8 +424,8 @@ async function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Pr
|
||||||
'Plugin:register:note_post_interruptor': values.FN_NATIVE(([handler]) => {
|
'Plugin:register:note_post_interruptor': values.FN_NATIVE(([handler]) => {
|
||||||
utils.assertFunction(handler);
|
utils.assertFunction(handler);
|
||||||
addPluginHandler(id, 'note_post_interruptor', {
|
addPluginHandler(id, 'note_post_interruptor', {
|
||||||
handler: withContext(ctx => async (note) => {
|
handler: (note) => withContext(ctx => {
|
||||||
return utils.valToJs(await ctx.execFn(handler, [utils.jsToVal(note)]));
|
return utils.valToJs(ctx.execFnSync(handler, [utils.jsToVal(note)]));
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
@ -429,8 +433,8 @@ async function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Pr
|
||||||
'Plugin:register:page_view_interruptor': values.FN_NATIVE(([handler]) => {
|
'Plugin:register:page_view_interruptor': values.FN_NATIVE(([handler]) => {
|
||||||
utils.assertFunction(handler);
|
utils.assertFunction(handler);
|
||||||
addPluginHandler(id, 'page_view_interruptor', {
|
addPluginHandler(id, 'page_view_interruptor', {
|
||||||
handler: withContext(ctx => async (page) => {
|
handler: (page) => withContext(ctx => {
|
||||||
return utils.valToJs(await ctx.execFn(handler, [utils.jsToVal(page)]));
|
return utils.valToJs(ctx.execFnSync(handler, [utils.jsToVal(page)])) as Misskey.entities.Page;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -25,11 +25,14 @@ export function migrateOldSettings() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const plugins = ColdDeviceStorage.get('plugins');
|
const plugins = ColdDeviceStorage.get('plugins');
|
||||||
prefer.commit('plugins', plugins.map(p => ({
|
prefer.commit('plugins', plugins.map(p => {
|
||||||
...p,
|
const { id, ...rest } = p;
|
||||||
installId: (p as any).id,
|
return {
|
||||||
id: undefined,
|
...rest,
|
||||||
})));
|
config: rest.config ?? {},
|
||||||
|
installId: id,
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
prefer.commit('deck.profile', deckStore.s.profile);
|
prefer.commit('deck.profile', deckStore.s.profile);
|
||||||
misskeyApi('i/registry/keys', {
|
misskeyApi('i/registry/keys', {
|
||||||
|
@ -115,7 +118,13 @@ export function migrateOldSettings() {
|
||||||
prefer.commit('enableCondensedLine', store.s.enableCondensedLine);
|
prefer.commit('enableCondensedLine', store.s.enableCondensedLine);
|
||||||
prefer.commit('keepScreenOn', store.s.keepScreenOn);
|
prefer.commit('keepScreenOn', store.s.keepScreenOn);
|
||||||
prefer.commit('useGroupedNotifications', store.s.useGroupedNotifications);
|
prefer.commit('useGroupedNotifications', store.s.useGroupedNotifications);
|
||||||
prefer.commit('dataSaver', store.s.dataSaver);
|
prefer.commit('dataSaver', {
|
||||||
|
...prefer.s.dataSaver,
|
||||||
|
media: store.s.dataSaver.media,
|
||||||
|
avatar: store.s.dataSaver.avatar,
|
||||||
|
urlPreviewThumbnail: store.s.dataSaver.urlPreview,
|
||||||
|
code: store.s.dataSaver.code,
|
||||||
|
});
|
||||||
prefer.commit('enableSeasonalScreenEffect', store.s.enableSeasonalScreenEffect);
|
prefer.commit('enableSeasonalScreenEffect', store.s.enableSeasonalScreenEffect);
|
||||||
prefer.commit('enableHorizontalSwipe', store.s.enableHorizontalSwipe);
|
prefer.commit('enableHorizontalSwipe', store.s.enableHorizontalSwipe);
|
||||||
prefer.commit('useNativeUiForVideoAudioPlayer', store.s.useNativeUIForVideoAudioPlayer);
|
prefer.commit('useNativeUiForVideoAudioPlayer', store.s.useNativeUIForVideoAudioPlayer);
|
||||||
|
|
|
@ -41,6 +41,14 @@ export type StatusbarStore = {
|
||||||
props: Record<string, any>;
|
props: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DataSaverStore = {
|
||||||
|
media: boolean;
|
||||||
|
avatar: boolean;
|
||||||
|
urlPreviewThumbnail: boolean;
|
||||||
|
disableUrlPreview: boolean;
|
||||||
|
code: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
type OmitStrict<T, K extends keyof T> = T extends any ? Pick<T, Exclude<keyof T, K>> : never;
|
type OmitStrict<T, K extends keyof T> = T extends any ? Pick<T, Exclude<keyof T, K>> : never;
|
||||||
|
|
||||||
// NOTE: デフォルト値は他の設定の状態に依存してはならない(依存していた場合、ユーザーがその設定項目単体で「初期値にリセット」した場合不具合の原因になる)
|
// NOTE: デフォルト値は他の設定の状態に依存してはならない(依存していた場合、ユーザーがその設定項目単体で「初期値にリセット」した場合不具合の原因になる)
|
||||||
|
@ -332,7 +340,7 @@ export const PREF_DEF = definePreferences({
|
||||||
urlPreviewThumbnail: false,
|
urlPreviewThumbnail: false,
|
||||||
disableUrlPreview: false,
|
disableUrlPreview: false,
|
||||||
code: false,
|
code: false,
|
||||||
} satisfies Record<string, boolean>,
|
} as DataSaverStore,
|
||||||
},
|
},
|
||||||
hemisphere: {
|
hemisphere: {
|
||||||
default: hemisphere as 'N' | 'S',
|
default: hemisphere as 'N' | 'S',
|
||||||
|
|
|
@ -381,7 +381,7 @@ export const store = markRaw(new Pizzax('base', {
|
||||||
avatar: false,
|
avatar: false,
|
||||||
urlPreview: false,
|
urlPreview: false,
|
||||||
code: false,
|
code: false,
|
||||||
} as Record<string, boolean>,
|
},
|
||||||
},
|
},
|
||||||
enableSeasonalScreenEffect: {
|
enableSeasonalScreenEffect: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
|
@ -483,7 +483,7 @@ export class ColdDeviceStorage {
|
||||||
lightTheme, // TODO: 消す(preferに移行済みのため)
|
lightTheme, // TODO: 消す(preferに移行済みのため)
|
||||||
darkTheme, // TODO: 消す(preferに移行済みのため)
|
darkTheme, // TODO: 消す(preferに移行済みのため)
|
||||||
syncDeviceDarkMode: true, // TODO: 消す(preferに移行済みのため)
|
syncDeviceDarkMode: true, // TODO: 消す(preferに移行済みのため)
|
||||||
plugins: [] as Plugin[], // TODO: 消す(preferに移行済みのため)
|
plugins: [] as (Omit<Plugin, 'installId'> & { id: string })[], // TODO: 消す(preferに移行済みのため)
|
||||||
};
|
};
|
||||||
|
|
||||||
public static watchers: Watcher[] = [];
|
public static watchers: Watcher[] = [];
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { unisonReload } from '@/utility/unison-reload.js';
|
import { unisonReload } from '@/utility/unison-reload.js';
|
||||||
|
import { misskeyApiGet } from '@/utility/misskey-api.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { fetchCustomEmojis } from '@/custom-emojis.js';
|
import { fetchCustomEmojis } from '@/custom-emojis.js';
|
||||||
|
@ -16,6 +17,9 @@ export async function clearCache() {
|
||||||
miLocalStorage.removeItem('theme');
|
miLocalStorage.removeItem('theme');
|
||||||
miLocalStorage.removeItem('emojis');
|
miLocalStorage.removeItem('emojis');
|
||||||
miLocalStorage.removeItem('lastEmojisFetchedAt');
|
miLocalStorage.removeItem('lastEmojisFetchedAt');
|
||||||
|
await misskeyApiGet('clear-browser-cache', {}).catch(() => {
|
||||||
|
// ignore
|
||||||
|
});
|
||||||
await fetchInstance(true);
|
await fetchInstance(true);
|
||||||
await fetchCustomEmojis(true);
|
await fetchCustomEmojis(true);
|
||||||
unisonReload();
|
unisonReload();
|
||||||
|
|
|
@ -36,7 +36,7 @@ export async function getTheme(mode: 'light' | 'dark', getName = false): Promise
|
||||||
_res = deepClone(theme.codeHighlighter.overrides);
|
_res = deepClone(theme.codeHighlighter.overrides);
|
||||||
} else {
|
} else {
|
||||||
const base = await bundledThemesInfo.find(t => t.id === theme.codeHighlighter!.base)?.import() ?? darkPlus;
|
const base = await bundledThemesInfo.find(t => t.id === theme.codeHighlighter!.base)?.import() ?? darkPlus;
|
||||||
_res = deepMerge(theme.codeHighlighter.overrides ?? {}, 'default' in base ? base.default : base);
|
_res = deepMerge<ThemeRegistration>(theme.codeHighlighter.overrides ?? {}, 'default' in base ? base.default : base);
|
||||||
}
|
}
|
||||||
if (_res.name == null) {
|
if (_res.name == null) {
|
||||||
_res.name = theme.id;
|
_res.name = theme.id;
|
||||||
|
|
|
@ -131,11 +131,11 @@ type GetItemType<Item extends FormItem> =
|
||||||
: Item extends RadioFormItem
|
: Item extends RadioFormItem
|
||||||
? GetRadioItemType<Item>
|
? GetRadioItemType<Item>
|
||||||
: Item extends RangeFormItem
|
: Item extends RangeFormItem
|
||||||
? NonNullableIfRequired<InferDefault<RangeFormItem, number>, Item>
|
? NonNullableIfRequired<InferDefault<Item, number>, Item>
|
||||||
: Item extends EnumFormItem
|
: Item extends EnumFormItem
|
||||||
? GetEnumItemType<Item>
|
? GetEnumItemType<Item>
|
||||||
: Item extends ArrayFormItem
|
: Item extends ArrayFormItem
|
||||||
? NonNullableIfRequired<InferDefault<ArrayFormItem, unknown[]>, Item>
|
? NonNullableIfRequired<InferDefault<Item, unknown[]>, Item>
|
||||||
: Item extends ObjectFormItem
|
: Item extends ObjectFormItem
|
||||||
? NonNullableIfRequired<InferDefault<Item, Record<string, unknown>>, Item>
|
? NonNullableIfRequired<InferDefault<Item, Record<string, unknown>>, Item>
|
||||||
: Item extends DriveFileFormItem
|
: Item extends DriveFileFormItem
|
||||||
|
|
|
@ -289,7 +289,6 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
|
||||||
caseSensitive: antenna.caseSensitive,
|
caseSensitive: antenna.caseSensitive,
|
||||||
withReplies: antenna.withReplies,
|
withReplies: antenna.withReplies,
|
||||||
withFile: antenna.withFile,
|
withFile: antenna.withFile,
|
||||||
notify: antenna.notify,
|
|
||||||
});
|
});
|
||||||
antennasCache.delete();
|
antennasCache.delete();
|
||||||
},
|
},
|
||||||
|
|
|
@ -38,12 +38,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
import { useLowresTime, TIME_UPDATE_INTERVAL } from '@/composables/use-lowres-time.js';
|
||||||
|
|
||||||
const name = 'calendar';
|
const name = 'calendar';
|
||||||
|
|
||||||
|
@ -65,6 +65,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
emit,
|
emit,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const fNow = useLowresTime();
|
||||||
const year = ref(0);
|
const year = ref(0);
|
||||||
const month = ref(0);
|
const month = ref(0);
|
||||||
const day = ref(0);
|
const day = ref(0);
|
||||||
|
@ -73,8 +74,14 @@ const yearP = ref(0);
|
||||||
const monthP = ref(0);
|
const monthP = ref(0);
|
||||||
const dayP = ref(0);
|
const dayP = ref(0);
|
||||||
const isHoliday = ref(false);
|
const isHoliday = ref(false);
|
||||||
const tick = () => {
|
|
||||||
const now = new Date();
|
const nextDay = new Date();
|
||||||
|
nextDay.setHours(24, 0, 0, 0);
|
||||||
|
let nextDayMidnightTime = nextDay.getTime();
|
||||||
|
let nextDayTimer: number | null = null;
|
||||||
|
|
||||||
|
function update(time: number) {
|
||||||
|
const now = new Date(time);
|
||||||
const nd = now.getDate();
|
const nd = now.getDate();
|
||||||
const nm = now.getMonth();
|
const nm = now.getMonth();
|
||||||
const ny = now.getFullYear();
|
const ny = now.getFullYear();
|
||||||
|
@ -104,11 +111,28 @@ const tick = () => {
|
||||||
yearP.value = yearNumer / yearDenom * 100;
|
yearP.value = yearNumer / yearDenom * 100;
|
||||||
|
|
||||||
isHoliday.value = now.getDay() === 0 || now.getDay() === 6;
|
isHoliday.value = now.getDay() === 0 || now.getDay() === 6;
|
||||||
};
|
}
|
||||||
|
|
||||||
useInterval(tick, 1000, {
|
watch(fNow, (to) => {
|
||||||
immediate: true,
|
update(to);
|
||||||
afterMounted: false,
|
|
||||||
|
// 次回更新までに日付が変わる場合、日付が変わった直後に強制的に更新するタイマーをセットする
|
||||||
|
if (nextDayMidnightTime - to <= TIME_UPDATE_INTERVAL) {
|
||||||
|
if (nextDayTimer != null) {
|
||||||
|
window.clearTimeout(nextDayTimer);
|
||||||
|
nextDayTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextDayTimer = window.setTimeout(() => {
|
||||||
|
update(nextDayMidnightTime);
|
||||||
|
nextDayTimer = null;
|
||||||
|
}, nextDayMidnightTime - to);
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
watch(day, () => {
|
||||||
|
nextDay.setHours(24, 0, 0, 0);
|
||||||
|
nextDayMidnightTime = nextDay.getTime();
|
||||||
});
|
});
|
||||||
|
|
||||||
defineExpose<WidgetComponentExpose>({
|
defineExpose<WidgetComponentExpose>({
|
||||||
|
|
|
@ -35,7 +35,7 @@ describe('MkUrlPreview', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = render(MkUrlPreview, {
|
const result = render(MkUrlPreview, {
|
||||||
props: { url: summary.url },
|
props: { url: summary.url! },
|
||||||
global: { directives, components },
|
global: { directives, components },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -11,16 +11,16 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "22.17.2",
|
"@types/node": "22.18.1",
|
||||||
"@types/wawoff2": "1.0.2",
|
"@types/wawoff2": "1.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
"@typescript-eslint/eslint-plugin": "8.42.0",
|
||||||
"@typescript-eslint/parser": "8.40.0"
|
"@typescript-eslint/parser": "8.42.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tabler/icons-webfont": "3.34.1",
|
"@tabler/icons-webfont": "3.34.1",
|
||||||
"harfbuzzjs": "0.4.9",
|
"harfbuzzjs": "0.4.11",
|
||||||
"tiny-glob": "0.2.9",
|
"tiny-glob": "0.2.9",
|
||||||
"tsx": "4.20.4",
|
"tsx": "4.20.5",
|
||||||
"typescript": "5.9.2",
|
"typescript": "5.9.2",
|
||||||
"wawoff2": "2.0.1"
|
"wawoff2": "2.0.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,9 +24,9 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/matter-js": "0.20.0",
|
"@types/matter-js": "0.20.0",
|
||||||
"@types/seedrandom": "3.0.8",
|
"@types/seedrandom": "3.0.8",
|
||||||
"@types/node": "22.17.2",
|
"@types/node": "22.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
"@typescript-eslint/eslint-plugin": "8.42.0",
|
||||||
"@typescript-eslint/parser": "8.40.0",
|
"@typescript-eslint/parser": "8.42.0",
|
||||||
"nodemon": "3.1.10",
|
"nodemon": "3.1.10",
|
||||||
"execa": "9.6.0",
|
"execa": "9.6.0",
|
||||||
"typescript": "5.9.2",
|
"typescript": "5.9.2",
|
||||||
|
|
|
@ -818,6 +818,18 @@ export type Channels = {
|
||||||
};
|
};
|
||||||
receives: null;
|
receives: null;
|
||||||
};
|
};
|
||||||
|
reversi: {
|
||||||
|
params: null;
|
||||||
|
events: {
|
||||||
|
matched: (payload: {
|
||||||
|
game: ReversiGameDetailed;
|
||||||
|
}) => void;
|
||||||
|
invited: (payload: {
|
||||||
|
user: User;
|
||||||
|
}) => void;
|
||||||
|
};
|
||||||
|
receives: null;
|
||||||
|
};
|
||||||
reversiGame: {
|
reversiGame: {
|
||||||
params: {
|
params: {
|
||||||
gameId: string;
|
gameId: string;
|
||||||
|
@ -1449,6 +1461,10 @@ export type Endpoints = Overwrite<Endpoints_2, {
|
||||||
}>;
|
}>;
|
||||||
res: AdminRolesCreateResponse;
|
res: AdminRolesCreateResponse;
|
||||||
};
|
};
|
||||||
|
'clear-browser-cache': {
|
||||||
|
req: EmptyRequest;
|
||||||
|
res: EmptyResponse;
|
||||||
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
|
@ -3834,8 +3850,8 @@ type VerifyEmailRequest = operations['verify-email']['requestBody']['content']['
|
||||||
//
|
//
|
||||||
// src/entities.ts:55:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
// src/entities.ts:55:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
||||||
// src/streaming.ts:57:3 - (ae-forgotten-export) The symbol "ReconnectingWebSocket" needs to be exported by the entry point index.d.ts
|
// src/streaming.ts:57:3 - (ae-forgotten-export) The symbol "ReconnectingWebSocket" needs to be exported by the entry point index.d.ts
|
||||||
// src/streaming.types.ts:218:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts
|
// src/streaming.types.ts:226:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts
|
||||||
// src/streaming.types.ts:228:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts
|
// src/streaming.types.ts:236:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts
|
||||||
|
|
||||||
// (No @packageDocumentation comment for this package)
|
// (No @packageDocumentation comment for this package)
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,9 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@readme/openapi-parser": "5.0.1",
|
"@readme/openapi-parser": "5.0.1",
|
||||||
"@types/node": "22.17.2",
|
"@types/node": "22.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
"@typescript-eslint/eslint-plugin": "8.42.0",
|
||||||
"@typescript-eslint/parser": "8.40.0",
|
"@typescript-eslint/parser": "8.42.0",
|
||||||
"openapi-types": "12.1.3",
|
"openapi-types": "12.1.3",
|
||||||
"openapi-typescript": "7.9.1",
|
"openapi-typescript": "7.9.1",
|
||||||
"ts-case-convert": "2.1.0",
|
"ts-case-convert": "2.1.0",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2025.9.0-alpha.1",
|
"version": "2025.9.0",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
|
@ -36,9 +36,9 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@microsoft/api-extractor": "7.52.11",
|
"@microsoft/api-extractor": "7.52.11",
|
||||||
"@types/node": "22.17.2",
|
"@types/node": "22.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
"@typescript-eslint/eslint-plugin": "8.42.0",
|
||||||
"@typescript-eslint/parser": "8.40.0",
|
"@typescript-eslint/parser": "8.42.0",
|
||||||
"@vitest/coverage-v8": "3.2.4",
|
"@vitest/coverage-v8": "3.2.4",
|
||||||
"esbuild": "0.25.9",
|
"esbuild": "0.25.9",
|
||||||
"execa": "9.6.0",
|
"execa": "9.6.0",
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import { Endpoints as Gen } from './autogen/endpoint.js';
|
import { Endpoints as Gen } from './autogen/endpoint.js';
|
||||||
import { UserDetailed } from './autogen/models.js';
|
import { UserDetailed } from './autogen/models.js';
|
||||||
import { AdminRolesCreateRequest, AdminRolesCreateResponse, UsersShowRequest } from './autogen/entities.js';
|
import {
|
||||||
|
AdminRolesCreateRequest,
|
||||||
|
AdminRolesCreateResponse,
|
||||||
|
EmptyRequest,
|
||||||
|
EmptyResponse,
|
||||||
|
UsersShowRequest,
|
||||||
|
} from './autogen/entities.js';
|
||||||
import {
|
import {
|
||||||
PartialRolePolicyOverride,
|
PartialRolePolicyOverride,
|
||||||
SigninFlowRequest,
|
SigninFlowRequest,
|
||||||
|
@ -106,6 +112,10 @@ export type Endpoints = Overwrite<
|
||||||
'admin/roles/create': {
|
'admin/roles/create': {
|
||||||
req: Overwrite<AdminRolesCreateRequest, { policies: PartialRolePolicyOverride }>;
|
req: Overwrite<AdminRolesCreateRequest, { policies: PartialRolePolicyOverride }>;
|
||||||
res: AdminRolesCreateResponse;
|
res: AdminRolesCreateResponse;
|
||||||
}
|
},
|
||||||
|
'clear-browser-cache': {
|
||||||
|
req: EmptyRequest;
|
||||||
|
res: EmptyResponse;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
|
@ -6049,7 +6049,9 @@ export interface operations {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
'application/json': components['schemas']['MeDetailed'];
|
'application/json': components['schemas']['MeDetailed'] & {
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description Client error */
|
/** @description Client error */
|
||||||
|
@ -35333,7 +35335,10 @@ export interface operations {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
'application/json': components['schemas']['UserList'];
|
'application/json': components['schemas']['UserList'] & {
|
||||||
|
likedCount?: number;
|
||||||
|
isLiked?: boolean;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description Client error */
|
/** @description Client error */
|
||||||
|
|
|
@ -206,6 +206,14 @@ export type Channels = {
|
||||||
};
|
};
|
||||||
receives: null;
|
receives: null;
|
||||||
};
|
};
|
||||||
|
reversi: {
|
||||||
|
params: null;
|
||||||
|
events: {
|
||||||
|
matched: (payload: { game: ReversiGameDetailed }) => void;
|
||||||
|
invited: (payload: { user: User }) => void;
|
||||||
|
};
|
||||||
|
receives: null;
|
||||||
|
};
|
||||||
reversiGame: {
|
reversiGame: {
|
||||||
params: {
|
params: {
|
||||||
gameId: string;
|
gameId: string;
|
||||||
|
|
|
@ -22,9 +22,9 @@
|
||||||
"lint": "pnpm typecheck && pnpm eslint"
|
"lint": "pnpm typecheck && pnpm eslint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "22.17.2",
|
"@types/node": "22.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
"@typescript-eslint/eslint-plugin": "8.42.0",
|
||||||
"@typescript-eslint/parser": "8.40.0",
|
"@typescript-eslint/parser": "8.42.0",
|
||||||
"execa": "9.6.0",
|
"execa": "9.6.0",
|
||||||
"nodemon": "3.1.10",
|
"nodemon": "3.1.10",
|
||||||
"typescript": "5.9.2",
|
"typescript": "5.9.2",
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
"misskey-js": "workspace:*"
|
"misskey-js": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/parser": "8.40.0",
|
"@typescript-eslint/parser": "8.42.0",
|
||||||
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
|
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
|
||||||
"eslint-plugin-import": "2.32.0",
|
"eslint-plugin-import": "2.32.0",
|
||||||
"nodemon": "3.1.10",
|
"nodemon": "3.1.10",
|
||||||
|
|
3271
pnpm-lock.yaml
3271
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -37,6 +37,7 @@
|
||||||
'packages/frontend/**/package.json',
|
'packages/frontend/**/package.json',
|
||||||
'packages/frontend-embed/**/package.json',
|
'packages/frontend-embed/**/package.json',
|
||||||
'packages/frontend-shared/**/package.json',
|
'packages/frontend-shared/**/package.json',
|
||||||
|
'packages/frontend-builder/**/package.json',
|
||||||
'packages/misskey-bubble-game/**/package.json',
|
'packages/misskey-bubble-game/**/package.json',
|
||||||
'packages/misskey-reversi/**/package.json',
|
'packages/misskey-reversi/**/package.json',
|
||||||
'packages/sw/**/package.json',
|
'packages/sw/**/package.json',
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
"remark-parse": "11.0.0",
|
"remark-parse": "11.0.0",
|
||||||
"typescript": "5.9.2",
|
"typescript": "5.9.2",
|
||||||
"unified": "11.0.5",
|
"unified": "11.0.5",
|
||||||
"vite": "6.3.5",
|
"vite": "6.3.6",
|
||||||
"vite-node": "3.2.4",
|
"vite-node": "3.2.4",
|
||||||
"vitest": "3.2.4"
|
"vitest": "3.2.4"
|
||||||
}
|
}
|
||||||
|
@ -2837,9 +2837,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.3.5",
|
"version": "6.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
|
||||||
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
|
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue