Merge branch 'sw-notification-action' of https://github.com/tamaina/misskey into sw-notification-action

This commit is contained in:
tamaina 2021-02-24 18:40:26 +09:00
commit 946d948989
67 changed files with 1274 additions and 265 deletions

View File

@ -88,6 +88,7 @@ enterEmoji: "أدخل إيموجي"
unrenote: "إلغاء مشاركة الملاحظة" unrenote: "إلغاء مشاركة الملاحظة"
quote: "اقتبس" quote: "اقتبس"
pinnedNote: "ملاحظة مدبسة" pinnedNote: "ملاحظة مدبسة"
pinned: "دبّسها على الصفحة الشخصية"
you: "أنت" you: "أنت"
clickToShow: "اضغط للعرض" clickToShow: "اضغط للعرض"
sensitive: "محتوى حساس" sensitive: "محتوى حساس"
@ -428,6 +429,9 @@ latestVersion: "آخر نسخة مستقرة"
usageAmount: "الإستخدام" usageAmount: "الإستخدام"
capacity: "السعة" capacity: "السعة"
inUse: "مستخدم" inUse: "مستخدم"
_email:
_follow:
title: "يتابعك"
_mfm: _mfm:
mention: "أشر الى" mention: "أشر الى"
quote: "اقتبس" quote: "اقتبس"

View File

@ -97,6 +97,7 @@ cantRenote: "Renote dieses Beitrags nicht möglich."
cantReRenote: "Renote einer Renote nicht möglich." cantReRenote: "Renote einer Renote nicht möglich."
quote: "Zitieren" quote: "Zitieren"
pinnedNote: "Angepinnte Notiz" pinnedNote: "Angepinnte Notiz"
pinned: "Anheften"
you: "Du" you: "Du"
clickToShow: "Klicke, um diesen Inhalt anzusehen" clickToShow: "Klicke, um diesen Inhalt anzusehen"
sensitive: "NSFW" sensitive: "NSFW"
@ -437,6 +438,7 @@ signinWith: "Mit {x} anmelden"
signinFailed: "Anmeldung fehlgeschlagen. Überprüfe Benutzername und Passswort." signinFailed: "Anmeldung fehlgeschlagen. Überprüfe Benutzername und Passswort."
tapSecurityKey: "Tippe deinen Sicherheitsschlüssel an" tapSecurityKey: "Tippe deinen Sicherheitsschlüssel an"
or: "Oder" or: "Oder"
language: "Sprache"
uiLanguage: "Sprache der Benutzeroberfläche" uiLanguage: "Sprache der Benutzeroberfläche"
groupInvited: "Du wurdest in eine Gruppe eingeladen" groupInvited: "Du wurdest in eine Gruppe eingeladen"
aboutX: "Über {x}" aboutX: "Über {x}"
@ -700,7 +702,18 @@ capacity: "Kapazität"
inUse: "Verwendet" inUse: "Verwendet"
editCode: "Code bearbeiten" editCode: "Code bearbeiten"
apply: "Anwenden" apply: "Anwenden"
receiveAnnouncementFromInstance: "Benachrichtigungen von der Instanz empfangen" receiveAnnouncementFromInstance: "E-Mail-Benachrichtigungen von dieser Instanz empfangen"
emailNotification: "E-Mail-Benachrichtigungen"
inChannelSearch: "In Kanal suchen"
useReactionPickerForContextMenu: "Reaktionsauswahl durch Rechtsklick öffnen"
jumpToSpecifiedDate: "Zu bestimmtem Datum springen"
showingPastTimeline: "Momentan wird eine alte Chronik angezeigt"
clear: "Zurückkehren"
_email:
_follow:
title: "Du hast einen neuen Follower"
_receiveFollowRequest:
title: "Du hast eine Follow-Anfrage erhalten"
_plugin: _plugin:
install: "Plugins installieren" install: "Plugins installieren"
installWarn: "Installiere bitte nur vertrauenswürdige Plugins." installWarn: "Installiere bitte nur vertrauenswürdige Plugins."
@ -1508,7 +1521,7 @@ _notification:
youGotPoll: "{name} hat auf deiner Umfrage abgestimmt" youGotPoll: "{name} hat auf deiner Umfrage abgestimmt"
youGotMessagingMessageFromUser: "{name} hat dir eine Chatnachricht gesendet" youGotMessagingMessageFromUser: "{name} hat dir eine Chatnachricht gesendet"
youGotMessagingMessageFromGroup: "In die Gruppe {name} wurde eine Chatnachricht gesendet" youGotMessagingMessageFromGroup: "In die Gruppe {name} wurde eine Chatnachricht gesendet"
youWereFollowed: "Du hast einen neuen Follower" youWereFollowed: "ist dir gefolgt"
youReceivedFollowRequest: "Du hast eine Follow-Anfrage erhalten" youReceivedFollowRequest: "Du hast eine Follow-Anfrage erhalten"
yourFollowRequestAccepted: "Deine Follow-Anfrage wurde akzeptiert" yourFollowRequestAccepted: "Deine Follow-Anfrage wurde akzeptiert"
youWereInvitedToGroup: "Du wurdest in eine Gruppe eingeladen" youWereInvitedToGroup: "Du wurdest in eine Gruppe eingeladen"

View File

@ -97,6 +97,7 @@ cantRenote: "This post can't be renoted."
cantReRenote: "A renote can't be renoted." cantReRenote: "A renote can't be renoted."
quote: "Quote" quote: "Quote"
pinnedNote: "Pinned note" pinnedNote: "Pinned note"
pinned: "Pin to profile"
you: "You" you: "You"
clickToShow: "Click to show" clickToShow: "Click to show"
sensitive: "NSFW" sensitive: "NSFW"
@ -437,6 +438,7 @@ signinWith: "Sign in with {x}"
signinFailed: "Unable to sign in. The username or password you entered is incorrect." signinFailed: "Unable to sign in. The username or password you entered is incorrect."
tapSecurityKey: "Tap your security key" tapSecurityKey: "Tap your security key"
or: "Or" or: "Or"
language: "Language"
uiLanguage: "UI display language" uiLanguage: "UI display language"
groupInvited: "Invited to group" groupInvited: "Invited to group"
aboutX: "About {x}" aboutX: "About {x}"
@ -700,7 +702,18 @@ capacity: "Capacity"
inUse: "Used" inUse: "Used"
editCode: "Edit code" editCode: "Edit code"
apply: "Apply" apply: "Apply"
receiveAnnouncementFromInstance: "Receive notifications from the instance" receiveAnnouncementFromInstance: "Receive Email notifications from this instance"
emailNotification: "Email notifications"
inChannelSearch: "Search in channel"
useReactionPickerForContextMenu: "Open reaction picker on right-click"
jumpToSpecifiedDate: "Jump to specific date"
showingPastTimeline: "Currently displaying an old timeline"
clear: "Return"
_email:
_follow:
title: "You've got a new follower"
_receiveFollowRequest:
title: "You've received a follow request"
_plugin: _plugin:
install: "Install plugins" install: "Install plugins"
installWarn: "Please do not install untrustworthy plugins." installWarn: "Please do not install untrustworthy plugins."

View File

@ -96,6 +96,7 @@ cantRenote: "No se puede renotar este post"
cantReRenote: "No se puede renotar una renota" cantReRenote: "No se puede renotar una renota"
quote: "Citar" quote: "Citar"
pinnedNote: "Nota fijada" pinnedNote: "Nota fijada"
pinned: "Fijar"
you: "Tú" you: "Tú"
clickToShow: "Click para ver" clickToShow: "Click para ver"
sensitive: "Marcado como sensible" sensitive: "Marcado como sensible"
@ -651,6 +652,9 @@ backgroundColor: "Fondo"
accentColor: "Acento" accentColor: "Acento"
textColor: "Texto" textColor: "Texto"
value: "Valores" value: "Valores"
_email:
_follow:
title: "te ha seguido"
_registry: _registry:
key: "Clave" key: "Clave"
keys: "Clave" keys: "Clave"

View File

@ -96,6 +96,7 @@ renoted: "Republier"
cantRenote: "Ce message ne peut pas être republié." cantRenote: "Ce message ne peut pas être republié."
quote: "Citer" quote: "Citer"
pinnedNote: "Note épinglée" pinnedNote: "Note épinglée"
pinned: "Épingler sur le profil"
you: "Vous" you: "Vous"
clickToShow: "Cliquer pour afficher" clickToShow: "Cliquer pour afficher"
sensitive: "Contenu sensible" sensitive: "Contenu sensible"
@ -648,6 +649,9 @@ closeAccount: "Fermer le compte"
usageAmount: "Utilisation" usageAmount: "Utilisation"
capacity: "Capacité " capacity: "Capacité "
inUse: "utilisé" inUse: "utilisé"
_email:
_follow:
title: "Vous suit"
_registry: _registry:
key: "Clé " key: "Clé "
keys: "Clé " keys: "Clé "

View File

@ -1,5 +1,7 @@
--- ---
_lang_: "Bahasa Jepang" _lang_: "Bahasa Jepang"
headlineMisskey: "Catatan terhubung jaringan"
introMisskey: "Selamat datang! Misskey adalah perangkat mikroblog tercatu bersifat sumber terbuka.\nMulailah menuliskan catatan, bagikan peristiwa terkini, serta ceritakan segala tentangmu.📡\nTunjukkan juga reaksimu pada catatan pengguna lain.👍\nMari jelajahi dunia baru🚀"
monthAndDay: "{day} {month}" monthAndDay: "{day} {month}"
search: "Pencarian" search: "Pencarian"
notifications: "Notifikasi" notifications: "Notifikasi"
@ -44,9 +46,30 @@ sendMessage: "Kirim pesan"
copyUsername: "Salin nama pengguna" copyUsername: "Salin nama pengguna"
searchUser: "Cari pengguna" searchUser: "Cari pengguna"
reply: "Balas" reply: "Balas"
loadMore: "Selebihnya"
showMore: "Selebihnya"
youGotNewFollower: "Sedang mengikuti"
receiveFollowRequest: "Permintaan mengikuti terkirim"
mention: "Panggilan"
files: "Berkas"
download: "Unduh"
driveFileDeleteConfirm: "Hapus {name}? Catatan dengan berkas terkait juga akan terhapus."
unfollowConfirm: "Berhenti mengikuti {name}?"
following: "Ikuti"
followers: "Pengikut"
followsYou: "Mengikuti Anda"
error: "Galat"
somethingHappened: "Terjadi kesalahan"
retry: "Coba lagi"
pageLoadError: "Gagal memuat halaman."
pageLoadErrorDescription: "Umumnya disebabkan jaringan atau tembolok perambah. Cobalah bersihkan tembolok peramban lalu tunggu sesaat sebelum mencoba kembali."
privacy: "Keleluasaan"
follow: "Ikuti"
unfollow: "Berhenti mengikuti"
cantReRenote: "Renote tidak dapat direnote" cantReRenote: "Renote tidak dapat direnote"
quote: "Kutip" quote: "Kutip"
pinnedNote: "Note yang disematkan" pinnedNote: "Note yang disematkan"
pinned: "Sematkan ke profil"
you: "Anda" you: "Anda"
clickToShow: "Klik untuk melihat" clickToShow: "Klik untuk melihat"
sensitive: "Konten sensitif" sensitive: "Konten sensitif"
@ -190,26 +213,41 @@ invites: "Undang"
invitations: "Undang" invitations: "Undang"
smtpUser: "Nama Pengguna" smtpUser: "Nama Pengguna"
smtpPass: "Kata sandi" smtpPass: "Kata sandi"
_email:
_follow:
title: "Sedang mengikuti"
_mfm: _mfm:
mention: "Panggilan"
quote: "Kutip" quote: "Kutip"
emoji: "Emoji kustom" emoji: "Emoji kustom"
search: "Pencarian" search: "Pencarian"
_theme:
keys:
mention: "Panggilan"
_sfx: _sfx:
notification: "Notifikasi" notification: "Notifikasi"
chat: "Pesan" chat: "Pesan"
_widgets: _widgets:
notifications: "Notifikasi" notifications: "Notifikasi"
timeline: "Linimasa" timeline: "Linimasa"
_cw:
show: "Selebihnya"
_visibility:
followers: "Pengikut"
_profile: _profile:
username: "Nama Pengguna" username: "Nama Pengguna"
_exportOrImport: _exportOrImport:
followingList: "Ikuti"
muteList: "Bisukan" muteList: "Bisukan"
blockingList: "Blokir" blockingList: "Blokir"
_rooms: _rooms:
_roomType: _roomType:
default: "Bawaan" default: "Bawaan"
_notification: _notification:
youWereFollowed: "Sedang mengikuti"
_types: _types:
follow: "Ikuti"
mention: "Panggilan"
quote: "Kutip" quote: "Kutip"
reaction: "Reaksi" reaction: "Reaksi"
_deck: _deck:

View File

