Compare commits

...

50 Commits

Author SHA1 Message Date
4a202f0f7e 12.9.0 2020-02-14 01:26:28 +09:00
6e6b12519a Refactor 2020-02-14 01:24:05 +09:00
f5f7654f4b Improve custom emoji managemant 2020-02-14 01:09:39 +09:00
5ac4c48ad1 動きのあるMFMを無効にするオプション 2020-02-13 23:20:12 +09:00
7e18fd18b0 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-02-13 23:11:14 +09:00
fb30c479ea Update CHANGELOG.md 2020-02-13 23:11:11 +09:00
DW
f40dcbfe13 Fix for CASCADE DELETE not being federated (#5812)
* Fix for CASCADE DELETE not being federated

* Use JOIN to get user

* fix typo
2020-02-13 23:08:33 +09:00
8755b5f353 Fix bug 2020-02-13 22:48:56 +09:00
691482bb28 Update dependencies 🚀 2020-02-13 22:39:23 +09:00
4248bb8ce0 New Crowdin translations (#5931)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Spanish)
2020-02-13 22:35:22 +09:00
a5653e33d3 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-02-13 22:13:35 +09:00
072dc1c7e6 Clean up 2020-02-13 22:13:31 +09:00
d76fceae85 Fix #5935 2020-02-13 22:13:24 +09:00
86107b2710 Fix For Messaging Icon (#5933)
* Add icon for messaging

This will add icon within messaging

* Update messaging-room.message.vue

Link to missing icon
2020-02-13 15:53:02 +09:00
a473768bef 🎨 2020-02-13 11:54:12 +09:00
f7fe13a177 i18n 2020-02-13 11:46:02 +09:00
acd29d22eb Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-02-13 11:45:02 +09:00
c228155514 Fix #5930 2020-02-13 11:44:58 +09:00
b601b98d5c New Crowdin translations (#5929)
* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Spanish)
2020-02-13 06:02:36 +09:00
1cd3419688 Refactor 2020-02-13 05:08:01 +09:00
6f5f233bb5 12.8.0 2020-02-13 03:13:30 +09:00
d33492cd49 New Crowdin translations (#5926)
* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)
2020-02-13 03:12:58 +09:00
83ad9f369f OSネイティブの絵文字を使用オプション 2020-02-13 03:11:37 +09:00
3a78b62520 Update about-misskey.vue 2020-02-13 03:01:39 +09:00
2479f75d8a インスタンス情報ページとMisskey情報ページを分離するなど 2020-02-13 02:48:52 +09:00
f0d187f71e Add crowdin link 2020-02-13 02:35:50 +09:00
056942391a Update CHANGELOG.md 2020-02-13 02:18:33 +09:00
2feef81516 グループ招待の通知とか
Resolve #5880
Resolve #5927
2020-02-13 02:17:54 +09:00
037d4b581b フランス語と関西弁を有効に (#5925) 2020-02-12 22:40:35 +09:00
da4cf6fdb4 New Crowdin translations (#5924)
* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)
2020-02-12 22:40:18 +09:00
848bcd5a63 Update CHANGELOG.md 2020-02-12 08:05:26 +09:00
7b60b6c6dc New Crowdin translations (#5921)
* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Kannada)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Danish)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Kannada)
2020-02-12 08:00:07 +09:00
fbc801d1da 言語切り替え 2020-02-12 07:12:58 +09:00
2f18f82e3d 🎨 2020-02-12 06:43:22 +09:00
c2d5a96bb6 翻訳の抜けを修正その2 (#5893)
* Missing translation

* use npx

* ✌️

* Update ja-JP.yml

* Update signup.vue

* Update ja-JP.yml

* Update messaging-room.vue

* Update ja-JP.yml

* Update signup.vue

* Update ja-JP.yml

* Update signin.vue

* Update ja-JP.yml

* Update index.vue

* Update ja-JP.yml

* Update signup.vue

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2020-02-12 06:27:11 +09:00
46575d4f04 Fix #5919 2020-02-12 06:19:31 +09:00
5e7be93980 Resolve #5907 2020-02-12 06:01:59 +09:00
3f749c6540 良い感じに 2020-02-12 05:21:08 +09:00
2a7d4ee866 Update sequential-entrance.vue 2020-02-12 03:31:46 +09:00
083095ded6 Update sequential-entrance.vue 2020-02-12 03:31:20 +09:00
7207663f37 🎨 2020-02-12 02:56:42 +09:00
66b49c909a 🎨 2020-02-12 02:55:35 +09:00
ab469aa243 Update CHANGELOG.md 2020-02-12 02:52:45 +09:00
afa4563e1e Update paging.ts 2020-02-12 02:52:43 +09:00
320b3d8617 🎨 2020-02-12 02:52:37 +09:00
7493429b4d 🎨 2020-02-12 02:35:03 +09:00
d70f7a717b Fix #5918 2020-02-12 01:01:17 +09:00
4ab38b7894 タイムラインを放置すると先頭の投稿が見えなくなるのを修正 Fix #5903 (#5913)
* seqent fix

* comment

* ✌️
2020-02-12 00:38:29 +09:00
2068407be0 Update app.vue 2020-02-12 00:31:48 +09:00
10a7369fec サーバーから切断されましたのダイアログは時間をおいて表示するように (#5916)
* timeout disconnect dialog

* 70ms

* 150ms
2020-02-11 22:57:09 +09:00
100 changed files with 2153 additions and 697 deletions

View File

@ -1,6 +1,31 @@
ChangeLog
=========
12.9.0 (2020/02/13)
-------------------
### ✨Improvements
* カスタム絵文字の管理を強化
* 動きのあるMFMを無効にするオプションを追加
### 🐛Fixes
* タイムラインに自分の返信と自分への返信と投稿者自身への返信以外の返信が含まれている問題を修正
* グループがない状態でグループチャットを開始しようとするとフリーズする問題を修正
* 通知インジケーターがずれる問題を修正
* AP: 投稿を削除したときに関係する投稿の削除アクティビティが連合されない問題を修正
12.8.0 (2020/02/13)
--------------------
### ✨Improvements
* タイムラインなどを遡っているときは新しいアイテムが来てもスクロールしないように
* 表示言語を切り替えられるように
* グループに招待されたときの通知を追加
* フランス語と関西弁を有効に
* OSネイティブの絵文字を使用オプションを追加
### 🐛Fixes
* リストを追加するとエラーが出る問題を修正
* タイムラインを放置すると先頭の投稿が見えなくなるのを修正
12.7.1 (2020/02/11)
--------------------
### 🐛Fixes

View File

@ -1 +1,2 @@
---
_lang_: "Čeština"

View File

@ -1 +1,2 @@
---
_lang_: "Dansk"

View File

@ -1 +1,2 @@
---
_lang_: "Deutsch"

View File

@ -1,21 +1,6 @@
---
_ago:
unknown: "Unknown"
future: "Future"
justNow: "Just now"
secondsAgo: "{n}s ago"
minutesAgo: "{n}m ago"
hoursAgo: "{n}h ago"
daysAgo: "{n}d ago"
weeksAgo: "{n}week(s) ago"
monthsAgo: "{n}month(s) ago"
yearsAgo: "{n}year(s) ago"
_time:
second: "s"
minute: "m"
hour: "h"
day: "d"
introMisskey: "Welcome! Misskey is an open source distributed microblogging service.\nCreate \"notes\" to share what's happening or to tell everyone about you📡\nThen send \"reactions\" to respond quickly to everyone's notes👍\nLet's explore a new world🚀"
_lang_: "English"
introMisskey: "Welcome! Misskey is an open source and also decentralized microblogging service.\nWrite the \"notes\" to share what is happening now, or send out your own words to everyone 📡\nWith the \"reactions\", you can add your feelings to everyone's notes faster than anyone 👍\nLet's explore the new world 🚀"
monthAndDay: "{month}/{day}"
search: "Search"
notifications: "Notifications"
@ -84,7 +69,7 @@ enterListName: "List name"
privacy: "Privacy"
makeFollowManuallyApprove: "Follow requests require approval"
defaultNoteVisibility: "Default visibility"
follow: "Following"
follow: "Follow"
followRequest: "Request follow"
followRequests: "Follow requests"
unfollow: "Unfollow"
@ -127,7 +112,7 @@ flagAsBot: "This account is a bot"
flagAsCat: "This account is a cat"
autoAcceptFollowed: "Automatically approve follow requests from users you're following"
addAcount: "Add Account"
loginFailed: "Sign in failure"
loginFailed: "Failed to sign in"
showOnRemote: "View on remote instance"
general: "General"
wallpaper: "Wallpaper"
@ -264,8 +249,8 @@ instanceDescription: "Instance description"
maintainerName: "Maintainer"
maintainerEmail: "Maintainer email"
tosUrl: "Terms of Service URL"
thisYear: "This year"
thisMonth: "This month"
thisYear: "Year"
thisMonth: "Month"
today: "Today"
dayX: "{day} days"
monthX: "{month} months"
@ -326,6 +311,7 @@ aboutMisskey: "About Misskey"
aboutMisskeyText: "Misskey is an open-source software developed by syuilo since 2014."
misskeyMembers: "It is currently developed an maintained by the members listed below:"
misskeySource: "Source code is available here:"
misskeyTranslation: "Help us with your contribution to translate Misskey:"
misskeyDonate: "Help us to keep improving the software by donating here:"
morePatrons: "We really appreciate the support of many other helpers not listed here. Thank you! 🥰"
patrons: "Backers"
@ -370,12 +356,58 @@ members: "Members"
transfer: "Transfer"
messagingWithUser: "Messaging with other user"
messagingWithGroup: "Messaging within group"
title: "Title"
text: "Text"
enable: "Enable"
next: "Next"
retype: "Enter again"
noteOf: "{user}'s notes"
inviteToGroup: "Invite to group"
maxNoteTextLength: "Character limit of the note"
quoteAttached: "Quoted"
quoteQuestion: "Do you want to append a quote?"
noMessagesYet: "No messages yet"
newMessageExists: "You've got a new message"
onlyOneFileCanBeAttached: "You can only attach one file to a message"
signinRequired: "Please sign in"
invitationCode: "Invitation code"
checking: "Checking"
available: "Available"
unavailable: "Not available"
usernameInvalidFormat: "letters, numbers and _ are acceptable."
tooShort: "Too short"
tooLong: "Too long"
weakPassword: "Weak password"
normalPassword: "Good password"
strongPassword: "Strong password"
passwordMatched: "Matched"
passwordNotMatched: "Doesn't match"
signinWith: "Sign in with {x}"
tapSecurityKey: "Tap your security key"
or: "Or"
uiLanguage: "UI display language"
groupInvited: "Invited to group"
aboutX: "About {x}"
useOsNativeEmojis: "Use the OS native Emojis"
noGroups: "No groups"
joinOrCreateGroup: "Get invited to join the groups or you can create your own group."
noHistory: "No history items"
_ago:
unknown: "Unknown"
future: "Future"
justNow: "Just now"
secondsAgo: "{n}s ago"
minutesAgo: "{n}m ago"
hoursAgo: "{n}h ago"
daysAgo: "{n}d ago"
weeksAgo: "{n}week(s) ago"
monthsAgo: "{n}month(s) ago"
yearsAgo: "{n}year(s) ago"
_time:
second: "s"
minute: "m"
hour: "h"
day: "d"
_tutorial:
title: "How to use Misskey"
step1_1: "Welcome!"

View File

@ -1,21 +1,6 @@
---
_ago:
unknown: "Desconocido"
future: "Futuro"
justNow: "Recién ahora"
secondsAgo: "Hace {n} segundos"
minutesAgo: "Hace {n} minutos"
hoursAgo: "Hace {n} horas"
daysAgo: "Hace {n} días"
weeksAgo: "Hace {n} semanas"
monthsAgo: "Hace {n} meses"
yearsAgo: "Hace {n} años"
_time:
second: "Segundos"
minute: "Minutos"
hour: "Horas"
day: "Días"
introMisskey: "¡Bienvenido/a! Misskey es un servicio de microblogging descentralizado de código abierto. Escribe \"notas\" para compartir lo que te ocurre ahora o para contar sobre ti a todos. 📡\nCon la función de \"reacciones\", puedes también añadir una reacción rápida a las notas de todos.👍\nExplora un nuevo mundo.🚀"
_lang_: "Español"
introMisskey: "¡Bienvenido/a! Misskey es un servicio de microblogging descentralizado de código abierto.\nEscribe \"notas\" para compartir lo que te ocurre ahora o para contar sobre ti a todos 📡\nCon la función de \"reacciones\", puedes también añadir una reacción rápida a las notas de todos 👍\nExplora un nuevo mundo 🚀"
monthAndDay: "{day}/{month}"
search: "Buscar"
notifications: "Notificaciones"
@ -326,6 +311,7 @@ aboutMisskey: "Sobre Misskey"
aboutMisskeyText: "Misskey es un software de código abierto, desarrollado por syuilo desde el 2014"
misskeyMembers: "Es creado y mantenido por los miembros aquí listados:"
misskeySource: "El código fuente está disponible aquí:"
misskeyTranslation: "Ayúdanos con tu contribución para traducir Misskey:"
misskeyDonate: "Puedes contribuir al desarrollo de Misskey donando aquí:"
morePatrons: "Muchas más personas nos apoyan. Muchas gracias🥰"
patrons: "Patrocinadores"
@ -370,12 +356,58 @@ members: "Miembros"
transfer: "Transferir"
messagingWithUser: "Chatear con usuario"
messagingWithGroup: "Chatear en grupo"
title: "Título"
text: "Texto"
enable: "Activar"
next: "Siguiente"
retype: "Intentar de nuevo"
noteOf: "Notas de {user}"
inviteToGroup: "Invitar al grupo"
maxNoteTextLength: "Límite de caracteres en una nota"
quoteAttached: "Cita añadida"
quoteQuestion: "¿Quiere añadir una cita?"
noMessagesYet: "Aún no hay chat"
newMessageExists: "Tienes un mensaje nuevo"
onlyOneFileCanBeAttached: "Solo se puede añadir un archivo al mensaje"
signinRequired: "Iniciar sesión"
invitationCode: "Código de invitación"
checking: "Comprobando"
available: "Disponible"
unavailable: "No disponible"
usernameInvalidFormat: "utiliza letras, números y/o -."
tooShort: "Demasiado corto"
tooLong: "Demasiado largo"
weakPassword: "Contraseña débil"
normalPassword: "Buena contraseña"
strongPassword: "Muy buena contraseña"
passwordMatched: "Correcto"
passwordNotMatched: "Las contraseñas no son las mismas"
signinWith: "Inicie sesión con {x}"
tapSecurityKey: "Toque la clave de seguridad"
or: "O"
uiLanguage: "Idioma de visualización de la interfaz"
groupInvited: "Invitado al grupo"
aboutX: "Acerca de {x}"
useOsNativeEmojis: "Usa los emojis nativos de la plataforma"
noGroups: "Sin grupos"
joinOrCreateGroup: "Obtenga una invitación para unirse al grupos o puede crear su propio grupo."
noHistory: "No hay datos en el historial"
_ago:
unknown: "Desconocido"
future: "Futuro"
justNow: "Recién ahora"
secondsAgo: "Hace {n} segundos"
minutesAgo: "Hace {n} minutos"
hoursAgo: "Hace {n} horas"
daysAgo: "Hace {n} días"
weeksAgo: "Hace {n} semanas"
monthsAgo: "Hace {n} meses"
yearsAgo: "Hace {n} años"
_time:
second: "Segundos"
minute: "Minutos"
hour: "Horas"
day: "Días"
_tutorial:
title: "Cómo usar Misskey"
step1_1: "Bienvenido"

View File

@ -1 +1,836 @@
---
_lang_: "Français"
introMisskey: "Bienvenue! Misskey est un service de microblogage décentralisé open source.\nÉcrivez des «notes» pour partager ce qui vous arrive maintenant ou pour parler de vous à tout le monde 📡\nAvec la fonction «réactions», vous pouvez également ajouter une réaction rapide aux notes de chacun 👍\nExplorez un nouveau monde 🚀"
monthAndDay: "{day}/{month}"
search: "Rechercher"
notifications: "Notifications"
username: "Nom d'utilisateur·rice"
password: "Mot de passe"
fetchingAsApObject: "Récupération depuis le fédiverse"
ok: "D'accord"
gotIt: "J'ai compris !"
cancel: "Annuler"
enterUsername: "Entrer un nom d'utilisateur·rice"
renotedBy: "Renoté par {user}"
noNotes: "Pas de notes"
noNotifications: "Pas de notifications"
instance: "Instance"
settings: "Paramètres"
profile: "Profil"
timeline: "Fil d'actualité"
noAccountDescription: "L'utilisateur·rice n'a pas renseigné de présentation sur son profil"
login: "Se connecter"
loggingIn: "Connexion en cours"
logout: "Se déconnecter"
signup: "S'enregistrer"
uploading: "Envoi en cours"
save: "Enregistrer"
users: "Utilisateur·rice·s"
addUser: "Ajouter un·e utilisateur·rice"
favorite: "Ajouter aux favoris"
favorites: "Favoris"
unfavorite: "Retirer des favoris"
pin: "Épingler sur le profil"
unpin: "Désépingler"
copyContent: "Copier le contenu"
copyLink: "Copier le lien"
delete: "Supprimer"
addToList: "Ajouter à une liste"
sendMessage: "Envoyer un message"
copyUsername: "Copier le nom d'utilisateur"
reply: "Répondre"
loadMore: "Voir plus"
youGotNewFollower: "Vous a suivi"
receiveFollowRequest: "Demande de suivi reçue"
followRequestAccepted: "Suivre la demande acceptée"
mentions: "Mentions"
directNotes: "Messages directs"
importAndExport: "Import et export"
import: "Importer"
export: "Exporter"
files: "Fichier·s"
download: "Télécharger"
driveFileDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer le fichier \"{name}\" ? Les notes avec ce fichier joint seront aussi supprimées."
unfollowConfirm: "Êtes-vous sûr·e ne plus vouloir suivre {name} ?"
exportRequested: "Vous avez demandé une exportation. Cela pourrait prendre un peu de temps. Une fois l'exportation terminée, le fichier résultant sera ajouté dans le Drive."
importRequested: "Vous avez initié un import. Cela pourrait prendre un peu de temps."
lists: "Listes"
noLists: "Aucune liste"
note: "Note"
notes: "Notes"
following: "Abonnements"
followers: "Abonné·e·s"
followsYou: "Votre abonné"
createList: "Créer une liste"
manageLists: "Gérer les listes"
error: "Une erreur est survenue"
retry: "Réessayer"
enterListName: "Nom de la liste"
privacy: "Vie privée"
makeFollowManuallyApprove: "Demandes dabonnements requiert lapprobation"
defaultNoteVisibility: "Visibilité par défaut"
follow: "Abonnement"
followRequest: "Demande dabonnement"
followRequests: "Demandes dabonnement"
unfollow: "Se désabonner"
followRequestPending: "En attente dapprobation"
enterEmoji: "ou entrez un émoji"
renote: "Renote"
unrenote: "Annuler Renote"
quote: "Citation"
pinnedNote: "Note épinglée"
you: "Vous"
clickToShow: "Cliquer pour afficher"
sensitive: "Contenu sensible"
add: "Ajouter"
reaction: "Réactions"
reactionSettingDescription: "Personnaliser les émojis à afficher dans le sélecteur de réactions, délimités par les sauts de ligne."
rememberNoteVisibility: "Se souvenir de la visibilité des notes"
renameFile: "Renommer le ficher"
attachCancel: "Enlever le fichier attaché"
markAsSensitive: "Marquer comme sensible"
unmarkAsSensitive: "Enlever le marquage comme sensible"
enterFileName: "Entrer le nom du fichier"
mute: "Mettre en sourdine"
unmute: "Enlever la sourdine"
block: "Bloquer"
unblock: "Débloquer"
suspend: "Suspendre"
unsuspend: "Annuler la suspension"
blockConfirm: "Désirez-vous bloquer ce compte ?"
unblockConfirm: "Désirez-vous débloquer ce compte ?"
suspendConfirm: "Désirez-vous suspendre ce compte ?"
unsuspendConfirm: "Désirez-vous annuler la suspension de ce compte ?"
selectList: "Sélectionner une liste"
customEmojis: "Émojis personnalisés"
emojiName: "Nom de lémoji"
emojiUrl: "URL de lémoji"
addEmoji: "Ajouter un émoji"
cacheRemoteFiles: "Mettre en cache des fichiers distants"
cacheRemoteFilesDescription: "Quand ce paramètre est désactivé, les fichiers distants sont chargés directement de l'instance distante. Désactiver cela diminuera l'utilisation du stockage mais augmentera le trafic parce les miniatures ne seront pas générées."
flagAsBot: "Ce compte est un robot"
flagAsCat: "Ce compte est un chat"
autoAcceptFollowed: "Approuver automatiquement les abonnements des utilisateurs abonné·e·s"
addAcount: "Ajouter un compte"
loginFailed: "Échec de la connexion"
showOnRemote: "Voir sur l'instance distante"
general: "Général"
wallpaper: "Arrière plan"
removeWallpaper: "Supprimer l'arrière plan"
searchWith: "Recherche : {q}"
youHaveNoLists: "Vous n'avez aucune liste"
followConfirm: "Désirez-vous suivre {name} ?"
proxyAccount: "Compte proxy"
proxyAccountDescription: "Un compte proxy se comporte, dans certaines conditions, comme un·e abonné·e distant pour les utilisateurs d'autres instances.\nExemple : quand un·e utilisateur·rice distant·e est ajouté·e à une liste, ses notes ne serait pas visibles sur l'instance si personne ne le·la suit. Le compte proxy va donc le·la suivre pour que ses notes soient acheminées."
host: "Hôte"
selectUser: "Sélectionner un·e utilisateur·rice"
recipient: "Correspondant·e"
annotation: "Commentaires"
federation: "Fédération"
instances: "Instance"
registeredAt: "Premier contact le"
latestRequestSentAt: "Dernière requête envoyée"
latestRequestReceivedAt: "Dernière requête reçue"
latestStatus: "Dernière statut"
storageUsage: "Stockage utilisé"
charts: "Graphiques"
perHour: "par heure"
perDay: "par jour"
stopActivityDelivery: "Arrêter l'envoi d'activités"
blockThisInstance: "Bloquer cette instnce"
operations: "Opérations"
software: "Logiciel"
version: "Version"
metadata: "Métadonnées"
withNFiles: "{n} fichier(s)"
monitor: "Écran de contrôle"
jobQueue: "File dattente"
cpuAndMemory: "Processeur et mémoire"
network: "Réseau"
disk: "Disque"
instanceInfo: "Informations sur l'instance"
statistics: "Statistiques"
clearQueue: "Vider la file d'attente"
clearQueueConfirmTitle: "Êtes-vous sûr·e de vouloir vider la file d'attente ?"
clearQueueConfirmText: "Les notes non distribuées ne seront pas livrées. Normalement, vous n'avez PAS besoin d'effectuer cette opération."
clearCachedFiles: "Vider le cache"
clearCachedFilesConfirm: "Êtes-vous sûr·e de vouloir vider le cache de fichiers distants ?"
blockedInstances: "Instances bloquées"
blockedInstancesDescription: "Listez les instance que vous désirez bloquer, une par ligne. Ces instances bloquées ne seront pas capable d'interagir avec cette instance."
muteAndBlock: "Masqués / Bloqués"
mutedUsers: "Utilisateur·rice·s en sourdine"
blockedUsers: "Utilisateur·rice·s bloqué·e·s"
noUsers: "Il n'y a aucun utilisateur·rice"
editProfile: "Modifier votre profil"
noteDeleteConfirm: "Confirmez-vous la suppression de cette note ?"
pinLimitExceeded: "Je ne peux plus épingler"
intro: "L'installation de Misskey est terminée! Créons le compte administrateur."
done: "Terminé"
processing: "Traitement en cours"
preview: "Prévisualisation"
noCustomEmojis: "Il a pas démoji"
customEmojisOfRemote: "Émojis l'instance distante"
noJobs: "Il n'y a aucune tâche planifiée"
federating: "En cours de fédération"
blocked: "Bloqué"
suspended: "Suspendu"
all: "Tous"
subscribing: "Abonné"
publishing: "Publié"
notResponding: "Ne répond pas"
instanceFollowing: "Abonnements une instance"
instanceFollowers: "Abonné·e·s de l'instance"
instanceUsers: "Utilisateur·e·s de l'instance"
changePassword: "Modifier votre mot de passe"
security: "Sécurité"
retypedNotMatch: "Les saisies ne correspondent pas."
currentPassword: "Mot de passe actuel"
newPassword: "Nouveau mot de passe"
newPasswordRetype: "Nouveau mot de passe (répéter)"
attachFile: "Joindre un fichier"
more: "Plus !"
featured: "Surlignage"
usernameOrUserId: "Nom d'utilisateur ou ID utilisateur"
noSuchUser: "Utilisateur non trouvé"
lookup: "Recherche"
announcements: "Annonces"
imageUrl: "URL de limage"
remove: "Supprimer"
removed: "Supprimé"
removeAreYouSure: "Supprimer «{x}» ?"
saved: "Enregistré"
messaging: "Discuter"
upload: "Téléchargez"
fromDrive: "Depuis le Drive"
fromUrl: "De l'URL"
explore: "Découvrir"
games: "Jeux de Misskey"
messageRead: "Lus"
recentUsedEmojis: "Emoji récemment utilisé"
noMoreHistory: "Plus d'histoire passée"
startMessaging: "Commencer à écrire un discutez"
nUsersRead: "{n} personnes ont lu"
agreeTo: "D'accord {0}"
tos: "Conditions d'utilisation"
start: "Commencer"
home: "Principal"
remoteUserCaution: "Les informations sont incomplètes en raison de l'utilisateur distant."
activity: "Activités"
images: "Images"
birthday: "Date de naissance"
yearsOld: "{age} ans"
registeredDate: "Date de création"
location: "Localisation"
theme: "Thème"
lightThemes: "Thème lumineux"
darkThemes: "Thème sombre"
drive: "Drive"
selectFile: "Choisir le fichier"
selectFiles: "Choisir le fichiers"
renameFolder: "Renommer le dossier"
createFolder: "Créer un dossier"
deleteFolder: "Supprimer le dossier"
addFile: "Ajoutez un fichier"
emptyDrive: "Le Drive est vide"
emptyFolder: "Le dossier est vide"
copyUrl: "Copier lURL"
rename: "Renommer"
avatar: "Avatar"
banner: "Bannière"
nsfw: "Contenu sensible"
disconnectedFromServer: "Déconnecté du serveur"
reloadConfirm: "Voulez-vous recharger?"
watch: "Surveiller"
unwatch: "Ne plus surveiller"
accept: "Autoriser"
reject: "Refuser"
instanceName: "Nom de linstance"
instanceDescription: "Description de linstance"
maintainerName: "Nom d'administrateur"
maintainerEmail: "Email de l'administrateur"
tosUrl: "URL des conditions d'utilisation"
thisYear: "Cette année"
thisMonth: "Ce mois-ci"
today: "Aujourd'hui"
dayX: "{day} jour"
monthX: "{month} mois"
yearX: "{year} année"
pages: "Pages"
integration: "Intégrations"
connectSerice: "Connecter"
disconnectSerice: "Déconnecter"
enableLocalTimeline: "Activer le fil local"
enableGlobalTimeline: "Activer le fil global"
disablingTimelinesInfo: "Si vous désactivez ces le fils, les administrateurs et les modérateurs pourront toujours y accéder pour plus de commodité."
registration: "S'inscrire"
enableRegistration: "Autoriser nimporte qui à senregistrés"
invite: "Inviter"
proxyRemoteFiles: "Proxy fichiers distants"
driveCapacityPerLocalAccount: "Volume du Drive par utilisateur local"
driveCapacityPerRemoteAccount: "Volume du Drive par utilisateur distant"
inMb: "en mégaoctets"
iconUrl: "URL de l'image de l'icône"
bannerUrl: "URL de l'image de la bannière"
basicInfo: "Informations basiques"
pinnedUsers: "Utilisateur·rice épinglé·e"
recaptcha: "reCAPTCHA"
enableRecaptcha: "Activation de reCAPTCHA"
recaptchaSiteKey: "Clé du site"
recaptchaSecretKey: "Clé secrète"
antennas: "Antenne"
manageAntennas: "Gestion d'antenne"
name: "Nom"
antennaSource: "Recevoir la source"
antennaKeywords: "Mots clés entrants"
antennaKeywordsDescription: "Lorsqu'il est séparé par un espace, il devient une spécification ET, et lorsqu'il est séparé par un saut de ligne, il devient une spécification OU."
notifyAntenna: "Notifier les nouvelles notes"
withFileAntenna: "Notes uniquement avec fichiers joints"
serviceworker: "ServiceWorker"
enableServiceworker: "Activer ServiceWorker"
antennaUsersDescription: "Spécifiez les noms d'utilisateurs séparés par des sauts de ligne"
caseSensitive: "Sensible à la casse"
withReplies: "Y compris répondres"
connectedTo: "Vous êtes connectés aux services suivants"
notesAndReplies: "Notes et Répondres"
withFiles: "Avec fichiers joints"
silence: "Mettre en masquer"
silenceConfirm: "Mettre l'utilisateur sous masquer ?"
unsilenceConfirm: "Voulez-vous annuler le masquer ?"
popularUsers: "Utilisateur·rice·s populaires"
recentlyUpdatedUsers: "Utilisateur·rice·s actif·ve·s récemment"
recentlyRegisteredUsers: "Utilisateur·rice·s récemment enregistrés"
recentlyDiscoveredUsers: "Utilisateur·rice·s récemment découverts"
exploreUsersCount: "Il y a {count} utilisateur·rice·s"
exploreFediverse: "Explorer le Fédiverse"
popularTags: "Mots-clés populaires"
userList: "Listes"
about: "Informations"
aboutMisskey: "À propos de Misskey"
aboutMisskeyText: "Misskey est un logiciel open source, développé par syuilo depuis 2014."
misskeyMembers: "Il est développé et maintenu par les membres répertoriés ici:"
misskeySource: "Le code source est disponible ici:"
misskeyTranslation: "Aidez-nous avec votre contribution à traduire Misskey:"
misskeyDonate: "Vous pouvez contribuer au développement de Misskey en faisant un don ici:"
morePatrons: "Nous apprécions vraiment le soutien de nombreux autres les soutiens non répertoriés ici. Merci beaucoup à tous! 🥰"
patrons: "Supporteurs"
administrator: "Administrateur"
token: "Jeton"
twoStepAuthentication: "Authentification à deux facteurs"
moderator: "Modérateurs"
nUsersMentioned: "{n} utilisateur·rice·s mentionné·e·s"
securityKey: "Clé de sécurité"
securityKeyName: "Nom de la clé"
registerSecurityKey: "Sinscrire la clé de sécurité"
lastUsed: "Dernier utilisé"
unregister: "Se désinscrire"
passwordLessLogin: "Connectez-vous sans mot de passe"
resetPassword: "Réinitialiser mot de passe"
newPasswordIs: "Votre nouveau mot de passe est \"{password}\""
post: "Notes"
posted: "Publié !"
autoReloadWhenDisconnected: "Rechargement automatique lorsque le serveur se déconnecte"
autoNoteWatch: "Surveiller automatique pour les notes"
autoNoteWatchDescription: "Soyez informé des notes auxquelles vous avez réagi ou répondu."
reduceUiAnimation: "Réduire l'animation de l'interface"
share: "Partager"
notFound: "Non trouvé"
notFoundDescription: "Aucune page ne correspond à l'URL spécifiée."
uploadFolder: "Emplacement de téléversement par défaut"
cacheClear: "Vider le cache"
markAsReadAllNotifications: "Marquer toutes les notifications comme lues"
markAsReadAllUnreadNotes: "Marquer toutes les notes comme lues"
markAsReadAllTalkMessages: "Marquer toutes les discutez comme lues"
help: "Aide"
inputMessageHere: "Tapez ici votre message"
close: "Fermer"
group: "Groupe"
groups: "Groupes"
createGroup: "Créer un groupe"
ownedGroups: "Groupe propriétaire"
joinedGroups: "Membre dans les groupes"
invites: "Inviter"
groupName: "Nom du groupe"
members: "Membres"
transfer: "Transférer"
messagingWithUser: "Discutez avec les utilisateurs"
messagingWithGroup: "Discuter en groupe"
title: "Titre"
text: "Texte"
enable: "Activer"
next: "Suivant"
retype: "Retapez"
noteOf: "{user} notes"
inviteToGroup: "Inviter au groupe"
maxNoteTextLength: "Limite de note caractères"
quoteAttached: "Avec citation"
quoteQuestion: "Souhaitez-vous ajoutez une citation ?"
noMessagesYet: "Pas encore discuté"
newMessageExists: "Vous avez un nouveau message"
onlyOneFileCanBeAttached: "Vous ne pouvez joindre qu'un seul fichier au message"
signinRequired: "Veuillez vous connecter"
invitationCode: "Code dinvitation"
checking: "Vérification"
available: "Disponible"
unavailable: "Non disponible"
usernameInvalidFormat: "Vous pouvez utiliser des lettres, des nombres et _"
tooShort: "Est trop court"
tooLong: "Est trop long"
weakPassword: "Faible mot de passe"
normalPassword: "Bon mot de passe"
strongPassword: "Fort mot de passe"
passwordMatched: "Correcte"
passwordNotMatched: "Ne correspond pas"
signinWith: "Connectez-vous avec {x}"
tapSecurityKey: "Touchez la clé de sécurité"
or: "OU"
uiLanguage: "Langue d'affichage de l'interface"
groupInvited: "Invité au groupe"
aboutX: "À propos de {x}"
useOsNativeEmojis: "Utilisez les emojis natifs de la plateforme"
noGroups: "Pas de groupes"
joinOrCreateGroup: "Soyez invité à rejoindre les groupes ou vous pouvez créer votre propre groupe."
noHistory: "Pas d'historique"
_ago:
unknown: "Inconnu"
future: "Futur"
justNow: "à linstant"
secondsAgo: "Il y a {n}s"
minutesAgo: "Il y a {n}min"
hoursAgo: "Il y a {n} heures"
daysAgo: "Il y a {n} jours"
weeksAgo: "Il y a {n} semaines"
monthsAgo: "Il y a {n} mois"
yearsAgo: "Il y a {n} ans"
_time:
second: "s"
minute: "min"
hour: "h"
day: "j"
_tutorial:
title: "Comment utiliser Misskey"
step1_1: "Bienvenue,"
_2fa:
alreadyRegistered: "Cette étape à déjà été complétée"
registerDevice: "Sinscrire l'appareil"
registerKey: "Sinscrire la clé"
step1: "Tout d'abord, installez une application d'authentification, telle que {a} ou {b}, sur votre appareil."
step2: "Ensuite, scannez le code QR affiché avec l'application."
step3: "Entrez le jeton affiché sur l'application et vous avez terminé."
step4: "Lorsque vous vous connectez, entrez le jeton de la même manière."
securityKeyInfo: "Vous pouvez configurer l'authentification WebAuthN pour sécuriser davantage le processus de connexion avec non seulement la clé de sécurité matérielle qui prend en charge FIDO2, mais également l'authentification par empreinte digitale ou PIN sur votre appareil."
_permissions:
"read:account": "Afficher les informations du compte"
"write:account": "Mettre à jour les informations de votre compte"
"read:blocks": "Voir les blocs"
"write:blocks": "Écrire des blocs"
"read:drive": "Parcourir le Drive"
"write:drive": "Écrire sur le Drive"
"read:favorites": "Afficher les favoris"
"write:favorites": "Écrire des favoris"
"read:following": "Voir les informations de l'abonné"
"write:following": "Abonnements/Se désabonner"
"read:messaging": "Cherche à discuter"
"write:messaging": "Contrôler le discuter"
"read:mutes": "Voir les comptes muets"
"write:mutes": "Gérer les comptes muets"
"write:notes": "Créer / supprimer des notes"
"read:notifications": "Afficher les notifications"
"write:notifications": "Gérer vos notifications"
"read:reactions": "Lire les réactions"
"write:reactions": "Gérer vos réactions"
"write:votes": "Voter"
"read:pages": "Afficher la page"
"write:pages": "Mettre à jour les Pages"
"read:page-likes": "Lire les favoris sur les Pages"
"write:page-likes": "Mettre à jour les favoris sur les Pages"
"read:user-groups": "Voir les groupes d'utilisateur·rice·s"
"write:user-groups": "Éditer les groupes des utilisateur·rice·s"
_auth:
shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?"
permissionAsk: "Cette application nécessite les autorisations suivantes "
_antennaSources:
all: "Toutes les notes"
homeTimeline: "Notes de l'utilisateur auquel je m'abonne"
users: "Notes des un ou plusieurs utilisateurs spécifiés"
userList: "Notes pour les utilisateurs de la liste spécifiée"
_weekday:
sunday: "Dimanche"
monday: "Lundi"
tuesday: "Mardi"
wednesday: "Mercredi"
thursday: "Jeudi"
friday: "Vendredi"
saturday: "Samedi"
_widgets:
memo: "Note collante"
notifications: "Notifications"
timeline: "Fil d'actualité"
calendar: "Calendrier"
trends: "Tendances"
clock: "Horloge"
rss: "Lecteur de flux RSS"
_cw:
hide: "Masquer"
show: "Voir plus"
chars: "{count} caractères"
files: "{count} fichiers"
poll: "Sondage"
_poll:
noOnlyOneChoice: "Au moins 2 réponses nécéssaires"
choiceN: "Choix {n}"
noMore: "Vous ne pouvez pas en ajouter davantage"
canMultipleVote: "Autoriser le multi-choix"
expiration: "Fin du sondage"
infinite: "Illimité"
at: "Choisir une date"
after: "Chosir une durée"
deadlineDate: "Date de fin"
deadlineTime: "Heure de fin"
duration: "Durée"
votesCount: "{n} votes"
totalVotes: "{n} votes au total"
vote: "Voter"
showResult: "Voir les résultats"
voted: "Déjà voté"
closed: "Terminé"
remainingDays: "{d} jours, {h} heures restantes"
remainingHours: "{h} heures et {m} minutes restantes"
remainingMinutes: "{m} minutes et {s} secondes restantes"
remainingSeconds: "{s} secondes restantes"
_visibility:
public: "Public"
publicDescription: "Publier à tou·te·s les utilisateur·rice·s"
home: "Principal"
homeDescription: "Publier sur le fil principal uniquement"
followers: "Abonné·e·s"
followersDescription: "Publier à vos abonné·e·s uniquement"
specified: "Direct"
specifiedDescription: "Publier uniquement aux utilisateur·rice·s mentionné·e·s"
localOnly: "Local seulement"
_postForm:
replyPlaceholder: "Répondre à cette note ..."
quotePlaceholder: "Citez cette note ..."
_placeholders:
a: "Qu'est-ce qu'il se passe ?"
b: "Quoi de neuf ?"
c: "Quavez-vous en tête ?"
d: "Désirez-vous publier quelques mots ?"
e: "Écrivez ici"
f: "En attente de vos écrits ..."
_profile:
name: "Nom"
username: "Nom d'utilisateur·rice"
description: "À propos de moi"
youCanIncludeHashtags: "Vous pouvez également inclure des hashtags."
metadata: "Informations complémentaires"
metadataLabel: "Étiquette"
metadataContent: "Contenu"
_exportOrImport:
allNotes: "Toutes les notes"
followingList: "Abonnements"
muteList: "Mettre en sourdine"
blockingList: "Bloquer"
userLists: "Listes"
_charts:
federationInstancesIncDec: "Variation du nombre d'instances"
federationInstancesTotal: "Nombre d'instances au total"
usersIncDec: "Variation du nombre d'utilisateur·rice·s"
usersTotal: "Nombre d'utilsateur·rice·s au total"
activeUsers: "Utilisateur·rice·s actif·ve·s"
notesIncDec: "Variation du nombre d'notes"
localNotesIncDec: "Variation du nombre de notes local"
remoteNotesIncDec: "Variation du nombre dnotes distant"
notesTotal: "Nombre d'notes au total"
filesIncDec: "Variation du nombre de fichiers"
filesTotal: "Nombre de fichiers au total"
storageUsageIncDec: "Variation de l'utilisation du stockage"
storageUsageTotal: "Utilisation totale du stockage"
_instanceCharts:
requests: "Requêtes"
users: "Variation du nombre d'utilisateur·rice·s"
usersTotal: "Somme du nombre d'utilisateur·rice·s accumulés"
notes: "Variation du nombre d'notes"
notesTotal: "Somme du nombre dnotes accumulés"
ff: "Variation des abonné·e·s"
ffTotal: "Somme du nombre d'abonnements accumulés"
cacheSize: "Variation de la taille du cache"
cacheSizeTotal: "Somme de la taille du cache accumulé"
files: "Variation du nombre de fichiers"
filesTotal: "Somme du nombre de fichiers accumulés"
_timelines:
home: "Principal"
local: "Local"
social: "Social"
global: "Global"
_pages:
newPage: "Créer une page"
editPage: "Modifier une page"
readPage: "Voir la source"
page-created: "Page a été créée !"
page-updated: "A mis à jour la page"
name-already-exists: "Une page portant le même nom existe déjà"
title-invalid-name: "LURL de la page spécifiée nest pas valide"
text-invalid-name: "Assurez-vous qu'il n'est pas vide"
editThisPage: "Éditer cette page"
viewSource: "Afficher la source"
viewPage: "Afficher la page"
like: "Favori"
unlike: "Je nfavoris pas"
liked-pages: "Pages favoris"
my-pages: "Mes pages"
inspector: "Inspecteur"
content: "Bloc de page"
variables: "Variables"
more-details: "Description"
title: "Titre"
url: "URL de page"
summary: "Résumé de page"
alignCenter: "Centrée"
hide-title-when-pinned: "Masquer le titre de la page lorsque celle-ci est épinglée au profil"
font: "Police de caractères"
fontSerif: "Serif"
fontSansSerif: "Sans Serif"
set-eye-catching-image: "Définir une image attirante"
remove-eye-catching-image: "Supprimer une image attirante"
chooseBlock: "Ajouter un bloc"
selectType: "Choisir un type"
enterVariableName: "Veuillez entrer un nom de variable"
the-variable-name-is-already-used: "Cette variable est déjà utilisée"
content-blocks: "Contenu"
input-blocks: "Entrée"
special-blocks: "Spécial"
post-from-post-form: "Publier ce contenu"
posted-from-post-form: "Publié !"
blocks:
text: "Texte"
textarea: "Zone de texte"
section: "Section"
image: "Images"
button: "Bouton"
if: "Si"
_if:
variable: "Variables"
post: "Formulaire à publier"
_post:
text: "Contenu"
textInput: "Entrée de textuelle"
_textInput:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
textareaInput: "Entrée de textuelle multiligne"
_textareaInput:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
numberInput: "Entrée numérique"
_numberInput:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
switch: "Basculer"
_switch:
name: "Nom de la variable"
text: "Titre"
default: "Valeur par défaut"
counter: "Compteur"
_counter:
name: "Nom de la variable"
text: "Titre"
inc: "Augmenter le chiffre"
_button:
text: "Titre"
colored: "Coloré"
action: "L'opération lorsque le bouton sera pressé"
_action:
dialog: "Afficher une fenêtre de dialogue"
_dialog:
content: "Contenu"
resetRandom: "Réinitialiser le nombre aléatoire"
pushEvent: "Envoyer un évènement"
_pushEvent:
event: "Nom de l'évènement"
message: "Message à afficher lorsque appuyé"
variable: "Variable à envoyer"
no-variable: "Rien"
radioButton: "Choix"
_radioButton:
name: "Nom de la variable"
title: "Titre"
values: "Choix séparés par des sauts de ligne"
default: "Valeur par défaut"
script:
categories:
flow: "Contrôle"
logical: "Opération logique"
operation: "Calculer"
comparison: "Comparer"
random: "Aléatoire"
value: "Valeur"
fn: "Fonction"
text: "Manipulation de texte"
convert: "Convertir"
list: "Listes"
blocks:
text: "Texte"
multiLineText: "Texte (Multi-lignes)"
textList: "Liste de texte"
_textList:
info: "Veuillez séparer chacun avec une nouvelle ligne"
strLen: "Longueur d'un texte"
_strLen:
arg1: "Texte"
strPick: "Extraire un caractère"
_strPick:
arg1: "Texte"
arg2: "Position du joueur"
strReplace: "Remplacement de texte"
_strReplace:
arg1: "Texte"
arg2: "Avant le remplacement"
arg3: "Après le remplacement"
strReverse: "Inverser le texte"
_strReverse:
arg1: "Texte"
join: "Concaténer du texte"
_join:
arg1: "Listes"
arg2: "Séparateur"
add: "Ajouter"
_add:
arg1: "A"
arg2: "B"
subtract: "Soustraire"
_subtract:
arg1: "A"
arg2: "B"
multiply: "Multiplier par"
_multiply:
arg1: "A"
arg2: "B"
divide: "Diviser par"
_divide:
arg1: "A"
arg2: "B"
mod: "Reste"
_mod:
arg1: "A"
arg2: "B"
round: "Décimal rond"
_round:
arg1: "Numérique"
eq: "A et B sont équivalents"
_eq:
arg1: "A"
arg2: "B"
notEq: "A et B sont différents"
_notEq:
arg1: "A"
arg2: "B"
and: "A et B"
_and:
arg1: "A"
arg2: "B"
or: "A ou B"
_or:
arg1: "A"
arg2: "B"
lt: "A est plus petit que B"
_lt:
arg1: "A"
arg2: "B"
gt: "A est supérieur à B"
_gt:
arg1: "A"
arg2: "B"
ltEq: "A est plus petit ou égal à B"
_ltEq:
arg1: "A"
arg2: "B"
gtEq: "A est supérieur ou égal à B"
_gtEq:
arg1: "A"
arg2: "B"
if: "Branche"
_if:
arg1: "Si"
arg2: "Si"
arg3: "Sinon"
not: "Nier"
_not:
arg1: "Nier"
random: "Aléatoire"
_random:
arg1: "Probabilité"
rannum: "Nombre aléatoire"
_rannum:
arg1: "Minimum"
arg2: "Maximum"
randomPick: "Sélectionner au hasard dans la liste"
_randomPick:
arg1: "Listes"
dailyRandom: "Aléatoire (Quotidien pour chaque utilisateur)"
_dailyRandom:
arg1: "Probabilité"
dailyRannum: "Numéros aléatoires (Quotidien pour chaque utilisateur)"
_dailyRannum:
arg1: "Minimum"
arg2: "Maximum"
dailyRandomPick: "Sélectionné au hasard dans la liste (Quotidien pour chaque utilisateur)"
_dailyRandomPick:
arg1: "Listes"
seedRandom: "Aléatoire (graine)"
_seedRandom:
arg1: "Graine"
arg2: "Probabilité"
seedRannum: "Nombre aléatoire (Graine)"
_seedRannum:
arg1: "Graine"
arg2: "Minimum"
arg3: "Maximum"
seedRandomPick: "Sélectionné au hasard dans la liste (graine)"
_seedRandomPick:
arg1: "Graine"
arg2: "Listes"
DRPWPM: "Sélectionné au hasard dans une liste de probabilités (Quotidien pour chaque utilisateur)"
_DRPWPM:
arg1: "Liste de texte"
pick: "Sélectionner dans la liste"
_pick:
arg1: "Listes"
arg2: "Position"
listLen: "Longueur de la liste"
_listLen:
arg1: "Listes"
number: "Numérique"
stringToNumber: "Convertir du texte en numérique"
_stringToNumber:
arg1: "Texte"
numberToString: "Convertir du numérique en texte"
_numberToString:
arg1: "Numérique"
splitStrByLine: "Séparer le texte par lignes"
_splitStrByLine:
arg1: "Texte"
ref: "Variables"
fn: "Fonction"
_fn:
slots: "Slots"
slots-info: "Veuillez délimiter chaque slot par un saut de ligne"
arg1: "Sortie"
for: "Répéter"
_for:
arg1: "Compter"
arg2: "Action"
typeError: "Le slot {slot} accepte \"{expect}\" mais a \"{actual}\" !"
thereIsEmptySlot: "Slot {slot} est vide !"
types:
string: "Texte"
number: "Numérique"
boolean: "Marqueur"
array: "Listes"
stringArray: "Liste de texte"
emptySlot: "Slot vide"
enviromentVariables: "Variables d'environnement"
pageVariables: "Élément de page"
argVariables: "Entrée slot"

View File

@ -19,9 +19,9 @@ const languages = [
//'de-DE',
'en-US',
'es-ES',
//'fr-FR',
'fr-FR',
'ja-JP',
//'ja-KS',
'ja-KS',
'ko-KR',
//'nl-NL',
//'pl-PL',

View File

@ -1,20 +1,4 @@
_ago:
unknown: "謎"
future: "未来"
justNow: "たった今"
secondsAgo: "{n}秒前"
minutesAgo: "{n}分前"
hoursAgo: "{n}時間前"
daysAgo: "{n}日前"
weeksAgo: "{n}週間前"
monthsAgo: "{n}ヶ月前"
yearsAgo: "{n}年前"
_time:
second: "秒"
minute: "分"
hour: "時間"
day: "日"
_lang_: "日本語"
introMisskey: "ようこそMisskeyは、オープンソースの分散型マイクロブログサービスです。\n「ート」を作成して、いま起こっていることを共有したり、あなたについて皆に発信しよう📡\n「リアクション」機能で、皆のートに素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀"
monthAndDay: "{month}月 {day}日"
@ -327,6 +311,7 @@ aboutMisskey: "Misskeyについて"
aboutMisskeyText: "Misskeyはsyuiloによって2014年から開発されている、オープンソースのソフトウェアです。"
misskeyMembers: "現在以下のメンバーによって開発・メンテナンスされています:"
misskeySource: "ソースコードはここで公開されています:"
misskeyTranslation: "Misskeyの翻訳にご協力をお願いします:"
misskeyDonate: "Misskeyに寄付をして開発をサポートできます:"
morePatrons: "他にも多くの方が支援してくれています。ありがとうございます🥰"
patrons: "支援者"
@ -371,12 +356,64 @@ members: "メンバー"
transfer: "譲渡"
messagingWithUser: "ユーザーとチャット"
messagingWithGroup: "グループでチャット"
title: "タイトル"
text: "テキスト"
enable: "有効にする"
next: "次"
retype: "再入力"
noteOf: "{user}のノート"
inviteToGroup: "グループに招待"
maxNoteTextLength: "ノートの文字数制限"
quoteAttached: "引用付き"
quoteQuestion: "引用として添付しますか?"
noMessagesYet: "まだチャットはありません"
newMessageExists: "新しいメッセージがあります"
onlyOneFileCanBeAttached: "メッセージに添付できるファイルはひとつです"
signinRequired: "ログインしてください"
invitationCode: "招待コード"
checking: "確認しています"
available: "利用できます"
unavailable: "利用できません"
usernameInvalidFormat: "a~z、A~Z、0~9、_が使えます"
tooShort: "短すぎます"
tooLong: "長すぎます"
weakPassword: "弱いパスワード"
normalPassword: "普通のパスワード"
strongPassword: "強いパスワード"
passwordMatched: "一致しました"
passwordNotMatched: "一致していません"
signinWith: "{x}でログイン"
tapSecurityKey: "セキュリティーキーにタッチ"
or: "もしくは"
uiLanguage: "UIの表示言語"
groupInvited: "グループに招待されました"
aboutX: "{x}について"
useOsNativeEmojis: "OSネイティブの絵文字を使用"
noGroups: "グループがありません"
joinOrCreateGroup: "既存のグループに招待してもらうか、新しくグループを作成してください。"
noHistory: "履歴はありません"
disableAnimatedMfm: "動きのあるMFMを無効にする"
doing: "やっています"
category: "カテゴリ"
tags: "タグ"
_ago:
unknown: "謎"
future: "未来"
justNow: "たった今"
secondsAgo: "{n}秒前"
minutesAgo: "{n}分前"
hoursAgo: "{n}時間前"
daysAgo: "{n}日前"
weeksAgo: "{n}週間前"
monthsAgo: "{n}ヶ月前"
yearsAgo: "{n}年前"
_time:
second: "秒"
minute: "分"
hour: "時間"
day: "日"
_tutorial:
title: "Misskeyの使い方"

View File

@ -1,20 +1,5 @@
---
_ago:
unknown: "謎"
future: "未来"
justNow: "たった今"
secondsAgo: "{n}秒前"
minutesAgo: "{n}分前"
hoursAgo: "{n}時間前"
daysAgo: "{n}日前"
weeksAgo: "{n}週間前"
monthsAgo: "{n}ヶ月前"
yearsAgo: "{n}年前"
_time:
second: "秒"
minute: "分"
hour: "時間"
day: "日"
_lang_: "日本語 (関西弁)"
introMisskey: "ようこそMisskeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ート」を作成しぃ、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「リアクション」機能で、皆のートに素はよ反応を追加することもできます✌\n新しい世界を探検しよう🚀"
monthAndDay: "{month}月 {day}日"
search: "探す"
@ -124,6 +109,22 @@ aboutMisskey: "Misskeyってなんや"
notFoundDescription: "指定されたURLに該当するページはあらへんやった。"
close: "さいなら"
joinedGroups: "参加しとるグループ"
_ago:
unknown: "謎"
future: "未来"
justNow: "たった今"
secondsAgo: "{n}秒前"
minutesAgo: "{n}分前"
hoursAgo: "{n}時間前"
daysAgo: "{n}日前"
weeksAgo: "{n}週間前"
monthsAgo: "{n}ヶ月前"
yearsAgo: "{n}年前"
_time:
second: "秒"
minute: "分"
hour: "時間"
day: "日"
_2fa:
alreadyRegistered: "もう設定終わっとるわ"
_auth:

2
locales/kn-IN.yml Normal file
View File

@ -0,0 +1,2 @@
---
_lang_: "ಕನ್ನಡ"

View File

@ -1,20 +1,5 @@
---
_ago:
unknown: "알 수 없음"
future: "미래"
justNow: "방금 전"
secondsAgo: "{n}초 전"
minutesAgo: "{n}분 전"
hoursAgo: "{n}시간 전"
daysAgo: "{n}일 전"
weeksAgo: "{n}주 전"
monthsAgo: "{n}개월 전"
yearsAgo: "{n}년 전"
_time:
second: "초"
minute: "분"
hour: "시간"
day: "일"
_lang_: "한국어"
introMisskey: "환영합니다! Misskey 는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n\"노트\" 를 작성해서, 지금 일어나고 있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요📡\n\"리액션\" 기능으로, 친구의 노트에 총알같이 반응을 추가할 수도 있습니다👍\n새로운 세계를 탐험해 보세요🚀"
monthAndDay: "{month}월 {day}일"
search: "검색"
@ -323,9 +308,10 @@ popularTags: "인기 태그"
userList: "리스트"
about: "정보"
aboutMisskey: "Misskey에 대하여"
aboutMisskeyText: "Misskey는 syuilo에 의해 2014년부터 개발 오픈 소스 소프트웨어입니다."
misskeyMembers: "현재는 아래 멤버들에 의해 개발 및 유지보수 되고 있습니다."
misskeySource: "소스 코드는 여기에서 보실 수 있습니다:"
aboutMisskeyText: "Misskey는 syuilo에 의해 2014년부터 개발되어 온 오픈소스 소프트웨어 입니다."
misskeyMembers: "현재는 아래 멤버들에 의해 개발 및 유지보수 되고 있습니다:"
misskeySource: "소스코드는 여기에 공개되어 있습니다:"
misskeyTranslation: "Misskey의 번역을 함께해 주시길 부탁드립니다:"
misskeyDonate: "Misskey에 기부하심으로써 개발에 도움을 주실 수 있습니다:"
morePatrons: "이 외에도 다른 많은 분들이 도움을 주시고 계십니다. 감사합니다🥰"
patrons: "후원자들"
@ -370,12 +356,58 @@ members: "멤버"
transfer: "양도"
messagingWithUser: "유저와 대화하기"
messagingWithGroup: "그룹끼리 대화하기"
title: "제목"
text: "텍스트"
enable: "사용"
next: "다음"
retype: "다시 입력"
noteOf: "{user}의 노트"
inviteToGroup: "그룹에 초대하기"
maxNoteTextLength: "노트의 문자 수 제한"
quoteAttached: "인용함"
quoteQuestion: "인용해서 작성하시겠습니까?"
noMessagesYet: "아직 대화가 없습니다"
newMessageExists: "새 메시지가 있습니다"
onlyOneFileCanBeAttached: "메시지에 첨부할 수 있는 파일은 하나까지입니다"
signinRequired: "로그인 해주세요"
invitationCode: "초대 코드"
checking: "확인하는 중입니다"
available: "사용 가능합니다"
unavailable: "사용할 수 없습니다"
usernameInvalidFormat: "a~z, A~Z, 0-9, _를 사용할 수 있습니다"
tooShort: "너무 짧습니다"
tooLong: "너무 깁니다"
weakPassword: "약한 비밀번호"
normalPassword: "좋은 비밀번호"
strongPassword: "강한 비밀번호"
passwordMatched: "일치합니다"
passwordNotMatched: "일치하지 않습니다"
signinWith: "{x}로 로그인"
tapSecurityKey: "보안 키를 터치"
or: "혹은"
uiLanguage: "UI 표시 언어"
groupInvited: "그룹에 초대되었습니다"
aboutX: "{x}에 대하여"
useOsNativeEmojis: "OS 기본 이모지를 사용"
noGroups: "그룹이 없습니다"
joinOrCreateGroup: "다른 그룹의 초대를 받거나, 직접 새 그룹을 만들어 보세요."
noHistory: "기록이 없습니다"
_ago:
unknown: "알 수 없음"
future: "미래"
justNow: "방금 전"
secondsAgo: "{n}초 전"
minutesAgo: "{n}분 전"
hoursAgo: "{n}시간 전"
daysAgo: "{n}일 전"
weeksAgo: "{n}주 전"
monthsAgo: "{n}개월 전"
yearsAgo: "{n}년 전"
_time:
second: "초"
minute: "분"
hour: "시간"
day: "일"
_tutorial:
title: "Misskey의 사용 방법"
step1_1: "환영합니다!"
@ -407,7 +439,7 @@ _2fa:
step2: "그 후, 표시되어 있는 QR코드를 앱으로 스캔합니다."
step3: "앱에 표시된 토큰을 입력하시면 완료됩니다."
step4: "다음 로그인부터는 토큰을 입력해야 합니다."
securityKeyInfo: "FIDO2를 지원하는 하드웨어 시큐리티 키 혹은 휴대전화의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할 수 있습니다."
securityKeyInfo: "FIDO2를 지원하는 하드웨어 보안 키 혹은 디바이스의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할 수 있습니다."
_permissions:
"read:account": "계정의 정보를 봅니다"
"write:account": "계정의 정보를 변경합니다"

View File

@ -1 +1,2 @@
---
_lang_: "Nederlands"

View File

@ -1 +1,2 @@
---
_lang_: "Norsk Bokmål"

View File

@ -1 +1,2 @@
---
_lang_: "język polski"

View File

@ -1 +1,2 @@
---
_lang_: "Português"

View File

@ -1 +1,2 @@
---
_lang_: "Русский язык"

View File

@ -1,20 +1,5 @@
---
_ago:
unknown: "未知"
future: "未来"
justNow: "最近"
secondsAgo: "{n}秒前"
minutesAgo: "{n}分前"
hoursAgo: "{n}小时前"
daysAgo: "{n}日前"
weeksAgo: "{n}周前"
monthsAgo: "{n}月前"
yearsAgo: "{n}年前"
_time:
second: "秒"
minute: "分"
hour: "小时"
day: "日"
_lang_: "中文(简体)"
introMisskey: "欢迎Misskey是一个开源的分散型SNS服务。\n通过「帖子」来分享现在发生的事情吧📡\n「反应」功能可以让你快速的对大家的「帖子」来表达感情👍\n一起来探索新的世界吧🚀"
monthAndDay: "{month}月 {day}日"
search: "搜索"
@ -350,12 +335,30 @@ invites: "邀请"
groupName: "群组名"
members: "成员"
transfer: "转让"
title: "标题"
text: "文本"
enable: "启用"
next: "下一个"
retype: "重新输入"
noteOf: "{user}的帖子"
inviteToGroup: "群组邀请"
maxNoteTextLength: "帖子的字数限制"
_ago:
unknown: "未知"
future: "未来"
justNow: "最近"
secondsAgo: "{n}秒前"
minutesAgo: "{n}分前"
hoursAgo: "{n}小时前"
daysAgo: "{n}日前"
weeksAgo: "{n}周前"
monthsAgo: "{n}月前"
yearsAgo: "{n}年前"
_time:
second: "秒"
minute: "分"
hour: "小时"
day: "日"
_tutorial:
title: "Misskey的使用方法"
step1_1: "欢迎!"

View File

@ -1 +1,2 @@
---
_lang_: "中文(繁体)"

View File

@ -0,0 +1,38 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class userGroupInvitation1581526429287 implements MigrationInterface {
name = 'userGroupInvitation1581526429287'
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`CREATE TABLE "user_group_invitation" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "userGroupId" character varying(32) NOT NULL, CONSTRAINT "PK_160c63ec02bf23f6a5c5e8140d6" PRIMARY KEY ("id"))`, undefined);
await queryRunner.query(`CREATE INDEX "IDX_bfbc6305547539369fe73eb144" ON "user_group_invitation" ("userId") `, undefined);
await queryRunner.query(`CREATE INDEX "IDX_5cc8c468090e129857e9fecce5" ON "user_group_invitation" ("userGroupId") `, undefined);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e9793f65f504e5a31fbaedbf2f" ON "user_group_invitation" ("userId", "userGroupId") `, undefined);
await queryRunner.query(`ALTER TABLE "notification" ADD "userGroupInvitationId" character varying(32)`, undefined);
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`, undefined);
await queryRunner.query(`CREATE TYPE "notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited')`, undefined);
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "notification_type_enum" USING "type"::"text"::"notification_type_enum"`, undefined);
await queryRunner.query(`DROP TYPE "notification_type_enum_old"`, undefined);
await queryRunner.query(`COMMENT ON COLUMN "notification"."type" IS 'The type of the Notification.'`, undefined);
await queryRunner.query(`ALTER TABLE "user_group_invitation" ADD CONSTRAINT "FK_bfbc6305547539369fe73eb144a" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
await queryRunner.query(`ALTER TABLE "user_group_invitation" ADD CONSTRAINT "FK_5cc8c468090e129857e9fecce5a" FOREIGN KEY ("userGroupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_8fe87814e978053a53b1beb7e98" FOREIGN KEY ("userGroupInvitationId") REFERENCES "user_group_invitation"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, undefined);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_8fe87814e978053a53b1beb7e98"`, undefined);
await queryRunner.query(`ALTER TABLE "user_group_invitation" DROP CONSTRAINT "FK_5cc8c468090e129857e9fecce5a"`, undefined);
await queryRunner.query(`ALTER TABLE "user_group_invitation" DROP CONSTRAINT "FK_bfbc6305547539369fe73eb144a"`, undefined);
await queryRunner.query(`COMMENT ON COLUMN "notification"."type" IS ''`, undefined);
await queryRunner.query(`CREATE TYPE "notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted')`, undefined);
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "notification_type_enum_old" USING "type"::"text"::"notification_type_enum_old"`, undefined);
await queryRunner.query(`DROP TYPE "notification_type_enum"`, undefined);
await queryRunner.query(`ALTER TYPE "notification_type_enum_old" RENAME TO "notification_type_enum"`, undefined);
await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "userGroupInvitationId"`, undefined);
await queryRunner.query(`DROP INDEX "IDX_e9793f65f504e5a31fbaedbf2f"`, undefined);
await queryRunner.query(`DROP INDEX "IDX_5cc8c468090e129857e9fecce5"`, undefined);
await queryRunner.query(`DROP INDEX "IDX_bfbc6305547539369fe73eb144"`, undefined);
await queryRunner.query(`DROP TABLE "user_group_invitation"`, undefined);
}
}

View File

@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.7.1",
"version": "12.9.0",
"codename": "indigo",
"repository": {
"type": "git",
@ -30,16 +30,16 @@
"lodash": "^4.17.13"
},
"dependencies": {
"@elastic/elasticsearch": "7.5.0",
"@fortawesome/fontawesome-svg-core": "1.2.26",
"@fortawesome/free-brands-svg-icons": "5.12.0",
"@fortawesome/free-regular-svg-icons": "5.12.0",
"@fortawesome/free-solid-svg-icons": "5.12.0",
"@elastic/elasticsearch": "7.6.0",
"@fortawesome/fontawesome-svg-core": "1.2.27",
"@fortawesome/free-brands-svg-icons": "5.12.1",
"@fortawesome/free-regular-svg-icons": "5.12.1",
"@fortawesome/free-solid-svg-icons": "5.12.1",
"@fortawesome/vue-fontawesome": "0.1.9",
"@juggle/resize-observer": "3.0.2",
"@koa/cors": "3.0.0",
"@koa/multer": "2.0.2",
"@koa/router": "8.0.6",
"@koa/router": "8.0.8",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.12.0",
"@types/cbor": "5.0.0",
@ -54,7 +54,7 @@
"@types/js-yaml": "3.12.2",
"@types/jsdom": "12.2.4",
"@types/katex": "0.11.0",
"@types/koa": "2.11.0",
"@types/koa": "2.11.1",
"@types/koa-bodyparser": "4.3.0",
"@types/koa-compress": "2.0.9",
"@types/koa-cors": "0.0.0",
@ -69,7 +69,7 @@
"@types/lolex": "5.1.0",
"@types/markdown-it": "0.0.9",
"@types/mocha": "7.0.1",
"@types/node": "13.7.0",
"@types/node": "13.7.1",
"@types/nodemailer": "6.4.0",
"@types/nprogress": "0.2.0",
"@types/oauth": "0.9.1",
@ -80,7 +80,7 @@
"@types/qrcode": "1.3.4",
"@types/random-seed": "0.3.3",
"@types/ratelimiter": "2.1.28",
"@types/redis": "2.8.14",
"@types/redis": "2.8.15",
"@types/rename": "1.0.1",
"@types/request": "2.48.4",
"@types/request-promise-native": "1.0.17",
@ -95,18 +95,18 @@
"@types/tmp": "0.1.0",
"@types/uuid": "3.4.7",
"@types/web-push": "3.3.0",
"@types/webpack": "4.41.3",
"@types/webpack": "4.41.6",
"@types/webpack-stream": "3.2.10",
"@types/websocket": "1.0.0",
"@types/ws": "7.2.1",
"@typescript-eslint/parser": "2.18.0",
"@typescript-eslint/parser": "2.19.2",
"agentkeepalive": "4.1.0",
"animejs": "3.1.0",
"apexcharts": "3.15.3",
"apexcharts": "3.15.6",
"autobind-decorator": "2.4.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
"aws-sdk": "2.610.0",
"aws-sdk": "2.617.0",
"bcryptjs": "2.4.3",
"bull": "3.12.1",
"cafy": "15.2.1",
@ -115,7 +115,7 @@
"chalk": "3.0.0",
"chart.js": "2.9.3",
"cli-highlight": "2.1.4",
"commander": "4.1.0",
"commander": "4.1.1",
"content-disposition": "0.5.3",
"crc-32": "1.2.0",
"css-loader": "3.4.2",
@ -128,7 +128,7 @@
"eventemitter3": "4.0.0",
"feed": "4.1.0",
"fibers": "4.0.2",
"file-type": "13.1.2",
"file-type": "14.1.2",
"fluent-ffmpeg": "2.1.2",
"glob": "7.1.6",
"gulp": "4.0.2",
@ -144,12 +144,12 @@
"hard-source-webpack-plugin": "0.13.1",
"html-minifier": "4.0.0",
"http-signature": "1.3.1",
"https-proxy-agent": "4.0.0",
"https-proxy-agent": "5.0.0",
"insert-text-at-cursor": "0.3.0",
"is-root": "2.1.0",
"is-svg": "4.2.1",
"js-yaml": "3.13.1",
"jsdom": "16.0.1",
"jsdom": "16.1.0",
"json5": "2.1.1",
"json5-loader": "3.0.0",
"jsrsasign": "8.0.12",
@ -198,16 +198,16 @@
"randomcolor": "0.5.4",
"ratelimiter": "3.4.0",
"recaptcha-promise": "0.1.3",
"reconnecting-websocket": "4.3.0",
"redis": "2.8.0",
"reconnecting-websocket": "4.4.0",
"redis": "3.0.2",
"redis-lock": "0.1.4",
"reflect-metadata": "0.1.13",
"rename": "1.0.4",
"request": "2.88.0",
"request": "2.88.2",
"request-promise-native": "1.0.8",
"request-stats": "3.0.0",
"require-all": "3.0.0",
"rimraf": "3.0.1",
"rimraf": "3.0.2",
"rndstr": "1.0.0",
"s-age": "1.1.2",
"sass": "1.25.0",
@ -221,7 +221,7 @@
"style-loader": "1.1.3",
"summaly": "2.3.1",
"syslog-pro": "1.0.0",
"systeminformation": "4.21.1",
"systeminformation": "4.21.2",
"syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "2.3.4",
"textarea-caret": "3.1.0",
@ -245,7 +245,7 @@
"vue-cropperjs": "4.0.1",
"vue-i18n": "8.15.3",
"vue-json-pretty": "1.6.3",
"vue-loader": "15.8.3",
"vue-loader": "15.9.0",
"vue-marquee-text-component": "1.1.1",
"vue-meta": "2.3.2",
"vue-prism-component": "1.1.1",
@ -256,10 +256,10 @@
"vue-template-compiler": "2.6.11",
"vuedraggable": "2.23.2",
"vuex": "3.1.2",
"vuex-persistedstate": "2.7.0",
"vuex-persistedstate": "2.7.1",
"web-push": "3.4.3",
"webpack": "4.41.5",
"webpack-cli": "3.3.10",
"webpack": "4.41.6",
"webpack-cli": "3.3.11",
"websocket": "1.0.31",
"ws": "7.2.1",
"xev": "2.0.1"

View File

@ -157,7 +157,7 @@ import { faBell, faEnvelope, faLaugh, faComments } from '@fortawesome/free-regul
import { ResizeObserver } from '@juggle/resize-observer';
import { v4 as uuid } from 'uuid';
import i18n from './i18n';
import { host } from './config';
import { host, instanceName } from './config';
import { search } from './scripts/search';
import contains from './scripts/contains';
import MkToast from './components/toast.vue';
@ -247,11 +247,15 @@ export default Vue.extend({
}
this.$root.stream.on('_disconnected_', () => {
if (!this.disconnectedDialog) {
if (this.$store.state.device.autoReload) {
location.reload();
return;
}
if (this.disconnectedDialog) return;
if (this.$store.state.device.autoReload) {
location.reload();
return;
}
setTimeout(() => {
if (this.$root.stream.state !== 'reconnecting') return;
this.disconnectedDialog = this.$root.dialog({
type: 'warning',
showCancelButton: true,
@ -263,7 +267,7 @@ export default Vue.extend({
}
this.disconnectedDialog = null;
});
}
}, 150)
});
},
@ -274,7 +278,7 @@ export default Vue.extend({
const lastChild = this.$refs.widgets.children[this.$refs.widgets.children.length - 1];
if (lastChild == null) return;
const width = lastChild.offsetLeft + 300;
const width = lastChild.offsetLeft + 300 + 16;
this.$refs.widgets.style.width = width + 'px';
};
setInterval(adjustWidgetsWidth, 1000);
@ -477,9 +481,14 @@ export default Vue.extend({
icon: faQuestionCircle,
}, {
type: 'link',
text: this.$t('about'),
text: this.$t('aboutX', { x: instanceName || host }),
to: '/about',
icon: faInfoCircle,
}, {
type: 'link',
text: this.$t('aboutMisskey'),
to: '/about-misskey',
icon: faInfoCircle,
}],
align: 'left',
fixed: true,
@ -744,10 +753,9 @@ export default Vue.extend({
position: relative;
> input {
$margin: 8px;
width: 200px;
width: 210px;
box-sizing: border-box;
margin-right: $margin;
margin-right: 8px;
padding: 0 12px 0 42px;
font-size: 1rem;
line-height: 38px;

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

View File

@ -18,7 +18,7 @@
<ol class="emojis" ref="suggests" v-if="emojis.length > 0">
<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1">
<span class="emoji" v-if="emoji.isCustomEmoji"><img :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span>
<span class="emoji" v-else-if="!useOsDefaultEmojis"><img :src="emoji.url" :alt="emoji.emoji"/></span>
<span class="emoji" v-else-if="!useOsNativeEmojis"><img :src="emoji.url" :alt="emoji.emoji"/></span>
<span class="emoji" v-else>{{ emoji.emoji }}</span>
<span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span>
<span class="alias" v-if="emoji.aliasOf">({{ emoji.aliasOf }})</span>
@ -130,8 +130,8 @@ export default Vue.extend({
return (this.$refs.suggests as Element).children;
},
useOsDefaultEmojis(): boolean {
return this.$store.state.device.useOsDefaultEmojis;
useOsNativeEmojis(): boolean {
return this.$store.state.device.useOsNativeEmojis;
}
},

View File

@ -23,13 +23,13 @@
<x-folder v-for="folder in folders" :key="folder.id" class="folder" :folder="folder"/>
<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid -->
<div class="padding" v-for="n in 16"></div>
<mk-button v-if="moreFolders">{{ $t('@.load-more') }}</mk-button>
<mk-button v-if="moreFolders">{{ $t('loadMore') }}</mk-button>
</div>
<div class="files" ref="filesContainer" v-if="files.length > 0">
<x-file v-for="file in files" :key="file.id" class="file" :file="file" :select-mode="selectMode"/>
<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid -->
<div class="padding" v-for="n in 16"></div>
<mk-button v-if="moreFiles" @click="fetchMoreFiles">{{ $t('@.load-more') }}</mk-button>
<mk-button v-if="moreFiles" @click="fetchMoreFiles">{{ $t('loadMore') }}</mk-button>
</div>
<div class="empty" v-if="files.length == 0 && folders.length == 0 && !fetching">
<p v-if="draghover">{{ $t('empty-draghover') }}</p>

View File

@ -1,7 +1,7 @@
<template>
<img v-if="customEmoji" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt"/>
<img v-else-if="char && !useOsDefaultEmojis" class="mk-emoji" :src="url" :alt="alt" :title="alt"/>
<span v-else-if="char && useOsDefaultEmojis">{{ char }}</span>
<img v-else-if="char && !useOsNativeEmojis" class="mk-emoji" :src="url" :alt="alt" :title="alt"/>
<span v-else-if="char && useOsNativeEmojis">{{ char }}</span>
<span v-else>:{{ name }}:</span>
</template>
@ -53,8 +53,8 @@ export default Vue.extend({
return this.customEmoji ? `:${this.customEmoji.name}:` : this.char;
},
useOsDefaultEmojis(): boolean {
return this.$store.state.device.useOsDefaultEmojis && !this.isReaction;
useOsNativeEmojis(): boolean {
return this.$store.state.device.useOsNativeEmojis && !this.isReaction;
},
ce() {

View File

@ -1,6 +1,6 @@
<template>
<div class="mjndxjcg _panel">
<img src="https://xn--931a.moe/assets/error.jpg" alt=""/>
<img src="https://xn--931a.moe/assets/error.png" alt="" class="_ghost"/>
<p><fa :icon="faExclamationTriangle"/> {{ $t('error') }}</p>
<mk-button @click="() => $emit('retry')" class="button">{{ $t('retry') }}</mk-button>
</div>
@ -45,8 +45,6 @@ export default Vue.extend({
height: 150px;
margin-bottom: 16px;
border-radius: 16px;
pointer-events: none;
user-select: none;
}
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div class="mk-google">
<input type="search" v-model="query" :placeholder="q">
<button @click="search"><fa icon="search"/> {{ $t('@.search') }}</button>
<button @click="search"><fa icon="search"/> {{ $t('search') }}</button>
</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<div class="yxspomdl">
<div class="yxspomdl" :class="{ inline }">
<div class="ring"></div>
</div>
</template>
@ -8,6 +8,13 @@
import Vue from 'vue';
export default Vue.extend({
props: {
inline: {
type: Boolean,
required: false,
default: false
}
}
});
</script>
@ -25,22 +32,31 @@ export default Vue.extend({
padding: 32px;
text-align: center;
&.inline {
display: inline;
padding: 0;
> .ring:after {
width: 32px;
height: 32px;
}
}
> .ring {
display: inline-block;
width: 80px;
height: 80px;
opacity: 0.7;
vertical-align: middle;
}
> .ring:after {
content: " ";
display: block;
width: 64px;
height: 64px;
margin: 8px;
box-sizing: border-box;
width: 48px;
height: 48px;
border-radius: 50%;
border: solid 6px;
border-color: var(--fg) transparent transparent transparent;
border: solid 4px;
border-color: currentColor transparent transparent transparent;
animation: ring 0.5s linear infinite;
}
}

View File

@ -125,6 +125,7 @@ export default Vue.extend({
> .item {
display: block;
position: relative;
padding: 8px 16px;
width: 100%;
box-sizing: border-box;

View File

@ -82,10 +82,10 @@ export default Vue.component('misskey-flavored-markdown', {
attrs: {
style: `display: inline-block; font-size: 150%;`
},
directives: [this.$store.state.settings.disableAnimatedMfm ? {} : {
directives: [this.$store.state.device.animatedMfm ? {
name: 'animate-css',
value: { classes: 'tada', iteration: 'infinite' }
}]
}: {}]
}, genEl(token.children));
}
@ -110,10 +110,10 @@ export default Vue.component('misskey-flavored-markdown', {
attrs: {
style: 'display: inline-block;'
},
directives: [this.$store.state.settings.disableAnimatedMfm ? {} : {
directives: [this.$store.state.device.animatedMfm ? {
name: 'animate-css',
value: { classes: 'rubberBand', iteration: 'infinite' }
}]
} : {}]
}, genEl(token.children));
}
@ -122,9 +122,8 @@ export default Vue.component('misskey-flavored-markdown', {
token.node.props.attr == 'left' ? 'reverse' :
token.node.props.attr == 'alternate' ? 'alternate' :
'normal';
const style = (this.$store.state.settings.disableAnimatedMfm)
? ''
: `animation: spin 1.5s linear infinite; animation-direction: ${direction};`;
const style = this.$store.state.device.animatedMfm
? `animation: spin 1.5s linear infinite; animation-direction: ${direction};` : '';
return (createElement as any)('span', {
attrs: {
style: 'display: inline-block;' + style
@ -135,7 +134,7 @@ export default Vue.component('misskey-flavored-markdown', {
case 'jump': {
return (createElement as any)('span', {
attrs: {
style: (this.$store.state.settings.disableAnimatedMfm) ? 'display: inline-block;' : 'display: inline-block; animation: jump 0.75s linear infinite;'
style: this.$store.state.device.animatedMfm ? 'display: inline-block; animation: jump 0.75s linear infinite;' : 'display: inline-block;'
},
}, genEl(token.children));
}

View File

@ -1,7 +1,7 @@
<template>
<div class="mk-notes" v-size="[{ max: 500 }]">
<div class="empty" v-if="empty">
<img src="https://xn--931a.moe/assets/info.jpg" alt=""/>
<img src="https://xn--931a.moe/assets/info.png" alt="" class="_ghost"/>
<div>{{ $t('noNotes') }}</div>
</div>
@ -11,28 +11,28 @@
<x-note :note="note" :detail="detail" :key="note.id"/>
</x-list>
<footer v-if="more">
<button @click="fetchMore()" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" class="_buttonPrimary">
<footer class="more" v-if="more">
<mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary>
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
<template v-if="moreFetching"><fa :icon="faSpinner" pulse fixed-width/></template>
</button>
<template v-if="moreFetching"><mk-loading inline/></template>
</mk-button>
</footer>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import i18n from '../i18n';
import paging from '../scripts/paging';
import XNote from './note.vue';
import XList from './date-separated-list.vue';
import MkButton from './ui/button.vue';
export default Vue.extend({
i18n,
components: {
XNote, XList
XNote, XList, MkButton
},
mixins: [
@ -63,12 +63,6 @@ export default Vue.extend({
}
},
data() {
return {
faSpinner
};
},
computed: {
notes(): any[] {
return this.extract ? this.extract(this.items) : this.items;
@ -94,8 +88,6 @@ export default Vue.extend({
height: 128px;
margin-bottom: 16px;
border-radius: 16px;
pointer-events: none;
user-select: none;
}
}
@ -113,23 +105,11 @@ export default Vue.extend({
}
}
> footer {
text-align: center;
&:empty {
display: none;
}
> button {
margin: 0;
padding: 16px;
width: 100%;
border-radius: var(--radius);
&:disabled {
opacity: 0.7;
}
}
> .more > .button {
margin-left: auto;
margin-right: auto;
height: 48px;
width: 100%;
}
}
</style>

View File

@ -6,6 +6,7 @@
<fa :icon="faPlus" v-if="notification.type === 'follow'"/>
<fa :icon="faClock" v-if="notification.type === 'receiveFollowRequest'"/>
<fa :icon="faCheck" v-if="notification.type === 'followRequestAccepted'"/>
<fa :icon="faIdCardAlt" v-if="notification.type === 'groupInvited'"/>
<fa :icon="faRetweet" v-if="notification.type === 'renote'"/>
<fa :icon="faReply" v-if="notification.type === 'reply'"/>
<fa :icon="faAt" v-if="notification.type === 'mention'"/>
@ -40,13 +41,14 @@
<span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ $t('youGotNewFollower') }}<div v-if="full"><mk-follow-button :user="notification.user" :full="true"/></div></span>
<span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ $t('followRequestAccepted') }}</span>
<span v-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ $t('receiveFollowRequest') }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ $t('accept') }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ $t('reject') }}</button></div></span>
<span v-if="notification.type === 'groupInvited'" class="text" style="opacity: 0.6;">{{ $t('groupInvited') }}: <b>{{ notification.invitation.group.name }}</b><div v-if="full && !groupInviteDone"><button class="_textButton" @click="acceptGroupInvitation()">{{ $t('accept') }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ $t('reject') }}</button></div></span>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faCheck } from '@fortawesome/free-solid-svg-icons';
import { faIdCardAlt, faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faCheck } from '@fortawesome/free-solid-svg-icons';
import { faClock } from '@fortawesome/free-regular-svg-icons';
import getNoteSummary from '../../misc/get-note-summary';
import XReactionIcon from './reaction-icon.vue';
@ -78,7 +80,8 @@ export default Vue.extend({
return {
getNoteSummary,
followRequestDone: false,
faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faClock, faCheck
groupInviteDone: false,
faIdCardAlt, faPlus, faQuoteLeft, faQuoteRight, faRetweet, faReply, faAt, faClock, faCheck
};
},
methods: {
@ -90,6 +93,18 @@ export default Vue.extend({
this.followRequestDone = true;
this.$root.api('following/requests/reject', { userId: this.notification.user.id });
},
acceptGroupInvitation() {
this.groupInviteDone = true;
this.$root.api('users/groups/invitations/accept', { invitationId: this.notification.invitation.id });
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
},
rejectGroupInvitation() {
this.groupInviteDone = true;
this.$root.api('users/groups/invitations/reject', { invitationId: this.notification.invitation.id });
},
}
});
</script>
@ -149,7 +164,7 @@ export default Vue.extend({
height: 100%;
}
&.follow, &.followRequestAccepted, &.receiveFollowRequest {
&.follow, &.followRequestAccepted, &.receiveFollowRequest, &.groupInvited {
padding: 3px;
background: #36aed2;
}

View File

@ -21,7 +21,7 @@
<div class="form">
<x-note-preview class="preview" v-if="reply" :note="reply"/>
<x-note-preview class="preview" v-if="renote" :note="renote"/>
<div class="with-quote" v-if="quoteId"><fa icon="quote-left"/> {{ $t('@.post-form.quote-attached') }}<button @click="quoteId = null"><fa icon="times"/></button></div>
<div class="with-quote" v-if="quoteId"><fa icon="quote-left"/> {{ $t('quoteAttached') }}<button @click="quoteId = null"><fa icon="times"/></button></div>
<div v-if="visibility === 'specified'" class="to-specified">
<span style="margin-right: 8px;">{{ $t('recipient') }}</span>
<div class="visibleUsers">
@ -445,7 +445,7 @@ export default Vue.extend({
this.$root.dialog({
type: 'info',
text: this.$t('@.post-form.quote-question'),
text: this.$t('quoteQuestion'),
showCancelButton: true
}).then(({ canceled }) => {
if (canceled) {
@ -610,6 +610,7 @@ export default Vue.extend({
right: 0;
> .text-count {
opacity: 0.7;
line-height: 66px;
@media (max-width: 500px) {

View File

@ -13,7 +13,7 @@
mode="out-in"
appear
>
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :tabindex="i + 1" :title="/^[a-z]+$/.test(reaction) ? $t('@.reactions.' + reaction) : reaction"><x-reaction-icon :reaction="reaction"/></button>
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :tabindex="i + 1" :title="reaction"><x-reaction-icon :reaction="reaction"/></button>
</transition-group>
<input class="text" v-model="text" :placeholder="$t('enterEmoji')" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
</div>

View File

@ -40,23 +40,31 @@ export default Vue.extend({
i: 0,
methods: {
beforeEnter(el) {
if (document.hidden) return;
el.style.opacity = 0;
el.style.transform = this.direction === 'down' ? 'translateY(-64px)' : 'translateY(64px)';
let index = this.$options.i;
const delay = this.delay * index;
const delay = this.delay * this.$options.i;
el.style.transition = [getComputedStyle(el).transition, `transform 0.7s cubic-bezier(0.23, 1, 0.32, 1) ${delay}ms`, `opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1) ${delay}ms`].filter(x => x != '').join(',');
this.$options.i++;
},
enter(el, done) {
setTimeout(() => {
el.style.transition = null;
el.style.transform = null;
el.style.opacity = null;
this.$options.i--;
}, delay + 710);
},
enter(el) {
if (document.hidden) {
el.style.opacity = 1;
el.style.transform = 'translateY(0px)';
el.addEventListener('transitionend', () => {
el.style.transition = '';
this.$options.i--;
done();
}, { once: true });
});
} else {
setTimeout(() => { // 必要
el.style.opacity = 1;
el.style.transform = 'translateY(0px)';
});
}
},
leave(el) {
el.style.opacity = 0;

View File

@ -12,15 +12,15 @@
<template #prefix><fa :icon="faLock"/></template>
</mk-input>
<mk-button type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $t('loggingIn') : $t('login') }}</mk-button>
<p v-if="meta && meta.enableTwitterIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/twitter`"><fa :icon="['fab', 'twitter']"/> {{ $t('signin-with-twitter') }}</a></p>
<p v-if="meta && meta.enableGithubIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/github`"><fa :icon="['fab', 'github']"/> {{ $t('signin-with-github') }}</a></p>
<p v-if="meta && meta.enableDiscordIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/discord`"><fa :icon="['fab', 'discord']"/> {{ $t('signin-with-discord') /* TODO: Make these layouts better */ }}</a></p>
<p v-if="meta && meta.enableTwitterIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/twitter`"><fa :icon="faTwitter"/> {{ $t('signinWith', { x: 'Twitter' }) }}</a></p>
<p v-if="meta && meta.enableGithubIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/github`"><fa :icon="faGithub"/> {{ $t('signinWith', { x: 'GitHub' }) }}</a></p>
<p v-if="meta && meta.enableDiscordIntegration" style="margin: 8px 0;"><a :href="`${apiUrl}/signin/discord`"><fa :icon="faDiscord"/> {{ $t('signinWith', { x: 'Discord' }) }}</a></p>
</div>
<div class="2fa-signin" v-if="totpLogin" :class="{ securityKeys: user && user.securityKeys }">
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
<p>{{ $t('tap-key') }}</p>
<p>{{ $t('tapSecurityKey') }}</p>
<mk-button @click="queryKey" v-if="!queryingKey">
{{ $t('@.error.retry') }}
{{ $t('retry') }}
</mk-button>
</div>
<div class="or-hr" v-if="user && user.securityKeys">
@ -46,6 +46,7 @@
import Vue from 'vue';
import { toUnicode } from 'punycode';
import { faLock, faGavel } from '@fortawesome/free-solid-svg-icons';
import { faTwitter, faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons';
import MkButton from './ui/button.vue';
import MkInput from './ui/input.vue';
import i18n from '../i18n';
@ -86,7 +87,7 @@ export default Vue.extend({
credential: null,
challengeData: null,
queryingKey: false,
faLock, faGavel
faLock, faGavel, faTwitter, faDiscord, faGithub
};
},

View File

@ -2,9 +2,8 @@
<form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()">
<template v-if="meta">
<mk-input v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required>
<span>{{ $t('invitation-code') }}</span>
<template #prefix><fa icon="id-card-alt"/></template>
<template #desc v-html="this.$t('invitation-info').replace('{}', 'mailto:' + meta.maintainerEmail)"></template>
<span>{{ $t('invitationCode') }}</span>
<template #prefix><fa :icon="faKey"/></template>
</mk-input>
<mk-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername">
<span>{{ $t('username') }}</span>
@ -15,26 +14,26 @@
<span v-if="usernameState == 'ok'" style="color:#3CB7B5"><fa :icon="faCheck" fixed-width/> {{ $t('available') }}</span>
<span v-if="usernameState == 'unavailable'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('unavailable') }}</span>
<span v-if="usernameState == 'error'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('error') }}</span>
<span v-if="usernameState == 'invalid-format'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('invalid-format') }}</span>
<span v-if="usernameState == 'min-range'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('too-short') }}</span>
<span v-if="usernameState == 'max-range'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('too-long') }}</span>
<span v-if="usernameState == 'invalid-format'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('usernameInvalidFormat') }}</span>
<span v-if="usernameState == 'min-range'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('tooShort') }}</span>
<span v-if="usernameState == 'max-range'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('tooLong') }}</span>
</template>
</mk-input>
<mk-input v-model="password" type="password" :autocomplete="Math.random()" required @input="onChangePassword">
<span>{{ $t('password') }}</span>
<template #prefix><fa :icon="faLock"/></template>
<template #desc>
<p v-if="passwordStrength == 'low'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('weak-password') }}</p>
<p v-if="passwordStrength == 'medium'" style="color:#3CB7B5"><fa :icon="faCheck" fixed-width/> {{ $t('normal-password') }}</p>
<p v-if="passwordStrength == 'high'" style="color:#3CB7B5"><fa :icon="faCheck" fixed-width/> {{ $t('strong-password') }}</p>
<p v-if="passwordStrength == 'low'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('weakPassword') }}</p>
<p v-if="passwordStrength == 'medium'" style="color:#3CB7B5"><fa :icon="faCheck" fixed-width/> {{ $t('normalPassword') }}</p>
<p v-if="passwordStrength == 'high'" style="color:#3CB7B5"><fa :icon="faCheck" fixed-width/> {{ $t('strongPassword') }}</p>
</template>
</mk-input>
<mk-input v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @input="onChangePasswordRetype">
<span>{{ $t('password') }} ({{ $t('retype') }})</span>
<template #prefix><fa :icon="faLock"/></template>
<template #desc>
<p v-if="passwordRetypeState == 'match'" style="color:#3CB7B5"><fa :icon="faCheck" fixed-width/> {{ $t('password-matched') }}</p>
<p v-if="passwordRetypeState == 'not-match'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('password-not-matched') }}</p>
<p v-if="passwordRetypeState == 'match'" style="color:#3CB7B5"><fa :icon="faCheck" fixed-width/> {{ $t('passwordMatched') }}</p>
<p v-if="passwordRetypeState == 'not-match'" style="color:#FF1161"><fa :icon="faExclamationTriangle" fixed-width/> {{ $t('passwordNotMatched') }}</p>
</template>
</mk-input>
<mk-switch v-model="ToSAgreement" v-if="meta.tosUrl">
@ -50,7 +49,7 @@
<script lang="ts">
import Vue from 'vue';
import { faLock, faExclamationTriangle, faSpinner, faCheck } from '@fortawesome/free-solid-svg-icons';
import { faLock, faExclamationTriangle, faSpinner, faCheck, faKey } from '@fortawesome/free-solid-svg-icons';
const getPasswordStrength = require('syuilo-password-strength');
import { toUnicode } from 'punycode';
import i18n from '../i18n';
@ -81,7 +80,7 @@ export default Vue.extend({
passwordRetypeState: null,
submitting: false,
ToSAgreement: false,
faLock, faExclamationTriangle, faSpinner, faCheck
faLock, faExclamationTriangle, faSpinner, faCheck, faKey
}
},
@ -175,7 +174,7 @@ export default Vue.extend({
this.$root.dialog({
type: 'error',
text: this.$t('some-error')
text: this.$t('error')
});
if (this.meta.enableRecaptcha) {

View File

@ -48,7 +48,7 @@ export default Vue.extend({
ago >= 10 ? this.$t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) :
ago >= -1 ? this.$t('_ago.justNow') :
ago < -1 ? this.$t('_ago.future') :
this.$t('@.time.unknown'));
this.$t('_ago.unknown'));
}
},
created() {

View File

@ -239,6 +239,10 @@ export default Vue.extend({
position: relative;
margin: 32px 0;
&:not(.inline):first-child {
margin-top: 8px;
}
> .icon {
position: absolute;
top: 0;

View File

@ -5,9 +5,9 @@
<slot name="empty"></slot>
</div>
<div class="more" v-if="more" key="_more_">
<mk-button :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()">
<mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary>
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
<template v-if="moreFetching"><fa :icon="faSpinner" pulse fixed-width/></template>
<template v-if="moreFetching"><mk-loading inline/></template>
</mk-button>
</div>
</sequential-entrance>
@ -15,7 +15,6 @@
<script lang="ts">
import Vue from 'vue';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import MkButton from './button.vue';
import paging from '../../scripts/paging';
@ -37,12 +36,6 @@ export default Vue.extend({
default: true
}
},
data() {
return {
faSpinner
};
},
});
</script>
@ -55,5 +48,12 @@ export default Vue.extend({
margin-bottom: 8px;
}
}
> .more > .button {
margin-left: auto;
margin-right: auto;
height: 48px;
min-width: 150px;
}
}
</style>

View File

@ -77,6 +77,10 @@ export default Vue.extend({
position: relative;
margin: 32px 0;
&:not(.inline):first-child {
margin-top: 8px;
}
> .icon {
position: absolute;
top: 0;

View File

@ -85,6 +85,10 @@ export default Vue.extend({
margin: 42px 0 32px 0;
position: relative;
&:first-child {
margin-top: 16px;
}
&:last-child {
margin-bottom: 0;
}

View File

@ -23,7 +23,7 @@
<mk-follow-button class="koudoku-button" v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" mini/>
</div>
<button class="more" :class="{ fetching: moreFetching }" v-if="more" @click="fetchMore()" :disabled="moreFetching">
<template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>{{ moreFetching ? $t('@.loading') : $t('@.load-more') }}
<template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>{{ moreFetching ? $t('loading') : $t('loadMore') }}
</button>
</div>
</mk-container>

View File

@ -3,7 +3,7 @@ declare const _VERSION_: string;
declare const _ENV_: string;
const address = new URL(location.href);
const siteName = document.querySelector('meta[property="og:site_name"]') as HTMLMetaElement;
const siteName = (document.querySelector('meta[property="og:site_name"]') as HTMLMetaElement)?.content;
export const host = address.host;
export const hostname = address.hostname;
@ -15,4 +15,4 @@ export const langs = _LANGS_;
export const locale = JSON.parse(localStorage.getItem('locale'));
export const version = _VERSION_;
export const env = _ENV_;
export const instanceName = siteName && siteName.content ? siteName.content : 'Misskey';
export const instanceName = siteName === 'Misskey' ? null : siteName;

View File

@ -61,20 +61,22 @@ if (localStorage.getItem('theme') == null) {
}
//#region Detect the user language
let lang = null;
let lang = localStorage.getItem('lang');
if (langs.map(x => x[0]).includes(navigator.language)) {
lang = navigator.language;
} else {
lang = langs.map(x => x[0]).find(x => x.split('-')[0] == navigator.language);
if (lang == null) {
if (langs.map(x => x[0]).includes(navigator.language)) {
lang = navigator.language;
} else {
lang = langs.map(x => x[0]).find(x => x.split('-')[0] == navigator.language);
if (lang == null) {
// Fallback
lang = 'en-US';
if (lang == null) {
// Fallback
lang = 'en-US';
}
}
}
localStorage.setItem('lang', lang);
localStorage.setItem('lang', lang);
}
//#endregion
// Detect the user agent
@ -147,7 +149,7 @@ os.init(async () => {
store: os.store,
metaInfo: {
title: null,
titleTemplate: title => title ? `${title} | ${instanceName}` : instanceName
titleTemplate: title => title ? `${title} | ${(instanceName || 'Misskey')}` : (instanceName || 'Misskey')
},
data() {
return {

View File

@ -42,9 +42,9 @@ export default class MiOS extends EventEmitter {
* @param callback A function that call when initialized
*/
@autobind
public async init(_callback) {
const callback = () => {
_callback();
public async init(callback) {
const finish = () => {
callback();
this.store.dispatch('instance/fetch').then(() => {
// Init service worker
@ -104,7 +104,7 @@ export default class MiOS extends EventEmitter {
this.initStream();
// Finish init
callback();
finish();
};
// キャッシュがあったとき
@ -133,7 +133,7 @@ export default class MiOS extends EventEmitter {
this.initStream();
// Finish init
callback();
finish();
}
});
}

View File

@ -0,0 +1,79 @@
<template>
<div class="znqjceqz">
<portal to="title">🍀 {{ $t('aboutMisskey') }}</portal>
<section class="_card">
<div class="_title">🍀 {{ $t('aboutMisskey') }}</div>
<div class="_content">
<div style="margin-bottom: 1em;">{{ $t('aboutMisskeyText') }}</div>
<div>🛠 {{ $t('misskeyMembers') }}</div>
<ul class="members">
<li><mk-link url="https://github.com/syuilo" class="at">@syuilo</mk-link></li>
<li><mk-link url="https://github.com/AyaMorisawa" class="at">@AyaMorisawa</mk-link></li>
<li><mk-link url="https://github.com/mei23" class="at">@mei23</mk-link></li>
<li><mk-link url="https://github.com/acid-chicken" class="at">@acid-chicken</mk-link></li>
<li><mk-link url="https://github.com/tamaina" class="at">@tamaina</mk-link></li>
<li><mk-link url="https://github.com/rinsuki" class="at">@rinsuki</mk-link></li>
</ul>
<div style="margin-top: 1em;">📦 {{ $t('misskeySource') }}</div>
<mk-url url="https://github.com/syuilo/misskey"/>
<div style="margin-top: 1em;">🌏 {{ $t('misskeyTranslation') }}</div>
<mk-url url="https://crowdin.com/project/misskey"/>
<div style="margin-top: 1em;">💴 {{ $t('misskeyDonate') }}</div>
<mk-url url="https://www.patreon.com/syuilo"/>
</div>
<div class="_content">
<span><mfm text="<motion>❤</motion>"/> {{ $t('patrons') }}</span>
<ul>
<li>Gargron</li>
<li>Satsuki Yanagi</li>
<li>noellabo</li>
<li>naga_rus</li>
<li>Melilot</li>
<li>AureoleArk</li>
<li>Peter G.</li>
<li>motcha</li>
<li>Atsuko Tominaga</li>
<li>dansup</li>
<li>Nokotaro Takeda</li>
<li>YUKIMOCHI</li>
<li>nanami kan</li>
<li>Hekovic</li>
<li>wara</li>
<li>Takashi Shibuya</li>
<li>Noizeman</li>
</ul>
<span>{{ $t('morePatrons') }}</span>
</div>
</section>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { version } from '../config';
import i18n from '../i18n';
import MkLink from '../components/link.vue';
export default Vue.extend({
i18n,
components: {
MkLink
},
metaInfo() {
return {
title: this.$t('aboutMisskey') as string
};
},
data() {
return {
version,
faInfoCircle
}
},
});
</script>

View File

@ -6,7 +6,7 @@
<section class="_card info" v-if="meta">
<div class="_title"><fa :icon="faInfoCircle"/> {{ $t('instanceInfo') }}</div>
<div class="_content" v-if="meta.description">
<div>{{ meta.description }}</div>
<div v-html="meta.description"></div>
</div>
<div class="_content table">
<div><b>{{ $t('administrator') }}</b><span>{{ meta.maintainerName }}</span></div>
@ -20,49 +20,6 @@
<div><b>Misskey</b><span>v{{ version }}</span></div>
</div>
</section>
<section class="_card aboutMisskey">
<div class="_title"><fa :icon="faInfoCircle"/> {{ $t('aboutMisskey') }}</div>
<div class="_content">
<div style="margin-bottom: 1em;">{{ $t('aboutMisskeyText') }}</div>
<div>{{ $t('misskeyMembers') }}</div>
<span class="members">
<a href="https://github.com/syuilo" target="_blank" class="_link">@syuilo</a>
<a href="https://github.com/AyaMorisawa" target="_blank" class="_link">@AyaMorisawa</a>
<a href="https://github.com/mei23" target="_blank" class="_link">@mei23</a>
<a href="https://github.com/acid-chicken" target="_blank" class="_link">@acid-chicken</a>
<a href="https://github.com/tamaina" target="_blank" class="_link">@tamaina</a>
<a href="https://github.com/rinsuki" target="_blank" class="_link">@rinsuki</a>
</span>
<div style="margin-top: 1em;">{{ $t('misskeySource') }}</div>
<mk-url url="https://github.com/syuilo/misskey"/>
<div style="margin-top: 1em;">{{ $t('misskeyDonate') }}</div>
<mk-url url="https://www.patreon.com/syuilo"/>
</div>
<div class="_content">
<span><mfm text="<motion>❤</motion>"/> {{ $t('patrons') }}</span>
<ul>
<li>Gargron</li>
<li>Satsuki Yanagi</li>
<li>noellabo</li>
<li>naga_rus</li>
<li>Melilot</li>
<li>AureoleArk</li>
<li>Peter G.</li>
<li>motcha</li>
<li>Atsuko Tominaga</li>
<li>dansup</li>
<li>Nokotaro Takeda</li>
<li>YUKIMOCHI</li>
<li>nanami kan</li>
<li>Hekovic</li>
<li>wara</li>
<li>Takashi Shibuya</li>
<li>Noizeman</li>
</ul>
<span>{{ $t('morePatrons') }}</span>
</div>
</section>
</div>
</template>
@ -117,15 +74,5 @@ export default Vue.extend({
}
}
}
> .aboutMisskey {
> ._content {
> .members {
> a {
margin-right: 0.5em;
}
}
}
}
}
</style>

View File

@ -10,7 +10,7 @@
import Vue from 'vue';
import XSetup from './index.welcome.setup.vue';
import XEntrance from './index.welcome.entrance.vue';
import { getInstanceName } from '../scripts/get-instance-name';
import { instanceName } from '../config';
export default Vue.extend({
components: {
@ -20,7 +20,7 @@ export default Vue.extend({
data() {
return {
instanceName: getInstanceName(),
instanceName: instanceName || 'Misskey',
}
},

View File

@ -5,7 +5,7 @@
<mk-button @click="add()" primary style="margin: 0 auto 16px auto;"><fa :icon="faPlus"/> {{ $t('add') }}</mk-button>
<section class="_card announcements">
<div class="_content announcement" v-for="announcement in announcements">
<mk-input v-model="announcement.title" style="margin-top: 8px;">
<mk-input v-model="announcement.title">
<span>{{ $t('title') }}</span>
</mk-input>
<mk-textarea v-model="announcement.text">

View File

@ -2,10 +2,10 @@
<div class="mk-instance-emojis">
<portal to="icon"><fa :icon="faLaugh"/></portal>
<portal to="title">{{ $t('customEmojis') }}</portal>
<section class="_card local">
<div class="_title"><fa :icon="faLaugh"/> {{ $t('customEmojis') }}</div>
<div class="_content">
<input ref="file" type="file" style="display: none;" @change="onChangeFile"/>
<mk-pagination :pagination="pagination" class="emojis" ref="emojis">
<template #empty><span>{{ $t('noCustomEmojis') }}</span></template>
<template #default="{items}">
@ -13,20 +13,30 @@
<img :src="emoji.url" class="img" :alt="emoji.name"/>
<div class="body">
<span class="name">{{ emoji.name }}</span>
<span class="info">
<b class="category">{{ emoji.category }}</b>
<span class="aliases">{{ emoji.aliases.join(' ') }}</span>
</span>
</div>
</div>
</template>
</mk-pagination>
</div>
<div class="_footer">
<mk-button inline primary @click="add()"><fa :icon="faPlus"/> {{ $t('addEmoji') }}</mk-button>
<div class="_content" v-if="selected">
<mk-input v-model="name"><span>{{ $t('name') }}</span></mk-input>
<mk-input v-model="category"><span>{{ $t('category') }}</span></mk-input>
<mk-input v-model="aliases"><span>{{ $t('tags') }}</span></mk-input>
<mk-button inline primary @click="update"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
<mk-button inline :disabled="selected == null" @click="del()"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</mk-button>
</div>
<div class="_footer">
<mk-button inline primary @click="add"><fa :icon="faPlus"/> {{ $t('addEmoji') }}</mk-button>
</div>
</section>
<section class="_card remote">
<div class="_title"><fa :icon="faLaugh"/> {{ $t('customEmojisOfRemote') }}</div>
<div class="_content">
<mk-input v-model="host" :debounce="true" style="margin-top: 0;"><span>{{ $t('host') }}</span></mk-input>
<mk-input v-model="host" :debounce="true"><span>{{ $t('host') }}</span></mk-input>
<mk-pagination :pagination="remotePagination" class="emojis" ref="remoteEmojis">
<template #empty><span>{{ $t('noCustomEmojis') }}</span></template>
<template #default="{items}">
@ -34,7 +44,7 @@
<img :src="emoji.url" class="img" :alt="emoji.name"/>
<div class="body">
<span class="name">{{ emoji.name }}</span>
<span class="host">{{ emoji.host }}</span>
<span class="info">{{ emoji.host }}</span>
</div>
</div>
</template>
@ -49,12 +59,12 @@
<script lang="ts">
import Vue from 'vue';
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import { faPlus, faSave } from '@fortawesome/free-solid-svg-icons';
import { faTrashAlt, faLaugh } from '@fortawesome/free-regular-svg-icons';
import MkButton from '../../components/ui/button.vue';
import MkInput from '../../components/ui/input.vue';
import MkPagination from '../../components/ui/pagination.vue';
import { apiUrl } from '../../config';
import { selectFile } from '../../scripts/select-file';
export default Vue.extend({
metaInfo() {
@ -71,9 +81,11 @@ export default Vue.extend({
data() {
return {
name: null,
selected: null,
selectedRemote: null,
name: null,
category: null,
aliases: null,
host: '',
pagination: {
endpoint: 'admin/emoji/list',
@ -86,52 +98,38 @@ export default Vue.extend({
host: this.host ? this.host : null
})
},
faTrashAlt, faPlus, faLaugh
faTrashAlt, faPlus, faLaugh, faSave
}
},
watch: {
host() {
this.$refs.remoteEmojis.reload();
},
selected() {
this.name = this.selected ? this.selected.name : null;
this.category = this.selected ? this.selected.category : null;
this.aliases = this.selected ? this.selected.aliases.join(' ') : null;
}
},
methods: {
async add() {
const { canceled: canceled, result: name } = await this.$root.dialog({
title: this.$t('emojiName'),
input: true
});
if (canceled) return;
this.name = name;
(this.$refs.file as any).click();
},
onChangeFile() {
const [file] = Array.from((this.$refs.file as any).files);
if (file == null) return;
const data = new FormData();
data.append('file', file);
data.append('name', this.name);
data.append('i', this.$store.state.i.token);
async add(e) {
const files = await selectFile(this, e.currentTarget || e.target, null, true);
const dialog = this.$root.dialog({
type: 'waiting',
text: this.$t('uploading') + '...',
text: this.$t('doing') + '...',
showOkButton: false,
showCancelButton: false,
cancelableByBgClick: false
});
fetch(apiUrl + '/admin/emoji/add', {
method: 'POST',
body: data
})
.then(response => response.json())
.then(f => {
Promise.all(files.map(file => this.$root.api('admin/emoji/add', {
fileId: file.id,
})))
.then(() => {
this.$refs.emojis.reload();
this.$root.dialog({
type: 'success',
@ -143,6 +141,22 @@ export default Vue.extend({
});
},
async update() {
await this.$root.api('admin/emoji/update', {
id: this.selected.id,
name: this.name,
category: this.category,
aliases: this.aliases.split(' '),
});
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
this.$refs.emojis.reload();
},
async del() {
const { canceled } = await this.$root.dialog({
type: 'warning',
@ -207,6 +221,18 @@ export default Vue.extend({
> .name {
display: block;
}
> .info {
opacity: 0.5;
> .category {
margin-right: 16px;
}
> .aliases {
font-style: oblique;
}
}
}
}
}
@ -241,7 +267,7 @@ export default Vue.extend({
display: block;
}
> .host {
> .info {
opacity: 0.5;
}
}

View File

@ -6,7 +6,7 @@
<section class="_card info">
<div class="_title"><fa :icon="faInfoCircle"/> {{ $t('basicInfo') }}</div>
<div class="_content">
<mk-input v-model="name" style="margin-top: 8px;">{{ $t('instanceName') }}</mk-input>
<mk-input v-model="name">{{ $t('instanceName') }}</mk-input>
<mk-textarea v-model="description">{{ $t('instanceDescription') }}</mk-textarea>
<mk-input v-model="iconUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('iconUrl') }}</mk-input>
<mk-input v-model="bannerUrl"><template #icon><fa :icon="faLink"/></template>{{ $t('bannerUrl') }}</mk-input>
@ -43,8 +43,8 @@
<div class="_content">
<mk-switch v-model="enableRecaptcha">{{ $t('enableRecaptcha') }}</mk-switch>
<template v-if="enableRecaptcha">
<mk-info>{{ $t('recaptcha-info') }}</mk-info>
<mk-info warn>{{ $t('recaptcha-info2') }}</mk-info>
<mk-info>{{ $t('recaptchaInfo') }}</mk-info>
<mk-info warn>{{ $t('recaptchaInfo2') }}</mk-info>
<mk-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><template #icon><fa :icon="faKey"/></template>{{ $t('recaptchaSiteKey') }}</mk-input>
<mk-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><template #icon><fa :icon="faKey"/></template>{{ $t('recaptchaSecretKey') }}</mk-input>
</template>
@ -61,12 +61,12 @@
<section class="_card">
<div class="_title"><fa :icon="faBolt"/> {{ $t('serviceworker') }}</div>
<div class="_content">
<mk-switch v-model="enableServiceWorker">{{ $t('enableServiceworker') }}<template #desc>{{ $t('serviceworker-info') }}</template></mk-switch>
<mk-switch v-model="enableServiceWorker">{{ $t('enableServiceworker') }}<template #desc>{{ $t('serviceworkerInfo') }}</template></mk-switch>
<template v-if="enableServiceWorker">
<mk-info>{{ $t('vapid-info') }}<br><code>npm i web-push -g<br>web-push generate-vapid-keys</code></mk-info>
<mk-info>{{ $t('vapidInfo') }}<br><code>npx web-push generate-vapid-keys</code></mk-info>
<mk-horizon-group inputs class="fit-bottom">
<mk-input v-model="swPublicKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>{{ $t('vapid-publickey') }}</mk-input>
<mk-input v-model="swPrivateKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>{{ $t('vapid-privatekey') }}</mk-input>
<mk-input v-model="swPublicKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>Public key</mk-input>
<mk-input v-model="swPrivateKey" :disabled="!enableServiceWorker"><template #icon><fa :icon="faKey"/></template>Private key</mk-input>
</mk-horizon-group>
</template>
</div>
@ -78,7 +78,7 @@
<section class="_card">
<div class="_title"><fa :icon="faThumbtack"/> {{ $t('pinnedUsers') }}</div>
<div class="_content">
<mk-textarea v-model="pinnedUsers" style="margin-top: 0;">
<mk-textarea v-model="pinnedUsers">
<template #desc>{{ $t('pinnedUsersDescription') }} <button class="_textButton" @click="addPinUser">{{ $t('addUser') }}</button></template>
</mk-textarea>
</div>
@ -111,7 +111,7 @@
<section class="_card">
<div class="_title"><fa :icon="faBan"/> {{ $t('blockedInstances') }}</div>
<div class="_content">
<mk-textarea v-model="blockedHosts" style="margin-top: 0;">
<mk-textarea v-model="blockedHosts">
<template #desc>{{ $t('blockedInstancesDescription') }}</template>
</mk-textarea>
</div>

View File

@ -6,7 +6,7 @@
<section class="_card lookup">
<div class="_title"><fa :icon="faSearch"/> {{ $t('lookup') }}</div>
<div class="_content">
<mk-input class="target" v-model="target" type="text" @enter="showUser()" style="margin-top: 0;">
<mk-input class="target" v-model="target" type="text" @enter="showUser()">
<span>{{ $t('usernameOrUserId') }}</span>
</mk-input>
<mk-button @click="showUser()" primary><fa :icon="faSearch"/> {{ $t('lookup') }}</mk-button>

View File

@ -100,7 +100,7 @@ export default Vue.extend({
const formatted = `${formatTimeString(new Date(file.lastModified), this.$store.state.settings.pastedFileName).replace(/{{number}}/g, '1')}${ext}`;
const name = this.$store.state.settings.pasteDialog
? await this.$root.dialog({
title: this.$t('@.post-form.enter-file-name'),
title: this.$t('enterFileName'),
input: {
default: formatted
},

View File

@ -3,8 +3,8 @@
<mk-avatar class="avatar" :user="message.user"/>
<div class="content">
<div class="balloon _panel" :data-no-text="message.text == null">
<button class="delete-button" v-if="isMe" :title="$t('@.delete')" @click="del">
<img src="/assets/desktop/remove.png" alt="Delete"/>
<button class="delete-button" v-if="isMe" :title="$t('delete')" @click="del">
<img src="/assets/remove.png" alt="Delete"/>
</button>
<div class="content" v-if="!message.isDeleted">
<mfm class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/>

View File

@ -14,10 +14,10 @@
<div class="body">
<mk-loading v-if="fetching"/>
<p class="empty" v-if="!fetching && messages.length == 0"><fa icon="info-circle"/>{{ user ? $t('not-talked-user') : $t('not-talked-group') }}</p>
<p class="empty" v-if="!fetching && messages.length == 0"><fa :icon="faInfoCircle"/>{{ $t('noMessagesYet') }}</p>
<p class="no-history" v-if="!fetching && messages.length > 0 && !existMoreMessages"><fa :icon="faFlag"/>{{ $t('noMoreHistory') }}</p>
<button class="more _button" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('loading') : $t('loadMore') }}
</button>
<x-list class="messages" :items="messages" v-slot="{ item: message, i }" direction="up" reversed>
<x-message :message="message" :is-group="group != null" :key="message.id"/>
@ -26,7 +26,7 @@
<footer>
<transition name="fade">
<div class="new-message" v-show="showIndicator">
<button class="_buttonPrimary" @click="onIndicatorClick"><i><fa :icon="faArrowCircleDown"/></i>{{ $t('new-message') }}</button>
<button class="_buttonPrimary" @click="onIndicatorClick"><i><fa :icon="faArrowCircleDown"/></i>{{ $t('newMessageExists') }}</button>
</div>
</transition>
<x-form v-if="!fetching" :user="user" :group="group" ref="form"/>
@ -36,7 +36,7 @@
<script lang="ts">
import Vue from 'vue';
import { faArrowCircleDown, faFlag, faUsers } from '@fortawesome/free-solid-svg-icons';
import { faArrowCircleDown, faFlag, faUsers, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import i18n from '../i18n';
import XList from '../components/date-separated-list.vue';
import XMessage from './messaging-room.message.vue';
@ -64,7 +64,7 @@ export default Vue.extend({
connection: null,
showIndicator: false,
timer: null,
faArrowCircleDown, faFlag, faUsers
faArrowCircleDown, faFlag, faUsers, faInfoCircle
};
},
@ -139,7 +139,7 @@ export default Vue.extend({
} else if (e.dataTransfer.files.length > 1) {
this.$root.dialog({
type: 'error',
text: this.$t('only-one-file-attached')
text: this.$t('onlyOneFileCanBeAttached')
});
return;
}

View File

@ -31,7 +31,10 @@
</div>
</router-link>
</sequential-entrance>
<p class="no-history" v-if="!fetching && messages.length == 0">{{ $t('no-history') }}</p>
<div class="no-history" v-if="!fetching && messages.length == 0">
<img src="https://xn--931a.moe/assets/info.png" alt="" class="_ghost"/>
<div>{{ $t('noHistory') }}</div>
</div>
<mk-loading v-if="fetching"/>
</div>
</template>
@ -139,6 +142,14 @@ export default Vue.extend({
async startGroup() {
const groups1 = await this.$root.api('users/groups/owned');
const groups2 = await this.$root.api('users/groups/joined');
if (groups1.length === 0 && groups2.length === 0) {
this.$root.dialog({
type: 'warning',
title: this.$t('noGroups'),
text: this.$t('joinOrCreateGroup'),
});
return;
}
const { canceled, result: group } = await this.$root.dialog({
type: null,
title: this.$t('group'),
@ -277,11 +288,15 @@ export default Vue.extend({
}
> .no-history {
margin: 0;
padding: 2em 1em;
padding: 32px;
text-align: center;
color: #999;
font-weight: 500;
> img {
vertical-align: bottom;
height: 128px;
margin-bottom: 16px;
border-radius: 16px;
}
}
@media (max-width: 400px) {

View File

@ -2,7 +2,7 @@
<div class="shaynizk _card">
<div class="_title" v-if="antenna.name">{{ antenna.name }}</div>
<div class="_content body">
<mk-input v-model="name" style="margin-top: 8px;">
<mk-input v-model="name">
<span>{{ $t('name') }}</span>
</mk-input>
<mk-select v-model="src">

View File

@ -17,13 +17,13 @@
<mk-container :body-togglable="true">
<template #header><fa :icon="faEnvelopeOpenText"/> {{ $t('invites') }}</template>
<mk-pagination :pagination="invitePagination" #default="{items}" ref="invites">
<div class="_frame" v-for="invite in items" :key="invite.id">
<div class="_title">{{ invite.group.name }}</div>
<div class="_content"><mk-avatars :user-ids="invite.group.userIds"/></div>
<mk-pagination :pagination="invitationPagination" #default="{items}" ref="invitations">
<div class="_frame" v-for="invitation in items" :key="invitation.id">
<div class="_title">{{ invitation.group.name }}</div>
<div class="_content"><mk-avatars :user-ids="invitation.group.userIds"/></div>
<div class="_footer">
<mk-button @click="acceptInvite(invite)" primary inline><fa :icon="faCheck"/> {{ $t('accept') }}</mk-button>
<mk-button @click="rejectInvite(invite)" primary inline><fa :icon="faBan"/> {{ $t('reject') }}</mk-button>
<mk-button @click="acceptInvite(invitation)" primary inline><fa :icon="faCheck"/> {{ $t('accept') }}</mk-button>
<mk-button @click="rejectInvite(invitation)" primary inline><fa :icon="faBan"/> {{ $t('reject') }}</mk-button>
</div>
</div>
</mk-pagination>
@ -73,7 +73,7 @@ export default Vue.extend({
endpoint: 'users/groups/joined',
limit: 10,
},
invitePagination: {
invitationPagination: {
endpoint: 'i/user-group-invites',
limit: 10,
},
@ -95,23 +95,23 @@ export default Vue.extend({
iconOnly: true, autoClose: true
});
},
acceptInvite(invite) {
acceptInvite(invitation) {
this.$root.api('users/groups/invitations/accept', {
inviteId: invite.id
invitationId: invitation.id
}).then(() => {
this.$root.dialog({
type: 'success',
iconOnly: true, autoClose: true
});
this.$refs.invites.reload();
this.$refs.invitations.reload();
this.$refs.joined.reload();
});
},
rejectInvite(invite) {
rejectInvite(invitation) {
this.$root.api('users/groups/invitations/reject', {
inviteId: invite.id
invitationId: invitation.id
}).then(() => {
this.$refs.invites.reload();
this.$refs.invitations.reload();
});
}
}

View File

@ -5,7 +5,7 @@
<section class="_card">
<div class="_content">
<img src="https://xn--931a.moe/assets/not-found.jpg" alt=""/>
<img src="https://xn--931a.moe/assets/not-found.png" alt="" class="_ghost"/>
<div>{{ $t('notFoundDescription') }}</div>
</div>
</section>
@ -45,8 +45,6 @@ export default Vue.extend({
height: 150px;
margin-bottom: 16px;
border-radius: 16px;
pointer-events: none;
user-select: none;
}
}
}

View File

@ -2,7 +2,7 @@
<section class="_card">
<div class="_title"><fa :icon="faCog"/> {{ $t('general') }}</div>
<div class="_content">
<mk-input type="file" @change="onWallpaperChange" style="margin-top: 0;">
<mk-input type="file" @change="onWallpaperChange">
<span>{{ $t('wallpaper') }}</span>
<template #icon><fa :icon="faImage"/></template>
<template #desc v-if="wallpaperUploading">{{ $t('uploading') }}<mk-ellipsis/></template>
@ -23,10 +23,20 @@
<mk-button @click="readAllMessagingMessages">{{ $t('markAsReadAllTalkMessages') }}</mk-button>
</div>
<div class="_content">
<mk-switch v-model="reduceAnimation">
{{ $t('reduceUiAnimation') }}
<mk-switch v-model="disableAnimatedMfm">{{ $t('disableAnimatedMfm') }}</mk-switch>
<mk-switch v-model="reduceAnimation">{{ $t('reduceUiAnimation') }}</mk-switch>
<mk-switch v-model="useOsNativeEmojis">
{{ $t('useOsNativeEmojis') }}
<template #desc><mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></template>
</mk-switch>
</div>
<div class="_content">
<mk-select v-model="lang">
<template #label>{{ $t('uiLanguage') }}</template>
<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
</mk-select>
</div>
</section>
</template>
@ -36,8 +46,9 @@ import { faImage, faCog } from '@fortawesome/free-solid-svg-icons';
import MkInput from '../../components/ui/input.vue';
import MkButton from '../../components/ui/button.vue';
import MkSwitch from '../../components/ui/switch.vue';
import MkSelect from '../../components/ui/select.vue';
import i18n from '../../i18n';
import { apiUrl } from '../../config';
import { apiUrl, langs } from '../../config';
export default Vue.extend({
i18n,
@ -46,10 +57,13 @@ export default Vue.extend({
MkInput,
MkButton,
MkSwitch,
MkSelect,
},
data() {
return {
langs,
lang: localStorage.getItem('lang'),
wallpaperUploading: false,
faImage, faCog
}
@ -70,6 +84,24 @@ export default Vue.extend({
get() { return !this.$store.state.device.animation; },
set(value) { this.$store.commit('device/set', { key: 'animation', value: !value }); }
},
disableAnimatedMfm: {
get() { return !this.$store.state.device.animatedMfm; },
set(value) { this.$store.commit('device/set', { key: 'animatedMfm', value: !value }); }
},
useOsNativeEmojis: {
get() { return this.$store.state.device.useOsNativeEmojis; },
set(value) { this.$store.commit('device/set', { key: 'useOsNativeEmojis', value }); }
},
},
watch: {
lang() {
localStorage.setItem('lang', this.lang);
localStorage.removeItem('locale');
location.reload();
}
},
methods: {

View File

@ -2,8 +2,7 @@
<section class="_card">
<div class="_title"><fa :icon="faBoxes"/> {{ $t('importAndExport') }}</div>
<div class="_content">
<input ref="file" type="file" style="display: none;" @change="onChangeFile"/>
<mk-select v-model="exportTarget" style="margin-top: 0;">
<mk-select v-model="exportTarget">
<option value="notes">{{ $t('_exportOrImport.allNotes') }}</option>
<option value="following">{{ $t('_exportOrImport.followingList') }}</option>
<option value="user-lists">{{ $t('_exportOrImport.userLists') }}</option>
@ -13,6 +12,7 @@
<mk-button inline @click="doExport()"><fa :icon="faDownload"/> {{ $t('export') }}</mk-button>
<mk-button inline @click="doImport()" :disabled="!['following', 'user-lists'].includes(exportTarget)"><fa :icon="faUpload"/> {{ $t('import') }}</mk-button>
</div>
<input ref="file" type="file" style="display: none;" @change="onChangeFile"/>
</section>
</template>

View File

@ -2,7 +2,7 @@
<section class="_card">
<div class="_title"><fa :icon="faLaugh"/> {{ $t('reaction') }}</div>
<div class="_content">
<mk-textarea v-model="reactions" style="margin-top: 16px;">{{ $t('reaction') }}<template #desc>{{ $t('reactionSettingDescription') }}</template></mk-textarea>
<mk-textarea v-model="reactions">{{ $t('reaction') }}<template #desc>{{ $t('reactionSettingDescription') }}</template></mk-textarea>
</div>
<div class="_footer">
<mk-button @click="save()" primary inline :disabled="!changed"><fa :icon="faSave"/> {{ $t('save') }}</mk-button>

View File

@ -20,6 +20,7 @@ export const router = new VueRouter({
{ path: '/@:user/pages/:pageName/view-source', component: page('page-editor/page-editor'), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) },
{ path: '/announcements', component: page('announcements') },
{ path: '/about', component: page('about') },
{ path: '/about-misskey', component: page('about-misskey') },
{ path: '/featured', component: page('featured') },
{ path: '/docs', component: page('docs') },
{ path: '/docs/:doc', component: page('doc'), props: true },
@ -62,11 +63,16 @@ export const router = new VueRouter({
],
// なんかHacky
// 通常の使い方をすると scroll メソッドの behavior を設定できないため、自前で window.scroll するようにする
// setTimeout しないと、アニメーション(トランジション)の関係でうまく動かない
scrollBehavior(to) {
window._scroll = () => { // さらにHacky
if (to.name === 'index') {
window.scroll({ top: indexScrollPos, behavior: 'instant' });
const i = setInterval(() => {
window.scroll({ top: indexScrollPos, behavior: 'instant' });
}, 10);
setTimeout(() => {
clearInterval(i);
}, 500);
} else {
window.scroll({ top: 0, behavior: 'instant' });
}

View File

@ -1,8 +0,0 @@
export function getInstanceName() {
const siteName = document.querySelector('meta[property="og:site_name"]') as HTMLMetaElement;
if (siteName && siteName.content) {
return siteName.content;
}
return 'Misskey';
}

View File

@ -1,15 +1,45 @@
import Vue from 'vue';
function getScrollContainer(el: Element | null): Element | null {
if (el == null || el.tagName === 'BODY') return null;
const style = window.getComputedStyle(el);
if (style.getPropertyValue('overflow') === 'auto') {
return el;
} else {
return getScrollContainer(el.parentElement);
}
}
function getScrollPosition(el: Element | null): number {
const container = getScrollContainer(el);
return container == null ? window.scrollY : container.scrollTop;
}
function onScrollTop(el, cb) {
const container = getScrollContainer(el) || window;
const onScroll = ev => {
if (!document.body.contains(el)) return;
const pos = getScrollPosition(el);
if (pos === 0) {
cb();
container.removeEventListener('scroll', onscroll);
}
};
container.addEventListener('scroll', onScroll, { passive: true });
}
export default (opts) => ({
data() {
return {
items: [],
queue: [],
offset: 0,
fetching: true,
moreFetching: false,
inited: false,
more: false,
backed: false,
isBackTop: false,
};
},
@ -32,13 +62,17 @@ export default (opts) => ({
created() {
opts.displayLimit = opts.displayLimit || 30;
this.init();
this.$on('hook:activated', () => {
this.isBackTop = false;
});
this.$on('hook:deactivated', () => {
this.isBackTop = window.scrollY === 0;
});
},
methods: {
isScrollTop() {
return window.scrollY <= 8;
},
updateItem(i, item) {
Vue.set((this as any).items, i, item);
},
@ -107,21 +141,26 @@ export default (opts) => ({
});
},
prepend(item, silent = false) {
if (opts.onPrepend) {
const cancel = opts.onPrepend(this, item, silent);
if (cancel) return;
}
prepend(item) {
const isTop = this.isBackTop || (document.body.contains(this.$el) && (getScrollPosition(this.$el) === 0));
// Prepend the item
this.items.unshift(item);
if (isTop) {
// Prepend the item
this.items.unshift(item);
if (this.isScrollTop()) {
// オーバーフローしたら古い投稿は捨てる
// オーバーフローしたら古いアイテムは捨てる
if (this.items.length >= opts.displayLimit) {
this.items = this.items.slice(0, opts.displayLimit);
this.more = true;
}
} else {
this.queue.push(item);
onScrollTop(this.$el, () => {
for (const item of this.queue) {
this.prepend(item);
}
this.queue = [];
});
}
},

View File

@ -2,7 +2,7 @@ export default ($root: any) => {
if ($root.$store.getters.isSignedIn) return;
$root.dialog({
title: $root.$t('@.signin-required'),
title: $root.$t('signinRequired'),
text: null
});

View File

@ -1,8 +1,8 @@
import { faUpload, faCloud, faLink } from '@fortawesome/free-solid-svg-icons';
import { faUpload, faCloud } from '@fortawesome/free-solid-svg-icons';
import { selectDriveFile } from './select-drive-file';
import { apiUrl } from '../config';
export function selectFile(component: any, src: any, label: string, multiple = false) {
export function selectFile(component: any, src: any, label: string | null, multiple = false) {
return new Promise((res, rej) => {
const chooseFileFromPc = () => {
const input = document.createElement('input');
@ -56,10 +56,10 @@ export function selectFile(component: any, src: any, label: string, multiple = f
};
component.$root.menu({
items: [{
items: [label ? {
text: label,
type: 'label'
}, {
} : undefined, {
text: component.$t('upload'),
icon: faUpload,
action: chooseFileFromPc

View File

@ -9,7 +9,7 @@ import MiOS from '../mios';
*/
export default class Stream extends EventEmitter {
private stream: ReconnectingWebsocket;
public state: string;
public state: 'initializing' | 'reconnecting' | 'connected';
private sharedConnectionPools: Pool[] = [];
private sharedConnections: SharedConnection[] = [];
private nonSharedConnections: NonSharedConnection[] = [];

View File

@ -31,13 +31,14 @@ const defaultDeviceSettings = {
lang: null,
loadRawImages: false,
alwaysShowNsfw: false,
useOsDefaultEmojis: false,
useOsNativeEmojis: false,
autoReload: false,
accounts: [],
recentEmojis: [],
themes: [],
theme: 'light',
animation: true,
animatedMfm: true,
userData: {},
};

View File

@ -164,6 +164,19 @@ a {
}
}
._noSelect {
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
}
._ghost {
&, * {
@extend ._noSelect;
pointer-events: none;
}
}
._button {
appearance: none;
padding: 0;
@ -175,9 +188,7 @@ a {
font-size: 1em;
&, * {
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
@extend ._noSelect;
}
* {

View File

@ -26,7 +26,7 @@ import { UserList } from '../models/entities/user-list';
import { UserListJoining } from '../models/entities/user-list-joining';
import { UserGroup } from '../models/entities/user-group';
import { UserGroupJoining } from '../models/entities/user-group-joining';
import { UserGroupInvite } from '../models/entities/user-group-invite';
import { UserGroupInvitation } from '../models/entities/user-group-invitation';
import { Hashtag } from '../models/entities/hashtag';
import { NoteFavorite } from '../models/entities/note-favorite';
import { AbuseUserReport } from '../models/entities/abuse-user-report';
@ -106,7 +106,7 @@ export const entities = [
UserListJoining,
UserGroup,
UserGroupJoining,
UserGroupInvite,
UserGroupInvitation,
UserNotePining,
UserSecurityKey,
UsedUsername,

View File

@ -1,9 +1,10 @@
import { fetchMeta } from './fetch-meta';
import { ILocalUser } from '../models/entities/user';
import { Users } from '../models';
import { ensure } from '../prelude/ensure';
export async function fetchProxyAccount(): Promise<ILocalUser | null> {
if (meta.proxyAccountId == null) return null;
const meta = await fetchMeta();
return await Users.findOne(meta.proxyAccountId);
if (meta.proxyAccountId == null) return null;
return await Users.findOne(meta.proxyAccountId).then(ensure) as ILocalUser;
}

View File

@ -3,6 +3,7 @@ import { User } from './user';
import { id } from '../id';
import { Note } from './note';
import { FollowRequest } from './follow-request';
import { UserGroupInvitation } from './user-group-invitation';
@Entity()
export class Notification {
@ -57,12 +58,13 @@ export class Notification {
* pollVote - (自分または自分がWatchしている)投稿の投票に投票された
* receiveFollowRequest - フォローリクエストされた
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
* groupInvited - グループに招待された
*/
@Column('enum', {
enum: ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted'],
enum: ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited'],
comment: 'The type of the Notification.'
})
public type: 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollVote' | 'receiveFollowRequest' | 'followRequestAccepted';
public type: 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollVote' | 'receiveFollowRequest' | 'followRequestAccepted' | 'groupInvited';
/**
* 通知が読まれたかどうか
@ -97,6 +99,18 @@ export class Notification {
@JoinColumn()
public followRequest: FollowRequest | null;
@Column({
...id(),
nullable: true
})
public userGroupInvitationId: UserGroupInvitation['id'] | null;
@ManyToOne(type => UserGroupInvitation, {
onDelete: 'CASCADE'
})
@JoinColumn()
public userGroupInvitation: UserGroupInvitation | null;
@Column('varchar', {
length: 128, nullable: true
})

View File

@ -5,12 +5,12 @@ import { id } from '../id';
@Entity()
@Index(['userId', 'userGroupId'], { unique: true })
export class UserGroupInvite {
export class UserGroupInvitation {
@PrimaryColumn(id())
public id: string;
@Column('timestamp with time zone', {
comment: 'The created date of the UserGroupInvite.'
comment: 'The created date of the UserGroupInvitation.'
})
public createdAt: Date;

View File

@ -24,7 +24,7 @@ import { UserListRepository } from './repositories/user-list';
import { UserListJoining } from './entities/user-list-joining';
import { UserGroupRepository } from './repositories/user-group';
import { UserGroupJoining } from './entities/user-group-joining';
import { UserGroupInviteRepository } from './repositories/user-group-invite';
import { UserGroupInvitationRepository } from './repositories/user-group-invitation';
import { FollowRequestRepository } from './repositories/follow-request';
import { MutingRepository } from './repositories/muting';
import { BlockingRepository } from './repositories/blocking';
@ -71,7 +71,7 @@ export const UserLists = getCustomRepository(UserListRepository);
export const UserListJoinings = getRepository(UserListJoining);
export const UserGroups = getCustomRepository(UserGroupRepository);
export const UserGroupJoinings = getRepository(UserGroupJoining);
export const UserGroupInvites = getCustomRepository(UserGroupInviteRepository);
export const UserGroupInvitations = getCustomRepository(UserGroupInvitationRepository);
export const UserNotePinings = getRepository(UserNotePining);
export const UsedUsernames = getRepository(UsedUsername);
export const Followings = getCustomRepository(FollowingRepository);

View File

@ -1,9 +1,8 @@
import { EntityRepository, Repository, In } from 'typeorm';
import { Note } from '../entities/note';
import { User } from '../entities/user';
import { unique, concat } from '../../prelude/array';
import { nyaize } from '../../misc/nyaize';
import { Emojis, Users, Apps, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..';
import { Emojis, Users, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..';
import { ensure } from '../../prelude/ensure';
import { SchemaType } from '../../misc/schema';
import { awaitAll } from '../../prelude/await-all';

View File

@ -1,5 +1,5 @@
import { EntityRepository, Repository } from 'typeorm';
import { Users, Notes } from '..';
import { Users, Notes, UserGroupInvitations } from '..';
import { Notification } from '../entities/notification';
import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
@ -39,7 +39,10 @@ export class NotificationRepository extends Repository<Notification> {
...(notification.type === 'pollVote' ? {
note: Notes.pack(notification.note || notification.noteId!, notification.notifieeId),
choice: notification.choice
} : {})
} : {}),
...(notification.type === 'groupInvited' ? {
invitation: UserGroupInvitations.pack(notification.userGroupInvitationId!),
} : {}),
});
}

View File

@ -0,0 +1,24 @@
import { EntityRepository, Repository } from 'typeorm';
import { UserGroupInvitation } from '../entities/user-group-invitation';
import { UserGroups } from '..';
import { ensure } from '../../prelude/ensure';
@EntityRepository(UserGroupInvitation)
export class UserGroupInvitationRepository extends Repository<UserGroupInvitation> {
public async pack(
src: UserGroupInvitation['id'] | UserGroupInvitation,
) {
const invitation = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return {
id: invitation.id,
group: await UserGroups.pack(invitation.userGroup || invitation.userGroupId),
};
}
public packMany(
invitations: any[],
) {
return Promise.all(invitations.map(x => this.pack(x)));
}
}

View File

@ -1,24 +0,0 @@
import { EntityRepository, Repository } from 'typeorm';
import { UserGroupInvite } from '../entities/user-group-invite';
import { UserGroups } from '..';
import { ensure } from '../../prelude/ensure';
@EntityRepository(UserGroupInvite)
export class UserGroupInviteRepository extends Repository<UserGroupInvite> {
public async pack(
src: UserGroupInvite['id'] | UserGroupInvite,
) {
const invite = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return {
id: invite.id,
group: await UserGroups.pack(invite.userGroup || invite.userGroupId),
};
}
public packMany(
invites: any[],
) {
return Promise.all(invites.map(x => this.pack(x)));
}
}

View File

@ -1,11 +1,12 @@
import $ from 'cafy';
import define from '../../../define';
import { detectUrlMime } from '../../../../../misc/detect-url-mime';
import { Emojis } from '../../../../../models';
import { Emojis, DriveFiles } from '../../../../../models';
import { genId } from '../../../../../misc/gen-id';
import { getConnection } from 'typeorm';
import { insertModerationLog } from '../../../../../services/insert-moderation-log';
import { ApiError } from '../../../error';
import { ID } from '../../../../../misc/cafy-id';
import rndstr from 'rndstr';
export const meta = {
desc: {
@ -18,52 +19,36 @@ export const meta = {
requireModerator: true,
params: {
name: {
validator: $.str.min(1)
fileId: {
validator: $.type(ID)
},
url: {
validator: $.str.min(1)
},
category: {
validator: $.optional.str
},
aliases: {
validator: $.optional.arr($.str.min(1)),
default: [] as string[]
}
},
errors: {
emojiAlredyExists: {
message: 'Emoji already exists.',
code: 'EMOJI_ALREADY_EXISTS',
noSuchFile: {
message: 'No such file.',
code: 'MO_SUCH_FILE',
id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf'
}
}
};
export default define(meta, async (ps, me) => {
const type = await detectUrlMime(ps.url);
const file = await DriveFiles.findOne(ps.fileId);
const exists = await Emojis.findOne({
name: ps.name,
host: null
});
if (file == null) throw new ApiError(meta.errors.noSuchFile);
if (exists != null) throw new ApiError(meta.errors.emojiAlredyExists);
const name = file.name.split('.')[0].match(/^[a-z0-9_]+$/) ? file.name.split('.')[0] : `_${rndstr('a-z0-9', 8)}_`;
const emoji = await Emojis.save({
id: genId(),
updatedAt: new Date(),
name: ps.name,
category: ps.category,
name: name,
category: null,
host: null,
aliases: ps.aliases,
url: ps.url,
type,
aliases: [],
url: file.url,
type: file.type,
});
await getConnection().queryResultCache!.remove(['meta_emojis']);

View File

@ -1,6 +1,5 @@
import $ from 'cafy';
import define from '../../../define';
import { detectUrlMime } from '../../../../../misc/detect-url-mime';
import { ID } from '../../../../../misc/cafy-id';
import { Emojis } from '../../../../../models';
import { getConnection } from 'typeorm';
@ -29,10 +28,6 @@ export const meta = {
validator: $.optional.str
},
url: {
validator: $.str
},
aliases: {
validator: $.arr($.str)
}
@ -52,15 +47,11 @@ export default define(meta, async (ps) => {
if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
const type = await detectUrlMime(ps.url);
await Emojis.update(emoji.id, {
updatedAt: new Date(),
name: ps.name,
category: ps.category,
aliases: ps.aliases,
url: ps.url,
type,
});
await getConnection().queryResultCache!.remove(['meta_emojis']);

View File

@ -1,7 +1,7 @@
import $ from 'cafy';
import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import { UserGroupInvites } from '../../../../models';
import { UserGroupInvitations } from '../../../../models';
import { makePaginationQuery } from '../../common/make-pagination-query';
export const meta = {
@ -33,13 +33,13 @@ export const meta = {
};
export default define(meta, async (ps, user) => {
const query = makePaginationQuery(UserGroupInvites.createQueryBuilder('invite'), ps.sinceId, ps.untilId)
.andWhere(`invite.userId = :meId`, { meId: user.id })
.leftJoinAndSelect('invite.userGroup', 'user_group');
const query = makePaginationQuery(UserGroupInvitations.createQueryBuilder('invitation'), ps.sinceId, ps.untilId)
.andWhere(`invitation.userId = :meId`, { meId: user.id })
.leftJoinAndSelect('invitation.userGroup', 'user_group');
const invites = await query
const invitations = await query
.take(ps.limit!)
.getMany();
return await UserGroupInvites.packMany(invites);
return await UserGroupInvitations.packMany(invitations);
});

View File

@ -7,6 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query';
import { Notes } from '../../../../models';
import { generateMuteQuery } from '../../common/generate-mute-query';
import { activeUsersChart } from '../../../../services/chart';
import { Brackets } from 'typeorm';
export const meta = {
desc: {
@ -77,6 +78,18 @@ export default define(meta, async (ps, user) => {
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('note.visibility = \'public\'')
.andWhere('note.replyId IS NULL')
.andWhere(new Brackets(qb => { qb
.where(`note.replyId IS NULL`) // 返信ではない
.orWhere('note.replyUserId = :meId', { meId: user.id }) // 返信だけど自分のノートへの返信
.orWhere(new Brackets(qb => { qb // 返信だけど自分の行った返信
.where(`note.replyId IS NOT NULL`)
.andWhere('note.userId = :meId', { meId: user.id });
}))
.orWhere(new Brackets(qb => { qb // 返信だけど投稿者自身への返信
.where(`note.replyId IS NOT NULL`)
.andWhere('note.replyUserId = note.userId', { meId: user.id });
}));
}))
.leftJoinAndSelect('note.user', 'user');
if (user) generateMuteQuery(query, user);

View File

@ -124,6 +124,18 @@ export default define(meta, async (ps, user) => {
qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id })
.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
}))
.andWhere(new Brackets(qb => { qb
.where(`note.replyId IS NULL`) // 返信ではない
.orWhere('note.replyUserId = :meId', { meId: user.id }) // 返信だけど自分のノートへの返信
.orWhere(new Brackets(qb => { qb // 返信だけど自分の行った返信
.where(`note.replyId IS NOT NULL`)
.andWhere('note.userId = :meId', { meId: user.id });
}))
.orWhere(new Brackets(qb => { qb // 返信だけど投稿者自身への返信
.where(`note.replyId IS NOT NULL`)
.andWhere('note.replyUserId = note.userId', { meId: user.id });
}));
}))
.leftJoinAndSelect('note.user', 'user')
.setParameters(followingQuery.getParameters());

View File

@ -93,6 +93,18 @@ export default define(meta, async (ps, user) => {
const query = makePaginationQuery(Notes.createQueryBuilder('note'),
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)')
.andWhere(new Brackets(qb => { qb
.where(`note.replyId IS NULL`) // 返信ではない
.orWhere('note.replyUserId = :meId', { meId: user.id }) // 返信だけど自分のノートへの返信
.orWhere(new Brackets(qb => { qb // 返信だけど自分の行った返信
.where(`note.replyId IS NOT NULL`)
.andWhere('note.userId = :meId', { meId: user.id });
}))
.orWhere(new Brackets(qb => { qb // 返信だけど投稿者自身への返信
.where(`note.replyId IS NOT NULL`)
.andWhere('note.replyUserId = note.userId', { meId: user.id });
}));
}))
.leftJoinAndSelect('note.user', 'user');
generateVisibilityQuery(query, user);

View File

@ -110,6 +110,18 @@ export default define(meta, async (ps, user) => {
.where(`note.userId IN (${ followingQuery.getQuery() })`)
.orWhere('note.userId = :meId', { meId: user.id });
}))
.andWhere(new Brackets(qb => { qb
.where(`note.replyId IS NULL`) // 返信ではない
.orWhere('note.replyUserId = :meId', { meId: user.id }) // 返信だけど自分のノートへの返信
.orWhere(new Brackets(qb => { qb // 返信だけど自分の行った返信
.where(`note.replyId IS NOT NULL`)
.andWhere('note.userId = :meId', { meId: user.id });
}))
.orWhere(new Brackets(qb => { qb // 返信だけど投稿者自身への返信
.where(`note.replyId IS NOT NULL`)
.andWhere('note.replyUserId = note.userId', { meId: user.id });
}));
}))
.leftJoinAndSelect('note.user', 'user')
.setParameters(followingQuery.getParameters());

View File

@ -2,14 +2,14 @@ import $ from 'cafy';
import { ID } from '../../../../../../misc/cafy-id';
import define from '../../../../define';
import { ApiError } from '../../../../error';
import { UserGroupJoinings, UserGroupInvites } from '../../../../../../models';
import { UserGroupJoinings, UserGroupInvitations } from '../../../../../../models';
import { genId } from '../../../../../../misc/gen-id';
import { UserGroupJoining } from '../../../../../../models/entities/user-group-joining';
export const meta = {
desc: {
'ja-JP': 'ユーザーグループへの招待を承認します。',
'en-US': 'Accept invite of a user group.'
'en-US': 'Accept invitation of a user group.'
},
tags: ['groups', 'users'],
@ -19,11 +19,11 @@ export const meta = {
kind: 'write:user-groups',
params: {
inviteId: {
invitationId: {
validator: $.type(ID),
desc: {
'ja-JP': '招待ID',
'en-US': 'The invite ID'
'en-US': 'The invitation ID'
}
},
},
@ -39,15 +39,15 @@ export const meta = {
export default define(meta, async (ps, user) => {
// Fetch the invitation
const invite = await UserGroupInvites.findOne({
id: ps.inviteId,
const invitation = await UserGroupInvitations.findOne({
id: ps.invitationId,
});
if (invite == null) {
if (invitation == null) {
throw new ApiError(meta.errors.noSuchInvitation);
}
if (invite.userId !== user.id) {
if (invitation.userId !== user.id) {
throw new ApiError(meta.errors.noSuchInvitation);
}
@ -56,8 +56,8 @@ export default define(meta, async (ps, user) => {
id: genId(),
createdAt: new Date(),
userId: user.id,
userGroupId: invite.userGroupId
userGroupId: invitation.userGroupId
} as UserGroupJoining);
UserGroupInvites.delete(invite.id);
UserGroupInvitations.delete(invitation.id);
});

View File

@ -2,12 +2,12 @@ import $ from 'cafy';
import { ID } from '../../../../../../misc/cafy-id';
import define from '../../../../define';
import { ApiError } from '../../../../error';
import { UserGroupInvites } from '../../../../../../models';
import { UserGroupInvitations } from '../../../../../../models';
export const meta = {
desc: {
'ja-JP': 'ユーザーグループへの招待を拒否します。',
'en-US': 'Reject invite of a user group.'
'en-US': 'Reject invitation of a user group.'
},
tags: ['groups', 'users'],
@ -17,11 +17,11 @@ export const meta = {
kind: 'write:user-groups',
params: {
inviteId: {
invitationId: {
validator: $.type(ID),
desc: {
'ja-JP': '招待ID',
'en-US': 'The invite ID'
'en-US': 'The invitation ID'
}
},
},
@ -37,17 +37,17 @@ export const meta = {
export default define(meta, async (ps, user) => {
// Fetch the invitation
const invite = await UserGroupInvites.findOne({
id: ps.inviteId,
const invitation = await UserGroupInvitations.findOne({
id: ps.invitationId,
});
if (invite == null) {
if (invitation == null) {
throw new ApiError(meta.errors.noSuchInvitation);
}
if (invite.userId !== user.id) {
if (invitation.userId !== user.id) {
throw new ApiError(meta.errors.noSuchInvitation);
}
await UserGroupInvites.delete(invite.id);
await UserGroupInvitations.delete(invitation.id);
});

View File

@ -3,9 +3,10 @@ import { ID } from '../../../../../misc/cafy-id';
import define from '../../../define';
import { ApiError } from '../../../error';
import { getUser } from '../../../common/getters';
import { UserGroups, UserGroupJoinings, UserGroupInvites } from '../../../../../models';
import { UserGroups, UserGroupJoinings, UserGroupInvitations } from '../../../../../models';
import { genId } from '../../../../../misc/gen-id';
import { UserGroupInvite } from '../../../../../models/entities/user-group-invite';
import { UserGroupInvitation } from '../../../../../models/entities/user-group-invitation';
import { createNotification } from '../../../../../services/create-notification';
export const meta = {
desc: {
@ -86,19 +87,24 @@ export default define(meta, async (ps, me) => {
throw new ApiError(meta.errors.alreadyAdded);
}
const invite = await UserGroupInvites.findOne({
const existInvitation = await UserGroupInvitations.findOne({
userGroupId: userGroup.id,
userId: user.id
});
if (invite) {
if (existInvitation) {
throw new ApiError(meta.errors.alreadyInvited);
}
await UserGroupInvites.save({
const invitation = await UserGroupInvitations.save({
id: genId(),
createdAt: new Date(),
userId: user.id,
userGroupId: userGroup.id
} as UserGroupInvite);
} as UserGroupInvitation);
// 通知を作成
createNotification(user.id, me.id, 'groupInvited', {
userGroupInvitationId: invitation.id
});
});

View File

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

View File

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

View File

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

View File

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

View File

@ -6,16 +6,18 @@ import { User } from '../models/entities/user';
import { Note } from '../models/entities/note';
import { Notification } from '../models/entities/notification';
import { FollowRequest } from '../models/entities/follow-request';
import { UserGroupInvitation } from '../models/entities/user-group-invitation';
export async function createNotification(
notifieeId: User['id'],
notifierId: User['id'],
type: string,
type: Notification['type'],
content?: {
noteId?: Note['id'];
reaction?: string;
choice?: number;
followRequestId?: FollowRequest['id'];
userGroupInvitationId?: UserGroupInvitation['id'];
}
) {
if (notifieeId === notifierId) {
@ -36,6 +38,7 @@ export async function createNotification(
if (content.reaction) data.reaction = content.reaction;
if (content.choice) data.choice = content.choice;
if (content.followRequestId) data.followRequestId = content.followRequestId;
if (content.userGroupInvitationId) data.userGroupInvitationId = content.userGroupInvitationId;
}
// Create notification

View File

@ -1,11 +1,17 @@
import { DriveFile } from '../../models/entities/drive-file';
import { InternalStorage } from './internal-storage';
import { DriveFiles, Instances, Notes } from '../../models';
import { DriveFiles, Instances, Notes, Users } from '../../models';
import { driveChart, perUserDriveChart, instanceChart } from '../chart';
import { createDeleteObjectStorageFileJob } from '../../queue';
import { fetchMeta } from '../../misc/fetch-meta';
import { getS3 } from './s3';
import { v4 as uuid } from 'uuid';
import { Note } from '../../models/entities/note';
import { renderActivity } from '../../remote/activitypub/renderer';
import renderDelete from '../../remote/activitypub/renderer/delete';
import renderTombstone from '../../remote/activitypub/renderer/tombstone';
import config from '../../config';
import { deliverToFollowers } from '../../remote/activitypub/deliver-manager';
export async function deleteFile(file: DriveFile, isExpired = false) {
if (file.storedInternal) {
@ -63,7 +69,7 @@ export async function deleteFileSync(file: DriveFile, isExpired = false) {
postProcess(file, isExpired);
}
function postProcess(file: DriveFile, isExpired = false) {
async function postProcess(file: DriveFile, isExpired = false) {
// リモートファイル期限切れ削除後は直リンクにする
if (isExpired && file.userHost !== null && file.uri != null) {
DriveFiles.update(file.id, {
@ -81,8 +87,23 @@ function postProcess(file: DriveFile, isExpired = false) {
DriveFiles.delete(file.id);
// TODO: トランザクション
const relatedNotes = await findRelatedNotes(file.id);
for (const relatedNote of relatedNotes) { // for each note with deleted driveFile
const cascadingNotes = (await findCascadingNotes(relatedNote)).filter(note => !note.localOnly);
for (const cascadingNote of cascadingNotes) { // for each notes subject to cascade deletion
if (!cascadingNote.user) continue;
if (!Users.isLocalUser(cascadingNote.user)) continue;
const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${cascadingNote.id}`), cascadingNote.user));
deliverToFollowers(cascadingNote.user, content); // federate delete msg
}
if (!relatedNote.user) continue;
if (Users.isLocalUser(relatedNote.user)) {
const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${relatedNote.id}`), relatedNote.user));
deliverToFollowers(relatedNote.user, content);
}
}
Notes.createQueryBuilder().delete()
.where(':id = ANY(fileIds)', { id: file.id })
.where(':id = ANY("fileIds")', { id: file.id })
.execute();
}
@ -106,3 +127,32 @@ export async function deleteObjectStorageFile(key: string) {
Key: key
}).promise();
}
async function findRelatedNotes(fileId: string) {
// NOTE: When running raw query, TypeORM converts field name to lowercase. Wrap in quotes to prevent conversion.
const relatedNotes = await Notes.createQueryBuilder('note').where(':id = ANY("fileIds")', { id: fileId }).getMany();
for (const relatedNote of relatedNotes) {
const user = await Users.findOne({ id: relatedNote.userId });
if (user)
relatedNote.user = user;
}
return relatedNotes;
}
async function findCascadingNotes(note: Note) {
const cascadingNotes: Note[] = [];
const recursive = async (noteId: string) => {
const query = Notes.createQueryBuilder('note')
.where('note.replyId = :noteId', { noteId })
.leftJoinAndSelect('note.user', 'user');
const replies = await query.getMany();
for (const reply of replies) {
cascadingNotes.push(reply);
await recursive(reply.id);
}
};
await recursive(note.id);
return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users
}

View File

@ -20,11 +20,6 @@ import { deliverToFollowers } from '../../remote/activitypub/deliver-manager';
export default async function(user: User, note: Note, quiet = false) {
const deletedAt = new Date();
await Notes.delete({
id: note.id,
userId: user.id
});
if (note.renoteId) {
Notes.decrement({ id: note.renoteId }, 'renoteCount', 1);
Notes.decrement({ id: note.renoteId }, 'score', 1);
@ -39,6 +34,7 @@ export default async function(user: User, note: Note, quiet = false) {
if (Users.isLocalUser(user)) {
let renote: Note | undefined;
// if deletd note is renote
if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length == 0)) {
renote = await Notes.findOne({
id: note.renoteId
@ -51,6 +47,15 @@ export default async function(user: User, note: Note, quiet = false) {
deliverToFollowers(user, content);
}
// also deliever delete activity to cascaded notes
const cascadingNotes = (await findCascadingNotes(note)).filter(note => !note.localOnly); // filter out local-only notes
for (const cascadingNote of cascadingNotes) {
if (!cascadingNote.user) continue;
if (!Users.isLocalUser(cascadingNote.user)) continue;
const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${cascadingNote.id}`), cascadingNote.user));
deliverToFollowers(cascadingNote.user, content);
}
//#endregion
// 統計を更新
@ -64,4 +69,27 @@ export default async function(user: User, note: Note, quiet = false) {
});
}
}
await Notes.delete({
id: note.id,
userId: user.id
});
}
async function findCascadingNotes(note: Note) {
const cascadingNotes: Note[] = [];
const recursive = async (noteId: string) => {
const query = Notes.createQueryBuilder('note')
.where('note.replyId = :noteId', { noteId })
.leftJoinAndSelect('note.user', 'user');
const replies = await query.getMany();
for (const reply of replies) {
cascadingNotes.push(reply);
await recursive(reply.id);
}
};
await recursive(note.id);
return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users
}

View File

@ -124,7 +124,7 @@ module.exports = {
}),
new webpack.DefinePlugin({
_VERSION_: JSON.stringify(meta.version),
_LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]: [string, any]) => [k, v && v.meta && v.meta.lang])),
_LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]: [string, any]) => [k, v._lang_])),
_ENV_: JSON.stringify(process.env.NODE_ENV)
}),
new VueLoaderPlugin(),

329
yarn.lock
View File

@ -33,10 +33,10 @@
dependencies:
regenerator-runtime "^0.13.2"
"@elastic/elasticsearch@7.5.0":
version "7.5.0"
resolved "https://registry.yarnpkg.com/@elastic/elasticsearch/-/elasticsearch-7.5.0.tgz#a54b07001e1a3912792bf9f34e6ec6a4fe0aef0c"
integrity sha512-ahzu451ppXepQMb3Zr8eFfWn8QR7yCcUWQjU1dS4X63Ctin0GNwwSPertt4WwDkm6MnZ25o932wAEgyMFLwzdA==
"@elastic/elasticsearch@7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@elastic/elasticsearch/-/elasticsearch-7.6.0.tgz#960e937b56826b53d4784bc48f3375ce4efc5377"
integrity sha512-mCS0gro7KM9fhPpRXf+X0Bxd2j688ONqhj2fibhU2E3vup6rmNgH0bw3Vuvv98D8MmFl2oCxwtv3a/Km/K8F6Q==
dependencies:
debug "^4.1.1"
decompress-response "^4.2.0"
@ -45,38 +45,38 @@
once "^1.4.0"
pump "^3.0.0"
"@fortawesome/fontawesome-common-types@^0.2.26":
version "0.2.26"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.26.tgz#6e0b13a752676036f8196f8a1500d53a27b4adc1"
integrity sha512-CcM/fIFwZlRdiWG/25xE/wHbtyUuCtqoCTrr6BsWw7hH072fR++n4L56KPydAr3ANgMJMjT8v83ZFIsDc7kE+A==
"@fortawesome/fontawesome-common-types@^0.2.27":
version "0.2.27"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.27.tgz#19706345859fc46adf3684ed01d11b40903b87e9"
integrity sha512-97GaByGaXDGMkzcJX7VmR/jRJd8h1mfhtA7RsxDBN61GnWE/PPCZhOdwG/8OZYktiRUF0CvFOr+VgRkJrt6TWg==
"@fortawesome/fontawesome-svg-core@1.2.26":
version "1.2.26"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.26.tgz#671569271d6b532cdea5e3deb8ff16f8b7ac251d"
integrity sha512-3Dfd/v2IztP1TxKOxZiB5+4kaOZK9mNy0KU1vVK7nFlPWz3gzxrCWB+AloQhQUoJ8HhGqbzjliK89Vl7PExGbw==
"@fortawesome/fontawesome-svg-core@1.2.27":
version "1.2.27"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.27.tgz#e4db8e3be81a40988213507c3e3d0c158a6641a3"
integrity sha512-sOD3DKynocnHYpuw2sLPnTunDj7rLk91LYhi2axUYwuGe9cPCw7Bsu9EWtVdNJP+IYgTCZIbyARKXuy5K/nv+Q==
dependencies:
"@fortawesome/fontawesome-common-types" "^0.2.26"
"@fortawesome/fontawesome-common-types" "^0.2.27"
"@fortawesome/free-brands-svg-icons@5.12.0":
version "5.12.0"
resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.12.0.tgz#b0c78627f811ac030ee0ac88df376567cf74119d"
integrity sha512-50uCFzVUki3wfmFmrMNLFhOt8dP6YZ53zwR4dK9FR7Lwq1IVHXnSBb8MtGLe3urLJ2sA+CSu7Pc7s3i6/zLxmA==
"@fortawesome/free-brands-svg-icons@5.12.1":
version "5.12.1"
resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.12.1.tgz#67977addd15e21e57aa1ed71cd2ddecdfaa88647"
integrity sha512-IYUYcgGsQuwiIHjRGfeSTCIQKUSZMb6FsV6mDj78K0D+YzGJkM4cvEBBUMHtnla5D2HCxncMI/9JX5YIk2GHeQ==
dependencies:
"@fortawesome/fontawesome-common-types" "^0.2.26"
"@fortawesome/fontawesome-common-types" "^0.2.27"
"@fortawesome/free-regular-svg-icons@5.12.0":
version "5.12.0"
resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.12.0.tgz#9b7ed5dbd7581aab47960802cc116fdc8b6c8b7d"
integrity sha512-FAvpmylTs0PosHwHrWPQX6/7ODc9M11kCE6AOAujFufDYzqTj2cPHT4yJO7zTEkKdAbbusJzbWpnOboMuyjeQA==
"@fortawesome/free-regular-svg-icons@5.12.1":
version "5.12.1"
resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.12.1.tgz#4b378d93655acc5e432d5ed7ee127768dd351a99"
integrity sha512-bGda18seHXb+24K6DPUFzqn4kG7B+JViP/BscMcNUXvT00M86xNhdgP2TXSdflQXn53QWqymKjx/8rhaDOJyhA==
dependencies:
"@fortawesome/fontawesome-common-types" "^0.2.26"
"@fortawesome/fontawesome-common-types" "^0.2.27"
"@fortawesome/free-solid-svg-icons@5.12.0":
version "5.12.0"
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.12.0.tgz#8decac5844e60453cc0c7c51437d1461df053a35"
integrity sha512-CnpsWs6GhTs9ekNB3d8rcO5HYqRkXbYKf2YNiAlTWbj5eVlPqsd/XH1F9If8jkcR1aegryAbln/qYeKVZzpM0g==
"@fortawesome/free-solid-svg-icons@5.12.1":
version "5.12.1"
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.12.1.tgz#76b6f958a3471821ff146f8f955e6d7cfe87147c"
integrity sha512-k3MwRFFUhyL4cuCJSaHDA0YNYMELDXX0h8JKtWYxO5XD3Dn+maXOMrVAAiNGooUyM2v/wz/TOaM0jxYVKeXX7g==
dependencies:
"@fortawesome/fontawesome-common-types" "^0.2.26"
"@fortawesome/fontawesome-common-types" "^0.2.27"
"@fortawesome/vue-fontawesome@0.1.9":
version "0.1.9"
@ -119,10 +119,10 @@
resolved "https://registry.yarnpkg.com/@koa/multer/-/multer-2.0.2.tgz#2bb5a5c5fa22329a174f6a2afb2d0710ef16a16f"
integrity sha512-cWZUbpRNcLjLWDQ6QTWMX3ucXS+xHpJh7ngrBnsywhL7XygU5M090dDeiC9Sq1jM7IwKn9WkFZA1LyPU0q89qA==
"@koa/router@8.0.6":
version "8.0.6"
resolved "https://registry.yarnpkg.com/@koa/router/-/router-8.0.6.tgz#bd3f76e7f61450b47691ed944ce8e052b861139d"
integrity sha512-b4BDdDks0hvLYlr20REnWpo1FmV9SVh4/GS3yvK6VQ8/zPOIYcA9x2kSJpRbLMx40nRuIV1GXkYsRT8u42jh4g==
"@koa/router@8.0.8":
version "8.0.8"
resolved "https://registry.yarnpkg.com/@koa/router/-/router-8.0.8.tgz#95f32d11373d03d89dcb63fabe9ac6f471095236"
integrity sha512-FnT93N4NUehnXr+juupDmG2yfi0JnWdCmNEuIXpCG4TtG+9xvtrLambBH3RclycopVUOEYAim2lydiNBI7IRVg==
dependencies:
debug "^4.1.1"
http-errors "^1.7.3"
@ -233,6 +233,11 @@
resolved "https://registry.yarnpkg.com/@types/dateformat/-/dateformat-3.0.1.tgz#98d747a2e5e9a56070c6bf14e27bff56204e34cc"
integrity sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==
"@types/debug@^4.1.5":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==
"@types/double-ended-queue@2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@types/double-ended-queue/-/double-ended-queue-2.1.1.tgz#f077386134f0f736d927812c85c43a04f21ddc27"
@ -440,7 +445,7 @@
dependencies:
"@types/koa" "*"
"@types/koa@*", "@types/koa@2.11.0":
"@types/koa@*":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.11.0.tgz#394a3e9ec94f796003a6c8374b4dbc2778746f20"
integrity sha512-Hgx/1/rVlJvqYBrdeCsS7PDiR2qbxlMt1RnmNWD4Uxi5FF9nwkYqIldo7urjc+dfNpk+2NRGcnAYd4L5xEhCcQ==
@ -452,6 +457,18 @@
"@types/koa-compose" "*"
"@types/node" "*"
"@types/koa@2.11.1":
version "2.11.1"
resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.11.1.tgz#b116c860d61ef1e3b6a9ab0e5bb9703a69d951ff"
integrity sha512-/kqQs+8Qd9GL0cdl39HEhK91k7xq6+Zci76RUdqtTLj1Mg1aVG7zwJm3snkeyFUeAvY8noY27eMXgqg1wHZgwA==
dependencies:
"@types/accepts" "*"
"@types/cookies" "*"
"@types/http-assert" "*"
"@types/keygrip" "*"
"@types/koa-compose" "*"
"@types/node" "*"
"@types/koa__cors@3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/koa__cors/-/koa__cors-3.0.1.tgz#a8cf8535f0fe682c9421f1b9379837c585f8b66b"
@ -505,11 +522,16 @@
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.1.tgz#5d7ec2a789a1f77c59b7ad071b9d50bf1abbfc9e"
integrity sha512-L/Nw/2e5KUaprNJoRA33oly+M8X8n0K+FwLTbYqwTcR14wdPWeRkigBLfSFpN/Asf9ENZTMZwLxjtjeYucAA4Q==
"@types/node@*", "@types/node@13.7.0":
"@types/node@*":
version "13.7.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.0.tgz#b417deda18cf8400f278733499ad5547ed1abec4"
integrity sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ==
"@types/node@13.7.1":
version "13.7.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.1.tgz#238eb34a66431b71d2aaddeaa7db166f25971a0d"
integrity sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA==
"@types/nodemailer@6.4.0":
version "6.4.0"
resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.0.tgz#d8c039be3ed685c4719a026455555be82c124b74"
@ -578,13 +600,20 @@
dependencies:
"@types/redis" "*"
"@types/redis@*", "@types/redis@2.8.14":
"@types/redis@*":
version "2.8.14"
resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.14.tgz#2ed46d0f923f7ccd63fbe73a46a1241e606cf716"
integrity sha512-255dzsOLJdXFHBio9/aMHGozNkoiBUgc+g2nlNjbTSp5qcAlmpm4Z6Xs3pKOBLNIKdZbA2BkUxWvYSIwKra0Yw==
dependencies:
"@types/node" "*"
"@types/redis@2.8.15":
version "2.8.15"
resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.15.tgz#60e178a116a2ce35b42cdd08787b40fa7fbeb3d6"
integrity sha512-kp05OQVXH6w2vPLd86t2y4Y64jjHClbciF3w2k1MHVs5uQu/zf0HM4ibUgFGDDogBloJkTYtfjZsXjKHlMBxqw==
dependencies:
"@types/node" "*"
"@types/rename@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/rename/-/rename-1.0.1.tgz#89ef152d73f2400835c5d835c5e22d3eb6b21ba6"
@ -753,7 +782,7 @@
"@types/node" "*"
"@types/webpack" "*"
"@types/webpack@*", "@types/webpack@4.41.3":
"@types/webpack@*":
version "4.41.3"
resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.3.tgz#30c2251db1d69a45bbffd79c0577dd9baf50e7ba"
integrity sha512-dH+BZ6pHBZFrXpnif0YU/PbmUq3lQrvRPnqkxsciSIzvG/DE+Vm/Wrjn56T7V3+B5ryQa5fw0oGnHL8tk4ll6w==
@ -765,6 +794,18 @@
"@types/webpack-sources" "*"
source-map "^0.6.0"
"@types/webpack@4.41.6":
version "4.41.6"
resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.6.tgz#c76afbdef59159d12e3e1332dc264b75574722a2"
integrity sha512-iWRpV5Ej+8uKrgxp6jXz3v7ZTjgtuMXY+rsxQjFNU0hYCnHkpA7vtiNffgxjuxX4feFHBbz0IF76OzX2OqDYPw==
dependencies:
"@types/anymatch" "*"
"@types/node" "*"
"@types/tapable" "*"
"@types/uglify-js" "*"
"@types/webpack-sources" "*"
source-map "^0.6.0"
"@types/websocket@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.0.tgz#828c794b0a50949ad061aa311af1009934197e4b"
@ -779,29 +820,29 @@
dependencies:
"@types/node" "*"
"@typescript-eslint/experimental-utils@2.18.0":
version "2.18.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.18.0.tgz#e4eab839082030282496c1439bbf9fdf2a4f3da8"
integrity sha512-J6MopKPHuJYmQUkANLip7g9I82ZLe1naCbxZZW3O2sIxTiq/9YYoOELEKY7oPg0hJ0V/AQ225h2z0Yp+RRMXhw==
"@typescript-eslint/experimental-utils@2.19.2":
version "2.19.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.19.2.tgz#4611d44cf0f0cb460c26aa7676fc0a787281e233"
integrity sha512-B88QuwT1wMJR750YvTJBNjMZwmiPpbmKYLm1yI7PCc3x0NariqPwqaPsoJRwU9DmUi0cd9dkhz1IqEnwfD+P1A==
dependencies:
"@types/json-schema" "^7.0.3"
"@typescript-eslint/typescript-estree" "2.18.0"
"@typescript-eslint/typescript-estree" "2.19.2"
eslint-scope "^5.0.0"
"@typescript-eslint/parser@2.18.0":
version "2.18.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.18.0.tgz#d5f7fc1839abd4a985394e40e9d2454bd56aeb1f"
integrity sha512-SJJPxFMEYEWkM6pGfcnjLU+NJIPo+Ko1QrCBL+i0+zV30ggLD90huEmMMhKLHBpESWy9lVEeWlQibweNQzyc+A==
"@typescript-eslint/parser@2.19.2":
version "2.19.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.19.2.tgz#21f42c0694846367e7d6a907feb08ab2f89c0879"
integrity sha512-8uwnYGKqX9wWHGPGdLB9sk9+12sjcdqEEYKGgbS8A0IvYX59h01o8os5qXUHMq2na8vpDRaV0suTLM7S8wraTA==
dependencies:
"@types/eslint-visitor-keys" "^1.0.0"
"@typescript-eslint/experimental-utils" "2.18.0"
"@typescript-eslint/typescript-estree" "2.18.0"
"@typescript-eslint/experimental-utils" "2.19.2"
"@typescript-eslint/typescript-estree" "2.19.2"
eslint-visitor-keys "^1.1.0"
"@typescript-eslint/typescript-estree@2.18.0":
version "2.18.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.18.0.tgz#cfbd16ed1b111166617d718619c19b62764c8460"
integrity sha512-gVHylf7FDb8VSi2ypFuEL3hOtoC4HkZZ5dOjXvVjoyKdRrvXAOPSzpNRnKMfaUUEiSLP8UF9j9X9EDLxC0lfZg==
"@typescript-eslint/typescript-estree@2.19.2":
version "2.19.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.19.2.tgz#67485b00172f400474d243c6c0be27581a579350"
integrity sha512-Xu/qa0MDk6upQWqE4Qy2X16Xg8Vi32tQS2PR0AvnT/ZYS4YGDvtn2MStOh5y8Zy2mg4NuL06KUHlvCh95j9C6Q==
dependencies:
debug "^4.1.1"
eslint-visitor-keys "^1.1.0"
@ -1050,10 +1091,12 @@ acorn@^7.1.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c"
integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==
agent-base@5:
version "5.1.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
agent-base@6:
version "6.0.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.0.tgz#5d0101f19bbfaed39980b22ae866de153b93f09a"
integrity sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==
dependencies:
debug "4"
agent-base@^4.3.0:
version "4.3.0"
@ -1215,10 +1258,10 @@ anymatch@~3.1.1:
normalize-path "^3.0.0"
picomatch "^2.0.4"
apexcharts@3.15.3:
version "3.15.3"
resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.15.3.tgz#e839ee30cad55c23e9bc2e80c279eae1a883c28c"
integrity sha512-DfXhSqFaulF3C7YyMR77Jk4ESa66QjoqrBvxw3gLvRs5KK0fhmk0kuuWtxtpBulm2oWBVsi6VKHiy+YlBRPasg==
apexcharts@3.15.6:
version "3.15.6"
resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.15.6.tgz#1716f8badd447b06796dbcd95b5e405ec676a3bf"
integrity sha512-8mZqg7eTZGU2zvjYUUOf+sTqgfmutipHU9lNgkqzZPtwIVGwR5PwXTBNKRJSI3AeSoQ8VZGYfzTJWoUDfGAeBw==
dependencies:
svg.draggable.js "^2.2.2"
svg.easing.js "^2.0.0"
@ -1469,10 +1512,10 @@ autwh@0.1.0:
dependencies:
oauth "0.9.15"
aws-sdk@2.610.0:
version "2.610.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.610.0.tgz#a8633204bed83df763095824f8110ced4a717d2a"
integrity sha512-kqcoCTKjbxrUo2KeLQR2Jw6l4PvkbHXSDk8KqF2hXcpHibiOcMXZZPVe9X+s90RC/B2+qU95M7FImp9ByMcw7A==
aws-sdk@2.617.0:
version "2.617.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.617.0.tgz#aa040a75b78494dafdec9c6cce9dc0b6933e19d7"
integrity sha512-08CVFCHdLgSqXC5kHDDyuCf62qgzJnzHcZEgSd64zM91VaQqFzGIqi/b9iTtE47JKl1UIvrj7v8g+LPrfrGULQ==
dependencies:
buffer "4.9.1"
events "1.1.1"
@ -2453,10 +2496,10 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
commander@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.0.tgz#545983a0603fe425bc672d66c9e3c89c42121a83"
integrity sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw==
commander@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
commander@^2.12.1, commander@^2.19.0, commander@^2.20.0, commander@~2.20.3:
version "2.20.3"
@ -2909,7 +2952,7 @@ cssom@~0.3.6:
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
cssstyle@^2.0.0:
cssstyle@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.2.0.tgz#e4c44debccd6b7911ed617a4395e5754bba59992"
integrity sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA==
@ -3107,7 +3150,7 @@ delegates@^1.0.0:
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
denque@^1.1.0:
denque@^1.1.0, denque@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf"
integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==
@ -3280,7 +3323,7 @@ dotenv@^6.2.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064"
integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==
double-ended-queue@2.1.0-0, double-ended-queue@^2.1.0-0:
double-ended-queue@2.1.0-0:
version "2.1.0-0"
resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
integrity sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=
@ -3860,13 +3903,13 @@ file-entry-cache@^5.0.1:
dependencies:
flat-cache "^2.0.1"
file-type@13.1.2:
version "13.1.2"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-13.1.2.tgz#0e5b72bca7569c152e2d5a84ece07e94f77f833f"
integrity sha512-NiHXbmclwHN38eHZfRklosbm7/W+1yacDzRCyddd0NiyuJUArQDmzJ8GPSFJGl82+I59u7sNGfcAVnJsfXJb8A==
file-type@14.1.2:
version "14.1.2"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-14.1.2.tgz#ee7adfa2a0483d8be3a38aea58fdf2705ab00fb4"
integrity sha512-9NI4+QzDlEPB6OETc/FcJt8i8vNT396VweRwEwLcE07MnorWkZDopNuc+MeoYA7ArbzoK044JbaDExWys8deTQ==
dependencies:
readable-web-to-node-stream "^2.0.0"
strtok3 "^5.0.2"
strtok3 "^6.0.0"
token-types "^2.0.0"
typedarray-to-buffer "^3.1.5"
@ -4477,7 +4520,7 @@ har-schema@^2.0.0:
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@~5.1.0:
har-validator@~5.1.0, har-validator@~5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
@ -4749,12 +4792,12 @@ https-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
https-proxy-agent@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b"
integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==
https-proxy-agent@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
dependencies:
agent-base "5"
agent-base "6"
debug "4"
https-proxy-agent@^3.0.0:
@ -5403,16 +5446,16 @@ jschardet@^2.1.0:
resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.1.1.tgz#af6f8fd0b3b0f5d46a8fd9614a4fce490575c184"
integrity sha512-pA5qG9Zwm8CBpGlK/lo2GE9jPxwqRgMV7Lzc/1iaPccw6v4Rhj8Zg2BTyrdmHmxlJojnbLupLeRnaPLsq03x6Q==
jsdom@16.0.1:
version "16.0.1"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.0.1.tgz#5a5214cf69609531bf6fa5b6322fdd90ceed5bf7"
integrity sha512-wKJe/APzq+ak9i+2ybWE20lDIhF9AkGKSZf8UsjPN39acatFB6oA7K397kQvHVikds0yQono2h6J7UjbPtPOWw==
jsdom@16.1.0:
version "16.1.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.1.0.tgz#7e245db708ee00302947be7beb88a2ffa587ad2b"
integrity sha512-kpIcNAuZYc/L17WADOOHslz/q5+3SipP/iRb3j6zd1zQ6pFJubLi/VCdD3NqBpj/IKKK4YXny1vv44rbEUSGFg==
dependencies:
abab "^2.0.3"
acorn "^7.1.0"
acorn-globals "^4.3.2"
cssom "^0.4.4"
cssstyle "^2.0.0"
cssstyle "^2.1.0"
data-urls "^2.0.0"
decimal.js "^10.2.0"
domexception "^2.0.1"
@ -8208,12 +8251,12 @@ rechoir@^0.6.2:
dependencies:
resolve "^1.1.6"
reconnecting-websocket@4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.3.0.tgz#aaefbc7629a89450aa45324b89aec2276e728cc5"
integrity sha512-3eaHIEVYB9Zb0GfYy1xdEHKJLA2JaawAegByZ1AZ8Npb3AiRgUN5l89cvE2H+pHTsFcoC88t32ky9qET6DJ75Q==
reconnecting-websocket@4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783"
integrity sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==
redis-commands@1.5.0, redis-commands@^1.2.0:
redis-commands@1.5.0, redis-commands@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.5.0.tgz#80d2e20698fe688f227127ff9e5164a7dd17e785"
integrity sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==
@ -8228,11 +8271,6 @@ redis-lock@0.1.4:
resolved "https://registry.yarnpkg.com/redis-lock/-/redis-lock-0.1.4.tgz#e83590bee22b5f01cdb65bfbd88d988045356272"
integrity sha512-7/+zu86XVQfJVx1nHTzux5reglDiyUCDwmW7TSlvVezfhH2YLc/Rc8NE0ejQG+8/0lwKzm29/u/4+ogKeLosiA==
redis-parser@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b"
integrity sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=
redis-parser@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4"
@ -8240,14 +8278,15 @@ redis-parser@^3.0.0:
dependencies:
redis-errors "^1.0.0"
redis@2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/redis/-/redis-2.8.0.tgz#202288e3f58c49f6079d97af7a10e1303ae14b02"
integrity sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==
redis@3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/redis/-/redis-3.0.2.tgz#bd47067b8a4a3e6a2e556e57f71cc82c7360150a"
integrity sha512-PNhLCrjU6vKVuMOyFu7oSP296mwBkcE6lrAjruBYG5LgdSqtRBoVQIylrMyVZD/lkF24RSNNatzvYag6HRBHjQ==
dependencies:
double-ended-queue "^2.1.0-0"
redis-commands "^1.2.0"
redis-parser "^2.6.0"
denque "^1.4.1"
redis-commands "^1.5.0"
redis-errors "^1.2.0"
redis-parser "^3.0.0"
reflect-metadata@0.1.13, reflect-metadata@^0.1.13:
version "0.1.13"
@ -8410,6 +8449,32 @@ request@2.88.0, request@^2.73.0, request@^2.83.0, request@^2.88.0:
tunnel-agent "^0.6.0"
uuid "^3.3.2"
request@2.88.2:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
caseless "~0.12.0"
combined-stream "~1.0.6"
extend "~3.0.2"
forever-agent "~0.6.1"
form-data "~2.3.2"
har-validator "~5.1.3"
http-signature "~1.2.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
json-stringify-safe "~5.0.1"
mime-types "~2.1.19"
oauth-sign "~0.9.0"
performance-now "^2.1.0"
qs "~6.5.2"
safe-buffer "^5.1.2"
tough-cookie "~2.5.0"
tunnel-agent "^0.6.0"
uuid "^3.3.2"
require-all@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/require-all/-/require-all-2.2.0.tgz#b4420c233ac0282d0ff49b277fb880a8b5de0894"
@ -8524,10 +8589,10 @@ rimraf@2.6.3:
dependencies:
glob "^7.1.3"
rimraf@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.1.tgz#48d3d4cb46c80d388ab26cd61b1b466ae9ae225a"
integrity sha512-IQ4ikL8SjBiEDZfk+DFVwqRK8md24RWMEJkdSlgNLkyyAImcjf8SWvU1qFMDOb4igBClbTQ/ugPqXcRwdFTxZw==
rimraf@3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
dependencies:
glob "^7.1.3"
@ -9256,12 +9321,13 @@ strip-json-comments@^3.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
strtok3@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-5.0.2.tgz#bb81f1f56742e16f1a30ccce5dc3d9498aa5475a"
integrity sha512-EFeVpFC5qDsqPEJSrIYyS/ueFBknGhgSK9cW+YAJF/cgJG/KSjoK7X6rK5xnpcLe7y1LVkVFCXWbAb+ClNKzKQ==
strtok3@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.0.0.tgz#d6b900863daeacfe6c1724c6e7bb36d7a58e83c8"
integrity sha512-ZXlmE22LZnIBvEU3n/kZGdh770fYFie65u5+2hLK9s74DoFtpkQIdBZVeYEzlolpGa+52G5IkzjUWn+iXynOEQ==
dependencies:
"@tokenizer/token" "^0.1.1"
"@types/debug" "^4.1.5"
debug "^4.1.1"
peek-readable "^3.1.0"
@ -9417,11 +9483,16 @@ syslog-pro@1.0.0:
dependencies:
moment "^2.22.2"
systeminformation@*, systeminformation@4.21.1:
systeminformation@*:
version "4.21.1"
resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-4.21.1.tgz#8394684676c9ca1da59d9f92458b166d9cd52cd6"
integrity sha512-IQMy+ieSThY+MfLZaCdQsGCteMah4nhsDQcnT9DhocoJnhMKVUqDY025j1i+MSm7qdUCMXS5oV7dvttr+pSodw==
systeminformation@4.21.2:
version "4.21.2"
resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-4.21.2.tgz#0ee92c0565687a5301ba141444643b43d0370ae2"
integrity sha512-7O6laxHTstfj9MSrX77lKLfWz0zqqutWL0+uoJkR7sOOr4XCTwD0QG4shUVCiOWlVX+a8/gHJUio420FrSk8/w==
syuilo-password-strength@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/syuilo-password-strength/-/syuilo-password-strength-0.0.1.tgz#08f71a8f0ecb77db649f3d9a6424510d9d945f52"
@ -9700,7 +9771,7 @@ token-types@^2.0.0:
"@tokenizer/token" "^0.1.0"
ieee754 "^1.1.13"
tough-cookie@^2.3.3:
tough-cookie@^2.3.3, tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
@ -10309,10 +10380,10 @@ vue-json-pretty@1.6.3:
resolved "https://registry.yarnpkg.com/vue-json-pretty/-/vue-json-pretty-1.6.3.tgz#c7f378f3c9f68977047de28197735bc2cf81b15b"
integrity sha512-dgb04fT9PMndfCI4RiQcqaV5P0hSg97LObm15Mkzjl2hL+qWJd833tOd2RhK/yk8AieZt8eJUac5NTZb31O6Mg==
vue-loader@15.8.3:
version "15.8.3"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.8.3.tgz#857cb9e30eb5fc25e66db48dce7e4f768602a23c"
integrity sha512-yFksTFbhp+lxlm92DrKdpVIWMpranXnTEuGSc0oW+Gk43M9LWaAmBTnfj5+FCdve715mTHvo78IdaXf5TbiTJg==
vue-loader@15.9.0:
version "15.9.0"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.0.tgz#5d4b0378a4606188fc83e587ed23c94bc3a10998"
integrity sha512-FeDHvTSpwyLeF7LIV1PYkvqUQgTJ8UmOxhSlCyRSxaXCKk+M6NF4tDQsLsPPNeDPyR7TfRQ8MLg6v+8PsDV9xQ==
dependencies:
"@vue/component-compiler-utils" "^3.1.0"
hash-sum "^1.0.2"
@ -10393,10 +10464,10 @@ vuedraggable@2.23.2:
dependencies:
sortablejs "^1.10.1"
vuex-persistedstate@2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/vuex-persistedstate/-/vuex-persistedstate-2.7.0.tgz#f60aae4e1163bf293696a625526dbffaa42e429e"
integrity sha512-mpko65DUMBY4mF4sSGsgrqjE7fwO373LFZeuNrC55glRuBBAK4dkgzjr4j4Bij7WtMoKuo2t2w0NGenjauISaQ==
vuex-persistedstate@2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/vuex-persistedstate/-/vuex-persistedstate-2.7.1.tgz#25ced829ea5789a2ed7d71a1ba057eb01a5235ad"
integrity sha512-Ktvp6Bt6ApYj35MuxTClu+9Lpukcgl3Z/0o4PU12+Z4jU6lyOMzos0k6zGT5xrukAkGM1VV3EYNwz1TnHPhgFA==
dependencies:
deepmerge "^4.2.2"
shvl "^2.0.0"
@ -10446,10 +10517,10 @@ webidl-conversions@^5.0.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==
webpack-cli@3.3.10:
version "3.3.10"
resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.10.tgz#17b279267e9b4fb549023fae170da8e6e766da13"
integrity sha512-u1dgND9+MXaEt74sJR4PR7qkPxXUSQ0RXYq8x1L6Jg1MYVEmGPrH6Ah6C4arD4r0J1P5HKjRqpab36k0eIzPqg==
webpack-cli@3.3.11:
version "3.3.11"
resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.11.tgz#3bf21889bf597b5d82c38f215135a411edfdc631"
integrity sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g==
dependencies:
chalk "2.4.2"
cross-spawn "6.0.5"
@ -10471,10 +10542,10 @@ webpack-sources@^1.0.1, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-
source-list-map "^2.0.0"
source-map "~0.6.1"
webpack@4.41.5:
version "4.41.5"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.5.tgz#3210f1886bce5310e62bb97204d18c263341b77c"
integrity sha512-wp0Co4vpyumnp3KlkmpM5LWuzvZYayDwM2n17EHFr4qxBBbRokC7DJawPJC7TfSFZ9HZ6GsdH40EBj4UV0nmpw==
webpack@4.41.6:
version "4.41.6"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.6.tgz#12f2f804bf6542ef166755050d4afbc8f66ba7e1"
integrity sha512-yxXfV0Zv9WMGRD+QexkZzmGIh54bsvEs+9aRWxnN8erLWEOehAKUTeNBoUbA6HPEZPlRo7KDi2ZcNveoZgK9MA==
dependencies:
"@webassemblyjs/ast" "1.8.5"
"@webassemblyjs/helper-module-context" "1.8.5"