Merge pull request #10578 from misskey-dev/develop

Release: 13.11.2
This commit is contained in:
syuilo 2023-04-11 15:51:07 +09:00 committed by GitHub
commit 75b28d6782
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 884 additions and 402 deletions

View File

@ -12,6 +12,35 @@
--> -->
## 13.11.2
### General
- チャンネルの検索用ページの追加
### Client
- 常に広告を見られるオプションを追加
- ユーザーページの画像一覧が表示されない問題を修正
- webhook, 連携アプリ一覧でコンテンツが重複して表示される問題を修正
- iPhoneで絵文字ピッカーの表示が崩れる問題を修正
- iPhoneでウィジェットドロワーの「ウィジェットを編集」が押しにくい問題を修正
- 投稿フォームのデザインを調整
- ギャラリーの人気の投稿が無限にページングされる問題を修正
### Server
- channels/search Endpoint APIの追加
- APIパラメータサイズ上限を32kbから1mbに緩和
- プッシュ通知送信時のパフォーマンスを改善
- ローカルのカスタム絵文字のキャッシュが効いていなかった問題を修正
- アンテナのノート、チャンネルのノート、通知が正常に作成できないことがある問題を修正
- ストリーミングのLTLチャンネルでサーバー側にエラーログが出るのを修正
### Service Worker
- 「通知が既読になったらプッシュ通知を削除する」を復活
* 「プッシュ通知が更新されました」の挙動を変えた(ホストとバージョンを表示するようにし、一定時間後の削除は行わないように)
- プッシュ通知が実績を解除 (achievementEarned) に対応
- プッシュ通知のアクションから既存のクライアントの投稿フォームを開くことになった際の挙動を修正
- たくさんのプッシュ通知を閉じた際、その通知の数だけnotifications/mark-all-as-readを叩くのをやめるように
## 13.11.1 ## 13.11.1
### General ### General

View File

@ -991,6 +991,7 @@ largeNoteReactions: "Reaktionen vergrößert anzeigen"
noteIdOrUrl: "Notiz-ID oder URL" noteIdOrUrl: "Notiz-ID oder URL"
accountMigration: "Konto-Umzug" accountMigration: "Konto-Umzug"
accountMoved: "Dieser Benutzer ist zu einem neuen Konto umgezogen:" accountMoved: "Dieser Benutzer ist zu einem neuen Konto umgezogen:"
forceShowAds: "Werbung immer anzeigen"
_accountMigration: _accountMigration:
moveTo: "Dieses Konto zu einem neuen umziehen" moveTo: "Dieses Konto zu einem neuen umziehen"
moveToLabel: "Umzugsziel:" moveToLabel: "Umzugsziel:"

View File

@ -67,7 +67,7 @@ import: "Import"
export: "Export" export: "Export"
files: "Files" files: "Files"
download: "Download" download: "Download"
driveFileDeleteConfirm: "Are you sure you want to delete \"{name}\"? All notes with this file attached will also be deleted." driveFileDeleteConfirm: "Are you sure you want to delete \"{name}\"? It will also vanish from all contents that use it."
unfollowConfirm: "Are you sure you want to unfollow {name}?" unfollowConfirm: "Are you sure you want to unfollow {name}?"
exportRequested: "You've requested an export. This may take a while. It will be added to your Drive once completed." exportRequested: "You've requested an export. This may take a while. It will be added to your Drive once completed."
importRequested: "You've requested an import. This may take a while." importRequested: "You've requested an import. This may take a while."
@ -500,7 +500,7 @@ objectStoragePrefixDesc: "Files will be stored under directories with this prefi
objectStorageEndpoint: "Endpoint" objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "Leave this empty if you are using AWS S3, otherwise specify the endpoint as '<host>' or '<host>:<port>', depending on the service you are using." objectStorageEndpointDesc: "Leave this empty if you are using AWS S3, otherwise specify the endpoint as '<host>' or '<host>:<port>', depending on the service you are using."
objectStorageRegion: "Region" objectStorageRegion: "Region"
objectStorageRegionDesc: "Specify a region like 'xx-east-1'. If your service does not distinguish between regions, leave this blank or enter 'us-east-1'." objectStorageRegionDesc: "Specify a region like 'xx-east-1'. If your service does not distinguish between regions, enter 'us-east-1'. Leave empty if using AWS configuration files or environment variables."
objectStorageUseSSL: "Use SSL" objectStorageUseSSL: "Use SSL"
objectStorageUseSSLDesc: "Turn this off if you are not going to use HTTPS for API connections" objectStorageUseSSLDesc: "Turn this off if you are not going to use HTTPS for API connections"
objectStorageUseProxy: "Connect over Proxy" objectStorageUseProxy: "Connect over Proxy"
@ -918,7 +918,7 @@ unsubscribePushNotification: "Disable push notifications"
pushNotificationAlreadySubscribed: "Push notifications are already enabled" pushNotificationAlreadySubscribed: "Push notifications are already enabled"
pushNotificationNotSupported: "Your browser or instance does not support push notifications" pushNotificationNotSupported: "Your browser or instance does not support push notifications"
sendPushNotificationReadMessage: "Delete push notifications once the relevant notifications or messages have been read" sendPushNotificationReadMessage: "Delete push notifications once the relevant notifications or messages have been read"
sendPushNotificationReadMessageCaption: "A notification containing the text \"{emptyPushNotificationMessage}\" will be displayed for a short time. This may increase the battery usage of your device, if applicable." sendPushNotificationReadMessageCaption: "A notification containing the text \"{emptyPushNotificationMessage}\" will be displayed for a short time. This may increase the power consumption of your device."
windowMaximize: "Maximize" windowMaximize: "Maximize"
windowMinimize: "Minimize" windowMinimize: "Minimize"
windowRestore: "Restore" windowRestore: "Restore"
@ -991,6 +991,7 @@ largeNoteReactions: "Enlargen displayed reactions"
noteIdOrUrl: "Note ID or URL" noteIdOrUrl: "Note ID or URL"
accountMigration: "Account Migration" accountMigration: "Account Migration"
accountMoved: "This user has moved to a new account:" accountMoved: "This user has moved to a new account:"
forceShowAds: "Always show ads"
_accountMigration: _accountMigration:
moveTo: "Migrate this account to a different one" moveTo: "Migrate this account to a different one"
moveToLabel: "Account to move to:" moveToLabel: "Account to move to:"
@ -1868,7 +1869,7 @@ _deck:
swapRight: "Swap with the right column" swapRight: "Swap with the right column"
swapUp: "Swap with the above column" swapUp: "Swap with the above column"
swapDown: "Swap with the below column" swapDown: "Swap with the below column"
stackLeft: "Stack with the left column" stackLeft: "Stack on left column"
popRight: "Pop column to the right" popRight: "Pop column to the right"
profile: "Profile" profile: "Profile"
newProfile: "New profile" newProfile: "New profile"

View File

@ -122,6 +122,8 @@ unmarkAsSensitive: "Hapus tanda konten sensitif"
enterFileName: "Masukkan nama berkas" enterFileName: "Masukkan nama berkas"
mute: "Bisukan" mute: "Bisukan"
unmute: "Hapus bisukan" unmute: "Hapus bisukan"
renoteMute: "Matikan renote"
renoteUnmute: "Batal mematikan renote"
block: "Blokir" block: "Blokir"
unblock: "Buka blokir" unblock: "Buka blokir"
suspend: "Bekukan" suspend: "Bekukan"
@ -393,11 +395,15 @@ about: "Informasi"
aboutMisskey: "Tentang Misskey" aboutMisskey: "Tentang Misskey"
administrator: "Admin" administrator: "Admin"
token: "Token" token: "Token"
totp: "Aplikasi autentikator"
totpDescription: "Gunakan aplikasi autentikator untuk mendapatkan kata sandi sekali pakai"
moderator: "Moderator" moderator: "Moderator"
moderation: "Moderasi" moderation: "Moderasi"
nUsersMentioned: "{n} pengguna disebut" nUsersMentioned: "{n} pengguna disebut"
securityKeyAndPasskey: "Security key dan passkey"
securityKey: "Kunci keamanan" securityKey: "Kunci keamanan"
lastUsed: "Terakhir digunakan" lastUsed: "Terakhir digunakan"
lastUsedAt: "Penggunaan terakhir: {t}"
unregister: "Batalkan pendaftaran" unregister: "Batalkan pendaftaran"
passwordLessLogin: "Setel login tanpa kata sandi" passwordLessLogin: "Setel login tanpa kata sandi"
resetPassword: "Atur ulang kata sandi" resetPassword: "Atur ulang kata sandi"
@ -844,6 +850,7 @@ tenMinutes: "10 Menit"
oneHour: "1 Jam" oneHour: "1 Jam"
oneDay: "1 Hari" oneDay: "1 Hari"
oneWeek: "1 Bulan" oneWeek: "1 Bulan"
oneMonth: "satu bulan"
reflectMayTakeTime: "Mungkin perlu beberapa saat untuk dicerminkan." reflectMayTakeTime: "Mungkin perlu beberapa saat untuk dicerminkan."
failedToFetchAccountInformation: "Gagal untuk mendapatkan informasi akun" failedToFetchAccountInformation: "Gagal untuk mendapatkan informasi akun"
rateLimitExceeded: "Batas sudah terlampaui" rateLimitExceeded: "Batas sudah terlampaui"
@ -901,6 +908,7 @@ pushNotificationNotSupported: "Browser atau instansi kamu tidak mendukung pember
sendPushNotificationReadMessage: "Hapus pemberitahuan push ketika pemberitahuan relevan atau pesan telah dibaca" sendPushNotificationReadMessage: "Hapus pemberitahuan push ketika pemberitahuan relevan atau pesan telah dibaca"
sendPushNotificationReadMessageCaption: "Pemberitahuan berisi teks「{emptyPushNotificationMessage}」akan ditampilkan dalam waktu pendek. Ini mungkin dapat menambah pemakaian baterai pada perangkat kamu." sendPushNotificationReadMessageCaption: "Pemberitahuan berisi teks「{emptyPushNotificationMessage}」akan ditampilkan dalam waktu pendek. Ini mungkin dapat menambah pemakaian baterai pada perangkat kamu."
windowMaximize: "Maksimalkan" windowMaximize: "Maksimalkan"
windowMinimize: "Minimalkan"
windowRestore: "Kembalikan" windowRestore: "Kembalikan"
caption: "Keterangan" caption: "Keterangan"
loggedInAsBot: "Sedang login sebagai bot" loggedInAsBot: "Sedang login sebagai bot"
@ -939,6 +947,12 @@ collapseRenotes: "Tutup renote yang sudah kamu lihat"
internalServerError: "Kesalahan internal peladen" internalServerError: "Kesalahan internal peladen"
internalServerErrorDescription: "Peladen sedang mengalami galat tak terduga" internalServerErrorDescription: "Peladen sedang mengalami galat tak terduga"
copyErrorInfo: "Salin detil galat" copyErrorInfo: "Salin detil galat"
joinThisServer: "Gabung server ini"
exploreOtherServers: "Cari server lain"
letsLookAtTimeline: "LIhat timeline"
disableFederationConfirm: "Matikan federasi?"
disableFederationConfirmWarn: "Mematikan federasi tidak membuat kiriman menjadi privat. Umumnya, mematikan federasi tidak diperlukan."
disableFederationOk: "Matikan federasi"
_achievements: _achievements:
earnedAt: "Terbuka pada" earnedAt: "Terbuka pada"
_types: _types:

View File

@ -20,6 +20,7 @@ noNotes: "ノートはありません"
noNotifications: "通知はありません" noNotifications: "通知はありません"
instance: "サーバー" instance: "サーバー"
settings: "設定" settings: "設定"
notificationSettings: "通知の設定"
basicSettings: "基本設定" basicSettings: "基本設定"
otherSettings: "その他の設定" otherSettings: "その他の設定"
openInWindow: "ウィンドウで開く" openInWindow: "ウィンドウで開く"
@ -917,8 +918,8 @@ subscribePushNotification: "プッシュ通知を有効化"
unsubscribePushNotification: "プッシュ通知を停止する" unsubscribePushNotification: "プッシュ通知を停止する"
pushNotificationAlreadySubscribed: "プッシュ通知は有効です" pushNotificationAlreadySubscribed: "プッシュ通知は有効です"
pushNotificationNotSupported: "ブラウザかサーバーがプッシュ通知に非対応" pushNotificationNotSupported: "ブラウザかサーバーがプッシュ通知に非対応"
sendPushNotificationReadMessage: "通知やメッセージが既読になったらプッシュ通知を削除する" sendPushNotificationReadMessage: "通知が既読になったらプッシュ通知を削除する"
sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」という通知が一瞬表示されるようになります。端末の電池消費量が増加する可能性があります。" sendPushNotificationReadMessageCaption: "端末の電池消費量が増加する可能性があります。"
windowMaximize: "最大化" windowMaximize: "最大化"
windowMinimize: "最小化" windowMinimize: "最小化"
windowRestore: "元に戻す" windowRestore: "元に戻す"
@ -991,6 +992,7 @@ largeNoteReactions: "ノートのリアクションを大きく表示"
noteIdOrUrl: "ートIDまたはURL" noteIdOrUrl: "ートIDまたはURL"
accountMigration: "アカウントの引っ越し" accountMigration: "アカウントの引っ越し"
accountMoved: "このユーザーは新しいアカウントに引っ越しました:" accountMoved: "このユーザーは新しいアカウントに引っ越しました:"
forceShowAds: "常に広告を表示する"
_accountMigration: _accountMigration:
moveTo: "このアカウントを新しいアカウントに引っ越す" moveTo: "このアカウントを新しいアカウントに引っ越す"
@ -1426,6 +1428,8 @@ _channel:
following: "フォロー中" following: "フォロー中"
usersCount: "{n}人が参加中" usersCount: "{n}人が参加中"
notesCount: "{n}投稿があります" notesCount: "{n}投稿があります"
nameAndDescription: "名前と説明"
nameOnly: "名前のみ"
_menuDisplay: _menuDisplay:
sideFull: "横" sideFull: "横"

View File

@ -991,6 +991,7 @@ largeNoteReactions: "ノートのリアクションを大きする"
noteIdOrUrl: "ートIDかURL" noteIdOrUrl: "ートIDかURL"
accountMigration: "アカウントのお引っ越し" accountMigration: "アカウントのお引っ越し"
accountMoved: "このユーザーはさらのアカウントに引っ越したで:" accountMoved: "このユーザーはさらのアカウントに引っ越したで:"
forceShowAds: "常に広告を表示しとく"
_accountMigration: _accountMigration:
moveTo: "このアカウントをさらのアカウントに引っ越すで" moveTo: "このアカウントをさらのアカウントに引っ越すで"
moveToLabel: "引っ越し先のアカウント:" moveToLabel: "引っ越し先のアカウント:"

View File