@ -1,67 +1,574 @@
--- ---
_lang_: "Italiano" _lang_: "Italiano"
monthAndDay: "{day}/{month}"
search: "Cerca" search: "Cerca"
notifications: "Notifiche" notifications: "Notifiche"
username: "Nome utente" username: "Nome utente"
password: "Password" password: "Password"
ok: "OK" ok: "OK"
cancel: "Annulla"
enterUsername: "Inserisci un nome utente"
renotedBy: "Rinotta da {user}"
noNotes: "Nessuna note"
noNotifications: "Nessuna notifica"
instance: "Istanza" instance: "Istanza"
settings: "Impostazioni" settings: "Impostazioni"
basicSettings: "Impostazioni generali" basicSettings: "Impostazioni generali"
otherSettings: "Altre impostazioni" otherSettings: "Altre impostazioni"
profile: "Profilo" profile: "Profilo"
timeline: "Timeline" timeline: "Timeline"
login: "Login" login: "Accedi"
logout: "Logout" logout: "Logout"
signup: "Iscriviti" signup: "Iscriviti"
uploading: "Caricamento..."
save: "Salva" save: "Salva"
users: "Utente" users: "Utente"
favorite: "Segnalibri"
favorites: "Segnalibri"
unfavorite: "Rimuovi Nota dai segnalibri"
favorited: "Nota salvato nei segnalibri."
alreadyFavorited: "Tweet salvato nei segnalibri."
pin: "Fissa sul profilo"
unpin: "Non fissare più sul profilo"
copyContent: "Copia il contenuto del Nota"
copyLink: "Copia link"
delete: "Elimina" delete: "Elimina"
deleteAndEdit: "Elimina & Modifica"
addToList: "Aggiungi alla lista"
sendMessage: "Invia messaggio"
copyUsername: "Copia nome utente"
searchUser: "Cerca Utente"
reply: "Rispondi" reply: "Rispondi"
loadMore: "Mostra altre"
showMore: "Mostra altre"
youGotNewFollower: "Nuovo seguace"
receiveFollowRequest: "Nuova richiesta di essere seguito"
mention: "Menzioni" mention: "Menzioni"
mentions: "Menzioni"
directNotes: "Note diretti"
importAndExport: "Importa ed Esporta"
import: "Importa" import: "Importa"
note: "Note" export: "Esporta"
notes: "Notes" files: "Allegato"
download: "Scarica"
lists: "Liste"
noLists: "Qui non c'è ancora niente"
note: "Nota"
notes: "Nota"
following: "Seiguiti"
followers: "Seguaci"
followsYou: "Ti segue"
createList: "Crea una nuova lista"
manageLists: "Modifica lista"
error: "Errore" error: "Errore"
somethingHappened: "Qualcosa è andato storto." somethingHappened: "Qualcosa è andato storto."
retry: "Riprova" retry: "Riprova"
enterListName: "Inserisci il nome della lista"
privacy: "Privacy" privacy: "Privacy"
quote: "Cita Note" follow: "Segui"
followRequest: "Richiesta di seguire"
followRequests: "Richiesta di seguire"
unfollow: "Smetti di seguire"
followRequestPending: "In sospeso"
renote: "Rinotta"
unrenote: "Annulla rinotta"
quote: "Cita Nota"
pinned: "Fissa sul profilo"
you: "Tu"
clickToShow: "Clicca per visualizzare"
sensitive: "Contenuto sensibile"
add: "Aggiungi"
reaction: "Reazione"
attachCancel: "Rimuovi allegato"
markAsSensitive: "Segna come sensibile"
unmarkAsSensitive: "Segna come non sensibile"
mute: "Silenzia" mute: "Silenzia"
unmute: "Riattiva"
block: "Blocca" block: "Blocca"
unblock: "Sblocca"
suspend: "Sospendi" suspend: "Sospendi"
unsuspend: "Annulla la sospensione dell'account"
blockConfirm: "Vuoi bloccare?"
unblockConfirm: "Vuoi sbloccare?"
editWidgetsExit: "Modifica fine"
emoji: "Emoji"
addAcount: "Aggiungi un account esistente"
general: "Generali"
wallpaper: "Sfondo"
setWallpaper: "Imposta sfondo"
searchWith: "Cerca: {q}"
annotation: "Descrizione"
federation: "Federazione"
instances: "Istanza" instances: "Istanza"
storageUsage: "Volume di dischi"
charts: "Grafici"
perHour: "All'ora"
perDay: "al giorno"
software: "Software"
version: "Versione"
metadata: "Metadato"
network: "Rete"
disk: "Disco"
statistics: "Statistiche"
blockedInstances: "Istanza bloccati"
muteAndBlock: "Silenziamento e blocco"
mutedUsers: "Account silenziati"
blockedUsers: "Account bloccati"
editProfile: "Modifica profilo"
noteDeleteConfirm: "Eliminare questo Nota?"
done: "Fine"
processing: "In elaborazione"
blocked: "Bloccati" blocked: "Bloccati"
all: "Tutti"
notResponding: "Nessuna risposta"
changePassword: "Aggiorna Password"
security: "Sicurezza"
retypedNotMatch: "Le password non corrispondono."
currentPassword: "Password attuale"
newPassword: "Nuova Password"
newPasswordRetype: "Conferma nuova password"
more: "Altri!"
lookup: "Cercare"
announcements: "Annuncio"
imageUrl: "URL dell'immagine"
remove: "Elimina" remove: "Elimina"
removed: "Il tuo Tweet è stato eliminato"
removeAreYouSure: "Eliminare \"{x}\"?"
deleteAreYouSure: "Eliminare \"{x}\"?"
resetAreYouSure: "Reimposta"
saved: "Salvato" saved: "Salvato"
messaging: "Messaggi"
upload: "Carica"
uploadFromUrl: "Incolla URL immagine"
explore: "Esplora"
games: "Misskey Giochi"
messageRead: "Visualizzato"
startMessaging: "Nuovo messaggio"
tos: "Termini di servizio"
home: "Home"
images: "Immagini"
birthday: "Compleanno" birthday: "Compleanno"
yearsOld: "{age}Anni"
registeredDate: "Iscrizione a.." registeredDate: "Iscrizione a.."
location: "Posizione" location: "Posizione"
theme: "Tema"
light: "Chiaro"
dark: "Scuro"
lightThemes: "Tema Chiaro"
darkThemes: "Tema Scuro"
drive: "Drive"
fileName: "Nome dell'allegato"
copyUrl: "Copia URL"
rename: "Modifica nome"
avatar: "Foto del profilo"
banner: "Foto d'intestazione"
nsfw: "Contenuti sensibili"
reload: "Ricarica"
watch: "Osserva"
unwatch: "Smetti di Osserva"
accept: "Accetta"
reject: "Rifiuta"
normal: "Normale"
instanceName: "Nome dell'istanza"
instanceDescription: "Descrizione dell'istanza"
maintainerName: "Nome dell'Amministratore"
maintainerEmail: "Indirizzo e-mail dell'Amministratore"
tosUrl: "Termini di servizio URL"
thisYear: "Anno"
thisMonth: "Mese"
today: "Oggi"
dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
integration: "App collegate"
connectSerice: "Connetti"
disconnectSerice: "Disconnetti"
registration: "Iscriviti"
invite: "Invita"
bannerUrl: "indirizzo Foto d'intestazione"
basicInfo: "Informazioni fondamentali"
hcaptcha: "hCaptcha"
enableHcaptcha: "Abilita hCaptcha"
recaptcha: "reCAPTCHA"
enableRecaptcha: "Abilita reCAPTCHA"
name: "Nome"
serviceworker: "ServiceWorker"
exploreFediverse: "Esplora Fediverse"
popularTags: "Tag di tendenza"
userList: "Liste"
about: "Informazioni"
aboutMisskey: "Informazioni di Misskey"
administrator: "Amministratore"
token: "Token"
twoStepAuthentication: "Autenticazione a due fattori"
moderator: "Moderatore"
lastUsed: "Ultima attività"
unregister: "Disattiva account"
resetPassword: "Reimposta password"
share: "Condividi"
cacheClear: "Svuota cache"
help: "Guida"
close: "Chiudi"
group: "Gruppo"
groups: "Gruppi"
createGroup: "Nuovo gruppo"
invites: "Invita"
transfer: "Trasferisci"
title: "Titolo"
next: "Avanti"
invitations: "Invita"
invitationCode: "Codice di invito"
available: "Consigliati"
unavailable: "Il nome utente è già in uso"
usernameInvalidFormat: "Il nome utente può contenere solo lettere, numeri e '_'"
tooShort: "Troppo breve"
tooLong: "Troppo lungo"
passwordNotMatched: "Le password non corrispondono."
signinHistory: "Cronologia di accesso all'account"
tags: "Tag"
createAccount: "Crea il tuo account"
existingAcount: "Account esistente"
local: "Locale"
remote: "Remoto"
accountSettings: "Impostazioni Account"
promote: "Pubblicizza"
objectStorageBucket: "Bucket"
objectStorageEndpoint: "Endpoint"
objectStorageRegion: "Region"
serverLogs: "Log del server"
deleteAll: "Cancella cronologia"
volume: "Volume"
details: "Dettagli"
install: "Installa"
uninstall: "Disinstalla"
installedDate: "Data installazione"
sort: "Ordina per"
visibility: "Privacy dei post"
poll: "Sondaggio"
useCw: "Nascondere media"
description: "Descrizione"
author: "Autore"
width: "Larghezza"
height: "Altezza"
large: "Grande"
medium: "Predefinito"
small: "Piccolo"
edit: "Modifica"
email: "Email"
smtpUser: "Nome utente" smtpUser: "Nome utente"
smtpPass: "Password" smtpPass: "Password"
wordMute: "Parole silenziate"
display: "Visualizza"
copy: "Copia"
logs: "Log"
database: "Base di dati"
channel: "Canale"
notificationSetting: "impostazioni delle notifiche"
other: "Avanzate"
abuseReports: "Segnala"
reportAbuse: "Segnala"
reportAbuseOf: "Segnala {name}"
random: "Casuale"
system: "Sistema"
optional: "Opzionale"
public: "Pubblico"
yes: "Sì"
no: "No"
contact: "Contatti"
developer: "Sviluppatore"
duplicate: "Duplica"
left: "Sinistra"
center: "Centro"
wide: "Largo"
nNotes: "{n}Nota"
backgroundColor: "Sfondo"
value: "Valore"
saveConfirm: "Vuoi salvare le modifiche?"
deleteConfirm: "Rimuovere?"
registry: "Registro"
closeAccount: "Disattiva account"
currentVersion: "Versione attuale"
latestVersion: "Ultima versione"
editCode: "Modifica codice"
apply: "Applica"
_email:
_follow:
title: "Nuovo seguace"
_registry:
key: "Dati"
keys: "Dati"
_aboutMisskey:
morePatrons: "Ci sono molti altri che ci sostengono. Grazie 🥰"
_mfm: _mfm:
mention: "Menzioni" mention: "Menzioni"
quote: "Cita Note" url: "URL"
link: "Link"
bold: "Grassetto"
blockCode: "Codice(blocco)"
inlineMath: "Espressione matematica(Immersione)"
blockMath: "Espressione matematica(blocco)"
quote: "Cita il nota"
search: "Cerca" search: "Cerca"
blur: "Sfocatura"
font: "Tipo di carattere"
_reversi:
black: "Nero"
white: "Bianco"
ended: "Esci"
_channel:
featured: "Tendenze"
_sidebar:
icon: "Foto del profilo"
hide: "Nascondere"
_theme: _theme:
constant: "Costante"
defaultValue: "Valore predefinito"
color: "Colore"
func: "Funzione"
darken: "Scuro"
lighten: "Chiaro"
keys: keys:
bg: "Sfondo"
shadow: "Ombra"
mention: "Menzioni" mention: "Menzioni"
renote: "Rinotta"
divider: "Interruzione di linea"
_sfx: _sfx:
note: "Notes" note: "Nota"
notification: "Notifiche" notification: "Notifiche"
chat: "Messaggi"
_ago:
unknown: "Sconosciuto"
future: "Futuro"
justNow: "Ora"
secondsAgo: "{n}s fa"
minutesAgo: "{n}min fa"
hoursAgo: "{n}h fa"
daysAgo: "{1} giorni fa"
weeksAgo: "{n} settimane fa"
monthsAgo: "{n} mesi fa"
yearsAgo: "{n} anni fa"
_time:
second: "s"
minute: "min"
hour: "ore"
day: "giorni"
_tutorial:
title: "Come usare Misskey"
step1_1: "Benvenuto"
_permissions:
"read:blocks": "Visualizza gli account che hai bloccato."
"write:blocks": "Gestisci gli account che hai bloccato."
"read:favorites": "Visualizza Segnalibri"
"write:favorites": "Gestisci Segnalibri"
"write:following": "Seguiti/ Smetti di seguire"
"read:notifications": "Visualizza notifiche"
_weekday:
sunday: "Domenica"
monday: "Lunedì"
tuesday: "Martedì"
wednesday: "Mercoledì"
thursday: "Giovedì"
friday: "Venerdì"
saturday: "Sabato"
_widgets: _widgets:
memo: "Memo"
notifications: "Notifiche" notifications: "Notifiche"
timeline: "Timeline" timeline: "Timeline"
calendar: "Calendario"
trends: "Tendenze"
clock: "Orologio"
rss: "Aggregatore rss"
activity: "Attività"
photos: "Foto"
digitalClock: "Orologio digitale"
federation: "Federazione"
_cw:
hide: "Nascondere"
show: "Mostra altre"
_poll:
noMore: "Hai aggiunto il numero massimo di opzioni."
canMultipleVote: "Risposte multiple"
expiration: "Scadenza"
infinite: "Permanente"
deadlineDate: "Data di scadenza"
deadlineTime: "h"
voted: "Votato"
closed: "Terminato"
_visibility:
public: "Pubblico"
home: "Home"
followers: "Seguaci"
localOnly: "Solo Locale"
localOnlyDescription: "Solo locale"
_postForm:
replyPlaceholder: "Nota la tua risposta.."
quotePlaceholder: "Cita Nota..."
_profile: _profile:
name: "Nome"
username: "Nome utente" username: "Nome utente"
description: "Bio"
metadata: "Metadati"
metadataLabel: "Etichetta"
metadataContent: "Contenuto"
_exportOrImport: _exportOrImport:
followingList: "Seiguiti"
muteList: "Silenzia" muteList: "Silenzia"
blockingList: "Blocca" blockingList: "Blocca"
userLists: "Liste"
_timelines:
home: "Home"
local: "Locale"
_rooms:
_roomType:
washitsu: "Washitsu"
_furnitures:
milk: "Cartone del latte"
bed: "Letto"
low-table: "Tavolino Coffee"
desk: "Tavolo"
chair: "Sedia"
chair2: "Sedia 2"
fan: "Ventilatore"
pc: "PC"
plant: "Pianta da appartamento"
plant2: "Pianta da appartamento2"
eraser: "Gomma"
pencil: "Matita"
pudding: "Pudding"
book: "Libro"
book2: "Libro2"
piano: "Pianoforte"
server: "Server"
moon: "Luna"
corkboard: "Bacheca"
mousepad: "Tappetino per il mouse"
monitor: "Monitor "
keyboard: "Tastiera"
mat: "Zerbino"
color-box: "Libreria"
wall-clock: "Orologio da parete"
photoframe: "Cornice"
cube: "Cubo"
tv: "Televisore"
pinguin: "Pinguini"
bin: "Cestino"
cup-noodle: "Noodle istantanei"
_pages:
like: "Mi piace"
unlike: "Togli Mi piace"
variables: "Variabili"
title: "Titolo"
font: "Tipo di carattere"
blocks:
image: "Immagini"
if: "Se"
_if:
variable: "Variabili"
_post:
text: "Contenuto"
_textInput:
text: "Titolo"
_textareaInput:
text: "Titolo"
_numberInput:
text: "Titolo"
_switch:
text: "Titolo"
_counter:
text: "Titolo"
_button:
text: "Titolo"
_action:
_dialog:
content: "Contenuto"
_radioButton:
title: "Titolo"
script:
categories:
comparison: "Metodo comparativo"
random: "Aleatorietà"
value: "Valore"
fn: "Funzione"
list: "Liste"
blocks:
_join:
arg1: "Liste"
_add:
arg1: "A"
arg2: "B"
_subtract:
arg1: "A"
arg2: "B"
_multiply:
arg1: "A"
arg2: "B"
_divide:
arg1: "A"
arg2: "B"
_mod:
arg1: "A"
arg2: "B"
_eq:
arg1: "A"
arg2: "B"
notEq: "A non è uguale a B"
_notEq:
arg1: "A"
arg2: "B"
and: "A e B"
_and:
arg1: "A"
arg2: "B"
or: "A o B"
_or:
arg1: "A"
arg2: "B"
_lt:
arg1: "A"
arg2: "B"
_gt:
arg1: "A"
arg2: "B"
_ltEq:
arg1: "A"
arg2: "B"
_gtEq:
arg1: "A"
arg2: "B"
_if:
arg1: "Se"
random: "Aleatorietà"
_randomPick:
arg1: "Liste"
_dailyRandomPick:
arg1: "Liste"
_seedRandomPick:
arg2: "Liste"
_pick:
arg1: "Liste"
_listLen:
arg1: "Liste"
ref: "Variabili"
fn: "Funzione"
types:
array: "Liste"
_notification: _notification:
youGotQuote: "{name} ha citato il tuo Nota e ha detto"
youRenoted: "{name} ha rinotta"
youGotPoll: "{name} ha volluto."
youWereFollowed: "Nuovo seguace"
_types: _types:
all: "Tutto"
follow: "Seiguiti"
mention: "Menzioni" mention: "Menzioni"
quote: "Cita Note" reply: "Rispondi"
renote: "Rinotta"
quote: "Cita il nota"
reaction: "Reazione"
_deck: _deck:
_columns: _columns:
notifications: "Notifiche" notifications: "Notifiche"
tl: "Timeline" tl: "Timeline"
list: "Liste"
mentions: "Menzioni"

View File

@ -704,6 +704,12 @@ editCode: "コードを編集"
apply: "適用" apply: "適用"
receiveAnnouncementFromInstance: "インスタンスからのお知らせを受け取る" receiveAnnouncementFromInstance: "インスタンスからのお知らせを受け取る"
emailNotification: "メール通知" emailNotification: "メール通知"
inChannelSearch: "チャンネル内検索"
useReactionPickerForContextMenu: "右クリックでリアクションピッカーを開く"
typingUsers: "{users}が入力中"
jumpToSpecifiedDate: "特定の日付にジャンプ"
showingPastTimeline: "過去のタイムラインを表示しています"
clear: "クリア"
_email: _email:
_follow: _follow:

View File

@ -96,6 +96,7 @@ cantRenote: "この投稿はRenoteできへんらしい。"
cantReRenote: "Renote自体はRenoteできへんで。" cantReRenote: "Renote自体はRenoteできへんで。"
quote: "引用" quote: "引用"
pinnedNote: "ピン留めされとるノート" pinnedNote: "ピン留めされとるノート"
pinned: "ピン留めしとく"
you: "あんた" you: "あんた"
clickToShow: "押したら見えるで" clickToShow: "押したら見えるで"
sensitive: "ちょっとアカンやつやで" sensitive: "ちょっとアカンやつやで"
@ -459,6 +460,7 @@ emailConfigInfo: "メールアドレスの確認とかパスワードリセッ
smtpHost: "ホスト" smtpHost: "ホスト"
smtpUser: "ユーザー名" smtpUser: "ユーザー名"
smtpPass: "パスワード" smtpPass: "パスワード"
notificationSettingDesc: "表示する通知の種類えらんでや。"
emailVerified: "メールアドレスは確認されたで" emailVerified: "メールアドレスは確認されたで"
pageLikesCount: "Pageにええやんと思った数" pageLikesCount: "Pageにええやんと思った数"
pageLikedCount: "Pageにええやんと思ってくれた数" pageLikedCount: "Pageにええやんと思ってくれた数"
@ -468,6 +470,9 @@ onlineUsersCount: "{n}人が起きとるで"
sendErrorReportsDescription: "オンにしたら、なんか変なことが起きたときにエラーの詳細がMisskeyに共有されて、ソフトウェアの品質向上に役立てられるんや。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれるで。" sendErrorReportsDescription: "オンにしたら、なんか変なことが起きたときにエラーの詳細がMisskeyに共有されて、ソフトウェアの品質向上に役立てられるんや。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれるで。"
youAreRunningUpToDateClient: "今使ってるクライアントが最新やで!" youAreRunningUpToDateClient: "今使ってるクライアントが最新やで!"
newVersionOfClientAvailable: "新しいバージョンのクライアントが使えるで。" newVersionOfClientAvailable: "新しいバージョンのクライアントが使えるで。"
_email:
_follow:
title: "フォローされたで"
_mfm: _mfm:
mention: "メンション" mention: "メンション"
quote: "引用" quote: "引用"

View File

@ -36,6 +36,9 @@ userList: "Tibdarin"
uiLanguage: "Tutlayt n wegrudem" uiLanguage: "Tutlayt n wegrudem"
smtpUser: "Isem n umseqdac" smtpUser: "Isem n umseqdac"
smtpPass: "Awal uffir" smtpPass: "Awal uffir"
_email:
_follow:
title: "Yeṭṭafaṛ-ik·em-id"
_mfm: _mfm:
mention: "Bder" mention: "Bder"
search: "Nadi" search: "Nadi"

View File

@ -53,10 +53,14 @@ files: "ಕಡತಗಳು"
download: "ಜಾಲದಿಂದಿಳಿಸು" download: "ಜಾಲದಿಂದಿಳಿಸು"
driveFileDeleteConfirm: "\"{name}\" ಕಡತವನ್ನು ಅಳಿಸಲು ನೀವು ಬಯಸುವಿರಾ? ಈ ನೋಡಿರಿ ಲಗತ್ತಿಸಲಾದ ಟಿಪ್ಪಣಿ ಸಹ ಕಣ್ಮರೆಯಾಗುತ್ತದೆ." driveFileDeleteConfirm: "\"{name}\" ಕಡತವನ್ನು ಅಳಿಸಲು ನೀವು ಬಯಸುವಿರಾ? ಈ ನೋಡಿರಿ ಲಗತ್ತಿಸಲಾದ ಟಿಪ್ಪಣಿ ಸಹ ಕಣ್ಮರೆಯಾಗುತ್ತದೆ."
unfollowConfirm: "{name}ಅನ್ನು ಹಿಂಬಾಲಿಸದಿರುವುದೇ?" unfollowConfirm: "{name}ಅನ್ನು ಹಿಂಬಾಲಿಸದಿರುವುದೇ?"
pinned: "ಪ್ರೊಫ಼ೈಲಿಗೆ ಅಂಟಿಸು"
instances: "ನಿದರ್ಶನ" instances: "ನಿದರ್ಶನ"
remove: "ಅಳಿಸು" remove: "ಅಳಿಸು"
smtpUser: "ಬಳಕೆಹೆಸರು" smtpUser: "ಬಳಕೆಹೆಸರು"
smtpPass: "ಗುಪ್ತಪದ" smtpPass: "ಗುಪ್ತಪದ"
_email:
_follow:
title: "ಹಿಂಬಾಲಿಸಿದರು"
_mfm: _mfm:
search: "ಹುಡುಕು" search: "ಹುಡುಕು"
_sfx: _sfx:

View File

