From bf444ce22ef36f4ac9f830fe31365b096a0d3ee2 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 23 Apr 2021 11:31:27 -0500 Subject: [PATCH 01/38] Typo: initilisation -> initialisation Signed-off-by: Aaron Raimist --- src/components/views/settings/EventIndexPanel.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js index d1a02de16d..4af5ad6123 100644 --- a/src/components/views/settings/EventIndexPanel.js +++ b/src/components/views/settings/EventIndexPanel.js @@ -229,7 +229,7 @@ export default class EventIndexPanel extends React.Component {

{this.state.enabling ? - : _t("Message search initilisation failed") + : _t("Message search initialisation failed") }

{EventIndexPeg.error && ( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f1b700540f..5910ee803c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1086,7 +1086,7 @@ "Securely cache encrypted messages locally for them to appear in search results.": "Securely cache encrypted messages locally for them to appear in search results.", "%(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 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 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 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.", - "Message search initilisation failed": "Message search initilisation failed", + "Message search initialisation failed": "Message search initialisation failed", "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.", From 284b9e48cee1698fad9cf571709ba66e284e3aa8 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 23 Apr 2021 11:38:53 -0500 Subject: [PATCH 02/38] Also fix translations --- src/i18n/strings/cs.json | 2 +- src/i18n/strings/de_DE.json | 2 +- src/i18n/strings/es.json | 2 +- src/i18n/strings/et.json | 2 +- src/i18n/strings/fr.json | 2 +- src/i18n/strings/gl.json | 2 +- src/i18n/strings/hu.json | 2 +- src/i18n/strings/it.json | 2 +- src/i18n/strings/nl.json | 2 +- src/i18n/strings/sq.json | 2 +- src/i18n/strings/sv.json | 2 +- src/i18n/strings/zh_Hant.json | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 7f9ff9341c..87dbb6c70c 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -3168,7 +3168,7 @@ "Open": "Otevřít", "Share decryption keys for room history when inviting users": "Při pozvání uživatelů sdílet dešifrovací klíče pro historii místnosti", "Manage & explore rooms": "Spravovat a prozkoumat místnosti", - "Message search initilisation failed": "Inicializace vyhledávání zpráv se nezdařila", + "Message search initialisation failed": "Inicializace vyhledávání zpráv se nezdařila", "%(count)s people you know have already joined|one": "%(count)s osoba, kterou znáte, se již připojila", "Invited people will be able to read old messages.": "Pozvaní lidé budou moci číst staré zprávy.", "If you do, please note that none of your messages will be deleted, but the search experience might be degraded for a few momentswhilst the index is recreated": "Pokud tak učiníte, nezapomeňte, že žádná z vašich zpráv nebude smazána, ale vyhledávání může být na několik okamžiků degradováno, zatímco index bude znovu vytvářen", diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 9551f00e55..133cfc430d 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -3227,7 +3227,7 @@ "This homeserver has been blocked by it's administrator.": "Dieser Heimserver wurde von seiner Administration blockiert.", "You have unverified logins": "Du hast nicht-bestätigte Anmeldungen", "Review to ensure your account is safe": "Überprüfen, um sicher zu sein, dass dein Konto sicher ist", - "Message search initilisation failed": "Initialisierung der Nachrichtensuche fehlgeschlagen", + "Message search initialisation failed": "Initialisierung der Nachrichtensuche fehlgeschlagen", "Support": "Unterstützen", "This room is suggested as a good one to join": "Dieser Raum wurde als gut zum Beitreten vorgeschlagen", "Your message wasn't sent because this homeserver has been blocked by it's administrator. Please contact your service administrator to continue using the service.": "Deine Nachricht wurde nicht versendet, weil dieser Heimserver von dessen Administrator gesperrt wurde. Bitte kontaktiere deinen Dienstadministrator um den Dienst weiterzunutzen.", diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index d396cc318f..a41dc65cc3 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -3213,7 +3213,7 @@ "Review to ensure your account is safe": "Revisa que tu cuenta esté segura", "Sends the given message as a spoiler": "Envía el mensaje como un spoiler", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Consultando a %(transferTarget)s. Transferir a %(transferee)s", - "Message search initilisation failed": "Ha fallado la inicialización de la búsqueda de mensajes", + "Message search initialisation failed": "Ha fallado la inicialización de la búsqueda de mensajes", "Reset event store?": "¿Restablecer almacenamiento de eventos?", "You most likely do not want to reset your event index store": "Lo más probable es que no quieras restablecer tu almacenamiento de índice de ecentos", "If you do, please note that none of your messages will be deleted, but the search experience might be degraded for a few momentswhilst the index is recreated": "Si lo haces, ten en cuenta que no se borrarán tus mensajes, pero la experiencia de búsqueda será peor durante unos momentos mientras se recrea el índice", diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 444475deea..509008012d 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -3249,7 +3249,7 @@ "Reset event store": "Lähtesta sündmuste andmekogu", "Verify other login": "Verifitseeri muu sisselogimissessioon", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Suhtlen teise osapoolega %(transferTarget)s. Saadan andmeid kasutajale %(transferee)s", - "Message search initilisation failed": "Sõnumite otsingu alustamine ei õnnestunud", + "Message search initialisation failed": "Sõnumite otsingu alustamine ei õnnestunud", "Invite messages are hidden by default. Click to show the message.": "Kutsed on vaikimisi peidetud. Sõnumi nägemiseks klõpsi.", "Accept on your other login…": "Nõustu oma teise sisselogimissessiooniga…", "Avatar": "Tunnuspilt", diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index fcc5ec9afe..3962525271 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -3254,7 +3254,7 @@ "Quick actions": "Actions rapides", "Invite to just this room": "Inviter seulement dans ce salon", "Warn before quitting": "Avertir avant de quitter", - "Message search initilisation failed": "Échec de l’initialisation de la recherche de message", + "Message search initialisation failed": "Échec de l’initialisation de la recherche de message", "Manage & explore rooms": "Gérer et découvrir les salons", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Consultation avec %(transferTarget)s. Transfert à %(transferee)s", "unknown person": "personne inconnue", diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index f6ebce684f..4ac3e1bcdd 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -3269,7 +3269,7 @@ "unknown person": "persoa descoñecida", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Consultando con %(transferTarget)s. Transferir a %(transferee)s", "Manage & explore rooms": "Xestionar e explorar salas", - "Message search initilisation failed": "Fallo a inicialización da busca de mensaxes", + "Message search initialisation failed": "Fallo a inicialización da busca de mensaxes", "Quick actions": "Accións rápidas", "Invite messages are hidden by default. Click to show the message.": "As mensaxes de convite están agochadas por defecto. Preme para amosar a mensaxe.", "Record a voice message": "Gravar mensaxe de voz", diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 2ec5af8a17..64949ad848 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -3272,7 +3272,7 @@ "Quick actions": "Gyors műveletek", "Invite to just this room": "Meghívás csak ebbe a szobába", "Warn before quitting": "Kilépés előtt figyelmeztet", - "Message search initilisation failed": "Üzenet keresés beállítása sikertelen", + "Message search initialisation failed": "Üzenet keresés beállítása sikertelen", "Manage & explore rooms": "Szobák kezelése és felderítése", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Egyeztetés vele: %(transferTarget)s. Átadás ide: %(transferee)s", "unknown person": "ismeretlen személy", diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 2d0edb77e6..c4ed6cd640 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -3260,7 +3260,7 @@ "Invite to just this room": "Invita solo in questa stanza", "%(count)s people you know have already joined|other": "%(count)s persone che conosci sono già entrate", "%(count)s people you know have already joined|one": "%(count)s persona che conosci è già entrata", - "Message search initilisation failed": "Inizializzazione ricerca messaggi fallita", + "Message search initialisation failed": "Inizializzazione ricerca messaggi fallita", "Add existing rooms": "Aggiungi stanze esistenti", "Warn before quitting": "Avvisa prima di uscire", "Invited people will be able to read old messages.": "Le persone invitate potranno leggere i vecchi messaggi.", diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index de98a878e8..aa6482ccf5 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -3163,7 +3163,7 @@ "Quick actions": "Snelle acties", "Invite to just this room": "Uitnodigen voor alleen dit gesprek", "Warn before quitting": "Waarschuwen voordat u afsluit", - "Message search initilisation failed": "Zoeken in berichten opstarten is mislukt", + "Message search initialisation failed": "Zoeken in berichten opstarten is mislukt", "Manage & explore rooms": "Beheer & ontdek gesprekken", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Overleggen met %(transferTarget)s. Verstuur naar %(transferee)s", "unknown person": "onbekend persoon", diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index ad768b59cb..1eabf09cac 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -3265,7 +3265,7 @@ "Quick actions": "Veprime të shpejta", "Invite to just this room": "Ftoje thjesht te kjo dhomë", "Warn before quitting": "Sinjalizo përpara daljes", - "Message search initilisation failed": "Dështoi gatitje kërkimi mesazhesh", + "Message search initialisation failed": "Dështoi gatitje kërkimi mesazhesh", "Manage & explore rooms": "Administroni & eksploroni dhoma", "unknown person": "person i panjohur", "Sends the given message as a spoiler": "E dërgon mesazhin e dhënë si spoiler", diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 42a7f78268..6e839750ff 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -3212,7 +3212,7 @@ "Verification requested": "Verifiering begärd", "Sends the given message as a spoiler": "Skickar det angivna meddelandet som en spoiler", "Manage & explore rooms": "Hantera och utforska rum", - "Message search initilisation failed": "Initialisering av meddelandesökning misslyckades", + "Message search initialisation failed": "Initialisering av meddelandesökning misslyckades", "Please choose a strong password": "Vänligen välj ett starkt lösenord", "Use another login": "Använd annan inloggning", "Verify your identity to access encrypted messages and prove your identity to others.": "Verifiera din identitet för att komma åt krypterade meddelanden och bevisa din identitet för andra.", diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index c9bb9bb2d7..6d0e0854ed 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -3254,7 +3254,7 @@ "You have unverified logins": "您有未驗證的登入", "unknown person": "不明身份的人", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "與 %(transferTarget)s 進行協商。轉讓至 %(transferee)s", - "Message search initilisation failed": "訊息搜尋初始化失敗", + "Message search initialisation failed": "訊息搜尋初始化失敗", "Invite to just this room": "邀請到此聊天室", "If you do, please note that none of your messages will be deleted, but the search experience might be degraded for a few momentswhilst the index is recreated": "如果這樣做,請注意,您的任何訊息都不會被刪除,但是在重新建立索引的同時,搜索體驗可能會降低片刻", "Let's create a room for each of them.": "讓我們為每個主題建立一個聊天室吧。", From f1413532e7f1fb30c275b391f32e24961b729497 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Thu, 6 May 2021 00:07:55 -0500 Subject: [PATCH 03/38] Revert "Also fix translations" This reverts commit 284b9e48cee1698fad9cf571709ba66e284e3aa8. Signed-off-by: Aaron Raimist --- src/i18n/strings/cs.json | 2 +- src/i18n/strings/de_DE.json | 2 +- src/i18n/strings/es.json | 2 +- src/i18n/strings/et.json | 2 +- src/i18n/strings/fr.json | 2 +- src/i18n/strings/gl.json | 2 +- src/i18n/strings/hu.json | 2 +- src/i18n/strings/it.json | 2 +- src/i18n/strings/nl.json | 2 +- src/i18n/strings/sq.json | 2 +- src/i18n/strings/sv.json | 2 +- src/i18n/strings/zh_Hant.json | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 87dbb6c70c..7f9ff9341c 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -3168,7 +3168,7 @@ "Open": "Otevřít", "Share decryption keys for room history when inviting users": "Při pozvání uživatelů sdílet dešifrovací klíče pro historii místnosti", "Manage & explore rooms": "Spravovat a prozkoumat místnosti", - "Message search initialisation failed": "Inicializace vyhledávání zpráv se nezdařila", + "Message search initilisation failed": "Inicializace vyhledávání zpráv se nezdařila", "%(count)s people you know have already joined|one": "%(count)s osoba, kterou znáte, se již připojila", "Invited people will be able to read old messages.": "Pozvaní lidé budou moci číst staré zprávy.", "If you do, please note that none of your messages will be deleted, but the search experience might be degraded for a few momentswhilst the index is recreated": "Pokud tak učiníte, nezapomeňte, že žádná z vašich zpráv nebude smazána, ale vyhledávání může být na několik okamžiků degradováno, zatímco index bude znovu vytvářen", diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 133cfc430d..9551f00e55 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -3227,7 +3227,7 @@ "This homeserver has been blocked by it's administrator.": "Dieser Heimserver wurde von seiner Administration blockiert.", "You have unverified logins": "Du hast nicht-bestätigte Anmeldungen", "Review to ensure your account is safe": "Überprüfen, um sicher zu sein, dass dein Konto sicher ist", - "Message search initialisation failed": "Initialisierung der Nachrichtensuche fehlgeschlagen", + "Message search initilisation failed": "Initialisierung der Nachrichtensuche fehlgeschlagen", "Support": "Unterstützen", "This room is suggested as a good one to join": "Dieser Raum wurde als gut zum Beitreten vorgeschlagen", "Your message wasn't sent because this homeserver has been blocked by it's administrator. Please contact your service administrator to continue using the service.": "Deine Nachricht wurde nicht versendet, weil dieser Heimserver von dessen Administrator gesperrt wurde. Bitte kontaktiere deinen Dienstadministrator um den Dienst weiterzunutzen.", diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index a41dc65cc3..d396cc318f 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -3213,7 +3213,7 @@ "Review to ensure your account is safe": "Revisa que tu cuenta esté segura", "Sends the given message as a spoiler": "Envía el mensaje como un spoiler", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Consultando a %(transferTarget)s. Transferir a %(transferee)s", - "Message search initialisation failed": "Ha fallado la inicialización de la búsqueda de mensajes", + "Message search initilisation failed": "Ha fallado la inicialización de la búsqueda de mensajes", "Reset event store?": "¿Restablecer almacenamiento de eventos?", "You most likely do not want to reset your event index store": "Lo más probable es que no quieras restablecer tu almacenamiento de índice de ecentos", "If you do, please note that none of your messages will be deleted, but the search experience might be degraded for a few momentswhilst the index is recreated": "Si lo haces, ten en cuenta que no se borrarán tus mensajes, pero la experiencia de búsqueda será peor durante unos momentos mientras se recrea el índice", diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 509008012d..444475deea 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -3249,7 +3249,7 @@ "Reset event store": "Lähtesta sündmuste andmekogu", "Verify other login": "Verifitseeri muu sisselogimissessioon", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Suhtlen teise osapoolega %(transferTarget)s. Saadan andmeid kasutajale %(transferee)s", - "Message search initialisation failed": "Sõnumite otsingu alustamine ei õnnestunud", + "Message search initilisation failed": "Sõnumite otsingu alustamine ei õnnestunud", "Invite messages are hidden by default. Click to show the message.": "Kutsed on vaikimisi peidetud. Sõnumi nägemiseks klõpsi.", "Accept on your other login…": "Nõustu oma teise sisselogimissessiooniga…", "Avatar": "Tunnuspilt", diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 3962525271..fcc5ec9afe 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -3254,7 +3254,7 @@ "Quick actions": "Actions rapides", "Invite to just this room": "Inviter seulement dans ce salon", "Warn before quitting": "Avertir avant de quitter", - "Message search initialisation failed": "Échec de l’initialisation de la recherche de message", + "Message search initilisation failed": "Échec de l’initialisation de la recherche de message", "Manage & explore rooms": "Gérer et découvrir les salons", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Consultation avec %(transferTarget)s. Transfert à %(transferee)s", "unknown person": "personne inconnue", diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 4ac3e1bcdd..f6ebce684f 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -3269,7 +3269,7 @@ "unknown person": "persoa descoñecida", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Consultando con %(transferTarget)s. Transferir a %(transferee)s", "Manage & explore rooms": "Xestionar e explorar salas", - "Message search initialisation failed": "Fallo a inicialización da busca de mensaxes", + "Message search initilisation failed": "Fallo a inicialización da busca de mensaxes", "Quick actions": "Accións rápidas", "Invite messages are hidden by default. Click to show the message.": "As mensaxes de convite están agochadas por defecto. Preme para amosar a mensaxe.", "Record a voice message": "Gravar mensaxe de voz", diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 64949ad848..2ec5af8a17 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -3272,7 +3272,7 @@ "Quick actions": "Gyors műveletek", "Invite to just this room": "Meghívás csak ebbe a szobába", "Warn before quitting": "Kilépés előtt figyelmeztet", - "Message search initialisation failed": "Üzenet keresés beállítása sikertelen", + "Message search initilisation failed": "Üzenet keresés beállítása sikertelen", "Manage & explore rooms": "Szobák kezelése és felderítése", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Egyeztetés vele: %(transferTarget)s. Átadás ide: %(transferee)s", "unknown person": "ismeretlen személy", diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index c4ed6cd640..2d0edb77e6 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -3260,7 +3260,7 @@ "Invite to just this room": "Invita solo in questa stanza", "%(count)s people you know have already joined|other": "%(count)s persone che conosci sono già entrate", "%(count)s people you know have already joined|one": "%(count)s persona che conosci è già entrata", - "Message search initialisation failed": "Inizializzazione ricerca messaggi fallita", + "Message search initilisation failed": "Inizializzazione ricerca messaggi fallita", "Add existing rooms": "Aggiungi stanze esistenti", "Warn before quitting": "Avvisa prima di uscire", "Invited people will be able to read old messages.": "Le persone invitate potranno leggere i vecchi messaggi.", diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index aa6482ccf5..de98a878e8 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -3163,7 +3163,7 @@ "Quick actions": "Snelle acties", "Invite to just this room": "Uitnodigen voor alleen dit gesprek", "Warn before quitting": "Waarschuwen voordat u afsluit", - "Message search initialisation failed": "Zoeken in berichten opstarten is mislukt", + "Message search initilisation failed": "Zoeken in berichten opstarten is mislukt", "Manage & explore rooms": "Beheer & ontdek gesprekken", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Overleggen met %(transferTarget)s. Verstuur naar %(transferee)s", "unknown person": "onbekend persoon", diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index 1eabf09cac..ad768b59cb 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -3265,7 +3265,7 @@ "Quick actions": "Veprime të shpejta", "Invite to just this room": "Ftoje thjesht te kjo dhomë", "Warn before quitting": "Sinjalizo përpara daljes", - "Message search initialisation failed": "Dështoi gatitje kërkimi mesazhesh", + "Message search initilisation failed": "Dështoi gatitje kërkimi mesazhesh", "Manage & explore rooms": "Administroni & eksploroni dhoma", "unknown person": "person i panjohur", "Sends the given message as a spoiler": "E dërgon mesazhin e dhënë si spoiler", diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 6e839750ff..42a7f78268 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -3212,7 +3212,7 @@ "Verification requested": "Verifiering begärd", "Sends the given message as a spoiler": "Skickar det angivna meddelandet som en spoiler", "Manage & explore rooms": "Hantera och utforska rum", - "Message search initialisation failed": "Initialisering av meddelandesökning misslyckades", + "Message search initilisation failed": "Initialisering av meddelandesökning misslyckades", "Please choose a strong password": "Vänligen välj ett starkt lösenord", "Use another login": "Använd annan inloggning", "Verify your identity to access encrypted messages and prove your identity to others.": "Verifiera din identitet för att komma åt krypterade meddelanden och bevisa din identitet för andra.", diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 6d0e0854ed..c9bb9bb2d7 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -3254,7 +3254,7 @@ "You have unverified logins": "您有未驗證的登入", "unknown person": "不明身份的人", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "與 %(transferTarget)s 進行協商。轉讓至 %(transferee)s", - "Message search initialisation failed": "訊息搜尋初始化失敗", + "Message search initilisation failed": "訊息搜尋初始化失敗", "Invite to just this room": "邀請到此聊天室", "If you do, please note that none of your messages will be deleted, but the search experience might be degraded for a few momentswhilst the index is recreated": "如果這樣做,請注意,您的任何訊息都不會被刪除,但是在重新建立索引的同時,搜索體驗可能會降低片刻", "Let's create a room for each of them.": "讓我們為每個主題建立一個聊天室吧。", From 6f98aa06c4d3247f4561224f39bea210327f50a1 Mon Sep 17 00:00:00 2001 From: Jaiwanth Date: Sun, 9 May 2021 13:18:01 +0530 Subject: [PATCH 04/38] Save edited state when switching rooms Signed-off-by: Jaiwanth --- src/components/structures/MessagePanel.js | 11 ++++- .../views/rooms/EditMessageComposer.js | 45 +++++++++++++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index c93f07fa0f..555ee38d17 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -34,6 +34,7 @@ import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResiz import DMRoomMap from "../../utils/DMRoomMap"; import NewRoomIntro from "../views/rooms/NewRoomIntro"; import {replaceableComponent} from "../../utils/replaceableComponent"; +import defaultDispatcher from '../../dispatcher/dispatcher'; const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes const continuedTypes = ['m.sticker', 'm.room.message']; @@ -564,15 +565,23 @@ export default class MessagePanel extends React.Component { return ret; } + _wasEventBeingEdited = (mxEv) => { + return localStorage.getItem(`mx_edit_state_${mxEv.getRoomId()} + _${mxEv.getId()}`) !== null; + } + _getTilesForEvent(prevEvent, mxEv, last, nextEvent, nextEventWithTile) { const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary'); const EventTile = sdk.getComponent('rooms.EventTile'); const DateSeparator = sdk.getComponent('messages.DateSeparator'); const ret = []; + if (!this.props.editState && this._wasEventBeingEdited(mxEv) ) { + defaultDispatcher.dispatch({action: "edit_event", event: mxEv}); + } + const isEditing = this.props.editState && this.props.editState.getEvent().getId() === mxEv.getId(); - // local echoes have a fake date, which could even be yesterday. Treat them // as 'today' for the date separators. let ts1 = mxEv.getTs(); diff --git a/src/components/views/rooms/EditMessageComposer.js b/src/components/views/rooms/EditMessageComposer.js index b006fe8c8d..dd87654438 100644 --- a/src/components/views/rooms/EditMessageComposer.js +++ b/src/components/views/rooms/EditMessageComposer.js @@ -34,6 +34,7 @@ import {Action} from "../../../dispatcher/actions"; import CountlyAnalytics from "../../../CountlyAnalytics"; import {getKeyBindingsManager, MessageComposerAction} from '../../../KeyBindingsManager'; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import SendHistoryManager from '../../../SendHistoryManager'; function _isReply(mxEvent) { const relatesTo = mxEvent.getContent()["m.relates_to"]; @@ -120,6 +121,7 @@ export default class EditMessageComposer extends React.Component { saveDisabled: true, }; this._createEditorModel(); + window.addEventListener("beforeunload", this._saveStoredEditorState); } _setEditorRef = ref => { @@ -174,10 +176,43 @@ export default class EditMessageComposer extends React.Component { } _cancelEdit = () => { + this._clearStoredEditorState(); dis.dispatch({action: "edit_event", event: null}); dis.fire(Action.FocusComposer); } + get _shouldSaveStoredEditorState() { + return localStorage.getItem(`mx_edit_state_${this.props.editState.getEvent().getRoomId()} + _${this.props.editState.getEvent().event.event_id}`) !== null; + } + + _restoreStoredEditorState(partCreator) { + const json = localStorage.getItem(this._editorStateKey); + if (json) { + try { + const {parts: serializedParts} = JSON.parse(json); + const parts = serializedParts.map(p => partCreator.deserializePart(p)); + return parts; + } catch (e) { + console.error(e); + } + } + } + + get _editorStateKey() { + return `mx_edit_state_${this.props.editState.getEvent().getRoomId()} + _${this.props.editState.getEvent().event.event_id}`; + } + + _clearStoredEditorState() { + localStorage.removeItem(this._editorStateKey); + } + + _saveStoredEditorState() { + const item = SendHistoryManager.createItem(this.model); + localStorage.setItem(this._editorStateKey, JSON.stringify(item)); + } + _isContentModified(newContent) { // if nothing has changed then bail const oldContent = this.props.editState.getEvent().getContent(); @@ -195,13 +230,13 @@ export default class EditMessageComposer extends React.Component { const editedEvent = this.props.editState.getEvent(); const editContent = createEditContent(this.model, editedEvent); const newContent = editContent["m.new_content"]; - // If content is modified then send an updated event into the room if (this._isContentModified(newContent)) { const roomId = editedEvent.getRoomId(); this._cancelPreviousPendingEdit(); const prom = this.context.sendMessage(roomId, editContent); dis.dispatch({action: "message_sent"}); + this._clearStoredEditorState(); CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, true, false, editContent); } @@ -235,6 +270,10 @@ export default class EditMessageComposer extends React.Component { // then when mounting the editor again with the same editor state, // it will set the cursor at the end. this.props.editState.setEditorState(caret, parts); + window.removeEventListener("beforeunload", this._saveStoredEditorState); + if (this._shouldSaveStoredEditorState) { + this._saveStoredEditorState(); + } } _createEditorModel() { @@ -247,10 +286,10 @@ export default class EditMessageComposer extends React.Component { // restore serialized parts from the state parts = editState.getSerializedParts().map(p => partCreator.deserializePart(p)); } else { - // otherwise, parse the body of the event - parts = parseEvent(editState.getEvent(), partCreator); + parts = this._restoreStoredEditorState(partCreator) || parseEvent(editState.getEvent(), partCreator); } this.model = new EditorModel(parts, partCreator); + this._saveStoredEditorState(); } _getInitialCaretPosition() { From 1f7704875013b70717fa0866a3db028319b2c972 Mon Sep 17 00:00:00 2001 From: Jaiwanth Date: Sun, 9 May 2021 16:12:04 +0530 Subject: [PATCH 05/38] Minor refactor --- src/components/views/rooms/EditMessageComposer.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/rooms/EditMessageComposer.js b/src/components/views/rooms/EditMessageComposer.js index dd87654438..46999bb793 100644 --- a/src/components/views/rooms/EditMessageComposer.js +++ b/src/components/views/rooms/EditMessageComposer.js @@ -182,8 +182,7 @@ export default class EditMessageComposer extends React.Component { } get _shouldSaveStoredEditorState() { - return localStorage.getItem(`mx_edit_state_${this.props.editState.getEvent().getRoomId()} - _${this.props.editState.getEvent().event.event_id}`) !== null; + return localStorage.getItem(this._editorStateKey) !== null; } _restoreStoredEditorState(partCreator) { From 376befd38eb87dc9afd5eaed64b79a5fcdc5eba1 Mon Sep 17 00:00:00 2001 From: Jaiwanth Date: Tue, 11 May 2021 10:37:53 +0530 Subject: [PATCH 06/38] Update src/components/views/rooms/EditMessageComposer.js Co-authored-by: Travis Ralston --- src/components/views/rooms/EditMessageComposer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/EditMessageComposer.js b/src/components/views/rooms/EditMessageComposer.js index 46999bb793..81a26df56c 100644 --- a/src/components/views/rooms/EditMessageComposer.js +++ b/src/components/views/rooms/EditMessageComposer.js @@ -193,7 +193,7 @@ export default class EditMessageComposer extends React.Component { const parts = serializedParts.map(p => partCreator.deserializePart(p)); return parts; } catch (e) { - console.error(e); + console.error("Error parsing editing state: ", e); } } } From 240753a84f0fdb6eb8db842fec4a578494132aac Mon Sep 17 00:00:00 2001 From: Jaiwanth Date: Tue, 11 May 2021 18:37:41 +0530 Subject: [PATCH 07/38] Check for a pending edit only once per render and clear any pending events while switching between edits --- src/components/structures/MessagePanel.js | 20 +++++++++------- .../views/rooms/EditMessageComposer.js | 24 ++++++++++++++----- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 00f2c4c826..73a2a3c4b6 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -472,6 +472,10 @@ export default class MessagePanel extends React.Component { return {nextEvent, nextTile}; } + get _roomHasPendingEdit() { + return localStorage.getItem(`mx_edit_room_${this.props.room.roomId}`); + } + _getEventTiles() { this.eventNodes = {}; @@ -560,6 +564,13 @@ export default class MessagePanel extends React.Component { } } + if (!this.props.editState && this._roomHasPendingEdit) { + defaultDispatcher.dispatch({ + action: "edit_event", + event: this.props.room.findEventById(this._roomHasPendingEdit), + }); + } + if (grouper) { ret.push(...grouper.getTiles()); } @@ -567,21 +578,12 @@ export default class MessagePanel extends React.Component { return ret; } - _wasEventBeingEdited = (mxEv) => { - return localStorage.getItem(`mx_edit_state_${mxEv.getRoomId()} - _${mxEv.getId()}`) !== null; - } - _getTilesForEvent(prevEvent, mxEv, last, isGrouped=false, nextEvent, nextEventWithTile) { const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary'); const EventTile = sdk.getComponent('rooms.EventTile'); const DateSeparator = sdk.getComponent('messages.DateSeparator'); const ret = []; - if (!this.props.editState && this._wasEventBeingEdited(mxEv) ) { - defaultDispatcher.dispatch({action: "edit_event", event: mxEv}); - } - const isEditing = this.props.editState && this.props.editState.getEvent().getId() === mxEv.getId(); // local echoes have a fake date, which could even be yesterday. Treat them diff --git a/src/components/views/rooms/EditMessageComposer.js b/src/components/views/rooms/EditMessageComposer.js index ceb49b32c6..897388d5d2 100644 --- a/src/components/views/rooms/EditMessageComposer.js +++ b/src/components/views/rooms/EditMessageComposer.js @@ -177,6 +177,14 @@ export default class EditMessageComposer extends React.Component { } } + get _editorRoomKey() { + return `mx_edit_room_${this._getRoom().roomId}`; + } + + get _editorStateKey() { + return `mx_edit_state_${this.props.editState.getEvent().getId()}`; + } + _cancelEdit = () => { this._clearStoredEditorState(); dis.dispatch({action: "edit_event", event: null}); @@ -184,7 +192,7 @@ export default class EditMessageComposer extends React.Component { } get _shouldSaveStoredEditorState() { - return localStorage.getItem(this._editorStateKey) !== null; + return localStorage.getItem(this._editorRoomKey) !== null; } _restoreStoredEditorState(partCreator) { @@ -200,17 +208,21 @@ export default class EditMessageComposer extends React.Component { } } - get _editorStateKey() { - return `mx_edit_state_${this.props.editState.getEvent().getRoomId()} - _${this.props.editState.getEvent().event.event_id}`; + _clearStoredEditorState() { + localStorage.removeItem(this._editorRoomKey); + localStorage.removeItem(this._editorStateKey); } - _clearStoredEditorState() { - localStorage.removeItem(this._editorStateKey); + _clearPreviousEdit() { + if (localStorage.getItem(this._editorRoomKey)) { + localStorage.removeItem(`mx_edit_state_${localStorage.getItem(this._editorRoomKey)}`); + } } _saveStoredEditorState() { const item = SendHistoryManager.createItem(this.model); + this._clearPreviousEdit(); + localStorage.setItem(this._editorRoomKey, this.props.editState.getEvent().getId()); localStorage.setItem(this._editorStateKey, JSON.stringify(item)); } From 565e41c3dfb24b3a318ee46d4e41a9dd4fe60528 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 12 May 2021 17:04:24 +0100 Subject: [PATCH 08/38] Extract UntrustedDeviceDialog and fix e2ee icon --- res/css/_components.scss | 1 + .../views/dialogs/_UntrustedDeviceDialog.scss | 26 ++++++++ .../views/dialogs/UntrustedDeviceDialog.js | 61 +++++++++++++++++++ src/verification.js | 35 +---------- 4 files changed, 89 insertions(+), 34 deletions(-) create mode 100644 res/css/views/dialogs/_UntrustedDeviceDialog.scss create mode 100644 src/components/views/dialogs/UntrustedDeviceDialog.js diff --git a/res/css/_components.scss b/res/css/_components.scss index d8e5247f9d..c8985cbb51 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -98,6 +98,7 @@ @import "./views/dialogs/_SpaceSettingsDialog.scss"; @import "./views/dialogs/_TabbedIntegrationManagerDialog.scss"; @import "./views/dialogs/_TermsDialog.scss"; +@import "./views/dialogs/_UntrustedDeviceDialog.scss"; @import "./views/dialogs/_UploadConfirmDialog.scss"; @import "./views/dialogs/_UserSettingsDialog.scss"; @import "./views/dialogs/_WidgetCapabilitiesPromptDialog.scss"; diff --git a/res/css/views/dialogs/_UntrustedDeviceDialog.scss b/res/css/views/dialogs/_UntrustedDeviceDialog.scss new file mode 100644 index 0000000000..0ecd9d4f71 --- /dev/null +++ b/res/css/views/dialogs/_UntrustedDeviceDialog.scss @@ -0,0 +1,26 @@ +/* +Copyright 2021 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_UntrustedDeviceDialog { + .mx_Dialog_title { + display: flex; + align-items: center; + + .mx_E2EIcon { + margin-left: 0; + } + } +} diff --git a/src/components/views/dialogs/UntrustedDeviceDialog.js b/src/components/views/dialogs/UntrustedDeviceDialog.js new file mode 100644 index 0000000000..11cbef39a1 --- /dev/null +++ b/src/components/views/dialogs/UntrustedDeviceDialog.js @@ -0,0 +1,61 @@ +/* +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 { _t } from '../../../languageHandler'; +import * as sdk from '../../../index'; +import {MatrixClientPeg} from '../../../MatrixClientPeg'; +import E2EIcon from "../rooms/E2EIcon"; + +function UntrustedDeviceDialog(props) { + const {device, user, onFinished} = props; + const BaseDialog = sdk.getComponent("dialogs.BaseDialog"); + const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); + let askToVerifyText; + let newSessionText; + + if (MatrixClientPeg.get().getUserId() === user.userId) { + newSessionText = _t("You signed in to a new session without verifying it:"); + askToVerifyText = _t("Verify your other session using one of the options below."); + } else { + newSessionText = _t("%(name)s (%(userId)s) signed in to a new session without verifying it:", + {name: user.displayName, userId: user.userId}); + askToVerifyText = _t("Ask this user to verify their session, or manually verify it below."); + } + + return + + { _t("Not Trusted")} + } + > +
+

{newSessionText}

+

{device.getDisplayName()} ({device.deviceId})

+

{askToVerifyText}

+
+
+ onFinished("legacy")}>{_t("Manually Verify by Text")} + onFinished("sas")}>{_t("Interactively verify by Emoji")} + onFinished()}>{_t("Done")} +
+
; +} + +export default UntrustedDeviceDialog; diff --git a/src/verification.js b/src/verification.js index 74e3897d3a..69577384a1 100644 --- a/src/verification.js +++ b/src/verification.js @@ -18,12 +18,12 @@ import {MatrixClientPeg} from './MatrixClientPeg'; import dis from "./dispatcher/dispatcher"; import Modal from './Modal'; import * as sdk from './index'; -import { _t } from './languageHandler'; import {RightPanelPhases} from "./stores/RightPanelStorePhases"; import {findDMForUser} from './createRoom'; import {accessSecretStorage} from './SecurityManager'; import {verificationMethods} from 'matrix-js-sdk/src/crypto'; import {Action} from './dispatcher/actions'; +import UntrustedDeviceDialog from "./components/views/dialogs/UntrustedDeviceDialog"; async function enable4SIfNeeded() { const cli = MatrixClientPeg.get(); @@ -39,39 +39,6 @@ async function enable4SIfNeeded() { return true; } -function UntrustedDeviceDialog(props) { - const {device, user, onFinished} = props; - const BaseDialog = sdk.getComponent("dialogs.BaseDialog"); - const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); - let askToVerifyText; - let newSessionText; - - if (MatrixClientPeg.get().getUserId() === user.userId) { - newSessionText = _t("You signed in to a new session without verifying it:"); - askToVerifyText = _t("Verify your other session using one of the options below."); - } else { - newSessionText = _t("%(name)s (%(userId)s) signed in to a new session without verifying it:", - {name: user.displayName, userId: user.userId}); - askToVerifyText = _t("Ask this user to verify their session, or manually verify it below."); - } - - return -
-

{newSessionText}

-

{device.getDisplayName()} ({device.deviceId})

-

{askToVerifyText}

-
-
- onFinished("legacy")}>{_t("Manually Verify by Text")} - onFinished("sas")}>{_t("Interactively verify by Emoji")} - onFinished()}>{_t("Done")} -
-
; -} - export async function verifyDevice(user, device) { const cli = MatrixClientPeg.get(); if (cli.isGuest()) { From 5430f44c2746faa181a9f37264bdf69b186ceaaf Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 12 May 2021 17:08:44 +0100 Subject: [PATCH 09/38] Convert verification and UntrustedDeviceDialog to TS --- ...iceDialog.js => UntrustedDeviceDialog.tsx} | 38 ++++++++++++------- src/components/views/right_panel/UserInfo.tsx | 2 +- src/{verification.js => verification.ts} | 25 ++++++------ 3 files changed, 40 insertions(+), 25 deletions(-) rename src/components/views/dialogs/{UntrustedDeviceDialog.js => UntrustedDeviceDialog.tsx} (67%) rename src/{verification.js => verification.ts} (83%) diff --git a/src/components/views/dialogs/UntrustedDeviceDialog.js b/src/components/views/dialogs/UntrustedDeviceDialog.tsx similarity index 67% rename from src/components/views/dialogs/UntrustedDeviceDialog.js rename to src/components/views/dialogs/UntrustedDeviceDialog.tsx index 11cbef39a1..da1492e30d 100644 --- a/src/components/views/dialogs/UntrustedDeviceDialog.js +++ b/src/components/views/dialogs/UntrustedDeviceDialog.tsx @@ -1,5 +1,5 @@ /* -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020, 2021 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. @@ -14,17 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React from "react"; +import { User } from "matrix-js-sdk/src/models/user"; -import { _t } from '../../../languageHandler'; -import * as sdk from '../../../index'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; +import { _t } from "../../../languageHandler"; +import { MatrixClientPeg } from "../../../MatrixClientPeg"; import E2EIcon from "../rooms/E2EIcon"; +import AccessibleButton from "../elements/AccessibleButton"; +import BaseDialog from "./BaseDialog"; +import { IDialogProps } from "./IDialogProps"; +import { IDevice } from "../right_panel/UserInfo"; -function UntrustedDeviceDialog(props) { - const {device, user, onFinished} = props; - const BaseDialog = sdk.getComponent("dialogs.BaseDialog"); - const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); +interface IProps extends IDialogProps { + user: User; + device: IDevice; +} + +const UntrustedDeviceDialog: React.FC = ({device, user, onFinished}) => { let askToVerifyText; let newSessionText; @@ -51,11 +57,17 @@ function UntrustedDeviceDialog(props) {

{askToVerifyText}

- onFinished("legacy")}>{_t("Manually Verify by Text")} - onFinished("sas")}>{_t("Interactively verify by Emoji")} - onFinished()}>{_t("Done")} + onFinished("legacy")}> + { _t("Manually Verify by Text") } + + onFinished("sas")}> + { _t("Interactively verify by Emoji") } + + onFinished(false)}> + { _t("Done") } +
; -} +}; export default UntrustedDeviceDialog; diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index faf8860cad..d5f67623a2 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -67,7 +67,7 @@ import RoomAvatar from "../avatars/RoomAvatar"; import RoomName from "../elements/RoomName"; import {mediaFromMxc} from "../../../customisations/Media"; -interface IDevice { +export interface IDevice { deviceId: string; ambiguous?: boolean; getDisplayName(): string; diff --git a/src/verification.js b/src/verification.ts similarity index 83% rename from src/verification.js rename to src/verification.ts index 69577384a1..acd9f6d2b2 100644 --- a/src/verification.js +++ b/src/verification.ts @@ -1,5 +1,5 @@ /* -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020, 2021 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. @@ -14,16 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {MatrixClientPeg} from './MatrixClientPeg'; +import { User } from "matrix-js-sdk/src/models/user"; + +import { MatrixClientPeg } from './MatrixClientPeg'; import dis from "./dispatcher/dispatcher"; import Modal from './Modal'; import * as sdk from './index'; -import {RightPanelPhases} from "./stores/RightPanelStorePhases"; -import {findDMForUser} from './createRoom'; -import {accessSecretStorage} from './SecurityManager'; -import {verificationMethods} from 'matrix-js-sdk/src/crypto'; -import {Action} from './dispatcher/actions'; +import { RightPanelPhases } from "./stores/RightPanelStorePhases"; +import { findDMForUser } from './createRoom'; +import { accessSecretStorage } from './SecurityManager'; +import { verificationMethods } from 'matrix-js-sdk/src/crypto'; +import { Action } from './dispatcher/actions'; import UntrustedDeviceDialog from "./components/views/dialogs/UntrustedDeviceDialog"; +import {IDevice} from "./components/views/right_panel/UserInfo"; async function enable4SIfNeeded() { const cli = MatrixClientPeg.get(); @@ -39,7 +42,7 @@ async function enable4SIfNeeded() { return true; } -export async function verifyDevice(user, device) { +export async function verifyDevice(user: User, device: IDevice) { const cli = MatrixClientPeg.get(); if (cli.isGuest()) { dis.dispatch({action: 'require_registration'}); @@ -82,7 +85,7 @@ export async function verifyDevice(user, device) { }); } -export async function legacyVerifyUser(user) { +export async function legacyVerifyUser(user: User) { const cli = MatrixClientPeg.get(); if (cli.isGuest()) { dis.dispatch({action: 'require_registration'}); @@ -102,7 +105,7 @@ export async function legacyVerifyUser(user) { }); } -export async function verifyUser(user) { +export async function verifyUser(user: User) { const cli = MatrixClientPeg.get(); if (cli.isGuest()) { dis.dispatch({action: 'require_registration'}); @@ -122,7 +125,7 @@ export async function verifyUser(user) { }); } -export function pendingVerificationRequestForUser(user) { +export function pendingVerificationRequestForUser(user: User) { const cli = MatrixClientPeg.get(); const dmRoom = findDMForUser(cli, user.userId); if (dmRoom) { From 236fcecf9cac5e97f5de92cc1458bffb6ac41878 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 12 May 2021 17:13:23 +0100 Subject: [PATCH 10/38] i18n --- src/i18n/strings/en_EN.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index da58680424..b977c2073a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -578,14 +578,6 @@ "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", "Light": "Light", "Dark": "Dark", - "You signed in to a new session without verifying it:": "You signed in to a new session without verifying it:", - "Verify your other session using one of the options below.": "Verify your other session using one of the options below.", - "%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s (%(userId)s) signed in to a new session without verifying it:", - "Ask this user to verify their session, or manually verify it below.": "Ask this user to verify their session, or manually verify it below.", - "Not Trusted": "Not Trusted", - "Manually Verify by Text": "Manually Verify by Text", - "Interactively verify by Emoji": "Interactively verify by Emoji", - "Done": "Done", "%(displayName)s is typing …": "%(displayName)s is typing …", "%(names)s and %(count)s others are typing …|other": "%(names)s and %(count)s others are typing …", "%(names)s and %(count)s others are typing …|one": "%(names)s and one other is typing …", @@ -2066,6 +2058,7 @@ "Close dialog": "Close dialog", "Beta feedback": "Beta feedback", "Thank you for your feedback, we really appreciate it.": "Thank you for your feedback, we really appreciate it.", + "Done": "Done", "Your platform and username will be noted to help us use your feedback as much as we can.": "Your platform and username will be noted to help us use your feedback as much as we can.", "To leave the beta, visit your settings.": "To leave the beta, visit your settings.", "Feedback": "Feedback", @@ -2388,6 +2381,13 @@ "Summary": "Summary", "Document": "Document", "Next": "Next", + "You signed in to a new session without verifying it:": "You signed in to a new session without verifying it:", + "Verify your other session using one of the options below.": "Verify your other session using one of the options below.", + "%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s (%(userId)s) signed in to a new session without verifying it:", + "Ask this user to verify their session, or manually verify it below.": "Ask this user to verify their session, or manually verify it below.", + "Not Trusted": "Not Trusted", + "Manually Verify by Text": "Manually Verify by Text", + "Interactively verify by Emoji": "Interactively verify by Emoji", "Upload files (%(current)s of %(total)s)": "Upload files (%(current)s of %(total)s)", "Upload files": "Upload files", "Upload all": "Upload all", From 654ce95c743fe1151d59905d7f362c999e5a5a8d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 12 May 2021 17:31:55 +0100 Subject: [PATCH 11/38] Progress from adding existing rooms to new space upon completion --- src/components/structures/SpaceRoomView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index ed0ae1afe7..828b2ff5fe 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -546,6 +546,7 @@ const SpaceAddExistingRooms = ({ space, onFinished }) => { } } setBusy(false); + onFinished(); }; buttonLabel = busy ? _t("Adding...") : _t("Add"); } From 9beb2b8d789bcda4da0fc8bc0a788b8dfe8e5814 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 12 May 2021 21:15:17 -0600 Subject: [PATCH 12/38] Try putting room list handling behind a lock Some of the logs relating to room list corruption appear to be badly timed race conditions so we'll try to linearize them here. --- src/stores/room-list/algorithms/Algorithm.ts | 315 ++++++++++--------- src/utils/MultiLock.ts | 30 ++ 2 files changed, 193 insertions(+), 152 deletions(-) create mode 100644 src/utils/MultiLock.ts diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index 024c484c41..395591d321 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -34,6 +34,7 @@ import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm"; import { getListAlgorithmInstance } from "./list-ordering"; import SettingsStore from "../../../settings/SettingsStore"; import { VisibilityProvider } from "../filters/VisibilityProvider"; +import {MultiLock} from "../../../utils/MultiLock"; /** * Fired when the Algorithm has determined a list has been updated. @@ -77,6 +78,7 @@ export class Algorithm extends EventEmitter { } = {}; private allowedByFilter: Map = new Map(); private allowedRoomsByFilters: Set = new Set(); + private handlerLock = new MultiLock(); /** * Set to true to suspend emissions of algorithm updates. @@ -679,191 +681,200 @@ export class Algorithm extends EventEmitter { public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise { if (SettingsStore.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`Handle room update for ${room.roomId} called with cause ${cause}`); + console.log(`Acquiring lock for ${room.roomId} with cause ${cause}`); } - if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from"); - - // Note: check the isSticky against the room ID just in case the reference is wrong - const isSticky = this._stickyRoom && this._stickyRoom.room && this._stickyRoom.room.roomId === room.roomId; - if (cause === RoomUpdateCause.NewRoom) { - const isForLastSticky = this._lastStickyRoom && this._lastStickyRoom.room === room; - const roomTags = this.roomIdsToTags[room.roomId]; - const hasTags = roomTags && roomTags.length > 0; - - // Don't change the cause if the last sticky room is being re-added. If we fail to - // pass the cause through as NewRoom, we'll fail to lie to the algorithm and thus - // lose the room. - if (hasTags && !isForLastSticky) { - console.warn(`${room.roomId} is reportedly new but is already known - assuming TagChange instead`); - cause = RoomUpdateCause.PossibleTagChange; + const release = await this.handlerLock.acquire(room.roomId); + try { + if (SettingsStore.getValue("advancedRoomListLogging")) { + // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 + console.log(`Handle room update for ${room.roomId} called with cause ${cause}`); } + if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from"); - // Check to see if the room is known first - let knownRoomRef = this.rooms.includes(room); - if (hasTags && !knownRoomRef) { - console.warn(`${room.roomId} might be a reference change - attempting to update reference`); - this.rooms = this.rooms.map(r => r.roomId === room.roomId ? room : r); - knownRoomRef = this.rooms.includes(room); - if (!knownRoomRef) { - console.warn(`${room.roomId} is still not referenced. It may be sticky.`); + // Note: check the isSticky against the room ID just in case the reference is wrong + const isSticky = this._stickyRoom && this._stickyRoom.room && this._stickyRoom.room.roomId === room.roomId; + if (cause === RoomUpdateCause.NewRoom) { + const isForLastSticky = this._lastStickyRoom && this._lastStickyRoom.room === room; + const roomTags = this.roomIdsToTags[room.roomId]; + const hasTags = roomTags && roomTags.length > 0; + + // Don't change the cause if the last sticky room is being re-added. If we fail to + // pass the cause through as NewRoom, we'll fail to lie to the algorithm and thus + // lose the room. + if (hasTags && !isForLastSticky) { + console.warn(`${room.roomId} is reportedly new but is already known - assuming TagChange instead`); + cause = RoomUpdateCause.PossibleTagChange; + } + + // Check to see if the room is known first + let knownRoomRef = this.rooms.includes(room); + if (hasTags && !knownRoomRef) { + console.warn(`${room.roomId} might be a reference change - attempting to update reference`); + this.rooms = this.rooms.map(r => r.roomId === room.roomId ? room : r); + knownRoomRef = this.rooms.includes(room); + if (!knownRoomRef) { + console.warn(`${room.roomId} is still not referenced. It may be sticky.`); + } + } + + // If we have tags for a room and don't have the room referenced, something went horribly + // wrong - the reference should have been updated above. + if (hasTags && !knownRoomRef && !isSticky) { + throw new Error(`${room.roomId} is missing from room array but is known - trying to find duplicate`); + } + + // Like above, update the reference to the sticky room if we need to + if (hasTags && isSticky) { + // Go directly in and set the sticky room's new reference, being careful not + // to trigger a sticky room update ourselves. + this._stickyRoom.room = room; + } + + // If after all that we're still a NewRoom update, add the room if applicable. + // We don't do this for the sticky room (because it causes duplication issues) + // or if we know about the reference (as it should be replaced). + if (cause === RoomUpdateCause.NewRoom && !isSticky && !knownRoomRef) { + this.rooms.push(room); } } - // If we have tags for a room and don't have the room referenced, something went horribly - // wrong - the reference should have been updated above. - if (hasTags && !knownRoomRef && !isSticky) { - throw new Error(`${room.roomId} is missing from room array but is known - trying to find duplicate`); - } + let didTagChange = false; + if (cause === RoomUpdateCause.PossibleTagChange) { + const oldTags = this.roomIdsToTags[room.roomId] || []; + const newTags = this.getTagsForRoom(room); + const diff = arrayDiff(oldTags, newTags); + if (diff.removed.length > 0 || diff.added.length > 0) { + for (const rmTag of diff.removed) { + if (SettingsStore.getValue("advancedRoomListLogging")) { + // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 + console.log(`Removing ${room.roomId} from ${rmTag}`); + } + const algorithm: OrderingAlgorithm = this.algorithms[rmTag]; + if (!algorithm) throw new Error(`No algorithm for ${rmTag}`); + await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved); + this._cachedRooms[rmTag] = algorithm.orderedRooms; + this.recalculateFilteredRoomsForTag(rmTag); // update filter to re-sort the list + this.recalculateStickyRoom(rmTag); // update sticky room to make sure it moves if needed + } + for (const addTag of diff.added) { + if (SettingsStore.getValue("advancedRoomListLogging")) { + // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 + console.log(`Adding ${room.roomId} to ${addTag}`); + } + const algorithm: OrderingAlgorithm = this.algorithms[addTag]; + if (!algorithm) throw new Error(`No algorithm for ${addTag}`); + await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom); + this._cachedRooms[addTag] = algorithm.orderedRooms; + } - // Like above, update the reference to the sticky room if we need to - if (hasTags && isSticky) { - // Go directly in and set the sticky room's new reference, being careful not - // to trigger a sticky room update ourselves. - this._stickyRoom.room = room; - } + // Update the tag map so we don't regen it in a moment + this.roomIdsToTags[room.roomId] = newTags; - // If after all that we're still a NewRoom update, add the room if applicable. - // We don't do this for the sticky room (because it causes duplication issues) - // or if we know about the reference (as it should be replaced). - if (cause === RoomUpdateCause.NewRoom && !isSticky && !knownRoomRef) { - this.rooms.push(room); - } - } - - let didTagChange = false; - if (cause === RoomUpdateCause.PossibleTagChange) { - const oldTags = this.roomIdsToTags[room.roomId] || []; - const newTags = this.getTagsForRoom(room); - const diff = arrayDiff(oldTags, newTags); - if (diff.removed.length > 0 || diff.added.length > 0) { - for (const rmTag of diff.removed) { if (SettingsStore.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`Removing ${room.roomId} from ${rmTag}`); + console.log(`Changing update cause for ${room.roomId} to Timeline to sort rooms`); } - const algorithm: OrderingAlgorithm = this.algorithms[rmTag]; - if (!algorithm) throw new Error(`No algorithm for ${rmTag}`); - await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved); - this._cachedRooms[rmTag] = algorithm.orderedRooms; - this.recalculateFilteredRoomsForTag(rmTag); // update filter to re-sort the list - this.recalculateStickyRoom(rmTag); // update sticky room to make sure it moves if needed - } - for (const addTag of diff.added) { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`Adding ${room.roomId} to ${addTag}`); - } - const algorithm: OrderingAlgorithm = this.algorithms[addTag]; - if (!algorithm) throw new Error(`No algorithm for ${addTag}`); - await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom); - this._cachedRooms[addTag] = algorithm.orderedRooms; - } - - // Update the tag map so we don't regen it in a moment - this.roomIdsToTags[room.roomId] = newTags; - - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`Changing update cause for ${room.roomId} to Timeline to sort rooms`); - } - cause = RoomUpdateCause.Timeline; - didTagChange = true; - } else { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`Received no-op update for ${room.roomId} - changing to Timeline update`); - } - cause = RoomUpdateCause.Timeline; - } - - if (didTagChange && isSticky) { - // Manually update the tag for the sticky room without triggering a sticky room - // update. The update will be handled implicitly by the sticky room handling and - // requires no changes on our part, if we're in the middle of a sticky room change. - if (this._lastStickyRoom) { - this._stickyRoom = { - room, - tag: this.roomIdsToTags[room.roomId][0], - position: 0, // right at the top as it changed tags - }; + cause = RoomUpdateCause.Timeline; + didTagChange = true; } else { - // We have to clear the lock as the sticky room change will trigger updates. - await this.setStickyRoom(room); + if (SettingsStore.getValue("advancedRoomListLogging")) { + // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 + console.log(`Received no-op update for ${room.roomId} - changing to Timeline update`); + } + cause = RoomUpdateCause.Timeline; + } + + if (didTagChange && isSticky) { + // Manually update the tag for the sticky room without triggering a sticky room + // update. The update will be handled implicitly by the sticky room handling and + // requires no changes on our part, if we're in the middle of a sticky room change. + if (this._lastStickyRoom) { + this._stickyRoom = { + room, + tag: this.roomIdsToTags[room.roomId][0], + position: 0, // right at the top as it changed tags + }; + } else { + // We have to clear the lock as the sticky room change will trigger updates. + await this.setStickyRoom(room); + } } } - } - // If the update is for a room change which might be the sticky room, prevent it. We - // need to make sure that the causes (NewRoom and RoomRemoved) are still triggered though - // as the sticky room relies on this. - if (cause !== RoomUpdateCause.NewRoom && cause !== RoomUpdateCause.RoomRemoved) { - if (this.stickyRoom === room) { - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.warn(`[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`); + // If the update is for a room change which might be the sticky room, prevent it. We + // need to make sure that the causes (NewRoom and RoomRemoved) are still triggered though + // as the sticky room relies on this. + if (cause !== RoomUpdateCause.NewRoom && cause !== RoomUpdateCause.RoomRemoved) { + if (this.stickyRoom === room) { + if (SettingsStore.getValue("advancedRoomListLogging")) { + // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 + console.warn(`[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`); + } + return false; } - return false; } - } - if (!this.roomIdsToTags[room.roomId]) { - if (CAUSES_REQUIRING_ROOM.includes(cause)) { + if (!this.roomIdsToTags[room.roomId]) { + if (CAUSES_REQUIRING_ROOM.includes(cause)) { + if (SettingsStore.getValue("advancedRoomListLogging")) { + // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 + console.warn(`Skipping tag update for ${room.roomId} because we don't know about the room`); + } + return false; + } + if (SettingsStore.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.warn(`Skipping tag update for ${room.roomId} because we don't know about the room`); + console.log(`[RoomListDebug] Updating tags for room ${room.roomId} (${room.name})`); + } + + // Get the tags for the room and populate the cache + const roomTags = this.getTagsForRoom(room).filter(t => !isNullOrUndefined(this.cachedRooms[t])); + + // "This should never happen" condition - we specify DefaultTagID.Untagged in getTagsForRoom(), + // which means we should *always* have a tag to go off of. + if (!roomTags.length) throw new Error(`Tags cannot be determined for ${room.roomId}`); + + this.roomIdsToTags[room.roomId] = roomTags; + + if (SettingsStore.getValue("advancedRoomListLogging")) { + // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 + console.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags); } - return false; } if (SettingsStore.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Updating tags for room ${room.roomId} (${room.name})`); + console.log(`[RoomListDebug] Reached algorithmic handling for ${room.roomId} and cause ${cause}`); } - // Get the tags for the room and populate the cache - const roomTags = this.getTagsForRoom(room).filter(t => !isNullOrUndefined(this.cachedRooms[t])); + const tags = this.roomIdsToTags[room.roomId]; + if (!tags) { + console.warn(`No tags known for "${room.name}" (${room.roomId})`); + return false; + } - // "This should never happen" condition - we specify DefaultTagID.Untagged in getTagsForRoom(), - // which means we should *always* have a tag to go off of. - if (!roomTags.length) throw new Error(`Tags cannot be determined for ${room.roomId}`); + let changed = didTagChange; + for (const tag of tags) { + const algorithm: OrderingAlgorithm = this.algorithms[tag]; + if (!algorithm) throw new Error(`No algorithm for ${tag}`); - this.roomIdsToTags[room.roomId] = roomTags; + await algorithm.handleRoomUpdate(room, cause); + this._cachedRooms[tag] = algorithm.orderedRooms; + + // Flag that we've done something + this.recalculateFilteredRoomsForTag(tag); // update filter to re-sort the list + this.recalculateStickyRoom(tag); // update sticky room to make sure it appears if needed + changed = true; + } if (SettingsStore.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags); + console.log(`[RoomListDebug] Finished handling ${room.roomId} with cause ${cause} (changed=${changed})`); } + return changed; + } finally { + release(); } - - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Reached algorithmic handling for ${room.roomId} and cause ${cause}`); - } - - const tags = this.roomIdsToTags[room.roomId]; - if (!tags) { - console.warn(`No tags known for "${room.name}" (${room.roomId})`); - return false; - } - - let changed = didTagChange; - for (const tag of tags) { - const algorithm: OrderingAlgorithm = this.algorithms[tag]; - if (!algorithm) throw new Error(`No algorithm for ${tag}`); - - await algorithm.handleRoomUpdate(room, cause); - this._cachedRooms[tag] = algorithm.orderedRooms; - - // Flag that we've done something - this.recalculateFilteredRoomsForTag(tag); // update filter to re-sort the list - this.recalculateStickyRoom(tag); // update sticky room to make sure it appears if needed - changed = true; - } - - if (SettingsStore.getValue("advancedRoomListLogging")) { - // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Finished handling ${room.roomId} with cause ${cause} (changed=${changed})`); - } - return changed; } } diff --git a/src/utils/MultiLock.ts b/src/utils/MultiLock.ts new file mode 100644 index 0000000000..97cc30306a --- /dev/null +++ b/src/utils/MultiLock.ts @@ -0,0 +1,30 @@ +/* +Copyright 2021 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 {EnhancedMap} from "./maps"; +import AwaitLock from "await-lock"; + +export type DoneFn = () => void; + +export class MultiLock { + private locks = new EnhancedMap(); + + public async acquire(key: string): Promise { + const lock = this.locks.getOrCreate(key, new AwaitLock()); + await lock.acquireAsync(); + return () => lock.release(); + } +} From deab424c935fa9949ab0b9b8723b192ce072b0f4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 12 May 2021 21:19:31 -0600 Subject: [PATCH 13/38] Appease the linter --- src/stores/room-list/algorithms/Algorithm.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index 395591d321..207035ffce 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -720,7 +720,7 @@ export class Algorithm extends EventEmitter { // If we have tags for a room and don't have the room referenced, something went horribly // wrong - the reference should have been updated above. if (hasTags && !knownRoomRef && !isSticky) { - throw new Error(`${room.roomId} is missing from room array but is known - trying to find duplicate`); + throw new Error(`${room.roomId} is missing from room array but is known`); } // Like above, update the reference to the sticky room if we need to @@ -808,7 +808,9 @@ export class Algorithm extends EventEmitter { if (this.stickyRoom === room) { if (SettingsStore.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.warn(`[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`); + console.warn( + `[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`, + ); } return false; } @@ -870,7 +872,9 @@ export class Algorithm extends EventEmitter { if (SettingsStore.getValue("advancedRoomListLogging")) { // TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602 - console.log(`[RoomListDebug] Finished handling ${room.roomId} with cause ${cause} (changed=${changed})`); + console.log( + `[RoomListDebug] Finished handling ${room.roomId} with cause ${cause} (changed=${changed})`, + ); } return changed; } finally { From 423c515708b7cbc50ad1139a50d259501057bd6b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 13 May 2021 09:46:45 +0100 Subject: [PATCH 14/38] Consolidate AddExistingToSpace between Dialog and Just Me integrated flow --- .../dialogs/_AddExistingToSpaceDialog.scss | 168 +++++----- src/components/structures/SpaceRoomView.tsx | 59 +--- .../dialogs/AddExistingToSpaceDialog.tsx | 289 +++++++++--------- 3 files changed, 243 insertions(+), 273 deletions(-) diff --git a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss index 91947be76a..575faf4a97 100644 --- a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss +++ b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss @@ -105,6 +105,90 @@ limitations under the License. mask-position: center; } } + + .mx_AddExistingToSpace_footer { + display: flex; + margin-top: 20px; + + > span { + flex-grow: 1; + font-size: $font-12px; + line-height: $font-15px; + color: $secondary-fg-color; + + .mx_ProgressBar { + height: 8px; + width: 100%; + + @mixin ProgressBarBorderRadius 8px; + } + + .mx_AddExistingToSpace_progressText { + margin-top: 8px; + font-size: $font-15px; + line-height: $font-24px; + color: $primary-fg-color; + } + + > * { + vertical-align: middle; + } + } + + .mx_AddExistingToSpace_error { + padding-left: 12px; + + > img { + align-self: center; + } + + .mx_AddExistingToSpace_errorHeading { + font-weight: $font-semi-bold; + font-size: $font-15px; + line-height: $font-18px; + color: $notice-primary-color; + } + + .mx_AddExistingToSpace_errorCaption { + margin-top: 4px; + font-size: $font-12px; + line-height: $font-15px; + color: $primary-fg-color; + } + } + + .mx_AccessibleButton { + display: inline-block; + align-self: center; + } + + .mx_AccessibleButton_kind_primary { + padding: 8px 36px; + } + + .mx_AddExistingToSpace_retryButton { + margin-left: 12px; + padding-left: 24px; + position: relative; + + &::before { + content: ''; + position: absolute; + background-color: $primary-fg-color; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + mask-image: url('$(res)/img/element-icons/retry.svg'); + width: 18px; + height: 18px; + left: 0; + } + } + + .mx_AccessibleButton_kind_link { + padding: 0; + } + } } .mx_AddExistingToSpaceDialog { @@ -189,88 +273,4 @@ limitations under the License. .mx_AddExistingToSpace { display: contents; } - - .mx_AddExistingToSpaceDialog_footer { - display: flex; - margin-top: 20px; - - > span { - flex-grow: 1; - font-size: $font-12px; - line-height: $font-15px; - color: $secondary-fg-color; - - .mx_ProgressBar { - height: 8px; - width: 100%; - - @mixin ProgressBarBorderRadius 8px; - } - - .mx_AddExistingToSpaceDialog_progressText { - margin-top: 8px; - font-size: $font-15px; - line-height: $font-24px; - color: $primary-fg-color; - } - - > * { - vertical-align: middle; - } - } - - .mx_AddExistingToSpaceDialog_error { - padding-left: 12px; - - > img { - align-self: center; - } - - .mx_AddExistingToSpaceDialog_errorHeading { - font-weight: $font-semi-bold; - font-size: $font-15px; - line-height: $font-18px; - color: $notice-primary-color; - } - - .mx_AddExistingToSpaceDialog_errorCaption { - margin-top: 4px; - font-size: $font-12px; - line-height: $font-15px; - color: $primary-fg-color; - } - } - - .mx_AccessibleButton { - display: inline-block; - align-self: center; - } - - .mx_AccessibleButton_kind_primary { - padding: 8px 36px; - } - - .mx_AddExistingToSpaceDialog_retryButton { - margin-left: 12px; - padding-left: 24px; - position: relative; - - &::before { - content: ''; - position: absolute; - background-color: $primary-fg-color; - mask-repeat: no-repeat; - mask-position: center; - mask-size: contain; - mask-image: url('$(res)/img/element-icons/retry.svg'); - width: 18px; - height: 18px; - left: 0; - } - } - - .mx_AccessibleButton_kind_link { - padding: 0; - } - } } diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 828b2ff5fe..4c4b076fdd 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -517,40 +517,6 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => { }; const SpaceAddExistingRooms = ({ space, onFinished }) => { - const [selectedToAdd, setSelectedToAdd] = useState(new Set()); - - const [busy, setBusy] = useState(false); - const [error, setError] = useState(""); - - let onClick = onFinished; - let buttonLabel = _t("Skip for now"); - if (selectedToAdd.size > 0) { - onClick = async () => { - setBusy(true); - - for (const room of selectedToAdd) { - const via = calculateRoomVia(room); - try { - await SpaceStore.instance.addRoomToSpace(space, room.roomId, via).catch(async e => { - if (e.errcode === "M_LIMIT_EXCEEDED") { - await sleep(e.data.retry_after_ms); - return SpaceStore.instance.addRoomToSpace(space, room.roomId, via); // retry - } - - throw e; - }); - } catch (e) { - console.error("Failed to add rooms to space", e); - setError(_t("Failed to add rooms to space")); - break; - } - } - setBusy(false); - onFinished(); - }; - buttonLabel = busy ? _t("Adding...") : _t("Add"); - } - return

{ _t("What do you want to organise?") }

@@ -558,29 +524,18 @@ const SpaceAddExistingRooms = ({ space, onFinished }) => { "no one will be informed. You can add more later.") }
- { error &&
{ error }
} - { - if (checked) { - selectedToAdd.add(room); - } else { - selectedToAdd.delete(room); - } - setSelectedToAdd(new Set(selectedToAdd)); - }} + emptySelectionButton={ + + { _t("Skip for now") } + + } + onFinished={onFinished} />
- - { buttonLabel } - +
; diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx index 8ac68e99b7..c952614c9f 100644 --- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx +++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {useContext, useMemo, useState} from "react"; +import React, {ReactNode, useContext, useMemo, useState} from "react"; import classNames from "classnames"; import {Room} from "matrix-js-sdk/src/models/room"; import {MatrixClient} from "matrix-js-sdk/src/client"; @@ -58,14 +58,23 @@ const Entry = ({ room, checked, onChange }) => { interface IAddExistingToSpaceProps { space: Room; - selected: Set; - onChange(checked: boolean, room: Room): void; + footerPrompt?: ReactNode; + emptySelectionButton?: ReactNode; + onFinished(added: boolean): void; } -export const AddExistingToSpace: React.FC = ({ space, selected, onChange }) => { +export const AddExistingToSpace: React.FC = ({ + space, + footerPrompt, + emptySelectionButton, + onFinished, +}) => { const cli = useContext(MatrixClientContext); const visibleRooms = useMemo(() => sortRooms(cli.getVisibleRooms()), [cli]); + const [selectedToAdd, setSelectedToAdd] = useState(new Set()); + const [progress, setProgress] = useState(null); + const [error, setError] = useState(null); const [query, setQuery] = useState(""); const lcQuery = query.toLowerCase(); @@ -93,120 +102,6 @@ export const AddExistingToSpace: React.FC = ({ space, return arr; }, [[], [], []]); - return
- - - { rooms.length > 0 ? ( -
-

{ _t("Rooms") }

- { rooms.map(room => { - return { - onChange(checked, room); - } : null} - />; - }) } -
- ) : undefined } - - { spaces.length > 0 ? ( -
-

{ _t("Spaces") }

-
-
{ _t("Feeling experimental?") }
-
{ _t("You can add existing spaces to a space.") }
-
- { spaces.map(space => { - return { - onChange(checked, space); - } : null} - />; - }) } -
- ) : null } - - { dms.length > 0 ? ( -
-

{ _t("Direct Messages") }

- { dms.map(room => { - return { - onChange(checked, room); - } : null} - />; - }) } -
- ) : null } - - { spaces.length + rooms.length + dms.length < 1 ? - { _t("No results") } - : undefined } -
-
; -}; - -const AddExistingToSpaceDialog: React.FC = ({ matrixClient: cli, space, onCreateRoomClick, onFinished }) => { - const [selectedSpace, setSelectedSpace] = useState(space); - const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId); - const [selectedToAdd, setSelectedToAdd] = useState(new Set()); - - const [progress, setProgress] = useState(null); - const [error, setError] = useState(null); - - let spaceOptionSection; - if (existingSubspaces.length > 0) { - const options = [space, ...existingSubspaces].map((space) => { - const classes = classNames("mx_AddExistingToSpaceDialog_dropdownOption", { - mx_AddExistingToSpaceDialog_dropdownOptionActive: space === selectedSpace, - }); - return
- - { space.name || getDisplayAliasForRoom(space) || space.roomId } -
; - }); - - spaceOptionSection = ( - { - setSelectedSpace(existingSubspaces.find(space => space.roomId === key) || space); - }} - value={selectedSpace.roomId} - label={_t("Space selection")} - > - { options } - - ); - } else { - spaceOptionSection =
- { space.name || getDisplayAliasForRoom(space) || space.roomId } -
; - } - - const title = - -
-

{ _t("Add existing rooms") }

- { spaceOptionSection } -
-
; - const addRooms = async () => { setError(null); setProgress(0); @@ -269,20 +164,145 @@ const AddExistingToSpaceDialog: React.FC = ({ matrixClient: cli, space, ; } else { + let button = emptySelectionButton; + if (!button || selectedToAdd.size > 0) { + button = + { _t("Add") } + ; + } + footer = <> -
{ _t("Want to add a new room instead?") }
- onCreateRoomClick(cli, space)} kind="link"> - { _t("Create a new room") } - + { footerPrompt }
- - { _t("Add") } - + { button } ; } + const onChange = !busy && !error ? (checked, room) => { + if (checked) { + selectedToAdd.add(room); + } else { + selectedToAdd.delete(room); + } + setSelectedToAdd(new Set(selectedToAdd)); + } : null; + + return
+ + + { rooms.length > 0 ? ( +
+

{ _t("Rooms") }

+ { rooms.map(room => { + return { + onChange(checked, room); + } : null} + />; + }) } +
+ ) : undefined } + + { spaces.length > 0 ? ( +
+

{ _t("Spaces") }

+
+
{ _t("Feeling experimental?") }
+
{ _t("You can add existing spaces to a space.") }
+
+ { spaces.map(space => { + return { + onChange(checked, space); + } : null} + />; + }) } +
+ ) : null } + + { dms.length > 0 ? ( +
+

{ _t("Direct Messages") }

+ { dms.map(room => { + return { + onChange(checked, room); + } : null} + />; + }) } +
+ ) : null } + + { spaces.length + rooms.length + dms.length < 1 ? + { _t("No results") } + : undefined } +
+ +
+ { footer } +
+
; +}; + +const AddExistingToSpaceDialog: React.FC = ({ matrixClient: cli, space, onCreateRoomClick, onFinished }) => { + const [selectedSpace, setSelectedSpace] = useState(space); + const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId); + + let spaceOptionSection; + if (existingSubspaces.length > 0) { + const options = [space, ...existingSubspaces].map((space) => { + const classes = classNames("mx_AddExistingToSpaceDialog_dropdownOption", { + mx_AddExistingToSpaceDialog_dropdownOptionActive: space === selectedSpace, + }); + return
+ + { space.name || getDisplayAliasForRoom(space) || space.roomId } +
; + }); + + spaceOptionSection = ( + { + setSelectedSpace(existingSubspaces.find(space => space.roomId === key) || space); + }} + value={selectedSpace.roomId} + label={_t("Space selection")} + > + { options } + + ); + } else { + spaceOptionSection =
+ { space.name || getDisplayAliasForRoom(space) || space.roomId } +
; + } + + const title = + +
+

{ _t("Add existing rooms") }

+ { spaceOptionSection } +
+
; + return = ({ matrixClient: cli, space, { - if (checked) { - selectedToAdd.add(room); - } else { - selectedToAdd.delete(room); - } - setSelectedToAdd(new Set(selectedToAdd)); - } : null} + onFinished={onFinished} + footerPrompt={<> +
{ _t("Want to add a new room instead?") }
+ onCreateRoomClick(cli, space)} kind="link"> + { _t("Create a new room") } + + } />
-
- { footer } -
onFinished(false)} />
; }; From dd04b479a1c751da456c8d55236e6c661ce45e36 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 13 May 2021 10:20:27 +0100 Subject: [PATCH 15/38] Wrap decodeURIComponent in try-catch to protect against malformed URIs --- src/linkify-matrix.js | 14 +++++++++----- src/utils/permalinks/Permalinks.ts | 11 ++++++++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/linkify-matrix.js b/src/linkify-matrix.js index 84a131f23a..feda257d8b 100644 --- a/src/linkify-matrix.js +++ b/src/linkify-matrix.js @@ -254,11 +254,15 @@ matrixLinkify.options = { target: function(href, type) { if (type === 'url') { - const transformed = tryTransformPermalinkToLocalHref(href); - if (transformed !== href || decodeURIComponent(href).match(matrixLinkify.ELEMENT_URL_PATTERN)) { - return null; - } else { - return '_blank'; + try { + const transformed = tryTransformPermalinkToLocalHref(href); + if (transformed !== href || decodeURIComponent(href).match(matrixLinkify.ELEMENT_URL_PATTERN)) { + return null; + } else { + return '_blank'; + } + } catch (e) { + // malformed URI } } return null; diff --git a/src/utils/permalinks/Permalinks.ts b/src/utils/permalinks/Permalinks.ts index 015ecca22e..d87c826cc2 100644 --- a/src/utils/permalinks/Permalinks.ts +++ b/src/utils/permalinks/Permalinks.ts @@ -346,9 +346,14 @@ export function tryTransformPermalinkToLocalHref(permalink: string): string { return permalink; } - const m = decodeURIComponent(permalink).match(matrixLinkify.ELEMENT_URL_PATTERN); - if (m) { - return m[1]; + try { + const m = decodeURIComponent(permalink).match(matrixLinkify.ELEMENT_URL_PATTERN); + if (m) { + return m[1]; + } + } catch (e) { + // Not a valid URI + return permalink; } // A bit of a hack to convert permalinks of unknown origin to Element links From 3af0138e9d1ee380bea09e8a837ffe718443c09b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 13 May 2021 10:28:35 +0100 Subject: [PATCH 16/38] Update emoji icons to new style --- res/css/views/messages/_MessageActionBar.scss | 1 + res/img/element-icons/room/composer/emoji.svg | 10 +++++----- res/img/element-icons/room/message-bar/emoji.svg | 6 ++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index 3ecbef0d1f..d41ac3a4ba 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -85,6 +85,7 @@ limitations under the License. left: 0; height: 100%; width: 100%; + mask-size: 18px; mask-repeat: no-repeat; mask-position: center; background-color: $message-action-bar-fg-color; diff --git a/res/img/element-icons/room/composer/emoji.svg b/res/img/element-icons/room/composer/emoji.svg index 9613d9edd9..b02cb69364 100644 --- a/res/img/element-icons/room/composer/emoji.svg +++ b/res/img/element-icons/room/composer/emoji.svg @@ -1,7 +1,7 @@ - - - - - + + + + + diff --git a/res/img/element-icons/room/message-bar/emoji.svg b/res/img/element-icons/room/message-bar/emoji.svg index 697f656b8a..07fee5b834 100644 --- a/res/img/element-icons/room/message-bar/emoji.svg +++ b/res/img/element-icons/room/message-bar/emoji.svg @@ -1,5 +1,3 @@ - - - - + + From b5fa4d88bf5005b5d45bf25ce2207eb570301e17 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 13 May 2021 10:56:51 +0100 Subject: [PATCH 17/38] Add extra add reactions button to encourage more diverse reactions to content --- res/css/views/messages/_ReactionsRow.scss | 20 ++++++++++ src/components/views/messages/ReactionsRow.js | 40 ++++++++++++++++++- src/i18n/strings/en_EN.json | 1 + 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/res/css/views/messages/_ReactionsRow.scss b/res/css/views/messages/_ReactionsRow.scss index 2f5695e1fb..e86d23dc7c 100644 --- a/res/css/views/messages/_ReactionsRow.scss +++ b/res/css/views/messages/_ReactionsRow.scss @@ -17,6 +17,26 @@ limitations under the License. .mx_ReactionsRow { margin: 6px 0; color: $primary-fg-color; + + .mx_ReactionsRow_addReactionButton { + position: relative; + display: inline-block; + width: 16px; + height: 16px; + vertical-align: sub; + + &::before { + content: ''; + position: absolute; + height: 100%; + width: 100%; + mask-size: cover; + mask-repeat: no-repeat; + mask-position: center; + background-color: $tertiary-fg-color; + mask-image: url('$(res)/img/element-icons/room/message-bar/emoji.svg'); + } + } } .mx_ReactionsRow_showAll { diff --git a/src/components/views/messages/ReactionsRow.js b/src/components/views/messages/ReactionsRow.js index d5c8ea2ac9..22f12709a7 100644 --- a/src/components/views/messages/ReactionsRow.js +++ b/src/components/views/messages/ReactionsRow.js @@ -16,16 +16,44 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; +import { EventType } from "matrix-js-sdk/src/@types/event"; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import { isContentActionable } from '../../../utils/EventUtils'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import {ContextMenuTooltipButton} from "../../../accessibility/context_menu/ContextMenuTooltipButton"; +import {aboveLeftOf, ContextMenu, useContextMenu} from "../../structures/ContextMenu"; // The maximum number of reactions to initially show on a message. const MAX_ITEMS_WHEN_LIMITED = 8; +const ReactButton = ({ mxEvent, reactions }) => { + const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); + + let contextMenu; + if (menuDisplayed) { + const buttonRect = button.current.getBoundingClientRect(); + const ReactionPicker = sdk.getComponent('emojipicker.ReactionPicker'); + contextMenu = + + ; + } + + return + + + { contextMenu } + ; +}; + @replaceableComponent("views.messages.ReactionsRow") export default class ReactionsRow extends React.PureComponent { static propTypes = { @@ -151,13 +179,21 @@ export default class ReactionsRow extends React.PureComponent { ; } + const cli = MatrixClientPeg.get(); + + let addReactionButton; + if (cli.getRoom(mxEvent.getRoomId()).currentState.maySendEvent(EventType.Reaction, cli.getUserId())) { + addReactionButton = ; + } + return
- {items} - {showAllButton} + { items } + { addReactionButton } + { showAllButton }
; } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index dbe712c691..b9e52a8356 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1864,6 +1864,7 @@ "You sent a verification request": "You sent a verification request", "Error decrypting video": "Error decrypting video", "Error processing voice message": "Error processing voice message", + "Add reaction": "Add reaction", "Show all": "Show all", "Reactions": "Reactions", " reacted with %(content)s": " reacted with %(content)s", From 2f28de8472b08ae01440db111b7ddcd937433735 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 13 May 2021 13:22:50 +0100 Subject: [PATCH 18/38] Show alternative button during space creation wizard if user opted to create 0 rooms --- src/components/structures/SpaceRoomView.tsx | 17 ++++++++++------- src/i18n/strings/en_EN.json | 1 + 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index ed0ae1afe7..583caf4b1e 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -78,6 +78,7 @@ interface IProps { interface IState { phase: Phase; + createdRooms?: boolean; // internal state for the creation wizard showRightPanel: boolean; myMembership: string; } @@ -461,7 +462,8 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => { setError(""); setBusy(true); try { - await Promise.all(roomNames.map(name => name.trim()).filter(Boolean).map(name => { + const filteredRoomNames = roomNames.map(name => name.trim()).filter(Boolean); + await Promise.all(filteredRoomNames.map(name => { return createRoom({ createOpts: { preset: space.getJoinRule() === "public" ? Preset.PublicChat : Preset.PrivateChat, @@ -474,7 +476,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => { parentSpace: space, }); })); - onFinished(); + onFinished(filteredRoomNames.length > 0); } catch (e) { console.error("Failed to create initial space rooms", e); setError(_t("Failed to create initial space rooms")); @@ -484,7 +486,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => { let onClick = (ev) => { ev.preventDefault(); - onFinished(); + onFinished(false); }; let buttonLabel = _t("Skip for now"); if (roomNames.some(name => name.trim())) { @@ -585,7 +587,7 @@ const SpaceAddExistingRooms = ({ space, onFinished }) => { ; }; -const SpaceSetupPublicShare = ({ justCreatedOpts, space, onFinished }) => { +const SpaceSetupPublicShare = ({ justCreatedOpts, space, onFinished, createdRooms }) => { return

{ _t("Share %(name)s", { name: justCreatedOpts?.createOpts?.name || space.name, @@ -598,7 +600,7 @@ const SpaceSetupPublicShare = ({ justCreatedOpts, space, onFinished }) => {
- { _t("Go to my first room") } + { createdRooms ? _t("Go to my first room") : _t("Go to my space") }
@@ -891,13 +893,14 @@ export default class SpaceRoomView extends React.PureComponent { _t("Let's create a room for each of them.") + "\n" + _t("You can add more later too, including already existing ones.") } - onFinished={() => this.setState({ phase: Phase.PublicShare })} + onFinished={(createdRooms: boolean) => this.setState({ phase: Phase.PublicShare, createdRooms })} />; case Phase.PublicShare: return ; case Phase.PrivateScope: @@ -919,7 +922,7 @@ export default class SpaceRoomView extends React.PureComponent { title={_t("What projects are you working on?")} description={_t("We'll create rooms for each of them. " + "You can add more later too, including already existing ones.")} - onFinished={() => this.setState({ phase: Phase.Landing })} + onFinished={(createdRooms: boolean) => this.setState({ phase: Phase.Landing, createdRooms })} />; case Phase.PrivateExistingRooms: return Date: Thu, 13 May 2021 13:32:38 +0100 Subject: [PATCH 19/38] Tweak alignment of reactions row, move add reaction to right and only show on hover --- res/css/views/messages/_ReactionsRow.scss | 11 ++++++++--- res/css/views/messages/_ReactionsRowButton.scss | 5 +++-- src/components/views/messages/ReactionsRow.js | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/res/css/views/messages/_ReactionsRow.scss b/res/css/views/messages/_ReactionsRow.scss index e86d23dc7c..15d251bd68 100644 --- a/res/css/views/messages/_ReactionsRow.scss +++ b/res/css/views/messages/_ReactionsRow.scss @@ -20,10 +20,11 @@ limitations under the License. .mx_ReactionsRow_addReactionButton { position: relative; - display: inline-block; + display: none; width: 16px; height: 16px; - vertical-align: sub; + vertical-align: middle; + margin-left: 6px; &::before { content: ''; @@ -39,12 +40,16 @@ limitations under the License. } } +.mx_EventTile:hover .mx_ReactionsRow_addReactionButton { + display: inline-block; +} + .mx_ReactionsRow_showAll { text-decoration: none; font-size: $font-10px; font-weight: 600; margin-left: 6px; - vertical-align: top; + vertical-align: middle; &:hover, &:link, diff --git a/res/css/views/messages/_ReactionsRowButton.scss b/res/css/views/messages/_ReactionsRowButton.scss index c132fa5a0f..766fea2f8f 100644 --- a/res/css/views/messages/_ReactionsRowButton.scss +++ b/res/css/views/messages/_ReactionsRowButton.scss @@ -16,14 +16,15 @@ limitations under the License. .mx_ReactionsRowButton { display: inline-flex; - line-height: $font-21px; + line-height: $font-20px; margin-right: 6px; - padding: 0 6px; + padding: 1px 6px; border: 1px solid $reaction-row-button-border-color; border-radius: 10px; background-color: $reaction-row-button-bg-color; cursor: pointer; user-select: none; + vertical-align: middle; &:hover { border-color: $reaction-row-button-hover-border-color; diff --git a/src/components/views/messages/ReactionsRow.js b/src/components/views/messages/ReactionsRow.js index 22f12709a7..2a67519d6b 100644 --- a/src/components/views/messages/ReactionsRow.js +++ b/src/components/views/messages/ReactionsRow.js @@ -192,8 +192,8 @@ export default class ReactionsRow extends React.PureComponent { aria-label={_t("Reactions")} > { items } - { addReactionButton } { showAllButton } + { addReactionButton }

; } } From 87ae47bd6114d660735bba82a71c4fc0883b5314 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 13 May 2021 13:59:10 +0100 Subject: [PATCH 20/38] tweak reactions row some more, third try lucky --- res/css/views/messages/_ReactionsRow.scss | 36 ++++++++++++------- src/components/views/messages/ReactionsRow.js | 5 ++- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/res/css/views/messages/_ReactionsRow.scss b/res/css/views/messages/_ReactionsRow.scss index 15d251bd68..f1c19f45a9 100644 --- a/res/css/views/messages/_ReactionsRow.scss +++ b/res/css/views/messages/_ReactionsRow.scss @@ -20,23 +20,33 @@ limitations under the License. .mx_ReactionsRow_addReactionButton { position: relative; - display: none; - width: 16px; - height: 16px; + display: none; // show on hover of the .mx_EventTile + width: 24px; + height: 24px; vertical-align: middle; - margin-left: 6px; + margin-left: 4px; &::before { content: ''; position: absolute; height: 100%; width: 100%; - mask-size: cover; + mask-size: 16px; mask-repeat: no-repeat; mask-position: center; background-color: $tertiary-fg-color; mask-image: url('$(res)/img/element-icons/room/message-bar/emoji.svg'); } + + &.mx_ReactionsRow_addReactionButton_active { + display: inline-block; // keep showing whilst the context menu is shown + } + + &:hover, &.mx_ReactionsRow_addReactionButton_active { + &::before { + background-color: $primary-fg-color; + } + } } } @@ -46,14 +56,16 @@ limitations under the License. .mx_ReactionsRow_showAll { text-decoration: none; - font-size: $font-10px; - font-weight: 600; - margin-left: 6px; + font-size: $font-12px; + line-height: $font-20px; + margin-left: 4px; vertical-align: middle; - &:hover, - &:link, - &:visited { - color: $accent-color; + &:link, &:visited { + color: $tertiary-fg-color + } + + &:hover { + color: $primary-fg-color; } } diff --git a/src/components/views/messages/ReactionsRow.js b/src/components/views/messages/ReactionsRow.js index 2a67519d6b..a7997c0e32 100644 --- a/src/components/views/messages/ReactionsRow.js +++ b/src/components/views/messages/ReactionsRow.js @@ -17,6 +17,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import { EventType } from "matrix-js-sdk/src/@types/event"; +import classNames from "classnames"; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; @@ -43,7 +44,9 @@ const ReactButton = ({ mxEvent, reactions }) => { return Date: Thu, 13 May 2021 14:13:00 +0100 Subject: [PATCH 21/38] Convert to Typescript and move from ClientPeg to Context --- res/css/views/messages/_ReactionsRow.scss | 2 +- .../{ReactionsRow.js => ReactionsRow.tsx} | 55 ++++++++------- ...onsRowButton.js => ReactionsRowButton.tsx} | 70 ++++++++++--------- ...oltip.js => ReactionsRowButtonTooltip.tsx} | 37 +++++----- 4 files changed, 87 insertions(+), 77 deletions(-) rename src/components/views/messages/{ReactionsRow.js => ReactionsRow.tsx} (82%) rename src/components/views/messages/{ReactionsRowButton.js => ReactionsRowButton.tsx} (73%) rename src/components/views/messages/{ReactionsRowButtonTooltip.js => ReactionsRowButtonTooltip.tsx} (74%) diff --git a/res/css/views/messages/_ReactionsRow.scss b/res/css/views/messages/_ReactionsRow.scss index f1c19f45a9..244439bf74 100644 --- a/res/css/views/messages/_ReactionsRow.scss +++ b/res/css/views/messages/_ReactionsRow.scss @@ -62,7 +62,7 @@ limitations under the License. vertical-align: middle; &:link, &:visited { - color: $tertiary-fg-color + color: $tertiary-fg-color; } &:hover { diff --git a/src/components/views/messages/ReactionsRow.js b/src/components/views/messages/ReactionsRow.tsx similarity index 82% rename from src/components/views/messages/ReactionsRow.js rename to src/components/views/messages/ReactionsRow.tsx index a7997c0e32..e1fcbe5364 100644 --- a/src/components/views/messages/ReactionsRow.js +++ b/src/components/views/messages/ReactionsRow.tsx @@ -1,5 +1,5 @@ /* -Copyright 2019 New Vector Ltd +Copyright 2019, 2021 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. @@ -14,29 +14,30 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; -import PropTypes from 'prop-types'; -import { EventType } from "matrix-js-sdk/src/@types/event"; +import React from "react"; import classNames from "classnames"; +import { EventType } from "matrix-js-sdk/src/@types/event"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { Relations } from "matrix-js-sdk/src/models/relations"; -import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import { isContentActionable } from '../../../utils/EventUtils'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; -import {replaceableComponent} from "../../../utils/replaceableComponent"; -import {ContextMenuTooltipButton} from "../../../accessibility/context_menu/ContextMenuTooltipButton"; -import {aboveLeftOf, ContextMenu, useContextMenu} from "../../structures/ContextMenu"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton"; +import { aboveLeftOf, ContextMenu, useContextMenu } from "../../structures/ContextMenu"; +import ReactionPicker from "../emojipicker/ReactionPicker"; +import ReactionsRowButton from "./ReactionsRowButton"; +import MatrixClientContext from "../../../contexts/MatrixClientContext"; // The maximum number of reactions to initially show on a message. const MAX_ITEMS_WHEN_LIMITED = 8; -const ReactButton = ({ mxEvent, reactions }) => { +const ReactButton = ({ mxEvent, reactions }: IProps) => { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); let contextMenu; if (menuDisplayed) { const buttonRect = button.current.getBoundingClientRect(); - const ReactionPicker = sdk.getComponent('emojipicker.ReactionPicker'); contextMenu = ; @@ -57,17 +58,24 @@ const ReactButton = ({ mxEvent, reactions }) => { ; }; -@replaceableComponent("views.messages.ReactionsRow") -export default class ReactionsRow extends React.PureComponent { - static propTypes = { - // The event we're displaying reactions for - mxEvent: PropTypes.object.isRequired, - // The Relations model from the JS SDK for reactions to `mxEvent` - reactions: PropTypes.object, - } +interface IProps { + // The event we're displaying reactions for + mxEvent: MatrixEvent; + // The Relations model from the JS SDK for reactions to `mxEvent` + reactions?: Relations; +} - constructor(props) { - super(props); +interface IState { + myReactions: MatrixEvent[]; + showAll: boolean; +} + +@replaceableComponent("views.messages.ReactionsRow") +export default class ReactionsRow extends React.PureComponent { + static contextType = MatrixClientContext; + + constructor(props, context) { + super(props, context); if (props.reactions) { props.reactions.on("Relations.add", this.onReactionsChange); @@ -123,7 +131,7 @@ export default class ReactionsRow extends React.PureComponent { if (!reactions) { return null; } - const userId = MatrixClientPeg.get().getUserId(); + const userId = this.context.getUserId(); const myReactions = reactions.getAnnotationsBySender()[userId]; if (!myReactions) { return null; @@ -145,7 +153,6 @@ export default class ReactionsRow extends React.PureComponent { return null; } - const ReactionsRowButton = sdk.getComponent('messages.ReactionsRowButton'); let items = reactions.getSortedAnnotationsByKey().map(([content, events]) => { const count = events.size; if (!count) { @@ -182,7 +189,7 @@ export default class ReactionsRow extends React.PureComponent { ; } - const cli = MatrixClientPeg.get(); + const cli = this.context; let addReactionButton; if (cli.getRoom(mxEvent.getRoomId()).currentState.maySendEvent(EventType.Reaction, cli.getUserId())) { diff --git a/src/components/views/messages/ReactionsRowButton.js b/src/components/views/messages/ReactionsRowButton.tsx similarity index 73% rename from src/components/views/messages/ReactionsRowButton.js rename to src/components/views/messages/ReactionsRowButton.tsx index b37a949e57..393c6bca88 100644 --- a/src/components/views/messages/ReactionsRowButton.js +++ b/src/components/views/messages/ReactionsRowButton.tsx @@ -1,5 +1,5 @@ /* -Copyright 2019 New Vector Ltd +Copyright 2019, 2021 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. @@ -14,49 +14,54 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; +import React from "react"; +import classNames from "classnames"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; -import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import { formatCommaSeparatedList } from '../../../utils/FormattingUtils'; import dis from "../../../dispatcher/dispatcher"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; +import ReactionsRowButtonTooltip from "./ReactionsRowButtonTooltip"; +import AccessibleButton from "../elements/AccessibleButton"; +import MatrixClientContext from "../../../contexts/MatrixClientContext"; + +interface IProps { + // The event we're displaying reactions for + mxEvent: MatrixEvent; + // The reaction content / key / emoji + content: string; + // The count of votes for this key + count: number; + // A Set of Martix reaction events for this key + reactionEvents: Set; + // A possible Matrix event if the current user has voted for this type + myReactionEvent?: MatrixEvent; +} + +interface IState { + tooltipRendered: boolean; + tooltipVisible: boolean; +} @replaceableComponent("views.messages.ReactionsRowButton") -export default class ReactionsRowButton extends React.PureComponent { - static propTypes = { - // The event we're displaying reactions for - mxEvent: PropTypes.object.isRequired, - // The reaction content / key / emoji - content: PropTypes.string.isRequired, - // The count of votes for this key - count: PropTypes.number.isRequired, - // A Set of Martix reaction events for this key - reactionEvents: PropTypes.object.isRequired, - // A possible Matrix event if the current user has voted for this type - myReactionEvent: PropTypes.object, - } +export default class ReactionsRowButton extends React.PureComponent { + static contextType = MatrixClientContext; - constructor(props) { - super(props); + state = { + tooltipRendered: false, + tooltipVisible: false, + }; - this.state = { - tooltipVisible: false, - }; - } - - onClick = (ev) => { + onClick = () => { const { mxEvent, myReactionEvent, content } = this.props; if (myReactionEvent) { - MatrixClientPeg.get().redactEvent( + this.context.redactEvent( mxEvent.getRoomId(), myReactionEvent.getId(), ); } else { - MatrixClientPeg.get().sendEvent(mxEvent.getRoomId(), "m.reaction", { + this.context.sendEvent(mxEvent.getRoomId(), "m.reaction", { "m.relates_to": { "rel_type": "m.annotation", "event_id": mxEvent.getId(), @@ -83,8 +88,6 @@ export default class ReactionsRowButton extends React.PureComponent { } render() { - const ReactionsRowButtonTooltip = - sdk.getComponent('messages.ReactionsRowButtonTooltip'); const { mxEvent, content, count, reactionEvents, myReactionEvent } = this.props; const classes = classNames({ @@ -102,7 +105,7 @@ export default class ReactionsRowButton extends React.PureComponent { />; } - const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId()); + const room = this.context.getRoom(mxEvent.getRoomId()); let label; if (room) { const senders = []; @@ -130,7 +133,6 @@ export default class ReactionsRowButton extends React.PureComponent { ); } const isPeeking = room.getMyMembership() !== "join"; - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); return ; + visible: boolean; +} @replaceableComponent("views.messages.ReactionsRowButtonTooltip") -export default class ReactionsRowButtonTooltip extends React.PureComponent { - static propTypes = { - // The event we're displaying reactions for - mxEvent: PropTypes.object.isRequired, - // The reaction content / key / emoji - content: PropTypes.string.isRequired, - // A Set of Martix reaction events for this key - reactionEvents: PropTypes.object.isRequired, - visible: PropTypes.bool.isRequired, - } +export default class ReactionsRowButtonTooltip extends React.PureComponent { + static contextType = MatrixClientContext; render() { - const Tooltip = sdk.getComponent('elements.Tooltip'); const { content, reactionEvents, mxEvent, visible } = this.props; - const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId()); + const room = this.context.getRoom(mxEvent.getRoomId()); let tooltipLabel; if (room) { const senders = []; From a41d76b588cd619a17770102457dbc57243e1e2f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 13 May 2021 14:14:01 +0100 Subject: [PATCH 22/38] fix typos --- src/components/views/messages/ReactionsRowButton.tsx | 2 +- src/components/views/messages/ReactionsRowButtonTooltip.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/messages/ReactionsRowButton.tsx b/src/components/views/messages/ReactionsRowButton.tsx index 393c6bca88..d163f1ad30 100644 --- a/src/components/views/messages/ReactionsRowButton.tsx +++ b/src/components/views/messages/ReactionsRowButton.tsx @@ -33,7 +33,7 @@ interface IProps { content: string; // The count of votes for this key count: number; - // A Set of Martix reaction events for this key + // A Set of Matrix reaction events for this key reactionEvents: Set; // A possible Matrix event if the current user has voted for this type myReactionEvent?: MatrixEvent; diff --git a/src/components/views/messages/ReactionsRowButtonTooltip.tsx b/src/components/views/messages/ReactionsRowButtonTooltip.tsx index d9e8a45b28..e60174530a 100644 --- a/src/components/views/messages/ReactionsRowButtonTooltip.tsx +++ b/src/components/views/messages/ReactionsRowButtonTooltip.tsx @@ -29,7 +29,7 @@ interface IProps { mxEvent: MatrixEvent; // The reaction content / key / emoji content: string; - // A Set of Martix reaction events for this key + // A Set of Matrix reaction events for this key reactionEvents: Set; visible: boolean; } From f6e8d38b872d6fd2d3a811d949ac955c04478871 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 13 May 2021 14:23:10 +0100 Subject: [PATCH 23/38] Remove redundant `tag` prop --- src/components/views/avatars/DecoratedRoomAvatar.tsx | 2 -- src/components/views/rooms/RoomBreadcrumbs.tsx | 1 - src/components/views/rooms/RoomHeader.js | 1 - src/components/views/rooms/RoomTile.tsx | 1 - 4 files changed, 5 deletions(-) diff --git a/src/components/views/avatars/DecoratedRoomAvatar.tsx b/src/components/views/avatars/DecoratedRoomAvatar.tsx index e95022687a..f15538eabf 100644 --- a/src/components/views/avatars/DecoratedRoomAvatar.tsx +++ b/src/components/views/avatars/DecoratedRoomAvatar.tsx @@ -20,7 +20,6 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { User } from "matrix-js-sdk/src/models/user"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { TagID } from '../../../stores/room-list/models'; import RoomAvatar from "./RoomAvatar"; import NotificationBadge from '../rooms/NotificationBadge'; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; @@ -35,7 +34,6 @@ import {replaceableComponent} from "../../../utils/replaceableComponent"; interface IProps { room: Room; avatarSize: number; - tag: TagID; displayBadge?: boolean; forceCount?: boolean; oobData?: object; diff --git a/src/components/views/rooms/RoomBreadcrumbs.tsx b/src/components/views/rooms/RoomBreadcrumbs.tsx index ea0ff233da..c061b79541 100644 --- a/src/components/views/rooms/RoomBreadcrumbs.tsx +++ b/src/components/views/rooms/RoomBreadcrumbs.tsx @@ -98,7 +98,6 @@ export default class RoomBreadcrumbs extends React.PureComponent diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index f856f7f6ef..84248f14c2 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -177,7 +177,6 @@ export default class RoomHeader extends React.Component { roomAvatar = ; diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index 7303f36489..edd644fd65 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -576,7 +576,6 @@ export default class RoomTile extends React.PureComponent { const roomAvatar = ; From 6aa477f0f52eeb875c251962f588a83a34f66404 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 13 May 2021 14:23:28 +0100 Subject: [PATCH 24/38] Decorate room avatars with publicity in add existing to space flow --- res/css/views/dialogs/_AddExistingToSpaceDialog.scss | 7 ++++++- src/components/views/dialogs/AddExistingToSpaceDialog.tsx | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss index 91947be76a..c2960ae62b 100644 --- a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss +++ b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss @@ -54,7 +54,8 @@ limitations under the License. display: flex; margin-top: 12px; - .mx_BaseAvatar { + // we can't target .mx_BaseAvatar here as it'll break the decorated avatar styling + .mx_DecoratedRoomAvatar { margin-right: 12px; } @@ -75,6 +76,10 @@ limitations under the License. } .mx_AddExistingToSpace_section_spaces { + .mx_BaseAvatar { + margin-right: 12px; + } + .mx_BaseAvatar_image { border-radius: 8px; } diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx index 8ac68e99b7..487eb36713 100644 --- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx +++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx @@ -37,6 +37,7 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm"; import ProgressBar from "../elements/ProgressBar"; import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView"; +import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; interface IProps extends IDialogProps { matrixClient: MatrixClient; @@ -46,7 +47,10 @@ interface IProps extends IDialogProps { const Entry = ({ room, checked, onChange }) => { return