@ -163,11 +163,15 @@ instanceInfo: "ອີນສະແຕນ"
statistics: "ສະຖິຕິ" statistics: "ສະຖິຕິ"
clearQueue: "ລ້າງຄິວ" clearQueue: "ລ້າງຄິວ"
clearCachedFiles: "ລຶບລ້າງແຄສ" clearCachedFiles: "ລຶບລ້າງແຄສ"
noUsers: "ບໍ່ພົບຜູ້ໃຊ້"
editProfile: "ແກ້ໄຂໂປຣໄຟລ໌" editProfile: "ແກ້ໄຂໂປຣໄຟລ໌"
done: "ສຳເລັດ" done: "ສຳເລັດ"
processing: "ກຳລັງປະມວນຜົນ" processing: "ກຳລັງປະມວນຜົນ"
preview: "ສະແດງເປັນຕົວຢ່າງ" preview: "ສະແດງເປັນຕົວຢ່າງ"
default: "ຄ່າເລີ່ມຕົ້ນ" default: "ຄ່າເລີ່ມຕົ້ນ"
defaultValueIs: "ຄ່າເລີ່ມຕົ້ນ: {value}"
noCustomEmojis: "ບໍ່ມີອີໂມຈິ"
noJobs: "ບໍ່ມີຊິ້ນວຽກ"
federating: "ສະຫະພັນ" federating: "ສະຫະພັນ"
blocked: "ບລັອກແລ້ວ " blocked: "ບລັອກແລ້ວ "
suspended: "ໂຈະ" suspended: "ໂຈະ"
@ -182,6 +186,9 @@ changePassword: "ປ່ຽນ​ລະ​ຫັດ​ຜ່ານ"
security: "ຄວາມປອດໄພ" security: "ຄວາມປອດໄພ"
retypedNotMatch: "ວັດສະດຸປ້ອນບໍ່ກົງກັນ" retypedNotMatch: "ວັດສະດຸປ້ອນບໍ່ກົງກັນ"
currentPassword: "ລະຫັດຜ່ານປະຈຸບັນ" currentPassword: "ລະຫັດຜ່ານປະຈຸບັນ"
newPassword: "ລະຫັດຜ່ານໃໝ່"
newPasswordRetype: "ໃສ່ລະຫັດຜ່ານໃໝ່ອີກເທື່ອໜຶ່ງ"
attachFile: "ແນບໄຟລ໌"
more: "ເພີ່ມເຕີມ!" more: "ເພີ່ມເຕີມ!"
featured: "ໄຮໄລທ໌" featured: "ໄຮໄລທ໌"
usernameOrUserId: "ຊື່ຜູ້ໃຊ້ ຫຼື id ຜູ້ໃຊ້" usernameOrUserId: "ຊື່ຜູ້ໃຊ້ ຫຼື id ຜູ້ໃຊ້"
@ -196,25 +203,31 @@ saved: "ບັນທຶກແລ້ວ"
messaging: "ແຊ໋ດ" messaging: "ແຊ໋ດ"
upload: "ອັບໂຫຼດ" upload: "ອັບໂຫຼດ"
keepOriginalUploading: "ຮັກສາຮູບພາບຕົ້ນສະບັບ" keepOriginalUploading: "ຮັກສາຮູບພາບຕົ້ນສະບັບ"
fromDrive: "ຈາກ Drive"
fromUrl: "ຈາກ URL" fromUrl: "ຈາກ URL"
uploadFromUrl: "ອັບໂຫຼດຈາກ URL" uploadFromUrl: "ອັບໂຫຼດຈາກ URL"
uploadFromUrlDescription: "URL ຂອງໄຟລ໌ທີ່ທ່ານຕ້ອງການອັບໂຫລດ" uploadFromUrlDescription: "URL ຂອງໄຟລ໌ທີ່ທ່ານຕ້ອງການອັບໂຫລດ"
uploadFromUrlRequested: "ຮ້ອງຂໍການອັບໂຫລດ"
messageRead: "ອ່ານແລ້ວ" messageRead: "ອ່ານແລ້ວ"
startMessaging: "ເລີ່ມການສົນທະນາໃໝ່" startMessaging: "ເລີ່ມການສົນທະນາໃໝ່"
nUsersRead: "ອ່ານໂດຍ {n}" nUsersRead: "ອ່ານໂດຍ {n}"
tos: "ເງື່ອນໄຂການໃຫ້ບໍລິການ" tos: "ເງື່ອນໄຂການໃຫ້ບໍລິການ"
start: "ເລີ່ມຕົ້ນນຳໃຊ້ເລີຍ" start: "ເລີ່ມຕົ້ນນຳໃຊ້ເລີຍ"
home: "ໜ້າຫຼັກ" home: "ໜ້າຫຼັກ"
activity: "ກິດຈະກຳ"
images: "ຮູບພາບ" images: "ຮູບພາບ"
birthday: "ວັນເກີດ" birthday: "ວັນເກີດ"
yearsOld: "{age} ປີ" yearsOld: "{age} ປີ"
registeredDate: "ວັນທີ່ເປັນສະມາຊິກ" registeredDate: "ວັນທີ່ເປັນສະມາຊິກ"
location: "ທີ່ຕັ້ງ" location: "ທີ່ຕັ້ງ"
theme: "ແທ໋ມ" theme: "ແທ໋ມ"
themeForLightMode: "ຮູບແບບສີສັນເພື່ອໃຊ້ໃນໂໝດແສງ"
themeForDarkMode: "ຮູບແບບສີສັນທີ່ຈະໃຊ້ຢູ່ໃນໂໝດມືດ"
light: "ສະຫວ່າງ" light: "ສະຫວ່າງ"
dark: "ມືດ" dark: "ມືດ"
lightThemes: "ຊຸດຮູບແບບສະຫວ່າງ" lightThemes: "ຊຸດຮູບແບບສະຫວ່າງ"
darkThemes: "ຮູບແບບສີສັນມືດ" darkThemes: "ຮູບແບບສີສັນມືດ"
syncDeviceDarkMode: "ຊິງຄ໌ໂໝດມືດກັບການຕັ້ງຄ່າທົ່ວອຸປະກອນ"
drive: "ຂັບ" drive: "ຂັບ"
fileName: "ຊື່ໄຟລ໌" fileName: "ຊື່ໄຟລ໌"
selectFile: "ເລືອກໄຟລ໌" selectFile: "ເລືອກໄຟລ໌"
@ -265,6 +278,9 @@ invite: "ເຊີນ"
driveCapacityPerLocalAccount: "ຄວາມອາດສາມາດຂັບຕໍ່ຜູ້ໃຊ້ທ້ອງຖິ່ນ" driveCapacityPerLocalAccount: "ຄວາມອາດສາມາດຂັບຕໍ່ຜູ້ໃຊ້ທ້ອງຖິ່ນ"
driveCapacityPerRemoteAccount: "ໄດຣຟ໌ຄວາມອາດສາມາດຕໍ່ຜູ້ໃຊ້ທາງໄກ" driveCapacityPerRemoteAccount: "ໄດຣຟ໌ຄວາມອາດສາມາດຕໍ່ຜູ້ໃຊ້ທາງໄກ"
pinnedNotes: "ບັນທຶກທີ່ປັກໝຸດໄວ້" pinnedNotes: "ບັນທຶກທີ່ປັກໝຸດໄວ້"
turnstileSiteKey: "ກະແຈໄຊທ໌"
turnstileSecretKey: "ກະແຈລັບ"
name: "ຊື່"
userList: "ລາຍການ" userList: "ລາຍການ"
about: "ກ່ຽວກັບ" about: "ກ່ຽວກັບ"
aboutMisskey: "ກ່ຽວກັບ Misskey" aboutMisskey: "ກ່ຽວກັບ Misskey"
@ -326,6 +342,7 @@ _widgets:
instanceInfo: "ອີນສະແຕນ" instanceInfo: "ອີນສະແຕນ"
notifications: "ການແຈ້ງເຕືອນ" notifications: "ການແຈ້ງເຕືອນ"
timeline: "​ເສັ້ນກຳ​ນົດ​ເວ​ລາ​" timeline: "​ເສັ້ນກຳ​ນົດ​ເວ​ລາ​"
activity: "ກິດຈະກຳ"
federation: "ສະຫະພັນ" federation: "ສະຫະພັນ"
_userList: _userList:
chooseList: "ເລືອກບັນຊີລາຍການ" chooseList: "ເລືອກບັນຊີລາຍການ"
@ -335,6 +352,7 @@ _visibility:
home: "ໜ້າຫຼັກ" home: "ໜ້າຫຼັກ"
followers: "ຜູ້ຕິດຕາມ" followers: "ຜູ້ຕິດຕາມ"
_profile: _profile:
name: "ຊື່"
username: "ຊື່ຜູ້ໃຊ້" username: "ຊື່ຜູ້ໃຊ້"
_exportOrImport: _exportOrImport:
followingList: "ກຳລັງຕິດຕາມ" followingList: "ກຳລັງຕິດຕາມ"
@ -368,3 +386,5 @@ _deck:
list: "ລາຍການ" list: "ລາຍການ"
channel: "ຊ່ອງ" channel: "ຊ່ອງ"
mentions: "ກ່າວເຖິງ" mentions: "ກ່າວເຖິງ"
_webhookSettings:
name: "ຊື່"

View File

@ -122,6 +122,8 @@ unmarkAsSensitive: "ยกเลิกทำเครื่องหมายเ
enterFileName: "พิมพ์ชื่อไฟล์" enterFileName: "พิมพ์ชื่อไฟล์"
mute: "ปิดเสียง" mute: "ปิดเสียง"
unmute: "ยกเลิกการปิดเสียง" unmute: "ยกเลิกการปิดเสียง"
renoteMute: "ปิดเสียงรีโน้ต"
renoteUnmute: "เปิดเสียง รีโน้ต"
block: "บล็อค" block: "บล็อค"
unblock: "เลิกปิดกั้น" unblock: "เลิกปิดกั้น"
suspend: "ถูกระงับ" suspend: "ถูกระงับ"
@ -153,6 +155,7 @@ flagShowTimelineReplies: "แสดงตอบกลับ ในไทม์
flagShowTimelineRepliesDescription: "แสดงการตอบกลับของผู้ใช้งานไปยังโน้ตของผู้ใช้งานรายอื่นๆในไทม์ไลน์หากได้เปิดเอาไว้" flagShowTimelineRepliesDescription: "แสดงการตอบกลับของผู้ใช้งานไปยังโน้ตของผู้ใช้งานรายอื่นๆในไทม์ไลน์หากได้เปิดเอาไว้"
autoAcceptFollowed: "อนุมัติคำขอติดตามโดยอัตโนมัติทันที จากผู้ใช้งานที่คุณกำลังติดตาม" autoAcceptFollowed: "อนุมัติคำขอติดตามโดยอัตโนมัติทันที จากผู้ใช้งานที่คุณกำลังติดตาม"
addAccount: "เพิ่มบัญชี" addAccount: "เพิ่มบัญชี"
reloadAccountsList: "รีโหลดรายการบัญชีใหม่"
loginFailed: "การเข้าสู่ระบบไม่สำเร็จ" loginFailed: "การเข้าสู่ระบบไม่สำเร็จ"
showOnRemote: "ดูบนอินสแตนซ์ระยะไกล" showOnRemote: "ดูบนอินสแตนซ์ระยะไกล"
general: "ทั่วไป" general: "ทั่วไป"
@ -503,6 +506,7 @@ objectStorageUseSSLDesc: "ปิดการทำงานนี้ไว้
objectStorageUseProxy: "เชื่อมต่อผ่านพร็อกซี" objectStorageUseProxy: "เชื่อมต่อผ่านพร็อกซี"
objectStorageUseProxyDesc: "ปิดสิ่งนี้ไว้ถ้าหากคุณจะไม่ใช้ Proxy สำหรับการเชื่อมต่อ API" objectStorageUseProxyDesc: "ปิดสิ่งนี้ไว้ถ้าหากคุณจะไม่ใช้ Proxy สำหรับการเชื่อมต่อ API"
objectStorageSetPublicRead: "ตั้งค่า \"public-read\" ในการอัปโหลด" objectStorageSetPublicRead: "ตั้งค่า \"public-read\" ในการอัปโหลด"
s3ForcePathStyleDesc: "ถ้าหากเปิดใช้งาน s3ForcePathStyle ชื่อบัคเก็ตนั้นอาจจะต้องรวมอยู่ในเส้นทางของ URL ซึ่งตรงข้ามกับชื่อโฮสต์ของ URL คุณอาจจะต้องเปิดใช้งานการตั้งค่านี้เมื่อใช้บริการต่างๆ เช่น อินสแตนซ์ Minio ที่โฮสต์เองนะ"
serverLogs: "บันทึกของเซิร์ฟเวอร์" serverLogs: "บันทึกของเซิร์ฟเวอร์"
deleteAll: "ลบทั้งหมด" deleteAll: "ลบทั้งหมด"
showFixedPostForm: "แสดงแบบฟอร์มการโพสต์ที่ด้านบนสุดของไทม์ไลน์" showFixedPostForm: "แสดงแบบฟอร์มการโพสต์ที่ด้านบนสุดของไทม์ไลน์"
@ -545,7 +549,9 @@ userSilenced: "ผู้ใช้รายนี้กำลังถูกป
yourAccountSuspendedTitle: "บัญชีนี้นั้นถูกระงับ" yourAccountSuspendedTitle: "บัญชีนี้นั้นถูกระงับ"
yourAccountSuspendedDescription: "บัญชีนี้ถูกระงับ เนื่องจากละเมิดข้อกำหนดในการให้บริการของเซิร์ฟเวอร์หรืออาจจะละเมิดหลักเกณฑ์ชุมชน หรือ อาจจะโดนร้องเรียนเรื่องการละเมิดลิขสิทธิ์และอื่นๆอย่างต่อเนื่องซ้ำๆ หากคุณคิดว่าไม่ได้ทำผิดจริงๆหรือตัดสินผิดพลาด ได้โปรดกรุณาติดต่อผู้ดูแลระบบหากคุณต้องการทราบเหตุผลโดยละเอียดเพิ่มเติม และขอความกรุณาอย่าสร้างบัญชีใหม่" yourAccountSuspendedDescription: "บัญชีนี้ถูกระงับ เนื่องจากละเมิดข้อกำหนดในการให้บริการของเซิร์ฟเวอร์หรืออาจจะละเมิดหลักเกณฑ์ชุมชน หรือ อาจจะโดนร้องเรียนเรื่องการละเมิดลิขสิทธิ์และอื่นๆอย่างต่อเนื่องซ้ำๆ หากคุณคิดว่าไม่ได้ทำผิดจริงๆหรือตัดสินผิดพลาด ได้โปรดกรุณาติดต่อผู้ดูแลระบบหากคุณต้องการทราบเหตุผลโดยละเอียดเพิ่มเติม และขอความกรุณาอย่าสร้างบัญชีใหม่"
tokenRevoked: "โทเค็นไม่ถูกต้อง" tokenRevoked: "โทเค็นไม่ถูกต้อง"
tokenRevokedDescription: "โทเค็นนี้หมดอายุแล้วนะค่ะกรุณาเข้าสู่ระบบอีกครั้งนะ"
accountDeleted: "ลบบัญชีแล้ว" accountDeleted: "ลบบัญชีแล้ว"
accountDeletedDescription: "บัญชีนี้ถูกลบไปแล้วนะ"
menu: "เมนู" menu: "เมนู"
divider: "ตัวแบ่ง" divider: "ตัวแบ่ง"
addItem: "เพิ่มรายการ" addItem: "เพิ่มรายการ"
@ -914,6 +920,7 @@ pushNotificationNotSupported: "เบราว์เซอร์หรืออ
sendPushNotificationReadMessage: "ลบการแจ้งเตือนแบบพุชเมื่ออ่านการแจ้งเตือนหรือข้อความที่เกี่ยวข้องแล้ว" sendPushNotificationReadMessage: "ลบการแจ้งเตือนแบบพุชเมื่ออ่านการแจ้งเตือนหรือข้อความที่เกี่ยวข้องแล้ว"
sendPushNotificationReadMessageCaption: "การแจ้งเตือนที่มีข้อความ \"{emptyPushNotificationMessage}\" จะแสดงขึ้นมาในช่วงระยะเวลาสั้นๆ การดำเนินการนี้อาจทำให้เพิ่มการใช้งานแบตเตอรี่ของอุปกรณ์ถ้าหากมีนะ" sendPushNotificationReadMessageCaption: "การแจ้งเตือนที่มีข้อความ \"{emptyPushNotificationMessage}\" จะแสดงขึ้นมาในช่วงระยะเวลาสั้นๆ การดำเนินการนี้อาจทำให้เพิ่มการใช้งานแบตเตอรี่ของอุปกรณ์ถ้าหากมีนะ"
windowMaximize: "ขยายใหญ่สุดแล้ว" windowMaximize: "ขยายใหญ่สุดแล้ว"
windowMinimize: "ย่อเล็กที่สุด"
windowRestore: "เลิกทำ" windowRestore: "เลิกทำ"
caption: "รายละเอียด" caption: "รายละเอียด"
loggedInAsBot: "ล็อกอินเป็นบอตอยู่ในขณะนี้" loggedInAsBot: "ล็อกอินเป็นบอตอยู่ในขณะนี้"
@ -955,11 +962,17 @@ copyErrorInfo: "คัดลอกรายละเอียดข้อผิ
joinThisServer: "ลงชื่อสมัครใช้ในอินสแตนซ์นี้" joinThisServer: "ลงชื่อสมัครใช้ในอินสแตนซ์นี้"
exploreOtherServers: "มองหาอินสแตนซ์อื่น" exploreOtherServers: "มองหาอินสแตนซ์อื่น"
letsLookAtTimeline: "ลองดูที่ไทม์ไลน์" letsLookAtTimeline: "ลองดูที่ไทม์ไลน์"
disableFederationConfirm: "ปิดใช้งานสหพันธ์จริงๆหรอแน่ใจแล้วนะ?"
disableFederationConfirmWarn: "แม้ว่าจะถูกยกเลิกเอาไว้โพสต์ดังกล่าวนั้นจะยังคงเป็นสาธารณะต่อไป เว้นแต่ว่า...จะตั้งค่าเป็นอย่างอื่น โดยปกติคุณไม่จำเป็นต้องทำตรงนี้หรอกนะค่ะ"
disableFederationOk: "ปิดการใช้งาน"
invitationRequiredToRegister: "อินสแตนซ์นี้เป็นแบบรับเชิญเท่านั้น คุณต้องป้อนรหัสเชิญที่ถูกต้องถึงจะลงทะเบียนได้นะค่ะ" invitationRequiredToRegister: "อินสแตนซ์นี้เป็นแบบรับเชิญเท่านั้น คุณต้องป้อนรหัสเชิญที่ถูกต้องถึงจะลงทะเบียนได้นะค่ะ"
emailNotSupported: "อินสแตนซ์นี้ไม่รองรับการส่งอีเมลนะค่ะ" emailNotSupported: "อินสแตนซ์นี้ไม่รองรับการส่งอีเมลนะค่ะ"
postToTheChannel: "โพสต์ลงช่อง" postToTheChannel: "โพสต์ลงช่อง"
cannotBeChangedLater: "สิ่งนี้ไม่สามารถเปลี่ยนแปลงได้ในภายหลังนะ" cannotBeChangedLater: "สิ่งนี้ไม่สามารถเปลี่ยนแปลงได้ในภายหลังนะ"
reactionAcceptance: "การยอมรับรีแอคชั่น"
likeOnly: "ที่ชอบเท่านั้น" likeOnly: "ที่ชอบเท่านั้น"
likeOnlyForRemote: "ไลค์สำหรับอินสแตนซ์ระยะไกลเท่านั้น"
rolesAssignedToMe: "บทบาทที่ได้รับมอบหมายให้ฉัน"
resetPasswordConfirm: "รีเซ็ตรหัสผ่านของคุณจริงๆหรอ?" resetPasswordConfirm: "รีเซ็ตรหัสผ่านของคุณจริงๆหรอ?"
sensitiveWords: "คำที่ละเอียดอ่อน" sensitiveWords: "คำที่ละเอียดอ่อน"
sensitiveWordsDescription: "การเปิดเผยโน้ตทั้งหมดที่มีคำที่กำหนดค่าไว้จะถูกตั้งค่าเป็น \"หน้าแรก\" โดยอัตโนมัติ คุณยังสามารถแสดงหลายรายการได้โดยแยกรายการโดยใช้ตัวแบ่งบรรทัดได้นะ" sensitiveWordsDescription: "การเปิดเผยโน้ตทั้งหมดที่มีคำที่กำหนดค่าไว้จะถูกตั้งค่าเป็น \"หน้าแรก\" โดยอัตโนมัติ คุณยังสามารถแสดงหลายรายการได้โดยแยกรายการโดยใช้ตัวแบ่งบรรทัดได้นะ"
@ -971,6 +984,22 @@ drivecleaner: "ทำความสะอาดไดรฟ์"
retryAllQueuesNow: "ลองเรียกใช้คิวทั้งหมดอีกครั้ง" retryAllQueuesNow: "ลองเรียกใช้คิวทั้งหมดอีกครั้ง"
retryAllQueuesConfirmTitle: "ลองใหม่ทั้งหมดจริงๆหรอแน่ใจนะ?" retryAllQueuesConfirmTitle: "ลองใหม่ทั้งหมดจริงๆหรอแน่ใจนะ?"
retryAllQueuesConfirmText: "สิ่งนี้จะเพิ่มการโหลดเซิร์ฟเวอร์ชั่วคราวนะ" retryAllQueuesConfirmText: "สิ่งนี้จะเพิ่มการโหลดเซิร์ฟเวอร์ชั่วคราวนะ"
enableChartsForRemoteUser: "สร้างแผนภูมิข้อมูลผู้ใช้ระยะไกล"
enableChartsForFederatedInstances: "สร้างแผนภูมิข้อมูลอินสแตนซ์ระยะไกล"
showClipButtonInNoteFooter: "เพิ่ม \"คลิป\" เพื่อบันทึกเมนูการทำงาน"
largeNoteReactions: "ขยายรีแอคชั่นการแสดงผล"
noteIdOrUrl: "โน้ต ID หรือ URL"
accountMigration: "การโยกย้ายบัญชี"
accountMoved: "ผู้ใช้รายนี้ได้ย้ายไปยังบัญชีใหม่แล้ว:"
forceShowAds: "แสดงโฆษณาเสมอ"
_accountMigration:
moveTo: "ย้ายข้อมูลบัญชีนี้ไปยังบัญชีอีกหนึ่ง"
moveToLabel: "บัญชีที่จะย้ายไปที่:"
moveAccountDescription: "การกระทำนี้ไม่สามารถย้อนกลับได้นะ ขั้นตอนแรก ต้องสร้างนามแฝงสำหรับบัญชีนี้ในบัญชีที่คุณต้องการย้ายไป หลังจากนั้นแล้ว ป้อนบัญชีที่จะย้ายไปในรูปแบบดังต่อไปนี้: @person@instance.com"
moveFrom: "ย้ายข้อมูลบัญชีอื่นไปยังอีกบัญชีนี้หนึ่ง"
moveFromLabel: "บัญชีที่จะย้ายจาก:"
moveFromDescription: "สร้างนามแฝงสำหรับบัญชีที่จะย้ายจากบัญชีนี้ ถ้าหากคุณต้องการโอนผู้ติดตาม สิ่งนี้ต้องทำก่อนโอนก่อนนะค่ะ! หลังจากนั้น ป้อนบัญชีที่จะย้ายไปในรูปแบบต่อไปนี้: @person@instance.com"
migrationConfirm: "ย้ายข้อมูลบัญชีนี้ไปที่ {account} จริงๆนะ เมื่อมีการเริ่มต้นแล้ว กระบวนการนี้จะไม่สามารถหยุดหรือนำกลับคืนมาได้ และคุณจะไม่สามารถใช้บัญชีนี้ในสถานะดั้งเดิมได้อีกต่อไป\n\nนอกจากนี้ เพื่อให้แน่ใจยืนยันว่าคุณได้สร้างนามแฝงในบัญชีที่จะย้ายข้อมูลนะค่ะ"
_achievements: _achievements:
earnedAt: "ได้รับเมื่อ" earnedAt: "ได้รับเมื่อ"
_types: _types:
@ -1267,6 +1296,8 @@ _role:
followersMoreThanOrEq: "จำนวนผู้ติดตามมากกว่าหรือเท่ากับ\n" followersMoreThanOrEq: "จำนวนผู้ติดตามมากกว่าหรือเท่ากับ\n"
followingLessThanOrEq: "จำนวนบัญชีต่อไปนี้คือ น้อยกว่าหรือเท่ากับ" followingLessThanOrEq: "จำนวนบัญชีต่อไปนี้คือ น้อยกว่าหรือเท่ากับ"
followingMoreThanOrEq: "จำนวนบัญชีต่อไปนี้คือ มากกว่าหรือเท่ากับ" followingMoreThanOrEq: "จำนวนบัญชีต่อไปนี้คือ มากกว่าหรือเท่ากับ"
notesLessThanOrEq: "จำนวนโพสต์น้อยกว่าเท่ากับ"
notesMoreThanOrEq: "จำนวนโพสต์มากกว่าเท่ากับ"
and: "และ" and: "และ"
or: "หรือ" or: "หรือ"
not: "ไม่" not: "ไม่"
@ -1866,5 +1897,16 @@ _drivecleaner:
orderBySizeDesc: "ขนาดไฟล์จากมากไปหาน้อย" orderBySizeDesc: "ขนาดไฟล์จากมากไปหาน้อย"
orderByCreatedAtAsc: "วันที่จากน้อยไปหามาก" orderByCreatedAtAsc: "วันที่จากน้อยไปหามาก"
_webhookSettings: _webhookSettings:
createWebhook: "สร้าง Webhook"
name: "ชื่อ" name: "ชื่อ"
secret: "ความลับ"
events: "อีเว้นท์ Webhook"
active: "เปิดใช้งาน" active: "เปิดใช้งาน"
_events:
follow: "เมื่อกำลังติดตามผู้ใช้"
followed: "เมื่อกำลังติดตามแล้ว"
note: "เมื่อกำลังโพสต์โน้ต"
reply: "เมื่อได้รับการตอบกลับ"
renote: "รีโน้ตแล้วเมื่อ"
reaction: "เมื่อได้รับรีแอคชั่น"
mention: "เมื่อกำลังถูกกล่าวถึง"