@ -1,5 +1,6 @@
--- ---
_lang_: "한국어" _lang_: "한국어"
headlineMisskey: "노트로 연결되는 네트워크"
introMisskey: "환영합니다! Misskey 는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n\"노트\" 를 작성해서, 지금 일어나고 있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요📡\n\"리액션\" 기능으로, 친구의 노트에 총알같이 반응을 추가할 수도 있습니다👍\n새로운 세계를 탐험해 보세요🚀" introMisskey: "환영합니다! Misskey 는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n\"노트\" 를 작성해서, 지금 일어나고 있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요📡\n\"리액션\" 기능으로, 친구의 노트에 총알같이 반응을 추가할 수도 있습니다👍\n새로운 세계를 탐험해 보세요🚀"
monthAndDay: "{month}월 {day}일" monthAndDay: "{month}월 {day}일"
search: "검색" search: "검색"
@ -96,6 +97,7 @@ cantRenote: "이 게시물은 Renote할 수 없습니다."
cantReRenote: "Renote를 Renote할 수 없습니다." cantReRenote: "Renote를 Renote할 수 없습니다."
quote: "인용" quote: "인용"
pinnedNote: "고정해놓은 노트" pinnedNote: "고정해놓은 노트"
pinned: "프로필에 고정"
you: "당신" you: "당신"
clickToShow: "클릭하여 보기" clickToShow: "클릭하여 보기"
sensitive: "열람주의" sensitive: "열람주의"
@ -436,6 +438,7 @@ signinWith: "{x}로 로그인"
signinFailed: "로그인할 수 없습니다. 사용자명과 비밀번호를 확인하여 주십시오." signinFailed: "로그인할 수 없습니다. 사용자명과 비밀번호를 확인하여 주십시오."
tapSecurityKey: "보안 키를 터치" tapSecurityKey: "보안 키를 터치"
or: "혹은" or: "혹은"
language: "언어"
uiLanguage: "UI 표시 언어" uiLanguage: "UI 표시 언어"
groupInvited: "그룹에 초대되었습니다" groupInvited: "그룹에 초대되었습니다"
aboutX: "{x}에 대하여" aboutX: "{x}에 대하여"
@ -687,6 +690,14 @@ deleteConfirm: "삭제하시겠습니까?"
invalidValue: "올바른 값이 아닙니다." invalidValue: "올바른 값이 아닙니다."
registry: "레지스트리" registry: "레지스트리"
closeAccount: "계정 폐쇄" closeAccount: "계정 폐쇄"
usageAmount: "사용량"
capacity: "용량"
inUse: "사용중"
editCode: "코드 수정"
apply: "적용"
_email:
_follow:
title: "새로운 팔로워가 있습니다"
_registry: _registry:
scope: "범위" scope: "범위"
key: "키" key: "키"
@ -834,6 +845,7 @@ _theme:
deleteConstantConfirm: "상수 {const}를 삭제하시겠습니까?" deleteConstantConfirm: "상수 {const}를 삭제하시겠습니까?"
keys: keys:
accent: "강조 색상" accent: "강조 색상"
panel: "패널"
link: "링크" link: "링크"
hashtag: "해시태그" hashtag: "해시태그"
mention: "멘션" mention: "멘션"
@ -1126,6 +1138,7 @@ _pages:
created: "페이지를 만들었습니다" created: "페이지를 만들었습니다"
updated: "페이지를 수정했습니다" updated: "페이지를 수정했습니다"
deleted: "페이지가 삭제되었습니다" deleted: "페이지가 삭제되었습니다"
pageSetting: "페이지 설정"
nameAlreadyExists: "지정한 페이지 URL이 이미 존재합니다" nameAlreadyExists: "지정한 페이지 URL이 이미 존재합니다"
invalidNameTitle: "유효하지 않은 페이지 URL입니다" invalidNameTitle: "유효하지 않은 페이지 URL입니다"
invalidNameText: "비어있지 않은지 확인해주세요" invalidNameText: "비어있지 않은지 확인해주세요"
@ -1136,6 +1149,7 @@ _pages:
unlike: "좋아요 해제" unlike: "좋아요 해제"
my: "내 페이지" my: "내 페이지"
liked: "좋아요한 페이지" liked: "좋아요한 페이지"
featured: "인기"
inspector: "인스펙터" inspector: "인스펙터"
contents: "콘텐츠" contents: "콘텐츠"
content: "페이지 블록" content: "페이지 블록"
@ -1191,7 +1205,10 @@ _pages:
id: "캔버스 ID" id: "캔버스 ID"
width: "폭" width: "폭"
height: "높이" height: "높이"
note: "노트필기"
_note: _note:
id: "노트 ID"
idDescription: "노트 URL을 붙여넣어 설정할 수도 있습니다."
detailed: "세부 정보 보기" detailed: "세부 정보 보기"
switch: "스위치" switch: "스위치"
_switch: _switch:
@ -1434,7 +1451,9 @@ _deck:
swapDown: "아래로 이동" swapDown: "아래로 이동"
stackLeft: "왼쪽에 쌓기" stackLeft: "왼쪽에 쌓기"
popRight: "오른쪽으로 빼기" popRight: "오른쪽으로 빼기"
profile: "프로파일"
_columns: _columns:
main: "메인"
widgets: "위젯" widgets: "위젯"
notifications: "알림" notifications: "알림"
tl: "타임라인" tl: "타임라인"

View File

@ -93,6 +93,7 @@ cantRenote: "Ten wpis nie może zostać udostępniony."
cantReRenote: "Udostępnienie nie może zostać udostępnione." cantReRenote: "Udostępnienie nie może zostać udostępnione."
quote: "Cytuj" quote: "Cytuj"
pinnedNote: "Przypięty wpis" pinnedNote: "Przypięty wpis"
pinned: "Przypnij do profilu"
you: "Ty" you: "Ty"
clickToShow: "Kliknij, aby wyświetlić" clickToShow: "Kliknij, aby wyświetlić"
sensitive: "NSFW" sensitive: "NSFW"
@ -643,6 +644,9 @@ backgroundColor: "Tło"
accentColor: "Akcent" accentColor: "Akcent"
textColor: "Tekst" textColor: "Tekst"
value: "Wartość" value: "Wartość"
_email:
_follow:
title: "Zaobserwował(a) Cię"
_registry: _registry:
key: "Klucz" key: "Klucz"
keys: "Klucz" keys: "Klucz"

View File

@ -97,6 +97,7 @@ cantRenote: "Это нельзя репостить."
cantReRenote: "Невозможно репостить репост." cantReRenote: "Невозможно репостить репост."
quote: "Цитата" quote: "Цитата"
pinnedNote: "Закреплённая заметка" pinnedNote: "Закреплённая заметка"
pinned: "Закрепить в профиле"
you: "Вы" you: "Вы"
clickToShow: "Нажмите для просмотра" clickToShow: "Нажмите для просмотра"
sensitive: "Содержимое не для всех" sensitive: "Содержимое не для всех"
@ -437,6 +438,7 @@ signinWith: "Использовать {x} для входа"
signinFailed: "Невозможно войти в систему. Введенное вами имя пользователя или пароль неверны." signinFailed: "Невозможно войти в систему. Введенное вами имя пользователя или пароль неверны."
tapSecurityKey: "Нажмите на свой электронный ключ" tapSecurityKey: "Нажмите на свой электронный ключ"
or: "или" or: "или"
language: "Язык"
uiLanguage: "Язык интерфейса" uiLanguage: "Язык интерфейса"
groupInvited: "Приглашение в группу" groupInvited: "Приглашение в группу"
aboutX: "Описание {x}" aboutX: "Описание {x}"
@ -701,6 +703,13 @@ inUse: "Занято"
editCode: "Редактировать исходный текст" editCode: "Редактировать исходный текст"
apply: "Применить" apply: "Применить"
receiveAnnouncementFromInstance: "Получать оповещения с инстанса" receiveAnnouncementFromInstance: "Получать оповещения с инстанса"
emailNotification: "Уведомления по электронной почте"
inChannelSearch: "Поиск по каналу"
_email:
_follow:
title: "Новый подписчик"
_receiveFollowRequest:
title: "Новый запрос на подписку."
_plugin: _plugin:
install: "Установка расширений" install: "Установка расширений"
installWarn: "Пожалуйста, не устанавливайте расширения, которым не доверяете." installWarn: "Пожалуйста, не устанавливайте расширения, которым не доверяете."

View File

@ -1,7 +1,7 @@
--- ---
_lang_: "Українська" _lang_: "Українська"
headlineMisskey: "Мережа об'єднана записами" headlineMisskey: "Мережа об'єднана записами"
introMisskey: "Ласкаво просимо! Misskey - децентралізована служба мікроблогів з відкритим кодом.\nСтворюйте \"записи\", щоб поділитися тим, що відбувається, і розповісти всім про себе 📡\nЗа допомогою \"реакцій\" ви також можете швидко висловити свої почуття щодо записів інших 👍\nДавайте досліджувати новий світ 🚀" introMisskey: "Ласкаво просимо! Misskey - децентралізована служба мікроблогів з відкритим кодом.\nСтворюйте \"нотатки\", щоб поділитися тим, що відбувається, і розповісти всім про себе 📡\nЗа допомогою \"реакцій\" ви також можете швидко висловити свої почуття щодо нотаток інших 👍\nДосліджуймо новий світ! 🚀"
monthAndDay: "{month}/{day}" monthAndDay: "{month}/{day}"
search: "Пошук" search: "Пошук"
notifications: "Сповіщення" notifications: "Сповіщення"
@ -97,6 +97,7 @@ cantRenote: "Неможливо поширити."
cantReRenote: "Поширення не можливо поширити." cantReRenote: "Поширення не можливо поширити."
quote: "Цитата" quote: "Цитата"
pinnedNote: "Закріплений запис" pinnedNote: "Закріплений запис"
pinned: "Закріпити"
you: "Ви" you: "Ви"
clickToShow: "Натисніть для перегляду" clickToShow: "Натисніть для перегляду"
sensitive: "NSFW" sensitive: "NSFW"
@ -688,6 +689,9 @@ deleteConfirm: "Ви дійсно бажаєте це видалити?"
invalidValue: "Некоректне значення." invalidValue: "Некоректне значення."
registry: "Реєстр" registry: "Реєстр"
closeAccount: "Закрити обліковий запис" closeAccount: "Закрити обліковий запис"
_email:
_follow:
title: "Новий підписник"
_registry: _registry:
key: "Ключ" key: "Ключ"
keys: "Ключі" keys: "Ключі"
@ -978,6 +982,7 @@ _weekday:
friday: "П'ятниця" friday: "П'ятниця"
saturday: "Субота" saturday: "Субота"
_widgets: _widgets:
memo: "Нагадування"
notifications: "Сповіщення" notifications: "Сповіщення"
timeline: "Стрічка" timeline: "Стрічка"
calendar: "Календар" calendar: "Календар"
@ -991,7 +996,10 @@ _widgets:
postForm: "Створення нотатки" postForm: "Створення нотатки"
slideshow: "Слайд-шоу" slideshow: "Слайд-шоу"
button: "Кнопка" button: "Кнопка"
onlineUsers: "Користувачі онлайн"
jobQueue: "Черга завдань" jobQueue: "Черга завдань"
serverMetric: "Показники сервера "
aiscript: "Консоль AiScript"
_cw: _cw:
hide: "Сховати" hide: "Сховати"
show: "Показати більше" show: "Показати більше"
@ -999,10 +1007,13 @@ _cw:
files: "{count} файлів" files: "{count} файлів"
_poll: _poll:
noOnlyOneChoice: "Потрібні принаймні два варіанти." noOnlyOneChoice: "Потрібні принаймні два варіанти."
choiceN: "Варіант {n}"
noMore: "Більше варіантів додати не можна" noMore: "Більше варіантів додати не можна"
canMultipleVote: "Можна вибрати кілька варіантів" canMultipleVote: "Можна вибрати кілька варіантів"
expiration: "Опитування закінчується" expiration: "Опитування закінчується"
infinite: "Ніколи" infinite: "Ніколи"
at: "На даті..."
after: "Через..."
deadlineDate: "Дата закінчення" deadlineDate: "Дата закінчення"
deadlineTime: "г" deadlineTime: "г"
duration: "Тривалість" duration: "Тривалість"

View File

@ -97,6 +97,7 @@ cantRenote: "该帖子无法转发。"
cantReRenote: "转发无法被再次转发。" cantReRenote: "转发无法被再次转发。"
quote: "引用" quote: "引用"
pinnedNote: "已置顶的帖子" pinnedNote: "已置顶的帖子"
pinned: "置顶"
you: "您" you: "您"
clickToShow: "点击以显示" clickToShow: "点击以显示"
sensitive: "敏感内容" sensitive: "敏感内容"
@ -308,7 +309,7 @@ monthX: "{month}月"
yearX: "{year}年" yearX: "{year}年"
pages: "页面" pages: "页面"
integration: "关联" integration: "关联"
connectSerice: "连接" connectSerice: "连接"
disconnectSerice: "断开连接" disconnectSerice: "断开连接"
enableLocalTimeline: "启用本地时间线功能" enableLocalTimeline: "启用本地时间线功能"
enableGlobalTimeline: "启用全局时间线" enableGlobalTimeline: "启用全局时间线"
@ -320,7 +321,7 @@ proxyRemoteFiles: "代理远程文件"
proxyRemoteFilesDescription: "启用此设置后,由于超出存储容量而导致未保存被删除的远程文件将被本地代理,并且会生成缩略图。不会影响服务器的存储。" proxyRemoteFilesDescription: "启用此设置后,由于超出存储容量而导致未保存被删除的远程文件将被本地代理,并且会生成缩略图。不会影响服务器的存储。"
driveCapacityPerLocalAccount: "每个用户的网盘空间" driveCapacityPerLocalAccount: "每个用户的网盘空间"
driveCapacityPerRemoteAccount: "每个远程用户的网盘容量" driveCapacityPerRemoteAccount: "每个远程用户的网盘容量"
inMb: "以兆字节(Mbps)为单位" inMb: "以兆字节(MegaByte)为单位"
iconUrl: "图标URL" iconUrl: "图标URL"
bannerUrl: "Banner URL" bannerUrl: "Banner URL"
basicInfo: "基本信息" basicInfo: "基本信息"
@ -437,6 +438,7 @@ signinWith: "以{x}登录"
signinFailed: "无法登录,请检查您的用户名和密码。" signinFailed: "无法登录,请检查您的用户名和密码。"
tapSecurityKey: "轻触硬件安全密钥" tapSecurityKey: "轻触硬件安全密钥"
or: "或者" or: "或者"
language: "语言"
uiLanguage: "显示语言" uiLanguage: "显示语言"
groupInvited: "群组招待" groupInvited: "群组招待"
aboutX: "关于 {x}" aboutX: "关于 {x}"
@ -577,7 +579,7 @@ smtpPort: "端口"
smtpUser: "用户名" smtpUser: "用户名"
smtpPass: "密码" smtpPass: "密码"
emptyToDisableSmtpAuth: "用户名和密码留空可以禁用SMTP验证" emptyToDisableSmtpAuth: "用户名和密码留空可以禁用SMTP验证"
smtpSecure: "在 SMTP 连接中使用隐式 SSL / TLS" smtpSecure: "在 SMTP 连接中默认使用 SSL / TLS"
smtpSecureInfo: "使用STARTTLS时关闭。" smtpSecureInfo: "使用STARTTLS时关闭。"
testEmail: "邮件发送测试" testEmail: "邮件发送测试"
wordMute: "文字屏蔽" wordMute: "文字屏蔽"
@ -636,8 +638,8 @@ repliedCount: "回复数"
renotedCount: "转发数" renotedCount: "转发数"
followingCount: "正在关注数量" followingCount: "正在关注数量"
followersCount: "关注者数量" followersCount: "关注者数量"
sentReactionsCount: "发送应数" sentReactionsCount: "发送应数"
receivedReactionsCount: "收到应数" receivedReactionsCount: "收到应数"
pollVotesCount: "问卷调查的投票数" pollVotesCount: "问卷调查的投票数"
pollVotedCount: "问卷调查的被投票数" pollVotedCount: "问卷调查的被投票数"
yes: "是" yes: "是"
@ -701,6 +703,18 @@ inUse: "已使用"
editCode: "编辑代码" editCode: "编辑代码"
apply: "应用" apply: "应用"
receiveAnnouncementFromInstance: "从实例接收通知" receiveAnnouncementFromInstance: "从实例接收通知"
emailNotification: "邮件通知"
inChannelSearch: "频道内搜索"
useReactionPickerForContextMenu: "单击右键打开回应工具栏"
typingUsers: "{users}正在输入"
jumpToSpecifiedDate: "跳转到特定日期"
showingPastTimeline: "显示过去的时间线"
clear: "清除"
_email:
_follow:
title: "你有新的关注者"
_receiveFollowRequest:
title: "收到关注请求"
_plugin: _plugin:
install: "安装插件" install: "安装插件"
installWarn: "请不要安装不可信的插件。" installWarn: "请不要安装不可信的插件。"

View File

@ -97,6 +97,7 @@ cantRenote: "無法轉發此貼文。"
cantReRenote: "無法轉發之前已經轉發過的內容" cantReRenote: "無法轉發之前已經轉發過的內容"
quote: "引用" quote: "引用"
pinnedNote: "已置頂的貼文" pinnedNote: "已置頂的貼文"
pinned: "置頂"
you: "您" you: "您"
clickToShow: "按一下以顯示" clickToShow: "按一下以顯示"
sensitive: "敏感內容" sensitive: "敏感內容"
@ -163,6 +164,7 @@ storageUsage: "已使用容量"
charts: "圖表" charts: "圖表"
perHour: "每小時" perHour: "每小時"
perDay: "每日" perDay: "每日"
stopActivityDelivery: "停止發送活動"
blockThisInstance: "封鎖此實例" blockThisInstance: "封鎖此實例"
operations: "操作" operations: "操作"
software: "軟體" software: "軟體"
@ -582,6 +584,7 @@ channel: "頻道"
create: "新增" create: "新增"
notificationSetting: "通知設定" notificationSetting: "通知設定"
notificationSettingDesc: "選擇顯示通知的類型" notificationSettingDesc: "選擇顯示通知的類型"
useGlobalSetting: "使用全域設定"
other: "其他" other: "其他"
regenerateLoginToken: "再生登入權杖" regenerateLoginToken: "再生登入權杖"
regenerateLoginTokenDescription: "再生用於登入的內部權杖。一般情況下是不需要這樣做的。一旦再生,所有裝置將會被登出。" regenerateLoginTokenDescription: "再生用於登入的內部權杖。一般情況下是不需要這樣做的。一旦再生,所有裝置將會被登出。"
@ -674,6 +677,9 @@ newVersionOfClientAvailable: "新版本的用戶端可用。"
usageAmount: "使用量" usageAmount: "使用量"
capacity: "容量" capacity: "容量"
inUse: "已使用" inUse: "已使用"
_email:
_follow:
title: "您有新的追隨者"
_registry: _registry:
scope: "範圍" scope: "範圍"
key: "機碼" key: "機碼"

View File

