From 6da4f407e54fa5bb570b8ffd4de0a5cb9e772a6d Mon Sep 17 00:00:00 2001 From: Lizzy Date: Wed, 26 Aug 2020 14:02:46 +0000 Subject: [PATCH 0001/1014] Translated using Weblate (Spanish) Currently translated at 87.7% (2050 of 2337 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/es/ --- src/i18n/strings/es.json | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index 1619bb7616..29f66eb57e 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -50,7 +50,7 @@ "Deops user with given id": "Degrada al usuario con la ID dada", "Default": "Por Defecto", "Disinvite": "Deshacer invitación", - "Displays action": "Muestra la acción", + "Displays action": "Hacer una acción", "Download %(text)s": "Descargar %(text)s", "Email": "Correo electrónico", "Email address": "Dirección de correo electrónico", @@ -285,7 +285,7 @@ "Uploading %(filename)s and %(count)s others|one": "Subiendo %(filename)s y otros %(count)s", "Uploading %(filename)s and %(count)s others|other": "Subiendo %(filename)s y otros %(count)s", "Upload avatar": "Subir avatar", - "Upload Failed": "No Se Pudo Subir", + "Upload Failed": "Subida fallida", "Upload file": "Subir archivo", "Upload new:": "Subir nuevo:", "Usage": "Uso", @@ -518,7 +518,7 @@ "Failed to invite the following users to %(groupId)s:": "No se pudo invitar a los siguientes usuarios a %(groupId)s:", "Failed to invite users to community": "No se pudo invitar usuarios a la comunidad", "Failed to invite users to %(groupId)s": "No se pudo invitar usuarios a %(groupId)s", - "Failed to add the following rooms to %(groupId)s:": "No se pudo añadir a las siguientes salas a %(groupId)s:", + "Failed to add the following rooms to %(groupId)s:": "No se pudieron añadir las siguientes salas a %(groupId)s:", "Restricted": "Restringido", "Missing roomId.": "Falta el Id de sala.", "Ignores a user, hiding their messages from you": "Ignora a un usuario, ocultando sus mensajes", @@ -1246,7 +1246,7 @@ "Are you sure you want to sign out?": "¿Estás seguro de que quieres salir?", "Message edits": "Ediciones del mensaje", "New session": "Nueva sesión", - "Use this session to verify your new one, granting it access to encrypted messages:": "Usa esta sesión para verificar tu nueva sesión, dándola acceso a mensajes encriptados:", + "Use this session to verify your new one, granting it access to encrypted messages:": "Usa esta sesión para verificar tu nueva sesión, dándole acceso a mensajes encriptados:", "If you didn’t sign in to this session, your account may be compromised.": "Si no te conectaste a esta sesión, es posible que tu cuenta haya sido comprometida.", "This wasn't me": "No fui yo", "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "Si encuentras algún error o quieres compartir una opinión, por favor, contacta con nosotros en GitHub.", @@ -1318,7 +1318,7 @@ "Your user agent": "Tu agente de usuario", "If you cancel now, you won't complete verifying the other user.": "Si cancelas ahora, no completarás la verificación del otro usuario.", "If you cancel now, you won't complete verifying your other session.": "Si cancelas ahora, no completarás la verificación de tu otra sesión.", - "Cancel entering passphrase?": "¿Cancelar la introducción de frase de contraseña?", + "Cancel entering passphrase?": "¿Cancelar el ingresar tu contraseña de recuperación?", "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s actualizó la regla bloqueando salas que coinciden con %(glob)s por %(reason)s", "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s actualizó la regla bloqueando servidores que coinciden con %(glob)s por %(reason)s", "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s": "%(senderName)s actualizó una regla de bloqueo correspondiente a %(glob)s por %(reason)s", @@ -1436,7 +1436,7 @@ "If you cancel now, you won't complete your operation.": "Si cancela ahora, no completará la operación.", "Review where you’re logged in": "Revise dónde hizo su registro", "New login. Was this you?": "Nuevo registro. ¿Fuiste tú?", - "%(name)s is requesting verification": "%(name)s solicita verificación", + "%(name)s is requesting verification": "%(name)s solicita verificación", "Sign In or Create Account": "Iniciar sesión o Crear una cuenta", "Use your account or create a new one to continue.": "Usa tu cuenta existente o crea una nueva para continuar.", "Create Account": "Crear cuenta", @@ -1614,7 +1614,7 @@ "Can't find this server or its room list": "No puedo encontrar este servidor o su lista de salas", "All rooms": "Todas las salas", "Your server": "Tu", - "Are you sure you want to remove %(serverName)s": "¿ Está seguro de querer eliminar %(serverName)s?", + "Are you sure you want to remove %(serverName)s": "¿Está seguro de querer eliminar %(serverName)s?", "Remove server": "Quitar servidor", "Matrix": "Matrix", "Add a new server": "Añadir un nuevo servidor", @@ -1914,7 +1914,7 @@ "Unable to restore backup": "No se pudo restaurar la copia de seguridad", "No backup found!": "¡No se encontró una copia de seguridad!", "Keys restored": "Se restauraron las claves", - "Failed to decrypt %(failedCount)s sessions!": "¡Error en descifrar %(failedCount) sesiones!", + "Failed to decrypt %(failedCount)s sessions!": "¡Error al descifrar %(failedCount)s sesiones!", "Successfully restored %(sessionCount)s keys": "%(sessionCount)s claves restauradas con éxito", "Warning: you should only set up key backup from a trusted computer.": "Advertencia: deberías configurar la copia de seguridad de claves solamente usando un ordenador de confianza.", "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Acceda a su historial de mensajes seguros y configure la mensajería segura introduciendo su contraseña de recuperación.", @@ -2075,5 +2075,27 @@ "%(targetName)s changed their avatar": "%(targetName)s ha cambiado su avatar", "You changed the room name": "Has cambiado el nombre de la sala", "%(senderName)s changed the room name": "%(senderName)s cambio el nombre de la sala", - "You invited %(targetName)s": "Has invitado a %(targetName)s" + "You invited %(targetName)s": "Has invitado a %(targetName)s", + "Are you sure you want to cancel entering passphrase?": "¿Estas seguro que quieres cancelar el ingresar tu contraseña de recuperación?", + "Go Back": "No cancelar", + "Joins room with given address": "Entrar a la sala con la dirección especificada", + "Unrecognised room address:": "No se encuentra la dirección de la sala:", + "Opens chat with the given user": "Abrir una conversación con el usuario especificado", + "Sends a message to the given user": "Enviar un mensaje al usuario especificado", + "Light": "Claro", + "Dark": "Oscuro", + "Unexpected server error trying to leave the room": "Error inesperado del servidor al abandonar esta sala", + "Error leaving room": "Error al salir de la sala", + "Your homeserver has exceeded its user limit.": "Tú servidor ha excedido su limite de usuarios.", + "Your homeserver has exceeded one of its resource limits.": "Tú servidor ha excedido el limite de sus recursos.", + "Contact your server admin.": "Contacta con el administrador del servidor.", + "The person who invited you already left the room.": "La persona que te invito abandono la sala.", + "The person who invited you already left the room, or their server is offline.": "La persona que te invito abandono la sala, o puede que su servidor se encuentre desconectado.", + "* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s", + "%(senderName)s: %(message)s": "%(senderName)s: %(message)s", + "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", + "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", + "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Tu nueva sesión se encuentra verificada ahora. Ahora tiene acceso a los mensajes encriptados y otros usuarios verán la sesión como verificada.", + "Your new session is now verified. Other users will see it as trusted.": "Tu sesión se encuentra ahora verificada. Otros usuarios la verán como confiable.", + "This session is encrypting history using the new recovery method.": "Esta sesión se encuentra encriptando el historial usando el nuevo método de verificación." } From 01eee3008901b08616c4e508cccbf47216d8f981 Mon Sep 17 00:00:00 2001 From: ziriSut Date: Wed, 26 Aug 2020 16:09:50 +0000 Subject: [PATCH 0002/1014] Translated using Weblate (Kabyle) Currently translated at 89.6% (2109 of 2353 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/kab/ --- src/i18n/strings/kab.json | 87 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/kab.json b/src/i18n/strings/kab.json index 9af55c0793..51fe8724bf 100644 --- a/src/i18n/strings/kab.json +++ b/src/i18n/strings/kab.json @@ -649,7 +649,7 @@ "Connecting to integration manager...": "Tuqqna ɣer umsefrak n useddu...", "Cannot connect to integration manager": "Ur nessaweḍ ara ad neqqen ɣer umsefrak n useddu", "Delete Backup": "Kkes aḥraz", - "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Iznan yettwawgelhen ttuḥerzen s uwgelhen n yixef ɣer yixef. Ala kečč d unermas (inermasen) i yesεan tisura akken ad ɣren iznan-a.", + "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Iznan yettwawgelhen ttuḥerzen s uwgelhen n yixef ɣer yixef. Ala kečč d uɣerwaḍ (yiɣerwaḍen) i yesεan tisura akken ad ɣren iznan-a.", "This session is backing up your keys. ": "Tiɣimit tḥerrez tisura-inek·inem. ", "Connect this session to Key Backup": "Qqen tiɣimit-a ɣer uḥraz n tsarut", "Server Name": "Isem n uqeddac", @@ -2074,5 +2074,88 @@ "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "War aswel n Secure Message Recovery, ad tmedleḍ amazray-ik·im n yiznan uffiren ma yella teffɣeḍ.", "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Ma yella ur tesbaduḍ ara tarrayt n tririt tamaynut, yezmer ad yili umaker ara iɛerḍen ad yekcem ɣer umiḍan-ik·im. Beddel awal uffir n umiḍan-ik·im syen sbadu tarrayt n tririt tamaynut din din deg yiɣewwaren.", "This session has detected that your recovery passphrase and key for Secure Messages have been removed.": "Tiɣimit-a tufa-d tafyirt-ik·im tuffirt n tririt d tsarut-ik·im n yiznan uffiren ttwakksent.", - "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Ma yella ur tekkiseḍ ara tarrayt n tririt tamaynut, yezmer ad yili umaker ara iɛerḍen ad yekcem ɣer umiḍan-ik·im. Beddel awal uffir n umiḍan-ik·im syen sbadu tarrayt n tririt tamaynut din din deg yiɣewwaren." + "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Ma yella ur tekkiseḍ ara tarrayt n tririt tamaynut, yezmer ad yili umaker ara iɛerḍen ad yekcem ɣer umiḍan-ik·im. Beddel awal uffir n umiḍan-ik·im syen sbadu tarrayt n tririt tamaynut din din deg yiɣewwaren.", + "Mirror local video feed": "Asbani n usuddem n tvidyut tadigant", + "Low bandwidth mode": "Askar n tehri n tesfift adday", + "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Sireg aqeddac n tallalt i yisawalen n ufran aneggaru turn.matrix.org ma yili aqeddac-ik·im agejdan ur d-yettmudd ara yiwen (tansa-ik·im n IP ad tettwabḍu lawan n usiwel)", + "Compare a unique set of emoji if you don't have a camera on either device": "Serwes tagrumma n yimujiten asufen ma yella ur tesɛiḍ ara takamiṛat ɣef yiwen seg sin yibenkan", + "Unable to find a supported verification method.": "D awezɣi ad d-naf tarrayt n usenqed yettusefraken.", + "Waiting for your other session, %(deviceName)s (%(deviceId)s), to verify…": "Deg uṛaǧu n tɣimit-ik·im tayeḍ, %(deviceName)s (%(deviceId)s), i usenqed…", + "To be secure, do this in person or use a trusted way to communicate.": "I wakken ad tḍemneḍ taɣellistik·im, eg ayagi s timmad-ik·im neɣ seqdec abrid n teywalt iɣef ara tettekleḍ.", + "You may need to manually permit %(brand)s to access your microphone/webcam": "Ilaq-ak·am ahat ad tesirgeḍ s ufus %(brand)s i unekcum ɣer usawaḍ/webcam", + "This room is not accessible by remote Matrix servers": "Anekcum er texxamt-a ulamek s yiqeddacen n Matrix inmeggagen", + "No users have specific privileges in this room": "Ulac aqeddac yesan ibauren deg texxamt-a ", + "Select the roles required to change various parts of the room": "Fren timlilin yettusran i usnifel n yiḥricen yemgaraden n texxamt", + "Guests cannot join this room even if explicitly invited.": "Ur zmiren ara inebgawen ad d-rnun ɣer texxamt-a alamma ttusnubegten-d s tidet.", + "Once enabled, encryption cannot be disabled.": "Akken ara yettwarmad, awgelhen ur yettizmir ara ad yens.", + "Click the link in the email you received to verify and then click continue again.": "Sit ɣef useɣwen yella deg yimayl i teṭṭfeḍ i usenqed syen sit tikkelt tayeḍ ad tkemmleḍ.", + "Discovery options will appear once you have added an email above.": "Tixtiṛiyin n usnirem ad d-banent akken ara ternuḍ imayl s ufella.", + "Discovery options will appear once you have added a phone number above.": "Tixtiṛiyin n usnirem ad d-banent akken ara ternuḍ uṭṭun n tilifun s ufella.", + "The maximum permitted number of widgets have already been added to this room.": "Amḍan afellay yettusirgen n yiwiǧiten yettwarna yakan ɣer texxamt-a.", + "This room doesn't exist. Are you sure you're at the right place?": "Taxxamt-a ulac-itt. Tetteḥqeḍ aql-ak·akem deg wadeg i iṣeḥḥan?", + "Try again later, or ask a room admin to check if you have access.": "Ɛreḍ tikkelt-nniḍen ticki, neɣ suter deg unedbal n texxamt ad iwali ma tzemreḍ ad tkecmeḍ.", + "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "%(errcode)s yuɣal-d lawan n uneɛruḍ n unekcum ɣer texxamt. Ma yella izen-a twalaḍ-t ur tebniḍ fell-as, ttxil-k·m azen aneqqis n wabug.", + "Never lose encrypted messages": "Ur ttamdal ara akk iznan iwgelhanen", + "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Iznan deg texxamt-a ttwaḥerzen s uwgelhen n yixef ɣer yixef. Ala kečč·kemm d uɣerwaḍ (yiɣerwaḍen) i yesεan tisura akken ad ɣren iznan-a.", + "Securely back up your keys to avoid losing them. Learn more.": "Ḥrez tisura-k·m s wudem aɣelsan i wakken ur ak·am-ttṛuḥunt ara. Issin ugar", + "Unrecognised command: %(commandText)s": "Taladna d tarussint: %(commandText)s", + "Hint: Begin your message with // to start it with a slash.": "Taxballut: Bdu izen-ik·im s // i wakken ad t-tebduḍ s uṣlac.", + "Failed to connect to integration manager": "Tuqqna ɣer umsefrak n umsidef ur yeddi ara", + "Jump to first unread message.": "Ɛeddi ɣer yzen amezwaru ur nettwaɣra ara. ", + "Error updating main address": "Tuccḍa deg usali n tensa tagejdant", + "You don't have permission to delete the address.": "Ur tesɛiḍ ara tisirag i wakken ad tekkseḍ tansa.", + "This room has no local addresses": "Taxxamt-a ur tesɛi ara tansiwin tidiganin", + "Error updating flair": "Tuccḍa deg uleqqem n lbenna", + "'%(groupId)s' is not a valid community ID": "'%(groupId)s' mačči d asulay n temɣiwent ameɣtu", + "Use bots, bridges, widgets and sticker packs": "Seqdec abuten, tileggiyin, iwiǧiten d tɣawsiwin n umyintaḍ", + "To continue you need to accept the terms of this service.": "I wakken ad tkemmleḍ tesriḍ ad tqebleḍ tiwtilin n umeẓlu-a.", + "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "Afaylu-a ɣezzif aṭas i wakken ad d-yali. Talast n teɣzi n ufaylu d %(limit)s.", + "These files are too large to upload. The file size limit is %(limit)s.": "Ifuyla-a ɣezzifit aṭas i wakken ad d-alin. Talast n teɣzi n ufaylu d %(limit)s.", + "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Kra n yifuyla ɣezzifit aṭas i wakken ad d-alin. Talast n teɣzi n ufaylu d %(limit)s.", + "Upload %(count)s other files|other": "Sali-d %(count)s ifuyla-nniḍen", + "Upload %(count)s other files|one": "Sali-d %(count)s afaylu-nniḍen", + "Upload Error": "Tuccḍa deg usali", + "A widget would like to verify your identity": "Awiǧit yebɣa ad issenqed timagit-inek·inem", + "Remember my selection for this widget": "Cfu ɣef tefrant-inu i uwiǧit-a", + "Wrong file type": "Anaw n yifuyla d arameɣtu", + "Looks good!": "Yettban igerrez!", + "Enter your Security Phrase or to continue.": "Sekcem tafyirt-ik·im n tɣellist neɣ ": "Ma yella tettuḍ tasarut-ik·im n uɛeddi, tzemreḍ ", + "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Wennez kullec neɣ sefsex kullec tura. Tzemreḍ daɣen ad tferneḍ iznan udmawanen i uwennez neɣ i usefsex.", + "Tried to load a specific point in this room's timeline, but was unable to find it.": "Ɛerḍeɣ ad d-saliɣ tazmilt tufrint tesnakudt n texxamt-a, maca ur ssawḍeɣ ara ad t-naf.", + "A verification email will be sent to your inbox to confirm setting your new password.": "Imayl n usenqed ad yettwazen ɣer tbewwaḍt-ik·im n yimayl i usentem n yiɣewwaren n wawal-ik·im uffir." } From a97d1ca34490c6543926d90ac40efe2ab647a8d8 Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Fri, 28 Aug 2020 12:11:13 +0000 Subject: [PATCH 0025/1014] Translated using Weblate (Russian) Currently translated at 100.0% (2356 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 2fa00ca92a..b6a2f483fb 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2428,7 +2428,7 @@ "Show": "Показать", "Send %(count)s invites|other": "Отправить %(count)s приглашений", "Send %(count)s invites|one": "Отправить %(count)s приглашение", - "Invite people to join %(communityName)s": "Пригласите людей присоединиться к %(communtyName)s", + "Invite people to join %(communityName)s": "Пригласите людей присоединиться к %(communityName)s", "There was an error creating your community. The name may be taken or the server is unable to process your request.": "При создании сообщества произошла ошибка. Имя может быть занято или сервер не может обработать ваш запрос.", "Community ID: +:%(domain)s": "ID сообщества: +:%(domain)s", "Use this when referencing your community to others. The community ID cannot be changed.": "Используйте это при обращении к другим людям. ID сообщества не может быть изменён.", @@ -2438,5 +2438,7 @@ "Add image (optional)": "Добавить изображение (необязательно)", "An image will help people identify your community.": "Изображение поможет людям идентифицировать ваше сообщество.", "Create a room in %(communityName)s": "Создать комнату в %(communityName)s", - "Create community": "Создать сообщество" + "Create community": "Создать сообщество", + "Cross-signing and secret storage are ready for use.": "Кросс-подпись и секретное хранилище готовы к использованию.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Кросс-подпись готова к использованию, но секретное хранилище в настоящее время не используется для резервного копирования ваших ключей." } From 0ea3bd820768a5a91b395146a28d4723cddd62d9 Mon Sep 17 00:00:00 2001 From: ziriSut Date: Fri, 28 Aug 2020 15:49:48 +0000 Subject: [PATCH 0026/1014] Translated using Weblate (Kabyle) Currently translated at 99.0% (2335 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/kab/ --- src/i18n/strings/kab.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/kab.json b/src/i18n/strings/kab.json index b66616d07a..ad2e683faa 100644 --- a/src/i18n/strings/kab.json +++ b/src/i18n/strings/kab.json @@ -2373,5 +2373,19 @@ "If you've forgotten your recovery key you can ": "Ma yella tettuḍ tasarut-ik·im n uɛeddi, tzemreḍ ", "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Wennez kullec neɣ sefsex kullec tura. Tzemreḍ daɣen ad tferneḍ iznan udmawanen i uwennez neɣ i usefsex.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Ɛerḍeɣ ad d-saliɣ tazmilt tufrint tesnakudt n texxamt-a, maca ur ssawḍeɣ ara ad t-naf.", - "A verification email will be sent to your inbox to confirm setting your new password.": "Imayl n usenqed ad yettwazen ɣer tbewwaḍt-ik·im n yimayl i usentem n yiɣewwaren n wawal-ik·im uffir." + "A verification email will be sent to your inbox to confirm setting your new password.": "Imayl n usenqed ad yettwazen ɣer tbewwaḍt-ik·im n yimayl i usentem n yiɣewwaren n wawal-ik·im uffir.", + "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Kcem ɣer umazray aɣelsan n yiznan-inek·inem syen sbadu tirawt taɣelsant s usekcem n tefyirt tuffirt n uɛeddi.", + "Access your secure message history and set up secure messaging by entering your recovery key.": "Kcem ɣer umazray aɣelsan n yiznan-ik·im syen sbadu tirawt taɣelsant s usekcem n tsarut n uɛeddi.", + "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Txuṣṣ tsarut tazayezt n captcha deg umtawi n uqeddac agejdan. Ttxil-k·m azen aneqqis ɣef waya i unedbal n uqeddac-ik·im agejdan.", + "Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of element.io.": "Sekcem adeg n uqeddac-ik·im agejdan n umeẓlu n Element Matrix. Yezmer ad iseqdec isem n taɣult-ik·im uzzig neɣ ad yili d taɣult tarnawt n element.io.", + "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "Ulac aqeddac n timagit yettusiwlen, ɣef waya ur tettizmireḍ ara ad ternuḍ tansa n yimayl i wakken ad twennzeḍ awal-ik·im uffir ɣer sdat.", + "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Ma yella ur d-tefrineḍ ara tansa n yimayl, ur tettizmireḍ ara ad twennzeḍ awal-ik·im uffir. Tebɣiḍ?", + "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Sbadu imayl i tririt n umiḍan. Seqdec imayl neɣ tiliɣri i wakken ad tettwaf s uxtiṛi sɣur inermisen i yellan.", + "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Sbadu imayl i tririt n umiḍan. Seqdec imayl s ufran i wakken ad d-iban i yinermisen i yellan.", + "%(brand)s uses many advanced browser features, some of which are not available or experimental in your current browser.": "%(brand)s isseqdac aṭas n tmahilin leqqayen n yiminig, kra seg-sent ulac-itent neɣ d tirmitanin deg yiminig-ik·im amiran.", + "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "S yiminig-ik·im amiran, ameẓraw d umḥulfan n usnas zemren ad ilin mačči akk d imeɣta, rnu kra neɣ akk timahilin zemrent ur teddunt ara. Ma yella tebɣiḍ ɣas akken ad t-tɛerḍeḍ tzemreḍ ad tkemmleḍ, maca ur tseɛɛuḍ ara akk tallalt ma yella temlaleḍ-d d wuguren!", + "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.": "Aql-ak·akem d (t)anedbal(t) n temɣiwent-a. Ur tettizmireḍ ara ad tɛawdeḍ anekcum alamma s tinubga n unedbal-nniḍen.", + "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Isnifal i d-yellan ɣef isem d avaṭar i temɣiwent-ik·im ur ttmeẓran ara sɣur yiseqdacen-nniḍen alamma d 30 tesdidin .", + "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Tixxamin-a ttwaskanent i yiɛeggalen n temɣiwent ɣef usebter n temɣiwent. Iɛeggalen n temɣiwent zemren ad ttekkin deg texxamin s usiti fell-asent.", + "Your community hasn't got a Long Description, a HTML page to show to community members.
Click here to open settings and give it one!": "Tamɣiwent-ik·im ur tesɛi ara aglam ɣezzifen, asebter HTML i uskan n yiɛeggalen n temɣiwent.
Sit da i wakken ad teldiḍ iɣewwaren syen rnu yiwet!" } From 3703786d258de8ca26cedbdbf5288df2a4d0c6fc Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Fri, 28 Aug 2020 17:26:16 +0000 Subject: [PATCH 0027/1014] Translated using Weblate (Swedish) Currently translated at 74.3% (1751 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 222 +++++++++++++++++++++++++++++++-------- 1 file changed, 178 insertions(+), 44 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 0978ff6c25..16be70f1c8 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -72,7 +72,7 @@ "Export": "Exportera", "Export E2E room keys": "Exportera krypteringsrumsnycklar", "Failed to ban user": "Det gick inte att banna användaren", - "Failed to change password. Is your password correct?": "Det gick inte att byta lösenord. Är lösenordet rätt?", + "Failed to change password. Is your password correct?": "Misslyckades att byta lösenord. Är lösenordet rätt?", "Failed to change power level": "Det gick inte att ändra behörighetsnivå", "Failed to forget room %(errCode)s": "Det gick inte att glömma bort rummet %(errCode)s", "Failed to join room": "Det gick inte att gå med i rummet", @@ -84,7 +84,7 @@ "Failed to reject invitation": "Det gick inte att avböja inbjudan", "Failed to send email": "Det gick inte att skicka epost", "Failed to send request.": "Det gick inte att sända begäran.", - "Failed to set display name": "Det gick inte att ange visningsnamn", + "Failed to set display name": "Misslyckades att ange visningsnamn", "Failed to unban": "Det gick inte att avbanna", "Failed to verify email address: make sure you clicked the link in the email": "Det gick inte att bekräfta e-postadressen: set till att du klickade på länken i e-postmeddelandet", "Favourite": "Favorit", @@ -114,8 +114,8 @@ "Hangup": "Lägg på", "Historical": "Historiska", "Home": "Hem", - "Homeserver is": "Hemserver är", - "Identity Server is": "Identitetsserver är", + "Homeserver is": "Hemservern är", + "Identity Server is": "Identitetsservern är", "I have verified my email address": "Jag har verifierat min epostadress", "Import": "Importera", "Import E2E room keys": "Importera rumskrypteringsnycklar", @@ -138,7 +138,7 @@ "%(senderName)s kicked %(targetName)s.": "%(senderName)s kickade %(targetName)s.", "Kick": "Kicka", "Kicks user with given id": "Kickar användaren med givet ID", - "Labs": "Labb", + "Labs": "Experiment", "Last seen": "Senast sedd", "Leave room": "Lämna rummet", "%(targetName)s left the room.": "%(targetName)s lämnade rummet.", @@ -190,7 +190,7 @@ "Return to login screen": "Tillbaka till login-skärmen", "%(brand)s does not have permission to send you notifications - please check your browser settings": "%(brand)s har inte tillstånd att skicka aviseringar - kontrollera webbläsarens inställningar", "%(brand)s was not given permission to send notifications - please try again": "%(brand)s fick inte tillstånd att skicka aviseringar - försök igen", - "%(brand)s version:": "%(brand)s -version:", + "%(brand)s version:": "%(brand)s-version:", "Room %(roomId)s not visible": "Rummet %(roomId)s är inte synligt", "Room Colour": "Rumsfärg", "%(roomName)s does not exist.": "%(roomName)s finns inte.", @@ -300,7 +300,7 @@ "Waiting for response from server": "Väntar på svar från servern", "Leave": "Lämna", "Uploaded on %(date)s by %(user)s": "%(user)s laddade upp %(date)s", - "Advanced notification settings": "Avancerade aviseringsinställingar", + "Advanced notification settings": "Avancerade aviseringsinställningar", "Forget": "Glöm bort", "You cannot delete this image. (%(code)s)": "Du kan inte radera den här bilden. (%(code)s)", "Cancel Sending": "Avbryt sändning", @@ -312,12 +312,12 @@ "Messages in one-to-one chats": "Meddelanden i en-till-en chattar", "Unavailable": "Otillgänglig", "View Decrypted Source": "Visa dekrypterad källa", - "Failed to update keywords": "Det gick inte att uppdatera nyckelorden", + "Failed to update keywords": "Kunde inte uppdatera nyckelorden", "remove %(name)s from the directory.": "ta bort %(name)s från katalogen.", "Notifications on the following keywords follow rules which can’t be displayed here:": "Aviseringar för följande nyckelord följer regler som inte kan visas här:", "Please set a password!": "Vänligen välj ett lösenord!", "You have successfully set a password!": "Du har valt ett nytt lösenord!", - "An error occurred whilst saving your email notification preferences.": "Ett fel uppstod då epostaviseringsinställningarna sparades.", + "An error occurred whilst saving your email notification preferences.": "Ett fel inträffade då e-postaviseringsinställningarna sparades.", "Explore Room State": "Utforska rumläget", "Source URL": "Käll-URL", "Failed to add tag %(tagName)s to room": "Det gick inte att lägga till etiketten \"%(tagName)s\" till rummet", @@ -330,7 +330,7 @@ "Keywords": "Nyckelord", "Enable notifications for this account": "Aktivera aviseringar för det här kontot", "Messages containing keywords": "Meddelanden som innehåller nyckelord", - "Error saving email notification preferences": "Ett fel uppstod då epostaviseringsinställningarna sparades", + "Error saving email notification preferences": "Fel när e-postaviseringsinställningarna sparades", "Tuesday": "tisdag", "Enter keywords separated by a comma:": "Skriv in nyckelord, separerade med kommatecken:", "Search…": "Sök…", @@ -356,12 +356,12 @@ "Send logs": "Skicka loggar", "All messages": "Alla meddelanden", "Call invitation": "Inbjudan till samtal", - "Downloading update...": "Laddar ned uppdatering...", + "Downloading update...": "Laddar ned uppdatering…", "You have successfully set a password and an email address!": "Du har framgångsrikt valt ett lösenord och en e-postadress!", "What's new?": "Vad är nytt?", "Notify me for anything else": "Avisera för allt annat", "When I'm invited to a room": "När jag bjuds in till ett rum", - "Can't update user notification settings": "Kan inte uppdatera aviseringsinställningarna", + "Can't update user notification settings": "Kan inte uppdatera användaraviseringsinställningarna", "Notify for all other messages/rooms": "Avisera för alla andra meddelanden/rum", "Unable to look up room ID from server": "Det gick inte att hämta rums-ID:t från servern", "Couldn't find a matching Matrix room": "Kunde inte hitta ett matchande Matrix-rum", @@ -378,21 +378,21 @@ "Yesterday": "igår", "Error encountered (%(errorDetail)s).": "Fel påträffat (%(errorDetail)s).", "Low Priority": "Låg prioritet", - "Unable to fetch notification target list": "Det gick inte att hämta aviseringsmållistan", + "Unable to fetch notification target list": "Kunde inte hämta aviseringsmållistan", "Set Password": "Välj lösenord", "Off": "Av", "%(brand)s does not know how to join a room on this network": "%(brand)s kan inte gå med i ett rum på det här nätverket", "Mentions only": "Endast omnämnande", "Failed to remove tag %(tagName)s from room": "Det gick inte att radera etiketten %(tagName)s från rummet", "You can now return to your account after signing out, and sign in on other devices.": "Du kan nu återgå till ditt konto efter att ha loggat ut och logga in på andra enheter.", - "Enable email notifications": "Aktivera epostaviseringar", + "Enable email notifications": "Aktivera e-postaviseringar", "Download this file": "Ladda ner filen", "Failed to change settings": "Det gick inte att spara inställningarna", "View Source": "Visa källa", "Thank you!": "Tack!", "Quote": "Citera", "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "Med din nuvarande webbläsare kan appens utseende vara helt fel, och vissa eller alla egenskaper kommer nödvändigtvis inte att fungera. Om du ändå vill försöka så kan du fortsätta, men gör det på egen risk!", - "Checking for an update...": "Letar efter uppdateringar...", + "Checking for an update...": "Letar efter uppdateringar…", "Who can access this room?": "Vilka kan komma åt detta rum?", "Who can read history?": "Vilka kan läsa historik?", "Members only (since the point in time of selecting this option)": "Endast medlemmar (från tidpunkten för när denna inställning valdes)", @@ -426,7 +426,7 @@ "Unnamed Room": "Namnlöst rum", "Your browser does not support the required cryptography extensions": "Din webbläsare stödjer inte nödvändiga kryptografitillägg", "Invite": "Bjud in", - "Unignore": "Ignorera inte", + "Unignore": "Avignorera", "Ignore": "Ignorera", "Jump to message": "Hoppa till meddelande", "Mention": "Nämn", @@ -535,7 +535,7 @@ "Clear filter": "Töm filter", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Försökte ladda en viss punkt i det här rummets tidslinje, men du har inte behörighet att visa det aktuella meddelandet.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Försökte ladda en specifik punkt i det här rummets tidslinje, men kunde inte hitta den.", - "Success": "Slutfört", + "Success": "Framgång", "Unable to remove contact information": "Det gick inte att ta bort kontaktuppgifter", "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Ett epostmeddelande har skickats till %(emailAddress)s. När du har öppnat länken i det, klicka nedan.", "Please note you are logging into the %(hs)s server, not matrix.org.": "Observera att du loggar in på %(hs)s-servern, inte matrix.org.", @@ -1004,26 +1004,26 @@ "Pin": "Häftstift", "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "Vi har skickat ett mail till dig för att verifiera din adress. Följ instruktionerna där och klicka sedan på knappen nedan.", "Email Address": "Epostadress", - "Add an email address to configure email notifications": "Lägg till en epostadress för att konfigurera epostaviseringar", + "Add an email address to configure email notifications": "Lägg till en e-postadress för att konfigurera e-postaviseringar", "Unable to verify phone number.": "Det gick inte att verifiera telefonnumret.", "Verification code": "Verifieringskod", "Phone Number": "Telefonnummer", "Profile picture": "Profilbild", "Display Name": "Visningsnamn", - "Set a new account password...": "Ange ett nytt lösenord för kontot...", - "Email addresses": "Epostadresser", + "Set a new account password...": "Ange ett nytt lösenord för kontot…", + "Email addresses": "E-postadresser", "Phone numbers": "Telefonnummer", "Language and region": "Språk och region", "Theme": "Tema", "Account management": "Kontohantering", "Deactivating your account is a permanent action - be careful!": "Inaktivering av ditt konto är en permanent åtgärd - var försiktig!", "General": "Allmänt", - "Credits": "Tack", + "Credits": "Medverkande", "For help with using %(brand)s, click here.": "För hjälp med att använda %(brand)s, klicka här.", - "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "För hjälp med att använda %(brand)s, klicka här eller starta en chatt med vår bot med knappen nedan.", - "Chat with %(brand)s Bot": "Chatta med %(brand)s Bot", - "Help & About": "Hjälp & Om", - "Bug reporting": "Felrapportering", + "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "För hjälp med att använda %(brand)s, klicka här eller starta en chatt med vår bott med knappen nedan.", + "Chat with %(brand)s Bot": "Chatta med %(brand)s-bott", + "Help & About": "Hjälp och om", + "Bug reporting": "Buggrapportering", "FAQ": "FAQ", "Versions": "Versioner", "Preferences": "Alternativ", @@ -1133,7 +1133,7 @@ "Verify this user by confirming the following emoji appear on their screen.": "Verifiera den här användaren genom att bekräfta att följande emojier visas på deras skärm.", "Verify this user by confirming the following number appears on their screen.": "Verifiera den här användaren genom att bekräfta att följande nummer visas på deras skärm.", "Unable to find a supported verification method.": "Kunde inte hitta en verifieringsmetod som stöds.", - "Delete Backup": "Ta bort säkerhetskopia", + "Delete Backup": "Radera säkerhetskopia", "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Är du säker? Du kommer att förlora dina krypterade meddelanden om dina nycklar inte säkerhetskopieras ordentligt.", "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Krypterade meddelanden är säkrade med totalsträckskryptering. Bara du och mottagaren/na har nycklarna för att läsa dessa meddelanden.", "Ignored users": "Ignorerade användare", @@ -1244,24 +1244,24 @@ "Public Name": "Offentligt namn", "Identity Server URL must be HTTPS": "URL för identitetsserver måste vara HTTPS", "Not a valid Identity Server (status code %(code)s)": "Inte en giltig identitetsserver (statuskod %(code)s)", - "Could not connect to Identity Server": "Det gick inte att ansluta till identitetsserver", - "Checking server": "Kontrollerar server", + "Could not connect to Identity Server": "Det gick inte att ansluta till identitetsservern", + "Checking server": "Kontrollerar servern", "Change identity server": "Byt identitetsserver", - "Disconnect from the identity server and connect to instead?": "Koppla från identitetsservern och ansluta till istället?", + "Disconnect from the identity server and connect to instead?": "Koppla ifrån från identitetsservern och anslut till istället?", "Only continue if you trust the owner of the server.": "Fortsätt endast om du litar på serverns ägare.", - "Disconnect identity server": "Koppla från identitetsserver", - "Disconnect from the identity server ?": "Koppla från identitetsserver ?", - "Disconnect": "Koppla från", - "You are still sharing your personal data on the identity server .": "Du delar fortfarande dina personuppgifter på identitetsserver .", + "Disconnect identity server": "Koppla ifrån identitetsservern", + "Disconnect from the identity server ?": "Koppla ifrån från identitetsservern ?", + "Disconnect": "Koppla ifrån", + "You are still sharing your personal data on the identity server .": "Du delar fortfarande dina personuppgifter på identitetsservern .", "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.": "Vi rekommenderar att du tar bort dina e-postadresser och telefonnummer från identitetsservern innan du kopplar från.", - "Disconnect anyway": "Koppla från ändå", + "Disconnect anyway": "Koppla ifrån ändå", "Identity Server (%(server)s)": "Identitetsserver (%(server)s)", - "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "Du använder för närvarande för att upptäcka och upptäckas av befintliga kontakter som du känner. Du kan ändra din identitetsserver nedan.", + "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "Du använder för närvarande för att upptäcka och upptäckas av befintliga kontakter som du känner. Du kan byta din identitetsserver nedan.", "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.": "Om du inte vill använda för att upptäcka och upptäckas av befintliga kontakter som du känner, ange en annan identitetsserver nedan.", "Identity Server": "Identitetsserver", "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "Du använder för närvarande inte en identitetsserver. Lägg till en nedan om du vill upptäcka och bli upptäckbar av befintliga kontakter som du känner.", - "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Att koppla från din identitetsserver betyder att du inte kan upptäckas av andra användare och att du inte kommer att kunna bjuda in andra via epost eller telefon.", - "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Att använda en identitetsserver är valfritt. Om du väljer att inte använda en identitetsserver kan du inte upptäckas av andra användare och inte heller bjuda in andra via epost eller telefon.", + "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Att koppla ifrån din identitetsserver betyder att du inte kan upptäckas av andra användare och att du inte kommer att kunna bjuda in andra via e-post eller telefon.", + "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Att använda en identitetsserver är valfritt. Om du väljer att inte använda en identitetsserver kan du inte upptäckas av andra användare och inte heller bjuda in andra via e-post eller telefon.", "Do not use an identity server": "Använd inte en identitetsserver", "Enter a new identity server": "Ange en ny identitetsserver", "Integration Manager": "Integrationshanterare", @@ -1326,9 +1326,9 @@ "Unexpected error resolving homeserver configuration": "Oväntat fel vid inläsning av hemserverkonfiguration", "Unexpected error resolving identity server configuration": "Oväntat fel vid inläsning av identitetsserverkonfiguration", "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Tillåt assistansservern turn.matrix.org för samtal som reserv när din hemserver inte erbjuder en (din IP-adress kommer delas under ett samtal)", - "Unable to load key backup status": "Det går inte att ladda status för nyckelsäkerhetskopiering", - "Restore from Backup": "Återställ från säkerhetskopiering", - "Backing up %(sessionsRemaining)s keys...": "Säkerhetskopierar %(sessionsRemaining)s nycklar...", + "Unable to load key backup status": "Kunde inte ladda status för nyckelsäkerhetskopiering", + "Restore from Backup": "Återställ från säkerhetskopia", + "Backing up %(sessionsRemaining)s keys...": "Säkerhetskopierar %(sessionsRemaining)s nycklar…", "All keys backed up": "Alla nycklar säkerhetskopierade", "Add Email Address": "Lägg till e-postadress", "Add Phone Number": "Lägg till telefonnummer", @@ -1351,13 +1351,13 @@ "Match system theme": "Matcha systemtema", "Decline (%(counter)s)": "Avvisa (%(counter)s)", "not found": "hittades inte", - "Connecting to integration manager...": "Ansluter till integrationshanterare...", - "Cannot connect to integration manager": "Det går inte att ansluta till integrationshanterare", + "Connecting to integration manager...": "Ansluter till integrationshanterare…", + "Cannot connect to integration manager": "Kan inte ansluta till integrationshanteraren", "The integration manager is offline or it cannot reach your homeserver.": "Integrationshanteraren är offline eller kan inte nå din hemserver.", "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Använd en integrationshanterare (%(serverName)s) för att hantera bottar, widgets och dekalpaket.", "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Använd en integrationshanterare för att hantera bottar, widgets och dekalpaket.", "Manage integrations": "Hantera integrationer", - "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integrationshanterare får konfigurationsdata och kan ändra widgetar, skicka ruminbjudningar och ställa in behörighetsnivåer via ditt konto.", + "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integrationshanterare får konfigurationsdata och kan ändra widgetar, skicka rumsinbjudningar och ställa in behörighetsnivåer å dina vägnar.", "Close preview": "Stäng förhandsvisning", "Room %(name)s": "Rum %(name)s", "Recent rooms": "Senaste rummen", @@ -1695,5 +1695,139 @@ "Workspace: %(networkName)s": "Arbetsyta: %(networkName)s", "Channel: %(channelName)s": "Kanal: %(channelName)s", "Show less": "Visa mindre", - "Changing password will currently reset any end-to-end encryption keys on all sessions, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Att byta lösenord återställer just nu alla krypteringsnycklar på alla sessioner, vilket gör krypterad chatthistorik oläslig om du inte först exporterar dina rumsnycklar och sedan importerar dem igen efteråt. Detta kommer att förbättras i framtiden." + "Changing password will currently reset any end-to-end encryption keys on all sessions, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Att byta lösenord återställer just nu alla krypteringsnycklar på alla sessioner, vilket gör krypterad chatthistorik oläslig om du inte först exporterar dina rumsnycklar och sedan importerar dem igen efteråt. Detta kommer att förbättras i framtiden.", + "Your homeserver does not support cross-signing.": "Din hemserver stöder inte korssignering.", + "Cross-signing and secret storage are ready for use.": "Korssignering och hemlig lagring är klara att använda.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Korssignering är klar att använda, men hemlig lagring används just nu inte för att säkerhetskopiera dina nycklar.", + "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Ditt konto har en korssigneringsidentitet i hemlig lagring, men den är inte betrodd av den här sessionen än.", + "Cross-signing and secret storage are not yet set up.": "Korssignering och hemlig lagring har inte blivit uppsatta än.", + "Reset cross-signing and secret storage": "Återställ korssignering och hemlig lagring", + "Bootstrap cross-signing and secret storage": "Sätt upp korssignering och hemlig lagring", + "well formed": "välformaterad", + "unexpected type": "oväntad typ", + "Cross-signing public keys:": "Publika nycklar för korssignering:", + "in memory": "i minne", + "Cross-signing private keys:": "Privata nycklar för korssignering:", + "in secret storage": "i hemlig lagring", + "Master private key:": "Privat huvudnyckel:", + "cached locally": "cachad lokalt", + "not found locally": "inte hittad lokalt", + "Self signing private key:": "Privat nyckel för självsignering:", + "User signing private key:": "Privat nyckel för användarsignering:", + "Session backup key:": "Sessionssäkerhetskopieringsnyckel:", + "Secret storage public key:": "Publik nyckel för hemlig lagring:", + "in account data": "i kontodata", + "Homeserver feature support:": "Hemserverns funktionsstöd:", + "exists": "existerar", + "Your homeserver does not support session management.": "Din hemservers stöder inte sessionshantering.", + "Unable to load session list": "Kunde inte ladda sessionslistan", + "Confirm deleting these sessions by using Single Sign On to prove your identity.|other": "Bekräfta radering av dessa sessioner genom att använda single sign-on för att bekräfta din identitet.", + "Confirm deleting these sessions by using Single Sign On to prove your identity.|one": "Bekräfta radering av denna session genom att använda single sign-on för att bekräfta din identitet.", + "Confirm deleting these sessions": "Bekräfta radering av dessa sessioner", + "Click the button below to confirm deleting these sessions.|other": "Klicka på knappen nedan för att bekräfta radering av dessa sessioner.", + "Click the button below to confirm deleting these sessions.|one": "Klicka på knappen nedan för att bekräfta radering av denna session.", + "Delete sessions|other": "Radera sessioner", + "Delete sessions|one": "Radera session", + "Delete %(count)s sessions|other": "Radera %(count)s sessioner", + "Delete %(count)s sessions|one": "Radera %(count)s session", + "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Verifiera individuellt varje session som används av en användare för att markera den som betrodd, och lita inte på korssignerade enheter.", + "Securely cache encrypted messages locally for them to appear in search results, using ": "Cacha krypterade meddelanden säkert lokalt för att de ska visas i sökresultat, med hjälp av ", + " to store messages from ": " för att lagra meddelanden från ", + "rooms.": "rum.", + "Manage": "Hantera", + "Securely cache encrypted messages locally for them to appear in search results.": "Cachar krypterade meddelanden säkert lokalt för att de ska visas i sökresultat.", + "Enable": "Aktivera", + "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with search components added.": "%(brand)s saknar vissa komponenter som krävs som krävs för att säkert cacha krypterade meddelanden lokalt. Om du vill experimentera med den här funktionen, bygg en anpassad %(brand)s Skrivbord med sökkomponenter tillagda.", + "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.": "%(brand)s kan inte säkert cacha krypterade meddelanden lokalt när den kör i en webbläsare. Använd %(brand)s Skrivbord för att krypterade meddelanden ska visas i sökresultaten.", + "This session is backing up your keys. ": "Den här sessionen säkerhetskopierar dina nycklar. ", + "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "Den här servern säkerhetskopierar inte dina nycklar, men du har en existerande säkerhetskopia du kan återställa ifrån och lägga till till i framtiden.", + "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "Anslut den här sessionen till nyckelsäkerhetskopiering innan du loggar ut för att undvika att du blir av med nycklar som kanske bara finns på den här sessionen.", + "Connect this session to Key Backup": "Anslut den här sessionen till nyckelsäkerhetskopiering", + "not stored": "inte lagrad", + "Backup has a valid signature from this user": "Säkerhetskopian har en giltig signatur från den här användaren", + "Backup has a invalid signature from this user": "Säkerhetskopian har en ogiltig signatur från den här användaren", + "Backup has a signature from unknown user with ID %(deviceId)s": "Säkerhetskopian har en signatur från en okänd användare med ID %(deviceId)s", + "Backup has a signature from unknown session with ID %(deviceId)s": "Säkerhetskopian har en signatur från en okänd session med ID %(deviceId)s", + "Backup has a valid signature from this session": "Säkerhetskopian har en giltig signatur från den här sessionen", + "Backup has an invalid signature from this session": "Säkerhetskopian har en ogiltig signatur från den här sessionen", + "Backup has a valid signature from verified session ": "Säkerhetskopian har en giltig signatur från den verifierade sessionen ", + "Backup has a valid signature from unverified session ": "Säkerhetskopian har en giltig signatur från den overifierade sessionen ", + "Backup has an invalid signature from verified session ": "Säkerhetskopian har en ogiltig signatur från den verifierade sessionen ", + "Backup has an invalid signature from unverified session ": "Säkerhetskopian har en ogiltig signatur från den overifierade sessionen ", + "Backup is not signed by any of your sessions": "Säkerhetskopian är inte signerad av någon av dina sessioner", + "This backup is trusted because it has been restored on this session": "Den här säkerhetskopian är betrodd för att den har återställts på den här sessionen", + "Backup version: ": "Säkerhetskopiaversion: ", + "Algorithm: ": "Algoritm: ", + "Backup key stored: ": "Säkerhetskopianyckel lagrad: ", + "Your keys are not being backed up from this session.": "Dina nycklar säkerhetskopieras inte från den här sessionen.", + "Back up your keys before signing out to avoid losing them.": "Säkerhetskopiera dina nycklar innan du loggar ut för att undvika att du blir av med dem.", + "Start using Key Backup": "Börja använda nyckelsäkerhetskopiering", + "Clear notifications": "Rensa aviseringar", + "There are advanced notifications which are not shown here.": "Det finns avancerade aviseringar som inte visas här.", + "You might have configured them in a client other than %(brand)s. You cannot tune them in %(brand)s but they still apply.": "Du kanske har konfigurerat dem i en annan klient än %(brand)s. Du kan inte ändra dem i %(brand)s men de används ändå.", + "Enable desktop notifications for this session": "Aktivera skrivbordsaviseringar för den här sessionen", + "Enable audible notifications for this session": "Aktivera ljudaviseringar för den här sessionen", + "Upgrade to your own domain": "Uppgradera till din egen domän", + "Terms of service not accepted or the identity server is invalid.": "Användarvillkoren accepterades inte eller identitetsservern är inte giltig.", + "The identity server you have chosen does not have any terms of service.": "Identitetsservern du har valt har inga användarvillkor.", + "You should remove your personal data from identity server before disconnecting. Unfortunately, identity server is currently offline or cannot be reached.": "Du bör ta bort din personliga information från identitetsservern innan du kopplar ifrån. Tyvärr är identitetsservern för närvarande offline eller kan inte nås.", + "You should:": "Du bör:", + "check your browser plugins for anything that might block the identity server (such as Privacy Badger)": "Kolla dina webbläsartillägg efter någonting som kanske blockerar identitetsservern (t.ex. Privacy Badger)", + "contact the administrators of identity server ": "kontakta administratören för identitetsservern ", + "wait and try again later": "vänta och försöka igen senare", + "New version available. Update now.": "Ny version tillgänglig. Uppdatera nu.", + "Hey you. You're the best!": "Hallå där. Du är bäst!", + "Size must be a number": "Storleken måste vara ett nummer", + "Custom font size can only be between %(min)s pt and %(max)s pt": "Anpassad teckenstorlek kan bara vara mellan %(min)s pt och %(max)s pt", + "Use between %(min)s pt and %(max)s pt": "Använd mellan %(min)s pt och %(max)s pt", + "Invalid theme schema.": "Ogiltigt temaschema.", + "Error downloading theme information.": "Fel vid nedladdning av temainformation.", + "Theme added!": "Tema tillagt!", + "Custom theme URL": "Anpassad tema-URL", + "Add theme": "Lägg till tema", + "Message layout": "Meddelandearrangemang", + "Compact": "Kompakt", + "Modern": "Modernt", + "Set the name of a font installed on your system & %(brand)s will attempt to use it.": "Sätt namnet för ett teckensnitt installerat på ditt system så kommer %(brand)s att försöka använda det.", + "Customise your appearance": "Anpassa ditt utseende", + "Appearance Settings only affect this %(brand)s session.": "Utseende inställningar påverkar bara den här %(brand)s-sessionen.", + "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them": "Ditt lösenord ändrades framgångsrikt. Du kommer inte motta pushnotiser på andra sessioner till du loggar in på dem igen", + "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Samtyck till identitetsserverns (%(serverName)s) användarvillkor för att låta dig själv vara upptäckbar med e-postadress eller telefonnummer.", + "Clear cache and reload": "Rensa cache och ladda om", + "To report a Matrix-related security issue, please read the Matrix.org Security Disclosure Policy.": "För att rapportera ett Matrix-relaterat säkerhetsproblem, vänligen läs Matrix.orgs riktlinjer för säkerhetspublicering.", + "Keyboard Shortcuts": "Tangentbordsgenvägar", + "Customise your experience with experimental labs features. Learn more.": "Anpassa din upplevelse med experimentella funktioner. Lär dig mer.", + "Ignored/Blocked": "Ignorerade/blockerade", + "Error adding ignored user/server": "Fel vid tilläggning av användare/server", + "Something went wrong. Please try again or view your console for hints.": "Någonting gick fel. Vänligen försök igen eller kolla i din konsol efter ledtrådar.", + "Error subscribing to list": "Fel vid prenumeration på listan", + "Please verify the room ID or address and try again.": "Vänligen verifiera rummets ID eller adress och försök igen.", + "Error removing ignored user/server": "Fel vid borttagning av ignorerad användare/server", + "Error unsubscribing from list": "Fel vid avprenumeration från listan", + "Please try again or view your console for hints.": "Vänligen försök igen eller kolla din konsol efter ledtrådar.", + "None": "Ingen", + "Ban list rules - %(roomName)s": "Bannlistregler - %(roomName)s", + "Server rules": "Serverregler", + "User rules": "Användarregler", + "You have not ignored anyone.": "Du har inte ignorerat någon.", + "You are currently ignoring:": "Du ignorerar just nu:", + "You are not subscribed to any lists": "Du prenumererar inte på några listor", + "Unsubscribe": "Avprenumerera", + "View rules": "Visa regler", + "You are currently subscribed to:": "Du prenumerera just nu på:", + "⚠ These settings are meant for advanced users.": "⚠ Dessa inställningar är till för avancerade användare.", + "Add users and servers you want to ignore here. Use asterisks to have %(brand)s match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Lägg till användare och servrar du vill ignorera här. Använd asterisker för att få %(brand)s att matchar vilka tecken som helt. Till exempel, @bot:* kommer att ignorera alla användare med namnet 'bot' på vilken server som helst.", + "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Ignorering av användare görs genom bannlistor som innehåller regler för vilka som bannas. Att prenumerera på en bannlista betyder att användare/servrar blockerade av den listan kommer att döljas för dig.", + "Personal ban list": "Personlig bannlista", + "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Din personliga bannlista innehåller alla användare/servrar du personligen inte vill se meddelanden ifrån. Efter att du ignorerar din första användare/server så kommer ett nytt rom att dyka upp i din rumslista med namnet 'Min bannlista' - stanna i det här rummet för att hålla bannlistan verksam.", + "Server or user ID to ignore": "Server- eller användar-ID att ignorera", + "eg: @bot:* or example.org": "t.ex.: @bot:* eller example.org", + "Subscribed lists": "Prenumererade listor", + "Subscribing to a ban list will cause you to join it!": "Att prenumerera till en bannlista kommer att få dig att gå med i den!", + "If this isn't what you want, please use a different tool to ignore users.": "Om det här inte är det du vill, använd ett annat verktyg för att ignorera användare.", + "Room ID or address of ban list": "Rums-ID eller adress för bannlista", + "Subscribe": "Prenumerera", + "Show tray icon and minimize window to it on close": "Visa systembricksikonen och minimera fönstret till den vid stängning", + "Read Marker lifetime (ms)": "Läsmarkörens livstid (ms)", + "Read Marker off-screen lifetime (ms)": "Läsmarkörens livstid utanför skärmen (ms)" } From b312eb88e9fcd7bb433f1f8319fb34faca1a1ddc Mon Sep 17 00:00:00 2001 From: purjolini Date: Sat, 29 Aug 2020 13:56:55 +0000 Subject: [PATCH 0028/1014] Translated using Weblate (Swedish) Currently translated at 74.3% (1751 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 16be70f1c8..7406ef8d95 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -1829,5 +1829,6 @@ "Subscribe": "Prenumerera", "Show tray icon and minimize window to it on close": "Visa systembricksikonen och minimera fönstret till den vid stängning", "Read Marker lifetime (ms)": "Läsmarkörens livstid (ms)", - "Read Marker off-screen lifetime (ms)": "Läsmarkörens livstid utanför skärmen (ms)" + "Read Marker off-screen lifetime (ms)": "Läsmarkörens livstid utanför skärmen (ms)", + "Session ID:": " Sessions ID:" } From 437a0b52e4db361c201eb0175606f61b63a09fc2 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Sat, 29 Aug 2020 13:57:14 +0000 Subject: [PATCH 0029/1014] Translated using Weblate (Swedish) Currently translated at 74.3% (1752 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 7406ef8d95..28374ff5b4 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -1830,5 +1830,5 @@ "Show tray icon and minimize window to it on close": "Visa systembricksikonen och minimera fönstret till den vid stängning", "Read Marker lifetime (ms)": "Läsmarkörens livstid (ms)", "Read Marker off-screen lifetime (ms)": "Läsmarkörens livstid utanför skärmen (ms)", - "Session ID:": " Sessions ID:" + "Session ID:": "Sessions-ID:" } From e1f0361bf26619ec0fcabb63131089196b2d12b2 Mon Sep 17 00:00:00 2001 From: purjolini Date: Sat, 29 Aug 2020 13:57:17 +0000 Subject: [PATCH 0030/1014] Translated using Weblate (Swedish) Currently translated at 74.3% (1752 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 28374ff5b4..e145f57aed 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -1830,5 +1830,6 @@ "Show tray icon and minimize window to it on close": "Visa systembricksikonen och minimera fönstret till den vid stängning", "Read Marker lifetime (ms)": "Läsmarkörens livstid (ms)", "Read Marker off-screen lifetime (ms)": "Läsmarkörens livstid utanför skärmen (ms)", - "Session ID:": "Sessions-ID:" + "Session ID:": "Sessions-ID:", + "Session key:": "Sessions nyckel:" } From 1c2e05e925529afe71910fa43cc6257c15f6cd03 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Mon, 31 Aug 2020 14:40:16 -0400 Subject: [PATCH 0031/1014] initial version of device rehydration support --- src/CrossSigningManager.js | 2 +- src/Login.js | 60 +++++++++++++++++++++++++++++++++++++- src/MatrixClientPeg.ts | 28 ++++++++++++++++-- 3 files changed, 85 insertions(+), 5 deletions(-) diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index 676c41d7d7..43d089010c 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -40,7 +40,7 @@ export class AccessCancelledError extends Error { } } -async function confirmToDismiss() { +export async function confirmToDismiss() { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const [sure] = await Modal.createDialog(QuestionDialog, { title: _t("Cancel entering passphrase?"), diff --git a/src/Login.js b/src/Login.js index 04805b4af9..4e46fc3665 100644 --- a/src/Login.js +++ b/src/Login.js @@ -18,7 +18,12 @@ See the License for the specific language governing permissions and limitations under the License. */ +import Modal from './Modal'; +import * as sdk from './index'; +import { AccessCancelledError, confirmToDismiss } from "./CrossSigningManager"; import Matrix from "matrix-js-sdk"; +import { deriveKey } from 'matrix-js-sdk/src/crypto/key_passphrase'; +import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey'; export default class Login { constructor(hsUrl, isUrl, fallbackHsUrl, opts) { @@ -159,12 +164,18 @@ export default class Login { * @returns {MatrixClientCreds} */ export async function sendLoginRequest(hsUrl, isUrl, loginType, loginParams) { + let rehydrationKeyInfo; + let rehydrationKey; + const client = Matrix.createClient({ baseUrl: hsUrl, idBaseUrl: isUrl, + cryptoCallbacks: { + getDehydrationKey + } }); - const data = await client.login(loginType, loginParams); + const data = await client.loginWithRehydration(null, loginType, loginParams); const wellknown = data.well_known; if (wellknown) { @@ -185,5 +196,52 @@ export async function sendLoginRequest(hsUrl, isUrl, loginType, loginParams) { userId: data.user_id, deviceId: data.device_id, accessToken: data.access_token, + rehydrationKeyInfo, + rehydrationKey, + olmAccount: data._olm_account, }; } + +async function getDehydrationKey(keyInfo) { + const inputToKey = async ({ passphrase, recoveryKey }) => { + if (passphrase) { + return deriveKey( + passphrase, + keyInfo.passphrase.salt, + keyInfo.passphrase.iterations, + ); + } else { + return decodeRecoveryKey(recoveryKey); + } + }; + const AccessSecretStorageDialog = + sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog"); + const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "", + AccessSecretStorageDialog, + /* props= */ + { + keyInfo, + checkPrivateKey: async (input) => { + // FIXME: + return true; + }, + }, + /* className= */ null, + /* isPriorityModal= */ false, + /* isStaticModal= */ false, + /* options= */ { + onBeforeClose: async (reason) => { + if (reason === "backgroundClick") { + return confirmToDismiss(); + } + return true; + }, + }, + ); + const [input] = await finished; + if (!input) { + throw new AccessCancelledError(); + } + const key = await inputToKey(input); + return key; +} diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index be16f5fe10..61b7a04069 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -42,6 +42,9 @@ export interface IMatrixClientCreds { accessToken: string; guest: boolean; pickleKey?: string; + rehydrationKey?: Uint8Array; + rehydrationKeyInfo?: {[props: string]: any}; + olmAccount?: any; } // TODO: Move this to the js-sdk @@ -248,12 +251,10 @@ class _MatrixClientPeg implements IMatrixClientPeg { private createClient(creds: IMatrixClientCreds): void { // TODO: Make these opts typesafe with the js-sdk - const opts = { + const opts: any = { baseUrl: creds.homeserverUrl, idBaseUrl: creds.identityServerUrl, accessToken: creds.accessToken, - userId: creds.userId, - deviceId: creds.deviceId, pickleKey: creds.pickleKey, timelineSupport: true, forceTURN: !SettingsStore.getValue('webRtcAllowPeerToPeer'), @@ -268,6 +269,23 @@ class _MatrixClientPeg implements IMatrixClientPeg { cryptoCallbacks: {}, }; + if (creds.olmAccount) { + opts.deviceToImport = { + olmDevice: { + pickledAccount: creds.olmAccount.pickle("DEFAULT_KEY"), + sessions: [], + pickleKey: "DEFAULT_KEY", + }, + userId: creds.userId, + deviceId: creds.deviceId, + }; + } else { + opts.userId = creds.userId; + opts.deviceId = creds.deviceId; + } + + // FIXME: modify crossSigningCallbacks.getSecretStorageKey so that it tries using rehydrationkey and/or saves the passphrase info + // These are always installed regardless of the labs flag so that // cross-signing features can toggle on without reloading and also be // accessed immediately after login. @@ -275,6 +293,10 @@ class _MatrixClientPeg implements IMatrixClientPeg { this.matrixClient = createMatrixClient(opts); + if (creds.rehydrationKey) { + this.matrixClient.cacheDehydrationKey(creds.rehydrationKey, creds.rehydrationKeyInfo || {}); + } + // we're going to add eventlisteners for each matrix event tile, so the // potential number of event listeners is quite high. this.matrixClient.setMaxListeners(500); From 47e834679d277f5359fe214f8c408c8d38d0d6c7 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Sat, 29 Aug 2020 13:57:21 +0000 Subject: [PATCH 0032/1014] Translated using Weblate (Swedish) Currently translated at 86.1% (2030 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 530 ++++++++++++++++++++++++++++----------- 1 file changed, 387 insertions(+), 143 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index e145f57aed..fff7f90b82 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -7,7 +7,7 @@ "No Microphones detected": "Ingen mikrofon hittades", "No Webcams detected": "Ingen webbkamera hittades", "No media permissions": "Inga mediebehörigheter", - "You may need to manually permit %(brand)s to access your microphone/webcam": "Du måste manuellt tillåta %(brand)s att komma åt din mikrofon/kamera", + "You may need to manually permit %(brand)s to access your microphone/webcam": "Du behöver manuellt tillåta %(brand)s att komma åt din mikrofon/kamera", "Default Device": "Standardenhet", "Microphone": "Mikrofon", "Camera": "Kamera", @@ -15,8 +15,8 @@ "Always show message timestamps": "Visa alltid tidsstämplar för meddelanden", "Authentication": "Autentisering", "%(items)s and %(lastItem)s": "%(items)s och %(lastItem)s", - "and %(count)s others...|other": "och %(count)s andra...", - "and %(count)s others...|one": "och en annan...", + "and %(count)s others...|other": "och %(count)s andra…", + "and %(count)s others...|one": "och en annan…", "A new password must be entered.": "Ett nytt lösenord måste anges.", "%(senderName)s answered the call.": "%(senderName)s svarade på samtalet.", "Anyone who knows the room's link, including guests": "Alla som har rummets adress, inklusive gäster", @@ -56,37 +56,37 @@ "Custom level": "Anpassad nivå", "/ddg is not a command": "/ddg är inte ett kommando", "Deactivate Account": "Inaktivera konto", - "Decrypt %(text)s": "Dekryptera %(text)s", + "Decrypt %(text)s": "Avkryptera %(text)s", "Deops user with given id": "Degraderar användaren med givet ID", "Default": "Standard", "Disinvite": "Häv inbjudan", "Displays action": "Visar åtgärd", "Download %(text)s": "Ladda ner %(text)s", "Email": "Epost", - "Email address": "Epostadress", + "Email address": "E-postadress", "Emoji": "Emoji", "%(senderName)s ended the call.": "%(senderName)s avslutade samtalet.", "Error": "Fel", - "Error decrypting attachment": "Det gick inte att dekryptera bilagan", + "Error decrypting attachment": "Fel vid avkryptering av bilagan", "Existing Call": "Existerande samtal", "Export": "Exportera", "Export E2E room keys": "Exportera krypteringsrumsnycklar", - "Failed to ban user": "Det gick inte att banna användaren", + "Failed to ban user": "Misslyckades att banna användaren", "Failed to change password. Is your password correct?": "Misslyckades att byta lösenord. Är lösenordet rätt?", - "Failed to change power level": "Det gick inte att ändra behörighetsnivå", + "Failed to change power level": "Misslyckades att ändra behörighetsnivå", "Failed to forget room %(errCode)s": "Det gick inte att glömma bort rummet %(errCode)s", - "Failed to join room": "Det gick inte att gå med i rummet", - "Failed to kick": "Det gick inte att kicka", + "Failed to join room": "Misslyckades att gå med i rummet", + "Failed to kick": "Misslyckades att kicka", "Failed to leave room": "Det gick inte att lämna rummet", "Failed to load timeline position": "Det gick inte att hämta positionen på tidslinjen", - "Failed to mute user": "Det gick inte att tysta användaren", + "Failed to mute user": "Misslyckades att tysta användaren", "Failed to reject invite": "Det gick inte att avböja inbjudan", "Failed to reject invitation": "Det gick inte att avböja inbjudan", "Failed to send email": "Det gick inte att skicka epost", - "Failed to send request.": "Det gick inte att sända begäran.", + "Failed to send request.": "Misslyckades att sända begäran.", "Failed to set display name": "Misslyckades att ange visningsnamn", - "Failed to unban": "Det gick inte att avbanna", - "Failed to verify email address: make sure you clicked the link in the email": "Det gick inte att bekräfta e-postadressen: set till att du klickade på länken i e-postmeddelandet", + "Failed to unban": "Misslyckades att avbanna", + "Failed to verify email address: make sure you clicked the link in the email": "Misslyckades att bekräfta e-postadressen: set till att du klickade på länken i e-postmeddelandet", "Favourite": "Favorit", "Accept": "Godkänn", "Access Token:": "Åtkomsttoken:", @@ -103,14 +103,14 @@ "Error: Problem communicating with the given homeserver.": "Fel: Det gick inte att kommunicera med den angivna hemservern.", "Failed to fetch avatar URL": "Det gick inte att hämta avatar-URL", "Failed to upload profile picture!": "Misslyckades att ladda upp profilbild!", - "Failure to create room": "Det gick inte att skapa rummet", + "Failure to create room": "Misslyckades att skapa rummet", "Favourites": "Favoriter", "Fill screen": "Fyll skärmen", "Filter room members": "Filtrera rumsmedlemmar", "Forget room": "Glöm bort rum", "For security, this session has been signed out. Please sign in again.": "Av säkerhetsskäl har den här sessionen loggats ut. Vänligen logga in igen.", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s från %(fromPowerLevel)s till %(toPowerLevel)s", - "Guests cannot join this room even if explicitly invited.": "Gäster kan inte gå med i det här rummet fastän de är uttryckligen inbjudna.", + "Guests cannot join this room even if explicitly invited.": "Gäster kan inte gå med i det här rummet även om de är uttryckligen inbjudna.", "Hangup": "Lägg på", "Historical": "Historiska", "Home": "Hem", @@ -124,7 +124,7 @@ "Incoming voice call from %(name)s": "Inkommande röstsamtal från %(name)s", "Incorrect username and/or password.": "Fel användarnamn och/eller lösenord.", "Incorrect verification code": "Fel verifieringskod", - "Invalid Email Address": "Ogiltig epostadress", + "Invalid Email Address": "Ogiltig e-postadress", "Invalid file%(extra)s": "Felaktig fil%(extra)s", "%(senderName)s invited %(targetName)s.": "%(senderName)s bjöd in %(targetName)s.", "Invited": "Inbjuden", @@ -134,7 +134,7 @@ "Join as voice or video.": "Gå med som röst eller video.", "Join Room": "Gå med i rum", "%(targetName)s joined the room.": "%(targetName)s gick med i rummet.", - "Jump to first unread message.": "Hoppa till första olästa meddelande.", + "Jump to first unread message.": "Hoppa till första olästa meddelandet.", "%(senderName)s kicked %(targetName)s.": "%(senderName)s kickade %(targetName)s.", "Kick": "Kicka", "Kicks user with given id": "Kickar användaren med givet ID", @@ -156,7 +156,7 @@ "Name": "Namn", "New passwords don't match": "De nya lösenorden matchar inte", "New passwords must match each other.": "De nya lösenorden måste vara de samma.", - "not specified": "inte specifierad", + "not specified": "inte specificerad", "Notifications": "Aviseringar", "(not supported by this browser)": "(stöds inte av webbläsaren)", "": "", @@ -207,7 +207,7 @@ "Server error": "Serverfel", "Server may be unavailable, overloaded, or search timed out :(": "Servern kan vara otillgänglig, överbelastad, eller så tog sökningen för lång tid :(", "Server may be unavailable, overloaded, or you hit a bug.": "Servern kan vara otillgänglig eller överbelastad, eller så stötte du på en bugg.", - "Server unavailable, overloaded, or something else went wrong.": "Servern är otillgänglig, överbelastad, eller så gick något annat fel.", + "Server unavailable, overloaded, or something else went wrong.": "Servern är otillgänglig eller överbelastad, eller så gick något annat fel.", "Session ID": "Sessions-ID", "%(senderName)s set a profile picture.": "%(senderName)s satte en profilbild.", "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s bytte sitt visningnamn till %(displayName)s.", @@ -229,8 +229,8 @@ "unknown error code": "okänd felkod", "Add a widget": "Lägg till en widget", "Allow": "Tillåt", - "Cannot add any more widgets": "Det går inte att lägga till fler widgets", - "Delete widget": "Ta bort widget", + "Cannot add any more widgets": "Kan inte lägga till fler widgets", + "Delete widget": "Radera widget", "Define the power level of a user": "Definiera behörighetsnivå för en användare", "Edit": "Ändra", "Enable automatic language detection for syntax highlighting": "Aktivera automatisk språkdetektering för syntaxmarkering", @@ -238,7 +238,7 @@ "AM": "FM", "PM": "EM", "Submit": "Lämna in", - "The maximum permitted number of widgets have already been added to this room.": "Den största tillåtna mängden widgetar har redan tillsats till rummet.", + "The maximum permitted number of widgets have already been added to this room.": "Den största tillåtna mängden widgetar har redan tillagts till rummet.", "The phone number entered looks invalid": "Det angivna telefonnumret är ogiltigt", "This email address is already in use": "Den här e-postadressen används redan", "This email address was not found": "Den här e-postadressen finns inte", @@ -246,7 +246,7 @@ "Online": "Online", "Unnamed room": "Namnlöst rum", "World readable": "Alla kan läsa", - "Guests can join": "Gäster kan gå med i rummet", + "Guests can join": "Gäster kan gå med", "No rooms to show": "Inga fler rum att visa", "This phone number is already in use": "Detta telefonnummer används redan", "The version of %(brand)s": "Version av %(brand)s", @@ -299,7 +299,7 @@ "Changelog": "Ändringslogg", "Waiting for response from server": "Väntar på svar från servern", "Leave": "Lämna", - "Uploaded on %(date)s by %(user)s": "%(user)s laddade upp %(date)s", + "Uploaded on %(date)s by %(user)s": "Uppladdad av %(user)s vid %(date)s", "Advanced notification settings": "Avancerade aviseringsinställningar", "Forget": "Glöm bort", "You cannot delete this image. (%(code)s)": "Du kan inte radera den här bilden. (%(code)s)", @@ -387,7 +387,7 @@ "You can now return to your account after signing out, and sign in on other devices.": "Du kan nu återgå till ditt konto efter att ha loggat ut och logga in på andra enheter.", "Enable email notifications": "Aktivera e-postaviseringar", "Download this file": "Ladda ner filen", - "Failed to change settings": "Det gick inte att spara inställningarna", + "Failed to change settings": "Misslyckades att spara inställningarna", "View Source": "Visa källa", "Thank you!": "Tack!", "Quote": "Citera", @@ -410,8 +410,8 @@ "You cannot place VoIP calls in this browser.": "Du kan inte ringa VoIP-samtal i den här webbläsaren.", "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Din e-postadress verkar inte vara kopplad till något Matrix-ID på den här hemservern.", "Restricted": "Begränsad", - "Failed to invite the following users to the %(roomName)s room:": "Det gick inte att bjuda in följande användare till rummet %(roomName)s:", - "Unable to create widget.": "Det gick inte att skapa widgeten.", + "Failed to invite the following users to the %(roomName)s room:": "Misslyckades att bjuda in följande användare till rummet %(roomName)s:", + "Unable to create widget.": "Kunde inte skapa widgeten.", "Ignored user": "Ignorerad användare", "You are now ignoring %(userId)s": "Du ignorerar nu %(userId)s", "Unignored user": "Avignorerad användare", @@ -436,8 +436,8 @@ "Send an encrypted reply…": "Skicka ett krypterat svar…", "Send an encrypted message…": "Skicka ett krypterat meddelande…", "You do not have permission to post to this room": "Du har inte behörighet att posta till detta rum", - "Loading...": "Laddar...", - "%(duration)ss": "%(duration)s", + "Loading...": "Laddar…", + "%(duration)ss": "%(duration)ss", "%(duration)sm": "%(duration)sm", "%(duration)sh": "%(duration)sh", "%(duration)sd": "%(duration)sd", @@ -450,7 +450,7 @@ "(~%(count)s results)|other": "(~%(count)s resultat)", "(~%(count)s results)|one": "(~%(count)s resultat)", "Upload avatar": "Ladda upp avatar", - "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (nivå %(powerLevelNumber)s)", + "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (behörighet %(powerLevelNumber)s)", "Unknown Address": "Okänd adress", "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sgick med %(count)s gånger", @@ -465,17 +465,17 @@ "%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)sgick med och lämnade", "%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)sgick med och lämnade %(count)s gånger", "%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)sgick med och lämnade", - "And %(count)s more...|other": "Och %(count)s till...", + "And %(count)s more...|other": "Och %(count)s till…", "ex. @bob:example.com": "t.ex. @kalle:exempel.com", "Add User": "Lägg till användare", "Matrix ID": "Matrix-ID", - "Matrix Room ID": "Matrix-rums-ID", - "email address": "epostadress", + "Matrix Room ID": "Matrixrums-ID", + "email address": "e-postadress", "Try using one of the following valid address types: %(validTypesList)s.": "Prova att använda någon av följande giltiga adresstyper: %(validTypesList)s.", "You have entered an invalid address.": "Du har angett en ogiltig adress.", - "Preparing to send logs": "Förbereder att skicka loggar", + "Preparing to send logs": "Förbereder sändning av loggar", "Logs sent": "Loggar skickade", - "Failed to send logs: ": "Det gick inte att skicka loggar: ", + "Failed to send logs: ": "Misslyckades att skicka loggar: ", "Submit debug logs": "Skicka felsökningsloggar", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Felsökningsloggar innehåller användningsdata för applikationen inklusive ditt användarnamn, ID:n eller alias för de rum och grupper du har besökt och användarnamn för andra användare. De innehåller inte meddelanden.", "An email has been sent to %(emailAddress)s": "Ett epostmeddelande har skickats till %(emailAddress)s", @@ -490,10 +490,10 @@ "Uploading %(filename)s and %(count)s others|other": "Laddar upp %(filename)s och %(count)s andra", "Uploading %(filename)s and %(count)s others|zero": "Laddar upp %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Laddar upp %(filename)s och %(count)s annan", - "This doesn't appear to be a valid email address": "Det här verkar inte vara en giltig epostadress", + "This doesn't appear to be a valid email address": "Det här verkar inte vara en giltig e-postadress", "Verification Pending": "Avvaktar verifiering", - "Unable to add email address": "Det gick inte att lägga till epostadress", - "Unable to verify email address.": "Det gick inte att verifiera epostadressen.", + "Unable to add email address": "Kunde inte lägga till e-postadress", + "Unable to verify email address.": "Kunde inte verifiera e-postadressen.", "Skip": "Hoppa över", "Username not available": "Användarnamn inte tillgängligt", "Username invalid: %(errMessage)s": "Ogiltigt användarnamn: %(errMessage)s", @@ -536,18 +536,18 @@ "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Försökte ladda en viss punkt i det här rummets tidslinje, men du har inte behörighet att visa det aktuella meddelandet.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Försökte ladda en specifik punkt i det här rummets tidslinje, men kunde inte hitta den.", "Success": "Framgång", - "Unable to remove contact information": "Det gick inte att ta bort kontaktuppgifter", + "Unable to remove contact information": "Kunde inte ta bort kontaktuppgifter", "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Ett epostmeddelande har skickats till %(emailAddress)s. När du har öppnat länken i det, klicka nedan.", "Please note you are logging into the %(hs)s server, not matrix.org.": "Observera att du loggar in på %(hs)s-servern, inte matrix.org.", "This homeserver doesn't offer any login flows which are supported by this client.": "Denna hemserver erbjuder inga inloggningsflöden som stöds av den här klienten.", "Upload new:": "Ladda upp ny:", "Copied!": "Kopierat!", - "Failed to copy": "Det gick inte att kopiera", - "Delete Widget": "Ta bort widget", - "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Widget tas bort för alla användare i rummet. Är du säker på att du vill ta bort den?", + "Failed to copy": "Misslyckades att kopiera", + "Delete Widget": "Radera widget", + "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Att radera en widget tar bort den för alla användare i rummet. Är du säker på att du vill radera den?", "Minimize apps": "Minimera appar", - "Failed to invite the following users to %(groupId)s:": "Det gick inte att bjuda in följande användare till %(groupId)s:", - "Failed to invite users to %(groupId)s": "Det gick inte att bjuda in användare till %(groupId)s", + "Failed to invite the following users to %(groupId)s:": "Misslyckades att bjuda in följande användare till %(groupId)s:", + "Failed to invite users to %(groupId)s": "Misslyckades att bjuda in användare till %(groupId)s", "This room is not public. You will not be able to rejoin without an invite.": "Detta rum är inte offentligt. Du kommer inte kunna gå med igen utan en inbjudan.", "Ignores a user, hiding their messages from you": "Ignorerar en användare och döljer dess meddelanden för dig", "Stops ignoring a user, showing their messages going forward": "Slutar ignorera en användare och visar dess meddelanden framöver", @@ -560,9 +560,9 @@ "File to import": "Fil att importera", "Which officially provided instance you are using, if any": "Vilken officiellt tillhandahållen instans du använder, om någon", "(unknown failure: %(reason)s)": "(okänt fel: %(reason)s)", - "(could not connect media)": "(det gick inte ansluta media)", + "(could not connect media)": "(kunde inte ansluta media)", " (unsupported)": " (stöds ej)", - "Drop file here to upload": "Släpp fil här för att ladda upp", + "Drop file here to upload": "Släpp en fil här för att ladda upp", "Ongoing conference call%(supportedText)s.": "Pågående gruppsamtal%(supportedText)s.", "%(senderName)s sent an image": "%(senderName)s skickade en bild", "%(senderName)s sent a video": "%(senderName)s skickade en video", @@ -574,8 +574,8 @@ "Banned by %(displayName)s": "Bannad av %(displayName)s", "Muted Users": "Dämpade användare", "This room is not accessible by remote Matrix servers": "Detta rum är inte tillgängligt för externa Matrix-servrar", - "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Det gick inte att ladda händelsen som svarades på, antingen finns den inte eller så har du inte behörighet att se den.", - "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Är du säker på att du vill ta bort den här händelsen? Observera att om du tar bort en rumsnamns- eller ämnesändring kan det ångra ändringen.", + "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Kunde inte ladda händelsen som svarades på, antingen så finns den inte eller så har du inte behörighet att se den.", + "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Är du säker på att du vill ta bort (radera) den här händelsen? Observera att om du tar bort en rumsnamns- eller ämnesändring kan det ångra ändringen.", "Send Custom Event": "Skicka anpassad händelse", "You must specify an event type!": "Du måste ange en händelsetyp!", "Event sent!": "Händelse skickad!", @@ -604,8 +604,8 @@ "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "För att fortsätta använda hemservern %(homeserverDomain)s måste du granska och godkänna våra villkor.", "Review terms and conditions": "Granska villkoren", "Old cryptography data detected": "Gammal krypteringsdata upptäckt", - "Unable to capture screen": "Det gick inte att ta skärmdump", - "Failed to add the following rooms to %(groupId)s:": "Det gick inte att lägga till följande rum till %(groupId)s:", + "Unable to capture screen": "Kunde inte ta skärmdump", + "Failed to add the following rooms to %(groupId)s:": "Misslyckades att lägga till följande rum till %(groupId)s:", "Missing roomId.": "Rums-ID saknas.", "This room is not recognised.": "Detta rum känns inte igen.", "Usage": "Användande", @@ -655,10 +655,10 @@ "%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)sbytte namn", "%(oneUser)schanged their name %(count)s times|other": "%(oneUser)sbytte namn %(count)s gånger", "%(oneUser)schanged their name %(count)s times|one": "%(oneUser)sbytte namn", - "%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)sändrade sin avatar %(count)s gånger", - "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)sändrade sin avatar", - "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)sändrade sin avatar %(count)s gånger", - "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)sändrade sin avatar", + "%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)sbytte sin avatar %(count)s gånger", + "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)sbytte sin avatar", + "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)sbytte sin avatar %(count)s gånger", + "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)sbytte sin avatar", "%(items)s and %(count)s others|other": "%(items)s och %(count)s till", "%(items)s and %(count)s others|one": "%(items)s och en till", "collapse": "fäll ihop", @@ -671,20 +671,20 @@ "Show these rooms to non-members on the community page and room list?": "Visa dessa rum för icke-medlemmar på gemenskapssidan och -rumslistan?", "Add rooms to the community": "Lägg till rum i gemenskapen", "Add to community": "Lägg till i gemenskap", - "Failed to invite users to community": "Det gick inte att bjuda in användare till gemenskapen", + "Failed to invite users to community": "Misslyckades att bjuda in användare till gemenskapen", "Mirror local video feed": "Spegla den lokala videoströmmen", "Community Invites": "Community-inbjudningar", "Invalid community ID": "Ogiltigt gemenskaps-ID", "'%(groupId)s' is not a valid community ID": "%(groupId)s är inte ett giltigt gemenskaps-ID", "New community ID (e.g. +foo:%(localDomain)s)": "Nytt gemenskaps-ID (t.ex. +foo:%(localDomain)s)", "Remove from community": "Ta bort från gemenskapen", - "Disinvite this user from community?": "Ta bort användarens inbjudan till gemenskapen?", + "Disinvite this user from community?": "Häv användarens inbjudan till gemenskapen?", "Remove this user from community?": "Ta bort användaren från gemenskapen?", - "Failed to remove user from community": "Det gick inte att ta bort användaren från gemenskapen", + "Failed to remove user from community": "Misslyckades att ta bort användaren från gemenskapen", "Filter community members": "Filtrera gemenskapsmedlemmar", - "Removing a room from the community will also remove it from the community page.": "Om du tar bort ett rum från gemenskapen tas det även bort från gemenskapens sida.", - "Failed to remove room from community": "Det gick inte att ta bort rummet från gemenskapen", - "Only visible to community members": "Endast synlig för gemenskapsmedlemmar", + "Removing a room from the community will also remove it from the community page.": "Om du tar bort ett rum från gemenskapen tas det även bort från gemenskapssidan.", + "Failed to remove room from community": "Misslyckades att ta bort rummet från gemenskapen", + "Only visible to community members": "Endast synligt för gemenskapsmedlemmar", "Filter community rooms": "Filtrera gemenskapsrum", "Community IDs cannot be empty.": "Gemenskaps-ID kan inte vara tomt.", "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Gemenskaps-ID får endast innehålla tecknen a-z, 0-9 och '=_-./'", @@ -703,7 +703,7 @@ "Community Settings": "Gemenskapsinställningar", "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Det kan dröja upp till 30 minuter innan ändringar på gemenskapens namn och avatar blir synliga för andra användare.", "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Dessa rum visas för gemenskapsmedlemmar på gemenskapssidan. Gemenskapsmedlemmar kan gå med i rummen genom att klicka på dem.", - "Add rooms to this community": "Lägg till rum i denna gemenskap", + "Add rooms to this community": "Lägg till rum till denna gemenskap", "%(inviter)s has invited you to join this community": "%(inviter)s har bjudit in dig till denna gemenskap", "Join this community": "Gå med i denna gemenskap", "Leave this community": "Lämna denna gemenskap", @@ -728,26 +728,26 @@ "Long Description (HTML)": "Lång beskrivning (HTML)", "Description": "Beskrivning", "Failed to load %(groupId)s": "Det gick inte att ladda %(groupId)s", - "Failed to withdraw invitation": "Det gick inte att ta bort inbjudan", - "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Är du säker på att du vill ta bort %(roomName)s från %(groupId)s?", - "Failed to remove '%(roomName)s' from %(groupId)s": "Det gick inte att ta bort %(roomName)s från %(groupId)s", + "Failed to withdraw invitation": "Misslyckades att dra tillbaka inbjudan", + "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Är du säker på att du vill ta bort '%(roomName)s' från %(groupId)s?", + "Failed to remove '%(roomName)s' from %(groupId)s": "Misslyckades att ta bort '%(roomName)s' från %(groupId)s", "Something went wrong!": "Något gick fel!", "The visibility of '%(roomName)s' in %(groupId)s could not be updated.": "Synligheten för '%(roomName)s' i %(groupId)s kunde inte uppdateras.", "Visibility in Room List": "Synlighet i rumslistan", - "Visible to everyone": "Synlig för alla", - "Please select the destination room for this message": "Välj vilket rum meddelandet ska skickas till", - "Disinvite this user?": "Ta bort användarens inbjudan?", - "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "Du kommer inte att kunna ångra den här ändringen eftersom du sänker din egen behörighetsnivå, om du är den sista privilegierade användaren i rummet blir det omöjligt att ändra behörigheter.", + "Visible to everyone": "Synligt för alla", + "Please select the destination room for this message": "Välj målrum för detta meddelande", + "Disinvite this user?": "Häv användarens inbjudan?", + "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "Du kommer inte att kunna ångra den här ändringen eftersom du degraderar dig själv. Om du är den sista privilegierade användaren i rummet blir det omöjligt att återfå behörigheter.", "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Du kommer inte att kunna ångra den här ändringen eftersom du höjer användaren till samma behörighetsnivå som dig själv.", "unknown caller": "okänd uppringare", "To use it, just wait for autocomplete results to load and tab through them.": "För att använda detta, vänta på att autokompletteringen laddas och tabba igenom resultatet.", "Enable inline URL previews by default": "Aktivera inbäddad URL-förhandsvisning som standard", "Enable URL previews for this room (only affects you)": "Aktivera URL-förhandsvisning för detta rum (påverkar bara dig)", "Enable URL previews by default for participants in this room": "Aktivera URL-förhandsvisning som standard för deltagare i detta rum", - "You have enabled URL previews by default.": "Du har aktiverat URL-förhandsvisning som standard.", - "You have disabled URL previews by default.": "Du har inaktiverat URL-förhandsvisning som standard.", - "URL previews are enabled by default for participants in this room.": "URL-förhandsvisning är aktiverat som standard för deltagare i detta rum.", - "URL previews are disabled by default for participants in this room.": "URL-förhandsvisning är inaktiverat som standard för deltagare i detta rum.", + "You have enabled URL previews by default.": "Du har aktiverat URL-förhandsvisning som förval.", + "You have disabled URL previews by default.": "Du har inaktiverat URL-förhandsvisning som förval.", + "URL previews are enabled by default for participants in this room.": "URL-förhandsvisning är aktiverat som förval för deltagare i detta rum.", + "URL previews are disabled by default for participants in this room.": "URL-förhandsvisning är inaktiverat som förval för deltagare i detta rum.", "URL Previews": "URL-förhandsvisning", "Which rooms would you like to add to this summary?": "Vilka rum vill du lägga till i översikten?", "Add to summary": "Lägg till i översikt", @@ -766,16 +766,16 @@ "Key request sent.": "Nyckelbegäran skickad.", "Unban": "Avbanna", "Unban this user?": "Avbanna användaren?", - "Unmute": "Ta bort dämpning", + "Unmute": "Avtysta", "You don't currently have any stickerpacks enabled": "Du har för närvarande inga dekalpaket aktiverade", "Stickerpack": "Dekalpaket", "Hide Stickers": "Dölj dekaler", "Show Stickers": "Visa dekaler", - "Error decrypting audio": "Det gick inte att dekryptera ljud", - "Error decrypting image": "Det gick inte att dekryptera bild", - "Error decrypting video": "Det gick inte att dekryptera video", + "Error decrypting audio": "Fel vid avkryptering av ljud", + "Error decrypting image": "Fel vid avkryptering av bild", + "Error decrypting video": "Fel vid avkryptering av video", "Add an Integration": "Lägg till integration", - "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "Du skickas till en tredjepartswebbplats så att du kan autentisera ditt konto för användning med %(integrationsUrl)s. Vill du fortsätta?", + "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "Du kommer att skickas till en tredjepartswebbplats så att du kan autentisera ditt konto för användning med %(integrationsUrl)s. Vill du fortsätta?", "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Om du inte anger en epostadress, kan du inte återställa ditt lösenord. Är du säker?", "Popout widget": "Poppa ut widget", "were unbanned %(count)s times|other": "blev avbannade %(count)s gånger", @@ -827,15 +827,15 @@ "Permission Required": "Behörighet krävs", "You do not have permission to start a conference call in this room": "Du har inte behörighet att starta ett gruppsamtal i detta rum", "This event could not be displayed": "Den här händelsen kunde inte visas", - "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "I krypterade rum, som detta, är URL-förhandsvisning inaktiverad som standard för att säkerställa att din hemserver (där förhandsvisningar genereras) inte kan samla information om länkar du ser i rummet.", + "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "I krypterade rum, som detta, är URL-förhandsvisning inaktiverad som förval för att säkerställa att din hemserver (där förhandsvisningar genereras) inte kan samla information om länkar du ser i rummet.", "The email field must not be blank.": "Epost-fältet får inte vara tomt.", "The phone number field must not be blank.": "Telefonnummer-fältet får inte vara tomt.", "The password field must not be blank.": "Lösenords-fältet får inte vara tomt.", - "Failed to remove widget": "Det gick inte att ta bort widget", - "An error ocurred whilst trying to remove the widget from the room": "Ett fel uppstod vid borttagning av widget från rummet", - "Demote yourself?": "Sänk egen behörighetsnivå?", + "Failed to remove widget": "Misslyckades att radera widget", + "An error ocurred whilst trying to remove the widget from the room": "Ett fel inträffade vid borttagning av widget från rummet", + "Demote yourself?": "Degradera dig själv?", "Demote": "Degradera", - "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "När någon postar en URL i sitt meddelande, kan URL-förhandsvisning ge mer information om länken, såsom titel, beskrivning, och en bild från webbplatsen.", + "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "När någon lägger en URL i sitt meddelande, kan URL-förhandsvisning ge mer information om länken, såsom titel, beskrivning, och en bild från webbplatsen.", "You can't send any messages until you review and agree to our terms and conditions.": "Du kan inte skicka några meddelanden innan du granskar och godkänner våra villkor.", "System Alerts": "Systemvarningar", "Sorry, your homeserver is too old to participate in this room.": "Tyvärr, din hemserver är för gammal för att delta i det här rummet.", @@ -861,14 +861,14 @@ "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Hindra användare från att prata i den gamla rumsversionen och posta ett meddelande som rekommenderar användare att flytta till det nya rummet", "Put a link back to the old room at the start of the new room so people can see old messages": "Sätta en länk tillbaka till det gamla rummet i början av det nya rummet så att folk kan se gamla meddelanden", "Forces the current outbound group session in an encrypted room to be discarded": "Tvingar den aktuella utgående gruppsessionen i ett krypterat rum att överges", - "Unable to connect to Homeserver. Retrying...": "Det gick inte att ansluta till hemservern. Försöker igen…", + "Unable to connect to Homeserver. Retrying...": "Kunde inte ansluta till hemservern. Försöker igen…", "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s satte huvudadressen för detta rum till %(address)s.", "%(senderName)s removed the main address for this room.": "%(senderName)s tog bort huvudadressen för detta rum.", "Add some now": "Lägg till några nu", "Please review and accept the policies of this homeserver:": "Granska och acceptera policyn för denna hemserver:", - "Before submitting logs, you must create a GitHub issue to describe your problem.": "Innan du skickar in loggar måste du skapa en GitHub-bugg för att beskriva problemet.", + "Before submitting logs, you must create a GitHub issue to describe your problem.": "Innan du skickar in loggar måste du skapa ett GitHub-ärende för att beskriva problemet.", "Updating %(brand)s": "Uppdaterar %(brand)s", - "Open Devtools": "Öppna Devtools", + "Open Devtools": "Öppna utvecklingsverktyg", "Show developer tools": "Visa utvecklarverktyg", "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.": "Du är administratör för denna gemenskap. Du kommer inte kunna gå med igen utan en inbjudan från en annan administratör.", "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Filen '%(fileName)s' överstiger denna hemserverns storleksgräns för uppladdningar", @@ -1002,10 +1002,10 @@ "Headphones": "Hörlurar", "Folder": "Mapp", "Pin": "Häftstift", - "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "Vi har skickat ett mail till dig för att verifiera din adress. Följ instruktionerna där och klicka sedan på knappen nedan.", - "Email Address": "Epostadress", + "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "Vi har skickat ett e-brev till dig för att verifiera din adress. Följ instruktionerna där och klicka sedan på knappen nedan.", + "Email Address": "E-postadress", "Add an email address to configure email notifications": "Lägg till en e-postadress för att konfigurera e-postaviseringar", - "Unable to verify phone number.": "Det gick inte att verifiera telefonnumret.", + "Unable to verify phone number.": "Kunde inte verifiera telefonnumret.", "Verification code": "Verifieringskod", "Phone Number": "Telefonnummer", "Profile picture": "Profilbild", @@ -1022,7 +1022,7 @@ "For help with using %(brand)s, click here.": "För hjälp med att använda %(brand)s, klicka här.", "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "För hjälp med att använda %(brand)s, klicka här eller starta en chatt med vår bott med knappen nedan.", "Chat with %(brand)s Bot": "Chatta med %(brand)s-bott", - "Help & About": "Hjälp och om", + "Help & About": "Hjälp & om", "Bug reporting": "Buggrapportering", "FAQ": "FAQ", "Versions": "Versioner", @@ -1030,14 +1030,14 @@ "Timeline": "Tidslinje", "Room list": "Rumslista", "Autocomplete delay (ms)": "Autokompletteringsfördröjning (ms)", - "Voice & Video": "Röst & Video", + "Voice & Video": "Röst & video", "Room information": "Rumsinformation", "Internal room ID:": "Internt rums-ID:", "Room version": "Rumsversion", "Room version:": "Rumsversion:", "Developer options": "Utvecklaralternativ", "Room Addresses": "Rumsadresser", - "That doesn't look like a valid email address": "Det verkar inte vara en giltig epostadress", + "That doesn't look like a valid email address": "Det verkar inte vara en giltig e-postadress", "Next": "Nästa", "Clear status": "Rensa status", "Update status": "Uppdatera status", @@ -1098,18 +1098,18 @@ "Modify widgets": "Ändra widgets", "Default role": "Standardroll", "Send messages": "Skicka meddelanden", - "Invite users": "Bjud in användare", + "Invite users": "Bjuda in användare", "Change settings": "Ändra inställningar", "Kick users": "Kicka användare", "Ban users": "Banna användare", "Remove messages": "Ta bort meddelanden", "Notify everyone": "Meddela alla", "Send %(eventType)s events": "Skicka %(eventType)s-händelser", - "Roles & Permissions": "Roller och behörigheter", + "Roles & Permissions": "Roller & behörigheter", "Enable encryption?": "Aktivera kryptering?", - "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.": "När det är aktiverat kan kryptering för ett rum inte inaktiveras. Meddelanden som skickas i ett krypterat rum kan inte ses av servern, utan endast av deltagarna i rummet. Att aktivera kryptering kan förhindra att många botar och bryggor att fungera korrekt. Läs mer om kryptering.", + "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.": "När det är aktiverat kan kryptering för ett rum inte inaktiveras. Meddelanden som skickas i ett krypterat rum kan inte ses av servern, utan endast av deltagarna i rummet. Att aktivera kryptering kan förhindra att många bottar och bryggor fungerar korrekt. Läs mer om kryptering.", "Encryption": "Kryptering", - "Once enabled, encryption cannot be disabled.": "Efter aktivering kan kryptering inte inaktiveras igen.", + "Once enabled, encryption cannot be disabled.": "Efter aktivering kan kryptering inte inaktiveras.", "Encrypted": "Krypterat", "Not now": "Inte nu", "Don't ask me again": "Fråga mig inte igen", @@ -1137,21 +1137,21 @@ "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Är du säker? Du kommer att förlora dina krypterade meddelanden om dina nycklar inte säkerhetskopieras ordentligt.", "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Krypterade meddelanden är säkrade med totalsträckskryptering. Bara du och mottagaren/na har nycklarna för att läsa dessa meddelanden.", "Ignored users": "Ignorerade användare", - "Bulk options": "Bulkalternativ", + "Bulk options": "Massalternativ", "Accept all %(invitedRooms)s invites": "Acceptera alla %(invitedRooms)s inbjudningar", - "Security & Privacy": "Säkerhet och sekretess", + "Security & Privacy": "Säkerhet & sekretess", "Upgrade this room to the recommended room version": "Uppgradera detta rum till rekommenderad rumsversion", "Select the roles required to change various parts of the room": "Välj de roller som krävs för att ändra olika delar av rummet", "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.": "Ändringar av vem som kan läsa historiken gäller endast för framtida meddelanden i detta rum. Synligheten för befintlig historik kommer att vara oförändrad.", "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Meddelanden i detta rum är säkrade med totalsträckskryptering. Bara du och mottagaren/na har nycklarna för att läsa dessa meddelanden.", - "Failed to revoke invite": "Det gick inte att återkalla inbjudan", - "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Kunde inte återkalla inbjudan. Servern kan ha ett tillfälligt problem eller så har du inte tillräckligt med behörigheter för att återkalla inbjudan.", + "Failed to revoke invite": "Misslyckades att återkalla inbjudan", + "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Kunde inte återkalla inbjudan. Servern kan ha ett tillfälligt problem eller så har du inte tillräckliga behörigheter för att återkalla inbjudan.", "Revoke invite": "Återkalla inbjudan", "Invited by %(sender)s": "Inbjuden av %(sender)s", - "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "Det uppstod ett fel vid uppdatering av rummets huvudadress. Det kanske inte tillåts av servern eller så inträffade ett tillfälligt fel.", + "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "Ett fel inträffade vid uppdatering av rummets huvudadress. Det kanske inte tillåts av servern, eller så inträffade ett tillfälligt fel.", "Main address": "Huvudadress", "Error updating flair": "Fel vid uppdatering av emblem", - "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "Det uppstod ett fel vid uppdatering av emblem för detta rum. Servern kanske inte tillåter det eller ett så inträffade tillfälligt fel.", + "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "Ett fel inträffade vid uppdatering av emblem för detta rum. Servern kanske inte tillåter det, eller ett så inträffade tillfälligt fel.", "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verifiera denna användare för att markera den som betrodd. Att kunna lita på användare ger en extra sinnesfrid när man använder totalsträckskrypterade meddelanden.", "A widget would like to verify your identity": "En widget vill verifiera din identitet", "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.": "En widget på %(widgetUrl)s vill verifiera din identitet. Genom att tillåta detta kommer widgeten att kunna verifiera ditt användar-ID, men inte agera som dig.", @@ -1167,16 +1167,16 @@ "Composer": "Meddelandefält", "Key backup": "Nyckelsäkerhetskopiering", "Never lose encrypted messages": "Förlora aldrig krypterade meddelanden", - "Securely back up your keys to avoid losing them. Learn more.": "Säkerhetskopiera dina nycklar på ett säkert sätt för att undvika att förlora dem. Läs mer.", - "Failed to load group members": "Det gick inte att ladda gruppmedlemmar", + "Securely back up your keys to avoid losing them. Learn more.": "Säkerhetskopiera dina nycklar på ett säkert sätt för att undvika att förlora dem. Lär dig mer.", + "Failed to load group members": "Misslyckades att ladda gruppmedlemmar", "Maximize apps": "Maximera appar", "Join": "Gå med", "Rotate counter-clockwise": "Rotera moturs", "Rotate clockwise": "Rotera medurs", "Power level": "Behörighetsnivå", - "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Det gick inte att hitta profiler för de Matrix-IDn som anges nedan - vill du bjuda in dem ändå?", + "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Kunde inte hitta profiler för de Matrix-ID:n som listas nedan - vill du bjuda in dem ändå?", "GitHub issue": "GitHub-ärende", - "Notes": "Noteringar", + "Notes": "Anteckningar", "You've previously used %(brand)s on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, %(brand)s needs to resync your account.": "Du har tidigare använt %(brand)s på %(host)s med lazy loading av medlemmar aktiverat. I den här versionen är lazy loading inaktiverat. Eftersom den lokala cachen inte är kompatibel mellan dessa två inställningar behöver %(brand)s synkronisera om ditt konto.", "If the other version of %(brand)s is still open in another tab, please close it as using %(brand)s on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Om den andra versionen av %(brand)s fortfarande är öppen i en annan flik, stäng den eftersom användning av %(brand)s på samma värd med både lazy loading aktiverad och inaktiverad samtidigt kommer att orsaka problem.", "Incompatible local cache": "Inkompatibel lokal cache", @@ -1244,7 +1244,7 @@ "Public Name": "Offentligt namn", "Identity Server URL must be HTTPS": "URL för identitetsserver måste vara HTTPS", "Not a valid Identity Server (status code %(code)s)": "Inte en giltig identitetsserver (statuskod %(code)s)", - "Could not connect to Identity Server": "Det gick inte att ansluta till identitetsservern", + "Could not connect to Identity Server": "Kunde inte ansluta till identitetsservern", "Checking server": "Kontrollerar servern", "Change identity server": "Byt identitetsserver", "Disconnect from the identity server and connect to instead?": "Koppla ifrån från identitetsservern och anslut till istället?", @@ -1272,23 +1272,23 @@ "View older messages in %(roomName)s.": "Visa äldre meddelanden i %(roomName)s.", "Uploaded sound": "Uppladdat ljud", "Sounds": "Ljud", - "Notification sound": "Notifikationsljud", + "Notification sound": "Aviseringsljud", "Reset": "Återställ", "Set a new custom sound": "Ställ in ett nytt anpassat ljud", "Upgrade the room": "Uppgradera rummet", "Enable room encryption": "Aktivera rumskryptering", "Revoke": "Återkalla", "Share": "Dela", - "Discovery options will appear once you have added an email above.": "Upptäcktsalternativ visas när du har lagt till en e-postadress ovan.", + "Discovery options will appear once you have added an email above.": "Upptäcktsalternativ kommer att visas när du har lagt till en e-postadress ovan.", "Remove %(email)s?": "Ta bort %(email)s?", "Remove %(phone)s?": "Ta bort %(phone)s?", - "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "Ett textmeddelande har skickats till +%(msisdn)s. Ange verifieringskoden som det innehåller.", + "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "Ett SMS har skickats till +%(msisdn)s. Ange verifieringskoden som det innehåller.", "Edit message": "Redigera meddelande", - "No recent messages by %(user)s found": "Inga nyliga meddelanden av %(user)s hittades", - "Try scrolling up in the timeline to see if there are any earlier ones.": "Pröva att scrolla upp i tidslinjen för att se om det finns några tidigare.", - "Remove recent messages by %(user)s": "Ta bort nyliga meddelanden av %(user)s", - "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "Du håller på att ta bort %(count)s meddelanden av %(user)s. Detta kan inte ångras. Vill du fortsätta?", - "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "För en stor mängd meddelanden kan det ta lite tid. Vänligen uppdatera inte din klient under tiden.", + "No recent messages by %(user)s found": "Inga nyliga meddelanden från %(user)s hittades", + "Try scrolling up in the timeline to see if there are any earlier ones.": "Pröva att skrolla upp i tidslinjen för att se om det finns några tidigare.", + "Remove recent messages by %(user)s": "Ta bort nyliga meddelanden från %(user)s", + "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "Du håller på att ta bort %(count)s meddelanden från %(user)s. Detta kan inte ångras. Vill du fortsätta?", + "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "För en stor mängd meddelanden kan det ta lite tid. Vänligen ladda inte om din klient under tiden.", "Remove %(count)s messages|other": "Ta bort %(count)s meddelanden", "Deactivate user?": "Inaktivera användare?", "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Vid inaktivering av användare loggas den ut och förhindras från att logga in igen. Den kommer dessutom att lämna alla rum den befinner sig i. Den här åtgärden kan inte ångras. Är du säker på att du vill inaktivera den här användaren?", @@ -1298,7 +1298,7 @@ "Italics": "Kursiv", "Strikethrough": "Genomstruken", "Code block": "Kodblock", - "Joining room …": "Gå med i rum …", + "Joining room …": "Går med i rummet …", "Loading …": "Laddar …", "Rejecting invite …": "Avvisar inbjudan …", "Join the conversation with an account": "Gå med i konversationen med ett konto", @@ -1307,8 +1307,8 @@ "Prompt before sending invites to potentially invalid matrix IDs": "Fråga innan inbjudningar skickas till potentiellt ogiltiga Matrix-ID:n", "Show all": "Visa alla", "reacted with %(shortName)s": "reagerade med %(shortName)s", - "Edited at %(date)s. Click to view edits.": "Ändrad %(date)s. Klicka för att visa ändringar.", - "edited": "ändrad", + "Edited at %(date)s. Click to view edits.": "Redigerat vid %(date)s. Klicka för att visa redigeringar.", + "edited": "redigerat", "Sign in to your Matrix account on ": "Logga in med ditt Matrix-konto på ", "Please install Chrome, Firefox, or Safari for the best experience.": "Installera Chrome, Firefox, eller Safari för den bästa upplevelsen.", "Couldn't load page": "Det gick inte att ladda sidan", @@ -1373,13 +1373,13 @@ "You're previewing %(roomName)s. Want to join it?": "Du förhandsgranskar %(roomName)s. Vill du gå med i det?", "%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s kan inte förhandsvisas. Vill du gå med i det?", "This room doesn't exist. Are you sure you're at the right place?": "Detta rum finns inte. Är du säker på att du är på rätt plats?", - "Try again later, or ask a room admin to check if you have access.": "Försök igen senare, eller be en rumsadministratör om att kontrollera om du har åtkomst.", - "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "%(errcode)s returnerades när du försökte komma åt rummet. Om du tror att du ser det här meddelandet felaktigt, vänligen skicka in en felrapport.", - "Failed to connect to integration manager": "Det gick inte att ansluta till integrationshanterare", + "Try again later, or ask a room admin to check if you have access.": "Försök igen senare, eller be en rumsadministratör att kolla om du har åtkomst.", + "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "%(errcode)s returnerades när du försökte komma åt rummet. Om du tror att du ser det här meddelandet felaktigt, vänligen skicka in en buggrapport.", + "Failed to connect to integration manager": "Kunde inte ansluta till integrationshanterare", "React": "Reagera", "Message Actions": "Meddelandeåtgärder", "Show image": "Visa bild", - "You have ignored this user, so their message is hidden. Show anyways.": "Du har ignorerat denna användare, så deras meddelande är dolt. Visa ändå.", + "You have ignored this user, so their message is hidden. Show anyways.": "Du har ignorerat den här användaren, så dess meddelande är dolt. Visa ändå.", "You verified %(name)s": "Du verifierade %(name)s", "You cancelled verifying %(name)s": "Du avbröt verifiering av %(name)s", "%(name)s cancelled verifying": "%(name)s avbröt verifiering", @@ -1391,18 +1391,18 @@ "You sent a verification request": "Du skickade en verifieringsbegäran", "Reactions": "Reaktioner", " reacted with %(content)s": " reagerade med %(content)s", - "Frequently Used": "Ofta använd", - "Smileys & People": "Smileys & Människor", - "Animals & Nature": "Djur & Natur", - "Food & Drink": "Mat & Dryck", + "Frequently Used": "Ofta använda", + "Smileys & People": "Smileys & personer", + "Animals & Nature": "Djur & natur", + "Food & Drink": "Mat & dryck", "Activities": "Aktiviteter", - "Travel & Places": "Resor & Platser", + "Travel & Places": "Resor & platser", "Objects": "Objekt", "Symbols": "Symboler", "Flags": "Flaggor", "Quick Reactions": "Snabbreaktioner", "Cancel search": "Avbryt sökningen", - "Any of the following data may be shared:": "Någon av följande data kan delas:", + "Any of the following data may be shared:": "Vissa av följande data kan delas:", "Your display name": "Ditt visningsnamn", "Your avatar URL": "Din avatar-URL", "Your user ID": "Ditt användar-ID", @@ -1410,13 +1410,13 @@ "%(brand)s URL": "%(brand)s-URL", "Room ID": "Rums-ID", "Widget ID": "Widget-ID", - "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Att använda denna widget kan dela data med %(widgetDomain)s och din Integrationshanterare.", + "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Att använda denna widget kan dela data med %(widgetDomain)s och din integrationshanterare.", "Using this widget may share data with %(widgetDomain)s.": "Att använda denna widget kan dela data med %(widgetDomain)s.", "Widgets do not use message encryption.": "Widgets använder inte meddelandekryptering.", "Widget added by": "Widget tillagd av", - "This widget may use cookies.": "Denna widget kan använda cookies.", + "This widget may use cookies.": "Denna widget kan använda kakor.", "More options": "Fler alternativ", - "Please create a new issue on GitHub so that we can investigate this bug.": "Vänligen skapa ett nytt ärende på GitHub så att vi kan undersöka detta fel.", + "Please create a new issue on GitHub so that we can investigate this bug.": "Vänligen skapa ett nytt ärende på GitHub så att vi kan undersöka denna bugg.", "Rotate Left": "Rotera vänster", "Rotate Right": "Rotera höger", "Language Dropdown": "Språkmeny", @@ -1424,12 +1424,12 @@ "%(severalUsers)smade no changes %(count)s times|one": "%(severalUsers)sgjorde inga ändringar", "%(oneUser)smade no changes %(count)s times|other": "%(oneUser)sgjorde inga ändringar %(count)s gånger", "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)sgjorde inga ändringar", - "e.g. my-room": "t.ex. mitt rum", + "e.g. my-room": "t.ex. mitt-rum", "Some characters not allowed": "Vissa tecken är inte tillåtna", - "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.": "Använd en identitetsserver för att bjuda in via epost. Använd standard (%(defaultIdentityServerName)s) eller hantera i Inställningar.", - "Use an identity server to invite by email. Manage in Settings.": "Använd en identitetsserver för att bjuda in via epost. Hantera i Inställningar.", + "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.": "Använd en identitetsserver för att bjuda in via e-post. Använd förval (%(defaultIdentityServerName)s) eller hantera i inställningarna.", + "Use an identity server to invite by email. Manage in Settings.": "Använd en identitetsserver för att bjuda in via e-post. Hantera i inställningarna.", "Close dialog": "Stäng dialogrutan", - "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Berätta vad som gick fel eller, bättre, skapa ett GitHub-ärende som beskriver problemet.", + "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Berätta vad som gick fel, eller skapa ännu hellre ett GitHub-ärende som beskriver problemet.", "View Servers in Room": "Visa servrar i rum", "Recent Conversations": "Senaste konversationerna", "Suggestions": "Förslag", @@ -1470,22 +1470,22 @@ "Unknown (user, session) pair:": "Okänt (användare, session)-par:", "Session already verified!": "Sessionen är redan verifierad!", "WARNING: Session already verified, but keys do NOT MATCH!": "VARNING: Sessionen har redan verifierats, men nycklarna MATCHAR INTE!", - "Unable to revoke sharing for email address": "Det gick inte att återkalla delning för e-postadress", - "Unable to share email address": "Det gick inte att dela e-postadress", + "Unable to revoke sharing for email address": "Kunde inte återkalla delning för e-postadress", + "Unable to share email address": "Kunde inte dela e-postadress", "Your email address hasn't been verified yet": "Din e-postadress har inte verifierats än", "Click the link in the email you received to verify and then click continue again.": "Klicka på länken i e-postmeddelandet för att bekräfta och klicka sedan på Fortsätt igen.", "Verify the link in your inbox": "Verifiera länken i din inkorg", "Complete": "Färdigställ", - "Unable to revoke sharing for phone number": "Det gick inte att återkalla delning för telefonnummer", - "Unable to share phone number": "Det gick inte att dela telefonnummer", - "Please enter verification code sent via text.": "Ange verifieringskod skickad via textmeddelande.", - "Discovery options will appear once you have added a phone number above.": "Upptäcktsalternativ visas när du har lagt till ett telefonnummer ovan.", + "Unable to revoke sharing for phone number": "Kunde inte återkalla delning för telefonnummer", + "Unable to share phone number": "Kunde inte dela telefonnummer", + "Please enter verification code sent via text.": "Ange verifieringskod skickad via SMS.", + "Discovery options will appear once you have added a phone number above.": "Upptäcktsalternativ kommer att visas när du har lagt till ett telefonnummer ovan.", "Verify session": "Verifiera sessionen", "Session name": "Sessionsnamn", "Session key": "Sessionsnyckel", "Automatically invite users": "Bjud in användare automatiskt", "Upgrade private room": "Uppgradera privat rum", - "Upgrade public room": "Uppgradera publikt rum", + "Upgrade public room": "Uppgradera offentligt rum", "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Att uppgradera ett rum är en avancerad åtgärd och rekommenderas vanligtvis när ett rum är instabilt på grund av buggar, saknade funktioner eller säkerhetsproblem.", "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.": "Detta påverkar vanligtvis bara hur rummet bearbetas på servern. Om du har problem med %(brand)s, rapportera ett fel.", "You'll upgrade this room from to .": "Du kommer att uppgradera detta rum från till .", @@ -1831,5 +1831,249 @@ "Read Marker lifetime (ms)": "Läsmarkörens livstid (ms)", "Read Marker off-screen lifetime (ms)": "Läsmarkörens livstid utanför skärmen (ms)", "Session ID:": "Sessions-ID:", - "Session key:": "Sessions nyckel:" + "Session key:": "Sessionsnyckel:", + "Message search": "Meddelandesök", + "Cross-signing": "Korssignering", + "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Din serveradministratör har inaktiverat totalsträckskryptering som förval för privata rum och direktmeddelanden.", + "Where you’re logged in": "Var du har loggat in", + "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Hantera namn för och logga ut ur dina sessioner nedan eller verifiera dem i din användarprofil.", + "A session's public name is visible to people you communicate with": "En sessions publika namn visas för personer du kommunicerar med", + "This room is bridging messages to the following platforms. Learn more.": "Det här rummet bryggar meddelanden till följande plattformar. Lär dig mer.", + "This room isn’t bridging messages to any platforms. Learn more.": "Det här rummet bryggar inte meddelanden till några plattformar. Lär dig mer.", + "Bridges": "Bryggor", + "Browse": "Bläddra", + "Error changing power level requirement": "Fel vid ändring av behörighetskrav", + "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.": "Ett fel inträffade vid ändring av rummets krav på behörighetsnivå. Försäkra att du har tillräcklig behörighet och försök igen.", + "Error changing power level": "Fel vid ändring av behörighetsnivå", + "An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.": "Ett fel inträffade vid ändring av användarens behörighetsnivå. Försäkra att du har tillräcklig behörighet och försök igen.", + "To link to this room, please add an address.": "För att länka till det här rummet, lägg till en adress.", + "This user has not verified all of their sessions.": "Den här användaren har inte verifierat alla sina sessioner.", + "You have not verified this user.": "Du har inte verifierat den här användaren.", + "You have verified this user. This user has verified all of their sessions.": "Du har verifierat den här användaren. Den här användaren har verifierat alla sina sessioner.", + "Someone is using an unknown session": "Någon använder en okänd session", + "This room is end-to-end encrypted": "Det här rummet är totalsträckskrypterat", + "Everyone in this room is verified": "Alla i det här rummet är verifierade", + "Mod": "Mod", + "Your key share request has been sent - please check your other sessions for key share requests.": "Din nyckeldelningsbegäran har skickats - vänligen kolla dina andra sessioner för nyckeldelningsbegäran.", + "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Nyckeldelningsbegäran skickas till dina andra sessioner automatiskt. Om du avvisade eller avfärdade nyckeldelningsbegäran på dina andra sessioner, klicka här för att begära nycklarna för den här sessionen igen.", + "If your other sessions do not have the key for this message you will not be able to decrypt them.": "Om dina andra sessioner inte har nyckeln för det här meddelandet så kommer du inte kunna avkryptera dem.", + "Re-request encryption keys from your other sessions.": "Återförfråga krypteringsnycklar från dina andra sessioner.", + "This message cannot be decrypted": "Det här meddelandet kan inte avkrypteras", + "Encrypted by an unverified session": "Krypterat av en overifierad session", + "Unencrypted": "Okrypterat", + "Encrypted by a deleted session": "Krypterat av en raderad session", + "The authenticity of this encrypted message can't be guaranteed on this device.": "Det krypterade meddelandets äkthet kan inte garanteras på den här enheten.", + "Scroll to most recent messages": "Skrolla till de senaste meddelandena", + "Emoji picker": "Emojiväljare", + "Send a reply…": "Skicka ett svar…", + "Send a message…": "Skicka ett meddelande…", + "No recently visited rooms": "Inga nyligen besökta rum", + "People": "Personer", + "Add room": "Lägg till rum", + "Explore community rooms": "Utforska gemenskapens rum", + "Explore public rooms": "Utforska offentliga rum", + "Custom Tag": "Anpassad etikett", + "Can't see what you’re looking for?": "Kan du inte se det du letar efter?", + "Explore all public rooms": "Utforska alla offentliga rum", + "%(count)s results|other": "%(count)s resultat", + "You were kicked from %(roomName)s by %(memberName)s": "Du blev kickad från %(roomName)s av %(memberName)s", + "Reason: %(reason)s": "Anledning: %(reason)s", + "Forget this room": "Glöm det här rummet", + "You were banned from %(roomName)s by %(memberName)s": "Du blev bannad från %(roomName)s av %(memberName)s", + "Something went wrong with your invite to %(roomName)s": "Någonting gick fel med din inbjudan till %(roomName)s", + "An error (%(errcode)s) was returned while trying to validate your invite. You could try to pass this information on to a room admin.": "Ett fel (%(errcode)s) returnerades vid försöket att validera din inbjudan. Du kan försöka att ge den här informationen till rumsadministratören.", + "You can only join it with a working invite.": "Du kan bara gå med i det med en fungerande inbjudan.", + "You can still join it because this is a public room.": "Du kan fortfarande gå med eftersom det här är ett offentligt rum.", + "This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "Denna inbjudan till %(roomName)s skickades till %(email)s vilken inte är associerad med det här kontot", + "Link this email with your account in Settings to receive invites directly in %(brand)s.": "Länka den här e-postadressen med ditt konto in inställningarna för att motta inbjudningar direkt i %(brand)s.", + "This invite to %(roomName)s was sent to %(email)s": "Denna inbjudan till %(roomName)s skickades till %(email)s", + "Use an identity server in Settings to receive invites directly in %(brand)s.": "Använd en identitetsserver i inställningarna för att motta inbjudningar direkt i %(brand)s.", + "Share this email in Settings to receive invites directly in %(brand)s.": "Dela denna e-postadress i inställningarna för att motta inbjudningar direkt i %(brand)s.", + "Reject & Ignore user": "Avvisa och ignorera användare", + "Appearance": "Utseende", + "Show rooms with unread messages first": "Visa rum med olästa meddelanden först", + "Show previews of messages": "Visa förhandsvisningar av meddelanden", + "Sort by": "Sortera efter", + "Activity": "Aktivitet", + "A-Z": "A-Ö", + "List options": "Lista alternativ", + "Jump to first unread room.": "Hoppa till första olästa rum.", + "Jump to first invite.": "Hoppa till första inbjudan.", + "Show %(count)s more|other": "Visa %(count)s till", + "Show %(count)s more|one": "Visa %(count)s till", + "Use default": "Använd förval", + "Mentions & Keywords": "Benämningar & nyckelord", + "Notification options": "Aviseringsinställningar", + "Forget Room": "Glöm rum", + "Favourited": "Favoritmarkerade", + "Leave Room": "Lämna rum", + "Room options": "Rumsinställningar", + "%(count)s unread messages including mentions.|other": "%(count)s olästa meddelanden inklusive omnämnanden.", + "%(count)s unread messages including mentions.|one": "1 oläst omnämnande.", + "%(count)s unread messages.|other": "%(count)s olästa meddelanden.", + "%(count)s unread messages.|one": "1 oläst meddelande.", + "Unread messages.": "Olästa meddelanden.", + "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Att uppgradera det här rummet kommer att stänga den nuvarande instansen av rummet och skapa ett uppgraderat rum med samma namn.", + "This room has already been upgraded.": "Det här rummet har redan uppgraderats.", + "This room is running room version , which this homeserver has marked as unstable.": "Det här rummet kör rumsversion , vilket den här hemservern har markerat som instabil.", + "Unknown Command": "Okänt kommando", + "Unrecognised command: %(commandText)s": "Okänt kommando: %(commandText)s", + "You can use /help to list available commands. Did you mean to send this as a message?": "Du kan använda /help för att lista tillgängliga kommandon. Menade du att skicka detta som ett meddelande?", + "Hint: Begin your message with // to start it with a slash.": "Tips: Börja ditt meddelande med // för att starta det med ett snedstreck.", + "Send as message": "Skicka som meddelande", + "Mark all as read": "Markera alla som lästa", + "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "Ett fel inträffade vid uppdatering av rummets alternativa adresser. Det kanske inte tillåts av servern, eller så inträffade ett tillfälligt fel.", + "Error creating address": "Fel vid skapande av adress", + "There was an error creating that address. It may not be allowed by the server or a temporary failure occurred.": "Ett fel inträffade vid skapande av adressen. Det kanske inte tillåts av servern, eller så inträffade ett tillfälligt fel.", + "You don't have permission to delete the address.": "Du har inte behörighet att radera den där adressen.", + "There was an error removing that address. It may no longer exist or a temporary error occurred.": "Ett fel inträffade vid borttagning av adressen. Den kanske inte längre existerar, eller så inträffade ett tillfälligt fel.", + "Error removing address": "Fel vi borttagning av adress", + "Local address": "Lokal adress", + "Published Addresses": "Publicerade adresser", + "Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first.": "Publicerade adresser kan användas av vem som helst på vilken server som helst för att gå med i ditt rum. För att publicera en adress måste den ställas in som en lokal adress först.", + "Other published addresses:": "Andra publicerade adresser:", + "No other published addresses yet, add one below": "Inga andra publicerade adresser än, lägg till en nedan", + "New published address (e.g. #alias:server)": "Ny publicerad adress (t.ex. #alias:server)", + "Local Addresses": "Lokala adresser", + "Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)": "Ange adresser för det här rummet så att användare kan hitta det här rummet via din hemserver (%(localDomain)s)", + "Waiting for you to accept on your other session…": "Väntar på att du ska acceptera din andra session…", + "Waiting for %(displayName)s to accept…": "Väntar på att %(displayName)s ska acceptera…", + "Accepting…": "Accepterar…", + "Start Verification": "Starta verifiering", + "Messages in this room are end-to-end encrypted.": "Meddelanden i det här rummet är totalsträckskrypterade.", + "Your messages are secured and only you and the recipient have the unique keys to unlock them.": "Dina meddelanden är säkrade och endast du och mottagaren har de unika nycklarna för att låsa upp dem.", + "Messages in this room are not end-to-end encrypted.": "Meddelanden i detta rum är inte totalsträckskrypterade.", + "In encrypted rooms, your messages are secured and only you and the recipient have the unique keys to unlock them.": "I krypterade rum är dina meddelanden säkrade och endast du och mottagaren har de unika nycklarna för att låsa upp dem.", + "Verify User": "Verifiera användare", + "For extra security, verify this user by checking a one-time code on both of your devices.": "För extra säkerhet, verifiera den här användaren genom att kolla en engångskod på båda era enheter.", + "Your messages are not secure": "Dina meddelanden är inte säkra", + "One of the following may be compromised:": "Någon av följande kan vara äventyrad:", + "Your homeserver": "Din hemserver", + "The homeserver the user you’re verifying is connected to": "Hemservern som användaren du verifierar är ansluten till", + "Yours, or the other users’ internet connection": "Din eller den andra användarens internetanslutning", + "Yours, or the other users’ session": "Din eller den andra användarens session", + "Trusted": "Betrodd", + "Not trusted": "Inte betrodd", + "%(count)s verified sessions|other": "%(count)s verifierade sessioner", + "%(count)s verified sessions|one": "1 verifierad session", + "Hide verified sessions": "Dölj verifierade sessioner", + "%(count)s sessions|other": "%(count)s sessioner", + "%(count)s sessions|one": "%(count)s session", + "Hide sessions": "Dölj sessioner", + "Direct message": "Direktmeddelande", + "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "Du håller på att ta bort 1 meddelande från %(user)s. Detta kan inte ångras. Vill du fortsätta?", + "Remove %(count)s messages|one": "Ta bort 1 meddelande", + "%(role)s in %(roomName)s": "%(role)s i %(roomName)s", + "Failed to deactivate user": "Misslyckades att inaktivera användaren", + "This client does not support end-to-end encryption.": "Den här klienten stöder inte totalsträckskryptering.", + "Security": "Säkerhet", + "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.": "Sessionen du försöker verifiera stöder inte skanning av en QR-kod eller emoji-verifiering, vilket är vad %(brand)s stöder. Försök med en annan klient.", + "Verify by scanning": "Verifiera med skanning", + "Ask %(displayName)s to scan your code:": "Be %(displayName)s att skanna din kod:", + "If you can't scan the code above, verify by comparing unique emoji.": "Om du inte kan skanna koden ovan, verifiera genom att jämföra unika emojier.", + "Verify by comparing unique emoji.": "Verifiera genom att jämföra unika emojier.", + "Verify by emoji": "Verifiera med emoji", + "Almost there! Is your other session showing the same shield?": "Nästan klar! Visar din andra session samma sköld?", + "Almost there! Is %(displayName)s showing the same shield?": "Nästan klar! Visar %(displayName)s samma sköld?", + "Verify all users in a room to ensure it's secure.": "Verifiera alla användare i ett rum för att försäkra att det är säkert.", + "In encrypted rooms, verify all users to ensure it’s secure.": "I krypterade rum, verifiera alla användare för att försäkra att det är säkert.", + "You've successfully verified your device!": "Du har verifierat din enhet framgångsrikt!", + "You've successfully verified %(deviceName)s (%(deviceId)s)!": "Du har verifierat %(deviceName)s (%(deviceId)s) framgångsrikt!", + "You've successfully verified %(displayName)s!": "Du har verifierat %(displayName)s framgångsrikt!", + "Verified": "Verifierad", + "Got it": "Uppfattat", + "Start verification again from the notification.": "Starta verifiering igen från aviseringen.", + "Start verification again from their profile.": "Starta verifiering igen från deras profil.", + "Verification timed out.": "Verifieringen löpte ut.", + "You cancelled verification on your other session.": "Du avbröt verifieringen på din andra session.", + "%(displayName)s cancelled verification.": "%(displayName)s avbröt verifiering.", + "You cancelled verification.": "Du avbröt verifiering.", + "Verification cancelled": "Verifiering avbruten", + "Compare emoji": "Jämför emoji", + "Encryption enabled": "Kryptering aktiverad", + "Messages in this room are end-to-end encrypted. Learn more & verify this user in their user profile.": "Meddelanden i det här rummet är totalsträckskrypterade. Lär dig mer och verifiera den här användaren i deras användarprofil.", + "Encryption not enabled": "Kryptering är inte aktiverad", + "The encryption used by this room isn't supported.": "Krypteringen som används i det här rummet stöds inte.", + "You declined": "Du avslog", + "%(name)s declined": "%(name)s avslog", + "Accepting …": "Accepterar …", + "Declining …": "Avslår …", + "Message deleted": "Meddelande raderat", + "Message deleted by %(name)s": "Meddelande raderat av %(name)s", + "Message deleted on %(date)s": "Meddelande raderat vid %(date)s", + "Edited at %(date)s": "Redigerat vid %(date)s", + "Click to view edits": "Klicka för att visa redigeringar", + "Can't load this message": "Kan inte ladda det här meddelandet", + "Submit logs": "Skicka loggar", + "Categories": "Kategorier", + "Information": "Information", + "QR Code": "QR-kod", + "Room address": "Rumsadress", + "Please provide a room address": "Vänligen välj en rumsadress", + "This address is available to use": "Adressen är tillgänglig", + "This address is already in use": "Adressen är upptagen", + "Sign in with single sign-on": "Logga in med single sign-on", + "Enter a server name": "Ange ett servernamn", + "Looks good": "Ser bra ut", + "Can't find this server or its room list": "Kan inte hitta den här servern eller dess rumslista", + "All rooms": "Alla rum", + "Your server": "Din server", + "Are you sure you want to remove %(serverName)s": "Är du säker på att du vill ta bort %(serverName)s", + "Remove server": "Ta bort server", + "Matrix": "Matrix", + "Add a new server": "Lägg till en ny server", + "Enter the name of a new server you want to explore.": "Ange namnet för en ny server du vill utforska.", + "Server name": "Servernamn", + "Add a new server...": "Lägg till en ny server…", + "%(networkName)s rooms": "%(networkName)s-rum", + "Matrix rooms": "Matrix-rum", + "Preparing to download logs": "Förbereder nedladdning av loggar", + "Reminder: Your browser is unsupported, so your experience may be unpredictable.": "Påminnelse: Din webbläsare stöds inte, så din upplevelse kan vara oförutsägbar.", + "Download logs": "Ladda ner loggar", + "If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.": "Om det finns ytterligare sammanhang som kan hjälpa för att analysera problemet, till exempel vad du gjorde vid den tiden, rums-ID:n, användar-ID:n o.s.v., vänligen inkludera dessa saker här.", + "Unable to load commit detail: %(msg)s": "Kunde inte ladda commit-detalj: %(msg)s", + "Add another email": "Lägg till en till e-postadress", + "People you know on %(brand)s": "Personer du känner på %(brand)s", + "Show": "Visa", + "Send %(count)s invites|other": "Skicka %(count)s inbjudningar", + "Send %(count)s invites|one": "Skicka %(count)s inbjudan", + "Invite people to join %(communityName)s": "Bjud in folk att gå med i %(communityName)s", + "Removing…": "Tar bort…", + "Destroy cross-signing keys?": "Förstöra korssigneringsnycklar?", + "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.": "Radering av korssigneringsnycklar är permanent. Alla du har verifierat med kommer att se säkerhetsvarningar. Du vill troligen inte göra detta, såvida du inte har tappat bort alla enheter du kan korssignera från.", + "Clear cross-signing keys": "Rensa korssigneringsnycklar", + "Clear all data in this session?": "Rensa all data i den här sessionen?", + "Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Rensning av all data från den här sessionen är permanent. Krypterade meddelande kommer att förloras om inte deras nycklar har säkerhetskopierats.", + "Clear all data": "Rensa all data", + "There was an error creating your community. The name may be taken or the server is unable to process your request.": "Ett fel inträffade vid skapande av din gemenskap. Namnet kan vara upptaget eller så kan servern inte hantera din begäran.", + "Community ID: +:%(domain)s": "Gemenskaps-ID: +:%(domain)s", + "Use this when referencing your community to others. The community ID cannot be changed.": "Använd detta när du hänvisar andra till din gemenskap. Gemenskaps-ID:t kan inte ändras.", + "You can change this later if needed.": "Du kan ändra detta senare om det behövs.", + "What's the name of your community or team?": "Vad är namnet på din gemenskap eller ditt lag?", + "Enter name": "Ange namn", + "Add image (optional)": "Lägg till bild (valfritt)", + "An image will help people identify your community.": "En bild hjälper folk att identifiera din gemenskap.", + "Please enter a name for the room": "Vänligen ange ett namn för rummet", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Privata rum kan endast hittas och gås med i med inbjudan. Offentliga rum kan hittas och gås med i av vem som helst.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Privata rum kan endast hittas och gås med i med inbjudan. Offentliga rum kan hittas och gås med i av vem som helst i den här gemenskapen.", + "You can’t disable this later. Bridges & most bots won’t work yet.": "Du kan inte inaktivera det här senare. Bryggor och bottar kommer inte fungera än.", + "Enable end-to-end encryption": "Aktivera totalsträckskryptering", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Du kanske vill aktivera detta om rummet endast kommer att användas för samarbete med interna lag på din hemserver. Detta kan inte ändras senare.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Du kanske vill inaktivera detta om rummet kommer att användas för samarbete med externa lag som har sin egen hemserver. Detta kan inte ändras senare.", + "Create a public room": "Skapa ett offentligt rum", + "Create a private room": "Skapa ett privat rum", + "Create a room in %(communityName)s": "Skapa ett rum i %(communityName)s", + "Topic (optional)": "Ämne (valfritt)", + "Make this room public": "Gör det här rummet offentligt", + "Hide advanced": "Dölj avancerat", + "Show advanced": "Visa avancerat", + "Block anyone not part of %(serverName)s from ever joining this room.": "Blockera alla som inte är medlem i %(serverName)s från att någonsin gå med i det här rummet.", + "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this": "För att undvika att förlora din chatthistorik måste du exportera dina rumsnycklar innan du loggar ut. Du behöver gå tillbaka till den nyare versionen av %(brand)s för att göra detta", + "You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "Du har tidigare använt en nyare version av %(brand)s med den här sessionen. Om du vill använda den här versionen igen med totalsträckskryptering behöver du logga ut och logga in igen.", + "Incompatible Database": "Inkompatibel databas", + "Continue With Encryption Disabled": "Fortsätt med kryptering inaktiverad", + "Confirm your account deactivation by using Single Sign On to prove your identity.": "Bekräfta din kontoinaktivering genom att använda single sign-on för att bevisa din identitet.", + "Are you sure you want to deactivate your account? This is irreversible.": "Är du säker på att du vill inaktivera ditt konto? Detta är oåterkalleligt.", + "Confirm account deactivation": "Bekräfta kontoinaktivering", + "Security & privacy": "Säkerhet & sekretess" } From 2219a6db9b787a059ba55740d10a422eb25f1e06 Mon Sep 17 00:00:00 2001 From: purjolini Date: Mon, 31 Aug 2020 19:22:30 +0000 Subject: [PATCH 0033/1014] Translated using Weblate (Swedish) Currently translated at 86.1% (2030 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index fff7f90b82..b45a6e5a32 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -860,7 +860,7 @@ "Update any local room aliases to point to the new room": "Uppdatera lokala rumsalias att peka på det nya rummet", "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Hindra användare från att prata i den gamla rumsversionen och posta ett meddelande som rekommenderar användare att flytta till det nya rummet", "Put a link back to the old room at the start of the new room so people can see old messages": "Sätta en länk tillbaka till det gamla rummet i början av det nya rummet så att folk kan se gamla meddelanden", - "Forces the current outbound group session in an encrypted room to be discarded": "Tvingar den aktuella utgående gruppsessionen i ett krypterat rum att överges", + "Forces the current outbound group session in an encrypted room to be discarded": "Tvingar den aktuella externa gruppsessionen i ett krypterat rum att överges", "Unable to connect to Homeserver. Retrying...": "Kunde inte ansluta till hemservern. Försöker igen…", "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s satte huvudadressen för detta rum till %(address)s.", "%(senderName)s removed the main address for this room.": "%(senderName)s tog bort huvudadressen för detta rum.", From 66ea469b2ccfc6939d55eb809425d854612d26a0 Mon Sep 17 00:00:00 2001 From: Yes Date: Mon, 31 Aug 2020 19:23:49 +0000 Subject: [PATCH 0034/1014] Translated using Weblate (Swedish) Currently translated at 86.1% (2030 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index b45a6e5a32..df82de8347 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -589,7 +589,7 @@ "Incorrect password": "Felaktigt lösenord", "State Key": "Lägesnyckel", "Send Account Data": "Skicka kontodata", - "Explore Account Data": "Utforska kontodata", + "Explore Account Data": "Utforska Konto Data", "Toolbox": "Verktygslåda", "Developer Tools": "Utvecklarverktyg", "Clear Storage and Sign Out": "Rensa lagring och logga ut", @@ -1436,7 +1436,7 @@ "Show more": "Visa mer", "Direct Messages": "Direktmeddelanden", "Go": "Gå", - "Waiting for partner to confirm...": "Väntar på att kompanjon ska bekräfta...", + "Waiting for partner to confirm...": "Väntar på att partnern ska bekräfta...", "Incoming Verification Request": "Inkommande verifieringsbegäran", "Integrations are disabled": "Integrationer är inaktiverade", "Enable 'Manage Integrations' in Settings to do this.": "Aktivera \"Hantera integrationer\" i Inställningar för att göra detta.", @@ -2075,5 +2075,9 @@ "Confirm your account deactivation by using Single Sign On to prove your identity.": "Bekräfta din kontoinaktivering genom att använda single sign-on för att bevisa din identitet.", "Are you sure you want to deactivate your account? This is irreversible.": "Är du säker på att du vill inaktivera ditt konto? Detta är oåterkalleligt.", "Confirm account deactivation": "Bekräfta kontoinaktivering", - "Security & privacy": "Säkerhet & sekretess" + "Security & privacy": "Säkerhet & sekretess", + "There was a problem communicating with the server. Please try again.": "Det var ett problem med att kommunicera med servern. Snälla försök igen.", + "Server did not require any authentication": "Servern behövde inte någon auktorisering", + "Verification Requests": "Verifikations Förfråga", + "Confirm to continue": "Konfirmera genom att fortsätta" } From 369287dcaf7d5b2f4f613dac8b7fea5fb5757171 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Sat, 29 Aug 2020 03:50:03 +0000 Subject: [PATCH 0035/1014] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2358 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 27eb560442..ad4020b845 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2445,5 +2445,12 @@ "An image will help people identify your community.": "圖片可以協助人們辨識您的社群。", "Create community": "建立社群", "Explore community rooms": "探索社群聊天室", - "Create a room in %(communityName)s": "在 %(communityName)s 中建立聊天室" + "Create a room in %(communityName)s": "在 %(communityName)s 中建立聊天室", + "Cross-signing and secret storage are ready for use.": "交叉簽章與秘密儲存空間已可使用。", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "交叉簽章已準備好使用,但目前未使用秘密儲存空間備份您的金鑰。", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "私人聊天室僅能透過邀請找到與加入。公開聊天室則任何人都可以找到並加入。", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "私人聊天室僅能透過邀請找到與加入。公開聊天室則任何在此社群的人都可以找到並加入。", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "如果聊天室僅用於與在您的家伺服器上的內部團隊協作的話,可以啟用此功能。這無法在稍後變更。", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "如果聊天室會用於與有自己的家伺服器的外部團隊協作的話,可以停用此功能。這無法在稍後變更。", + "Block anyone not part of %(serverName)s from ever joining this room.": "阻止任何不屬於 %(serverName)s 的人加入此聊天室。" } From 053510ea9c1c61de4743ed4b656fb0bfadae17a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Fri, 28 Aug 2020 20:07:57 +0000 Subject: [PATCH 0036/1014] Translated using Weblate (Estonian) Currently translated at 100.0% (2358 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index f13c28ca3f..a2b0bb2339 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2444,5 +2444,10 @@ "Explore community rooms": "Sirvi kogukonna jututubasid", "Create a room in %(communityName)s": "Loo uus jututuba %(communityName)s kogukonda", "Cross-signing and secret storage are ready for use.": "Risttunnustamine ja turvahoidla on kasutamiseks valmis.", - "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Risttunnustamine on kasutamiseks valmis, kuid turvahoidla ei ole hetkel krüptovõtmete varundamiseks kasutusel." + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Risttunnustamine on kasutamiseks valmis, kuid turvahoidla ei ole hetkel krüptovõtmete varundamiseks kasutusel.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Omavahelisi jututubasid on võimalik leida ning nendega liituda vaid kutse alusel. Avalikke jututubasid saavad kõik leida ning nendega liituda.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Omavahelisi jututubasid on võimalik leida ning nendega liituda vaid kutse alusel. Selles kogukonnas saavad avalikke jututubasid kõik leida ning nendega liituda.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Sa võid sellise võimaluse kasutusele võtta, kui seda jututuba kasutatakse vaid organisatsioonisiseste tiimide ühistööks oma koduserveri piires. Seda ei saa hiljem muuta.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Sa võid sellise võimaluse jätta kasutusele võtmata, kui seda jututuba kasutatakse erinevate väliste tiimide ühistööks kasutades erinevaid koduservereid. Seda ei saa hiljem muuta.", + "Block anyone not part of %(serverName)s from ever joining this room.": "Keela kõikide niisuguste kasutajate liitumine selle jututoaga, kelle kasutajakonto ei asu %(serverName)s koduserveris." } From 5533d2cc2353585804f471ab52c37a6d869aa13c Mon Sep 17 00:00:00 2001 From: Boo Teille Date: Mon, 31 Aug 2020 14:03:09 +0000 Subject: [PATCH 0037/1014] Translated using Weblate (French) Currently translated at 97.7% (2303 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 42f9d74502..42eead2057 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2377,5 +2377,23 @@ "%(brand)s Web": "%(brand)s Web", "%(brand)s Desktop": "%(brand)s Desktop", "%(brand)s iOS": "%(brand)s iOS", - "%(brand)s X for Android": "%(brand)s X pour Android" + "%(brand)s X for Android": "%(brand)s X pour Android", + "Are you sure you want to cancel entering passphrase?": "Souhaitez-vous vraiment annuler l'entrée de la phrase de passe ?", + "Unexpected server error trying to leave the room": "Erreur de serveur inattendue en essayant de quitter le salon", + "Error leaving room": "Erreur en essayant de quitter le salon", + "The person who invited you already left the room.": "La personne vous ayant invité a déjà quitté le salon.", + "The person who invited you already left the room, or their server is offline.": "La personne vous ayant invité a déjà quitté le salon, ou son serveur est hors-ligne.", + "* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s", + "Change notification settings": "Modifier les paramètres de notification", + "Show message previews for reactions in DMs": "Afficher la prévisualisation des messages pour les réactions dans les messages privés", + "Show message previews for reactions in all rooms": "Afficher la prévisualisation des messages pour les réactions dans tous les salons", + "Enable advanced debugging for the room list": "Activer le débogage avancé pour la liste de salons", + "Uploading logs": "Téléversement des journaux", + "Downloading logs": "Téléchargement des journaux", + "Your server isn't responding to some requests.": "Votre serveur ne répond pas à certaines requêtes.", + "Cross-signing and secret storage are ready for use.": "La signature croisée et le coffre secret sont prêt à l'emploi.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "La signature croisée est prête à l'emploi, mais le coffre secret n'est pas actuellement utilisé pour sauvegarder vos clés.", + "Master private key:": "Clé privée maîtresse :", + "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.": "%(brand)s ne peut actuellement mettre en cache vos messages chiffrés localement de manière sécurisée via le navigateur Web. Utilisez %(brand)s Desktop pour que les messages chiffrés apparaissent dans vos résultats de recherche.", + "There are advanced notifications which are not shown here.": "Des notifications avancées ne sont pas affichées ici." } From 57bdb8902743c5941d69856bfd7abc3d211b31f6 Mon Sep 17 00:00:00 2001 From: XoseM Date: Sun, 30 Aug 2020 06:32:55 +0000 Subject: [PATCH 0038/1014] Translated using Weblate (Galician) Currently translated at 100.0% (2358 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 0f2c83fd55..0348203887 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -1884,7 +1884,7 @@ "Message layout": "Disposición da mensaxe", "Compact": "Compacta", "Modern": "Moderna", - "Power level": "Poderío", + "Power level": "Nivel de permisos", "Verify this device to mark it as trusted. Trusting this device gives you and other users extra peace of mind when using end-to-end encrypted messages.": "Verifica este dispositivo para marcalo como confiable. Confiando neste dispositivo permite que ti e outras usuarias estedes máis tranquilas ao utilizar mensaxes cifradas.", "Verifying this device will mark it as trusted, and users who have verified with you will trust this device.": "Ao verificar este dispositivo marcaralo como confiable, e as usuarias que confiaron en ti tamén confiarán nel.", "Waiting for partner to confirm...": "Agardando a que o compañeiro confirme...", @@ -2425,5 +2425,29 @@ "Error leaving room": "Erro ó saír da sala", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Prototipos de Comunidades v2. Require un servidor compatible. Característica experimental - usa con tino.", "Explore rooms in %(communityName)s": "Explorar salas en %(communityName)s", - "Set up Secure Backup": "Configurar Copia de apoio Segura" + "Set up Secure Backup": "Configurar Copia de apoio Segura", + "Cross-signing and secret storage are ready for use.": "A Sinatura-Cruzada e o almacenaxe segredo están listos para usar.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "A Sinatura-Cruzada está preparada para usala, mais o almacenaxe segredo aínda non foi usado para facer copia das chaves.", + "Explore community rooms": "Explorar salas da comunidade", + "Information": "Información", + "Add another email": "Engadir outro email", + "People you know on %(brand)s": "Persoas que coñeces en %(brand)s", + "Send %(count)s invites|other": "Enviar %(count)s convites", + "Send %(count)s invites|one": "Enviar %(count)s convite", + "Invite people to join %(communityName)s": "Convida a persoas a unirse a %(communityName)s", + "There was an error creating your community. The name may be taken or the server is unable to process your request.": "Algo fallou ó crear a túa comunidade. O nome podería estar pillado ou o servidor non pode procesar a túa solicitude.", + "Community ID: +:%(domain)s": "ID da comunidade: +:%(domain)s", + "Use this when referencing your community to others. The community ID cannot be changed.": "Usa esto cando queiras falar sobre a túa comunidade. O ID da comunidade non se pode cambiar.", + "You can change this later if needed.": "Podes cambiar esto máis tarde se o precisas.", + "What's the name of your community or team?": "¿Cómo se chama a túa comunidade ou equipo?", + "Enter name": "Escribe o nome", + "Add image (optional)": "Engade unha imaxe (optativo)", + "An image will help people identify your community.": "Unha imaxe axudaralle á xente a identificar a túa comunidade.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "As salas privadas só se poden atopar e unirse por convite. As salas públicas son accesibles para calquera.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "As salas privadas só poden ser atopadas e unirse por convite. As salas públicas son accesibles para calquera nesta comunidade.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Pode resultar útil se a sala vai ser utilizada só polo equipo de xestión interna do servidor. Non se pode cambiar máis tarde.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Poderías desactivalo se a sala vai ser utilizada para colaborar con equipos externos que teñen o seu propio servidor. Esto non se pode cambiar máis tarde.", + "Create a room in %(communityName)s": "Crear unha sala en %(communityName)s", + "Block anyone not part of %(serverName)s from ever joining this room.": "Evitar que calquera externo a %(serverName)s se poida unir a esta sala.", + "Create community": "Crear comunidade" } From 71095e1a94cc1a8256af8cf179c5ea9d6bc56e8d Mon Sep 17 00:00:00 2001 From: Michael Albert Date: Mon, 31 Aug 2020 16:47:19 +0000 Subject: [PATCH 0039/1014] Translated using Weblate (German) Currently translated at 99.5% (2346 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 59ecf9e250..c403724f88 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -889,12 +889,12 @@ "Go back to set it again.": "Gehe zurück und setze es erneut.", "Download": "Herunterladen", "Print it and store it somewhere safe": "Drucke ihn aus und lagere ihn an einem sicheren Ort", - "Save it on a USB key or backup drive": "Speichere ihn auf einem USB-Schlüssel oder Sicherungsslaufwerk", + "Save it on a USB key or backup drive": "Speichere ihn auf einem USB-Schlüssel oder Sicherungslaufwerk", "Copy it to your personal cloud storage": "Kopiere ihn in deinen persönlichen Cloud-Speicher", "Unable to create key backup": "Konnte Schlüsselsicherung nicht erstellen", "Retry": "Erneut probieren", - "Unable to restore backup": "Konnte Sicherung nicht wiederherstellen", - "No backup found!": "Keine Sicherung gefunden!", + "Unable to restore backup": "Konnte Schlüsselsicherung nicht wiederherstellen", + "No backup found!": "Keine Schlüsselsicherung gefunden!", "This looks like a valid recovery key!": "Dies sieht wie ein gültiger Wiederherstellungsschlüssel aus!", "Not a valid recovery key": "Kein valider Wiederherstellungsschlüssel", "There was an error joining the room": "Es gab einen Fehler beim Raum-Beitreten", @@ -934,7 +934,7 @@ "Use a longer keyboard pattern with more turns": "Nutze ein längeres Tastaturmuster mit mehr Abwechslung", "Straight rows of keys are easy to guess": "Gerade Reihen von Tasten sind einfach zu erraten", "Custom user status messages": "Angepasste Nutzerstatus-Nachrichten", - "Unable to load key backup status": "Konnte Status des Schlüsselbackups nicht laden", + "Unable to load key backup status": "Konnte Status der Schlüsselsicherung nicht laden", "Don't ask again": "Nicht erneut fragen", "Set up": "Einrichten", "Please review and accept all of the homeserver's policies": "Bitte prüfen und akzeptieren Sie alle Richtlinien des Heimservers", @@ -942,7 +942,7 @@ "That doesn't look like a valid email address": "Sieht nicht nach einer validen E-Mail-Adresse aus", "Unable to load commit detail: %(msg)s": "Konnte Commit-Details nicht laden: %(msg)s", "Checking...": "Überprüfe...", - "Unable to load backup status": "Konnte Backupstatus nicht laden", + "Unable to load backup status": "Konnte Sicherungsstatus nicht laden", "Failed to decrypt %(failedCount)s sessions!": "Konnte %(failedCount)s Sitzungen nicht entschlüsseln!", "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Greifen Sie auf Ihre sichere Nachrichtenhistorie zu und richten Sie einen sicheren Nachrichtenversand ein, indem Sie Ihre Wiederherstellungspassphrase eingeben.", "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "Wenn du deinen Wiederherstellungspassphrase vergessen hast, kannst du deinen Wiederherstellungsschlüssel benutzen oder neue Wiederherstellungsoptionen einrichten", @@ -1128,7 +1128,7 @@ "Back up your keys before signing out to avoid losing them.": "Sichere deine Schlüssel bevor du dich abmeldest, damit du sie nicht verlierst.", "Start using Key Backup": "Beginne Schlüsselsicherung zu nutzen", "Credits": "Danksagungen", - "Starting backup...": "Starte Backup...", + "Starting backup...": "Starte Sicherung...", "Success!": "Erfolgreich!", "Your keys are being backed up (the first backup could take a few minutes).": "Deine Schlüssel werden gesichert (Das erste Backup könnte ein paar Minuten in Anspruch nehmen).", "Voice & Video": "Sprach- & Videoanruf", @@ -1384,7 +1384,7 @@ "Cannot connect to integration manager": "Verbindung zum Integrationsmanager fehlgeschlagen", "The integration manager is offline or it cannot reach your homeserver.": "Der Integrationsmanager ist offline oder er kann den Heimserver nicht erreichen.", "not stored": "nicht gespeichert", - "Backup has a signature from unknown user with ID %(deviceId)s": "Backup hat eine Signatur von Unbekanntem Nutzer mit ID %(deviceId)s", + "Backup has a signature from unknown user with ID %(deviceId)s": "Die Sicherung hat eine Signatur von Unbekanntem Nutzer mit ID %(deviceId)s", "Backup key stored: ": "Backup Schlüssel gespeichert: ", "Clear notifications": "Benachrichtigungen löschen", "Disconnect from the identity server and connect to instead?": "Verbindung vom Identitätsserver trennen und stattdessen zu verbinden?", @@ -2094,7 +2094,7 @@ "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.": "Ein Widget unter %(widgetUrl)s möchte deine Identität überprüfen. Wenn du dies zulässt, kann das Widget deine Nutzer-ID überprüfen, jedoch keine Aktionen in deinem Namen ausführen.", "Unable to access secret storage. Please verify that you entered the correct recovery passphrase.": "Der sichere Speicher konnte nicht geladen werden. Bitte stelle sicher dass du die richtige Wiederherstellungspassphrase eingegeben hast.", "Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.": "Die Sicherung konnte nicht mit dem angegebenen Wiederherstellungsschlüssel entschlüsselt werden: Bitte überprüfe ob du den richtigen Wiederherstellungsschlüssel eingegeben hast.", - "Backup could not be decrypted with this recovery passphrase: please verify that you entered the correct recovery passphrase.": "Die Sicherung konnte mit diesem Wiederherstellungsschlüssel nicht entschlüsselt werden: Bitte überprüfe ob du den richtigen Wiederherstellungspassphrase eingegeben hast.", + "Backup could not be decrypted with this recovery passphrase: please verify that you entered the correct recovery passphrase.": "Die Sicherung konnte mit diesem Wiederherstellungsschlüssel nicht entschlüsselt werden: Bitte überprüfe ob du die richtige Wiederherstellungspassphrase eingegeben hast.", "Nice, strong password!": "Super, ein starkes Passwort!", "Other users can invite you to rooms using your contact details": "Andere Benutzer können dich mit deinen Kontaktdaten in Räume einladen", "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Lege eine E-Mail für die Kontowiederherstellung fest. Verwende optional E-Mail oder Telefon, um von Anderen gefunden zu werden.", @@ -2423,7 +2423,7 @@ "Error leaving room": "Fehler beim Verlassen des Raums", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Communities v2 Prototyp. Benötigt einen kompatiblen Heimserver. Höchst experimentell - mit Vorsicht verwenden.", "Explore rooms in %(communityName)s": "Erkunde Räume in %(communityName)s", - "Set up Secure Backup": "Sicherung einrichten", + "Set up Secure Backup": "Schlüsselsicherung einrichten", "Information": "Information", "Add another email": "Weitere E-Mail-Adresse hinzufügen", "Send %(count)s invites|other": "%(count)s Einladungen senden", From e96b4112e3645ce8bab8e308582e65af4a7e1215 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Sun, 30 Aug 2020 18:12:03 +0000 Subject: [PATCH 0040/1014] Translated using Weblate (Hungarian) Currently translated at 100.0% (2358 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index dea22e8b3b..bcdabf7b30 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2442,5 +2442,12 @@ "Create community": "Közösség létrehozása", "Set up Secure Backup": "Biztonsági mentés beállítása", "Explore community rooms": "Fedezd fel a közösségi szobákat", - "Create a room in %(communityName)s": "Készíts szobát itt: %(communityName)s" + "Create a room in %(communityName)s": "Készíts szobát itt: %(communityName)s", + "Cross-signing and secret storage are ready for use.": "Az eszközök közti hitelesítés és a biztonsági tároló kész a használatra.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Az eszközök közti hitelesítés kész a használatra, de a biztonsági tároló nincs használva a kulcsok mentéséhez.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Privát szobák csak meghívóval találhatók meg és meghívóval lehet belépni. A nyilvános szobákat bárki megtalálhatja és be is léphet.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Privát szobák csak meghívóval találhatók meg és meghívóval lehet belépni. A nyilvános szobákat a közösség bármely tagja megtalálhatja és be is léphet.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Beállíthatod, ha a szobát csak egy belső csoport használja majd a matrix szervereden. Ezt később nem lehet megváltoztatni.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Ne engedélyezd ezt, ha a szobát külső csapat is használja másik matrix szerverről. Később nem lehet megváltoztatni.", + "Block anyone not part of %(serverName)s from ever joining this room.": "A szobába való belépés megtagadása mindenkitől ki nem ezt a matrix szervert használja: %(serverName)s." } From 69ea555b0712944bd7b895da61bbecec843c1740 Mon Sep 17 00:00:00 2001 From: random Date: Mon, 31 Aug 2020 13:05:18 +0000 Subject: [PATCH 0041/1014] Translated using Weblate (Italian) Currently translated at 100.0% (2358 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index d639d19e24..48e2b2df20 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2447,5 +2447,10 @@ "Create a room in %(communityName)s": "Crea una stanza in %(communityName)s", "Explore rooms in %(communityName)s": "Esplora le stanze in %(communityName)s", "Create community": "Crea comunità", - "Set up Secure Backup": "Imposta il Backup Sicuro" + "Set up Secure Backup": "Imposta il Backup Sicuro", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Le stanze private possono essere trovate e visitate solo con invito. Le stanze pubbliche invece sono aperte a tutti.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Le stanze private possono essere trovate e visitate solo con invito. Le stanze pubbliche invece sono aperte a tutti i membri di questa comunità.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Dovresti attivarlo se questa stanza verrà usata solo per collaborazioni tra squadre interne nel tuo homeserver. Non può essere cambiato in seguito.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Dovresti disattivarlo se questa stanza verrà usata per collaborazioni con squadre esterne che hanno il loro homeserver. Non può essere cambiato in seguito.", + "Block anyone not part of %(serverName)s from ever joining this room.": "Blocca l'accesso alla stanza per chiunque non faccia parte di %(serverName)s." } From 50c4d7a91e05e875fccb80cb48dbb9f6f255d95b Mon Sep 17 00:00:00 2001 From: ziriSut Date: Fri, 28 Aug 2020 17:21:47 +0000 Subject: [PATCH 0042/1014] Translated using Weblate (Kabyle) Currently translated at 100.0% (2358 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/kab/ --- src/i18n/strings/kab.json | 40 ++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/i18n/strings/kab.json b/src/i18n/strings/kab.json index ad2e683faa..fae9a1476e 100644 --- a/src/i18n/strings/kab.json +++ b/src/i18n/strings/kab.json @@ -1592,7 +1592,7 @@ "Your homeserver has exceeded one of its resource limits.": "Aqeddac-inek·inem agejdan iɛedda yiwet seg tlisa-ines tiɣbula.", "Contact your server admin.": "Nermes anedbal-inek·inem n uqeddac.", "To return to your account in future you need to set a password": "Akken ad tuɣaleḍ ɣer umiḍan-ik·im ɣer sdat tesriḍ ad tesbaduḍ awal uffir", - "New spinner design": "Afeṣṣel amaynut n tuzzya ", + "New spinner design": "Afeṣṣel amaynut n tuzzya", "Render simple counters in room header": "Err amsiḍen afessa ɣef uqerru n texxamt", "Multiple integration managers": "Imsefrak n waṭas n yimsidaf", "Try out new ways to ignore people (experimental)": "Ɛreḍ iberdan-nniḍen i tigtin n yimdanen (armitan)", @@ -1877,7 +1877,7 @@ "Show previews/thumbnails for images": "Sken tiskanin/tinfulin i tugniwin", "How fast should messages be downloaded.": "Acḥal i ilaq ad yili urured i wakken ad d-adren yiznan.", "Enable experimental, compact IRC style layout": "Rmed aseflu n uɣanib n IRC armitan, ussid", - "Waiting for %(displayName)s to verify…": "Aṛaǧu n %(displayName)s i usenqed...", + "Waiting for %(displayName)s to verify…": "Aṛaǧu n %(displayName)s i usenqed…", "Securely cache encrypted messages locally for them to appear in search results, using ": "Ḥrez iznan iwgelhanen idiganen s wudem awurman i wakken ad d-banen deg yigmaḍ n unadi, s useqdec ", "Securely cache encrypted messages locally for them to appear in search results.": "Ḥrez iznan iwgelhanen idiganen s wudem awurman i wakken ad d-banen deg yigmaḍ n unadi.", "The integration manager is offline or it cannot reach your homeserver.": "Amsefrak n umsidef ha-t-an beṛṛa n tuqqna neɣ ur yezmir ara ad yaweḍ ɣer uqeddac-ik·im agejdan.", @@ -2003,12 +2003,12 @@ "Read Marker off-screen lifetime (ms)": "Ɣer tanzagt n tudert n tecreḍt beṛṛa n ugdil (ms)", "Unignore": "Ur yettwazgel ara", "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Anedbal-ik·im n uqeddac issens awgelhen seg yixef ɣer yixef s wudem amezwer deg texxamin tusligin & yiznan usriden.", - "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Sefrek ismawen syen ffeɣ seg tɣimiyin-ik·im ddaw neɣ senqed-itent deg umaɣnu-ik·im n useqdac.", + "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Sefrek ismawen syen ffeɣ seg tɣimiyin-ik·im ddaw neɣ senqed-itent deg umaɣnu-ik·im n useqdac.", "A session's public name is visible to people you communicate with": "Isem n tiɣimit tazayezt yettban i yimdanen wukud tettmeslayeḍ", "%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)s ileqqeḍ tasleḍt tudrigt i wakken ad aɣ-iɛawen ad nesnerni asnas.", "You have ignored this user, so their message is hidden. Show anyways.": "Tzegleḍ useqdac-a, ihi iznan-ines ffren. Ɣas akken sken-iten-id.", "You cancelled verifying %(name)s": "Tesfesxeḍ asenqed n %(name)s", - "Declining …": "Tigtin...", + "Declining …": "Tigtin…", "You sent a verification request": "Tuzneḍ asuter n usenqed", "Error decrypting video": "Tuccḍa deg uwgelhen n tvidyut", "Reactions": "Tisedmirin", @@ -2109,7 +2109,7 @@ "'%(groupId)s' is not a valid community ID": "'%(groupId)s' mačči d asulay n temɣiwent ameɣtu", "Use bots, bridges, widgets and sticker packs": "Seqdec abuten, tileggiyin, iwiǧiten d tɣawsiwin n umyintaḍ", "To continue you need to accept the terms of this service.": "I wakken ad tkemmleḍ tesriḍ ad tqebleḍ tiwtilin n umeẓlu-a.", - "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "Afaylu-a ɣezzif aṭas i wakken ad d-yali. Talast n teɣzi n ufaylu d %(limit)s.", + "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "Afaylu-a ɣezzif aṭas i wakken ad d-yali. Talast n teɣzi n ufaylu d %(limit)s maca afaylu-a d %(sizeOfThisFile)s.", "These files are too large to upload. The file size limit is %(limit)s.": "Ifuyla-a ɣezzifit aṭas i wakken ad d-alin. Talast n teɣzi n ufaylu d %(limit)s.", "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Kra n yifuyla ɣezzifit aṭas i wakken ad d-alin. Talast n teɣzi n ufaylu d %(limit)s.", "Upload %(count)s other files|other": "Sali-d %(count)s ifuyla-nniḍen", @@ -2119,7 +2119,7 @@ "Remember my selection for this widget": "Cfu ɣef tefrant-inu i uwiǧit-a", "Wrong file type": "Anaw n yifuyla d arameɣtu", "Looks good!": "Yettban igerrez!", - "Enter your Security Phrase or to continue.": "Sekcem tafyirt-ik·im n tɣellist neɣ to continue.": "Sekcem tafyirt-ik·im n tɣellist neɣ to continue.": "Mata in din säkerhetsfras eller för att fortsätta.", + "Security Key": "Säkerhetsnyckel", + "Use your Security Key to continue.": "Använd din säkerhetsnyckel för att fortsätta.", + "Restoring keys from backup": "Återställer nycklar från säkerhetskopia", + "Fetching keys from server...": "Hämtar nycklar från servern…", + "%(completed)s of %(total)s keys restored": "%(completed)s av %(total)s nycklar återställda", + "Recovery key mismatch": "Återställningsnyckeln matchade inte", + "Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.": "Säkerhetskopian kunde inte avkrypteras med den här återställningsnyckeln: vänligen verifiera att du matade in rätt återställningsnyckel.", + "Incorrect recovery passphrase": "Fel återställningslösenfras", + "Backup could not be decrypted with this recovery passphrase: please verify that you entered the correct recovery passphrase.": "Säkerhetskopian kunde inte avkrypteras med den här återställningslösenfrasen: vänligen verifiera att du matade in rätt återställningslösenfras.", + "Unable to restore backup": "Kunde inte återställa säkerhetskopia", + "No backup found!": "Ingen säkerhetskopia hittad!", + "Keys restored": "Nycklar återställda", + "Failed to decrypt %(failedCount)s sessions!": "Misslyckades att avkryptera %(failedCount)s sessioner!", + "Successfully restored %(sessionCount)s keys": "Återställde framgångsrikt %(sessionCount)s nycklar", + "Enter recovery passphrase": "Mata in återställningslösenfras", + "Warning: you should only set up key backup from a trusted computer.": "Varning: Du bör endast sätta upp nyckelsäkerhetskopiering från en betrodd dator.", + "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Kom åt din säkra meddelandehistorik och sätt upp säker kommunikation genom att skriva in din återställningslösenfras.", + "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "Om du har glömt din återställningslösenfras kan du använda din återställningsnyckel eller ställa in nya återställningsalternativ", + "Enter recovery key": "Skriv in återställningsnyckel", + "This looks like a valid recovery key!": "Det här ser ut som en giltig återställningsnyckel!", + "Not a valid recovery key": "Inte en giltig återställningsnyckel", + "Warning: You should only set up key backup from a trusted computer.": "Varning: Du bör endast sätta upp nyckelsäkerhetskopiering från en betrodd dator." } From 9351f949cbca0421a80ac37a169dec3f8931633e Mon Sep 17 00:00:00 2001 From: Marcelo Filho Date: Wed, 2 Sep 2020 16:15:33 +0000 Subject: [PATCH 0057/1014] Translated using Weblate (Portuguese (Brazil)) Currently translated at 95.8% (2248 of 2346 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/pt_BR/ --- src/i18n/strings/pt_BR.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index 3a18d6ce4a..451c2aa629 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -332,7 +332,7 @@ "Add": "Adicionar", "Error: Problem communicating with the given homeserver.": "Erro: problema de comunicação com o Servidor de Base fornecido.", "Failed to fetch avatar URL": "Falha ao obter o link da foto de perfil", - "Home": "Início", + "Home": "Home", "The phone number entered looks invalid": "O número de telefone inserido parece ser inválido", "Uploading %(filename)s and %(count)s others|zero": "Enviando o arquivo %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Enviando o arquivo %(filename)s e %(count)s outros arquivos", @@ -1700,7 +1700,7 @@ "Verification Requests": "Solicitações de confirmação", "Integrations are disabled": "As integrações estão desativadas", "Integrations not allowed": "As integrações não estão permitidas", - "End": "Fim", + "End": "End", "List options": "Opções da Lista", "Jump to first unread room.": "Ir para a primeira sala não lida.", "Jump to first invite.": "Ir para o primeiro convite.", @@ -2285,5 +2285,9 @@ "Verified": "Confirmado", "Close dialog or context menu": "Fechar caixa de diálogo ou menu", "Try scrolling up in the timeline to see if there are any earlier ones.": "Tente rolar para cima na conversa para ver se há mensagens anteriores.", - "Navigate composer history": "Ver o histórico de mensagens enviadas no campo de texto" + "Navigate composer history": "Ver o histórico de mensagens enviadas no campo de texto", + "Unexpected server error trying to leave the room": "Erro inesperado no servidor, ao tentar sair da sala", + "Error leaving room": "Erro ao sair da sala", + "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Protótipo da segunda versão das Comunidades. Requer servidor principal compatível. Altamente experimental - use com cautela.", + "Space": "Barra de espaço" } From 0aafce5ee442959f9f0db2f73566c13dbadc44f5 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Wed, 2 Sep 2020 14:43:30 +0000 Subject: [PATCH 0058/1014] Translated using Weblate (Swedish) Currently translated at 90.1% (2114 of 2346 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 8d0bee60e6..fc4435eeaa 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -2167,5 +2167,6 @@ "Enter recovery key": "Skriv in återställningsnyckel", "This looks like a valid recovery key!": "Det här ser ut som en giltig återställningsnyckel!", "Not a valid recovery key": "Inte en giltig återställningsnyckel", - "Warning: You should only set up key backup from a trusted computer.": "Varning: Du bör endast sätta upp nyckelsäkerhetskopiering från en betrodd dator." + "Warning: You should only set up key backup from a trusted computer.": "Varning: Du bör endast sätta upp nyckelsäkerhetskopiering från en betrodd dator.", + "Access your secure message history and set up secure messaging by entering your recovery key.": "Kom åt din säkra meddelandehistorik och sätt upp säker kommunikation genom att skriva in din återställningsnyckel." } From e3f971a3d0cddb938e50596383687d5b184a7cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 2 Sep 2020 16:32:16 +0000 Subject: [PATCH 0059/1014] Translated using Weblate (Estonian) Currently translated at 100.0% (2348 of 2348 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index a2b0bb2339..788579ef67 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2449,5 +2449,7 @@ "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Omavahelisi jututubasid on võimalik leida ning nendega liituda vaid kutse alusel. Selles kogukonnas saavad avalikke jututubasid kõik leida ning nendega liituda.", "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Sa võid sellise võimaluse kasutusele võtta, kui seda jututuba kasutatakse vaid organisatsioonisiseste tiimide ühistööks oma koduserveri piires. Seda ei saa hiljem muuta.", "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Sa võid sellise võimaluse jätta kasutusele võtmata, kui seda jututuba kasutatakse erinevate väliste tiimide ühistööks kasutades erinevaid koduservereid. Seda ei saa hiljem muuta.", - "Block anyone not part of %(serverName)s from ever joining this room.": "Keela kõikide niisuguste kasutajate liitumine selle jututoaga, kelle kasutajakonto ei asu %(serverName)s koduserveris." + "Block anyone not part of %(serverName)s from ever joining this room.": "Keela kõikide niisuguste kasutajate liitumine selle jututoaga, kelle kasutajakonto ei asu %(serverName)s koduserveris.", + "May include members not in %(communityName)s": "Siin võib leiduda kasutajaid, kes ei ole %(communityName)s kogukonna liikmed", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Alusta vestust teise osapoolega tema nime, kasutajanime (näiteks ) või e-posti aadressi alusel. Sellega aga sa ei kutsu teda %(communityName)s kogukonna liikmeks. Kui soovid kedagi kutsuda %(communityName)s kogukonda, siis vajuta siia." } From b72f0f4d923f940ae0fbc6fcd318271808e5dd7f Mon Sep 17 00:00:00 2001 From: Marcelo Filho Date: Wed, 2 Sep 2020 16:29:06 +0000 Subject: [PATCH 0060/1014] Translated using Weblate (Portuguese (Brazil)) Currently translated at 96.0% (2254 of 2348 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/pt_BR/ --- src/i18n/strings/pt_BR.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index 451c2aa629..975281aa00 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -2289,5 +2289,11 @@ "Unexpected server error trying to leave the room": "Erro inesperado no servidor, ao tentar sair da sala", "Error leaving room": "Erro ao sair da sala", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Protótipo da segunda versão das Comunidades. Requer servidor principal compatível. Altamente experimental - use com cautela.", - "Space": "Barra de espaço" + "Space": "Barra de espaço", + "People you know on %(brand)s": "Pessoas que você conhece em %(brand)s", + "Show": "Mostrar", + "Send %(count)s invites|other": "Enviar %(count)s convites", + "Send %(count)s invites|one": "Enviar %(count)s convite", + "Community ID: +:%(domain)s": "ID da comunidade: +:%(domain)s", + "Enter name": "Digitar nome" } From bba4d8ec62e0d6b3c7117765ecec3f85f38c18b3 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 3 Sep 2020 05:11:31 +0000 Subject: [PATCH 0061/1014] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2354 of 2354 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index ad4020b845..4ec70aa8f7 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2452,5 +2452,13 @@ "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "私人聊天室僅能透過邀請找到與加入。公開聊天室則任何在此社群的人都可以找到並加入。", "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "如果聊天室僅用於與在您的家伺服器上的內部團隊協作的話,可以啟用此功能。這無法在稍後變更。", "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "如果聊天室會用於與有自己的家伺服器的外部團隊協作的話,可以停用此功能。這無法在稍後變更。", - "Block anyone not part of %(serverName)s from ever joining this room.": "阻止任何不屬於 %(serverName)s 的人加入此聊天室。" + "Block anyone not part of %(serverName)s from ever joining this room.": "阻止任何不屬於 %(serverName)s 的人加入此聊天室。", + "There was an error updating your community. The server is unable to process your request.": "更新您的社群時發生錯誤。伺服器無法處理您的請求。", + "Update community": "更新社群", + "May include members not in %(communityName)s": "可能包含不在 %(communityName)s 中的成員", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "使用某人的名稱、使用者名稱(如 )或電子郵件地址開始與他們對話。這不會邀請他們加入 %(communityName)s。要邀請某人加入 %(communityName)s,請點擊此處。", + "Failed to find the general chat for this community": "找不到此社群的一般聊天紀錄", + "Community settings": "社群設定", + "User settings": "使用者設定", + "Community and user menu": "社群與使用者選單" } From 31aead1527d3b3e4137e3dccae8a1b234a653f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 2 Sep 2020 16:53:01 +0000 Subject: [PATCH 0062/1014] Translated using Weblate (Estonian) Currently translated at 100.0% (2354 of 2354 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 788579ef67..c617470601 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2451,5 +2451,11 @@ "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Sa võid sellise võimaluse jätta kasutusele võtmata, kui seda jututuba kasutatakse erinevate väliste tiimide ühistööks kasutades erinevaid koduservereid. Seda ei saa hiljem muuta.", "Block anyone not part of %(serverName)s from ever joining this room.": "Keela kõikide niisuguste kasutajate liitumine selle jututoaga, kelle kasutajakonto ei asu %(serverName)s koduserveris.", "May include members not in %(communityName)s": "Siin võib leiduda kasutajaid, kes ei ole %(communityName)s kogukonna liikmed", - "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Alusta vestust teise osapoolega tema nime, kasutajanime (näiteks ) või e-posti aadressi alusel. Sellega aga sa ei kutsu teda %(communityName)s kogukonna liikmeks. Kui soovid kedagi kutsuda %(communityName)s kogukonda, siis vajuta siia." + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Alusta vestust teise osapoolega tema nime, kasutajanime (näiteks ) või e-posti aadressi alusel. Sellega aga sa ei kutsu teda %(communityName)s kogukonna liikmeks. Kui soovid kedagi kutsuda %(communityName)s kogukonda, siis vajuta siia.", + "There was an error updating your community. The server is unable to process your request.": "Sinu kogukonna andmete uuendamisel tekkis viga. Server ei suuda sinu päringut töödelda.", + "Update community": "Uuenda kogukonda", + "Failed to find the general chat for this community": "Ei õnnestunud tuvastada selle kogukonna üldist rühmavestlust", + "Community settings": "Kogukonna seadistused", + "User settings": "Kasutaja seadistused", + "Community and user menu": "Kogukonna ja kasutaja menüü" } From 6dafbea5ca16ef84158986cc34bae51dc5f93566 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Wed, 2 Sep 2020 18:07:48 +0000 Subject: [PATCH 0063/1014] Translated using Weblate (Hungarian) Currently translated at 100.0% (2354 of 2354 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index bcdabf7b30..a9e209c169 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -483,7 +483,7 @@ "Community ID": "Közösség azonosító", "Add rooms to the community summary": "Szobák hozzáadása a közösségi összefoglalóhoz", "Add users to the community summary": "Felhasználók hozzáadása a közösségi összefoglalóhoz", - "Failed to update community": "Közösség frissítése sikertelen", + "Failed to update community": "Közösség módosítása sikertelen", "Leave Community": "Közösség elhagyása", "Add rooms to this community": "Szobák hozzáadása ehhez a közösséghez", "%(inviter)s has invited you to join this community": "%(inviter)s meghívott ebbe a közösségbe", @@ -2449,5 +2449,13 @@ "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Privát szobák csak meghívóval találhatók meg és meghívóval lehet belépni. A nyilvános szobákat a közösség bármely tagja megtalálhatja és be is léphet.", "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Beállíthatod, ha a szobát csak egy belső csoport használja majd a matrix szervereden. Ezt később nem lehet megváltoztatni.", "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Ne engedélyezd ezt, ha a szobát külső csapat is használja másik matrix szerverről. Később nem lehet megváltoztatni.", - "Block anyone not part of %(serverName)s from ever joining this room.": "A szobába való belépés megtagadása mindenkitől ki nem ezt a matrix szervert használja: %(serverName)s." + "Block anyone not part of %(serverName)s from ever joining this room.": "A szobába való belépés megtagadása mindenkitől ki nem ezt a matrix szervert használja: %(serverName)s.", + "There was an error updating your community. The server is unable to process your request.": "A közösség módosításakor hiba történt. A szerver nem tudja feldolgozni a kérést.", + "Update community": "Közösség módosítása", + "May include members not in %(communityName)s": "Olyan tagok is lehetnek akik nincsenek ebben a közösségben: %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Kezdj beszélgetést valakivel akár a neve, felhasználói neve (mint ) vagy az e-mail címe segítségével. %(communityName)s közösségbe nem lesznek meghívva. Ha valakit meg szeretnél hívni ide: %(communityName)s, kattints ide.", + "Failed to find the general chat for this community": "Ehhez a közösséghez nem található általános csevegés", + "Community settings": "Közösségi beállítások", + "User settings": "Felhasználói beállítások", + "Community and user menu": "Közösségi és felhasználói menü" } From cbbaae48fc8cf6167941e3c0b52b4a6847a0f554 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Wed, 2 Sep 2020 18:28:01 +0000 Subject: [PATCH 0064/1014] Translated using Weblate (Swedish) Currently translated at 92.4% (2176 of 2354 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 120 ++++++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 38 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index fc4435eeaa..45998f5264 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -62,7 +62,7 @@ "Disinvite": "Häv inbjudan", "Displays action": "Visar åtgärd", "Download %(text)s": "Ladda ner %(text)s", - "Email": "Epost", + "Email": "E-post", "Email address": "E-postadress", "Emoji": "Emoji", "%(senderName)s ended the call.": "%(senderName)s avslutade samtalet.", @@ -74,14 +74,14 @@ "Failed to ban user": "Misslyckades att banna användaren", "Failed to change password. Is your password correct?": "Misslyckades att byta lösenord. Är lösenordet rätt?", "Failed to change power level": "Misslyckades att ändra behörighetsnivå", - "Failed to forget room %(errCode)s": "Det gick inte att glömma bort rummet %(errCode)s", + "Failed to forget room %(errCode)s": "Misslyckades att glömma bort rummet %(errCode)s", "Failed to join room": "Misslyckades att gå med i rummet", "Failed to kick": "Misslyckades att kicka", "Failed to leave room": "Det gick inte att lämna rummet", "Failed to load timeline position": "Det gick inte att hämta positionen på tidslinjen", "Failed to mute user": "Misslyckades att tysta användaren", "Failed to reject invite": "Det gick inte att avböja inbjudan", - "Failed to reject invitation": "Det gick inte att avböja inbjudan", + "Failed to reject invitation": "Misslyckades att avböja inbjudan", "Failed to send email": "Det gick inte att skicka epost", "Failed to send request.": "Misslyckades att sända begäran.", "Failed to set display name": "Misslyckades att ange visningsnamn", @@ -237,7 +237,7 @@ "Publish this room to the public in %(domain)s's room directory?": "Publicera rummet i den offentliga rumskatalogen på %(domain)s?", "AM": "FM", "PM": "EM", - "Submit": "Lämna in", + "Submit": "Skicka in", "The maximum permitted number of widgets have already been added to this room.": "Den största tillåtna mängden widgetar har redan tillagts till rummet.", "The phone number entered looks invalid": "Det angivna telefonnumret är ogiltigt", "This email address is already in use": "Den här e-postadressen används redan", @@ -311,7 +311,7 @@ "Messages containing my display name": "Meddelanden som innehåller mitt visningsnamn", "Messages in one-to-one chats": "Meddelanden i en-till-en chattar", "Unavailable": "Otillgänglig", - "View Decrypted Source": "Visa dekrypterad källa", + "View Decrypted Source": "Visa avkrypterad källa", "Failed to update keywords": "Kunde inte uppdatera nyckelorden", "remove %(name)s from the directory.": "ta bort %(name)s från katalogen.", "Notifications on the following keywords follow rules which can’t be displayed here:": "Aviseringar för följande nyckelord följer regler som inte kan visas här:", @@ -478,10 +478,10 @@ "Failed to send logs: ": "Misslyckades att skicka loggar: ", "Submit debug logs": "Skicka felsökningsloggar", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Felsökningsloggar innehåller användningsdata för applikationen inklusive ditt användarnamn, ID:n eller alias för de rum och grupper du har besökt och användarnamn för andra användare. De innehåller inte meddelanden.", - "An email has been sent to %(emailAddress)s": "Ett epostmeddelande har skickats till %(emailAddress)s", - "Please check your email to continue registration.": "Vänligen kolla din epost för att fortsätta registreringen.", - "Token incorrect": "Ogiltig token", - "A text message has been sent to %(msisdn)s": "Ett textmeddelande har skickats till %(msisdn)s", + "An email has been sent to %(emailAddress)s": "Ett e-postmeddelande har skickats till %(emailAddress)s", + "Please check your email to continue registration.": "Vänligen kolla din e-post för att fortsätta registreringen.", + "Token incorrect": "Felaktig token", + "A text message has been sent to %(msisdn)s": "Ett SMS har skickats till %(msisdn)s", "Please enter the code it contains:": "Vänligen ange koden det innehåller:", "Code": "Kod", "Set a display name:": "Ange ett visningsnamn:", @@ -508,7 +508,7 @@ "Start automatically after system login": "Starta automatiskt vid systeminloggning", "This will allow you to reset your password and receive notifications.": "Det här låter dig återställa lösenordet och ta emot aviseringar.", "You have no visible notifications": "Du har inga synliga aviseringar", - "Failed to upload image": "Det gick inte att ladda upp bild", + "Failed to upload image": "Misslyckades att ladda upp bild", "New Password": "Nytt lösenord", "Do you want to set an email address?": "Vill du ange en e-postadress?", "Not a valid %(brand)s keyfile": "Inte en giltig %(brand)s-nyckelfil", @@ -537,7 +537,7 @@ "Tried to load a specific point in this room's timeline, but was unable to find it.": "Försökte ladda en specifik punkt i det här rummets tidslinje, men kunde inte hitta den.", "Success": "Framgång", "Unable to remove contact information": "Kunde inte ta bort kontaktuppgifter", - "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Ett epostmeddelande har skickats till %(emailAddress)s. När du har öppnat länken i det, klicka nedan.", + "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Ett e-brev har skickats till %(emailAddress)s. När du har öppnat länken i det, klicka nedan.", "Please note you are logging into the %(hs)s server, not matrix.org.": "Observera att du loggar in på %(hs)s-servern, inte matrix.org.", "This homeserver doesn't offer any login flows which are supported by this client.": "Denna hemserver erbjuder inga inloggningsflöden som stöds av den här klienten.", "Upload new:": "Ladda upp ny:", @@ -548,7 +548,7 @@ "Minimize apps": "Minimera appar", "Failed to invite the following users to %(groupId)s:": "Misslyckades att bjuda in följande användare till %(groupId)s:", "Failed to invite users to %(groupId)s": "Misslyckades att bjuda in användare till %(groupId)s", - "This room is not public. You will not be able to rejoin without an invite.": "Detta rum är inte offentligt. Du kommer inte kunna gå med igen utan en inbjudan.", + "This room is not public. You will not be able to rejoin without an invite.": "Det här rummet är inte offentligt. Du kommer inte kunna gå med igen utan en inbjudan.", "Ignores a user, hiding their messages from you": "Ignorerar en användare och döljer dess meddelanden för dig", "Stops ignoring a user, showing their messages going forward": "Slutar ignorera en användare och visar dess meddelanden framöver", "Opens the Developer Tools dialog": "Öppna dialogrutan Utvecklarverktyg", @@ -599,11 +599,11 @@ "We encountered an error trying to restore your previous session.": "Ett fel uppstod vid återställning av din tidigare session.", "If you have previously used a more recent version of %(brand)s, your session may be incompatible with this version. Close this window and return to the more recent version.": "Om du nyligen har använt en senare version av %(brand)s kan din session vara inkompatibel med den här versionen. Stäng det här fönstret och använd senare versionen istället.", "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Att rensa webbläsarens lagring kan lösa problemet, men då loggas du ut och krypterad chatthistorik blir oläslig.", - "Collapse Reply Thread": "Dölj svarstråd", + "Collapse Reply Thread": "Kollapsa svarstråd", "Terms and Conditions": "Villkor", "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "För att fortsätta använda hemservern %(homeserverDomain)s måste du granska och godkänna våra villkor.", "Review terms and conditions": "Granska villkoren", - "Old cryptography data detected": "Gammal krypteringsdata upptäckt", + "Old cryptography data detected": "Gammal kryptografidata upptäckt", "Unable to capture screen": "Kunde inte ta skärmdump", "Failed to add the following rooms to %(groupId)s:": "Misslyckades att lägga till följande rum till %(groupId)s:", "Missing roomId.": "Rums-ID saknas.", @@ -616,20 +616,20 @@ "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Där denna sida innehåller identifierbar information, till exempel ett rums-, användar- eller grupp-ID, tas datan bort innan den skickas till servern.", "The remote side failed to pick up": "Mottagaren svarade inte", "Jump to read receipt": "Hoppa till läsindikation", - "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "Denna process låter dig exportera nycklarna för meddelanden som du har fått i krypterade rum till en lokal fil. Du kommer sedan att kunna importera filen i en annan Matrix-klient i framtiden, så att den klienten också kan dekryptera meddelandena.", + "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "Denna process låter dig exportera nycklarna för meddelanden som du har fått i krypterade rum till en lokal fil. Du kommer sedan att kunna importera filen i en annan Matrix-klient i framtiden, så att den klienten också kan avkryptera meddelandena.", "Unknown for %(duration)s": "Okänt i %(duration)s", "Unknown": "Okänt", "e.g. %(exampleValue)s": "t.ex. %(exampleValue)s", "Can't leave Server Notices room": "Kan inte lämna serveraviseringsrummet", "This room is used for important messages from the Homeserver, so you cannot leave it.": "Detta rum används för viktiga meddelanden från hemservern, så du kan inte lämna det.", - "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Data från en äldre version av %(brand)s has upptäckts. Detta ska ha orsakat att totalsträckskryptering inte fungerat i den äldre versionen. Krypterade meddelanden som nyligen har skickats medans den äldre versionen användes kanske inte kan dekrypteras i denna version. Detta kan även orsaka att meddelanden skickade med denna version inte fungerar. Om du upplever problem, logga ut och in igen. För att behålla meddelandehistoriken, exportera dina nycklar och importera dem igen.", + "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Data från en äldre version av %(brand)s has upptäckts. Detta ska ha orsakat att totalsträckskryptering inte fungerat i den äldre versionen. Krypterade meddelanden som nyligen har skickats medans den äldre versionen användes kanske inte kan avkrypteras i denna version. Detta kan även orsaka att meddelanden skickade med denna version inte fungerar. Om du upplever problem, logga ut och in igen. För att behålla meddelandehistoriken, exportera dina nycklar och importera dem igen.", "Confirm Removal": "Bekräfta borttagning", "%(severalUsers)sleft and rejoined %(count)s times|other": "%(severalUsers)slämnade och gick med igen %(count)s gånger", "%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)slämnade och gick med igen", "%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)slämnade och gick med igen %(count)s gånger", "%(oneUser)sleft and rejoined %(count)s times|one": "%(oneUser)slämnade och gick med igen", "%(severalUsers)srejected their invitations %(count)s times|other": "%(severalUsers)savböjde sina inbjudningar %(count)s gånger", - "Unable to reject invite": "Det gick inte att avböja inbjudan", + "Unable to reject invite": "Kunde inte avböja inbjudan", "Reject all %(invitedRooms)s invites": "Avböj alla %(invitedRooms)s inbjudningar", "%(severalUsers)srejected their invitations %(count)s times|one": "%(severalUsers)savböjde sina inbjudningar", "%(oneUser)srejected their invitation %(count)s times|other": "%(oneUser)savböjde sin inbjudan %(count)s gånger", @@ -696,10 +696,10 @@ "

HTML for your community's page

\n

\n Use the long description to introduce new members to the community, or distribute\n some important links\n

\n

\n You can even use 'img' tags\n

\n": "

HTML för din gemenskapssida

\n

\n Använd den långa beskrivningen för att introducera nya medlemmar till gemenskapen, eller dela\n några viktiga länkar\n

\n

\n Du kan även använda 'img'-taggar\n

\n", "Add rooms to the community summary": "Lägg till rum i gemenskapsöversikten", "Add users to the community summary": "Lägg till användare i gemenskapsöversikten", - "Failed to update community": "Det gick inte att uppdatera gemenskapen", - "Unable to join community": "Det gick inte att gå med i gemenskapen", + "Failed to update community": "Misslyckades att uppdatera gemenskapen", + "Unable to join community": "Kunde inte gå med i gemenskapen", "Leave Community": "Lämna gemenskapen", - "Unable to leave community": "Det gick inte att lämna gemenskap", + "Unable to leave community": "Kunde inte lämna gemenskapen", "Community Settings": "Gemenskapsinställningar", "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Det kan dröja upp till 30 minuter innan ändringar på gemenskapens namn och avatar blir synliga för andra användare.", "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Dessa rum visas för gemenskapsmedlemmar på gemenskapssidan. Gemenskapsmedlemmar kan gå med i rummen genom att klicka på dem.", @@ -710,7 +710,7 @@ "You are an administrator of this community": "Du är administratör för denna gemenskap", "You are a member of this community": "Du är medlem i denna gemenskap", "Who can join this community?": "Vem kan gå med i denna gemenskap?", - "Your community hasn't got a Long Description, a HTML page to show to community members.
Click here to open settings and give it one!": "Din gemenskap har ingen lång beskrivning eller HTML-sida att visa för medlemmar.
Klicka här för att öppna inställningarna och lägga till det!", + "Your community hasn't got a Long Description, a HTML page to show to community members.
Click here to open settings and give it one!": "Din gemenskap har ingen lång beskrivning, en HTML-sida att visa för medlemmar.
Klicka här för att öppna inställningarna och lägga till en!", "Community %(groupId)s not found": "Gemenskapen %(groupId)s hittades inte", "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "För att skapa ett filter, dra en gemenskapsavatar till filterpanelen längst till vänster på skärmen. Du kan när som helst klicka på en avatar i filterpanelen för att bara se rum och personer som är associerade med den gemenskapen.", "Create a new community": "Skapa en ny gemenskap", @@ -727,7 +727,7 @@ "Everyone": "Alla", "Long Description (HTML)": "Lång beskrivning (HTML)", "Description": "Beskrivning", - "Failed to load %(groupId)s": "Det gick inte att ladda %(groupId)s", + "Failed to load %(groupId)s": "Misslyckades att ladda %(groupId)s", "Failed to withdraw invitation": "Misslyckades att dra tillbaka inbjudan", "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Är du säker på att du vill ta bort '%(roomName)s' från %(groupId)s?", "Failed to remove '%(roomName)s' from %(groupId)s": "Misslyckades att ta bort '%(roomName)s' från %(groupId)s", @@ -751,16 +751,16 @@ "URL Previews": "URL-förhandsvisning", "Which rooms would you like to add to this summary?": "Vilka rum vill du lägga till i översikten?", "Add to summary": "Lägg till i översikt", - "Failed to add the following rooms to the summary of %(groupId)s:": "Det gick inte att lägga till följande rum i översikten för %(groupId)s:", + "Failed to add the following rooms to the summary of %(groupId)s:": "Misslyckades att lägga till följande rum i översikten för %(groupId)s:", "Add a Room": "Lägg till ett rum", - "Failed to remove the room from the summary of %(groupId)s": "Det gick inte att ta bort rummet från översikten i %(groupId)s", + "Failed to remove the room from the summary of %(groupId)s": "Misslyckades att ta bort rummet från översikten i %(groupId)s", "The room '%(roomName)s' could not be removed from the summary.": "Rummet '%(roomName)s' kunde inte tas bort från översikten.", "Who would you like to add to this summary?": "Vem vill du lägga till i översikten?", - "Failed to add the following users to the summary of %(groupId)s:": "Det gick inte att lägga till följande användare i översikten för %(groupId)s:", + "Failed to add the following users to the summary of %(groupId)s:": "Misslyckades att lägga till följande användare i översikten för %(groupId)s:", "Add a User": "Lägg till en användare", - "Failed to remove a user from the summary of %(groupId)s": "Det gick inte att ta bort en användare från översikten i %(groupId)s", + "Failed to remove a user from the summary of %(groupId)s": "Misslyckades att ta bort en användare från översikten i %(groupId)s", "The user '%(displayName)s' could not be removed from the summary.": "Användaren '%(displayName)s' kunde inte tas bort från översikten.", - "Unable to accept invite": "Det gick inte att acceptera inbjudan", + "Unable to accept invite": "Kunde inte acceptera inbjudan", "Leave %(groupName)s?": "Lämna %(groupName)s?", "Enable widget screenshots on supported widgets": "Aktivera widget-skärmdumpar för widgets som stöder det", "Key request sent.": "Nyckelbegäran skickad.", @@ -776,7 +776,7 @@ "Error decrypting video": "Fel vid avkryptering av video", "Add an Integration": "Lägg till integration", "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "Du kommer att skickas till en tredjepartswebbplats så att du kan autentisera ditt konto för användning med %(integrationsUrl)s. Vill du fortsätta?", - "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Om du inte anger en epostadress, kan du inte återställa ditt lösenord. Är du säker?", + "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Om du inte anger en e-postadress, kan du inte återställa ditt lösenord. Är du säker?", "Popout widget": "Poppa ut widget", "were unbanned %(count)s times|other": "blev avbannade %(count)s gånger", "were unbanned %(count)s times|one": "blev avbannade", @@ -828,9 +828,9 @@ "You do not have permission to start a conference call in this room": "Du har inte behörighet att starta ett gruppsamtal i detta rum", "This event could not be displayed": "Den här händelsen kunde inte visas", "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "I krypterade rum, som detta, är URL-förhandsvisning inaktiverad som förval för att säkerställa att din hemserver (där förhandsvisningar genereras) inte kan samla information om länkar du ser i rummet.", - "The email field must not be blank.": "Epost-fältet får inte vara tomt.", - "The phone number field must not be blank.": "Telefonnummer-fältet får inte vara tomt.", - "The password field must not be blank.": "Lösenords-fältet får inte vara tomt.", + "The email field must not be blank.": "E-postfältet får inte vara tomt.", + "The phone number field must not be blank.": "Telefonnummerfältet får inte vara tomt.", + "The password field must not be blank.": "Lösenordsfältet får inte vara tomt.", "Failed to remove widget": "Misslyckades att radera widget", "An error ocurred whilst trying to remove the widget from the room": "Ett fel inträffade vid borttagning av widget från rummet", "Demote yourself?": "Degradera dig själv?", @@ -1042,7 +1042,7 @@ "Clear status": "Rensa status", "Update status": "Uppdatera status", "Set status": "Ange status", - "Set a new status...": "Ange en ny status...", + "Set a new status...": "Ange en ny status…", "Hide": "Dölj", "This homeserver would like to make sure you are not a robot.": "Denna hemserver vill se till att du inte är en robot.", "Server Name": "Servernamn", @@ -1052,16 +1052,16 @@ "Sign in to your Matrix account on %(serverName)s": "Logga in med ditt Matrix-konto på %(serverName)s", "Change": "Ändra", "Create your Matrix account on %(serverName)s": "Skapa ditt Matrix-konto på %(serverName)s", - "Email (optional)": "Epost (valfritt)", + "Email (optional)": "E-post (valfritt)", "Phone (optional)": "Telefon (valfritt)", "Confirm": "Bekräfta", "Other servers": "Andra servrar", "Homeserver URL": "Hemserver-URL", "Identity Server URL": "Identitetsserver-URL", "Free": "Gratis", - "Join millions for free on the largest public server": "Bli medlem gratis på den största offentliga servern", + "Join millions for free on the largest public server": "Gå med miljontals användare gratis på den största publika servern", "Premium": "Premium", - "Premium hosting for organisations Learn more": "Premium-hosting för organisationer Läs mer", + "Premium hosting for organisations Learn more": "Premium-servervärd för organisationer Läs mer", "Other": "Annat", "Find other public servers or use a custom server": "Hitta andra offentliga servrar eller använd en anpassad server", "Your Matrix account on %(serverName)s": "Ditt Matrix-konto på %(serverName)s", @@ -1311,7 +1311,7 @@ "edited": "redigerat", "Sign in to your Matrix account on ": "Logga in med ditt Matrix-konto på ", "Please install Chrome, Firefox, or Safari for the best experience.": "Installera Chrome, Firefox, eller Safari för den bästa upplevelsen.", - "Couldn't load page": "Det gick inte att ladda sidan", + "Couldn't load page": "Kunde inte ladda sidan", "Want more than a community? Get your own server": "Vill du ha mer än en gemenskap? Skaffa din egen server", "This homeserver does not support communities": "Denna hemserver stöder inte gemenskaper", "Explore": "Utforska", @@ -1444,7 +1444,7 @@ "Your %(brand)s doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Din %(brand)s tillåter dig inte att använda en integrationshanterare för att göra detta. Vänligen kontakta en administratör.", "Your homeserver doesn't seem to support this feature.": "Din hemserver verkar inte stödja den här funktionen.", "Message edits": "Meddelanderedigeringar", - "Preview": "Förhandsvisa", + "Preview": "Förhandsgranska", "The message you are trying to send is too large.": "Meddelandet du försöker skicka är för stort.", "Find others by phone or email": "Hitta andra via telefon eller e-post", "Be found by phone or email": "Bli hittad via telefon eller e-post", @@ -2168,5 +2168,49 @@ "This looks like a valid recovery key!": "Det här ser ut som en giltig återställningsnyckel!", "Not a valid recovery key": "Inte en giltig återställningsnyckel", "Warning: You should only set up key backup from a trusted computer.": "Varning: Du bör endast sätta upp nyckelsäkerhetskopiering från en betrodd dator.", - "Access your secure message history and set up secure messaging by entering your recovery key.": "Kom åt din säkra meddelandehistorik och sätt upp säker kommunikation genom att skriva in din återställningsnyckel." + "Access your secure message history and set up secure messaging by entering your recovery key.": "Kom åt din säkra meddelandehistorik och sätt upp säker kommunikation genom att skriva in din återställningsnyckel.", + "If you've forgotten your recovery key you can ": "Om du har glömt din återställningsnyckel så kan du ", + "Resend edit": "Skicka redigering igen", + "Resend %(unsentCount)s reaction(s)": "Skicka %(unsentCount)s reaktion(er) igen", + "Resend removal": "Skicka borttagning igen", + "Share Permalink": "Dela permalänk", + "Report Content": "Rapportera innehåll", + "This room is public": "Det här rummet är offentligt", + "Away": "Borta", + "Country Dropdown": "Land-dropdown", + "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.": "Du kan använda de anpassade serveralternativen för att logga in på andra Matrix-servrar genom att ange en annan hemserver-URL. Detta gör att du kan använda %(brand)s med ett befintligt Matrix-konto på en annan hemserver.", + "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Saknar publik nyckel för captcha i hemserverns konfiguration. Vänligen rapportera detta till din hemservers administratör.", + "Please review and accept all of the homeserver's policies": "Vänligen granska och acceptera alla hemserverns policyer", + "Unable to validate homeserver/identity server": "Kunde inte validera hemserver/identitetsserver", + "Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of element.io.": "Ange platsen för din Element Matrix Services-hemserver. Den kan använda ditt eget domännamn eller vara en underdomän till element.io.", + "Enter password": "Skriv in lösenord", + "Nice, strong password!": "Bra, säkert lösenord!", + "Password is allowed, but unsafe": "Lösenordet är tillåtet men osäkert", + "Keep going...": "Fortsätt…", + "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "Ingen identitetsserver är konfigurerad så du kan inte lägga till en e-postadress för att återställa ditt lösenord i framtiden.", + "Use an email address to recover your account": "Använd en a-postadress för att återställa ditt konto", + "Enter email address (required on this homeserver)": "Skriv in e-postadress (krävs på den här hemservern)", + "Doesn't look like a valid email address": "Det ser inte ut som en giltig e-postadress", + "Passwords don't match": "Lösenorden matchar inte", + "Other users can invite you to rooms using your contact details": "Andra användare kan bjuda in dig till rum med dina kontaktuppgifter", + "Enter phone number (required on this homeserver)": "Skriv in telefonnummer (krävs på den här hemservern)", + "Doesn't look like a valid phone number": "Det ser inte ut som ett giltigt telefonnummer", + "Use lowercase letters, numbers, dashes and underscores only": "Använd endast små bokstäver, siffror, bindestreck och understreck", + "Enter username": "Skriv in användarnamn", + "Create your Matrix account on ": "Skapa ditt Matrix-konto på ", + "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Sätt en e-postadress för kontoåterställning. Använd valfritt en e-postadress eller ett telefonnummer för kunna upptäckas av existerande kontakter.", + "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Sätt en e-postadress för kontoåterställning. Använd valfritt en e-postadress för kunna upptäckas av existerande kontakter.", + "Enter your custom homeserver URL What does this mean?": "Skriv in din hemserver-URL Vad betyder det här?", + "Enter your custom identity server URL What does this mean?": "Skriv in din anpassade identitetsserver-URL Vad betyder det här?", + "Sign in with SSO": "Logga in med SSO", + "No files visible in this room": "Inga filer synliga i det här rummet", + "Attach files from chat or just drag and drop them anywhere in a room.": "Bifoga filer från chatten eller dra och släpp dem vart som helst i rummet.", + "Welcome to %(appName)s": "Välkommen till %(appName)s", + "Liberate your communication": "Befria din kommunikation", + "Send a Direct Message": "Skicka ett direktmeddelande", + "Explore Public Rooms": "Utforska offentliga rum", + "Create a Group Chat": "Skapa en gruppchatt", + "Explore rooms": "Utforska rum", + "Self-verification request": "Självverifieringsförfrågan", + "%(creator)s created and configured the room.": "%(creator)s skapade och konfigurerade rummet." } From 9b937ecf238155298d4888b236f242227670b1cd Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Thu, 3 Sep 2020 16:16:22 +0000 Subject: [PATCH 0065/1014] Translated using Weblate (Swedish) Currently translated at 92.7% (2182 of 2354 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 45998f5264..4bc5065bc2 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -2212,5 +2212,11 @@ "Create a Group Chat": "Skapa en gruppchatt", "Explore rooms": "Utforska rum", "Self-verification request": "Självverifieringsförfrågan", - "%(creator)s created and configured the room.": "%(creator)s skapade och konfigurerade rummet." + "%(creator)s created and configured the room.": "%(creator)s skapade och konfigurerade rummet.", + "You’re all caught up": "Du är ikapp", + "You have no visible notifications in this room.": "Du har inga synliga aviseringar i det här rummet.", + "%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "%(brand)s misslyckades att hämta protokollistan från hemservern. Hemservern kan vara för gammal för att stödja tredjepartsnätverk.", + "%(brand)s failed to get the public room list.": "%(brand)s misslyckades att hämta listan över offentliga rum.", + "The homeserver may be unavailable or overloaded.": "Hemservern kan vara otillgänglig eller överbelastad.", + "Delete the room address %(alias)s and remove %(name)s from the directory?": "Radera rumsadressen %(alias)s och ta bort %(name)s från den här katalogen?" } From 4398f1eb949c8f4b5b3e61c51c756bec3c9c97d8 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Thu, 3 Sep 2020 16:28:42 -0400 Subject: [PATCH 0066/1014] support setting up dehydration from blank account, SSO support, and other fixes --- src/CrossSigningManager.js | 26 +++++++++++ src/Lifecycle.js | 44 ++++++++++++++++++- src/Login.js | 18 +++++--- src/MatrixClientPeg.ts | 24 +++++++--- .../CreateSecretStorageDialog.js | 5 +++ 5 files changed, 103 insertions(+), 14 deletions(-) diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index 43d089010c..111fc26889 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -30,6 +30,16 @@ import {encodeBase64} from "matrix-js-sdk/src/crypto/olmlib"; let secretStorageKeys = {}; let secretStorageBeingAccessed = false; +let dehydrationInfo = {}; + +export function cacheDehydrationKey(key, keyInfo = {}) { + dehydrationInfo = {key, keyInfo}; +} + +export function getDehydrationKeyCache() { + return dehydrationInfo; +} + function isCachingAllowed() { return secretStorageBeingAccessed; } @@ -64,6 +74,22 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { return [name, secretStorageKeys[name]]; } + // if we dehydrated a device, see if that key works for SSSS + if (dehydrationInfo.key) { + try { + if (await MatrixClientPeg.get().checkSecretStorageKey(dehydrationInfo.key, info)) { + const key = dehydrationInfo.key; + // Save to cache to avoid future prompts in the current session + if (isCachingAllowed()) { + secretStorageKeys[name] = key; + } + dehydrationInfo = {}; + return [name, key]; + } + } catch {} + dehydrationInfo = {}; + } + const inputToKey = async ({ passphrase, recoveryKey }) => { if (passphrase) { return deriveKey( diff --git a/src/Lifecycle.js b/src/Lifecycle.js index d2de31eb80..9a84d4e1f4 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -42,6 +42,7 @@ import {Mjolnir} from "./mjolnir/Mjolnir"; import DeviceListener from "./DeviceListener"; import {Jitsi} from "./widgets/Jitsi"; import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform"; +import {decodeBase64, encodeBase64} from "matrix-js-sdk/src/crypto/olmlib"; const HOMESERVER_URL_KEY = "mx_hs_url"; const ID_SERVER_URL_KEY = "mx_is_url"; @@ -311,6 +312,25 @@ async function _restoreFromLocalStorage(opts) { console.log("No pickle key available"); } + const rehydrationKeyInfoJSON = sessionStorage.getItem("mx_rehydration_key_info"); + const rehydrationKeyInfo = rehydrationKeyInfoJSON && JSON.parse(rehydrationKeyInfoJSON); + const rehydrationKeyB64 = sessionStorage.getItem("mx_rehydration_key"); + const rehydrationKey = rehydrationKeyB64 && decodeBase64(rehydrationKeyB64); + const rehydrationOlmPickle = sessionStorage.getItem("mx_rehydration_account"); + let olmAccount; + if (rehydrationOlmPickle) { + olmAccount = new global.Olm.Account(); + try { + olmAccount.unpickle("DEFAULT_KEY", rehydrationOlmPickle); + } catch { + olmAccount.free(); + olmAccount = undefined; + } + } + sessionStorage.removeItem("mx_rehydration_key_info"); + sessionStorage.removeItem("mx_rehydration_key"); + sessionStorage.removeItem("mx_rehydration_account"); + console.log(`Restoring session for ${userId}`); await _doSetLoggedIn({ userId: userId, @@ -320,6 +340,9 @@ async function _restoreFromLocalStorage(opts) { identityServerUrl: isUrl, guest: isGuest, pickleKey: pickleKey, + rehydrationKey: rehydrationKey, + rehydrationKeyInfo: rehydrationKeyInfo, + olmAccount: olmAccount, }, false); return true; } else { @@ -463,7 +486,13 @@ async function _doSetLoggedIn(credentials, clearStorage) { if (localStorage) { try { - _persistCredentialsToLocalStorage(credentials); + // drop dehydration key and olm account before persisting. (Those + // get persisted for token login, but aren't needed at this point.) + const strippedCredentials = Object.assign({}, credentials); + delete strippedCredentials.rehydrationKeyInfo; + delete strippedCredentials.rehydrationKey; + delete strippedCredentials.olmAcconut; + _persistCredentialsToLocalStorage(strippedCredentials); // The user registered as a PWLU (PassWord-Less User), the generated password // is cached here such that the user can change it at a later time. @@ -528,6 +557,19 @@ function _persistCredentialsToLocalStorage(credentials) { localStorage.setItem("mx_device_id", credentials.deviceId); } + // Temporarily save dehydration information if it's provided. This is + // needed for token logins, because the page reloads after the login, so we + // can't keep it in memory. + if (credentials.rehydrationKeyInfo) { + sessionStorage.setItem("mx_rehydration_key_info", JSON.stringify(credentials.rehydrationKeyInfo)); + } + if (credentials.rehydrationKey) { + sessionStorage.setItem("mx_rehydration_key", encodeBase64(credentials.rehydrationKey)); + } + if (credentials.olmAccount) { + sessionStorage.setItem("mx_rehydration_account", credentials.olmAccount.pickle("DEFAULT_KEY")); + } + console.log(`Session persisted for ${credentials.userId}`); } diff --git a/src/Login.js b/src/Login.js index 4e46fc3665..0563952c5d 100644 --- a/src/Login.js +++ b/src/Login.js @@ -20,7 +20,12 @@ limitations under the License. import Modal from './Modal'; import * as sdk from './index'; -import { AccessCancelledError, confirmToDismiss } from "./CrossSigningManager"; +import { + AccessCancelledError, + cacheDehydrationKey, + confirmToDismiss, + getDehydrationKeyCache, +} from "./CrossSigningManager"; import Matrix from "matrix-js-sdk"; import { deriveKey } from 'matrix-js-sdk/src/crypto/key_passphrase'; import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey'; @@ -164,9 +169,6 @@ export default class Login { * @returns {MatrixClientCreds} */ export async function sendLoginRequest(hsUrl, isUrl, loginType, loginParams) { - let rehydrationKeyInfo; - let rehydrationKey; - const client = Matrix.createClient({ baseUrl: hsUrl, idBaseUrl: isUrl, @@ -190,14 +192,16 @@ export async function sendLoginRequest(hsUrl, isUrl, loginType, loginParams) { } } + const dehydrationKeyCache = getDehydrationKeyCache(); + return { homeserverUrl: hsUrl, identityServerUrl: isUrl, userId: data.user_id, deviceId: data.device_id, accessToken: data.access_token, - rehydrationKeyInfo, - rehydrationKey, + rehydrationKeyInfo: dehydrationKeyCache.keyInfo, + rehydrationKey: dehydrationKeyCache.key, olmAccount: data._olm_account, }; } @@ -243,5 +247,7 @@ async function getDehydrationKey(keyInfo) { throw new AccessCancelledError(); } const key = await inputToKey(input); + // need to copy the key because rehydration (unpickling) will clobber it + cacheDehydrationKey(new Uint8Array(key), keyInfo); return key; } diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index 61b7a04069..18af378fac 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -31,7 +31,7 @@ import {verificationMethods} from 'matrix-js-sdk/src/crypto'; import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientBackedSettingsHandler"; import * as StorageManager from './utils/StorageManager'; import IdentityAuthClient from './IdentityAuthClient'; -import { crossSigningCallbacks } from './CrossSigningManager'; +import { cacheDehydrationKey, crossSigningCallbacks } from './CrossSigningManager'; import {SHOW_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode"; export interface IMatrixClientCreds { @@ -270,33 +270,43 @@ class _MatrixClientPeg implements IMatrixClientPeg { }; if (creds.olmAccount) { + console.log("got a dehydrated account"); opts.deviceToImport = { olmDevice: { - pickledAccount: creds.olmAccount.pickle("DEFAULT_KEY"), + pickledAccount: creds.olmAccount.pickle(creds.pickleKey || "DEFAULT_KEY"), sessions: [], - pickleKey: "DEFAULT_KEY", + pickleKey: creds.pickleKey || "DEFAULT_KEY", }, userId: creds.userId, deviceId: creds.deviceId, }; + creds.olmAccount.free(); } else { opts.userId = creds.userId; opts.deviceId = creds.deviceId; } - // FIXME: modify crossSigningCallbacks.getSecretStorageKey so that it tries using rehydrationkey and/or saves the passphrase info - // These are always installed regardless of the labs flag so that // cross-signing features can toggle on without reloading and also be // accessed immediately after login. Object.assign(opts.cryptoCallbacks, crossSigningCallbacks); - this.matrixClient = createMatrixClient(opts); + // set dehydration key after cross-signing gets set up -- we wait until + // cross-signing is set up because we want to cross-sign the dehydrated + // key + const origGetSecretStorageKey = opts.cryptoCallbacks.getSecretStorageKey + opts.cryptoCallbacks.getSecretStorageKey = async (keyinfo, ssssItemName) => { + const [name, key] = await origGetSecretStorageKey(keyinfo, ssssItemName); + this.matrixClient.setDehydrationKey(key, {passphrase: keyinfo.keys[name].passphrase}); + return [name, key]; + } if (creds.rehydrationKey) { - this.matrixClient.cacheDehydrationKey(creds.rehydrationKey, creds.rehydrationKeyInfo || {}); + cacheDehydrationKey(creds.rehydrationKey, creds.rehydrationKeyInfo); } + this.matrixClient = createMatrixClient(opts); + // we're going to add eventlisteners for each matrix event tile, so the // potential number of event listeners is quite high. this.matrixClient.setMaxListeners(500); diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 53b3033330..b1c9dc5a60 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -304,6 +304,11 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }, }); } + const dehydrationKeyInfo = + this._recoveryKey.keyInfo && this._recoveryKey.keyInfo.passphrase + ? {passphrase: this._recoveryKey.keyInfo.passphrase} + : {}; + await cli.setDehydrationKey(this._recoveryKey.privateKey, dehydrationKeyInfo); this.props.onFinished(true); } catch (e) { if (this.state.canUploadKeysWithPasswordOnly && e.httpStatus === 401 && e.data.flows) { From 7c65c592383f9f9e22c400370165ca7f9100a553 Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Thu, 3 Sep 2020 21:06:43 +0000 Subject: [PATCH 0067/1014] Translated using Weblate (Russian) Currently translated at 100.0% (2354 of 2354 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index d2664a9ca5..919077af24 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2445,5 +2445,13 @@ "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Приватные комнаты можно найти и присоединиться только по приглашению. Публичные комнаты могут находить и присоединяться к ним любые участники этого сообщества.", "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Вы можете включить это, если комната будет использоваться только для совместной работы с внутренними командами на вашем домашнем сервере. Это не может быть изменено позже.", "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Вы можете отключить это, если комната будет использоваться для совместной работы с внешними командами, у которых есть собственный домашний сервер. Это не может быть изменено позже.", - "Block anyone not part of %(serverName)s from ever joining this room.": "Запретить кому-либо, не входящему в %(serverName)s, когда-либо присоединяться к этой комнате." + "Block anyone not part of %(serverName)s from ever joining this room.": "Запретить кому-либо, не входящему в %(serverName)s, когда-либо присоединяться к этой комнате.", + "There was an error updating your community. The server is unable to process your request.": "Произошла ошибка в обновлении вашего сообщества. Сервер не может обработать ваш запрос.", + "Update community": "Обновить сообщество", + "May include members not in %(communityName)s": "Может включать участников, не входящих в %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Начните разговор с кем-нибудь, используя имя, имя пользователя (например, ) или адрес электронной почты. Это не пригласит их в %(communityName)s. Чтобы пригласить кого-нибудь в %(communityName)s, нажмите здесь.", + "Failed to find the general chat for this community": "Не удалось найти общий чат для этого сообщества", + "Community settings": "Настройки сообщества", + "User settings": "Пользовательские настройки", + "Community and user menu": "Сообщество и меню пользователя" } From 9ff8c172ef88d08233a23e7d2bc14c3f437a18d1 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Thu, 3 Sep 2020 16:34:26 +0000 Subject: [PATCH 0068/1014] Translated using Weblate (Swedish) Currently translated at 93.0% (2189 of 2354 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 4bc5065bc2..eb416bb2d0 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -283,7 +283,7 @@ "You need to be able to invite users to do that.": "Du behöver kunna bjuda in användare för att göra det där.", "You are not in this room.": "Du är inte i det här rummet.", "You do not have permission to do that in this room.": "Du har inte behörighet att göra det i det här rummet.", - "Fetching third party location failed": "Det gick inte att hämta platsdata från tredje part", + "Fetching third party location failed": "Misslyckades att hämta platsdata från tredje part", "All notifications are currently disabled for all targets.": "Alla aviseringar är för tillfället avstängda för alla mål.", "Uploading report": "Laddar upp rapport", "Sunday": "söndag", @@ -363,7 +363,7 @@ "When I'm invited to a room": "När jag bjuds in till ett rum", "Can't update user notification settings": "Kan inte uppdatera användaraviseringsinställningarna", "Notify for all other messages/rooms": "Avisera för alla andra meddelanden/rum", - "Unable to look up room ID from server": "Det gick inte att hämta rums-ID:t från servern", + "Unable to look up room ID from server": "Kunde inte hämta rums-ID:t från servern", "Couldn't find a matching Matrix room": "Kunde inte hitta ett matchande Matrix-rum", "Invite to this room": "Bjud in till rummet", "Thursday": "torsdag", @@ -371,8 +371,8 @@ "Back": "Tillbaka", "Reply": "Svara", "Show message in desktop notification": "Visa meddelande i skrivbordsavisering", - "Unhide Preview": "Visa förhandsvisning", - "Unable to join network": "Det gick inte att ansluta till nätverket", + "Unhide Preview": "Visa förhandsgranskning", + "Unable to join network": "Kunde inte ansluta till nätverket", "Sorry, your browser is not able to run %(brand)s.": "Beklagar, din webbläsare kan inte köra %(brand)s.", "Messages in group chats": "Meddelanden i gruppchattar", "Yesterday": "igår", @@ -381,7 +381,7 @@ "Unable to fetch notification target list": "Kunde inte hämta aviseringsmållistan", "Set Password": "Välj lösenord", "Off": "Av", - "%(brand)s does not know how to join a room on this network": "%(brand)s kan inte gå med i ett rum på det här nätverket", + "%(brand)s does not know how to join a room on this network": "%(brand)s vet inte hur den ska gå med i ett rum på det här nätverket", "Mentions only": "Endast omnämnande", "Failed to remove tag %(tagName)s from room": "Det gick inte att radera etiketten %(tagName)s från rummet", "You can now return to your account after signing out, and sign in on other devices.": "Du kan nu återgå till ditt konto efter att ha loggat ut, och logga in på andra enheter.", @@ -532,7 +532,7 @@ "Sent messages will be stored until your connection has returned.": "Skickade meddelanden kommer att lagras tills anslutningen är tillbaka.", "There's no one else here! Would you like to invite others or stop warning about the empty room?": "Det är ingen annan här! Vill du bjuda in någon eller sluta varna om det tomma rummet?", "Room": "Rum", - "Clear filter": "Töm filter", + "Clear filter": "Rensa filter", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Försökte ladda en viss punkt i det här rummets tidslinje, men du har inte behörighet att visa det aktuella meddelandet.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Försökte ladda en specifik punkt i det här rummets tidslinje, men kunde inte hitta den.", "Success": "Framgång", @@ -835,7 +835,7 @@ "An error ocurred whilst trying to remove the widget from the room": "Ett fel inträffade vid borttagning av widget från rummet", "Demote yourself?": "Degradera dig själv?", "Demote": "Degradera", - "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "När någon lägger en URL i sitt meddelande, kan URL-förhandsvisning ge mer information om länken, såsom titel, beskrivning, och en bild från webbplatsen.", + "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "När någon lägger en URL i sitt meddelande, kan URL-förhandsgranskning ge mer information om länken, såsom titel, beskrivning, och en bild från webbplatsen.", "You can't send any messages until you review and agree to our terms and conditions.": "Du kan inte skicka några meddelanden innan du granskar och godkänner våra villkor.", "System Alerts": "Systemvarningar", "Sorry, your homeserver is too old to participate in this room.": "Tyvärr, din hemserver är för gammal för att delta i det här rummet.", @@ -1358,10 +1358,10 @@ "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Använd en integrationshanterare för att hantera bottar, widgets och dekalpaket.", "Manage integrations": "Hantera integrationer", "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integrationshanterare får konfigurationsdata och kan ändra widgetar, skicka rumsinbjudningar och ställa in behörighetsnivåer å dina vägnar.", - "Close preview": "Stäng förhandsvisning", + "Close preview": "Stäng förhandsgranskning", "Room %(name)s": "Rum %(name)s", "Recent rooms": "Senaste rummen", - "Loading room preview": "Laddar förhandsvisning av rummet", + "Loading room preview": "Laddar förhandsgranskning av rummet", "Re-join": "Gå med igen", "Try to join anyway": "Försök att gå med ändå", "Join the discussion": "Gå med i diskussionen", @@ -2218,5 +2218,12 @@ "%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "%(brand)s misslyckades att hämta protokollistan från hemservern. Hemservern kan vara för gammal för att stödja tredjepartsnätverk.", "%(brand)s failed to get the public room list.": "%(brand)s misslyckades att hämta listan över offentliga rum.", "The homeserver may be unavailable or overloaded.": "Hemservern kan vara otillgänglig eller överbelastad.", - "Delete the room address %(alias)s and remove %(name)s from the directory?": "Radera rumsadressen %(alias)s och ta bort %(name)s från den här katalogen?" + "Delete the room address %(alias)s and remove %(name)s from the directory?": "Radera rumsadressen %(alias)s och ta bort %(name)s från den här katalogen?", + "delete the address.": "radera adressen.", + "View": "Visa", + "Find a room…": "Hitta ett rum…", + "Find a room… (e.g. %(exampleRoom)s)": "Hitta ett rum… (t.ex. %(exampleRoom)s)", + "If you can't find the room you're looking for, ask for an invite or Create a new room.": "Om du inte hittar rummet du letar efter, be om en inbjudan eller skapa ett nytt rum.", + "Explore rooms in %(communityName)s": "Utforska rum i %(communityName)s", + "Search rooms": "Sök bland rum" } From 46bc6b1d149ed2c93914e5ee9bdfa3eb596df1e7 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 4 Sep 2020 11:37:06 +0100 Subject: [PATCH 0069/1014] Rename key backup to secure backup --- res/css/_components.scss | 2 +- ...ckupPanel.scss => _SecureBackupPanel.scss} | 2 +- ...KeyBackupPanel.js => SecureBackupPanel.js} | 2 +- .../tabs/user/SecurityUserSettingsTab.js | 10 +-- src/i18n/strings/en_EN.json | 62 +++++++++---------- 5 files changed, 39 insertions(+), 39 deletions(-) rename res/css/views/settings/{_KeyBackupPanel.scss => _SecureBackupPanel.scss} (95%) rename src/components/views/settings/{KeyBackupPanel.js => SecureBackupPanel.js} (99%) diff --git a/res/css/_components.scss b/res/css/_components.scss index 45ed6b3300..be1818cf8e 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -199,10 +199,10 @@ @import "./views/settings/_E2eAdvancedPanel.scss"; @import "./views/settings/_EmailAddresses.scss"; @import "./views/settings/_IntegrationManager.scss"; -@import "./views/settings/_KeyBackupPanel.scss"; @import "./views/settings/_Notifications.scss"; @import "./views/settings/_PhoneNumbers.scss"; @import "./views/settings/_ProfileSettings.scss"; +@import "./views/settings/_SecureBackupPanel.scss"; @import "./views/settings/_SetIdServer.scss"; @import "./views/settings/_SetIntegrationManager.scss"; @import "./views/settings/_UpdateCheckButton.scss"; diff --git a/res/css/views/settings/_KeyBackupPanel.scss b/res/css/views/settings/_SecureBackupPanel.scss similarity index 95% rename from res/css/views/settings/_KeyBackupPanel.scss rename to res/css/views/settings/_SecureBackupPanel.scss index 872162caad..3522a81e32 100644 --- a/res/css/views/settings/_KeyBackupPanel.scss +++ b/res/css/views/settings/_SecureBackupPanel.scss @@ -1,6 +1,6 @@ /* Copyright 2018 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js similarity index 99% rename from src/components/views/settings/KeyBackupPanel.js rename to src/components/views/settings/SecureBackupPanel.js index 8a74276f58..35009bc22d 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -23,7 +23,7 @@ import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; import { isSecureBackupRequired } from '../../../utils/WellKnownUtils'; -export default class KeyBackupPanel extends React.PureComponent { +export default class SecureBackupPanel extends React.PureComponent { constructor(props) { super(props); diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index 90dcc0b658..c25ac438e0 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -29,6 +29,7 @@ import {sleep} from "../../../../../utils/promise"; import dis from "../../../../../dispatcher/dispatcher"; import {privateShouldBeEncrypted} from "../../../../../createRoom"; import {SettingLevel} from "../../../../../settings/SettingLevel"; +import SecureBackupPanel from "../../SecureBackupPanel"; export class IgnoredUser extends React.Component { static propTypes = { @@ -288,12 +289,11 @@ export default class SecurityUserSettingsTab extends React.Component { const SettingsFlag = sdk.getComponent('views.elements.SettingsFlag'); const EventIndexPanel = sdk.getComponent('views.settings.EventIndexPanel'); - const KeyBackupPanel = sdk.getComponent('views.settings.KeyBackupPanel'); - const keyBackup = ( + const secureBackup = (
- {_t("Key backup")} + {_t("Secure Backup")}
- +
); @@ -352,7 +352,7 @@ export default class SecurityUserSettingsTab extends React.Component {
{_t("Encryption")}
- {keyBackup} + {secureBackup} {eventIndex} {crossSigning} {this._renderCurrentDeviceInfo()} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 95b6c23a77..464edba6ce 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -697,36 +697,6 @@ "Connecting to integration manager...": "Connecting to integration manager...", "Cannot connect to integration manager": "Cannot connect to integration manager", "The integration manager is offline or it cannot reach your homeserver.": "The integration manager is offline or it cannot reach your homeserver.", - "Delete Backup": "Delete Backup", - "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.", - "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", - "Unable to load key backup status": "Unable to load key backup status", - "Restore from Backup": "Restore from Backup", - "This session is backing up your keys. ": "This session is backing up your keys. ", - "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.", - "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.", - "Connect this session to Key Backup": "Connect this session to Key Backup", - "not stored": "not stored", - "Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...", - "All keys backed up": "All keys backed up", - "Backup has a valid signature from this user": "Backup has a valid signature from this user", - "Backup has a invalid signature from this user": "Backup has a invalid signature from this user", - "Backup has a signature from unknown user with ID %(deviceId)s": "Backup has a signature from unknown user with ID %(deviceId)s", - "Backup has a signature from unknown session with ID %(deviceId)s": "Backup has a signature from unknown session with ID %(deviceId)s", - "Backup has a valid signature from this session": "Backup has a valid signature from this session", - "Backup has an invalid signature from this session": "Backup has an invalid signature from this session", - "Backup has a valid signature from verified session ": "Backup has a valid signature from verified session ", - "Backup has a valid signature from unverified session ": "Backup has a valid signature from unverified session ", - "Backup has an invalid signature from verified session ": "Backup has an invalid signature from verified session ", - "Backup has an invalid signature from unverified session ": "Backup has an invalid signature from unverified session ", - "Backup is not signed by any of your sessions": "Backup is not signed by any of your sessions", - "This backup is trusted because it has been restored on this session": "This backup is trusted because it has been restored on this session", - "Backup version: ": "Backup version: ", - "Algorithm: ": "Algorithm: ", - "Backup key stored: ": "Backup key stored: ", - "Your keys are not being backed up from this session.": "Your keys are not being backed up from this session.", - "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.", - "Start using Key Backup": "Start using Key Backup", "Error saving email notification preferences": "Error saving email notification preferences", "An error occurred whilst saving your email notification preferences.": "An error occurred whilst saving your email notification preferences.", "Keywords": "Keywords", @@ -758,6 +728,36 @@ "Display Name": "Display Name", "Profile picture": "Profile picture", "Save": "Save", + "Delete Backup": "Delete Backup", + "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.", + "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", + "Unable to load key backup status": "Unable to load key backup status", + "Restore from Backup": "Restore from Backup", + "This session is backing up your keys. ": "This session is backing up your keys. ", + "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.", + "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.", + "Connect this session to Key Backup": "Connect this session to Key Backup", + "not stored": "not stored", + "Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...", + "All keys backed up": "All keys backed up", + "Backup has a valid signature from this user": "Backup has a valid signature from this user", + "Backup has a invalid signature from this user": "Backup has a invalid signature from this user", + "Backup has a signature from unknown user with ID %(deviceId)s": "Backup has a signature from unknown user with ID %(deviceId)s", + "Backup has a signature from unknown session with ID %(deviceId)s": "Backup has a signature from unknown session with ID %(deviceId)s", + "Backup has a valid signature from this session": "Backup has a valid signature from this session", + "Backup has an invalid signature from this session": "Backup has an invalid signature from this session", + "Backup has a valid signature from verified session ": "Backup has a valid signature from verified session ", + "Backup has a valid signature from unverified session ": "Backup has a valid signature from unverified session ", + "Backup has an invalid signature from verified session ": "Backup has an invalid signature from verified session ", + "Backup has an invalid signature from unverified session ": "Backup has an invalid signature from unverified session ", + "Backup is not signed by any of your sessions": "Backup is not signed by any of your sessions", + "This backup is trusted because it has been restored on this session": "This backup is trusted because it has been restored on this session", + "Backup version: ": "Backup version: ", + "Algorithm: ": "Algorithm: ", + "Backup key stored: ": "Backup key stored: ", + "Your keys are not being backed up from this session.": "Your keys are not being backed up from this session.", + "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.", + "Start using Key Backup": "Start using Key Backup", "Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS", "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)", "Could not connect to Identity Server": "Could not connect to Identity Server", @@ -904,7 +904,7 @@ "Bulk options": "Bulk options", "Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites", "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites", - "Key backup": "Key backup", + "Secure Backup": "Secure Backup", "Message search": "Message search", "Cross-signing": "Cross-signing", "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.", From e3f47525e55be59888654ee4d80f097e73ec198b Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 4 Sep 2020 11:37:56 +0100 Subject: [PATCH 0070/1014] Rename backup CSS classes --- res/css/views/settings/_SecureBackupPanel.scss | 12 ++++++------ src/components/views/settings/SecureBackupPanel.js | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/res/css/views/settings/_SecureBackupPanel.scss b/res/css/views/settings/_SecureBackupPanel.scss index 3522a81e32..548e72fbc3 100644 --- a/res/css/views/settings/_SecureBackupPanel.scss +++ b/res/css/views/settings/_SecureBackupPanel.scss @@ -15,23 +15,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_KeyBackupPanel_sigValid, .mx_KeyBackupPanel_sigInvalid, -.mx_KeyBackupPanel_deviceVerified, .mx_KeyBackupPanel_deviceNotVerified { +.mx_SecureBackupPanel_sigValid, .mx_SecureBackupPanel_sigInvalid, +.mx_SecureBackupPanel_deviceVerified, .mx_SecureBackupPanel_deviceNotVerified { font-weight: bold; } -.mx_KeyBackupPanel_sigValid, .mx_KeyBackupPanel_deviceVerified { +.mx_SecureBackupPanel_sigValid, .mx_SecureBackupPanel_deviceVerified { color: $e2e-verified-color; } -.mx_KeyBackupPanel_sigInvalid, .mx_KeyBackupPanel_deviceNotVerified { +.mx_SecureBackupPanel_sigInvalid, .mx_SecureBackupPanel_deviceNotVerified { color: $e2e-warning-color; } -.mx_KeyBackupPanel_deviceName { +.mx_SecureBackupPanel_deviceName { font-style: italic; } -.mx_KeyBackupPanel_buttonRow { +.mx_SecureBackupPanel_buttonRow { margin: 1em 0; } diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 35009bc22d..684818aa1d 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -229,14 +229,14 @@ export default class SecureBackupPanel extends React.PureComponent { let backupSigStatuses = this.state.backupSigStatus.sigs.map((sig, i) => { const deviceName = sig.device ? (sig.device.getDisplayName() || sig.device.deviceId) : null; const validity = sub => - + {sub} ; const verify = sub => - + {sub} ; - const device = sub => {deviceName}; + const device = sub => {deviceName}; const fromThisDevice = ( sig.device && sig.device.getFingerprint() === MatrixClientPeg.get().getDeviceEd25519Key() @@ -324,7 +324,7 @@ export default class SecureBackupPanel extends React.PureComponent { } const buttonRow = ( -
+
{restoreButtonCaption}     @@ -355,7 +355,7 @@ export default class SecureBackupPanel extends React.PureComponent {

{encryptedMessageAreEncrypted}

{_t("Back up your keys before signing out to avoid losing them.")}

-
+
{_t("Start using Key Backup")} From 19e9e0137c027cbf1766b1fed4d8c7471be27d61 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 4 Sep 2020 13:05:30 +0100 Subject: [PATCH 0071/1014] Reorganise backup panel so feature description is always present This ensure the feature description is always shown at the top. --- .../views/settings/SecureBackupPanel.js | 77 ++++++++++--------- src/i18n/strings/en_EN.json | 3 +- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 684818aa1d..ec345f6317 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -165,31 +165,30 @@ export default class SecureBackupPanel extends React.PureComponent { render() { const Spinner = sdk.getComponent("elements.Spinner"); const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); - const encryptedMessageAreEncrypted = _t( - "Encrypted messages are secured with end-to-end encryption. " + - "Only you and the recipient(s) have the keys to read these messages.", + const featureDescription = _t( + "Back up your encryption keys with your account data in case you " + + "lose access to your sessions. Your keys will be secured with a " + + "unique Recovery Key.", ); + let statusDescription; + let details; + let actions; if (this.state.error) { - return ( + statusDescription = (
{_t("Unable to load key backup status")}
); } else if (this.state.loading) { - return ; + statusDescription = ; } else if (this.state.backupInfo) { - let clientBackupStatus; let restoreButtonCaption = _t("Restore from Backup"); if (MatrixClientPeg.get().getKeyBackupEnabled()) { - clientBackupStatus =
-

{encryptedMessageAreEncrypted}

-

✅ {_t("This session is backing up your keys. ")}

-
; + statusDescription =

✅ {_t("This session is backing up your keys. ")}

; } else { - clientBackupStatus =
-

{encryptedMessageAreEncrypted}

+ statusDescription = <>

{_t( "This session is not backing up your keys, " + "but you do have an existing backup you can restore from " + @@ -200,7 +199,7 @@ export default class SecureBackupPanel extends React.PureComponent { "Connect this session to key backup before signing out to avoid " + "losing any keys that may only be on this session.", )}

-
; + ; restoreButtonCaption = _t("Connect this session to Key Backup"); } @@ -323,17 +322,7 @@ export default class SecureBackupPanel extends React.PureComponent { ; } - const buttonRow = ( -
- - {restoreButtonCaption} -     - {deleteBackupButton} -
- ); - - return
-
{clientBackupStatus}
+ details = (
{_t("Advanced")}
{_t("Backup version: ")}{this.state.backupInfo.version}
@@ -343,24 +332,40 @@ export default class SecureBackupPanel extends React.PureComponent {
{backupSigStatuses}
{trustedLocally}
- {buttonRow} -
; - } else { - return
-
-

{_t( - "Your keys are not being backed up from this session.", {}, - {b: sub => {sub}}, - )}

-

{encryptedMessageAreEncrypted}

-

{_t("Back up your keys before signing out to avoid losing them.")}

+ ); + + actions = ( +
+ + {restoreButtonCaption} +     + {deleteBackupButton}
+ ); + } else { + statusDescription = <> +

{_t( + "Your keys are not being backed up from this session.", {}, + {b: sub => {sub}}, + )}

+

{_t("Back up your keys before signing out to avoid losing them.")}

+ ; + actions = (
{_t("Start using Key Backup")}
-
; + ); } + + return ( +
+

{featureDescription}

+ {statusDescription} + {details} + {actions} +
+ ); } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 464edba6ce..843373ad07 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -730,7 +730,7 @@ "Save": "Save", "Delete Backup": "Delete Backup", "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.", - "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", + "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.", "Unable to load key backup status": "Unable to load key backup status", "Restore from Backup": "Restore from Backup", "This session is backing up your keys. ": "This session is backing up your keys. ", @@ -1732,6 +1732,7 @@ "Clear cache and resync": "Clear cache and resync", "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!", "Updating %(brand)s": "Updating %(brand)s", + "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", "I don't want my encrypted messages": "I don't want my encrypted messages", "Manually export keys": "Manually export keys", "You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages", From cc2c179a0f429fe2b5890bd9ffcf76f78c67521d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 4 Sep 2020 13:18:57 +0100 Subject: [PATCH 0072/1014] Set default text colour for Settings tabs --- res/css/views/settings/tabs/_SettingsTab.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss index 5f00ed86f7..892f5fe744 100644 --- a/res/css/views/settings/tabs/_SettingsTab.scss +++ b/res/css/views/settings/tabs/_SettingsTab.scss @@ -14,6 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_SettingsTab { + color: $muted-fg-color; +} + .mx_SettingsTab_warningText { color: $warning-color; } From b484bc5e098e72b09d8904042f4cf67540c304db Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 4 Sep 2020 13:52:14 +0100 Subject: [PATCH 0073/1014] Rearrange backup status to always have advanced --- .../views/settings/SecureBackupPanel.js | 68 ++++++++++--------- src/i18n/strings/en_EN.json | 6 +- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index ec345f6317..93e28c8bc9 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -165,24 +165,28 @@ export default class SecureBackupPanel extends React.PureComponent { render() { const Spinner = sdk.getComponent("elements.Spinner"); const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); - const featureDescription = _t( - "Back up your encryption keys with your account data in case you " + - "lose access to your sessions. Your keys will be secured with a " + - "unique Recovery Key.", - ); + + const { + loading, + error, + backupInfo, + backupSigStatus, + backupKeyStored, + sessionsRemaining, + } = this.state; let statusDescription; - let details; + let extraDetails; let actions; - if (this.state.error) { + if (error) { statusDescription = (
{_t("Unable to load key backup status")}
); - } else if (this.state.loading) { + } else if (loading) { statusDescription = ; - } else if (this.state.backupInfo) { + } else if (backupInfo) { let restoreButtonCaption = _t("Restore from Backup"); if (MatrixClientPeg.get().getKeyBackupEnabled()) { @@ -203,15 +207,7 @@ export default class SecureBackupPanel extends React.PureComponent { restoreButtonCaption = _t("Connect this session to Key Backup"); } - let keyStatus; - if (this.state.backupKeyStored === true) { - keyStatus = _t("in secret storage"); - } else { - keyStatus = _t("not stored"); - } - let uploadStatus; - const { sessionsRemaining } = this.state; if (!MatrixClientPeg.get().getKeyBackupEnabled()) { // No upload status to show when backup disabled. uploadStatus = ""; @@ -225,7 +221,7 @@ export default class SecureBackupPanel extends React.PureComponent {
; } - let backupSigStatuses = this.state.backupSigStatus.sigs.map((sig, i) => { + let backupSigStatuses = backupSigStatus.sigs.map((sig, i) => { const deviceName = sig.device ? (sig.device.getDisplayName() || sig.device.deviceId) : null; const validity = sub => @@ -306,12 +302,12 @@ export default class SecureBackupPanel extends React.PureComponent { {sigStatus}
; }); - if (this.state.backupSigStatus.sigs.length === 0) { + if (backupSigStatus.sigs.length === 0) { backupSigStatuses = _t("Backup is not signed by any of your sessions"); } let trustedLocally; - if (this.state.backupSigStatus.trusted_locally) { + if (backupSigStatus.trusted_locally) { trustedLocally = _t("This backup is trusted because it has been restored on this session"); } @@ -322,17 +318,13 @@ export default class SecureBackupPanel extends React.PureComponent { ; } - details = ( -
- {_t("Advanced")} -
{_t("Backup version: ")}{this.state.backupInfo.version}
-
{_t("Algorithm: ")}{this.state.backupInfo.algorithm}
-
{_t("Backup key stored: ")}{keyStatus}
- {uploadStatus} -
{backupSigStatuses}
-
{trustedLocally}
-
- ); + extraDetails = <> +
{_t("Backup version: ")}{backupInfo.version}
+
{_t("Algorithm: ")}{backupInfo.algorithm}
+ {uploadStatus} +
{backupSigStatuses}
+
{trustedLocally}
+ ; actions = (
@@ -361,9 +353,19 @@ export default class SecureBackupPanel extends React.PureComponent { return (
-

{featureDescription}

+

{_t( + "Back up your encryption keys with your account data in case you " + + "lose access to your sessions. Your keys will be secured with a " + + "unique Recovery Key.", + )}

{statusDescription} - {details} +
+ {_t("Advanced")} +
{_t("Backup key stored: ")}{ + backupKeyStored === true ? _t("in secret storage") : _t("not stored") + }
+ {extraDetails} +
{actions}
); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 843373ad07..49e8b1d2e8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -730,14 +730,12 @@ "Save": "Save", "Delete Backup": "Delete Backup", "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.", - "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.", "Unable to load key backup status": "Unable to load key backup status", "Restore from Backup": "Restore from Backup", "This session is backing up your keys. ": "This session is backing up your keys. ", "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.", "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.", "Connect this session to Key Backup": "Connect this session to Key Backup", - "not stored": "not stored", "Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...", "All keys backed up": "All keys backed up", "Backup has a valid signature from this user": "Backup has a valid signature from this user", @@ -754,10 +752,12 @@ "This backup is trusted because it has been restored on this session": "This backup is trusted because it has been restored on this session", "Backup version: ": "Backup version: ", "Algorithm: ": "Algorithm: ", - "Backup key stored: ": "Backup key stored: ", "Your keys are not being backed up from this session.": "Your keys are not being backed up from this session.", "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.", "Start using Key Backup": "Start using Key Backup", + "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.", + "Backup key stored: ": "Backup key stored: ", + "not stored": "not stored", "Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS", "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)", "Could not connect to Identity Server": "Could not connect to Identity Server", From 217f14591021c2718408aac1935132edeae3611e Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 4 Sep 2020 13:55:25 +0100 Subject: [PATCH 0074/1014] Switch to imports in backup panel --- src/components/views/settings/SecureBackupPanel.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 93e28c8bc9..683120f5b6 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -17,11 +17,14 @@ limitations under the License. import React from 'react'; -import * as sdk from '../../../index'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; import { isSecureBackupRequired } from '../../../utils/WellKnownUtils'; +import Spinner from '../elements/Spinner'; +import AccessibleButton from '../elements/AccessibleButton'; +import QuestionDialog from '../dialogs/QuestionDialog'; +import RestoreKeyBackupDialog from '../dialogs/keybackup/RestoreKeyBackupDialog'; export default class SecureBackupPanel extends React.PureComponent { constructor(props) { @@ -135,7 +138,6 @@ export default class SecureBackupPanel extends React.PureComponent { } _deleteBackup = () => { - const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog'); Modal.createTrackedDialog('Delete Backup', '', QuestionDialog, { title: _t('Delete Backup'), description: _t( @@ -155,7 +157,6 @@ export default class SecureBackupPanel extends React.PureComponent { } _restoreBackup = async () => { - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); Modal.createTrackedDialog( 'Restore Backup', '', RestoreKeyBackupDialog, null, null, /* priority = */ false, /* static = */ true, @@ -163,9 +164,6 @@ export default class SecureBackupPanel extends React.PureComponent { } render() { - const Spinner = sdk.getComponent("elements.Spinner"); - const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); - const { loading, error, From 999b5afa0ad9c40a810a39dd2d77a92ed8ab6b09 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 4 Sep 2020 21:41:14 -0600 Subject: [PATCH 0075/1014] Acknowledge the visibility request --- src/FromWidgetPostMessageApi.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/FromWidgetPostMessageApi.js b/src/FromWidgetPostMessageApi.js index d5d7c08d50..bbccc47d28 100644 --- a/src/FromWidgetPostMessageApi.js +++ b/src/FromWidgetPostMessageApi.js @@ -218,6 +218,9 @@ export default class FromWidgetPostMessageApi { if (ActiveWidgetStore.widgetHasCapability(widgetId, Capability.AlwaysOnScreen)) { ActiveWidgetStore.setWidgetPersistence(widgetId, val); } + + // acknowledge + this.sendResponse(event, {}); } else if (action === 'get_openid') { // Handled by caller } else { From b7ef80cd503f3d5349dc6c3ee274672336206f52 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Fri, 4 Sep 2020 18:03:00 +0000 Subject: [PATCH 0076/1014] Translated using Weblate (Swedish) Currently translated at 94.2% (2218 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 75 ++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index eb416bb2d0..7351171228 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -33,7 +33,7 @@ "Ban": "Banna", "Attachment": "Bilaga", "Call Timeout": "Samtalstimeout", - "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Det går inte att ansluta till en hemserver via HTTP då adressen i webbläsaren är HTTPS. Använd HTTPS, eller aktivera osäkra skript.", + "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Kan inte ansluta till en hemserver via HTTP då adressen i webbläsaren är HTTPS. Använd HTTPS, eller aktivera osäkra skript.", "Change Password": "Byt lösenord", "%(senderName)s changed their profile picture.": "%(senderName)s bytte sin profilbild.", "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s bytte rummets namn till %(roomName)s.", @@ -42,9 +42,9 @@ "Changes your display nickname": "Ändrar ditt visningsnamn", "Click here to fix": "Klicka här för att fixa", "Click to mute audio": "Klicka för att tysta ljud", - "Click to mute video": "Klicka för att stänga av video", + "Click to mute video": "Klicka för att tysta videon", "click to reveal": "klicka för att avslöja", - "Click to unmute video": "Klicka för att sätta på video", + "Click to unmute video": "Klicka för att sluta tysta videon", "Click to unmute audio": "Klicka för att sätta på ljud", "Command error": "Kommandofel", "Commands": "Kommandon", @@ -78,11 +78,11 @@ "Failed to join room": "Misslyckades att gå med i rummet", "Failed to kick": "Misslyckades att kicka", "Failed to leave room": "Det gick inte att lämna rummet", - "Failed to load timeline position": "Det gick inte att hämta positionen på tidslinjen", + "Failed to load timeline position": "Misslyckades att hämta positionen på tidslinjen", "Failed to mute user": "Misslyckades att tysta användaren", - "Failed to reject invite": "Det gick inte att avböja inbjudan", + "Failed to reject invite": "Misslyckades att avböja inbjudan", "Failed to reject invitation": "Misslyckades att avböja inbjudan", - "Failed to send email": "Det gick inte att skicka epost", + "Failed to send email": "Misslyckades att skicka e-post", "Failed to send request.": "Misslyckades att sända begäran.", "Failed to set display name": "Misslyckades att ange visningsnamn", "Failed to unban": "Misslyckades att avbanna", @@ -100,7 +100,7 @@ "Decline": "Avvisa", "Drop File Here": "Dra filen hit", "Enter passphrase": "Ange lösenfras", - "Error: Problem communicating with the given homeserver.": "Fel: Det gick inte att kommunicera med den angivna hemservern.", + "Error: Problem communicating with the given homeserver.": "Fel: Problem med att kommunicera med den angivna hemservern.", "Failed to fetch avatar URL": "Det gick inte att hämta avatar-URL", "Failed to upload profile picture!": "Misslyckades att ladda upp profilbild!", "Failure to create room": "Misslyckades att skapa rummet", @@ -116,7 +116,7 @@ "Home": "Hem", "Homeserver is": "Hemservern är", "Identity Server is": "Identitetsservern är", - "I have verified my email address": "Jag har verifierat min epostadress", + "I have verified my email address": "Jag har verifierat min e-postadress", "Import": "Importera", "Import E2E room keys": "Importera rumskrypteringsnycklar", "Incoming call from %(name)s": "Inkommande samtal från %(name)s", @@ -155,7 +155,7 @@ "Mute": "Tysta", "Name": "Namn", "New passwords don't match": "De nya lösenorden matchar inte", - "New passwords must match each other.": "De nya lösenorden måste vara de samma.", + "New passwords must match each other.": "De nya lösenorden måste matcha.", "not specified": "inte specificerad", "Notifications": "Aviseringar", "(not supported by this browser)": "(stöds inte av webbläsaren)", @@ -187,7 +187,7 @@ "Remove": "Ta bort", "%(senderName)s requested a VoIP conference.": "%(senderName)s begärde ett VoIP-gruppsamtal.", "Results from DuckDuckGo": "Resultat från DuckDuckGo", - "Return to login screen": "Tillbaka till login-skärmen", + "Return to login screen": "Tillbaka till inloggningsskärmen", "%(brand)s does not have permission to send you notifications - please check your browser settings": "%(brand)s har inte tillstånd att skicka aviseringar - kontrollera webbläsarens inställningar", "%(brand)s was not given permission to send notifications - please try again": "%(brand)s fick inte tillstånd att skicka aviseringar - försök igen", "%(brand)s version:": "%(brand)s-version:", @@ -205,7 +205,7 @@ "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s skickade en bild.", "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s bjöd in %(targetDisplayName)s att gå med i rummet.", "Server error": "Serverfel", - "Server may be unavailable, overloaded, or search timed out :(": "Servern kan vara otillgänglig, överbelastad, eller så tog sökningen för lång tid :(", + "Server may be unavailable, overloaded, or search timed out :(": "Servern kan vara otillgänglig eller överbelastad, eller så tog sökningen för lång tid :(", "Server may be unavailable, overloaded, or you hit a bug.": "Servern kan vara otillgänglig eller överbelastad, eller så stötte du på en bugg.", "Server unavailable, overloaded, or something else went wrong.": "Servern är otillgänglig eller överbelastad, eller så gick något annat fel.", "Session ID": "Sessions-ID", @@ -239,10 +239,10 @@ "PM": "EM", "Submit": "Skicka in", "The maximum permitted number of widgets have already been added to this room.": "Den största tillåtna mängden widgetar har redan tillagts till rummet.", - "The phone number entered looks invalid": "Det angivna telefonnumret är ogiltigt", + "The phone number entered looks invalid": "Det angivna telefonnumret ser ogiltigt ut", "This email address is already in use": "Den här e-postadressen används redan", "This email address was not found": "Den här e-postadressen finns inte", - "The email address linked to your account must be entered.": "Epostadressen som är kopplad till ditt konto måste anges.", + "The email address linked to your account must be entered.": "E-postadressen som är kopplad till ditt konto måste anges.", "Online": "Online", "Unnamed room": "Namnlöst rum", "World readable": "Alla kan läsa", @@ -487,9 +487,9 @@ "Set a display name:": "Ange ett visningsnamn:", "Upload an avatar:": "Ladda upp en avatar:", "This server does not support authentication with a phone number.": "Denna server har inte support för autentisering via telefonnummer.", - "Uploading %(filename)s and %(count)s others|other": "Laddar upp %(filename)s och %(count)s andra", + "Uploading %(filename)s and %(count)s others|other": "Laddar upp %(filename)s och %(count)s till", "Uploading %(filename)s and %(count)s others|zero": "Laddar upp %(filename)s", - "Uploading %(filename)s and %(count)s others|one": "Laddar upp %(filename)s och %(count)s annan", + "Uploading %(filename)s and %(count)s others|one": "Laddar upp %(filename)s och %(count)s till", "This doesn't appear to be a valid email address": "Det här verkar inte vara en giltig e-postadress", "Verification Pending": "Avvaktar verifiering", "Unable to add email address": "Kunde inte lägga till e-postadress", @@ -526,8 +526,8 @@ "You seem to be in a call, are you sure you want to quit?": "Du verkar vara i ett samtal, är du säker på att du vill avsluta?", "Active call": "Aktivt samtal", "%(count)s of your messages have not been sent.|one": "Ditt meddelande skickades inte.", - "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Skicka om alla eller ångra alla nu. Du kan även välja enskilda meddelanden för att skicka om eller ångra.", - "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|one": "Skicka om meddelande eller ångra meddelande nu.", + "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Skicka om alla eller avbryt alla nu. Du kan även välja enskilda meddelanden för att skicka om eller avbryta.", + "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|one": "Skicka om meddelande eller avbryt meddelande nu.", "Connectivity to the server has been lost.": "Anslutning till servern har brutits.", "Sent messages will be stored until your connection has returned.": "Skickade meddelanden kommer att lagras tills anslutningen är tillbaka.", "There's no one else here! Would you like to invite others or stop warning about the empty room?": "Det är ingen annan här! Vill du bjuda in någon eller sluta varna om det tomma rummet?", @@ -538,7 +538,7 @@ "Success": "Framgång", "Unable to remove contact information": "Kunde inte ta bort kontaktuppgifter", "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Ett e-brev har skickats till %(emailAddress)s. När du har öppnat länken i det, klicka nedan.", - "Please note you are logging into the %(hs)s server, not matrix.org.": "Observera att du loggar in på %(hs)s-servern, inte matrix.org.", + "Please note you are logging into the %(hs)s server, not matrix.org.": "Observera att du loggar in på servern %(hs)s, inte matrix.org.", "This homeserver doesn't offer any login flows which are supported by this client.": "Denna hemserver erbjuder inga inloggningsflöden som stöds av den här klienten.", "Upload new:": "Ladda upp ny:", "Copied!": "Kopierat!", @@ -843,10 +843,10 @@ "Please contact your service administrator to continue using the service.": "Kontakta din tjänstadministratör för att fortsätta använda tjänsten.", "This homeserver has hit its Monthly Active User limit.": "Hemservern har nått sin månatliga gräns för användaraktivitet.", "This homeserver has exceeded one of its resource limits.": "Hemservern har överskridit en av sina resursgränser.", - "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Ditt meddelande skickades inte för hemservern har nått sin månatliga gräns för användaraktivitet. Kontakta din serviceadministratör för att fortsätta använda servicen.", - "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Ditt meddelande skickades inte för hemservern har överskridit en av sina resursgränser. Kontakta din serviceadministratör för att fortsätta använda servicen.", + "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Ditt meddelande skickades inte eftersom hemservern har nått sin månatliga gräns för användaraktivitet. Vänligen kontakta din serviceadministratör för att fortsätta använda tjänsten.", + "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Ditt meddelande skickades inte eftersom hemservern har överskridit en av sina resursgränser. Vänligen kontakta din serviceadministratör för att fortsätta använda tjänsten.", "Legal": "Juridiskt", - "Please contact your service administrator to continue using this service.": "Kontakta din serviceadministratör för att fortsätta använda servicen.", + "Please contact your service administrator to continue using this service.": "Vänligen kontakta din tjänstadministratör för att fortsätta använda tjänsten.", "This room has been replaced and is no longer active.": "Detta rum har ersatts och är inte längre aktivt.", "The conversation continues here.": "Konversationen fortsätter här.", "Only room administrators will see this warning": "Endast rumsadministratörer kommer att se denna varning", @@ -1069,7 +1069,7 @@ "Your password has been reset.": "Ditt lösenord har återställts.", "Set a new password": "Ange ett nytt lösenord", "General failure": "Allmänt fel", - "This homeserver does not support login using email address.": "Denna hemserver stöder inte inloggning med epostadress.", + "This homeserver does not support login using email address.": "Denna hemserver stöder inte inloggning med e-postadress.", "Create account": "Skapa konto", "Registration has been disabled on this homeserver.": "Registrering har inaktiverats på denna hemserver.", "Unable to query for supported registration methods.": "Det gick inte att fråga efter stödda registreringsmetoder.", @@ -2225,5 +2225,34 @@ "Find a room… (e.g. %(exampleRoom)s)": "Hitta ett rum… (t.ex. %(exampleRoom)s)", "If you can't find the room you're looking for, ask for an invite or Create a new room.": "Om du inte hittar rummet du letar efter, be om en inbjudan eller skapa ett nytt rum.", "Explore rooms in %(communityName)s": "Utforska rum i %(communityName)s", - "Search rooms": "Sök bland rum" + "Search rooms": "Sök bland rum", + "You have %(count)s unread notifications in a prior version of this room.|other": "Du har %(count)s olästa aviseringar i en tidigare version av det här rummet.", + "You have %(count)s unread notifications in a prior version of this room.|one": "Du har %(count)s oläst avisering i en tidigare version av det här rummet.", + "Create community": "Skapa gemenskap", + "Failed to find the general chat for this community": "Misslyckades att hitta den allmänna chatten för den här gemenskapen", + "Notification settings": "Aviseringsinställningar", + "All settings": "Alla inställningar", + "Feedback": "Återkoppling", + "Community settings": "Gemenskapsinställningar", + "User settings": "Användarinställningar", + "Switch to light mode": "Byt till ljust läge", + "Switch to dark mode": "Byt till mörkt läge", + "Switch theme": "Byt tema", + "User menu": "Användarmeny", + "Community and user menu": "Gemenskaps- och användarmeny", + "Verify this login": "Verifiera den här inloggningen", + "Session verified": "Session verifierad", + "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Om du ändrar lösenordet så kommer alla nycklar för totalsträckskryptering att återställas på alla dina sessioner, vilket gör krypterad chatthistorik oläslig. Ställ in nyckelsäkerhetskopiering eller exportera dina rumsnycklar från en annan session innan du återställer lösenordet.", + "Your Matrix account on ": "Ditt Matrix-konto på ", + "No identity server is configured: add one in server settings to reset your password.": "Ingen identitetsserver konfigurerad: lägg till en i serverinställningarna för att återställa ditt konto.", + "A verification email will be sent to your inbox to confirm setting your new password.": "Ett e-brev för verifiering skickas till din inkorg för att bekräfta ditt nya lösenord.", + "Invalid homeserver discovery response": "Ogiltigt hemserverupptäcktssvar", + "Failed to get autodiscovery configuration from server": "Misslyckades att få konfiguration för autoupptäckt från servern", + "Invalid base_url for m.homeserver": "Ogiltig base_url för m.homeserver", + "Homeserver URL does not appear to be a valid Matrix homeserver": "Hemserver-URL:en verkar inte vara en giltig Matrix-hemserver", + "Invalid identity server discovery response": "Ogiltigt identitetsserverupptäcktssvar", + "Invalid base_url for m.identity_server": "Ogiltig base_url för m.identity_server", + "Identity server URL does not appear to be a valid identity server": "Identitetsserver-URL:en verkar inte vara en giltig Matrix-identitetsserver", + "This account has been deactivated.": "Det här kontot har avaktiverats.", + "Failed to perform homeserver discovery": "Misslyckades att genomföra hemserverupptäckt" } From d25cb36babed4f775db213907302fa1817299463 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Sat, 5 Sep 2020 11:58:32 +0000 Subject: [PATCH 0077/1014] Translated using Weblate (Swedish) Currently translated at 94.2% (2218 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 7351171228..8bd05474b4 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -93,7 +93,7 @@ "Active call (%(roomName)s)": "Aktiv samtal (%(roomName)s)", "Add": "Lägg till", "Admin Tools": "Admin-verktyg", - "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Det gick inte att ansluta till hemservern - kontrollera anslutningen, se till att hemserverns SSL-certifikat är betrott, och att inget webbläsartillägg blockerar förfrågningar.", + "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Kan inte ansluta till homeservern - kolla din nätverksanslutning, se till att homeserverns SSL certifikat är betrodd, och att ingen webbläsare extension blockerar begäran.", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s ändrade behörighetsnivå för %(powerLevelDiffText)s.", "Close": "Stäng", "Custom": "Egen", From c5927af11f0d8ca0e4d3420a7e867e6b2d2eb846 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Sat, 5 Sep 2020 01:48:58 +0000 Subject: [PATCH 0078/1014] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2355 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 4ec70aa8f7..3348e0cb2c 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2460,5 +2460,6 @@ "Failed to find the general chat for this community": "找不到此社群的一般聊天紀錄", "Community settings": "社群設定", "User settings": "使用者設定", - "Community and user menu": "社群與使用者選單" + "Community and user menu": "社群與使用者選單", + "Privacy": "隱私" } From 54e08fd0209515c99e8d5f2fff4c8ab4c8e26d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Fri, 4 Sep 2020 17:45:37 +0000 Subject: [PATCH 0079/1014] Translated using Weblate (Estonian) Currently translated at 100.0% (2355 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index c617470601..ed9c138931 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2457,5 +2457,6 @@ "Failed to find the general chat for this community": "Ei õnnestunud tuvastada selle kogukonna üldist rühmavestlust", "Community settings": "Kogukonna seadistused", "User settings": "Kasutaja seadistused", - "Community and user menu": "Kogukonna ja kasutaja menüü" + "Community and user menu": "Kogukonna ja kasutaja menüü", + "Privacy": "Privaatsus" } From ea241bbb5e2f48fad8416a3b31d5f03756117f65 Mon Sep 17 00:00:00 2001 From: XoseM Date: Sat, 5 Sep 2020 16:51:25 +0000 Subject: [PATCH 0080/1014] Translated using Weblate (Galician) Currently translated at 100.0% (2355 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 0348203887..b8dcc48b68 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2449,5 +2449,14 @@ "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Poderías desactivalo se a sala vai ser utilizada para colaborar con equipos externos que teñen o seu propio servidor. Esto non se pode cambiar máis tarde.", "Create a room in %(communityName)s": "Crear unha sala en %(communityName)s", "Block anyone not part of %(serverName)s from ever joining this room.": "Evitar que calquera externo a %(serverName)s se poida unir a esta sala.", - "Create community": "Crear comunidade" + "Create community": "Crear comunidade", + "Privacy": "Privacidade", + "There was an error updating your community. The server is unable to process your request.": "Algo fallou ó actualizar a comunidade. O servidor non é quen de procesar a solicitude.", + "Update community": "Actualizar comunidade", + "May include members not in %(communityName)s": "Podería incluir membros que non están en %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Iniciar unha conversa con alguén utilizando o seu nome, nome de usuaria (como ) ou enderezo de email. Esto non as convidará a %(communityName)s. Para convidar a alguén a %(communityName)s, preme aquí.", + "Failed to find the general chat for this community": "Non se atopou o chat xenérico para esta comunidade", + "Community settings": "Axustes da comunidade", + "User settings": "Axustes de usuaria", + "Community and user menu": "Menú de usuaria e comunidade" } From 865280cc35e3e57d2951c9eddf938f7dd68ccf69 Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Sat, 5 Sep 2020 07:58:28 +0000 Subject: [PATCH 0081/1014] Translated using Weblate (Russian) Currently translated at 100.0% (2355 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 919077af24..311cab0a97 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2453,5 +2453,6 @@ "Failed to find the general chat for this community": "Не удалось найти общий чат для этого сообщества", "Community settings": "Настройки сообщества", "User settings": "Пользовательские настройки", - "Community and user menu": "Сообщество и меню пользователя" + "Community and user menu": "Сообщество и меню пользователя", + "Privacy": "Конфиденциальность" } From 40032deb37912058e0545ae1ce5977b621cd8c68 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Sat, 5 Sep 2020 12:00:15 +0000 Subject: [PATCH 0082/1014] Translated using Weblate (Swedish) Currently translated at 100.0% (2355 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 169 +++++++++++++++++++++++++++++++++++---- 1 file changed, 153 insertions(+), 16 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 8bd05474b4..6cbbfd9098 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -93,7 +93,7 @@ "Active call (%(roomName)s)": "Aktiv samtal (%(roomName)s)", "Add": "Lägg till", "Admin Tools": "Admin-verktyg", - "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Kan inte ansluta till homeservern - kolla din nätverksanslutning, se till att homeserverns SSL certifikat är betrodd, och att ingen webbläsare extension blockerar begäran.", + "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Kan inte ansluta till hemservern - vänligen kolla din nätverksanslutning, se till att hemserverns SSL-certifikat är betrott, och att inget webbläsartillägg blockerar förfrågningar.", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s ändrade behörighetsnivå för %(powerLevelDiffText)s.", "Close": "Stäng", "Custom": "Egen", @@ -101,7 +101,7 @@ "Drop File Here": "Dra filen hit", "Enter passphrase": "Ange lösenfras", "Error: Problem communicating with the given homeserver.": "Fel: Problem med att kommunicera med den angivna hemservern.", - "Failed to fetch avatar URL": "Det gick inte att hämta avatar-URL", + "Failed to fetch avatar URL": "Misslyckades att hämta avatar-URL", "Failed to upload profile picture!": "Misslyckades att ladda upp profilbild!", "Failure to create room": "Misslyckades att skapa rummet", "Favourites": "Favoriter", @@ -289,7 +289,7 @@ "Sunday": "söndag", "Messages sent by bot": "Meddelanden från bottar", "Notification targets": "Aviseringsmål", - "Failed to set direct chat tag": "Det gick inte att markera rummet som direkt-chatt", + "Failed to set direct chat tag": "Misslyckades att markera rummet som direktchatt", "Today": "idag", "You are not receiving desktop notifications": "Du får inte skrivbordsaviseringar", "Friday": "fredag", @@ -320,7 +320,7 @@ "An error occurred whilst saving your email notification preferences.": "Ett fel inträffade då e-postaviseringsinställningarna sparades.", "Explore Room State": "Utforska rumsstatus", "Source URL": "Käll-URL", - "Failed to add tag %(tagName)s to room": "Det gick inte att lägga till etiketten \"%(tagName)s\" till rummet", + "Failed to add tag %(tagName)s to room": "Misslyckades att lägga till etiketten %(tagName)s till rummet", "Filter results": "Filtrera resultaten", "Members": "Medlemmar", "No update available.": "Ingen uppdatering tillgänglig.", @@ -383,7 +383,7 @@ "Off": "Av", "%(brand)s does not know how to join a room on this network": "%(brand)s vet inte hur den ska gå med i ett rum på det här nätverket", "Mentions only": "Endast omnämnande", - "Failed to remove tag %(tagName)s from room": "Det gick inte att radera etiketten %(tagName)s från rummet", + "Failed to remove tag %(tagName)s from room": "Misslyckades att radera etiketten %(tagName)s från rummet", "You can now return to your account after signing out, and sign in on other devices.": "Du kan nu återgå till ditt konto efter att ha loggat ut, och logga in på andra enheter.", "Enable email notifications": "Aktivera e-postaviseringar", "Download this file": "Ladda ner filen", @@ -486,7 +486,7 @@ "Code": "Kod", "Set a display name:": "Ange ett visningsnamn:", "Upload an avatar:": "Ladda upp en avatar:", - "This server does not support authentication with a phone number.": "Denna server har inte support för autentisering via telefonnummer.", + "This server does not support authentication with a phone number.": "Denna server stöder inte autentisering via telefonnummer.", "Uploading %(filename)s and %(count)s others|other": "Laddar upp %(filename)s och %(count)s till", "Uploading %(filename)s and %(count)s others|zero": "Laddar upp %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Laddar upp %(filename)s och %(count)s till", @@ -793,8 +793,8 @@ "Learn more about how we use analytics.": "Läs mer om hur vi använder statistik.", "Analytics": "Statistik", "Send analytics data": "Skicka statistik", - "Passphrases must match": "Passfraser måste matcha", - "Passphrase must not be empty": "Lösenfras får inte vara tom", + "Passphrases must match": "Lösenfraser måste matcha", + "Passphrase must not be empty": "Lösenfrasen får inte vara tom", "Confirm passphrase": "Bekräfta lösenfrasen", "%(senderName)s changed the pinned messages for the room.": "%(senderName)s ändrade fastnålade meddelanden för rummet.", "Message Pinning": "Fastnålning av meddelanden", @@ -802,9 +802,9 @@ "No pinned messages.": "Inga fastnålade meddelanden.", "Pinned Messages": "Fastnålade meddelanden", "Pin Message": "Nåla fast meddelande", - "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "Den exporterade filen kommer att låta någon som kan läsa den att dekryptera alla krypterade meddelanden som du kan se, så du bör vara noga med att hålla den säker. För att hjälpa till med detta, bör du ange en lösenfras nedan, som kommer att användas för att kryptera exporterad data. Det kommer bara vara möjligt att importera data genom att använda samma lösenfras.", - "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "Denna process möjliggör import av krypteringsnycklar som tidigare exporterats från en annan Matrix-klient. Du kommer då kunna dekryptera alla meddelanden som den andra klienten kunde dekryptera.", - "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Den exporterade filen kommer vara skyddad med en lösenfras. Du måste ange lösenfrasen här, för att dekryptera filen.", + "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "Den exporterade filen kommer att låta de som kan läsa den att dekryptera alla krypterade meddelanden som du kan se, så du bör vara noga med att hålla den säker. För att hjälpa till med detta, bör du ange en lösenfras nedan, som kommer att användas för att kryptera exporterad data. Det kommer bara vara möjligt att importera data genom att använda samma lösenfras.", + "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "Denna process möjliggör import av krypteringsnycklar som tidigare exporterats från en annan Matrix-klient. Du kommer då kunna avkryptera alla meddelanden som den andra klienten kunde avkryptera.", + "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Den exporterade filen kommer vara skyddad med en lösenfras. Du måste ange lösenfrasen här, för att avkryptera filen.", "Flair": "Emblem", "Showing flair for these communities:": "Visar emblem för dessa gemenskaper:", "This room is not showing flair for any communities": "Detta rum visar inte emblem för några gemenskaper", @@ -1072,7 +1072,7 @@ "This homeserver does not support login using email address.": "Denna hemserver stöder inte inloggning med e-postadress.", "Create account": "Skapa konto", "Registration has been disabled on this homeserver.": "Registrering har inaktiverats på denna hemserver.", - "Unable to query for supported registration methods.": "Det gick inte att fråga efter stödda registreringsmetoder.", + "Unable to query for supported registration methods.": "Kunde inte fråga efter stödda registreringsmetoder.", "Create your account": "Skapa ditt konto", "Retry": "Försök igen", "Don't ask again": "Fråga inte igen", @@ -1504,7 +1504,7 @@ "Navigation": "Navigering", "Calls": "Samtal", "Room List": "Rumslista", - "Autocomplete": "Komplettera automatiskt", + "Autocomplete": "Autokomplettera", "Alt": "Alt", "Alt Gr": "Alt Gr", "Shift": "Shift", @@ -2145,7 +2145,7 @@ "Invalid Recovery Key": "Ogiltig återställningsnyckel", "Security Phrase": "Säkerhetsfras", "Unable to access secret storage. Please verify that you entered the correct recovery passphrase.": "Kunde inte komma åt hemlig lagring. Vänligen verifiera att du matade in rätt återställningslösenfras.", - "Enter your Security Phrase or to continue.": "Mata in din säkerhetsfras eller för att fortsätta.", + "Enter your Security Phrase or to continue.": "Ange din säkerhetsfras eller för att fortsätta.", "Security Key": "Säkerhetsnyckel", "Use your Security Key to continue.": "Använd din säkerhetsnyckel för att fortsätta.", "Restoring keys from backup": "Återställer nycklar från säkerhetskopia", @@ -2160,7 +2160,7 @@ "Keys restored": "Nycklar återställda", "Failed to decrypt %(failedCount)s sessions!": "Misslyckades att avkryptera %(failedCount)s sessioner!", "Successfully restored %(sessionCount)s keys": "Återställde framgångsrikt %(sessionCount)s nycklar", - "Enter recovery passphrase": "Mata in återställningslösenfras", + "Enter recovery passphrase": "Ange återställningslösenfras", "Warning: you should only set up key backup from a trusted computer.": "Varning: Du bör endast sätta upp nyckelsäkerhetskopiering från en betrodd dator.", "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Kom åt din säkra meddelandehistorik och sätt upp säker kommunikation genom att skriva in din återställningslösenfras.", "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "Om du har glömt din återställningslösenfras kan du använda din återställningsnyckel eller ställa in nya återställningsalternativ", @@ -2254,5 +2254,142 @@ "Invalid base_url for m.identity_server": "Ogiltig base_url för m.identity_server", "Identity server URL does not appear to be a valid identity server": "Identitetsserver-URL:en verkar inte vara en giltig Matrix-identitetsserver", "This account has been deactivated.": "Det här kontot har avaktiverats.", - "Failed to perform homeserver discovery": "Misslyckades att genomföra hemserverupptäckt" + "Failed to perform homeserver discovery": "Misslyckades att genomföra hemserverupptäckt", + "Privacy": "Sekretess", + "There was an error updating your community. The server is unable to process your request.": "Ett fel inträffade vid uppdatering av din gemenskap. Serven kan inte behandla din begäran.", + "Update community": "Uppdatera gemenskap", + "May include members not in %(communityName)s": "Kan inkludera medlemmar som inte är i %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Starta en konversation med någon med deras namn, användarnamn (som ) eller e-postadress. Detta kommer inte att bjuda in dem till %(communityName)s. För att bjuda in någon till %(communityName)s, klicka här.", + "Syncing...": "Synkar…", + "Signing In...": "Loggar in…", + "If you've joined lots of rooms, this might take a while": "Om du har gått med i många rum kan det här ta ett tag", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Ditt nya konto (%(newAccountId)s) är registrerat, men du är redan inloggad på ett annat konto (%(loggedInUserId)s).", + "Continue with previous account": "Fortsätt med de tidigare kontot", + "Log in to your new account.": "Logga in i ditt nya konto.", + "You can now close this window or log in to your new account.": "Du kan nu stänga det här fönstret eller logga in i ditt nya konto.", + "Registration Successful": "Registrering lyckades", + "Use Recovery Key or Passphrase": "Använd återställningsnyckel eller -lösenfras", + "Use Recovery Key": "Använd återställningsnyckel", + "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Bekräfta din identitet genom att verifiera den här inloggningen från en av dina andra sessioner och ge den åtkomst till krypterade meddelanden.", + "This requires the latest %(brand)s on your other devices:": "Det här kräver den senaste %(brand)s på dina andra enheter:", + "%(brand)s Web": "%(brand)s webb", + "%(brand)s Desktop": "%(brand)s skrivbord", + "%(brand)s iOS": "%(brand)s iOS", + "%(brand)s Android": "%(brand)s Android", + "or another cross-signing capable Matrix client": "eller en annan Matrix-klient som stöder korssignering", + "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Din nya session har nu verifierats. Den har tillgång till dina krypterade meddelanden, och andra användare kommer att se den som betrodd.", + "Your new session is now verified. Other users will see it as trusted.": "Din nya session har nu verifierats. Andra användare kommer att se den som betrodd.", + "Without completing security on this session, it won’t have access to encrypted messages.": "Utan att slutföra säkerheten på den här sessionen får den inte tillgång till krypterade meddelanden.", + "Failed to re-authenticate due to a homeserver problem": "Misslyckades att återautentisera p.g.a. ett hemserverproblem", + "Failed to re-authenticate": "Misslyckades att återautentisera", + "Regain access to your account and recover encryption keys stored in this session. Without them, you won’t be able to read all of your secure messages in any session.": "Återfå tillgång till ditt konto och återställ krypteringsnycklar som lagrats i den här sessionen. Utan dem kan du inte läsa alla dina säkra meddelanden i någon session.", + "Enter your password to sign in and regain access to your account.": "Ange ditt lösenord för att logga in och återfå tillgång till ditt konto.", + "Forgotten your password?": "Glömt ditt lösenord?", + "Sign in and regain access to your account.": "Logga in och återfå tillgång till ditt konto.", + "You cannot sign in to your account. Please contact your homeserver admin for more information.": "Du kan inte logga in på ditt konto. Vänligen kontakta din hemserveradministratör för mer information.", + "You're signed out": "Du är utloggad", + "Clear personal data": "Rensa personlig information", + "Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.": "Varning: Din personliga information (inklusive krypteringsnycklar) lagras fortfarande i den här sessionen. Rensa den om du är färdig med den här sessionen, eller vill logga in på ett annat konto.", + "Command Autocomplete": "Autokomplettering av kommandon", + "DuckDuckGo Results": "DuckDuckGo-resultat", + "Emoji Autocomplete": "Autokomplettering av emoji", + "Notification Autocomplete": "Autokomplettering av aviseringar", + "Room Autocomplete": "Autokomplettering av rum", + "User Autocomplete": "Autokomplettering av användare", + "Confirm encryption setup": "Bekräfta krypteringsinställning", + "Click the button below to confirm setting up encryption.": "Klicka på knappen nedan för att bekräfta inställning av kryptering.", + "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Skydda mot att förlora åtkomst till krypterade meddelanden och data genom att säkerhetskopiera krypteringsnycklar på din server.", + "Generate a Security Key": "Generera en säkerhetsnyckel", + "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "Vi kommer att generera en säkerhetsnyckel som du kan lagra någonstans säkert, som en lösenordshanterare eller ett kassaskåp.", + "Enter a Security Phrase": "Ange en säkerhetsfras", + "Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "Använd en hemlig fras endast du känner till, och spara valfritt en säkerhetsnyckel att använda för säkerhetskopiering.", + "Enter your account password to confirm the upgrade:": "Ange ditt kontolösenord för att bekräfta uppgraderingen:", + "Restore your key backup to upgrade your encryption": "Återställ din nyckelsäkerhetskopia för att uppgradera din kryptering", + "Restore": "Återställ", + "You'll need to authenticate with the server to confirm the upgrade.": "Du kommer behöva autentisera mot servern för att bekräfta uppgraderingen.", + "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Uppgradera den här sessionen för att låta den verifiera andra sessioner, för att ge dem åtkomst till krypterade meddelanden och markera dem som betrodda för andra användare.", + "Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.": "Ange en säkerhetsfras endast du känner till, eftersom den används för att hålla din data säker. För att vara säker bör inte återanvända ditt kontolösenord.", + "Enter a recovery passphrase": "Ange en återställningslösenfras", + "Great! This recovery passphrase looks strong enough.": "Strålande! Den här återställningslösenfrasen ser stark nog ut.", + "That matches!": "Det matchar!", + "Use a different passphrase?": "Använd en annan lösenfras?", + "That doesn't match.": "Det matchar inte.", + "Go back to set it again.": "Gå tillbaka och sätt den igen.", + "Enter your recovery passphrase a second time to confirm it.": "Ange din återställningslösenfras en gång till för att bekräfta den.", + "Confirm your recovery passphrase": "Bekräfta din återställningslösenfras", + "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.": "Förvara din säkerhetsnyckel någonstans säkert, som en lösenordshanterare eller ett kassaskåp, eftersom den används för att skydda dina krypterade data.", + "Download": "Ladda ner", + "Unable to query secret storage status": "Kunde inte fråga efter status på hemlig lagring", + "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "Om du avbryter nu så kan du förlora krypterade meddelanden och data om du förlorar åtkomst till dina inloggningar.", + "You can also set up Secure Backup & manage your keys in Settings.": "Du kan även ställa in säker säkerhetskopiering och hantera dina nycklar i inställningarna.", + "Set up Secure Backup": "Ställ in säker säkerhetskopiering", + "Upgrade your encryption": "Uppgradera din kryptering", + "Set a Security Phrase": "Sätt en säkerhetsfras", + "Confirm Security Phrase": "Bekräfta säkerhetsfras", + "Save your Security Key": "Spara din säkerhetsnyckel", + "Unable to set up secret storage": "Kunde inte sätta upp hemlig lagring", + "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "Vi lagrar en krypterad kopia av dina nycklar på vår server. Säkra din säkerhetskopia med en återställningslösenfras.", + "For maximum security, this should be different from your account password.": "För maximal säkerhet bör det här skilja sig från ditt kontolösenord.", + "Set up with a recovery key": "Sätt en återställningsnyckel", + "Please enter your recovery passphrase a second time to confirm.": "Vänligen ange din återställningslösenfras en gång till för att bekräfta.", + "Repeat your recovery passphrase...": "Repetera din återställningslösenfras…", + "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Din återställningsnyckel är ett skyddsnät - du kan använda den för att återfå åtkomst till dina krypterade meddelanden om du glömmer din återställningslösenfras.", + "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Förvara en kopia av den någonstans säkert, som en lösenordshanterare eller till och med ett kassaskåp.", + "Your recovery key": "Din återställningsnyckel", + "Your recovery key has been copied to your clipboard, paste it to:": "Din återställningsnyckel har kopierats till ditt klippbord, klistra in den i:", + "Your recovery key is in your Downloads folder.": "Din återställningsnyckel är i din Hämtningar-mapp.", + "Print it and store it somewhere safe": "Skriv ut den och förvara den någonstans säkert", + "Save it on a USB key or backup drive": "Spara den på ett USB-minne eller en säkerhetskopieringshårddisk", + "Copy it to your personal cloud storage": "Kopiera den till din personliga molnlagring", + "Your keys are being backed up (the first backup could take a few minutes).": "Dina nycklar säkerhetskopieras (den första säkerhetskopieringen kan ta några minuter).", + "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Om du inte ställer in säker meddelandeåterställning kommer du inte kunna återställa din krypterade meddelandehistorik om du loggar ut eller använder en annan session.", + "Set up Secure Message Recovery": "Ställ in säker meddelandeåterställning", + "Secure your backup with a recovery passphrase": "Säkra din säkerhetskopia med en återställningslösenfras", + "Make a copy of your recovery key": "Ta en kopia av din återställningsnyckel", + "Starting backup...": "Startar säkerhetskopiering…", + "Success!": "Framgång!", + "Create key backup": "Skapa nyckelsäkerhetskopia", + "Unable to create key backup": "Kunde inte skapa nyckelsäkerhetskopia", + "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Om du inte ställer in säker meddelandeåterställning så kommer du förlora åtkomst till din säkra meddelandehistorik när du loggar ut.", + "If you don't want to set this up now, you can later in Settings.": "Om du inte vill ställa in det här nu så kan du göra det senare i inställningarna.", + "New Recovery Method": "Ny återställningsmetod", + "A new recovery passphrase and key for Secure Messages have been detected.": "En ny återställningslösenfras och -nyckel för säkra meddelanden har hittats.", + "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Om du inte har ställt in den nya återställningsmetoden kan en angripare försöka komma åt ditt konto. Ändra ditt kontolösenord och ställ in en ny återställningsmetod omedelbart i inställningarna.", + "This session is encrypting history using the new recovery method.": "Den här sessionen krypterar historik med den nya återställningsmetoden.", + "Go to Settings": "Gå till inställningarna", + "Set up Secure Messages": "Ställ in säkra meddelanden", + "Recovery Method Removed": "Återställningsmetod borttagen", + "This session has detected that your recovery passphrase and key for Secure Messages have been removed.": "Den här sessionen har detekterat att din återställningslösenfras och -nyckel för säkra meddelanden har tagits bort.", + "If you did this accidentally, you can setup Secure Messages on this session which will re-encrypt this session's message history with a new recovery method.": "Om du gjorde det av misstag kan du ställa in säkra meddelanden på den här sessionen som krypterar sessionens meddelandehistorik igen med en ny återställningsmetod.", + "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Om du inte tog bort återställningsmetoden kan en angripare försöka komma åt ditt konto. Ändra ditt kontolösenord och ställ in en ny återställningsmetod omedelbart i inställningarna.", + "If disabled, messages from encrypted rooms won't appear in search results.": "Om den är inaktiverad visas inte meddelanden från krypterade rum i sökresultaten.", + "Disable": "Inaktivera", + "Not currently indexing messages for any room.": "Indexerar för närvarande inte meddelanden för något rum.", + "Currently indexing: %(currentRoom)s": "Indexerar för närvarande: %(currentRoom)s", + "%(brand)s is securely caching encrypted messages locally for them to appear in search results:": "%(brand)s cachar säkert krypterade meddelanden lokalt för att de ska visas i sökresultat:", + "Message downloading sleep time(ms)": "Vilotid för meddelandenedladdning (ms)", + "Navigate recent messages to edit": "Navigera nyliga meddelanden att redigera", + "Jump to start/end of the composer": "Hoppa till början/slut av meddelanderedigeraren", + "Navigate composer history": "Navigera redigerarhistorik", + "Cancel replying to a message": "Avbryt svar på ett meddelande", + "Toggle microphone mute": "Växla mikrofontystning", + "Toggle video on/off": "Växla video på/av", + "Scroll up/down in the timeline": "Skrolla upp/ner i tidslinjen", + "Dismiss read marker and jump to bottom": "Avfärda läsmarkering och hoppa till botten", + "Jump to oldest unread message": "Hoppa till äldsta olästa meddelandet", + "Upload a file": "Ladda upp en fil", + "Navigate up/down in the room list": "Navigera upp/ner i rumslistan", + "Select room from the room list": "Välj rum från rumslistan", + "Collapse room list section": "Kollapsa rumslistsektionen", + "Expand room list section": "Expandera rumslistsektionen", + "Clear room list filter field": "Rensa filterfältet för rumslistan", + "Previous/next unread room or DM": "Förra/nästa olästa rum eller DM", + "Previous/next room or DM": "Förra/nästa rum eller DM", + "Toggle the top left menu": "Växla menyn högst upp till vänster", + "Close dialog or context menu": "Stäng dialogrutan eller snabbmenyn", + "Activate selected button": "Aktivera den valda knappen", + "Toggle right panel": "Växla högerpanelen", + "Toggle this dialog": "Växla den här dialogrutan", + "Move autocomplete selection up/down": "Flytta autokompletteringssektionen upp/ner", + "Cancel autocomplete": "Stäng autokomplettering" } From 4491d5c47892a4e4971c7a7d3539874488bd00cb Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Mon, 7 Sep 2020 11:29:17 +0000 Subject: [PATCH 0083/1014] Translated using Weblate (German) Currently translated at 100.0% (2355 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 744715bc13..ea65273733 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2447,5 +2447,14 @@ "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Private Räume können nur auf Einladung gefunden und betreten werden. Öffentliche Räume können von jedem/r in dieser Community gefunden und betreten werden.", "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Du solltest dies aktivieren, wenn der Raum nur für die Zusammenarbeit mit internen Teams auf deinem Heimserver verwendet wird. Dies kann später nicht mehr geändert werden.", "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Du solltest dies deaktivieren, wenn der Raum für die Zusammenarbeit mit externen Teams auf deren Home-Server verwendet wird. Dies kann später nicht mehr geändert werden.", - "Block anyone not part of %(serverName)s from ever joining this room.": "Blockiere alle, die nicht Teil von %(serverName)s sind, diesen Raum jemals zu betreten." + "Block anyone not part of %(serverName)s from ever joining this room.": "Blockiere alle, die nicht Teil von %(serverName)s sind, diesen Raum jemals zu betreten.", + "Privacy": "Privatsphäre", + "There was an error updating your community. The server is unable to process your request.": "Beim Aktualisieren deiner Community ist ein Fehler aufgetreten. Der Server kann deine Anfrage nicht verarbeiten.", + "Update community": "Community aktualisieren", + "May include members not in %(communityName)s": "Kann Mitglieder enthalten, die nicht in %(communityName)s enthalten sind", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Starte ein Gespräch mit jemandem unter Verwendung seines/ihres Namens, Nutzernamens (wie ) oder E-Mail-Adresse. Dadurch werden sie nicht zu %(communityName)s eingeladen. Klicke hier hier, um jemanden zu %(communityName)s einzuladen.", + "Failed to find the general chat for this community": "Der allgemeine Chat für diese Community konnte nicht gefunden werden", + "Community settings": "Community-Einstellungen", + "User settings": "Nutzer-Einstellungen", + "Community and user menu": "Community- und Nutzer-Menü" } From a7d41281546e15eaed0ddc15cabd3277b411184a Mon Sep 17 00:00:00 2001 From: Johnny998 <78mikey87@gmail.com> Date: Mon, 7 Sep 2020 08:18:05 +0000 Subject: [PATCH 0084/1014] Translated using Weblate (Slovak) Currently translated at 69.8% (1644 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sk/ --- src/i18n/strings/sk.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sk.json b/src/i18n/strings/sk.json index 667768fd57..5ed562e400 100644 --- a/src/i18n/strings/sk.json +++ b/src/i18n/strings/sk.json @@ -1798,5 +1798,18 @@ "Security & privacy": "Bezpečnosť & súkromie", "All settings": "Všetky nastavenia", "Feedback": "Spätná väzba", - "Indexed rooms:": "Indexované miestnosti:" + "Indexed rooms:": "Indexované miestnosti:", + "Unexpected server error trying to leave the room": "Neočakávaná chyba servera pri pokuse opustiť miestnosť", + "Emoji picker": "Vybrať emoji", + "Send a reply…": "Odoslať odpoveď…", + "Send a message…": "Odoslať správu…", + "Bold": "Tučné", + "Italics": "Kurzíva", + "Strikethrough": "Preškrtnuté", + "Leave Room": "Opustiť miestnosť", + "Direct message": "Priama správa", + "Security": "Zabezpečenie", + "Send a Direct Message": "Poslať priamu správu", + "User menu": "Používateľské menu", + "Toggle Italics": "Prepnúť kurzíva" } From a934878fdb70fd7d35c811dc27dbcd8f181f6336 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Tue, 8 Sep 2020 03:21:26 +0000 Subject: [PATCH 0085/1014] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2356 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 3348e0cb2c..6aa980cd72 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2461,5 +2461,6 @@ "Community settings": "社群設定", "User settings": "使用者設定", "Community and user menu": "社群與使用者選單", - "Privacy": "隱私" + "Privacy": "隱私", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "把 ( ͡° ͜ʖ ͡°) 加在純文字訊息前" } From b2292241f1330beb470e66e6ae83010cc72220ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Mon, 7 Sep 2020 14:43:07 +0000 Subject: [PATCH 0086/1014] Translated using Weblate (Estonian) Currently translated at 100.0% (2356 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index ed9c138931..5500a4bd02 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2458,5 +2458,6 @@ "Community settings": "Kogukonna seadistused", "User settings": "Kasutaja seadistused", "Community and user menu": "Kogukonna ja kasutaja menüü", - "Privacy": "Privaatsus" + "Privacy": "Privaatsus", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Lisa ( ͡° ͜ʖ ͡°) smaili vormindamata sõnumi algusesse" } From 72901ad88cd01f9b366b5e49f6984b0a19c6cd2c Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Mon, 7 Sep 2020 20:50:40 +0000 Subject: [PATCH 0087/1014] Translated using Weblate (German) Currently translated at 100.0% (2356 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index ea65273733..14a87f8308 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2456,5 +2456,6 @@ "Failed to find the general chat for this community": "Der allgemeine Chat für diese Community konnte nicht gefunden werden", "Community settings": "Community-Einstellungen", "User settings": "Nutzer-Einstellungen", - "Community and user menu": "Community- und Nutzer-Menü" + "Community and user menu": "Community- und Nutzer-Menü", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Stellt ( ͡° ͜ʖ ͡°) einer Klartextnachricht voran" } From e891e1ff05c6eaa513b0df866108a7305413015b Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Mon, 7 Sep 2020 13:33:24 +0000 Subject: [PATCH 0088/1014] Translated using Weblate (Russian) Currently translated at 100.0% (2356 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 311cab0a97..203f407ece 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2454,5 +2454,6 @@ "Community settings": "Настройки сообщества", "User settings": "Пользовательские настройки", "Community and user menu": "Сообщество и меню пользователя", - "Privacy": "Конфиденциальность" + "Privacy": "Конфиденциальность", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Добавляет ( ͡° ͜ʖ ͡°) к текстовому сообщению" } From 8e760318714b7877ae71a0178c70424df5e89b17 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Tue, 8 Sep 2020 07:02:33 +0000 Subject: [PATCH 0089/1014] Translated using Weblate (Swedish) Currently translated at 100.0% (2356 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 6cbbfd9098..03a6bc1e2e 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -2391,5 +2391,6 @@ "Toggle right panel": "Växla högerpanelen", "Toggle this dialog": "Växla den här dialogrutan", "Move autocomplete selection up/down": "Flytta autokompletteringssektionen upp/ner", - "Cancel autocomplete": "Stäng autokomplettering" + "Cancel autocomplete": "Stäng autokomplettering", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Lägger till ( ͡° ͜ʖ ͡°) i början på ett textmeddelande" } From 6a304ce16ef079eebb403056fbdd02a6fa89907e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Sep 2020 17:31:32 +0100 Subject: [PATCH 0090/1014] Remove ancient RoomView props.eventPixelOffset --- src/components/structures/LoggedInView.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 33e7c4a238..1ac15caa4c 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -76,7 +76,6 @@ interface IProps { hideToSRUsers: boolean; resizeNotifier: ResizeNotifier; middleDisabled: boolean; - initialEventPixelOffset: number; leftDisabled: boolean; rightDisabled: boolean; // eslint-disable-next-line camelcase @@ -635,7 +634,6 @@ class LoggedInView extends React.Component { thirdPartyInvite={this.props.thirdPartyInvite} oobData={this.props.roomOobData} viaServers={this.props.viaServers} - eventPixelOffset={this.props.initialEventPixelOffset} key={this.props.currentRoomId || 'roomview'} disabled={this.props.middleDisabled} ConferenceHandler={this.props.ConferenceHandler} From 906d4defd5411eb148d1eae47522bd964a37ce8c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Sep 2020 17:32:03 +0100 Subject: [PATCH 0091/1014] Convert RoomView and RoomContext to TS --- .eslintignore.errorfiles | 1 - .../structures/{RoomView.js => RoomView.tsx} | 794 ++++++++++-------- src/contexts/RoomContext.js | 25 - src/contexts/RoomContext.ts | 48 ++ src/utils/ShieldUtils.ts | 14 +- 5 files changed, 482 insertions(+), 400 deletions(-) rename src/components/structures/{RoomView.js => RoomView.tsx} (77%) delete mode 100644 src/contexts/RoomContext.js create mode 100644 src/contexts/RoomContext.ts diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 1faffbbdf7..2e2a404338 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -2,7 +2,6 @@ src/components/structures/RoomDirectory.js src/components/structures/RoomStatusBar.js -src/components/structures/RoomView.js src/components/structures/ScrollPanel.js src/components/structures/SearchBox.js src/components/structures/UploadBar.js diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.tsx similarity index 77% rename from src/components/structures/RoomView.js rename to src/components/structures/RoomView.tsx index d98a19ebe8..05494318b9 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.tsx @@ -21,14 +21,16 @@ limitations under the License. // - Search results component // - Drag and drop -import shouldHideEvent from '../../shouldHideEvent'; - import React, {createRef} from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { _t } from '../../languageHandler'; -import {RoomPermalinkCreator} from '../../utils/permalinks/Permalinks'; +import {Room} from "matrix-js-sdk/src/models/room"; +import {MatrixEvent} from "matrix-js-sdk/src/models/event"; +import {EventSubscription} from "fbemitter"; +import shouldHideEvent from '../../shouldHideEvent'; +import {_t} from '../../languageHandler'; +import {RoomPermalinkCreator} from '../../utils/permalinks/Permalinks'; +import ResizeNotifier from '../../utils/ResizeNotifier'; import ContentMessages from '../../ContentMessages'; import Modal from '../../Modal'; import * as sdk from '../../index'; @@ -39,9 +41,7 @@ import rate_limited_func from '../../ratelimitedfunc'; import * as ObjectUtils from '../../ObjectUtils'; import * as Rooms from '../../Rooms'; import eventSearch, {searchPagination} from '../../Searching'; - import {isOnlyCtrlOrCmdIgnoreShiftKeyEvent, isOnlyCtrlOrCmdKeyEvent, Key} from '../../Keyboard'; - import MainSplit from './MainSplit'; import RightPanel from './RightPanel'; import RoomViewStore from '../../stores/RoomViewStore'; @@ -53,12 +53,25 @@ import RightPanelStore from "../../stores/RightPanelStore"; import {haveTileForEvent} from "../views/rooms/EventTile"; import RoomContext from "../../contexts/RoomContext"; import MatrixClientContext from "../../contexts/MatrixClientContext"; -import { shieldStatusForRoom } from '../../utils/ShieldUtils'; +import {E2EStatus, shieldStatusForRoom} from '../../utils/ShieldUtils'; import {Action} from "../../dispatcher/actions"; import {SettingLevel} from "../../settings/SettingLevel"; +import {IMatrixClientCreds} from "../../MatrixClientPeg"; +import ScrollPanel from "./ScrollPanel"; +import TimelinePanel from "./TimelinePanel"; +import ErrorBoundary from "../views/elements/ErrorBoundary"; +import RoomPreviewBar from "../views/rooms/RoomPreviewBar"; +import ForwardMessage from "../views/rooms/ForwardMessage"; +import SearchBar from "../views/rooms/SearchBar"; +import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar"; +import RoomRecoveryReminder from "../views/rooms/RoomRecoveryReminder"; +import PinnedEventsPanel from "../views/rooms/PinnedEventsPanel"; +import AuxPanel from "../views/rooms/AuxPanel"; +import RoomHeader from "../views/rooms/RoomHeader"; +import TintableSvg from "../views/elements/TintableSvg"; const DEBUG = false; -let debuglog = function() {}; +let debuglog = function(msg: string) {}; const BROWSER_SUPPORTS_SANDBOX = 'sandbox' in document.createElement('iframe'); @@ -67,36 +80,119 @@ if (DEBUG) { debuglog = console.log.bind(console); } -export default class RoomView extends React.Component { - static propTypes = { - ConferenceHandler: PropTypes.any, +interface IProps { + ConferenceHandler?: any; - // Called with the credentials of a registered user (if they were a ROU that - // transitioned to PWLU) - onRegistered: PropTypes.func, - - // An object representing a third party invite to join this room - // Fields: - // * inviteSignUrl (string) The URL used to join this room from an email invite - // (given as part of the link in the invite email) - // * invitedEmail (string) The email address that was invited to this room - thirdPartyInvite: PropTypes.object, - - // Any data about the room that would normally come from the homeserver - // but has been passed out-of-band, eg. the room name and avatar URL - // from an email invite (a workaround for the fact that we can't - // get this information from the HS using an email invite). - // Fields: - // * name (string) The room's name - // * avatarUrl (string) The mxc:// avatar URL for the room - // * inviterName (string) The display name of the person who - // * invited us to the room - oobData: PropTypes.object, - - // Servers the RoomView can use to try and assist joins - viaServers: PropTypes.arrayOf(PropTypes.string), + // An object representing a third party invite to join this room + // Fields: + // * inviteSignUrl (string) The URL used to join this room from an email invite + // (given as part of the link in the invite email) + // * invitedEmail (string) The email address that was invited to this room + thirdPartyInvite?: { + inviteSignUrl: string; + invitedEmail: string; }; + // Any data about the room that would normally come from the homeserver + // but has been passed out-of-band, eg. the room name and avatar URL + // from an email invite (a workaround for the fact that we can't + // get this information from the HS using an email invite). + // Fields: + // * name (string) The room's name + // * avatarUrl (string) The mxc:// avatar URL for the room + // * inviterName (string) The display name of the person who + // * invited us to the room + oobData?: any; + + // Servers the RoomView can use to try and assist joins + viaServers?: string[]; + + autoJoin?: boolean; + disabled?: boolean; + resizeNotifier: ResizeNotifier; + + // Called with the credentials of a registered user (if they were a ROU that transitioned to PWLU) + onRegistered?(credentials: IMatrixClientCreds): void; +} + +export interface IState { + room?: Room; + roomId?: string; + roomAlias?: string; + roomLoading: boolean; + peekLoading: boolean; + shouldPeek: boolean; + // used to trigger a rerender in TimelinePanel once the members are loaded, + // so RR are rendered again (now with the members available), ... + membersLoaded: boolean; + // The event to be scrolled to initially + initialEventId?: string; + // The offset in pixels from the event with which to scroll vertically + initialEventPixelOffset?: number; + // Whether to highlight the event scrolled to + isInitialEventHighlighted?: boolean; + forwardingEvent?: MatrixEvent; + numUnreadMessages: number; + draggingFile: boolean; + searching: boolean; + searchTerm?: string; + searchScope?: "All" | "Room"; + searchResults?: any; + searchHighlights?: string[]; + searchInProgress?: boolean; + callState?: string; + guestsCanJoin: boolean; + canPeek: boolean; + showApps: boolean; + isAlone: boolean; + isPeeking: boolean; + showingPinned: boolean; + showReadReceipts: boolean; + showRightPanel: boolean; + // error object, as from the matrix client/server API + // If we failed to load information about the room, + // store the error here. + roomLoadError?: Error; + // Have we sent a request to join the room that we're waiting to complete? + joining: boolean; + // this is true if we are fully scrolled-down, and are looking at + // the end of the live timeline. It has the effect of hiding the + // 'scroll to bottom' knob, among a couple of other things. + atEndOfLiveTimeline: boolean; + // used by componentDidUpdate to avoid unnecessary checks + atEndOfLiveTimelineInit: boolean; + showTopUnreadMessagesBar: boolean; + auxPanelMaxHeight?: number; + statusBarVisible: boolean; + // We load this later by asking the js-sdk to suggest a version for us. + // This object is the result of Room#getRecommendedVersion() + upgradeRecommendation?: any; + canReact: boolean; + canReply: boolean; + useIRCLayout: boolean; + matrixClientIsReady: boolean; + showUrlPreview?: boolean; + e2eStatus?: E2EStatus; + displayConfCallNotification?: boolean; + rejecting?: boolean; + rejectError?: Error; +} + +export default class RoomView extends React.Component { + private readonly dispatcherRef: string; + private readonly roomStoreToken: EventSubscription; + private readonly rightPanelStoreToken: EventSubscription; + private readonly showReadReceiptsWatchRef: string; + private readonly layoutWatcherRef: string; + + private unmounted = false; + private permalinkCreators: Record = {}; + private searchId: number; + + private roomView = createRef(); + private searchResultsPanel = createRef(); + private messagePanel: TimelinePanel; + static contextType = MatrixClientContext; constructor(props, context) { @@ -104,26 +200,11 @@ export default class RoomView extends React.Component { const llMembers = this.context.hasLazyLoadMembersEnabled(); this.state = { - room: null, roomId: null, roomLoading: true, peekLoading: false, shouldPeek: true, - - // Media limits for uploading. - mediaConfig: undefined, - - // used to trigger a rerender in TimelinePanel once the members are loaded, - // so RR are rendered again (now with the members available), ... membersLoaded: !llMembers, - // The event to be scrolled to initially - initialEventId: null, - // The offset in pixels from the event with which to scroll vertically - initialEventPixelOffset: null, - // Whether to highlight the event scrolled to - isInitialEventHighlighted: null, - - forwardingEvent: null, numUnreadMessages: 0, draggingFile: false, searching: false, @@ -137,36 +218,14 @@ export default class RoomView extends React.Component { showingPinned: false, showReadReceipts: true, showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, - - // error object, as from the matrix client/server API - // If we failed to load information about the room, - // store the error here. - roomLoadError: null, - - // Have we sent a request to join the room that we're waiting to complete? joining: false, - - // this is true if we are fully scrolled-down, and are looking at - // the end of the live timeline. It has the effect of hiding the - // 'scroll to bottom' knob, among a couple of other things. atEndOfLiveTimeline: true, - atEndOfLiveTimelineInit: false, // used by componentDidUpdate to avoid unnecessary checks - + atEndOfLiveTimelineInit: false, showTopUnreadMessagesBar: false, - - auxPanelMaxHeight: undefined, - statusBarVisible: false, - - // We load this later by asking the js-sdk to suggest a version for us. - // This object is the result of Room#getRecommendedVersion() - upgradeRecommendation: null, - canReact: false, canReply: false, - useIRCLayout: SettingsStore.getValue("useIRCLayout"), - matrixClientIsReady: this.context && this.context.isInitialSyncComplete(), }; @@ -184,31 +243,28 @@ export default class RoomView extends React.Component { this.context.on("userTrustStatusChanged", this.onUserVerificationChanged); this.context.on("crossSigning.keysChanged", this.onCrossSigningKeysChanged); // Start listening for RoomViewStore updates - this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); - this._rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this._onRightPanelStoreUpdate); + this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); + this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate); - WidgetEchoStore.on('update', this._onWidgetEchoStoreUpdate); - this._showReadReceiptsWatchRef = SettingsStore.watchSetting("showReadReceipts", null, - this._onReadReceiptsChange); - - this._roomView = createRef(); - this._searchResultsPanel = createRef(); - - this._layoutWatcherRef = SettingsStore.watchSetting("useIRCLayout", null, this.onLayoutChange); + WidgetEchoStore.on('update', this.onWidgetEchoStoreUpdate); + this.showReadReceiptsWatchRef = SettingsStore.watchSetting("showReadReceipts", null, + this.onReadReceiptsChange); + this.layoutWatcherRef = SettingsStore.watchSetting("useIRCLayout", null, this.onLayoutChange); } // TODO: [REACT-WARNING] Move into constructor + // eslint-disable-next-line camelcase UNSAFE_componentWillMount() { - this._onRoomViewStoreUpdate(true); + this.onRoomViewStoreUpdate(true); } - _onReadReceiptsChange = () => { + private onReadReceiptsChange = () => { this.setState({ showReadReceipts: SettingsStore.getValue("showReadReceipts", this.state.roomId), }); }; - _onRoomViewStoreUpdate = initial => { + private onRoomViewStoreUpdate = (initial?: boolean) => { if (this.unmounted) { return; } @@ -230,7 +286,7 @@ export default class RoomView extends React.Component { const roomId = RoomViewStore.getRoomId(); - const newState = { + const newState: Pick = { roomId, roomAlias: RoomViewStore.getRoomAlias(), roomLoading: RoomViewStore.isRoomLoading(), @@ -266,8 +322,8 @@ export default class RoomView extends React.Component { if (initial) { newState.room = this.context.getRoom(newState.roomId); if (newState.room) { - newState.showApps = this._shouldShowApps(newState.room); - this._onRoomLoaded(newState.room); + newState.showApps = this.shouldShowApps(newState.room); + this.onRoomLoaded(newState.room); } } @@ -300,48 +356,47 @@ export default class RoomView extends React.Component { // callback because this would prevent the setStates from being batched, // ie. cause it to render RoomView twice rather than the once that is necessary. if (initial) { - this._setupRoom(newState.room, newState.roomId, newState.joining, newState.shouldPeek); + this.setupRoom(newState.room, newState.roomId, newState.joining, newState.shouldPeek); } }; - _getRoomId() { - // According to `_onRoomViewStoreUpdate`, `state.roomId` can be null + private getRoomId = () => { + // According to `onRoomViewStoreUpdate`, `state.roomId` can be null // if we have a room alias we haven't resolved yet. To work around this, // first we'll try the room object if it's there, and then fallback to // the bare room ID. (We may want to update `state.roomId` after // resolving aliases, so we could always trust it.) return this.state.room ? this.state.room.roomId : this.state.roomId; - } + }; - _getPermalinkCreatorForRoom(room) { - if (!this._permalinkCreators) this._permalinkCreators = {}; - if (this._permalinkCreators[room.roomId]) return this._permalinkCreators[room.roomId]; + private getPermalinkCreatorForRoom(room: Room) { + if (this.permalinkCreators[room.roomId]) return this.permalinkCreators[room.roomId]; - this._permalinkCreators[room.roomId] = new RoomPermalinkCreator(room); + this.permalinkCreators[room.roomId] = new RoomPermalinkCreator(room); if (this.state.room && room.roomId === this.state.room.roomId) { // We want to watch for changes in the creator for the primary room in the view, but // don't need to do so for search results. - this._permalinkCreators[room.roomId].start(); + this.permalinkCreators[room.roomId].start(); } else { - this._permalinkCreators[room.roomId].load(); + this.permalinkCreators[room.roomId].load(); } - return this._permalinkCreators[room.roomId]; + return this.permalinkCreators[room.roomId]; } - _stopAllPermalinkCreators() { - if (!this._permalinkCreators) return; - for (const roomId of Object.keys(this._permalinkCreators)) { - this._permalinkCreators[roomId].stop(); + private stopAllPermalinkCreators() { + if (!this.permalinkCreators) return; + for (const roomId of Object.keys(this.permalinkCreators)) { + this.permalinkCreators[roomId].stop(); } } - _onWidgetEchoStoreUpdate = () => { + private onWidgetEchoStoreUpdate = () => { this.setState({ - showApps: this._shouldShowApps(this.state.room), + showApps: this.shouldShowApps(this.state.room), }); }; - _setupRoom(room, roomId, joining, shouldPeek) { + private setupRoom(room: Room, roomId: string, joining: boolean, shouldPeek: boolean) { // if this is an unknown room then we're in one of three states: // - This is a room we can peek into (search engine) (we can /peek) // - This is a room we can publicly join or were invited to. (we can /join) @@ -374,7 +429,7 @@ export default class RoomView extends React.Component { room: room, peekLoading: false, }); - this._onRoomLoaded(room); + this.onRoomLoaded(room); }).catch((err) => { if (this.unmounted) { return; @@ -405,7 +460,7 @@ export default class RoomView extends React.Component { } } - _shouldShowApps(room) { + private shouldShowApps(room: Room) { if (!BROWSER_SUPPORTS_SANDBOX) return false; // Check if user has previously chosen to hide the app drawer for this @@ -419,13 +474,13 @@ export default class RoomView extends React.Component { } componentDidMount() { - const call = this._getCallForRoom(); + const call = this.getCallForRoom(); const callState = call ? call.call_state : "ended"; this.setState({ callState: callState, }); - this._updateConfCallNotification(); + this.updateConfCallNotification(); window.addEventListener('beforeunload', this.onPageUnload); if (this.props.resizeNotifier) { @@ -442,8 +497,8 @@ export default class RoomView extends React.Component { } componentDidUpdate() { - if (this._roomView.current) { - const roomView = this._roomView.current; + if (this.roomView.current) { + const roomView = this.roomView.current; if (!roomView.ondrop) { roomView.addEventListener('drop', this.onDrop); roomView.addEventListener('dragover', this.onDragOver); @@ -457,10 +512,10 @@ export default class RoomView extends React.Component { // in render() prevents the ref from being set on first mount, so we try and // catch the messagePanel when it does mount. Because we only want the ref once, // we use a boolean flag to avoid duplicate work. - if (this._messagePanel && !this.state.atEndOfLiveTimelineInit) { + if (this.messagePanel && !this.state.atEndOfLiveTimelineInit) { this.setState({ atEndOfLiveTimelineInit: true, - atEndOfLiveTimeline: this._messagePanel.isAtEndOfLiveTimeline(), + atEndOfLiveTimeline: this.messagePanel.isAtEndOfLiveTimeline(), }); } } @@ -474,7 +529,7 @@ export default class RoomView extends React.Component { // update the scroll map before we get unmounted if (this.state.roomId) { - RoomScrollStateStore.setScrollState(this.state.roomId, this._getScrollState()); + RoomScrollStateStore.setScrollState(this.state.roomId, this.getScrollState()); } if (this.state.shouldPeek) { @@ -482,14 +537,14 @@ export default class RoomView extends React.Component { } // stop tracking room changes to format permalinks - this._stopAllPermalinkCreators(); + this.stopAllPermalinkCreators(); - if (this._roomView.current) { + if (this.roomView.current) { // disconnect the D&D event listeners from the room view. This // is really just for hygiene - we're going to be // deleted anyway, so it doesn't matter if the event listeners // don't get cleaned up. - const roomView = this._roomView.current; + const roomView = this.roomView.current; roomView.removeEventListener('drop', this.onDrop); roomView.removeEventListener('dragover', this.onDragOver); roomView.removeEventListener('dragleave', this.onDragLeaveOrEnd); @@ -519,55 +574,54 @@ export default class RoomView extends React.Component { document.removeEventListener("keydown", this.onNativeKeyDown); // Remove RoomStore listener - if (this._roomStoreToken) { - this._roomStoreToken.remove(); + if (this.roomStoreToken) { + this.roomStoreToken.remove(); } // Remove RightPanelStore listener - if (this._rightPanelStoreToken) { - this._rightPanelStoreToken.remove(); + if (this.rightPanelStoreToken) { + this.rightPanelStoreToken.remove(); } - WidgetEchoStore.removeListener('update', this._onWidgetEchoStoreUpdate); + WidgetEchoStore.removeListener('update', this.onWidgetEchoStoreUpdate); - if (this._showReadReceiptsWatchRef) { - SettingsStore.unwatchSetting(this._showReadReceiptsWatchRef); - this._showReadReceiptsWatchRef = null; + if (this.showReadReceiptsWatchRef) { + SettingsStore.unwatchSetting(this.showReadReceiptsWatchRef); } // cancel any pending calls to the rate_limited_funcs - this._updateRoomMembers.cancelPendingCall(); + this.updateRoomMembers.cancelPendingCall(); // no need to do this as Dir & Settings are now overlays. It just burnt CPU. // console.log("Tinter.tint from RoomView.unmount"); // Tinter.tint(); // reset colourscheme - SettingsStore.unwatchSetting(this._layoutWatcherRef); + SettingsStore.unwatchSetting(this.layoutWatcherRef); } - onLayoutChange = () => { + private onLayoutChange = () => { this.setState({ useIRCLayout: SettingsStore.getValue("useIRCLayout"), }); }; - _onRightPanelStoreUpdate = () => { + private onRightPanelStoreUpdate = () => { this.setState({ showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, }); }; - onPageUnload = event => { + private onPageUnload = event => { if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) { return event.returnValue = _t("You seem to be uploading files, are you sure you want to quit?"); - } else if (this._getCallForRoom() && this.state.callState !== 'ended') { + } else if (this.getCallForRoom() && this.state.callState !== 'ended') { return event.returnValue = _t("You seem to be in a call, are you sure you want to quit?"); } }; // we register global shortcuts here, they *must not conflict* with local shortcuts elsewhere or both will fire - onNativeKeyDown = ev => { + private onNativeKeyDown = ev => { let handled = false; const ctrlCmdOnly = isOnlyCtrlOrCmdKeyEvent(ev); @@ -593,13 +647,13 @@ export default class RoomView extends React.Component { } }; - onReactKeyDown = ev => { + private onReactKeyDown = ev => { let handled = false; switch (ev.key) { case Key.ESCAPE: if (!ev.altKey && !ev.ctrlKey && !ev.shiftKey && !ev.metaKey) { - this._messagePanel.forgetReadMarker(); + this.messagePanel.forgetReadMarker(); this.jumpToLiveTimeline(); handled = true; } @@ -624,20 +678,21 @@ export default class RoomView extends React.Component { } }; - onAction = payload => { + private onAction = payload => { switch (payload.action) { case 'message_send_failed': case 'message_sent': - this._checkIfAlone(this.state.room); + this.checkIfAlone(this.state.room); break; case 'post_sticker_message': - this.injectSticker( - payload.data.content.url, - payload.data.content.info, - payload.data.description || payload.data.name); - break; + this.injectSticker( + payload.data.content.url, + payload.data.content.info, + payload.data.description || payload.data.name); + break; case 'picture_snapshot': - ContentMessages.sharedInstance().sendContentListToRoom([payload.file], this.state.room.roomId, this.context); + ContentMessages.sharedInstance().sendContentListToRoom( + [payload.file], this.state.room.roomId, this.context); break; case 'notifier_enabled': case 'upload_started': @@ -645,7 +700,7 @@ export default class RoomView extends React.Component { case 'upload_canceled': this.forceUpdate(); break; - case 'call_state': + case 'call_state': { // don't filter out payloads for room IDs other than props.room because // we may be interested in the conf 1:1 room @@ -653,24 +708,22 @@ export default class RoomView extends React.Component { return; } - var call = this._getCallForRoom(); - var callState; + const call = this.getCallForRoom(); + let callState = "ended"; if (call) { callState = call.call_state; - } else { - callState = "ended"; } // possibly remove the conf call notification if we're now in // the conf - this._updateConfCallNotification(); + this.updateConfCallNotification(); this.setState({ callState: callState, }); - break; + } case 'appsDrawer': this.setState({ showApps: payload.show, @@ -703,14 +756,14 @@ export default class RoomView extends React.Component { matrixClientIsReady: this.context && this.context.isInitialSyncComplete(), }, () => { // send another "initial" RVS update to trigger peeking if needed - this._onRoomViewStoreUpdate(true); + this.onRoomViewStoreUpdate(true); }); } break; } }; - onRoomTimeline = (ev, room, toStartOfTimeline, removed, data) => { + private onRoomTimeline = (ev: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed, data) => { if (this.unmounted) return; // ignore events for other rooms @@ -721,11 +774,11 @@ export default class RoomView extends React.Component { if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return; if (ev.getType() === "org.matrix.room.preview_urls") { - this._updatePreviewUrlVisibility(room); + this.updatePreviewUrlVisibility(room); } if (ev.getType() === "m.room.encryption") { - this._updateE2EStatus(room); + this.updateE2EStatus(room); } // ignore anything but real-time updates at the end of the room: @@ -748,49 +801,49 @@ export default class RoomView extends React.Component { } }; - onRoomName = room => { + private onRoomName = (room: Room) => { if (this.state.room && room.roomId == this.state.room.roomId) { this.forceUpdate(); } }; - onRoomRecoveryReminderDontAskAgain = () => { + private onRoomRecoveryReminderDontAskAgain = () => { // Called when the option to not ask again is set: // force an update to hide the recovery reminder this.forceUpdate(); }; - onKeyBackupStatus = () => { + private onKeyBackupStatus = () => { // Key backup status changes affect whether the in-room recovery // reminder is displayed. this.forceUpdate(); }; - canResetTimeline = () => { - if (!this._messagePanel) { + public canResetTimeline = () => { + if (!this.messagePanel) { return true; } - return this._messagePanel.canResetTimeline(); + return this.messagePanel.canResetTimeline(); }; // called when state.room is first initialised (either at initial load, // after a successful peek, or after we join the room). - _onRoomLoaded = room => { - this._calculatePeekRules(room); - this._updatePreviewUrlVisibility(room); - this._loadMembersIfJoined(room); - this._calculateRecommendedVersion(room); - this._updateE2EStatus(room); - this._updatePermissions(room); + private onRoomLoaded = (room: Room) => { + this.calculatePeekRules(room); + this.updatePreviewUrlVisibility(room); + this.loadMembersIfJoined(room); + this.calculateRecommendedVersion(room); + this.updateE2EStatus(room); + this.updatePermissions(room); }; - async _calculateRecommendedVersion(room) { + private async calculateRecommendedVersion(room: Room) { this.setState({ upgradeRecommendation: await room.getRecommendedVersion(), }); } - async _loadMembersIfJoined(room) { + private async loadMembersIfJoined(room: Room) { // lazy load members if enabled if (this.context.hasLazyLoadMembersEnabled()) { if (room && room.getMyMembership() === 'join') { @@ -809,7 +862,7 @@ export default class RoomView extends React.Component { } } - _calculatePeekRules(room) { + private calculatePeekRules(room: Room) { const guestAccessEvent = room.currentState.getStateEvents("m.room.guest_access", ""); if (guestAccessEvent && guestAccessEvent.getContent().guest_access === "can_join") { this.setState({ @@ -825,7 +878,7 @@ export default class RoomView extends React.Component { } } - _updatePreviewUrlVisibility({roomId}) { + private updatePreviewUrlVisibility({roomId}: Room) { // URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit const key = this.context.isRoomEncrypted(roomId) ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled'; this.setState({ @@ -833,41 +886,41 @@ export default class RoomView extends React.Component { }); } - onRoom = room => { + private onRoom = (room: Room) => { if (!room || room.roomId !== this.state.roomId) { return; } this.setState({ room: room, }, () => { - this._onRoomLoaded(room); + this.onRoomLoaded(room); }); }; - onDeviceVerificationChanged = (userId, device) => { + private onDeviceVerificationChanged = (userId: string, device: object) => { const room = this.state.room; if (!room.currentState.getMember(userId)) { return; } - this._updateE2EStatus(room); + this.updateE2EStatus(room); }; - onUserVerificationChanged = (userId, _trustStatus) => { + private onUserVerificationChanged = (userId: string, trustStatus: object) => { const room = this.state.room; if (!room || !room.currentState.getMember(userId)) { return; } - this._updateE2EStatus(room); + this.updateE2EStatus(room); }; - onCrossSigningKeysChanged = () => { + private onCrossSigningKeysChanged = () => { const room = this.state.room; if (room) { - this._updateE2EStatus(room); + this.updateE2EStatus(room); } }; - async _updateE2EStatus(room) { + private async updateE2EStatus(room: Room) { if (!this.context.isRoomEncrypted(room.roomId)) { return; } @@ -876,7 +929,7 @@ export default class RoomView extends React.Component { // so we don't know what the answer is. Let's error on the safe side and show // a warning for this case. this.setState({ - e2eStatus: "warning", + e2eStatus: E2EStatus.Warning, }); return; } @@ -887,7 +940,7 @@ export default class RoomView extends React.Component { }); } - updateTint() { + private updateTint() { const room = this.state.room; if (!room) return; @@ -896,15 +949,15 @@ export default class RoomView extends React.Component { Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color); } - onAccountData = event => { + private onAccountData = (event: MatrixEvent) => { const type = event.getType(); if ((type === "org.matrix.preview_urls" || type === "im.vector.web.settings") && this.state.room) { // non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls` - this._updatePreviewUrlVisibility(this.state.room); + this.updatePreviewUrlVisibility(this.state.room); } }; - onRoomAccountData = (event, room) => { + private onRoomAccountData = (event: MatrixEvent, room: Room) => { if (room.roomId == this.state.roomId) { const type = event.getType(); if (type === "org.matrix.room.color_scheme") { @@ -914,21 +967,21 @@ export default class RoomView extends React.Component { Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color); } else if (type === "org.matrix.room.preview_urls" || type === "im.vector.web.settings") { // non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls` - this._updatePreviewUrlVisibility(room); + this.updatePreviewUrlVisibility(room); } } }; - onRoomStateEvents = (ev, state) => { + private onRoomStateEvents = (ev: MatrixEvent, state) => { // ignore if we don't have a room yet if (!this.state.room || this.state.room.roomId !== state.roomId) { return; } - this._updatePermissions(this.state.room); + this.updatePermissions(this.state.room); }; - onRoomStateMember = (ev, state, member) => { + private onRoomStateMember = (ev: MatrixEvent, state, member) => { // ignore if we don't have a room yet if (!this.state.room) { return; @@ -939,18 +992,18 @@ export default class RoomView extends React.Component { return; } - this._updateRoomMembers(member); + this.updateRoomMembers(member); }; - onMyMembership = (room, membership, oldMembership) => { + private onMyMembership = (room: Room, membership: string, oldMembership: string) => { if (room.roomId === this.state.roomId) { this.forceUpdate(); - this._loadMembersIfJoined(room); - this._updatePermissions(room); + this.loadMembersIfJoined(room); + this.updatePermissions(room); } }; - _updatePermissions(room) { + private updatePermissions(room: Room) { if (room) { const me = this.context.getUserId(); const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent("m.reaction", me); @@ -960,13 +1013,12 @@ export default class RoomView extends React.Component { } } - // rate limited because a power level change will emit an event for every - // member in the room. - _updateRoomMembers = rate_limited_func((dueToMember) => { + // rate limited because a power level change will emit an event for every member in the room. + private updateRoomMembers = rate_limited_func((dueToMember) => { // a member state changed in this room // refresh the conf call notification state - this._updateConfCallNotification(); - this._updateDMState(); + this.updateConfCallNotification(); + this.updateDMState(); let memberCountInfluence = 0; if (dueToMember && dueToMember.membership === "invite" && this.state.room.getInvitedMemberCount() === 0) { @@ -974,15 +1026,15 @@ export default class RoomView extends React.Component { // count by 1 to counteract this. memberCountInfluence = 1; } - this._checkIfAlone(this.state.room, memberCountInfluence); + this.checkIfAlone(this.state.room, memberCountInfluence); - this._updateE2EStatus(this.state.room); + this.updateE2EStatus(this.state.room); }, 500); - _checkIfAlone(room, countInfluence) { + private checkIfAlone(room: Room, countInfluence?: number) { let warnedAboutLonelyRoom = false; if (localStorage) { - warnedAboutLonelyRoom = localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId); + warnedAboutLonelyRoom = !!localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId); } if (warnedAboutLonelyRoom) { if (this.state.isAlone) this.setState({isAlone: false}); @@ -994,7 +1046,7 @@ export default class RoomView extends React.Component { this.setState({isAlone: joinedOrInvitedMemberCount === 1}); } - _updateConfCallNotification() { + private updateConfCallNotification() { const room = this.state.room; if (!room || !this.props.ConferenceHandler) { return; @@ -1018,7 +1070,7 @@ export default class RoomView extends React.Component { }); } - _updateDMState() { + private updateDMState() { const room = this.state.room; if (room.getMyMembership() != "join") { return; @@ -1029,7 +1081,7 @@ export default class RoomView extends React.Component { } } - onSearchResultsFillRequest = backwards => { + private onSearchResultsFillRequest = (backwards: boolean) => { if (!backwards) { return Promise.resolve(false); } @@ -1037,14 +1089,14 @@ export default class RoomView extends React.Component { if (this.state.searchResults.next_batch) { debuglog("requesting more search results"); const searchPromise = searchPagination(this.state.searchResults); - return this._handleSearchResult(searchPromise); + return this.handleSearchResult(searchPromise); } else { debuglog("no more search results"); return Promise.resolve(false); } }; - onInviteButtonClick = () => { + private onInviteButtonClick = () => { // call AddressPickerDialog dis.dispatch({ action: 'view_invite', @@ -1053,14 +1105,14 @@ export default class RoomView extends React.Component { this.setState({isAlone: false}); // there's a good chance they'll invite someone }; - onStopAloneWarningClick = () => { + private onStopAloneWarningClick = () => { if (localStorage) { - localStorage.setItem('mx_user_alone_warned_' + this.state.room.roomId, true); + localStorage.setItem('mx_user_alone_warned_' + this.state.room.roomId, String(true)); } this.setState({isAlone: false}); }; - onJoinButtonClicked = ev => { + private onJoinButtonClicked = () => { // If the user is a ROU, allow them to transition to a PWLU if (this.context && this.context.isGuest()) { // Join this room once the user has registered and logged in @@ -1069,7 +1121,7 @@ export default class RoomView extends React.Component { action: 'do_after_sync_prepared', deferred_action: { action: 'view_room', - room_id: this._getRoomId(), + room_id: this.getRoomId(), }, }); @@ -1121,8 +1173,8 @@ export default class RoomView extends React.Component { } }; - onMessageListScroll = ev => { - if (this._messagePanel.isAtEndOfLiveTimeline()) { + private onMessageListScroll = ev => { + if (this.messagePanel.isAtEndOfLiveTimeline()) { this.setState({ numUnreadMessages: 0, atEndOfLiveTimeline: true, @@ -1132,10 +1184,10 @@ export default class RoomView extends React.Component { atEndOfLiveTimeline: false, }); } - this._updateTopUnreadMessagesBar(); + this.updateTopUnreadMessagesBar(); }; - onDragOver = ev => { + private onDragOver = ev => { ev.stopPropagation(); ev.preventDefault(); @@ -1154,7 +1206,7 @@ export default class RoomView extends React.Component { } }; - onDrop = ev => { + private onDrop = ev => { ev.stopPropagation(); ev.preventDefault(); ContentMessages.sharedInstance().sendContentListToRoom( @@ -1164,13 +1216,13 @@ export default class RoomView extends React.Component { dis.fire(Action.FocusComposer); }; - onDragLeaveOrEnd = ev => { + private onDragLeaveOrEnd = ev => { ev.stopPropagation(); ev.preventDefault(); this.setState({ draggingFile: false }); }; - injectSticker(url, info, text) { + private injectSticker(url, info, text) { if (this.context.isGuest()) { dis.dispatch({action: 'require_registration'}); return; @@ -1185,7 +1237,7 @@ export default class RoomView extends React.Component { }); } - onSearch = (term, scope) => { + private onSearch = (term: string, scope) => { this.setState({ searchTerm: term, searchScope: scope, @@ -1195,8 +1247,8 @@ export default class RoomView extends React.Component { // if we already have a search panel, we need to tell it to forget // about its scroll state. - if (this._searchResultsPanel.current) { - this._searchResultsPanel.current.resetScrollState(); + if (this.searchResultsPanel.current) { + this.searchResultsPanel.current.resetScrollState(); } // make sure that we don't end up showing results from @@ -1210,12 +1262,10 @@ export default class RoomView extends React.Component { debuglog("sending search request"); const searchPromise = eventSearch(term, roomId); - this._handleSearchResult(searchPromise); + this.handleSearchResult(searchPromise); }; - _handleSearchResult(searchPromise) { - const self = this; - + private handleSearchResult(searchPromise: Promise) { // keep a record of the current search id, so that if the search terms // change before we get a response, we can ignore the results. const localSearchId = this.searchId; @@ -1224,9 +1274,9 @@ export default class RoomView extends React.Component { searchInProgress: true, }); - return searchPromise.then(function(results) { + return searchPromise.then((results) => { debuglog("search complete"); - if (self.unmounted || !self.state.searching || self.searchId != localSearchId) { + if (this.unmounted || !this.state.searching || this.searchId != localSearchId) { console.error("Discarding stale search results"); return; } @@ -1238,8 +1288,8 @@ export default class RoomView extends React.Component { // whether it was used by the search engine or not. let highlights = results.highlights; - if (highlights.indexOf(self.state.searchTerm) < 0) { - highlights = highlights.concat(self.state.searchTerm); + if (highlights.indexOf(this.state.searchTerm) < 0) { + highlights = highlights.concat(this.state.searchTerm); } // For overlapping highlights, @@ -1248,25 +1298,26 @@ export default class RoomView extends React.Component { return b.length - a.length; }); - self.setState({ + this.setState({ searchHighlights: highlights, searchResults: results, }); - }, function(error) { + }, (error) => { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Search failed", error); Modal.createTrackedDialog('Search failed', '', ErrorDialog, { title: _t("Search failed"), - description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or search timed out :(")), + description: ((error && error.message) ? error.message : + _t("Server may be unavailable, overloaded, or search timed out :(")), }); - }).finally(function() { - self.setState({ + }).finally(() => { + this.setState({ searchInProgress: false, }); }); } - getSearchResultTiles() { + private getSearchResultTiles() { const SearchResultTile = sdk.getComponent('rooms.SearchResultTile'); const Spinner = sdk.getComponent("elements.Spinner"); @@ -1277,20 +1328,20 @@ export default class RoomView extends React.Component { if (this.state.searchInProgress) { ret.push(
  • - -
  • ); + + ); } if (!this.state.searchResults.next_batch) { if (this.state.searchResults.results.length == 0) { ret.push(
  • -

    { _t("No results") }

    -
  • , +

    { _t("No results") }

    + , ); } else { ret.push(
  • -

    { _t("No more results") }

    -
  • , +

    { _t("No more results") }

    + , ); } } @@ -1298,7 +1349,7 @@ export default class RoomView extends React.Component { // once dynamic content in the search results load, make the scrollPanel check // the scroll offsets. const onHeightChanged = () => { - const scrollPanel = this._searchResultsPanel.current; + const scrollPanel = this.searchResultsPanel.current; if (scrollPanel) { scrollPanel.checkScroll(); } @@ -1330,36 +1381,38 @@ export default class RoomView extends React.Component { if (this.state.searchScope === 'All') { if (roomId !== lastRoomId) { ret.push(
  • -

    { _t("Room") }: { room.name }

    -
  • ); +

    { _t("Room") }: { room.name }

    + ); lastRoomId = roomId; } } const resultLink = "#/room/"+roomId+"/"+mxEv.getId(); - ret.push(); + ret.push(); } return ret; } - onPinnedClick = () => { + private onPinnedClick = () => { const nowShowingPinned = !this.state.showingPinned; const roomId = this.state.room.roomId; this.setState({showingPinned: nowShowingPinned, searching: false}); SettingsStore.setValue("PinnedEvents.isOpen", roomId, SettingLevel.ROOM_DEVICE, nowShowingPinned); }; - onSettingsClick = () => { + private onSettingsClick = () => { dis.dispatch({ action: 'open_room_settings' }); }; - onCancelClick = () => { + private onCancelClick = () => { console.log("updateTint from onCancelClick"); this.updateTint(); if (this.state.forwardingEvent) { @@ -1371,31 +1424,30 @@ export default class RoomView extends React.Component { dis.fire(Action.FocusComposer); }; - onLeaveClick = () => { + private onLeaveClick = () => { dis.dispatch({ action: 'leave_room', room_id: this.state.room.roomId, }); }; - onForgetClick = () => { + private onForgetClick = () => { dis.dispatch({ action: 'forget_room', room_id: this.state.room.roomId, }); }; - onRejectButtonClicked = ev => { - const self = this; + private onRejectButtonClicked = ev => { this.setState({ rejecting: true, }); - this.context.leave(this.state.roomId).then(function() { + this.context.leave(this.state.roomId).then(() => { dis.dispatch({ action: 'view_next_room' }); - self.setState({ + this.setState({ rejecting: false, }); - }, function(error) { + }, (error) => { console.error("Failed to reject invite: %s", error); const msg = error.message ? error.message : JSON.stringify(error); @@ -1405,14 +1457,14 @@ export default class RoomView extends React.Component { description: msg, }); - self.setState({ + this.setState({ rejecting: false, rejectError: error, }); }); }; - onRejectAndIgnoreClick = async () => { + private onRejectAndIgnoreClick = async () => { this.setState({ rejecting: true, }); @@ -1439,14 +1491,14 @@ export default class RoomView extends React.Component { description: msg, }); - self.setState({ + this.setState({ rejecting: false, rejectError: error, }); } }; - onRejectThreepidInviteButtonClicked = ev => { + private onRejectThreepidInviteButtonClicked = ev => { // We can reject 3pid invites in the same way that we accept them, // using /leave rather than /join. In the short term though, we // just ignore them. @@ -1454,14 +1506,14 @@ export default class RoomView extends React.Component { dis.fire(Action.ViewRoomDirectory); }; - onSearchClick = () => { + private onSearchClick = () => { this.setState({ searching: !this.state.searching, showingPinned: false, }); }; - onCancelSearchClick = () => { + private onCancelSearchClick = () => { this.setState({ searching: false, searchResults: null, @@ -1469,29 +1521,29 @@ export default class RoomView extends React.Component { }; // jump down to the bottom of this room, where new events are arriving - jumpToLiveTimeline = () => { - this._messagePanel.jumpToLiveTimeline(); + private jumpToLiveTimeline = () => { + this.messagePanel.jumpToLiveTimeline(); dis.fire(Action.FocusComposer); }; // jump up to wherever our read marker is - jumpToReadMarker = () => { - this._messagePanel.jumpToReadMarker(); + private jumpToReadMarker = () => { + this.messagePanel.jumpToReadMarker(); }; // update the read marker to match the read-receipt - forgetReadMarker = ev => { + private forgetReadMarker = ev => { ev.stopPropagation(); - this._messagePanel.forgetReadMarker(); + this.messagePanel.forgetReadMarker(); }; // decide whether or not the top 'unread messages' bar should be shown - _updateTopUnreadMessagesBar = () => { - if (!this._messagePanel) { + private updateTopUnreadMessagesBar = () => { + if (!this.messagePanel) { return; } - const showBar = this._messagePanel.canJumpToReadMarker(); + const showBar = this.messagePanel.canJumpToReadMarker(); if (this.state.showTopUnreadMessagesBar != showBar) { this.setState({showTopUnreadMessagesBar: showBar}); } @@ -1500,8 +1552,8 @@ export default class RoomView extends React.Component { // get the current scroll position of the room, so that it can be // restored when we switch back to it. // - _getScrollState() { - const messagePanel = this._messagePanel; + private getScrollState() { + const messagePanel = this.messagePanel; if (!messagePanel) return null; // if we're following the live timeline, we want to return null; that @@ -1537,7 +1589,7 @@ export default class RoomView extends React.Component { }; } - onResize = () => { + private onResize = () => { // It seems flexbox doesn't give us a way to constrain the auxPanel height to have // a minimum of the height of the video element, whilst also capping it from pushing out the page // so we have to do it via JS instead. In this implementation we cap the height by putting @@ -1557,15 +1609,15 @@ export default class RoomView extends React.Component { this.setState({auxPanelMaxHeight: auxPanelMaxHeight}); }; - onFullscreenClick = () => { + private onFullscreenClick = () => { dis.dispatch({ action: 'video_fullscreen', fullscreen: true, }, true); }; - onMuteAudioClick = () => { - const call = this._getCallForRoom(); + private onMuteAudioClick = () => { + const call = this.getCallForRoom(); if (!call) { return; } @@ -1574,8 +1626,8 @@ export default class RoomView extends React.Component { this.forceUpdate(); // TODO: just update the voip buttons }; - onMuteVideoClick = () => { - const call = this._getCallForRoom(); + private onMuteVideoClick = () => { + const call = this.getCallForRoom(); if (!call) { return; } @@ -1584,14 +1636,14 @@ export default class RoomView extends React.Component { this.forceUpdate(); // TODO: just update the voip buttons }; - onStatusBarVisible = () => { + private onStatusBarVisible = () => { if (this.unmounted) return; this.setState({ statusBarVisible: true, }); }; - onStatusBarHidden = () => { + private onStatusBarHidden = () => { // This is currently not desired as it is annoying if it keeps expanding and collapsing if (this.unmounted) return; this.setState({ @@ -1604,12 +1656,12 @@ export default class RoomView extends React.Component { * * We pass it down to the scroll panel. */ - handleScrollKey = ev => { + private handleScrollKey = ev => { let panel; - if (this._searchResultsPanel.current) { - panel = this._searchResultsPanel.current; - } else if (this._messagePanel) { - panel = this._messagePanel; + if (this.searchResultsPanel.current) { + panel = this.searchResultsPanel.current; + } else if (this.messagePanel) { + panel = this.messagePanel; } if (panel) { @@ -1620,7 +1672,7 @@ export default class RoomView extends React.Component { /** * get any current call for this room */ - _getCallForRoom() { + private getCallForRoom() { if (!this.state.room) { return null; } @@ -1629,47 +1681,34 @@ export default class RoomView extends React.Component { // this has to be a proper method rather than an unnamed function, // otherwise react calls it with null on each update. - _gatherTimelinePanelRef = r => { - this._messagePanel = r; + private gatherTimelinePanelRef = r => { + this.messagePanel = r; if (r) { - console.log("updateTint from RoomView._gatherTimelinePanelRef"); + console.log("updateTint from RoomView.gatherTimelinePanelRef"); this.updateTint(); } }; - _getOldRoom() { + private getOldRoom() { const createEvent = this.state.room.currentState.getStateEvents("m.room.create", ""); if (!createEvent || !createEvent.getContent()['predecessor']) return null; return this.context.getRoom(createEvent.getContent()['predecessor']['room_id']); } - _getHiddenHighlightCount() { - const oldRoom = this._getOldRoom(); + getHiddenHighlightCount() { + const oldRoom = this.getOldRoom(); if (!oldRoom) return 0; return oldRoom.getUnreadNotificationCount('highlight'); } - _onHiddenHighlightsClick = () => { - const oldRoom = this._getOldRoom(); + onHiddenHighlightsClick = () => { + const oldRoom = this.getOldRoom(); if (!oldRoom) return; dis.dispatch({action: "view_room", room_id: oldRoom.roomId}); }; render() { - const RoomHeader = sdk.getComponent('rooms.RoomHeader'); - const ForwardMessage = sdk.getComponent("rooms.ForwardMessage"); - const AuxPanel = sdk.getComponent("rooms.AuxPanel"); - const SearchBar = sdk.getComponent("rooms.SearchBar"); - const PinnedEventsPanel = sdk.getComponent("rooms.PinnedEventsPanel"); - const ScrollPanel = sdk.getComponent("structures.ScrollPanel"); - const TintableSvg = sdk.getComponent("elements.TintableSvg"); - const RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar"); - const TimelinePanel = sdk.getComponent("structures.TimelinePanel"); - const RoomUpgradeWarningBar = sdk.getComponent("rooms.RoomUpgradeWarningBar"); - const RoomRecoveryReminder = sdk.getComponent("rooms.RoomRecoveryReminder"); - const ErrorBoundary = sdk.getComponent("elements.ErrorBoundary"); - if (!this.state.room) { const loading = !this.state.matrixClientIsReady || this.state.roomLoading || this.state.peekLoading; if (loading) { @@ -1690,11 +1729,11 @@ export default class RoomView extends React.Component {
    ); } else { - var inviterName = undefined; + let inviterName = undefined; if (this.props.oobData) { inviterName = this.props.oobData.inviterName; } - var invitedEmail = undefined; + let invitedEmail = undefined; if (this.props.thirdPartyInvite) { invitedEmail = this.props.thirdPartyInvite.invitedEmail; } @@ -1773,13 +1812,13 @@ export default class RoomView extends React.Component { // We have successfully loaded this room, and are not previewing. // Display the "normal" room view. - const call = this._getCallForRoom(); + const call = this.getCallForRoom(); let inCall = false; if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) { inCall = true; } - const scrollheader_classes = classNames({ + const scrollheaderClasses = classNames({ mx_RoomView_scrollheader: true, }); @@ -1818,17 +1857,21 @@ export default class RoomView extends React.Component { this.context.getKeyBackupEnabled() === false ); - const hiddenHighlightCount = this._getHiddenHighlightCount(); + const hiddenHighlightCount = this.getHiddenHighlightCount(); let aux = null; let previewBar; let hideCancel = false; let forceHideRightPanel = false; - if (this.state.forwardingEvent !== null) { + if (this.state.forwardingEvent) { aux = ; } else if (this.state.searching) { hideCancel = true; // has own cancel - aux = ; + aux = ; } else if (showRoomUpgradeBar) { aux = ; hideCancel = true; @@ -1841,25 +1884,26 @@ export default class RoomView extends React.Component { } else if (myMembership !== "join") { // We do have a room object for this room, but we're not currently in it. // We may have a 3rd party invite to it. - var inviterName = undefined; + let inviterName = undefined; if (this.props.oobData) { inviterName = this.props.oobData.inviterName; } - var invitedEmail = undefined; + let invitedEmail = undefined; if (this.props.thirdPartyInvite) { invitedEmail = this.props.thirdPartyInvite.invitedEmail; } hideCancel = true; previewBar = ( - ); if (!this.state.canPeek) { @@ -1873,8 +1917,11 @@ export default class RoomView extends React.Component { } } else if (hiddenHighlightCount > 0) { aux = ( - + {_t( "You have %(count)s unread notifications in a prior version of this room.", {count: hiddenHighlightCount}, @@ -1916,7 +1963,7 @@ export default class RoomView extends React.Component { showApps={this.state.showApps} e2eStatus={this.state.e2eStatus} resizeNotifier={this.props.resizeNotifier} - permalinkCreator={this._getPermalinkCreatorForRoom(this.state.room)} + permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)} />; } @@ -1936,26 +1983,37 @@ export default class RoomView extends React.Component { if (call.type === "video") { zoomButton = (
    - +
    ); videoMuteButton =
    - +
    ; } const voiceMuteButton =
    - +
    ; // wrap the existing status bar into a 'callStatusBar' which adds more knobs. @@ -1976,16 +2034,18 @@ export default class RoomView extends React.Component { if (this.state.searchResults) { // show searching spinner if (this.state.searchResults.results === undefined) { - searchResultsPanel = (
    ); + searchResultsPanel = ( +
    + ); } else { searchResultsPanel = ( -
  • +
  • { this.getSearchResultTiles() } ); @@ -2011,7 +2071,7 @@ export default class RoomView extends React.Component { // console.info("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview); const messagePanel = ( ); + topUnreadMessagesBar = ( + + ); } let jumpToBottom; // Do not show JumpToBottomButton if we have search results showing, it makes no sense @@ -2050,19 +2109,14 @@ export default class RoomView extends React.Component { onScrollToBottomClick={this.jumpToLiveTimeline} />); } - const statusBarAreaClass = classNames( - "mx_RoomView_statusArea", - { - "mx_RoomView_statusArea_expanded": isStatusAreaExpanded, - }, - ); - const fadableSectionClasses = classNames( - "mx_RoomView_body", "mx_fadable", - { - "mx_fadable_faded": this.props.disabled, - }, - ); + const statusBarAreaClass = classNames("mx_RoomView_statusArea", { + "mx_RoomView_statusArea_expanded": isStatusAreaExpanded, + }); + + const fadableSectionClasses = classNames("mx_RoomView_body", "mx_fadable", { + "mx_fadable_faded": this.props.disabled, + }); const showRightPanel = !forceHideRightPanel && this.state.room && this.state.showRightPanel; const rightPanel = showRightPanel @@ -2079,7 +2133,7 @@ export default class RoomView extends React.Component { return ( -
    +
    ({ + roomLoading: true, + peekLoading: false, + shouldPeek: true, + membersLoaded: false, + numUnreadMessages: 0, + draggingFile: false, + searching: false, + guestsCanJoin: false, + canPeek: false, + showApps: false, + isAlone: false, + isPeeking: false, + showingPinned: false, + showReadReceipts: true, + showRightPanel: true, + joining: false, + atEndOfLiveTimeline: true, + atEndOfLiveTimelineInit: false, + showTopUnreadMessagesBar: false, + statusBarVisible: false, + canReact: false, + canReply: false, + useIRCLayout: false, + matrixClientIsReady: false, +}); +RoomContext.displayName = "RoomContext"; +export default RoomContext; diff --git a/src/utils/ShieldUtils.ts b/src/utils/ShieldUtils.ts index 878ed3959c..5fe653fed0 100644 --- a/src/utils/ShieldUtils.ts +++ b/src/utils/ShieldUtils.ts @@ -18,7 +18,13 @@ interface Room { roomId: string; } -export async function shieldStatusForRoom(client: Client, room: Room): Promise { +export enum E2EStatus { + Warning = "warning", + Verified = "verified", + Normal = "normal" +} + +export async function shieldStatusForRoom(client: Client, room: Room): Promise { const members = (await room.getEncryptionTargetMembers()).map(({userId}) => userId); const inDMMap = !!DMRoomMap.shared().getUserIdForRoomId(room.roomId); @@ -33,7 +39,7 @@ export async function shieldStatusForRoom(client: Client, room: Room): Promise Date: Tue, 8 Sep 2020 11:05:33 +0100 Subject: [PATCH 0092/1014] delint --- src/components/structures/RoomView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 05494318b9..de911325e8 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -37,7 +37,7 @@ import * as sdk from '../../index'; import CallHandler from '../../CallHandler'; import dis from '../../dispatcher/dispatcher'; import Tinter from '../../Tinter'; -import rate_limited_func from '../../ratelimitedfunc'; +import rateLimitedFunc from '../../ratelimitedfunc'; import * as ObjectUtils from '../../ObjectUtils'; import * as Rooms from '../../Rooms'; import eventSearch, {searchPagination} from '../../Searching'; @@ -1014,7 +1014,7 @@ export default class RoomView extends React.Component { } // rate limited because a power level change will emit an event for every member in the room. - private updateRoomMembers = rate_limited_func((dueToMember) => { + private updateRoomMembers = rateLimitedFunc((dueToMember) => { // a member state changed in this room // refresh the conf call notification state this.updateConfCallNotification(); From abaf470261af94e40e29126b989a34a5b1697fc3 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Tue, 8 Sep 2020 09:04:47 +0000 Subject: [PATCH 0093/1014] Translated using Weblate (Swedish) Currently translated at 100.0% (2356 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 03a6bc1e2e..2032eca4ff 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -615,7 +615,7 @@ "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s satte framtida rumshistorik till okänd synlighet (%(visibility)s).", "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Där denna sida innehåller identifierbar information, till exempel ett rums-, användar- eller grupp-ID, tas datan bort innan den skickas till servern.", "The remote side failed to pick up": "Mottagaren svarade inte", - "Jump to read receipt": "Hoppa till läsindikation", + "Jump to read receipt": "Hoppa till läskvitto", "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "Denna process låter dig exportera nycklarna för meddelanden som du har fått i krypterade rum till en lokal fil. Du kommer sedan att kunna importera filen i en annan Matrix-klient i framtiden, så att den klienten också kan avkryptera meddelandena.", "Unknown for %(duration)s": "Okänt i %(duration)s", "Unknown": "Okänt", @@ -929,7 +929,7 @@ "Show join/leave messages (invites/kicks/bans unaffected)": "Visa \"gå med\"/lämna-meddelanden (inbjudningar/kickningar/banningar opåverkat)", "Show avatar changes": "Visa avatarändringar", "Show display name changes": "Visa visningsnamnsändringar", - "Show read receipts sent by other users": "Visa läsindikationer som skickats av andra användare", + "Show read receipts sent by other users": "Visa läskvitton som skickats av andra användare", "Show avatars in user and room mentions": "Visa avatarer i användar- och rumsbenämningar", "Enable big emoji in chat": "Aktivera stora emojier i chatt", "Send typing notifications": "Skicka \"skriver\"-statusar", @@ -1237,7 +1237,7 @@ "Multiple integration managers": "Flera integrationshanterare", "Show hidden events in timeline": "Visa dolda händelser i tidslinjen", "Low bandwidth mode": "Läge för låg bandbredd", - "Send read receipts for messages (requires compatible homeserver to disable)": "Skicka läsindikationer för meddelanden (kräver kompatibel hemserver för att inaktivera)", + "Send read receipts for messages (requires compatible homeserver to disable)": "Skicka läskvitton för meddelanden (kräver kompatibel hemserver för att inaktivera)", "When rooms are upgraded": "När rum uppgraderas", "Accept to continue:": "Acceptera för att fortsätta:", "ID": "ID", @@ -1522,12 +1522,12 @@ "Space": "Mellanslag", "End": "End", "You have been logged out of all sessions and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "Du har blivit utloggad från alla dina sessioner och kommer inte längre att motta pushnotiser. För att återaktivera pushnotiser, logga in igen på varje enhet.", - "Use Single Sign On to continue": "Använd single sign-on för att fortsätta", - "Confirm adding this email address by using Single Sign On to prove your identity.": "Bekräfta tilläggning av e-postadressen genom att använda single sign-on för att bevisa din identitet.", - "Single Sign On": "Single sign-on", + "Use Single Sign On to continue": "Använd externt konto för att fortsätta", + "Confirm adding this email address by using Single Sign On to prove your identity.": "Bekräfta tilläggning av e-postadressen genom att använda externt konto för att bevisa din identitet.", + "Single Sign On": "Externt konto", "Confirm adding email": "Bekräfta tilläggning av e-postadressen", "Click the button below to confirm adding this email address.": "Klicka på knappen nedan för att bekräfta tilläggning av e-postadressen.", - "Confirm adding this phone number by using Single Sign On to prove your identity.": "Bekräfta tilläggning av telefonnumret genom att använda single sign-on för att bevisa din identitet.", + "Confirm adding this phone number by using Single Sign On to prove your identity.": "Bekräfta tilläggning av telefonnumret genom att använda externt konto för att bevisa din identitet.", "Confirm adding phone number": "Bekräfta tilläggning av telefonnumret", "Click the button below to confirm adding this phone number.": "Klicka på knappen nedan för att bekräfta tilläggning av telefonnumret.", "Are you sure you want to cancel entering passphrase?": "Är du säker på att du vill avbryta inmatning av lösenfrasen?", @@ -1721,8 +1721,8 @@ "exists": "existerar", "Your homeserver does not support session management.": "Din hemservers stöder inte sessionshantering.", "Unable to load session list": "Kunde inte ladda sessionslistan", - "Confirm deleting these sessions by using Single Sign On to prove your identity.|other": "Bekräfta radering av dessa sessioner genom att använda single sign-on för att bekräfta din identitet.", - "Confirm deleting these sessions by using Single Sign On to prove your identity.|one": "Bekräfta radering av denna session genom att använda single sign-on för att bekräfta din identitet.", + "Confirm deleting these sessions by using Single Sign On to prove your identity.|other": "Bekräfta radering av dessa sessioner genom att använda externt konto för att bekräfta din identitet.", + "Confirm deleting these sessions by using Single Sign On to prove your identity.|one": "Bekräfta radering av denna session genom att använda externt konto för att bekräfta din identitet.", "Confirm deleting these sessions": "Bekräfta radering av dessa sessioner", "Click the button below to confirm deleting these sessions.|other": "Klicka på knappen nedan för att bekräfta radering av dessa sessioner.", "Click the button below to confirm deleting these sessions.|one": "Klicka på knappen nedan för att bekräfta radering av denna session.", @@ -2012,7 +2012,7 @@ "Please provide a room address": "Vänligen välj en rumsadress", "This address is available to use": "Adressen är tillgänglig", "This address is already in use": "Adressen är upptagen", - "Sign in with single sign-on": "Logga in med single sign-on", + "Sign in with single sign-on": "Logga in med externt konto", "Enter a server name": "Ange ett servernamn", "Looks good": "Ser bra ut", "Can't find this server or its room list": "Kan inte hitta den här servern eller dess rumslista", @@ -2072,7 +2072,7 @@ "You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "Du har tidigare använt en nyare version av %(brand)s med den här sessionen. Om du vill använda den här versionen igen med totalsträckskryptering behöver du logga ut och logga in igen.", "Incompatible Database": "Inkompatibel databas", "Continue With Encryption Disabled": "Fortsätt med kryptering inaktiverad", - "Confirm your account deactivation by using Single Sign On to prove your identity.": "Bekräfta din kontoinaktivering genom att använda single sign-on för att bevisa din identitet.", + "Confirm your account deactivation by using Single Sign On to prove your identity.": "Bekräfta din kontoinaktivering genom att använda externt konto för att bevisa din identitet.", "Are you sure you want to deactivate your account? This is irreversible.": "Är du säker på att du vill inaktivera ditt konto? Detta är oåterkalleligt.", "Confirm account deactivation": "Bekräfta kontoinaktivering", "Security & privacy": "Säkerhet & sekretess", @@ -2084,7 +2084,7 @@ "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.": "Att verifiera den här användaren kommer att markera dess session som betrodd, och markera din session som betrodd för denne.", "Verify this device to mark it as trusted. Trusting this device gives you and other users extra peace of mind when using end-to-end encrypted messages.": "Verifiera denna enhet för att markera den som betrodd. Att lita på denna enhet och andra användare ger en extra sinnesfrid när man använder totalsträckskrypterade meddelanden.", "Verifying this device will mark it as trusted, and users who have verified with you will trust this device.": "Att verifiera den här enheten kommer att markera den som betrodd, användare som har verifierat dig kommer att lita på den här enheten.", - "To continue, use Single Sign On to prove your identity.": "För att fortsätta, använd single sign-on för att bevisa din identitet.", + "To continue, use Single Sign On to prove your identity.": "För att fortsätta, använd externt konto för att bevisa din identitet.", "Click the button below to confirm your identity.": "Klicka på knappen nedan för att bekräfta din identitet.", "Failed to invite the following users to chat: %(csvUsers)s": "Misslyckades att bjuda in följande användare till chatten: %(csvUsers)s", "We couldn't create your DM. Please check the users you want to invite and try again.": "Vi kunde inte skapa ditt DM. Vänligen kolla användarna du försöker bjuda in och försök igen.", From 9d85d0436c4be60c5ef3ddc8ee680ca3ee41da36 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 15:22:23 +0100 Subject: [PATCH 0094/1014] iterate PR --- src/components/structures/RoomView.tsx | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index de911325e8..3395a6011c 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -69,6 +69,8 @@ import PinnedEventsPanel from "../views/rooms/PinnedEventsPanel"; import AuxPanel from "../views/rooms/AuxPanel"; import RoomHeader from "../views/rooms/RoomHeader"; import TintableSvg from "../views/elements/TintableSvg"; +import type * as ConferenceHandler from '../../VectorConferenceHandler'; +import {XOR} from "../../@types/common"; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -81,7 +83,7 @@ if (DEBUG) { } interface IProps { - ConferenceHandler?: any; + ConferenceHandler?: ConferenceHandler; // An object representing a third party invite to join this room // Fields: @@ -102,7 +104,11 @@ interface IProps { // * avatarUrl (string) The mxc:// avatar URL for the room // * inviterName (string) The display name of the person who // * invited us to the room - oobData?: any; + oobData?: { + name?: string; + avatarUrl?: string; + inviterName?: string; + }; // Servers the RoomView can use to try and assist joins viaServers?: string[]; @@ -137,7 +143,12 @@ export interface IState { searching: boolean; searchTerm?: string; searchScope?: "All" | "Room"; - searchResults?: any; + searchResults?: XOR<{}, { + count: number; + highlights: string[]; + results: MatrixEvent[]; + next_batch: string; // eslint-disable-line camelcase + }>; searchHighlights?: string[]; searchInProgress?: boolean; callState?: string; @@ -166,7 +177,11 @@ export interface IState { statusBarVisible: boolean; // We load this later by asking the js-sdk to suggest a version for us. // This object is the result of Room#getRecommendedVersion() - upgradeRecommendation?: any; + upgradeRecommendation?: { + version: string; + needsUpgrade: boolean; + urgent: boolean; + }; canReact: boolean; canReply: boolean; useIRCLayout: boolean; @@ -1034,7 +1049,7 @@ export default class RoomView extends React.Component { private checkIfAlone(room: Room, countInfluence?: number) { let warnedAboutLonelyRoom = false; if (localStorage) { - warnedAboutLonelyRoom = !!localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId); + warnedAboutLonelyRoom = Boolean(localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId)); } if (warnedAboutLonelyRoom) { if (this.state.isAlone) this.setState({isAlone: false}); From 2fbb551035efa159bedbbd52211a25e3da9604ab Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 4 Sep 2020 14:09:34 +0100 Subject: [PATCH 0095/1014] Put backup details in a table --- .../views/settings/_SecureBackupPanel.scss | 12 +++++++++ .../views/settings/SecureBackupPanel.js | 26 +++++++++++++++---- src/i18n/strings/en_EN.json | 6 ++--- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/res/css/views/settings/_SecureBackupPanel.scss b/res/css/views/settings/_SecureBackupPanel.scss index 548e72fbc3..587cab8f36 100644 --- a/res/css/views/settings/_SecureBackupPanel.scss +++ b/res/css/views/settings/_SecureBackupPanel.scss @@ -35,3 +35,15 @@ limitations under the License. .mx_SecureBackupPanel_buttonRow { margin: 1em 0; } + +.mx_SecureBackupPanel_statusList { + border-spacing: 0; + + td { + padding: 0; + + &:first-of-type { + padding-inline-end: 1em; + } + } +} diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 683120f5b6..7f7a014df9 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -174,6 +174,7 @@ export default class SecureBackupPanel extends React.PureComponent { } = this.state; let statusDescription; + let extraDetailsTableRows; let extraDetails; let actions; if (error) { @@ -316,9 +317,18 @@ export default class SecureBackupPanel extends React.PureComponent { ; } + extraDetailsTableRows = <> + + {_t("Backup version:")} + {backupInfo.version} + + + {_t("Algorithm:")} + {backupInfo.algorithm} + + ; + extraDetails = <> -
    {_t("Backup version: ")}{backupInfo.version}
    -
    {_t("Algorithm: ")}{backupInfo.algorithm}
    {uploadStatus}
    {backupSigStatuses}
    {trustedLocally}
    @@ -359,9 +369,15 @@ export default class SecureBackupPanel extends React.PureComponent { {statusDescription}
    {_t("Advanced")} -
    {_t("Backup key stored: ")}{ - backupKeyStored === true ? _t("in secret storage") : _t("not stored") - }
    + + + + + + {extraDetailsTableRows} +
    {_t("Backup key stored:")}{ + backupKeyStored === true ? _t("in secret storage") : _t("not stored") + }
    {extraDetails}
    {actions} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 49e8b1d2e8..476ce11b2d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -750,13 +750,13 @@ "Backup has an invalid signature from unverified session ": "Backup has an invalid signature from unverified session ", "Backup is not signed by any of your sessions": "Backup is not signed by any of your sessions", "This backup is trusted because it has been restored on this session": "This backup is trusted because it has been restored on this session", - "Backup version: ": "Backup version: ", - "Algorithm: ": "Algorithm: ", + "Backup version:": "Backup version:", + "Algorithm:": "Algorithm:", "Your keys are not being backed up from this session.": "Your keys are not being backed up from this session.", "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.", "Start using Key Backup": "Start using Key Backup", "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.", - "Backup key stored: ": "Backup key stored: ", + "Backup key stored:": "Backup key stored:", "not stored": "not stored", "Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS", "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)", From aae68f7d1aa3fecb68a590a420de062ee695308f Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 8 Sep 2020 14:10:34 +0100 Subject: [PATCH 0096/1014] Move 4S status to backup panel This moves the various 4S status diagnostics to the backup panel and out of the cross-signing panel. The available actions are unchanged as part of this commit, but they will be updated next. --- .../views/settings/CrossSigningPanel.js | 61 +++------------ .../views/settings/SecureBackupPanel.js | 78 +++++++++++++++---- src/i18n/strings/en_EN.json | 18 +++-- 3 files changed, 84 insertions(+), 73 deletions(-) diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 3eeb072e2d..8ef68e4b2a 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -31,13 +31,13 @@ export default class CrossSigningPanel extends React.PureComponent { this.state = { error: null, - crossSigningPublicKeysOnDevice: false, - crossSigningPrivateKeysInStorage: false, - masterPrivateKeyCached: false, - selfSigningPrivateKeyCached: false, - userSigningPrivateKeyCached: false, - sessionBackupKeyCached: false, - secretStorageKeyInAccount: false, + crossSigningPublicKeysOnDevice: null, + crossSigningPrivateKeysInStorage: null, + masterPrivateKeyCached: null, + selfSigningPrivateKeyCached: null, + userSigningPrivateKeyCached: null, + homeserverSupportsCrossSigning: null, + crossSigningReady: null, }; } @@ -83,14 +83,9 @@ export default class CrossSigningPanel extends React.PureComponent { const masterPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("master")); const selfSigningPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("self_signing")); const userSigningPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("user_signing")); - const sessionBackupKeyFromCache = await cli._crypto.getSessionBackupPrivateKey(); - const sessionBackupKeyCached = !!(sessionBackupKeyFromCache); - const sessionBackupKeyWellFormed = sessionBackupKeyFromCache instanceof Uint8Array; - const secretStorageKeyInAccount = await secretStorage.hasKey(); const homeserverSupportsCrossSigning = await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing"); const crossSigningReady = await cli.isCrossSigningReady(); - const secretStorageReady = await cli.isSecretStorageReady(); this.setState({ crossSigningPublicKeysOnDevice, @@ -98,12 +93,8 @@ export default class CrossSigningPanel extends React.PureComponent { masterPrivateKeyCached, selfSigningPrivateKeyCached, userSigningPrivateKeyCached, - sessionBackupKeyCached, - sessionBackupKeyWellFormed, - secretStorageKeyInAccount, homeserverSupportsCrossSigning, crossSigningReady, - secretStorageReady, }); } @@ -149,12 +140,8 @@ export default class CrossSigningPanel extends React.PureComponent { masterPrivateKeyCached, selfSigningPrivateKeyCached, userSigningPrivateKeyCached, - sessionBackupKeyCached, - sessionBackupKeyWellFormed, - secretStorageKeyInAccount, homeserverSupportsCrossSigning, crossSigningReady, - secretStorageReady, } = this.state; let errorSection; @@ -169,14 +156,9 @@ export default class CrossSigningPanel extends React.PureComponent { summarisedStatus =

    {_t( "Your homeserver does not support cross-signing.", )}

    ; - } else if (crossSigningReady && secretStorageReady) { + } else if (crossSigningReady) { summarisedStatus =

    ✅ {_t( - "Cross-signing and secret storage are ready for use.", - )}

    ; - } else if (crossSigningReady && !secretStorageReady) { - summarisedStatus =

    ✅ {_t( - "Cross-signing is ready for use, but secret storage is " + - "currently not being used to backup your keys.", + "Cross-signing is ready for use.", )}

    ; } else if (crossSigningPrivateKeysInStorage) { summarisedStatus =

    {_t( @@ -185,17 +167,15 @@ export default class CrossSigningPanel extends React.PureComponent { )}

    ; } else { summarisedStatus =

    {_t( - "Cross-signing and secret storage are not yet set up.", + "Cross-signing is not set up.", )}

    ; } const keysExistAnywhere = ( - secretStorageKeyInAccount || crossSigningPrivateKeysInStorage || crossSigningPublicKeysOnDevice ); const keysExistEverywhere = ( - secretStorageKeyInAccount && crossSigningPrivateKeysInStorage && crossSigningPublicKeysOnDevice ); @@ -223,16 +203,6 @@ export default class CrossSigningPanel extends React.PureComponent { ); } - let sessionBackupKeyWellFormedText = ""; - if (sessionBackupKeyCached) { - sessionBackupKeyWellFormedText = ", "; - if (sessionBackupKeyWellFormed) { - sessionBackupKeyWellFormedText += _t("well formed"); - } else { - sessionBackupKeyWellFormedText += _t("unexpected type"); - } - } - return (
    {summarisedStatus} @@ -259,17 +229,6 @@ export default class CrossSigningPanel extends React.PureComponent { {_t("User signing private key:")} {userSigningPrivateKeyCached ? _t("cached locally") : _t("not found locally")} - - {_t("Session backup key:")} - - {sessionBackupKeyCached ? _t("cached locally") : _t("not found locally")} - {sessionBackupKeyWellFormedText} - - - - {_t("Secret storage public key:")} - {secretStorageKeyInAccount ? _t("in account data") : _t("not found")} - {_t("Homeserver feature support:")} {homeserverSupportsCrossSigning ? _t("exists") : _t("not found")} diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 7f7a014df9..0f43770288 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -34,9 +34,13 @@ export default class SecureBackupPanel extends React.PureComponent { this.state = { loading: true, error: null, + backupKeyStored: null, + backupKeyCached: null, + backupKeyWellFormed: null, + secretStorageKeyInAccount: null, + secretStorageReady: null, backupInfo: null, backupSigStatus: null, - backupKeyStored: null, sessionsRemaining: 0, }; } @@ -76,56 +80,73 @@ export default class SecureBackupPanel extends React.PureComponent { } async _checkKeyBackupStatus() { + this._getUpdatedDiagnostics(); try { const {backupInfo, trustInfo} = await MatrixClientPeg.get().checkKeyBackup(); - const backupKeyStored = Boolean(await MatrixClientPeg.get().isKeyBackupKeyStored()); this.setState({ + loading: false, + error: null, backupInfo, backupSigStatus: trustInfo, - backupKeyStored, - error: null, - loading: false, }); } catch (e) { console.log("Unable to fetch check backup status", e); if (this._unmounted) return; this.setState({ + loading: false, error: e, backupInfo: null, backupSigStatus: null, - backupKeyStored: null, - loading: false, }); } } async _loadBackupStatus() { - this.setState({loading: true}); + this.setState({ loading: true }); + this._getUpdatedDiagnostics(); try { const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); const backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo); - const backupKeyStored = await MatrixClientPeg.get().isKeyBackupKeyStored(); if (this._unmounted) return; this.setState({ + loading: false, error: null, backupInfo, backupSigStatus, - backupKeyStored, - loading: false, }); } catch (e) { console.log("Unable to fetch key backup status", e); if (this._unmounted) return; this.setState({ + loading: false, error: e, backupInfo: null, backupSigStatus: null, - backupKeyStored: null, - loading: false, }); } } + async _getUpdatedDiagnostics() { + const cli = MatrixClientPeg.get(); + const secretStorage = cli._crypto._secretStorage; + + const backupKeyStored = await cli.isKeyBackupKeyStored(); + const backupKeyFromCache = await cli._crypto.getSessionBackupPrivateKey(); + const backupKeyCached = !!(backupKeyFromCache); + const backupKeyWellFormed = backupKeyFromCache instanceof Uint8Array; + const secretStorageKeyInAccount = await secretStorage.hasKey(); + const secretStorageReady = await cli.isSecretStorageReady(); + + if (this._unmounted) return; + this.setState({ + backupKeyStored, + backupKeyCached, + backupKeyWellFormed, + secretStorageKeyInAccount, + secretStorageReady, + }); + } + _startNewBackup = () => { Modal.createTrackedDialogAsync('Key Backup', 'Key Backup', import('../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'), @@ -167,9 +188,13 @@ export default class SecureBackupPanel extends React.PureComponent { const { loading, error, + backupKeyStored, + backupKeyCached, + backupKeyWellFormed, + secretStorageKeyInAccount, + secretStorageReady, backupInfo, backupSigStatus, - backupKeyStored, sessionsRemaining, } = this.state; @@ -359,6 +384,16 @@ export default class SecureBackupPanel extends React.PureComponent { ); } + let backupKeyWellFormedText = ""; + if (backupKeyCached) { + backupKeyWellFormedText = ", "; + if (backupKeyWellFormed) { + backupKeyWellFormedText += _t("well formed"); + } else { + backupKeyWellFormedText += _t("unexpected type"); + } + } + return (

    {_t( @@ -376,6 +411,21 @@ export default class SecureBackupPanel extends React.PureComponent { backupKeyStored === true ? _t("in secret storage") : _t("not stored") } + + {_t("Backup key cached:")} + + {backupKeyCached ? _t("cached locally") : _t("not found locally")} + {backupKeyWellFormedText} + + + + {_t("Secret storage public key:")} + {secretStorageKeyInAccount ? _t("in account data") : _t("not found")} + + + {_t("Secret storage:")} + {secretStorageReady ? _t("ready") : _t("not ready")} + {extraDetailsTableRows} {extraDetails} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 476ce11b2d..1bf431f6e0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -645,14 +645,11 @@ "Confirm password": "Confirm password", "Change Password": "Change Password", "Your homeserver does not support cross-signing.": "Your homeserver does not support cross-signing.", - "Cross-signing and secret storage are ready for use.": "Cross-signing and secret storage are ready for use.", - "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.", + "Cross-signing is ready for use.": "Cross-signing is ready for use.", "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.", - "Cross-signing and secret storage are not yet set up.": "Cross-signing and secret storage are not yet set up.", + "Cross-signing is not set up.": "Cross-signing is not set up.", "Reset cross-signing and secret storage": "Reset cross-signing and secret storage", "Bootstrap cross-signing and secret storage": "Bootstrap cross-signing and secret storage", - "well formed": "well formed", - "unexpected type": "unexpected type", "Cross-signing public keys:": "Cross-signing public keys:", "in memory": "in memory", "not found": "not found", @@ -663,9 +660,6 @@ "not found locally": "not found locally", "Self signing private key:": "Self signing private key:", "User signing private key:": "User signing private key:", - "Session backup key:": "Session backup key:", - "Secret storage public key:": "Secret storage public key:", - "in account data": "in account data", "Homeserver feature support:": "Homeserver feature support:", "exists": "exists", "Your homeserver does not support session management.": "Your homeserver does not support session management.", @@ -755,9 +749,17 @@ "Your keys are not being backed up from this session.": "Your keys are not being backed up from this session.", "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.", "Start using Key Backup": "Start using Key Backup", + "well formed": "well formed", + "unexpected type": "unexpected type", "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.", "Backup key stored:": "Backup key stored:", "not stored": "not stored", + "Backup key cached:": "Backup key cached:", + "Secret storage public key:": "Secret storage public key:", + "in account data": "in account data", + "Secret storage:": "Secret storage:", + "ready": "ready", + "not ready": "not ready", "Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS", "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)", "Could not connect to Identity Server": "Could not connect to Identity Server", From 11e349d6c8cc8416dc329bc3d077cf0d12e6eb91 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 28 Aug 2020 10:46:01 +0100 Subject: [PATCH 0097/1014] Update e2e iconography --- res/img/e2e/disabled.svg | 5 +++++ res/img/e2e/normal.svg | 4 ++-- res/img/e2e/verified.svg | 4 ++-- res/img/e2e/warning.svg | 4 ++-- 4 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 res/img/e2e/disabled.svg diff --git a/res/img/e2e/disabled.svg b/res/img/e2e/disabled.svg new file mode 100644 index 0000000000..2f6110a36a --- /dev/null +++ b/res/img/e2e/disabled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/e2e/normal.svg b/res/img/e2e/normal.svg index 23ca78e44d..83b544a326 100644 --- a/res/img/e2e/normal.svg +++ b/res/img/e2e/normal.svg @@ -1,3 +1,3 @@ - - + + diff --git a/res/img/e2e/verified.svg b/res/img/e2e/verified.svg index ac4827baed..f90d9db554 100644 --- a/res/img/e2e/verified.svg +++ b/res/img/e2e/verified.svg @@ -1,3 +1,3 @@ - - + + diff --git a/res/img/e2e/warning.svg b/res/img/e2e/warning.svg index d42922892a..58f5c3b7d1 100644 --- a/res/img/e2e/warning.svg +++ b/res/img/e2e/warning.svg @@ -1,3 +1,3 @@ - - + + From 25273442949ab1a6099b4ba5a574ba413959b8b8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 28 Aug 2020 10:50:27 +0100 Subject: [PATCH 0098/1014] Create name/title Widget utils --- src/components/views/elements/PersistentApp.js | 2 +- src/components/views/rooms/AppsDrawer.js | 2 +- src/i18n/strings/en_EN.json | 1 + src/utils/WidgetUtils.js | 11 +++++++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/PersistentApp.js b/src/components/views/elements/PersistentApp.js index bdf5f60234..686739a9f7 100644 --- a/src/components/views/elements/PersistentApp.js +++ b/src/components/views/elements/PersistentApp.js @@ -76,7 +76,7 @@ export default class PersistentApp extends React.Component { userId={MatrixClientPeg.get().credentials.userId} show={true} creatorUserId={app.creatorUserId} - widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''} + widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)} waitForIframeLoad={app.waitForIframeLoad} whitelistCapabilities={capWhitelist} showDelete={false} diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index fca46b453f..1eca493e14 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -171,7 +171,7 @@ export default class AppsDrawer extends React.Component { userId={this.props.userId} show={this.props.showApps} creatorUserId={app.creatorUserId} - widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''} + widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)} waitForIframeLoad={app.waitForIframeLoad} whitelistCapabilities={capWhitelist} />); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 47063bdae4..54a4bd6695 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -387,6 +387,7 @@ "Common names and surnames are easy to guess": "Common names and surnames are easy to guess", "Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess", "Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess", + "Unknown App": "Unknown App", "Help us improve %(brand)s": "Help us improve %(brand)s", "Send anonymous usage data which helps us improve %(brand)s. This will use a cookie.": "Send anonymous usage data which helps us improve %(brand)s. This will use a cookie.", "I want to help": "I want to help", diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index be176d042f..d4ed093a24 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -32,6 +32,7 @@ import {Capability} from "../widgets/WidgetApi"; import {Room} from "matrix-js-sdk/src/models/room"; import {WidgetType} from "../widgets/WidgetType"; import {objectClone} from "./objects"; +import {_t} from "../languageHandler"; export default class WidgetUtils { /* Returns true if user is able to send state events to modify widgets in this room @@ -486,4 +487,14 @@ export default class WidgetUtils { IntegrationManagers.sharedInstance().getPrimaryManager().open(room, 'type_' + app.type, app.id); } } + + static getWidgetName(app) { + if (!app || !app.name) return ""; + return app.name.trim() || _t("Unknown App"); + } + + static getWidgetDataTitle(app) { + if (!app || !app.data || !app.data.title) return ""; + return app.data.title.trim(); + } } From 8f94a42dafcf30cf4776c2a6802ce94275034830 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 28 Aug 2020 10:52:04 +0100 Subject: [PATCH 0099/1014] Update Right Panel phase recall behaviour --- src/components/structures/RightPanel.js | 6 +----- src/stores/RightPanelStore.ts | 10 ++++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index 11416b29fb..b2b1ceae00 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -47,10 +47,10 @@ export default class RightPanel extends React.Component { constructor(props, context) { super(props, context); this.state = { + ...RightPanelStore.getSharedInstance().roomPanelPhaseParams, phase: this._getPhaseFromProps(), isUserPrivilegedInGroup: null, member: this._getUserForPanel(), - verificationRequest: RightPanelStore.getSharedInstance().roomPanelPhaseParams.verificationRequest, }; this.onAction = this.onAction.bind(this); this.onRoomStateMember = this.onRoomStateMember.bind(this); @@ -102,10 +102,6 @@ export default class RightPanel extends React.Component { } return RightPanelPhases.RoomMemberInfo; } else { - if (!RIGHT_PANEL_PHASES_NO_ARGS.includes(rps.roomPanelPhase)) { - dis.dispatch({action: Action.SetRightPanelPhase, phase: RightPanelPhases.RoomMemberList}); - return RightPanelPhases.RoomMemberList; - } return rps.roomPanelPhase; } } diff --git a/src/stores/RightPanelStore.ts b/src/stores/RightPanelStore.ts index 34445d007b..c539fcdb40 100644 --- a/src/stores/RightPanelStore.ts +++ b/src/stores/RightPanelStore.ts @@ -33,6 +33,8 @@ interface RightPanelStoreState { lastRoomPhase: RightPanelPhases; lastGroupPhase: RightPanelPhases; + previousPhase?: RightPanelPhases; + // Extra information about the last phase lastRoomPhaseParams: {[key: string]: any}; } @@ -89,6 +91,10 @@ export default class RightPanelStore extends Store { return this.state.lastGroupPhase; } + get previousPhase(): RightPanelPhases | null { + return RIGHT_PANEL_PHASES_NO_ARGS.includes(this.state.previousPhase) ? this.state.previousPhase : null; + } + get visibleRoomPanelPhase(): RightPanelPhases { return this.isOpenForRoom ? this.roomPanelPhase : null; } @@ -176,23 +182,27 @@ export default class RightPanelStore extends Store { if (targetPhase === this.state.lastGroupPhase) { this.setState({ showGroupPanel: !this.state.showGroupPanel, + previousPhase: null, }); } else { this.setState({ lastGroupPhase: targetPhase, showGroupPanel: true, + previousPhase: this.state.lastGroupPhase, }); } } else { if (targetPhase === this.state.lastRoomPhase && !refireParams) { this.setState({ showRoomPanel: !this.state.showRoomPanel, + previousPhase: null, }); } else { this.setState({ lastRoomPhase: targetPhase, showRoomPanel: true, lastRoomPhaseParams: refireParams || {}, + previousPhase: this.state.lastRoomPhase, }); } } From 89a836100d62842c1af721749b83c0bfaa90d680 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 1 Sep 2020 14:32:21 +0100 Subject: [PATCH 0100/1014] small css tweaks to closer match the figma --- res/css/structures/_RightPanel.scss | 16 +++++++--------- res/css/structures/_UserMenu.scss | 3 +-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index c7c0d6fac4..7e5ab7cdbd 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -95,15 +95,7 @@ limitations under the License. mask-position: center; } -.mx_RightPanel_headerButton_highlight { - background: rgba($accent-color, 0.25); - // make the icon the accent color too - &::before { - background-color: $accent-color !important; - } -} - -.mx_RightPanel_headerButton:not(.mx_RightPanel_headerButton_highlight) { +.mx_RightPanel_headerButton { &:hover { background: rgba($accent-color, 0.1); @@ -113,6 +105,12 @@ limitations under the License. } } +.mx_RightPanel_headerButton_highlight { + &::before { + background-color: $accent-color !important; + } +} + .mx_RightPanel_headerButton_badge { font-size: $font-8px; border-radius: 8px; diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index 6fa2f2578e..fecac40e4e 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -15,7 +15,6 @@ limitations under the License. */ .mx_UserMenu { - // to make the menu button sort of aligned with the explore button below padding-right: 6px; @@ -59,7 +58,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background: $primary-fg-color; + background: $tertiary-fg-color; mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } } From eb7f6f4c4b5036e1c0e46babb9a3e3fafeb65963 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Sep 2020 16:19:42 +0100 Subject: [PATCH 0101/1014] Create new WidgetStore to track all widgets stuff --- src/@types/global.d.ts | 2 + src/stores/WidgetStore.ts | 191 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 src/stores/WidgetStore.ts diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 1a361e7b55..de3eb5e767 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -29,6 +29,7 @@ import {ActiveRoomObserver} from "../ActiveRoomObserver"; import {Notifier} from "../Notifier"; import type {Renderer} from "react-dom"; import RightPanelStore from "../stores/RightPanelStore"; +import {WidgetStore} from "../stores/WidgetStore"; declare global { interface Window { @@ -51,6 +52,7 @@ declare global { mxSettingsStore: SettingsStore; mxNotifier: typeof Notifier; mxRightPanelStore: RightPanelStore; + mxWidgetStore: WidgetStore; } interface Document { diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts new file mode 100644 index 0000000000..b31fc99515 --- /dev/null +++ b/src/stores/WidgetStore.ts @@ -0,0 +1,191 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { Room } from "matrix-js-sdk/src/models/room"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + +import { ActionPayload } from "../dispatcher/payloads"; +import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; +import defaultDispatcher from "../dispatcher/dispatcher"; +import SettingsStore from "../settings/SettingsStore"; +import WidgetEchoStore from "../stores/WidgetEchoStore"; +import WidgetUtils from "../utils/WidgetUtils"; +import {IRRELEVANT_ROOM} from "../settings/WatchManager"; +import {SettingLevel} from "../settings/SettingLevel"; + +interface IState {} + +export interface IApp { + id: string; + type: string; + roomId: string; + eventId: string; + creatorUserId: string; + waitForIframeLoad?: boolean; + // eslint-disable-next-line camelcase + avatar_url: string; // MSC2765 https://github.com/matrix-org/matrix-doc/pull/2765 +} + +interface IRoomWidgets { + widgets: IApp[]; + pinned: Set; +} + +// TODO consolidate WidgetEchoStore into this +// TODO consolidate ActiveWidgetStore into this +export class WidgetStore extends AsyncStoreWithClient { + private static internalInstance = new WidgetStore(); + + private widgetMap = new Map(); + private roomMap = new Map(); + + private constructor() { + super(defaultDispatcher, {}); + + SettingsStore.watchSetting("Widgets.pinned", IRRELEVANT_ROOM, this.onPinnedWidgetsChange); + WidgetEchoStore.on("update", this.onWidgetEchoStoreUpdate); + } + + public static get instance(): WidgetStore { + return WidgetStore.internalInstance; + } + + private initRoom(roomId: string) { + if (!this.roomMap.has(roomId)) { + this.roomMap.set(roomId, { + pinned: new Set(), + widgets: [], + }); + } + } + + protected async onReady(): Promise { + this.matrixClient.on("RoomState.events", this.onRoomStateEvents); + this.matrixClient.getRooms().forEach((room: Room) => { + const pinned = SettingsStore.getValue("Widgets.pinned", room.roomId); + + if (pinned || WidgetUtils.getRoomWidgets(room).length) { + this.initRoom(room.roomId); + } + + if (pinned) { + this.getRoom(room.roomId).pinned = new Set(pinned); + } + + this.loadRoomWidgets(room); + }); + this.emit("update"); + } + + protected async onNotReady(): Promise { + this.matrixClient.off("RoomState.events", this.onRoomStateEvents); + this.widgetMap = new Map(); + this.roomMap = new Map(); + await this.reset({}); + } + + // We don't need this, but our contract says we do. + protected async onAction(payload: ActionPayload) { + return; + } + + private onWidgetEchoStoreUpdate(roomId: string, widgetId: string) { + this.initRoom(roomId); + this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); + this.emit("update"); + } + + private generateApps(room: Room): IApp[] { + return WidgetEchoStore.getEchoedRoomWidgets(room.roomId, WidgetUtils.getRoomWidgets(room)).map((ev) => { + return WidgetUtils.makeAppConfig( + ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(), + ); + }); + } + + private loadRoomWidgets(room: Room) { + const roomInfo = this.roomMap.get(room.roomId); + roomInfo.widgets = []; + this.generateApps(room).forEach(app => { + this.widgetMap.set(app.id, app); + roomInfo.widgets.push(app); + }); + this.emit(room.roomId); + } + + private onRoomStateEvents(ev: MatrixEvent) { + if (ev.getType() !== "im.vector.modular.widgets") return; + const roomId = ev.getRoomId(); + this.initRoom(roomId); + this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); + this.emit("update"); + } + + public getRoomId = (widgetId: string) => { + const app = this.widgetMap.get(widgetId); + if (!app) return null; + return app.roomId; + } + + public getRoom = (roomId: string) => { + return this.roomMap.get(roomId); + }; + + private onPinnedWidgetsChange = (settingName: string, roomId: string) => { + const pinned = SettingsStore.getValue(settingName, roomId); + this.initRoom(roomId); + this.getRoom(roomId).pinned = new Set(pinned); + this.emit(roomId); + this.emit("update"); + }; + + public isPinned(widgetId: string) { + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + // TODO heuristic for Jitsi etc + return roomInfo ? roomInfo.pinned.has(widgetId) : false; + } + + public pinWidget(widgetId: string) { + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + if (!roomInfo) return; + roomInfo.pinned.add(widgetId); + SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, Array.from(roomInfo.pinned)); + this.emit(roomId); + this.emit("update"); + } + + public unpinWidget(widgetId: string) { + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + if (!roomInfo) return; + roomInfo.pinned.delete(widgetId); + SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, Array.from(roomInfo.pinned)); + this.emit(roomId); + this.emit("update"); + } + + public getApps(room: Room, pinned?: boolean): IApp[] { + const apps = this.getRoom(room.roomId).widgets; + if (pinned) { + return apps.filter(app => this.isPinned(app.id)); + } + return apps + } +} + +window.mxWidgetStore = WidgetStore.instance; From 31cca5e0f2a4b8249eb3cc5b48409781b087b2ca Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 08:48:03 +0100 Subject: [PATCH 0102/1014] Create new right panel cards --- res/css/_components.scss | 3 + res/css/views/right_panel/_BaseCard.scss | 164 +++++++++++++ .../views/right_panel/_RoomSummaryCard.scss | 157 ++++++++++++ res/css/views/right_panel/_WidgetCard.scss | 25 ++ res/css/views/rooms/_RoomHeader.scss | 12 - src/components/structures/RightPanel.js | 41 +++- src/components/views/right_panel/BaseCard.tsx | 93 +++++++ .../views/right_panel/HeaderButtons.tsx | 3 +- .../views/right_panel/RoomHeaderButtons.tsx | 43 ++-- .../views/right_panel/RoomSummaryCard.tsx | 231 ++++++++++++++++++ .../views/right_panel/WidgetCard.tsx | 107 ++++++++ .../payloads/SetRightPanelPhasePayload.ts | 1 + src/settings/Settings.ts | 4 + src/stores/RightPanelStorePhases.ts | 3 + 14 files changed, 844 insertions(+), 43 deletions(-) create mode 100644 res/css/views/right_panel/_BaseCard.scss create mode 100644 res/css/views/right_panel/_RoomSummaryCard.scss create mode 100644 res/css/views/right_panel/_WidgetCard.scss create mode 100644 src/components/views/right_panel/BaseCard.tsx create mode 100644 src/components/views/right_panel/RoomSummaryCard.tsx create mode 100644 src/components/views/right_panel/WidgetCard.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 45ed6b3300..27ec1088c3 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -155,9 +155,12 @@ @import "./views/messages/_UnknownBody.scss"; @import "./views/messages/_ViewSourceEvent.scss"; @import "./views/messages/_common_CryptoEvent.scss"; +@import "./views/right_panel/_BaseCard.scss"; @import "./views/right_panel/_EncryptionInfo.scss"; +@import "./views/right_panel/_RoomSummaryCard.scss"; @import "./views/right_panel/_UserInfo.scss"; @import "./views/right_panel/_VerificationPanel.scss"; +@import "./views/right_panel/_WidgetCard.scss"; @import "./views/room_settings/_AliasSettings.scss"; @import "./views/rooms/_AppsDrawer.scss"; @import "./views/rooms/_Autocomplete.scss"; diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss new file mode 100644 index 0000000000..c66b7261e3 --- /dev/null +++ b/res/css/views/right_panel/_BaseCard.scss @@ -0,0 +1,164 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_BaseCard { + padding: 0 8px; + overflow: hidden; + display: flex; + flex-direction: column; + flex: 1; + + .mx_BaseCard_header { + margin: 8px 0; + + h2 { + margin: 0 44px; + font-size: $font-18px; + font-weight: $font-semi-bold; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .mx_BaseCard_back, .mx_BaseCard_close { + position: absolute; + background-color: rgba(141, 151, 165, 0.2); // TODO + height: 20px; + width: 20px; + margin: 12px; + top: 0; + + &::before { + content: ""; + position: absolute; + height: 16px; + width: 16px; + top: 2px; + left: 2px; + mask-repeat: no-repeat; + mask-position: center; + background-color: $rightpanel-button-color; + } + } + + .mx_BaseCard_back { + border-radius: 4px; + left: 0; + + &::before { + transform: rotate(90deg); + mask-size: 20px; + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); // TODO + } + } + + .mx_BaseCard_close { + border-radius: 10px; + right: 0; + + &::before { + mask-image: url('$(res)/img/icons-close.svg'); // TODO + } + } + } + + .mx_AutoHideScrollbar { + // collapse the margin into a padding to move the scrollbar into the right gutter + margin-right: -8px; + padding-right: 8px; + min-height: 0; + width: 100%; + height: 100%; + } + + .mx_BaseCard_Group { + margin: 20px 0 16px; + + & > * { + margin-left: 10px; + margin-right: 10px; + } + + h1 { + color: $tertiary-fg-color; + font-size: $font-12px; + font-weight: 500; + } + + .mx_BaseCard_Button { + padding: 10px 38px 10px 12px; + margin: 0; + position: relative; + font-size: $font-13px; + height: 20px; + line-height: 20px; + border-radius: 8px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + &:hover { + background-color: rgba(141, 151, 165, 0.1); + } + + &::after { + content: ''; + position: absolute; + top: 10px; + right: 6px; + height: 20px; + width: 20px; + mask-repeat: no-repeat; + mask-position: center; + background-color: $icon-button-color; + transform: rotate(270deg); + mask-size: 20px; + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); // TODO + } + } + } + + .mx_BaseCard_footer { + padding-top: 4px; + text-align: center; + + .mx_AccessibleButton_kind_secondary { + color: $secondary-fg-color; + background-color: rgba(141, 151, 165, 0.2); // TODO + font-weight: $font-semi-bold; + font-size: $font-14px; + min-width: 70px; + + & + .mx_AccessibleButton_kind_secondary { + margin-left: 16px; + } + } + } +} + +.mx_FilePanel, +.mx_UserInfo, +.mx_NotificationPanel, +.mx_MemberList { + &.mx_BaseCard { + padding: 32px 0 0; + + .mx_AutoHideScrollbar { + margin-right: unset; + padding-right: unset; + } + } +} diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss new file mode 100644 index 0000000000..3c46727348 --- /dev/null +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -0,0 +1,157 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_RoomSummaryCard { + // shrink left gutter by 12 and instead add it as margin to all things except the buttons + // as their hover effect should go into the gutter + & > * { // TODO consolidate this as the standard effect + margin-left: 10px; + margin-right: 10px; + } + .mx_AutoHideScrollbar { + margin-left: 0; + } + + .mx_BaseCard_header { + text-align: center; + margin-top: 20px; + + h2 { + font-weight: $font-semi-bold; + font-size: $font-18px; + margin: 12px 0 4px; + } + + .mx_RoomSummaryCard_alias { + font-size: $font-13px; + color: $secondary-fg-color; + } + + h2, .mx_RoomSummaryCard_alias { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + } + + .mx_RoomSummaryCard_avatar { + display: inline-flex; + + .mx_RoomSummaryCard_e2ee { + display: inline-block; + position: relative; + width: 54px; + height: 54px; + border-radius: 50%; + background-color: #737D8C; + margin-top: -3px; // alignment + margin-left: -10px; // overlap + border: 3px solid $dark-panel-bg-color; + + &::before { + content: ''; + position: absolute; + top: 13px; + left: 13px; + height: 28px; + width: 28px; + mask-size: cover; + mask-repeat: no-repeat; + mask-position: center; + mask-image: url('$(res)/img/e2e/disabled.svg'); + background-color: #ffffff; + } + } + + .mx_RoomSummaryCard_e2ee_secure{ + background-color: #5ABFF2; + &::before { + mask-image: url('$(res)/img/e2e/normal.svg'); + } + } + } + } + + .mx_RoomSummaryCard_aboutGroup { + .mx_RoomSummaryCard_Button { + padding-left: 48px; + + &::before { + content: ''; + position: absolute; + top: 8px; + left: 8px; + height: 24px; + width: 24px; + mask-repeat: no-repeat; + mask-position: center; + background-color: $icon-button-color; + } + } + } + + .mx_RoomSummaryCard_appsGroup { + .mx_RoomSummaryCard_Button { + padding-left: 10px; + color: $tertiary-fg-color; + + span { + color: $primary-fg-color; + } + + img { + vertical-align: top; + margin-right: 18px; + border-radius: 4px; + } + + &::before { + content: unset; + } + } + + .mx_RoomSummaryCard_icon_app_pinned::after { + mask-image: url('$(res)/img/element-icons/room/pin2.svg'); + background-color: $accent-color; + transform: unset; + } + } + + .mx_AccessibleButton_kind_link { + padding: 0; + margin-top: 12px; + margin-bottom: 12px; + font-size: $font-13px; + font-weight: $font-semi-bold; + } +} + +.mx_RoomSummaryCard_icon_people::before { + mask-image: url("$(res)/img/element-icons/room/members.svg"); +} + +.mx_RoomSummaryCard_icon_files::before { + mask-image: url('$(res)/img/element-icons/room/files.svg'); +} + +.mx_RoomSummaryCard_icon_share::before { + mask-image: url('$(res)/img/element-icons/room/share.svg'); +} + +.mx_RoomSummaryCard_icon_settings::before { + mask-image: url('$(res)/img/element-icons/settings.svg'); +} diff --git a/res/css/views/right_panel/_WidgetCard.scss b/res/css/views/right_panel/_WidgetCard.scss new file mode 100644 index 0000000000..b863c53c33 --- /dev/null +++ b/res/css/views/right_panel/_WidgetCard.scss @@ -0,0 +1,25 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_WidgetCard { + height: 100%; + display: contents; + + .mx_AppTileFullWidth { + height: 100%; + border: 0; + } +} diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index a880a7bee2..d240877507 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -236,10 +236,6 @@ limitations under the License. } } -.mx_RoomHeader_settingsButton::before { - mask-image: url('$(res)/img/element-icons/settings.svg'); -} - .mx_RoomHeader_forgetButton::before { mask-image: url('$(res)/img/element-icons/leave.svg'); width: 26px; @@ -249,14 +245,6 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/search-inset.svg'); } -.mx_RoomHeader_shareButton::before { - mask-image: url('$(res)/img/element-icons/room/share.svg'); -} - -.mx_RoomHeader_manageIntegsButton::before { - mask-image: url('$(res)/img/element-icons/room/integrations.svg'); -} - .mx_RoomHeader_showPanel { height: 16px; } diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index b2b1ceae00..57c0f46fad 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -32,6 +32,9 @@ import {RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPa import RightPanelStore from "../../stores/RightPanelStore"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import {Action} from "../../dispatcher/actions"; +import RoomSummaryCard from "../views/right_panel/RoomSummaryCard"; +import WidgetCard from "../views/right_panel/WidgetCard"; +import defaultDispatcher from "../../dispatcher/dispatcher"; export default class RightPanel extends React.Component { static get propTypes() { @@ -182,6 +185,7 @@ export default class RightPanel extends React.Component { event: payload.event, verificationRequest: payload.verificationRequest, verificationRequestPromise: payload.verificationRequestPromise, + widgetId: payload.widgetId, }); } } @@ -209,6 +213,14 @@ export default class RightPanel extends React.Component { } }; + onClose = () => { + // the RightPanelStore has no way of knowing which mode room/group it is in, so we handle closing here + defaultDispatcher.dispatch({ + action: Action.ToggleRightPanel, + type: this.props.groupId ? "group" : "room", + }); + }; + render() { const MemberList = sdk.getComponent('rooms.MemberList'); const UserInfo = sdk.getComponent('right_panel.UserInfo'); @@ -225,17 +237,24 @@ export default class RightPanel extends React.Component { switch (this.state.phase) { case RightPanelPhases.RoomMemberList: if (this.props.room.roomId) { - panel = ; + panel = ; } break; + case RightPanelPhases.GroupMemberList: if (this.props.groupId) { panel = ; } break; + case RightPanelPhases.GroupRoomList: panel = ; break; + case RightPanelPhases.RoomMemberInfo: case RightPanelPhases.EncryptionPanel: panel = ; break; + case RightPanelPhases.Room3pidMemberInfo: panel = ; break; + case RightPanelPhases.GroupMemberInfo: panel = ; break; + case RightPanelPhases.GroupRoomInfo: panel = ; break; + case RightPanelPhases.NotificationPanel: - panel = ; + panel = ; break; + case RightPanelPhases.FilePanel: - panel = ; + panel = ; + break; + + case RightPanelPhases.RoomSummary: + panel = ; + break; + + case RightPanelPhases.Widget: + panel = ; break; } diff --git a/src/components/views/right_panel/BaseCard.tsx b/src/components/views/right_panel/BaseCard.tsx new file mode 100644 index 0000000000..3e95da1bc1 --- /dev/null +++ b/src/components/views/right_panel/BaseCard.tsx @@ -0,0 +1,93 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, {ReactNode} from 'react'; +import classNames from 'classnames'; + +import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; +import {_t} from "../../../languageHandler"; +import AccessibleButton from "../elements/AccessibleButton"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import {Action} from "../../../dispatcher/actions"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; + +interface IProps { + header?: ReactNode; + footer?: ReactNode; + className?: string; + withoutScrollContainer?: boolean; + previousPhase?: RightPanelPhases; + onClose?(): void; +} + +interface IGroupProps { + className?: string; + title: string; +} + +export const Group: React.FC = ({ className, title, children }) => { + return

    +

    {title}

    + {children} +
    ; +}; + +const BaseCard: React.FC = ({ + onClose, + className, + header, + footer, + withoutScrollContainer, + previousPhase, + children, +}) => { + let backButton; + if (previousPhase) { + const onBackClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: previousPhase, + }); + }; + backButton = ; + } + + let closeButton; + if (onClose) { + closeButton = ; + } + + if (!withoutScrollContainer) { + children = + { children } + ; + } + + return ( +
    +
    + { backButton } + { closeButton } + { header } +
    + { children } + { footer &&
    { footer }
    } +
    + ); +}; + +export default BaseCard; diff --git a/src/components/views/right_panel/HeaderButtons.tsx b/src/components/views/right_panel/HeaderButtons.tsx index e922959bbb..543c7c067f 100644 --- a/src/components/views/right_panel/HeaderButtons.tsx +++ b/src/components/views/right_panel/HeaderButtons.tsx @@ -96,8 +96,7 @@ export default abstract class HeaderButtons extends React.Component + return
    {this.renderButtons()}
    ; } diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index 7d732b8ae3..e19a440005 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -26,7 +26,10 @@ import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; import {Action} from "../../../dispatcher/actions"; import {ActionPayload} from "../../../dispatcher/payloads"; -const MEMBER_PHASES = [ +const ROOM_INFO_PHASES = [ + RightPanelPhases.RoomSummary, + RightPanelPhases.Widget, + RightPanelPhases.FilePanel, RightPanelPhases.RoomMemberList, RightPanelPhases.RoomMemberInfo, RightPanelPhases.EncryptionPanel, @@ -54,20 +57,10 @@ export default class RoomHeaderButtons extends HeaderButtons { } } - private onMembersClicked = () => { - if (this.state.phase === RightPanelPhases.RoomMemberInfo) { - // send the active phase to trigger a toggle - // XXX: we should pass refireParams here but then it won't collapse as we desire it to - this.setPhase(RightPanelPhases.RoomMemberInfo); - } else { - // This toggles for us, if needed - this.setPhase(RightPanelPhases.RoomMemberList); - } - }; - - private onFilesClicked = () => { + // TODO make it restore whatever widget they were on last + private onRoomSummaryClicked = () => { // This toggles for us, if needed - this.setPhase(RightPanelPhases.FilePanel); + this.setPhase(RightPanelPhases.RoomSummary); }; private onNotificationsClicked = () => { @@ -77,19 +70,17 @@ export default class RoomHeaderButtons extends HeaderButtons { public renderButtons() { return [ - , - , - = ({ children, className, onClick }) => { + return + { children } + ; +}; + +export const useWidgets = (room: Room) => { + const [apps, setApps] = useState(WidgetStore.instance.getApps(room)); + + const updateApps = useCallback(() => { + // Copy the array so that we always trigger a re-render, as some updates mutate the array of apps/settings + setApps([...WidgetStore.instance.getApps(room)]); + }, [room]); + + useEffect(updateApps, [room]); + useEventEmitter(WidgetEchoStore, "update", updateApps); + useEventEmitter(WidgetStore.instance, room.roomId, updateApps); + + return apps; +}; + +const AppsSection: React.FC = ({ room }) => { + const cli = useContext(MatrixClientContext); + const apps = useWidgets(room); + + const onManageIntegrations = () => { + const managers = IntegrationManagers.sharedInstance(); + if (!managers.hasManager()) { + managers.openNoManagerDialog(); + } else { + if (SettingsStore.getValue("feature_many_integration_managers")) { + managers.openAll(room); + } else { + managers.getPrimaryManager().open(room); + } + } + }; + + return + { apps.map(app => { + const name = WidgetUtils.getWidgetName(app); + const dataTitle = WidgetUtils.getWidgetDataTitle(app); + const subtitle = dataTitle && " - " + dataTitle; + + let iconUrls = [require("../../../../res/img/element-icons/room/default_app.svg")]; + // heuristics for some better icons until Widgets support their own icons + if (app.type.includes("meeting") || app.type.includes("calendar")) { + iconUrls = [require("../../../../res/img/element-icons/room/default_cal.svg")]; + } else if (app.type.includes("pad") || app.type.includes("doc") || app.type.includes("calc")) { + iconUrls = [require("../../../../res/img/element-icons/room/default_doc.svg")]; + } else if (app.type.includes("clock")) { + iconUrls = [require("../../../../res/img/element-icons/room/default_clock.svg")]; + } + + if (app.avatar_url) { // MSC2765 + iconUrls.unshift(getHttpUriForMxc(cli.getHomeserverUrl(), app.avatar_url, 20, 20, "crop")); + } + + const isPinned = WidgetStore.instance.isPinned(app.id); + const classes = classNames("mx_RoomSummaryCard_icon_app", { + mx_RoomSummaryCard_icon_app_pinned: isPinned, + }); + + if (isPinned) { + const onClick = () => { + WidgetStore.instance.unpinWidget(app.id); + }; + + return + + {name} + { subtitle } + + } + + const onOpenWidgetClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.Widget, + refireParams: { + widgetId: app.id, + }, + }); + }; + + return ; + }) } + + + { apps.length > 0 ? _t("Edit apps") : _t("Add applications") } + + ; +}; + +const onRoomMembersClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomMemberList, + }); +}; + +const onRoomFilesClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.FilePanel, + }); +}; + +const onRoomSettingsClick = () => { + defaultDispatcher.dispatch({ action: "open_room_settings" }); +}; + +const RoomSummaryCard: React.FC = ({ room, onClose }) => { + const cli = useContext(MatrixClientContext); + + const onShareRoomClick = () => { + Modal.createTrackedDialog('share room dialog', '', ShareDialog, { + target: room, + }); + }; + + const isRoomEncrypted = useIsEncrypted(cli, room); + + const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || ""; + const header = +
    + + +
    + +

    { room.name }

    +
    + { alias } +
    +
    ; + + return + + + + + + + + + ; +}; + +export default RoomSummaryCard; diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx new file mode 100644 index 0000000000..c447bb2952 --- /dev/null +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -0,0 +1,107 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, {useContext, useEffect} from "react"; +import {Room} from "matrix-js-sdk/src/models/room"; + +import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import BaseCard from "./BaseCard"; +import WidgetUtils from "../../../utils/WidgetUtils"; +import AccessibleButton from "../elements/AccessibleButton"; +import AppTile from "../elements/AppTile"; +import {_t} from "../../../languageHandler"; +import {useWidgets} from "./RoomSummaryCard"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import {Action} from "../../../dispatcher/actions"; +import {WidgetStore} from "../../../stores/WidgetStore"; + +interface IProps { + room: Room; + widgetId: string; + onClose(): void; +} + +const onFinished = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomSummary, + }); +} + +const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { + const cli = useContext(MatrixClientContext); + + const apps = useWidgets(room); + const app = apps.find(a => a.id === widgetId); + const isPinned = app && WidgetStore.instance.isPinned(app.id); + + useEffect(() => { + if (!app || isPinned) { + // TODO maybe we should do this in the ActiveWidgetStore instead + onFinished(); + } + }, [app, isPinned]); + + // Don't render anything as we are about to transition + if (!app || isPinned) return null; + + const header = +

    { WidgetUtils.getWidgetName(app) }

    +
    ; + + const onPinClick = () => { + WidgetStore.instance.pinWidget(app.id); + }; + + const onEditClick = () => { + WidgetUtils.editWidget(room, app); + }; + + const footer = + + { _t("Pin to room") } + + + { _t("Edit") } + + ; + + return + + ; +}; + +export default WidgetCard; diff --git a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts index 75dea9f3df..4126e8a669 100644 --- a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts +++ b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts @@ -34,4 +34,5 @@ export interface SetRightPanelPhaseRefireParams { groupRoomId?: string; // XXX: The type for event should 'view_3pid_invite' action's payload event?: any; + widgetId?: string; } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 95861e11df..43bb989d1f 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -607,4 +607,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td("Enable experimental, compact IRC style layout"), default: false, }, + "Widgets.pinned": { + supportedLevels: LEVELS_ROOM_OR_ACCOUNT, + default: [], + }, }; diff --git a/src/stores/RightPanelStorePhases.ts b/src/stores/RightPanelStorePhases.ts index 9045b17193..11b51dfc2d 100644 --- a/src/stores/RightPanelStorePhases.ts +++ b/src/stores/RightPanelStorePhases.ts @@ -22,6 +22,8 @@ export enum RightPanelPhases { NotificationPanel = 'NotificationPanel', RoomMemberInfo = 'RoomMemberInfo', EncryptionPanel = 'EncryptionPanel', + RoomSummary = 'RoomSummary', + Widget = 'Widget', Room3pidMemberInfo = 'Room3pidMemberInfo', // Group stuff @@ -34,6 +36,7 @@ export enum RightPanelPhases { // These are the phases that are safe to persist (the ones that don't require additional // arguments). export const RIGHT_PANEL_PHASES_NO_ARGS = [ + RightPanelPhases.RoomSummary, RightPanelPhases.NotificationPanel, RightPanelPhases.FilePanel, RightPanelPhases.RoomMemberList, From 98b59fb217b705c934355093fe71ec0a63f407d1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 10:19:51 +0100 Subject: [PATCH 0103/1014] Consolidate all the work thus far --- res/css/structures/_HeaderButtons.scss | 8 ++ res/css/structures/_MainSplit.scss | 1 + res/css/structures/_RightPanel.scss | 17 ++-- .../context_menus/_IconizedContextMenu.scss | 5 +- res/css/views/right_panel/_BaseCard.scss | 11 +-- res/css/views/right_panel/_UserInfo.scss | 4 +- src/@types/global.d.ts | 2 +- src/components/structures/ContextMenu.tsx | 6 +- src/components/structures/FilePanel.js | 35 ++++++-- .../structures/NotificationPanel.js | 38 +++++---- src/components/structures/RoomView.js | 6 +- .../context_menus/IconizedContextMenu.tsx | 4 +- .../views/context_menus/WidgetContextMenu.js | 11 +++ src/components/views/elements/AppTile.js | 39 +++++---- .../views/elements/ManageIntegsButton.js | 63 -------------- .../views/right_panel/RoomSummaryCard.tsx | 2 +- src/components/views/right_panel/UserInfo.js | 30 +++---- .../views/right_panel/WidgetCard.tsx | 84 +++++++++++++++++-- src/components/views/rooms/AppsDrawer.js | 52 ++---------- src/components/views/rooms/MemberList.js | 51 ++++++----- src/components/views/rooms/RoomHeader.js | 36 -------- src/dispatcher/actions.ts | 10 +++ .../payloads/AppTileActionPayload.ts | 23 +++++ src/i18n/strings/en_EN.json | 33 +++++--- src/stores/WidgetStore.ts | 10 ++- src/utils/WidgetUtils.js | 30 +++++-- 26 files changed, 337 insertions(+), 274 deletions(-) delete mode 100644 src/components/views/elements/ManageIntegsButton.js create mode 100644 src/dispatcher/payloads/AppTileActionPayload.ts diff --git a/res/css/structures/_HeaderButtons.scss b/res/css/structures/_HeaderButtons.scss index 9ef40e9d6a..72b663ef0e 100644 --- a/res/css/structures/_HeaderButtons.scss +++ b/res/css/structures/_HeaderButtons.scss @@ -18,6 +18,14 @@ limitations under the License. display: flex; } +.mx_RoomHeader_buttons + .mx_HeaderButtons { + // remove the | separator line for when next to RoomHeaderButtons + // TODO: remove this once when we redo communities and make the right panel similar to the new rooms one + &::before { + content: unset; + } +} + .mx_HeaderButtons::before { content: ""; background-color: $header-divider-color; diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index dc62cb8218..ad1656efbb 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -25,6 +25,7 @@ limitations under the License. padding: 5px; // margin left to not allow the handle to not encroach on the space for the scrollbar margin-left: 8px; + height: calc(100vh - 51px); // height of .mx_RoomHeader.light-panel &:hover .mx_RightPanel_ResizeHandle { // Need to use important to override element style attributes diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index 7e5ab7cdbd..b9b765db56 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -70,21 +70,16 @@ limitations under the License. } } -.mx_RightPanel_membersButton::before { - mask-image: url('$(res)/img/element-icons/room/members.svg'); - mask-position: center; -} - -.mx_RightPanel_filesButton::before { - mask-image: url('$(res)/img/element-icons/room/files.svg'); - mask-position: center; -} - .mx_RightPanel_notifsButton::before { mask-image: url('$(res)/img/element-icons/notifications.svg'); mask-position: center; } +.mx_RightPanel_roomSummaryButton::before { + mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); + mask-position: center; +} + .mx_RightPanel_groupMembersButton::before { mask-image: url('$(res)/img/element-icons/community-members.svg'); mask-position: center; @@ -144,7 +139,7 @@ limitations under the License. } .mx_RightPanel_empty { - margin-right: -42px; + margin-right: -28px; h2 { font-weight: 700; diff --git a/res/css/views/context_menus/_IconizedContextMenu.scss b/res/css/views/context_menus/_IconizedContextMenu.scss index 7913058995..d911ac6dfe 100644 --- a/res/css/views/context_menus/_IconizedContextMenu.scss +++ b/res/css/views/context_menus/_IconizedContextMenu.scss @@ -82,7 +82,6 @@ limitations under the License. } span.mx_IconizedContextMenu_label { // labels - padding-left: 14px; width: 100%; flex: 1; @@ -91,6 +90,10 @@ limitations under the License. overflow: hidden; white-space: nowrap; } + + .mx_IconizedContextMenu_icon + .mx_IconizedContextMenu_label { + padding-left: 14px; + } } } diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss index c66b7261e3..661f496e8d 100644 --- a/res/css/views/right_panel/_BaseCard.scss +++ b/res/css/views/right_panel/_BaseCard.scss @@ -134,17 +134,18 @@ limitations under the License. .mx_BaseCard_footer { padding-top: 4px; text-align: center; + display: flex; + justify-content: space-around; .mx_AccessibleButton_kind_secondary { color: $secondary-fg-color; background-color: rgba(141, 151, 165, 0.2); // TODO font-weight: $font-semi-bold; font-size: $font-14px; - min-width: 70px; - - & + .mx_AccessibleButton_kind_secondary { - margin-left: 16px; - } + } + + .mx_AccessibleButton_disabled { + cursor: not-allowed; } } } diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss index 6f86d1ad18..9fcf06e5d0 100644 --- a/res/css/views/right_panel/_UserInfo.scss +++ b/res/css/views/right_panel/_UserInfo.scss @@ -15,7 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_UserInfo { +.mx_UserInfo.mx_BaseCard { + // UserInfo has a circular image at the top so it fits between the back & close buttons + padding-top: 0; display: flex; flex-direction: column; flex: 1; diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index de3eb5e767..e1111a8a94 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -29,7 +29,7 @@ import {ActiveRoomObserver} from "../ActiveRoomObserver"; import {Notifier} from "../Notifier"; import type {Renderer} from "react-dom"; import RightPanelStore from "../stores/RightPanelStore"; -import {WidgetStore} from "../stores/WidgetStore"; +import WidgetStore from "../stores/WidgetStore"; declare global { interface Window { diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index 64e0160d83..884f77aba5 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {CSSProperties, useRef, useState} from "react"; +import React, {CSSProperties, RefObject, useRef, useState} from "react"; import ReactDOM from "react-dom"; import classNames from "classnames"; @@ -416,8 +416,8 @@ export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None return menuOptions; }; -export const useContextMenu = () => { - const button = useRef(null); +export const useContextMenu = (): [boolean, RefObject, () => void, () => void, (val: boolean) => void] => { + const button = useRef(null); const [isOpen, setIsOpen] = useState(false); const open = () => { setIsOpen(true); diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 8aa1192458..8812ba4302 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -23,6 +23,8 @@ import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; import EventIndexPeg from "../../indexing/EventIndexPeg"; import { _t } from '../../languageHandler'; +import BaseCard from "../views/right_panel/BaseCard"; +import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; /* * Component which shows the filtered file using a TimelinePanel @@ -30,6 +32,7 @@ import { _t } from '../../languageHandler'; class FilePanel extends React.Component { static propTypes = { roomId: PropTypes.string.isRequired, + onClose: PropTypes.func.isRequired, }; // This is used to track if a decrypted event was a live event and should be @@ -188,18 +191,26 @@ class FilePanel extends React.Component { render() { if (MatrixClientPeg.get().isGuest()) { - return
    + return
    { _t("You must register to use this functionality", {}, { 'a': (sub) => { sub } }) }
    -
    ; + ; } else if (this.noRoom) { - return
    + return
    { _t("You must join the room to see its files") }
    -
    ; + ; } // wrap a TimelinePanel with the jump-to-event bits turned off. @@ -215,7 +226,11 @@ class FilePanel extends React.Component { // console.log("rendering TimelinePanel for timelineSet " + this.state.timelineSet.room.roomId + " " + // "(" + this.state.timelineSet._timelines.join(", ") + ")" + " with key " + this.props.roomId); return ( -
    + -
    + ); } else { return ( -
    + -
    + ); } } diff --git a/src/components/structures/NotificationPanel.js b/src/components/structures/NotificationPanel.js index 6ae7f91142..a561ade799 100644 --- a/src/components/structures/NotificationPanel.js +++ b/src/components/structures/NotificationPanel.js @@ -17,14 +17,21 @@ limitations under the License. */ import React from 'react'; +import PropTypes from "prop-types"; + import { _t } from '../../languageHandler'; import {MatrixClientPeg} from "../../MatrixClientPeg"; import * as sdk from "../../index"; +import BaseCard from "../views/right_panel/BaseCard"; /* * Component which shows the global notification list using a TimelinePanel */ class NotificationPanel extends React.Component { + static propTypes = { + onClose: PropTypes.func.isRequired, + }; + render() { // wrap a TimelinePanel with the jump-to-event bits turned off. const TimelinePanel = sdk.getComponent("structures.TimelinePanel"); @@ -35,28 +42,27 @@ class NotificationPanel extends React.Component {

    {_t('You have no visible notifications in this room.')}

    ); + let content; const timelineSet = MatrixClientPeg.get().getNotifTimelineSet(); if (timelineSet) { - return ( -
    - -
    + content = ( + ); } else { console.error("No notifTimelineSet available!"); - return ( -
    - -
    - ); + content = ; } + + return + { content } + ; } } diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index d98a19ebe8..be75f56e1d 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -56,6 +56,7 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import { shieldStatusForRoom } from '../../utils/ShieldUtils'; import {Action} from "../../dispatcher/actions"; import {SettingLevel} from "../../settings/SettingLevel"; +import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; const DEBUG = false; let debuglog = function() {}; @@ -1356,7 +1357,10 @@ export default class RoomView extends React.Component { }; onSettingsClick = () => { - dis.dispatch({ action: 'open_room_settings' }); + dis.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomSummary, + }); }; onCancelClick = () => { diff --git a/src/components/views/context_menus/IconizedContextMenu.tsx b/src/components/views/context_menus/IconizedContextMenu.tsx index b3ca9fde6f..a3fb00a9f4 100644 --- a/src/components/views/context_menus/IconizedContextMenu.tsx +++ b/src/components/views/context_menus/IconizedContextMenu.tsx @@ -37,7 +37,7 @@ interface IOptionListProps { } interface IOptionProps extends React.ComponentProps { - iconClassName: string; + iconClassName?: string; } interface ICheckboxProps extends React.ComponentProps { @@ -92,7 +92,7 @@ export const IconizedContextMenuCheckbox: React.FC = ({ export const IconizedContextMenuOption: React.FC = ({label, iconClassName, ...props}) => { return - + { iconClassName && } {label} ; }; diff --git a/src/components/views/context_menus/WidgetContextMenu.js b/src/components/views/context_menus/WidgetContextMenu.js index 1ec74b2e6c..9182b92c8c 100644 --- a/src/components/views/context_menus/WidgetContextMenu.js +++ b/src/components/views/context_menus/WidgetContextMenu.js @@ -26,6 +26,9 @@ export default class WidgetContextMenu extends React.Component { // Callback for when the revoke button is clicked. Required. onRevokeClicked: PropTypes.func.isRequired, + // Callback for when the unpin button is clicked. Required. + onUnpinClicked: PropTypes.func.isRequired, + // Callback for when the snapshot button is clicked. Button not shown // without a callback. onSnapshotClicked: PropTypes.func, @@ -70,6 +73,8 @@ export default class WidgetContextMenu extends React.Component { this.proxyClick(this.props.onRevokeClicked); }; + onUnpinClicked = () => this.proxyClick(this.props.onUnpinClicked); + render() { const options = []; @@ -81,6 +86,12 @@ export default class WidgetContextMenu extends React.Component { ); } + options.push( + + {_t("Unpin")} + , + ); + if (this.props.onReloadClicked) { options.push( diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 1c93841afb..812539ec0d 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -42,6 +42,8 @@ import {WidgetType} from "../../../widgets/WidgetType"; import {Capability} from "../../../widgets/WidgetApi"; import {sleep} from "../../../utils/promise"; import {SettingLevel} from "../../../settings/SettingLevel"; +import WidgetStore from "../../../stores/WidgetStore"; +import {Action} from "../../../dispatcher/actions"; const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:']; const ENABLE_REACT_PERF = false; @@ -315,17 +317,7 @@ export default class AppTile extends React.Component { } _onSnapshotClick() { - console.log("Requesting widget snapshot"); - ActiveWidgetStore.getWidgetMessaging(this.props.app.id).getScreenshot() - .catch((err) => { - console.error("Failed to get screenshot", err); - }) - .then((screenshot) => { - dis.dispatch({ - action: 'picture_snapshot', - file: screenshot, - }, true); - }); + WidgetUtils.snapshotWidget(this.props.app); } /** @@ -406,6 +398,10 @@ export default class AppTile extends React.Component { } } + _onUnpinClicked = () => { + WidgetStore.instance.unpinWidget(this.props.app.id); + } + _onRevokeClicked() { console.info("Revoke widget permissions - %s", this.props.app.id); this._revokeWidgetPermission(); @@ -477,12 +473,20 @@ export default class AppTile extends React.Component { if (payload.widgetId === this.props.app.id) { switch (payload.action) { case 'm.sticker': - if (this._hasCapability('m.sticker')) { - dis.dispatch({action: 'post_sticker_message', data: payload.data}); - } else { - console.warn('Ignoring sticker message. Invalid capability'); - } - break; + if (this._hasCapability('m.sticker')) { + dis.dispatch({action: 'post_sticker_message', data: payload.data}); + } else { + console.warn('Ignoring sticker message. Invalid capability'); + } + break; + + case Action.AppTileDelete: + this._onDeleteClick(); + break; + + case Action.AppTileRevoke: + this._onRevokeClicked(); + break; } } } @@ -826,6 +830,7 @@ export default class AppTile extends React.Component { contextMenu = ( { - ev.preventDefault(); - - const managers = IntegrationManagers.sharedInstance(); - if (!managers.hasManager()) { - managers.openNoManagerDialog(); - } else { - if (SettingsStore.getValue("feature_many_integration_managers")) { - managers.openAll(this.props.room); - } else { - managers.getPrimaryManager().open(this.props.room); - } - } - }; - - render() { - let integrationsButton =
    ; - if (IntegrationManagers.sharedInstance().hasManager()) { - integrationsButton = ( - - ); - } - - return integrationsButton; - } -} - -ManageIntegsButton.propTypes = { - room: PropTypes.object.isRequired, -}; diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 018b02c879..64743e1bb8 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -39,7 +39,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import TextWithTooltip from "../elements/TextWithTooltip"; import BaseAvatar from "../avatars/BaseAvatar"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -import {IApp, WidgetStore} from "../../../stores/WidgetStore"; +import WidgetStore, {IApp} from "../../../stores/WidgetStore"; interface IProps { room: Room; diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 518bb133ce..a79e65cb8b 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -46,6 +46,7 @@ import { useAsyncMemo } from '../../../hooks/useAsyncMemo'; import { verifyUser, legacyVerifyUser, verifyDevice } from '../../../verification'; import {Action} from "../../../dispatcher/actions"; import {useIsEncrypted} from "../../../hooks/useIsEncrypted"; +import BaseCard from "./BaseCard"; const _disambiguateDevices = (devices) => { const names = Object.create(null); @@ -451,7 +452,7 @@ const _isMuted = (member, powerLevelContent) => { return member.powerLevel < levelToSend; }; -const useRoomPowerLevels = (cli, room) => { +export const useRoomPowerLevels = (cli, room) => { const [powerLevels, setPowerLevels] = useState({}); const update = useCallback(() => { @@ -1364,16 +1365,9 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { ; }; -const UserInfoHeader = ({onClose, member, e2eStatus}) => { +const UserInfoHeader = ({member, e2eStatus}) => { const cli = useContext(MatrixClientContext); - let closeButton; - if (onClose) { - closeButton = -
    - ; - } - const onMemberAvatarClick = useCallback(() => { const avatarUrl = member.getMxcAvatarUrl ? member.getMxcAvatarUrl() : member.avatarUrl; if (!avatarUrl) return; @@ -1448,7 +1442,6 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => { const displayName = member.name || member.displayname; return - { closeButton } { avatarElement }
    @@ -1510,15 +1503,16 @@ const UserInfo = ({user, groupId, roomId, onClose, phase=RightPanelPhases.RoomMe break; } - return ( -
    - - + let previousPhase: RightPanelPhases; + // We have no previousPhase for when viewing a UserInfo from a Group or without a Room at this time + if (room) { + previousPhase = RightPanelPhases.RoomMemberList; + } - { content } - -
    - ); + const header = ; + return + { content } + ; }; UserInfo.propTypes = { diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx index c447bb2952..8abff36c31 100644 --- a/src/components/views/right_panel/WidgetCard.tsx +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -28,7 +28,15 @@ import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; import {Action} from "../../../dispatcher/actions"; -import {WidgetStore} from "../../../stores/WidgetStore"; +import WidgetStore from "../../../stores/WidgetStore"; +import ActiveWidgetStore from "../../../stores/ActiveWidgetStore"; +import {ChevronFace, ContextMenuButton, useContextMenu} from "../../structures/ContextMenu"; +import IconizedContextMenu, { + IconizedContextMenuOption, + IconizedContextMenuOptionList, +} from "../context_menus/IconizedContextMenu"; +import {AppTileActionPayload} from "../../../dispatcher/payloads/AppTileActionPayload"; +import {Capability} from "../../../widgets/WidgetApi"; interface IProps { room: Room; @@ -50,6 +58,8 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { const app = apps.find(a => a.id === widgetId); const isPinned = app && WidgetStore.instance.isPinned(app.id); + const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu(); + useEffect(() => { if (!app || isPinned) { // TODO maybe we should do this in the ActiveWidgetStore instead @@ -64,6 +74,58 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => {

    { WidgetUtils.getWidgetName(app) }

    ; + const canModify = WidgetUtils.canUserModifyWidgets(room.roomId); + + let contextMenu; + if (menuDisplayed) { + let snapshotButton; + if (ActiveWidgetStore.widgetHasCapability(app.id, Capability.Screenshot)) { + const onSnapshotClick = () => { + WidgetUtils.snapshotWidget(app); + closeMenu(); + }; + + snapshotButton = ; + } + + let deleteButton; + if (canModify) { + const onDeleteClick = () => { + defaultDispatcher.dispatch({ + action: Action.AppTileDelete, + widgetId: app.id, + }); + closeMenu(); + }; + + deleteButton = ; + } + + const onRevokeClick = () => { + defaultDispatcher.dispatch({ + action: Action.AppTileRevoke, + widgetId: app.id, + }); + closeMenu(); + }; + + const rect = handle.current.getBoundingClientRect(); + contextMenu = ( + + + { snapshotButton } + { deleteButton } + + + + ); + } + const onPinClick = () => { WidgetStore.instance.pinWidget(app.id); }; @@ -73,12 +135,24 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { }; const footer = - - { _t("Pin to room") } - - + { _t("Edit") } + + { _t("Pin to room") } + + + ... + + + { contextMenu } ; return { - if (ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'im.vector.modular.widgets') { - return; - } - this._updateApps(); - }; - - _getApps() { - const widgets = WidgetEchoStore.getEchoedRoomWidgets( - this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room), - ); - return widgets.map((ev) => { - return WidgetUtils.makeAppConfig( - ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(), - ); - }); - } + _getApps = () => WidgetStore.instance.getApps(this.props.room, true); _updateApps = () => { - const apps = this._getApps(); this.setState({ - apps: apps, + apps: this._getApps(), }); }; @@ -144,18 +120,6 @@ export default class AppsDrawer extends React.Component { onClickAddWidget = (e) => { e.preventDefault(); - // Display a warning dialog if the max number of widgets have already been added to the room - const apps = this._getApps(); - if (apps && apps.length >= MAX_WIDGETS) { - const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); - const errorMsg = `The maximum number of ${MAX_WIDGETS} widgets have already been added to this room.`; - console.error(errorMsg); - Modal.createDialog(ErrorDialog, { - title: _t('Cannot add any more widgets'), - description: _t('The maximum permitted number of widgets have already been added to this room.'), - }); - return; - } this._launchManageIntegrations(); }; diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 1fdc67eee7..40b3b042b1 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -20,13 +20,14 @@ import React from 'react'; import { _t } from '../../../languageHandler'; import SdkConfig from '../../../SdkConfig'; import dis from '../../../dispatcher/dispatcher'; -import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import {isValid3pidInvite} from "../../../RoomInvite"; import rate_limited_func from "../../../ratelimitedfunc"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import * as sdk from "../../../index"; import CallHandler from "../../../CallHandler"; import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore"; +import BaseCard from "../right_panel/BaseCard"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; const INITIAL_LOAD_NUM_MEMBERS = 30; const INITIAL_LOAD_NUM_INVITED = 5; @@ -438,7 +439,13 @@ export default class MemberList extends React.Component { render() { if (this.state.loading) { const Spinner = sdk.getComponent("elements.Spinner"); - return
    ; + return + + ; } const SearchBox = sdk.getComponent('structures.SearchBox'); @@ -485,25 +492,29 @@ export default class MemberList extends React.Component { />; } - return ( -
    - { inviteButton } - -
    - - { invitedHeader } - { invitedSection } -
    -
    - - -
    + const footer = ( + ); + + return +
    + + { invitedHeader } + { invitedSection } +
    +
    ; } onInviteButtonClick = () => { diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 2a44f53d21..1a116838ac 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -18,14 +18,11 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; -import Modal from "../../../Modal"; import RateLimitedFunc from '../../../ratelimitedfunc'; import { linkifyElement } from '../../../HtmlUtils'; -import ManageIntegsButton from '../elements/ManageIntegsButton'; import {CancelButton} from './SimpleRoomHeader'; import SettingsStore from "../../../settings/SettingsStore"; import RoomHeaderButtons from '../right_panel/RoomHeaderButtons'; @@ -114,13 +111,6 @@ export default class RoomHeader extends React.Component { this.forceUpdate(); }; - onShareRoomClick = (ev) => { - const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); - Modal.createTrackedDialog('share room dialog', '', ShareDialog, { - target: this.props.room, - }); - }; - _hasUnreadPins() { const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", ''); if (!currentPinEvent) return false; @@ -150,7 +140,6 @@ export default class RoomHeader extends React.Component { render() { let searchStatus = null; let cancelButton = null; - let settingsButton = null; let pinnedEventsButton = null; if (this.props.onCancelClick) { @@ -214,14 +203,6 @@ export default class RoomHeader extends React.Component { />; } - if (this.props.onSettingsClick) { - settingsButton = - ; - } - if (this.props.onPinnedClick && SettingsStore.getValue('feature_pinning')) { let pinsIndicator = null; if (this._hasUnreadPins()) { @@ -258,26 +239,9 @@ export default class RoomHeader extends React.Component { title={_t("Search")} />; } - let shareRoomButton; - if (this.props.inRoom) { - shareRoomButton = - ; - } - - let manageIntegsButton; - if (this.props.room && this.props.room.roomId && this.props.inRoom) { - manageIntegsButton = ; - } - const rightRow =
    - { settingsButton } { pinnedEventsButton } - { shareRoomButton } - { manageIntegsButton } { forgetButton } { searchButton }
    ; diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 6fb71df30d..26d585b76e 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -94,4 +94,14 @@ export enum Action { * Trigged after the phase of the right panel is set. Should be used with AfterRightPanelPhaseChangePayload. */ AfterRightPanelPhaseChange = "after_right_panel_phase_change", + + /** + * Requests that the AppTile deletes the widget. Should be used with the AppTileActionPayload. + */ + AppTileDelete = "appTile_delete", + + /** + * Requests that the AppTile revokes the widget. Should be used with the AppTileActionPayload. + */ + AppTileRevoke = "appTile_revoke", } diff --git a/src/dispatcher/payloads/AppTileActionPayload.ts b/src/dispatcher/payloads/AppTileActionPayload.ts new file mode 100644 index 0000000000..3cdb0f8c1f --- /dev/null +++ b/src/dispatcher/payloads/AppTileActionPayload.ts @@ -0,0 +1,23 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +export interface AppTileActionPayload extends ActionPayload { + action: Action.AppTileDelete | Action.AppTileRevoke; + widgetId: string; +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 54a4bd6695..9abebfaf60 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1029,8 +1029,6 @@ "Remove %(phone)s?": "Remove %(phone)s?", "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.", "Phone Number": "Phone Number", - "Cannot add any more widgets": "Cannot add any more widgets", - "The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.", "Add a widget": "Add a widget", "Drop File Here": "Drop File Here", "Drop file here to upload": "Drop file here to upload", @@ -1115,10 +1113,8 @@ "(~%(count)s results)|other": "(~%(count)s results)", "(~%(count)s results)|one": "(~%(count)s result)", "Join Room": "Join Room", - "Settings": "Settings", "Forget room": "Forget room", "Search": "Search", - "Share room": "Share room", "Invites": "Invites", "Favourites": "Favourites", "People": "People", @@ -1135,6 +1131,7 @@ "Can't see what you’re looking for?": "Can't see what you’re looking for?", "Explore all public rooms": "Explore all public rooms", "%(count)s results|other": "%(count)s results", + "%(count)s results|one": "%(count)s result", "This room": "This room", "Joining room …": "Joining room …", "Loading …": "Loading …", @@ -1197,6 +1194,7 @@ "Favourited": "Favourited", "Favourite": "Favourite", "Low Priority": "Low Priority", + "Settings": "Settings", "Leave Room": "Leave Room", "Room options": "Room options", "%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.", @@ -1267,6 +1265,7 @@ "URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.", "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.", "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.", + "Back": "Back", "Waiting for you to accept on your other session…": "Waiting for you to accept on your other session…", "Waiting for %(displayName)s to accept…": "Waiting for %(displayName)s to accept…", "Accepting…": "Accepting…", @@ -1284,7 +1283,18 @@ "Yours, or the other users’ internet connection": "Yours, or the other users’ internet connection", "Yours, or the other users’ session": "Yours, or the other users’ session", "Members": "Members", - "Files": "Files", + "Room Info": "Room Info", + "Apps": "Apps", + "Unpin app": "Unpin app", + "Edit apps": "Edit apps", + "Add applications": "Add applications", + "Not encrypted": "Not encrypted", + "About": "About", + "%(count)s people|other": "%(count)s people", + "%(count)s people|one": "%(count)s person", + "Show files": "Show files", + "Share room": "Share room", + "Room settings": "Room settings", "Trusted": "Trusted", "Not trusted": "Not trusted", "%(count)s verified sessions|other": "%(count)s verified sessions", @@ -1362,6 +1372,12 @@ "You cancelled verification.": "You cancelled verification.", "Verification cancelled": "Verification cancelled", "Compare emoji": "Compare emoji", + "Reload": "Reload", + "Take a picture": "Take a picture", + "Remove for everyone": "Remove for everyone", + "Remove for me": "Remove for me", + "Edit": "Edit", + "Pin to room": "Pin to room", "Sunday": "Sunday", "Monday": "Monday", "Tuesday": "Tuesday", @@ -1379,7 +1395,6 @@ "Error decrypting audio": "Error decrypting audio", "React": "React", "Reply": "Reply", - "Edit": "Edit", "Message Actions": "Message Actions", "Attachment": "Attachment", "Error decrypting attachment": "Error decrypting attachment", @@ -1490,7 +1505,6 @@ "Download this file": "Download this file", "Information": "Information", "Language Dropdown": "Language Dropdown", - "Manage Integrations": "Manage Integrations", "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times", "%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)sjoined", @@ -1670,7 +1684,6 @@ "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.", "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.", "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)", - "Back": "Back", "Send": "Send", "Send Custom Event": "Send Custom Event", "You must specify an event type!": "You must specify an event type!", @@ -1911,10 +1924,8 @@ "Set status": "Set status", "Set a new status...": "Set a new status...", "View Community": "View Community", - "Reload": "Reload", + "Unpin": "Unpin", "Take picture": "Take picture", - "Remove for everyone": "Remove for everyone", - "Remove for me": "Remove for me", "This room is public": "This room is public", "Away": "Away", "User Status": "User Status", diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index b31fc99515..f6a838344a 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -46,7 +46,7 @@ interface IRoomWidgets { // TODO consolidate WidgetEchoStore into this // TODO consolidate ActiveWidgetStore into this -export class WidgetStore extends AsyncStoreWithClient { +export default class WidgetStore extends AsyncStoreWithClient { private static internalInstance = new WidgetStore(); private widgetMap = new Map(); @@ -159,6 +159,14 @@ export class WidgetStore extends AsyncStoreWithClient { return roomInfo ? roomInfo.pinned.has(widgetId) : false; } + public canPin(widgetId: string) { + // only allow pinning up to a max of two as we do not yet have grid splits + // the only case it will go to three is if you have two and then a Jitsi gets added + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + return roomInfo && roomInfo.pinned.size < 2; + } + public pinWidget(widgetId: string) { const roomId = this.getRoomId(widgetId); const roomInfo = this.getRoom(roomId); diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index d4ed093a24..771bc0887a 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -479,15 +479,6 @@ export default class WidgetUtils { return url.href; } - static editWidget(room, app) { - // TODO: Open the right manager for the widget - if (SettingsStore.getValue("feature_many_integration_managers")) { - IntegrationManagers.sharedInstance().openAll(room, 'type_' + app.type, app.id); - } else { - IntegrationManagers.sharedInstance().getPrimaryManager().open(room, 'type_' + app.type, app.id); - } - } - static getWidgetName(app) { if (!app || !app.name) return ""; return app.name.trim() || _t("Unknown App"); @@ -497,4 +488,25 @@ export default class WidgetUtils { if (!app || !app.data || !app.data.title) return ""; return app.data.title.trim(); } + + static editWidget(room, app) { + // TODO: Open the right manager for the widget + if (SettingsStore.getValue("feature_many_integration_managers")) { + IntegrationManagers.sharedInstance().openAll(room, 'type_' + app.type, app.id); + } else { + IntegrationManagers.sharedInstance().getPrimaryManager().open(room, 'type_' + app.type, app.id); + } + } + + static snapshotWidget(app) { + console.log("Requesting widget snapshot"); + ActiveWidgetStore.getWidgetMessaging(app.id).getScreenshot().catch((err) => { + console.error("Failed to get screenshot", err); + }).then((screenshot) => { + dis.dispatch({ + action: 'picture_snapshot', + file: screenshot, + }, true); + }); + } } From 0fe6ce18424da21cf8a77045b013af1dfb84bf12 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 11:18:41 +0100 Subject: [PATCH 0104/1014] Stage svgs --- res/css/views/right_panel/_RoomSummaryCard.scss | 2 +- res/img/element-icons/room/default_app.svg | 11 +++++++++++ res/img/element-icons/room/default_cal.svg | 6 ++++++ res/img/element-icons/room/default_clock.svg | 5 +++++ res/img/element-icons/room/default_doc.svg | 4 ++++ res/img/element-icons/room/pin-upright.svg | 7 +++++++ res/img/element-icons/room/room-summary.svg | 3 +++ 7 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 res/img/element-icons/room/default_app.svg create mode 100644 res/img/element-icons/room/default_cal.svg create mode 100644 res/img/element-icons/room/default_clock.svg create mode 100644 res/img/element-icons/room/default_doc.svg create mode 100644 res/img/element-icons/room/pin-upright.svg create mode 100644 res/img/element-icons/room/room-summary.svg diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index 3c46727348..41df1ca2eb 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -125,7 +125,7 @@ limitations under the License. } .mx_RoomSummaryCard_icon_app_pinned::after { - mask-image: url('$(res)/img/element-icons/room/pin2.svg'); + mask-image: url('$(res)/img/element-icons/room/pin-upright.svg'); background-color: $accent-color; transform: unset; } diff --git a/res/img/element-icons/room/default_app.svg b/res/img/element-icons/room/default_app.svg new file mode 100644 index 0000000000..08734170df --- /dev/null +++ b/res/img/element-icons/room/default_app.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/img/element-icons/room/default_cal.svg b/res/img/element-icons/room/default_cal.svg new file mode 100644 index 0000000000..5bced115cf --- /dev/null +++ b/res/img/element-icons/room/default_cal.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/element-icons/room/default_clock.svg b/res/img/element-icons/room/default_clock.svg new file mode 100644 index 0000000000..cc21716d15 --- /dev/null +++ b/res/img/element-icons/room/default_clock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/room/default_doc.svg b/res/img/element-icons/room/default_doc.svg new file mode 100644 index 0000000000..93e7507be3 --- /dev/null +++ b/res/img/element-icons/room/default_doc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/element-icons/room/pin-upright.svg b/res/img/element-icons/room/pin-upright.svg new file mode 100644 index 0000000000..9297f62a02 --- /dev/null +++ b/res/img/element-icons/room/pin-upright.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/element-icons/room/room-summary.svg b/res/img/element-icons/room/room-summary.svg new file mode 100644 index 0000000000..b6ac258b18 --- /dev/null +++ b/res/img/element-icons/room/room-summary.svg @@ -0,0 +1,3 @@ + + + From ef0843d4ad00bfd9c3572469351c5d771f858836 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 12:01:44 +0100 Subject: [PATCH 0105/1014] Iterate to match design --- res/css/views/right_panel/_WidgetCard.scss | 16 +++++++ .../views/right_panel/WidgetCard.tsx | 42 ++++++++++++++++--- src/stores/WidgetStore.ts | 3 +- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/res/css/views/right_panel/_WidgetCard.scss b/res/css/views/right_panel/_WidgetCard.scss index b863c53c33..0f859738b1 100644 --- a/res/css/views/right_panel/_WidgetCard.scss +++ b/res/css/views/right_panel/_WidgetCard.scss @@ -22,4 +22,20 @@ limitations under the License. height: 100%; border: 0; } + + &.mx_WidgetCard_noEdit { + .mx_AccessibleButton_kind_secondary { + margin: 0 12px; + + &:first-child { + // expand the Pin to room primary action + flex-grow: 1; + } + } + } +} + +.mx_WidgetCard_maxPinnedTooltip { + background-color: $notice-primary-color; + color: #ffffff; } diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx index 8abff36c31..bbd02c18fb 100644 --- a/src/components/views/right_panel/WidgetCard.tsx +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -37,6 +37,8 @@ import IconizedContextMenu, { } from "../context_menus/IconizedContextMenu"; import {AppTileActionPayload} from "../../../dispatcher/payloads/AppTileActionPayload"; import {Capability} from "../../../widgets/WidgetApi"; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; +import classNames from "classnames"; interface IProps { room: Room; @@ -134,13 +136,39 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { WidgetUtils.editWidget(room, app); }; - const footer = - + let editButton; + if (canModify) { + editButton = { _t("Edit") } - - + ; + } + + const pinButtonClasses = canModify ? "" : "mx_WidgetCard_widePinButton"; + + let pinButton; + if (WidgetStore.instance.canPin(app.id)) { + pinButton = { _t("Pin to room") } - + ; + } else { + pinButton = + { _t("Pin to room") } + ; + } + + const footer = + { editButton } + { pinButton } = ({ room, widgetId, onClose }) => { return { private constructor() { super(defaultDispatcher, {}); - SettingsStore.watchSetting("Widgets.pinned", IRRELEVANT_ROOM, this.onPinnedWidgetsChange); + SettingsStore.watchSetting("Widgets.pinned", null, this.onPinnedWidgetsChange); WidgetEchoStore.on("update", this.onWidgetEchoStoreUpdate); } From 73dcba1425b3b8d87d9a3bef9c843d52be8fbb15 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 12:02:48 +0100 Subject: [PATCH 0106/1014] i18n --- src/i18n/strings/en_EN.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9abebfaf60..b3da8cbbb1 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1372,12 +1372,12 @@ "You cancelled verification.": "You cancelled verification.", "Verification cancelled": "Verification cancelled", "Compare emoji": "Compare emoji", - "Reload": "Reload", "Take a picture": "Take a picture", "Remove for everyone": "Remove for everyone", "Remove for me": "Remove for me", "Edit": "Edit", "Pin to room": "Pin to room", + "You can only pin 2 apps at a time": "You can only pin 2 apps at a time", "Sunday": "Sunday", "Monday": "Monday", "Tuesday": "Tuesday", @@ -1925,6 +1925,7 @@ "Set a new status...": "Set a new status...", "View Community": "View Community", "Unpin": "Unpin", + "Reload": "Reload", "Take picture": "Take picture", "This room is public": "This room is public", "Away": "Away", From 3a993434334655a17509b4d45c3c5827484c626b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 15:04:03 +0100 Subject: [PATCH 0107/1014] iterate styling --- res/css/views/right_panel/_BaseCard.scss | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss index 661f496e8d..ee267d93a5 100644 --- a/res/css/views/right_panel/_BaseCard.scss +++ b/res/css/views/right_panel/_BaseCard.scss @@ -35,7 +35,7 @@ limitations under the License. .mx_BaseCard_back, .mx_BaseCard_close { position: absolute; - background-color: rgba(141, 151, 165, 0.2); // TODO + background-color: rgba(141, 151, 165, 0.2); height: 20px; width: 20px; margin: 12px; @@ -44,10 +44,10 @@ limitations under the License. &::before { content: ""; position: absolute; - height: 16px; - width: 16px; - top: 2px; - left: 2px; + height: 20px; + width: 20px; + top: 0; + left: 0; mask-repeat: no-repeat; mask-position: center; background-color: $rightpanel-button-color; @@ -60,8 +60,8 @@ limitations under the License. &::before { transform: rotate(90deg); - mask-size: 20px; - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); // TODO + mask-size: 22px; + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } } @@ -70,7 +70,8 @@ limitations under the License. right: 0; &::before { - mask-image: url('$(res)/img/icons-close.svg'); // TODO + mask-image: url('$(res)/img/icons-close.svg'); + mask-size: 8px; } } } @@ -126,7 +127,7 @@ limitations under the License. background-color: $icon-button-color; transform: rotate(270deg); mask-size: 20px; - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); // TODO + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } } } @@ -139,11 +140,11 @@ limitations under the License. .mx_AccessibleButton_kind_secondary { color: $secondary-fg-color; - background-color: rgba(141, 151, 165, 0.2); // TODO + background-color: rgba(141, 151, 165, 0.2); font-weight: $font-semi-bold; font-size: $font-14px; } - + .mx_AccessibleButton_disabled { cursor: not-allowed; } From 4a4a8cd6110f4af5eb4795d2efd42a71a4e63b8e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 15:39:58 +0100 Subject: [PATCH 0108/1014] styling and fix `i` button behaviour --- res/css/structures/_RightPanel.scss | 18 ++++++++---------- .../views/right_panel/RoomHeaderButtons.tsx | 11 ++++++++--- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index b9b765db56..5bf0d953f3 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -68,6 +68,14 @@ limitations under the License. mask-repeat: no-repeat; mask-size: contain; } + + &:hover { + background: rgba($accent-color, 0.1); + + &::before { + background-color: $accent-color; + } + } } .mx_RightPanel_notifsButton::before { @@ -90,16 +98,6 @@ limitations under the License. mask-position: center; } -.mx_RightPanel_headerButton { - &:hover { - background: rgba($accent-color, 0.1); - - &::before { - background-color: $accent-color; - } - } -} - .mx_RightPanel_headerButton_highlight { &::before { background-color: $accent-color !important; diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index e19a440005..fa97568fba 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -19,7 +19,7 @@ limitations under the License. */ import React from 'react'; -import { _t } from '../../../languageHandler'; +import {_t} from '../../../languageHandler'; import HeaderButton from './HeaderButton'; import HeaderButtons, {HeaderKind} from './HeaderButtons'; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; @@ -59,8 +59,13 @@ export default class RoomHeaderButtons extends HeaderButtons { // TODO make it restore whatever widget they were on last private onRoomSummaryClicked = () => { - // This toggles for us, if needed - this.setPhase(RightPanelPhases.RoomSummary); + if (this.state.phase === RightPanelPhases.Widget) { + // Close the panel + this.setPhase(RightPanelPhases.Widget); + } else { + // This toggles for us, if needed + this.setPhase(RightPanelPhases.RoomSummary); + } }; private onNotificationsClicked = () => { From 8d14d26e2b6841cbb610fa039602df614bcff212 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:27:09 +0100 Subject: [PATCH 0109/1014] do the todos --- .../views/right_panel/_RoomSummaryCard.scss | 8 ++-- .../views/right_panel/RoomHeaderButtons.tsx | 13 ++++-- .../views/right_panel/WidgetCard.tsx | 14 +++--- src/settings/Settings.ts | 2 +- src/stores/WidgetStore.ts | 43 +++++++++++-------- 5 files changed, 45 insertions(+), 35 deletions(-) diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index 41df1ca2eb..84e02af7bc 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -17,7 +17,7 @@ limitations under the License. .mx_RoomSummaryCard { // shrink left gutter by 12 and instead add it as margin to all things except the buttons // as their hover effect should go into the gutter - & > * { // TODO consolidate this as the standard effect + & > * { margin-left: 10px; margin-right: 10px; } @@ -57,7 +57,7 @@ limitations under the License. width: 54px; height: 54px; border-radius: 50%; - background-color: #737D8C; + background-color: #737D8C; // TODO margin-top: -3px; // alignment margin-left: -10px; // overlap border: 3px solid $dark-panel-bg-color; @@ -77,8 +77,8 @@ limitations under the License. } } - .mx_RoomSummaryCard_e2ee_secure{ - background-color: #5ABFF2; + .mx_RoomSummaryCard_e2ee_secure { + background-color: #5ABFF2; // TODO &::before { mask-image: url('$(res)/img/e2e/normal.svg'); } diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index fa97568fba..338290b7c9 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -25,6 +25,7 @@ import HeaderButtons, {HeaderKind} from './HeaderButtons'; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; import {Action} from "../../../dispatcher/actions"; import {ActionPayload} from "../../../dispatcher/payloads"; +import RightPanelStore from "../../../stores/RightPanelStore"; const ROOM_INFO_PHASES = [ RightPanelPhases.RoomSummary, @@ -57,11 +58,15 @@ export default class RoomHeaderButtons extends HeaderButtons { } } - // TODO make it restore whatever widget they were on last private onRoomSummaryClicked = () => { - if (this.state.phase === RightPanelPhases.Widget) { - // Close the panel - this.setPhase(RightPanelPhases.Widget); + // use roomPanelPhase rather than this.state.phase as it remembers the latest one if we close + const lastPhase = RightPanelStore.getSharedInstance().roomPanelPhase; + if (ROOM_INFO_PHASES.includes(lastPhase)) { + if (this.state.phase === lastPhase) { + this.setPhase(lastPhase); + } else { + this.setPhase(lastPhase, RightPanelStore.getSharedInstance().roomPanelPhaseParams); + } } else { // This toggles for us, if needed this.setPhase(RightPanelPhases.RoomSummary); diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx index bbd02c18fb..621b1aa1c7 100644 --- a/src/components/views/right_panel/WidgetCard.tsx +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -46,13 +46,6 @@ interface IProps { onClose(): void; } -const onFinished = () => { - defaultDispatcher.dispatch({ - action: Action.SetRightPanelPhase, - phase: RightPanelPhases.RoomSummary, - }); -} - const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { const cli = useContext(MatrixClientContext); @@ -64,8 +57,11 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { useEffect(() => { if (!app || isPinned) { - // TODO maybe we should do this in the ActiveWidgetStore instead - onFinished(); + // stop showing this card + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomSummary, + }); } }, [app, isPinned]); diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 43bb989d1f..e15cb46145 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -609,6 +609,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "Widgets.pinned": { supportedLevels: LEVELS_ROOM_OR_ACCOUNT, - default: [], + default: {}, }, }; diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index cf74081633..a43a6bfc30 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -24,6 +24,7 @@ import SettingsStore from "../settings/SettingsStore"; import WidgetEchoStore from "../stores/WidgetEchoStore"; import WidgetUtils from "../utils/WidgetUtils"; import {SettingLevel} from "../settings/SettingLevel"; +import {WidgetType} from "../widgets/WidgetType"; interface IState {} @@ -40,7 +41,7 @@ export interface IApp { interface IRoomWidgets { widgets: IApp[]; - pinned: Set; + pinned: Record; } // TODO consolidate WidgetEchoStore into this @@ -65,7 +66,7 @@ export default class WidgetStore extends AsyncStoreWithClient { private initRoom(roomId: string) { if (!this.roomMap.has(roomId)) { this.roomMap.set(roomId, { - pinned: new Set(), + pinned: {}, widgets: [], }); } @@ -81,7 +82,7 @@ export default class WidgetStore extends AsyncStoreWithClient { } if (pinned) { - this.getRoom(room.roomId).pinned = new Set(pinned); + this.getRoom(room.roomId).pinned = pinned; } this.loadRoomWidgets(room); @@ -144,9 +145,8 @@ export default class WidgetStore extends AsyncStoreWithClient { }; private onPinnedWidgetsChange = (settingName: string, roomId: string) => { - const pinned = SettingsStore.getValue(settingName, roomId); this.initRoom(roomId); - this.getRoom(roomId).pinned = new Set(pinned); + this.getRoom(roomId).pinned = SettingsStore.getValue(settingName, roomId); this.emit(roomId); this.emit("update"); }; @@ -154,8 +154,11 @@ export default class WidgetStore extends AsyncStoreWithClient { public isPinned(widgetId: string) { const roomId = this.getRoomId(widgetId); const roomInfo = this.getRoom(roomId); - // TODO heuristic for Jitsi etc - return roomInfo ? roomInfo.pinned.has(widgetId) : false; + + let pinned = roomInfo && roomInfo.pinned[widgetId]; + // Jitsi widgets should be pinned by default + if (pinned === undefined && WidgetType.JITSI.matches(this.widgetMap.get(widgetId).type)) pinned = true; + return pinned; } public canPin(widgetId: string) { @@ -163,25 +166,31 @@ export default class WidgetStore extends AsyncStoreWithClient { // the only case it will go to three is if you have two and then a Jitsi gets added const roomId = this.getRoomId(widgetId); const roomInfo = this.getRoom(roomId); - return roomInfo && roomInfo.pinned.size < 2; + return roomInfo && Object.keys(roomInfo.pinned).length < 2; } public pinWidget(widgetId: string) { - const roomId = this.getRoomId(widgetId); - const roomInfo = this.getRoom(roomId); - if (!roomInfo) return; - roomInfo.pinned.add(widgetId); - SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, Array.from(roomInfo.pinned)); - this.emit(roomId); - this.emit("update"); + this.setPinned(widgetId, true); } public unpinWidget(widgetId: string) { + this.setPinned(widgetId, false); + } + + private setPinned(widgetId: string, value: boolean) { const roomId = this.getRoomId(widgetId); const roomInfo = this.getRoom(roomId); if (!roomInfo) return; - roomInfo.pinned.delete(widgetId); - SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, Array.from(roomInfo.pinned)); + roomInfo.pinned[widgetId] = value; + + // Clean up the pinned record + Object.keys(roomInfo).forEach(wId => { + if (!roomInfo.widgets.some(w => w.id === wId)) { + delete roomInfo.pinned[wId]; + } + }); + + SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, roomInfo.pinned); this.emit(roomId); this.emit("update"); } From 6c7cb473dc46104687f3cf8ef5356462dea20bf3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:33:26 +0100 Subject: [PATCH 0110/1014] finalise colours from Figma --- res/css/views/right_panel/_RoomSummaryCard.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index 84e02af7bc..c8c2cceec7 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -57,7 +57,7 @@ limitations under the License. width: 54px; height: 54px; border-radius: 50%; - background-color: #737D8C; // TODO + background-color: #737d8c; margin-top: -3px; // alignment margin-left: -10px; // overlap border: 3px solid $dark-panel-bg-color; @@ -78,7 +78,7 @@ limitations under the License. } .mx_RoomSummaryCard_e2ee_secure { - background-color: #5ABFF2; // TODO + background-color: #5abff2; &::before { mask-image: url('$(res)/img/e2e/normal.svg'); } From b982bf87b5a22fbe8c6ea5df48173e9f41a3db6e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:33:55 +0100 Subject: [PATCH 0111/1014] delint --- src/components/views/right_panel/UserInfo.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index a79e65cb8b..84995aeb73 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -31,7 +31,6 @@ import AccessibleButton from '../elements/AccessibleButton'; import SdkConfig from '../../../SdkConfig'; import SettingsStore from "../../../settings/SettingsStore"; import {EventTimeline} from "matrix-js-sdk"; -import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import RoomViewStore from "../../../stores/RoomViewStore"; import MultiInviter from "../../../utils/MultiInviter"; import GroupStore from "../../../stores/GroupStore"; From 1532048f33a02ae88055c0452926b5c78d3decfb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:49:50 +0100 Subject: [PATCH 0112/1014] Fix behaviour WidgetStore for new/unknown rooms --- src/stores/WidgetStore.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index a43a6bfc30..d29ee81e13 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -196,11 +196,12 @@ export default class WidgetStore extends AsyncStoreWithClient { } public getApps(room: Room, pinned?: boolean): IApp[] { - const apps = this.getRoom(room.roomId).widgets; + const roomInfo = this.getRoom(room.roomId); + if (!roomInfo) return []; if (pinned) { - return apps.filter(app => this.isPinned(app.id)); + return roomInfo.widgets.filter(app => this.isPinned(app.id)); } - return apps + return roomInfo.widgets; } } From b878c2774125c1f15f2f55678bdb5d3d83c04b88 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:56:22 +0100 Subject: [PATCH 0113/1014] Tidy devDeps, all the webpack stuff lives in the layer above --- package.json | 2 -- yarn.lock | 76 +++------------------------------------------------- 2 files changed, 3 insertions(+), 75 deletions(-) diff --git a/package.json b/package.json index b85191dc22..b403569eb8 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,6 @@ "eslint-plugin-flowtype": "^2.50.3", "eslint-plugin-react": "^7.20.3", "eslint-plugin-react-hooks": "^2.5.1", - "file-loader": "^3.0.1", "glob": "^5.0.15", "jest": "^24.9.0", "jest-canvas-mock": "^2.2.0", @@ -158,7 +157,6 @@ "matrix-react-test-utils": "^0.2.2", "react-test-renderer": "^16.13.1", "rimraf": "^2.7.1", - "source-map-loader": "^0.2.4", "stylelint": "^9.10.1", "stylelint-config-standard": "^18.3.0", "stylelint-scss": "^3.18.0", diff --git a/yarn.lock b/yarn.lock index ec099bbf7c..063b83e0c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1907,17 +1907,7 @@ airbnb-prop-types@^2.15.0: prop-types-exact "^1.2.0" react-is "^16.9.0" -ajv-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" - integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== - -ajv-keywords@^3.1.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" - integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== - -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5: +ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5: version "6.12.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== @@ -2142,13 +2132,6 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -async@^2.5.0: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== - dependencies: - lodash "^4.17.14" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2294,11 +2277,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -2898,15 +2876,6 @@ crc-32@^0.3.0: resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-0.3.0.tgz#6a3d3687f5baec41f7e9b99fe1953a2e5d19775e" integrity sha1-aj02h/W67EH36bmf4ZU6Ll0Zd14= -create-react-class@^15.6.3: - version "15.6.3" - resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" - integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg== - dependencies: - fbjs "^0.8.9" - loose-envify "^1.3.1" - object-assign "^4.1.1" - cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -3294,11 +3263,6 @@ emojibase-regex@^4.0.1: resolved "https://registry.yarnpkg.com/emojibase-regex/-/emojibase-regex-4.0.1.tgz#a2cd4bbb42825422da9ec72f15e970bc2c90b46a" integrity sha512-S42UHkFfz15i4NNz+wi9iMKFo+B6Kalc6PJLpYX0BUANViXw4vSyYZMFdBGRLduSabWHuEcTLZl9xOa2YP3eJw== -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - encoding@^0.1.11: version "0.1.12" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" @@ -3988,7 +3952,7 @@ fbjs@0.1.0-alpha.7: promise "^7.0.3" whatwg-fetch "^0.9.0" -fbjs@^0.8.4, fbjs@^0.8.9: +fbjs@^0.8.4: version "0.8.17" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= @@ -4027,14 +3991,6 @@ file-entry-cache@^5.0.1: dependencies: flat-cache "^2.0.1" -file-loader@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-3.0.1.tgz#f8e0ba0b599918b51adfe45d66d1e771ad560faa" - integrity sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw== - dependencies: - loader-utils "^1.0.2" - schema-utils "^1.0.0" - file-saver@^1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8" @@ -5755,15 +5711,6 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" -loader-utils@^1.0.2, loader-utils@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -5841,7 +5788,7 @@ longest-streak@^2.0.1: resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -7693,15 +7640,6 @@ scheduler@^0.19.1: loose-envify "^1.1.0" object-assign "^4.1.1" -schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" - integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== - dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" - "semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -7859,14 +7797,6 @@ socks@~2.3.2: ip "1.1.5" smart-buffer "^4.1.0" -source-map-loader@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.4.tgz#c18b0dc6e23bf66f6792437557c569a11e072271" - integrity sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ== - dependencies: - async "^2.5.0" - loader-utils "^1.1.0" - source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" From 093ed37c176bcaaddee396732e5e9c111cd0916c Mon Sep 17 00:00:00 2001 From: TERMICO Date: Tue, 8 Sep 2020 14:40:02 +0000 Subject: [PATCH 0114/1014] Translated using Weblate (Spanish) Currently translated at 88.0% (2073 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/es/ --- src/i18n/strings/es.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index 891c69ff03..307c8c10c9 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -802,7 +802,7 @@ "Old cryptography data detected": "Se detectó información de criptografía antigua", "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Se detectó una versión más antigua de %(brand)s. Esto habrá provocado que la criptografía de extremo a extremo funcione incorrectamente en la versión más antigua. Los mensajes cifrados de extremo a extremo intercambiados recientemente mientras usaba la versión más antigua puede que no sean descifrables con esta versión. Esto también puede hacer que fallen con la más reciente. Si experimenta problemas, desconecte y vuelva a ingresar. Para conservar el historial de mensajes, exporte y vuelva a importar sus claves.", "Your Communities": "Sus Comunidades", - "Did you know: you can use communities to filter your %(brand)s experience!": "Sabía que: puede usar comunidades para filtrar su experiencia con %(brand)s", + "Did you know: you can use communities to filter your %(brand)s experience!": "Sabía que: puede usar comunidades para filtrar su experiencia con %(brand)s !", "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Para configurar un filtro, arrastre un avatar de comunidad sobre el panel de filtro en la parte izquierda de la pantalla. Puede pulsar sobre un avatar en el panel de filtro en cualquier momento para ver solo las salas y personas asociadas con esa comunidad.", "Error whilst fetching joined communities": "Error al recuperar las comunidades a las que estás unido", "Create a new community": "Crear una comunidad nueva", @@ -1614,7 +1614,7 @@ "Can't find this server or its room list": "No puedo encontrar este servidor o su lista de salas", "All rooms": "Todas las salas", "Your server": "Tu", - "Are you sure you want to remove %(serverName)s": "¿Está seguro de querer eliminar %(serverName)s?", + "Are you sure you want to remove %(serverName)s": "¿Está seguro de querer eliminar %(serverName)s ?", "Remove server": "Quitar servidor", "Matrix": "Matrix", "Add a new server": "Añadir un nuevo servidor", From a17b2ba1e559bb9f35764be8ec6097ff27f3da47 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 17:38:13 +0100 Subject: [PATCH 0115/1014] use constant --- src/stores/WidgetStore.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index d29ee81e13..c01d77855c 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -25,6 +25,7 @@ import WidgetEchoStore from "../stores/WidgetEchoStore"; import WidgetUtils from "../utils/WidgetUtils"; import {SettingLevel} from "../settings/SettingLevel"; import {WidgetType} from "../widgets/WidgetType"; +import {UPDATE_EVENT} from "./AsyncStore"; interface IState {} @@ -87,7 +88,7 @@ export default class WidgetStore extends AsyncStoreWithClient { this.loadRoomWidgets(room); }); - this.emit("update"); + this.emit(UPDATE_EVENT); } protected async onNotReady(): Promise { @@ -105,7 +106,7 @@ export default class WidgetStore extends AsyncStoreWithClient { private onWidgetEchoStoreUpdate(roomId: string, widgetId: string) { this.initRoom(roomId); this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); - this.emit("update"); + this.emit(UPDATE_EVENT); } private generateApps(room: Room): IApp[] { @@ -131,7 +132,7 @@ export default class WidgetStore extends AsyncStoreWithClient { const roomId = ev.getRoomId(); this.initRoom(roomId); this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); - this.emit("update"); + this.emit(UPDATE_EVENT); } public getRoomId = (widgetId: string) => { @@ -148,7 +149,7 @@ export default class WidgetStore extends AsyncStoreWithClient { this.initRoom(roomId); this.getRoom(roomId).pinned = SettingsStore.getValue(settingName, roomId); this.emit(roomId); - this.emit("update"); + this.emit(UPDATE_EVENT); }; public isPinned(widgetId: string) { @@ -192,7 +193,7 @@ export default class WidgetStore extends AsyncStoreWithClient { SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, roomInfo.pinned); this.emit(roomId); - this.emit("update"); + this.emit(UPDATE_EVENT); } public getApps(room: Room, pinned?: boolean): IApp[] { From 01a8ac25c98d792da0e41601cad64e28411a2538 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 17:40:57 +0100 Subject: [PATCH 0116/1014] Use null coalescing operator --- src/utils/WidgetUtils.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index 771bc0887a..d1daba7ca5 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -480,13 +480,11 @@ export default class WidgetUtils { } static getWidgetName(app) { - if (!app || !app.name) return ""; - return app.name.trim() || _t("Unknown App"); + return app?.name?.trim() || _t("Unknown App"); } static getWidgetDataTitle(app) { - if (!app || !app.data || !app.data.title) return ""; - return app.data.title.trim(); + return app?.data?.title?.trim() || ""; } static editWidget(room, app) { From 596060c5069b76556bbe1b68d9f60af854bd7530 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 17:57:08 +0100 Subject: [PATCH 0117/1014] fix alignments and iterate PR --- res/css/views/right_panel/_BaseCard.scss | 8 ++++---- .../views/right_panel/_RoomSummaryCard.scss | 18 ++++-------------- .../views/right_panel/RoomSummaryCard.tsx | 12 +++++++----- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss index ee267d93a5..26f846fe0a 100644 --- a/res/css/views/right_panel/_BaseCard.scss +++ b/res/css/views/right_panel/_BaseCard.scss @@ -24,7 +24,7 @@ limitations under the License. .mx_BaseCard_header { margin: 8px 0; - h2 { + > h2 { margin: 0 44px; font-size: $font-18px; font-weight: $font-semi-bold; @@ -89,11 +89,11 @@ limitations under the License. margin: 20px 0 16px; & > * { - margin-left: 10px; - margin-right: 10px; + margin-left: 12px; + margin-right: 12px; } - h1 { + > h1 { color: $tertiary-fg-color; font-size: $font-12px; font-weight: 500; diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index c8c2cceec7..78324c5e89 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -15,16 +15,6 @@ limitations under the License. */ .mx_RoomSummaryCard { - // shrink left gutter by 12 and instead add it as margin to all things except the buttons - // as their hover effect should go into the gutter - & > * { - margin-left: 10px; - margin-right: 10px; - } - .mx_AutoHideScrollbar { - margin-left: 0; - } - .mx_BaseCard_header { text-align: center; margin-top: 20px; @@ -88,13 +78,13 @@ limitations under the License. .mx_RoomSummaryCard_aboutGroup { .mx_RoomSummaryCard_Button { - padding-left: 48px; + padding-left: 44px; &::before { content: ''; position: absolute; top: 8px; - left: 8px; + left: 10px; height: 24px; width: 24px; mask-repeat: no-repeat; @@ -106,7 +96,7 @@ limitations under the License. .mx_RoomSummaryCard_appsGroup { .mx_RoomSummaryCard_Button { - padding-left: 10px; + padding-left: 12px; color: $tertiary-fg-color; span { @@ -115,7 +105,7 @@ limitations under the License. img { vertical-align: top; - margin-right: 18px; + margin-right: 12px; border-radius: 4px; } diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 64743e1bb8..83d249b482 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -148,11 +148,13 @@ const AppsSection: React.FC = ({ room }) => { }); }; - return ; + return ( + + ); }) } From 12a6bc8231017b40b3543477b35fc37a87362ec0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 18:00:21 +0100 Subject: [PATCH 0118/1014] update copy --- src/components/views/right_panel/RoomSummaryCard.tsx | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 83d249b482..544582d206 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -158,7 +158,7 @@ const AppsSection: React.FC = ({ room }) => { }) } - { apps.length > 0 ? _t("Edit apps") : _t("Add applications") } + { apps.length > 0 ? _t("Edit apps") : _t("Add apps") } ; }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b3da8cbbb1..41d908b4fb 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1287,7 +1287,7 @@ "Apps": "Apps", "Unpin app": "Unpin app", "Edit apps": "Edit apps", - "Add applications": "Add applications", + "Add apps": "Add apps", "Not encrypted": "Not encrypted", "About": "About", "%(count)s people|other": "%(count)s people", From bbe2084f66b4102934db263cb2767ef3443ba11a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 8 Sep 2020 18:01:56 +0100 Subject: [PATCH 0119/1014] Add independent set up / reset actions in Settings This adds set up and reset actions to each of cross-signing and secure backup that do separate things, rather than mixing concerns together. (It's temporarily still a bit of lie for backup, as more changes are needed to stop resetting cross-signing as well.) --- .../views/settings/_SecureBackupPanel.scss | 4 ++ src/SecurityManager.js | 2 +- .../CreateSecretStorageDialog.js | 3 + .../views/settings/CrossSigningPanel.js | 51 +++++++++----- .../views/settings/SecureBackupPanel.js | 69 +++++++++++++------ src/i18n/strings/en_EN.json | 6 +- 6 files changed, 90 insertions(+), 45 deletions(-) diff --git a/res/css/views/settings/_SecureBackupPanel.scss b/res/css/views/settings/_SecureBackupPanel.scss index 587cab8f36..a9dab06b57 100644 --- a/res/css/views/settings/_SecureBackupPanel.scss +++ b/res/css/views/settings/_SecureBackupPanel.scss @@ -34,6 +34,10 @@ limitations under the License. .mx_SecureBackupPanel_buttonRow { margin: 1em 0; + + :nth-child(n + 1) { + margin-inline-end: 10px; + } } .mx_SecureBackupPanel_statusList { diff --git a/src/SecurityManager.js b/src/SecurityManager.js index 891f43b705..cc7db3ead7 100644 --- a/src/SecurityManager.js +++ b/src/SecurityManager.js @@ -250,7 +250,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f 'Cross-signing keys dialog', '', InteractiveAuthDialog, { title: _t("Setting up keys"), - matrixClient: MatrixClientPeg.get(), + matrixClient: cli, makeRequest, }, ); diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 07ff3c9b76..d4b1a73c3e 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -280,6 +280,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent { const { forceReset } = this.props; try { + // JRS: In an upcoming change, the cross-signing steps will be + // removed from here and this will instead be about secret storage + // only. if (forceReset) { console.log("Forcing cross-signing and secret storage reset"); await cli.bootstrapSecretStorage({ diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 8ef68e4b2a..a0ca84645f 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -19,9 +19,9 @@ import React from 'react'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; -import { accessSecretStorage } from '../../../SecurityManager'; import Modal from '../../../Modal'; import Spinner from '../elements/Spinner'; +import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog'; export default class CrossSigningPanel extends React.PureComponent { constructor(props) { @@ -66,7 +66,7 @@ export default class CrossSigningPanel extends React.PureComponent { }; _onBootstrapClick = () => { - this._bootstrapSecureSecretStorage(false); + this._bootstrapCrossSigning({ forceReset: false }); }; onStatusChanged = () => { @@ -99,35 +99,50 @@ export default class CrossSigningPanel extends React.PureComponent { } /** - * Bootstrapping secret storage may take one of these paths: - * 1. Create secret storage from a passphrase and store cross-signing keys - * in secret storage. + * Bootstrapping cross-signing take one of these paths: + * 1. Create cross-signing keys locally and store in secret storage (if it + * already exists on the account). * 2. Access existing secret storage by requesting passphrase and accessing * cross-signing keys as needed. * 3. All keys are loaded and there's nothing to do. * @param {bool} [forceReset] Bootstrap again even if keys already present */ - _bootstrapSecureSecretStorage = async (forceReset=false) => { + _bootstrapCrossSigning = async ({ forceReset = false }) => { this.setState({ error: null }); try { - await accessSecretStorage(() => undefined, forceReset); + const cli = MatrixClientPeg.get(); + await cli.bootstrapCrossSigning({ + authUploadDeviceSigningKeys: async (makeRequest) => { + const { finished } = Modal.createTrackedDialog( + 'Cross-signing keys dialog', '', InteractiveAuthDialog, + { + title: _t("Setting up keys"), + matrixClient: cli, + makeRequest, + }, + ); + const [confirmed] = await finished; + if (!confirmed) { + throw new Error("Cross-signing key upload auth canceled"); + } + }, + setupNewCrossSigning: forceReset, + }); } catch (e) { this.setState({ error: e }); - console.error("Error bootstrapping secret storage", e); + console.error("Error bootstrapping cross-signing", e); } if (this._unmounted) return; this._getUpdatedStatus(); } - onDestroyStorage = (act) => { - if (!act) return; - this._bootstrapSecureSecretStorage(true); - } - - _destroySecureSecretStorage = () => { + _resetCrossSigning = () => { const ConfirmDestroyCrossSigningDialog = sdk.getComponent("dialogs.ConfirmDestroyCrossSigningDialog"); Modal.createDialog(ConfirmDestroyCrossSigningDialog, { - onFinished: this.onDestroyStorage, + onFinished: (act) => { + if (!act) return; + this._bootstrapCrossSigning({ forceReset: true }); + }, }); } @@ -184,8 +199,8 @@ export default class CrossSigningPanel extends React.PureComponent { if (keysExistAnywhere) { resetButton = (
    - - {_t("Reset cross-signing and secret storage")} + + {_t("Reset")}
    ); @@ -197,7 +212,7 @@ export default class CrossSigningPanel extends React.PureComponent { bootstrapButton = (
    - {_t("Bootstrap cross-signing and secret storage")} + {_t("Set up")}
    ); diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 0f43770288..7d4cfc2c10 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -25,6 +25,7 @@ import Spinner from '../elements/Spinner'; import AccessibleButton from '../elements/AccessibleButton'; import QuestionDialog from '../dialogs/QuestionDialog'; import RestoreKeyBackupDialog from '../dialogs/keybackup/RestoreKeyBackupDialog'; +import { accessSecretStorage } from '../../../SecurityManager'; export default class SecureBackupPanel extends React.PureComponent { constructor(props) { @@ -184,6 +185,19 @@ export default class SecureBackupPanel extends React.PureComponent { ); } + _resetSecretStorage = async () => { + this.setState({ error: null }); + try { + await accessSecretStorage(() => { }, /* forceReset = */ true); + } catch (e) { + console.error("Error resetting secret storage", e); + if (this._unmounted) return; + this.setState({ error: e }); + } + if (this._unmounted) return; + this._loadBackupStatus(); + } + render() { const { loading, @@ -201,7 +215,7 @@ export default class SecureBackupPanel extends React.PureComponent { let statusDescription; let extraDetailsTableRows; let extraDetails; - let actions; + let actions = []; if (error) { statusDescription = (
    @@ -335,13 +349,6 @@ export default class SecureBackupPanel extends React.PureComponent { trustedLocally = _t("This backup is trusted because it has been restored on this session"); } - let deleteBackupButton; - if (!isSecureBackupRequired()) { - deleteBackupButton = - {_t("Delete Backup")} - ; - } - extraDetailsTableRows = <> {_t("Backup version:")} @@ -359,14 +366,19 @@ export default class SecureBackupPanel extends React.PureComponent {
    {trustedLocally}
    ; - actions = ( -
    - - {restoreButtonCaption} -     - {deleteBackupButton} -
    + actions.push( + + {restoreButtonCaption} + , ); + + if (!isSecureBackupRequired()) { + actions.push( + + {_t("Delete Backup")} + , + ); + } } else { statusDescription = <>

    {_t( @@ -375,12 +387,18 @@ export default class SecureBackupPanel extends React.PureComponent { )}

    {_t("Back up your keys before signing out to avoid losing them.")}

    ; - actions = ( -
    - - {_t("Start using Key Backup")} - -
    + actions.push( + + {_t("Set up")} + , + ); + } + + if (secretStorageKeyInAccount) { + actions.push( + + {_t("Reset")} + , ); } @@ -394,6 +412,13 @@ export default class SecureBackupPanel extends React.PureComponent { } } + let actionRow; + if (actions.length) { + actionRow =
    + {actions} +
    ; + } + return (

    {_t( @@ -430,7 +455,7 @@ export default class SecureBackupPanel extends React.PureComponent { {extraDetails} - {actions} + {actionRow}

    ); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1bf431f6e0..b73d2b25fe 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -648,8 +648,7 @@ "Cross-signing is ready for use.": "Cross-signing is ready for use.", "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.", "Cross-signing is not set up.": "Cross-signing is not set up.", - "Reset cross-signing and secret storage": "Reset cross-signing and secret storage", - "Bootstrap cross-signing and secret storage": "Bootstrap cross-signing and secret storage", + "Reset": "Reset", "Cross-signing public keys:": "Cross-signing public keys:", "in memory": "in memory", "not found": "not found", @@ -748,7 +747,6 @@ "Algorithm:": "Algorithm:", "Your keys are not being backed up from this session.": "Your keys are not being backed up from this session.", "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.", - "Start using Key Backup": "Start using Key Backup", "well formed": "well formed", "unexpected type": "unexpected type", "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.", @@ -948,7 +946,6 @@ "Uploaded sound": "Uploaded sound", "Sounds": "Sounds", "Notification sound": "Notification sound", - "Reset": "Reset", "Set a new custom sound": "Set a new custom sound", "Browse": "Browse", "Change room avatar": "Change room avatar", @@ -1173,6 +1170,7 @@ "%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.", "Try again later, or ask a room admin to check if you have access.": "Try again later, or ask a room admin to check if you have access.", "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.", + "Start using Key Backup": "Start using Key Backup", "Never lose encrypted messages": "Never lose encrypted messages", "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", "Securely back up your keys to avoid losing them. Learn more.": "Securely back up your keys to avoid losing them. Learn more.", From 421af52e76942bfd3127674d398ba481ffe3cb95 Mon Sep 17 00:00:00 2001 From: Nigel Mansell Date: Tue, 8 Sep 2020 15:29:16 -0400 Subject: [PATCH 0120/1014] Fixed 1px jump upwards on filter room members box Signed-off-by: Nigel Mansell --- res/css/structures/_SearchBox.scss | 4 ++-- res/css/views/rooms/_MemberList.scss | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/res/css/structures/_SearchBox.scss b/res/css/structures/_SearchBox.scss index 8c36ceee9e..23ee06f7b3 100644 --- a/res/css/structures/_SearchBox.scss +++ b/res/css/structures/_SearchBox.scss @@ -26,8 +26,8 @@ limitations under the License. cursor: pointer; background-image: url('$(res)/img/icons-close.svg'); background-repeat: no-repeat; - width: 15px; - height: 15px; + width: 16px; + height: 16px; background-position: center; padding: 9px; } diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss index 90667d41b4..2366667c95 100644 --- a/res/css/views/rooms/_MemberList.scss +++ b/res/css/views/rooms/_MemberList.scss @@ -70,6 +70,10 @@ limitations under the License. } } +.mx_MemberList_query { + height: 16px; +} + .mx_MemberList_wrapper { padding: 10px; } From 644ff56ace9c1f05a5fbeb47e6a8116388e11674 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 09:38:13 +0100 Subject: [PATCH 0121/1014] Fix e2e tests --- test/end-to-end-tests/src/usecases/room-settings.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index 11e2f52c6e..f5fb37167c 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -45,7 +45,9 @@ async function findTabs(session) { /// XXX delay is needed here, possibly because the header is being rerendered /// click doesn't do anything otherwise await session.delay(1000); - const settingsButton = await session.query(".mx_RoomHeader .mx_AccessibleButton[aria-label=Settings]"); + const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]'); + await roomSummaryButton.click(); + const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); await settingsButton.click(); //find tabs From 29c2a0ef35d7de332f61d9396cbf11087139557a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 09:50:08 +0100 Subject: [PATCH 0122/1014] Fix FilePanel and NotificationPanel overscroll issues --- src/components/structures/FilePanel.js | 1 + src/components/structures/NotificationPanel.js | 2 +- src/components/structures/TimelinePanel.js | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 8812ba4302..6d618d0b9d 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -230,6 +230,7 @@ class FilePanel extends React.Component { className="mx_FilePanel" onClose={this.props.onClose} previousPhase={RightPanelPhases.RoomSummary} + withoutScrollContainer > ; } - return + return { content } ; } diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index daa18bb290..97f9ba48ed 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -104,8 +104,8 @@ class TimelinePanel extends React.Component { // shape property to be passed to EventTiles tileShape: PropTypes.string, - // placeholder text to use if the timeline is empty - empty: PropTypes.string, + // placeholder to use if the timeline is empty + empty: PropTypes.node, // whether to show reactions for an event showReactions: PropTypes.bool, From c8bc80a3b1493d92349c3fd78b09cd5e384c844c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 10:07:43 +0100 Subject: [PATCH 0123/1014] test with delay --- res/css/views/right_panel/_WidgetCard.scss | 1 + test/end-to-end-tests/src/usecases/room-settings.js | 1 + 2 files changed, 2 insertions(+) diff --git a/res/css/views/right_panel/_WidgetCard.scss b/res/css/views/right_panel/_WidgetCard.scss index 0f859738b1..28f09bf319 100644 --- a/res/css/views/right_panel/_WidgetCard.scss +++ b/res/css/views/right_panel/_WidgetCard.scss @@ -19,6 +19,7 @@ limitations under the License. display: contents; .mx_AppTileFullWidth { + max-width: unset; height: 100%; border: 0; } diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index f5fb37167c..c7f4495a86 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -47,6 +47,7 @@ async function findTabs(session) { await session.delay(1000); const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]'); await roomSummaryButton.click(); + await session.delay(1000); const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); await settingsButton.click(); From fb0b784369ba2daf66bbe61580bf88bb70a2d408 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 10:56:14 +0100 Subject: [PATCH 0124/1014] test CI --- test/end-to-end-tests/src/usecases/room-settings.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index c7f4495a86..01894ee794 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -47,9 +47,8 @@ async function findTabs(session) { await session.delay(1000); const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]'); await roomSummaryButton.click(); - await session.delay(1000); - const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); - await settingsButton.click(); + // const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); + // await settingsButton.click(); //find tabs const tabButtons = await session.queryAll(".mx_RoomSettingsDialog .mx_TabbedView_tabLabel"); From 37c0d524bcbce5e48da581230c1fad806a9e5baf Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 11:06:15 +0100 Subject: [PATCH 0125/1014] re-order top right buttons --- .../views/right_panel/RoomHeaderButtons.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index 338290b7c9..c2364546fd 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -80,14 +80,6 @@ export default class RoomHeaderButtons extends HeaderButtons { public renderButtons() { return [ - , , + , ]; } } From bb9858714306f4b8976c0f84459bd447a257f5a3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 11:42:41 +0100 Subject: [PATCH 0126/1014] fix e2e tests. Change the default Room Tab to RoomSummary --- src/settings/Settings.ts | 2 +- test/end-to-end-tests/src/usecases/room-settings.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index e15cb46145..9e0f36b1ba 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -566,7 +566,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "lastRightPanelPhaseForRoom": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, - default: RightPanelPhases.RoomMemberInfo, + default: RightPanelPhases.RoomSummary, }, "lastRightPanelPhaseForGroup": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index 01894ee794..f5fb37167c 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -47,8 +47,8 @@ async function findTabs(session) { await session.delay(1000); const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]'); await roomSummaryButton.click(); - // const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); - // await settingsButton.click(); + const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); + await settingsButton.click(); //find tabs const tabButtons = await session.queryAll(".mx_RoomSettingsDialog .mx_TabbedView_tabLabel"); From 8dcb2d47199ff26e975424c65c2d100b9d8833d0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 12:06:16 +0100 Subject: [PATCH 0127/1014] attempt to fix CI tests --- test/end-to-end-tests/src/usecases/memberlist.js | 5 +++++ test/end-to-end-tests/src/usecases/verify.js | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/end-to-end-tests/src/usecases/memberlist.js b/test/end-to-end-tests/src/usecases/memberlist.js index e974eea95b..cc641ae4c9 100644 --- a/test/end-to-end-tests/src/usecases/memberlist.js +++ b/test/end-to-end-tests/src/usecases/memberlist.js @@ -17,6 +17,11 @@ limitations under the License. const assert = require('assert'); +module.exports.openMemberList = async function(session) { + const peopleButton = await session.query(".mx_RoomSummaryCard_icon_people"); + await peopleButton.click(); +}; + async function openMemberInfo(session, name) { const membersAndNames = await getMembersInMemberlist(session); const matchingLabel = membersAndNames.filter((m) => { diff --git a/test/end-to-end-tests/src/usecases/verify.js b/test/end-to-end-tests/src/usecases/verify.js index 98e73ad6b7..4021c97dc9 100644 --- a/test/end-to-end-tests/src/usecases/verify.js +++ b/test/end-to-end-tests/src/usecases/verify.js @@ -16,10 +16,11 @@ limitations under the License. */ const assert = require('assert'); -const {openMemberInfo} = require("./memberlist"); +const {openMemberList, openMemberInfo} = require("./memberlist"); async function startVerification(session, name) { session.log.step("opens their opponent's profile and starts verification"); + await openMemberList(session); await openMemberInfo(session, name); // click verify in member info const firstVerifyButton = await session.query(".mx_UserInfo_verifyButton"); From b635598bc3266190b238e6aa8708ef4ab41c78f8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 12:28:12 +0100 Subject: [PATCH 0128/1014] Attempt to fix tests and fix RoomSummaryCard having wrong member count --- .../views/right_panel/RoomSummaryCard.tsx | 12 ++++++- .../src/usecases/memberlist.js | 31 +++++++++++-------- test/end-to-end-tests/src/usecases/verify.js | 3 +- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 544582d206..c782654637 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -181,6 +181,14 @@ const onRoomSettingsClick = () => { defaultDispatcher.dispatch({ action: "open_room_settings" }); }; +const useMemberCount = (room: Room) => { + const [count, setCount] = useState(room.getJoinedMembers().length); + useEventEmitter(room.currentState, "RoomState.members", () => { + setCount(room.getJoinedMembers().length); + }); + return count; +}; + const RoomSummaryCard: React.FC = ({ room, onClose }) => { const cli = useContext(MatrixClientContext); @@ -210,10 +218,12 @@ const RoomSummaryCard: React.FC = ({ room, onClose }) => {
    ; + const memberCount = useMemberCount(room); + return to continue.": "Ingrese su Frase de seguridad o para continuar.", + "Security Key": "Clave de seguridad", + "Use your Security Key to continue.": "Usa tu llave de seguridad para continuar.", + "Unpin": "Desprender", + "This room is public": "Esta sala es pública", + "Away": "Lejos", + "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.": "Puede utilizar las opciones del servidor personalizado para iniciar sesión en otros servidores Matrix especificando una URL de servidor principal diferente. Esto le permite utilizar %(brand)s con una cuenta Matrix existente en un servidor doméstico diferente.", + "Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of element.io.": "Ingrese la ubicación de su servidor doméstico de Element Matrix Services. Puede usar su propio nombre de dominio o ser un subdominio de element.io.", + "No files visible in this room": "No hay archivos visibles en esta sala", + "Attach files from chat or just drag and drop them anywhere in a room.": "Adjunte archivos desde el chat o simplemente arrástrelos y suéltelos en cualquier lugar de una sala.", + "You’re all caught up": "Estás al día", + "You have no visible notifications in this room.": "No tienes notificaciones visibles en esta sala.", + "Delete the room address %(alias)s and remove %(name)s from the directory?": "¿Eliminar la dirección de la sala %(alias)s y eliminar %(name)s del directorio?", + "delete the address.": "eliminar la dirección.", + "Explore rooms in %(communityName)s": "Explora habitaciones en %(communityName)s", + "Search rooms": "Buscar salas", + "Create community": "Crear comunidad", + "Failed to find the general chat for this community": "No se pudo encontrar el chat general de esta comunidad", + "Security & privacy": "Seguridad y Privacidad", + "All settings": "Todos los ajustes", + "Feedback": "Realimentación", + "Community settings": "Configuración de la comunidad", + "User settings": "Ajustes de usuario", + "Switch to light mode": "Cambiar al modo de luz", + "Switch to dark mode": "Cambiar al modo oscuro", + "Switch theme": "Cambiar tema", + "User menu": "Menú del Usuario", + "Community and user menu": "Menú de comunidad y usuario", + "Failed to perform homeserver discovery": "No se pudo realizar el descubrimiento del servidor doméstico", + "Syncing...": "Sincronizando ...", + "Signing In...": "Iniciando sesión...", + "If you've joined lots of rooms, this might take a while": "Si se ha unido a muchas salas, esto puede llevar un tiempo", + "Create account": "Crear una cuenta", + "Unable to query for supported registration methods.": "No se pueden consultar los métodos de registro admitidos.", + "Registration has been disabled on this homeserver.": "El registro ha sido deshabilitado en este servidor doméstico.", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Su nueva cuenta (%(newAccountId)s) está registrada, pero ya inició sesión en una cuenta diferente (%(loggedInUserId)s).", + "Continue with previous account": "Continuar con la cuenta anterior", + "Log in to your new account.": "Inicie sesión en su nueva cuenta.", + "You can now close this window or log in to your new account.": "Ahora puede cerrar esta ventana o iniciar sesión en su nueva cuenta.", + "Registration Successful": "Registro exitoso", + "Create your account": "Crea tu cuenta", + "Use Recovery Key or Passphrase": "Usar clave de recuperación o frase de contraseña", + "Use Recovery Key": "Usar clave de recuperación", + "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Confirme su identidad verificando este inicio de sesión de una de sus otras sesiones, otorgándole acceso a los mensajes cifrados.", + "This requires the latest %(brand)s on your other devices:": "Esto requiere la última %(brand)s en sus otros dispositivos:", + "%(brand)s Web": "%(brand)s Web", + "%(brand)s Desktop": "%(brand)s Escritorio", + "%(brand)s iOS": "%(brand)s iOS", + "%(brand)s Android": "%(brand)s Android", + "or another cross-signing capable Matrix client": "u otro cliente Matrix con capacidad de firma cruzada", + "Without completing security on this session, it won’t have access to encrypted messages.": "Sin completar la seguridad en esta sesión, no tendrá acceso a los mensajes cifrados.", + "Failed to re-authenticate due to a homeserver problem": "No se pudo volver a autenticar debido a un problema con el servidor doméstico", + "Failed to re-authenticate": "No se pudo volver a autenticar", + "Regain access to your account and recover encryption keys stored in this session. Without them, you won’t be able to read all of your secure messages in any session.": "Recupere el acceso a su cuenta y recupere las claves de cifrado almacenadas en esta sesión. Sin ellos, no podrá leer todos sus mensajes seguros en ninguna sesión.", + "Enter your password to sign in and regain access to your account.": "Ingrese su contraseña para iniciar sesión y recuperar el acceso a su cuenta.", + "Forgotten your password?": "¿Olvidaste tu contraseña?", + "Sign in and regain access to your account.": "Inicie sesión y recupere el acceso a su cuenta.", + "You cannot sign in to your account. Please contact your homeserver admin for more information.": "No puede iniciar sesión en su cuenta. Comuníquese con el administrador de su servidor doméstico para obtener más información.", + "You're signed out": "Estás desconectado", + "Clear personal data": "Borrar datos personales", + "Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.": "Advertencia: sus datos personales (incluidas las claves de cifrado) todavía se almacenan en esta sesión. Bórrelo si terminó de usar esta sesión o si desea iniciar sesión en otra cuenta.", + "Command Autocomplete": "Comando Autocompletar", + "Community Autocomplete": "Autocompletar de la comunidad", + "DuckDuckGo Results": "Resultados de DuckDuckGo", + "Emoji Autocomplete": "Autocompletar Emoji", + "Notification Autocomplete": "Autocompletar notificación", + "Room Autocomplete": "Autocompletar habitación", + "User Autocomplete": "Autocompletar de usuario", + "Confirm encryption setup": "Confirmar la configuración de cifrado", + "Click the button below to confirm setting up encryption.": "Haga clic en el botón de abajo para confirmar la configuración del cifrado.", + "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Protéjase contra la pérdida de acceso a los mensajes y datos cifrados haciendo una copia de seguridad de las claves de cifrado en su servidor.", + "Generate a Security Key": "Generar una llave de seguridad", + "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "Generaremos una llave de seguridad para que la guardes en un lugar seguro, como un administrador de contraseñas o una caja fuerte.", + "Enter a Security Phrase": "Ingrese una frase de seguridad", + "Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "Use una frase secreta que solo usted conozca y, opcionalmente, guarde una clave de seguridad para usarla como respaldo.", + "Enter your account password to confirm the upgrade:": "Ingrese la contraseña de su cuenta para confirmar la actualización:", + "Restore your key backup to upgrade your encryption": "Restaure la copia de seguridad de su clave para actualizar su cifrado", + "Restore": "Restaurar", + "You'll need to authenticate with the server to confirm the upgrade.": "Deberá autenticarse con el servidor para confirmar la actualización.", + "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Actualice esta sesión para permitirle verificar otras sesiones, otorgándoles acceso a mensajes cifrados y marcándolos como confiables para otros usuarios.", + "Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.": "Ingrese una frase de seguridad que solo usted conozca, ya que se usa para proteger sus datos. Para estar seguro, no debe volver a utilizar la contraseña de su cuenta.", + "Enter a recovery passphrase": "Ingrese una frase de contraseña de recuperación", + "Great! This recovery passphrase looks strong enough.": "¡Excelente! Esta frase de contraseña de recuperación parece lo suficientemente sólida.", + "That matches!": "¡Eso combina!", + "Use a different passphrase?": "¿Utiliza una frase de contraseña diferente?", + "That doesn't match.": "Eso no coincide.", + "Go back to set it again.": "Regrese para configurarlo nuevamente.", + "Enter your recovery passphrase a second time to confirm it.": "Ingrese su contraseña de recuperación por segunda vez para confirmarla.", + "Confirm your recovery passphrase": "Confirma tu contraseña de recuperación", + "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.": "Guarde su llave de seguridad en un lugar seguro, como un administrador de contraseñas o una caja fuerte, ya que se usa para proteger sus datos cifrados.", + "Download": "Descargar", + "Unable to query secret storage status": "No se puede consultar el estado del almacenamiento secreto", + "Retry": "Reintentar", + "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "Si cancela ahora, puede perder mensajes y datos cifrados si pierde el acceso a sus inicios de sesión.", + "You can also set up Secure Backup & manage your keys in Settings.": "También puede configurar la Copia de seguridad segura y administrar sus claves en Configuración.", + "Set up Secure Backup": "Configurar copia de seguridad segura", + "Upgrade your encryption": "Actualice su cifrado", + "Set a Security Phrase": "Establecer una frase de seguridad", + "Confirm Security Phrase": "Confirmar la frase de seguridad", + "Save your Security Key": "Guarde su llave de seguridad", + "Unable to set up secret storage": "No se puede configurar el almacenamiento secreto", + "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "Almacenaremos una copia encriptada de sus claves en nuestro servidor. Asegure su copia de seguridad con una contraseña de recuperación.", + "For maximum security, this should be different from your account password.": "Para mayor seguridad, esta debe ser diferente a la contraseña de su cuenta.", + "Set up with a recovery key": "Configurar con una clave de recuperación", + "Please enter your recovery passphrase a second time to confirm.": "Ingrese su contraseña de recuperación por segunda vez para confirmar.", + "Repeat your recovery passphrase...": "Repite tu contraseña de recuperación ...", + "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Su clave de recuperación es una red de seguridad; puede usarla para restaurar el acceso a sus mensajes cifrados si olvida su contraseña de recuperación.", + "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Guarde una copia en un lugar seguro, como un administrador de contraseñas o incluso una caja fuerte.", + "Your recovery key": "Tu clave de recuperación", + "Your recovery key has been copied to your clipboard, paste it to:": "Tu clave de recuperación se ha copiado en tu portapapeles, pégala en:", + "Your recovery key is in your Downloads folder.": "Tu clave de recuperación está en tu carpeta Descargas.", + "Print it and store it somewhere safe": "Imprímelo y guárdalo en un lugar seguro", + "Save it on a USB key or backup drive": "Guárdelo en una llave USB o unidad de respaldo", + "Copy it to your personal cloud storage": "Cópielo a su almacenamiento personal en la nube", + "Your keys are being backed up (the first backup could take a few minutes).": "Se está realizando una copia de seguridad de sus claves (la primera copia de seguridad puede tardar unos minutos).", + "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Sin configurar Secure Message Recovery, no podrá restaurar su historial de mensajes encriptados si cierra sesión o usa otra sesión.", + "Set up Secure Message Recovery": "Configurar la recuperación segura de mensajes", + "Secure your backup with a recovery passphrase": "Asegure su copia de seguridad con una frase de contraseña de recuperación", + "Make a copy of your recovery key": "Haz una copia de tu clave de recuperación", + "Starting backup...": "Iniciando copia de seguridad ...", + "Success!": "¡Éxito!", + "Create key backup": "Crear copia de seguridad de claves", + "Unable to create key backup": "No se puede crear una copia de seguridad de la clave", + "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Sin configurar Secure Message Recovery, perderá su historial de mensajes seguros cuando cierre la sesión.", + "If you don't want to set this up now, you can later in Settings.": "Si no desea configurar esto ahora, puede hacerlo más tarde en Configuración.", + "Don't ask again": "No vuelvas a preguntar", + "New Recovery Method": "Nuevo método de recuperación", + "A new recovery passphrase and key for Secure Messages have been detected.": "Se han detectado una nueva contraseña y clave de recuperación para mensajes seguros.", + "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Si no configuró el nuevo método de recuperación, es posible que un atacante esté intentando acceder a su cuenta. Cambie la contraseña de su cuenta y configure un nuevo método de recuperación inmediatamente en Configuración.", + "Go to Settings": "Ir a la configuración", + "Set up Secure Messages": "Configurar mensajes seguros", + "Recovery Method Removed": "Método de recuperación eliminado", + "This session has detected that your recovery passphrase and key for Secure Messages have been removed.": "Esta sesión ha detectado que se han eliminado su contraseña de recuperación y la clave para Mensajes seguros.", + "If you did this accidentally, you can setup Secure Messages on this session which will re-encrypt this session's message history with a new recovery method.": "Si hizo esto accidentalmente, puede configurar Mensajes seguros en esta sesión que volverá a cifrar el historial de mensajes de esta sesión con un nuevo método de recuperación.", + "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Si no eliminó el método de recuperación, es posible que un atacante esté intentando acceder a su cuenta. Cambie la contraseña de su cuenta y configure un nuevo método de recuperación inmediatamente en Configuración.", + "If disabled, messages from encrypted rooms won't appear in search results.": "Si está desactivado, los mensajes de las salas cifradas no aparecerán en los resultados de búsqueda.", + "Disable": "Inhabilitar", + "Not currently indexing messages for any room.": "Actualmente no indexa mensajes para ninguna sala.", + "Currently indexing: %(currentRoom)s": "Actualmente indexando: %(currentRoom)s", + "%(brand)s is securely caching encrypted messages locally for them to appear in search results:": "%(brand)s está almacenando en caché de forma segura los mensajes cifrados localmente para que aparezcan en los resultados de búsqueda:", + "Space used:": "Espacio usado:", + "Indexed messages:": "Mensajes indexados:", + "Indexed rooms:": "Salas indexadas:", + "%(doneRooms)s out of %(totalRooms)s": "%(doneRooms)s fuera de %(totalRooms)s", + "Message downloading sleep time(ms)": "Tiempo de suspensión de descarga de mensajes(ms)", + "Navigation": "Navegación", + "Calls": "Llamadas", + "Room List": "Lista de habitaciones", + "Autocomplete": "Autocompletar", + "Alt": "Alt", + "Alt Gr": "Alt Gr", + "Shift": "Shift", + "Super": "Super", + "Ctrl": "Ctrl", + "Toggle Bold": "Alternar negrita", + "Toggle Italics": "Alternar cursiva", + "Toggle Quote": "Alternar Entrecomillar", + "New line": "Nueva línea", + "Navigate recent messages to edit": "Navegar por mensajes recientes para editar", + "Jump to start/end of the composer": "Saltar al inicio / final del compositor", + "Navigate composer history": "Navegar por el historial del compositor", + "Cancel replying to a message": "Cancelar la respuesta a un mensaje", + "Toggle microphone mute": "Alternar silencio del micrófono", + "Toggle video on/off": "Activar/desactivar video", + "Scroll up/down in the timeline": "Desplazarse hacia arriba o hacia abajo en la línea de tiempo", + "Dismiss read marker and jump to bottom": "Descartar el marcador de lectura y saltar al final", + "Jump to oldest unread message": "Ir al mensaje no leído más antiguo", + "Upload a file": "Cargar un archivo", + "Jump to room search": "Ir a la búsqueda de Salas", + "Navigate up/down in the room list": "Navegar hacia arriba/abajo en la lista de salas", + "Select room from the room list": "Seleccionar sala de la lista de salas", + "Collapse room list section": "Contraer la sección de lista de salas", + "Expand room list section": "Expandir la sección de la lista de salas", + "Clear room list filter field": "Borrar campo de filtro de lista de salas", + "Previous/next unread room or DM": "Sala o DM anterior/siguiente sin leer", + "Previous/next room or DM": "Sala anterior/siguiente o DM", + "Toggle the top left menu": "Alternar el menú superior izquierdo", + "Close dialog or context menu": "Cerrar cuadro de diálogo o menú contextual", + "Activate selected button": "Activar botón seleccionado", + "Toggle right panel": "Alternar panel derecho", + "Toggle this dialog": "Alternar este diálogo", + "Move autocomplete selection up/down": "Mover la selección de autocompletar hacia arriba/abajo", + "Cancel autocomplete": "Cancelar autocompletar", + "Page Up": "Página arriba", + "Page Down": "Página abajo", + "Esc": "Esc", + "Enter": "Enter", + "Space": "Espacio" } From ba738472968c7e4be4e1ebf2e5d3d1abb40944dd Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 14 Sep 2020 13:31:41 +0100 Subject: [PATCH 0200/1014] Upgrade matrix-js-sdk to 8.3.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 46b211dbe2..d780e222cf 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "is-ip": "^2.0.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.19", - "matrix-js-sdk": "8.3.0-rc.1", + "matrix-js-sdk": "8.3.0", "minimist": "^1.2.5", "pako": "^1.0.11", "parse5": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index 6aa2497a56..ad478a271a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5930,10 +5930,10 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@8.3.0-rc.1: - version "8.3.0-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-8.3.0-rc.1.tgz#19546de4bb9476e52e1e2761db54a9f138510f49" - integrity sha512-TnnaM2qsCNzZpNbzuduCDbIWchO9rrKhOjVHcbbbNBlpPUukEeoQo0FYw6MUjy7MSjyH1GIstAmRFlg0vbu0Qw== +matrix-js-sdk@8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-8.3.0.tgz#a57a6d071619c24b6e8501ae21fa4d21f6bbd052" + integrity sha512-ndKedUtZt72/4KWjlMevNwNDGfhPTOn/i4U6Iv1ZEfm7uZfbp5u3hVIyr8tyOiVuvMIxmcTajRdwSlRsNtYFkA== dependencies: "@babel/runtime" "^7.8.3" another-json "^0.2.0" From c6eda819f032561b6695b2aacd4197587de536de Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 14 Sep 2020 13:36:31 +0100 Subject: [PATCH 0201/1014] Prepare changelog for v3.4.0 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fe6d82b8b..f0275d25a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +Changes in [3.4.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.4.0) (2020-09-14) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.4.0-rc.1...v3.4.0) + + * Upgrade to JS SDK 8.3.0 + * [Release] Show verification status in the room summary card + [\#5196](https://github.com/matrix-org/matrix-react-sdk/pull/5196) + * Fix user info scrolling in new card view + [\#5200](https://github.com/matrix-org/matrix-react-sdk/pull/5200) + * Fix sticker picker height + [\#5199](https://github.com/matrix-org/matrix-react-sdk/pull/5199) + * [Release] Account for via in pill matching regex + [\#5190](https://github.com/matrix-org/matrix-react-sdk/pull/5190) + Changes in [3.4.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.4.0-rc.1) (2020-09-09) ============================================================================================================= [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.3.0...v3.4.0-rc.1) From 099f50b10e881745673223d395f2f25b60d37374 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 14 Sep 2020 13:36:32 +0100 Subject: [PATCH 0202/1014] v3.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d780e222cf..99d2e42d8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.4.0-rc.1", + "version": "3.4.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 148cad6ea2daecee3dc799616dc102698b0751f8 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 14 Sep 2020 13:42:34 +0100 Subject: [PATCH 0203/1014] Reset matrix-js-sdk back to develop branch --- package.json | 2 +- yarn.lock | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 99d2e42d8f..52bced7a46 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "is-ip": "^2.0.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.19", - "matrix-js-sdk": "8.3.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "minimist": "^1.2.5", "pako": "^1.0.11", "parse5": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index ad478a271a..515ddddfc7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5930,10 +5930,9 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@8.3.0: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "8.3.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-8.3.0.tgz#a57a6d071619c24b6e8501ae21fa4d21f6bbd052" - integrity sha512-ndKedUtZt72/4KWjlMevNwNDGfhPTOn/i4U6Iv1ZEfm7uZfbp5u3hVIyr8tyOiVuvMIxmcTajRdwSlRsNtYFkA== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b9886d4f3479c041fc6d91ebc88c4a998e9d2e7c" dependencies: "@babel/runtime" "^7.8.3" another-json "^0.2.0" From 99811bfc74bf7fedaa6f8fb784363a297585d0cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Mon, 14 Sep 2020 09:03:36 +0000 Subject: [PATCH 0204/1014] Translated using Weblate (Estonian) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 5fd83557bd..6855c87efb 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2490,5 +2490,8 @@ "Secret storage:": "Turvahoidla:", "ready": "valmis", "not ready": "ei ole valmis", - "Secure Backup": "Turvaline varundus" + "Secure Backup": "Turvaline varundus", + "End Call": "Lõpeta kõne", + "Remove the group call from the room?": "Kas eemaldame jututoast rühmakõne?", + "You don't have permission to remove the call from the room": "Sinul pole õigusi rühmakõne eemaldamiseks sellest jututoast" } From 2317300a59cf235442cb3f032e1df06cfc2db11b Mon Sep 17 00:00:00 2001 From: Michael Albert Date: Mon, 14 Sep 2020 10:49:24 +0000 Subject: [PATCH 0205/1014] Translated using Weblate (German) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index d5f71062cd..2c2f832fc8 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2262,7 +2262,7 @@ "Message layout": "Nachrichtenlayout", "Compact": "Kompakt", "Modern": "Modern", - "Use a system font": "Verwende die System-Schriftart", + "Use a system font": "Verwende eine System-Schriftart", "System font name": "System-Schriftart", "Customise your appearance": "Verändere das Erscheinungsbild", "Appearance Settings only affect this %(brand)s session.": "Einstellungen zum Erscheinungsbild wirken sich nur auf diese %(brand)s Sitzung aus.", @@ -2488,5 +2488,8 @@ "Secret storage:": "Sicherer Speicher:", "ready": "bereit", "not ready": "nicht bereit", - "Secure Backup": "Sicheres Backup" + "Secure Backup": "Sicheres Backup", + "End Call": "Anruf beenden", + "Remove the group call from the room?": "Konferenzgespräch aus diesem Raum entfernen?", + "You don't have permission to remove the call from the room": "Du hast keine Berechtigung um den Konferenzanruf aus dem Raum zu entfernen" } From 475ea4aa2a6c4a8735f024128c1c8b8e03e318e8 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Mon, 14 Sep 2020 09:02:37 +0000 Subject: [PATCH 0206/1014] Translated using Weblate (Hungarian) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 648885480b..8c797a16b1 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2490,5 +2490,8 @@ "Secret storage:": "Biztonsági tároló:", "ready": "kész", "not ready": "nem kész", - "Secure Backup": "Biztonsági Mentés" + "Secure Backup": "Biztonsági Mentés", + "End Call": "Hívás befejezése", + "Remove the group call from the room?": "Törlöd a konferenciahívást a szobából?", + "You don't have permission to remove the call from the room": "A konferencia hívás törléséhez nincs jogosultságod" } From f6a405ffd7bc2101bf380f65258707237e447eda Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Mon, 14 Sep 2020 09:18:15 +0000 Subject: [PATCH 0207/1014] Translated using Weblate (Russian) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 906f9a7901..0827b920b2 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2486,5 +2486,8 @@ "Secure Backup": "Безопасное резервное копирование", "Group call modified by %(senderName)s": "%(senderName)s изменил(а) групповой вызов", "Group call started by %(senderName)s": "Групповой вызов начат %(senderName)s", - "Group call ended by %(senderName)s": "%(senderName)s завершил(а) групповой вызов" + "Group call ended by %(senderName)s": "%(senderName)s завершил(а) групповой вызов", + "End Call": "Завершить звонок", + "Remove the group call from the room?": "Удалить групповой вызов из комнаты?", + "You don't have permission to remove the call from the room": "У вас нет разрешения на удаление звонка из комнаты" } From 73608c4aa9db90bba569fea605527ffb52d513ef Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 14 Sep 2020 14:03:51 +0100 Subject: [PATCH 0208/1014] Don't count widgets which no longer exist towards pinned count --- src/stores/WidgetStore.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index 377512223a..10327ce4e9 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -167,7 +167,9 @@ export default class WidgetStore extends AsyncStoreWithClient { // the only case it will go to three is if you have two and then a Jitsi gets added const roomId = this.getRoomId(widgetId); const roomInfo = this.getRoom(roomId); - return roomInfo && Object.keys(roomInfo.pinned).length < 2; + return roomInfo && Object.keys(roomInfo.pinned).filter(k => { + return roomInfo.widgets.some(app => app.id === k); + }).length < 2; } public pinWidget(widgetId: string) { From 493d3ae28859907010815dfbe4804247b6e79e15 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 14 Sep 2020 14:35:08 +0100 Subject: [PATCH 0209/1014] Don't show Notifications Prompt Toast if user has master rule enabled --- src/Notifier.ts | 3 ++- src/settings/controllers/NotificationControllers.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Notifier.ts b/src/Notifier.ts index 473de6c161..03f037be98 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -33,6 +33,7 @@ import Modal from './Modal'; import SettingsStore from "./settings/SettingsStore"; import { hideToast as hideNotificationsToast } from "./toasts/DesktopNotificationsToast"; import {SettingLevel} from "./settings/SettingLevel"; +import {isPushNotifyDisabled} from "./settings/controllers/NotificationControllers"; /* * Dispatches: @@ -302,7 +303,7 @@ export const Notifier = { return false; } const isGuest = client.isGuest(); - return !isGuest && this.supportsDesktopNotifications() && + return !isGuest && this.supportsDesktopNotifications() && !isPushNotifyDisabled() && !this.isEnabled() && !this._isToolbarHidden(); }, diff --git a/src/settings/controllers/NotificationControllers.ts b/src/settings/controllers/NotificationControllers.ts index bf5971d02e..323c64e9c3 100644 --- a/src/settings/controllers/NotificationControllers.ts +++ b/src/settings/controllers/NotificationControllers.ts @@ -24,7 +24,7 @@ import {PushProcessor} from "matrix-js-sdk/src/pushprocessor"; // .m.rule.master being enabled means all events match that push rule // default action on this rule is dont_notify, but it could be something else -function isPushNotifyDisabled(): boolean { +export function isPushNotifyDisabled(): boolean { // Return the value of the master push rule as a default const processor = new PushProcessor(MatrixClientPeg.get()); const masterRule = processor.getPushRuleById(".m.rule.master"); From 229967aa33c0ffeef1cc59be3fc614d9efd95d48 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 14 Sep 2020 15:16:29 +0100 Subject: [PATCH 0210/1014] Retry joinRoom up to 5 times in the case of a 504 GATEWAY TIMEOUT --- src/stores/RoomViewStore.tsx | 25 +++++++++++++++++++------ src/utils/promise.ts | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index a0f0fb8f68..be1141fa1e 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -18,6 +18,7 @@ limitations under the License. import React from "react"; import {Store} from 'flux/utils'; +import {MatrixError} from "matrix-js-sdk/src/http-api"; import dis from '../dispatcher/dispatcher'; import {MatrixClientPeg} from '../MatrixClientPeg'; @@ -26,6 +27,9 @@ import Modal from '../Modal'; import { _t } from '../languageHandler'; import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache'; import {ActionPayload} from "../dispatcher/payloads"; +import {retry} from "../utils/promise"; + +const NUM_JOIN_RETRY = 5; const INITIAL_STATE = { // Whether we're joining the currently viewed room (see isJoining()) @@ -259,24 +263,32 @@ class RoomViewStore extends Store { }); } - private joinRoom(payload: ActionPayload) { + private async joinRoom(payload: ActionPayload) { this.setState({ joining: true, }); - MatrixClientPeg.get().joinRoom( - this.state.roomAlias || this.state.roomId, payload.opts, - ).then(() => { + + const cli = MatrixClientPeg.get(); + const address = this.state.roomAlias || this.state.roomId; + try { + await retry(() => cli.joinRoom(address, payload.opts), NUM_JOIN_RETRY, (err) => { + // if we received a Gateway timeout then retry + return err.httpStatus === 504; + }); + // We do *not* clear the 'joining' flag because the Room object and/or our 'joined' member event may not // have come down the sync stream yet, and that's the point at which we'd consider the user joined to the // room. dis.dispatch({ action: 'join_room_ready' }); - }, (err) => { + } catch (err) { dis.dispatch({ action: 'join_room_error', err: err, }); + let msg = err.message ? err.message : JSON.stringify(err); console.log("Failed to join room:", msg); + if (err.name === "ConnectionError") { msg = _t("There was an error joining the room"); } else if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') { @@ -296,12 +308,13 @@ class RoomViewStore extends Store { } } } + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, { title: _t("Failed to join room"), description: msg, }); - }); + } } private getInvitingUserId(roomId: string): string { diff --git a/src/utils/promise.ts b/src/utils/promise.ts index d3ae2c3d1b..f828ddfdaf 100644 --- a/src/utils/promise.ts +++ b/src/utils/promise.ts @@ -68,3 +68,21 @@ export function allSettled(promises: Promise[]): Promise(fn: () => Promise, num: number, predicate?: (e: E) => boolean) { + let lastErr: E; + for (let i = 0; i < num; i++) { + try { + const v = await fn(); + // If `await fn()` throws then we won't reach here + return v; + } catch (err) { + if (predicate && !predicate(err)) { + throw err; + } + lastErr = err; + } + } + throw lastErr; +} From fa50b31fd2291cdd3654449a0bd464f32e2c90dd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 14 Sep 2020 15:20:06 +0100 Subject: [PATCH 0211/1014] Fix crashes with cannot read isResizing of undefined --- src/components/structures/ScrollPanel.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index b4f5195803..99a3da2565 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -198,7 +198,8 @@ export default class ScrollPanel extends React.Component { } onScroll = ev => { - if (this.props.resizeNotifier.isResizing) return; // skip scroll events caused by resizing + // skip scroll events caused by resizing + if (this.props.resizeNotifier && this.props.resizeNotifier.isResizing) return; debuglog("onScroll", this._getScrollNode().scrollTop); this._scrollTimeout.restart(); this._saveScrollState(); From ca0ad0b7ed4603d7b7b0d46c892343fda3856af7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 14 Sep 2020 14:03:51 +0100 Subject: [PATCH 0212/1014] Don't count widgets which no longer exist towards pinned count --- src/stores/WidgetStore.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index 377512223a..10327ce4e9 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -167,7 +167,9 @@ export default class WidgetStore extends AsyncStoreWithClient { // the only case it will go to three is if you have two and then a Jitsi gets added const roomId = this.getRoomId(widgetId); const roomInfo = this.getRoom(roomId); - return roomInfo && Object.keys(roomInfo.pinned).length < 2; + return roomInfo && Object.keys(roomInfo.pinned).filter(k => { + return roomInfo.widgets.some(app => app.id === k); + }).length < 2; } public pinWidget(widgetId: string) { From e6e77633ad41ad2297fd85cf19c5cb8b534c4ed1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 14 Sep 2020 15:20:06 +0100 Subject: [PATCH 0213/1014] Fix crashes with cannot read isResizing of undefined --- src/components/structures/ScrollPanel.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index b4f5195803..99a3da2565 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -198,7 +198,8 @@ export default class ScrollPanel extends React.Component { } onScroll = ev => { - if (this.props.resizeNotifier.isResizing) return; // skip scroll events caused by resizing + // skip scroll events caused by resizing + if (this.props.resizeNotifier && this.props.resizeNotifier.isResizing) return; debuglog("onScroll", this._getScrollNode().scrollTop); this._scrollTimeout.restart(); this._saveScrollState(); From 9a3dfe5235e8585265cd06008816c9498134fb3a Mon Sep 17 00:00:00 2001 From: random Date: Mon, 14 Sep 2020 13:58:22 +0000 Subject: [PATCH 0214/1014] Translated using Weblate (Italian) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index eb33a8b3a6..9730c055ec 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2493,5 +2493,8 @@ "Failed to find the general chat for this community": "Impossibile trovare la chat generale di questa comunità", "Community settings": "Impostazioni comunità", "User settings": "Impostazioni utente", - "Community and user menu": "Menu comunità e utente" + "Community and user menu": "Menu comunità e utente", + "End Call": "Chiudi chiamata", + "Remove the group call from the room?": "Rimuovere la chiamata di gruppo dalla stanza?", + "You don't have permission to remove the call from the room": "Non hai l'autorizzazione per rimuovere la chiamata dalla stanza" } From e32cb175cb24b7f43c0f1bc8cf9ae2814b03870b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 14 Sep 2020 15:44:00 +0100 Subject: [PATCH 0215/1014] Fix Bridges tab crashing when the room does not have bridges --- .../views/settings/tabs/room/BridgeSettingsTab.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx b/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx index 8638105cd9..3c74bd4c1a 100644 --- a/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx @@ -46,9 +46,10 @@ export default class BridgeSettingsTab extends React.Component { const client = MatrixClientPeg.get(); const roomState = client.getRoom(roomId).currentState; - return [].concat(...BRIDGE_EVENT_TYPES.map((typeName) => - Array.from(roomState.events.get(typeName).values()), - )); + return BRIDGE_EVENT_TYPES.map(typeName => { + const events = roomState.events.get(typeName); + return events ? Array.from(events.values()) : []; + }).flat(1); } render() { From a5d42d1e2af0c90801e11a892ff8dc7a03a1a56b Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 14 Sep 2020 15:49:09 +0100 Subject: [PATCH 0216/1014] Prepare changelog for v3.4.1 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0275d25a1..6fa9cc29f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +Changes in [3.4.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.4.1) (2020-09-14) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.4.0...v3.4.1) + + * Don't count widgets which no longer exist towards pinned count + [\#5202](https://github.com/matrix-org/matrix-react-sdk/pull/5202) + * Fix crashes with cannot read isResizing of undefined + [\#5205](https://github.com/matrix-org/matrix-react-sdk/pull/5205) + Changes in [3.4.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.4.0) (2020-09-14) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.4.0-rc.1...v3.4.0) From 4f930ac50cb114d6233424fcb502fa273ca9636f Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 14 Sep 2020 15:49:09 +0100 Subject: [PATCH 0217/1014] v3.4.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 99d2e42d8f..2dd3e32077 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.4.0", + "version": "3.4.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From b177843e459a01c8a4792f7fe7134f34c633ea84 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 14 Sep 2020 22:03:54 -0600 Subject: [PATCH 0218/1014] Remove defunct "always show encryption icons" setting It doesn't do anything in practice. Fixes https://github.com/vector-im/element-web/issues/8725 --- res/css/views/rooms/_EventTile.scss | 10 ---------- src/components/views/rooms/EventTile.js | 6 +----- .../settings/tabs/user/PreferencesUserSettingsTab.js | 1 - src/settings/Settings.ts | 5 ----- 4 files changed, 1 insertion(+), 21 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index eb0e1dd7b0..3b9a491db5 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -394,16 +394,6 @@ $left-gutter: 64px; opacity: 1; } -.mx_EventTile_e2eIcon_hidden { - display: none; -} - -/* always override hidden attribute for blocked and warning */ -.mx_EventTile_e2eIcon_hidden[src*="img/e2e-blocked.svg"], -.mx_EventTile_e2eIcon_hidden[src*="img/e2e-warning.svg"] { - display: block; -} - .mx_EventTile_keyRequestInfo { font-size: $font-12px; } diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 647ef585d7..ab9f240f2d 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -1027,11 +1027,7 @@ class E2ePadlock extends React.Component { tooltip = ; } - let classes = `mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${this.props.icon}`; - if (!SettingsStore.getValue("alwaysShowEncryptionIcons")) { - classes += ' mx_EventTile_e2eIcon_hidden'; - } - + const classes = `mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${this.props.icon}`; return (
    Date: Mon, 14 Sep 2020 22:27:40 -0600 Subject: [PATCH 0219/1014] Introduce a concept of UI features, using it for URL previews at first Fixes https://github.com/vector-im/element-web/issues/15176 This is effectively the base for all of https://github.com/vector-im/element-web/issues/15185 --- docs/settings.md | 13 +++++- .../tabs/room/GeneralRoomSettingsTab.js | 17 +++++-- .../tabs/user/PreferencesUserSettingsTab.js | 5 +++ src/i18n/strings/en_EN.json | 2 +- src/settings/Settings.ts | 12 +++++ src/settings/UIFeature.ts | 20 +++++++++ .../controllers/UIFeatureController.ts | 45 +++++++++++++++++++ 7 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 src/settings/UIFeature.ts create mode 100644 src/settings/controllers/UIFeatureController.ts diff --git a/docs/settings.md b/docs/settings.md index 4172c72c15..891877a57a 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -120,6 +120,18 @@ Call `SettingsStore.getValue()` as you would for any other setting. Call `SettingsStore.setValue("feature_name", null, SettingLevel.DEVICE, true)`. +### A note on UI features + +UI features are a different concept to plain features. Instead of being representative of unstable or +unpredicatable behaviour, they are logical chunks of UI which can be disabled by deployments for ease +of understanding with users. They are simply represented as boring settings with a convention of being +named as `UIFeature.$location` where `$location` is a rough descriptor of what is toggled, such as +`URLPreviews` or `Communities`. + +UI features also tend to have their own setting controller (see below) to manipulate settings which might +be affected by the UI feature being disabled. For example, if URL previews are disabled as a UI feature +then the URL preview options will use the `UIFeatureController` to ensure they remain disabled while the +UI feature is disabled. ## Setting controllers @@ -226,4 +238,3 @@ In practice, handlers which rely on remote changes (account data, room events, e generalized `WatchManager` - a class specifically designed to deduplicate the logic of managing watchers. The handlers which are localized to the local client (device) generally just trigger the `WatchManager` when they manipulate the setting themselves as there's nothing to really 'watch'. - diff --git a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js index 1f12396413..90eb60e632 100644 --- a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js @@ -22,6 +22,8 @@ import * as sdk from "../../../../.."; import AccessibleButton from "../../../elements/AccessibleButton"; import dis from "../../../../../dispatcher/dispatcher"; import MatrixClientContext from "../../../../../contexts/MatrixClientContext"; +import SettingsStore from "../../../../../settings/SettingsStore"; +import {UIFeature} from "../../../../../settings/UIFeature"; export default class GeneralRoomSettingsTab extends React.Component { static propTypes = { @@ -61,6 +63,16 @@ export default class GeneralRoomSettingsTab extends React.Component { const canChangeGroups = room.currentState.mayClientSendStateEvent("m.room.related_groups", client); const groupsEvent = room.currentState.getStateEvents("m.room.related_groups", ""); + let urlPreviewSettings = <> + {_t("URL Previews")} +
    + +
    + ; + if (!SettingsStore.getValue(UIFeature.URLPreviews)) { + urlPreviewSettings = null; + } + return (
    {_t("General")}
    @@ -82,10 +94,7 @@ export default class GeneralRoomSettingsTab extends React.Component { relatedGroupsEvent={groupsEvent} />
    - {_t("URL Previews")} -
    - -
    + {urlPreviewSettings} {_t("Leave room")}
    diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index a77815a68c..0da8129568 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -23,6 +23,7 @@ import Field from "../../../elements/Field"; import * as sdk from "../../../../.."; import PlatformPeg from "../../../../../PlatformPeg"; import {SettingLevel} from "../../../../../settings/SettingLevel"; +import {UIFeature} from "../../../../../settings/UIFeature"; export default class PreferencesUserSettingsTab extends React.Component { static ROOM_LIST_SETTINGS = [ @@ -138,6 +139,10 @@ export default class PreferencesUserSettingsTab extends React.Component { }; _renderGroup(settingIds) { + if (!SettingsStore.getValue(UIFeature.URLPreviews)) { + settingIds = settingIds.filter(i => i !== 'urlPreviewsEnabled'); + } + const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag"); return settingIds.map(i => ); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 93781160ce..1249345e7c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -946,8 +946,8 @@ "This room is bridging messages to the following platforms. Learn more.": "This room is bridging messages to the following platforms. Learn more.", "This room isn’t bridging messages to any platforms. Learn more.": "This room isn’t bridging messages to any platforms. Learn more.", "Bridges": "Bridges", - "Room Addresses": "Room Addresses", "URL Previews": "URL Previews", + "Room Addresses": "Room Addresses", "Uploaded sound": "Uploaded sound", "Sounds": "Sounds", "Notification sound": "Notification sound", diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 9e0f36b1ba..03d8c8b136 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -32,6 +32,8 @@ import UseSystemFontController from './controllers/UseSystemFontController'; import { SettingLevel } from "./SettingLevel"; import SettingController from "./controllers/SettingController"; import { RightPanelPhases } from "../stores/RightPanelStorePhases"; +import UIFeatureController from "./controllers/UIFeatureController"; +import { UIFeature } from "./UIFeature"; // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times const LEVELS_ROOM_SETTINGS = [ @@ -69,6 +71,10 @@ const LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG = [ SettingLevel.DEVICE, SettingLevel.CONFIG, ]; +const LEVELS_UI_FEATURE = [ + SettingLevel.CONFIG, + // in future we might have a .well-known level or something +]; export interface ISetting { // Must be set to true for features. Default is 'false'. @@ -447,6 +453,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { "room": _td("Enable URL previews by default for participants in this room"), }, default: true, + controller: new UIFeatureController(UIFeature.URLPreviews), }, "urlPreviewsEnabled_e2ee": { supportedLevels: [SettingLevel.ROOM_DEVICE, SettingLevel.ROOM_ACCOUNT], @@ -454,6 +461,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { "room-account": _td("Enable URL previews for this room (only affects you)"), }, default: false, + controller: new UIFeatureController(UIFeature.URLPreviews), }, "roomColor": { supportedLevels: LEVELS_ROOM_SETTINGS_WITH_ROOM, @@ -611,4 +619,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_ROOM_OR_ACCOUNT, default: {}, }, + [UIFeature.URLPreviews]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts new file mode 100644 index 0000000000..3ca0c67484 --- /dev/null +++ b/src/settings/UIFeature.ts @@ -0,0 +1,20 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// see settings.md for documentation on conventions +export enum UIFeature { + URLPreviews = "UIFeature.URLPreviews", +} diff --git a/src/settings/controllers/UIFeatureController.ts b/src/settings/controllers/UIFeatureController.ts new file mode 100644 index 0000000000..ed6598a6e8 --- /dev/null +++ b/src/settings/controllers/UIFeatureController.ts @@ -0,0 +1,45 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import SettingController from "./SettingController"; +import { SettingLevel } from "../SettingLevel"; +import SettingsStore from "../SettingsStore"; + +/** + * Enforces that a boolean setting cannot be enabled if the corresponding + * UI feature is disabled. If the UI feature is enabled, the setting value + * is unchanged. + * + * Settings using this controller are assumed to return `false` when disabled. + */ +export default class UIFeatureController extends SettingController { + public constructor(private uiFeatureName: string) { + super(); + } + + public getValueOverride( + level: SettingLevel, + roomId: string, + calculatedValue: any, + calculatedAtLevel: SettingLevel, + ): any { + if (!SettingsStore.getValue(this.uiFeatureName)) { + // per the docs: we force a disabled state when the feature isn't active + return false; + } + return null; // no override + } +} From 0cac6874d4151bcd0d4696fcd5298558bec652e0 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 14 Sep 2020 22:31:45 -0600 Subject: [PATCH 0220/1014] Update i18n --- src/i18n/strings/en_EN.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 93781160ce..fc8bef0fa8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -474,7 +474,6 @@ "Show timestamps in 12 hour format (e.g. 2:30pm)": "Show timestamps in 12 hour format (e.g. 2:30pm)", "Always show message timestamps": "Always show message timestamps", "Autoplay GIFs and videos": "Autoplay GIFs and videos", - "Always show encryption icons": "Always show encryption icons", "Show a reminder to enable Secure Message Recovery in encrypted rooms": "Show a reminder to enable Secure Message Recovery in encrypted rooms", "Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting", "Show avatars in user and room mentions": "Show avatars in user and room mentions", From 5202037eeb3c9f73ab0b8aa65ee3945254cda20a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 14 Sep 2020 15:16:29 +0100 Subject: [PATCH 0221/1014] Retry joinRoom up to 5 times in the case of a 504 GATEWAY TIMEOUT --- src/stores/RoomViewStore.tsx | 25 +++++++++++++++++++------ src/utils/promise.ts | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index a0f0fb8f68..be1141fa1e 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -18,6 +18,7 @@ limitations under the License. import React from "react"; import {Store} from 'flux/utils'; +import {MatrixError} from "matrix-js-sdk/src/http-api"; import dis from '../dispatcher/dispatcher'; import {MatrixClientPeg} from '../MatrixClientPeg'; @@ -26,6 +27,9 @@ import Modal from '../Modal'; import { _t } from '../languageHandler'; import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache'; import {ActionPayload} from "../dispatcher/payloads"; +import {retry} from "../utils/promise"; + +const NUM_JOIN_RETRY = 5; const INITIAL_STATE = { // Whether we're joining the currently viewed room (see isJoining()) @@ -259,24 +263,32 @@ class RoomViewStore extends Store { }); } - private joinRoom(payload: ActionPayload) { + private async joinRoom(payload: ActionPayload) { this.setState({ joining: true, }); - MatrixClientPeg.get().joinRoom( - this.state.roomAlias || this.state.roomId, payload.opts, - ).then(() => { + + const cli = MatrixClientPeg.get(); + const address = this.state.roomAlias || this.state.roomId; + try { + await retry(() => cli.joinRoom(address, payload.opts), NUM_JOIN_RETRY, (err) => { + // if we received a Gateway timeout then retry + return err.httpStatus === 504; + }); + // We do *not* clear the 'joining' flag because the Room object and/or our 'joined' member event may not // have come down the sync stream yet, and that's the point at which we'd consider the user joined to the // room. dis.dispatch({ action: 'join_room_ready' }); - }, (err) => { + } catch (err) { dis.dispatch({ action: 'join_room_error', err: err, }); + let msg = err.message ? err.message : JSON.stringify(err); console.log("Failed to join room:", msg); + if (err.name === "ConnectionError") { msg = _t("There was an error joining the room"); } else if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') { @@ -296,12 +308,13 @@ class RoomViewStore extends Store { } } } + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, { title: _t("Failed to join room"), description: msg, }); - }); + } } private getInvitingUserId(roomId: string): string { diff --git a/src/utils/promise.ts b/src/utils/promise.ts index d3ae2c3d1b..f828ddfdaf 100644 --- a/src/utils/promise.ts +++ b/src/utils/promise.ts @@ -68,3 +68,21 @@ export function allSettled(promises: Promise[]): Promise(fn: () => Promise, num: number, predicate?: (e: E) => boolean) { + let lastErr: E; + for (let i = 0; i < num; i++) { + try { + const v = await fn(); + // If `await fn()` throws then we won't reach here + return v; + } catch (err) { + if (predicate && !predicate(err)) { + throw err; + } + lastErr = err; + } + } + throw lastErr; +} From 608249745ae3215c51acb6f28a1af61640f0f9e7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 15 Sep 2020 13:19:47 +0100 Subject: [PATCH 0222/1014] Attempt to fix tests some more --- src/languageHandler.tsx | 15 ++++++++++++--- test/i18n-test/languageHandler-test.js | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index d9feec95b1..e699f8e301 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -27,6 +27,7 @@ import PlatformPeg from "./PlatformPeg"; // @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config import webpackLangJsonUrl from "$webapp/i18n/languages.json"; import { SettingLevel } from "./settings/SettingLevel"; +import {retry} from "./utils/promise"; const i18nFolder = 'i18n/'; @@ -327,7 +328,7 @@ export function setLanguage(preferredLangs: string | string[]) { console.error("Unable to find an appropriate language"); } - return getLanguage(i18nFolder + availLangs[langToUse].fileName); + return getLanguageRetry(i18nFolder + availLangs[langToUse].fileName); }).then((langData) => { counterpart.registerTranslations(langToUse, langData); counterpart.setLocale(langToUse); @@ -336,7 +337,7 @@ export function setLanguage(preferredLangs: string | string[]) { // Set 'en' as fallback language: if (langToUse !== "en") { - return getLanguage(i18nFolder + availLangs['en'].fileName); + return getLanguageRetry(i18nFolder + availLangs['en'].fileName); } }).then((langData) => { if (langData) counterpart.registerTranslations('en', langData); @@ -482,7 +483,15 @@ function weblateToCounterpart(inTrs: object): object { return outTrs; } -function getLanguage(langPath: string): object { +async function getLanguageRetry(langPath: string, num = 3): Promise { + return retry(() => getLanguage(langPath), num, e => { + console.log("Failed to load i18n", langPath); + console.error(e); + return true; // always retry + }); +} + +function getLanguage(langPath: string): Promise { return new Promise((resolve, reject) => { request( { method: "GET", url: langPath }, diff --git a/test/i18n-test/languageHandler-test.js b/test/i18n-test/languageHandler-test.js index 7968186e9e..b9bc955269 100644 --- a/test/i18n-test/languageHandler-test.js +++ b/test/i18n-test/languageHandler-test.js @@ -12,11 +12,11 @@ describe('languageHandler', function() { languageHandler.setMissingEntryGenerator(key => key.split("|", 2)[1]); }); - it('translates a string to german', function() { + it('translates a string to german', function(done) { languageHandler.setLanguage('de').then(function() { const translated = languageHandler._t('Rooms'); expect(translated).toBe('Räume'); - }); + }).then(done); }); it('handles plurals', function() { From 8e871c455ce457596bda265a3e6414dd3edd79b0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 15 Sep 2020 13:24:20 +0100 Subject: [PATCH 0223/1014] Fix german i18n test which was previously broken due to async --- __mocks__/browser-request.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/__mocks__/browser-request.js b/__mocks__/browser-request.js index 7d231fb9db..391be7c54f 100644 --- a/__mocks__/browser-request.js +++ b/__mocks__/browser-request.js @@ -1,4 +1,5 @@ const en = require("../src/i18n/strings/en_EN"); +const de = require("../src/i18n/strings/de_DE"); module.exports = jest.fn((opts, cb) => { const url = opts.url || opts.uri; @@ -8,9 +9,15 @@ module.exports = jest.fn((opts, cb) => { "fileName": "en_EN.json", "label": "English", }, + "de": { + "fileName": "de_DE.json", + "label": "German", + }, })); } else if (url && url.endsWith("en_EN.json")) { cb(undefined, {status: 200}, JSON.stringify(en)); + } else if (url && url.endsWith("de_DE.json")) { + cb(undefined, {status: 200}, JSON.stringify(de)); } else { cb(true, {status: 404}, ""); } From 6b5426bddd7f882a818bbab5db46f70491ae8e5a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 15 Sep 2020 13:58:29 +0100 Subject: [PATCH 0224/1014] Rename toolbar Notifier methods to prompt --- src/Notifier.ts | 10 +++++----- src/components/structures/MatrixChat.tsx | 2 +- src/toasts/DesktopNotificationsToast.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Notifier.ts b/src/Notifier.ts index 03f037be98..2643de1abc 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -259,7 +259,7 @@ export const Notifier = { } // set the notifications_hidden flag, as the user has knowingly interacted // with the setting we shouldn't nag them any further - this.setToolbarHidden(true); + this.setPromptHidden(true); }, isEnabled: function() { @@ -284,7 +284,7 @@ export const Notifier = { return SettingsStore.getValue("audioNotificationsEnabled"); }, - setToolbarHidden: function(hidden: boolean, persistent = true) { + setPromptHidden: function(hidden: boolean, persistent = true) { this.toolbarHidden = hidden; Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', hidden); @@ -297,17 +297,17 @@ export const Notifier = { } }, - shouldShowToolbar: function() { + shouldShowPrompt: function() { const client = MatrixClientPeg.get(); if (!client) { return false; } const isGuest = client.isGuest(); return !isGuest && this.supportsDesktopNotifications() && !isPushNotifyDisabled() && - !this.isEnabled() && !this._isToolbarHidden(); + !this.isEnabled() && !this._isPromptHidden(); }, - _isToolbarHidden: function() { + _isPromptHidden: function() { // Check localStorage for any such meta data if (global.localStorage) { return global.localStorage.getItem("notifications_hidden") === "true"; diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index d3d5835dae..4cdb58014e 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1344,7 +1344,7 @@ export default class MatrixChat extends React.PureComponent { this.firstSyncComplete = true; this.firstSyncPromise.resolve(); - if (Notifier.shouldShowToolbar()) { + if (Notifier.shouldShowPrompt()) { showNotificationsToast(); } diff --git a/src/toasts/DesktopNotificationsToast.ts b/src/toasts/DesktopNotificationsToast.ts index 413e82e20b..d8aa7647a3 100644 --- a/src/toasts/DesktopNotificationsToast.ts +++ b/src/toasts/DesktopNotificationsToast.ts @@ -24,7 +24,7 @@ const onAccept = () => { }; const onReject = () => { - Notifier.setToolbarHidden(true); + Notifier.setPromptHidden(true); }; const TOAST_KEY = "desktopnotifications"; From d643f908b1a9ab1e8cb45d805845c3f14861dca1 Mon Sep 17 00:00:00 2001 From: linsui Date: Tue, 15 Sep 2020 03:57:38 +0000 Subject: [PATCH 0225/1014] Translated using Weblate (Chinese (Simplified)) Currently translated at 97.5% (2316 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/zh_Hans/ --- src/i18n/strings/zh_Hans.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json index f1a3af31d7..eaf3b0d329 100644 --- a/src/i18n/strings/zh_Hans.json +++ b/src/i18n/strings/zh_Hans.json @@ -2371,5 +2371,18 @@ "Toggle this dialog": "切换此对话框", "End": "End", "The server is not configured to indicate what the problem is (CORS).": "服务器没有配置为提示错误是什么(CORS)。", - "Activate selected button": "激活选择的按钮" + "Activate selected button": "激活选择的按钮", + "End Call": "结束通话", + "Remove the group call from the room?": "是否从聊天室中移除聊天室?", + "You don't have permission to remove the call from the room": "您没有权限从聊天室中移除此通话", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "在纯文本消息之前附加 ( ͡° ͜ʖ ͡°)", + "Group call modified by %(senderName)s": "群通话被 %(senderName)s 修改", + "Group call started by %(senderName)s": "%(senderName)s 发起的群通话", + "Group call ended by %(senderName)s": "%(senderName)s 结束了群通话", + "Unknown App": "未知应用", + "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "社区 v2 原型。需要兼容的主服务器。高度实验性 - 谨慎使用。", + "Cross-signing is ready for use.": "交叉签名已可用。", + "Cross-signing is not set up.": "未设置交叉签名。", + "Backup version:": "备份版本:", + "Algorithm:": "算法:" } From 0e3c478e38d2ef13ef035a464d9dbd577ecde8e5 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Tue, 15 Sep 2020 02:22:58 +0000 Subject: [PATCH 0226/1014] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index c7dc5555c4..0a2c16343d 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2493,5 +2493,8 @@ "Secret storage:": "秘密儲存空間:", "ready": "準備好", "not ready": "尚未準備好", - "Secure Backup": "安全備份" + "Secure Backup": "安全備份", + "End Call": "結束通話", + "Remove the group call from the room?": "從聊天室中移除群組通話?", + "You don't have permission to remove the call from the room": "您沒有從聊天室移除通話的權限" } From c605c3636b92dbf9962d45dc4f47404d1c6ea826 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Tue, 15 Sep 2020 06:34:18 +0000 Subject: [PATCH 0227/1014] Translated using Weblate (Swedish) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 8be68c031e..3feee476c6 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -2423,5 +2423,8 @@ "Secret storage:": "Hemlig lagring:", "ready": "klart", "not ready": "inte klart", - "Secure Backup": "Säker säkerhetskopiering" + "Secure Backup": "Säker säkerhetskopiering", + "End Call": "Avsluta samtal", + "Remove the group call from the room?": "Ta bort gruppsamtalet från rummet?", + "You don't have permission to remove the call from the room": "Du har inte behörighet från att ta bort samtalet från rummet" } From 6c166f05601fc519fb9a5efc67d65d234ff12ce6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 15 Sep 2020 15:49:25 +0100 Subject: [PATCH 0228/1014] If no bug_report_endpoint_url, hide rageshaking from the App --- src/SlashCommands.tsx | 15 ++++++++--- src/autocomplete/CommandProvider.tsx | 4 +-- .../views/dialogs/SlashCommandHelpDialog.js | 1 + .../views/rooms/BasicMessageComposer.tsx | 3 ++- .../settings/tabs/user/HelpUserSettingsTab.js | 25 ++++++++++++------- 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 6456368211..7ba2022c6d 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -43,6 +43,7 @@ import { ensureDMExists } from "./createRoom"; import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload"; import { Action } from "./dispatcher/actions"; import { EffectiveMembership, getEffectiveMembership, leaveRoomBehaviour } from "./utils/membership"; +import SdkConfig from "./SdkConfig"; // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 interface HTMLInputEvent extends Event { @@ -87,6 +88,7 @@ interface ICommandOpts { runFn?: RunFn; category: string; hideCompletionAfterSpace?: boolean; + isEnabled?(): boolean; } export class Command { @@ -97,6 +99,7 @@ export class Command { runFn: undefined | RunFn; category: string; hideCompletionAfterSpace: boolean; + _isEnabled?: () => boolean; constructor(opts: ICommandOpts) { this.command = opts.command; @@ -106,6 +109,7 @@ export class Command { this.runFn = opts.runFn; this.category = opts.category || CommandCategories.other; this.hideCompletionAfterSpace = opts.hideCompletionAfterSpace || false; + this._isEnabled = opts.isEnabled; } getCommand() { @@ -125,6 +129,10 @@ export class Command { getUsage() { return _t('Usage') + ': ' + this.getCommandWithArgs(); } + + isEnabled() { + return this._isEnabled ? this._isEnabled() : true; + } } function reject(error) { @@ -971,6 +979,7 @@ export const Commands = [ command: "rageshake", aliases: ["bugreport"], description: _td("Send a bug report with logs"), + isEnabled: () => !!SdkConfig.get().bug_report_endpoint_url, args: "", runFn: function(roomId, args) { return success( @@ -1048,7 +1057,7 @@ Commands.forEach(cmd => { }); }); -export function parseCommandString(input) { +export function parseCommandString(input: string) { // trim any trailing whitespace, as it can confuse the parser for // IRC-style commands input = input.replace(/\s+$/, ''); @@ -1075,10 +1084,10 @@ export function parseCommandString(input) { * processing the command, or 'promise' if a request was sent out. * Returns null if the input didn't match a command. */ -export function getCommand(roomId, input) { +export function getCommand(roomId: string, input: string) { const {cmd, args} = parseCommandString(input); - if (CommandMap.has(cmd)) { + if (CommandMap.has(cmd) && CommandMap.get(cmd).isEnabled()) { return () => CommandMap.get(cmd).run(roomId, args, cmd); } } diff --git a/src/autocomplete/CommandProvider.tsx b/src/autocomplete/CommandProvider.tsx index 3ff8ff0469..c2d1290e08 100644 --- a/src/autocomplete/CommandProvider.tsx +++ b/src/autocomplete/CommandProvider.tsx @@ -47,7 +47,7 @@ export default class CommandProvider extends AutocompleteProvider { if (command[0] !== command[1]) { // The input looks like a command with arguments, perform exact match const name = command[1].substr(1); // strip leading `/` - if (CommandMap.has(name)) { + if (CommandMap.has(name) && CommandMap.get(name).isEnabled()) { // some commands, namely `me` and `ddg` don't suit having the usage shown whilst typing their arguments if (CommandMap.get(name).hideCompletionAfterSpace) return []; matches = [CommandMap.get(name)]; @@ -63,7 +63,7 @@ export default class CommandProvider extends AutocompleteProvider { } - return matches.map((result) => { + return matches.filter(cmd => cmd.isEnabled()).map((result) => { let completion = result.getCommand() + ' '; const usedAlias = result.aliases.find(alias => `/${alias}` === command[1]); // If the command (or an alias) is the same as the one they entered, we don't want to discard their arguments diff --git a/src/components/views/dialogs/SlashCommandHelpDialog.js b/src/components/views/dialogs/SlashCommandHelpDialog.js index bae5b37993..5b4148e939 100644 --- a/src/components/views/dialogs/SlashCommandHelpDialog.js +++ b/src/components/views/dialogs/SlashCommandHelpDialog.js @@ -24,6 +24,7 @@ export default ({onFinished}) => { const categories = {}; Commands.forEach(cmd => { + if (!cmd.isEnabled()) return; if (!categories[cmd.category]) { categories[cmd.category] = []; } diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 6024f272ec..7c2eb83a94 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -207,7 +207,8 @@ export default class BasicMessageEditor extends React.Component // If the user is entering a command, only consider them typing if it is one which sends a message into the room if (isTyping && this.props.model.parts[0].type === "command") { const {cmd} = parseCommandString(this.props.model.parts[0].text); - if (!CommandMap.has(cmd) || CommandMap.get(cmd).category !== CommandCategories.messages) { + const command = CommandMap.get(cmd); + if (!command || !command.isEnabled() || command.category !== CommandCategories.messages) { isTyping = false; } } diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js index 64807ddb21..85ba22a353 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js @@ -204,9 +204,9 @@ export default class HelpUserSettingsTab extends React.Component { updateButton = ; } - return ( -
    -
    {_t("Help & About")}
    + let bugReportingSection; + if (SdkConfig.get().bug_report_endpoint_url) { + bugReportingSection = (
    {_t('Bug reporting')}
    @@ -223,22 +223,24 @@ export default class HelpUserSettingsTab extends React.Component { {_t("Submit debug logs")}
    -
    - - {_t("Clear cache and reload")} - -
    { _t( "To report a Matrix-related security issue, please read the Matrix.org " + "Security Disclosure Policy.", {}, { 'a': (sub) => {sub}, + rel="noreferrer noopener" target="_blank">{sub}, }) }
    + ); + } + + return ( +
    +
    {_t("Help & About")}
    + { bugReportingSection }
    {_t("FAQ")}
    @@ -268,6 +270,11 @@ export default class HelpUserSettingsTab extends React.Component { data-spoiler={MatrixClientPeg.get().getAccessToken()}> <{ _t("click to reveal") }> +
    + + {_t("Clear cache and reload")} + +
    From 60c6e23b945c5d05120dbae5bda49455af7116c7 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 15 Sep 2020 08:55:03 -0600 Subject: [PATCH 0229/1014] Match intended case for settings --- src/settings/UIFeature.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 3ca0c67484..e7355a98eb 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -16,5 +16,5 @@ limitations under the License. // see settings.md for documentation on conventions export enum UIFeature { - URLPreviews = "UIFeature.URLPreviews", + URLPreviews = "UIFeature.urlPreviews", } From 16d8ea184dbb809b8193dc27a4ec226b1a1b259d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 15 Sep 2020 15:58:23 +0100 Subject: [PATCH 0230/1014] i18n --- src/i18n/strings/en_EN.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 93781160ce..7abbd9331b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -841,12 +841,11 @@ "For help with using %(brand)s, click here.": "For help with using %(brand)s, click here.", "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "For help with using %(brand)s, click here or start a chat with our bot using the button below.", "Chat with %(brand)s Bot": "Chat with %(brand)s Bot", - "Help & About": "Help & About", "Bug reporting": "Bug reporting", "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.", "Submit debug logs": "Submit debug logs", - "Clear cache and reload": "Clear cache and reload", "To report a Matrix-related security issue, please read the Matrix.org Security Disclosure Policy.": "To report a Matrix-related security issue, please read the Matrix.org Security Disclosure Policy.", + "Help & About": "Help & About", "FAQ": "FAQ", "Keyboard Shortcuts": "Keyboard Shortcuts", "Versions": "Versions", @@ -856,6 +855,7 @@ "Identity Server is": "Identity Server is", "Access Token:": "Access Token:", "click to reveal": "click to reveal", + "Clear cache and reload": "Clear cache and reload", "Labs": "Labs", "Customise your experience with experimental labs features. Learn more.": "Customise your experience with experimental labs features. Learn more.", "Ignored/Blocked": "Ignored/Blocked", From 771ab8259844a908f0f22256a1ac90b3055fc47f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 10:29:21 +0100 Subject: [PATCH 0231/1014] Hide Analytics sections if piwik config is not provided --- src/Analytics.js | 8 +++- src/components/structures/MatrixChat.tsx | 4 +- .../tabs/user/SecurityUserSettingsTab.js | 38 ++++++++++--------- src/i18n/strings/en_EN.json | 6 +-- 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/Analytics.js b/src/Analytics.js index 9966d0845e..135cc2eb7a 100644 --- a/src/Analytics.js +++ b/src/Analytics.js @@ -170,15 +170,19 @@ class Analytics { return !this.baseUrl; } + canEnable() { + const config = SdkConfig.get(); + return navigator.doNotTrack !== "1" && config && config.piwik && config.piwik.url && config.piwik.siteId; + } + /** * Enable Analytics if initialized but disabled * otherwise try and initalize, no-op if piwik config missing */ async enable() { if (!this.disabled) return; - + if (!this.canEnable()) return; const config = SdkConfig.get(); - if (!config || !config.piwik || !config.piwik.url || !config.piwik.siteId) return; this.baseUrl = new URL("piwik.php", config.piwik.url); // set constants diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 4cdb58014e..9dfac86d63 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1214,8 +1214,8 @@ export default class MatrixChat extends React.PureComponent { StorageManager.tryPersistStorage(); - if (SettingsStore.getValue("showCookieBar") && this.props.config.piwik && navigator.doNotTrack !== "1") { - showAnalyticsToast(this.props.config.piwik && this.props.config.piwik.policyUrl); + if (SettingsStore.getValue("showCookieBar") && Analytics.canEnable()) { + showAnalyticsToast(this.props.config.piwik?.policyUrl); } } diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index c25ac438e0..03093d2dca 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -329,6 +329,26 @@ export default class SecurityUserSettingsTab extends React.Component { ; } + let analyticsSection; + if (Analytics.canEnable()) { + analyticsSection =
    + {_t("Analytics")} +
    + {_t( + "%(brand)s collects anonymous analytics to allow us to improve the application.", + { brand }, + )} +   + {_t("Privacy is important to us, so we don't collect any personal or " + + "identifiable data for our analytics.")} + + {_t("Learn more about how we use analytics.")} + +
    + +
    ; + } + return (
    {warning} @@ -358,23 +378,7 @@ export default class SecurityUserSettingsTab extends React.Component { {this._renderCurrentDeviceInfo()}
    {_t("Privacy")}
    -
    - {_t("Analytics")} -
    - {_t( - "%(brand)s collects anonymous analytics to allow us to improve the application.", - { brand }, - )} -   - {_t("Privacy is important to us, so we don't collect any personal or " + - "identifiable data for our analytics.")} - - {_t("Learn more about how we use analytics.")} - -
    - -
    + { analyticsSection }
    {_t("Advanced")}
    {this._renderIgnoredUsers()} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4414077005..070cf7e597 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -912,13 +912,13 @@ "Message search": "Message search", "Cross-signing": "Cross-signing", "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.", + "%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)s collects anonymous analytics to allow us to improve the application.", + "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.", + "Learn more about how we use analytics.": "Learn more about how we use analytics.", "Where you’re logged in": "Where you’re logged in", "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Manage the names of and sign out of your sessions below or verify them in your User Profile.", "A session's public name is visible to people you communicate with": "A session's public name is visible to people you communicate with", "Privacy": "Privacy", - "%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)s collects anonymous analytics to allow us to improve the application.", - "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.", - "Learn more about how we use analytics.": "Learn more about how we use analytics.", "No media permissions": "No media permissions", "You may need to manually permit %(brand)s to access your microphone/webcam": "You may need to manually permit %(brand)s to access your microphone/webcam", "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", From d9909864071985f884189aba751755b5ca7cb89f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 10:37:15 +0100 Subject: [PATCH 0232/1014] Also hide Privacy heading in settings and fix React Key warning --- .../views/settings/SecureBackupPanel.js | 8 ++-- .../tabs/user/SecurityUserSettingsTab.js | 38 ++++++++++--------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 7f0655d54a..c058e27de7 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -367,14 +367,14 @@ export default class SecureBackupPanel extends React.PureComponent { ; actions.push( - + {restoreButtonCaption} , ); if (!isSecureBackupRequired()) { actions.push( - + {_t("Delete Backup")} , ); @@ -388,7 +388,7 @@ export default class SecureBackupPanel extends React.PureComponent {

    {_t("Back up your keys before signing out to avoid losing them.")}

    ; actions.push( - + {_t("Set up")} , ); @@ -396,7 +396,7 @@ export default class SecureBackupPanel extends React.PureComponent { if (secretStorageKeyInAccount) { actions.push( - + {_t("Reset")} , ); diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index 03093d2dca..0c49f108d6 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -329,24 +329,27 @@ export default class SecurityUserSettingsTab extends React.Component {
    ; } - let analyticsSection; + let privacySection; if (Analytics.canEnable()) { - analyticsSection =
    - {_t("Analytics")} -
    - {_t( - "%(brand)s collects anonymous analytics to allow us to improve the application.", - { brand }, - )} -   - {_t("Privacy is important to us, so we don't collect any personal or " + - "identifiable data for our analytics.")} - - {_t("Learn more about how we use analytics.")} - + privacySection = +
    {_t("Privacy")}
    +
    + {_t("Analytics")} +
    + {_t( + "%(brand)s collects anonymous analytics to allow us to improve the application.", + { brand }, + )} +   + {_t("Privacy is important to us, so we don't collect any personal or " + + "identifiable data for our analytics.")} + + {_t("Learn more about how we use analytics.")} + +
    +
    - -
    ; + ; } return ( @@ -377,8 +380,7 @@ export default class SecurityUserSettingsTab extends React.Component { {crossSigning} {this._renderCurrentDeviceInfo()}
    -
    {_t("Privacy")}
    - { analyticsSection } + { privacySection }
    {_t("Advanced")}
    {this._renderIgnoredUsers()} From caef9b27a0f5de9da5937b517768efb13f4c4c51 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 10:50:53 +0100 Subject: [PATCH 0233/1014] Also hide bug reporting prompts from the Error Boundaries --- src/components/views/elements/ErrorBoundary.js | 15 ++++++++++++--- .../views/messages/TileErrorBoundary.js | 13 ++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/components/views/elements/ErrorBoundary.js b/src/components/views/elements/ErrorBoundary.js index 68bec667d8..9fe6861250 100644 --- a/src/components/views/elements/ErrorBoundary.js +++ b/src/components/views/elements/ErrorBoundary.js @@ -20,6 +20,7 @@ import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import PlatformPeg from '../../../PlatformPeg'; import Modal from '../../../Modal'; +import SdkConfig from "../../../SdkConfig"; /** * This error boundary component can be used to wrap large content areas and @@ -73,9 +74,10 @@ export default class ErrorBoundary extends React.PureComponent { if (this.state.error) { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const newIssueUrl = "https://github.com/vector-im/element-web/issues/new"; - return
    -
    -

    {_t("Something went wrong!")}

    + + let bugReportSection; + if (SdkConfig.get().bug_report_endpoint_url) { + bugReportSection =

    {_t( "Please create a new issue " + "on GitHub so that we can investigate this bug.", {}, { @@ -94,6 +96,13 @@ export default class ErrorBoundary extends React.PureComponent { {_t("Submit debug logs")} + ; + } + + return

    +
    +

    {_t("Something went wrong!")}

    + { bugReportSection } {_t("Clear cache and reload")} diff --git a/src/components/views/messages/TileErrorBoundary.js b/src/components/views/messages/TileErrorBoundary.js index e42ddab16a..9b67e32548 100644 --- a/src/components/views/messages/TileErrorBoundary.js +++ b/src/components/views/messages/TileErrorBoundary.js @@ -19,6 +19,7 @@ import classNames from 'classnames'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; import Modal from '../../../Modal'; +import SdkConfig from "../../../SdkConfig"; export default class TileErrorBoundary extends React.Component { constructor(props) { @@ -54,14 +55,20 @@ export default class TileErrorBoundary extends React.Component { mx_EventTile_content: true, mx_EventTile_tileError: true, }; + + let submitLogsButton; + if (SdkConfig.get().bug_report_endpoint_url) { + submitLogsButton = + {_t("Submit logs")} + ; + } + return (
    {_t("Can't load this message")} { mxEvent && ` (${mxEvent.getType()})` } - - {_t("Submit logs")} - + { submitLogsButton }
    ); From dbd3245e02f0051fd1d5431d919b78c9d3e099ff Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 10:53:30 +0100 Subject: [PATCH 0234/1014] i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7abbd9331b..42c4b9d198 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1441,8 +1441,8 @@ "Click to view edits": "Click to view edits", "Edited at %(date)s. Click to view edits.": "Edited at %(date)s. Click to view edits.", "edited": "edited", - "Can't load this message": "Can't load this message", "Submit logs": "Submit logs", + "Can't load this message": "Can't load this message", "Failed to load group members": "Failed to load group members", "Filter community members": "Filter community members", "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?", From 04d7aebad0908654a693324101e7ef6909c541f6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 10:59:14 +0100 Subject: [PATCH 0235/1014] UI Feature Flag: Disable feedback button --- src/components/structures/UserMenu.tsx | 22 ++++++++++++---------- src/settings/Settings.ts | 4 ++++ src/settings/UIFeature.ts | 1 + 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index b83369d296..369d3b7720 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -50,6 +50,7 @@ import dis from "../../dispatcher/dispatcher"; import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; import ErrorDialog from "../views/dialogs/ErrorDialog"; import EditCommunityPrototypeDialog from "../views/dialogs/EditCommunityPrototypeDialog"; +import {UIFeature} from "../../settings/UIFeature"; interface IProps { isMinimized: boolean; @@ -285,6 +286,15 @@ export default class UserMenu extends React.Component { ); } + let feedbackButton; + if (SettingsStore.getValue(UIFeature.Feedback)) { + feedbackButton = ; + } + let primaryHeader = (
    @@ -319,11 +329,7 @@ export default class UserMenu extends React.Component { label={_t("Archived rooms")} onClick={this.onShowArchived} /> */} - + { feedbackButton } { aria-label={_t("User settings")} onClick={(e) => this.onSettingsOpen(e, null)} /> - + { feedbackButton } Date: Wed, 16 Sep 2020 11:26:15 +0100 Subject: [PATCH 0236/1014] UI Feature Flag: Hide flair --- src/components/structures/MessagePanel.js | 7 +++++- src/components/structures/TimelinePanel.js | 2 ++ .../views/dialogs/UserSettingsDialog.js | 15 +++++++----- .../views/elements/EventTilePreview.tsx | 8 ++++++- src/components/views/elements/ReplyThread.js | 2 ++ src/components/views/rooms/EventTile.js | 7 ++++-- src/components/views/rooms/ReplyPreview.js | 14 +++++++---- .../views/rooms/SearchResultTile.js | 23 ++++++++++++++----- .../tabs/room/GeneralRoomSettingsTab.js | 22 +++++++++++------- src/settings/Settings.ts | 4 ++++ src/settings/UIFeature.ts | 1 + 11 files changed, 76 insertions(+), 29 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 230d136e04..fe7b20a2d9 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -135,6 +135,9 @@ export default class MessagePanel extends React.Component { // whether to use the irc layout useIRCLayout: PropTypes.bool, + + // whether or not to show flair at all + enableFlair: PropTypes.bool, }; // Force props to be loaded for useIRCLayout @@ -579,7 +582,8 @@ export default class MessagePanel extends React.Component { data-scroll-tokens={scrollToken} > - , diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 97f9ba48ed..8bbc66bf40 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -35,6 +35,7 @@ import Timer from '../../utils/Timer'; import shouldHideEvent from '../../shouldHideEvent'; import EditorStateTransfer from '../../utils/EditorStateTransfer'; import {haveTileForEvent} from "../views/rooms/EventTile"; +import {UIFeature} from "../../settings/UIFeature"; const PAGINATE_SIZE = 20; const INITIAL_SIZE = 20; @@ -1446,6 +1447,7 @@ class TimelinePanel extends React.Component { editState={this.state.editState} showReactions={this.props.showReactions} useIRCLayout={this.props.useIRCLayout} + enableFlair={SettingsStore.getValue(UIFeature.Flair)} /> ); } diff --git a/src/components/views/dialogs/UserSettingsDialog.js b/src/components/views/dialogs/UserSettingsDialog.js index ffde03fe31..8cb5a76760 100644 --- a/src/components/views/dialogs/UserSettingsDialog.js +++ b/src/components/views/dialogs/UserSettingsDialog.js @@ -32,6 +32,7 @@ import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab"; import * as sdk from "../../../index"; import SdkConfig from "../../../SdkConfig"; import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab"; +import {UIFeature} from "../../../settings/UIFeature"; export const USER_GENERAL_TAB = "USER_GENERAL_TAB"; export const USER_APPEARANCE_TAB = "USER_APPEARANCE_TAB"; @@ -86,12 +87,14 @@ export default class UserSettingsDialog extends React.Component { "mx_UserSettingsDialog_appearanceIcon", , )); - tabs.push(new Tab( - USER_FLAIR_TAB, - _td("Flair"), - "mx_UserSettingsDialog_flairIcon", - , - )); + if (SettingsStore.getValue(UIFeature.Flair)) { + tabs.push(new Tab( + USER_FLAIR_TAB, + _td("Flair"), + "mx_UserSettingsDialog_flairIcon", + , + )); + } tabs.push(new Tab( USER_NOTIFICATIONS_TAB, _td("Notifications"), diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx index 61e5f5381d..35019a901e 100644 --- a/src/components/views/elements/EventTilePreview.tsx +++ b/src/components/views/elements/EventTilePreview.tsx @@ -21,6 +21,8 @@ import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import * as Avatar from '../../../Avatar'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import EventTile from '../rooms/EventTile'; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; interface IProps { /** @@ -121,7 +123,11 @@ export default class EventTilePreview extends React.Component { }); return
    - +
    ; } } diff --git a/src/components/views/elements/ReplyThread.js b/src/components/views/elements/ReplyThread.js index 70592c72c5..2d17c858a2 100644 --- a/src/components/views/elements/ReplyThread.js +++ b/src/components/views/elements/ReplyThread.js @@ -28,6 +28,7 @@ import escapeHtml from "escape-html"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {Action} from "../../../dispatcher/actions"; import sanitizeHtml from "sanitize-html"; +import {UIFeature} from "../../../settings/UIFeature"; // This component does no cycle detection, simply because the only way to make such a cycle would be to // craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would @@ -366,6 +367,7 @@ export default class ReplyThread extends React.Component { isRedacted={ev.isRedacted()} isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")} useIRCLayout={this.props.useIRCLayout} + enableFlair={SettingsStore.getValue(UIFeature.Flair)} /> ; }); diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index ab9f240f2d..f444fb1f1a 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -206,6 +206,9 @@ export default class EventTile extends React.Component { // whether to use the irc layout useIRCLayout: PropTypes.bool, + + // whether or not to show flair at all + enableFlair: PropTypes.bool, }; static defaultProps = { @@ -736,10 +739,10 @@ export default class EventTile extends React.Component { else if (msgtype === 'm.file') text = _td('%(senderName)s uploaded a file'); sender = ; } else { - sender = ; + sender = ; } } diff --git a/src/components/views/rooms/ReplyPreview.js b/src/components/views/rooms/ReplyPreview.js index de70338245..c7872d95ed 100644 --- a/src/components/views/rooms/ReplyPreview.js +++ b/src/components/views/rooms/ReplyPreview.js @@ -22,6 +22,7 @@ import RoomViewStore from '../../../stores/RoomViewStore'; import SettingsStore from "../../../settings/SettingsStore"; import PropTypes from "prop-types"; import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; +import {UIFeature} from "../../../settings/UIFeature"; function cancelQuoting() { dis.dispatch({ @@ -80,11 +81,14 @@ export default class ReplyPreview extends React.Component { onClick={cancelQuoting} />
    - +
    ; } diff --git a/src/components/views/rooms/SearchResultTile.js b/src/components/views/rooms/SearchResultTile.js index 136bd23729..8b2a9c2d61 100644 --- a/src/components/views/rooms/SearchResultTile.js +++ b/src/components/views/rooms/SearchResultTile.js @@ -19,6 +19,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import {haveTileForEvent} from "./EventTile"; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; export default class SearchResultTile extends React.Component { static propTypes = { @@ -45,18 +47,27 @@ export default class SearchResultTile extends React.Component { const ret = []; const timeline = result.context.getTimeline(); - for (var j = 0; j < timeline.length; j++) { + for (let j = 0; j < timeline.length; j++) { const ev = timeline[j]; - var highlights; + let highlights; const contextual = (j != result.context.getOurEventIndex()); if (!contextual) { highlights = this.props.searchHighlights; } if (haveTileForEvent(ev)) { - ret.push(); + ret.push(( + + )); } } return ( diff --git a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js index 90eb60e632..9b8004d9d6 100644 --- a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js @@ -73,6 +73,18 @@ export default class GeneralRoomSettingsTab extends React.Component { urlPreviewSettings = null; } + let flairSection; + if (SettingsStore.getValue(UIFeature.Flair)) { + flairSection = <> + {_t("Flair")} +
    + +
    + ; + } + return (
    {_t("General")}
    @@ -87,14 +99,8 @@ export default class GeneralRoomSettingsTab extends React.Component { canonicalAliasEvent={canonicalAliasEv} aliasEvents={aliasEvents} />
    {_t("Other")}
    - {_t("Flair")} -
    - -
    - - {urlPreviewSettings} + { flairSection } + { urlPreviewSettings } {_t("Leave room")}
    diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 511daf5cc6..c57394d970 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -618,4 +618,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.Flair]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index e7355a98eb..8cbf7c207b 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -17,4 +17,5 @@ limitations under the License. // see settings.md for documentation on conventions export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", + Flair = "UIFeature.flair", } From 45420ff13bd7e0dbb06367e734c5037304d03292 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 11:27:39 +0100 Subject: [PATCH 0237/1014] i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4414077005..767dcd390c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2132,10 +2132,10 @@ "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other", "Failed to find the general chat for this community": "Failed to find the general chat for this community", + "Feedback": "Feedback", "Notification settings": "Notification settings", "Security & privacy": "Security & privacy", "All settings": "All settings", - "Feedback": "Feedback", "Community settings": "Community settings", "User settings": "User settings", "Switch to light mode": "Switch to light mode", From f4f94e31d184c8c3635a53d1b154f84e1a0bb0a8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 11:38:50 +0100 Subject: [PATCH 0238/1014] UI Feature Flag: Disable integrations entry UI --- src/SlashCommands.tsx | 3 +++ .../views/right_panel/RoomSummaryCard.tsx | 3 ++- src/components/views/rooms/AuxPanel.js | 24 +++++++++++-------- src/components/views/rooms/MessageComposer.js | 6 ++++- .../tabs/user/GeneralUserSettingsTab.js | 3 +++ src/settings/Settings.ts | 4 ++++ src/settings/UIFeature.ts | 1 + 7 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 7ba2022c6d..a6481d5b95 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -44,6 +44,8 @@ import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload"; import { Action } from "./dispatcher/actions"; import { EffectiveMembership, getEffectiveMembership, leaveRoomBehaviour } from "./utils/membership"; import SdkConfig from "./SdkConfig"; +import SettingsStore from "./settings/SettingsStore"; +import {UIFeature} from "./settings/UIFeature"; // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 interface HTMLInputEvent extends Event { @@ -797,6 +799,7 @@ export const Commands = [ command: 'addwidget', args: '', description: _td('Adds a custom widget by URL to the room'), + isEnabled: () => SettingsStore.getValue(UIFeature.Widgets), runFn: function(roomId, widgetUrl) { if (!widgetUrl) { return reject(_t("Please supply a widget URL or embed code")); diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index f51f66a5ea..9d20dc1fe1 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -42,6 +42,7 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import WidgetStore, {IApp} from "../../../stores/WidgetStore"; import { E2EStatus } from "../../../utils/ShieldUtils"; import RoomContext from "../../../contexts/RoomContext"; +import {UIFeature} from "../../../settings/UIFeature"; interface IProps { room: Room; @@ -242,7 +243,7 @@ const RoomSummaryCard: React.FC = ({ room, onClose }) => { - + { SettingsStore.getValue(UIFeature.Widgets) && } ; }; diff --git a/src/components/views/rooms/AuxPanel.js b/src/components/views/rooms/AuxPanel.js index 1f6f104487..f2211dba5c 100644 --- a/src/components/views/rooms/AuxPanel.js +++ b/src/components/views/rooms/AuxPanel.js @@ -28,6 +28,7 @@ import RateLimitedFunc from '../../../ratelimitedfunc'; import SettingsStore from "../../../settings/SettingsStore"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import CallView from "../voip/CallView"; +import {UIFeature} from "../../../settings/UIFeature"; export default class AuxPanel extends React.Component { @@ -198,18 +199,21 @@ export default class AuxPanel extends React.Component { /> ); - const appsDrawer = ; + let appsDrawer; + if (SettingsStore.getValue(UIFeature.Widgets)) { + appsDrawer = ; + } let stateViews = null; if (this.state.counters && SettingsStore.getValue("feature_state_counters")) { - let counters = []; + const counters = []; this.state.counters.forEach((counter, idx) => { const title = counter.title; @@ -218,7 +222,7 @@ export default class AuxPanel extends React.Component { const severity = counter.severity; const stateKey = counter.stateKey; - let span = { title }: { value } + let span = { title }: { value }; if (link) { span = ( diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 922cc2b11e..81c2ae7a33 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -31,6 +31,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import {aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu} from "../../structures/ContextMenu"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import ReplyPreview from "./ReplyPreview"; +import {UIFeature} from "../../../settings/UIFeature"; function ComposerAvatar(props) { const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); @@ -384,9 +385,12 @@ export default class MessageComposer extends React.Component { permalinkCreator={this.props.permalinkCreator} />, , , - , ); + if (SettingsStore.getValue(UIFeature.Widgets)) { + controls.push(); + } + if (this.state.showCallButtons) { if (callInProgress) { controls.push( diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 1ebefae590..42e12077f2 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -37,6 +37,7 @@ import {abbreviateUrl} from "../../../../../utils/UrlUtils"; import { getThreepidsWithBindStatus } from '../../../../../boundThreepids'; import Spinner from "../../../elements/Spinner"; import {SettingLevel} from "../../../../../settings/SettingLevel"; +import {UIFeature} from "../../../../../settings/UIFeature"; export default class GeneralUserSettingsTab extends React.Component { static propTypes = { @@ -366,6 +367,8 @@ export default class GeneralUserSettingsTab extends React.Component { } _renderIntegrationManagerSection() { + if (!SettingsStore.getValue(UIFeature.Widgets)) return null; + const SetIntegrationManager = sdk.getComponent("views.settings.SetIntegrationManager"); return ( diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 511daf5cc6..b35fa3db13 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -618,4 +618,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.Widgets]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index e7355a98eb..99196e5d30 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -17,4 +17,5 @@ limitations under the License. // see settings.md for documentation on conventions export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", + Widgets = "UIFeature.widgets", } From 8cbd67d6117647a51082b1d2bea4205f6282b72e Mon Sep 17 00:00:00 2001 From: Mirza Arnaut Date: Tue, 15 Sep 2020 22:38:43 +0000 Subject: [PATCH 0239/1014] Translated using Weblate (Bosnian) Currently translated at 0.2% (4 of 2374 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/bs/ --- src/i18n/strings/bs.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/bs.json b/src/i18n/strings/bs.json index 9e26dfeeb6..dc4ebda993 100644 --- a/src/i18n/strings/bs.json +++ b/src/i18n/strings/bs.json @@ -1 +1,6 @@ -{} \ No newline at end of file +{ + "Dismiss": "Odbaci", + "Create Account": "Otvori račun", + "Sign In": "Prijavite se", + "Explore rooms": "Istražite sobe" +} From c653e7c5a06bfa8ade2efb1c6f0ab2685fd4c78e Mon Sep 17 00:00:00 2001 From: call_xz Date: Tue, 15 Sep 2020 20:59:48 +0000 Subject: [PATCH 0240/1014] Translated using Weblate (Japanese) Currently translated at 56.6% (1344 of 2374 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ja/ --- src/i18n/strings/ja.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json index 4edf415efb..f5c2b89f8c 100644 --- a/src/i18n/strings/ja.json +++ b/src/i18n/strings/ja.json @@ -1319,7 +1319,7 @@ "Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)": "他のユーザーがあなたのホームサーバー (%(localDomain)s) を通じてこの部屋を見つけられるよう、アドレスを設定しましょう", "Enter recovery key": "リカバリキーを入力", "Verify this login": "このログインを承認", - "Signing In...": "サインインしています...", + "Signing In...": "サインイン中...", "If you've joined lots of rooms, this might take a while": "たくさんの部屋に参加している場合は、時間がかかる可能性があります", "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "このログインを他のセッションで承認し、あなたの認証情報を確認すれば、暗号化されたメッセージへアクセスできるようになります。", "This requires the latest %(brand)s on your other devices:": "最新版の %(brand)s が他のあなたのデバイスで実行されている必要があります:", @@ -1387,5 +1387,15 @@ "Your server": "あなたのサーバー", "Matrix": "Matrix", "Add a new server": "新しいサーバーを追加", - "Server name": "サーバー名" + "Server name": "サーバー名", + "Privacy": "プライバシー", + "Syncing...": "同期中...", + "Log in to your new account.": "新しいアカウントでログインする。", + "Use Recovery Key or Passphrase": "リカバリーキーまたはパスフレーズを使う", + "Use Recovery Key": "リカバリーキーを使う", + "%(brand)s Web": "%(brand)s ウェブ", + "%(brand)s Desktop": "%(brand)s デスクトップ", + "%(brand)s iOS": "%(brand)s iOS", + "%(brand)s Android": "%(brand)s Android", + "Your recovery key": "あなたのリカバリーキー" } From 7a448be1dc59b13a19916fc189123086d5069fbd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 12:14:33 +0100 Subject: [PATCH 0241/1014] UI Feature Flag: Disable advanced options and tidy up some copy --- .../views/dialogs/RoomSettingsDialog.js | 15 ++++---- .../tabs/user/PreferencesUserSettingsTab.js | 8 ++--- .../tabs/user/SecurityUserSettingsTab.js | 36 +++++++++++-------- src/settings/Settings.ts | 4 +++ src/settings/UIFeature.ts | 1 + 5 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.js index 613708e436..a43b284c42 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.js +++ b/src/components/views/dialogs/RoomSettingsDialog.js @@ -29,6 +29,7 @@ import * as sdk from "../../../index"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import dis from "../../../dispatcher/dispatcher"; import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; export const ROOM_GENERAL_TAB = "ROOM_GENERAL_TAB"; export const ROOM_SECURITY_TAB = "ROOM_SECURITY_TAB"; @@ -96,12 +97,14 @@ export default class RoomSettingsDialog extends React.Component { )); } - tabs.push(new Tab( - ROOM_ADVANCED_TAB, - _td("Advanced"), - "mx_RoomSettingsDialog_warningIcon", - , - )); + if (SettingsStore.getValue(UIFeature.AdvancedSettings)) { + tabs.push(new Tab( + ROOM_ADVANCED_TAB, + _td("Advanced"), + "mx_RoomSettingsDialog_warningIcon", + , + )); + } return tabs; } diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index d5dafe146a..347cfccd56 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -50,10 +50,10 @@ export default class PreferencesUserSettingsTab extends React.Component { 'showAvatarChanges', 'showDisplaynameChanges', 'showImages', + 'Pill.shouldShowPillAvatar', ]; - static ADVANCED_SETTINGS = [ - 'Pill.shouldShowPillAvatar', + static GENERAL_SETTINGS = [ 'TagPanel.enableTagPanel', 'promptBeforeInviteUnknownUsers', // Start automatically after startup (electron-only) @@ -191,8 +191,8 @@ export default class PreferencesUserSettingsTab extends React.Component {
    - {_t("Advanced")} - {this._renderGroup(PreferencesUserSettingsTab.ADVANCED_SETTINGS)} + {_t("General")} + {this._renderGroup(PreferencesUserSettingsTab.GENERAL_SETTINGS)} {minimizeToTrayOption} {autoHideMenuOption} {autoLaunchOption} diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index c25ac438e0..6509fece13 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -30,6 +30,8 @@ import dis from "../../../../../dispatcher/dispatcher"; import {privateShouldBeEncrypted} from "../../../../../createRoom"; import {SettingLevel} from "../../../../../settings/SettingLevel"; import SecureBackupPanel from "../../SecureBackupPanel"; +import SettingsStore from "../../../../../settings/SettingsStore"; +import {UIFeature} from "../../../../../settings/UIFeature"; export class IgnoredUser extends React.Component { static propTypes = { @@ -311,15 +313,13 @@ export default class SecurityUserSettingsTab extends React.Component { // can remove this. const CrossSigningPanel = sdk.getComponent('views.settings.CrossSigningPanel'); const crossSigning = ( -
    - {_t("Cross-signing")} -
    - -
    +
    + {_t("Cross-signing")} +
    +
    - ); - - const E2eAdvancedPanel = sdk.getComponent('views.settings.E2eAdvancedPanel'); +
    + ); let warning; if (!privateShouldBeEncrypted()) { @@ -329,6 +329,19 @@ export default class SecurityUserSettingsTab extends React.Component {
    ; } + const E2eAdvancedPanel = sdk.getComponent('views.settings.E2eAdvancedPanel'); + let advancedSection; + if (SettingsStore.getValue(UIFeature.AdvancedSettings)) { + advancedSection = <> +
    {_t("Advanced")}
    +
    + {this._renderIgnoredUsers()} + {this._renderManageInvites()} + +
    + ; + } + return (
    {warning} @@ -375,12 +388,7 @@ export default class SecurityUserSettingsTab extends React.Component {
    -
    {_t("Advanced")}
    -
    - {this._renderIgnoredUsers()} - {this._renderManageInvites()} - -
    + { advancedSection }
    ); } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..5fd5eebca4 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -622,4 +622,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.AdvancedSettings]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 99196e5d30..d94b44e2c2 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,5 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", + AdvancedSettings = "UIFeature.advancedSettings", } From 2bea8457e9050782da88fffed0bf3b45b943976c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 12:55:04 +0100 Subject: [PATCH 0242/1014] UI Feature Flag: Communities --- .../settings/tabs/user/PreferencesUserSettingsTab.js | 9 +++------ src/settings/Settings.ts | 8 ++++++++ src/settings/SettingsStore.ts | 5 +++++ src/settings/UIFeature.ts | 1 + src/settings/controllers/SettingController.ts | 7 +++++++ src/settings/controllers/UIFeatureController.ts | 10 +++++++--- 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index d5dafe146a..b3a5d06707 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -23,7 +23,6 @@ import Field from "../../../elements/Field"; import * as sdk from "../../../../.."; import PlatformPeg from "../../../../../PlatformPeg"; import {SettingLevel} from "../../../../../settings/SettingLevel"; -import {UIFeature} from "../../../../../settings/UIFeature"; export default class PreferencesUserSettingsTab extends React.Component { static ROOM_LIST_SETTINGS = [ @@ -138,12 +137,10 @@ export default class PreferencesUserSettingsTab extends React.Component { }; _renderGroup(settingIds) { - if (!SettingsStore.getValue(UIFeature.URLPreviews)) { - settingIds = settingIds.filter(i => i !== 'urlPreviewsEnabled'); - } - const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag"); - return settingIds.map(i => ); + return settingIds.filter(SettingsStore.isEnabled).map(i => { + return ; + }); } render() { diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index c57394d970..5c4dc2e4d0 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -337,6 +337,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td('Enable Community Filter Panel'), default: true, invertedSettingName: 'TagPanel.disableTagPanel', + // We force the value to true because the invertedSettingName causes it to flip + controller: new UIFeatureController(UIFeature.Communities, true), }, "theme": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, @@ -621,5 +623,11 @@ export const SETTINGS: {[setting: string]: ISetting} = { [UIFeature.Flair]: { supportedLevels: LEVELS_UI_FEATURE, default: true, + // Disable Flair when Communities are disabled + controller: new UIFeatureController(UIFeature.Communities), + }, + [UIFeature.Communities]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, }, }; diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 9e146ad799..498a2d269d 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -257,6 +257,11 @@ export default class SettingsStore { return SETTINGS[settingName].isFeature; } + public static isEnabled(settingName: string) { + if (!SETTINGS[settingName]) return false; + return SETTINGS[settingName].controller ? !SETTINGS[settingName].controller.settingDisabled : true; + } + /** * Gets the value of a setting. The room ID is optional if the setting is not to * be applied to any particular room, otherwise it should be supplied. diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 8cbf7c207b..3bd7e0f25b 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,5 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Flair = "UIFeature.flair", + Communities = "UIFeature.communities", } diff --git a/src/settings/controllers/SettingController.ts b/src/settings/controllers/SettingController.ts index d90eba1e9e..ba78597da7 100644 --- a/src/settings/controllers/SettingController.ts +++ b/src/settings/controllers/SettingController.ts @@ -55,4 +55,11 @@ export default abstract class SettingController { public onChange(level: SettingLevel, roomId: string, newValue: any) { // do nothing by default } + + /** + * Gets whether the setting has been disabled due to this controller. + */ + public get settingDisabled() { + return false; + } } diff --git a/src/settings/controllers/UIFeatureController.ts b/src/settings/controllers/UIFeatureController.ts index ed6598a6e8..2748eec16a 100644 --- a/src/settings/controllers/UIFeatureController.ts +++ b/src/settings/controllers/UIFeatureController.ts @@ -26,7 +26,7 @@ import SettingsStore from "../SettingsStore"; * Settings using this controller are assumed to return `false` when disabled. */ export default class UIFeatureController extends SettingController { - public constructor(private uiFeatureName: string) { + public constructor(private uiFeatureName: string, private forcedValue = false) { super(); } @@ -36,10 +36,14 @@ export default class UIFeatureController extends SettingController { calculatedValue: any, calculatedAtLevel: SettingLevel, ): any { - if (!SettingsStore.getValue(this.uiFeatureName)) { + if (this.settingDisabled) { // per the docs: we force a disabled state when the feature isn't active - return false; + return this.forcedValue; } return null; // no override } + + public get settingDisabled(): boolean { + return !SettingsStore.getValue(this.uiFeatureName); + } } From d1070c05ddd3f7bf3f7c2171f4c5c831466a200b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 13:40:27 +0100 Subject: [PATCH 0243/1014] UI Feature Flag: Disable VoIP --- src/components/structures/MatrixChat.tsx | 3 +++ .../views/dialogs/UserSettingsDialog.js | 17 +++++++++++------ src/settings/Settings.ts | 5 +++++ src/settings/UIFeature.ts | 1 + 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index dde5dc6fb2..48dc8a79d1 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -79,6 +79,7 @@ import { SettingLevel } from "../../settings/SettingLevel"; import { leaveRoomBehaviour } from "../../utils/membership"; import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog"; import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore"; +import {UIFeature} from "../../settings/UIFeature"; /** constants for MatrixChat.state.view */ export enum Views { @@ -1373,6 +1374,8 @@ export default class MatrixChat extends React.PureComponent { }); }); cli.on('Call.incoming', function(call) { + // Check if the VoIP UI has been disabled + if (!SettingsStore.getValue(UIFeature.Voip)) return; // we dispatch this synchronously to make sure that the event // handlers on the call are set up immediately (so that if // we get an immediate hangup, we don't get a stuck call) diff --git a/src/components/views/dialogs/UserSettingsDialog.js b/src/components/views/dialogs/UserSettingsDialog.js index ffde03fe31..f74f57b970 100644 --- a/src/components/views/dialogs/UserSettingsDialog.js +++ b/src/components/views/dialogs/UserSettingsDialog.js @@ -32,6 +32,7 @@ import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab"; import * as sdk from "../../../index"; import SdkConfig from "../../../SdkConfig"; import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab"; +import {UIFeature} from "../../../settings/UIFeature"; export const USER_GENERAL_TAB = "USER_GENERAL_TAB"; export const USER_APPEARANCE_TAB = "USER_APPEARANCE_TAB"; @@ -104,12 +105,16 @@ export default class UserSettingsDialog extends React.Component { "mx_UserSettingsDialog_preferencesIcon", , )); - tabs.push(new Tab( - USER_VOICE_TAB, - _td("Voice & Video"), - "mx_UserSettingsDialog_voiceIcon", - , - )); + + if (SettingsStore.getValue(UIFeature.Voip)) { + tabs.push(new Tab( + USER_VOICE_TAB, + _td("Voice & Video"), + "mx_UserSettingsDialog_voiceIcon", + , + )); + } + tabs.push(new Tab( USER_SECURITY_TAB, _td("Security & Privacy"), diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..a7250982bf 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -588,6 +588,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { "showCallButtonsInComposer": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, default: true, + controller: new UIFeatureController(UIFeature.Voip), }, "e2ee.manuallyVerifyAllSessions": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, @@ -622,4 +623,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.Voip]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 99196e5d30..ce174ec4b5 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,5 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", + Voip = "UIFeature.voip", } From dfabe79335b9024a9efd68d6a68bec0589b2c15c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 13:44:24 +0100 Subject: [PATCH 0244/1014] tidy up event handler --- src/components/structures/MatrixChat.tsx | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 48dc8a79d1..7a207dd9a5 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1373,17 +1373,19 @@ export default class MatrixChat extends React.PureComponent { ready: true, }); }); - cli.on('Call.incoming', function(call) { - // Check if the VoIP UI has been disabled - if (!SettingsStore.getValue(UIFeature.Voip)) return; - // we dispatch this synchronously to make sure that the event - // handlers on the call are set up immediately (so that if - // we get an immediate hangup, we don't get a stuck call) - dis.dispatch({ - action: 'incoming_call', - call: call, - }, true); - }); + + if (SettingsStore.getValue(UIFeature.Voip)) { + cli.on('Call.incoming', function(call) { + // we dispatch this synchronously to make sure that the event + // handlers on the call are set up immediately (so that if + // we get an immediate hangup, we don't get a stuck call) + dis.dispatch({ + action: 'incoming_call', + call: call, + }, true); + }); + } + cli.on('Session.logged_out', function(errObj) { if (Lifecycle.isLoggingOut()) return; From d3c84e25f5287f7793f9faa723bf88520d6a861a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 14:45:34 +0100 Subject: [PATCH 0245/1014] UI Feature Flag: Identity server --- src/components/views/dialogs/InviteDialog.js | 85 ++++++++++++++----- .../tabs/user/GeneralUserSettingsTab.js | 11 ++- src/settings/Settings.ts | 4 + src/settings/UIFeature.ts | 1 + 4 files changed, 76 insertions(+), 25 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 80d8f1fc2c..3347a1381f 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -38,6 +38,8 @@ import {Action} from "../../../dispatcher/actions"; import {DefaultTagID} from "../../../stores/room-list/models"; import RoomListStore from "../../../stores/room-list/RoomListStore"; import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore"; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; // we have a number of types defined from the Matrix spec which can't reasonably be altered here. /* eslint-disable camelcase */ @@ -549,7 +551,7 @@ export default class InviteDialog extends React.PureComponent { if (this.state.filterText.startsWith('@')) { // Assume mxid newMember = new DirectoryMember({user_id: this.state.filterText, display_name: null, avatar_url: null}); - } else { + } else if (SettingsStore.getValue(UIFeature.IdentityServer)) { // Assume email newMember = new ThreepidMember(this.state.filterText); } @@ -734,7 +736,7 @@ export default class InviteDialog extends React.PureComponent { this.setState({tryingIdentityServer: true}); return; } - if (term.indexOf('@') > 0 && Email.looksValid(term)) { + if (term.indexOf('@') > 0 && Email.looksValid(term) && SettingsStore.getValue(UIFeature.IdentityServer)) { // Start off by suggesting the plain email while we try and resolve it // to a real account. this.setState({ @@ -1037,7 +1039,9 @@ export default class InviteDialog extends React.PureComponent { } _renderIdentityServerWarning() { - if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer) { + if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer || + !SettingsStore.getValue(UIFeature.IdentityServer) + ) { return null; } @@ -1086,22 +1090,41 @@ export default class InviteDialog extends React.PureComponent { let buttonText; let goButtonFn; + const identityServersEnabled = SettingsStore.getValue(UIFeature.IdentityServer); + const userId = MatrixClientPeg.get().getUserId(); if (this.props.kind === KIND_DM) { title = _t("Direct Messages"); - helpText = _t( + + if (identityServersEnabled) { + helpText = _t( "Start a conversation with someone using their name, username (like ) or email address.", {}, {userId: () => { - return {userId}; - }}, - ); + return ( + {userId} + ); + }}, + ); + } else { + helpText = _t( + "Start a conversation with someone using their name or username (like ).", + {}, + {userId: () => { + return ( + {userId} + ); + }}, + ); + } + if (CommunityPrototypeStore.instance.getSelectedCommunityId()) { const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName(); - helpText = _t( - "Start a conversation with someone using their name, username (like ) or email address. " + - "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click " + - "here.", + + helpText = + { helpText } {_t( + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, " + + "click here", {communityName}, { userId: () => { return ( @@ -1120,23 +1143,39 @@ export default class InviteDialog extends React.PureComponent { >{sub} ); }, - }, - ); + })} + ; } buttonText = _t("Go"); goButtonFn = this._startDm; } else { // KIND_INVITE title = _t("Invite to this room"); - helpText = _t( - "Invite someone using their name, username (like ), email address or share this room.", - {}, - { - userId: () => - {userId}, - a: (sub) => - {sub}, - }, - ); + + if (identityServersEnabled) { + helpText = _t( + "Invite someone using their name, username (like ), email address or " + + "share this room.", + {}, + { + userId: () => + {userId}, + a: (sub) => + {sub}, + }, + ); + } else { + helpText = _t( + "Invite someone using their name, username (like ) or share this room.", + {}, + { + userId: () => + {userId}, + a: (sub) => + {sub}, + }, + ); + } + buttonText = _t("Invite"); goButtonFn = this._inviteUsers; } diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 42e12077f2..40fd57d311 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -386,14 +386,21 @@ export default class GeneralUserSettingsTab extends React.Component { width="18" height="18" alt={_t("Warning")} /> : null; + let discoverySection; + if (SettingsStore.getValue(UIFeature.IdentityServer)) { + discoverySection = <> +
    {discoWarning} {_t("Discovery")}
    + {this._renderDiscoverySection()} + ; + } + return (
    {_t("General")}
    {this._renderProfileSection()} {this._renderAccountSection()} {this._renderLanguageSection()} -
    {discoWarning} {_t("Discovery")}
    - {this._renderDiscoverySection()} + { discoverySection } {this._renderIntegrationManagerSection() /* Has its own title */}
    {_t("Deactivate account")}
    {this._renderManagementSection()} diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..a18d0f2187 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -622,4 +622,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.IdentityServer]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 99196e5d30..4de1d954d1 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,5 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", + IdentityServer = "UIFeature.identityServer", } From 1c44f15d2d4457127852fc82ae8c2c8dc28156db Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 14:54:30 +0100 Subject: [PATCH 0246/1014] i18n --- src/components/views/dialogs/InviteDialog.js | 6 +++--- src/i18n/strings/en_EN.json | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 3347a1381f..86411c43da 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -1098,9 +1098,9 @@ export default class InviteDialog extends React.PureComponent { if (identityServersEnabled) { helpText = _t( - "Start a conversation with someone using their name, username (like ) or email address.", - {}, - {userId: () => { + "Start a conversation with someone using their name, username (like ) or email address.", + {}, + {userId: () => { return ( {userId} ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4414077005..7c5212444c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -832,8 +832,8 @@ "Account management": "Account management", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", "Deactivate Account": "Deactivate Account", - "General": "General", "Discovery": "Discovery", + "General": "General", "Deactivate account": "Deactivate account", "Legal": "Legal", "Credits": "Credits", @@ -1732,9 +1732,11 @@ "Recently Direct Messaged": "Recently Direct Messaged", "Direct Messages": "Direct Messages", "Start a conversation with someone using their name, username (like ) or email address.": "Start a conversation with someone using their name, username (like ) or email address.", - "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.", + "Start a conversation with someone using their name or username (like ).": "Start a conversation with someone using their name or username (like ).", + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here", "Go": "Go", "Invite someone using their name, username (like ), email address or share this room.": "Invite someone using their name, username (like ), email address or share this room.", + "Invite someone using their name, username (like ) or share this room.": "Invite someone using their name, username (like ) or share this room.", "a new master key signature": "a new master key signature", "a new cross-signing key signature": "a new cross-signing key signature", "a device cross-signing signature": "a device cross-signing signature", From aa25bad68955bdab93f8e83f6cb06cf66dde6c7a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 14:57:46 +0100 Subject: [PATCH 0247/1014] tidy --- src/components/views/dialogs/InviteDialog.js | 51 ++++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 86411c43da..f66de67a1d 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -1108,9 +1108,9 @@ export default class InviteDialog extends React.PureComponent { ); } else { helpText = _t( - "Start a conversation with someone using their name or username (like ).", - {}, - {userId: () => { + "Start a conversation with someone using their name or username (like ).", + {}, + {userId: () => { return ( {userId} ); @@ -1120,30 +1120,29 @@ export default class InviteDialog extends React.PureComponent { if (CommunityPrototypeStore.instance.getSelectedCommunityId()) { const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName(); - - helpText = - { helpText } {_t( - "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, " + - "click here", + const inviteText = _t("This won't invite them to %(communityName)s. " + + "To invite someone to %(communityName)s, click here", {communityName}, { - userId: () => { - return ( - {userId} - ); - }, - a: (sub) => { - return ( - {sub} - ); - }, - })} + userId: () => { + return ( + {userId} + ); + }, + a: (sub) => { + return ( + {sub} + ); + }, + }); + helpText = + { helpText } {inviteText} ; } buttonText = _t("Go"); From 7bd5e3fa310238ed57a806564a4e8cd8cd9cca6c Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 10 Sep 2020 13:56:07 +0100 Subject: [PATCH 0248/1014] Move security-related dialogs to a common directory --- res/css/_components.scss | 10 +++++----- .../_AccessSecretStorageDialog.scss | 0 .../_CreateKeyBackupDialog.scss | 0 .../_CreateSecretStorageDialog.scss | 0 .../_KeyBackupFailedDialog.scss | 0 .../_RestoreKeyBackupDialog.scss | 0 src/SecurityManager.js | 7 +++---- .../{keybackup => security}/CreateKeyBackupDialog.js | 0 .../CreateSecretStorageDialog.js | 2 +- .../dialogs/{ => security}/ExportE2eKeysDialog.js | 6 +++--- .../IgnoreRecoveryReminderDialog.js | 0 .../dialogs/{ => security}/ImportE2eKeysDialog.js | 6 +++--- .../NewRecoveryMethodDialog.js | 2 +- .../RecoveryMethodRemovedDialog.js | 0 src/components/structures/MatrixChat.tsx | 4 ++-- src/components/structures/auth/E2eSetup.js | 2 +- src/components/views/dialogs/LogoutDialog.js | 8 ++++---- .../AccessSecretStorageDialog.js | 0 .../ConfirmDestroyCrossSigningDialog.js | 4 ++-- .../RestoreKeyBackupDialog.js | 0 .../dialogs/{ => security}/SetupEncryptionDialog.js | 12 ++++++------ src/components/views/rooms/RoomRecoveryReminder.js | 6 +++--- src/components/views/settings/ChangePassword.js | 2 +- src/components/views/settings/CrossSigningPanel.js | 2 +- src/components/views/settings/SecureBackupPanel.js | 4 ++-- .../settings/tabs/user/SecurityUserSettingsTab.js | 4 ++-- src/toasts/SetupEncryptionToast.ts | 2 +- .../views/dialogs/AccessSecretStorageDialog-test.js | 2 +- 28 files changed, 42 insertions(+), 43 deletions(-) rename res/css/views/dialogs/{secretstorage => security}/_AccessSecretStorageDialog.scss (100%) rename res/css/views/dialogs/{keybackup => security}/_CreateKeyBackupDialog.scss (100%) rename res/css/views/dialogs/{secretstorage => security}/_CreateSecretStorageDialog.scss (100%) rename res/css/views/dialogs/{keybackup => security}/_KeyBackupFailedDialog.scss (100%) rename res/css/views/dialogs/{keybackup => security}/_RestoreKeyBackupDialog.scss (100%) rename src/async-components/views/dialogs/{keybackup => security}/CreateKeyBackupDialog.js (100%) rename src/async-components/views/dialogs/{secretstorage => security}/CreateSecretStorageDialog.js (99%) rename src/async-components/views/dialogs/{ => security}/ExportE2eKeysDialog.js (97%) rename src/async-components/views/dialogs/{keybackup => security}/IgnoreRecoveryReminderDialog.js (100%) rename src/async-components/views/dialogs/{ => security}/ImportE2eKeysDialog.js (97%) rename src/async-components/views/dialogs/{keybackup => security}/NewRecoveryMethodDialog.js (97%) rename src/async-components/views/dialogs/{keybackup => security}/RecoveryMethodRemovedDialog.js (100%) rename src/components/views/dialogs/{secretstorage => security}/AccessSecretStorageDialog.js (100%) rename src/components/views/dialogs/{ => security}/ConfirmDestroyCrossSigningDialog.js (96%) rename src/components/views/dialogs/{keybackup => security}/RestoreKeyBackupDialog.js (100%) rename src/components/views/dialogs/{ => security}/SetupEncryptionDialog.js (80%) diff --git a/res/css/_components.scss b/res/css/_components.scss index 54e7436886..3263e3e28b 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -91,11 +91,11 @@ @import "./views/dialogs/_UploadConfirmDialog.scss"; @import "./views/dialogs/_UserSettingsDialog.scss"; @import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss"; -@import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss"; -@import "./views/dialogs/keybackup/_KeyBackupFailedDialog.scss"; -@import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss"; -@import "./views/dialogs/secretstorage/_AccessSecretStorageDialog.scss"; -@import "./views/dialogs/secretstorage/_CreateSecretStorageDialog.scss"; +@import "./views/dialogs/security/_AccessSecretStorageDialog.scss"; +@import "./views/dialogs/security/_CreateKeyBackupDialog.scss"; +@import "./views/dialogs/security/_CreateSecretStorageDialog.scss"; +@import "./views/dialogs/security/_KeyBackupFailedDialog.scss"; +@import "./views/dialogs/security/_RestoreKeyBackupDialog.scss"; @import "./views/directory/_NetworkDropdown.scss"; @import "./views/elements/_AccessibleButton.scss"; @import "./views/elements/_AddressSelector.scss"; diff --git a/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss b/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss similarity index 100% rename from res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss rename to res/css/views/dialogs/security/_AccessSecretStorageDialog.scss diff --git a/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss b/res/css/views/dialogs/security/_CreateKeyBackupDialog.scss similarity index 100% rename from res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss rename to res/css/views/dialogs/security/_CreateKeyBackupDialog.scss diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/security/_CreateSecretStorageDialog.scss similarity index 100% rename from res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss rename to res/css/views/dialogs/security/_CreateSecretStorageDialog.scss diff --git a/res/css/views/dialogs/keybackup/_KeyBackupFailedDialog.scss b/res/css/views/dialogs/security/_KeyBackupFailedDialog.scss similarity index 100% rename from res/css/views/dialogs/keybackup/_KeyBackupFailedDialog.scss rename to res/css/views/dialogs/security/_KeyBackupFailedDialog.scss diff --git a/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss b/res/css/views/dialogs/security/_RestoreKeyBackupDialog.scss similarity index 100% rename from res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss rename to res/css/views/dialogs/security/_RestoreKeyBackupDialog.scss diff --git a/src/SecurityManager.js b/src/SecurityManager.js index cc7db3ead7..f6b9c993d0 100644 --- a/src/SecurityManager.js +++ b/src/SecurityManager.js @@ -22,6 +22,8 @@ import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey'; import { _t } from './languageHandler'; import {encodeBase64} from "matrix-js-sdk/src/crypto/olmlib"; import { isSecureBackupRequired } from './utils/WellKnownUtils'; +import AccessSecretStorageDialog from './components/views/dialogs/security/AccessSecretStorageDialog'; +import RestoreKeyBackupDialog from './components/views/dialogs/security/RestoreKeyBackupDialog'; // This stores the secret storage private keys in memory for the JS SDK. This is // only meant to act as a cache to avoid prompting the user multiple times @@ -87,8 +89,6 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { return decodeRecoveryKey(recoveryKey); } }; - const AccessSecretStorageDialog = - sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog"); const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "", AccessSecretStorageDialog, /* props= */ @@ -181,7 +181,6 @@ export const crossSigningCallbacks = { export async function promptForBackupPassphrase() { let key; - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, { showSummary: false, keyCallback: k => key = k, }, null, /* priority = */ false, /* static = */ true); @@ -221,7 +220,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f // This dialog calls bootstrap itself after guiding the user through // passphrase creation. const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '', - import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"), + import("./async-components/views/dialogs/security/CreateSecretStorageDialog"), { forceReset, }, diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.js similarity index 100% rename from src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js rename to src/async-components/views/dialogs/security/CreateKeyBackupDialog.js diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js similarity index 99% rename from src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js rename to src/async-components/views/dialogs/security/CreateSecretStorageDialog.js index d4b1a73c3e..3908b7cd4a 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js @@ -30,6 +30,7 @@ import StyledRadioButton from '../../../../components/views/elements/StyledRadio import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; import DialogButtons from "../../../../components/views/elements/DialogButtons"; import InlineSpinner from "../../../../components/views/elements/InlineSpinner"; +import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog"; import { isSecureBackupRequired } from '../../../../utils/WellKnownUtils'; const PHASE_LOADING = 0; @@ -341,7 +342,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { // so let's stash it here, rather than prompting for it twice. const keyCallback = k => this._backupKey = k; - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); const { finished } = Modal.createTrackedDialog( 'Restore Backup', '', RestoreKeyBackupDialog, { diff --git a/src/async-components/views/dialogs/ExportE2eKeysDialog.js b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.js similarity index 97% rename from src/async-components/views/dialogs/ExportE2eKeysDialog.js rename to src/async-components/views/dialogs/security/ExportE2eKeysDialog.js index 406ffd8749..4dd296a8f1 100644 --- a/src/async-components/views/dialogs/ExportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.js @@ -17,11 +17,11 @@ limitations under the License. import FileSaver from 'file-saver'; import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import { _t } from '../../../languageHandler'; +import { _t } from '../../../../languageHandler'; import { MatrixClient } from 'matrix-js-sdk'; -import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption'; -import * as sdk from '../../../index'; +import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption'; +import * as sdk from '../../../../index'; const PHASE_EDIT = 1; const PHASE_EXPORTING = 2; diff --git a/src/async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog.js b/src/async-components/views/dialogs/security/IgnoreRecoveryReminderDialog.js similarity index 100% rename from src/async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog.js rename to src/async-components/views/dialogs/security/IgnoreRecoveryReminderDialog.js diff --git a/src/async-components/views/dialogs/ImportE2eKeysDialog.js b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.js similarity index 97% rename from src/async-components/views/dialogs/ImportE2eKeysDialog.js rename to src/async-components/views/dialogs/security/ImportE2eKeysDialog.js index c2d17f681d..e7bae3578b 100644 --- a/src/async-components/views/dialogs/ImportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.js @@ -18,9 +18,9 @@ import React, {createRef} from 'react'; import PropTypes from 'prop-types'; import { MatrixClient } from 'matrix-js-sdk'; -import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption'; -import * as sdk from '../../../index'; -import { _t } from '../../../languageHandler'; +import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption'; +import * as sdk from '../../../../index'; +import { _t } from '../../../../languageHandler'; function readFileAsArrayBuffer(file) { return new Promise((resolve, reject) => { diff --git a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js b/src/async-components/views/dialogs/security/NewRecoveryMethodDialog.js similarity index 97% rename from src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js rename to src/async-components/views/dialogs/security/NewRecoveryMethodDialog.js index 74552a5c08..9f5045635d 100644 --- a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js +++ b/src/async-components/views/dialogs/security/NewRecoveryMethodDialog.js @@ -22,6 +22,7 @@ import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import dis from "../../../../dispatcher/dispatcher"; import { _t } from "../../../../languageHandler"; import Modal from "../../../../Modal"; +import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog"; import {Action} from "../../../../dispatcher/actions"; export default class NewRecoveryMethodDialog extends React.PureComponent { @@ -41,7 +42,6 @@ export default class NewRecoveryMethodDialog extends React.PureComponent { } onSetupClick = async () => { - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); Modal.createTrackedDialog( 'Restore Backup', '', RestoreKeyBackupDialog, { onFinished: this.props.onFinished, diff --git a/src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js b/src/async-components/views/dialogs/security/RecoveryMethodRemovedDialog.js similarity index 100% rename from src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js rename to src/async-components/views/dialogs/security/RecoveryMethodRemovedDialog.js diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index dde5dc6fb2..95f60be86e 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1496,12 +1496,12 @@ export default class MatrixChat extends React.PureComponent { if (haveNewVersion) { Modal.createTrackedDialogAsync('New Recovery Method', 'New Recovery Method', - import('../../async-components/views/dialogs/keybackup/NewRecoveryMethodDialog'), + import('../../async-components/views/dialogs/security/NewRecoveryMethodDialog'), { newVersionInfo }, ); } else { Modal.createTrackedDialogAsync('Recovery Method Removed', 'Recovery Method Removed', - import('../../async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog'), + import('../../async-components/views/dialogs/security/RecoveryMethodRemovedDialog'), ); } }); diff --git a/src/components/structures/auth/E2eSetup.js b/src/components/structures/auth/E2eSetup.js index 9b390d24cc..91382d594d 100644 --- a/src/components/structures/auth/E2eSetup.js +++ b/src/components/structures/auth/E2eSetup.js @@ -29,7 +29,7 @@ export default class E2eSetup extends React.Component { super(); // awkwardly indented because https://github.com/eslint/eslint/issues/11310 this._createStorageDialogPromise = - import("../../../async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"); + import("../../../async-components/views/dialogs/security/CreateSecretStorageDialog"); } render() { diff --git a/src/components/views/dialogs/LogoutDialog.js b/src/components/views/dialogs/LogoutDialog.js index 930acaa0b8..af36dba2b6 100644 --- a/src/components/views/dialogs/LogoutDialog.js +++ b/src/components/views/dialogs/LogoutDialog.js @@ -20,7 +20,8 @@ import Modal from '../../../Modal'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; import { _t } from '../../../languageHandler'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; +import RestoreKeyBackupDialog from './security/RestoreKeyBackupDialog'; export default class LogoutDialog extends React.Component { defaultProps = { @@ -73,7 +74,7 @@ export default class LogoutDialog extends React.Component { _onExportE2eKeysClicked() { Modal.createTrackedDialogAsync('Export E2E Keys', '', - import('../../../async-components/views/dialogs/ExportE2eKeysDialog'), + import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), { matrixClient: MatrixClientPeg.get(), }, @@ -93,14 +94,13 @@ export default class LogoutDialog extends React.Component { // A key backup exists for this account, but the creating device is not // verified, so restore the backup which will give us the keys from it and // allow us to trust it (ie. upload keys to it) - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); Modal.createTrackedDialog( 'Restore Backup', '', RestoreKeyBackupDialog, null, null, /* priority = */ false, /* static = */ true, ); } else { Modal.createTrackedDialogAsync("Key Backup", "Key Backup", - import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"), + import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog"), null, null, /* priority = */ false, /* static = */ true, ); } diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/security/AccessSecretStorageDialog.js similarity index 100% rename from src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js rename to src/components/views/dialogs/security/AccessSecretStorageDialog.js diff --git a/src/components/views/dialogs/ConfirmDestroyCrossSigningDialog.js b/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.js similarity index 96% rename from src/components/views/dialogs/ConfirmDestroyCrossSigningDialog.js rename to src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.js index 9e1980e98d..abc1586205 100644 --- a/src/components/views/dialogs/ConfirmDestroyCrossSigningDialog.js +++ b/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.js @@ -16,8 +16,8 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {_t} from "../../../languageHandler"; -import * as sdk from "../../../index"; +import {_t} from "../../../../languageHandler"; +import * as sdk from "../../../../index"; export default class ConfirmDestroyCrossSigningDialog extends React.Component { static propTypes = { diff --git a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js b/src/components/views/dialogs/security/RestoreKeyBackupDialog.js similarity index 100% rename from src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js rename to src/components/views/dialogs/security/RestoreKeyBackupDialog.js diff --git a/src/components/views/dialogs/SetupEncryptionDialog.js b/src/components/views/dialogs/security/SetupEncryptionDialog.js similarity index 80% rename from src/components/views/dialogs/SetupEncryptionDialog.js rename to src/components/views/dialogs/security/SetupEncryptionDialog.js index d7723de588..9ce3144534 100644 --- a/src/components/views/dialogs/SetupEncryptionDialog.js +++ b/src/components/views/dialogs/security/SetupEncryptionDialog.js @@ -16,16 +16,16 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import SetupEncryptionBody from '../../structures/auth/SetupEncryptionBody'; -import BaseDialog from './BaseDialog'; -import { _t } from '../../../languageHandler'; -import { SetupEncryptionStore, PHASE_DONE } from '../../../stores/SetupEncryptionStore'; +import SetupEncryptionBody from '../../../structures/auth/SetupEncryptionBody'; +import BaseDialog from '../BaseDialog'; +import { _t } from '../../../../languageHandler'; +import { SetupEncryptionStore, PHASE_DONE } from '../../../../stores/SetupEncryptionStore'; function iconFromPhase(phase) { if (phase === PHASE_DONE) { - return require("../../../../res/img/e2e/verified.svg"); + return require("../../../../../res/img/e2e/verified.svg"); } else { - return require("../../../../res/img/e2e/warning.svg"); + return require("../../../../../res/img/e2e/warning.svg"); } } diff --git a/src/components/views/rooms/RoomRecoveryReminder.js b/src/components/views/rooms/RoomRecoveryReminder.js index 859df6dd1b..552de681c3 100644 --- a/src/components/views/rooms/RoomRecoveryReminder.js +++ b/src/components/views/rooms/RoomRecoveryReminder.js @@ -23,6 +23,7 @@ import Modal from "../../../Modal"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import SettingsStore from "../../../settings/SettingsStore"; import {SettingLevel} from "../../../settings/SettingLevel"; +import RestoreKeyBackupDialog from "../dialogs/security/RestoreKeyBackupDialog"; export default class RoomRecoveryReminder extends React.PureComponent { static propTypes = { @@ -70,14 +71,13 @@ export default class RoomRecoveryReminder extends React.PureComponent { // A key backup exists for this account, but the creating device is not // verified, so restore the backup which will give us the keys from it and // allow us to trust it (ie. upload keys to it) - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); Modal.createTrackedDialog( 'Restore Backup', '', RestoreKeyBackupDialog, null, null, /* priority = */ false, /* static = */ true, ); } else { Modal.createTrackedDialogAsync("Key Backup", "Key Backup", - import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"), + import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog"), null, null, /* priority = */ false, /* static = */ true, ); } @@ -91,7 +91,7 @@ export default class RoomRecoveryReminder extends React.PureComponent { // When you choose "Don't ask again" from the room reminder, we show a // dialog to confirm the choice. Modal.createTrackedDialogAsync("Ignore Recovery Reminder", "Ignore Recovery Reminder", - import("../../../async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog"), + import("../../../async-components/views/dialogs/security/IgnoreRecoveryReminderDialog"), { onDontAskAgain: async () => { await SettingsStore.setValue( diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index 725f04dede..0b62f1fa81 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -184,7 +184,7 @@ export default class ChangePassword extends React.Component { _onExportE2eKeysClicked = () => { Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password', - import('../../../async-components/views/dialogs/ExportE2eKeysDialog'), + import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), { matrixClient: MatrixClientPeg.get(), }, diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index a0ca84645f..fd8fef0544 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -22,6 +22,7 @@ import * as sdk from '../../../index'; import Modal from '../../../Modal'; import Spinner from '../elements/Spinner'; import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog'; +import ConfirmDestroyCrossSigningDialog from '../dialogs/security/ConfirmDestroyCrossSigningDialog'; export default class CrossSigningPanel extends React.PureComponent { constructor(props) { @@ -137,7 +138,6 @@ export default class CrossSigningPanel extends React.PureComponent { } _resetCrossSigning = () => { - const ConfirmDestroyCrossSigningDialog = sdk.getComponent("dialogs.ConfirmDestroyCrossSigningDialog"); Modal.createDialog(ConfirmDestroyCrossSigningDialog, { onFinished: (act) => { if (!act) return; diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 7f0655d54a..f94a4c9590 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -24,7 +24,7 @@ import { isSecureBackupRequired } from '../../../utils/WellKnownUtils'; import Spinner from '../elements/Spinner'; import AccessibleButton from '../elements/AccessibleButton'; import QuestionDialog from '../dialogs/QuestionDialog'; -import RestoreKeyBackupDialog from '../dialogs/keybackup/RestoreKeyBackupDialog'; +import RestoreKeyBackupDialog from '../dialogs/security/RestoreKeyBackupDialog'; import { accessSecretStorage } from '../../../SecurityManager'; export default class SecureBackupPanel extends React.PureComponent { @@ -150,7 +150,7 @@ export default class SecureBackupPanel extends React.PureComponent { _startNewBackup = () => { Modal.createTrackedDialogAsync('Key Backup', 'Key Backup', - import('../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'), + import('../../../async-components/views/dialogs/security/CreateKeyBackupDialog'), { onFinished: () => { this._loadBackupStatus(); diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index c25ac438e0..de03360f2a 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -103,14 +103,14 @@ export default class SecurityUserSettingsTab extends React.Component { _onExportE2eKeysClicked = () => { Modal.createTrackedDialogAsync('Export E2E Keys', '', - import('../../../../../async-components/views/dialogs/ExportE2eKeysDialog'), + import('../../../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), {matrixClient: MatrixClientPeg.get()}, ); }; _onImportE2eKeysClicked = () => { Modal.createTrackedDialogAsync('Import E2E Keys', '', - import('../../../../../async-components/views/dialogs/ImportE2eKeysDialog'), + import('../../../../../async-components/views/dialogs/security/ImportE2eKeysDialog'), {matrixClient: MatrixClientPeg.get()}, ); }; diff --git a/src/toasts/SetupEncryptionToast.ts b/src/toasts/SetupEncryptionToast.ts index 9dbc4acafc..5e3da94eda 100644 --- a/src/toasts/SetupEncryptionToast.ts +++ b/src/toasts/SetupEncryptionToast.ts @@ -18,7 +18,7 @@ import Modal from "../Modal"; import * as sdk from "../index"; import { _t } from "../languageHandler"; import DeviceListener from "../DeviceListener"; -import SetupEncryptionDialog from "../components/views/dialogs/SetupEncryptionDialog"; +import SetupEncryptionDialog from "../components/views/dialogs/security/SetupEncryptionDialog"; import { accessSecretStorage } from "../SecurityManager"; import ToastStore from "../stores/ToastStore"; import GenericToast from "../components/views/toasts/GenericToast"; diff --git a/test/components/views/dialogs/AccessSecretStorageDialog-test.js b/test/components/views/dialogs/AccessSecretStorageDialog-test.js index 5a8dcbf763..7c4b2996c9 100644 --- a/test/components/views/dialogs/AccessSecretStorageDialog-test.js +++ b/test/components/views/dialogs/AccessSecretStorageDialog-test.js @@ -20,7 +20,7 @@ import sdk from '../../../skinned-sdk'; import {MatrixClientPeg} from '../../../../src/MatrixClientPeg'; import { stubClient } from '../../../test-utils'; -const AccessSecretStorageDialog = sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog"); +const AccessSecretStorageDialog = sdk.getComponent("dialogs.security.AccessSecretStorageDialog"); describe("AccessSecretStorageDialog", function() { it("Closes the dialog if _onRecoveryKeyNext is called with a valid key", (done) => { From 3259ab1f250ee0d027a4a5d020f360848a6562db Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 11 Sep 2020 14:09:54 +0100 Subject: [PATCH 0249/1014] Place cross-signing action buttons on a single row Part of https://github.com/vector-im/element-web/issues/13895 --- .../views/settings/_CrossSigningPanel.scss | 4 ++ .../views/settings/CrossSigningPanel.js | 40 ++++++++++--------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/res/css/views/settings/_CrossSigningPanel.scss b/res/css/views/settings/_CrossSigningPanel.scss index fa9f76a963..12a0e36835 100644 --- a/res/css/views/settings/_CrossSigningPanel.scss +++ b/res/css/views/settings/_CrossSigningPanel.scss @@ -28,4 +28,8 @@ limitations under the License. .mx_CrossSigningPanel_buttonRow { margin: 1em 0; + + :nth-child(n + 1) { + margin-inline-end: 10px; + } } diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index fd8fef0544..5b5ef56024 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -195,29 +195,32 @@ export default class CrossSigningPanel extends React.PureComponent { crossSigningPublicKeysOnDevice ); - let resetButton; - if (keysExistAnywhere) { - resetButton = ( -
    - - {_t("Reset")} - -
    + const actions = []; + + // TODO: determine how better to expose this to users in addition to prompts at login/toast + if (!keysExistEverywhere && homeserverSupportsCrossSigning) { + actions.push( + + {_t("Set up")} + , ); } - // TODO: determine how better to expose this to users in addition to prompts at login/toast - let bootstrapButton; - if (!keysExistEverywhere && homeserverSupportsCrossSigning) { - bootstrapButton = ( -
    - - {_t("Set up")} - -
    + if (keysExistAnywhere) { + actions.push( + + {_t("Reset")} + , ); } + let actionRow; + if (actions.length) { + actionRow =
    + {actions} +
    ; + } + return (
    {summarisedStatus} @@ -251,8 +254,7 @@ export default class CrossSigningPanel extends React.PureComponent { {errorSection} - {bootstrapButton} - {resetButton} + {actionRow}
    ); } From 685878a10189043ca09b39daab04ce40a22b7c53 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 11 Sep 2020 14:20:08 +0100 Subject: [PATCH 0250/1014] Clarify diagnostic about keys in storage Part of https://github.com/vector-im/element-web/issues/13895 --- src/components/views/settings/CrossSigningPanel.js | 2 +- src/i18n/strings/en_EN.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 5b5ef56024..fd5966ca0a 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -233,7 +233,7 @@ export default class CrossSigningPanel extends React.PureComponent { {_t("Cross-signing private keys:")} - {crossSigningPrivateKeysInStorage ? _t("in secret storage") : _t("not found")} + {crossSigningPrivateKeysInStorage ? _t("in secret storage") : _t("not found in storage")} {_t("Master private key:")} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4414077005..76ca0bf738 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -658,6 +658,7 @@ "not found": "not found", "Cross-signing private keys:": "Cross-signing private keys:", "in secret storage": "in secret storage", + "not found in storage": "not found in storage", "Master private key:": "Master private key:", "cached locally": "cached locally", "not found locally": "not found locally", From 7be27e70c964329769d9ceab8185824d5ea975da Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 15 Sep 2020 12:45:08 +0100 Subject: [PATCH 0251/1014] Add component key to actions array --- src/components/views/settings/CrossSigningPanel.js | 4 ++-- src/components/views/settings/SecureBackupPanel.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index fd5966ca0a..669c2e84d9 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -200,7 +200,7 @@ export default class CrossSigningPanel extends React.PureComponent { // TODO: determine how better to expose this to users in addition to prompts at login/toast if (!keysExistEverywhere && homeserverSupportsCrossSigning) { actions.push( - + {_t("Set up")} , ); @@ -208,7 +208,7 @@ export default class CrossSigningPanel extends React.PureComponent { if (keysExistAnywhere) { actions.push( - + {_t("Reset")} , ); diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index f94a4c9590..7e9fb6cd3d 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -367,14 +367,14 @@ export default class SecureBackupPanel extends React.PureComponent { ; actions.push( - + {restoreButtonCaption} , ); if (!isSecureBackupRequired()) { actions.push( - + {_t("Delete Backup")} , ); @@ -388,7 +388,7 @@ export default class SecureBackupPanel extends React.PureComponent {

    {_t("Back up your keys before signing out to avoid losing them.")}

    ; actions.push( - + {_t("Set up")} , ); @@ -396,7 +396,7 @@ export default class SecureBackupPanel extends React.PureComponent { if (secretStorageKeyInAccount) { actions.push( - + {_t("Reset")} , ); From 46f37fb969798069e4e7d1040494275e9e7b6af9 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 15 Sep 2020 15:25:50 +0100 Subject: [PATCH 0252/1014] Create cross-signing keys during authentication With this change, Element now creates cross-signing keys during auth flows for password login. For other auth flows like token / SSO, it will not happen until a cross-signing / secret storage dialog flow as before. --- .../security/_CreateCrossSigningDialog.scss | 33 ++++ .../security/CreateSecretStorageDialog.js | 16 +- src/components/structures/MatrixChat.tsx | 7 + src/components/structures/auth/E2eSetup.js | 17 +- .../security/CreateCrossSigningDialog.js | 187 ++++++++++++++++++ src/i18n/strings/en_EN.json | 101 +++++----- test/end-to-end-tests/src/usecases/signup.js | 15 -- 7 files changed, 290 insertions(+), 86 deletions(-) create mode 100644 res/css/views/dialogs/security/_CreateCrossSigningDialog.scss create mode 100644 src/components/views/dialogs/security/CreateCrossSigningDialog.js diff --git a/res/css/views/dialogs/security/_CreateCrossSigningDialog.scss b/res/css/views/dialogs/security/_CreateCrossSigningDialog.scss new file mode 100644 index 0000000000..8303e02b9e --- /dev/null +++ b/res/css/views/dialogs/security/_CreateCrossSigningDialog.scss @@ -0,0 +1,33 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_CreateCrossSigningDialog { + // Why you ask? Because CompleteSecurityBody is 600px so this is the width + // we end up when in there, but when in our own dialog we set our own width + // so need to fix it to something sensible as otherwise we'd end up either + // really wide or really narrow depending on the phase. I bet you wish you + // never asked. + width: 560px; + + details .mx_AccessibleButton { + margin: 1em 0; // emulate paragraph spacing because we can't put this button in a paragraph due to HTML rules + } +} + +.mx_CreateCrossSigningDialog .mx_Dialog_title { + /* TODO: Consider setting this for all dialog titles. */ + margin-bottom: 1em; +} diff --git a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js index 3908b7cd4a..f3b52da141 100644 --- a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js @@ -281,21 +281,21 @@ export default class CreateSecretStorageDialog extends React.PureComponent { const { forceReset } = this.props; try { - // JRS: In an upcoming change, the cross-signing steps will be - // removed from here and this will instead be about secret storage - // only. if (forceReset) { - console.log("Forcing cross-signing and secret storage reset"); + console.log("Forcing secret storage reset"); await cli.bootstrapSecretStorage({ createSecretStorageKey: async () => this._recoveryKey, setupNewKeyBackup: true, setupNewSecretStorage: true, }); - await cli.bootstrapCrossSigning({ - authUploadDeviceSigningKeys: this._doBootstrapUIAuth, - setupNewCrossSigning: true, - }); } else { + // For password authentication users after 2020-09, this cross-signing + // step will be a no-op since it is now setup during registration or login + // when needed. We should keep this here to cover other cases such as: + // * Users with existing sessions prior to 2020-09 changes + // * SSO authentication users which require interactive auth to upload + // keys (and also happen to skip all post-authentication flows at the + // moment via token login) await cli.bootstrapCrossSigning({ authUploadDeviceSigningKeys: this._doBootstrapUIAuth, }); diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 95f60be86e..c4d4a82a82 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1876,6 +1876,13 @@ export default class MatrixChat extends React.PureComponent { return this.props.makeRegistrationUrl(params); }; + /** + * After registration or login, we run various post-auth steps before entering the app + * proper, such setting up cross-signing or verifying the new session. + * + * Note: SSO users (and any others using token login) currently do not pass through + * this, as they instead jump straight into the app after `attemptTokenLogin`. + */ onUserCompletedLoginFlow = async (credentials: object, password: string) => { this.accountPassword = password; // self-destruct the password after 5mins diff --git a/src/components/structures/auth/E2eSetup.js b/src/components/structures/auth/E2eSetup.js index 91382d594d..6df8158002 100644 --- a/src/components/structures/auth/E2eSetup.js +++ b/src/components/structures/auth/E2eSetup.js @@ -16,8 +16,9 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import AsyncWrapper from '../../../AsyncWrapper'; -import * as sdk from '../../../index'; +import AuthPage from '../../views/auth/AuthPage'; +import CompleteSecurityBody from '../../views/auth/CompleteSecurityBody'; +import CreateCrossSigningDialog from '../../views/dialogs/security/CreateCrossSigningDialog'; export default class E2eSetup extends React.Component { static propTypes = { @@ -25,21 +26,11 @@ export default class E2eSetup extends React.Component { accountPassword: PropTypes.string, }; - constructor() { - super(); - // awkwardly indented because https://github.com/eslint/eslint/issues/11310 - this._createStorageDialogPromise = - import("../../../async-components/views/dialogs/security/CreateSecretStorageDialog"); - } - render() { - const AuthPage = sdk.getComponent("auth.AuthPage"); - const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody"); return ( - diff --git a/src/components/views/dialogs/security/CreateCrossSigningDialog.js b/src/components/views/dialogs/security/CreateCrossSigningDialog.js new file mode 100644 index 0000000000..226419e759 --- /dev/null +++ b/src/components/views/dialogs/security/CreateCrossSigningDialog.js @@ -0,0 +1,187 @@ +/* +Copyright 2018, 2019 New Vector Ltd +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { MatrixClientPeg } from '../../../../MatrixClientPeg'; +import { _t } from '../../../../languageHandler'; +import Modal from '../../../../Modal'; +import { SSOAuthEntry } from '../../auth/InteractiveAuthEntryComponents'; +import DialogButtons from '../../elements/DialogButtons'; +import BaseDialog from '../BaseDialog'; +import Spinner from '../../elements/Spinner'; +import InteractiveAuthDialog from '../InteractiveAuthDialog'; + +/* + * Walks the user through the process of creating a cross-signing keys. In most + * cases, only a spinner is shown, but for more complex auth like SSO, the user + * may need to complete some steps to proceed. + */ +export default class CreateCrossSigningDialog extends React.PureComponent { + static propTypes = { + accountPassword: PropTypes.string, + }; + + constructor(props) { + super(props); + + this.state = { + error: null, + // Does the server offer a UI auth flow with just m.login.password + // for /keys/device_signing/upload? + canUploadKeysWithPasswordOnly: null, + accountPassword: props.accountPassword || "", + }; + + if (this.state.accountPassword) { + // If we have an account password in memory, let's simplify and + // assume it means password auth is also supported for device + // signing key upload as well. This avoids hitting the server to + // test auth flows, which may be slow under high load. + this.state.canUploadKeysWithPasswordOnly = true; + } else { + this._queryKeyUploadAuth(); + } + } + + componentDidMount() { + this._bootstrapCrossSigning(); + } + + async _queryKeyUploadAuth() { + try { + await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {}); + // We should never get here: the server should always require + // UI auth to upload device signing keys. If we do, we upload + // no keys which would be a no-op. + console.log("uploadDeviceSigningKeys unexpectedly succeeded without UI auth!"); + } catch (error) { + if (!error.data || !error.data.flows) { + console.log("uploadDeviceSigningKeys advertised no flows!"); + return; + } + const canUploadKeysWithPasswordOnly = error.data.flows.some(f => { + return f.stages.length === 1 && f.stages[0] === 'm.login.password'; + }); + this.setState({ + canUploadKeysWithPasswordOnly, + }); + } + } + + _doBootstrapUIAuth = async (makeRequest) => { + if (this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) { + await makeRequest({ + type: 'm.login.password', + identifier: { + type: 'm.id.user', + user: MatrixClientPeg.get().getUserId(), + }, + // TODO: Remove `user` once servers support proper UIA + // See https://github.com/matrix-org/synapse/issues/5665 + user: MatrixClientPeg.get().getUserId(), + password: this.state.accountPassword, + }); + } else { + const dialogAesthetics = { + [SSOAuthEntry.PHASE_PREAUTH]: { + title: _t("Use Single Sign On to continue"), + body: _t("To continue, use Single Sign On to prove your identity."), + continueText: _t("Single Sign On"), + continueKind: "primary", + }, + [SSOAuthEntry.PHASE_POSTAUTH]: { + title: _t("Confirm encryption setup"), + body: _t("Click the button below to confirm setting up encryption."), + continueText: _t("Confirm"), + continueKind: "primary", + }, + }; + + const { finished } = Modal.createTrackedDialog( + 'Cross-signing keys dialog', '', InteractiveAuthDialog, + { + title: _t("Setting up keys"), + matrixClient: MatrixClientPeg.get(), + makeRequest, + aestheticsForStagePhases: { + [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, + [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, + }, + }, + ); + const [confirmed] = await finished; + if (!confirmed) { + throw new Error("Cross-signing key upload auth canceled"); + } + } + } + + _bootstrapCrossSigning = async () => { + this.setState({ + error: null, + }); + + const cli = MatrixClientPeg.get(); + + try { + await cli.bootstrapCrossSigning({ + authUploadDeviceSigningKeys: this._doBootstrapUIAuth, + }); + this.props.onFinished(true); + } catch (e) { + this.setState({ error: e }); + console.error("Error bootstrapping cross-signing", e); + } + } + + _onCancel = () => { + this.props.onFinished(false); + } + + render() { + let content; + if (this.state.error) { + content =
    +

    {_t("Unable to set up keys")}

    +
    + +
    +
    ; + } else { + content =
    + +
    ; + } + + return ( + +
    + {content} +
    +
    + ); + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 76ca0bf738..ea558fbd93 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1631,9 +1631,6 @@ "Invite people to join %(communityName)s": "Invite people to join %(communityName)s", "You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)", "Removing…": "Removing…", - "Destroy cross-signing keys?": "Destroy cross-signing keys?", - "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.": "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.", - "Clear cross-signing keys": "Clear cross-signing keys", "Confirm Removal": "Confirm Removal", "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.", "Clear all data in this session?": "Clear all data in this session?", @@ -1886,6 +1883,13 @@ "Enter your Security Phrase or to continue.": "Enter your Security Phrase or to continue.", "Security Key": "Security Key", "Use your Security Key to continue.": "Use your Security Key to continue.", + "Destroy cross-signing keys?": "Destroy cross-signing keys?", + "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.": "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.", + "Clear cross-signing keys": "Clear cross-signing keys", + "Confirm encryption setup": "Confirm encryption setup", + "Click the button below to confirm setting up encryption.": "Click the button below to confirm setting up encryption.", + "Unable to set up keys": "Unable to set up keys", + "Retry": "Retry", "Restoring keys from backup": "Restoring keys from backup", "Fetching keys from server...": "Fetching keys from server...", "%(completed)s of %(total)s keys restored": "%(completed)s of %(total)s keys restored", @@ -2235,22 +2239,36 @@ "Room Autocomplete": "Room Autocomplete", "Users": "Users", "User Autocomplete": "User Autocomplete", - "Passphrases must match": "Passphrases must match", - "Passphrase must not be empty": "Passphrase must not be empty", - "Unknown error": "Unknown error", - "Export room keys": "Export room keys", - "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.", - "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.", - "Enter passphrase": "Enter passphrase", - "Confirm passphrase": "Confirm passphrase", - "Export": "Export", - "Import room keys": "Import room keys", - "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.", - "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.", - "File to import": "File to import", - "Import": "Import", - "Confirm encryption setup": "Confirm encryption setup", - "Click the button below to confirm setting up encryption.": "Click the button below to confirm setting up encryption.", + "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.", + "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", + "Enter a recovery passphrase": "Enter a recovery passphrase", + "Great! This recovery passphrase looks strong enough.": "Great! This recovery passphrase looks strong enough.", + "Set up with a recovery key": "Set up with a recovery key", + "That matches!": "That matches!", + "Use a different passphrase?": "Use a different passphrase?", + "That doesn't match.": "That doesn't match.", + "Go back to set it again.": "Go back to set it again.", + "Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.", + "Repeat your recovery passphrase...": "Repeat your recovery passphrase...", + "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.", + "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Keep a copy of it somewhere secure, like a password manager or even a safe.", + "Your recovery key": "Your recovery key", + "Download": "Download", + "Your recovery key has been copied to your clipboard, paste it to:": "Your recovery key has been copied to your clipboard, paste it to:", + "Your recovery key is in your Downloads folder.": "Your recovery key is in your Downloads folder.", + "Print it and store it somewhere safe": "Print it and store it somewhere safe", + "Save it on a USB key or backup drive": "Save it on a USB key or backup drive", + "Copy it to your personal cloud storage": "Copy it to your personal cloud storage", + "Your keys are being backed up (the first backup could take a few minutes).": "Your keys are being backed up (the first backup could take a few minutes).", + "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.", + "Set up Secure Message Recovery": "Set up Secure Message Recovery", + "Secure your backup with a recovery passphrase": "Secure your backup with a recovery passphrase", + "Confirm your recovery passphrase": "Confirm your recovery passphrase", + "Make a copy of your recovery key": "Make a copy of your recovery key", + "Starting backup...": "Starting backup...", + "Success!": "Success!", + "Create key backup": "Create key backup", + "Unable to create key backup": "Unable to create key backup", "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.", "Generate a Security Key": "Generate a Security Key", "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.", @@ -2262,18 +2280,9 @@ "You'll need to authenticate with the server to confirm the upgrade.": "You'll need to authenticate with the server to confirm the upgrade.", "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.", "Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.": "Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.", - "Enter a recovery passphrase": "Enter a recovery passphrase", - "Great! This recovery passphrase looks strong enough.": "Great! This recovery passphrase looks strong enough.", - "That matches!": "That matches!", - "Use a different passphrase?": "Use a different passphrase?", - "That doesn't match.": "That doesn't match.", - "Go back to set it again.": "Go back to set it again.", "Enter your recovery passphrase a second time to confirm it.": "Enter your recovery passphrase a second time to confirm it.", - "Confirm your recovery passphrase": "Confirm your recovery passphrase", "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.": "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.", - "Download": "Download", "Unable to query secret storage status": "Unable to query secret storage status", - "Retry": "Retry", "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.", "You can also set up Secure Backup & manage your keys in Settings.": "You can also set up Secure Backup & manage your keys in Settings.", "Set up Secure Backup": "Set up Secure Backup", @@ -2282,31 +2291,23 @@ "Confirm Security Phrase": "Confirm Security Phrase", "Save your Security Key": "Save your Security Key", "Unable to set up secret storage": "Unable to set up secret storage", - "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.", - "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", - "Set up with a recovery key": "Set up with a recovery key", - "Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.", - "Repeat your recovery passphrase...": "Repeat your recovery passphrase...", - "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.", - "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Keep a copy of it somewhere secure, like a password manager or even a safe.", - "Your recovery key": "Your recovery key", - "Your recovery key has been copied to your clipboard, paste it to:": "Your recovery key has been copied to your clipboard, paste it to:", - "Your recovery key is in your Downloads folder.": "Your recovery key is in your Downloads folder.", - "Print it and store it somewhere safe": "Print it and store it somewhere safe", - "Save it on a USB key or backup drive": "Save it on a USB key or backup drive", - "Copy it to your personal cloud storage": "Copy it to your personal cloud storage", - "Your keys are being backed up (the first backup could take a few minutes).": "Your keys are being backed up (the first backup could take a few minutes).", - "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.", - "Set up Secure Message Recovery": "Set up Secure Message Recovery", - "Secure your backup with a recovery passphrase": "Secure your backup with a recovery passphrase", - "Make a copy of your recovery key": "Make a copy of your recovery key", - "Starting backup...": "Starting backup...", - "Success!": "Success!", - "Create key backup": "Create key backup", - "Unable to create key backup": "Unable to create key backup", + "Passphrases must match": "Passphrases must match", + "Passphrase must not be empty": "Passphrase must not be empty", + "Unknown error": "Unknown error", + "Export room keys": "Export room keys", + "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.", + "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.", + "Enter passphrase": "Enter passphrase", + "Confirm passphrase": "Confirm passphrase", + "Export": "Export", "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.", "If you don't want to set this up now, you can later in Settings.": "If you don't want to set this up now, you can later in Settings.", "Don't ask again": "Don't ask again", + "Import room keys": "Import room keys", + "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.", + "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.", + "File to import": "File to import", + "Import": "Import", "New Recovery Method": "New Recovery Method", "A new recovery passphrase and key for Secure Messages have been detected.": "A new recovery passphrase and key for Secure Messages have been detected.", "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.", diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index fd41ef1a71..ef8a259091 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -79,21 +79,6 @@ module.exports = async function signup(session, username, password, homeserver) const acceptButton = await session.query('.mx_InteractiveAuthEntryComponents_termsSubmit'); await acceptButton.click(); - // Continue with the default (generate a security key) - const xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); - await xsignContButton.click(); - - //ignore the recovery key - //TODO: It's probably important for the tests to know the recovery key - const copyButton = await session.query('.mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn'); - await copyButton.click(); - - //acknowledge that we copied the recovery key to a safe place - const copyContinueButton = await session.query( - '.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary', - ); - await copyContinueButton.click(); - //wait for registration to finish so the hash gets set //onhashchange better? From ada00a3535833ee7687c0ce6696d1ae7d45a028c Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 15 Sep 2020 17:37:05 +0100 Subject: [PATCH 0253/1014] Recheck security status on room encryption change This ensures we are alerted when you first interact with an encrypted room. Part of https://github.com/vector-im/element-web/issues/13895 --- src/DeviceListener.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index aa0508924d..89cf968c6b 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -33,7 +33,7 @@ import { privateShouldBeEncrypted } from "./createRoom"; import { isSecretStorageBeingAccessed, accessSecretStorage } from "./SecurityManager"; import { isSecureBackupRequired } from './utils/WellKnownUtils'; import { isLoggedIn } from './components/structures/MatrixChat'; - +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000; @@ -66,6 +66,7 @@ export default class DeviceListener { MatrixClientPeg.get().on('crossSigning.keysChanged', this._onCrossSingingKeysChanged); MatrixClientPeg.get().on('accountData', this._onAccountData); MatrixClientPeg.get().on('sync', this._onSync); + MatrixClientPeg.get().on('RoomState.events', this._onRoomStateEvents); this.dispatcherRef = dis.register(this._onAction); this._recheck(); } @@ -79,6 +80,7 @@ export default class DeviceListener { MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this._onCrossSingingKeysChanged); MatrixClientPeg.get().removeListener('accountData', this._onAccountData); MatrixClientPeg.get().removeListener('sync', this._onSync); + MatrixClientPeg.get().removeListener('RoomState.events', this._onRoomStateEvents); } if (this.dispatcherRef) { dis.unregister(this.dispatcherRef); @@ -169,6 +171,16 @@ export default class DeviceListener { if (state === 'PREPARED' && prevState === null) this._recheck(); }; + _onRoomStateEvents = (ev: MatrixEvent) => { + if (ev.getType() !== "m.room.encryption") { + return; + } + + // If a room changes to encrypted, re-check as it may be our first + // encrypted room. This also catches encrypted room creation as well. + this._recheck(); + }; + _onAction = ({ action }) => { if (action !== "on_logged_in") return; this._recheck(); From 26b465f1fd537d45ad5bfd3f0320157d73c74e1d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 15 Sep 2020 17:53:04 +0100 Subject: [PATCH 0254/1014] Remove room recovery reminder The Secure Backup toast replaces this with better UX. Part of https://github.com/vector-im/element-web/issues/13895 --- res/css/_components.scss | 2 +- .../views/rooms/_RoomRecoveryReminder.scss | 39 ---- .../security/IgnoreRecoveryReminderDialog.js | 70 -------- src/components/structures/RoomView.tsx | 17 -- .../views/rooms/RoomRecoveryReminder.js | 170 ------------------ src/i18n/strings/en_EN.json | 11 +- src/settings/Settings.ts | 5 - 7 files changed, 2 insertions(+), 312 deletions(-) delete mode 100644 res/css/views/rooms/_RoomRecoveryReminder.scss delete mode 100644 src/async-components/views/dialogs/security/IgnoreRecoveryReminderDialog.js delete mode 100644 src/components/views/rooms/RoomRecoveryReminder.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 3263e3e28b..35b4c1b965 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -92,6 +92,7 @@ @import "./views/dialogs/_UserSettingsDialog.scss"; @import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss"; @import "./views/dialogs/security/_AccessSecretStorageDialog.scss"; +@import "./views/dialogs/security/_CreateCrossSigningDialog.scss"; @import "./views/dialogs/security/_CreateKeyBackupDialog.scss"; @import "./views/dialogs/security/_CreateSecretStorageDialog.scss"; @import "./views/dialogs/security/_KeyBackupFailedDialog.scss"; @@ -187,7 +188,6 @@ @import "./views/rooms/_RoomHeader.scss"; @import "./views/rooms/_RoomList.scss"; @import "./views/rooms/_RoomPreviewBar.scss"; -@import "./views/rooms/_RoomRecoveryReminder.scss"; @import "./views/rooms/_RoomSublist.scss"; @import "./views/rooms/_RoomTile.scss"; @import "./views/rooms/_RoomUpgradeWarningBar.scss"; diff --git a/res/css/views/rooms/_RoomRecoveryReminder.scss b/res/css/views/rooms/_RoomRecoveryReminder.scss deleted file mode 100644 index 09b28ae235..0000000000 --- a/res/css/views/rooms/_RoomRecoveryReminder.scss +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright 2018 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_RoomRecoveryReminder { - display: flex; - flex-direction: column; - text-align: center; - background-color: $room-warning-bg-color; - padding: 20px; - border: 1px solid $primary-hairline-color; - border-bottom: unset; -} - -.mx_RoomRecoveryReminder_header { - font-weight: bold; - margin-bottom: 1em; -} - -.mx_RoomRecoveryReminder_body { - margin-bottom: 1em; -} - -.mx_RoomRecoveryReminder_secondary { - font-size: 90%; - margin-top: 1em; -} diff --git a/src/async-components/views/dialogs/security/IgnoreRecoveryReminderDialog.js b/src/async-components/views/dialogs/security/IgnoreRecoveryReminderDialog.js deleted file mode 100644 index b79911c66e..0000000000 --- a/src/async-components/views/dialogs/security/IgnoreRecoveryReminderDialog.js +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 2018 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from "react"; -import PropTypes from "prop-types"; -import * as sdk from "../../../../index"; -import { _t } from "../../../../languageHandler"; - -export default class IgnoreRecoveryReminderDialog extends React.PureComponent { - static propTypes = { - onDontAskAgain: PropTypes.func.isRequired, - onFinished: PropTypes.func.isRequired, - onSetup: PropTypes.func.isRequired, - } - - onDontAskAgainClick = () => { - this.props.onFinished(); - this.props.onDontAskAgain(); - } - - onSetupClick = () => { - this.props.onFinished(); - this.props.onSetup(); - } - - render() { - const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog"); - const DialogButtons = sdk.getComponent("views.elements.DialogButtons"); - - return ( - -
    -

    {_t( - "Without setting up Secure Message Recovery, " + - "you'll lose your secure message history when you " + - "log out.", - )}

    -

    {_t( - "If you don't want to set this up now, you can later " + - "in Settings.", - )}

    -
    - -
    -
    -
    - ); - } -} diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 039d36a8de..f568f31dbd 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -65,7 +65,6 @@ import RoomPreviewBar from "../views/rooms/RoomPreviewBar"; import ForwardMessage from "../views/rooms/ForwardMessage"; import SearchBar from "../views/rooms/SearchBar"; import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar"; -import RoomRecoveryReminder from "../views/rooms/RoomRecoveryReminder"; import PinnedEventsPanel from "../views/rooms/PinnedEventsPanel"; import AuxPanel from "../views/rooms/AuxPanel"; import RoomHeader from "../views/rooms/RoomHeader"; @@ -816,12 +815,6 @@ export default class RoomView extends React.Component { } }; - private onRoomRecoveryReminderDontAskAgain = () => { - // Called when the option to not ask again is set: - // force an update to hide the recovery reminder - this.forceUpdate(); - }; - private onKeyBackupStatus = () => { // Key backup status changes affect whether the in-room recovery // reminder is displayed. @@ -1858,13 +1851,6 @@ export default class RoomView extends React.Component { this.state.room.userMayUpgradeRoom(this.context.credentials.userId) ); - const showRoomRecoveryReminder = ( - this.context.isCryptoEnabled() && - SettingsStore.getValue("showRoomRecoveryReminder") && - this.context.isRoomEncrypted(this.state.room.roomId) && - this.context.getKeyBackupEnabled() === false - ); - const hiddenHighlightCount = this.getHiddenHighlightCount(); let aux = null; @@ -1883,9 +1869,6 @@ export default class RoomView extends React.Component { } else if (showRoomUpgradeBar) { aux = ; hideCancel = true; - } else if (showRoomRecoveryReminder) { - aux = ; - hideCancel = true; } else if (this.state.showingPinned) { hideCancel = true; // has own cancel aux = ; diff --git a/src/components/views/rooms/RoomRecoveryReminder.js b/src/components/views/rooms/RoomRecoveryReminder.js deleted file mode 100644 index 552de681c3..0000000000 --- a/src/components/views/rooms/RoomRecoveryReminder.js +++ /dev/null @@ -1,170 +0,0 @@ -/* -Copyright 2018, 2019 New Vector Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from "react"; -import PropTypes from "prop-types"; -import * as sdk from "../../../index"; -import { _t } from "../../../languageHandler"; -import Modal from "../../../Modal"; -import {MatrixClientPeg} from "../../../MatrixClientPeg"; -import SettingsStore from "../../../settings/SettingsStore"; -import {SettingLevel} from "../../../settings/SettingLevel"; -import RestoreKeyBackupDialog from "../dialogs/security/RestoreKeyBackupDialog"; - -export default class RoomRecoveryReminder extends React.PureComponent { - static propTypes = { - // called if the user sets the option to suppress this reminder in the future - onDontAskAgainSet: PropTypes.func, - } - - static defaultProps = { - onDontAskAgainSet: function() {}, - } - - constructor(props) { - super(props); - - this.state = { - loading: true, - error: null, - backupInfo: null, - notNowClicked: false, - }; - } - - componentDidMount() { - this._loadBackupStatus(); - } - - async _loadBackupStatus() { - try { - const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); - this.setState({ - loading: false, - backupInfo, - }); - } catch (e) { - console.log("Unable to fetch key backup status", e); - this.setState({ - loading: false, - error: e, - }); - } - } - - showSetupDialog = () => { - if (this.state.backupInfo) { - // A key backup exists for this account, but the creating device is not - // verified, so restore the backup which will give us the keys from it and - // allow us to trust it (ie. upload keys to it) - Modal.createTrackedDialog( - 'Restore Backup', '', RestoreKeyBackupDialog, null, null, - /* priority = */ false, /* static = */ true, - ); - } else { - Modal.createTrackedDialogAsync("Key Backup", "Key Backup", - import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog"), - null, null, /* priority = */ false, /* static = */ true, - ); - } - } - - onOnNotNowClick = () => { - this.setState({notNowClicked: true}); - } - - onDontAskAgainClick = () => { - // When you choose "Don't ask again" from the room reminder, we show a - // dialog to confirm the choice. - Modal.createTrackedDialogAsync("Ignore Recovery Reminder", "Ignore Recovery Reminder", - import("../../../async-components/views/dialogs/security/IgnoreRecoveryReminderDialog"), - { - onDontAskAgain: async () => { - await SettingsStore.setValue( - "showRoomRecoveryReminder", - null, - SettingLevel.ACCOUNT, - false, - ); - this.props.onDontAskAgainSet(); - }, - onSetup: () => { - this.showSetupDialog(); - }, - }, - ); - } - - onSetupClick = () => { - this.showSetupDialog(); - } - - render() { - // If there was an error loading just don't display the banner: we'll try again - // next time the user switchs to the room. - if (this.state.error || this.state.loading || this.state.notNowClicked) { - return null; - } - - const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton"); - - let setupCaption; - if (this.state.backupInfo) { - setupCaption = _t("Connect this session to Key Backup"); - } else { - setupCaption = _t("Start using Key Backup"); - } - - return ( -
    -
    {_t( - "Never lose encrypted messages", - )}
    -
    -

    {_t( - "Messages in this room are secured with end-to-end " + - "encryption. Only you and the recipient(s) have the " + - "keys to read these messages.", - )}

    -

    {_t( - "Securely back up your keys to avoid losing them. " + - "Learn more.", {}, - { - // TODO: We don't have this link yet: this will prevent the translators - // having to re-translate the string when we do. - a: sub => '', - }, - )}

    -
    -
    - - {setupCaption} - - - { _t("Not now") } - - - { _t("Don't ask me again") } - -
    -
    - ); - } -} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ea558fbd93..727898f1a0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -474,7 +474,6 @@ "Show timestamps in 12 hour format (e.g. 2:30pm)": "Show timestamps in 12 hour format (e.g. 2:30pm)", "Always show message timestamps": "Always show message timestamps", "Autoplay GIFs and videos": "Autoplay GIFs and videos", - "Show a reminder to enable Secure Message Recovery in encrypted rooms": "Show a reminder to enable Secure Message Recovery in encrypted rooms", "Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting", "Show avatars in user and room mentions": "Show avatars in user and room mentions", "Enable big emoji in chat": "Enable big emoji in chat", @@ -1172,12 +1171,6 @@ "%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.", "Try again later, or ask a room admin to check if you have access.": "Try again later, or ask a room admin to check if you have access.", "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.", - "Start using Key Backup": "Start using Key Backup", - "Never lose encrypted messages": "Never lose encrypted messages", - "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", - "Securely back up your keys to avoid losing them. Learn more.": "Securely back up your keys to avoid losing them. Learn more.", - "Not now": "Not now", - "Don't ask me again": "Don't ask me again", "Appearance": "Appearance", "Show rooms with unread messages first": "Show rooms with unread messages first", "Show previews of messages": "Show previews of messages", @@ -1750,6 +1743,7 @@ "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!", "Updating %(brand)s": "Updating %(brand)s", "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", + "Start using Key Backup": "Start using Key Backup", "I don't want my encrypted messages": "I don't want my encrypted messages", "Manually export keys": "Manually export keys", "You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages", @@ -2300,9 +2294,6 @@ "Enter passphrase": "Enter passphrase", "Confirm passphrase": "Confirm passphrase", "Export": "Export", - "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.", - "If you don't want to set this up now, you can later in Settings.": "If you don't want to set this up now, you can later in Settings.", - "Don't ask again": "Don't ask again", "Import room keys": "Import room keys", "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.", "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.", diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..91bc4c2d85 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -281,11 +281,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td('Autoplay GIFs and videos'), default: false, }, - "showRoomRecoveryReminder": { - supportedLevels: LEVELS_ACCOUNT_SETTINGS, - displayName: _td('Show a reminder to enable Secure Message Recovery in encrypted rooms'), - default: true, - }, "enableSyntaxHighlightLanguageDetection": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Enable automatic language detection for syntax highlighting'), From 0d25f62a9adb9e4bb3bae6081f8c7eced3c82fc5 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 12:00:49 +0100 Subject: [PATCH 0255/1014] Tweak diagnostics for session backup key --- src/components/views/settings/SecureBackupPanel.js | 2 +- src/rageshake/submit-rageshake.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 7e9fb6cd3d..3547efc3f2 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -131,7 +131,7 @@ export default class SecureBackupPanel extends React.PureComponent { const cli = MatrixClientPeg.get(); const secretStorage = cli._crypto._secretStorage; - const backupKeyStored = await cli.isKeyBackupKeyStored(); + const backupKeyStored = !!(await cli.isKeyBackupKeyStored()); const backupKeyFromCache = await cli._crypto.getSessionBackupPrivateKey(); const backupKeyCached = !!(backupKeyFromCache); const backupKeyWellFormed = backupKeyFromCache instanceof Uint8Array; diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts index dd60cde16d..d361f6b0dd 100644 --- a/src/rageshake/submit-rageshake.ts +++ b/src/rageshake/submit-rageshake.ts @@ -112,6 +112,7 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true) { body.append("secret_storage_ready", String(await client.isSecretStorageReady())); body.append("secret_storage_key_in_account", String(!!(await secretStorage.hasKey()))); + body.append("session_backup_key_in_secret_storage", String(!!(await client.isKeyBackupKeyStored()))); const sessionBackupKeyFromCache = await client._crypto.getSessionBackupPrivateKey(); body.append("session_backup_key_cached", String(!!sessionBackupKeyFromCache)); body.append("session_backup_key_well_formed", String(sessionBackupKeyFromCache instanceof Uint8Array)); From 6130d9e8826863b9faa0e763c55fc8dd49fb5ef5 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 12:25:02 +0100 Subject: [PATCH 0256/1014] Delay encryption setup toasts until encrypted rooms present Part of https://github.com/vector-im/element-web/issues/13895 --- src/DeviceListener.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index 89cf968c6b..df494e6bdd 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -29,7 +29,6 @@ import { hideToast as hideUnverifiedSessionsToast, showToast as showUnverifiedSessionsToast, } from "./toasts/UnverifiedSessionToast"; -import { privateShouldBeEncrypted } from "./createRoom"; import { isSecretStorageBeingAccessed, accessSecretStorage } from "./SecurityManager"; import { isSecureBackupRequired } from './utils/WellKnownUtils'; import { isLoggedIn } from './components/structures/MatrixChat'; @@ -201,9 +200,7 @@ export default class DeviceListener { // If we're in the middle of a secret storage operation, we're likely // modifying the state involved here, so don't add new toasts to setup. if (isSecretStorageBeingAccessed()) return false; - // In a default configuration, show the toasts. If the well-known config causes e2ee default to be false - // then do not show the toasts until user is in at least one encrypted room. - if (privateShouldBeEncrypted()) return true; + // Show setup toasts once the user is in at least one encrypted room. const cli = MatrixClientPeg.get(); return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId)); } @@ -219,8 +216,6 @@ export default class DeviceListener { // (we add a listener on sync to do once check after the initial sync is done) if (!cli.isInitialSyncComplete()) return; - // JRS: This will change again in the next PR which moves secret storage - // later in the process. const crossSigningReady = await cli.isCrossSigningReady(); const secretStorageReady = await cli.isSecretStorageReady(); const allSystemsReady = crossSigningReady && secretStorageReady; From 550a53e49ce127db64767671e5b887d476861e40 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 13:20:45 +0100 Subject: [PATCH 0257/1014] Check cross-signing cached keys when showing setup button --- src/components/views/settings/CrossSigningPanel.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 669c2e84d9..1c548bd9d8 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -187,12 +187,18 @@ export default class CrossSigningPanel extends React.PureComponent { } const keysExistAnywhere = ( + crossSigningPublicKeysOnDevice || crossSigningPrivateKeysInStorage || - crossSigningPublicKeysOnDevice + masterPrivateKeyCached || + selfSigningPrivateKeyCached || + userSigningPrivateKeyCached ); const keysExistEverywhere = ( + crossSigningPublicKeysOnDevice && crossSigningPrivateKeysInStorage && - crossSigningPublicKeysOnDevice + masterPrivateKeyCached && + selfSigningPrivateKeyCached && + userSigningPrivateKeyCached ); const actions = []; From 7a5b0a964f041ad5f2e515f44166615ffef0ca4d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 13:57:23 +0100 Subject: [PATCH 0258/1014] Adjust main encryption toast to reference Secure Backup This adjusts the main toast to focus on Secure Backup as suggested in designs. Part of https://github.com/vector-im/element-web/issues/13895 --- res/css/structures/_ToastContainer.scss | 5 +++++ src/i18n/strings/en_EN.json | 7 +++---- src/toasts/SetupEncryptionToast.ts | 18 ++++++++++++++---- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index 544dcbc180..c381668a6a 100644 --- a/res/css/structures/_ToastContainer.scss +++ b/res/css/structures/_ToastContainer.scss @@ -80,6 +80,11 @@ limitations under the License. } } + &.mx_Toast_icon_secure_backup::after { + mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); + background-color: $primary-fg-color; + } + .mx_Toast_title, .mx_Toast_body { grid-column: 2; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 727898f1a0..17d80b7647 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -411,13 +411,12 @@ "Set password": "Set password", "To return to your account in future you need to set a password": "To return to your account in future you need to set a password", "Set Password": "Set Password", - "Set up encryption": "Set up encryption", + "Set up Secure Backup": "Set up Secure Backup", "Encryption upgrade available": "Encryption upgrade available", "Verify this session": "Verify this session", - "Set up": "Set up", "Upgrade": "Upgrade", "Verify": "Verify", - "Verify yourself & others to keep your chats safe": "Verify yourself & others to keep your chats safe", + "Safeguard against losing access to encrypted messages & data": "Safeguard against losing access to encrypted messages & data", "Other users may not trust it": "Other users may not trust it", "New login. Was this you?": "New login. Was this you?", "Verify the new login accessing your account: %(name)s": "Verify the new login accessing your account: %(name)s", @@ -651,6 +650,7 @@ "Cross-signing is ready for use.": "Cross-signing is ready for use.", "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.", "Cross-signing is not set up.": "Cross-signing is not set up.", + "Set up": "Set up", "Reset": "Reset", "Cross-signing public keys:": "Cross-signing public keys:", "in memory": "in memory", @@ -2279,7 +2279,6 @@ "Unable to query secret storage status": "Unable to query secret storage status", "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.", "You can also set up Secure Backup & manage your keys in Settings.": "You can also set up Secure Backup & manage your keys in Settings.", - "Set up Secure Backup": "Set up Secure Backup", "Upgrade your encryption": "Upgrade your encryption", "Set a Security Phrase": "Set a Security Phrase", "Confirm Security Phrase": "Confirm Security Phrase", diff --git a/src/toasts/SetupEncryptionToast.ts b/src/toasts/SetupEncryptionToast.ts index 5e3da94eda..5aa030e497 100644 --- a/src/toasts/SetupEncryptionToast.ts +++ b/src/toasts/SetupEncryptionToast.ts @@ -28,7 +28,7 @@ const TOAST_KEY = "setupencryption"; const getTitle = (kind: Kind) => { switch (kind) { case Kind.SET_UP_ENCRYPTION: - return _t("Set up encryption"); + return _t("Set up Secure Backup"); case Kind.UPGRADE_ENCRYPTION: return _t("Encryption upgrade available"); case Kind.VERIFY_THIS_SESSION: @@ -36,10 +36,20 @@ const getTitle = (kind: Kind) => { } }; +const getIcon = (kind: Kind) => { + switch (kind) { + case Kind.SET_UP_ENCRYPTION: + case Kind.UPGRADE_ENCRYPTION: + return "secure_backup"; + case Kind.VERIFY_THIS_SESSION: + return "verification_warning"; + } +}; + const getSetupCaption = (kind: Kind) => { switch (kind) { case Kind.SET_UP_ENCRYPTION: - return _t("Set up"); + return _t("Continue"); case Kind.UPGRADE_ENCRYPTION: return _t("Upgrade"); case Kind.VERIFY_THIS_SESSION: @@ -51,7 +61,7 @@ const getDescription = (kind: Kind) => { switch (kind) { case Kind.SET_UP_ENCRYPTION: case Kind.UPGRADE_ENCRYPTION: - return _t("Verify yourself & others to keep your chats safe"); + return _t("Safeguard against losing access to encrypted messages & data"); case Kind.VERIFY_THIS_SESSION: return _t("Other users may not trust it"); } @@ -88,7 +98,7 @@ export const showToast = (kind: Kind) => { ToastStore.sharedInstance().addOrReplaceToast({ key: TOAST_KEY, title: getTitle(kind), - icon: "verification_warning", + icon: getIcon(kind), props: { description: getDescription(kind), acceptLabel: getSetupCaption(kind), From feb37878d85f723039d737f8ffdf451706c9b9f4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 15:04:13 +0100 Subject: [PATCH 0259/1014] tidy --- src/components/views/dialogs/InviteDialog.js | 35 ++++++++++---------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index f66de67a1d..73101056f3 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -1123,24 +1123,25 @@ export default class InviteDialog extends React.PureComponent { const inviteText = _t("This won't invite them to %(communityName)s. " + "To invite someone to %(communityName)s, click here", {communityName}, { - userId: () => { - return ( - {userId} - ); + userId: () => { + return ( + {userId} + ); + }, + a: (sub) => { + return ( + {sub} + ); + }, }, - a: (sub) => { - return ( - {sub} - ); - }, - }); + ); helpText = { helpText } {inviteText} ; From 06c4abd65e0da867319fb328eff22657616dd80a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 15:23:08 +0100 Subject: [PATCH 0260/1014] Clean up UserInfo to not show a blank Power Selector for users not in room --- src/components/views/right_panel/UserInfo.js | 51 +++++++++----------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index fc73c8b542..3171890955 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -952,30 +952,26 @@ function useRoomPermissions(cli, room, user) { const PowerLevelSection = ({user, room, roomPermissions, powerLevels}) => { const [isEditing, setEditing] = useState(false); - if (room && user.roomId) { // is in room - if (isEditing) { - return ( setEditing(false)} />); - } else { - const IconButton = sdk.getComponent('elements.IconButton'); - const powerLevelUsersDefault = powerLevels.users_default || 0; - const powerLevel = parseInt(user.powerLevel, 10); - const modifyButton = roomPermissions.canEdit ? - ( setEditing(true)} />) : null; - const role = textualPowerLevel(powerLevel, powerLevelUsersDefault); - const label = _t("%(role)s in %(roomName)s", - {role, roomName: room.name}, - {strong: label => {label}}, - ); - return ( -
    -
    {label}{modifyButton}
    -
    - ); - } + if (isEditing) { + return ( setEditing(false)} />); } else { - return null; + const IconButton = sdk.getComponent('elements.IconButton'); + const powerLevelUsersDefault = powerLevels.users_default || 0; + const powerLevel = parseInt(user.powerLevel, 10); + const modifyButton = roomPermissions.canEdit ? + ( setEditing(true)} />) : null; + const role = textualPowerLevel(powerLevel, powerLevelUsersDefault); + const label = _t("%(role)s in %(roomName)s", + {role, roomName: room.name}, + {strong: label => {label}}, + ); + return ( +
    +
    {label}{modifyButton}
    +
    + ); } }; @@ -1268,14 +1264,15 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { spinner = ; } - const memberDetails = ( - - ); + />; + } // only display the devices list if our client supports E2E const cryptoEnabled = cli.isCryptoEnabled(); From c11abb74e0020610f1af696b463d64a44e9ddaf9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 16:06:17 +0100 Subject: [PATCH 0261/1014] UI Feature Flag: Share dialog QR code and social icons --- res/css/views/dialogs/_ShareDialog.scss | 5 +- src/components/views/dialogs/ShareDialog.tsx | 53 ++++++++++++-------- src/settings/Settings.ts | 8 +++ src/settings/UIFeature.ts | 2 + 4 files changed, 46 insertions(+), 22 deletions(-) diff --git a/res/css/views/dialogs/_ShareDialog.scss b/res/css/views/dialogs/_ShareDialog.scss index c343b872fd..ce3fdd021f 100644 --- a/res/css/views/dialogs/_ShareDialog.scss +++ b/res/css/views/dialogs/_ShareDialog.scss @@ -71,9 +71,12 @@ limitations under the License. margin-right: 64px; } +.mx_ShareDialog_qrcode_container + .mx_ShareDialog_social_container { + width: 299px; +} + .mx_ShareDialog_social_container { display: inline-block; - width: 299px; } .mx_ShareDialog_social_icon { diff --git a/src/components/views/dialogs/ShareDialog.tsx b/src/components/views/dialogs/ShareDialog.tsx index e849f7efe3..1569977d58 100644 --- a/src/components/views/dialogs/ShareDialog.tsx +++ b/src/components/views/dialogs/ShareDialog.tsx @@ -32,6 +32,8 @@ import {copyPlaintext, selectText} from "../../../utils/strings"; import StyledCheckbox from '../elements/StyledCheckbox'; import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; import { IDialogProps } from "./IDialogProps"; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; const socials = [ { @@ -197,6 +199,35 @@ export default class ShareDialog extends React.PureComponent { const matrixToUrl = this.getUrl(); const encodedUrl = encodeURIComponent(matrixToUrl); + const showQrCode = SettingsStore.getValue(UIFeature.ShareQRCode); + const showSocials = SettingsStore.getValue(UIFeature.ShareSocial); + + let qrSocialSection; + if (showQrCode || showSocials) { + qrSocialSection = <> +
    +
    + { showQrCode &&
    + +
    } + { showSocials &&
    + { socials.map((social) => ( + + {social.name} + + )) } +
    } +
    + ; + } + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return { />
    { checkbox } -
    - -
    -
    - -
    -
    - { socials.map((social) => ( - - {social.name} - - )) } -
    -
    + { qrSocialSection }
    ; } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..3731125f09 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -622,4 +622,12 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.ShareQRCode]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, + [UIFeature.ShareSocial]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 99196e5d30..c4825dbbba 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,6 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", + ShareQRCode = "UIFeature.shareQrCode", + ShareSocial = "UIFeature.shareSocial", } From 93323febcba986cee7fbead49de882fd17b4c72b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 16:10:24 +0100 Subject: [PATCH 0262/1014] undo change --- src/components/views/settings/SecureBackupPanel.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index c058e27de7..7f0655d54a 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -367,14 +367,14 @@ export default class SecureBackupPanel extends React.PureComponent { ; actions.push( - + {restoreButtonCaption} , ); if (!isSecureBackupRequired()) { actions.push( - + {_t("Delete Backup")} , ); @@ -388,7 +388,7 @@ export default class SecureBackupPanel extends React.PureComponent {

    {_t("Back up your keys before signing out to avoid losing them.")}

    ; actions.push( - + {_t("Set up")} , ); @@ -396,7 +396,7 @@ export default class SecureBackupPanel extends React.PureComponent { if (secretStorageKeyInAccount) { actions.push( - + {_t("Reset")} , ); From 9be04f8a67790a6a849c213aa1a9b696f1a0fa59 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 16:10:43 +0100 Subject: [PATCH 0263/1014] i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 070cf7e597..e8803ddc32 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -912,13 +912,13 @@ "Message search": "Message search", "Cross-signing": "Cross-signing", "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.", + "Privacy": "Privacy", "%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)s collects anonymous analytics to allow us to improve the application.", "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.", "Learn more about how we use analytics.": "Learn more about how we use analytics.", "Where you’re logged in": "Where you’re logged in", "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Manage the names of and sign out of your sessions below or verify them in your User Profile.", "A session's public name is visible to people you communicate with": "A session's public name is visible to people you communicate with", - "Privacy": "Privacy", "No media permissions": "No media permissions", "You may need to manually permit %(brand)s to access your microphone/webcam": "You may need to manually permit %(brand)s to access your microphone/webcam", "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", From a18d0271c370c25706d9174c74bc7089be05d18d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 16:51:10 +0100 Subject: [PATCH 0264/1014] Adjust tests for Secure Backup toast --- .../src/scenarios/e2e-encryption.js | 2 + .../end-to-end-tests/src/usecases/security.js | 42 +++++++++++++++++++ test/end-to-end-tests/src/usecases/signup.js | 2 + 3 files changed, 46 insertions(+) create mode 100644 test/end-to-end-tests/src/usecases/security.js diff --git a/test/end-to-end-tests/src/scenarios/e2e-encryption.js b/test/end-to-end-tests/src/scenarios/e2e-encryption.js index d31d2c0d57..20e8af2947 100644 --- a/test/end-to-end-tests/src/scenarios/e2e-encryption.js +++ b/test/end-to-end-tests/src/scenarios/e2e-encryption.js @@ -21,6 +21,7 @@ const {receiveMessage} = require('../usecases/timeline'); const {createDm} = require('../usecases/create-room'); const {checkRoomSettings} = require('../usecases/room-settings'); const {startSasVerifcation, acceptSasVerification} = require('../usecases/verify'); +const { setupSecureBackup } = require('../usecases/security'); const assert = require('assert'); module.exports = async function e2eEncryptionScenarios(alice, bob) { @@ -43,4 +44,5 @@ module.exports = async function e2eEncryptionScenarios(alice, bob) { const bobMessage = "You've got to tell me!"; await sendMessage(bob, bobMessage); await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true}); + await setupSecureBackup(alice); }; diff --git a/test/end-to-end-tests/src/usecases/security.js b/test/end-to-end-tests/src/usecases/security.js new file mode 100644 index 0000000000..31540874e9 --- /dev/null +++ b/test/end-to-end-tests/src/usecases/security.js @@ -0,0 +1,42 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +const { acceptToast } = require("./toasts"); + +async function setupSecureBackup(session) { + session.log.step("sets up Secure Backup"); + + await acceptToast(session, "Set up Secure Backup"); + + // Continue with the default (generate a security key) + const xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); + await xsignContButton.click(); + + //ignore the recovery key + //TODO: It's probably important for the tests to know the recovery key + const copyButton = await session.query('.mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn'); + await copyButton.click(); + + //acknowledge that we copied the recovery key to a safe place + const copyContinueButton = await session.query( + '.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary', + ); + await copyContinueButton.click(); + + session.log.done(); +} + +module.exports = { setupSecureBackup }; diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index ef8a259091..e4b2a9a62d 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -15,6 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +const { acceptToast } = require("./toasts"); + const assert = require('assert'); module.exports = async function signup(session, username, password, homeserver) { From b5000b236f09dd4159f1768121d62a801aabe7b1 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 17:12:51 +0100 Subject: [PATCH 0265/1014] Fix lint error --- test/end-to-end-tests/src/usecases/signup.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index e4b2a9a62d..ef8a259091 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -15,8 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -const { acceptToast } = require("./toasts"); - const assert = require('assert'); module.exports = async function signup(session, username, password, homeserver) { From b4af0140d425c03ffe2e8044edf30f1fe8326d7a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 12:38:47 -0600 Subject: [PATCH 0266/1014] Render Jitsi widget state events in a more obvious way A clear improvement to this would be to include join/leave buttons in the tiles, however this is currently deferred. --- res/css/_components.scss | 1 + .../views/messages/_MJitsiWidgetEvent.scss | 55 ++++++++++++++ src/TextForEvent.js | 4 - .../views/messages/MJitsiWidgetEvent.tsx | 74 +++++++++++++++++++ src/components/views/rooms/EventTile.js | 20 ++++- src/i18n/strings/en_EN.json | 4 + 6 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 res/css/views/messages/_MJitsiWidgetEvent.scss create mode 100644 src/components/views/messages/MJitsiWidgetEvent.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 54e7436886..26ad802955 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -139,6 +139,7 @@ @import "./views/messages/_MEmoteBody.scss"; @import "./views/messages/_MFileBody.scss"; @import "./views/messages/_MImageBody.scss"; +@import "./views/messages/_MJitsiWidgetEvent.scss"; @import "./views/messages/_MNoticeBody.scss"; @import "./views/messages/_MStickerBody.scss"; @import "./views/messages/_MTextBody.scss"; diff --git a/res/css/views/messages/_MJitsiWidgetEvent.scss b/res/css/views/messages/_MJitsiWidgetEvent.scss new file mode 100644 index 0000000000..3e51e89744 --- /dev/null +++ b/res/css/views/messages/_MJitsiWidgetEvent.scss @@ -0,0 +1,55 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_MJitsiWidgetEvent { + display: grid; + grid-template-columns: 24px minmax(0, 1fr) min-content; + + &::before { + grid-column: 1; + grid-row: 1 / 3; + width: 16px; + height: 16px; + content: ""; + top: 0; + bottom: 0; + left: 0; + right: 0; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + background-color: $composer-e2e-icon-color; // XXX: Variable abuse + margin-top: 4px; + mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + } + + .mx_MJitsiWidgetEvent_title { + font-weight: 600; + font-size: $font-15px; + grid-column: 2; + grid-row: 1; + } + + .mx_MJitsiWidgetEvent_subtitle { + grid-column: 2; + grid-row: 2; + } + + .mx_MJitsiWidgetEvent_title, + .mx_MJitsiWidgetEvent_subtitle { + overflow-wrap: break-word; + } +} diff --git a/src/TextForEvent.js b/src/TextForEvent.js index a76c1f59e6..46e1878d5f 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -476,10 +476,6 @@ function textForWidgetEvent(event) { const {name: prevName, type: prevType, url: prevUrl} = event.getPrevContent(); const {name, type, url} = event.getContent() || {}; - if (WidgetType.JITSI.matches(type) || WidgetType.JITSI.matches(prevType)) { - return textForJitsiWidgetEvent(event, senderName, url, prevUrl); - } - let widgetName = name || prevName || type || prevType || ''; // Apply sentence case to widget name if (widgetName && widgetName.length > 0) { diff --git a/src/components/views/messages/MJitsiWidgetEvent.tsx b/src/components/views/messages/MJitsiWidgetEvent.tsx new file mode 100644 index 0000000000..1bfefbff6a --- /dev/null +++ b/src/components/views/messages/MJitsiWidgetEvent.tsx @@ -0,0 +1,74 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { _t } from "../../../languageHandler"; + +interface IProps { + mxEvent: MatrixEvent; +} + +interface IState { +} + +export default class MJitsiWidgetEvent extends React.PureComponent { + constructor(props) { + super(props); + this.state = {}; + } + + render() { + const url = this.props.mxEvent.getContent()['url']; + const prevUrl = this.props.mxEvent.getPrevContent()['url']; + const senderName = this.props.mxEvent.sender?.name || this.props.mxEvent.getSender(); + + if (!url) { + // removed + return ( +
    +
    + {_t("Video conference ended by %(senderName)s", {senderName})} +
    +
    + ); + } else if (prevUrl) { + // modified + return ( +
    +
    + {_t("Video conference updated by %(senderName)s", {senderName})} +
    +
    + {_t("Join the conference at the top of this room.")} +
    +
    + ); + } else { + // assume added + return ( +
    +
    + {_t("Video conference started by %(senderName)s", {senderName})} +
    +
    + {_t("Join the conference at the top of this room.")} +
    +
    + ); + } + } +} diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index ab9f240f2d..ef9317704d 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -34,6 +34,7 @@ import * as ObjectUtils from "../../../ObjectUtils"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {E2E_STATE} from "./E2EIcon"; import {toRem} from "../../../utils/units"; +import {WidgetType} from "../../../widgets/WidgetType"; const eventTileTypes = { 'm.room.message': 'messages.MessageEvent', @@ -110,6 +111,19 @@ export function getHandlerTile(ev) { } } + // TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111) + if (type === "im.vector.modular.widgets") { + let type = ev.getContent()['type']; + if (!type) { + // deleted/invalid widget - try the past widget type + type = ev.getPrevContent()['type']; + } + + if (WidgetType.JITSI.matches(type)) { + return "messages.MJitsiWidgetEvent"; + } + } + return ev.isState() ? stateEventTileTypes[type] : eventTileTypes[type]; } @@ -619,16 +633,18 @@ export default class EventTile extends React.Component { const msgtype = content.msgtype; const eventType = this.props.mxEvent.getType(); + let tileHandler = getHandlerTile(this.props.mxEvent); + // Info messages are basically information about commands processed on a room const isBubbleMessage = eventType.startsWith("m.key.verification") || (eventType === "m.room.message" && msgtype && msgtype.startsWith("m.key.verification")) || - (eventType === "m.room.encryption"); + (eventType === "m.room.encryption") || + (tileHandler === "messages.MJitsiWidgetEvent"); let isInfoMessage = ( !isBubbleMessage && eventType !== 'm.room.message' && eventType !== 'm.sticker' && eventType !== 'm.room.create' ); - let tileHandler = getHandlerTile(this.props.mxEvent); // If we're showing hidden events in the timeline, we should use the // source tile when there's no regular tile for an event and also for // replace relations (which otherwise would display as a confusing diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b2b4e01202..9d1d39477c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1405,6 +1405,10 @@ "Invalid file%(extra)s": "Invalid file%(extra)s", "Error decrypting image": "Error decrypting image", "Show image": "Show image", + "Video conference ended by %(senderName)s": "Video conference ended by %(senderName)s", + "Video conference updated by %(senderName)s": "Video conference updated by %(senderName)s", + "Join the conference at the top of this room.": "Join the conference at the top of this room.", + "Video conference started by %(senderName)s": "Video conference started by %(senderName)s", "You have ignored this user, so their message is hidden. Show anyways.": "You have ignored this user, so their message is hidden. Show anyways.", "You verified %(name)s": "You verified %(name)s", "You cancelled verifying %(name)s": "You cancelled verifying %(name)s", From 12fb1ee1cf82a2d4c70636681314c5bc1a087a78 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 12:43:28 -0600 Subject: [PATCH 0267/1014] Clean up now-unused code --- src/TextForEvent.js | 19 ------------------- src/i18n/strings/en_EN.json | 3 --- 2 files changed, 22 deletions(-) diff --git a/src/TextForEvent.js b/src/TextForEvent.js index 46e1878d5f..c55380bd9b 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -19,7 +19,6 @@ import { _t } from './languageHandler'; import * as Roles from './Roles'; import {isValid3pidInvite} from "./RoomInvite"; import SettingsStore from "./settings/SettingsStore"; -import {WidgetType} from "./widgets/WidgetType"; import {ALL_RULE_TYPES, ROOM_RULE_TYPES, SERVER_RULE_TYPES, USER_RULE_TYPES} from "./mjolnir/BanList"; function textForMemberEvent(ev) { @@ -501,24 +500,6 @@ function textForWidgetEvent(event) { } } -function textForJitsiWidgetEvent(event, senderName, url, prevUrl) { - if (url) { - if (prevUrl) { - return _t('Group call modified by %(senderName)s', { - senderName, - }); - } else { - return _t('Group call started by %(senderName)s', { - senderName, - }); - } - } else { - return _t('Group call ended by %(senderName)s', { - senderName, - }); - } -} - function textForMjolnirEvent(event) { const senderName = event.getSender(); const {entity: prevEntity} = event.getPrevContent(); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9d1d39477c..01d334505c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -280,9 +280,6 @@ "%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s", "%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s", "%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s", - "Group call modified by %(senderName)s": "Group call modified by %(senderName)s", - "Group call started by %(senderName)s": "Group call started by %(senderName)s", - "Group call ended by %(senderName)s": "Group call ended by %(senderName)s", "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s removed the rule banning users matching %(glob)s", "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s removed the rule banning rooms matching %(glob)s", "%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s removed the rule banning servers matching %(glob)s", From 1ffc6d5bd34fa2d2e87c0ea533c7cd2d9104cf5f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 14:35:50 -0600 Subject: [PATCH 0268/1014] Make the hangup button do things for conference calls Behaviour constraints: * If you're not in the conference, use a grey button that does nothing. * If you're in the conference, show a button: * If you're able to modify widgets in the room, annotate it in the context of ending the call for everyone and remove the widget. Use a confirmation dialog. * If you're not able to modify widgets in the room, hang up. For this we know that persistent Jitsi widgets will mean that the user is in the call, so we use that to determine if they are actually participating. --- res/css/views/rooms/_MessageComposer.scss | 2 +- src/CallHandler.js | 77 ++++++++++++------- src/WidgetMessaging.js | 11 +++ src/components/views/rooms/MessageComposer.js | 63 +++++++++++++-- src/i18n/strings/en_EN.json | 7 +- src/stores/WidgetStore.ts | 19 +++++ src/widgets/WidgetApi.ts | 7 +- 7 files changed, 144 insertions(+), 42 deletions(-) diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index a403a8dc4c..71c0db947e 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -217,7 +217,7 @@ limitations under the License. } } - &.mx_MessageComposer_hangup::before { + &.mx_MessageComposer_hangup:not(.mx_AccessibleButton_disabled)::before { background-color: $warning-color; } } diff --git a/src/CallHandler.js b/src/CallHandler.js index ad40332af5..e40c97f025 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -70,6 +70,8 @@ import {base32} from "rfc4648"; import QuestionDialog from "./components/views/dialogs/QuestionDialog"; import ErrorDialog from "./components/views/dialogs/ErrorDialog"; +import WidgetStore from "./stores/WidgetStore"; +import ActiveWidgetStore from "./stores/ActiveWidgetStore"; global.mxCalls = { //room_id: MatrixCall @@ -310,6 +312,14 @@ function _onAction(payload) { console.info("Place conference call in %s", payload.room_id); _startCallApp(payload.room_id, payload.type); break; + case 'end_conference': + console.info("Terminating conference call in %s", payload.room_id); + _terminateCallApp(payload.room_id); + break; + case 'hangup_conference': + console.info("Leaving conference call in %s", payload.room_id); + _hangupWithCallApp(payload.room_id); + break; case 'incoming_call': { if (callHandler.getAnyActiveCall()) { @@ -357,10 +367,12 @@ async function _startCallApp(roomId, type) { show: true, }); + // prevent double clicking the call button const room = MatrixClientPeg.get().getRoom(roomId); const currentJitsiWidgets = WidgetUtils.getRoomWidgetsOfType(room, WidgetType.JITSI); - - if (WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentJitsiWidgets, WidgetType.JITSI)) { + const hasJitsi = currentJitsiWidgets.length > 0 + || WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentJitsiWidgets, WidgetType.JITSI); + if (hasJitsi) { Modal.createTrackedDialog('Call already in progress', '', ErrorDialog, { title: _t('Call in Progress'), description: _t('A call is currently being placed!'), @@ -368,33 +380,6 @@ async function _startCallApp(roomId, type) { return; } - if (currentJitsiWidgets.length > 0) { - console.warn( - "Refusing to start conference call widget in " + roomId + - " a conference call widget is already present", - ); - - if (WidgetUtils.canUserModifyWidgets(roomId)) { - Modal.createTrackedDialog('Already have Jitsi Widget', '', QuestionDialog, { - title: _t('End Call'), - description: _t('Remove the group call from the room?'), - button: _t('End Call'), - cancelButton: _t('Cancel'), - onFinished: (endCall) => { - if (endCall) { - WidgetUtils.setRoomWidget(roomId, currentJitsiWidgets[0].getContent()['id']); - } - }, - }); - } else { - Modal.createTrackedDialog('Already have Jitsi Widget', '', ErrorDialog, { - title: _t('Call in Progress'), - description: _t("You don't have permission to remove the call from the room"), - }); - } - return; - } - const jitsiDomain = Jitsi.getInstance().preferredDomain; const jitsiAuth = await Jitsi.getInstance().getJitsiAuth(); let confId; @@ -444,6 +429,40 @@ async function _startCallApp(roomId, type) { }); } +function _terminateCallApp(roomId) { + Modal.createTrackedDialog('Confirm Jitsi Terminate', '', QuestionDialog, { + hasCancelButton: true, + title: _t("End conference"), + description: _t("Ending the conference will end the call for everyone. Continue?"), + button: _t("End conference"), + onFinished: (proceed) => { + if (!proceed) return; + + // We'll just obliterate them all. There should only ever be one, but might as well + // be safe. + const roomInfo = WidgetStore.instance.getRoom(roomId); + const jitsiWidgets = roomInfo.widgets.filter(w => WidgetType.JITSI.matches(w.type)); + jitsiWidgets.forEach(w => { + // setting invalid content removes it + WidgetUtils.setRoomWidget(roomId, w.id); + }); + }, + }); +} + +function _hangupWithCallApp(roomId) { + const roomInfo = WidgetStore.instance.getRoom(roomId); + if (!roomInfo) return; // "should never happen" clauses go here + + const jitsiWidgets = roomInfo.widgets.filter(w => WidgetType.JITSI.matches(w.type)); + jitsiWidgets.forEach(w => { + const messaging = ActiveWidgetStore.getWidgetMessaging(w.id); + if (!messaging) return; // more "should never happen" words + + messaging.hangup(); + }); +} + // FIXME: Nasty way of making sure we only register // with the dispatcher once if (!global.mxCallHandler) { diff --git a/src/WidgetMessaging.js b/src/WidgetMessaging.js index c68e926ac1..0f8626ec66 100644 --- a/src/WidgetMessaging.js +++ b/src/WidgetMessaging.js @@ -107,6 +107,17 @@ export default class WidgetMessaging { }); } + /** + * Tells the widget to hang up on its call. + * @returns {Promise<*>} Resolves when teh widget has acknowledged the message. + */ + hangup() { + return this.messageToWidget({ + api: OUTBOUND_API_NAME, + action: KnownWidgetActions.Hangup, + }); + } + /** * Request a screenshot from a widget * @return {Promise} To be resolved with screenshot data when it has been generated diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 81c2ae7a33..3eab58557e 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -1,6 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017, 2018 New Vector Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -32,6 +33,10 @@ import {aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu} from import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import ReplyPreview from "./ReplyPreview"; import {UIFeature} from "../../../settings/UIFeature"; +import WidgetStore from "../../../stores/WidgetStore"; +import WidgetUtils from "../../../utils/WidgetUtils"; +import {UPDATE_EVENT} from "../../../stores/AsyncStore"; +import ActiveWidgetStore from "../../../stores/ActiveWidgetStore"; function ComposerAvatar(props) { const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); @@ -85,8 +90,15 @@ VideoCallButton.propTypes = { }; function HangupButton(props) { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const onHangupClick = () => { + if (props.isConference) { + dis.dispatch({ + action: props.canEndConference ? 'end_conference' : 'hangup_conference', + room_id: props.roomId, + }); + return; + } + const call = CallHandler.getCallForRoom(props.roomId); if (!call) { return; @@ -98,14 +110,28 @@ function HangupButton(props) { room_id: call.roomId, }); }; - return (); + title={tooltip} + disabled={!canLeaveConference} + /> + ); } HangupButton.propTypes = { roomId: PropTypes.string.isRequired, + isConference: PropTypes.bool.isRequired, + canEndConference: PropTypes.bool, + isInConference: PropTypes.bool, }; const EmojiButton = ({addEmoji}) => { @@ -226,12 +252,17 @@ export default class MessageComposer extends React.Component { this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this); this._onTombstoneClick = this._onTombstoneClick.bind(this); this.renderPlaceholderText = this.renderPlaceholderText.bind(this); + WidgetStore.instance.on(UPDATE_EVENT, this._onWidgetUpdate); + ActiveWidgetStore.on('update', this._onActiveWidgetUpdate); this._dispatcherRef = null; + this.state = { isQuoting: Boolean(RoomViewStore.getQuotingEvent()), tombstone: this._getRoomTombstone(), canSendMessages: this.props.room.maySendMessage(), showCallButtons: SettingsStore.getValue("showCallButtonsInComposer"), + hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room), + joinedConference: WidgetStore.instance.isJoinedToConferenceIn(this.props.room), }; } @@ -247,6 +278,14 @@ export default class MessageComposer extends React.Component { } }; + _onWidgetUpdate = () => { + this.setState({hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room)}); + }; + + _onActiveWidgetUpdate = () => { + this.setState({joinedConference: WidgetStore.instance.isJoinedToConferenceIn(this.props.room)}); + }; + componentDidMount() { this.dispatcherRef = dis.register(this.onAction); MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents); @@ -277,6 +316,8 @@ export default class MessageComposer extends React.Component { if (this._roomStoreToken) { this._roomStoreToken.remove(); } + WidgetStore.instance.removeListener(UPDATE_EVENT, this._onWidgetUpdate); + ActiveWidgetStore.removeListener('update', this._onActiveWidgetUpdate); dis.unregister(this.dispatcherRef); } @@ -392,9 +433,19 @@ export default class MessageComposer extends React.Component { } if (this.state.showCallButtons) { - if (callInProgress) { + if (this.state.hasConference) { + const canEndConf = WidgetUtils.canUserModifyWidgets(this.props.room.roomId); controls.push( - , + , + ); + } else if (callInProgress) { + controls.push( + , ); } else { controls.push( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b2b4e01202..b5ecf26cb7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -50,12 +50,10 @@ "You cannot place a call with yourself.": "You cannot place a call with yourself.", "Call in Progress": "Call in Progress", "A call is currently being placed!": "A call is currently being placed!", - "End Call": "End Call", - "Remove the group call from the room?": "Remove the group call from the room?", - "Cancel": "Cancel", - "You don't have permission to remove the call from the room": "You don't have permission to remove the call from the room", "Permission Required": "Permission Required", "You do not have permission to start a conference call in this room": "You do not have permission to start a conference call in this room", + "End conference": "End conference", + "Ending the conference will end the call for everyone. Continue?": "Ending the conference will end the call for everyone. Continue?", "Replying With Files": "Replying With Files", "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "At this time it is not possible to reply with a file. Would you like to upload this file without replying?", "Continue": "Continue", @@ -143,6 +141,7 @@ "Cancel entering passphrase?": "Cancel entering passphrase?", "Are you sure you want to cancel entering passphrase?": "Are you sure you want to cancel entering passphrase?", "Go Back": "Go Back", + "Cancel": "Cancel", "Setting up keys": "Setting up keys", "Messages": "Messages", "Actions": "Actions", diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index 10327ce4e9..be2233961b 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -22,6 +22,7 @@ import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; import defaultDispatcher from "../dispatcher/dispatcher"; import SettingsStore from "../settings/SettingsStore"; import WidgetEchoStore from "../stores/WidgetEchoStore"; +import ActiveWidgetStore from "../stores/ActiveWidgetStore"; import WidgetUtils from "../utils/WidgetUtils"; import {SettingLevel} from "../settings/SettingLevel"; import {WidgetType} from "../widgets/WidgetType"; @@ -206,6 +207,24 @@ export default class WidgetStore extends AsyncStoreWithClient { } return roomInfo.widgets; } + + public doesRoomHaveConference(room: Room): boolean { + const roomInfo = this.getRoom(room.roomId); + if (!roomInfo) return false; + + const currentWidgets = roomInfo.widgets.filter(w => WidgetType.JITSI.matches(w.type)); + const hasPendingWidgets = WidgetEchoStore.roomHasPendingWidgetsOfType(room.roomId, [], WidgetType.JITSI); + return currentWidgets.length > 0 || hasPendingWidgets; + } + + public isJoinedToConferenceIn(room: Room): boolean { + const roomInfo = this.getRoom(room.roomId); + if (!roomInfo) return false; + + // A persistent conference widget indicates that we're participating + const widgets = roomInfo.widgets.filter(w => WidgetType.JITSI.matches(w.type)); + return widgets.some(w => ActiveWidgetStore.getWidgetPersistence(w.id)); + } } window.mxWidgetStore = WidgetStore.instance; diff --git a/src/widgets/WidgetApi.ts b/src/widgets/WidgetApi.ts index 672cbf2a56..c25d607948 100644 --- a/src/widgets/WidgetApi.ts +++ b/src/widgets/WidgetApi.ts @@ -39,6 +39,7 @@ export enum KnownWidgetActions { SetAlwaysOnScreen = "set_always_on_screen", ClientReady = "im.vector.ready", Terminate = "im.vector.terminate", + Hangup = "im.vector.hangup", } export type WidgetAction = KnownWidgetActions | string; @@ -119,13 +120,15 @@ export class WidgetApi extends EventEmitter { // Automatically acknowledge so we can move on this.replyToRequest(payload, {}); - } else if (payload.action === KnownWidgetActions.Terminate) { + } else if (payload.action === KnownWidgetActions.Terminate + || payload.action === KnownWidgetActions.Hangup) { // Finalization needs to be async, so postpone with a promise let finalizePromise = Promise.resolve(); const wait = (promise) => { finalizePromise = finalizePromise.then(() => promise); }; - this.emit('terminate', wait); + const emitName = payload.action === KnownWidgetActions.Terminate ? 'terminate' : 'hangup'; + this.emit(emitName, wait); Promise.resolve(finalizePromise).then(() => { // Acknowledge that we're shut down now this.replyToRequest(payload, {}); From f412f8defeab4a6af02722f3c91872b9857de83b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 14:59:15 -0600 Subject: [PATCH 0269/1014] Change copy if the widget is unpinned --- .../views/messages/MJitsiWidgetEvent.tsx | 22 +++++++++++++++---- src/i18n/strings/en_EN.json | 3 ++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/components/views/messages/MJitsiWidgetEvent.tsx b/src/components/views/messages/MJitsiWidgetEvent.tsx index 1bfefbff6a..6f87aaec28 100644 --- a/src/components/views/messages/MJitsiWidgetEvent.tsx +++ b/src/components/views/messages/MJitsiWidgetEvent.tsx @@ -17,6 +17,8 @@ limitations under the License. import React from 'react'; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { _t } from "../../../languageHandler"; +import WidgetStore from "../../../stores/WidgetStore"; +import { WidgetType } from "../../../widgets/WidgetType"; interface IProps { mxEvent: MatrixEvent; @@ -36,12 +38,24 @@ export default class MJitsiWidgetEvent extends React.PureComponent WidgetType.JITSI.matches(w.type) && WidgetStore.instance.isPinned(w.id)); + + let joinCopy = _t('Join the conference at the top of this room'); + if (!isPinned) { + joinCopy = _t('Join the conference from the room information card on the right'); + } + if (!url) { // removed return (
    - {_t("Video conference ended by %(senderName)s", {senderName})} + {_t('Video conference ended by %(senderName)s', {senderName})}
    ); @@ -50,10 +64,10 @@ export default class MJitsiWidgetEvent extends React.PureComponent
    - {_t("Video conference updated by %(senderName)s", {senderName})} + {_t('Video conference updated by %(senderName)s', {senderName})}
    - {_t("Join the conference at the top of this room.")} + {joinCopy}
    ); @@ -65,7 +79,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent
    - {_t("Join the conference at the top of this room.")} + {joinCopy}
    ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 01d334505c..dc218aefc5 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1402,9 +1402,10 @@ "Invalid file%(extra)s": "Invalid file%(extra)s", "Error decrypting image": "Error decrypting image", "Show image": "Show image", + "Join the conference at the top of this room": "Join the conference at the top of this room", + "Join the conference from the room information card on the right": "Join the conference from the room information card on the right", "Video conference ended by %(senderName)s": "Video conference ended by %(senderName)s", "Video conference updated by %(senderName)s": "Video conference updated by %(senderName)s", - "Join the conference at the top of this room.": "Join the conference at the top of this room.", "Video conference started by %(senderName)s": "Video conference started by %(senderName)s", "You have ignored this user, so their message is hidden. Show anyways.": "You have ignored this user, so their message is hidden. Show anyways.", "You verified %(name)s": "You verified %(name)s", From 959b8dd31419003d598991785005d34c2d28255d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 14:59:40 -0600 Subject: [PATCH 0270/1014] de-state --- src/components/views/messages/MJitsiWidgetEvent.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/views/messages/MJitsiWidgetEvent.tsx b/src/components/views/messages/MJitsiWidgetEvent.tsx index 6f87aaec28..5171780ecc 100644 --- a/src/components/views/messages/MJitsiWidgetEvent.tsx +++ b/src/components/views/messages/MJitsiWidgetEvent.tsx @@ -24,13 +24,9 @@ interface IProps { mxEvent: MatrixEvent; } -interface IState { -} - -export default class MJitsiWidgetEvent extends React.PureComponent { +export default class MJitsiWidgetEvent extends React.PureComponent { constructor(props) { super(props); - this.state = {}; } render() { From 815a1559ffa8af51ab9794771a5915549e3cb9e2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 15:05:14 -0600 Subject: [PATCH 0271/1014] Fix setState() usage in the constructor of RoomDirectory React doesn't like it when we setState() in the constructor --- src/components/structures/RoomDirectory.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index 16ab8edbed..44652e5073 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -70,10 +70,10 @@ export default class RoomDirectory extends React.Component { this.scrollPanel = null; this.protocols = null; - this.setState({protocolsLoading: true}); + this.state.protocolsLoading = true; if (!MatrixClientPeg.get()) { // We may not have a client yet when invoked from welcome page - this.setState({protocolsLoading: false}); + this.state.protocolsLoading = false; return; } @@ -102,14 +102,16 @@ export default class RoomDirectory extends React.Component { }); } else { // We don't use the protocols in the communities v2 prototype experience - this.setState({protocolsLoading: false}); + this.state.protocolsLoading = false; // Grab the profile info async FlairStore.getGroupProfileCached(MatrixClientPeg.get(), this.state.selectedCommunityId).then(profile => { this.setState({communityName: profile.name}); }); } + } + componentDidMount() { this.refreshRoomList(); } From dca48b984fa3440e2b78be33f984da459d16327e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 15:47:06 -0600 Subject: [PATCH 0272/1014] Be more sane --- src/components/views/messages/MJitsiWidgetEvent.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/components/views/messages/MJitsiWidgetEvent.tsx b/src/components/views/messages/MJitsiWidgetEvent.tsx index 5171780ecc..bd161b5ca2 100644 --- a/src/components/views/messages/MJitsiWidgetEvent.tsx +++ b/src/components/views/messages/MJitsiWidgetEvent.tsx @@ -34,15 +34,8 @@ export default class MJitsiWidgetEvent extends React.PureComponent { const prevUrl = this.props.mxEvent.getPrevContent()['url']; const senderName = this.props.mxEvent.sender?.name || this.props.mxEvent.getSender(); - // XXX: We are assuming that there will only be one Jitsi widget per room, which isn't entirely - // safe but if there's more than 1 the user will be super confused anyways - the copy doesn't - // need to concern itself with this. - const roomInfo = WidgetStore.instance.getRoom(this.props.mxEvent.getRoomId()); - const isPinned = roomInfo?.widgets - .some(w => WidgetType.JITSI.matches(w.type) && WidgetStore.instance.isPinned(w.id)); - let joinCopy = _t('Join the conference at the top of this room'); - if (!isPinned) { + if (!WidgetStore.instance.isPinned(this.props.mxEvent.getStateKey())) { joinCopy = _t('Join the conference from the room information card on the right'); } From bfbbf44dfcc85e990eb579bd67aa7782d6280d6a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 17:23:37 -0600 Subject: [PATCH 0273/1014] Add a note to use the desktop builds when seshat isn't available Fixes https://github.com/vector-im/element-web/issues/15184 This is currently temporary design for https://github.com/vector-im/element-web/issues/12896 but does not fix it. --- res/css/_components.scss | 1 + .../views/elements/_DesktopBuildsNotice.scss | 28 ++++ res/css/views/rooms/_SearchBar.scss | 1 + res/img/element-desktop-logo.svg | 157 ++++++++++++++++++ src/SdkConfig.ts | 5 + src/components/structures/FilePanel.js | 4 + src/components/structures/RoomView.tsx | 1 + .../views/elements/DesktopBuildsNotice.tsx | 77 +++++++++ src/components/views/rooms/SearchBar.js | 35 ++-- src/i18n/strings/en_EN.json | 4 + 10 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 res/css/views/elements/_DesktopBuildsNotice.scss create mode 100644 res/img/element-desktop-logo.svg create mode 100644 src/components/views/elements/DesktopBuildsNotice.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 54e7436886..4c83dd7a31 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -100,6 +100,7 @@ @import "./views/elements/_AccessibleButton.scss"; @import "./views/elements/_AddressSelector.scss"; @import "./views/elements/_AddressTile.scss"; +@import "./views/elements/_DesktopBuildsNotice.scss"; @import "./views/elements/_DirectorySearchBox.scss"; @import "./views/elements/_Dropdown.scss"; @import "./views/elements/_EditableItemList.scss"; diff --git a/res/css/views/elements/_DesktopBuildsNotice.scss b/res/css/views/elements/_DesktopBuildsNotice.scss new file mode 100644 index 0000000000..3672595bf1 --- /dev/null +++ b/res/css/views/elements/_DesktopBuildsNotice.scss @@ -0,0 +1,28 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_DesktopBuildsNotice { + text-align: center; + padding: 0 16px; + + > * { + vertical-align: middle; + } + + > img { + margin-right: 8px; + } +} diff --git a/res/css/views/rooms/_SearchBar.scss b/res/css/views/rooms/_SearchBar.scss index fecc8d78d8..d9f730a8b6 100644 --- a/res/css/views/rooms/_SearchBar.scss +++ b/res/css/views/rooms/_SearchBar.scss @@ -68,3 +68,4 @@ limitations under the License. cursor: pointer; } } + diff --git a/res/img/element-desktop-logo.svg b/res/img/element-desktop-logo.svg new file mode 100644 index 0000000000..2031733ce3 --- /dev/null +++ b/res/img/element-desktop-logo.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SdkConfig.ts b/src/SdkConfig.ts index b914aaaf6d..7d7caa2d24 100644 --- a/src/SdkConfig.ts +++ b/src/SdkConfig.ts @@ -33,6 +33,11 @@ export const DEFAULTS: ConfigOptions = { // Default conference domain preferredDomain: "jitsi.riot.im", }, + desktopBuilds: { + available: true, + logo: require("../res/img/element-desktop-logo.svg"), + url: "https://element.io/get-started", + }, }; export default class SdkConfig { diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 6d618d0b9d..4836b0f554 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -25,6 +25,7 @@ import EventIndexPeg from "../../indexing/EventIndexPeg"; import { _t } from '../../languageHandler'; import BaseCard from "../views/right_panel/BaseCard"; import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; +import DesktopBuildsNotice, {WarningKind} from "../views/elements/DesktopBuildsNotice"; /* * Component which shows the filtered file using a TimelinePanel @@ -222,6 +223,8 @@ class FilePanel extends React.Component {

    {_t('Attach files from chat or just drag and drop them anywhere in a room.')}

    ); + const isRoomEncrypted = this.noRoom ? false : MatrixClientPeg.get().isRoomEncrypted(this.props.roomId); + if (this.state.timelineSet) { // console.log("rendering TimelinePanel for timelineSet " + this.state.timelineSet.room.roomId + " " + // "(" + this.state.timelineSet._timelines.join(", ") + ")" + " with key " + this.props.roomId); @@ -232,6 +235,7 @@ class FilePanel extends React.Component { previousPhase={RightPanelPhases.RoomSummary} withoutScrollContainer > + { searchInProgress={this.state.searchInProgress} onCancelClick={this.onCancelSearchClick} onSearch={this.onSearch} + isRoomEncrypted={this.context.isRoomEncrypted(this.state.room.roomId)} />; } else if (showRoomUpgradeBar) { aux = ; diff --git a/src/components/views/elements/DesktopBuildsNotice.tsx b/src/components/views/elements/DesktopBuildsNotice.tsx new file mode 100644 index 0000000000..688d0669da --- /dev/null +++ b/src/components/views/elements/DesktopBuildsNotice.tsx @@ -0,0 +1,77 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import EventIndexPeg from "../../../indexing/EventIndexPeg"; +import { _t } from "../../../languageHandler"; +import SdkConfig from "../../../SdkConfig"; +import React from "react"; + +export enum WarningKind { + Files, + Search, +} + +interface IProps { + isRoomEncrypted: boolean; + kind: WarningKind; +} + +export default function DesktopBuildsNotice({isRoomEncrypted, kind}: IProps) { + if (!isRoomEncrypted) return null; + if (EventIndexPeg.get()) return null; + + const {desktopBuilds, brand} = SdkConfig.get(); + + let text = null; + let logo = null; + if (desktopBuilds.available) { + logo = ; + switch(kind) { + case WarningKind.Files: + text = _t("Use the Desktop app to see encrypted files", {}, { + a: sub => ({sub}), + }); + break; + case WarningKind.Search: + text = _t("Use the Desktop app to search encrypted messages", {}, { + a: sub => ({sub}), + }); + break; + } + } else { + switch(kind) { + case WarningKind.Files: + text = _t("This version of %(brand)s does not support viewing encrypted files", {brand}); + break; + case WarningKind.Search: + text = _t("This version of %(brand)s does not support searching encrypted messages", {brand}); + break; + } + } + + // for safety + if (!text) { + console.warn("Unknown desktop builds warning kind: ", kind); + return null; + } + + return ( +
    + {logo} + {text} +
    + ); +} diff --git a/src/components/views/rooms/SearchBar.js b/src/components/views/rooms/SearchBar.js index 767f5a35f5..4bf97aac10 100644 --- a/src/components/views/rooms/SearchBar.js +++ b/src/components/views/rooms/SearchBar.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,6 +20,9 @@ import AccessibleButton from "../elements/AccessibleButton"; import classNames from "classnames"; import { _t } from '../../../languageHandler'; import {Key} from "../../../Keyboard"; +import SdkConfig from "../../../SdkConfig"; +import EventIndexPeg from "../../../indexing/EventIndexPeg"; +import DesktopBuildsNotice, {WarningKind} from "../elements/DesktopBuildsNotice"; export default class SearchBar extends React.Component { constructor(props) { @@ -72,21 +76,24 @@ export default class SearchBar extends React.Component { }); return ( -
    -
    - - {_t("This Room")} - - - {_t("All Rooms")} - + <> +
    +
    + + {_t("This Room")} + + + {_t("All Rooms")} + +
    +
    + + +
    +
    -
    - - -
    - -
    + + ); } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b2b4e01202..d4053f4418 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1493,6 +1493,10 @@ "Maximize apps": "Maximize apps", "Popout widget": "Popout widget", "More options": "More options", + "Use the Desktop app to see encrypted files": "Use the Desktop app to see encrypted files", + "Use the Desktop app to search encrypted messages": "Use the Desktop app to search encrypted messages", + "This version of %(brand)s does not support viewing encrypted files": "This version of %(brand)s does not support viewing encrypted files", + "This version of %(brand)s does not support searching encrypted messages": "This version of %(brand)s does not support searching encrypted messages", "Join": "Join", "No results": "No results", "Please create a new issue on GitHub so that we can investigate this bug.": "Please create a new issue on GitHub so that we can investigate this bug.", From e52a02d733505d1a15e0957de9011f2296fffb77 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 17:26:00 -0600 Subject: [PATCH 0274/1014] Appease the linter --- src/components/views/messages/MJitsiWidgetEvent.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/messages/MJitsiWidgetEvent.tsx b/src/components/views/messages/MJitsiWidgetEvent.tsx index bd161b5ca2..3d191209f9 100644 --- a/src/components/views/messages/MJitsiWidgetEvent.tsx +++ b/src/components/views/messages/MJitsiWidgetEvent.tsx @@ -18,7 +18,6 @@ import React from 'react'; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { _t } from "../../../languageHandler"; import WidgetStore from "../../../stores/WidgetStore"; -import { WidgetType } from "../../../widgets/WidgetType"; interface IProps { mxEvent: MatrixEvent; From c3a37544323da3c2d7801114563c597fd57e352a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 17:27:45 -0600 Subject: [PATCH 0275/1014] Appease the linter --- src/components/views/elements/DesktopBuildsNotice.tsx | 4 ++-- src/components/views/rooms/SearchBar.js | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/views/elements/DesktopBuildsNotice.tsx b/src/components/views/elements/DesktopBuildsNotice.tsx index 688d0669da..cc5b9174d1 100644 --- a/src/components/views/elements/DesktopBuildsNotice.tsx +++ b/src/components/views/elements/DesktopBuildsNotice.tsx @@ -39,7 +39,7 @@ export default function DesktopBuildsNotice({isRoomEncrypted, kind}: IProps) { let logo = null; if (desktopBuilds.available) { logo = ; - switch(kind) { + switch (kind) { case WarningKind.Files: text = _t("Use the Desktop app to see encrypted files", {}, { a: sub => ({sub}), @@ -52,7 +52,7 @@ export default function DesktopBuildsNotice({isRoomEncrypted, kind}: IProps) { break; } } else { - switch(kind) { + switch (kind) { case WarningKind.Files: text = _t("This version of %(brand)s does not support viewing encrypted files", {brand}); break; diff --git a/src/components/views/rooms/SearchBar.js b/src/components/views/rooms/SearchBar.js index 4bf97aac10..ac637673e4 100644 --- a/src/components/views/rooms/SearchBar.js +++ b/src/components/views/rooms/SearchBar.js @@ -20,8 +20,6 @@ import AccessibleButton from "../elements/AccessibleButton"; import classNames from "classnames"; import { _t } from '../../../languageHandler'; import {Key} from "../../../Keyboard"; -import SdkConfig from "../../../SdkConfig"; -import EventIndexPeg from "../../../indexing/EventIndexPeg"; import DesktopBuildsNotice, {WarningKind} from "../elements/DesktopBuildsNotice"; export default class SearchBar extends React.Component { From 8129333dcc35d4ea8cec32521489e1819cc52f5b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 22:38:12 -0600 Subject: [PATCH 0276/1014] Make the PIP Jitsi look and feel like the 1:1 PIP * Similar sizing * Fix pointers so the jitsi widget doesn't feel clickable when it's not * We might want to introduce click-to-visit-room for the Jitsi widget (like the 1:1 call), however the Jitsi widget has many more controls to worry about * Remove the menu bar from the widget to avoid accidents --- res/css/views/rooms/_AppsDrawer.scss | 4 ++-- res/css/views/voip/_CallContainer.scss | 14 ++++++++++++-- src/components/views/elements/PersistentApp.js | 1 + 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index fee3d61153..b9249d310a 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -$MiniAppTileHeight: 114px; +$MiniAppTileHeight: 200px; .mx_AppsDrawer { margin: 5px 5px 5px 18px; @@ -220,7 +220,7 @@ $MiniAppTileHeight: 114px; } .mx_AppTileBody_mini { - height: 112px; + height: $MiniAppTileHeight; width: 100%; overflow: hidden; } diff --git a/res/css/views/voip/_CallContainer.scss b/res/css/views/voip/_CallContainer.scss index 4d26d8a312..650302b7e1 100644 --- a/res/css/views/voip/_CallContainer.scss +++ b/res/css/views/voip/_CallContainer.scss @@ -23,9 +23,16 @@ limitations under the License. z-index: 100; box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); - cursor: pointer; + // Disable pointer events for Jitsi widgets to function. Direct + // calls have their own cursor and behaviour, but we need to make + // sure the cursor hits the iframe for Jitsi which will be at a + // different level. + pointer-events: none; .mx_CallPreview { + pointer-events: initial; // restore pointer events so the user can leave/interact + cursor: pointer; + .mx_VideoView { width: 350px; } @@ -37,7 +44,7 @@ limitations under the License. } .mx_AppTile_persistedWrapper div { - min-width: 300px; + min-width: 350px; } .mx_IncomingCallBox { @@ -45,6 +52,9 @@ limitations under the License. background-color: $primary-bg-color; padding: 8px; + pointer-events: initial; // restore pointer events so the user can accept/decline + cursor: pointer; + .mx_IncomingCallBox_CallerInfo { display: flex; direction: row; diff --git a/src/components/views/elements/PersistentApp.js b/src/components/views/elements/PersistentApp.js index 686739a9f7..a3e413151a 100644 --- a/src/components/views/elements/PersistentApp.js +++ b/src/components/views/elements/PersistentApp.js @@ -82,6 +82,7 @@ export default class PersistentApp extends React.Component { showDelete={false} showMinimise={false} miniMode={true} + showMenubar={false} />; } } From e849cd8fe54466468ef6867d0546f43aafe57dc9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 18:13:52 -0600 Subject: [PATCH 0277/1014] Null-check the widget before continuing Deleted widgets should return isPinned=false --- src/stores/WidgetStore.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index 10327ce4e9..f3b8ee1299 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -158,7 +158,8 @@ export default class WidgetStore extends AsyncStoreWithClient { let pinned = roomInfo && roomInfo.pinned[widgetId]; // Jitsi widgets should be pinned by default - if (pinned === undefined && WidgetType.JITSI.matches(this.widgetMap.get(widgetId).type)) pinned = true; + const widget = this.widgetMap.get(widgetId); + if (pinned === undefined && WidgetType.JITSI.matches(widget?.type)) pinned = true; return pinned; } From d340dd58d1f4828f45dbf243d436bb21f32e1dca Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Sep 2020 11:55:10 +0100 Subject: [PATCH 0278/1014] UI Feature Flag: Registration, Password Reset, Deactivate --- res/css/views/auth/_Welcome.scss | 6 ++++++ src/components/structures/MatrixChat.tsx | 10 ++++++---- src/components/structures/auth/Login.js | 4 +++- src/components/views/auth/Welcome.js | 8 +++++++- .../settings/tabs/user/GeneralUserSettingsTab.js | 11 +++++++++-- src/settings/Settings.ts | 12 ++++++++++++ src/settings/UIFeature.ts | 3 +++ 7 files changed, 46 insertions(+), 8 deletions(-) diff --git a/res/css/views/auth/_Welcome.scss b/res/css/views/auth/_Welcome.scss index 9043289184..f0e2b3de33 100644 --- a/res/css/views/auth/_Welcome.scss +++ b/res/css/views/auth/_Welcome.scss @@ -18,6 +18,12 @@ limitations under the License. display: flex; flex-direction: column; align-items: center; + + &.mx_WelcomePage_registrationDisabled { + .mx_ButtonCreateAccount { + display: none; + } + } } .mx_Welcome .mx_AuthBody_language { diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 1875d80fa4..26d1941574 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -79,6 +79,7 @@ import { SettingLevel } from "../../settings/SettingLevel"; import { leaveRoomBehaviour } from "../../utils/membership"; import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog"; import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore"; +import {UIFeature} from "../../settings/UIFeature"; /** constants for MatrixChat.state.view */ export enum Views { @@ -1942,7 +1943,7 @@ export default class MatrixChat extends React.PureComponent { render() { const fragmentAfterLogin = this.getFragmentAfterLogin(); - let view; + let view = null; if (this.state.view === Views.LOADING) { const Spinner = sdk.getComponent('elements.Spinner'); @@ -2021,7 +2022,7 @@ export default class MatrixChat extends React.PureComponent { } else if (this.state.view === Views.WELCOME) { const Welcome = sdk.getComponent('auth.Welcome'); view = ; - } else if (this.state.view === Views.REGISTER) { + } else if (this.state.view === Views.REGISTER && SettingsStore.getValue(UIFeature.Registration)) { const Registration = sdk.getComponent('structures.auth.Registration'); const email = ThreepidInviteStore.instance.pickBestInvite()?.toEmail; view = ( @@ -2039,7 +2040,7 @@ export default class MatrixChat extends React.PureComponent { {...this.getServerProperties()} /> ); - } else if (this.state.view === Views.FORGOT_PASSWORD) { + } else if (this.state.view === Views.FORGOT_PASSWORD && SettingsStore.getValue(UIFeature.PasswordReset)) { const ForgotPassword = sdk.getComponent('structures.auth.ForgotPassword'); view = ( { /> ); } else if (this.state.view === Views.LOGIN) { + const showPasswordReset = SettingsStore.getValue(UIFeature.PasswordReset); const Login = sdk.getComponent('structures.auth.Login'); view = ( { onRegisterClick={this.onRegisterClick} fallbackHsUrl={this.getFallbackHsUrl()} defaultDeviceDisplayName={this.props.defaultDeviceDisplayName} - onForgotPasswordClick={this.onForgotPasswordClick} + onForgotPasswordClick={showPasswordReset ? this.onForgotPasswordClick : undefined} onServerConfigChange={this.onServerConfigChange} fragmentAfterLogin={fragmentAfterLogin} {...this.getServerProperties()} diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js index a20bf0dd0a..118eed59e3 100644 --- a/src/components/structures/auth/Login.js +++ b/src/components/structures/auth/Login.js @@ -28,6 +28,8 @@ import classNames from "classnames"; import AuthPage from "../../views/auth/AuthPage"; import SSOButton from "../../views/elements/SSOButton"; import PlatformPeg from '../../../PlatformPeg'; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; // For validating phone numbers without country codes const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/; @@ -679,7 +681,7 @@ export default class LoginComponent extends React.Component { {_t("If you've joined lots of rooms, this might take a while")}
    } ; - } else { + } else if (SettingsStore.getValue(UIFeature.Registration)) { footer = ( { _t('Create account') } diff --git a/src/components/views/auth/Welcome.js b/src/components/views/auth/Welcome.js index 5a30a02490..21032f4f1a 100644 --- a/src/components/views/auth/Welcome.js +++ b/src/components/views/auth/Welcome.js @@ -15,10 +15,14 @@ limitations under the License. */ import React from 'react'; +import classNames from "classnames"; + import * as sdk from '../../../index'; import SdkConfig from '../../../SdkConfig'; import AuthPage from "./AuthPage"; import {_td} from "../../../languageHandler"; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; // translatable strings for Welcome pages _td("Sign in with SSO"); @@ -39,7 +43,9 @@ export default class Welcome extends React.PureComponent { return ( -
    +
    : null; + let accountManagementSection; + if (SettingsStore.getValue(UIFeature.Deactivate)) { + accountManagementSection = <> +
    {_t("Deactivate account")}
    + {this._renderManagementSection()} + ; + } + return (
    {_t("General")}
    @@ -395,8 +403,7 @@ export default class GeneralUserSettingsTab extends React.Component {
    {discoWarning} {_t("Discovery")}
    {this._renderDiscoverySection()} {this._renderIntegrationManagerSection() /* Has its own title */} -
    {_t("Deactivate account")}
    - {this._renderManagementSection()} + { accountManagementSection }
    ); } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 21b3935c3e..f7a1b6655c 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -626,4 +626,16 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.Registration]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, + [UIFeature.PasswordReset]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, + [UIFeature.Deactivate]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index dddef82df1..71821917bf 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -19,4 +19,7 @@ export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", Feedback = "UIFeature.feedback", + Registration = "UIFeature.registration", + PasswordReset = "UIFeature.passwordReset", + Deactivate = "UIFeature.deactivate", } From f52b267bd39a19872a0ff3300925f0ce9e403123 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Sep 2020 12:07:17 +0100 Subject: [PATCH 0279/1014] i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b2b4e01202..d91fe475df 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -832,9 +832,9 @@ "Account management": "Account management", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", "Deactivate Account": "Deactivate Account", + "Deactivate account": "Deactivate account", "General": "General", "Discovery": "Discovery", - "Deactivate account": "Deactivate account", "Legal": "Legal", "Credits": "Credits", "For help with using %(brand)s, click
    here.": "For help with using %(brand)s, click here.", From eda2dee63fd3be2d1f3b8d5a5bede8d46c4bfeb2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Sep 2020 13:25:18 +0100 Subject: [PATCH 0280/1014] UI Feature Flag: 3PIDs --- .../views/settings/tabs/user/GeneralUserSettingsTab.js | 4 +++- src/settings/Settings.ts | 4 ++++ src/settings/UIFeature.ts | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 42e12077f2..fadb4c756b 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -248,7 +248,9 @@ export default class GeneralUserSettingsTab extends React.Component { // validate 3PID ownership even if we're just adding to the homeserver only. // For newer homeservers with separate 3PID add and bind methods (MSC2290), // there is no such concern, so we can always show the HS account 3PIDs. - if (this.state.haveIdServer || this.state.serverSupportsSeparateAddAndBind === true) { + if (SettingsStore.getValue(UIFeature.ThirdPartyID) && + (this.state.haveIdServer || this.state.serverSupportsSeparateAddAndBind === true) + ) { const emails = this.state.loading3pids ? : Date: Thu, 17 Sep 2020 13:57:47 +0100 Subject: [PATCH 0281/1014] i18n --- src/i18n/strings/en_EN.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d91fe475df..4c2a55d09e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -833,8 +833,8 @@ "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", "Deactivate Account": "Deactivate Account", "Deactivate account": "Deactivate account", - "General": "General", "Discovery": "Discovery", + "General": "General", "Legal": "Legal", "Credits": "Credits", "For help with using %(brand)s, click here.": "For help with using %(brand)s, click here.", @@ -1732,9 +1732,11 @@ "Recently Direct Messaged": "Recently Direct Messaged", "Direct Messages": "Direct Messages", "Start a conversation with someone using their name, username (like ) or email address.": "Start a conversation with someone using their name, username (like ) or email address.", - "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.", + "Start a conversation with someone using their name or username (like ).": "Start a conversation with someone using their name or username (like ).", + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here", "Go": "Go", "Invite someone using their name, username (like ), email address or share this room.": "Invite someone using their name, username (like ), email address or share this room.", + "Invite someone using their name, username (like ) or share this room.": "Invite someone using their name, username (like ) or share this room.", "a new master key signature": "a new master key signature", "a new cross-signing key signature": "a new cross-signing key signature", "a device cross-signing signature": "a device cross-signing signature", From 24d0950b7edd73dc16ba8a9860df20775645f0f5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 17 Sep 2020 09:23:06 -0600 Subject: [PATCH 0282/1014] Adjust layout and formatting of notifications / file cards This follows some polish time with a designer. The placeholder text on the two panels was tracking up/down when the width was changed. This is fixed by adjusting some of the flexbox properties to center it more safely. We also spent some time making the notifications panel more legible while we wait for the overhaul to land. --- res/css/structures/_FilePanel.scss | 7 ++++ res/css/structures/_NotificationPanel.scss | 37 ++++++++++++++++++---- res/css/structures/_RoomView.scss | 8 ++--- src/components/views/rooms/EventTile.js | 2 ++ 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/res/css/structures/_FilePanel.scss b/res/css/structures/_FilePanel.scss index 21b30d804a..2aa068b674 100644 --- a/res/css/structures/_FilePanel.scss +++ b/res/css/structures/_FilePanel.scss @@ -23,6 +23,13 @@ limitations under the License. .mx_FilePanel .mx_RoomView_messageListWrapper { margin-right: 20px; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.mx_FilePanel .mx_RoomView_MessageList { + width: 100%; } .mx_FilePanel .mx_RoomView_MessageList h2 { diff --git a/res/css/structures/_NotificationPanel.scss b/res/css/structures/_NotificationPanel.scss index 715a94fe2c..2da334b385 100644 --- a/res/css/structures/_NotificationPanel.scss +++ b/res/css/structures/_NotificationPanel.scss @@ -22,7 +22,13 @@ limitations under the License. } .mx_NotificationPanel .mx_RoomView_messageListWrapper { - margin-right: 20px; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.mx_NotificationPanel .mx_RoomView_MessageList { + width: 100%; } .mx_NotificationPanel .mx_RoomView_MessageList h2 { @@ -35,11 +41,32 @@ limitations under the License. .mx_NotificationPanel .mx_EventTile { word-break: break-word; + position: relative; + padding-bottom: 18px; + + &:not(.mx_EventTile_last)::after { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background-color: $tertiary-fg-color; + height: 1px; + opacity: 0.4; + content: ''; + } } .mx_NotificationPanel .mx_EventTile_roomName { font-weight: bold; font-size: $font-14px; + + > * { + vertical-align: middle; + } + + > .mx_BaseAvatar { + margin-right: 8px; + } } .mx_NotificationPanel .mx_EventTile_roomName a { @@ -47,8 +74,7 @@ limitations under the License. } .mx_NotificationPanel .mx_EventTile_avatar { - top: 8px; - left: 0px; + display: none; } .mx_NotificationPanel .mx_EventTile .mx_SenderProfile, @@ -60,8 +86,7 @@ limitations under the License. } .mx_NotificationPanel .mx_EventTile_senderDetails { - padding-left: 32px; - padding-top: 8px; + padding-left: 36px; position: relative; a { @@ -82,7 +107,7 @@ limitations under the License. .mx_NotificationPanel .mx_EventTile_line { margin-right: 0px; - padding-left: 32px; + padding-left: 36px; padding-top: 0px; padding-bottom: 0px; padding-right: 0px; diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 3b60c4e62b..f63f80f470 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -185,13 +185,11 @@ limitations under the License. } .mx_RoomView_empty { - flex: 1 1 auto; font-size: $font-13px; - padding-left: 3em; - padding-right: 3em; - margin-right: 20px; - margin-top: 33%; + padding: 0 24px; + margin-right: 30px; text-align: center; + margin-bottom: 80px; } .mx_RoomView_MessageList { diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index f444fb1f1a..121930b294 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -34,6 +34,7 @@ import * as ObjectUtils from "../../../ObjectUtils"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {E2E_STATE} from "./E2EIcon"; import {toRem} from "../../../utils/units"; +import RoomAvatar from "../avatars/RoomAvatar"; const eventTileTypes = { 'm.room.message': 'messages.MessageEvent', @@ -821,6 +822,7 @@ export default class EventTile extends React.Component { return (
    + { room ? room.name : '' } From 55ceb2abd6278b26b8a7d3cdf30ea0703c85088f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 17 Sep 2020 09:33:05 -0600 Subject: [PATCH 0283/1014] speeeeeeling Co-authored-by: J. Ryan Stinnett --- src/WidgetMessaging.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WidgetMessaging.js b/src/WidgetMessaging.js index 0f8626ec66..9394abf025 100644 --- a/src/WidgetMessaging.js +++ b/src/WidgetMessaging.js @@ -109,7 +109,7 @@ export default class WidgetMessaging { /** * Tells the widget to hang up on its call. - * @returns {Promise<*>} Resolves when teh widget has acknowledged the message. + * @returns {Promise<*>} Resolves when the widget has acknowledged the message. */ hangup() { return this.messageToWidget({ From 14a7b839880761a63aac457664b0aad637cc79a8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 17 Sep 2020 10:40:48 -0600 Subject: [PATCH 0284/1014] Don't show a bottom border ahead of the date separator --- res/css/structures/_NotificationPanel.scss | 2 +- src/components/structures/MessagePanel.js | 13 +++++++++++-- src/components/views/rooms/EventTile.js | 5 +++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/res/css/structures/_NotificationPanel.scss b/res/css/structures/_NotificationPanel.scss index 2da334b385..6366bcaec5 100644 --- a/res/css/structures/_NotificationPanel.scss +++ b/res/css/structures/_NotificationPanel.scss @@ -44,7 +44,7 @@ limitations under the License. position: relative; padding-bottom: 18px; - &:not(.mx_EventTile_last)::after { + &:not(.mx_EventTile_last):not(.mx_EventTile_lastInSection)::after { position: absolute; bottom: 0; left: 0; diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index fe7b20a2d9..e2e3592536 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -518,10 +518,13 @@ export default class MessagePanel extends React.Component { if (!grouper) { const wantTile = this._shouldShowEvent(mxEv); if (wantTile) { + const nextEvent = i < this.props.events.length - 1 + ? this.props.events[i + 1] + : null; // make sure we unpack the array returned by _getTilesForEvent, // otherwise react will auto-generate keys and we will end up // replacing all of the DOM elements every time we paginate. - ret.push(...this._getTilesForEvent(prevEvent, mxEv, last)); + ret.push(...this._getTilesForEvent(prevEvent, mxEv, last, nextEvent)); prevEvent = mxEv; } @@ -537,7 +540,7 @@ export default class MessagePanel extends React.Component { return ret; } - _getTilesForEvent(prevEvent, mxEv, last) { + _getTilesForEvent(prevEvent, mxEv, last, nextEvent) { const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary'); const EventTile = sdk.getComponent('rooms.EventTile'); const DateSeparator = sdk.getComponent('messages.DateSeparator'); @@ -562,6 +565,11 @@ export default class MessagePanel extends React.Component { ret.push(dateSeparator); } + let willWantDateSeparator = false; + if (nextEvent) { + willWantDateSeparator = this._wantsDateSeparator(mxEv, nextEvent.getDate() || new Date()); + } + // is this a continuation of the previous message? const continuation = !wantsDateSeparator && shouldFormContinuation(prevEvent, mxEv); @@ -598,6 +606,7 @@ export default class MessagePanel extends React.Component { isTwelveHour={this.props.isTwelveHour} permalinkCreator={this.props.permalinkCreator} last={last} + lastInSection={willWantDateSeparator} isSelectedEvent={highlight} getRelationsForEvent={this.props.getRelationsForEvent} showReactions={this.props.showReactions} diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 121930b294..a1cc681a4c 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -148,6 +148,10 @@ export default class EventTile extends React.Component { */ last: PropTypes.bool, + // true if the event is the last event in a section (adds a css class for + // targeting) + lastInSection: PropTypes.bool, + /* true if this is search context (which has the effect of greying out * the text */ @@ -674,6 +678,7 @@ export default class EventTile extends React.Component { mx_EventTile_selected: this.props.isSelectedEvent, mx_EventTile_continuation: this.props.tileShape ? '' : this.props.continuation, mx_EventTile_last: this.props.last, + mx_EventTile_lastInSection: this.props.lastInSection, mx_EventTile_contextual: this.props.contextual, mx_EventTile_actionBarFocused: this.state.actionBarFocused, mx_EventTile_verified: !isBubbleMessage && this.state.verified === E2E_STATE.VERIFIED, From 4657a34bbb9715edca6b53e0f398c2443fae7eff Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 17 Sep 2020 10:44:18 -0600 Subject: [PATCH 0285/1014] Document some of the magic values --- res/css/structures/_NotificationPanel.scss | 6 +++--- res/css/structures/_RoomView.scss | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/res/css/structures/_NotificationPanel.scss b/res/css/structures/_NotificationPanel.scss index 6366bcaec5..8282b92bc4 100644 --- a/res/css/structures/_NotificationPanel.scss +++ b/res/css/structures/_NotificationPanel.scss @@ -74,7 +74,7 @@ limitations under the License. } .mx_NotificationPanel .mx_EventTile_avatar { - display: none; + display: none; // we don't need this in this view } .mx_NotificationPanel .mx_EventTile .mx_SenderProfile, @@ -86,7 +86,7 @@ limitations under the License. } .mx_NotificationPanel .mx_EventTile_senderDetails { - padding-left: 36px; + padding-left: 36px; // align with the room name position: relative; a { @@ -107,7 +107,7 @@ limitations under the License. .mx_NotificationPanel .mx_EventTile_line { margin-right: 0px; - padding-left: 36px; + padding-left: 36px; // align with the room name padding-top: 0px; padding-bottom: 0px; padding-right: 0px; diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index f63f80f470..572c7166d2 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -189,7 +189,7 @@ limitations under the License. padding: 0 24px; margin-right: 30px; text-align: center; - margin-bottom: 80px; + margin-bottom: 80px; // visually center the content (intentional offset) } .mx_RoomView_MessageList { From f5f48cbc21bef4526ef967772e7307762702b861 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 17 Sep 2020 10:44:58 -0600 Subject: [PATCH 0286/1014] Fix indentation --- res/css/structures/_NotificationPanel.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/structures/_NotificationPanel.scss b/res/css/structures/_NotificationPanel.scss index 8282b92bc4..1258ace069 100644 --- a/res/css/structures/_NotificationPanel.scss +++ b/res/css/structures/_NotificationPanel.scss @@ -65,7 +65,7 @@ limitations under the License. } > .mx_BaseAvatar { - margin-right: 8px; + margin-right: 8px; } } From 849a5e4a3976b7856e0c1efb998ed375c0a5887f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 17 Sep 2020 14:58:48 -0600 Subject: [PATCH 0287/1014] Round the jitsi pip corners --- res/css/views/rooms/_AppsDrawer.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index b9249d310a..244e88ca3e 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -223,6 +223,7 @@ $MiniAppTileHeight: 200px; height: $MiniAppTileHeight; width: 100%; overflow: hidden; + border-radius: 8px; } .mx_AppTile .mx_AppTileBody, From feaa5f31eabd94cf34db78bd518a1c85ee31f7be Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 17 Sep 2020 15:00:35 -0600 Subject: [PATCH 0288/1014] Match consistency --- src/CallHandler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index e40c97f025..3de1566234 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -318,7 +318,7 @@ function _onAction(payload) { break; case 'hangup_conference': console.info("Leaving conference call in %s", payload.room_id); - _hangupWithCallApp(payload.room_id); + _hangupCallApp(payload.room_id); break; case 'incoming_call': { @@ -450,7 +450,7 @@ function _terminateCallApp(roomId) { }); } -function _hangupWithCallApp(roomId) { +function _hangupCallApp(roomId) { const roomInfo = WidgetStore.instance.getRoom(roomId); if (!roomInfo) return; // "should never happen" clauses go here From 38f8c0a8358e403326a111c6fce9bdaba3dff593 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 17 Sep 2020 22:46:01 -0600 Subject: [PATCH 0289/1014] Disable the e2ee toggle when creating a room on a server with forced e2e Fixes https://github.com/vector-im/element-web/issues/15186 Requires https://github.com/matrix-org/matrix-js-sdk/pull/1470 --- .../views/dialogs/CreateRoomDialog.js | 19 +++++++++++++++++-- src/i18n/strings/en_EN.json | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index 21d48409e8..2b6bb5e187 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -45,7 +45,11 @@ export default class CreateRoomDialog extends React.Component { detailsOpen: false, noFederate: config.default_federate === false, nameIsValid: false, + canChangeEncryption: true, }; + + MatrixClientPeg.get().doesServerForceEncryptionForPreset("private") + .then(isForced => this.setState({canChangeEncryption: !isForced})); } _roomCreateOptions() { @@ -68,7 +72,13 @@ export default class CreateRoomDialog extends React.Component { } if (!this.state.isPublic) { - opts.encryption = this.state.isEncrypted; + if (this.state.canChangeEncryption) { + opts.encryption = this.state.isEncrypted; + } else { + // the server should automatically do this for us, but for safety + // we'll demand it too. + opts.encryption = true; + } } if (CommunityPrototypeStore.instance.getSelectedCommunityId()) { @@ -208,7 +218,11 @@ export default class CreateRoomDialog extends React.Component { if (!this.state.isPublic) { let microcopy; if (privateShouldBeEncrypted()) { - microcopy = _t("You can’t disable this later. Bridges & most bots won’t work yet."); + if (this.state.canChangeEncryption) { + microcopy = _t("You can’t disable this later. Bridges & most bots won’t work yet."); + } else { + microcopy = _t("Your server requires encryption to be enabled in private rooms."); + } } else { microcopy = _t("Your server admin has disabled end-to-end encryption by default " + "in private rooms & Direct Messages."); @@ -219,6 +233,7 @@ export default class CreateRoomDialog extends React.Component { onChange={this.onEncryptedChange} value={this.state.isEncrypted} className='mx_CreateRoomDialog_e2eSwitch' // for end-to-end tests + disabled={!this.state.canChangeEncryption} />

    { microcopy }

    ; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4c2a55d09e..177d02a3e6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1659,6 +1659,7 @@ "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.", "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.", "You can’t disable this later. Bridges & most bots won’t work yet.": "You can’t disable this later. Bridges & most bots won’t work yet.", + "Your server requires encryption to be enabled in private rooms.": "Your server requires encryption to be enabled in private rooms.", "Enable end-to-end encryption": "Enable end-to-end encryption", "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.", "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.", From 3707359ec329d0db5cc6aed8f5b340de6c83d6e5 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Fri, 18 Sep 2020 02:20:26 +0000 Subject: [PATCH 0290/1014] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2376 of 2376 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 0a2c16343d..6bcd94bd9a 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2496,5 +2496,8 @@ "Secure Backup": "安全備份", "End Call": "結束通話", "Remove the group call from the room?": "從聊天室中移除群組通話?", - "You don't have permission to remove the call from the room": "您沒有從聊天室移除通話的權限" + "You don't have permission to remove the call from the room": "您沒有從聊天室移除通話的權限", + "Start a conversation with someone using their name or username (like ).": "使用某人的名字或使用者名稱(如 )以與他們開始對話。", + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "這不會邀請他們加入 %(communityName)s。要邀請某人加入 %(communityName)s,請點擊這裡", + "Invite someone using their name, username (like ) or share this room.": "使用某人的名字、使用者名稱(如 )或分享此聊天室來邀請他們。" } From 72d40b604e762880cf59cdf3c7e7b75a1608836a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Thu, 17 Sep 2020 20:42:10 +0000 Subject: [PATCH 0291/1014] Translated using Weblate (Estonian) Currently translated at 100.0% (2376 of 2376 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 6855c87efb..15fef484e2 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2493,5 +2493,8 @@ "Secure Backup": "Turvaline varundus", "End Call": "Lõpeta kõne", "Remove the group call from the room?": "Kas eemaldame jututoast rühmakõne?", - "You don't have permission to remove the call from the room": "Sinul pole õigusi rühmakõne eemaldamiseks sellest jututoast" + "You don't have permission to remove the call from the room": "Sinul pole õigusi rühmakõne eemaldamiseks sellest jututoast", + "Start a conversation with someone using their name or username (like ).": "Alusta vestlust kasutades teise osapoole nime või kasutajanime (näiteks ).", + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Sellega ei kutsu sa teda %(communityName)s kogukonna liikmeks. %(communityName)s kogukonna kutse saatmiseks klõpsi siin", + "Invite someone using their name, username (like ) or share this room.": "Kutsu kedagi tema nime, kasutajanime (nagu ) alusel või jaga seda jututuba." } From 2c4a4a13a4cf7a1208fc31f96ab3b9c729a153f7 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Thu, 17 Sep 2020 19:11:16 +0000 Subject: [PATCH 0292/1014] Translated using Weblate (Hungarian) Currently translated at 100.0% (2376 of 2376 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 8c797a16b1..0e899ad242 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2493,5 +2493,8 @@ "Secure Backup": "Biztonsági Mentés", "End Call": "Hívás befejezése", "Remove the group call from the room?": "Törlöd a konferenciahívást a szobából?", - "You don't have permission to remove the call from the room": "A konferencia hívás törléséhez nincs jogosultságod" + "You don't have permission to remove the call from the room": "A konferencia hívás törléséhez nincs jogosultságod", + "Start a conversation with someone using their name or username (like ).": "Indíts beszélgetést valakivel és használd hozzá a nevét vagy a felhasználói nevét (mint ).", + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Ez nem hívja meg őket ebbe a közösségbe: %(communityName)s. Hogy meghívj valakit ebbe a közösségbe: %(communityName)s kattints ide", + "Invite someone using their name, username (like ) or share this room.": "Hívj meg valakit a nevével, felhasználói nevével (pl. ) vagy oszd meg ezt a szobát." } From 1a965b1cb704f50ec2ea7ab9494451b3d828120e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 11:15:48 +0100 Subject: [PATCH 0293/1014] UIF 3PID implies UIF Identity Server Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/settings/Settings.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 4a4594b6dc..2a699897a6 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -649,6 +649,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { [UIFeature.IdentityServer]: { supportedLevels: LEVELS_UI_FEATURE, default: true, + // Identity Server (Discovery) Settings make no sense if 3PIDs in general are hidden + controller: new UIFeatureController(UIFeature.ThirdPartyID), }, [UIFeature.ThirdPartyID]: { supportedLevels: LEVELS_UI_FEATURE, From 9dbc1dbc85cb45e7dc92473dfaa6e0ae95fc1b06 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 11:34:35 +0100 Subject: [PATCH 0294/1014] Hide Advanced Appearance Settings Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/settings/tabs/user/AppearanceUserSettingsTab.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index b4c05a2ecb..9f9acd8e3c 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -36,6 +36,7 @@ import EventTilePreview from '../../../elements/EventTilePreview'; import StyledRadioGroup from "../../../elements/StyledRadioGroup"; import classNames from 'classnames'; import { SettingLevel } from "../../../../../settings/SettingLevel"; +import {UIFeature} from "../../../../../settings/UIFeature"; interface IProps { } @@ -386,6 +387,8 @@ export default class AppearanceUserSettingsTab extends React.Component Date: Fri, 18 Sep 2020 12:15:56 +0100 Subject: [PATCH 0295/1014] Fix Search Results Tile undefined variable access regression --- src/components/views/rooms/SearchResultTile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/SearchResultTile.js b/src/components/views/rooms/SearchResultTile.js index 8b2a9c2d61..29def9e368 100644 --- a/src/components/views/rooms/SearchResultTile.js +++ b/src/components/views/rooms/SearchResultTile.js @@ -71,7 +71,7 @@ export default class SearchResultTile extends React.Component { } } return ( -
  • +
  • { ret }
  • ); } From 780aea1a3653a6c56b392efe3832f1233413c0d8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 12:32:33 +0100 Subject: [PATCH 0296/1014] Mac sends lowercase event.key even when holding Shift unlike Windows --- src/components/structures/RoomView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index f568f31dbd..59a5efda91 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -672,6 +672,7 @@ export default class RoomView extends React.Component { handled = true; } break; + case Key.U: // Mac returns lowercase case Key.U.toUpperCase(): if (isOnlyCtrlOrCmdIgnoreShiftKeyEvent(ev) && ev.shiftKey) { dis.dispatch({ action: "upload_file" }); From d267092b192d85934d424a30897c0ed2663ac06e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 12:33:25 +0100 Subject: [PATCH 0297/1014] Make the upload_file dispatch synchronous to make Firefox happy about it --- src/components/structures/RoomView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 59a5efda91..4c418e9994 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -675,7 +675,7 @@ export default class RoomView extends React.Component { case Key.U: // Mac returns lowercase case Key.U.toUpperCase(): if (isOnlyCtrlOrCmdIgnoreShiftKeyEvent(ev) && ev.shiftKey) { - dis.dispatch({ action: "upload_file" }); + dis.dispatch({ action: "upload_file" }, true); handled = true; } break; From adcb75facbefec580207bc976cd652e908e73348 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 14:34:51 +0100 Subject: [PATCH 0298/1014] Only show User Info verify button if the other user has e2ee devices Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/right_panel/UserInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 3171890955..a02b53b413 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -1306,7 +1306,7 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { const showDeviceListSpinner = devices === undefined; if (canVerify) { - if (hasCrossSigningKeys !== undefined) { + if (hasCrossSigningKeys !== undefined && devices.length > 0) { // Note: mx_UserInfo_verifyButton is for the end-to-end tests verifyButton = ( { From e3f7860f30af6faa05b6d5f0c783f7082b02274e Mon Sep 17 00:00:00 2001 From: XoseM Date: Fri, 18 Sep 2020 13:01:03 +0000 Subject: [PATCH 0299/1014] Translated using Weblate (Galician) Currently translated at 100.0% (2368 of 2368 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 82c3453dc5..51f39758e1 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2490,5 +2490,14 @@ "Secret storage:": "Almacenaxe segreda:", "ready": "lista", "not ready": "non lista", - "Secure Backup": "Copia Segura" + "Secure Backup": "Copia Segura", + "End Call": "Finalizar chamada", + "Remove the group call from the room?": "Eliminar a chamada en grupo da sala?", + "You don't have permission to remove the call from the room": "Non tes permiso para eliminar a chamada da sala", + "Safeguard against losing access to encrypted messages & data": "Protéxete de perder o acceso a mensaxes e datos cifrados", + "not found in storage": "non atopado no almacenaxe", + "Start a conversation with someone using their name or username (like ).": "Inicia unha conversa con alguén usando o seu nome ou nome de usuaria (como ).", + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Esto non as convidará a %(communityName)s. Para convidar alguén a %(communityName)s, preme aquí", + "Invite someone using their name, username (like ) or share this room.": "Convida a alguén usando o seu nome, nome de usuaria (como ) ou comparte esta sala.", + "Unable to set up keys": "Non se puideron configurar as chaves" } From 004c68b394e04d9e39a106079cee08bded7290fc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 15:43:08 +0100 Subject: [PATCH 0300/1014] Fix Room Directory View & Preview actions for federated joins Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomDirectory.js | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index 44652e5073..55c6527f06 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -392,22 +392,12 @@ export default class RoomDirectory extends React.Component { }; onPreviewClick = (ev, room) => { - this.props.onFinished(); - dis.dispatch({ - action: 'view_room', - room_id: room.room_id, - should_peek: true, - }); + this.showRoom(room, null, false, true); ev.stopPropagation(); }; onViewClick = (ev, room) => { - this.props.onFinished(); - dis.dispatch({ - action: 'view_room', - room_id: room.room_id, - should_peek: false, - }); + this.showRoom(room); ev.stopPropagation(); }; @@ -428,11 +418,12 @@ export default class RoomDirectory extends React.Component { this.showRoom(null, alias, autoJoin); } - showRoom(room, room_alias, autoJoin=false) { + showRoom(room, room_alias, autoJoin = false, shouldPeek = false) { this.props.onFinished(); const payload = { action: 'view_room', auto_join: autoJoin, + should_peek: shouldPeek, }; if (room) { // Don't let the user view a room they won't be able to either @@ -457,6 +448,7 @@ export default class RoomDirectory extends React.Component { }; if (this.state.roomServer) { + payload.via_servers = [this.state.roomServer]; payload.opts = { viaServers: [this.state.roomServer], }; From 949b8d9afe39e60ab264d71f4e3ef2959996fefc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 16:22:35 +0100 Subject: [PATCH 0301/1014] Rename apps back to widgets Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/elements/AppTile.js | 4 ++-- src/components/views/right_panel/RoomSummaryCard.tsx | 4 ++-- src/components/views/right_panel/WidgetCard.tsx | 2 +- src/i18n/strings/en_EN.json | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 79a88ed43d..6aaeab060f 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -863,13 +863,13 @@ export default class AppTile extends React.Component { { /* Minimise widget */ } { showMinimiseButton && } { /* Maximise widget */ } { showMaximiseButton && } { /* Title */ } diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 9d20dc1fe1..95b159deed 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -99,7 +99,7 @@ const AppsSection: React.FC = ({ room }) => { } }; - return + return { apps.map(app => { const name = WidgetUtils.getWidgetName(app); const dataTitle = WidgetUtils.getWidgetDataTitle(app); @@ -161,7 +161,7 @@ const AppsSection: React.FC = ({ room }) => { }) } - { apps.length > 0 ? _t("Edit apps, bridges & bots") : _t("Add apps, bridges & bots") } + { apps.length > 0 ? _t("Edit widgets, bridges & bots") : _t("Add widgets, bridges & bots") } ; }; diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx index dec30a57f2..1677494708 100644 --- a/src/components/views/right_panel/WidgetCard.tsx +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -152,7 +152,7 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { ; } else { pinButton = Date: Fri, 18 Sep 2020 17:13:45 +0100 Subject: [PATCH 0302/1014] Fix New Room List arrow key management Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LeftPanel.tsx | 2 +- src/components/structures/RoomSearch.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 1c2295384c..090a64904c 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -52,7 +52,7 @@ interface IState { // List of CSS classes which should be included in keyboard navigation within the room list const cssClasses = [ "mx_RoomSearch_input", - "mx_RoomSearch_icon", // minimized + "mx_RoomSearch_minimizedHandle", // minimized "mx_RoomSublist_headerText", "mx_RoomTile", "mx_RoomSublist_showNButton", diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 768bc38d23..526aecddd7 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -165,7 +165,7 @@ export default class RoomSearch extends React.PureComponent { icon = ( ); From 8838bd724b1a23805e61e92565c1992ddc7c88a8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 18 Sep 2020 10:58:17 -0600 Subject: [PATCH 0303/1014] Update copy for files --- src/components/views/elements/DesktopBuildsNotice.tsx | 4 ++-- src/i18n/strings/en_EN.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/views/elements/DesktopBuildsNotice.tsx b/src/components/views/elements/DesktopBuildsNotice.tsx index cc5b9174d1..fd1c7848aa 100644 --- a/src/components/views/elements/DesktopBuildsNotice.tsx +++ b/src/components/views/elements/DesktopBuildsNotice.tsx @@ -41,7 +41,7 @@ export default function DesktopBuildsNotice({isRoomEncrypted, kind}: IProps) { logo = ; switch (kind) { case WarningKind.Files: - text = _t("Use the Desktop app to see encrypted files", {}, { + text = _t("Use the Desktop app to see all encrypted files", {}, { a: sub => ({sub}), }); break; @@ -54,7 +54,7 @@ export default function DesktopBuildsNotice({isRoomEncrypted, kind}: IProps) { } else { switch (kind) { case WarningKind.Files: - text = _t("This version of %(brand)s does not support viewing encrypted files", {brand}); + text = _t("This version of %(brand)s does not support viewing some encrypted files", {brand}); break; case WarningKind.Search: text = _t("This version of %(brand)s does not support searching encrypted messages", {brand}); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 024809f214..01fd172879 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1487,9 +1487,9 @@ "Maximize apps": "Maximize apps", "Popout widget": "Popout widget", "More options": "More options", - "Use the Desktop app to see encrypted files": "Use the Desktop app to see encrypted files", + "Use the Desktop app to see all encrypted files": "Use the Desktop app to see all encrypted files", "Use the Desktop app to search encrypted messages": "Use the Desktop app to search encrypted messages", - "This version of %(brand)s does not support viewing encrypted files": "This version of %(brand)s does not support viewing encrypted files", + "This version of %(brand)s does not support viewing some encrypted files": "This version of %(brand)s does not support viewing some encrypted files", "This version of %(brand)s does not support searching encrypted messages": "This version of %(brand)s does not support searching encrypted messages", "Join": "Join", "No results": "No results", From 7e0b5534e68d78f22f779b9771c3f7c4b3ea6ad8 Mon Sep 17 00:00:00 2001 From: Marcelo Filho Date: Fri, 18 Sep 2020 14:12:02 +0000 Subject: [PATCH 0304/1014] Translated using Weblate (Portuguese (Brazil)) Currently translated at 94.1% (2229 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/pt_BR/ --- src/i18n/strings/pt_BR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index 975281aa00..3276952d28 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -866,7 +866,7 @@ "Submit debug logs": "Submeter registros de depuração", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Os registros de depuração contêm dados de uso do aplicativo, incluindo seu nome de usuário, os IDs ou aliases das salas ou comunidades que você visitou e os nomes de usuários de outros usuários. Eles não contêm mensagens.", "Before submitting logs, you must create a GitHub issue to describe your problem.": "Antes de enviar os registros, você deve criar um bilhete de erro no GitHub para descrever seu problema.", - "Unable to load commit detail: %(msg)s": "Não é possível carregar os detalhes do commit: %(msg)s", + "Unable to load commit detail: %(msg)s": "Não foi possível carregar os detalhes do envio: %(msg)s", "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this": "Para evitar perder seu histórico de bate-papo, você precisa exportar as chaves da sua sala antes de se desconectar. Quando entrar novamente, você precisará usar a versão mais atual do %(brand)s", "Incompatible Database": "Banco de dados incompatível", "Continue With Encryption Disabled": "Continuar com criptografia desativada", From 6f7d6f27f1bf1f3cfcc73179a21553b4c76cbd1a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 18:15:05 +0100 Subject: [PATCH 0305/1014] move the check Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/right_panel/UserInfo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index a02b53b413..a9aebd9b33 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -1296,7 +1296,7 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { const userTrust = cryptoEnabled && cli.checkUserTrust(member.userId); const userVerified = cryptoEnabled && userTrust.isCrossSigningVerified(); const isMe = member.userId === cli.getUserId(); - const canVerify = cryptoEnabled && homeserverSupportsCrossSigning && !userVerified && !isMe; + const canVerify = cryptoEnabled && homeserverSupportsCrossSigning && !userVerified && !isMe && devices.length > 0; const setUpdating = (updating) => { setPendingUpdateCount(count => count + (updating ? 1 : -1)); @@ -1306,7 +1306,7 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { const showDeviceListSpinner = devices === undefined; if (canVerify) { - if (hasCrossSigningKeys !== undefined && devices.length > 0) { + if (hasCrossSigningKeys !== undefined) { // Note: mx_UserInfo_verifyButton is for the end-to-end tests verifyButton = ( { From 5630f3571533d6e1b9f86f40f8a3c3ec4029d94d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 18 Sep 2020 11:33:02 -0600 Subject: [PATCH 0306/1014] Add a UI feature to disable advanced encryption options --- .../views/settings/E2eAdvancedPanel.js | 5 ++ .../tabs/room/SecurityRoomSettingsTab.js | 12 +++-- .../tabs/user/SecurityUserSettingsTab.js | 35 ++++++++---- src/settings/Settings.ts | 18 +++++-- src/settings/UIFeature.ts | 1 + .../controllers/OrderedMultiController.ts | 54 +++++++++++++++++++ 6 files changed, 108 insertions(+), 17 deletions(-) create mode 100644 src/settings/controllers/OrderedMultiController.ts diff --git a/src/components/views/settings/E2eAdvancedPanel.js b/src/components/views/settings/E2eAdvancedPanel.js index 0650630901..a8764fa855 100644 --- a/src/components/views/settings/E2eAdvancedPanel.js +++ b/src/components/views/settings/E2eAdvancedPanel.js @@ -19,6 +19,7 @@ import React from 'react'; import * as sdk from '../../../index'; import {_t} from "../../../languageHandler"; import {SettingLevel} from "../../../settings/SettingLevel"; +import SettingsStore from "../../../settings/SettingsStore"; const SETTING_MANUALLY_VERIFY_ALL_SESSIONS = "e2ee.manuallyVerifyAllSessions"; @@ -37,3 +38,7 @@ const E2eAdvancedPanel = props => { }; export default E2eAdvancedPanel; + +export function isE2eAdvancedPanelPossible(): boolean { + return SettingsStore.isEnabled(SETTING_MANUALLY_VERIFY_ALL_SESSIONS); +} diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js index 48115146f1..0a0c693158 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js @@ -24,6 +24,7 @@ import Modal from "../../../../../Modal"; import QuestionDialog from "../../../dialogs/QuestionDialog"; import StyledRadioGroup from '../../../elements/StyledRadioGroup'; import {SettingLevel} from "../../../../../settings/SettingLevel"; +import SettingsStore from "../../../../../settings/SettingsStore"; export default class SecurityRoomSettingsTab extends React.Component { static propTypes = { @@ -340,10 +341,13 @@ export default class SecurityRoomSettingsTab extends React.Component { const canEnableEncryption = !isEncrypted && hasEncryptionPermission; let encryptionSettings = null; - if (isEncrypted) { - encryptionSettings = ; + if (isEncrypted && SettingsStore.isEnabled("blacklistUnverifiedDevices")) { + encryptionSettings = ; } return ( diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index 9984baeb13..61402e8881 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -32,6 +32,7 @@ import {SettingLevel} from "../../../../../settings/SettingLevel"; import SecureBackupPanel from "../../SecureBackupPanel"; import SettingsStore from "../../../../../settings/SettingsStore"; import {UIFeature} from "../../../../../settings/UIFeature"; +import {isE2eAdvancedPanelPossible} from "../../E2eAdvancedPanel"; export class IgnoredUser extends React.Component { static propTypes = { @@ -219,6 +220,15 @@ export default class SecurityUserSettingsTab extends React.Component { ); } + let noSendUnverifiedSetting; + if (SettingsStore.isEnabled("blacklistUnverifiedDevices")) { + noSendUnverifiedSetting = ; + } + return (
    {_t("Cryptography")} @@ -233,8 +243,7 @@ export default class SecurityUserSettingsTab extends React.Component { {importExportButtons} - + {noSendUnverifiedSetting}
    ); } @@ -355,14 +364,20 @@ export default class SecurityUserSettingsTab extends React.Component { const E2eAdvancedPanel = sdk.getComponent('views.settings.E2eAdvancedPanel'); let advancedSection; if (SettingsStore.getValue(UIFeature.AdvancedSettings)) { - advancedSection = <> -
    {_t("Advanced")}
    -
    - {this._renderIgnoredUsers()} - {this._renderManageInvites()} - -
    - ; + const ignoreUsersPanel = this._renderIgnoredUsers(); + const invitesPanel = this._renderManageInvites(); + const e2ePanel = isE2eAdvancedPanelPossible() ? : null; + // only show the section if there's something to show + if (ignoreUsersPanel || invitesPanel || e2ePanel) { + advancedSection = <> +
    {_t("Advanced")}
    +
    + {ignoreUsersPanel} + {invitesPanel} + {e2ePanel} +
    + ; + } } return ( diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 16366aaa01..737c882919 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -34,6 +34,7 @@ import SettingController from "./controllers/SettingController"; import { RightPanelPhases } from "../stores/RightPanelStorePhases"; import UIFeatureController from "./controllers/UIFeatureController"; import { UIFeature } from "./UIFeature"; +import { OrderedMultiController } from "./controllers/OrderedMultiController"; // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times const LEVELS_ROOM_SETTINGS = [ @@ -436,6 +437,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { "room-device": _td('Never send encrypted messages to unverified sessions in this room from this session'), }, default: false, + controller: new UIFeatureController(UIFeature.AdvancedEncryption), }, "urlPreviewsEnabled": { supportedLevels: LEVELS_ROOM_SETTINGS_WITH_ROOM, @@ -591,9 +593,15 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, displayName: _td("Manually verify all remote sessions"), default: false, - controller: new PushToMatrixClientController( - MatrixClient.prototype.setCryptoTrustCrossSignedDevices, true, - ), + controller: new OrderedMultiController([ + // Apply the feature controller first to ensure that the setting doesn't + // show up and can't be toggled. PushToMatrixClientController doesn't + // do any overrides anyways. + new UIFeatureController(UIFeature.AdvancedEncryption), + new PushToMatrixClientController( + MatrixClient.prototype.setCryptoTrustCrossSignedDevices, true, + ), + ]), }, "ircDisplayNameWidth": { // We specifically want to have room-device > device so that users may set a device default @@ -612,6 +620,10 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_ROOM_OR_ACCOUNT, default: {}, }, + [UIFeature.AdvancedEncryption]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, [UIFeature.URLPreviews]: { supportedLevels: LEVELS_UI_FEATURE, default: true, diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 57aefa3a78..231752e19c 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -16,6 +16,7 @@ limitations under the License. // see settings.md for documentation on conventions export enum UIFeature { + AdvancedEncryption = "UIFeature.advancedEncryption", URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", Voip = "UIFeature.voip", diff --git a/src/settings/controllers/OrderedMultiController.ts b/src/settings/controllers/OrderedMultiController.ts new file mode 100644 index 0000000000..2f093fe25a --- /dev/null +++ b/src/settings/controllers/OrderedMultiController.ts @@ -0,0 +1,54 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import SettingController from "./SettingController"; +import { SettingLevel } from "../SettingLevel"; + +/** + * Allows for multiple controllers to affect a setting. The first controller + * provided to this class which overrides the setting value will affect + * the value - other controllers are not called. Change notification handlers + * are proxied through to all controllers. + * + * Similarly, the first controller which indicates that a setting is disabled + * will be used - other controllers will not be considered. + */ +export class OrderedMultiController extends SettingController { + constructor(public readonly controllers: SettingController[]) { + super(); + } + + public getValueOverride(level: SettingLevel, roomId: string, calculatedValue: any, calculatedAtLevel: SettingLevel): any { + for (const controller of this.controllers) { + const override = controller.getValueOverride(level, roomId, calculatedValue, calculatedAtLevel); + if (override !== undefined && override !== null) return override; + } + return null; // no override + } + + public onChange(level: SettingLevel, roomId: string, newValue: any) { + for (const controller of this.controllers) { + controller.onChange(level, roomId, newValue); + } + } + + public get settingDisabled(): boolean { + for (const controller of this.controllers) { + if (controller.settingDisabled) return true; + } + return false; + } +} From a90cf46fefc7bb162bdb0d25c05367fb2ef701a9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 18 Sep 2020 11:39:41 -0600 Subject: [PATCH 0307/1014] Appease the linter --- src/settings/controllers/OrderedMultiController.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/settings/controllers/OrderedMultiController.ts b/src/settings/controllers/OrderedMultiController.ts index 2f093fe25a..fb94d6d7ef 100644 --- a/src/settings/controllers/OrderedMultiController.ts +++ b/src/settings/controllers/OrderedMultiController.ts @@ -31,7 +31,12 @@ export class OrderedMultiController extends SettingController { super(); } - public getValueOverride(level: SettingLevel, roomId: string, calculatedValue: any, calculatedAtLevel: SettingLevel): any { + public getValueOverride( + level: SettingLevel, + roomId: string, + calculatedValue: any, + calculatedAtLevel: SettingLevel, + ): any { for (const controller of this.controllers) { const override = controller.getValueOverride(level, roomId, calculatedValue, calculatedAtLevel); if (override !== undefined && override !== null) return override; From cf7b4dd3113577a4717e7c8de57928accd8eea04 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 19:09:45 +0100 Subject: [PATCH 0308/1014] Add isEnabled comment Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/settings/SettingsStore.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 498a2d269d..85d5e1702b 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -257,6 +257,12 @@ export default class SettingsStore { return SETTINGS[settingName].isFeature; } + /** + * Determines if a setting is enabled. + * If a setting is disabled then it should be hidden from the user. + * @param {string} settingName The setting to look up. + * @return {boolean} True if the setting is enabled. + */ public static isEnabled(settingName: string) { if (!SETTINGS[settingName]) return false; return SETTINGS[settingName].controller ? !SETTINGS[settingName].controller.settingDisabled : true; From 8e9ff05762f9176b66168fc17dea68f01f0ced96 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 19:14:24 +0100 Subject: [PATCH 0309/1014] Update src/settings/SettingsStore.ts Co-authored-by: Travis Ralston --- src/settings/SettingsStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 85d5e1702b..7c05e4b500 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -263,7 +263,7 @@ export default class SettingsStore { * @param {string} settingName The setting to look up. * @return {boolean} True if the setting is enabled. */ - public static isEnabled(settingName: string) { + public static isEnabled(settingName: string): boolean { if (!SETTINGS[settingName]) return false; return SETTINGS[settingName].controller ? !SETTINGS[settingName].controller.settingDisabled : true; } From 1574dd551020ef370324cca5f427453f8e86e68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Fri, 18 Sep 2020 18:23:18 +0000 Subject: [PATCH 0310/1014] Translated using Weblate (Estonian) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 15fef484e2..2cba9908d1 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2496,5 +2496,15 @@ "You don't have permission to remove the call from the room": "Sinul pole õigusi rühmakõne eemaldamiseks sellest jututoast", "Start a conversation with someone using their name or username (like ).": "Alusta vestlust kasutades teise osapoole nime või kasutajanime (näiteks ).", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Sellega ei kutsu sa teda %(communityName)s kogukonna liikmeks. %(communityName)s kogukonna kutse saatmiseks klõpsi siin", - "Invite someone using their name, username (like ) or share this room.": "Kutsu kedagi tema nime, kasutajanime (nagu ) alusel või jaga seda jututuba." + "Invite someone using their name, username (like ) or share this room.": "Kutsu kedagi tema nime, kasutajanime (nagu ) alusel või jaga seda jututuba.", + "Safeguard against losing access to encrypted messages & data": "Hoia ära, et kaotad ligipääsu krüptitud sõnumitele ja andmetele", + "not found in storage": "ei leidunud turvahoidlas", + "Widgets": "Vidinad", + "Edit widgets, bridges & bots": "Muuda vidinaid, võrgusildu ja roboteid", + "Add widgets, bridges & bots": "Lisa vidinaid, võrgusildu ja roboteid", + "You can only pin 2 widgets at a time": "Korraga saavad kinniklammerdatud olla vaid 2 vidinat", + "Minimize widget": "Vähenda vidinat", + "Maximize widget": "Suurenda vidinat", + "Your server requires encryption to be enabled in private rooms.": "Sinu koduserveri seadistused eeldavad, et mitteavalikud jututoad asutavad läbivat krüptimist.", + "Unable to set up keys": "Krüptovõtmete kasutuselevõtmine ei õnnestu" } From c05dc45eb50dd48a65a995e593d49b7ff2fd367e Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Fri, 18 Sep 2020 19:41:34 +0000 Subject: [PATCH 0311/1014] Translated using Weblate (Russian) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 0827b920b2..8ac246af4b 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2489,5 +2489,18 @@ "Group call ended by %(senderName)s": "%(senderName)s завершил(а) групповой вызов", "End Call": "Завершить звонок", "Remove the group call from the room?": "Удалить групповой вызов из комнаты?", - "You don't have permission to remove the call from the room": "У вас нет разрешения на удаление звонка из комнаты" + "You don't have permission to remove the call from the room": "У вас нет разрешения на удаление звонка из комнаты", + "Safeguard against losing access to encrypted messages & data": "Защита от потери доступа к зашифрованным сообщениям и данным", + "not found in storage": "не найдено в хранилище", + "Widgets": "Виджеты", + "Edit widgets, bridges & bots": "Редактировать виджеты, мосты и ботов", + "Add widgets, bridges & bots": "Добавить виджеты, мосты и ботов", + "You can only pin 2 widgets at a time": "Вы можете закрепить только 2 виджета за раз", + "Minimize widget": "Свернуть виджет", + "Maximize widget": "Развернуть виджет", + "Your server requires encryption to be enabled in private rooms.": "Вашему серверу необходимо включить шифрование в приватных комнатах.", + "Start a conversation with someone using their name or username (like ).": "Начните разговор с кем-нибудь, используя его имя или имя пользователя (например, ).", + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Это не пригласит их в %(communityName)s. Чтобы пригласить кого-нибудь в %(communityName)s, нажмите здесь", + "Invite someone using their name, username (like ) or share this room.": "Пригласите кого-нибудь, используя его имя, имя пользователя (например, ) или поделитесь этой комнатой.", + "Unable to set up keys": "Невозможно настроить ключи" } From 26b18811ce9bfa2e86e743c221ae79be81238d26 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 18 Sep 2020 16:04:19 -0600 Subject: [PATCH 0312/1014] Add some permission checks to the communities v2 prototype Prototype behaviour: * If you can't create a room in the community, say so. * The UX for this could probably be improved, but for now the intention is to not break muscle memory by hiding the create room option. * If you can't change settings in the community, or can't invite people, don't show those respective options. * Breaking muscle memory here is moderately okay. --- src/components/structures/MatrixChat.tsx | 14 ++++++++++++ src/components/structures/UserMenu.tsx | 27 ++++++++++++++++++------ src/i18n/strings/en_EN.json | 2 ++ src/stores/CommunityPrototypeStore.ts | 24 +++++++++++++++++++-- 4 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index ea1f424af6..1fdb96a971 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -80,6 +80,8 @@ import { leaveRoomBehaviour } from "../../utils/membership"; import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog"; import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore"; import {UIFeature} from "../../settings/UIFeature"; +import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore"; +import GroupStore from "../../stores/GroupStore"; /** constants for MatrixChat.state.view */ export enum Views { @@ -1016,6 +1018,18 @@ export default class MatrixChat extends React.PureComponent { } private async createRoom(defaultPublic = false) { + const communityId = CommunityPrototypeStore.instance.getSelectedCommunityId(); + if (communityId) { + // double check the user will have permission to associate this room with the community + if (CommunityPrototypeStore.instance.isAdminOf(communityId)) { + Modal.createTrackedDialog('Pre-failure to create room', '', ErrorDialog, { + title: _t("Cannot create rooms in this community"), + description: _t("You do not have permission to create rooms in this community."), + }); + return; + } + } + const CreateRoomDialog = sdk.getComponent('dialogs.CreateRoomDialog'); const modal = Modal.createTrackedDialog('Create Room', '', CreateRoomDialog, { defaultPublic }); diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 369d3b7720..17523290b9 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -343,6 +343,7 @@ export default class UserMenu extends React.Component { let secondarySection = null; if (prototypeCommunityName) { + const communityId = CommunityPrototypeStore.instance.getSelectedCommunityId(); primaryHeader = (
    @@ -350,24 +351,36 @@ export default class UserMenu extends React.Component {
    ); - primaryOptionList = ( - + let settingsOption; + let inviteOption; + if (CommunityPrototypeStore.instance.canInviteTo(communityId)) { + inviteOption = ( + + ); + } + if (CommunityPrototypeStore.instance.isAdminOf(communityId)) { + settingsOption = ( + ); + } + primaryOptionList = ( + + {settingsOption} - + {inviteOption} ); secondarySection = ( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d7360430ae..0cd0c6bc7b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2057,6 +2057,8 @@ "Create a Group Chat": "Create a Group Chat", "Explore rooms": "Explore rooms", "Failed to reject invitation": "Failed to reject invitation", + "Cannot create rooms in this community": "Cannot create rooms in this community", + "You do not have permission to create rooms in this community.": "You do not have permission to create rooms in this community.", "This room is not public. You will not be able to rejoin without an invite.": "This room is not public. You will not be able to rejoin without an invite.", "Are you sure you want to leave the room '%(roomName)s'?": "Are you sure you want to leave the room '%(roomName)s'?", "Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s", diff --git a/src/stores/CommunityPrototypeStore.ts b/src/stores/CommunityPrototypeStore.ts index db747d105c..4ff859d4fe 100644 --- a/src/stores/CommunityPrototypeStore.ts +++ b/src/stores/CommunityPrototypeStore.ts @@ -24,9 +24,9 @@ import * as utils from "matrix-js-sdk/src/utils"; import { UPDATE_EVENT } from "./AsyncStore"; import FlairStore from "./FlairStore"; import TagOrderStore from "./TagOrderStore"; -import { MatrixClientPeg } from "../MatrixClientPeg"; import GroupStore from "./GroupStore"; import dis from "../dispatcher/dispatcher"; +import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; interface IState { // nothing of value - we use account data @@ -77,7 +77,7 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient { public getGeneralChat(communityId: string): Room { const rooms = GroupStore.getGroupRooms(communityId) - .map(r => MatrixClientPeg.get().getRoom(r.roomId)) + .map(r => this.matrixClient.getRoom(r.roomId)) .filter(r => !!r); let chat = rooms.find(r => { const idState = r.currentState.getStateEvents("im.vector.general_chat", ""); @@ -88,6 +88,26 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient { return chat; // can be null } + public isAdminOf(communityId: string): boolean { + const members = GroupStore.getGroupMembers(communityId); + const myMember = members.find(m => m.userId === this.matrixClient.getUserId()); + return myMember?.isPrivileged; + } + + public canInviteTo(communityId: string): boolean { + const generalChat = this.getGeneralChat(communityId); + if (!generalChat) return this.isAdminOf(communityId); + + const myMember = generalChat.getMember(this.matrixClient.getUserId()); + if (!myMember) return this.isAdminOf(communityId); + + const pl = generalChat.currentState.getStateEvents("m.room.power_levels", ""); + if (!pl) return this.isAdminOf(communityId); + + const invitePl = isNullOrUndefined(pl.invite) ? 50 : Number(pl.invite); + return invitePl <= myMember.powerLevel; + } + protected async onAction(payload: ActionPayload): Promise { if (!this.matrixClient || !SettingsStore.getValue("feature_communities_v2_prototypes")) { return; From b1b7215532596acac7fe17fd83f65ca317dd8e7d Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 18 Sep 2020 18:08:17 -0400 Subject: [PATCH 0313/1014] fix lint and merge issues --- src/Login.js | 6 +++--- src/SecurityManager.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Login.js b/src/Login.js index 0563952c5d..c04b086afa 100644 --- a/src/Login.js +++ b/src/Login.js @@ -25,7 +25,7 @@ import { cacheDehydrationKey, confirmToDismiss, getDehydrationKeyCache, -} from "./CrossSigningManager"; +} from "./SecurityManager"; import Matrix from "matrix-js-sdk"; import { deriveKey } from 'matrix-js-sdk/src/crypto/key_passphrase'; import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey'; @@ -173,8 +173,8 @@ export async function sendLoginRequest(hsUrl, isUrl, loginType, loginParams) { baseUrl: hsUrl, idBaseUrl: isUrl, cryptoCallbacks: { - getDehydrationKey - } + getDehydrationKey, + }, }); const data = await client.loginWithRehydration(null, loginType, loginParams); diff --git a/src/SecurityManager.js b/src/SecurityManager.js index e8bd63d2ff..967c0cc266 100644 --- a/src/SecurityManager.js +++ b/src/SecurityManager.js @@ -91,7 +91,7 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { // if we dehydrated a device, see if that key works for SSSS if (dehydrationInfo.key) { try { - if (await MatrixClientPeg.get().checkSecretStorageKey(dehydrationInfo.key, info)) { + if (await MatrixClientPeg.get().checkSecretStorageKey(dehydrationInfo.key, keyInfo)) { const key = dehydrationInfo.key; // Save to cache to avoid future prompts in the current session if (isCachingAllowed()) { From d4c14b33997ddf524c20f8dee34be8eb009b3c79 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 18 Sep 2020 16:11:18 -0600 Subject: [PATCH 0314/1014] Don't import things we don't use --- src/components/structures/MatrixChat.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 1fdb96a971..3f4b3115af 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -81,7 +81,6 @@ import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityProt import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore"; import {UIFeature} from "../../settings/UIFeature"; import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore"; -import GroupStore from "../../stores/GroupStore"; /** constants for MatrixChat.state.view */ export enum Views { From 4e2397a79db8242f6ff04f9b5c5e61693d21533c Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 18 Sep 2020 20:53:39 -0400 Subject: [PATCH 0315/1014] doc fixes and minor code improvement --- src/MatrixClientPeg.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index a5fa0fb3cf..84bc610896 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -271,11 +271,12 @@ class _MatrixClientPeg implements IMatrixClientPeg { if (creds.olmAccount) { console.log("got a dehydrated account"); + const pickleKey = creds.pickleKey || "DEFAULT_KEY"; opts.deviceToImport = { olmDevice: { - pickledAccount: creds.olmAccount.pickle(creds.pickleKey || "DEFAULT_KEY"), + pickledAccount: creds.olmAccount.pickle(pickleKey), sessions: [], - pickleKey: creds.pickleKey || "DEFAULT_KEY", + pickleKey: pickleKey, }, userId: creds.userId, deviceId: creds.deviceId, @@ -293,7 +294,7 @@ class _MatrixClientPeg implements IMatrixClientPeg { // set dehydration key after cross-signing gets set up -- we wait until // cross-signing is set up because we want to cross-sign the dehydrated - // key + // device const origGetSecretStorageKey = opts.cryptoCallbacks.getSecretStorageKey opts.cryptoCallbacks.getSecretStorageKey = async (keyinfo, ssssItemName) => { const [name, key] = await origGetSecretStorageKey(keyinfo, ssssItemName); @@ -302,6 +303,8 @@ class _MatrixClientPeg implements IMatrixClientPeg { } if (creds.rehydrationKey) { + // cache the key so that the SSSS prompt tries using it without + // prompting the user cacheDehydrationKey(creds.rehydrationKey, creds.rehydrationKeyInfo); } From 590c24ab3737c151b29ff631698fe2f4571da10e Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Sat, 19 Sep 2020 11:51:37 +0000 Subject: [PATCH 0316/1014] Translated using Weblate (Albanian) Currently translated at 99.7% (2362 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sq/ --- src/i18n/strings/sq.json | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index 397013f9b7..9d44131ed0 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -2472,5 +2472,33 @@ "Failed to find the general chat for this community": "S’u arrit të gjendej fjalosja e përgjithshme për këtë bashkësi", "Community settings": "Rregullime bashkësie", "User settings": "Rregullime përdoruesi", - "Community and user menu": "Menu bashkësie dhe përdoruesish" + "Community and user menu": "Menu bashkësie dhe përdoruesish", + "End Call": "Përfundoje Thirrjen", + "Remove the group call from the room?": "Të hiqet nga dhoma thirrja e grupit?", + "You don't have permission to remove the call from the room": "S’keni leje të hiqni thirrjen nga dhoma", + "Group call modified by %(senderName)s": "Thirrja e grupit u modifikua nga %(senderName)s", + "Group call started by %(senderName)s": "Thirrje grupi e nisur nga %(senderName)s", + "Group call ended by %(senderName)s": "Thirrje grupi e përfunduar nga %(senderName)s", + "Safeguard against losing access to encrypted messages & data": "Mbrohuni nga humbja e hyrjes te mesazhe & të dhëna të fshehtëzuara", + "not found in storage": "s’u gjet në depozitë", + "Backup version:": "Version kopjeruajtjeje:", + "Algorithm:": "Algoritëm:", + "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Kopjeruani kyçet tuaj të fshehtëzimit me të dhënat e llogarisë tuaj, për rastin kur humbni hyrje te sesionet tuaj. Kyçet tuaj do të sigurohen me një Kyç unik Rimarrjesh.", + "Backup key stored:": "Kyç kopjeruajtjesh i depozituar:", + "Backup key cached:": "Kyç kopjeruajtjesh i ruajtur në fshehtinë:", + "Secret storage:": "Depozitë e fshehtë:", + "ready": "gati", + "not ready": "jo gati", + "Secure Backup": "Kopjeruajtje e Sigurt", + "Widgets": "Widget-e", + "Edit widgets, bridges & bots": "Përpunoni widget-e, ura & robotë", + "Add widgets, bridges & bots": "Shtoni widget-e, ura & robotë", + "You can only pin 2 widgets at a time": "Mundeni të fiksoni vetëm 2 widget-e në herë", + "Minimize widget": "Minimizoje widget-in", + "Maximize widget": "Maksimizoj widget-in", + "Your server requires encryption to be enabled in private rooms.": "Shërbyesi juaj lyp që fshehtëzimi të jetë i aktivizuar në dhoma private.", + "Start a conversation with someone using their name or username (like ).": "Nisni një bisedë me dikë duke përdorur emrin e tij ose emrin e tij të përdoruesit (bie fjala, ).", + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Kjo s’do ta ftojë te %(communityName)s. Që të ftoni dikë te %(communityName)s, klikoni këtu", + "Invite someone using their name, username (like ) or share this room.": "Ftoni dikë duke përdorur emrin e tij, emrin e tij të përdoruesit (bie fjala, ) ose ndani me të këtë dhomë.", + "Unable to set up keys": "S’arrihet të ujdisen kyçe" } From 28eef21714a62b8638448cb6c8cdea457b66e9fe Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Sat, 19 Sep 2020 01:27:34 +0000 Subject: [PATCH 0317/1014] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 6bcd94bd9a..fcdb7c9927 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2499,5 +2499,15 @@ "You don't have permission to remove the call from the room": "您沒有從聊天室移除通話的權限", "Start a conversation with someone using their name or username (like ).": "使用某人的名字或使用者名稱(如 )以與他們開始對話。", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "這不會邀請他們加入 %(communityName)s。要邀請某人加入 %(communityName)s,請點擊這裡", - "Invite someone using their name, username (like ) or share this room.": "使用某人的名字、使用者名稱(如 )或分享此聊天室來邀請他們。" + "Invite someone using their name, username (like ) or share this room.": "使用某人的名字、使用者名稱(如 )或分享此聊天室來邀請他們。", + "Safeguard against losing access to encrypted messages & data": "防止遺失對加密訊息與資料的存取權", + "not found in storage": "在儲存空間中找不到", + "Widgets": "小工具", + "Edit widgets, bridges & bots": "編輯小工具、橋接與機器人", + "Add widgets, bridges & bots": "新增小工具、橋接與機器人", + "You can only pin 2 widgets at a time": "您僅能同時釘選兩個小工具", + "Minimize widget": "最小化小工具", + "Maximize widget": "最大化小工具", + "Your server requires encryption to be enabled in private rooms.": "您的伺服器需要在私人聊天室中啟用加密。", + "Unable to set up keys": "無法設定金鑰" } From 3523342a5a5fdfbb61b6c3aab3d7c2bc22baa216 Mon Sep 17 00:00:00 2001 From: notramo Date: Sun, 20 Sep 2020 05:44:46 +0000 Subject: [PATCH 0318/1014] Translated using Weblate (Hungarian) Currently translated at 99.8% (2365 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 0e899ad242..8095401bf9 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -31,7 +31,7 @@ "Default Device": "Alapértelmezett eszköz", "Microphone": "Mikrofon", "Camera": "Kamera", - "Advanced": "Speciális", + "Advanced": "Haladó", "Always show message timestamps": "Üzenet időbélyeg folyamatos megjelenítése", "Authentication": "Azonosítás", "Failed to change password. Is your password correct?": "Nem sikerült megváltoztatni a jelszót. Helyesen írtad be a jelszavadat?", @@ -1070,7 +1070,7 @@ "Identity Server URL": "Azonosítási Szerver URL", "Free": "Szabad", "Join millions for free on the largest public server": "Milliók kapcsolódnak ingyen a legnagyobb nyilvános szerveren", - "Premium": "Pérmium", + "Premium": "Prémium", "Premium hosting for organisations Learn more": "Prémium üzemeltetés szervezetek részére Tudj meg többet", "Other": "Más", "Find other public servers or use a custom server": "Találj más nyilvános szervereket vagy használj egyedi szervert", @@ -2496,5 +2496,11 @@ "You don't have permission to remove the call from the room": "A konferencia hívás törléséhez nincs jogosultságod", "Start a conversation with someone using their name or username (like ).": "Indíts beszélgetést valakivel és használd hozzá a nevét vagy a felhasználói nevét (mint ).", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Ez nem hívja meg őket ebbe a közösségbe: %(communityName)s. Hogy meghívj valakit ebbe a közösségbe: %(communityName)s kattints ide", - "Invite someone using their name, username (like ) or share this room.": "Hívj meg valakit a nevével, felhasználói nevével (pl. ) vagy oszd meg ezt a szobát." + "Invite someone using their name, username (like ) or share this room.": "Hívj meg valakit a nevével, felhasználói nevével (pl. ) vagy oszd meg ezt a szobát.", + "Add widgets, bridges & bots": "Widget-ek, hidak, és botok hozzáadása", + "You can only pin 2 widgets at a time": "Egyszerre csak 2 widget-et lehet kitűzni", + "Minimize widget": "Widget minimalizálása", + "Maximize widget": "Widget maximalizálása", + "Your server requires encryption to be enabled in private rooms.": "A szervered megköveteli, hogy a titkosítás be legyen kapcsolva a privát szobákban.", + "Unable to set up keys": "Nem sikerült a kulcsok beállítása" } From 60013e74edd445b94f7c544d5f354e0ed631af44 Mon Sep 17 00:00:00 2001 From: call_xz Date: Sat, 19 Sep 2020 03:22:33 +0000 Subject: [PATCH 0319/1014] Translated using Weblate (Japanese) Currently translated at 57.7% (1367 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ja/ --- src/i18n/strings/ja.json | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json index f5c2b89f8c..ca1cf0a200 100644 --- a/src/i18n/strings/ja.json +++ b/src/i18n/strings/ja.json @@ -126,7 +126,7 @@ "You are not receiving desktop notifications": "デスクトップ通知を受け取っていません", "Update": "アップデート", "Unable to fetch notification target list": "通知先リストを取得できませんでした", - "Uploaded on %(date)s by %(user)s": "%(date)s に %(user)s によりアップロードされました", + "Uploaded on %(date)s by %(user)s": "このファイルは %(date)s に %(user)s によりアップロードされました", "Send Custom Event": "カスタムイベントを送信する", "All notifications are currently disabled for all targets.": "現在すべての対象についての全通知が無効です。", "Failed to send logs: ": "ログの送信に失敗しました: ", @@ -341,7 +341,7 @@ "Unable to connect to Homeserver. Retrying...": "ホームサーバーに接続できません。 再試行中...", "Your browser does not support the required cryptography extensions": "お使いのブラウザは、必要な暗号化拡張機能をサポートしていません", "Not a valid %(brand)s keyfile": "有効な%(brand)sキーファイルではありません", - "Authentication check failed: incorrect password?": "認証チェックに失敗しました: パスワードの間違い?", + "Authentication check failed: incorrect password?": "認証に失敗しました: パスワードの間違っている可能性があります。", "Sorry, your homeserver is too old to participate in this room.": "申し訳ありませんが、あなたのホームサーバーはこの部屋に参加するには古すぎます。", "Please contact your homeserver administrator.": "ホームサーバー管理者に連絡してください。", "Failed to join room": "部屋に参加できませんでした", @@ -454,7 +454,7 @@ "(~%(count)s results)|one": "(~%(count)s 結果)", "Join Room": "部屋に入る", "Forget room": "部屋を忘れる", - "Share room": "部屋を共有する", + "Share room": "部屋を共有", "Community Invites": "コミュニティへの招待", "Invites": "招待", "Unban": "ブロック解除", @@ -508,10 +508,10 @@ "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "メッセージにURLを入力すると、URLプレビューが表示され、タイトル、説明、ウェブサイトからの画像など、そのリンクに関する詳細情報が表示されます。", "Error decrypting audio": "オーディオの復号化エラー", "Error decrypting attachment": "添付ファイルの復号化エラー", - "Decrypt %(text)s": "%(text)s を解読する", + "Decrypt %(text)s": "%(text)s を復号", "Download %(text)s": "%(text)s をダウンロード", "Invalid file%(extra)s": "無効なファイル %(extra)s", - "Error decrypting image": "イメージの復号化エラー", + "Error decrypting image": "画像の復号中にエラーが発生しました", "Error decrypting video": "動画の復号エラー", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s が %(roomName)s のアバターを変更しました", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s がルームアバターを削除しました。", @@ -783,7 +783,7 @@ "Connectivity to the server has been lost.": "サーバーへの接続が失われました。", "Sent messages will be stored until your connection has returned.": "送信されたメッセージは、接続が復旧するまで保存されます。", "Active call": "アクティブコール", - "There's no one else here! Would you like to invite others or stop warning about the empty room?": "他に誰もいません! 他のユーザーを招待または空の部屋に関する警告を停止しますか?", + "There's no one else here! Would you like to invite others or stop warning about the empty room?": "この部屋には他に誰もいません!:他のユーザーを招待この警告を停止", "You seem to be uploading files, are you sure you want to quit?": "ファイルをアップロードしているようですが、中止しますか?", "You seem to be in a call, are you sure you want to quit?": "通話中のようですが、本当にやめたいですか?", "Search failed": "検索に失敗しました", @@ -1397,5 +1397,31 @@ "%(brand)s Desktop": "%(brand)s デスクトップ", "%(brand)s iOS": "%(brand)s iOS", "%(brand)s Android": "%(brand)s Android", - "Your recovery key": "あなたのリカバリーキー" + "Your recovery key": "あなたのリカバリーキー", + "a few seconds ago": "数秒前", + "about a minute ago": "約1分前", + "about an hour ago": "約1時間前", + "about a day ago": "約1日前", + "a few seconds from now": "今から数秒前", + "about a minute from now": "今から約1分前", + "%(num)s minutes from now": "今から %(num)s 分前", + "about an hour from now": "今から約1時間前", + "%(num)s hours from now": "今から %(num)s 時間前", + "about a day from now": "今から約1日前", + "%(num)s days from now": "今から %(num)s 日前", + "%(name)s (%(userId)s)": "%(name)s (%(userId)s)", + "User %(user_id)s may or may not exist": "ユーザー %(user_id)s は存在しないとは限りません", + "Unknown App": "未知のアプリ", + "Room Info": "部屋の情報", + "About": "概要", + "%(count)s people|other": "%(count)s 人の参加者", + "%(count)s people|one": "%(count)s 人の参加者", + "Show files": "ファイルを表示", + "Room settings": "部屋の設定", + "Show image": "画像を表示", + "Upload files (%(current)s of %(total)s)": "ファイルのアップロード (%(current)s/%(total)s)", + "Upload files": "ファイルのアップロード", + "Upload all": "全てアップロード", + "No files visible in this room": "この部屋にファイルはありません", + "Attach files from chat or just drag and drop them anywhere in a room.": "チャットでファイルを添付するか、部屋のどこかにドラッグ&ドロップするとファイルを追加できます。" } From 30c020ce13cd09b999423104f85d5c237278ed29 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Sat, 19 Sep 2020 09:27:30 +0000 Subject: [PATCH 0320/1014] Translated using Weblate (Swedish) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 3feee476c6..a5421b2bc0 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -2426,5 +2426,18 @@ "Secure Backup": "Säker säkerhetskopiering", "End Call": "Avsluta samtal", "Remove the group call from the room?": "Ta bort gruppsamtalet från rummet?", - "You don't have permission to remove the call from the room": "Du har inte behörighet från att ta bort samtalet från rummet" + "You don't have permission to remove the call from the room": "Du har inte behörighet från att ta bort samtalet från rummet", + "Safeguard against losing access to encrypted messages & data": "Skydda mot att förlora åtkomst till krypterade meddelanden och data", + "not found in storage": "hittades inte i lagring", + "Widgets": "Widgets", + "Edit widgets, bridges & bots": "Redigera widgets, bryggor och bottar", + "Add widgets, bridges & bots": "Lägg till widgets, bryggor och bottar", + "You can only pin 2 widgets at a time": "Du kan bara fästa 2 widgets i taget", + "Minimize widget": "Minimera widget", + "Maximize widget": "Maximera widget", + "Your server requires encryption to be enabled in private rooms.": "Din server kräver att kryptering ska användas i privata rum.", + "Start a conversation with someone using their name or username (like ).": "Starta en konversation med någon med deras namn eller användarnamn (som ).", + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Detta kommer inte att bjuda in dem till %(communityName)s. För att bjuda in någon till %(communityName)s, klicka här", + "Invite someone using their name, username (like ) or share this room.": "Bjud in någon med deras namn eller användarnamn (som ) eller dela det här rummet.", + "Unable to set up keys": "Kunde inte ställa in nycklar" } From 42cdf4b7c9b1491bfe1c2f2e6cacba5888dd3dbb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 21 Sep 2020 13:57:33 +0100 Subject: [PATCH 0321/1014] fix undefined devices case Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/right_panel/UserInfo.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index a9aebd9b33..8440532b9d 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -1296,7 +1296,8 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { const userTrust = cryptoEnabled && cli.checkUserTrust(member.userId); const userVerified = cryptoEnabled && userTrust.isCrossSigningVerified(); const isMe = member.userId === cli.getUserId(); - const canVerify = cryptoEnabled && homeserverSupportsCrossSigning && !userVerified && !isMe && devices.length > 0; + const canVerify = cryptoEnabled && homeserverSupportsCrossSigning && !userVerified && !isMe && + devices && devices.length > 0; const setUpdating = (updating) => { setPendingUpdateCount(count => count + (updating ? 1 : -1)); From ed0e188b4f354072413e881a6fdd5fef5fd7f26f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 21 Sep 2020 14:35:35 +0100 Subject: [PATCH 0322/1014] Validation improve pattern for derived data Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/auth/PassphraseField.tsx | 30 ++++++--------- src/components/views/elements/Validation.tsx | 38 +++++++++++-------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/components/views/auth/PassphraseField.tsx b/src/components/views/auth/PassphraseField.tsx index 2f5064447e..b420ed0872 100644 --- a/src/components/views/auth/PassphraseField.tsx +++ b/src/components/views/auth/PassphraseField.tsx @@ -40,11 +40,7 @@ interface IProps { onValidate(result: IValidationResult); } -interface IState { - complexity: zxcvbn.ZXCVBNResult; -} - -class PassphraseField extends PureComponent { +class PassphraseField extends PureComponent { static defaultProps = { label: _td("Password"), labelEnterPassword: _td("Enter password"), @@ -52,14 +48,16 @@ class PassphraseField extends PureComponent { labelAllowedButUnsafe: _td("Password is allowed, but unsafe"), }; - state = { complexity: null }; - - public readonly validate = withValidation({ - description: function() { - const complexity = this.state.complexity; + public readonly validate = withValidation({ + description: function(complexity) { const score = complexity ? complexity.score : 0; return ; }, + deriveData: async ({ value }) => { + if (!value) return null; + const { scorePassword } = await import('../../../utils/PasswordScorer'); + return scorePassword(value); + }, rules: [ { key: "required", @@ -68,28 +66,24 @@ class PassphraseField extends PureComponent { }, { key: "complexity", - test: async function({ value }) { + test: async function({ value }, complexity) { if (!value) { return false; } - const { scorePassword } = await import('../../../utils/PasswordScorer'); - const complexity = scorePassword(value); - this.setState({ complexity }); const safe = complexity.score >= this.props.minScore; const allowUnsafe = SdkConfig.get()["dangerously_allow_unsafe_and_insecure_passwords"]; return allowUnsafe || safe; }, - valid: function() { + valid: function(complexity) { // Unsafe passwords that are valid are only possible through a // configuration flag. We'll print some helper text to signal // to the user that their password is allowed, but unsafe. - if (this.state.complexity.score >= this.props.minScore) { + if (complexity.score >= this.props.minScore) { return _t(this.props.labelStrongPassword); } return _t(this.props.labelAllowedButUnsafe); }, - invalid: function() { - const complexity = this.state.complexity; + invalid: function(complexity) { if (!complexity) { return null; } diff --git a/src/components/views/elements/Validation.tsx b/src/components/views/elements/Validation.tsx index 50544c9f51..55e5714719 100644 --- a/src/components/views/elements/Validation.tsx +++ b/src/components/views/elements/Validation.tsx @@ -21,18 +21,19 @@ import classNames from "classnames"; type Data = Pick; -interface IRule { +interface IRule { key: string; final?: boolean; - skip?(this: T, data: Data): boolean; - test(this: T, data: Data): boolean | Promise; - valid?(this: T): string; - invalid?(this: T): string; + skip?(this: T, data: Data, derivedData: D): boolean; + test(this: T, data: Data, derivedData: D): boolean | Promise; + valid?(this: T, derivedData: D): string; + invalid?(this: T, derivedData: D): string; } -interface IArgs { - rules: IRule[]; - description(this: T): React.ReactChild; +interface IArgs { + rules: IRule[]; + description(this: T, derivedData: D): React.ReactChild; + deriveData?(data: Data): Promise; } export interface IFieldState { @@ -53,6 +54,10 @@ export interface IValidationResult { * @param {Function} description * Function that returns a string summary of the kind of value that will * meet the validation rules. Shown at the top of the validation feedback. + * @param {Function} deriveData + * Optional function that returns a Promise to an object of generic type D. + * The result of this Promise is passed to rule methods `skip`, `test`, `valid`, and `invalid`. + * Useful for doing calculations per-value update once rather than in each of the above rule methods. * @param {Object} rules * An array of rules describing how to check to input value. Each rule in an object * and may have the following properties: @@ -66,7 +71,7 @@ export interface IValidationResult { * A validation function that takes in the current input value and returns * the overall validity and a feedback UI that can be rendered for more detail. */ -export default function withValidation({ description, rules }: IArgs) { +export default function withValidation({ description, deriveData, rules }: IArgs) { return async function onValidate({ value, focused, allowEmpty = true }: IFieldState): Promise { if (!value && allowEmpty) { return { @@ -75,6 +80,9 @@ export default function withValidation({ description, rules }: IA }; } + const data = { value, allowEmpty }; + const derivedData = deriveData ? await deriveData(data) : undefined; + const results = []; let valid = true; if (rules && rules.length) { @@ -87,20 +95,18 @@ export default function withValidation({ description, rules }: IA continue; } - const data = { value, allowEmpty }; - - if (rule.skip && rule.skip.call(this, data)) { + if (rule.skip && rule.skip.call(this, data, derivedData)) { continue; } // We're setting `this` to whichever component holds the validation // function. That allows rules to access the state of the component. - const ruleValid = await rule.test.call(this, data); + const ruleValid = await rule.test.call(this, data, derivedData); valid = valid && ruleValid; if (ruleValid && rule.valid) { // If the rule's result is valid and has text to show for // the valid state, show it. - const text = rule.valid.call(this); + const text = rule.valid.call(this, derivedData); if (!text) { continue; } @@ -112,7 +118,7 @@ export default function withValidation({ description, rules }: IA } else if (!ruleValid && rule.invalid) { // If the rule's result is invalid and has text to show for // the invalid state, show it. - const text = rule.invalid.call(this); + const text = rule.invalid.call(this, derivedData); if (!text) { continue; } @@ -153,7 +159,7 @@ export default function withValidation({ description, rules }: IA if (description) { // We're setting `this` to whichever component holds the validation // function. That allows rules to access the state of the component. - const content = description.call(this); + const content = description.call(this, derivedData); summary =
    {content}
    ; } From 1d3ea35267dc284faed8a5310b7b582b30d7a4f5 Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Mon, 21 Sep 2020 13:25:32 +0000 Subject: [PATCH 0323/1014] Translated using Weblate (German) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 2c2f832fc8..08cf756c8b 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -1434,7 +1434,7 @@ "Changing password will currently reset any end-to-end encryption keys on all sessions, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Durch die Änderung des Passworts werden derzeit alle Ende-zu-Ende-Verschlüsselungsschlüssel in allen Sitzungen zurückgesetzt, sodass der verschlüsselte Chat-Verlauf nicht mehr lesbar ist, es sei denn, du exportierst zuerst deine Raumschlüssel und importierst sie anschließend wieder. In Zukunft wird dies verbessert werden.", "Delete %(count)s sessions|other": "Lösche %(count)s Sitzungen", "Backup is not signed by any of your sessions": "Die Sicherung wurde von keiner deiner Sitzungen unterzeichnet", - "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them": "Ihr Passwort wurde erfolgreich geändert. Sie erhalten keine Push-Benachrichtigungen zu anderen Sitzungen, bis Sie sich wieder bei diesen anmelden", + "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them": "Dein Passwort wurde erfolgreich geändert. Du erhälst keine Push-Benachrichtigungen zu anderen Sitzungen, bis du dich wieder bei diesen anmeldst", "Notification sound": "Benachrichtigungston", "Set a new custom sound": "Setze einen neuen benutzerdefinierten Ton", "Browse": "Durchsuche", @@ -2491,5 +2491,18 @@ "Secure Backup": "Sicheres Backup", "End Call": "Anruf beenden", "Remove the group call from the room?": "Konferenzgespräch aus diesem Raum entfernen?", - "You don't have permission to remove the call from the room": "Du hast keine Berechtigung um den Konferenzanruf aus dem Raum zu entfernen" + "You don't have permission to remove the call from the room": "Du hast keine Berechtigung um den Konferenzanruf aus dem Raum zu entfernen", + "Safeguard against losing access to encrypted messages & data": "Schütze dich vor dem Verlust des Zugriffs auf verschlüsselte Nachrichten und Daten", + "not found in storage": "nicht im Speicher gefunden", + "Widgets": "Widgets", + "Edit widgets, bridges & bots": "Widgets, Bridges & Bots bearbeiten", + "Add widgets, bridges & bots": "Widgets, Bridges & Bots hinzufügen", + "You can only pin 2 widgets at a time": "Du kannst jeweils nur 2 Widgets anheften", + "Minimize widget": "Widget minimieren", + "Maximize widget": "Widget maximieren", + "Your server requires encryption to be enabled in private rooms.": "Für deinen Server muss die Verschlüsselung in privaten Räumen aktiviert sein.", + "Start a conversation with someone using their name or username (like ).": "Starte ein Gespräch unter Verwendung des Namen oder Benutzernamens des Gegenübers (z. B. ).", + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Das wird sie nicht zu %(communityName)s einladen. Um jemand zu %(communityName)s einzuladen, klicke hier", + "Invite someone using their name, username (like ) or share this room.": "Lade jemand mittels seinem/ihrem Namen oder Benutzernamen (z.B. ) ein, oder teile diesem Raum.", + "Unable to set up keys": "Schlüssel können nicht eingerichtet werden" } From 93492572397eb96d0aa26812d61fe47497ccbdb6 Mon Sep 17 00:00:00 2001 From: Michael Albert Date: Mon, 21 Sep 2020 13:38:25 +0000 Subject: [PATCH 0324/1014] Translated using Weblate (German) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 08cf756c8b..4cebb135ac 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -1456,8 +1456,8 @@ "If disabled, messages from encrypted rooms won't appear in search results.": "Wenn deaktiviert, werden Nachrichten von verschlüsselten Räumen nicht in den Ergebnissen auftauchen.", "This user has not verified all of their sessions.": "Dieser Benutzer hat nicht alle seine Sitzungen verifiziert.", "You have verified this user. This user has verified all of their sessions.": "Sie haben diesen Benutzer verifiziert. Dieser Benutzer hat alle seine Sitzungen verifiziert.", - "Your key share request has been sent - please check your other sessions for key share requests.": "Ihre Anfrage zur Schlüssel-Teilung wurde gesendet - bitte überprüfen Sie Ihre anderen Sitzungen auf Anfragen zur Schlüssel-Teilung.", - "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Anfragen zum Teilen von Schlüsseln werden automatisch an Ihre anderen Sitzungen gesendet. Wenn Sie die Anfragen zum Teilen von Schlüsseln in Ihren anderen Sitzungen abgelehnt oder abgewiesen haben, klicken Sie hier, um die Schlüssel für diese Sitzung erneut anzufordern.", + "Your key share request has been sent - please check your other sessions for key share requests.": "Deine Schlüsselanfrage wurde gesendet - sieh in deinen anderen Sitzungen nach der Schlüsselanfrage.", + "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Schlüsselanfragen werden automatisch an deine anderen Sitzungen gesendet. Wenn du sie abgelehnt oder ignoriert hast klicke hier um die Schlüssel erneut anzufordern.", "If your other sessions do not have the key for this message you will not be able to decrypt them.": "Wenn Ihre anderen Sitzungen nicht über den Schlüssel für diese Nachricht verfügen, können Sie sie nicht entschlüsseln.", "Re-request encryption keys from your other sessions.": "Fordern Sie Verschlüsselungsschlüssel aus Ihren anderen Sitzungen erneut an.", "Room %(name)s": "Raum %(name)s", From e7eb3b62a97d643da8b67b8a9c676e28dcf8120a Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Mon, 21 Sep 2020 13:39:23 +0000 Subject: [PATCH 0325/1014] Translated using Weblate (German) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 4cebb135ac..43ecff16b2 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -880,7 +880,7 @@ "Delete Backup": "Sicherung löschen", "Backup version: ": "Sicherungsversion: ", "Algorithm: ": "Algorithmus: ", - "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this": "Um zu vermeiden, dass Ihr Chat-Verlauf verloren geht, müssen Sie Ihre Raum-Schlüssel exportieren, bevor Sie sich abmelden. Dazu müssen Sie auf die neuere Version von %(brand)s zurückgehen", + "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this": "Um zu vermeiden, dass dein Chat-Verlauf verloren geht, musst du deine Raum-Schlüssel exportieren, bevor du dich abmeldest. Dazu musst du auf die neuere Version von %(brand)s zurückgehen", "Incompatible Database": "Inkompatible Datenbanken", "Continue With Encryption Disabled": "Mit deaktivierter Verschlüsselung fortfahren", "Next": "Weiter", @@ -944,9 +944,9 @@ "Checking...": "Überprüfe...", "Unable to load backup status": "Konnte Sicherungsstatus nicht laden", "Failed to decrypt %(failedCount)s sessions!": "Konnte %(failedCount)s Sitzungen nicht entschlüsseln!", - "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Greifen Sie auf Ihre sichere Nachrichtenhistorie zu und richten Sie einen sicheren Nachrichtenversand ein, indem Sie Ihre Wiederherstellungspassphrase eingeben.", + "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Greife auf deine gesicherten Chatverlauf zu und richten einen sicheren Nachrichtenversand ein, indem du deine Wiederherstellungspassphrase eingibst.", "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "Wenn du deinen Wiederherstellungspassphrase vergessen hast, kannst du deinen Wiederherstellungsschlüssel benutzen oder neue Wiederherstellungsoptionen einrichten", - "Access your secure message history and set up secure messaging by entering your recovery key.": "Greifen Sie auf Ihren sicheren Nachrichtenverlauf zu und richten Sie durch Eingabe Ihres Wiederherstellungsschlüssels einen sicheren Nachrichtenversand ein.", + "Access your secure message history and set up secure messaging by entering your recovery key.": "Greife auf deinen gesicherten Chatverlauf zu und richten durch Eingabe deines Wiederherstellungsschlüssels einen sicheren Nachrichtenversand ein.", "Set a new status...": "Setze einen neuen Status...", "Clear status": "Status löschen", "Invalid homeserver discovery response": "Ungültige Antwort beim Aufspüren des Heimservers", @@ -957,7 +957,7 @@ "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Ohne Sichere Nachrichten-Wiederherstellung einzurichten, wirst du deine sichere Nachrichtenhistorie verlieren, wenn du dich abmeldest.", "If you don't want to set this up now, you can later in Settings.": "Wenn du dies jetzt nicht einrichten willst, kannst du dies später in den Einstellungen tun.", "New Recovery Method": "Neue Wiederherstellungsmethode", - "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Wenn Sie die neue Wiederherstellungsmethode nicht festgelegt haben, versucht ein Angreifer möglicherweise, auf Ihr Konto zuzugreifen. Ändern Sie Ihr Kontopasswort und legen Sie sofort eine neue Wiederherstellungsmethode in den Einstellungen fest.", + "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Wenn du die neue Wiederherstellungsmethode nicht festgelegt hast, versucht ein/e Angreifer!n möglicherweise, auf dein Konto zuzugreifen. Ändere dein Kontopasswort und lege sofort eine neue Wiederherstellungsmethode in den Einstellungen fest.", "Set up Secure Messages": "Richte sichere Nachrichten ein", "Go to Settings": "Gehe zu Einstellungen", "Sign in with single sign-on": "Melden Sie sich mit Single Sign-On an", @@ -1417,7 +1417,7 @@ "You are currently subscribed to:": "Du abonnierst momentan:", "⚠ These settings are meant for advanced users.": "⚠ Diese Einstellungen sind für fortgeschrittene Nutzer gedacht.", "Whether you're using %(brand)s on a device where touch is the primary input mechanism": "Ob du %(brand)s auf einem Gerät verwendest, bei dem Berührung der primäre Eingabemechanismus ist", - "Whether you're using %(brand)s as an installed Progressive Web App": "Ob Sie %(brand)s als installierte progressive Web-App verwenden", + "Whether you're using %(brand)s as an installed Progressive Web App": "Ob du %(brand)s als installierte progressive Web-App verwendest", "Your user agent": "Dein User-Agent", "If you cancel now, you won't complete verifying the other user.": "Wenn Sie jetzt abbrechen, werden Sie die Verifizierung des anderen Nutzers nicht beenden können.", "If you cancel now, you won't complete verifying your other session.": "Wenn Sie jetzt abbrechen, werden Sie die Verifizierung der anderen Sitzung nicht beenden können.", @@ -1439,7 +1439,7 @@ "Set a new custom sound": "Setze einen neuen benutzerdefinierten Ton", "Browse": "Durchsuche", "Direct Messages": "Direktnachrichten", - "You can use /help to list available commands. Did you mean to send this as a message?": "Sie können /help benutzen, um verfügbare Befehle aufzulisten. Wollten Sie dies als Nachricht senden?", + "You can use /help to list available commands. Did you mean to send this as a message?": "Du kannst /help benutzen, um verfügbare Befehle aufzulisten. Willst du dies als Nachricht senden?", "Direct message": "Direktnachricht", "Suggestions": "Vorschläge", "Recently Direct Messaged": "Kürzlich direkt verschickt", @@ -1455,9 +1455,9 @@ "Notification Autocomplete": "Benachrichtigung Autovervollständigen", "If disabled, messages from encrypted rooms won't appear in search results.": "Wenn deaktiviert, werden Nachrichten von verschlüsselten Räumen nicht in den Ergebnissen auftauchen.", "This user has not verified all of their sessions.": "Dieser Benutzer hat nicht alle seine Sitzungen verifiziert.", - "You have verified this user. This user has verified all of their sessions.": "Sie haben diesen Benutzer verifiziert. Dieser Benutzer hat alle seine Sitzungen verifiziert.", + "You have verified this user. This user has verified all of their sessions.": "Du hast diese/n Nutzer!n verifiziert. Er/Sie hat alle seine/ihre Sitzungen verifiziert.", "Your key share request has been sent - please check your other sessions for key share requests.": "Deine Schlüsselanfrage wurde gesendet - sieh in deinen anderen Sitzungen nach der Schlüsselanfrage.", - "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Schlüsselanfragen werden automatisch an deine anderen Sitzungen gesendet. Wenn du sie abgelehnt oder ignoriert hast klicke hier um die Schlüssel erneut anzufordern.", + "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Schlüsselanfragen werden automatisch an deine anderen Sitzungen gesendet. Wenn du sie abgelehnt oder ignoriert hast klicke hier, um die Schlüssel erneut anzufordern.", "If your other sessions do not have the key for this message you will not be able to decrypt them.": "Wenn Ihre anderen Sitzungen nicht über den Schlüssel für diese Nachricht verfügen, können Sie sie nicht entschlüsseln.", "Re-request encryption keys from your other sessions.": "Fordern Sie Verschlüsselungsschlüssel aus Ihren anderen Sitzungen erneut an.", "Room %(name)s": "Raum %(name)s", @@ -1468,16 +1468,16 @@ "%(count)s sessions|other": "%(count)s Sitzungen", "Hide sessions": "Sitzungen ausblenden", "Encryption enabled": "Verschlüsselung aktiviert", - "Messages in this room are end-to-end encrypted. Learn more & verify this user in their user profile.": "Nachrichten in diesem Raum sind Ende-zu-Ende verschlüsselt. Erfahren Sie mehr & überprüfen Sie diesen Benutzer in seinem Benutzerprofil.", + "Messages in this room are end-to-end encrypted. Learn more & verify this user in their user profile.": "Nachrichten in diesem Raum sind Ende-zu-Ende verschlüsselt. Erfahre mehr & überprüfe diesen Benutzer in seinem Benutzerprofil.", "Encryption not enabled": "Verschlüsselung nicht aktiviert", "You verified %(name)s": "Du hast %(name)s verifiziert", - "You cancelled verifying %(name)s": "Sie haben die Verifizierung von %(name)s abgebrochen", + "You cancelled verifying %(name)s": "Du hast die Verifizierung von %(name)s abgebrochen", "%(name)s cancelled verifying": "%(name)s hat die Verifizierung abgebrochen", "%(name)s accepted": "%(name)s hat akzeptiert", "%(name)s declined": "%(name)s hat abgelehnt", "%(name)s cancelled": "%(name)s hat abgebrochen", "%(name)s wants to verify": "%(name)s will eine Verifizierung", - "Your display name": "Ihr Anzeigename", + "Your display name": "Dein Anzeigename", "Please enter a name for the room": "Bitte geben Sie einen Namen für den Raum ein", "This room is private, and can only be joined by invitation.": "Dieser Raum ist privat und kann nur auf Einladung betreten werden.", "Create a private room": "Erstelle einen privaten Raum", @@ -1486,9 +1486,9 @@ "Hide advanced": "Weitere Einstellungen ausblenden", "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)": "Hindere Benutzer auf anderen Matrix-Homeservern daran, diesem Raum beizutreten (Diese Einstellung kann später nicht geändert werden!)", "Session name": "Name der Sitzung", - "This will allow you to return to your account after signing out, and sign in on other sessions.": "So können Sie nach der Abmeldung zu Ihrem Konto zurückkehren und sich bei anderen Sitzungen anmelden.", + "This will allow you to return to your account after signing out, and sign in on other sessions.": "So kannst du nach der Abmeldung zu deinem Konto zurückkehren und dich bei anderen Sitzungen anmelden.", "Use bots, bridges, widgets and sticker packs": "Benutze Bots, Bridges, Widgets und Sticker-Packs", - "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Wenn Sie Ihr Passwort ändern, werden alle End-to-End-Verschlüsselungsschlüssel für alle Ihre Sitzungen zurückgesetzt, sodass der verschlüsselte Chat-Verlauf nicht mehr lesbar ist. Richten Sie ein Schlüssel-Backup ein oder exportieren Sie Ihre Raumschlüssel aus einer anderen Sitzung, bevor Sie Ihr Passwort zurücksetzen.", + "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Wenn du dein Passwort änderst, werden alle Ende-zu-Ende-Verschlüsselungsschlüssel für alle deine Sitzungen zurückgesetzt, sodass der verschlüsselte Chat-Verlauf nicht mehr lesbar ist. Richte ein Schlüssel-Backup ein oder exportiere deine Raumschlüssel aus einer anderen Sitzung, bevor du dein Passwort zurücksetzst.", "You have been logged out of all sessions and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "Sie wurden von allen Sitzungen abgemeldet und erhalten keine Push-Benachrichtigungen mehr. Um die Benachrichtigungen wieder zu aktivieren, melden Sie sich auf jedem Gerät erneut an.", "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Aktualisieren Sie diese Sitzung, damit sie andere Sitzungen verifizieren kann, indem sie ihnen Zugang zu verschlüsselten Nachrichten gewährt und sie für andere Benutzer als vertrauenswürdig markiert.", "Sign out and remove encryption keys?": "Abmelden und Verschlüsselungsschlüssel entfernen?", @@ -2410,7 +2410,7 @@ "You can also set up Secure Backup & manage your keys in Settings.": "Du kannst auch in den Einstellungen eine Sicherung erstellen & deine Schlüssel verwalten.", "Set up Secure backup": "Sicheres Backup einrichten", "Show message previews for reactions in DMs": "Anzeigen einer Nachrichtenvorschau für Reaktionen in DMs", - "Show message previews for reactions in all rooms": "Zeigen Sie eine Nachrichtenvorschau für Reaktionen in allen Räumen an", + "Show message previews for reactions in all rooms": "Zeige eine Nachrichtenvorschau für Reaktionen in allen Räumen an", "Uploading logs": "Protokolle werden hochgeladen", "Downloading logs": "Protokolle werden heruntergeladen", "Explore public rooms": "Erkunde öffentliche Räume", From e92d75ea6a688e0758c6d9e55442879efa430893 Mon Sep 17 00:00:00 2001 From: Michael Albert Date: Mon, 21 Sep 2020 13:39:47 +0000 Subject: [PATCH 0326/1014] Translated using Weblate (German) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 43ecff16b2..fb4ce48796 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -1458,8 +1458,8 @@ "You have verified this user. This user has verified all of their sessions.": "Du hast diese/n Nutzer!n verifiziert. Er/Sie hat alle seine/ihre Sitzungen verifiziert.", "Your key share request has been sent - please check your other sessions for key share requests.": "Deine Schlüsselanfrage wurde gesendet - sieh in deinen anderen Sitzungen nach der Schlüsselanfrage.", "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Schlüsselanfragen werden automatisch an deine anderen Sitzungen gesendet. Wenn du sie abgelehnt oder ignoriert hast klicke hier, um die Schlüssel erneut anzufordern.", - "If your other sessions do not have the key for this message you will not be able to decrypt them.": "Wenn Ihre anderen Sitzungen nicht über den Schlüssel für diese Nachricht verfügen, können Sie sie nicht entschlüsseln.", - "Re-request encryption keys from your other sessions.": "Fordern Sie Verschlüsselungsschlüssel aus Ihren anderen Sitzungen erneut an.", + "If your other sessions do not have the key for this message you will not be able to decrypt them.": "Wenn deine anderen Sitzungen nicht über den Schlüssel für diese Nachricht verfügen, kannst du die Nachricht nicht entschlüsseln.", + "Re-request encryption keys from your other sessions.": "Fordere die Verschlüsselungsschlüssel aus deinen anderen Sitzungen erneut an.", "Room %(name)s": "Raum %(name)s", "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Ein Upgrade dieses Raums schaltet die aktuelle Instanz des Raums ab und erstellt einen aktualisierten Raum mit demselben Namen.", "%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s (%(userId)s) hat sich zu einer neuen Sitzung angemeldet, ohne sie zu verifizieren:", From 10dfb64092d8e6aeaab3f159d4e57d562b742159 Mon Sep 17 00:00:00 2001 From: Christian Paul Date: Mon, 21 Sep 2020 13:48:10 +0000 Subject: [PATCH 0327/1014] Translated using Weblate (German) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index fb4ce48796..e211ef6a2a 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -960,7 +960,7 @@ "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Wenn du die neue Wiederherstellungsmethode nicht festgelegt hast, versucht ein/e Angreifer!n möglicherweise, auf dein Konto zuzugreifen. Ändere dein Kontopasswort und lege sofort eine neue Wiederherstellungsmethode in den Einstellungen fest.", "Set up Secure Messages": "Richte sichere Nachrichten ein", "Go to Settings": "Gehe zu Einstellungen", - "Sign in with single sign-on": "Melden Sie sich mit Single Sign-On an", + "Sign in with single sign-on": "Melden Sie sich mit „Single Sign-On“ an", "Unrecognised address": "Nicht erkannte Adresse", "User %(user_id)s may or may not exist": "Existenz der Benutzer %(user_id)s unsicher", "Prompt before sending invites to potentially invalid matrix IDs": "Nachfragen bevor Einladungen zu möglichen ungültigen Matrix IDs gesendet werden", From 115c7ccd4e5d22c27ca757102fbca0be0c34882f Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 21 Sep 2020 15:48:47 +0100 Subject: [PATCH 0328/1014] Support HS-preferred Secure Backup setup methods This adds support for the `secure_backup_setup_methods` key, which allows HS admins to state that Element should simplify down to only one setup method, rather than offering both. Fixes https://github.com/vector-im/element-web/issues/15238 --- .../security/CreateSecretStorageDialog.js | 78 ++++++++++++------- src/i18n/strings/en_EN.json | 2 +- src/utils/WellKnownUtils.ts | 16 ++++ 3 files changed, 67 insertions(+), 29 deletions(-) diff --git a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js index f3b52da141..00aad2a0ce 100644 --- a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js @@ -31,7 +31,7 @@ import AccessibleButton from "../../../../components/views/elements/AccessibleBu import DialogButtons from "../../../../components/views/elements/DialogButtons"; import InlineSpinner from "../../../../components/views/elements/InlineSpinner"; import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog"; -import { isSecureBackupRequired } from '../../../../utils/WellKnownUtils'; +import { getSecureBackupSetupMethods, isSecureBackupRequired } from '../../../../utils/WellKnownUtils'; const PHASE_LOADING = 0; const PHASE_LOADERROR = 1; @@ -87,10 +87,16 @@ export default class CreateSecretStorageDialog extends React.PureComponent { canUploadKeysWithPasswordOnly: null, accountPassword: props.accountPassword || "", accountPasswordCorrect: null, - passPhraseKeySelected: CREATE_STORAGE_OPTION_KEY, canSkip: !isSecureBackupRequired(), }; + const setupMethods = getSecureBackupSetupMethods(); + if (setupMethods.includes("key")) { + this.state.passPhraseKeySelected = CREATE_STORAGE_OPTION_KEY; + } else { + this.state.passPhraseKeySelected = CREATE_STORAGE_OPTION_PASSPHRASE; + } + this._passphraseField = createRef(); this._fetchBackupInfo(); @@ -441,39 +447,55 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }); } + _renderOptionKey() { + return ( + +
    + + {_t("Generate a Security Key")} +
    +
    {_t("We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.")}
    +
    + ); + } + + _renderOptionPassphrase() { + return ( + +
    + + {_t("Enter a Security Phrase")} +
    +
    {_t("Use a secret phrase only you know, and optionally save a Security Key to use for backup.")}
    +
    + ); + } + _renderPhaseChooseKeyPassphrase() { + const setupMethods = getSecureBackupSetupMethods(); + const optionKey = setupMethods.includes("key") ? this._renderOptionKey() : null; + const optionPassphrase = setupMethods.includes("passphrase") ? this._renderOptionPassphrase() : null; + return

    {_t( "Safeguard against losing access to encrypted messages & data by " + "backing up encryption keys on your server.", )}

    - -
    - - {_t("Generate a Security Key")} -
    -
    {_t("We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.")}
    -
    - -
    - - {_t("Enter a Security Phrase")} -
    -
    {_t("Use a secret phrase only you know, and optionally save a Security Key to use for backup.")}
    -
    + {optionKey} + {optionPassphrase}
    Date: Mon, 21 Sep 2020 12:13:41 +0000 Subject: [PATCH 0329/1014] Translated using Weblate (Dutch) Currently translated at 82.7% (1958 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/nl/ --- src/i18n/strings/nl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index bb0fb5def6..1ec887c364 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -2026,5 +2026,6 @@ "To return to your account in future you need to set a password": "Zonder wachtwoord kunt u later niet tot uw account terugkeren", "Restart": "Herstarten", "People": "Tweegesprekken", - "Set a room address to easily share your room with other people.": "Geef het gesprek een adres om het gemakkelijk met anderen te kunnen delen." + "Set a room address to easily share your room with other people.": "Geef het gesprek een adres om het gemakkelijk met anderen te kunnen delen.", + "Invite people to join %(communityName)s": "Stuur uitnodigingen voor %(communityName)s" } From b7d4a94edd0f7f21d526f5ddb03cd319902b6dde Mon Sep 17 00:00:00 2001 From: XoseM Date: Mon, 21 Sep 2020 08:24:55 +0000 Subject: [PATCH 0330/1014] Translated using Weblate (Galician) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 51f39758e1..b19cb28420 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2499,5 +2499,12 @@ "Start a conversation with someone using their name or username (like ).": "Inicia unha conversa con alguén usando o seu nome ou nome de usuaria (como ).", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Esto non as convidará a %(communityName)s. Para convidar alguén a %(communityName)s, preme aquí", "Invite someone using their name, username (like ) or share this room.": "Convida a alguén usando o seu nome, nome de usuaria (como ) ou comparte esta sala.", - "Unable to set up keys": "Non se puideron configurar as chaves" + "Unable to set up keys": "Non se puideron configurar as chaves", + "Widgets": "Widgets", + "Edit widgets, bridges & bots": "Editar widgets, pontes e bots", + "Add widgets, bridges & bots": "Engade widgets, pontes e bots", + "You can only pin 2 widgets at a time": "Só podes fixar 2 widgets ó mesmo tempo", + "Minimize widget": "Minimizar widget", + "Maximize widget": "Maximizar widget", + "Your server requires encryption to be enabled in private rooms.": "O servidor require que actives o cifrado nas salas privadas." } From e691f78c513505e1db3068c9d6dc88c4e5629533 Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Mon, 21 Sep 2020 13:48:27 +0000 Subject: [PATCH 0331/1014] Translated using Weblate (German) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index e211ef6a2a..b21898a561 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -801,7 +801,7 @@ "Muted Users": "Stummgeschaltete Benutzer", "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "Dies wird deinen Account permanent unbenutzbar machen. Du wirst nicht in der Lage sein, dich anzumelden und keiner wird dieselbe Benutzer-ID erneut registrieren können. Alle Räume, in denen der Account ist, werden verlassen und deine Account-Daten werden vom Identitätsserver gelöscht. Diese Aktion ist unumkehrbar.", "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "Standardmäßig werden die von dir gesendeten Nachrichten beim Deaktiveren nicht gelöscht. Wenn du dies von uns möchtest, aktivere das Auswalfeld unten.", - "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Sie Sichtbarkeit der Nachrichten in Matrix ist vergleichbar mit E-Mails: Wenn wir deine Nachrichten vergessen heißt das, dass diese nicht mit neuen oder nicht registrierten Nutzern teilen werden, aber registrierte Nutzer, die bereits zugriff haben, werden Zugriff auf ihre Kopie behalten.", + "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Die Sichtbarkeit der Nachrichten in Matrix ist vergleichbar mit E-Mails: Wenn wir deine Nachrichten vergessen heißt das, dass diese nicht mit neuen oder nicht registrierten Nutzern teilen werden, aber registrierte Nutzer, die bereits zugriff haben, werden Zugriff auf ihre Kopie behalten.", "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Bitte vergesst alle Nachrichten, die ich gesendet habe, wenn mein Account deaktiviert wird. (Warnung: Zukünftige Nutzer werden eine unvollständige Konversation sehen)", "To continue, please enter your password:": "Um fortzufahren, bitte Passwort eingeben:", "Can't leave Server Notices room": "Du kannst den Raum für Server-Notizen nicht verlassen", @@ -937,9 +937,9 @@ "Unable to load key backup status": "Konnte Status der Schlüsselsicherung nicht laden", "Don't ask again": "Nicht erneut fragen", "Set up": "Einrichten", - "Please review and accept all of the homeserver's policies": "Bitte prüfen und akzeptieren Sie alle Richtlinien des Heimservers", + "Please review and accept all of the homeserver's policies": "Bitte prüfe und akzeptiere alle Richtlinien des Heimservers", "Failed to load group members": "Konnte Gruppenmitglieder nicht laden", - "That doesn't look like a valid email address": "Sieht nicht nach einer validen E-Mail-Adresse aus", + "That doesn't look like a valid email address": "Sieht nicht nach einer gültigen E-Mail-Adresse aus", "Unable to load commit detail: %(msg)s": "Konnte Commit-Details nicht laden: %(msg)s", "Checking...": "Überprüfe...", "Unable to load backup status": "Konnte Sicherungsstatus nicht laden", @@ -960,7 +960,7 @@ "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Wenn du die neue Wiederherstellungsmethode nicht festgelegt hast, versucht ein/e Angreifer!n möglicherweise, auf dein Konto zuzugreifen. Ändere dein Kontopasswort und lege sofort eine neue Wiederherstellungsmethode in den Einstellungen fest.", "Set up Secure Messages": "Richte sichere Nachrichten ein", "Go to Settings": "Gehe zu Einstellungen", - "Sign in with single sign-on": "Melden Sie sich mit „Single Sign-On“ an", + "Sign in with single sign-on": "Melde dich mit „Single Sign-On“ an", "Unrecognised address": "Nicht erkannte Adresse", "User %(user_id)s may or may not exist": "Existenz der Benutzer %(user_id)s unsicher", "Prompt before sending invites to potentially invalid matrix IDs": "Nachfragen bevor Einladungen zu möglichen ungültigen Matrix IDs gesendet werden", @@ -1141,11 +1141,11 @@ "Are you sure you want to sign out?": "Bist du sicher, dass du dich abmelden möchtest?", "Manually export keys": "Manueller Schlüssel Export", "Composer": "Nachrichteneingabefeld", - "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Überprüfen Sie diesen Benutzer, um ihn als vertrauenswürdig zu kennzeichnen. Benutzern zu vertrauen gibt Ihnen zusätzliche Sicherheit bei der Verwendung von Ende-zu-Ende-verschlüsselten Nachrichten.", + "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Überprüfe diesen Benutzer, um ihn als vertrauenswürdig zu kennzeichnen. Benutzern zu vertrauen gibt dir zusätzliche Sicherheit bei der Verwendung von Ende-zu-Ende-verschlüsselten Nachrichten.", "I don't want my encrypted messages": "Ich möchte meine verschlüsselten Nachrichten nicht", "You'll lose access to your encrypted messages": "Du wirst den Zugang zu deinen verschlüsselten Nachrichten verlieren", "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "Wenn du Fehler bemerkst oder eine Rückmeldung geben möchtest, teile dies uns auf GitHub mit.", - "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.": "Um doppelte Issues zu vermeiden, schauen Sie bitte zuerst die existierenden Issues an (und fügen Sie ein \"+1\" hinzu), oder erstellen Sie ein neues Issue, wenn Sie keines finden können.", + "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.": "Um doppelte Issues zu vermeiden, schaue bitte zuerst die existierenden Issues an (und füge ein \"+1\" hinzu), oder erstelle ein neues Issue, wenn du kein passendes findest.", "Report bugs & give feedback": "Melde Fehler & gib Rückmeldungen", "Update status": "Aktualisiere Status", "Set status": "Setze Status", @@ -1188,7 +1188,7 @@ "Enter the location of your Modular homeserver. It may use your own domain name or be a subdomain of modular.im.": "Gib die Adresse deines Modular-Heimservers an. Es kann deine eigene Domain oder eine Subdomain von modular.im sein.", "Unable to query for supported registration methods.": "Konnte unterstützte Registrierungsmethoden nicht abrufen.", "Bulk options": "Sammeloptionen", - "Join millions for free on the largest public server": "Schließen Sie sich auf dem größten öffentlichen Server kostenlos Millionen von Menschen an", + "Join millions for free on the largest public server": "Schließe dich kostenlos auf dem größten öffentlichen Server Millionen von Menschen an", "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Fügt ¯\\_(ツ)_/¯ vor einer Klartextnachricht ein", "Changes your display nickname in the current room only": "Ändert den Anzeigenamen ausschließlich für den aktuellen Raum", "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s aktivierte Abzeichen der Gruppen %(groups)s für diesen Raum.", @@ -1445,12 +1445,12 @@ "Recently Direct Messaged": "Kürzlich direkt verschickt", "Go": "Los", "Command Help": "Befehl Hilfe", - "To help us prevent this in future, please send us logs.": "Um uns zu helfen, dies in Zukunft zu vermeiden, senden Sie uns bitte Logs.", + "To help us prevent this in future, please send us logs.": "Um uns zu helfen, dies in Zukunft zu vermeiden, sende uns bitte Logs.", "Notification settings": "Benachrichtigungseinstellungen", "Help": "Hilf uns", "Filter": "Filtern", "Filter rooms…": "Räume filtern…", - "You have %(count)s unread notifications in a prior version of this room.|one": "Sie haben %(count)s ungelesene Benachrichtigungen in einer früheren Version dieses Raumes.", + "You have %(count)s unread notifications in a prior version of this room.|one": "Du hast %(count)s ungelesene Benachrichtigungen in einer früheren Version dieses Raumes.", "Go Back": "Gehe zurück", "Notification Autocomplete": "Benachrichtigung Autovervollständigen", "If disabled, messages from encrypted rooms won't appear in search results.": "Wenn deaktiviert, werden Nachrichten von verschlüsselten Räumen nicht in den Ergebnissen auftauchen.", @@ -1478,19 +1478,19 @@ "%(name)s cancelled": "%(name)s hat abgebrochen", "%(name)s wants to verify": "%(name)s will eine Verifizierung", "Your display name": "Dein Anzeigename", - "Please enter a name for the room": "Bitte geben Sie einen Namen für den Raum ein", + "Please enter a name for the room": "Bitte gib einen Namen für den Raum ein", "This room is private, and can only be joined by invitation.": "Dieser Raum ist privat und kann nur auf Einladung betreten werden.", "Create a private room": "Erstelle einen privaten Raum", "Topic (optional)": "Thema (optional)", - "Make this room public": "Machen Sie diesen Raum öffentlich", + "Make this room public": "Mache diesen Raum öffentlich", "Hide advanced": "Weitere Einstellungen ausblenden", "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)": "Hindere Benutzer auf anderen Matrix-Homeservern daran, diesem Raum beizutreten (Diese Einstellung kann später nicht geändert werden!)", "Session name": "Name der Sitzung", "This will allow you to return to your account after signing out, and sign in on other sessions.": "So kannst du nach der Abmeldung zu deinem Konto zurückkehren und dich bei anderen Sitzungen anmelden.", "Use bots, bridges, widgets and sticker packs": "Benutze Bots, Bridges, Widgets und Sticker-Packs", "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Wenn du dein Passwort änderst, werden alle Ende-zu-Ende-Verschlüsselungsschlüssel für alle deine Sitzungen zurückgesetzt, sodass der verschlüsselte Chat-Verlauf nicht mehr lesbar ist. Richte ein Schlüssel-Backup ein oder exportiere deine Raumschlüssel aus einer anderen Sitzung, bevor du dein Passwort zurücksetzst.", - "You have been logged out of all sessions and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "Sie wurden von allen Sitzungen abgemeldet und erhalten keine Push-Benachrichtigungen mehr. Um die Benachrichtigungen wieder zu aktivieren, melden Sie sich auf jedem Gerät erneut an.", - "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Aktualisieren Sie diese Sitzung, damit sie andere Sitzungen verifizieren kann, indem sie ihnen Zugang zu verschlüsselten Nachrichten gewährt und sie für andere Benutzer als vertrauenswürdig markiert.", + "You have been logged out of all sessions and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "Du wurdest von allen Sitzungen abgemeldet und erhälst keine Push-Benachrichtigungen mehr. Um die Benachrichtigungen wieder zu aktivieren, melde dich auf jedem Gerät erneut an.", + "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Aktualisiere diese Sitzung, damit sie andere Sitzungen verifizieren kann, indem sie dir Zugang zu verschlüsselten Nachrichten gewährt und sie für andere Benutzer als vertrauenswürdig markiert.", "Sign out and remove encryption keys?": "Abmelden und Verschlüsselungsschlüssel entfernen?", "Sign in to your Matrix account on ": "Melde dich bei deinem Matrix-Konto auf an", "Enter your password to sign in and regain access to your account.": "Gib dein Passwort ein, um dich anzumelden und wieder Zugang zu deinem Konto zu erhalten.", From 342f1d5b438710daafc0ee0f4c1a94af8dcbf9ee Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 21 Sep 2020 14:36:16 -0600 Subject: [PATCH 0332/1014] Extremely bad support for "temporary widgets" --- src/FromWidgetPostMessageApi.js | 10 +- src/Modal.tsx | 2 +- src/WidgetMessaging.js | 30 ++++ .../views/dialogs/TempWidgetDialog.tsx | 155 ++++++++++++++++++ src/i18n/strings/en_EN.json | 4 + src/stores/TempWidgetStore.ts | 54 ++++++ src/widgets/WidgetApi.ts | 38 ++++- 7 files changed, 287 insertions(+), 6 deletions(-) create mode 100644 src/components/views/dialogs/TempWidgetDialog.tsx create mode 100644 src/stores/TempWidgetStore.ts diff --git a/src/FromWidgetPostMessageApi.js b/src/FromWidgetPostMessageApi.js index d5d7c08d50..c5a25468a8 100644 --- a/src/FromWidgetPostMessageApi.js +++ b/src/FromWidgetPostMessageApi.js @@ -24,8 +24,10 @@ import {MatrixClientPeg} from "./MatrixClientPeg"; import RoomViewStore from "./stores/RoomViewStore"; import {IntegrationManagers} from "./integrations/IntegrationManagers"; import SettingsStore from "./settings/SettingsStore"; -import {Capability} from "./widgets/WidgetApi"; +import {Capability, KnownWidgetActions} from "./widgets/WidgetApi"; import {objectClone} from "./utils/objects"; +import {Action} from "./dispatcher/actions"; +import {TempWidgetStore} from "./stores/TempWidgetStore"; const WIDGET_API_VERSION = '0.0.2'; // Current API version const SUPPORTED_WIDGET_API_VERSIONS = [ @@ -218,8 +220,12 @@ export default class FromWidgetPostMessageApi { if (ActiveWidgetStore.widgetHasCapability(widgetId, Capability.AlwaysOnScreen)) { ActiveWidgetStore.setWidgetPersistence(widgetId, val); } - } else if (action === 'get_openid') { + } else if (action === 'get_openid' + || action === KnownWidgetActions.CloseWidget) { // Handled by caller + } else if (action === KnownWidgetActions.OpenTempWidget) { + TempWidgetStore.instance.openTempWidget(event.data.data, widgetId); + this.sendResponse(event, {}); // ack } else { console.warn('Widget postMessage event unhandled'); this.sendError(event, {message: 'The postMessage was unhandled'}); diff --git a/src/Modal.tsx b/src/Modal.tsx index 0a36813961..3d95bc1a2b 100644 --- a/src/Modal.tsx +++ b/src/Modal.tsx @@ -28,7 +28,7 @@ import AsyncWrapper from './AsyncWrapper'; const DIALOG_CONTAINER_ID = "mx_Dialog_Container"; const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer"; -interface IModal { +export interface IModal { elem: React.ReactNode; className?: string; beforeClosePromise?: Promise; diff --git a/src/WidgetMessaging.js b/src/WidgetMessaging.js index c68e926ac1..6a2eeb852c 100644 --- a/src/WidgetMessaging.js +++ b/src/WidgetMessaging.js @@ -147,6 +147,36 @@ export default class WidgetMessaging { }); } + sendThemeInfo(themeInfo: any) { + return this.messageToWidget({ + api: OUTBOUND_API_NAME, + action: KnownWidgetActions.UpdateThemeInfo, + data: themeInfo, + }).catch((error) => { + console.error("Failed to send theme info: ", error); + }); + } + + sendWidgetConfig(widgetConfig: any) { + return this.messageToWidget({ + api: OUTBOUND_API_NAME, + action: KnownWidgetActions.SendWidgetConfig, + data: widgetConfig, + }).catch((error) => { + console.error("Failed to send widget info: ", error); + }); + } + + sendTempCloseInfo(info: any) { + return this.messageToWidget({ + api: OUTBOUND_API_NAME, + action: KnownWidgetActions.ClosedWidgetResponse, + data: info, + }).catch((error) => { + console.error("Failed to send temp widget close info: ", error); + }); + } + start() { this.fromWidget.addEndpoint(this.widgetId, this.renderedUrl); this.fromWidget.addListener("get_openid", this._onOpenIdRequest); diff --git a/src/components/views/dialogs/TempWidgetDialog.tsx b/src/components/views/dialogs/TempWidgetDialog.tsx new file mode 100644 index 0000000000..1fd3b26b5c --- /dev/null +++ b/src/components/views/dialogs/TempWidgetDialog.tsx @@ -0,0 +1,155 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import * as React from 'react'; +import BaseDialog from './BaseDialog'; +import { _t } from '../../../languageHandler'; +import { IDialogProps } from "./IDialogProps"; +import WidgetMessaging from "../../../WidgetMessaging"; +import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; +import Field from "../elements/Field"; +import { KnownWidgetActions } from "../../../widgets/WidgetApi"; +import ActiveWidgetStore from "../../../stores/ActiveWidgetStore"; + +interface IState { + messaging?: WidgetMessaging; + + androidMode: boolean; + darkTheme: boolean; + accentColor: string; +} + +interface IProps extends IDialogProps { + widgetDefinition: {url: string, data: any}; + sourceWidgetId: string; +} + +// TODO: Make a better dialog + +export default class TempWidgetDialog extends React.PureComponent { + private appFrame: React.RefObject = React.createRef(); + + constructor(props) { + super(props); + this.state = { + androidMode: false, + darkTheme: false, + accentColor: "#03b381", + }; + } + + public componentDidMount() { + // TODO: Don't violate every principle of widget creation + const messaging = new WidgetMessaging( + "TEMP_ID", + this.props.widgetDefinition.url, + this.props.widgetDefinition.url, + false, + this.appFrame.current.contentWindow, + ); + this.setState({messaging}); + } + + public componentWillUnmount() { + this.state.messaging.fromWidget.removeListener(KnownWidgetActions.CloseWidget, this.onWidgetClose); + this.state.messaging.stop(); + } + + private onLoad = () => { + this.state.messaging.getCapabilities().then(caps => { + console.log("Requested capabilities: ", caps); + this.sendTheme(); + this.state.messaging.sendWidgetConfig(this.props.widgetDefinition.data); + }); + this.state.messaging.fromWidget.addListener(KnownWidgetActions.CloseWidget, this.onWidgetClose); + }; + + private sendTheme() { + if (!this.state.messaging) return; + this.state.messaging.sendThemeInfo({ + clientName: this.state.androidMode ? "element-android" : "element-web", + isDark: this.state.darkTheme, + accentColor: this.state.accentColor, + }); + } + + public static sendExitData(sourceWidgetId: string, success: boolean, data?: any) { + const sourceMessaging = ActiveWidgetStore.getWidgetMessaging(sourceWidgetId); + if (!sourceMessaging) { + console.error("No source widget messaging for temp widget"); + return; + } + sourceMessaging.sendTempCloseInfo({success, ...data}); + } + + private onWidgetClose = (req) => { + this.props.onFinished(true); + TempWidgetDialog.sendExitData(this.props.sourceWidgetId, true, req.data); + } + + private onClientToggleChanged = (androidMode) => { + this.setState({androidMode}, () => this.sendTheme()); + }; + + private onDarkThemeChanged = (darkTheme) => { + this.setState({darkTheme}, () => this.sendTheme()); + }; + + private onAccentColorChanged = (ev) => { + this.setState({accentColor: ev.target.value}, () => this.sendTheme()); + }; + + public render() { + // TODO: Don't violate every single security principle + + const widgetUrl = this.props.widgetDefinition.url + + "?widgetId=TEMP_ID&parentUrl=" + encodeURIComponent(window.location.href); + + return +
    + + + +
    +
    + ; + return