View File

@ -991,6 +991,7 @@ largeNoteReactions: "使用大图标来显示回应"
noteIdOrUrl: "帖子ID或URL" noteIdOrUrl: "帖子ID或URL"
accountMigration: "账户迁移" accountMigration: "账户迁移"
accountMoved: "此用户已迁移账户" accountMoved: "此用户已迁移账户"
forceShowAds: "总是显示广告"
_accountMigration: _accountMigration:
moveTo: "把这个账户迁移到新的账户" moveTo: "把这个账户迁移到新的账户"
moveToLabel: "迁移后的账户" moveToLabel: "迁移后的账户"

View File

@ -985,9 +985,15 @@ showClipButtonInNoteFooter: "將摘錄添加至貼文"
largeNoteReactions: "將貼文的反應放大顯示" largeNoteReactions: "將貼文的反應放大顯示"
noteIdOrUrl: "貼文ID或URL" noteIdOrUrl: "貼文ID或URL"
accountMigration: "遷移帳戶" accountMigration: "遷移帳戶"
forceShowAds: "總是顯示廣告"
_accountMigration: _accountMigration:
moveTo: "將這個帳戶遷移至新的帳戶" moveTo: "將這個帳戶遷移至新的帳戶"
moveToLabel: "要遷移的帳戶:" moveToLabel: "要遷移到的帳戶:"
moveAccountDescription: "這個操作不可撤銷。首先,請確認已在要遷移到的帳戶中為這個帳戶建立了一個別名。建立別名之後,像這樣輸入你要遷移到的帳戶:@person@instance.com"
moveFrom: "從其他帳戶遷移到這個帳戶"
moveFromLabel: "要遷移過來的帳戶:"
moveFromDescription: "如果你想把跟隨者從別的帳戶遷移過來,必須先在這裡建立別名。請務必在執行遷移之前建立別名!請像這樣輸入要遷移的帳戶:@person@instance.com"
migrationConfirm: "確定要將這個帳戶遷移至 {account} 嗎?一旦遷移就無法撤銷,也就無法以原來的狀態使用這個帳戶。\n另外請確認在要遷移到的帳戶已經建立了一個別名。"
_achievements: _achievements:
earnedAt: "獲得日期" earnedAt: "獲得日期"
_types: _types:

View File

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "13.11.1", "version": "13.11.2",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -37,8 +37,24 @@ const $redis: Provider = {
inject: [DI.config], inject: [DI.config],
}; };
const $redisForPubsub: Provider = { const $redisForPub: Provider = {
provide: DI.redisForPubsub, provide: DI.redisForPub,
useFactory: (config) => {
const redis = new Redis({
port: config.redisForPubsub.port,
host: config.redisForPubsub.host,
family: config.redisForPubsub.family == null ? 0 : config.redisForPubsub.family,
password: config.redisForPubsub.pass,
keyPrefix: `${config.redisForPubsub.prefix}:`,
db: config.redisForPubsub.db ?? 0,
});
return redis;
},
inject: [DI.config],
};
const $redisForSub: Provider = {
provide: DI.redisForSub,
useFactory: (config) => { useFactory: (config) => {
const redis = new Redis({ const redis = new Redis({
port: config.redisForPubsub.port, port: config.redisForPubsub.port,
@ -57,14 +73,15 @@ const $redisForPubsub: Provider = {
@Global() @Global()
@Module({ @Module({
imports: [RepositoryModule], imports: [RepositoryModule],
providers: [$config, $db, $redis, $redisForPubsub], providers: [$config, $db, $redis, $redisForPub, $redisForSub],
exports: [$config, $db, $redis, $redisForPubsub, RepositoryModule], exports: [$config, $db, $redis, $redisForPub, $redisForSub, RepositoryModule],
}) })
export class GlobalModule implements OnApplicationShutdown { export class GlobalModule implements OnApplicationShutdown {
constructor( constructor(
@Inject(DI.db) private db: DataSource, @Inject(DI.db) private db: DataSource,
@Inject(DI.redis) private redisClient: Redis.Redis, @Inject(DI.redis) private redisClient: Redis.Redis,
@Inject(DI.redisForPubsub) private redisForPubsub: Redis.Redis, @Inject(DI.redisForPub) private redisForPub: Redis.Redis,
@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
) {} ) {}
async onApplicationShutdown(signal: string): Promise<void> { async onApplicationShutdown(signal: string): Promise<void> {
@ -79,7 +96,8 @@ export class GlobalModule implements OnApplicationShutdown {
await Promise.all([ await Promise.all([
this.db.destroy(), this.db.destroy(),
this.redisClient.disconnect(), this.redisClient.disconnect(),
this.redisForPubsub.disconnect(), this.redisForPub.disconnect(),
this.redisForSub.disconnect(),
]); ]);
} }
} }

View File

@ -27,8 +27,8 @@ export class AntennaService implements OnApplicationShutdown {
@Inject(DI.redis) @Inject(DI.redis)
private redisClient: Redis.Redis, private redisClient: Redis.Redis,
@Inject(DI.redisForPubsub) @Inject(DI.redisForSub)
private redisForPubsub: Redis.Redis, private redisForSub: Redis.Redis,
@Inject(DI.mutingsRepository) @Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository, private mutingsRepository: MutingsRepository,
@ -52,12 +52,12 @@ export class AntennaService implements OnApplicationShutdown {
this.antennasFetched = false; this.antennasFetched = false;
this.antennas = []; this.antennas = [];
this.redisForPubsub.on('message', this.onRedisMessage); this.redisForSub.on('message', this.onRedisMessage);
} }
@bindThis @bindThis
public onApplicationShutdown(signal?: string | undefined) { public onApplicationShutdown(signal?: string | undefined) {
this.redisForPubsub.off('message', this.onRedisMessage); this.redisForSub.off('message', this.onRedisMessage);
} }
@bindThis @bindThis
@ -95,7 +95,7 @@ export class AntennaService implements OnApplicationShutdown {
this.redisClient.xadd( this.redisClient.xadd(
`antennaTimeline:${antenna.id}`, `antennaTimeline:${antenna.id}`,
'MAXLEN', '~', '200', 'MAXLEN', '~', '200',
`${this.idService.parse(note.id).date.getTime()}-*`, '*',
'note', note.id); 'note', note.id);
this.globalEventService.publishAntennaStream(antenna.id, 'note', note); this.globalEventService.publishAntennaStream(antenna.id, 'note', note);

View File

@ -27,8 +27,8 @@ export class CacheService implements OnApplicationShutdown {
@Inject(DI.redis) @Inject(DI.redis)
private redisClient: Redis.Redis, private redisClient: Redis.Redis,
@Inject(DI.redisForPubsub) @Inject(DI.redisForSub)
private redisForPubsub: Redis.Redis, private redisForSub: Redis.Redis,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -116,7 +116,7 @@ export class CacheService implements OnApplicationShutdown {
fromRedisConverter: (value) => new Set(JSON.parse(value)), fromRedisConverter: (value) => new Set(JSON.parse(value)),
}); });
this.redisForPubsub.on('message', this.onMessage); this.redisForSub.on('message', this.onMessage);
} }
@bindThis @bindThis
@ -167,6 +167,6 @@ export class CacheService implements OnApplicationShutdown {
@bindThis @bindThis
public onApplicationShutdown(signal?: string | undefined) { public onApplicationShutdown(signal?: string | undefined) {
this.redisForPubsub.off('message', this.onMessage); this.redisForSub.off('message', this.onMessage);
} }
} }

View File

@ -43,12 +43,8 @@ export class CustomEmojiService {
lifetime: 1000 * 60 * 30, // 30m lifetime: 1000 * 60 * 30, // 30m
memoryCacheLifetime: 1000 * 60 * 3, // 3m memoryCacheLifetime: 1000 * 60 * 3, // 3m
fetcher: () => this.emojisRepository.find({ where: { host: IsNull() } }).then(emojis => new Map(emojis.map(emoji => [emoji.name, emoji]))), fetcher: () => this.emojisRepository.find({ where: { host: IsNull() } }).then(emojis => new Map(emojis.map(emoji => [emoji.name, emoji]))),
toRedisConverter: (value) => JSON.stringify(value.values()), toRedisConverter: (value) => JSON.stringify(Array.from(value.values())),
fromRedisConverter: (value) => { fromRedisConverter: (value) => new Map(JSON.parse(value).map((x: Emoji) => [x.name, x])), // TODO: Date型の変換
// 原因不明だが配列以外が入ってくることがあるため
if (!Array.isArray(JSON.parse(value))) return undefined;
return new Map(JSON.parse(value).map((x: Emoji) => [x.name, x]));
}, // TODO: Date型の変換
}); });
} }

View File

@ -26,8 +26,8 @@ export class GlobalEventService {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.redis) @Inject(DI.redisForPub)
private redisClient: Redis.Redis, private redisForPub: Redis.Redis,
) { ) {
} }
@ -37,7 +37,7 @@ export class GlobalEventService {
{ type: type, body: null } : { type: type, body: null } :
{ type: type, body: value }; { type: type, body: value };
this.redisClient.publish(this.config.host, JSON.stringify({ this.redisForPub.publish(this.config.host, JSON.stringify({
channel: channel, channel: channel,
message: message, message: message,
})); }));

View File

@ -14,8 +14,8 @@ export class MetaService implements OnApplicationShutdown {
private intervalId: NodeJS.Timer; private intervalId: NodeJS.Timer;
constructor( constructor(
@Inject(DI.redisForPubsub) @Inject(DI.redisForSub)
private redisForPubsub: Redis.Redis, private redisForSub: Redis.Redis,
@Inject(DI.db) @Inject(DI.db)
private db: DataSource, private db: DataSource,
@ -33,7 +33,7 @@ export class MetaService implements OnApplicationShutdown {
}, 1000 * 60 * 5); }, 1000 * 60 * 5);
} }
this.redisForPubsub.on('message', this.onMessage); this.redisForSub.on('message', this.onMessage);
} }
@bindThis @bindThis
@ -122,6 +122,6 @@ export class MetaService implements OnApplicationShutdown {
@bindThis @bindThis
public onApplicationShutdown(signal?: string | undefined) { public onApplicationShutdown(signal?: string | undefined) {
clearInterval(this.intervalId); clearInterval(this.intervalId);
this.redisForPubsub.off('message', this.onMessage); this.redisForSub.off('message', this.onMessage);
} }
} }