@ -1,7 +1,7 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>", "author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.69.0", "version": "12.71.0",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",
@ -62,9 +62,9 @@
"@types/is-url": "1.2.28", "@types/is-url": "1.2.28",
"@types/js-yaml": "4.0.0", "@types/js-yaml": "4.0.0",
"@types/jsdom": "16.2.6", "@types/jsdom": "16.2.6",
"@types/jsonld": "1.5.2", "@types/jsonld": "1.5.3",
"@types/katex": "0.11.0", "@types/katex": "0.11.0",
"@types/koa": "2.11.7", "@types/koa": "2.13.0",
"@types/koa-bodyparser": "4.3.0", "@types/koa-bodyparser": "4.3.0",
"@types/koa-cors": "0.0.0", "@types/koa-cors": "0.0.0",
"@types/koa-favicon": "2.0.19", "@types/koa-favicon": "2.0.19",
@ -77,8 +77,8 @@
"@types/koa__router": "8.0.4", "@types/koa__router": "8.0.4",
"@types/markdown-it": "12.0.1", "@types/markdown-it": "12.0.1",
"@types/matter-js": "0.14.10", "@types/matter-js": "0.14.10",
"@types/mocha": "8.2.0", "@types/mocha": "8.2.1",
"@types/node": "14.14.25", "@types/node": "14.14.31",
"@types/node-fetch": "2.5.8", "@types/node-fetch": "2.5.8",
"@types/nodemailer": "6.4.0", "@types/nodemailer": "6.4.0",
"@types/nprogress": "0.2.0", "@types/nprogress": "0.2.0",
@ -110,23 +110,23 @@
"@typescript-eslint/parser": "4.14.2", "@typescript-eslint/parser": "4.14.2",
"@vue/compiler-sfc": "3.0.5", "@vue/compiler-sfc": "3.0.5",
"abort-controller": "3.0.0", "abort-controller": "3.0.0",
"apexcharts": "3.24.0", "apexcharts": "3.25.0",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autosize": "4.0.2", "autosize": "4.0.2",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.840.0", "aws-sdk": "2.848.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "1.1.3", "blurhash": "1.1.3",
"broadcast-channel": "3.4.1", "broadcast-channel": "3.4.1",
"bull": "3.20.1", "bull": "3.20.1",
"cafy": "15.2.1", "cafy": "15.2.1",
"cbor": "6.0.1", "cbor": "7.0.1",
"chalk": "4.1.0", "chalk": "4.1.0",
"chart.js": "2.9.4", "chart.js": "2.9.4",
"cli-highlight": "2.1.10", "cli-highlight": "2.1.10",
"commander": "4.1.1", "commander": "4.1.1",
"content-disposition": "0.5.3", "content-disposition": "0.5.3",
"core-js": "3.8.3", "core-js": "3.9.0",
"crc-32": "1.2.0", "crc-32": "1.2.0",
"css-loader": "5.0.2", "css-loader": "5.0.2",
"cssnano": "4.1.10", "cssnano": "4.1.10",
@ -134,8 +134,8 @@
"diskusage": "1.1.3", "diskusage": "1.1.3",
"double-ended-queue": "2.1.0-0", "double-ended-queue": "2.1.0-0",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"eslint": "7.19.0", "eslint": "7.20.0",
"eslint-plugin-vue": "7.5.0", "eslint-plugin-vue": "7.6.0",
"eventemitter3": "4.0.7", "eventemitter3": "4.0.7",
"feed": "4.2.2", "feed": "4.2.2",
"fibers": "5.0.0", "fibers": "5.0.0",
@ -163,7 +163,7 @@
"jsdom": "16.4.0", "jsdom": "16.4.0",
"json5": "2.2.0", "json5": "2.2.0",
"json5-loader": "4.0.1", "json5-loader": "4.0.1",
"jsonld": "3.3.0", "jsonld": "4.0.1",
"jsrsasign": "8.0.20", "jsrsasign": "8.0.20",
"katex": "0.12.0", "katex": "0.12.0",
"koa": "2.13.1", "koa": "2.13.1",
@ -194,7 +194,7 @@
"parsimmon": "1.16.0", "parsimmon": "1.16.0",
"pg": "8.5.1", "pg": "8.5.1",
"portscanner": "2.2.0", "portscanner": "2.2.0",
"postcss": "8.2.5", "postcss": "8.2.6",
"postcss-loader": "5.0.0", "postcss-loader": "5.0.0",
"prismjs": "1.23.0", "prismjs": "1.23.0",
"probe-image-size": "6.0.0", "probe-image-size": "6.0.0",
@ -218,7 +218,7 @@
"rimraf": "3.0.2", "rimraf": "3.0.2",
"rndstr": "1.0.0", "rndstr": "1.0.0",
"s-age": "1.1.2", "s-age": "1.1.2",
"sass": "1.32.6", "sass": "1.32.8",
"sass-loader": "11.0.1", "sass-loader": "11.0.1",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"sharp": "0.27.1", "sharp": "0.27.1",
@ -234,11 +234,11 @@
"throttle-debounce": "3.0.1", "throttle-debounce": "3.0.1",
"tinycolor2": "1.4.2", "tinycolor2": "1.4.2",
"tmp": "0.2.1", "tmp": "0.2.1",
"ts-loader": "8.0.16", "ts-loader": "8.0.17",
"ts-node": "9.1.1", "ts-node": "9.1.1",
"tslint": "6.1.3", "tslint": "6.1.3",
"tslint-sonarts": "1.9.0", "tslint-sonarts": "1.9.0",
"typeorm": "0.2.30", "typeorm": "0.2.31",
"typescript": "4.1.5", "typescript": "4.1.5",
"ulid": "2.3.0", "ulid": "2.3.0",
"url-loader": "4.1.1", "url-loader": "4.1.1",
@ -254,7 +254,7 @@
"vue-style-loader": "4.1.2", "vue-style-loader": "4.1.2",
"vuedraggable": "4.0.1", "vuedraggable": "4.0.1",
"web-push": "3.4.4", "web-push": "3.4.4",
"webpack": "5.21.2", "webpack": "5.23.0",
"webpack-cli": "4.5.0", "webpack-cli": "4.5.0",
"websocket": "1.0.33", "websocket": "1.0.33",
"ws": "7.4.3", "ws": "7.4.3",

View File