View File

@ -329,7 +329,7 @@ export class NoteCreateService implements OnApplicationShutdown {
this.redisClient.xadd( this.redisClient.xadd(
`channelTimeline:${data.channel.id}`, `channelTimeline:${data.channel.id}`,
'MAXLEN', '~', '1000', 'MAXLEN', '~', '1000',
`${this.idService.parse(note.id).date.getTime()}-*`, '*',
'note', note.id); 'note', note.id);
} }

View File

@ -66,6 +66,7 @@ export class NotificationService implements OnApplicationShutdown {
@bindThis @bindThis
private postReadAllNotifications(userId: User['id']) { private postReadAllNotifications(userId: User['id']) {
this.globalEventService.publishMainStream(userId, 'readAllNotifications'); this.globalEventService.publishMainStream(userId, 'readAllNotifications');
this.pushNotificationService.pushNotification(userId, 'readAllNotifications', undefined);
} }
@bindThis @bindThis
@ -99,7 +100,7 @@ export class NotificationService implements OnApplicationShutdown {
const redisIdPromise = this.redisClient.xadd( const redisIdPromise = this.redisClient.xadd(
`notificationTimeline:${notifieeId}`, `notificationTimeline:${notifieeId}`,
'MAXLEN', '~', '300', 'MAXLEN', '~', '300',
`${this.idService.parse(notification.id).date.getTime()}-*`, '*',
'data', JSON.stringify(notification)); 'data', JSON.stringify(notification));
const packed = await this.notificationEntityService.pack(notification, notifieeId, {}); const packed = await this.notificationEntityService.pack(notification, notifieeId, {});

View File

@ -1,12 +1,14 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import push from 'web-push'; import push from 'web-push';
import Redis from 'ioredis';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { Packed } from '@/misc/json-schema'; import type { Packed } from '@/misc/json-schema';
import { getNoteSummary } from '@/misc/get-note-summary.js'; import { getNoteSummary } from '@/misc/get-note-summary.js';
import type { SwSubscriptionsRepository } from '@/models/index.js'; import type { SwSubscription, SwSubscriptionsRepository } from '@/models/index.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { RedisKVCache } from '@/misc/cache.js';
// Defined also packages/sw/types.ts#L13 // Defined also packages/sw/types.ts#L13
type PushNotificationsTypes = { type PushNotificationsTypes = {
@ -15,6 +17,7 @@ type PushNotificationsTypes = {
antenna: { id: string, name: string }; antenna: { id: string, name: string };
note: Packed<'Note'>; note: Packed<'Note'>;
}; };
'readAllNotifications': undefined;
}; };
// Reduce length because push message servers have character limits // Reduce length because push message servers have character limits
@ -40,15 +43,27 @@ function truncateBody<T extends keyof PushNotificationsTypes>(type: T, body: Pus
@Injectable() @Injectable()
export class PushNotificationService { export class PushNotificationService {
private subscriptionsCache: RedisKVCache<SwSubscription[]>;
constructor( constructor(
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.swSubscriptionsRepository) @Inject(DI.swSubscriptionsRepository)
private swSubscriptionsRepository: SwSubscriptionsRepository, private swSubscriptionsRepository: SwSubscriptionsRepository,
private metaService: MetaService, private metaService: MetaService,
) { ) {
this.subscriptionsCache = new RedisKVCache<SwSubscription[]>(this.redisClient, 'userSwSubscriptions', {
lifetime: 1000 * 60 * 60 * 1, // 1h
memoryCacheLifetime: 1000 * 60 * 3, // 3m
fetcher: (key) => this.swSubscriptionsRepository.findBy({ userId: key }),
toRedisConverter: (value) => JSON.stringify(value),
fromRedisConverter: (value) => JSON.parse(value),
});
} }
@bindThis @bindThis
@ -62,12 +77,13 @@ export class PushNotificationService {
meta.swPublicKey, meta.swPublicKey,
meta.swPrivateKey); meta.swPrivateKey);
// Fetch const subscriptions = await this.subscriptionsCache.fetch(userId);
const subscriptions = await this.swSubscriptionsRepository.findBy({
userId: userId,
});
for (const subscription of subscriptions) { for (const subscription of subscriptions) {
if ([
'readAllNotifications',
].includes(type) && !subscription.sendReadMessage) continue;
const pushSubscription = { const pushSubscription = {
endpoint: subscription.endpoint, endpoint: subscription.endpoint,
keys: { keys: {

View File

@ -64,8 +64,8 @@ export class RoleService implements OnApplicationShutdown {
public static NotAssignedError = class extends Error {}; public static NotAssignedError = class extends Error {};
constructor( constructor(
@Inject(DI.redisForPubsub) @Inject(DI.redisForSub)
private redisForPubsub: Redis.Redis, private redisForSub: Redis.Redis,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -87,7 +87,7 @@ export class RoleService implements OnApplicationShutdown {
this.rolesCache = new MemorySingleCache<Role[]>(1000 * 60 * 60 * 1); this.rolesCache = new MemorySingleCache<Role[]>(1000 * 60 * 60 * 1);
this.roleAssignmentByUserIdCache = new MemoryKVCache<RoleAssignment[]>(1000 * 60 * 60 * 1); this.roleAssignmentByUserIdCache = new MemoryKVCache<RoleAssignment[]>(1000 * 60 * 60 * 1);
this.redisForPubsub.on('message', this.onMessage); this.redisForSub.on('message', this.onMessage);
} }
@bindThis @bindThis
@ -400,6 +400,6 @@ export class RoleService implements OnApplicationShutdown {
@bindThis @bindThis
public onApplicationShutdown(signal?: string | undefined) { public onApplicationShutdown(signal?: string | undefined) {
this.redisForPubsub.off('message', this.onMessage); this.redisForSub.off('message', this.onMessage);
} }
} }

View File

@ -13,14 +13,14 @@ export class WebhookService implements OnApplicationShutdown {
private webhooks: Webhook[] = []; private webhooks: Webhook[] = [];
constructor( constructor(
@Inject(DI.redisForPubsub) @Inject(DI.redisForSub)
private redisForPubsub: Redis.Redis, private redisForSub: Redis.Redis,
@Inject(DI.webhooksRepository) @Inject(DI.webhooksRepository)
private webhooksRepository: WebhooksRepository, private webhooksRepository: WebhooksRepository,
) { ) {
//this.onMessage = this.onMessage.bind(this); //this.onMessage = this.onMessage.bind(this);
this.redisForPubsub.on('message', this.onMessage); this.redisForSub.on('message', this.onMessage);
} }
@bindThis @bindThis
@ -82,6 +82,6 @@ export class WebhookService implements OnApplicationShutdown {
@bindThis @bindThis
public onApplicationShutdown(signal?: string | undefined) { public onApplicationShutdown(signal?: string | undefined) {
this.redisForPubsub.off('message', this.onMessage); this.redisForSub.off('message', this.onMessage);
} }
} }

View File

@ -2,7 +2,8 @@ export const DI = {
config: Symbol('config'), config: Symbol('config'),
db: Symbol('db'), db: Symbol('db'),
redis: Symbol('redis'), redis: Symbol('redis'),
redisForPubsub: Symbol('redisForPubsub'), redisForPub: Symbol('redisForPub'),
redisForSub: Symbol('redisForSub'),
//#region Repositories //#region Repositories
usersRepository: Symbol('usersRepository'), usersRepository: Symbol('usersRepository'),

View File

@ -8,7 +8,7 @@ export class RedisKVCache<T> {
private memoryCache: MemoryKVCache<T>; private memoryCache: MemoryKVCache<T>;
private fetcher: (key: string) => Promise<T>; private fetcher: (key: string) => Promise<T>;
private toRedisConverter: (value: T) => string; private toRedisConverter: (value: T) => string;
private fromRedisConverter: (value: string) => T | undefined; // undefined means no cache private fromRedisConverter: (value: string) => T;
constructor(redisClient: RedisKVCache<T>['redisClient'], name: RedisKVCache<T>['name'], opts: { constructor(redisClient: RedisKVCache<T>['redisClient'], name: RedisKVCache<T>['name'], opts: {
lifetime: RedisKVCache<T>['lifetime']; lifetime: RedisKVCache<T>['lifetime'];
@ -92,7 +92,7 @@ export class RedisSingleCache<T> {
private memoryCache: MemorySingleCache<T>; private memoryCache: MemorySingleCache<T>;
private fetcher: () => Promise<T>; private fetcher: () => Promise<T>;
private toRedisConverter: (value: T) => string; private toRedisConverter: (value: T) => string;
private fromRedisConverter: (value: string) => T | undefined; // undefined means no cache private fromRedisConverter: (value: string) => T;
constructor(redisClient: RedisSingleCache<T>['redisClient'], name: RedisSingleCache<T>['name'], opts: { constructor(redisClient: RedisSingleCache<T>['redisClient'], name: RedisSingleCache<T>['name'], opts: {
lifetime: RedisSingleCache<T>['lifetime']; lifetime: RedisSingleCache<T>['lifetime'];

View File

@ -89,7 +89,7 @@ export class ApiServerService {
Params: { endpoint: string; }, Params: { endpoint: string; },
Body: Record<string, unknown>, Body: Record<string, unknown>,
Querystring: Record<string, unknown>, Querystring: Record<string, unknown>,
}>('/' + endpoint.name, { bodyLimit: 1024 * 32 }, async (request, reply) => { }>('/' + endpoint.name, { bodyLimit: 1024 * 1024 }, async (request, reply) => {
if (request.method === 'GET' && !endpoint.meta.allowGet) { if (request.method === 'GET' && !endpoint.meta.allowGet) {
reply.code(405); reply.code(405);
reply.send(); reply.send();

View File

@ -98,6 +98,7 @@ import * as ep___channels_update from './endpoints/channels/update.js';
import * as ep___channels_favorite from './endpoints/channels/favorite.js'; import * as ep___channels_favorite from './endpoints/channels/favorite.js';
import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js'; import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js';
import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js'; import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js';
import * as ep___channels_search from './endpoints/channels/search.js';
import * as ep___charts_activeUsers from './endpoints/charts/active-users.js'; import * as ep___charts_activeUsers from './endpoints/charts/active-users.js';
import * as ep___charts_apRequest from './endpoints/charts/ap-request.js'; import * as ep___charts_apRequest from './endpoints/charts/ap-request.js';
import * as ep___charts_drive from './endpoints/charts/drive.js'; import * as ep___charts_drive from './endpoints/charts/drive.js';
@ -431,6 +432,7 @@ const $channels_update: Provider = { provide: 'ep:channels/update', useClass: ep
const $channels_favorite: Provider = { provide: 'ep:channels/favorite', useClass: ep___channels_favorite.default }; const $channels_favorite: Provider = { provide: 'ep:channels/favorite', useClass: ep___channels_favorite.default };
const $channels_unfavorite: Provider = { provide: 'ep:channels/unfavorite', useClass: ep___channels_unfavorite.default }; const $channels_unfavorite: Provider = { provide: 'ep:channels/unfavorite', useClass: ep___channels_unfavorite.default };
const $channels_myFavorites: Provider = { provide: 'ep:channels/my-favorites', useClass: ep___channels_myFavorites.default }; const $channels_myFavorites: Provider = { provide: 'ep:channels/my-favorites', useClass: ep___channels_myFavorites.default };
const $channels_search: Provider = { provide: 'ep:channels/search', useClass: ep___channels_search.default };
const $charts_activeUsers: Provider = { provide: 'ep:charts/active-users', useClass: ep___charts_activeUsers.default }; const $charts_activeUsers: Provider = { provide: 'ep:charts/active-users', useClass: ep___charts_activeUsers.default };
const $charts_apRequest: Provider = { provide: 'ep:charts/ap-request', useClass: ep___charts_apRequest.default }; const $charts_apRequest: Provider = { provide: 'ep:charts/ap-request', useClass: ep___charts_apRequest.default };
const $charts_drive: Provider = { provide: 'ep:charts/drive', useClass: ep___charts_drive.default }; const $charts_drive: Provider = { provide: 'ep:charts/drive', useClass: ep___charts_drive.default };
@ -768,6 +770,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$channels_favorite, $channels_favorite,
$channels_unfavorite, $channels_unfavorite,
$channels_myFavorites, $channels_myFavorites,
$channels_search,
$charts_activeUsers, $charts_activeUsers,
$charts_apRequest, $charts_apRequest,
$charts_drive, $charts_drive,
@ -1099,6 +1102,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$channels_favorite, $channels_favorite,
$channels_unfavorite, $channels_unfavorite,
$channels_myFavorites, $channels_myFavorites,
$channels_search,
$charts_activeUsers, $charts_activeUsers,
$charts_apRequest, $charts_apRequest,
$charts_drive, $charts_drive,

View File

@ -22,8 +22,8 @@ export class StreamingApiServerService {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.redisForPubsub) @Inject(DI.redisForSub)
private redisForPubsub: Redis.Redis, private redisForSub: Redis.Redis,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -81,7 +81,7 @@ export class StreamingApiServerService {
ev.emit(parsed.channel, parsed.message); ev.emit(parsed.channel, parsed.message);
} }
this.redisForPubsub.on('message', onRedisMessage); this.redisForSub.on('message', onRedisMessage);
const main = new MainStreamConnection( const main = new MainStreamConnection(
this.channelsService, this.channelsService,
@ -111,7 +111,7 @@ export class StreamingApiServerService {
connection.once('close', () => { connection.once('close', () => {
ev.removeAllListeners(); ev.removeAllListeners();
main.dispose(); main.dispose();
this.redisForPubsub.off('message', onRedisMessage); this.redisForSub.off('message', onRedisMessage);
if (intervalId) clearInterval(intervalId); if (intervalId) clearInterval(intervalId);
}); });

View File

@ -98,6 +98,7 @@ import * as ep___channels_update from './endpoints/channels/update.js';
import * as ep___channels_favorite from './endpoints/channels/favorite.js'; import * as ep___channels_favorite from './endpoints/channels/favorite.js';
import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js'; import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js';
import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js'; import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js';
import * as ep___channels_search from './endpoints/channels/search.js';
import * as ep___charts_activeUsers from './endpoints/charts/active-users.js'; import * as ep___charts_activeUsers from './endpoints/charts/active-users.js';
import * as ep___charts_apRequest from './endpoints/charts/ap-request.js'; import * as ep___charts_apRequest from './endpoints/charts/ap-request.js';
import * as ep___charts_drive from './endpoints/charts/drive.js'; import * as ep___charts_drive from './endpoints/charts/drive.js';
@ -429,6 +430,7 @@ const eps = [
['channels/favorite', ep___channels_favorite], ['channels/favorite', ep___channels_favorite],
['channels/unfavorite', ep___channels_unfavorite], ['channels/unfavorite', ep___channels_unfavorite],
['channels/my-favorites', ep___channels_myFavorites], ['channels/my-favorites', ep___channels_myFavorites],
['channels/search', ep___channels_search],
['charts/active-users', ep___charts_activeUsers], ['charts/active-users', ep___charts_activeUsers],
['charts/ap-request', ep___charts_apRequest], ['charts/ap-request', ep___charts_apRequest],
['charts/drive', ep___charts_drive], ['charts/drive', ep___charts_drive],

View File

@ -0,0 +1,67 @@
import { Inject, Injectable } from '@nestjs/common';
import { Brackets } from 'typeorm';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueryService } from '@/core/QueryService.js';
import type { ChannelsRepository } from '@/models/index.js';
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
import { DI } from '@/di-symbols.js';
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
export const meta = {
tags: ['channels'],
requireCredential: false,
res: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'Channel',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
query: { type: 'string' },
type: { type: 'string', enum: ['nameAndDescription', 'nameOnly'], default: 'nameAndDescription' },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 },
},
required: ['query'],
} as const;
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.channelsRepository)
private channelsRepository: ChannelsRepository,
private channelEntityService: ChannelEntityService,
private queryService: QueryService,
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId);
if (ps.type === 'nameAndDescription') {
query.andWhere(new Brackets(qb => { qb
.where('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` })
.orWhere('channel.description ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
}));
} else {
query.andWhere('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
}
const channels = await query
.take(ps.limit)
.getMany();
return await Promise.all(channels.map(x => this.channelEntityService.pack(x, me)));
});
}
}

View File

@ -54,10 +54,10 @@ class LocalTimelineChannel extends Channel {
} }
// 関係ない返信は除外 // 関係ない返信は除外
if (note.reply && !this.user!.showTimelineReplies) { if (note.reply && this.user && !this.user.showTimelineReplies) {
const reply = note.reply; const reply = note.reply;
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合 // 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return; if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
} }
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する

View File

@ -77,6 +77,7 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host = 'mi
createdAt: '2016-12-28T22:49:51.000Z', createdAt: '2016-12-28T22:49:51.000Z',
description: 'I am a cool user!', description: 'I am a cool user!',
ffVisibility: 'public', ffVisibility: 'public',
roles: [],
fields: [ fields: [
{ {
name: 'Website', name: 'Website',

View File

@ -398,6 +398,7 @@ function toStories(component: string): string {
Promise.all([ Promise.all([
glob('src/components/global/*.vue'), glob('src/components/global/*.vue'),
glob('src/components/MkGalleryPostPreview.vue'), glob('src/components/MkGalleryPostPreview.vue'),
glob('src/pages/user/home.vue'),
]) ])
.then((globs) => globs.flat()) .then((globs) => globs.flat())
.then((components) => Promise.all(components.map((component) => { .then((components) => Promise.all(components.map((component) => {

View File

@ -0,0 +1,31 @@
<template>
<MkPagination :pagination="pagination">
<template #empty>
<div class="_fullinfo">
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
<div>{{ i18n.ts.notFound }}</div>
</div>
</template>
<template #default="{ items }">
<MkChannelPreview v-for="item in items" :key="item.id" class="_margin" :channel="extractor(item)"/>
</template>
</MkPagination>
</template>
<script lang="ts" setup>
import MkChannelPreview from '@/components/MkChannelPreview.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import { i18n } from '@/i18n';
const props = withDefaults(defineProps<{
pagination: Paging;
noGap?: boolean;
extractor?: (item: any) => any;
}>(), {
extractor: (item) => item,
});
</script>
<style lang="scss" scoped>
</style>

View File

@ -82,6 +82,7 @@ export default defineComponent({
omitted: null, omitted: null,
ignoreOmit: false, ignoreOmit: false,
defaultStore, defaultStore,
i18n,
}; };
}, },
mounted() { mounted() {

View File

@ -439,7 +439,6 @@ defineExpose({
&.asDrawer { &.asDrawer {
width: 100% !important; width: 100% !important;
padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0;
> .emojis { > .emojis {
::v-deep(section) { ::v-deep(section) {
@ -498,6 +497,10 @@ defineExpose({
background: transparent; background: transparent;
color: var(--fg); color: var(--fg);
&:not(:focus):not(.filled) {
margin-bottom: env(safe-area-inset-bottom, 0px);
}
&:not(.filled) { &:not(.filled) {
order: 1; order: 1;
z-index: 2; z-index: 2;

View File

@ -1124,16 +1124,16 @@ defineExpose({
display: grid; display: grid;
grid-auto-flow: row; grid-auto-flow: row;
grid-template-columns: repeat(auto-fill, minmax(42px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(42px, 1fr));
grid-auto-rows: 46px; grid-auto-rows: 40px;
} }
.footerRight { .footerRight {
flex: 0.3; flex: 0;
margin-left: auto; margin-left: auto;
display: grid; display: grid;
grid-auto-flow: row; grid-auto-flow: row;
grid-template-columns: repeat(auto-fill, minmax(42px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(42px, 1fr));
grid-auto-rows: 46px; grid-auto-rows: 40px;
direction: rtl; direction: rtl;
} }
@ -1198,13 +1198,21 @@ defineExpose({
} }
} }
@container (max-width: 330px) { @container (max-width: 350px) {
.footer {
font-size: 0.9em;
}
.footerLeft {
grid-template-columns: repeat(auto-fill, minmax(38px, 1fr));
}
.footerRight {
grid-template-columns: repeat(auto-fill, minmax(38px, 1fr));
}
.headerRight { .headerRight {
gap: 0; gap: 0;
} }
.footer {
font-size: 14px;
}
} }
</style> </style>

View File

@ -83,7 +83,7 @@ const choseAd = (): Ad | null => {
}; };
const chosen = ref(choseAd()); const chosen = ref(choseAd());
const shouldHide = $ref($i && $i.policies.canHideAds && (props.specify == null)); const shouldHide = $ref(!defaultStore.state.forceShowAds && $i && $i.policies.canHideAds && (props.specify == null));
function reduceFrequency(): void { function reduceFrequency(): void {
if (chosen.value == null) return; if (chosen.value == null) return;

View File

@ -2,6 +2,23 @@
<MkStickyContainer> <MkStickyContainer>
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :content-max="700"> <MkSpacer :content-max="700">
<div v-if="tab === 'search'">
<div class="_gaps">
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search">
<template #prefix><i class="ti ti-search"></i></template>
</MkInput>
<MkRadios v-model="searchType" @update:model-value="search()">
<option value="nameAndDescription">{{ i18n.ts._channel.nameAndDescription }}</option>
<option value="nameOnly">{{ i18n.ts._channel.nameOnly }}</option>
</MkRadios>
<MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton>
</div>
<MkFoldableSection v-if="channelPagination">
<template #header>{{ i18n.ts.searchResult }}</template>
<MkChannelList :key="key" :pagination="channelPagination"/>
</MkFoldableSection>
</div>
<div v-if="tab === 'featured'"> <div v-if="tab === 'featured'">
<MkPagination v-slot="{items}" :pagination="featuredPagination"> <MkPagination v-slot="{items}" :pagination="featuredPagination">
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/> <MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
@ -28,17 +45,35 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed, onMounted } from 'vue';
import MkChannelPreview from '@/components/MkChannelPreview.vue'; import MkChannelPreview from '@/components/MkChannelPreview.vue';
import MkChannelList from '@/components/MkChannelList.vue';
import MkPagination from '@/components/MkPagination.vue'; import MkPagination from '@/components/MkPagination.vue';
import MkInput from '@/components/MkInput.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import { useRouter } from '@/router'; import { useRouter } from '@/router';
import { definePageMetadata } from '@/scripts/page-metadata'; import { definePageMetadata } from '@/scripts/page-metadata';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
const router = useRouter(); const router = useRouter();
let tab = $ref('featured'); const props = defineProps<{
query: string;
type?: string;
}>();
let key = $ref('');
let tab = $ref('search');
let searchQuery = $ref('');
let searchType = $ref('nameAndDescription');
let channelPagination = $ref();
onMounted(() => {
searchQuery = props.query ?? '';
searchType = props.type ?? 'nameAndDescription';
});
const featuredPagination = { const featuredPagination = {
endpoint: 'channels/featured' as const, endpoint: 'channels/featured' as const,
@ -58,6 +93,25 @@ const ownedPagination = {
limit: 10, limit: 10,
}; };
async function search() {
const query = searchQuery.toString().trim();
if (query == null || query === '') return;
const type = searchType.toString().trim();
channelPagination = {
endpoint: 'channels/search',
limit: 10,
params: {
query: searchQuery,
type: type,
},
};
key = query + type;
}
function create() { function create() {
router.push('/channels/new'); router.push('/channels/new');
} }
@ -69,6 +123,10 @@ const headerActions = $computed(() => [{
}]); }]);
const headerTabs = $computed(() => [{ const headerTabs = $computed(() => [{
key: 'search',
title: i18n.ts.search,
icon: 'ti ti-search',
}, {
key: 'featured', key: 'featured',
title: i18n.ts._channel.featured, title: i18n.ts._channel.featured,
icon: 'ti ti-comet', icon: 'ti ti-comet',

View File

@ -66,7 +66,7 @@ const recentPostsPagination = {
}; };
const popularPostsPagination = { const popularPostsPagination = {
endpoint: 'gallery/featured' as const, endpoint: 'gallery/featured' as const,
limit: 5, noPaging: true,
}; };
const myPostsPagination = { const myPostsPagination = {
endpoint: 'i/gallery/posts' as const, endpoint: 'i/gallery/posts' as const,

View File

@ -8,6 +8,7 @@
</div> </div>
</template> </template>
<template #default="{items}"> <template #default="{items}">
<div class="_gaps">
<div v-for="token in items" :key="token.id" class="_panel bfomjevm"> <div v-for="token in items" :key="token.id" class="_panel bfomjevm">
<img v-if="token.iconUrl" class="icon" :src="token.iconUrl" alt=""/> <img v-if="token.iconUrl" class="icon" :src="token.iconUrl" alt=""/>
<div class="body"> <div class="body">
@ -32,6 +33,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
</FormPagination> </FormPagination>
</div> </div>
@ -51,6 +53,7 @@ const list = ref<any>(null);
const pagination = { const pagination = {
endpoint: 'i/apps' as const, endpoint: 'i/apps' as const,
limit: 100, limit: 100,
noPaging: true,
params: { params: {
sort: '+lastUsedAt', sort: '+lastUsedAt',
}, },

View File

@ -61,6 +61,7 @@
<MkSwitch v-model="squareAvatars">{{ i18n.ts.squareAvatars }}</MkSwitch> <MkSwitch v-model="squareAvatars">{{ i18n.ts.squareAvatars }}</MkSwitch>
<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch> <MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch> <MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
</div> </div>
<div> <div>
<MkRadios v-model="emojiStyle"> <MkRadios v-model="emojiStyle">
@ -157,6 +158,7 @@ const advancedMfm = computed(defaultStore.makeGetterSetter('advancedMfm'));
const emojiStyle = computed(defaultStore.makeGetterSetter('emojiStyle')); const emojiStyle = computed(defaultStore.makeGetterSetter('emojiStyle'));
const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer')); const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer'));
const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages')); const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages'));
const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages')); const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab')); const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
const nsfw = computed(defaultStore.makeGetterSetter('nsfw')); const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));

View File

@ -399,7 +399,7 @@ function menu(ev: MouseEvent, profileId: string) {
icon: 'ti ti-device-floppy', icon: 'ti ti-device-floppy',
action: () => save(profileId), action: () => save(profileId),
}, null, { }, null, {
text: ts._preferencesBackups.delete, text: ts.delete,
icon: 'ti ti-trash', icon: 'ti ti-trash',
action: () => deleteProfile(profileId), action: () => deleteProfile(profileId),
danger: true, danger: true,

View File

@ -7,7 +7,8 @@
<FormSection> <FormSection>
<MkPagination :pagination="pagination"> <MkPagination :pagination="pagination">
<template #default="{items}"> <template #default="{items}">
<FormLink v-for="webhook in items" :key="webhook.id" :to="`/settings/webhook/edit/${webhook.id}`" class="_margin"> <div class="_gaps">
<FormLink v-for="webhook in items" :key="webhook.id" :to="`/settings/webhook/edit/${webhook.id}`">
<template #icon> <template #icon>
<i v-if="webhook.active === false" class="ti ti-player-pause"></i> <i v-if="webhook.active === false" class="ti ti-player-pause"></i>
<i v-else-if="webhook.latestStatus === null" class="ti ti-circle"></i> <i v-else-if="webhook.latestStatus === null" class="ti ti-circle"></i>
@ -19,6 +20,7 @@
<MkTime v-if="webhook.latestSentAt" :time="webhook.latestSentAt"></MkTime> <MkTime v-if="webhook.latestSentAt" :time="webhook.latestSentAt"></MkTime>
</template> </template>
</FormLink> </FormLink>
</div>
</template> </template>
</MkPagination> </MkPagination>
</FormSection> </FormSection>
@ -35,7 +37,8 @@ import { i18n } from '@/i18n';
const pagination = { const pagination = {
endpoint: 'i/webhooks/list' as const, endpoint: 'i/webhooks/list' as const,
limit: 10, limit: 100,
noPaging: true,
}; };
const headerActions = $computed(() => []); const headerActions = $computed(() => []);

View File

@ -0,0 +1,74 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw';
import { userDetailed } from '../../../.storybook/fakes';
import { commonHandlers } from '../../../.storybook/mocks';
import home_ from './home.vue';
export const Default = {
render(args) {
return {
components: {
home_,
},
setup() {
return {
args,
};
},
computed: {
props() {
return {
...this.args,
};
},
},
template: '<home_ v-bind="props" />',
};
},
args: {
user: userDetailed(),
disableNotes: false,
},
parameters: {
layout: 'fullscreen',
msw: {
handlers: [
...commonHandlers,
rest.post('/api/users/notes', (req, res, ctx) => {
return res(ctx.json([]));
}),
rest.get('/api/charts/user/notes', (req, res, ctx) => {
const length = Math.max(Math.min(parseInt(req.url.searchParams.get('limit') ?? '30', 10), 1), 300);
return res(ctx.json({
total: Array.from({ length }, () => 0),
inc: Array.from({ length }, () => 0),
dec: Array.from({ length }, () => 0),
diffs: {
normal: Array.from({ length }, () => 0),
reply: Array.from({ length }, () => 0),
renote: Array.from({ length }, () => 0),
withFile: Array.from({ length }, () => 0),
},
}));
}),
rest.get('/api/charts/user/pv', (req, res, ctx) => {
const length = Math.max(Math.min(parseInt(req.url.searchParams.get('limit') ?? '30', 10), 1), 300);
return res(ctx.json({
upv: {
user: Array.from({ length }, () => 0),
visitor: Array.from({ length }, () => 0),
},
pv: {
user: Array.from({ length }, () => 0),
visitor: Array.from({ length }, () => 0),
},
}));
}),
],
},
chromatic: {
// `XActivity` is not compatible with Chromatic for now
disableSnapshot: true,
},
},
} satisfies StoryObj<typeof home_>;

View File

@ -298,6 +298,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device', where: 'device',
default: false, default: false,
}, },
forceShowAds: {
where: 'device',
default: false,
},
aiChanMode: { aiChanMode: {
where: 'device', where: 'device',
default: false, default: false,

View File

@ -1,17 +1,18 @@
import { post } from '@/os'; import { api, post } from '@/os';
import { $i, login } from '@/account'; import { $i, login } from '@/account';
import { getAccountFromId } from '@/scripts/get-account-from-id'; import { getAccountFromId } from '@/scripts/get-account-from-id';
import { mainRouter } from '@/router'; import { mainRouter } from '@/router';
import { deepClone } from '@/scripts/clone';
export function swInject() { export function swInject() {
navigator.serviceWorker.addEventListener('message', ev => { navigator.serviceWorker.addEventListener('message', async ev => {
if (_DEV_) { if (_DEV_) {
console.log('sw msg', ev.data); console.log('sw msg', ev.data);
} }
if (ev.data.type !== 'order') return; if (ev.data.type !== 'order') return;
if (ev.data.loginId !== $i?.id) { if (ev.data.loginId && ev.data.loginId !== $i?.id) {
return getAccountFromId(ev.data.loginId).then(account => { return getAccountFromId(ev.data.loginId).then(account => {
if (!account) return; if (!account) return;
return login(account.token, ev.data.url); return login(account.token, ev.data.url);
@ -19,8 +20,18 @@ export function swInject() {
} }
switch (ev.data.order) { switch (ev.data.order) {
case 'post': case 'post': {
return post(ev.data.options); const props = deepClone(ev.data.options);
// プッシュ通知から来たreply,renoteはtruncateBodyが通されているため、
// 完全なノートを取得しなおす
if (props.reply) {
props.reply = await api('notes/show', { noteId: props.reply.id });
}
if (props.renote) {
props.renote = await api('notes/show', { noteId: props.renote.id });
}
return post(props);
}
case 'push': case 'push':
if (mainRouter.currentRoute.value.path === ev.data.url) { if (mainRouter.currentRoute.value.path === ev.data.url) {
return window.scroll({ top: 0, behavior: 'smooth' }); return window.scroll({ top: 0, behavior: 'smooth' });

View File

@ -250,6 +250,7 @@ onMounted(() => {
> .widgets { > .widgets {
//--panelBorder: none; //--panelBorder: none;
width: 300px; width: 300px;
padding-bottom: calc(var(--margin) + env(safe-area-inset-bottom, 0px));
@media (max-width: $widgets-hide-threshold) { @media (max-width: $widgets-hide-threshold) {
display: none; display: none;
@ -304,7 +305,7 @@ onMounted(() => {
right: 0; right: 0;
z-index: 1001; z-index: 1001;
height: 100dvh; height: 100dvh;
padding: var(--margin); padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px));
box-sizing: border-box; box-sizing: border-box;
overflow: auto; overflow: auto;
background: var(--bg); background: var(--bg);

View File

@ -296,7 +296,7 @@ $widgets-hide-threshold: 1090px;
} }
.widgets { .widgets {
padding: 0 var(--margin); padding: 0 var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px));
border-left: solid 0.5px var(--divider); border-left: solid 0.5px var(--divider);
background: var(--bg); background: var(--bg);
@ -329,7 +329,7 @@ $widgets-hide-threshold: 1090px;
right: 0; right: 0;
z-index: 1001; z-index: 1001;
height: 100dvh; height: 100dvh;
padding: var(--margin) !important; padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px)) !important;
box-sizing: border-box; box-sizing: border-box;
overflow: auto; overflow: auto;
overscroll-behavior: contain; overscroll-behavior: contain;

View File

@ -3,7 +3,7 @@
<XWidgets :class="$style.widgets" :edit="editMode" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> <XWidgets :class="$style.widgets" :edit="editMode" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/>
<button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="ti ti-check"></i> {{ i18n.ts.editWidgetsExit }}</button> <button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="ti ti-check"></i> {{ i18n.ts.editWidgetsExit }}</button>
<button v-else class="_textButton mk-widget-edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ i18n.ts.editWidgets }}</button> <button v-else class="_textButton mk-widget-edit" :class="$style.edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ i18n.ts.editWidgets }}</button>
</div> </div>
</template> </template>
@ -91,4 +91,8 @@ function updateWidgets(thisWidgets) {
.widgets { .widgets {
width: 300px; width: 300px;
} }
.edit {
width: 100%;
}
</style> </style>

View File

@ -1,3 +1,5 @@
// @ts-check
const esbuild = require('esbuild'); const esbuild = require('esbuild');
const locales = require('../../locales'); const locales = require('../../locales');
const meta = require('../../package.json'); const meta = require('../../package.json');
@ -5,33 +7,36 @@ const watch = process.argv[2]?.includes('watch');
console.log('Starting SW building...'); console.log('Starting SW building...');
esbuild.build({ /** @type {esbuild.BuildOptions} */
entryPoints: [ `${__dirname}/src/sw.ts` ], const buildOptions = {
bundle: true,
format: 'esm',
treeShaking: true,
minify: process.env.NODE_ENV === 'production',
absWorkingDir: __dirname, absWorkingDir: __dirname,
bundle: true,
define: {
_DEV_: JSON.stringify(process.env.NODE_ENV !== 'production'),
_ENV_: JSON.stringify(process.env.NODE_ENV ?? ''), // `NODE_ENV`が`undefined`なとき`JSON.stringify`が`undefined`を返してエラーになってしまうので`??`を使っている
_LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])),
_PERF_PREFIX_: JSON.stringify('Misskey:'),
_VERSION_: JSON.stringify(meta.version),
},
entryPoints: [`${__dirname}/src/sw.ts`],
format: 'esm',
loader: {
'.ts': 'ts',
},
minify: process.env.NODE_ENV === 'production',
outbase: `${__dirname}/src`, outbase: `${__dirname}/src`,
outdir: `${__dirname}/../../built/_sw_dist_`, outdir: `${__dirname}/../../built/_sw_dist_`,
loader: { treeShaking: true,
'.ts': 'ts'
},
tsconfig: `${__dirname}/tsconfig.json`, tsconfig: `${__dirname}/tsconfig.json`,
define: { };
_VERSION_: JSON.stringify(meta.version),
_LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])), (async () => {
_ENV_: JSON.stringify(process.env.NODE_ENV), if (!watch) {
_DEV_: process.env.NODE_ENV !== 'production', await esbuild.build(buildOptions);
_PERF_PREFIX_: JSON.stringify('Misskey:'), console.log('done');
}, } else {
watch: watch ? { const context = await esbuild.context(buildOptions);
onRebuild(error, result) { await context.watch();
if (error) console.error('SW: watch build failed:', error); console.log('watching...');
else console.log('SW: watch build succeeded:', result); }
}, })();
} : false,
}).then(result => {
if (watch) console.log('watching...');
else console.log('done,', JSON.stringify(result));
});

View File

@ -9,7 +9,7 @@
"lint": "pnpm typecheck && pnpm eslint" "lint": "pnpm typecheck && pnpm eslint"
}, },
"dependencies": { "dependencies": {
"esbuild": "0.14.42", "esbuild": "0.17.15",
"idb-keyval": "6.2.0", "idb-keyval": "6.2.0",
"misskey-js": "workspace:*" "misskey-js": "workspace:*"
}, },

View File

@ -3,7 +3,7 @@
*/ */
import { swLang } from '@/scripts/lang'; import { swLang } from '@/scripts/lang';
import { cli } from '@/scripts/operations'; import { cli } from '@/scripts/operations';
import { badgeNames, pushNotificationDataMap } from '@/types'; import { BadgeNames, PushNotificationDataMap } from '@/types';
import getUserName from '@/scripts/get-user-name'; import getUserName from '@/scripts/get-user-name';
import { I18n } from '@/scripts/i18n'; import { I18n } from '@/scripts/i18n';
import { getAccountFromId } from '@/scripts/get-account-from-id'; import { getAccountFromId } from '@/scripts/get-account-from-id';
@ -16,16 +16,16 @@ const closeNotificationsByTags = async (tags: string[]) => {
} }
}; };
const iconUrl = (name: badgeNames) => `/static-assets/tabler-badges/${name}.png`; const iconUrl = (name: BadgeNames) => `/static-assets/tabler-badges/${name}.png`;
/* How to add a new badge: /* How to add a new badge:
* 1. Find the icon and download png from https://tabler-icons.io/ * 1. Find the icon and download png from https://tabler-icons.io/
* 2. vips resize ~/Downloads/icon-name.png vipswork.png 0.4; vips scRGB2BW vipswork.png ~/icon-name.png"[compression=9,strip]"; rm vipswork.png; * 2. vips resize ~/Downloads/icon-name.png vipswork.png 0.4; vips scRGB2BW vipswork.png ~/icon-name.png"[compression=9,strip]"; rm vipswork.png;
* 3. mv ~/icon-name.png ~/misskey/packages/backend/assets/tabler-badges/ * 3. mv ~/icon-name.png ~/misskey/packages/backend/assets/tabler-badges/
* 4. Add 'icon-name' to badgeNames * 4. Add 'icon-name' to BadgeNames
* 5. Add `badge: iconUrl('icon-name'),` * 5. Add `badge: iconUrl('icon-name'),`
*/ */
export async function createNotification<K extends keyof pushNotificationDataMap>(data: pushNotificationDataMap[K]) { export async function createNotification<K extends keyof PushNotificationDataMap>(data: PushNotificationDataMap[K]) {
const n = await composeNotification(data); const n = await composeNotification(data);
if (n) { if (n) {
@ -36,7 +36,7 @@ export async function createNotification<K extends keyof pushNotificationDataMap
} }
} }
async function composeNotification(data: pushNotificationDataMap[keyof pushNotificationDataMap]): Promise<[string, NotificationOptions] | null> { async function composeNotification(data: PushNotificationDataMap[keyof PushNotificationDataMap]): Promise<[string, NotificationOptions] | null> {
if (!swLang.i18n) swLang.fetchLocale(); if (!swLang.i18n) swLang.fetchLocale();
const i18n = await swLang.i18n as I18n<any>; const i18n = await swLang.i18n as I18n<any>;
const { t } = i18n; const { t } = i18n;
@ -168,14 +168,6 @@ async function composeNotification(data: pushNotificationDataMap[keyof pushNotif
}]; }];
} }
case 'pollEnded':
return [t('_notification.pollEnded'), {
body: data.body.note.text || '',
badge: iconUrl('chart-arrows'),
tag: `poll:${data.body.note.id}`,
data,
}];
case 'receiveFollowRequest': case 'receiveFollowRequest':
return [t('_notification.youReceivedFollowRequest'), { return [t('_notification.youReceivedFollowRequest'), {
body: getUserName(data.body.user), body: getUserName(data.body.user),
@ -202,6 +194,14 @@ async function composeNotification(data: pushNotificationDataMap[keyof pushNotif
data, data,
}]; }];
case 'achievementEarned':
return [t('_notification.achievementEarned'), {
body: t(`_achievements._types._${data.body.achievement}.title`),
badge: iconUrl('medal'),
data,
tag: `achievement:${data.body.achievement}`,
}];
case 'app': case 'app':
return [data.body.header ?? data.body.body, { return [data.body.header ?? data.body.body, {
body: data.body.header ? data.body.body : '', body: data.body.header ? data.body.body : '',
@ -233,17 +233,29 @@ export async function createEmptyNotification() {
const { t } = i18n; const { t } = i18n;
await globalThis.registration.showNotification( await globalThis.registration.showNotification(
t('_notification.emptyPushNotificationMessage'), (new URL(origin)).host,
{ {
body: `Misskey v${_VERSION_}`,
silent: true, silent: true,
badge: iconUrl('null'), badge: iconUrl('null'),
tag: 'read_notification', tag: 'read_notification',
actions: [
{
action: 'markAllAsRead',
title: t('markAllAsRead'),
},
{
action: 'settings',
title: t('notificationSettings'),
},
],
data: {},
}, },
); );
setTimeout(async () => { setTimeout(async () => {
try { try {
await closeNotificationsByTags(['user_visible_auto_notification', 'read_notification']); await closeNotificationsByTags(['user_visible_auto_notification']);
} finally { } finally {
res(); res();
} }

View File

@ -3,8 +3,7 @@
* *
*/ */
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { SwMessage, swMessageOrderType } from '@/types'; import { SwMessage, SwMessageOrderType } from '@/types';
import { acct as getAcct } from '@/filters/user';
import { getAccountFromId } from '@/scripts/get-account-from-id'; import { getAccountFromId } from '@/scripts/get-account-from-id';
import { getUrlWithLoginId } from '@/scripts/login-id'; import { getUrlWithLoginId } from '@/scripts/login-id';
@ -17,13 +16,27 @@ export async function api<E extends keyof Misskey.Endpoints>(endpoint: E, userId
return cli.request(endpoint, options, account.token); return cli.request(endpoint, options, account.token);
} }
// mark-all-as-read送出を1秒間隔に制限する
const readBlockingStatus = new Map<string, boolean>();
export function sendMarkAllAsRead(userId: string): Promise<null | undefined | void> {
if (readBlockingStatus.get(userId)) return Promise.resolve();
readBlockingStatus.set(userId, true);
return new Promise(resolve => {
setTimeout(() => {
readBlockingStatus.set(userId, false);
api('notifications/mark-all-as-read', userId)
.then(resolve, resolve);
}, 1000);
});
}
// rendered acctからユーザーを開く // rendered acctからユーザーを開く
export function openUser(acct: string, loginId: string) { export function openUser(acct: string, loginId?: string) {
return openClient('push', `/@${acct}`, loginId, { acct }); return openClient('push', `/@${acct}`, loginId, { acct });
} }
// noteIdからートを開く // noteIdからートを開く
export function openNote(noteId: string, loginId: string) { export function openNote(noteId: string, loginId?: string) {
return openClient('push', `/notes/${noteId}`, loginId, { noteId }); return openClient('push', `/notes/${noteId}`, loginId, { noteId });
} }
@ -33,7 +46,7 @@ export function openAntenna(antennaId: string, loginId: string) {
} }
// post-formのオプションから投稿フォームを開く // post-formのオプションから投稿フォームを開く
export async function openPost(options: any, loginId: string) { export async function openPost(options: any, loginId?: string) {
// クエリを作成しておく // クエリを作成しておく
let url = '/share?'; let url = '/share?';
if (options.initialText) url += `text=${options.initialText}&`; if (options.initialText) url += `text=${options.initialText}&`;
@ -43,7 +56,7 @@ export async function openPost(options: any, loginId: string) {
return openClient('post', url, loginId, { options }); return openClient('post', url, loginId, { options });
} }
export async function openClient(order: swMessageOrderType, url: string, loginId: string, query: any = {}) { export async function openClient(order: SwMessageOrderType, url: string, loginId?: string, query: any = {}) {
const client = await findClient(); const client = await findClient();
if (client) { if (client) {
@ -51,7 +64,7 @@ export async function openClient(order: swMessageOrderType, url: string, loginId
return client; return client;
} }
return globalThis.clients.openWindow(getUrlWithLoginId(url, loginId)); return globalThis.clients.openWindow(loginId ? getUrlWithLoginId(url, loginId) : url);
} }
export async function findClient() { export async function findClient() {
@ -59,7 +72,7 @@ export async function findClient() {
type: 'window', type: 'window',
}); });
for (const c of clients) { for (const c of clients) {
if (!new URL(c.url).searchParams.has('zen')) return c; if (!(new URL(c.url)).searchParams.has('zen')) return c;
} }
return null; return null;
} }

View File

@ -1,9 +1,9 @@
import { createEmptyNotification, createNotification } from '@/scripts/create-notification'; import { createEmptyNotification, createNotification } from '@/scripts/create-notification';
import { swLang } from '@/scripts/lang'; import { swLang } from '@/scripts/lang';
import { api } from '@/scripts/operations'; import { PushNotificationDataMap } from '@/types';
import { pushNotificationDataMap } from '@/types';
import * as swos from '@/scripts/operations'; import * as swos from '@/scripts/operations';
import { acct as getAcct } from '@/filters/user'; import { acct as getAcct } from '@/filters/user';
import { get } from 'idb-keyval';
globalThis.addEventListener('install', ev => { globalThis.addEventListener('install', ev => {
//ev.waitUntil(globalThis.skipWaiting()); //ev.waitUntil(globalThis.skipWaiting());
@ -44,7 +44,7 @@ globalThis.addEventListener('push', ev => {
includeUncontrolled: true, includeUncontrolled: true,
type: 'window', type: 'window',
}).then(async (clients: readonly WindowClient[]) => { }).then(async (clients: readonly WindowClient[]) => {
const data: pushNotificationDataMap[keyof pushNotificationDataMap] = ev.data?.json(); const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.data?.json();
switch (data.type) { switch (data.type) {
// case 'driveFileCreated': // case 'driveFileCreated':
@ -54,6 +54,10 @@ globalThis.addEventListener('push', ev => {
if ((new Date()).getTime() - data.dateTime > 1000 * 60 * 60 * 24) break; if ((new Date()).getTime() - data.dateTime > 1000 * 60 * 60 * 24) break;
return createNotification(data); return createNotification(data);
case 'readAllNotifications':
await globalThis.registration.getNotifications()
.then(notifications => notifications.forEach(n => n.close()));
break;
} }
await createEmptyNotification(); await createEmptyNotification();
@ -68,7 +72,7 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv
} }
const { action, notification } = ev; const { action, notification } = ev;
const data: pushNotificationDataMap[keyof pushNotificationDataMap] = notification.data; const data: PushNotificationDataMap[keyof PushNotificationDataMap] = notification.data ?? {};
const { userId: loginId } = data; const { userId: loginId } = data;
let client: WindowClient | null = null; let client: WindowClient | null = null;
@ -124,13 +128,29 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv
break; break;
case 'unreadAntennaNote': case 'unreadAntennaNote':
client = await swos.openAntenna(data.body.antenna.id, loginId); client = await swos.openAntenna(data.body.antenna.id, loginId);
break;
default:
switch (action) {
case 'markAllAsRead':
await globalThis.registration.getNotifications()
.then(notifications => notifications.forEach(n => n.close()));
await get('accounts').then(accounts => {
return Promise.all(accounts.map(async account => {
await swos.sendMarkAllAsRead(account.id);
}));
});
break;
case 'settings':
client = await swos.openClient('push', '/settings/notifications', loginId);
break;
}
} }
if (client) { if (client) {
client.focus(); client.focus();
} }
if (data.type === 'notification') { if (data.type === 'notification') {
api('notifications/mark-all-as-read', data.userId); await swos.sendMarkAllAsRead(loginId);
} }
notification.close(); notification.close();
@ -138,11 +158,14 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv
}); });
globalThis.addEventListener('notificationclose', (ev: ServiceWorkerGlobalScopeEventMap['notificationclose']) => { globalThis.addEventListener('notificationclose', (ev: ServiceWorkerGlobalScopeEventMap['notificationclose']) => {
const data: pushNotificationDataMap[keyof pushNotificationDataMap] = ev.notification.data; const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.notification.data;
ev.waitUntil((async () => {
if (data.type === 'notification') { if (data.type === 'notification') {
api('notifications/mark-all-as-read', data.userId); await swos.sendMarkAllAsRead(data.userId);
} }
return;
})());
}); });
globalThis.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => { globalThis.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => {

View File

@ -1,42 +1,44 @@
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
export type swMessageOrderType = 'post' | 'push'; export type SwMessageOrderType = 'post' | 'push';
export type SwMessage = { export type SwMessage = {
type: 'order'; type: 'order';
order: swMessageOrderType; order: SwMessageOrderType;
loginId: string; loginId: string;
url: string; url: string;
[x: string]: any; [x: string]: any;
}; };
// Defined also @/core/PushNotificationService.ts#L12 // Defined also @/core/PushNotificationService.ts#L12
type pushNotificationDataSourceMap = { type PushNotificationDataSourceMap = {
notification: Misskey.entities.Notification; notification: Misskey.entities.Notification;
unreadAntennaNote: { unreadAntennaNote: {
antenna: { id: string, name: string }; antenna: { id: string, name: string };
note: Misskey.entities.Note; note: Misskey.entities.Note;
}; };
readAllNotifications: undefined;
}; };
export type pushNotificationData<K extends keyof pushNotificationDataSourceMap> = { export type PushNotificationData<K extends keyof PushNotificationDataSourceMap> = {
type: K; type: K;
body: pushNotificationDataSourceMap[K]; body: PushNotificationDataSourceMap[K];
userId: string; userId: string;
dateTime: number; dateTime: number;
}; };
export type pushNotificationDataMap = { export type PushNotificationDataMap = {
[K in keyof pushNotificationDataSourceMap]: pushNotificationData<K>; [K in keyof PushNotificationDataSourceMap]: PushNotificationData<K>;
}; };
export type badgeNames = export type BadgeNames =
'null' 'null'
| 'antenna' | 'antenna'
| 'arrow-back-up' | 'arrow-back-up'
| 'at' | 'at'
| 'chart-arrows' | 'chart-arrows'
| 'circle-check' | 'circle-check'
| 'medal'
| 'messages' | 'messages'
| 'plus' | 'plus'
| 'quote' | 'quote'

View File

@ -1014,8 +1014,8 @@ importers:
packages/sw: packages/sw:
dependencies: dependencies:
esbuild: esbuild:
specifier: 0.14.42 specifier: 0.17.15
version: 0.14.42 version: 0.17.15
idb-keyval: idb-keyval:
specifier: 6.2.0 specifier: 6.2.0
version: 6.2.0 version: 6.2.0
@ -3655,6 +3655,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/android-arm64@0.17.15:
resolution: {integrity: sha512-0kOB6Y7Br3KDVgHeg8PRcvfLkq+AccreK///B4Z6fNZGr/tNHX0z2VywCc7PTeWp+bPvjA5WMvNXltHw5QjAIA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
requiresBuild: true
optional: true
/@esbuild/android-arm@0.17.14: /@esbuild/android-arm@0.17.14:
resolution: {integrity: sha512-0CnlwnjDU8cks0yJLXfkaU/uoLyRf9VZJs4p1PskBr2AlAHeEsFEwJEo0of/Z3g+ilw5mpyDwThlxzNEIxOE4g==} resolution: {integrity: sha512-0CnlwnjDU8cks0yJLXfkaU/uoLyRf9VZJs4p1PskBr2AlAHeEsFEwJEo0of/Z3g+ilw5mpyDwThlxzNEIxOE4g==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3663,6 +3671,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/android-arm@0.17.15:
resolution: {integrity: sha512-sRSOVlLawAktpMvDyJIkdLI/c/kdRTOqo8t6ImVxg8yT7LQDUYV5Rp2FKeEosLr6ZCja9UjYAzyRSxGteSJPYg==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
requiresBuild: true
optional: true
/@esbuild/android-x64@0.17.14: /@esbuild/android-x64@0.17.14:
resolution: {integrity: sha512-nrfQYWBfLGfSGLvRVlt6xi63B5IbfHm3tZCdu/82zuFPQ7zez4XjmRtF/wIRYbJQ/DsZrxJdEvYFE67avYXyng==} resolution: {integrity: sha512-nrfQYWBfLGfSGLvRVlt6xi63B5IbfHm3tZCdu/82zuFPQ7zez4XjmRtF/wIRYbJQ/DsZrxJdEvYFE67avYXyng==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3671,6 +3687,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/android-x64@0.17.15:
resolution: {integrity: sha512-MzDqnNajQZ63YkaUWVl9uuhcWyEyh69HGpMIrf+acR4otMkfLJ4sUCxqwbCyPGicE9dVlrysI3lMcDBjGiBBcQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
requiresBuild: true
optional: true
/@esbuild/darwin-arm64@0.17.14: /@esbuild/darwin-arm64@0.17.14:
resolution: {integrity: sha512-eoSjEuDsU1ROwgBH/c+fZzuSyJUVXQTOIN9xuLs9dE/9HbV/A5IqdXHU1p2OfIMwBwOYJ9SFVGGldxeRCUJFyw==} resolution: {integrity: sha512-eoSjEuDsU1ROwgBH/c+fZzuSyJUVXQTOIN9xuLs9dE/9HbV/A5IqdXHU1p2OfIMwBwOYJ9SFVGGldxeRCUJFyw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3679,6 +3703,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/darwin-arm64@0.17.15:
resolution: {integrity: sha512-7siLjBc88Z4+6qkMDxPT2juf2e8SJxmsbNVKFY2ifWCDT72v5YJz9arlvBw5oB4W/e61H1+HDB/jnu8nNg0rLA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
optional: true
/@esbuild/darwin-x64@0.17.14: /@esbuild/darwin-x64@0.17.14:
resolution: {integrity: sha512-zN0U8RWfrDttdFNkHqFYZtOH8hdi22z0pFm0aIJPsNC4QQZv7je8DWCX5iA4Zx6tRhS0CCc0XC2m7wKsbWEo5g==} resolution: {integrity: sha512-zN0U8RWfrDttdFNkHqFYZtOH8hdi22z0pFm0aIJPsNC4QQZv7je8DWCX5iA4Zx6tRhS0CCc0XC2m7wKsbWEo5g==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3687,6 +3719,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/darwin-x64@0.17.15:
resolution: {integrity: sha512-NbImBas2rXwYI52BOKTW342Tm3LTeVlaOQ4QPZ7XuWNKiO226DisFk/RyPk3T0CKZkKMuU69yOvlapJEmax7cg==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
requiresBuild: true
optional: true
/@esbuild/freebsd-arm64@0.17.14: /@esbuild/freebsd-arm64@0.17.14:
resolution: {integrity: sha512-z0VcD4ibeZWVQCW1O7szaLxGsx54gcCnajEJMdYoYjLiq4g1jrP2lMq6pk71dbS5+7op/L2Aod+erw+EUr28/A==} resolution: {integrity: sha512-z0VcD4ibeZWVQCW1O7szaLxGsx54gcCnajEJMdYoYjLiq4g1jrP2lMq6pk71dbS5+7op/L2Aod+erw+EUr28/A==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3695,6 +3735,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/freebsd-arm64@0.17.15:
resolution: {integrity: sha512-Xk9xMDjBVG6CfgoqlVczHAdJnCs0/oeFOspFap5NkYAmRCT2qTn1vJWA2f419iMtsHSLm+O8B6SLV/HlY5cYKg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
optional: true
/@esbuild/freebsd-x64@0.17.14: /@esbuild/freebsd-x64@0.17.14:
resolution: {integrity: sha512-hd9mPcxfTgJlolrPlcXkQk9BMwNBvNBsVaUe5eNUqXut6weDQH8whcNaKNF2RO8NbpT6GY8rHOK2A9y++s+ehw==} resolution: {integrity: sha512-hd9mPcxfTgJlolrPlcXkQk9BMwNBvNBsVaUe5eNUqXut6weDQH8whcNaKNF2RO8NbpT6GY8rHOK2A9y++s+ehw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3703,6 +3751,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/freebsd-x64@0.17.15:
resolution: {integrity: sha512-3TWAnnEOdclvb2pnfsTWtdwthPfOz7qAfcwDLcfZyGJwm1SRZIMOeB5FODVhnM93mFSPsHB9b/PmxNNbSnd0RQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
requiresBuild: true
optional: true
/@esbuild/linux-arm64@0.17.14: /@esbuild/linux-arm64@0.17.14:
resolution: {integrity: sha512-FhAMNYOq3Iblcj9i+K0l1Fp/MHt+zBeRu/Qkf0LtrcFu3T45jcwB6A1iMsemQ42vR3GBhjNZJZTaCe3VFPbn9g==} resolution: {integrity: sha512-FhAMNYOq3Iblcj9i+K0l1Fp/MHt+zBeRu/Qkf0LtrcFu3T45jcwB6A1iMsemQ42vR3GBhjNZJZTaCe3VFPbn9g==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3711,6 +3767,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/linux-arm64@0.17.15:
resolution: {integrity: sha512-T0MVnYw9KT6b83/SqyznTs/3Jg2ODWrZfNccg11XjDehIved2oQfrX/wVuev9N936BpMRaTR9I1J0tdGgUgpJA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-arm@0.17.14: /@esbuild/linux-arm@0.17.14:
resolution: {integrity: sha512-BNTl+wSJ1omsH8s3TkQmIIIQHwvwJrU9u1ggb9XU2KTVM4TmthRIVyxSp2qxROJHhZuW/r8fht46/QE8hU8Qvg==} resolution: {integrity: sha512-BNTl+wSJ1omsH8s3TkQmIIIQHwvwJrU9u1ggb9XU2KTVM4TmthRIVyxSp2qxROJHhZuW/r8fht46/QE8hU8Qvg==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3719,6 +3783,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/linux-arm@0.17.15:
resolution: {integrity: sha512-MLTgiXWEMAMr8nmS9Gigx43zPRmEfeBfGCwxFQEMgJ5MC53QKajaclW6XDPjwJvhbebv+RzK05TQjvH3/aM4Xw==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-ia32@0.17.14: /@esbuild/linux-ia32@0.17.14:
resolution: {integrity: sha512-91OK/lQ5y2v7AsmnFT+0EyxdPTNhov3y2CWMdizyMfxSxRqHazXdzgBKtlmkU2KYIc+9ZK3Vwp2KyXogEATYxQ==} resolution: {integrity: sha512-91OK/lQ5y2v7AsmnFT+0EyxdPTNhov3y2CWMdizyMfxSxRqHazXdzgBKtlmkU2KYIc+9ZK3Vwp2KyXogEATYxQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3727,6 +3799,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/linux-ia32@0.17.15:
resolution: {integrity: sha512-wp02sHs015T23zsQtU4Cj57WiteiuASHlD7rXjKUyAGYzlOKDAjqK6bk5dMi2QEl/KVOcsjwL36kD+WW7vJt8Q==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-loong64@0.17.14: /@esbuild/linux-loong64@0.17.14:
resolution: {integrity: sha512-vp15H+5NR6hubNgMluqqKza85HcGJgq7t6rMH7O3Y6ApiOWPkvW2AJfNojUQimfTp6OUrACUXfR4hmpcENXoMQ==} resolution: {integrity: sha512-vp15H+5NR6hubNgMluqqKza85HcGJgq7t6rMH7O3Y6ApiOWPkvW2AJfNojUQimfTp6OUrACUXfR4hmpcENXoMQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3735,6 +3815,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/linux-loong64@0.17.15:
resolution: {integrity: sha512-k7FsUJjGGSxwnBmMh8d7IbObWu+sF/qbwc+xKZkBe/lTAF16RqxRCnNHA7QTd3oS2AfGBAnHlXL67shV5bBThQ==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-mips64el@0.17.14: /@esbuild/linux-mips64el@0.17.14:
resolution: {integrity: sha512-90TOdFV7N+fgi6c2+GO9ochEkmm9kBAKnuD5e08GQMgMINOdOFHuYLPQ91RYVrnWwQ5683sJKuLi9l4SsbJ7Hg==} resolution: {integrity: sha512-90TOdFV7N+fgi6c2+GO9ochEkmm9kBAKnuD5e08GQMgMINOdOFHuYLPQ91RYVrnWwQ5683sJKuLi9l4SsbJ7Hg==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3743,6 +3831,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/linux-mips64el@0.17.15:
resolution: {integrity: sha512-ZLWk6czDdog+Q9kE/Jfbilu24vEe/iW/Sj2d8EVsmiixQ1rM2RKH2n36qfxK4e8tVcaXkvuV3mU5zTZviE+NVQ==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-ppc64@0.17.14: /@esbuild/linux-ppc64@0.17.14:
resolution: {integrity: sha512-NnBGeoqKkTugpBOBZZoktQQ1Yqb7aHKmHxsw43NddPB2YWLAlpb7THZIzsRsTr0Xw3nqiPxbA1H31ZMOG+VVPQ==} resolution: {integrity: sha512-NnBGeoqKkTugpBOBZZoktQQ1Yqb7aHKmHxsw43NddPB2YWLAlpb7THZIzsRsTr0Xw3nqiPxbA1H31ZMOG+VVPQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3751,6 +3847,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/linux-ppc64@0.17.15:
resolution: {integrity: sha512-mY6dPkIRAiFHRsGfOYZC8Q9rmr8vOBZBme0/j15zFUKM99d4ILY4WpOC7i/LqoY+RE7KaMaSfvY8CqjJtuO4xg==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-riscv64@0.17.14: /@esbuild/linux-riscv64@0.17.14:
resolution: {integrity: sha512-0qdlKScLXA8MGVy21JUKvMzCYWovctuP8KKqhtE5A6IVPq4onxXhSuhwDd2g5sRCzNDlDjitc5sX31BzDoL5Fw==} resolution: {integrity: sha512-0qdlKScLXA8MGVy21JUKvMzCYWovctuP8KKqhtE5A6IVPq4onxXhSuhwDd2g5sRCzNDlDjitc5sX31BzDoL5Fw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3759,6 +3863,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/linux-riscv64@0.17.15:
resolution: {integrity: sha512-EcyUtxffdDtWjjwIH8sKzpDRLcVtqANooMNASO59y+xmqqRYBBM7xVLQhqF7nksIbm2yHABptoioS9RAbVMWVA==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-s390x@0.17.14: /@esbuild/linux-s390x@0.17.14:
resolution: {integrity: sha512-Hdm2Jo1yaaOro4v3+6/zJk6ygCqIZuSDJHdHaf8nVH/tfOuoEX5Riv03Ka15LmQBYJObUTNS1UdyoMk0WUn9Ww==} resolution: {integrity: sha512-Hdm2Jo1yaaOro4v3+6/zJk6ygCqIZuSDJHdHaf8nVH/tfOuoEX5Riv03Ka15LmQBYJObUTNS1UdyoMk0WUn9Ww==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3767,6 +3879,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/linux-s390x@0.17.15:
resolution: {integrity: sha512-BuS6Jx/ezxFuHxgsfvz7T4g4YlVrmCmg7UAwboeyNNg0OzNzKsIZXpr3Sb/ZREDXWgt48RO4UQRDBxJN3B9Rbg==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/linux-x64@0.17.14: /@esbuild/linux-x64@0.17.14:
resolution: {integrity: sha512-8KHF17OstlK4DuzeF/KmSgzrTWQrkWj5boluiiq7kvJCiQVzUrmSkaBvcLB2UgHpKENO2i6BthPkmUhNDaJsVw==} resolution: {integrity: sha512-8KHF17OstlK4DuzeF/KmSgzrTWQrkWj5boluiiq7kvJCiQVzUrmSkaBvcLB2UgHpKENO2i6BthPkmUhNDaJsVw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3775,6 +3895,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/linux-x64@0.17.15:
resolution: {integrity: sha512-JsdS0EgEViwuKsw5tiJQo9UdQdUJYuB+Mf6HxtJSPN35vez1hlrNb1KajvKWF5Sa35j17+rW1ECEO9iNrIXbNg==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
requiresBuild: true
optional: true
/@esbuild/netbsd-x64@0.17.14: /@esbuild/netbsd-x64@0.17.14:
resolution: {integrity: sha512-nVwpqvb3yyXztxIT2+VsxJhB5GCgzPdk1n0HHSnchRAcxqKO6ghXwHhJnr0j/B+5FSyEqSxF4q03rbA2fKXtUQ==} resolution: {integrity: sha512-nVwpqvb3yyXztxIT2+VsxJhB5GCgzPdk1n0HHSnchRAcxqKO6ghXwHhJnr0j/B+5FSyEqSxF4q03rbA2fKXtUQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3783,6 +3911,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/netbsd-x64@0.17.15:
resolution: {integrity: sha512-R6fKjtUysYGym6uXf6qyNephVUQAGtf3n2RCsOST/neIwPqRWcnc3ogcielOd6pT+J0RDR1RGcy0ZY7d3uHVLA==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
requiresBuild: true
optional: true
/@esbuild/openbsd-x64@0.17.14: /@esbuild/openbsd-x64@0.17.14:
resolution: {integrity: sha512-1RZ7uQQ9zcy/GSAJL1xPdN7NDdOOtNEGiJalg/MOzeakZeTrgH/DoCkbq7TaPDiPhWqnDF+4bnydxRqQD7il6g==} resolution: {integrity: sha512-1RZ7uQQ9zcy/GSAJL1xPdN7NDdOOtNEGiJalg/MOzeakZeTrgH/DoCkbq7TaPDiPhWqnDF+4bnydxRqQD7il6g==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3791,6 +3927,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/openbsd-x64@0.17.15:
resolution: {integrity: sha512-mVD4PGc26b8PI60QaPUltYKeSX0wxuy0AltC+WCTFwvKCq2+OgLP4+fFd+hZXzO2xW1HPKcytZBdjqL6FQFa7w==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
requiresBuild: true
optional: true
/@esbuild/sunos-x64@0.17.14: /@esbuild/sunos-x64@0.17.14:
resolution: {integrity: sha512-nqMjDsFwv7vp7msrwWRysnM38Sd44PKmW8EzV01YzDBTcTWUpczQg6mGao9VLicXSgW/iookNK6AxeogNVNDZA==} resolution: {integrity: sha512-nqMjDsFwv7vp7msrwWRysnM38Sd44PKmW8EzV01YzDBTcTWUpczQg6mGao9VLicXSgW/iookNK6AxeogNVNDZA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3799,6 +3943,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/sunos-x64@0.17.15:
resolution: {integrity: sha512-U6tYPovOkw3459t2CBwGcFYfFRjivcJJc1WC8Q3funIwX8x4fP+R6xL/QuTPNGOblbq/EUDxj9GU+dWKX0oWlQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
requiresBuild: true
optional: true
/@esbuild/win32-arm64@0.17.14: /@esbuild/win32-arm64@0.17.14:
resolution: {integrity: sha512-xrD0mccTKRBBIotrITV7WVQAwNJ5+1va6L0H9zN92v2yEdjfAN7864cUaZwJS7JPEs53bDTzKFbfqVlG2HhyKQ==} resolution: {integrity: sha512-xrD0mccTKRBBIotrITV7WVQAwNJ5+1va6L0H9zN92v2yEdjfAN7864cUaZwJS7JPEs53bDTzKFbfqVlG2HhyKQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3807,6 +3959,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/win32-arm64@0.17.15:
resolution: {integrity: sha512-W+Z5F++wgKAleDABemiyXVnzXgvRFs+GVKThSI+mGgleLWluv0D7Diz4oQpgdpNzh4i2nNDzQtWbjJiqutRp6Q==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
requiresBuild: true
optional: true
/@esbuild/win32-ia32@0.17.14: /@esbuild/win32-ia32@0.17.14:
resolution: {integrity: sha512-nXpkz9bbJrLLyUTYtRotSS3t5b+FOuljg8LgLdINWFs3FfqZMtbnBCZFUmBzQPyxqU87F8Av+3Nco/M3hEcu1w==} resolution: {integrity: sha512-nXpkz9bbJrLLyUTYtRotSS3t5b+FOuljg8LgLdINWFs3FfqZMtbnBCZFUmBzQPyxqU87F8Av+3Nco/M3hEcu1w==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3815,6 +3975,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/win32-ia32@0.17.15:
resolution: {integrity: sha512-Muz/+uGgheShKGqSVS1KsHtCyEzcdOn/W/Xbh6H91Etm+wiIfwZaBn1W58MeGtfI8WA961YMHFYTthBdQs4t+w==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
requiresBuild: true
optional: true
/@esbuild/win32-x64@0.17.14: /@esbuild/win32-x64@0.17.14:
resolution: {integrity: sha512-gPQmsi2DKTaEgG14hc3CHXHp62k8g6qr0Pas+I4lUxRMugGSATh/Bi8Dgusoz9IQ0IfdrvLpco6kujEIBoaogA==} resolution: {integrity: sha512-gPQmsi2DKTaEgG14hc3CHXHp62k8g6qr0Pas+I4lUxRMugGSATh/Bi8Dgusoz9IQ0IfdrvLpco6kujEIBoaogA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3823,6 +3991,14 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@esbuild/win32-x64@0.17.15:
resolution: {integrity: sha512-DjDa9ywLUUmjhV2Y9wUTIF+1XsmuFGvZoCmOWkli1XcNAh5t25cc7fgsCx4Zi/Uurep3TTLyDiKATgGEg61pkA==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
requiresBuild: true
optional: true
/@eslint-community/eslint-utils@4.4.0(eslint@8.37.0): /@eslint-community/eslint-utils@4.4.0(eslint@8.37.0):
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -5284,10 +5460,10 @@ packages:
'@storybook/node-logger': 7.0.2 '@storybook/node-logger': 7.0.2
'@types/ejs': 3.1.2 '@types/ejs': 3.1.2
'@types/find-cache-dir': 3.2.1 '@types/find-cache-dir': 3.2.1
'@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.17.14) '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.17.15)
browser-assert: 1.2.1 browser-assert: 1.2.1
ejs: 3.1.8 ejs: 3.1.8
esbuild: 0.17.14 esbuild: 0.17.15
esbuild-plugin-alias: 0.2.1 esbuild-plugin-alias: 0.2.1
express: 4.18.2 express: 4.18.2
find-cache-dir: 3.3.2 find-cache-dir: 3.3.2
@ -5495,8 +5671,8 @@ packages:
'@types/node': 16.18.16 '@types/node': 16.18.16
'@types/pretty-hrtime': 1.0.1 '@types/pretty-hrtime': 1.0.1
chalk: 4.1.2 chalk: 4.1.2
esbuild: 0.17.14 esbuild: 0.17.15
esbuild-register: 3.4.2(esbuild@0.17.14) esbuild-register: 3.4.2(esbuild@0.17.15)
file-system-cache: 2.0.2 file-system-cache: 2.0.2
find-up: 5.0.0 find-up: 5.0.0
fs-extra: 11.1.0 fs-extra: 11.1.0
@ -7403,13 +7579,13 @@ packages:
tunnel: 0.0.6 tunnel: 0.0.6
dev: true dev: true
/@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.17.14): /@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.17.15):
resolution: {integrity: sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==} resolution: {integrity: sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==}
engines: {node: '>=14.15.0'} engines: {node: '>=14.15.0'}
peerDependencies: peerDependencies:
esbuild: '>=0.10.0' esbuild: '>=0.10.0'
dependencies: dependencies:
esbuild: 0.17.14 esbuild: 0.17.15
tslib: 2.5.0 tslib: 2.5.0
dev: true dev: true
@ -10330,228 +10506,21 @@ packages:
es6-symbol: 3.1.3 es6-symbol: 3.1.3
dev: false dev: false
/esbuild-android-64@0.14.42:
resolution: {integrity: sha512-P4Y36VUtRhK/zivqGVMqhptSrFILAGlYp0Z8r9UQqHJ3iWztRCNWnlBzD9HRx0DbueXikzOiwyOri+ojAFfW6A==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
requiresBuild: true
dev: false
optional: true
/esbuild-android-arm64@0.14.42:
resolution: {integrity: sha512-0cOqCubq+RWScPqvtQdjXG3Czb3AWI2CaKw3HeXry2eoA2rrPr85HF7IpdU26UWdBXgPYtlTN1LUiuXbboROhg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: false
optional: true
/esbuild-darwin-64@0.14.42:
resolution: {integrity: sha512-ipiBdCA3ZjYgRfRLdQwP82rTiv/YVMtW36hTvAN5ZKAIfxBOyPXY7Cejp3bMXWgzKD8B6O+zoMzh01GZsCuEIA==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/esbuild-darwin-arm64@0.14.42:
resolution: {integrity: sha512-bU2tHRqTPOaoH/4m0zYHbFWpiYDmaA0gt90/3BMEFaM0PqVK/a6MA2V/ypV5PO0v8QxN6gH5hBPY4YJ2lopXgA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/esbuild-freebsd-64@0.14.42:
resolution: {integrity: sha512-75h1+22Ivy07+QvxHyhVqOdekupiTZVLN1PMwCDonAqyXd8TVNJfIRFrdL8QmSJrOJJ5h8H1I9ETyl2L8LQDaw==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: false
optional: true
/esbuild-freebsd-arm64@0.14.42:
resolution: {integrity: sha512-W6Jebeu5TTDQMJUJVarEzRU9LlKpNkPBbjqSu+GUPTHDCly5zZEQq9uHkmHHl7OKm+mQ2zFySN83nmfCeZCyNA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: false
optional: true
/esbuild-linux-32@0.14.42:
resolution: {integrity: sha512-Ooy/Bj+mJ1z4jlWcK5Dl6SlPlCgQB9zg1UrTCeY8XagvuWZ4qGPyYEWGkT94HUsRi2hKsXvcs6ThTOjBaJSMfg==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: false
optional: true
/esbuild-linux-64@0.14.42:
resolution: {integrity: sha512-2L0HbzQfbTuemUWfVqNIjOfaTRt9zsvjnme6lnr7/MO9toz/MJ5tZhjqrG6uDWDxhsaHI2/nsDgrv8uEEN2eoA==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/esbuild-linux-arm64@0.14.42:
resolution: {integrity: sha512-c3Ug3e9JpVr8jAcfbhirtpBauLxzYPpycjWulD71CF6ZSY26tvzmXMJYooQ2YKqDY4e/fPu5K8bm7MiXMnyxuA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/esbuild-linux-arm@0.14.42:
resolution: {integrity: sha512-STq69yzCMhdRaWnh29UYrLSr/qaWMm/KqwaRF1pMEK7kDiagaXhSL1zQGXbYv94GuGY/zAwzK98+6idCMUOOCg==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: false
optional: true
/esbuild-linux-mips64le@0.14.42:
resolution: {integrity: sha512-QuvpHGbYlkyXWf2cGm51LBCHx6eUakjaSrRpUqhPwjh/uvNUYvLmz2LgPTTPwCqaKt0iwL+OGVL0tXA5aDbAbg==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: false
optional: true
/esbuild-linux-ppc64le@0.14.42:
resolution: {integrity: sha512-8ohIVIWDbDT+i7lCx44YCyIRrOW1MYlks9fxTo0ME2LS/fxxdoJBwHWzaDYhjvf8kNpA+MInZvyOEAGoVDrMHg==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/esbuild-linux-riscv64@0.14.42:
resolution: {integrity: sha512-DzDqK3TuoXktPyG1Lwx7vhaF49Onv3eR61KwQyxYo4y5UKTpL3NmuarHSIaSVlTFDDpcIajCDwz5/uwKLLgKiQ==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/esbuild-linux-s390x@0.14.42:
resolution: {integrity: sha512-YFRhPCxl8nb//Wn6SiS5pmtplBi4z9yC2gLrYoYI/tvwuB1jldir9r7JwAGy1Ck4D7sE7wBN9GFtUUX/DLdcEQ==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: false
optional: true
/esbuild-netbsd-64@0.14.42:
resolution: {integrity: sha512-QYSD2k+oT9dqB/4eEM9c+7KyNYsIPgzYOSrmfNGDIyJrbT1d+CFVKvnKahDKNJLfOYj8N4MgyFaU9/Ytc6w5Vw==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: false
optional: true
/esbuild-openbsd-64@0.14.42:
resolution: {integrity: sha512-M2meNVIKWsm2HMY7+TU9AxM7ZVwI9havdsw6m/6EzdXysyCFFSoaTQ/Jg03izjCsK17FsVRHqRe26Llj6x0MNA==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: false
optional: true
/esbuild-plugin-alias@0.2.1: /esbuild-plugin-alias@0.2.1:
resolution: {integrity: sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==} resolution: {integrity: sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==}
dev: true dev: true
/esbuild-register@3.4.2(esbuild@0.17.14): /esbuild-register@3.4.2(esbuild@0.17.15):
resolution: {integrity: sha512-kG/XyTDyz6+YDuyfB9ZoSIOOmgyFCH+xPRtsCa8W85HLRV5Csp+o3jWVbOSHgSLfyLc5DmP+KFDNwty4mEjC+Q==} resolution: {integrity: sha512-kG/XyTDyz6+YDuyfB9ZoSIOOmgyFCH+xPRtsCa8W85HLRV5Csp+o3jWVbOSHgSLfyLc5DmP+KFDNwty4mEjC+Q==}
peerDependencies: peerDependencies:
esbuild: '>=0.12 <1' esbuild: '>=0.12 <1'
dependencies: dependencies:
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4(supports-color@8.1.1)
esbuild: 0.17.14 esbuild: 0.17.15
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/esbuild-sunos-64@0.14.42:
resolution: {integrity: sha512-uXV8TAZEw36DkgW8Ak3MpSJs1ofBb3Smkc/6pZ29sCAN1KzCAQzsje4sUwugf+FVicrHvlamCOlFZIXgct+iqQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: false
optional: true
/esbuild-windows-32@0.14.42:
resolution: {integrity: sha512-4iw/8qWmRICWi9ZOnJJf9sYt6wmtp3hsN4TdI5NqgjfOkBVMxNdM9Vt3626G1Rda9ya2Q0hjQRD9W1o+m6Lz6g==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: false
optional: true
/esbuild-windows-64@0.14.42:
resolution: {integrity: sha512-j3cdK+Y3+a5H0wHKmLGTJcq0+/2mMBHPWkItR3vytp/aUGD/ua/t2BLdfBIzbNN9nLCRL9sywCRpOpFMx3CxzA==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/esbuild-windows-arm64@0.14.42:
resolution: {integrity: sha512-+lRAARnF+hf8J0mN27ujO+VbhPbDqJ8rCcJKye4y7YZLV6C4n3pTRThAb388k/zqF5uM0lS5O201u0OqoWSicw==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/esbuild@0.14.42:
resolution: {integrity: sha512-V0uPZotCEHokJdNqyozH6qsaQXqmZEOiZWrXnds/zaH/0SyrIayRXWRB98CENO73MIZ9T3HBIOsmds5twWtmgw==}
engines: {node: '>=12'}
requiresBuild: true
optionalDependencies:
esbuild-android-64: 0.14.42
esbuild-android-arm64: 0.14.42
esbuild-darwin-64: 0.14.42
esbuild-darwin-arm64: 0.14.42
esbuild-freebsd-64: 0.14.42
esbuild-freebsd-arm64: 0.14.42
esbuild-linux-32: 0.14.42
esbuild-linux-64: 0.14.42
esbuild-linux-arm: 0.14.42
esbuild-linux-arm64: 0.14.42
esbuild-linux-mips64le: 0.14.42
esbuild-linux-ppc64le: 0.14.42
esbuild-linux-riscv64: 0.14.42
esbuild-linux-s390x: 0.14.42
esbuild-netbsd-64: 0.14.42
esbuild-openbsd-64: 0.14.42
esbuild-sunos-64: 0.14.42
esbuild-windows-32: 0.14.42
esbuild-windows-64: 0.14.42
esbuild-windows-arm64: 0.14.42
dev: false
/esbuild@0.17.14: /esbuild@0.17.14:
resolution: {integrity: sha512-vOO5XhmVj/1XQR9NQ1UPq6qvMYL7QFJU57J5fKBKBKxp17uDt5PgxFDb4A2nEiXhr1qQs4x0F5+66hVVw4ruNw==} resolution: {integrity: sha512-vOO5XhmVj/1XQR9NQ1UPq6qvMYL7QFJU57J5fKBKBKxp17uDt5PgxFDb4A2nEiXhr1qQs4x0F5+66hVVw4ruNw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -10581,6 +10550,35 @@ packages:
'@esbuild/win32-ia32': 0.17.14 '@esbuild/win32-ia32': 0.17.14
'@esbuild/win32-x64': 0.17.14 '@esbuild/win32-x64': 0.17.14
/esbuild@0.17.15:
resolution: {integrity: sha512-LBUV2VsUIc/iD9ME75qhT4aJj0r75abCVS0jakhFzOtR7TQsqQA5w0tZ+KTKnwl3kXE0MhskNdHDh/I5aCR1Zw==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@esbuild/android-arm': 0.17.15
'@esbuild/android-arm64': 0.17.15
'@esbuild/android-x64': 0.17.15
'@esbuild/darwin-arm64': 0.17.15
'@esbuild/darwin-x64': 0.17.15
'@esbuild/freebsd-arm64': 0.17.15
'@esbuild/freebsd-x64': 0.17.15
'@esbuild/linux-arm': 0.17.15
'@esbuild/linux-arm64': 0.17.15
'@esbuild/linux-ia32': 0.17.15
'@esbuild/linux-loong64': 0.17.15
'@esbuild/linux-mips64el': 0.17.15
'@esbuild/linux-ppc64': 0.17.15
'@esbuild/linux-riscv64': 0.17.15
'@esbuild/linux-s390x': 0.17.15
'@esbuild/linux-x64': 0.17.15
'@esbuild/netbsd-x64': 0.17.15
'@esbuild/openbsd-x64': 0.17.15
'@esbuild/sunos-x64': 0.17.15
'@esbuild/win32-arm64': 0.17.15
'@esbuild/win32-ia32': 0.17.15
'@esbuild/win32-x64': 0.17.15
/escalade@3.1.1: /escalade@3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'} engines: {node: '>=6'}