@ -17,6 +17,7 @@ const data = localStorage.getItem('account');
export const $i = data ? reactive(JSON.parse(data) as Account) : null; export const $i = data ? reactive(JSON.parse(data) as Account) : null;
export async function signout() { export async function signout() {
waiting();
localStorage.removeItem('account'); localStorage.removeItem('account');
//#region Remove account //#region Remove account
@ -42,7 +43,7 @@ export async function signout() {
document.cookie = `igi=; path=/`; document.cookie = `igi=; path=/`;
if (accounts.length > 0) login(accounts[0].token); if (accounts.length > 0) login(accounts[0].token);
else location.href = '/'; else unisonReload(true);
} }
export async function getAccounts(): Promise<{ id: Account['id'], token: Account['token'] }[]> { export async function getAccounts(): Promise<{ id: Account['id'], token: Account['token'] }[]> {
@ -92,16 +93,16 @@ export function refreshAccount() {
return fetchAccount($i.token).then(updateAccount); return fetchAccount($i.token).then(updateAccount);
} }
export async function login(token: Account['token'], href?: string) { export async function login(token: Account['token'], redirect?: string) {
waiting(); waiting();
if (_DEV_) console.log('logging as token ', token); if (_DEV_) console.log('logging as token ', token);
const me = await fetchAccount(token); const me = await fetchAccount(token);
localStorage.setItem('account', JSON.stringify(me)); localStorage.setItem('account', JSON.stringify(me));
await addAccount(me.id, token); await addAccount(me.id, token);
if (href) { if (redirect) {
reloadChannel.postMessage('reload'); reloadChannel.postMessage('reload');
location.href = href; location.href = redirect;
return; return;
} }

View File

@ -756,7 +756,13 @@ export default defineComponent({
}; };
if (isLink(e.target)) return; if (isLink(e.target)) return;
if (window.getSelection().toString() !== '') return; if (window.getSelection().toString() !== '') return;
if (this.$store.state.useReactionPickerForContextMenu) {
e.preventDefault();
this.react();
} else {
os.contextMenu(this.getMenu(), e).then(this.focus); os.contextMenu(this.getMenu(), e).then(this.focus);
}
}, },
menu(viaKeyboard = false) { menu(viaKeyboard = false) {

View File

@ -731,7 +731,13 @@ export default defineComponent({
}; };
if (isLink(e.target)) return; if (isLink(e.target)) return;
if (window.getSelection().toString() !== '') return; if (window.getSelection().toString() !== '') return;
if (this.$store.state.useReactionPickerForContextMenu) {
e.preventDefault();
this.react();
} else {
os.contextMenu(this.getMenu(), e).then(this.focus); os.contextMenu(this.getMenu(), e).then(this.focus);
}
}, },
menu(viaKeyboard = false) { menu(viaKeyboard = false) {

View File

@ -8,10 +8,10 @@
<MkError v-if="error" @retry="init()"/> <MkError v-if="error" @retry="init()"/>
<div v-show="more && reversed" style="margin-bottom: var(--margin);"> <div v-show="more && reversed" style="margin-bottom: var(--margin);">
<button class="_buttonPrimary" @click="fetchMoreFeature" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> <MkButton style="margin: 0 auto;" @click="fetchMoreFeature" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $ts.loadMore }}</template> <template v-if="!moreFetching">{{ $ts.loadMore }}</template>
<template v-if="moreFetching"><MkLoading inline/></template> <template v-if="moreFetching"><MkLoading inline/></template>
</button> </MkButton>
</div> </div>
<XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed"> <XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed">
@ -19,10 +19,10 @@
</XList> </XList>
<div v-show="more && !reversed" style="margin-top: var(--margin);"> <div v-show="more && !reversed" style="margin-top: var(--margin);">
<button class="_buttonPrimary" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> <MkButton style="margin: 0 auto;" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $ts.loadMore }}</template> <template v-if="!moreFetching">{{ $ts.loadMore }}</template>
<template v-if="moreFetching"><MkLoading inline/></template> <template v-if="moreFetching"><MkLoading inline/></template>
</button> </MkButton>
</div> </div>
</div> </div>
</template> </template>
@ -32,10 +32,11 @@ import { defineComponent } from 'vue';
import paging from '@/scripts/paging'; import paging from '@/scripts/paging';
import XNote from './note.vue'; import XNote from './note.vue';
import XList from './date-separated-list.vue'; import XList from './date-separated-list.vue';
import MkButton from '@/components/ui/button.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
XNote, XList, XNote, XList, MkButton,
}, },
mixins: [ mixins: [

View File

@ -115,14 +115,14 @@ export default defineComponent({
this.connection = os.stream.useSharedConnection('main'); this.connection = os.stream.useSharedConnection('main');
//
// notification.isRead
this.connection.on('readAllNotifications', () => { this.connection.on('readAllNotifications', () => {
this.readObserver.unobserve(this.$el); this.readObserver.unobserve(this.$el);
this.notification = markNotificationRead(this.notification);
}); });
this.connection.on('readNotifications', notificationIds => { this.connection.on('readNotifications', notificationIds => {
if (notificationIds.includes(this.notification.id)) { if (notificationIds.includes(this.notification.id)) {
this.readObserver.unobserve(this.$el); this.readObserver.unobserve(this.$el);
this.notification = markNotificationRead(this.notification);
} }
}) })
} }

View File

@ -70,6 +70,7 @@ import * as os from '@/os';
import { selectFile } from '@/scripts/select-file'; import { selectFile } from '@/scripts/select-file';
import { notePostInterruptors, postFormActions } from '@/store'; import { notePostInterruptors, postFormActions } from '@/store';
import { isMobile } from '@/scripts/is-mobile'; import { isMobile } from '@/scripts/is-mobile';
import { throttle } from 'throttle-debounce';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -160,6 +161,11 @@ export default defineComponent({
quoteId: null, quoteId: null,
recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'), recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'),
imeText: '', imeText: '',
typing: throttle(3000, () => {
if (this.channel) {
os.stream.send('typingOnChannel', { channel: this.channel.id });
}
}),
postFormActions, postFormActions,
faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faPollH, faGlobe, faHome, faUnlock, faEnvelope, faEyeSlash, faLaughSquint, faPlus, faPhotoVideo, faAt, faBiohazard, faPlug faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faPollH, faGlobe, faHome, faUnlock, faEnvelope, faEyeSlash, faLaughSquint, faPlus, faPhotoVideo, faAt, faBiohazard, faPlug
}; };
@ -462,10 +468,12 @@ export default defineComponent({
onKeydown(e: KeyboardEvent) { onKeydown(e: KeyboardEvent) {
if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post(); if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post();
if (e.which === 27) this.$emit('esc'); if (e.which === 27) this.$emit('esc');
this.typing();
}, },
onCompositionUpdate(e: CompositionEvent) { onCompositionUpdate(e: CompositionEvent) {
this.imeText = e.data; this.imeText = e.data;
this.typing();
}, },
onCompositionEnd(e: CompositionEvent) { onCompositionEnd(e: CompositionEvent) {

View File

@ -70,6 +70,7 @@ export default defineComponent({
// TODO: ResizeObserver // TODO: ResizeObserver
new ResizeObserver((entries, observer) => { new ResizeObserver((entries, observer) => {
const rect = this.src.getBoundingClientRect(); const rect = this.src.getBoundingClientRect();
const width = popover.offsetWidth; const width = popover.offsetWidth;
const height = popover.offsetHeight; const height = popover.offsetHeight;

View File

@ -66,12 +66,15 @@ import { search } from '@/scripts/search';
import { isMobile } from '@/scripts/is-mobile'; import { isMobile } from '@/scripts/is-mobile';
import { getThemes } from '@/theme-store'; import { getThemes } from '@/theme-store';
import { initializeSw } from '@/scripts/initialize-sw'; import { initializeSw } from '@/scripts/initialize-sw';
import { reloadChannel } from '@/scripts/unison-reload'; import { reload, reloadChannel } from '@/scripts/unison-reload';
import { deleteLoginId } from '@/scripts/login-id'; import { deleteLoginId } from '@/scripts/login-id';
import { getAccountFromId } from '@/scripts/get-account-from-id'; import { getAccountFromId } from '@/scripts/get-account-from-id';
console.info(`Misskey v${version}`); console.info(`Misskey v${version}`);
// boot.jsのやつを解除
window.onerror = null;
if (_DEV_) { if (_DEV_) {
console.warn('Development mode!!!'); console.warn('Development mode!!!');
@ -117,7 +120,7 @@ if (defaultStore.state.reportError && !_DEV_) {
document.addEventListener('touchend', () => {}, { passive: true }); document.addEventListener('touchend', () => {}, { passive: true });
// 一斉リロード // 一斉リロード
reloadChannel.addEventListener('message', () => location.reload()); reloadChannel.addEventListener('message', reload);
//#region SEE: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ //#region SEE: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
// TODO: いつの日にか消したい // TODO: いつの日にか消したい

View File

@ -7,6 +7,7 @@
v-model="text" v-model="text"
ref="text" ref="text"
@keypress="onKeypress" @keypress="onKeypress"
@compositionupdate="onCompositionUpdate"
@paste="onPaste" @paste="onPaste"
:placeholder="$ts.inputMessageHere" :placeholder="$ts.inputMessageHere"
></textarea> ></textarea>
@ -29,6 +30,7 @@ import { formatTimeString } from '../../../misc/format-time-string';
import { selectFile } from '@/scripts/select-file'; import { selectFile } from '@/scripts/select-file';
import * as os from '@/os'; import * as os from '@/os';
import { Autocomplete } from '@/scripts/autocomplete'; import { Autocomplete } from '@/scripts/autocomplete';
import { throttle } from 'throttle-debounce';
export default defineComponent({ export default defineComponent({
props: { props: {
@ -46,6 +48,9 @@ export default defineComponent({
text: null, text: null,
file: null, file: null,
sending: false, sending: false,
typing: throttle(3000, () => {
os.stream.send('typingOnMessaging', this.user ? { partner: this.user.id } : { group: this.group.id });
}),
faPaperPlane, faPhotoVideo, faLaughSquint faPaperPlane, faPhotoVideo, faLaughSquint
}; };
}, },
@ -147,11 +152,16 @@ export default defineComponent({
}, },
onKeypress(e) { onKeypress(e) {
this.typing();
if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canSend) { if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canSend) {
this.send(); this.send();
} }
}, },
onCompositionUpdate() {
this.typing();
},
chooseFile(e) { chooseFile(e) {
selectFile(e.currentTarget || e.target, this.$ts.selectFile, false).then(file => { selectFile(e.currentTarget || e.target, this.$ts.selectFile, false).then(file => {
this.file = file; this.file = file;

View File

@ -16,6 +16,14 @@
</XList> </XList>
</div> </div>
<footer> <footer>
<div class="typers" v-if="typers.length > 0">
<I18n :src="$ts.typingUsers" text-tag="span" class="users">
<template #users>
<b v-for="user in typers" :key="user.id" class="user">{{ user.username }}</b>
</template>
</I18n>
<MkEllipsis/>
</div>
<transition name="fade"> <transition name="fade">
<div class="new-message" v-show="showIndicator"> <div class="new-message" v-show="showIndicator">
<button class="_buttonPrimary" @click="onIndicatorClick"><i><Fa :icon="faArrowCircleDown"/></i>{{ $ts.newMessageExists }}</button> <button class="_buttonPrimary" @click="onIndicatorClick"><i><Fa :icon="faArrowCircleDown"/></i>{{ $ts.newMessageExists }}</button>
@ -86,6 +94,7 @@ const Component = defineComponent({
connection: null, connection: null,
showIndicator: false, showIndicator: false,
timer: null, timer: null,
typers: [],
ilObserver: new IntersectionObserver( ilObserver: new IntersectionObserver(
(entries) => entries.some((entry) => entry.isIntersecting) (entries) => entries.some((entry) => entry.isIntersecting)
&& !this.fetching && !this.fetching
@ -142,6 +151,9 @@ const Component = defineComponent({
this.connection.on('message', this.onMessage); this.connection.on('message', this.onMessage);
this.connection.on('read', this.onRead); this.connection.on('read', this.onRead);
this.connection.on('deleted', this.onDeleted); this.connection.on('deleted', this.onDeleted);
this.connection.on('typers', typers => {
this.typers = typers.filter(u => u.id !== this.$i.id);
});
document.addEventListener('visibilitychange', this.onVisibilitychange); document.addEventListener('visibilitychange', this.onVisibilitychange);
@ -397,6 +409,7 @@ export default Component;
> footer { > footer {
width: 100%; width: 100%;
position: relative;
> .new-message { > .new-message {
position: absolute; position: absolute;
@ -422,6 +435,25 @@ export default Component;
} }
} }
} }
> .typers {
position: absolute;
bottom: 100%;
padding: 0 8px 0 8px;
font-size: 0.9em;
color: var(--fgTransparentWeak);
> .users {
> .user + .user:before {
content: ", ";
font-weight: normal;
}
> .user:last-of-type:after {
content: " ";
}
}
}
} }
} }

View File

@ -28,6 +28,7 @@ export default defineComponent({
limit: 10, limit: 10,
params: () => ({ params: () => ({
query: this.$route.query.q, query: this.$route.query.q,
channelId: this.$route.query.channel,
}) })
}, },
}; };

View File

@ -60,7 +60,7 @@ export default defineComponent({
} }
}).then(({ canceled, result: password }) => { }).then(({ canceled, result: password }) => {
if (canceled) return; if (canceled) return;
os.api('i/update-email', { os.apiWithDialog('i/update-email', {
password: password, password: password,
email: this.emailAddress, email: this.emailAddress,
}); });

View File

@ -19,6 +19,7 @@
<template #label>{{ $ts.behavior }}</template> <template #label>{{ $ts.behavior }}</template>
<FormSwitch v-model:value="imageNewTab">{{ $ts.openImageInNewTab }}</FormSwitch> <FormSwitch v-model:value="imageNewTab">{{ $ts.openImageInNewTab }}</FormSwitch>
<FormSwitch v-model:value="enableInfiniteScroll">{{ $ts.enableInfiniteScroll }}</FormSwitch> <FormSwitch v-model:value="enableInfiniteScroll">{{ $ts.enableInfiniteScroll }}</FormSwitch>
<FormSwitch v-model:value="useReactionPickerForContextMenu">{{ $ts.useReactionPickerForContextMenu }}</FormSwitch>
<FormSwitch v-model:value="disablePagesScript">{{ $ts.disablePagesScript }}</FormSwitch> <FormSwitch v-model:value="disablePagesScript">{{ $ts.disablePagesScript }}</FormSwitch>
</FormGroup> </FormGroup>
@ -144,6 +145,7 @@ export default defineComponent({
chatOpenBehavior: ColdDeviceStorage.makeGetterSetter('chatOpenBehavior'), chatOpenBehavior: ColdDeviceStorage.makeGetterSetter('chatOpenBehavior'),
instanceTicker: defaultStore.makeGetterSetter('instanceTicker'), instanceTicker: defaultStore.makeGetterSetter('instanceTicker'),
enableInfiniteScroll: defaultStore.makeGetterSetter('enableInfiniteScroll'), enableInfiniteScroll: defaultStore.makeGetterSetter('enableInfiniteScroll'),
useReactionPickerForContextMenu: defaultStore.makeGetterSetter('useReactionPickerForContextMenu'),
}, },
watch: { watch: {

View File

@ -192,8 +192,6 @@ export default (opts) => ({
this.items = this.items.slice(-opts.displayLimit); this.items = this.items.slice(-opts.displayLimit);
this.more = true; this.more = true;
} }
} else {
} }
this.items.push(item); this.items.push(item);
// TODO // TODO

View File

@ -1,10 +1,18 @@
// SafariがBroadcastChannel未実装なのでライブラリを使う // SafariがBroadcastChannel未実装なのでライブラリを使う
import { BroadcastChannel } from 'broadcast-channel'; import { BroadcastChannel } from 'broadcast-channel';
export const reloadChannel = new BroadcastChannel<'reload'>('reload'); export const reloadChannel = new BroadcastChannel<boolean>('reload');
// BroadcastChannelを用いて、クライアントが一斉にreloadするようにします。 // BroadcastChannelを用いて、クライアントが一斉にreloadするようにします。
export function unisonReload() { export function unisonReload(redirectToRoot: boolean = false) {
reloadChannel.postMessage('reload'); reloadChannel.postMessage(!!redirectToRoot);
reload();
}
export function reload(redirectToRoot: boolean = false) {
if (redirectToRoot) {
location.href = '/';
} else {
location.reload(); location.reload();
} }
}

View File

@ -144,6 +144,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device', where: 'device',
default: true default: true
}, },
useReactionPickerForContextMenu: {
where: 'device',
default: true
},
showGapBetweenNotesInTimeline: { showGapBetweenNotesInTimeline: {
where: 'device', where: 'device',
default: true default: true

View File

@ -32,7 +32,7 @@ export default defineComponent({
}); });
} }
return h(TransitionGroup, { return h(this.reversed ? 'div' : TransitionGroup, {
class: 'hmjzthxl', class: 'hmjzthxl',
name: this.reversed ? 'list-reversed' : 'list', name: this.reversed ? 'list-reversed' : 'list',
tag: 'div', tag: 'div',

View File

@ -1,12 +1,15 @@
<template> <template>
<div class="_monospace"> <div class="acemodlh _monospace">
<span> <div>
<span v-text="y"></span>/<span v-text="m"></span>/<span v-text="d"></span>
</div>
<div>
<span v-text="hh"></span> <span v-text="hh"></span>
<span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
<span v-text="mm"></span> <span v-text="mm"></span>
<span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
<span v-text="ss"></span> <span v-text="ss"></span>
</span> </div>
</div> </div>
</template> </template>
@ -18,6 +21,9 @@ export default defineComponent({
data() { data() {
return { return {
clock: null, clock: null,
y: null,
m: null,
d: null,
hh: null, hh: null,
mm: null, mm: null,
ss: null, ss: null,
@ -34,6 +40,9 @@ export default defineComponent({
methods: { methods: {
tick() { tick() {
const now = new Date(); const now = new Date();
this.y = now.getFullYear().toString();
this.m = (now.getMonth() + 1).toString().padStart(2, '0');
this.d = now.getDate().toString().padStart(2, '0');
this.hh = now.getHours().toString().padStart(2, '0'); this.hh = now.getHours().toString().padStart(2, '0');
this.mm = now.getMinutes().toString().padStart(2, '0'); this.mm = now.getMinutes().toString().padStart(2, '0');
this.ss = now.getSeconds().toString().padStart(2, '0'); this.ss = now.getSeconds().toString().padStart(2, '0');
@ -42,3 +51,12 @@ export default defineComponent({
} }
}); });
</script> </script>
<style lang="scss" scoped>
.acemodlh {
opacity: 0.7;
font-size: 0.85em;
line-height: 1em;
text-align: center;
}
</style>

View File

@ -99,7 +99,13 @@
<div class="right"> <div class="right">
<div class="instance">{{ instanceName }}</div> <div class="instance">{{ instanceName }}</div>
<XHeaderClock class="clock"/> <XHeaderClock class="clock"/>
<button class="_button button search" @click="search" v-tooltip="$ts.search"> <button class="_button button timetravel" @click="timetravel" v-tooltip="$ts.jumpToSpecifiedDate">
<Fa :icon="faCalendarAlt"/>
</button>
<button class="_button button search" v-if="tl.startsWith('channel:') && currentChannel" @click="inChannelSearch" v-tooltip="$ts.inChannelSearch">
<Fa :icon="faSearch"/>
</button>
<button class="_button button search" v-else @click="search" v-tooltip="$ts.search">
<Fa :icon="faSearch"/> <Fa :icon="faSearch"/>
</button> </button>
<button class="_button button follow" v-if="tl.startsWith('channel:') && currentChannel" :class="{ followed: currentChannel.isFollowing }" @click="toggleChannelFollow" v-tooltip="currentChannel.isFollowing ? $ts.unfollow : $ts.follow"> <button class="_button button follow" v-if="tl.startsWith('channel:') && currentChannel" :class="{ followed: currentChannel.isFollowing }" @click="toggleChannelFollow" v-tooltip="currentChannel.isFollowing ? $ts.unfollow : $ts.follow">
@ -111,14 +117,9 @@
</button> </button>
</div> </div>
</header> </header>
<div class="body">
<XTimeline v-if="tl.startsWith('channel:')" src="channel" :key="tl" :channel="tl.replace('channel:', '')"/> <XTimeline class="body" ref="tl" v-if="tl.startsWith('channel:')" src="channel" :key="tl" :channel="tl.replace('channel:', '')"/>
<XTimeline v-else :src="tl" :key="tl"/> <XTimeline class="body" ref="tl" v-else :src="tl" :key="tl"/>
</div>
<footer class="footer">
<XPostForm v-if="tl.startsWith('channel:')" :key="tl" :channel="tl.replace('channel:', '')"/>
<XPostForm v-else/>
</footer>
</main> </main>
<XSide class="side" ref="side" @open="sideViewOpening = true" @close="sideViewOpening = false"/> <XSide class="side" ref="side" @open="sideViewOpening = true" @close="sideViewOpening = false"/>
@ -133,20 +134,20 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, defineAsyncComponent } from 'vue'; import { defineComponent, defineAsyncComponent } from 'vue';
import { faLayerGroup, faBars, faHome, faCircle, faWindowMaximize, faColumns, faPencilAlt, faShareAlt, faSatelliteDish, faListUl, faSatellite, faCog, faSearch, faPlus, faStar, faAt, faLink, faEllipsisH, faGlobe } from '@fortawesome/free-solid-svg-icons'; import { faLayerGroup, faBars, faHome, faCircle, faWindowMaximize, faColumns, faPencilAlt, faShareAlt, faSatelliteDish, faListUl, faSatellite, faCog, faSearch, faPlus, faStar, faAt, faLink, faEllipsisH, faGlobe } from '@fortawesome/free-solid-svg-icons';
import { faBell, faStar as farStar, faEnvelope, faComments } from '@fortawesome/free-regular-svg-icons'; import { faBell, faStar as farStar, faEnvelope, faComments, faCalendarAlt } from '@fortawesome/free-regular-svg-icons';
import { instanceName, url } from '@/config'; import { instanceName, url } from '@/config';
import XSidebar from '@/components/sidebar.vue'; import XSidebar from '@/components/sidebar.vue';
import XWidgets from './widgets.vue'; import XWidgets from './widgets.vue';
import XCommon from '../_common_/common.vue'; import XCommon from '../_common_/common.vue';
import XSide from './side.vue'; import XSide from './side.vue';
import XTimeline from './timeline.vue'; import XTimeline from './timeline.vue';
import XPostForm from './post-form.vue';
import XHeaderClock from './header-clock.vue'; import XHeaderClock from './header-clock.vue';
import * as os from '@/os'; import * as os from '@/os';
import { router } from '@/router'; import { router } from '@/router';
import { sidebarDef } from '@/sidebar'; import { sidebarDef } from '@/sidebar';
import { search } from '@/scripts/search'; import { search } from '@/scripts/search';
import copyToClipboard from '@/scripts/copy-to-clipboard'; import copyToClipboard from '@/scripts/copy-to-clipboard';
import { store } from './store';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -155,7 +156,6 @@ export default defineComponent({
XWidgets, XWidgets,
XSide, // NOTE: dynamic importAsyncComponentWrapperref XSide, // NOTE: dynamic importAsyncComponentWrapperref
XTimeline, XTimeline,
XPostForm,
XHeaderClock, XHeaderClock,
}, },
@ -186,7 +186,7 @@ export default defineComponent({
data() { data() {
return { return {
tl: 'home', tl: store.state.tl,
lists: null, lists: null,
antennas: null, antennas: null,
followedChannels: null, followedChannels: null,
@ -195,7 +195,7 @@ export default defineComponent({
menuDef: sidebarDef, menuDef: sidebarDef,
sideViewOpening: false, sideViewOpening: false,
instanceName, instanceName,
faLayerGroup, faBars, faBell, faHome, faCircle, faPencilAlt, faShareAlt, faSatelliteDish, faListUl, faSatellite, faCog, faSearch, faPlus, faStar, farStar, faAt, faLink, faEllipsisH, faGlobe, faComments, faEnvelope, faLayerGroup, faBars, faBell, faHome, faCircle, faPencilAlt, faShareAlt, faSatelliteDish, faListUl, faSatellite, faCog, faSearch, faPlus, faStar, farStar, faAt, faLink, faEllipsisH, faGlobe, faComments, faEnvelope, faCalendarAlt,
}; };
}, },
@ -219,11 +219,12 @@ export default defineComponent({
this.antennas = antennas; this.antennas = antennas;
}); });
os.api('channels/followed').then(channels => { os.api('channels/followed', { limit: 20 }).then(channels => {
this.followedChannels = channels; this.followedChannels = channels;
}); });
os.api('channels/featured').then(channels => { // TODO: pagination
os.api('channels/featured', { limit: 20 }).then(channels => {
this.featuredChannels = channels; this.featuredChannels = channels;
}); });
@ -233,6 +234,7 @@ export default defineComponent({
this.currentChannel = channel; this.currentChannel = channel;
}); });
} }
store.set('tl', this.tl);
}, { immediate: true }); }, { immediate: true });
}, },
@ -245,10 +247,31 @@ export default defineComponent({
os.post(); os.post();
}, },
async timetravel() {
const { canceled, result: date } = await os.dialog({
title: this.$ts.date,
input: {
type: 'date'
}
});
if (canceled) return;
this.$refs.tl.timetravel(new Date(date));
},
search() { search() {
search(); search();
}, },
async inChannelSearch() {
const { canceled, result: query } = await os.dialog({
title: this.$ts.inChannelSearch,
input: true
});
if (canceled || query == null || query === '') return;
router.push(`/search?q=${encodeURIComponent(query)}&channel=${this.currentChannel.id}`);
},
top() { top() {
window.scroll({ top: 0, behavior: 'smooth' }); window.scroll({ top: 0, behavior: 'smooth' });
}, },
@ -458,6 +481,9 @@ export default defineComponent({
display: block; display: block;
padding: 6px 8px; padding: 6px 8px;
border-radius: 4px; border-radius: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:hover { &:hover {
text-decoration: none; text-decoration: none;
@ -569,16 +595,6 @@ export default defineComponent({
} }
} }
} }
> .footer {
padding: 0 16px 16px 16px;
}
> .body {
flex: 1;
min-width: 0;
overflow: auto;
}
} }
> .side { > .side {

View File

@ -741,7 +741,13 @@ export default defineComponent({
}; };
if (isLink(e.target)) return; if (isLink(e.target)) return;
if (window.getSelection().toString() !== '') return; if (window.getSelection().toString() !== '') return;
if (this.$store.state.useReactionPickerForContextMenu) {
e.preventDefault();
this.react();
} else {
os.contextMenu(this.getMenu(), e).then(this.focus); os.contextMenu(this.getMenu(), e).then(this.focus);
}
}, },
menu(viaKeyboard = false) { menu(viaKeyboard = false) {
@ -1004,7 +1010,7 @@ export default defineComponent({
flex-shrink: 0; flex-shrink: 0;
display: block; display: block;
position: sticky; position: sticky;
top: 12px; top: 0;
margin: 0 14px 0 0; margin: 0 14px 0 0;
width: 46px; width: 46px;
height: 46px; height: 46px;
@ -1085,6 +1091,7 @@ export default defineComponent({
> .poll { > .poll {
font-size: 80%; font-size: 80%;
max-width: 500px;
} }
> .renote { > .renote {

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="" :ref="mounted"> <div class="">
<div class="_fullinfo" v-if="empty"> <div class="_fullinfo" v-if="empty">
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
<div>{{ $ts.noNotes }}</div> <div>{{ $ts.noNotes }}</div>
@ -8,10 +8,10 @@
<MkError v-if="error" @retry="init()"/> <MkError v-if="error" @retry="init()"/>
<div v-show="more && reversed" style="margin-bottom: var(--margin);"> <div v-show="more && reversed" style="margin-bottom: var(--margin);">
<button class="_buttonPrimary" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> <MkButton style="margin: 0 auto;" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $ts.loadMore }}</template> <template v-if="!moreFetching">{{ $ts.loadMore }}</template>
<template v-if="moreFetching"><MkLoading inline/></template> <template v-if="moreFetching"><MkLoading inline/></template>
</button> </MkButton>
</div> </div>
<XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed"> <XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed">
@ -19,10 +19,10 @@
</XList> </XList>
<div v-show="more && !reversed" style="margin-top: var(--margin);"> <div v-show="more && !reversed" style="margin-top: var(--margin);">
<button class="_buttonPrimary" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> <MkButton style="margin: 0 auto;" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $ts.loadMore }}</template> <template v-if="!moreFetching">{{ $ts.loadMore }}</template>
<template v-if="moreFetching"><MkLoading inline/></template> <template v-if="moreFetching"><MkLoading inline/></template>
</button> </MkButton>
</div> </div>
</div> </div>
</template> </template>
@ -32,10 +32,11 @@ import { defineComponent } from 'vue';
import paging from '@/scripts/paging'; import paging from '@/scripts/paging';
import XNote from './note.vue'; import XNote from './note.vue';
import XList from './date-separated-list.vue'; import XList from './date-separated-list.vue';
import MkButton from '@/components/ui/button.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
XNote, XList, XNote, XList, MkButton,
}, },
mixins: [ mixins: [

View File

@ -65,6 +65,7 @@ import * as os from '@/os';
import { selectFile } from '@/scripts/select-file'; import { selectFile } from '@/scripts/select-file';
import { notePostInterruptors, postFormActions } from '@/store'; import { notePostInterruptors, postFormActions } from '@/store';
import { isMobile } from '@/scripts/is-mobile'; import { isMobile } from '@/scripts/is-mobile';
import { throttle } from 'throttle-debounce';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -131,6 +132,11 @@ export default defineComponent({
quoteId: null, quoteId: null,
recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'), recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'),
imeText: '', imeText: '',
typing: throttle(3000, () => {
if (this.channel) {
os.stream.send('typingOnChannel', { channel: this.channel });
}
}),
postFormActions, postFormActions,
faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faPollH, faGlobe, faHome, faUnlock, faEnvelope, faEyeSlash, faLaughSquint, faPlus, faPhotoVideo, faAt, faBiohazard, faPlug faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faPollH, faGlobe, faHome, faUnlock, faEnvelope, faEyeSlash, faLaughSquint, faPlus, faPhotoVideo, faAt, faBiohazard, faPlug
}; };
@ -421,10 +427,12 @@ export default defineComponent({
onKeydown(e: KeyboardEvent) { onKeydown(e: KeyboardEvent) {
if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post(); if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post();
if (e.which === 27) this.$emit('esc'); if (e.which === 27) this.$emit('esc');
this.typing();
}, },
onCompositionUpdate(e: CompositionEvent) { onCompositionUpdate(e: CompositionEvent) {
this.imeText = e.data; this.imeText = e.data;
this.typing();
}, },
onCompositionEnd(e: CompositionEvent) { onCompositionEnd(e: CompositionEvent) {

View File

@ -10,4 +10,8 @@ export const store = markRaw(new Storage('chatUi', {
data: Record<string, any>; data: Record<string, any>;
}[] }[]
}, },
tl: {
where: 'deviceAccount',
default: 'home'
},
})); }));

View File

@ -1,8 +1,25 @@
<template> <template>
<div class="dbiokgaf"> <div class="dbiokgaf info" v-if="date">
<MkInfo>{{ $ts.showingPastTimeline }} <button class="_textButton clear" @click="timetravel()">{{ $ts.clear }}</button></MkInfo>
</div>
<div class="dbiokgaf top" v-if="['home', 'local', 'social', 'global'].includes(src)">
<XPostForm/>
</div>
<div class="dbiokgaf tl" ref="body">
<div class="new" v-if="queue > 0" :style="{ width: width + 'px', [pagination.reversed ? 'bottom' : 'top']: pagination.reversed ? bottom + 'px' : top + 'px' }"><button class="_buttonPrimary" @click="goTop()">{{ $ts.newNoteRecived }}</button></div> <div class="new" v-if="queue > 0" :style="{ width: width + 'px', [pagination.reversed ? 'bottom' : 'top']: pagination.reversed ? bottom + 'px' : top + 'px' }"><button class="_buttonPrimary" @click="goTop()">{{ $ts.newNoteRecived }}</button></div>
<XNotes class="tl" ref="tl" :pagination="pagination" @queue="queueUpdated" v-follow="pagination.reversed"/> <XNotes class="tl" ref="tl" :pagination="pagination" @queue="queueUpdated" v-follow="pagination.reversed"/>
</div> </div>
<div class="dbiokgaf bottom" v-if="src === 'channel'">
<div class="typers" v-if="typers.length > 0">
<I18n :src="$ts.typingUsers" text-tag="span" class="users">
<template #users>
<b v-for="user in typers" :key="user.id" class="user">{{ user.username }}</b>
</template>
</I18n>
<MkEllipsis/>
</div>
<XPostForm :channel="channel"/>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -12,10 +29,14 @@ import * as os from '@/os';
import * as sound from '@/scripts/sound'; import * as sound from '@/scripts/sound';
import { scrollToBottom, getScrollPosition, getScrollContainer } from '@/scripts/scroll'; import { scrollToBottom, getScrollPosition, getScrollContainer } from '@/scripts/scroll';
import follow from '@/directives/follow-append'; import follow from '@/directives/follow-append';
import XPostForm from './post-form.vue';
import MkInfo from '@/components/ui/info.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
XNotes XNotes,
XPostForm,
MkInfo,
}, },
directives: { directives: {
@ -45,11 +66,6 @@ export default defineComponent({
type: String, type: String,
required: false required: false
}, },
sound: {
type: Boolean,
required: false,
default: false,
}
}, },
emits: ['note', 'queue', 'before', 'after'], emits: ['note', 'queue', 'before', 'after'],
@ -69,6 +85,8 @@ export default defineComponent({
width: 0, width: 0,
top: 0, top: 0,
bottom: 0, bottom: 0,
typers: [],
date: null
}; };
}, },
@ -78,9 +96,7 @@ export default defineComponent({
this.$emit('note'); this.$emit('note');
if (this.sound) {
sound.play(note.userId === this.$i.id ? 'noteMy' : 'note'); sound.play(note.userId === this.$i.id ? 'noteMy' : 'note');
}
}; };
const onUserAdded = () => { const onUserAdded = () => {
@ -166,6 +182,9 @@ export default defineComponent({
channelId: this.channel channelId: this.channel
}); });
this.connection.on('note', prepend); this.connection.on('note', prepend);
this.connection.on('typers', typers => {
this.typers = this.$i ? typers.filter(u => u.id !== this.$i.id) : typers;
});
} }
this.pagination = { this.pagination = {
@ -173,7 +192,7 @@ export default defineComponent({
reversed, reversed,
limit: 10, limit: 10,
params: init => ({ params: init => ({
untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined), untilDate: this.date?.getTime(),
...this.baseQuery, ...this.query ...this.baseQuery, ...this.query
}) })
}; };
@ -190,34 +209,73 @@ export default defineComponent({
methods: { methods: {
focus() { focus() {
this.$refs.tl.focus(); this.$refs.body.focus();
}, },
goTop() { goTop() {
const container = getScrollContainer(this.$el); const container = getScrollContainer(this.$refs.body);
container.scrollTop = 0; container.scrollTop = 0;
}, },
queueUpdated(q) { queueUpdated(q) {
if (this.$el.offsetWidth !== 0) { if (this.$refs.body.offsetWidth !== 0) {
const rect = this.$el.getBoundingClientRect(); const rect = this.$refs.body.getBoundingClientRect();
const scrollTop = getScrollPosition(this.$el); this.width = this.$refs.body.offsetWidth;
this.width = this.$el.offsetWidth; this.top = rect.top;
this.top = rect.top + scrollTop; this.bottom = this.$refs.body.offsetHeight;
this.bottom = this.$el.offsetHeight;
} }
this.queue = q; this.queue = q;
}, },
timetravel(date?: Date) {
this.date = date;
this.$refs.tl.reload();
}
} }
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.dbiokgaf { .dbiokgaf.info{
padding: 16px 0; padding: 16px 16px 0 16px;
}
// TODO: position sticky .dbiokgaf.top {
overflow: hidden; padding: 16px 16px 0 16px;
}
.dbiokgaf.bottom {
padding: 0 16px 16px 16px;
position: relative;
> .typers {
position: absolute;
bottom: 100%;
padding: 0 8px 0 8px;
font-size: 0.9em;
background: var(--panel);
border-radius: 0 8px 0 0;
color: var(--fgTransparentWeak);
> .users {
> .user + .user:before {
content: ", ";
font-weight: normal;
}
> .user:last-of-type:after {
content: " ";
}
}
}
}
.dbiokgaf.tl {
position: relative;
padding: 16px 0;
flex: 1;
min-width: 0;
overflow: auto;
> .new { > .new {
position: fixed; position: fixed;

View File

@ -10,7 +10,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, defineAsyncComponent } from 'vue'; import { defineComponent, defineAsyncComponent } from 'vue';
import XWidgets from '@/components/widgets.vue'; import XWidgets from '@/components/widgets.vue';
import { store } from './store.ts'; import { store } from './store';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -34,6 +34,7 @@ export default defineComponent({
}, },
updateWidget({ id, data }) { updateWidget({ id, data }) {
// TODO: throttle
store.set('widgets', store.state.widgets.map(w => w.id === id ? { store.set('widgets', store.state.widgets.map(w => w.id === id ? {
...w, ...w,
data: data data: data

View File

@ -1,4 +1,5 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { throttle } from 'throttle-debounce';
import { Form } from '@/scripts/form'; import { Form } from '@/scripts/form';
import * as os from '@/os'; import * as os from '@/os';
@ -21,7 +22,10 @@ export default function <T extends Form>(data: {
data() { data() {
return { return {
props: this.widget ? JSON.parse(JSON.stringify(this.widget.data)) : {} props: this.widget ? JSON.parse(JSON.stringify(this.widget.data)) : {},
save: throttle(3000, () => {
this.$emit('updateProps', this.props);
}),
}; };
}, },
@ -66,10 +70,6 @@ export default function <T extends Form>(data: {
this.save(); this.save();
}, },
save() {
this.$emit('updateProps', this.props);
}
} }
}); });
} }

View File

@ -5,19 +5,19 @@
<div class="values"> <div class="values">
<div> <div>
<div>Process</div> <div>Process</div>
<div>{{ number(inbox.activeSincePrevTick) }}</div> <div :class="{ inc: inbox.activeSincePrevTick > prev.inbox.activeSincePrevTick, dec: inbox.activeSincePrevTick < prev.inbox.activeSincePrevTick }">{{ number(inbox.activeSincePrevTick) }}</div>
</div> </div>
<div> <div>
<div>Active</div> <div>Active</div>
<div>{{ number(inbox.active) }}</div> <div :class="{ inc: inbox.active > prev.inbox.active, dec: inbox.active < prev.inbox.active }">{{ number(inbox.active) }}</div>
</div> </div>
<div> <div>
<div>Delayed</div> <div>Delayed</div>
<div>{{ number(inbox.delayed) }}</div> <div :class="{ inc: inbox.delayed > prev.inbox.delayed, dec: inbox.delayed < prev.inbox.delayed }">{{ number(inbox.delayed) }}</div>
</div> </div>
<div> <div>
<div>Waiting</div> <div>Waiting</div>
<div>{{ number(inbox.waiting) }}</div> <div :class="{ inc: inbox.waiting > prev.inbox.waiting, dec: inbox.waiting < prev.inbox.waiting }">{{ number(inbox.waiting) }}</div>
</div> </div>
</div> </div>
</div> </div>
@ -26,19 +26,19 @@
<div class="values"> <div class="values">
<div> <div>
<div>Process</div> <div>Process</div>
<div>{{ number(deliver.activeSincePrevTick) }}</div> <div :class="{ inc: deliver.activeSincePrevTick > prev.deliver.activeSincePrevTick, dec: deliver.activeSincePrevTick < prev.deliver.activeSincePrevTick }">{{ number(deliver.activeSincePrevTick) }}</div>
</div> </div>
<div> <div>
<div>Active</div> <div>Active</div>
<div>{{ number(deliver.active) }}</div> <div :class="{ inc: deliver.active > prev.deliver.active, dec: deliver.active < prev.deliver.active }">{{ number(deliver.active) }}</div>
</div> </div>
<div> <div>
<div>Delayed</div> <div>Delayed</div>
<div>{{ number(deliver.delayed) }}</div> <div :class="{ inc: deliver.delayed > prev.deliver.delayed, dec: deliver.delayed < prev.deliver.delayed }">{{ number(deliver.delayed) }}</div>
</div> </div>
<div> <div>
<div>Waiting</div> <div>Waiting</div>
<div>{{ number(deliver.waiting) }}</div> <div :class="{ inc: deliver.waiting > prev.deliver.waiting, dec: deliver.waiting < prev.deliver.waiting }">{{ number(deliver.waiting) }}</div>
</div> </div>
</div> </div>
</div> </div>
@ -79,10 +79,15 @@ export default defineComponent({
waiting: 0, waiting: 0,
delayed: 0, delayed: 0,
}, },
prev: {},
faExclamationTriangle, faExclamationTriangle,
}; };
}, },
created() { created() {
for (const domain of ['inbox', 'deliver']) {
this.prev[domain] = JSON.parse(JSON.stringify(this[domain]));
}
this.connection.on('stats', this.onStats); this.connection.on('stats', this.onStats);
this.connection.on('statsLog', this.onStatsLog); this.connection.on('statsLog', this.onStatsLog);
@ -99,6 +104,7 @@ export default defineComponent({
methods: { methods: {
onStats(stats) { onStats(stats) {
for (const domain of ['inbox', 'deliver']) { for (const domain of ['inbox', 'deliver']) {
this.prev[domain] = JSON.parse(JSON.stringify(this[domain]));
this[domain].activeSincePrevTick = stats[domain].activeSincePrevTick; this[domain].activeSincePrevTick = stats[domain].activeSincePrevTick;
this[domain].active = stats[domain].active; this[domain].active = stats[domain].active;
this[domain].waiting = stats[domain].waiting; this[domain].waiting = stats[domain].waiting;
@ -152,6 +158,16 @@ export default defineComponent({
> div:first-child { > div:first-child {
opacity: 0.7; opacity: 0.7;
} }
> div:last-child {
&.inc {
color: var(--warn);
}
&.dec {
color: var(--success);
}
}
} }
} }
} }

View File

@ -1,4 +1,4 @@
# Creating plugins # New Plugin
If you use the plugin function of the Misskey web client, you can expand the web client with a variety of different functionality. This page will list metadata definitions for plugin creation as well as an AiScript API reference for plugins. If you use the plugin function of the Misskey web client, you can expand the web client with a variety of different functionality. This page will list metadata definitions for plugin creation as well as an AiScript API reference for plugins.
## Metadata ## Metadata

View File

@ -1,2 +1,2 @@
# フォロー # Ikuti
ユーザーをフォローすると、タイムラインにそのユーザーの投稿が表示されるようになります。ただし、他のユーザーに対する返信は含まれません。 ユーザーをフォローするには、ユーザーページの「フォロー」ボタンをクリックします。フォローを解除するには、もう一度クリックします。 ユーザーをフォローすると、タイムラインにそのユーザーの投稿が表示されるようになります。ただし、他のユーザーに対する返信は含まれません。 ユーザーをフォローするには、ユーザーページの「フォロー」ボタンをクリックします。フォローを解除するには、もう一度クリックします。

View File

@ -1,4 +1,4 @@
# AiScript # AiScript
## 関数 ## Funzione
デフォルトで値渡しです。 デフォルトで値渡しです。

View File

@ -1,7 +1,7 @@
# プラグインの作成 # プラグインの作成
Misskey Webクライアントのプラグイン機能を使うと、クライアントを拡張し、様々な機能を追加できます。 ここではプラグインの作成にあたってのメタデータ定義や、AiScript APIリファレンスを掲載します。 Misskey Webクライアントのプラグイン機能を使うと、クライアントを拡張し、様々な機能を追加できます。 ここではプラグインの作成にあたってのメタデータ定義や、AiScript APIリファレンスを掲載します。
## メタデータ ## Metadato
プラグインは、AiScriptのメタデータ埋め込み機能を使って、デフォルトとしてプラグインのメタデータを定義する必要があります。 メタデータは次のプロパティを含むオブジェクトです。 プラグインは、AiScriptのメタデータ埋め込み機能を使って、デフォルトとしてプラグインのメタデータを定義する必要があります。 メタデータは次のプロパティを含むオブジェクトです。
### name ### name

View File

@ -1,2 +1,2 @@
# フォロー # Seiguiti
ユーザーをフォローすると、タイムラインにそのユーザーの投稿が表示されるようになります。ただし、他のユーザーに対する返信は含まれません。 ユーザーをフォローするには、ユーザーページの「フォロー」ボタンをクリックします。フォローを解除するには、もう一度クリックします。 ユーザーをフォローすると、タイムラインにそのユーザーの投稿が表示されるようになります。ただし、他のユーザーに対する返信は含まれません。 ユーザーをフォローするには、ユーザーページの「フォロー」ボタンをクリックします。フォローを解除するには、もう一度クリックします。

View File

@ -1,6 +1,6 @@
# Pages # Pages
## 変数 ## Variabili
変数を使うことで動的なページを作成できます。テキスト内で <b>{ 変数名 }</b> と書くとそこに変数の値を埋め込めます。例えば <b>Hello { thing } world!</b> というテキストで、変数(thing)の値が <b>ai</b> だった場合、テキストは <b>Hello ai world!</b> になります。 変数を使うことで動的なページを作成できます。テキスト内で <b>{ 変数名 }</b> と書くとそこに変数の値を埋め込めます。例えば <b>Hello { thing } world!</b> というテキストで、変数(thing)の値が <b>ai</b> だった場合、テキストは <b>Hello ai world!</b> になります。
変数の評価(値を算出すること)は上から下に行われるので、ある変数の中で自分より下の変数を参照することはできません。例えば上から <b>A、B、C</b> と3つの変数を定義したとき、<b>C</b>の中で<b>A</b><b>B</b>を参照することはできますが、<b>A</b>の中で<b>B</b><b>C</b>を参照することはできません。 変数の評価(値を算出すること)は上から下に行われるので、ある変数の中で自分より下の変数を参照することはできません。例えば上から <b>A、B、C</b> と3つの変数を定義したとき、<b>C</b>の中で<b>A</b><b>B</b>を参照することはできますが、<b>A</b>の中で<b>B</b><b>C</b>を参照することはできません。

View File

@ -1,4 +1,4 @@
# リアクション # Reazione
他の人のノートに、絵文字を付けて簡単にあなたの反応を伝えられる機能です。 リアクションするには、ノートの + アイコンをクリックしてピッカーを表示し、絵文字を選択します。 リアクションには[カスタム絵文字](./custom-emoji)も使用できます。 他の人のノートに、絵文字を付けて簡単にあなたの反応を伝えられる機能です。 リアクションするには、ノートの + アイコンをクリックしてピッカーを表示し、絵文字を選択します。 リアクションには[カスタム絵文字](./custom-emoji)も使用できます。
## リアクションピッカーのカスタマイズ ## リアクションピッカーのカスタマイズ

View File

@ -31,7 +31,7 @@
**ストリームでのやり取りはすべてJSONです。** **ストリームでのやり取りはすべてJSONです。**
## チャンネル ## Canale
MisskeyのストリーミングAPIにはチャンネルという概念があります。これは、送受信する情報を分離するための仕組みです。 Misskeyのストリームに接続しただけでは、まだリアルタイムでタイムラインの投稿を受信したりはできません。 ストリーム上でチャンネルに接続することで、様々な情報を受け取ったり情報を送信したりすることができるようになります。 MisskeyのストリーミングAPIにはチャンネルという概念があります。これは、送受信する情報を分離するための仕組みです。 Misskeyのストリームに接続しただけでは、まだリアルタイムでタイムラインの投稿を受信したりはできません。 ストリーム上でチャンネルに接続することで、様々な情報を受け取ったり情報を送信したりすることができるようになります。
### チャンネルに接続する ### チャンネルに接続する

View File

@ -1,4 +1,4 @@
# テーマ # Tema
テーマを設定して、Misskeyクライアントの見た目を変更できます。 テーマを設定して、Misskeyクライアントの見た目を変更できます。
@ -61,8 +61,8 @@
* 関数(後述) * 関数(後述)
* `:{関数名}<{引数}<{色}` * `:{関数名}<{引数}<{色}`
#### 定数 #### Costante
「CSS変数として出力はしたくないが、他のCSS変数の値として使いまわしたい」値があるときは、定数を使うと便利です。 キー名を`$`で始めると、そのキーはCSS変数として出力されません。 「CSS変数として出力はしたくないが、他のCSS変数の値として使いまわしたい」値があるときは、定数を使うと便利です。 キー名を`$`で始めると、そのキーはCSS変数として出力されません。
#### 関数 #### Funzione
wip wip

View File

@ -2,10 +2,10 @@
https://docs.google.com/spreadsheets/d/1lxQ2ugKrhz58Bg96HTDK_2F98BUritkMyIiBkOByjHA/edit?usp=sharing https://docs.google.com/spreadsheets/d/1lxQ2ugKrhz58Bg96HTDK_2F98BUritkMyIiBkOByjHA/edit?usp=sharing
## ホーム ## Home
自分のフォローしているユーザーの投稿 自分のフォローしているユーザーの投稿
## ローカル ## Locale
全てのローカルユーザーの「ホーム」指定されていない投稿 全てのローカルユーザーの「ホーム」指定されていない投稿
## ソーシャル ## ソーシャル

View File

@ -18,21 +18,21 @@ APIを使い始めるには、まずアクセストークンを取得する必
### アプリケーション利用者にアクセストークンの発行をリクエストする ### アプリケーション利用者にアクセストークンの発行をリクエストする
アプリケーション利用者のアクセストークンを取得するには、以下の手順で発行をリクエストします。 アプリケーション利用者のアクセストークンを取得するには、以下の手順で発行をリクエストします。
#### Step 1 #### Крок 1
UUIDを生成する。以後これをセッションIDと呼びます。 Створити UUID.以後これをセッションIDと呼びます。
> このセッションIDは毎回生成し、使いまわさないようにしてください。 > このセッションIDは毎回生成し、使いまわさないようにしてください。
#### Step 2 #### Крок 2
`{_URL_}/miauth/{session}`をユーザーのブラウザで表示させる。`{session}`の部分は、セッションIDに置き換えてください。 `{_URL_}/miauth/{session}`をユーザーのブラウザで表示させる。`{session}`の部分は、セッションIDに置き換えてください。
> 例: `{_URL_}/miauth/c1f6d42b-468b-4fd2-8274-e58abdedef6f` > 例: `{_URL_}/miauth/c1f6d42b-468b-4fd2-8274-e58abdedef6f`
表示する際、URLにクエリパラメータとしていくつかのオプションを設定できます: 表示する際、URLにクエリパラメータとしていくつかのオプションを設定できます:
* `name` ... アプリケーション名 * `name` ... Назва додатка
* > 例: `MissDeck` * > 例: `MissDeck`
* `icon` ... アプリケーションのアイコン画像URL * `icon` ... URL піктограми додатка
* > 例: `https://missdeck.example.com/icon.png` * > 例: `https://missdeck.example.com/icon.png`
* `callback` ... 認証が終わった後にリダイレクトするURL * `callback` ... 認証が終わった後にリダイレクトするURL
* > 例: `https://missdeck.example.com/callback` * > 例: `https://missdeck.example.com/callback`
@ -42,7 +42,7 @@ UUIDを生成する。以後これをセッションIDと呼びます。
* 要求する権限を`,`で区切って列挙します * 要求する権限を`,`で区切って列挙します
* どのような権限があるかは[APIリファレンス](/api-doc)で確認できます * どのような権限があるかは[APIリファレンス](/api-doc)で確認できます
#### Step 3 #### Крок 3
ユーザーが発行を許可した後、`{_URL_}/api/miauth/{session}/check`にPOSTリクエストすると、レスポンスとしてアクセストークンを含むJSONが返ります。 ユーザーが発行を許可した後、`{_URL_}/api/miauth/{session}/check`にPOSTリクエストすると、レスポンスとしてアクセストークンを含むJSONが返ります。
レスポンスに含まれるプロパティ: レスポンスに含まれるプロパティ:
@ -51,8 +51,8 @@ UUIDを生成する。以後これをセッションIDと呼びます。
[「APIの使い方」へ進む](#APIの使い方) [「APIの使い方」へ進む](#APIの使い方)
## APIの使い方 ## Використання API
**APIはすべてPOSTで、リクエスト/レスポンスともにJSON形式です。RESTではありません。** アクセストークンは、`i`というパラメータ名でリクエストに含めます。 **APIはすべてPOSTで、リクエスト/レスポンスともにJSON形式です。Підтримка REST відсутня.** アクセストークンは、`i`というパラメータ名でリクエストに含めます。
* [APIリファレンス](/api-doc) * [Довідник API](/api-doc)
* [ストリーミングAPI](./stream) * [Потокове API](./stream)

View File

@ -1,4 +1,4 @@
# プラグインの作成 # Створення плагінів
Misskey Webクライアントのプラグイン機能を使うと、クライアントを拡張し、様々な機能を追加できます。 ここではプラグインの作成にあたってのメタデータ定義や、AiScript APIリファレンスを掲載します。 Misskey Webクライアントのプラグイン機能を使うと、クライアントを拡張し、様々な機能を追加できます。 ここではプラグインの作成にあたってのメタデータ定義や、AiScript APIリファレンスを掲載します。
## Метадані ## Метадані
@ -34,7 +34,7 @@ Misskey Webクライアントのプラグイン機能を使うと、クライア
#### default #### default
設定のデフォルト値 設定のデフォルト値
## APIリファレンス ## Довідник API
AiScript標準で組み込まれているAPIは掲載しません。 AiScript標準で組み込まれているAPIは掲載しません。
### Mk:dialog(title text type) ### Mk:dialog(title text type)

View File

@ -1,4 +1,4 @@
# ストリーミングAPI # Потокове API
ストリーミングAPIを使うと、リアルタイムで様々な情報(例えばタイムラインに新しい投稿が流れてきた、メッセージが届いた、フォローされた、など)を受け取ったり、様々な操作を行ったりすることができます。 ストリーミングAPIを使うと、リアルタイムで様々な情報(例えばタイムラインに新しい投稿が流れてきた、メッセージが届いた、フォローされた、など)を受け取ったり、様々な操作を行ったりすることができます。

View File

@ -85,7 +85,7 @@ export default define(meta, async (ps, user) => {
} }
//#region Construct query //#region Construct query
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('note.channelId = :channelId', { channelId: channel.id }) .andWhere('note.channelId = :channelId', { channelId: channel.id })
.leftJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.channel', 'channel'); .leftJoinAndSelect('note.channel', 'channel');

View File

@ -46,6 +46,11 @@ export const meta = {
validator: $.optional.nullable.type(ID), validator: $.optional.nullable.type(ID),
default: null default: null
}, },
channelId: {
validator: $.optional.nullable.type(ID),
default: null
},
}, },
res: { res: {
@ -64,7 +69,15 @@ export const meta = {
export default define(meta, async (ps, me) => { export default define(meta, async (ps, me) => {
if (es == null) { if (es == null) {
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId);
if (ps.userId) {
query.andWhere('note.userId = :userId', { userId: ps.userId });
} else if (ps.channelId) {
query.andWhere('note.channelId = :channelId', { channelId: ps.channelId });
}
query
.andWhere('note.text ILIKE :q', { q: `%${ps.query}%` }) .andWhere('note.text ILIKE :q', { q: `%${ps.query}%` })
.leftJoinAndSelect('note.user', 'user'); .leftJoinAndSelect('note.user', 'user');

View File

@ -1,14 +1,17 @@
import autobind from 'autobind-decorator'; import autobind from 'autobind-decorator';
import Channel from '../channel'; import Channel from '../channel';
import { Notes } from '../../../../models'; import { Notes, Users } from '../../../../models';
import { isMutedUserRelated } from '../../../../misc/is-muted-user-related'; import { isMutedUserRelated } from '../../../../misc/is-muted-user-related';
import { PackedNote } from '../../../../models/repositories/note'; import { PackedNote } from '../../../../models/repositories/note';
import { User } from '../../../../models/entities/user';
export default class extends Channel { export default class extends Channel {
public readonly chName = 'channel'; public readonly chName = 'channel';
public static shouldShare = false; public static shouldShare = false;
public static requireCredential = false; public static requireCredential = false;
private channelId: string; private channelId: string;
private typers: Record<User['id'], Date> = {};
private emitTypersIntervalId: ReturnType<typeof setInterval>;
@autobind @autobind
public async init(params: any) { public async init(params: any) {
@ -16,6 +19,8 @@ export default class extends Channel {
// Subscribe stream // Subscribe stream
this.subscriber.on('notesStream', this.onNote); this.subscriber.on('notesStream', this.onNote);
this.subscriber.on(`channelStream:${this.channelId}`, this.onEvent);
this.emitTypersIntervalId = setInterval(this.emitTypers, 5000);
} }
@autobind @autobind
@ -41,9 +46,41 @@ export default class extends Channel {
this.send('note', note); this.send('note', note);
} }
@autobind
private onEvent(data: any) {
if (data.type === 'typing') {
const id = data.body;
const begin = this.typers[id] == null;
this.typers[id] = new Date();
if (begin) {
this.emitTypers();
}
}
}
@autobind
private async emitTypers() {
const now = new Date();
// Remove not typing users
for (const [userId, date] of Object.entries(this.typers)) {
if (now.getTime() - date.getTime() > 5000) delete this.typers[userId];
}
const users = await Users.packMany(Object.keys(this.typers), null, { detail: false });
this.send({
type: 'typers',
body: users,
});
}
@autobind @autobind
public dispose() { public dispose() {
// Unsubscribe events // Unsubscribe events
this.subscriber.off('notesStream', this.onNote); this.subscriber.off('notesStream', this.onNote);
this.subscriber.off(`channelStream:${this.channelId}`, this.onEvent);
clearInterval(this.emitTypersIntervalId);
} }
} }

View File

@ -15,7 +15,7 @@ export default class extends Channel {
private gameId: ReversiGame['id'] | null = null; private gameId: ReversiGame['id'] | null = null;
private watchers: Record<User['id'], Date> = {}; private watchers: Record<User['id'], Date> = {};
private emitWatchersIntervalId: any; private emitWatchersIntervalId: ReturnType<typeof setInterval>;
@autobind @autobind
public async init(params: any) { public async init(params: any) {

View File

@ -12,6 +12,9 @@ export default class extends Channel {
private otherpartyId: string | null; private otherpartyId: string | null;
private otherparty?: User; private otherparty?: User;
private groupId: string | null; private groupId: string | null;
private subCh: string;
private typers: Record<User['id'], Date> = {};
private emitTypersIntervalId: ReturnType<typeof setInterval>;
@autobind @autobind
public async init(params: any) { public async init(params: any) {
@ -31,14 +34,28 @@ export default class extends Channel {
} }
} }
const subCh = this.otherpartyId this.emitTypersIntervalId = setInterval(this.emitTypers, 5000);
this.subCh = this.otherpartyId
? `messagingStream:${this.user!.id}-${this.otherpartyId}` ? `messagingStream:${this.user!.id}-${this.otherpartyId}`
: `messagingStream:${this.groupId}`; : `messagingStream:${this.groupId}`;
// Subscribe messaging stream // Subscribe messaging stream
this.subscriber.on(subCh, data => { this.subscriber.on(this.subCh, this.onEvent);
}
@autobind
private onEvent(data: any) {
if (data.type === 'typing') {
const id = data.body;
const begin = this.typers[id] == null;
this.typers[id] = new Date();
if (begin) {
this.emitTypers();
}
} else {
this.send(data); this.send(data);
}); }
} }
@autobind @autobind
@ -60,4 +77,28 @@ export default class extends Channel {
break; break;
} }
} }
@autobind
private async emitTypers() {
const now = new Date();
// Remove not typing users
for (const [userId, date] of Object.entries(this.typers)) {
if (now.getTime() - date.getTime() > 5000) delete this.typers[userId];
}
const users = await Users.packMany(Object.keys(this.typers), null, { detail: false });
this.send({
type: 'typers',
body: users,
});
}
@autobind
public dispose() {
this.subscriber.off(this.subCh, this.onEvent);
clearInterval(this.emitTypersIntervalId);
}
} }

View File

@ -12,6 +12,8 @@ import { Users, Followings, Mutings, UserProfiles, ChannelFollowings } from '../
import { ApiError } from '../error'; import { ApiError } from '../error';
import { AccessToken } from '../../../models/entities/access-token'; import { AccessToken } from '../../../models/entities/access-token';
import { UserProfile } from '../../../models/entities/user-profile'; import { UserProfile } from '../../../models/entities/user-profile';
import { publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from '../../../services/stream';
import { UserGroup } from '../../../models/entities/user-group';
/** /**
* Main stream connection * Main stream connection
@ -27,10 +29,10 @@ export default class Connection {
public subscriber: EventEmitter; public subscriber: EventEmitter;
private channels: Channel[] = []; private channels: Channel[] = [];
private subscribingNotes: any = {}; private subscribingNotes: any = {};
private followingClock: NodeJS.Timer; private followingClock: ReturnType<typeof setInterval>;
private mutingClock: NodeJS.Timer; private mutingClock: ReturnType<typeof setInterval>;
private followingChannelsClock: NodeJS.Timer; private followingChannelsClock: ReturnType<typeof setInterval>;
private userProfileClock: NodeJS.Timer; private userProfileClock: ReturnType<typeof setInterval>;
constructor( constructor(
wsConnection: websocket.connection, wsConnection: websocket.connection,
@ -93,6 +95,12 @@ export default class Connection {
case 'disconnect': this.onChannelDisconnectRequested(body); break; case 'disconnect': this.onChannelDisconnectRequested(body); break;
case 'channel': this.onChannelMessageRequested(body); break; case 'channel': this.onChannelMessageRequested(body); break;
case 'ch': this.onChannelMessageRequested(body); break; // alias case 'ch': this.onChannelMessageRequested(body); break; // alias
// 個々のチャンネルではなくルートレベルでこれらのメッセージを受け取る理由は、
// クライアントの事情を考慮したとき、入力フォームはノートチャンネルやメッセージのメインコンポーネントとは別
// なこともあるため、それらのコンポーネントがそれぞれ各チャンネルに接続するようにするのは面倒なため。
case 'typingOnChannel': this.typingOnChannel(body.channel); break;
case 'typingOnMessaging': this.typingOnMessaging(body); break;
} }
} }
@ -258,6 +266,24 @@ export default class Connection {
} }
} }
@autobind
private typingOnChannel(channel: ChannelModel['id']) {
if (this.user) {
publishChannelStream(channel, 'typing', this.user.id);
}
}
@autobind
private typingOnMessaging(param: { partner?: User['id']; group?: UserGroup['id']; }) {
if (this.user) {
if (param.partner) {
publishMessagingStream(param.partner, this.user.id, 'typing', this.user.id);
} else if (param.group) {
publishGroupMessagingStream(param.group, 'typing', this.user.id);
}
}
}
@autobind @autobind
private async updateFollowing() { private async updateFollowing() {
const followings = await Followings.find({ const followings = await Followings.find({

View File

@ -11,6 +11,10 @@
'use strict'; 'use strict';
window.onerror = (e) => {
document.documentElement.innerHTML = '問題が発生しました。';
};
// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので // ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
(async () => { (async () => {
const v = localStorage.getItem('v') || VERSION; const v = localStorage.getItem('v') || VERSION;

View File

@ -6,6 +6,7 @@ import { ReversiGame } from '../models/entities/games/reversi/game';
import { UserGroup } from '../models/entities/user-group'; import { UserGroup } from '../models/entities/user-group';
import config from '../config'; import config from '../config';
import { Antenna } from '../models/entities/antenna'; import { Antenna } from '../models/entities/antenna';
import { Channel } from '../models/entities/channel';
class Publisher { class Publisher {
private publish = (channel: string, type: string | null, value?: any): void => { private publish = (channel: string, type: string | null, value?: any): void => {
@ -38,6 +39,10 @@ class Publisher {
}); });
} }
public publishChannelStream = (channelId: Channel['id'], type: string, value?: any): void => {
this.publish(`channelStream:${channelId}`, type, typeof value === 'undefined' ? null : value);
}
public publishUserListStream = (listId: UserList['id'], type: string, value?: any): void => { public publishUserListStream = (listId: UserList['id'], type: string, value?: any): void => {
this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value); this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value);
} }
@ -84,6 +89,7 @@ export const publishMainStream = publisher.publishMainStream;
export const publishDriveStream = publisher.publishDriveStream; export const publishDriveStream = publisher.publishDriveStream;
export const publishNoteStream = publisher.publishNoteStream; export const publishNoteStream = publisher.publishNoteStream;
export const publishNotesStream = publisher.publishNotesStream; export const publishNotesStream = publisher.publishNotesStream;
export const publishChannelStream = publisher.publishChannelStream;
export const publishUserListStream = publisher.publishUserListStream; export const publishUserListStream = publisher.publishUserListStream;
export const publishAntennaStream = publisher.publishAntennaStream; export const publishAntennaStream = publisher.publishAntennaStream;
export const publishMessagingStream = publisher.publishMessagingStream; export const publishMessagingStream = publisher.publishMessagingStream;

204
yarn.lock
View File

@ -2,6 +2,13 @@
# yarn lockfile v1 # yarn lockfile v1
"@babel/code-frame@7.12.11":
version "7.12.11"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==
dependencies:
"@babel/highlight" "^7.10.4"
"@babel/code-frame@^7.0.0": "@babel/code-frame@^7.0.0":
version "7.8.3" version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
@ -36,6 +43,15 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80"
integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g== integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==
"@babel/highlight@^7.10.4":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.12.13.tgz#8ab538393e00370b26271b01fa08f7f27f2e795c"
integrity sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==
dependencies:
"@babel/helper-validator-identifier" "^7.12.11"
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/highlight@^7.8.3": "@babel/highlight@^7.8.3":
version "7.9.0" version "7.9.0"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079"
@ -587,10 +603,10 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
"@types/jsonld@1.5.2": "@types/jsonld@1.5.3":
version "1.5.2" version "1.5.3"
resolved "https://registry.yarnpkg.com/@types/jsonld/-/jsonld-1.5.2.tgz#6de5589f72a73be07f2d0a99a39337f4c4518a78" resolved "https://registry.yarnpkg.com/@types/jsonld/-/jsonld-1.5.3.tgz#5bef463655522eef0530db0011bdc119573b759b"
integrity sha512-e6mgulD3RB3lrjGN6Vvu3ixpRSZ2Qwvsm0oSQIuHsAQUvRklrqnmvKNNyXcQTh/PbB+TrHVVmdLHeYFCH52QnA== integrity sha512-xDjKgwTBOrdevh46a6dota4Lusrn0R6lC1EKEQkBhTrOx4xzGpQKZ/JsXPx7NP2N9qVj7AYo6k5ubWtXAYJkLw==
"@types/katex@0.11.0": "@types/katex@0.11.0":
version "0.11.0" version "0.11.0"
@ -679,10 +695,10 @@
"@types/koa-compose" "*" "@types/koa-compose" "*"
"@types/node" "*" "@types/node" "*"
"@types/koa@2.11.7": "@types/koa@2.13.0":
version "2.11.7" version "2.13.0"
resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.11.7.tgz#3b38f3b9faa66315a84890a771d166fb36463100" resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.13.0.tgz#6356c48521c0941103b6fcfb97bb01426a99d56d"
integrity sha512-1iXJZZWCePoMe9LGSIPWsu5k5RI4ooXijW78c+nljMn3YbUts8PXoEESu1OeFmrazLPl1l97vTxzwvmH32TWVQ== integrity sha512-hNs1Z2lX+R5sZroIy/WIGbPlH/719s/Nd5uIjSIAdHn9q+g7z6mxOnhwMjK1urE4/NUP0SOoYUOD4MnvD9FRNw==
dependencies: dependencies:
"@types/accepts" "*" "@types/accepts" "*"
"@types/content-disposition" "*" "@types/content-disposition" "*"
@ -748,10 +764,10 @@
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/mocha@8.2.0": "@types/mocha@8.2.1":
version "8.2.0" version "8.2.1"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.0.tgz#3eb56d13a1de1d347ecb1957c6860c911704bc44" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.1.tgz#f3f3ae4590c5386fc7c543aae9b78d4cf30ffee9"
integrity sha512-/Sge3BymXo4lKc31C8OINJgXLaw+7vL1/L1pGiBNpGrBiT8FQiaFpSYV0uhTaG4y78vcMBTMFsWaHDvuD+xGzQ== integrity sha512-NysN+bNqj6E0Hv4CTGWSlPzMW6vTKjDpOteycDkV4IWBsO+PU48JonrPzV9ODjiI2XrjmA05KInLgF5ivZ/YGQ==
"@types/node-fetch@2.5.8": "@types/node-fetch@2.5.8":
version "2.5.8" version "2.5.8"
@ -766,10 +782,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.13.tgz#9e425079799322113ae8477297ae6ef51b8e0cdf" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.13.tgz#9e425079799322113ae8477297ae6ef51b8e0cdf"
integrity sha512-vbxr0VZ8exFMMAjCW8rJwaya0dMCDyYW2ZRdTyjtrCvJoENMpdUHOT/eTzvgyA5ZnqRZ/sI0NwqAxNHKYokLJQ== integrity sha512-vbxr0VZ8exFMMAjCW8rJwaya0dMCDyYW2ZRdTyjtrCvJoENMpdUHOT/eTzvgyA5ZnqRZ/sI0NwqAxNHKYokLJQ==
"@types/node@14.14.25": "@types/node@14.14.31":
version "14.14.25" version "14.14.31"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.25.tgz#15967a7b577ff81383f9b888aa6705d43fbbae93" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055"
integrity sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ== integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==
"@types/nodemailer@6.4.0": "@types/nodemailer@6.4.0":
version "6.4.0" version "6.4.0"
@ -1531,10 +1547,10 @@ anymatch@~3.1.1:
normalize-path "^3.0.0" normalize-path "^3.0.0"
picomatch "^2.0.4" picomatch "^2.0.4"
apexcharts@3.24.0: apexcharts@3.25.0:
version "3.24.0" version "3.25.0"
resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.24.0.tgz#0fc513e940448524ae9702d39ec287567522d1eb" resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.25.0.tgz#f3f0f9f344f997230f5c7f2918408aa072627496"
integrity sha512-iT6czJCIVrmAtrcO90MZTQCvC+xi6R6Acf0jNH/d40FVTtCfcqECuKIh5iAMyOTtgUb7+fQ8rbadH2bm1kbL9Q== integrity sha512-uM7OF+jLL4ba79noYcrMwMgJW8DI+Ff28CCQoGq23g25z8nGSQEoU+u12YWlECA9gBA5tbmdaQhMxjlK+M6B9Q==
dependencies: dependencies:
svg.draggable.js "^2.2.2" svg.draggable.js "^2.2.2"
svg.easing.js "^2.0.0" svg.easing.js "^2.0.0"
@ -1790,10 +1806,10 @@ autwh@0.1.0:
dependencies: dependencies:
oauth "0.9.15" oauth "0.9.15"
aws-sdk@2.840.0: aws-sdk@2.848.0:
version "2.840.0" version "2.848.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.840.0.tgz#f5529c9bd3bf0be7f8e855a23ff9c12b1705418f" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.848.0.tgz#5e7706ddd30a55a2d5a5b64c29682a757607ee64"
integrity sha512-ngesHJqb0PXYjJNnCsAX4yLkR6JFQJB+3eDGwh3mYRjcq9voix5RfbCFQT1lwWu7bcMBPCrRIA2lJkkTMYXq+A== integrity sha512-c/e5kaEFl+9aYkrYDkmu5mSZlL+EfP6DnBOMD06fH12gIsaFSMBGtbsDTHABhvSu++LxeI1dJAD148O17MuZvg==
dependencies: dependencies:
buffer "4.9.2" buffer "4.9.2"
events "1.1.1" events "1.1.1"
@ -1885,11 +1901,6 @@ big.js@^5.2.2:
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
bignumber.js@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5"
integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==
binary-extensions@^2.0.0: binary-extensions@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
@ -2240,13 +2251,12 @@ caseless@~0.12.0:
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
cbor@6.0.1: cbor@7.0.1:
version "6.0.1" version "7.0.1"
resolved "https://registry.yarnpkg.com/cbor/-/cbor-6.0.1.tgz#f559abb1b986f54fb9cb1a6855085847bcc1cd61" resolved "https://registry.yarnpkg.com/cbor/-/cbor-7.0.1.tgz#b939a0ae1ce9bb76338e1d193ab2ccd9a85d55d0"
integrity sha512-gVJ2e/DFInWOriOUqNyrZe5xN8RSK49X7G+pLalz32GwKs1xHNXtrkcbV5K4+Z2X7qJiv6f700PnUEaJoIEPGQ== integrity sha512-+SVEDS4B2x9aat+if8rtUbm8WdxArH2/tVKiSD8eCxy7hnVNlESd4EQQM16EOFrUCvSHECscsvq61N1FPejZtw==
dependencies: dependencies:
bignumber.js "^9.0.1" nofilter "^2.0.0"
nofilter "^1.0.4"
center-align@^0.1.1: center-align@^0.1.1:
version "0.1.3" version "0.1.3"
@ -2866,10 +2876,10 @@ copy-to@^2.0.1:
resolved "https://registry.yarnpkg.com/copy-to/-/copy-to-2.0.1.tgz#2680fbb8068a48d08656b6098092bdafc906f4a5" resolved "https://registry.yarnpkg.com/copy-to/-/copy-to-2.0.1.tgz#2680fbb8068a48d08656b6098092bdafc906f4a5"
integrity sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU= integrity sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU=
core-js@3.8.3: core-js@3.9.0:
version "3.8.3" version "3.9.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.8.3.tgz#c21906e1f14f3689f93abcc6e26883550dd92dd0" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.9.0.tgz#790b1bb11553a2272b36e2625c7179db345492f8"
integrity sha512-KPYXeVZYemC2TkNEkX/01I+7yd+nX3KddKwZ1Ww7SKWdI2wQprSgLmrTddT8nw92AjEklTsPBoSdQBhbI1bQ6Q== integrity sha512-PyFBJaLq93FlyYdsndE5VaueA9K5cNB7CGzeCj191YYLhkQM0gdZR2SKihM70oF0wdqKSKClv/tEBOpoRmdOVQ==
core-util-is@1.0.2, core-util-is@~1.0.0: core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2" version "1.0.2"
@ -3799,15 +3809,15 @@ escodegen@^1.14.1:
optionalDependencies: optionalDependencies:
source-map "~0.6.1" source-map "~0.6.1"
eslint-plugin-vue@7.5.0: eslint-plugin-vue@7.6.0:
version "7.5.0" version "7.6.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-7.5.0.tgz#cc6d983eb22781fa2440a7573cf39af439bb5725" resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-7.6.0.tgz#ea616e6dfd45d545adb16cba628c5a992cc31f0b"
integrity sha512-QnMMTcyV8PLxBz7QQNAwISSEs6LYk2LJvGlxalXvpCtfKnqo7qcY0aZTIxPe8QOnHd7WCwiMZLOJzg6A03T0Gw== integrity sha512-qYpKwAvpcQXyUXVcG8Zd+fxHDx9iSgTQuO7dql7Ug/2BCvNNDr6s3I9p8MoUo23JJdO7ZAjW3vSwY/EBf4uBcw==
dependencies: dependencies:
eslint-utils "^2.1.0" eslint-utils "^2.1.0"
natural-compare "^1.4.0" natural-compare "^1.4.0"
semver "^7.3.2" semver "^7.3.2"
vue-eslint-parser "^7.4.1" vue-eslint-parser "^7.5.0"
eslint-scope@^5.0.0, eslint-scope@^5.1.1: eslint-scope@^5.0.0, eslint-scope@^5.1.1:
version "5.1.1" version "5.1.1"
@ -3834,12 +3844,12 @@ eslint-visitor-keys@^2.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
eslint@7.19.0: eslint@7.20.0:
version "7.19.0" version "7.20.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.19.0.tgz#6719621b196b5fad72e43387981314e5d0dc3f41" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.20.0.tgz#db07c4ca4eda2e2316e7aa57ac7fc91ec550bdc7"
integrity sha512-CGlMgJY56JZ9ZSYhJuhow61lMPPjUzWmChFya71Z/jilVos7mR/jPgaEfVGgMBY5DshbKdG8Ezb8FDCHcoMEMg== integrity sha512-qGi0CTcOGP2OtCQBgWZlQjcTuP0XkIpYFj25XtRTQSHC+umNnp7UMshr2G8SLsRFYDdAPFeHOsiteadmMH02Yw==
dependencies: dependencies:
"@babel/code-frame" "^7.0.0" "@babel/code-frame" "7.12.11"
"@eslint/eslintrc" "^0.3.0" "@eslint/eslintrc" "^0.3.0"
ajv "^6.10.0" ajv "^6.10.0"
chalk "^4.0.0" chalk "^4.0.0"
@ -3851,7 +3861,7 @@ eslint@7.19.0:
eslint-utils "^2.1.0" eslint-utils "^2.1.0"
eslint-visitor-keys "^2.0.0" eslint-visitor-keys "^2.0.0"
espree "^7.3.1" espree "^7.3.1"
esquery "^1.2.0" esquery "^1.4.0"
esutils "^2.0.2" esutils "^2.0.2"
file-entry-cache "^6.0.0" file-entry-cache "^6.0.0"
functional-red-black-tree "^1.0.1" functional-red-black-tree "^1.0.1"
@ -3905,10 +3915,10 @@ esprima@^4.0.0, esprima@^4.0.1:
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
esquery@^1.0.1, esquery@^1.2.0: esquery@^1.4.0:
version "1.3.1" version "1.4.0"
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5"
integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==
dependencies: dependencies:
estraverse "^5.1.0" estraverse "^5.1.0"
@ -5032,11 +5042,6 @@ idb-keyval@5.0.2:
resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-5.0.2.tgz#243cf2b7db1bee2a8a41b78c14a18a85db0e1646" resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-5.0.2.tgz#243cf2b7db1bee2a8a41b78c14a18a85db0e1646"
integrity sha512-1DYjY/nX2U9pkTkwFoAmKcK1ZWmkNgO32Oon9tp/9+HURizxUQ4fZRxMJZs093SldP7q6dotVj03kIkiqOILyA== integrity sha512-1DYjY/nX2U9pkTkwFoAmKcK1ZWmkNgO32Oon9tp/9+HURizxUQ4fZRxMJZs093SldP7q6dotVj03kIkiqOILyA==
idb-keyval@5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-5.0.1.tgz#d3913debfb58edee299da5cf2dded6c2670c05ef"
integrity sha512-bfi+Znn6oSPPgGcVUj2tYMIOQ5TD6V1qj50SdKQecGZx9lqUATcQ7ArHOt9sPcEhACoYe//yr2igmS6SMc59SA==
ieee754@1.1.13, ieee754@^1.1.13, ieee754@^1.1.4: ieee754@1.1.13, ieee754@^1.1.13, ieee754@^1.1.4:
version "1.1.13" version "1.1.13"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
@ -5734,10 +5739,10 @@ json5@^2.1.2, json5@^2.1.3:
dependencies: dependencies:
minimist "^1.2.5" minimist "^1.2.5"
jsonld@3.3.0: jsonld@4.0.1:
version "3.3.0" version "4.0.1"
resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-3.3.0.tgz#1b03fe1458f9ffc2918fc30e90320343c988d07b" resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-4.0.1.tgz#b8d464ae94bf00b54a219a99782cb488c49842d5"
integrity sha512-0y/rctORxwlezPw/vHp+vbR2qTqHSwt9weZK3RjtDYSzmbGjtQlJZuHduCBfIoXggXPvlnI/2EjYcoyzDD/WRw== integrity sha512-ltEqMQB37ZxZnsgmI+9rqHYkz1M6PqUykuS1t2aQNuH1oiLrUDYz5nyVkHQDgjFd7CFKTIWeLiNhwdwFrH5o5A==
dependencies: dependencies:
canonicalize "^1.0.1" canonicalize "^1.0.1"
lru-cache "^5.1.1" lru-cache "^5.1.1"
@ -5745,7 +5750,6 @@ jsonld@3.3.0:
rdf-canonize "^2.0.1" rdf-canonize "^2.0.1"
request "^2.88.0" request "^2.88.0"
semver "^6.3.0" semver "^6.3.0"
xmldom "0.1.19"
jsprim@^1.2.2: jsprim@^1.2.2:
version "1.4.1" version "1.4.1"
@ -6832,10 +6836,10 @@ nodemailer@6.4.18:
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.18.tgz#2788c85792844fc17befda019031609017f4b9a1" resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.18.tgz#2788c85792844fc17befda019031609017f4b9a1"
integrity sha512-ht9cXxQ+lTC+t00vkSIpKHIyM4aXIsQ1tcbQCn5IOnxYHi81W2XOaU66EQBFFpbtzLEBTC94gmkbD4mGZQzVpA== integrity sha512-ht9cXxQ+lTC+t00vkSIpKHIyM4aXIsQ1tcbQCn5IOnxYHi81W2XOaU66EQBFFpbtzLEBTC94gmkbD4mGZQzVpA==
nofilter@^1.0.4: nofilter@^2.0.0:
version "1.0.4" version "2.0.0"
resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-1.0.4.tgz#78d6f4b6a613e7ced8b015cec534625f7667006e" resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-2.0.0.tgz#57a2d7c6fcd34dd396f490d6942c4f58640b5823"
integrity sha512-N8lidFp+fCz+TD51+haYdbDGrcBWwuHX40F5+z0qkUjMJ5Tp+rdSuAkMJ9N9eoolDlEVTf6u5icM+cNKkKW2mA== integrity sha512-i3ck2PUWBa+trsGGBvwS3msnTowbFei5G++BgpOpT7y7VTrprXphMQP5svTdsMLdttKDZFo+5RqVWRqhmf+BwQ==
noop-logger@^0.1.1: noop-logger@^0.1.1:
version "0.1.1" version "0.1.1"
@ -8162,10 +8166,10 @@ postcss-zindex@^2.0.1:
postcss "^5.0.4" postcss "^5.0.4"
uniqs "^2.0.0" uniqs "^2.0.0"
postcss@8.2.5, postcss@^8.2.4: postcss@8.2.6:
version "8.2.5" version "8.2.6"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.5.tgz#3c75149ada4e93db9521913654c0144517f77c9a" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.6.tgz#5d69a974543b45f87e464bc4c3e392a97d6be9fe"
integrity sha512-wMcb7BpDcm3gxQOQx46NDNT36Kk0Ao6PJLLI2ed5vehbbbxCEuslSQzbQ2sfSKy+gkYxhWcGWSeaK+gwm4KIZg== integrity sha512-xpB8qYxgPuly166AGlpRjUdEYtmOWx2iCwGmrv4vqZL9YPVviDVPZPRXxnXr6xPZOdxQ9lp3ZBFCRgWJ7LE3Sg==
dependencies: dependencies:
colorette "^1.2.1" colorette "^1.2.1"
nanoid "^3.1.20" nanoid "^3.1.20"
@ -8190,6 +8194,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.27, postcss@^7.0.3
source-map "^0.6.1" source-map "^0.6.1"
supports-color "^6.1.0" supports-color "^6.1.0"
postcss@^8.2.4:
version "8.2.5"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.5.tgz#3c75149ada4e93db9521913654c0144517f77c9a"
integrity sha512-wMcb7BpDcm3gxQOQx46NDNT36Kk0Ao6PJLLI2ed5vehbbbxCEuslSQzbQ2sfSKy+gkYxhWcGWSeaK+gwm4KIZg==
dependencies:
colorette "^1.2.1"
nanoid "^3.1.20"
source-map "^0.6.1"
postgres-array@~2.0.0: postgres-array@~2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e"
@ -9130,10 +9143,10 @@ sass-loader@11.0.1:
klona "^2.0.4" klona "^2.0.4"
neo-async "^2.6.2" neo-async "^2.6.2"
sass@1.32.6: sass@1.32.8:
version "1.32.6" version "1.32.8"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.6.tgz#e3646c8325cd97ff75a8a15226007f3ccd221393" resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.8.tgz#f16a9abd8dc530add8834e506878a2808c037bdc"
integrity sha512-1bcDHDcSqeFtMr0JXI3xc/CXX6c4p0wHHivJdru8W7waM7a1WjKMm4m/Z5sY7CbVw4Whi2Chpcw6DFfSWwGLzQ== integrity sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ==
dependencies: dependencies:
chokidar ">=2.0.0 <4.0.0" chokidar ">=2.0.0 <4.0.0"
@ -10254,10 +10267,10 @@ trace-redirect@1.0.6:
resolved "https://registry.yarnpkg.com/trace-redirect/-/trace-redirect-1.0.6.tgz#ac629b5bf8247d30dde5a35fe9811b811075b504" resolved "https://registry.yarnpkg.com/trace-redirect/-/trace-redirect-1.0.6.tgz#ac629b5bf8247d30dde5a35fe9811b811075b504"
integrity sha512-UUfa1DjjU5flcjMdaFIiIEGDTyu2y/IiMjOX4uGXa7meKBS4vD4f2Uy/tken9Qkd4Jsm4sRsfZcIIPqrRVF3Mg== integrity sha512-UUfa1DjjU5flcjMdaFIiIEGDTyu2y/IiMjOX4uGXa7meKBS4vD4f2Uy/tken9Qkd4Jsm4sRsfZcIIPqrRVF3Mg==
ts-loader@8.0.16: ts-loader@8.0.17:
version "8.0.16" version "8.0.17"
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.16.tgz#a60311f01f015518e1cfbb5698e6ca8830cd2391" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.17.tgz#98f2ccff9130074f4079fd89b946b4c637b1f2fc"
integrity sha512-Cr9ywsgg1n8cjGjIogHLPlqe3WJUHzuJaqwNo5I596KpIqekKzxvSENbrXeOypHcXSPPsr8hV6mglngyXvcKrg== integrity sha512-OeVfSshx6ot/TCxRwpBHQ/4lRzfgyTkvi7ghDVrLXOHzTbSK413ROgu/xNqM72i3AFeAIJgQy78FwSMKmOW68w==
dependencies: dependencies:
chalk "^4.1.0" chalk "^4.1.0"
enhanced-resolve "^4.0.0" enhanced-resolve "^4.0.0"
@ -10408,10 +10421,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typeorm@0.2.30: typeorm@0.2.31:
version "0.2.30" version "0.2.31"
resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.30.tgz#a0df2256402cbcdde8049a244437560495ce9b38" resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.31.tgz#82b8a1b233224f81c738f53b0380386ccf360917"
integrity sha512-qpr8AO3Phi6ZF7qMHOrRdNisVt8jE1KfmW0ooLFcXscA87aJ12aBPyB9cJfxGNjNwd7B3WIK9ZlBveWiqd74QA== integrity sha512-dVvCEVHH48DG0QPXAKfo0l6ecQrl3A8ucGP4Yw4myz4YEDMProebTQo8as83uyES+nrwCbu3qdkL4ncC2+qcMA==
dependencies: dependencies:
"@sqltools/formatter" "1.2.2" "@sqltools/formatter" "1.2.2"
app-root-path "^3.0.0" app-root-path "^3.0.0"
@ -10761,16 +10774,16 @@ vue-color@2.8.1:
material-colors "^1.0.0" material-colors "^1.0.0"
tinycolor2 "^1.1.2" tinycolor2 "^1.1.2"
vue-eslint-parser@^7.4.1: vue-eslint-parser@^7.5.0:
version "7.4.1" version "7.5.0"
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-7.4.1.tgz#e4adcf7876a7379758d9056a72235af18a587f92" resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-7.5.0.tgz#b68221c55fee061899afcfb4441ec74c1495285e"
integrity sha512-AFvhdxpFvliYq1xt/biNBslTHE/zbEvSnr1qfHA/KxRIpErmEDrQZlQnvEexednRHmLfDNOMuDYwZL5xkLzIXQ== integrity sha512-6EHzl00hIpy4yWZo3qSbtvtVw1A1cTKOv1w95QSuAqGgk4113XtRjvNIiEGo49r0YWOPYsrmI4Dl64axL5Agrw==
dependencies: dependencies:
debug "^4.1.1" debug "^4.1.1"
eslint-scope "^5.0.0" eslint-scope "^5.0.0"
eslint-visitor-keys "^1.1.0" eslint-visitor-keys "^1.1.0"
espree "^6.2.1" espree "^6.2.1"
esquery "^1.0.1" esquery "^1.4.0"
lodash "^4.17.15" lodash "^4.17.15"
vue-json-pretty@1.7.1: vue-json-pretty@1.7.1:
@ -10909,10 +10922,10 @@ webpack-sources@^2.1.1:
source-list-map "^2.0.1" source-list-map "^2.0.1"
source-map "^0.6.1" source-map "^0.6.1"
webpack@5.21.2: webpack@5.23.0:
version "5.21.2" version "5.23.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.21.2.tgz#647507e50d3637695be28af58a6a8246050394e7" resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.23.0.tgz#9ed57e9a54b267b3549899271ad780cddc6ee316"
integrity sha512-xHflCenx+AM4uWKX71SWHhxml5aMXdy2tu/vdi4lClm7PADKxlyDAFFN1rEFzNV0MAoPpHtBeJnl/+K6F4QBPg== integrity sha512-RC6dwDuRxiU75F8XC4H08NtzUrMfufw5LDnO8dTtaKU2+fszEdySCgZhNwSBBn516iNaJbQI7T7OPHIgCwcJmg==
dependencies: dependencies:
"@types/eslint-scope" "^3.7.0" "@types/eslint-scope" "^3.7.0"
"@types/estree" "^0.0.46" "@types/estree" "^0.0.46"
@ -11155,11 +11168,6 @@ xmlchars@^2.2.0:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
xmldom@0.1.19:
version "0.1.19"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc"
integrity sha1-Yx/Ad3bv2EEYvyUXGzftTQdaCrw=
xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"