From b878c2774125c1f15f2f55678bdb5d3d83c04b88 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:56:22 +0100 Subject: [PATCH 001/155] Tidy devDeps, all the webpack stuff lives in the layer above --- package.json | 2 -- yarn.lock | 76 +++------------------------------------------------- 2 files changed, 3 insertions(+), 75 deletions(-) diff --git a/package.json b/package.json index b85191dc22..b403569eb8 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,6 @@ "eslint-plugin-flowtype": "^2.50.3", "eslint-plugin-react": "^7.20.3", "eslint-plugin-react-hooks": "^2.5.1", - "file-loader": "^3.0.1", "glob": "^5.0.15", "jest": "^24.9.0", "jest-canvas-mock": "^2.2.0", @@ -158,7 +157,6 @@ "matrix-react-test-utils": "^0.2.2", "react-test-renderer": "^16.13.1", "rimraf": "^2.7.1", - "source-map-loader": "^0.2.4", "stylelint": "^9.10.1", "stylelint-config-standard": "^18.3.0", "stylelint-scss": "^3.18.0", diff --git a/yarn.lock b/yarn.lock index ec099bbf7c..063b83e0c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1907,17 +1907,7 @@ airbnb-prop-types@^2.15.0: prop-types-exact "^1.2.0" react-is "^16.9.0" -ajv-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" - integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== - -ajv-keywords@^3.1.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" - integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== - -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5: +ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5: version "6.12.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== @@ -2142,13 +2132,6 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -async@^2.5.0: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== - dependencies: - lodash "^4.17.14" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2294,11 +2277,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -2898,15 +2876,6 @@ crc-32@^0.3.0: resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-0.3.0.tgz#6a3d3687f5baec41f7e9b99fe1953a2e5d19775e" integrity sha1-aj02h/W67EH36bmf4ZU6Ll0Zd14= -create-react-class@^15.6.3: - version "15.6.3" - resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" - integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg== - dependencies: - fbjs "^0.8.9" - loose-envify "^1.3.1" - object-assign "^4.1.1" - cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -3294,11 +3263,6 @@ emojibase-regex@^4.0.1: resolved "https://registry.yarnpkg.com/emojibase-regex/-/emojibase-regex-4.0.1.tgz#a2cd4bbb42825422da9ec72f15e970bc2c90b46a" integrity sha512-S42UHkFfz15i4NNz+wi9iMKFo+B6Kalc6PJLpYX0BUANViXw4vSyYZMFdBGRLduSabWHuEcTLZl9xOa2YP3eJw== -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - encoding@^0.1.11: version "0.1.12" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" @@ -3988,7 +3952,7 @@ fbjs@0.1.0-alpha.7: promise "^7.0.3" whatwg-fetch "^0.9.0" -fbjs@^0.8.4, fbjs@^0.8.9: +fbjs@^0.8.4: version "0.8.17" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= @@ -4027,14 +3991,6 @@ file-entry-cache@^5.0.1: dependencies: flat-cache "^2.0.1" -file-loader@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-3.0.1.tgz#f8e0ba0b599918b51adfe45d66d1e771ad560faa" - integrity sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw== - dependencies: - loader-utils "^1.0.2" - schema-utils "^1.0.0" - file-saver@^1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8" @@ -5755,15 +5711,6 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" -loader-utils@^1.0.2, loader-utils@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -5841,7 +5788,7 @@ longest-streak@^2.0.1: resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -7693,15 +7640,6 @@ scheduler@^0.19.1: loose-envify "^1.1.0" object-assign "^4.1.1" -schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" - integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== - dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" - "semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -7859,14 +7797,6 @@ socks@~2.3.2: ip "1.1.5" smart-buffer "^4.1.0" -source-map-loader@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.4.tgz#c18b0dc6e23bf66f6792437557c569a11e072271" - integrity sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ== - dependencies: - async "^2.5.0" - loader-utils "^1.1.0" - source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" From 9586a981de1b0066f5e06286a640b8def7f1c314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 9 Sep 2020 15:36:43 +0000 Subject: [PATCH 002/155] Translated using Weblate (Estonian) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 5500a4bd02..8811e49f30 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -618,7 +618,7 @@ "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s eemaldas jututoa nime.", "%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.": "%(senderDisplayName)s muutis jututoa vana nime %(oldRoomName)s uueks nimeks %(newRoomName)s.", "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s muutis jututoa nimeks %(roomName)s.", - "Show info about bridges in room settings": "Näita jututoa seadistustes teavet võrgusildade kohta", + "Show info about bridges in room settings": "Näita jututoa seadistustes teavet sõnumisildade kohta", "Upload": "Lae üles", "Save": "Salvesta", "General": "Üldist", @@ -1578,7 +1578,7 @@ "You have successfully set a password and an email address!": "Salasõna loomine ja e-posti aadressi salvestamine õnnestus!", "You can now return to your account after signing out, and sign in on other devices.": "Nüüd sa saad peale väljalogimist pöörduda tagasi oma konto juurde või logida sisse muudest seadmetest.", "Remember, you can always set an email address in user settings if you change your mind.": "Jäta meelde, et sa saad alati hiljem määrata kasutajaseadetest oma e-posti aadressi.", - "Use bots, bridges, widgets and sticker packs": "Kasuta roboteid, võrgusildu, vidinaid või kleepsupakke", + "Use bots, bridges, widgets and sticker packs": "Kasuta roboteid, sõnumisildu, vidinaid või kleepsupakke", "Upload all": "Lae kõik üles", "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "See fail on üleslaadimiseks liiga suur. Üleslaetavate failide mahupiir on %(limit)s, kuid selle faili suurus on %(sizeOfThisFile)s.", "Appearance": "Välimus", @@ -2459,5 +2459,22 @@ "User settings": "Kasutaja seadistused", "Community and user menu": "Kogukonna ja kasutaja menüü", "Privacy": "Privaatsus", - "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Lisa ( ͡° ͜ʖ ͡°) smaili vormindamata sõnumi algusesse" + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Lisa ( ͡° ͜ʖ ͡°) smaili vormindamata sõnumi algusesse", + "Unknown App": "Tundmatu rakendus", + "%(count)s results|one": "%(count)s tulemus", + "Room Info": "Jututoa teave", + "Apps": "Rakendused", + "Unpin app": "Eemalda rakenduse klammerdus", + "Edit apps, bridges & bots": "Muuda rakendusi, sõnumisildu ja roboteid", + "Add apps, bridges & bots": "Lisa rakendusi, sõnumisildu ja roboteid", + "Not encrypted": "Krüptimata", + "About": "Rakenduse teave", + "%(count)s people|other": "%(count)s inimest", + "%(count)s people|one": "%(count)s isik", + "Show files": "Näita faile", + "Room settings": "Jututoa seadistused", + "Take a picture": "Tee foto", + "Pin to room": "Klammerda jututoa külge", + "You can only pin 2 apps at a time": "Sul võib korraga olla vaid kaks klammerdatud rakendust", + "Unpin": "Eemalda klammerdus" } From 884b54fd02b7faef8f7f8bc596e5d8b29d012f78 Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Wed, 9 Sep 2020 14:56:29 +0000 Subject: [PATCH 003/155] Translated using Weblate (German) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 14a87f8308..19b7ca014b 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2457,5 +2457,22 @@ "Community settings": "Community-Einstellungen", "User settings": "Nutzer-Einstellungen", "Community and user menu": "Community- und Nutzer-Menü", - "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Stellt ( ͡° ͜ʖ ͡°) einer Klartextnachricht voran" + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Stellt ( ͡° ͜ʖ ͡°) einer Klartextnachricht voran", + "Unknown App": "Unbekannte App", + "%(count)s results|one": "%(count)s Ergebnis", + "Room Info": "Raum-Info", + "Apps": "Apps", + "Unpin app": "App nicht mehr anheften", + "Edit apps, bridges & bots": "Apps, Bridges & Bots bearbeiten", + "Add apps, bridges & bots": "Apps, Bridges & Bots hinzufügen", + "Not encrypted": "Nicht verschlüsselt", + "About": "Über", + "%(count)s people|other": "%(count)s Personen", + "%(count)s people|one": "%(count)s Person", + "Show files": "Dateien anzeigen", + "Room settings": "Raum-Einstellungen", + "Take a picture": "Foto aufnehmen", + "Pin to room": "An Raum anheften", + "You can only pin 2 apps at a time": "Du kannst nur 2 Apps gleichzeitig anheften", + "Unpin": "Nicht mehr anheften" } From 188aee0fcf3ad3bacf94bb8eebb636ce4b3f0dbd Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Wed, 9 Sep 2020 15:13:51 +0000 Subject: [PATCH 004/155] Translated using Weblate (Russian) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 203f407ece..b9e203b201 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2455,5 +2455,22 @@ "User settings": "Пользовательские настройки", "Community and user menu": "Сообщество и меню пользователя", "Privacy": "Конфиденциальность", - "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Добавляет ( ͡° ͜ʖ ͡°) к текстовому сообщению" + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Добавляет ( ͡° ͜ʖ ͡°) к текстовому сообщению", + "Unknown App": "Неизвестное приложение", + "%(count)s results|one": "%(count)s результат", + "Room Info": "Информация о комнате", + "Apps": "Приложения", + "Unpin app": "Открепить приложение", + "Edit apps, bridges & bots": "Редактировать приложения, мосты и ботов", + "Add apps, bridges & bots": "Добавить приложения, мосты и ботов", + "Not encrypted": "Не зашифровано", + "About": "О приложение", + "%(count)s people|other": "%(count)s человек", + "%(count)s people|one": "%(count)s человек", + "Show files": "Показать файлы", + "Room settings": "Настройки комнаты", + "Take a picture": "Сделать снимок", + "Pin to room": "Закрепить в комнате", + "You can only pin 2 apps at a time": "Вы можете закрепить только 2 приложения за раз", + "Unpin": "Открепить" } From 17a6f33b91b816f18806ba67e612c863e5613ca9 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Thu, 10 Sep 2020 08:41:25 +0000 Subject: [PATCH 005/155] Translated using Weblate (Albanian) Currently translated at 99.7% (2361 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sq/ --- src/i18n/strings/sq.json | 52 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index c156e3bad3..397013f9b7 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -2422,5 +2422,55 @@ "Error leaving room": "Gabim në dalje nga dhoma", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Prototipe bashkësie v2. Lyp shërbyes Home të përputhshëm. Tejet eksperimentale - përdoreni me kujdes.", "Explore rooms in %(communityName)s": "Eksploroni dhoma në %(communityName)s", - "Set up Secure Backup": "Ujdisni Kopjeruajtje të Sigurt" + "Set up Secure Backup": "Ujdisni Kopjeruajtje të Sigurt", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Një mesazhi tekst të thjeshtë vëri përpara ( ͡° ͜ʖ ͡°)", + "Unknown App": "Aplikacion i Panjohur", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Cross-signing është gati për përdorim, por depozita e fshehtë s’është duke u përdorur për kopjeruajtje të kyçeve tuaj.", + "Privacy": "Privatësi", + "Explore community rooms": "Eksploroni dhoma bashkësie", + "%(count)s results|one": "%(count)s përfundim", + "Room Info": "Të dhëna Dhome", + "Apps": "Aplikacione", + "Unpin app": "Shfiksoje aplikacionin", + "Edit apps, bridges & bots": "Përpunoni aplikacione, ura & robotë", + "Add apps, bridges & bots": "Shtoni aplikacione, ura & robotë", + "Not encrypted": "Jo e fshehtëzuar", + "About": "Mbi", + "%(count)s people|other": "%(count)s vetë", + "%(count)s people|one": "%(count)s person", + "Show files": "Shfaq kartela", + "Room settings": "Rregullime dhome", + "Take a picture": "Bëni një foto", + "Pin to room": "Fiksoje te dhoma", + "You can only pin 2 apps at a time": "Mund të fiksoni vetëm 2 aplikacione në herë", + "Information": "Informacion", + "Add another email": "Shtoni email tjetër", + "People you know on %(brand)s": "Persona që njihni në %(brand)s", + "Send %(count)s invites|other": "Dërgo %(count)s ftesa", + "Send %(count)s invites|one": "Dërgo %(count)s ftesë", + "Invite people to join %(communityName)s": "Ftoni njerëz të marrin pjesë në %(communityName)s", + "There was an error creating your community. The name may be taken or the server is unable to process your request.": "Pati një gabim teksa krijohej bashkësia juaj. Emri mund të jetë i zënë ose shërbyesi s’arrin të merret me kërkesën tuaj.", + "Community ID: +:%(domain)s": "ID Bashkësie: +:%(domain)s", + "Use this when referencing your community to others. The community ID cannot be changed.": "Përdoreni këtë kur ia referoheni bashkësinë tuaj të tjerëve. ID-ja e bashkësisë s’mund të ndryshohet.", + "You can change this later if needed.": "Këtë mund ta ndryshoni më vonë, nëse ju duhet.", + "What's the name of your community or team?": "Cili është emri i bashkësisë apo ekipit tuaj?", + "Enter name": "Jepni emër", + "Add image (optional)": "Shtoni figurë (në daçi)", + "An image will help people identify your community.": "Një figurë do t’i ndihmojë njerëzit të identifikojnë bashkësinë tuaj.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Dhoma private mund të gjenden dhe në to të hyhet vetëm me ftesë. Dhomat publike mund të gjenden dhe në to të hyhet nga kushdo.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Dhoma private mund të gjenden dhe në to të hyhet vetëm me ftesë. Dhomat publike mund të gjenden dhe në to të hyhet nga kushdo në këtë bashkësi.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Këtë mund ta aktivizonit, nëse kjo dhomë do të përdoret vetëm për bashkëpunim me ekipe të brendshëm në shërbyesin tuaj Home. Kjo s’mund të ndryshohet më vonë.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Këtë mund të çaktivizonit, nëse dhoma do të përdoret për bashkëpunim me ekipe të jashtëm që kanë shërbyesin e tyre Home. Kjo s’mund të ndryshohet më vonë.", + "Create a room in %(communityName)s": "Krijo një dhomë te %(communityName)s", + "Block anyone not part of %(serverName)s from ever joining this room.": "Bllokoji cilitdo që s’është pjesë e %(serverName)s marrjen pjesë në këtë dhomë.", + "There was an error updating your community. The server is unable to process your request.": "Pati një gabim teksa përditësohej bashkësia juaj. Shërbyesi s’është në gjendje të merret me kërkesën tuaj.", + "Update community": "Përditësoni bashkësinë", + "May include members not in %(communityName)s": "Mund të përfshijë anëtarë jo në %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Filloni një bisedë me dikë duke përdorur emrin e tij, emrin e përdoruesit (bie fjala, ) ose adresën e tij email. Kjo s’do të përbëjë ftesë për ta për t’u bërë pjesë e %(communityName)s. Për të ftuar dikë te %(communityName)s, klikoni këtu.", + "Unpin": "Shfiksoje", + "Create community": "Krijoni bashkësi", + "Failed to find the general chat for this community": "S’u arrit të gjendej fjalosja e përgjithshme për këtë bashkësi", + "Community settings": "Rregullime bashkësie", + "User settings": "Rregullime përdoruesi", + "Community and user menu": "Menu bashkësie dhe përdoruesish" } From 7f1c5537fb44f034cb7ac2ac634b7c52900c344b Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 10 Sep 2020 01:39:02 +0000 Subject: [PATCH 006/155] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 6aa980cd72..389ccc6b03 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2462,5 +2462,22 @@ "User settings": "使用者設定", "Community and user menu": "社群與使用者選單", "Privacy": "隱私", - "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "把 ( ͡° ͜ʖ ͡°) 加在純文字訊息前" + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "把 ( ͡° ͜ʖ ͡°) 加在純文字訊息前", + "Unknown App": "未知的應用程式", + "%(count)s results|one": "%(count)s 個結果", + "Room Info": "聊天室資訊", + "Apps": "應用程式", + "Unpin app": "取消釘選應用程式", + "Edit apps, bridges & bots": "編輯應用程式、橋接與機器人", + "Add apps, bridges & bots": "新增應用程式、橋接與機器人", + "Not encrypted": "未加密", + "About": "關於", + "%(count)s people|other": "%(count)s 個夥伴", + "%(count)s people|one": "%(count)s 個人", + "Show files": "顯示檔案", + "Room settings": "聊天室設定", + "Take a picture": "拍照", + "Pin to room": "釘選到聊天室", + "You can only pin 2 apps at a time": "您僅能同時釘選 2 個應用程式", + "Unpin": "取消釘選" } From c5bf61e270d63a12b821713b0b8dfb65affd01bf Mon Sep 17 00:00:00 2001 From: XoseM Date: Thu, 10 Sep 2020 05:43:54 +0000 Subject: [PATCH 007/155] Translated using Weblate (Galician) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index b8dcc48b68..8a56fe84e0 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2458,5 +2458,23 @@ "Failed to find the general chat for this community": "Non se atopou o chat xenérico para esta comunidade", "Community settings": "Axustes da comunidade", "User settings": "Axustes de usuaria", - "Community and user menu": "Menú de usuaria e comunidade" + "Community and user menu": "Menú de usuaria e comunidade", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Engade ( ͡° ͜ʖ ͡°) a unha mensaxe de texto-plano", + "Unknown App": "App descoñecida", + "%(count)s results|one": "%(count)s resultado", + "Room Info": "Info da sala", + "Apps": "Apps", + "Unpin app": "Desafixar app", + "Edit apps, bridges & bots": "Editar apps, pontes e bots", + "Add apps, bridges & bots": "Engadir apps, pontes e bots", + "Not encrypted": "Sen cifrar", + "About": "Acerca de", + "%(count)s people|other": "%(count)s persoas", + "%(count)s people|one": "%(count)s persoa", + "Show files": "Mostrar ficheiros", + "Room settings": "Axustes da sala", + "Take a picture": "Tomar unha foto", + "Pin to room": "Fixar a sala", + "You can only pin 2 apps at a time": "Só podes fixar 2 apps ó tempo", + "Unpin": "Desafixar" } From 9c749626e4d267703091b28c73a4c1f32c01ce9e Mon Sep 17 00:00:00 2001 From: Szimszon Date: Thu, 10 Sep 2020 09:47:42 +0000 Subject: [PATCH 008/155] Translated using Weblate (Hungarian) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index a9e209c169..1ebc9027d0 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2457,5 +2457,24 @@ "Failed to find the general chat for this community": "Ehhez a közösséghez nem található általános csevegés", "Community settings": "Közösségi beállítások", "User settings": "Felhasználói beállítások", - "Community and user menu": "Közösségi és felhasználói menü" + "Community and user menu": "Közösségi és felhasználói menü", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "( ͡° ͜ʖ ͡°) -t tesz a szöveg elejére", + "Unknown App": "Ismeretlen alkalmazás", + "Privacy": "Adatvédelem", + "%(count)s results|one": "%(count)s találat", + "Room Info": "Szoba információ", + "Apps": "Alkalmazások", + "Unpin app": "Alkalmazás kitűzésének megszüntetése", + "Edit apps, bridges & bots": "Alkalmazások, hidak és botok szerkesztése", + "Add apps, bridges & bots": "Alkalmazások, hidak és botok hozzáadása", + "Not encrypted": "Nem titkosított", + "About": "Névjegy", + "%(count)s people|other": "%(count)s személy", + "%(count)s people|one": "%(count)s személy", + "Show files": "Fájlok megjelenítése", + "Room settings": "Szoba beállítások", + "Take a picture": "Fénykép készítése", + "Pin to room": "A szobába kitűz", + "You can only pin 2 apps at a time": "Csak 2 alkalmazást tűzhetsz ki egyszerre", + "Unpin": "Leszed" } From 8d4a2521844721e11f28148acef114e3d334ea26 Mon Sep 17 00:00:00 2001 From: yuuki-san Date: Thu, 10 Sep 2020 11:43:18 +0000 Subject: [PATCH 009/155] Translated using Weblate (Slovak) Currently translated at 69.2% (1640 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sk/ --- src/i18n/strings/sk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/sk.json b/src/i18n/strings/sk.json index 5ed562e400..454d86cb64 100644 --- a/src/i18n/strings/sk.json +++ b/src/i18n/strings/sk.json @@ -295,7 +295,7 @@ "A text message has been sent to %(msisdn)s": "Na číslo %(msisdn)s bola odoslaná textová správa", "Please enter the code it contains:": "Prosím, zadajte kód z tejto správy:", "Start authentication": "Spustiť overenie", - "powered by Matrix": "Poháňa Matrix", + "powered by Matrix": "používa protokol Matrix", "Sign in with": "Na prihlásenie sa použije", "Email address": "Emailová adresa", "Sign in": "Prihlásiť sa", From 7d3e0ed9ce51f39d594456d74024955dbcd5363a Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Thu, 10 Sep 2020 08:01:58 +0000 Subject: [PATCH 010/155] Translated using Weblate (Swedish) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 2032eca4ff..c230c361be 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -801,7 +801,7 @@ "Unpin Message": "Ta bort fastnålning", "No pinned messages.": "Inga fastnålade meddelanden.", "Pinned Messages": "Fastnålade meddelanden", - "Pin Message": "Nåla fast meddelande", + "Pin Message": "Fäst meddelande", "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "Den exporterade filen kommer att låta de som kan läsa den att dekryptera alla krypterade meddelanden som du kan se, så du bör vara noga med att hålla den säker. För att hjälpa till med detta, bör du ange en lösenfras nedan, som kommer att användas för att kryptera exporterad data. Det kommer bara vara möjligt att importera data genom att använda samma lösenfras.", "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "Denna process möjliggör import av krypteringsnycklar som tidigare exporterats från en annan Matrix-klient. Du kommer då kunna avkryptera alla meddelanden som den andra klienten kunde avkryptera.", "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Den exporterade filen kommer vara skyddad med en lösenfras. Du måste ange lösenfrasen här, för att avkryptera filen.", @@ -2392,5 +2392,22 @@ "Toggle this dialog": "Växla den här dialogrutan", "Move autocomplete selection up/down": "Flytta autokompletteringssektionen upp/ner", "Cancel autocomplete": "Stäng autokomplettering", - "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Lägger till ( ͡° ͜ʖ ͡°) i början på ett textmeddelande" + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Lägger till ( ͡° ͜ʖ ͡°) i början på ett textmeddelande", + "Unknown App": "Okänd app", + "%(count)s results|one": "%(count)s resultat", + "Room Info": "Rumsinfo", + "Apps": "Appar", + "Unpin app": "Avfäst app", + "Edit apps, bridges & bots": "Redigera appar, bryggor och bottar", + "Add apps, bridges & bots": "Lägg till appar, bryggor och bottar", + "Not encrypted": "Inte krypterad", + "About": "Om", + "%(count)s people|other": "%(count)s personer", + "%(count)s people|one": "%(count)s person", + "Show files": "Visa filer", + "Room settings": "Rumsinställningar", + "Take a picture": "Ta en bild", + "Pin to room": "Fäst i rum", + "You can only pin 2 apps at a time": "Du kan bara fästa två appar på en gång", + "Unpin": "Avfäst" } From 19ebc6401f5b500434f99872a14bfda193e97516 Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Fri, 11 Sep 2020 09:08:15 +0000 Subject: [PATCH 011/155] Translated using Weblate (Russian) Currently translated at 100.0% (2370 of 2370 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index b9e203b201..904763db67 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2472,5 +2472,16 @@ "Take a picture": "Сделать снимок", "Pin to room": "Закрепить в комнате", "You can only pin 2 apps at a time": "Вы можете закрепить только 2 приложения за раз", - "Unpin": "Открепить" + "Unpin": "Открепить", + "Cross-signing is ready for use.": "Кросс-подпись готова к использованию.", + "Cross-signing is not set up.": "Кросс-подпись не настроена.", + "Backup version:": "Версия резервной копии:", + "Algorithm:": "Алгоритм:", + "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Сделайте резервную копию ключей шифрования с данными вашей учетной записи на случай, если вы потеряете доступ к своим сеансам. Ваши ключи будут защищены уникальным ключом восстановления.", + "Backup key stored:": "Резервный ключ сохранён:", + "Backup key cached:": "Резервный ключ кэширован:", + "Secret storage:": "Секретное хранилище:", + "ready": "готов", + "not ready": "не готов", + "Secure Backup": "Безопасное резервное копирование" } From 277aefdd5d69c48233503fbbe094320c18fda5bf Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Fri, 11 Sep 2020 13:09:59 +0000 Subject: [PATCH 012/155] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2373 of 2373 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 389ccc6b03..c7dc5555c4 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2479,5 +2479,19 @@ "Take a picture": "拍照", "Pin to room": "釘選到聊天室", "You can only pin 2 apps at a time": "您僅能同時釘選 2 個應用程式", - "Unpin": "取消釘選" + "Unpin": "取消釘選", + "Group call modified by %(senderName)s": "由 %(senderName)s 修改的群組通話", + "Group call started by %(senderName)s": "由 %(senderName)s 開始的群組通話", + "Group call ended by %(senderName)s": "由 %(senderName)s 結束的群組通話", + "Cross-signing is ready for use.": "交叉簽章已準備好使用。", + "Cross-signing is not set up.": "交叉簽章尚未設定。", + "Backup version:": "備份版本:", + "Algorithm:": "演算法:", + "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "如果您無法存取您的工作階段,請使用您的帳號資料來備份加密金鑰。您的金鑰將會被獨一無二的金鑰保護。", + "Backup key stored:": "備份金鑰已儲存:", + "Backup key cached:": "備份金鑰已快取:", + "Secret storage:": "秘密儲存空間:", + "ready": "準備好", + "not ready": "尚未準備好", + "Secure Backup": "安全備份" } From b1bdb0650771797faca192e9762af8d281437ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Fri, 11 Sep 2020 11:40:31 +0000 Subject: [PATCH 013/155] Translated using Weblate (Estonian) Currently translated at 100.0% (2373 of 2373 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 8811e49f30..5fd83557bd 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2476,5 +2476,19 @@ "Take a picture": "Tee foto", "Pin to room": "Klammerda jututoa külge", "You can only pin 2 apps at a time": "Sul võib korraga olla vaid kaks klammerdatud rakendust", - "Unpin": "Eemalda klammerdus" + "Unpin": "Eemalda klammerdus", + "Group call modified by %(senderName)s": "%(senderName)s muutis rühmakõnet", + "Group call started by %(senderName)s": "%(senderName)s algatas rühmakõne", + "Group call ended by %(senderName)s": "%(senderName)s lõpetas rühmakõne", + "Cross-signing is ready for use.": "Risttunnustamine on kasutamiseks valmis.", + "Cross-signing is not set up.": "Risttunnustamine on seadistamata.", + "Backup version:": "Varukoopia versioon:", + "Algorithm:": "Algoritm:", + "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Selleks puhuks, kui sa kaotad ligipääsu kõikidele oma sessioonidele, tee varukoopia oma krüptovõtmetest ja kasutajakonto seadistustest. Unikaalse taastevõtmega tagad selle, et sinu varukoopia on turvaline.", + "Backup key stored:": "Varukoopia võti on salvestatud:", + "Backup key cached:": "Varukoopia võti on puhverdatud:", + "Secret storage:": "Turvahoidla:", + "ready": "valmis", + "not ready": "ei ole valmis", + "Secure Backup": "Turvaline varundus" } From ada8b8deec33b541d2807efc7ad8d2ea11fba28b Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Fri, 11 Sep 2020 12:39:06 +0000 Subject: [PATCH 014/155] Translated using Weblate (Russian) Currently translated at 100.0% (2373 of 2373 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 904763db67..906f9a7901 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2483,5 +2483,8 @@ "Secret storage:": "Секретное хранилище:", "ready": "готов", "not ready": "не готов", - "Secure Backup": "Безопасное резервное копирование" + "Secure Backup": "Безопасное резервное копирование", + "Group call modified by %(senderName)s": "%(senderName)s изменил(а) групповой вызов", + "Group call started by %(senderName)s": "Групповой вызов начат %(senderName)s", + "Group call ended by %(senderName)s": "%(senderName)s завершил(а) групповой вызов" } From d67ad473c3fc7690fdf0110198d40ad0ca4803a2 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Fri, 11 Sep 2020 11:01:47 +0000 Subject: [PATCH 015/155] Translated using Weblate (Swedish) Currently translated at 100.0% (2373 of 2373 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index c230c361be..8be68c031e 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -2409,5 +2409,19 @@ "Take a picture": "Ta en bild", "Pin to room": "Fäst i rum", "You can only pin 2 apps at a time": "Du kan bara fästa två appar på en gång", - "Unpin": "Avfäst" + "Unpin": "Avfäst", + "Group call modified by %(senderName)s": "Gruppsamtal ändrat av %(senderName)s", + "Group call started by %(senderName)s": "Gruppsamtal startat av %(senderName)s", + "Group call ended by %(senderName)s": "Gruppsamtal avslutat av %(senderName)s", + "Cross-signing is ready for use.": "Korssignering är klart att användas.", + "Cross-signing is not set up.": "Korssignering är inte inställt.", + "Backup version:": "Version av säkerhetskopia:", + "Algorithm:": "Algoritm:", + "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Säkerhetskopiera dina krypteringsnycklar med dina kontodata ifall du förlorar åtkomst till dina sessioner. Dina nycklar skyddas med en unik återställningsnyckel.", + "Backup key stored:": "Lagrad säkerhetskopieringsnyckel:", + "Backup key cached:": "Cachad säkerhetskopieringsnyckel:", + "Secret storage:": "Hemlig lagring:", + "ready": "klart", + "not ready": "inte klart", + "Secure Backup": "Säker säkerhetskopiering" } From 4c7d2363cee4782d4bb794eb52edef55ffd004ca Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Fri, 11 Sep 2020 14:10:51 +0000 Subject: [PATCH 016/155] Translated using Weblate (German) Currently translated at 99.9% (2372 of 2373 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 19b7ca014b..c664caed14 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2474,5 +2474,19 @@ "Take a picture": "Foto aufnehmen", "Pin to room": "An Raum anheften", "You can only pin 2 apps at a time": "Du kannst nur 2 Apps gleichzeitig anheften", - "Unpin": "Nicht mehr anheften" + "Unpin": "Nicht mehr anheften", + "Group call modified by %(senderName)s": "Gruppenanruf wurde von %(senderName)s verändert", + "Group call started by %(senderName)s": "Gruppenanruf von %(senderName)s gestartet", + "Group call ended by %(senderName)s": "Gruppenanruf wurde von %(senderName)s beendet", + "Cross-signing is ready for use.": "Cross-Signing ist bereit zur Anwendung.", + "Cross-signing is not set up.": "Cross-Signing wurde nicht eingerichtet.", + "Backup version:": "Backup-Version:", + "Algorithm:": "Algorithmus:", + "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Sichere deine Verschlüsselungsschlüssel mit deinen Kontodaten, falls du den Zugriff auf deine Sitzungen verlierst. Deine Schlüssel werden mit einem eindeutigen Wiederherstellungsschlüssel gesichert.", + "Backup key stored:": "Sicherungsschlüssel gespeichert:", + "Backup key cached:": "Sicherungsschlüssel zwischengespeichert:", + "Secret storage:": "Sicherer Speicher:", + "ready": "bereit", + "not ready": "nicht bereit", + "Secure Backup": "Sicheres Backup" } From d5b8588cb4828ee17fc16ae3a9f075dbb703c986 Mon Sep 17 00:00:00 2001 From: toastbroot Date: Fri, 11 Sep 2020 14:39:32 +0000 Subject: [PATCH 017/155] Translated using Weblate (German) Currently translated at 99.9% (2372 of 2373 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index c664caed14..9313861206 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -1633,7 +1633,7 @@ "Sends a message as html, without interpreting it as markdown": "Verschickt eine Nachricht im html-Format, ohne sie in Markdown zu formatieren", "Show rooms with unread notifications first": "Räume mit ungelesenen Benachrichtigungen zuerst zeigen", "Show shortcuts to recently viewed rooms above the room list": "Kurzbefehle zu den kürzlich gesichteten Räumen über der Raumliste anzeigen", - "Use Single Sign On to continue": "Verwende Single Sign on um fortzufahren", + "Use Single Sign On to continue": "Benutze Single Sign-On um fortzufahren", "Confirm adding this email address by using Single Sign On to prove your identity.": "Bestätige die hinzugefügte E-Mail-Adresse mit Single Sign-On, um deine Identität nachzuweisen.", "Single Sign On": "Single Sign-On", "Confirm adding email": "Bestätige hinzugefügte E-Mail-Addresse", From 7a26eb98401e01243f82b0a98940a884f15a7ad0 Mon Sep 17 00:00:00 2001 From: felix adernog Date: Fri, 11 Sep 2020 14:43:50 +0000 Subject: [PATCH 018/155] Translated using Weblate (German) Currently translated at 99.9% (2372 of 2373 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 9313861206..0cf4b6323f 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -1330,7 +1330,7 @@ "If you can't find the room you're looking for, ask for an invite or Create a new room.": "Wenn du den gesuchten Raum nicht finden kannst, frage nach einer Einladung für den Raum oder Erstelle einen neuen Raum.", "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Alternativ kannst du versuchen, den öffentlichen Server unter turn.matrix.org zu verwenden. Allerdings wird dieser nicht so zuverlässig sein, und deine IP-Adresse mit diesem Server teilen. Du kannst dies auch in den Einstellungen konfigurieren.", "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Diese Handlung erfordert es, auf den Standard-Identitätsserver zuzugreifen, um eine E-Mail Adresse oder Telefonnummer zu validieren, aber der Server hat keine Nutzungsbedingungen.", - "Only continue if you trust the owner of the server.": "Fahre nur fort, wenn du den Inhaber*innen des Servers vertraust.", + "Only continue if you trust the owner of the server.": "Fahre nur fort, wenn du dem Besitzer*in des Servers vertraust.", "Trust": "Vertrauen", "Custom (%(level)s)": "Benutzerdefinierte (%(level)s)", "Sends a message as plain text, without interpreting it as markdown": "Verschickt eine Nachricht in reinem Textformat, ohne sie in Markdown zu formatieren", From 0721ff85fe070c3e6a0873718ad25c5c6ef9c57f Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Fri, 11 Sep 2020 14:44:02 +0000 Subject: [PATCH 019/155] Translated using Weblate (German) Currently translated at 99.9% (2372 of 2373 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 0cf4b6323f..4deb58887a 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -1330,7 +1330,7 @@ "If you can't find the room you're looking for, ask for an invite or Create a new room.": "Wenn du den gesuchten Raum nicht finden kannst, frage nach einer Einladung für den Raum oder Erstelle einen neuen Raum.", "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Alternativ kannst du versuchen, den öffentlichen Server unter turn.matrix.org zu verwenden. Allerdings wird dieser nicht so zuverlässig sein, und deine IP-Adresse mit diesem Server teilen. Du kannst dies auch in den Einstellungen konfigurieren.", "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Diese Handlung erfordert es, auf den Standard-Identitätsserver zuzugreifen, um eine E-Mail Adresse oder Telefonnummer zu validieren, aber der Server hat keine Nutzungsbedingungen.", - "Only continue if you trust the owner of the server.": "Fahre nur fort, wenn du dem Besitzer*in des Servers vertraust.", + "Only continue if you trust the owner of the server.": "Fahre nur fort, wenn du dem/r Besitzer*in des Servers vertraust.", "Trust": "Vertrauen", "Custom (%(level)s)": "Benutzerdefinierte (%(level)s)", "Sends a message as plain text, without interpreting it as markdown": "Verschickt eine Nachricht in reinem Textformat, ohne sie in Markdown zu formatieren", From 80f8a18c2a78206435d321a5a0ced1c13f85c862 Mon Sep 17 00:00:00 2001 From: Safa Alfulaij Date: Sat, 12 Sep 2020 10:45:32 +0000 Subject: [PATCH 020/155] Translated using Weblate (Arabic) Currently translated at 14.9% (354 of 2373 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ar/ --- src/i18n/strings/ar.json | 200 +++++++++++++++++++-------------------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/src/i18n/strings/ar.json b/src/i18n/strings/ar.json index a6a52b147d..718edd4c26 100644 --- a/src/i18n/strings/ar.json +++ b/src/i18n/strings/ar.json @@ -1,5 +1,5 @@ { - "Continue": "إستمر", + "Continue": "واصِل", "Username available": "اسم المستخدم متاح", "Username not available": "الإسم المستخدم غير موجود", "Something went wrong!": "هناك خطأ ما!", @@ -7,18 +7,18 @@ "Close": "إغلاق", "Create new room": "إنشاء غرفة جديدة", "Custom Server Options": "الإعدادات الشخصية للخادوم", - "Dismiss": "تجاهل", + "Dismiss": "أهمِل", "Failed to change password. Is your password correct?": "فشلت عملية تعديل الكلمة السرية. هل كلمتك السرية صحيحة ؟", "Warning": "تنبيه", - "Error": "خطأ", + "Error": "عُطل", "Remove": "حذف", "Send": "إرسال", "Edit": "تعديل", "This email address is already in use": "عنوان البريد هذا مستخدم بالفعل", "This phone number is already in use": "رقم الهاتف هذا مستخدم بالفعل", - "Failed to verify email address: make sure you clicked the link in the email": "فشل تأكيد عنوان البريد الإلكتروني: تحقق من نقر الرابط في البريد", + "Failed to verify email address: make sure you clicked the link in the email": "فشل التثبّت من عنوان البريد الإلكتروني: تأكّد من نقر الرابط في البريد المُرسل", "The version of %(brand)s": "إصدارة %(brand)s", - "Whether or not you're using the Richtext mode of the Rich Text Editor": "فيما إذا كنت تستخدم وضع النص الغني لمحرر النصوص الغني أم لا", + "Whether or not you're using the Richtext mode of the Rich Text Editor": "فيما إذا كنت تستعمل وضع النص الغني في محرّر النصوص الغنية", "Your homeserver's URL": "عنوان خادوم المنزل", "Analytics": "التحاليل", "The information being sent to us to help make %(brand)s better includes:": "تحتوي المعلومات التي تُرسل إلينا للمساعدة بتحسين جودة %(brand)s الآتي:", @@ -59,120 +59,120 @@ "Checking for an update...": "البحث عن تحديث …", "powered by Matrix": "مشغل بواسطة Matrix", "The platform you're on": "المنصة الحالية", - "Your language of choice": "اللغة المختارة", - "e.g. %(exampleValue)s": "مثال %(exampleValue)s", - "Use Single Sign On to continue": "استخدم تسجيل الدخول الموحد للاستمرار", - "Confirm adding this email address by using Single Sign On to prove your identity.": "اكد اضافة بريدك الالكتروني عن طريق الدخول الموحد (SSO) لتثبت هويتك.", - "Single Sign On": "تسجيل الدخول الموحد", - "Confirm adding email": "تأكيد اضافة بريدك الالكتروني", - "Click the button below to confirm adding this email address.": "انقر على الزر ادناه لتأكد اضافة هذا البريد الالكتروني.", - "Confirm": "تأكيد", - "Add Email Address": "اضافة بريد الكتروني", - "Confirm adding this phone number by using Single Sign On to prove your identity.": "قم بتأكيد اضافة رقم الهاتف هذا باستخدام تقنية الدخول الموحد لتثبت هويتك.", - "Confirm adding phone number": "قم بتأكيد اضافة رقم الهاتف", - "Click the button below to confirm adding this phone number.": "انقر الزر ادناه لتأكيد اضافة رقم الهاتف.", - "Add Phone Number": "اضافة رقم هاتف", - "Which officially provided instance you are using, if any": "التي تقدم البيئة التي تستخدمها بشكل رسمي، اذا كان هناك", - "Whether you're using %(brand)s on a device where touch is the primary input mechanism": "عندما تستخدم %(brand)s على جهاز تكون شاشة اللمس هي طريقة الادخال الرئيسية", - "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "اذا كنت تستخدم او لا تستخدم ميزة 'breadcrumbs' (الافاتار فوق قائمة الغرف)", - "Whether you're using %(brand)s as an installed Progressive Web App": "اذا كنت تستخدم %(brand)s كتطبيق ويب", - "Your user agent": "وكيل المستخدم الخاص بك", - "Unable to load! Check your network connectivity and try again.": "غير قادر على التحميل! قم فحص اتصالك الشبكي وحاول مرة اخرى.", - "Call Timeout": "مهلة الاتصال", - "Call failed due to misconfigured server": "فشل الاتصال بسبب إعداد السيرفر بشكل خاطئ", - "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.": "يرجى مطالبة مسئول سيرفرك (%(homeserverDomain)s) بإعداد سيرفر TURN لكي تعمل المكالمات بشكل صحيح.", - "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "بدلاً من ذلك، يمكنك محاولة استخدام السيرفر العام على turn.matrix.org، ولكن هذا لن يكون موثوقًا به، وسيشارك عنوان IP الخاص بك مع هذا السيرفر. يمكنك أيضًا تعديل ذلك في الإعدادات.", - "Try using turn.matrix.org": "جرب استخدام turn.matrix.org", - "OK": "حسنا", - "Unable to capture screen": "غير قادر على التقاط الشاشة", - "Call Failed": "فل الاتصال", - "You are already in a call.": "أنت بالفعل في مكالمة.", + "Your language of choice": "اللغة التي تريد", + "e.g. %(exampleValue)s": "مثال: %(exampleValue)s", + "Use Single Sign On to continue": "استعمل الولوج الموحّد للمواصلة", + "Confirm adding this email address by using Single Sign On to prove your identity.": "أكّد إضافتك لعنوان البريد هذا باستعمال الولوج الموحّد لإثبات هويّتك.", + "Single Sign On": "الولوج الموحّد", + "Confirm adding email": "أكّد إضافة البريد الإلكتروني", + "Click the button below to confirm adding this email address.": "انقر الزر أسفله لتأكيد إضافة عنوان البريد الإلكتروني هذا.", + "Confirm": "أكّد", + "Add Email Address": "أضِف بريدًا إلكترونيًا", + "Confirm adding this phone number by using Single Sign On to prove your identity.": "أكّد إضافتك لرقم الهاتف هذا باستعمال الولوج الموحّد لإثبات هويّتك.", + "Confirm adding phone number": "أكّد إضافة رقم الهاتف", + "Click the button below to confirm adding this phone number.": "انقر الزر أسفله لتأكيد إضافة رقم الهاتف هذا", + "Add Phone Number": "أضِف رقم الهاتف", + "Which officially provided instance you are using, if any": "السيرورة المقدّمة رسميًا التي تستعملها، لو وُجدت", + "Whether you're using %(brand)s on a device where touch is the primary input mechanism": "فيما إذا كنت تستعمل %(brand)s على جهاز اللمس فيه هو طريقة الإدخال الرئيسة", + "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "", + "Whether you're using %(brand)s as an installed Progressive Web App": "فيما إذا كنت تستعمل %(brand)s كتطبيق وِب تدرّجي", + "Your user agent": "وكيل المستخدم الذي تستعمله", + "Unable to load! Check your network connectivity and try again.": "تعذر التحميل! افحص اتصالك بالشبكة وأعِد المحاولة.", + "Call Timeout": "انتهت مهلة الاتصال", + "Call failed due to misconfigured server": "فشل الاتصال بسبب سوء ضبط الخادوم", + "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.": "من فضلك اطلب من مسؤول الخادوم المنزل الذي تستعمله (%(homeserverDomain)s) أن يضبط خادوم TURN كي تعمل الاتصالات بنحوٍ يكون محط ثقة.", + "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "أو يمكنك محاولة الخادوم العمومي turn.matrix.org إلا أنه لن يكون محطّ ثقة إذ سيُشارك عنوان IP لديك بذاك الخادوم. يمكنك أيضًا إدارة هذا من الإعدادات.", + "Try using turn.matrix.org": "جرّب استعمال turn.matrix.org", + "OK": "حسنًا", + "Unable to capture screen": "تعذر التقاط الشاشة", + "Call Failed": "فشل الاتصال", + "You are already in a call.": "تُجري مكالمة الآن.", "VoIP is unsupported": "تقنية VoIP غير مدعومة", - "You cannot place VoIP calls in this browser.": "لايمكنك اجراء مكالمات VoIP عبر هذا المتصفح.", - "A call is currently being placed!": "يتم حاليًا إجراء مكالمة!", - "A call is already in progress!": "المكالمة جارية بالفعل!", - "Permission Required": "مطلوب صلاحية", - "You do not have permission to start a conference call in this room": "ليس لديك صلاحية لبدء مكالمة جماعية في هذه الغرفة", - "Replying With Files": "الرد مع الملفات", - "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "في الوقت الحالي ، لا يمكن الرد مع ملف. هل تريد تحميل هذا الملف بدون رد؟", - "The file '%(fileName)s' failed to upload.": "فشل في رفع الملف '%(fileName)s'.", - "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "إن حجم الملف '%(fileName)s' يتجاوز الحد المسموح به للرفع في السيرفر", + "You cannot place VoIP calls in this browser.": "لا يمكنك إجراء مكالمات VoIP عبر هذا المتصفح.", + "A call is currently being placed!": "يجري إجراء المكالمة!", + "A call is already in progress!": "تُجري مكالمة الآن فعلًا!", + "Permission Required": "التصريح مطلوب", + "You do not have permission to start a conference call in this room": "ينقصك تصريح بدء مكالمة جماعية في هذه الغرفة", + "Replying With Files": "الرد مع ملفات", + "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "لا يمكنك حاليًا إرسال رد مع ملف. أتريد رفع الملف دون الرد؟", + "The file '%(fileName)s' failed to upload.": "فشل رفع الملف ”%(fileName)s“.", + "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "حجم الملف ”%(fileName)s“ يتجاوز الحجم الأقصى الذي يسمح به الخادوم المنزل", "Upload Failed": "فشل الرفع", - "Server may be unavailable, overloaded, or you hit a bug.": "قد يكون السيرفر غير متوفر، او محملا بشكل زائد او انك طلبت ميزة بها مشكلة.", - "The server does not support the room version specified.": "السيرفر لا يدعم إصدار الغرفة المحدد.", - "Failure to create room": "فشل في انشاء الغرفة", + "Server may be unavailable, overloaded, or you hit a bug.": "قد لا يكون الخادوم متاحًا، أو أن عليه ضغط، أو أنك واجهت علة.", + "The server does not support the room version specified.": "لا يدعم الخادوم إصدارة الغرفة المحدّدة.", + "Failure to create room": "فشل إنشاء الغرفة", "Cancel entering passphrase?": "هل تريد إلغاء إدخال عبارة المرور؟", "Are you sure you want to cancel entering passphrase?": "هل أنت متأكد من أنك تريد إلغاء إدخال عبارة المرور؟", "Go Back": "الرجوع للخلف", "Setting up keys": "إعداد المفاتيح", - "Sun": "احد", - "Mon": "اثنين", - "Tue": "ثلاثاء", - "Wed": "اربعاء", - "Thu": "خميس", - "Fri": "جمعة", - "Sat": "سبت", + "Sun": "الأحد", + "Mon": "الإثنين", + "Tue": "الثلاثاء", + "Wed": "الأربعاء", + "Thu": "الخميس", + "Fri": "الجمعة", + "Sat": "السبت", "Jan": "يناير", "Feb": "فبراير", "Mar": "مارس", - "Apr": "ابريل", + "Apr": "أبريل", "May": "مايو", "Jun": "يونيو", "Jul": "يوليو", - "Aug": "اغسطس", + "Aug": "أغسطس", "Sep": "سبتمبر", - "Oct": "اكتوبر", + "Oct": "أكتوبر", "Nov": "نوفمبر", "Dec": "ديسمبر", - "PM": "مساء", - "AM": "صباحا", - "%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s", - "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s", + "PM": "م", + "AM": "ص", + "%(weekDayName)s %(time)s": "%(weekDayName)s ‏%(time)s", + "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", - "Who would you like to add to this community?": "هل ترغب في اضافة هذا المجتمع؟", - "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "تحذير: أي شخص تضيفه إلى مجتمع سيكون مرئيًا للعامة لأي شخص يعرف معرف المجتمع", - "Invite new community members": "دعوى اعضاء جدد للمجتمع", - "Name or Matrix ID": "الاسم او معرف Matrix", - "Invite to Community": "دعوة الى المجتمع", - "Which rooms would you like to add to this community?": "ما هي الغرف التي ترغب في إضافتها إلى هذا المجتمع؟", - "Show these rooms to non-members on the community page and room list?": "هل تريد إظهار هذه الغرف لغير الأعضاء في صفحة المجتمع وقائمة الغرف؟", - "Add rooms to the community": "اضافة غرف الى المجتمع", - "Room name or address": "اسم او عنوان الغرفة", - "Add to community": "اضافة لمجتمع", - "Failed to invite the following users to %(groupId)s:": "فشل في اضافة المستخدمين التاليين الى %(groupId)s:", - "Failed to invite users to community": "فشل دعوة المستخدمين إلى المجتمع", - "Failed to invite users to %(groupId)s": "فشل في دعوة المستخدمين الى %(groupId)s", - "Failed to add the following rooms to %(groupId)s:": "فشل في اضافة الغرف التالية الى %(groupId)s:", + "Who would you like to add to this community?": "مَن تريد إضافته إلى هذا المجتمع؟", + "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "تحذير: كلّ من تُضيفه إلى أحد المجتمعات سيكون ظاهرًا لكل من يعرف معرّف المجتمع", + "Invite new community members": "ادعُ أعضاء جدد إلى المجتمع", + "Name or Matrix ID": "الاسم أو معرّف «ماترِكس", + "Invite to Community": "ادعُ إلى المجتمع", + "Which rooms would you like to add to this community?": "ما الغرف التي تُريد إضافتها إلى هذا المجتمع؟", + "Show these rooms to non-members on the community page and room list?": "أتريد عرض هذه الغرف على غير المسجلين كأعضاء في صفحة المجتمع وقائمة الغُرف؟", + "Add rooms to the community": "أضِف غرف إلى المجتمع", + "Room name or address": "اسم الغرفة أو العنوان", + "Add to community": "أضِف إلى المجتمع", + "Failed to invite the following users to %(groupId)s:": "فشلت دعوة المستخدمين الآتية أسمائهم إلى %(groupId)s:", + "Failed to invite users to community": "فشلت دعوة المستخدمين إلى المجتمع", + "Failed to invite users to %(groupId)s": "فشلت دعوة المستخدمين إلى %(groupId)s", + "Failed to add the following rooms to %(groupId)s:": "فشلت إضافة الغرف الآتية إلى %(groupId)s:", "Unnamed Room": "غرفة بدون اسم", - "Identity server has no terms of service": "سيرفر الهوية ليس لديه شروط للخدمة", - "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "هذا الحدث يتطلب الوصول الى السيرفر الافتراضي للهوية للتحقق من البريد الالكتروني او رقم الهاتف، ولكن هذا السيرفر ليس لديه اي شروط للخدمة.", - "Only continue if you trust the owner of the server.": "لا تستمر إلا إذا كنت تثق في مالك السيرفر.", - "Trust": "ثِق", - "%(name)s is requesting verification": "%(name)s يطلب التحقق", - "%(brand)s does not have permission to send you notifications - please check your browser settings": "%(brand)s ليس لديه الصلاحية لارسال التنبيهات - يرجى فحص اعدادات متصفحك", - "%(brand)s was not given permission to send notifications - please try again": "لم تعطى الصلاحية ل %(brand)s لارسال التنبيهات - يرجى المحاولة ثانية", - "Unable to enable Notifications": "غير قادر على تفعيل التنبيهات", - "This email address was not found": "لم يتم العثور على البريد الالكتروني هذا", - "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "يبدو ان بريدك الالكتروني غير مرتبط بمعرف Matrix على هذا السيرفر.", + "Identity server has no terms of service": "ليس لخادوم الهويّة أيّ شروط خدمة", + "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "يطلب هذا الإجراء الوصول إلى خادوم الهويّات المبدئيللتثبّت من عنوان البريد الإلكتروني أو رقم الهاتف، ولكن ليس للخادوم أيّ شروط خدمة.", + "Only continue if you trust the owner of the server.": "لا تُواصل لو لم تكن تثق بمالك الخادوم.", + "Trust": "أثق به", + "%(name)s is requesting verification": "يطلب %(name)s التثبّت", + "%(brand)s does not have permission to send you notifications - please check your browser settings": "لا يملك %(brand)s التصريح لإرسال التنبيهات. من فضلك تحقّق من إعدادات المتصفح", + "%(brand)s was not given permission to send notifications - please try again": "لم تقدّم التصريح اللازم كي يُرسل %(brand)s التنبيهات. من فضلك أعِد المحاولة", + "Unable to enable Notifications": "تعذر تفعيل التنبيهات", + "This email address was not found": "لم يوجد عنوان البريد الإلكتروني هذا", + "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "لا يظهر بأن عنوان بريدك مرتبط بمعرّف «ماترِكس» على الخادوم المنزل هذا.", "Use your account to sign in to the latest version": "استخدم حسابك للدخول الى الاصدار الاخير", "We’re excited to announce Riot is now Element": "نحن سعيدون باعلان ان Riot اصبح الان Element", "Riot is now Element!": "Riot اصبح الان Element!", "Learn More": "تعلم المزيد", - "Sign In or Create Account": "قم بتسجيل الدخول او انشاء حساب جديد", - "Use your account or create a new one to continue.": "استخدم حسابك او قم بانشاء حساب اخر للاستمرار.", - "Create Account": "انشاء حساب", - "Sign In": "الدخول", - "Default": "افتراضي", + "Sign In or Create Account": "لِج أو أنشِئ حسابًا", + "Use your account or create a new one to continue.": "استعمل حسابك أو أنشِئ واحدًا جديدًا للمواصلة.", + "Create Account": "أنشِئ حسابًا", + "Sign In": "لِج", + "Default": "المبدئي", "Restricted": "مقيد", "Moderator": "مشرف", "Admin": "مدير", "Custom (%(level)s)": "(%(level)s) مخصص", - "Failed to invite": "فشل في الدعوة", + "Failed to invite": "فشلت الدعوة", "Operation failed": "فشلت العملية", - "Failed to invite users to the room:": "فشل في دعوة المستخدمين للغرفة:", - "Failed to invite the following users to the %(roomName)s room:": "فشل في دعوة المستخدمين التالية اسمائهم الى الغرفة %(roomName)s:", - "You need to be logged in.": "تحتاج إلى تسجيل الدخول.", + "Failed to invite users to the room:": "فشلت دعوة المستخدمين إلى الغرفة:", + "Failed to invite the following users to the %(roomName)s room:": "فشلت دعوة المستخدمين الآتية أسمائهم إلى غرفة %(roomName)s:", + "You need to be logged in.": "عليك الولوج.", "You need to be able to invite users to do that.": "يجب أن تكون قادرًا على دعوة المستخدمين للقيام بذلك.", "Unable to create widget.": "غير قادر على إنشاء Widget.", "Missing roomId.": "معرف الغرفة مفقود.", @@ -320,15 +320,15 @@ "%(widgetName)s widget modified by %(senderName)s": "الودجت %(widgetName)s تعدلت بواسطة %(senderName)s", "%(widgetName)s widget added by %(senderName)s": "الودجت %(widgetName)s اضيفت بواسطة %(senderName)s", "%(widgetName)s widget removed by %(senderName)s": "الودجت %(widgetName)s حذفت بواسطة %(senderName)s", - "Whether or not you're logged in (we don't record your username)": "سواءً كنت مسجلا دخولك أم لا (لا نحتفظ باسم المستخدم)", - "Every page you use in the app": "كل صفحة تستخدمها في التطبيق", - "e.g. ": "مثلا <رابط الصفحة الحالية>", - "Your device resolution": "دقة شاشة جهازك", - "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "على الرغم من كون هذه الصفحة تحوي معلومات تمكن تحديد الهوية، مثل معرف الغرفة والمستخدم والمجموعة، فهذه البيانات يتم حذفها قبل أن ترسل للسيرفر.", - "The remote side failed to pick up": "الطرف الآخر لم يتمكن من الرد", - "Existing Call": "مكالمة موجودة", + "Whether or not you're logged in (we don't record your username)": "سواءً كنت والجًا أم لا (لا نحتفظ باسم المستخدم)", + "Every page you use in the app": "كل صفحة تستعملها في التطبيق", + "e.g. ": "مثال: <عنوان_الصفحة_الحالية>", + "Your device resolution": "ميز الجهاز لديك", + "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "على الرغم من احتواء هذه الصفحة على معلومات تُحدّد الهويّة (مثل معرّف الغرفة والمستخدم والمجموعة) إلّا أن هذه البيانات تُحذف قبل إرسالها إلى الخادوم.", + "The remote side failed to pick up": "لم يردّ الطرف الآخر", + "Existing Call": "مكالمة جارية", "You cannot place a call with yourself.": "لا يمكنك الاتصال بنفسك.", - "Call in Progress": "المكالمة قيد التحضير", + "Call in Progress": "إجراء المكالمة جارٍ", "Please install Chrome, Firefox, or Safari for the best experience.": "يرجى تثبيت Chrome, Firefox, or Safari for the best experience.", "%(senderName)s removed the rule banning users matching %(glob)s": "%(اسم المرسل)S إزالة القاعدة التي تحظر المستخدمين المتطابقين %(عام)s", "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(اسم المرسل)s إزالة القاعدة التي تحظر الغرف المتطابقة %(عام)s", From 0923eeb2ad4e7bb7b70577598fd54de91872fdaf Mon Sep 17 00:00:00 2001 From: XoseM Date: Sat, 12 Sep 2020 06:36:39 +0000 Subject: [PATCH 021/155] Translated using Weblate (Galician) Currently translated at 100.0% (2373 of 2373 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 8a56fe84e0..82c3453dc5 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2476,5 +2476,19 @@ "Take a picture": "Tomar unha foto", "Pin to room": "Fixar a sala", "You can only pin 2 apps at a time": "Só podes fixar 2 apps ó tempo", - "Unpin": "Desafixar" + "Unpin": "Desafixar", + "Group call modified by %(senderName)s": "Chamada en grupo modificada por %(senderName)s", + "Group call started by %(senderName)s": "Chamada en grupo iniciada por %(senderName)s", + "Group call ended by %(senderName)s": "Chamada en grupo rematada por %(senderName)s", + "Cross-signing is ready for use.": "A Sinatura-Cruzada está lista para usar.", + "Cross-signing is not set up.": "Non está configurada a Sinatura-Cruzada.", + "Backup version:": "Versión da copia:", + "Algorithm:": "Algoritmo:", + "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Fai copia de apoio das chaves de cifrado para a continxencia de perder o acceso a todas as sesións. As chaves quedarán aseguradas cunha Chave de Recuperación única.", + "Backup key stored:": "Chave da copia gardada:", + "Backup key cached:": "Chave da copia na caché:", + "Secret storage:": "Almacenaxe segreda:", + "ready": "lista", + "not ready": "non lista", + "Secure Backup": "Copia Segura" } From 91dc346e5ba1bbd0d5a606a6c25cbd4fa6755c19 Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Sat, 12 Sep 2020 12:56:31 +0000 Subject: [PATCH 022/155] Translated using Weblate (German) Currently translated at 100.0% (2373 of 2373 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 4deb58887a..d5f71062cd 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -1836,7 +1836,7 @@ "Mark all as read": "Alle als gelesen markieren", "Local address": "Lokale Adresse", "Published Addresses": "Öffentliche Adresse", - "Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first.": "Öffentliche Adressen können von jedem verwendet werden um den Raum zu betreten. Um eine Adresse zu veröffentlichen musst du zunächst eine lokale Adresse anlegen.", + "Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first.": "Öffentliche Adressen können von jedem/r verwendet werden, um den Raum zu betreten. Um eine Adresse zu veröffentlichen musst du zunächst eine lokale Adresse anlegen.", "Other published addresses:": "Andere öffentliche Adressen:", "No other published addresses yet, add one below": "Keine anderen öffentlichen Adressen vorhanden, füge unten eine hinzu", "New published address (e.g. #alias:server)": "Neue öffentliche Adresse (z.B. #alias:server)", From b857b1a239a6cf434ac1aa35c73a7ad623b0ee58 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Fri, 11 Sep 2020 18:14:04 +0000 Subject: [PATCH 023/155] Translated using Weblate (Hungarian) Currently translated at 100.0% (2373 of 2373 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 1ebc9027d0..648885480b 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2476,5 +2476,19 @@ "Take a picture": "Fénykép készítése", "Pin to room": "A szobába kitűz", "You can only pin 2 apps at a time": "Csak 2 alkalmazást tűzhetsz ki egyszerre", - "Unpin": "Leszed" + "Unpin": "Leszed", + "Group call modified by %(senderName)s": "A konferenciahívást módosította: %(senderName)s", + "Group call started by %(senderName)s": "A konferenciahívást elindította: %(senderName)s", + "Group call ended by %(senderName)s": "A konferenciahívást befejezte: %(senderName)s", + "Cross-signing is ready for use.": "Eszközök közötti hitelesítés kész a használatra.", + "Cross-signing is not set up.": "Eszközök közötti hitelesítés nincs beállítva.", + "Backup version:": "Mentés verzió:", + "Algorithm:": "Algoritmus:", + "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Ments el a titkosítási kulcsaidat a fiókadatokkal arra az esetre ha levesztenéd a hozzáférést a munkameneteidhez. A kulcsok egy egyedi visszaállítási kulccsal lesznek védve.", + "Backup key stored:": "Mentési kulcs tár:", + "Backup key cached:": "Mentési kulcs gyorsítótár:", + "Secret storage:": "Biztonsági tároló:", + "ready": "kész", + "not ready": "nem kész", + "Secure Backup": "Biztonsági Mentés" } From 6950159f227a020f3774ed16cbae661e4d73cfe1 Mon Sep 17 00:00:00 2001 From: random Date: Sat, 12 Sep 2020 09:59:05 +0000 Subject: [PATCH 024/155] Translated using Weblate (Italian) Currently translated at 100.0% (2373 of 2373 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 43 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 48e2b2df20..eb33a8b3a6 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2452,5 +2452,46 @@ "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Le stanze private possono essere trovate e visitate solo con invito. Le stanze pubbliche invece sono aperte a tutti i membri di questa comunità.", "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Dovresti attivarlo se questa stanza verrà usata solo per collaborazioni tra squadre interne nel tuo homeserver. Non può essere cambiato in seguito.", "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Dovresti disattivarlo se questa stanza verrà usata per collaborazioni con squadre esterne che hanno il loro homeserver. Non può essere cambiato in seguito.", - "Block anyone not part of %(serverName)s from ever joining this room.": "Blocca l'accesso alla stanza per chiunque non faccia parte di %(serverName)s." + "Block anyone not part of %(serverName)s from ever joining this room.": "Blocca l'accesso alla stanza per chiunque non faccia parte di %(serverName)s.", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Antepone ( ͡° ͜ʖ ͡°) ad un messaggio di testo", + "Group call modified by %(senderName)s": "Chiamata di gruppo modificata da %(senderName)s", + "Group call started by %(senderName)s": "Chiamata di gruppo iniziata da %(senderName)s", + "Group call ended by %(senderName)s": "Chiamata di gruppo terminata da %(senderName)s", + "Unknown App": "App sconosciuta", + "Cross-signing is ready for use.": "La firma incrociata è pronta all'uso.", + "Cross-signing is not set up.": "La firma incrociata non è impostata.", + "Backup version:": "Versione backup:", + "Algorithm:": "Algoritmo:", + "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Fai il backup delle tue chiavi di crittografia con i dati del tuo account in caso perdessi l'accesso alle sessioni. Le tue chiavi saranno protette con una chiave di recupero univoca.", + "Backup key stored:": "Chiave di backup salvata:", + "Backup key cached:": "Chiave di backup in cache:", + "Secret storage:": "Archivio segreto:", + "ready": "pronto", + "not ready": "non pronto", + "Secure Backup": "Backup Sicuro", + "Privacy": "Privacy", + "%(count)s results|one": "%(count)s risultato", + "Room Info": "Info stanza", + "Apps": "App", + "Unpin app": "Sblocca app", + "Edit apps, bridges & bots": "Modifica app, bridge e bot", + "Add apps, bridges & bots": "Aggiungi app, bridge e bot", + "Not encrypted": "Non cifrato", + "About": "Al riguardo", + "%(count)s people|other": "%(count)s persone", + "%(count)s people|one": "%(count)s persona", + "Show files": "Mostra file", + "Room settings": "Impostazioni stanza", + "Take a picture": "Scatta una foto", + "Pin to room": "Fissa nella stanza", + "You can only pin 2 apps at a time": "Puoi fissare solo 2 app alla volta", + "There was an error updating your community. The server is unable to process your request.": "Si è verificato un errore nell'aggiornamento della comunità. Il server non riesce ad elaborare la richiesta.", + "Update community": "Aggiorna comunità", + "May include members not in %(communityName)s": "Può includere membri non in %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Inizia una conversazione con qualcuno usando il suo nome, nome utente (come ) o indirizzo email. Ciò non lo inviterà in %(communityName)s. Per invitare qualcuno in %(communityName)s, clicca qui.", + "Unpin": "Sblocca", + "Failed to find the general chat for this community": "Impossibile trovare la chat generale di questa comunità", + "Community settings": "Impostazioni comunità", + "User settings": "Impostazioni utente", + "Community and user menu": "Menu comunità e utente" } From e5e7c873bc041045e73d6b95b7806d655cc807cf Mon Sep 17 00:00:00 2001 From: Kahina Messaoudi Date: Sun, 13 Sep 2020 21:16:40 +0000 Subject: [PATCH 025/155] Translated using Weblate (Kabyle) Currently translated at 99.5% (2360 of 2373 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/kab/ --- src/i18n/strings/kab.json | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/kab.json b/src/i18n/strings/kab.json index f5efc1bb87..3b9b08ca6a 100644 --- a/src/i18n/strings/kab.json +++ b/src/i18n/strings/kab.json @@ -2410,5 +2410,33 @@ "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Yegguma ad yeqqen ɣer uqeddac agejdan - ttxil-k·m senqed tuqqna-inek·inem, tḍemneḍ belli aselken n SSL n uqeddac agejdan yettwattkal, rnu aseɣzan n yiminig-nni ur issewḥal ara isutar.", "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Amiḍan-ik·im amaynut (%(newAccountId)s) yettwaseklas, maca teqqneḍ yakan ɣer umiḍan wayeḍ (%(loggedInUserId)s).", "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Tasarut-ik·im n tririt d azeṭṭa aɣelsan - tzemreḍ ad tt-tesqedceḍ i wakken ad d-terreḍ anekcum ɣer yiznan-ik·im yettwawgelhen ma yella tettuḍ tafyirt-ik·im tuffirt n tririt.", - "Trophy": "Arraz" + "Trophy": "Arraz", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Irennu ( ͡° ͜ʖ ͡°) ɣer yizen s uḍris arewway", + "Group call modified by %(senderName)s": "Asiwel n ugraw yettwabeddel sɣur %(senderName)s", + "Group call started by %(senderName)s": "Asiwel n ugraw yebda-t-id %(senderName)s", + "Group call ended by %(senderName)s": "Asiwel n ugraw yekfa-t %(senderName)s", + "Unknown App": "Asnas arussin", + "Cross-signing is ready for use.": "Azmul anmidag yewjed i useqdec.", + "Cross-signing is not set up.": "Azmul anmidag ur yettwasebded ara.", + "Backup version:": "Lqem n uklas:", + "Algorithm:": "Alguritm:", + "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Kkes tisura-k•m n uwgelhen s yisefka n umiḍan-ik•im ma ur tezmireḍ ara ad tkecmeḍ ɣer tɣimiyin-ik•im. Tisura-k•m ad ttwaɣellsent s yiwet n tsarut n tiririt.", + "Backup key stored:": "Tasarut n uklas tettwaḥrez:", + "ready": "yewjed", + "not ready": "ur yewjid ara", + "Secure Backup": "Aklas aɣellsan", + "Privacy": "Tabaḍnit", + "Room Info": "Talɣut ɣef texxamt", + "Apps": "Isnasen", + "Not encrypted": "Ur yettwawgelhen ara", + "About": "Ɣef", + "%(count)s people|other": "%(count)s n yimdanen", + "%(count)s people|one": "%(count)s n umdan", + "Show files": "Sken ifuyla", + "Room settings": "Iɣewwaṛen n texxamt", + "Take a picture": "Ṭṭef tawlaft", + "There was an error updating your community. The server is unable to process your request.": "Tella-d tuccḍa deg uleqqem n temɣiwent-ik•im. Aqeddac ur izmir ara ad isesfer asuter.", + "Update community": "Leqqem tamɣiwent", + "May include members not in %(communityName)s": "Yezmer ad d-isseddu iɛeggalen ur nelli deg %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Bdu adiwenni akked ḥedd s useqdec n yisem-is, isem uffir (am ) neɣ tansa imayl. Aya ur ten-iecced ara ɣer %(communityName)s. Akked ad d-tnecdeḍ yiwen ɣer %(communityName)s sit ɣef da." } From 06a8ab20fb7d86fcbbd8ed3bf63e0938e3d356aa Mon Sep 17 00:00:00 2001 From: discapacidad5 Date: Sun, 13 Sep 2020 20:35:59 +0000 Subject: [PATCH 026/155] Translated using Weblate (Spanish) Currently translated at 100.0% (2373 of 2373 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/es/ --- src/i18n/strings/es.json | 314 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 313 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index 307c8c10c9..34c40800f7 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -2135,5 +2135,317 @@ "Explore public rooms": "Buscar salas publicas", "Can't see what you’re looking for?": "¿No encuentras nada de lo que buscas?", "Explore all public rooms": "Buscar todas las salas publicas", - "%(count)s results|other": "%(count)s resultados" + "%(count)s results|other": "%(count)s resultados", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Antepone ( ͡° ͜ʖ ͡°) a un mensaje de texto sin formato", + "Group call modified by %(senderName)s": "Llamada grupal modificada por %(senderName)s", + "Group call started by %(senderName)s": "Llamada grupal iniciada por %(senderName)s", + "Group call ended by %(senderName)s": "Llamada de grupo finalizada por %(senderName)s", + "Unknown App": "Aplicación desconocida", + "New spinner design": "Nuevo diseño de ruleta", + "Show message previews for reactions in DMs": "Mostrar vistas previas de mensajes para reacciones en DM", + "Show message previews for reactions in all rooms": "Mostrar vistas previas de mensajes para reacciones en todas las salas", + "Enable advanced debugging for the room list": "Habilite la depuración avanzada para la lista de salas", + "IRC display name width": "Ancho del nombre de visualización de IRC", + "Unknown caller": "Llamador desconocido", + "Cross-signing is ready for use.": "La firma cruzada está lista para su uso.", + "Cross-signing is not set up.": "La firma cruzada no está configurada.", + "Master private key:": "Clave privada maestra:", + "%(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 no puede almacenar en caché de forma segura mensajes cifrados localmente mientras se ejecuta en un navegador web. Utilizar %(brand)s Escritorio para que los mensajes cifrados aparezcan en los resultados de búsqueda.", + "Backup version:": "Versión de respaldo:", + "Algorithm:": "Algoritmo:", + "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Haga una copia de seguridad de sus claves de cifrado con los datos de su cuenta en caso de que pierda el acceso a sus sesiones. Sus claves estarán protegidas con una clave de recuperación única.", + "Backup key stored:": "Clave de respaldo almacenada:", + "Backup key cached:": "Clave de respaldo almacenada en caché:", + "Secret storage:": "Almacenamiento secreto:", + "ready": "Listo", + "not ready": "no está listo", + "Room ID or address of ban list": "ID de habitación o dirección de la lista de prohibición", + "Secure Backup": "Copia de seguridad segura", + "Privacy": "Intimidad", + "Emoji picker": "Selector de emoji", + "Explore community rooms": "Explore las salas comunitarias", + "Custom Tag": "Etiqueta personalizada", + "%(count)s results|one": "%(count)s resultado", + "Appearance": "Apariencia", + "Show rooms with unread messages first": "Mostrar primero las salas con mensajes no leídos", + "Show previews of messages": "Mostrar vistas previas de mensajes", + "Sort by": "Ordenar por", + "Activity": "Actividad", + "A-Z": "A-Z", + "List options": "Opciones de lista", + "Show %(count)s more|other": "Mostrar %(count)s más", + "Show %(count)s more|one": "Mostrar %(count)s más", + "Use default": "Uso por defecto", + "Mentions & Keywords": "Menciones y palabras clave", + "Notification options": "Opciones de notificación", + "Forget Room": "Olvidar habitación", + "Favourited": "Favorecido", + "Leave Room": "Dejar la habitación", + "Room options": "Opciones de habitación", + "Error creating address": "Error al crear la dirección", + "There was an error creating that address. It may not be allowed by the server or a temporary failure occurred.": "Hubo un error al crear esa dirección. Es posible que el servidor no lo permita o que haya ocurrido una falla temporal.", + "You don't have permission to delete the address.": "No tienes permiso para borrar la dirección.", + "There was an error removing that address. It may no longer exist or a temporary error occurred.": "Se produjo un error al eliminar esa dirección. Puede que ya no exista o se haya producido un error temporal.", + "Error removing address": "Error al eliminar la dirección", + "Room Info": "Información de la habitación", + "Apps": "Aplicaciones", + "Unpin app": "Desanclar aplicación", + "Edit apps, bridges & bots": "Edite aplicaciones, puentes y bots", + "Add apps, bridges & bots": "Agregar aplicaciones, puentes y bots", + "Not encrypted": "No encriptado", + "About": "Acerca de", + "%(count)s people|other": "%(count)s personas", + "%(count)s people|one": "%(count)s persona", + "Show files": "Mostrar archivos", + "Room settings": "Configuración de la habitación", + "You've successfully verified your device!": "¡Ha verificado correctamente su dispositivo!", + "Take a picture": "Toma una foto", + "Pin to room": "Anclar a la habitación", + "You can only pin 2 apps at a time": "Solo puedes anclar 2 aplicaciones a la vez", + "Message deleted on %(date)s": "Mensaje eliminado el %(date)s", + "Edited at %(date)s": "Editado el %(date)s", + "Click to view edits": "Haga clic para ver las ediciones", + "Categories": "Categorías", + "Information": "Información", + "QR Code": "Código QR", + "Room address": "Dirección de la habitación", + "Please provide a room address": "Proporcione una dirección de habitación", + "This address is available to use": "Esta dirección está disponible para usar", + "This address is already in use": "Esta dirección ya está en uso", + "Preparing to download logs": "Preparándose para descargar registros", + "Download logs": "Descargar registros", + "Add another email": "Agregar otro correo electrónico", + "People you know on %(brand)s": "Gente que conoces %(brand)s", + "Show": "Mostrar", + "Send %(count)s invites|other": "Enviar %(count)s invitaciones", + "Send %(count)s invites|one": "Enviar invitación a %(count)s", + "Invite people to join %(communityName)s": "Invita a personas a unirse %(communityName)s", + "There was an error creating your community. The name may be taken or the server is unable to process your request.": "Hubo un error al crear tu comunidad. El nombre puede ser tomado o el servidor no puede procesar su solicitud.", + "Community ID: +:%(domain)s": "ID de comunidad: +:%(domain)s", + "Use this when referencing your community to others. The community ID cannot be changed.": "Use esto cuando haga referencia a su comunidad con otras. La identificación de la comunidad no se puede cambiar.", + "You can change this later if needed.": "Puede cambiar esto más tarde si es necesario.", + "What's the name of your community or team?": "¿Cuál es el nombre de tu comunidad o equipo?", + "Enter name": "Ingrese su nombre", + "Add image (optional)": "Agregar imagen (opcional)", + "An image will help people identify your community.": "Una imagen ayudará a las personas a identificar su comunidad.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Las salas privadas se pueden encontrar y unirse solo con invitación. Cualquier persona puede encontrar y unirse a las salas públicas.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Las salas privadas se pueden encontrar y unirse solo con invitación. Cualquier persona de esta comunidad puede encontrar salas públicas y unirse a ellas.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Puede habilitar esto si la sala solo se usará para colaborar con equipos internos en su servidor doméstico. Esto no se puede cambiar después.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Puede desactivar esto si la sala se utilizará para colaborar con equipos externos que tengan su propio servidor doméstico. Esto no se puede cambiar después.", + "Create a room in %(communityName)s": "Crea una habitación en %(communityName)s", + "Block anyone not part of %(serverName)s from ever joining this room.": "Bloquea a cualquier persona que no sea parte de %(serverName)s para que no se una a esta sala.", + "You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "Anteriormente usaste una versión más nueva de %(brand)s con esta sesión. Para volver a utilizar esta versión con cifrado de extremo a extremo, deberá cerrar sesión y volver a iniciar sesión.", + "There was an error updating your community. The server is unable to process your request.": "Hubo un error al actualizar tu comunidad. El servidor no puede procesar su solicitud.", + "Update community": "Actualizar comunidad", + "To continue, use Single Sign On to prove your identity.": "Para continuar, utilice el inicio de sesión único para demostrar su identidad.", + "Confirm to continue": "Confirmar para continuar", + "Click the button below to confirm your identity.": "Haga clic en el botón de abajo para confirmar su identidad.", + "May include members not in %(communityName)s": "Puede incluir miembros que no están en %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Inicie una conversación con alguien usando su nombre, nombre de usuario (como) o dirección de correo electrónico. Esto no los invitará a %(communityName)s Para invitar a alguien a %(communityName)s, haga clic aquí.", + "You're all caught up.": "Estás al día.", + "Server isn't responding": "El servidor no responde", + "Your server isn't responding to some of your requests. Below are some of the most likely reasons.": "Su servidor no responde a algunas de sus solicitudes. A continuación se presentan algunas de las razones más probables.", + "The server (%(serverName)s) took too long to respond.": "El servidor (%(serverName)s) tardó demasiado en responder.", + "Your firewall or anti-virus is blocking the request.": "Su firewall o antivirus está bloqueando la solicitud.", + "A browser extension is preventing the request.": "Una extensión del navegador impide la solicitud.", + "The server is offline.": "El servidor está desconectado.", + "The server has denied your request.": "El servidor ha denegado su solicitud.", + "Your area is experiencing difficulties connecting to the internet.": "Su área está experimentando dificultades para conectarse a Internet.", + "A connection error occurred while trying to contact the server.": "Se produjo un error de conexión al intentar contactar con el servidor.", + "The server is not configured to indicate what the problem is (CORS).": "El servidor no está configurado para indicar cuál es el problema (CORS).", + "Recent changes that have not yet been received": "Cambios recientes que aún no se han recibido", + "Copy": "Copiar", + "Wrong file type": "Tipo de archivo incorrecto", + "Looks good!": "¡Se ve bien!", + "Wrong Recovery Key": "Clave de recuperación incorrecta", + "Invalid Recovery Key": "Clave de recuperación no válida", + "Security Phrase": "Frase de seguridad", + "Enter your Security Phrase or to continue.": "Ingrese su Frase de seguridad o para continuar.", + "Security Key": "Clave de seguridad", + "Use your Security Key to continue.": "Usa tu llave de seguridad para continuar.", + "Unpin": "Desprender", + "This room is public": "Esta sala es pública", + "Away": "Lejos", + "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.": "Puede utilizar las opciones del servidor personalizado para iniciar sesión en otros servidores Matrix especificando una URL de servidor principal diferente. Esto le permite utilizar %(brand)s con una cuenta Matrix existente en un servidor doméstico diferente.", + "Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of element.io.": "Ingrese la ubicación de su servidor doméstico de Element Matrix Services. Puede usar su propio nombre de dominio o ser un subdominio de element.io.", + "No files visible in this room": "No hay archivos visibles en esta sala", + "Attach files from chat or just drag and drop them anywhere in a room.": "Adjunte archivos desde el chat o simplemente arrástrelos y suéltelos en cualquier lugar de una sala.", + "You’re all caught up": "Estás al día", + "You have no visible notifications in this room.": "No tienes notificaciones visibles en esta sala.", + "Delete the room address %(alias)s and remove %(name)s from the directory?": "¿Eliminar la dirección de la sala %(alias)s y eliminar %(name)s del directorio?", + "delete the address.": "eliminar la dirección.", + "Explore rooms in %(communityName)s": "Explora habitaciones en %(communityName)s", + "Search rooms": "Buscar salas", + "Create community": "Crear comunidad", + "Failed to find the general chat for this community": "No se pudo encontrar el chat general de esta comunidad", + "Security & privacy": "Seguridad y Privacidad", + "All settings": "Todos los ajustes", + "Feedback": "Realimentación", + "Community settings": "Configuración de la comunidad", + "User settings": "Ajustes de usuario", + "Switch to light mode": "Cambiar al modo de luz", + "Switch to dark mode": "Cambiar al modo oscuro", + "Switch theme": "Cambiar tema", + "User menu": "Menú del Usuario", + "Community and user menu": "Menú de comunidad y usuario", + "Failed to perform homeserver discovery": "No se pudo realizar el descubrimiento del servidor doméstico", + "Syncing...": "Sincronizando ...", + "Signing In...": "Iniciando sesión...", + "If you've joined lots of rooms, this might take a while": "Si se ha unido a muchas salas, esto puede llevar un tiempo", + "Create account": "Crear una cuenta", + "Unable to query for supported registration methods.": "No se pueden consultar los métodos de registro admitidos.", + "Registration has been disabled on this homeserver.": "El registro ha sido deshabilitado en este servidor doméstico.", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Su nueva cuenta (%(newAccountId)s) está registrada, pero ya inició sesión en una cuenta diferente (%(loggedInUserId)s).", + "Continue with previous account": "Continuar con la cuenta anterior", + "Log in to your new account.": "Inicie sesión en su nueva cuenta.", + "You can now close this window or log in to your new account.": "Ahora puede cerrar esta ventana o iniciar sesión en su nueva cuenta.", + "Registration Successful": "Registro exitoso", + "Create your account": "Crea tu cuenta", + "Use Recovery Key or Passphrase": "Usar clave de recuperación o frase de contraseña", + "Use Recovery Key": "Usar clave de recuperación", + "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Confirme su identidad verificando este inicio de sesión de una de sus otras sesiones, otorgándole acceso a los mensajes cifrados.", + "This requires the latest %(brand)s on your other devices:": "Esto requiere la última %(brand)s en sus otros dispositivos:", + "%(brand)s Web": "%(brand)s Web", + "%(brand)s Desktop": "%(brand)s Escritorio", + "%(brand)s iOS": "%(brand)s iOS", + "%(brand)s Android": "%(brand)s Android", + "or another cross-signing capable Matrix client": "u otro cliente Matrix con capacidad de firma cruzada", + "Without completing security on this session, it won’t have access to encrypted messages.": "Sin completar la seguridad en esta sesión, no tendrá acceso a los mensajes cifrados.", + "Failed to re-authenticate due to a homeserver problem": "No se pudo volver a autenticar debido a un problema con el servidor doméstico", + "Failed to re-authenticate": "No se pudo volver a autenticar", + "Regain access to your account and recover encryption keys stored in this session. Without them, you won’t be able to read all of your secure messages in any session.": "Recupere el acceso a su cuenta y recupere las claves de cifrado almacenadas en esta sesión. Sin ellos, no podrá leer todos sus mensajes seguros en ninguna sesión.", + "Enter your password to sign in and regain access to your account.": "Ingrese su contraseña para iniciar sesión y recuperar el acceso a su cuenta.", + "Forgotten your password?": "¿Olvidaste tu contraseña?", + "Sign in and regain access to your account.": "Inicie sesión y recupere el acceso a su cuenta.", + "You cannot sign in to your account. Please contact your homeserver admin for more information.": "No puede iniciar sesión en su cuenta. Comuníquese con el administrador de su servidor doméstico para obtener más información.", + "You're signed out": "Estás desconectado", + "Clear personal data": "Borrar datos personales", + "Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.": "Advertencia: sus datos personales (incluidas las claves de cifrado) todavía se almacenan en esta sesión. Bórrelo si terminó de usar esta sesión o si desea iniciar sesión en otra cuenta.", + "Command Autocomplete": "Comando Autocompletar", + "Community Autocomplete": "Autocompletar de la comunidad", + "DuckDuckGo Results": "Resultados de DuckDuckGo", + "Emoji Autocomplete": "Autocompletar Emoji", + "Notification Autocomplete": "Autocompletar notificación", + "Room Autocomplete": "Autocompletar habitación", + "User Autocomplete": "Autocompletar de usuario", + "Confirm encryption setup": "Confirmar la configuración de cifrado", + "Click the button below to confirm setting up encryption.": "Haga clic en el botón de abajo para confirmar la configuración del cifrado.", + "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Protéjase contra la pérdida de acceso a los mensajes y datos cifrados haciendo una copia de seguridad de las claves de cifrado en su servidor.", + "Generate a Security Key": "Generar una llave de seguridad", + "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "Generaremos una llave de seguridad para que la guardes en un lugar seguro, como un administrador de contraseñas o una caja fuerte.", + "Enter a Security Phrase": "Ingrese una frase de seguridad", + "Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "Use una frase secreta que solo usted conozca y, opcionalmente, guarde una clave de seguridad para usarla como respaldo.", + "Enter your account password to confirm the upgrade:": "Ingrese la contraseña de su cuenta para confirmar la actualización:", + "Restore your key backup to upgrade your encryption": "Restaure la copia de seguridad de su clave para actualizar su cifrado", + "Restore": "Restaurar", + "You'll need to authenticate with the server to confirm the upgrade.": "Deberá autenticarse con el servidor para confirmar la actualización.", + "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Actualice esta sesión para permitirle verificar otras sesiones, otorgándoles acceso a mensajes cifrados y marcándolos como confiables para otros usuarios.", + "Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.": "Ingrese una frase de seguridad que solo usted conozca, ya que se usa para proteger sus datos. Para estar seguro, no debe volver a utilizar la contraseña de su cuenta.", + "Enter a recovery passphrase": "Ingrese una frase de contraseña de recuperación", + "Great! This recovery passphrase looks strong enough.": "¡Excelente! Esta frase de contraseña de recuperación parece lo suficientemente sólida.", + "That matches!": "¡Eso combina!", + "Use a different passphrase?": "¿Utiliza una frase de contraseña diferente?", + "That doesn't match.": "Eso no coincide.", + "Go back to set it again.": "Regrese para configurarlo nuevamente.", + "Enter your recovery passphrase a second time to confirm it.": "Ingrese su contraseña de recuperación por segunda vez para confirmarla.", + "Confirm your recovery passphrase": "Confirma tu contraseña de recuperación", + "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.": "Guarde su llave de seguridad en un lugar seguro, como un administrador de contraseñas o una caja fuerte, ya que se usa para proteger sus datos cifrados.", + "Download": "Descargar", + "Unable to query secret storage status": "No se puede consultar el estado del almacenamiento secreto", + "Retry": "Reintentar", + "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "Si cancela ahora, puede perder mensajes y datos cifrados si pierde el acceso a sus inicios de sesión.", + "You can also set up Secure Backup & manage your keys in Settings.": "También puede configurar la Copia de seguridad segura y administrar sus claves en Configuración.", + "Set up Secure Backup": "Configurar copia de seguridad segura", + "Upgrade your encryption": "Actualice su cifrado", + "Set a Security Phrase": "Establecer una frase de seguridad", + "Confirm Security Phrase": "Confirmar la frase de seguridad", + "Save your Security Key": "Guarde su llave de seguridad", + "Unable to set up secret storage": "No se puede configurar el almacenamiento secreto", + "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "Almacenaremos una copia encriptada de sus claves en nuestro servidor. Asegure su copia de seguridad con una contraseña de recuperación.", + "For maximum security, this should be different from your account password.": "Para mayor seguridad, esta debe ser diferente a la contraseña de su cuenta.", + "Set up with a recovery key": "Configurar con una clave de recuperación", + "Please enter your recovery passphrase a second time to confirm.": "Ingrese su contraseña de recuperación por segunda vez para confirmar.", + "Repeat your recovery passphrase...": "Repite tu contraseña de recuperación ...", + "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Su clave de recuperación es una red de seguridad; puede usarla para restaurar el acceso a sus mensajes cifrados si olvida su contraseña de recuperación.", + "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Guarde una copia en un lugar seguro, como un administrador de contraseñas o incluso una caja fuerte.", + "Your recovery key": "Tu clave de recuperación", + "Your recovery key has been copied to your clipboard, paste it to:": "Tu clave de recuperación se ha copiado en tu portapapeles, pégala en:", + "Your recovery key is in your Downloads folder.": "Tu clave de recuperación está en tu carpeta Descargas.", + "Print it and store it somewhere safe": "Imprímelo y guárdalo en un lugar seguro", + "Save it on a USB key or backup drive": "Guárdelo en una llave USB o unidad de respaldo", + "Copy it to your personal cloud storage": "Cópielo a su almacenamiento personal en la nube", + "Your keys are being backed up (the first backup could take a few minutes).": "Se está realizando una copia de seguridad de sus claves (la primera copia de seguridad puede tardar unos minutos).", + "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Sin configurar Secure Message Recovery, no podrá restaurar su historial de mensajes encriptados si cierra sesión o usa otra sesión.", + "Set up Secure Message Recovery": "Configurar la recuperación segura de mensajes", + "Secure your backup with a recovery passphrase": "Asegure su copia de seguridad con una frase de contraseña de recuperación", + "Make a copy of your recovery key": "Haz una copia de tu clave de recuperación", + "Starting backup...": "Iniciando copia de seguridad ...", + "Success!": "¡Éxito!", + "Create key backup": "Crear copia de seguridad de claves", + "Unable to create key backup": "No se puede crear una copia de seguridad de la clave", + "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Sin configurar Secure Message Recovery, perderá su historial de mensajes seguros cuando cierre la sesión.", + "If you don't want to set this up now, you can later in Settings.": "Si no desea configurar esto ahora, puede hacerlo más tarde en Configuración.", + "Don't ask again": "No vuelvas a preguntar", + "New Recovery Method": "Nuevo método de recuperación", + "A new recovery passphrase and key for Secure Messages have been detected.": "Se han detectado una nueva contraseña y clave de recuperación para mensajes seguros.", + "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Si no configuró el nuevo método de recuperación, es posible que un atacante esté intentando acceder a su cuenta. Cambie la contraseña de su cuenta y configure un nuevo método de recuperación inmediatamente en Configuración.", + "Go to Settings": "Ir a la configuración", + "Set up Secure Messages": "Configurar mensajes seguros", + "Recovery Method Removed": "Método de recuperación eliminado", + "This session has detected that your recovery passphrase and key for Secure Messages have been removed.": "Esta sesión ha detectado que se han eliminado su contraseña de recuperación y la clave para Mensajes seguros.", + "If you did this accidentally, you can setup Secure Messages on this session which will re-encrypt this session's message history with a new recovery method.": "Si hizo esto accidentalmente, puede configurar Mensajes seguros en esta sesión que volverá a cifrar el historial de mensajes de esta sesión con un nuevo método de recuperación.", + "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Si no eliminó el método de recuperación, es posible que un atacante esté intentando acceder a su cuenta. Cambie la contraseña de su cuenta y configure un nuevo método de recuperación inmediatamente en Configuración.", + "If disabled, messages from encrypted rooms won't appear in search results.": "Si está desactivado, los mensajes de las salas cifradas no aparecerán en los resultados de búsqueda.", + "Disable": "Inhabilitar", + "Not currently indexing messages for any room.": "Actualmente no indexa mensajes para ninguna sala.", + "Currently indexing: %(currentRoom)s": "Actualmente indexando: %(currentRoom)s", + "%(brand)s is securely caching encrypted messages locally for them to appear in search results:": "%(brand)s está almacenando en caché de forma segura los mensajes cifrados localmente para que aparezcan en los resultados de búsqueda:", + "Space used:": "Espacio usado:", + "Indexed messages:": "Mensajes indexados:", + "Indexed rooms:": "Salas indexadas:", + "%(doneRooms)s out of %(totalRooms)s": "%(doneRooms)s fuera de %(totalRooms)s", + "Message downloading sleep time(ms)": "Tiempo de suspensión de descarga de mensajes(ms)", + "Navigation": "Navegación", + "Calls": "Llamadas", + "Room List": "Lista de habitaciones", + "Autocomplete": "Autocompletar", + "Alt": "Alt", + "Alt Gr": "Alt Gr", + "Shift": "Shift", + "Super": "Super", + "Ctrl": "Ctrl", + "Toggle Bold": "Alternar negrita", + "Toggle Italics": "Alternar cursiva", + "Toggle Quote": "Alternar Entrecomillar", + "New line": "Nueva línea", + "Navigate recent messages to edit": "Navegar por mensajes recientes para editar", + "Jump to start/end of the composer": "Saltar al inicio / final del compositor", + "Navigate composer history": "Navegar por el historial del compositor", + "Cancel replying to a message": "Cancelar la respuesta a un mensaje", + "Toggle microphone mute": "Alternar silencio del micrófono", + "Toggle video on/off": "Activar/desactivar video", + "Scroll up/down in the timeline": "Desplazarse hacia arriba o hacia abajo en la línea de tiempo", + "Dismiss read marker and jump to bottom": "Descartar el marcador de lectura y saltar al final", + "Jump to oldest unread message": "Ir al mensaje no leído más antiguo", + "Upload a file": "Cargar un archivo", + "Jump to room search": "Ir a la búsqueda de Salas", + "Navigate up/down in the room list": "Navegar hacia arriba/abajo en la lista de salas", + "Select room from the room list": "Seleccionar sala de la lista de salas", + "Collapse room list section": "Contraer la sección de lista de salas", + "Expand room list section": "Expandir la sección de la lista de salas", + "Clear room list filter field": "Borrar campo de filtro de lista de salas", + "Previous/next unread room or DM": "Sala o DM anterior/siguiente sin leer", + "Previous/next room or DM": "Sala anterior/siguiente o DM", + "Toggle the top left menu": "Alternar el menú superior izquierdo", + "Close dialog or context menu": "Cerrar cuadro de diálogo o menú contextual", + "Activate selected button": "Activar botón seleccionado", + "Toggle right panel": "Alternar panel derecho", + "Toggle this dialog": "Alternar este diálogo", + "Move autocomplete selection up/down": "Mover la selección de autocompletar hacia arriba/abajo", + "Cancel autocomplete": "Cancelar autocompletar", + "Page Up": "Página arriba", + "Page Down": "Página abajo", + "Esc": "Esc", + "Enter": "Enter", + "Space": "Espacio" } From 99811bfc74bf7fedaa6f8fb784363a297585d0cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Mon, 14 Sep 2020 09:03:36 +0000 Subject: [PATCH 027/155] Translated using Weblate (Estonian) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 5fd83557bd..6855c87efb 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2490,5 +2490,8 @@ "Secret storage:": "Turvahoidla:", "ready": "valmis", "not ready": "ei ole valmis", - "Secure Backup": "Turvaline varundus" + "Secure Backup": "Turvaline varundus", + "End Call": "Lõpeta kõne", + "Remove the group call from the room?": "Kas eemaldame jututoast rühmakõne?", + "You don't have permission to remove the call from the room": "Sinul pole õigusi rühmakõne eemaldamiseks sellest jututoast" } From 2317300a59cf235442cb3f032e1df06cfc2db11b Mon Sep 17 00:00:00 2001 From: Michael Albert Date: Mon, 14 Sep 2020 10:49:24 +0000 Subject: [PATCH 028/155] Translated using Weblate (German) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index d5f71062cd..2c2f832fc8 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2262,7 +2262,7 @@ "Message layout": "Nachrichtenlayout", "Compact": "Kompakt", "Modern": "Modern", - "Use a system font": "Verwende die System-Schriftart", + "Use a system font": "Verwende eine System-Schriftart", "System font name": "System-Schriftart", "Customise your appearance": "Verändere das Erscheinungsbild", "Appearance Settings only affect this %(brand)s session.": "Einstellungen zum Erscheinungsbild wirken sich nur auf diese %(brand)s Sitzung aus.", @@ -2488,5 +2488,8 @@ "Secret storage:": "Sicherer Speicher:", "ready": "bereit", "not ready": "nicht bereit", - "Secure Backup": "Sicheres Backup" + "Secure Backup": "Sicheres Backup", + "End Call": "Anruf beenden", + "Remove the group call from the room?": "Konferenzgespräch aus diesem Raum entfernen?", + "You don't have permission to remove the call from the room": "Du hast keine Berechtigung um den Konferenzanruf aus dem Raum zu entfernen" } From 475ea4aa2a6c4a8735f024128c1c8b8e03e318e8 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Mon, 14 Sep 2020 09:02:37 +0000 Subject: [PATCH 029/155] Translated using Weblate (Hungarian) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 648885480b..8c797a16b1 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2490,5 +2490,8 @@ "Secret storage:": "Biztonsági tároló:", "ready": "kész", "not ready": "nem kész", - "Secure Backup": "Biztonsági Mentés" + "Secure Backup": "Biztonsági Mentés", + "End Call": "Hívás befejezése", + "Remove the group call from the room?": "Törlöd a konferenciahívást a szobából?", + "You don't have permission to remove the call from the room": "A konferencia hívás törléséhez nincs jogosultságod" } From f6a405ffd7bc2101bf380f65258707237e447eda Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Mon, 14 Sep 2020 09:18:15 +0000 Subject: [PATCH 030/155] Translated using Weblate (Russian) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 906f9a7901..0827b920b2 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2486,5 +2486,8 @@ "Secure Backup": "Безопасное резервное копирование", "Group call modified by %(senderName)s": "%(senderName)s изменил(а) групповой вызов", "Group call started by %(senderName)s": "Групповой вызов начат %(senderName)s", - "Group call ended by %(senderName)s": "%(senderName)s завершил(а) групповой вызов" + "Group call ended by %(senderName)s": "%(senderName)s завершил(а) групповой вызов", + "End Call": "Завершить звонок", + "Remove the group call from the room?": "Удалить групповой вызов из комнаты?", + "You don't have permission to remove the call from the room": "У вас нет разрешения на удаление звонка из комнаты" } From 229967aa33c0ffeef1cc59be3fc614d9efd95d48 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 14 Sep 2020 15:16:29 +0100 Subject: [PATCH 031/155] Retry joinRoom up to 5 times in the case of a 504 GATEWAY TIMEOUT --- src/stores/RoomViewStore.tsx | 25 +++++++++++++++++++------ src/utils/promise.ts | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index a0f0fb8f68..be1141fa1e 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -18,6 +18,7 @@ limitations under the License. import React from "react"; import {Store} from 'flux/utils'; +import {MatrixError} from "matrix-js-sdk/src/http-api"; import dis from '../dispatcher/dispatcher'; import {MatrixClientPeg} from '../MatrixClientPeg'; @@ -26,6 +27,9 @@ import Modal from '../Modal'; import { _t } from '../languageHandler'; import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache'; import {ActionPayload} from "../dispatcher/payloads"; +import {retry} from "../utils/promise"; + +const NUM_JOIN_RETRY = 5; const INITIAL_STATE = { // Whether we're joining the currently viewed room (see isJoining()) @@ -259,24 +263,32 @@ class RoomViewStore extends Store { }); } - private joinRoom(payload: ActionPayload) { + private async joinRoom(payload: ActionPayload) { this.setState({ joining: true, }); - MatrixClientPeg.get().joinRoom( - this.state.roomAlias || this.state.roomId, payload.opts, - ).then(() => { + + const cli = MatrixClientPeg.get(); + const address = this.state.roomAlias || this.state.roomId; + try { + await retry(() => cli.joinRoom(address, payload.opts), NUM_JOIN_RETRY, (err) => { + // if we received a Gateway timeout then retry + return err.httpStatus === 504; + }); + // We do *not* clear the 'joining' flag because the Room object and/or our 'joined' member event may not // have come down the sync stream yet, and that's the point at which we'd consider the user joined to the // room. dis.dispatch({ action: 'join_room_ready' }); - }, (err) => { + } catch (err) { dis.dispatch({ action: 'join_room_error', err: err, }); + let msg = err.message ? err.message : JSON.stringify(err); console.log("Failed to join room:", msg); + if (err.name === "ConnectionError") { msg = _t("There was an error joining the room"); } else if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') { @@ -296,12 +308,13 @@ class RoomViewStore extends Store { } } } + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, { title: _t("Failed to join room"), description: msg, }); - }); + } } private getInvitingUserId(roomId: string): string { diff --git a/src/utils/promise.ts b/src/utils/promise.ts index d3ae2c3d1b..f828ddfdaf 100644 --- a/src/utils/promise.ts +++ b/src/utils/promise.ts @@ -68,3 +68,21 @@ export function allSettled(promises: Promise[]): Promise(fn: () => Promise, num: number, predicate?: (e: E) => boolean) { + let lastErr: E; + for (let i = 0; i < num; i++) { + try { + const v = await fn(); + // If `await fn()` throws then we won't reach here + return v; + } catch (err) { + if (predicate && !predicate(err)) { + throw err; + } + lastErr = err; + } + } + throw lastErr; +} From 9a3dfe5235e8585265cd06008816c9498134fb3a Mon Sep 17 00:00:00 2001 From: random Date: Mon, 14 Sep 2020 13:58:22 +0000 Subject: [PATCH 032/155] Translated using Weblate (Italian) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index eb33a8b3a6..9730c055ec 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2493,5 +2493,8 @@ "Failed to find the general chat for this community": "Impossibile trovare la chat generale di questa comunità", "Community settings": "Impostazioni comunità", "User settings": "Impostazioni utente", - "Community and user menu": "Menu comunità e utente" + "Community and user menu": "Menu comunità e utente", + "End Call": "Chiudi chiamata", + "Remove the group call from the room?": "Rimuovere la chiamata di gruppo dalla stanza?", + "You don't have permission to remove the call from the room": "Non hai l'autorizzazione per rimuovere la chiamata dalla stanza" } From 5202037eeb3c9f73ab0b8aa65ee3945254cda20a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 14 Sep 2020 15:16:29 +0100 Subject: [PATCH 033/155] Retry joinRoom up to 5 times in the case of a 504 GATEWAY TIMEOUT --- src/stores/RoomViewStore.tsx | 25 +++++++++++++++++++------ src/utils/promise.ts | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index a0f0fb8f68..be1141fa1e 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -18,6 +18,7 @@ limitations under the License. import React from "react"; import {Store} from 'flux/utils'; +import {MatrixError} from "matrix-js-sdk/src/http-api"; import dis from '../dispatcher/dispatcher'; import {MatrixClientPeg} from '../MatrixClientPeg'; @@ -26,6 +27,9 @@ import Modal from '../Modal'; import { _t } from '../languageHandler'; import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache'; import {ActionPayload} from "../dispatcher/payloads"; +import {retry} from "../utils/promise"; + +const NUM_JOIN_RETRY = 5; const INITIAL_STATE = { // Whether we're joining the currently viewed room (see isJoining()) @@ -259,24 +263,32 @@ class RoomViewStore extends Store { }); } - private joinRoom(payload: ActionPayload) { + private async joinRoom(payload: ActionPayload) { this.setState({ joining: true, }); - MatrixClientPeg.get().joinRoom( - this.state.roomAlias || this.state.roomId, payload.opts, - ).then(() => { + + const cli = MatrixClientPeg.get(); + const address = this.state.roomAlias || this.state.roomId; + try { + await retry(() => cli.joinRoom(address, payload.opts), NUM_JOIN_RETRY, (err) => { + // if we received a Gateway timeout then retry + return err.httpStatus === 504; + }); + // We do *not* clear the 'joining' flag because the Room object and/or our 'joined' member event may not // have come down the sync stream yet, and that's the point at which we'd consider the user joined to the // room. dis.dispatch({ action: 'join_room_ready' }); - }, (err) => { + } catch (err) { dis.dispatch({ action: 'join_room_error', err: err, }); + let msg = err.message ? err.message : JSON.stringify(err); console.log("Failed to join room:", msg); + if (err.name === "ConnectionError") { msg = _t("There was an error joining the room"); } else if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') { @@ -296,12 +308,13 @@ class RoomViewStore extends Store { } } } + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, { title: _t("Failed to join room"), description: msg, }); - }); + } } private getInvitingUserId(roomId: string): string { diff --git a/src/utils/promise.ts b/src/utils/promise.ts index d3ae2c3d1b..f828ddfdaf 100644 --- a/src/utils/promise.ts +++ b/src/utils/promise.ts @@ -68,3 +68,21 @@ export function allSettled(promises: Promise[]): Promise(fn: () => Promise, num: number, predicate?: (e: E) => boolean) { + let lastErr: E; + for (let i = 0; i < num; i++) { + try { + const v = await fn(); + // If `await fn()` throws then we won't reach here + return v; + } catch (err) { + if (predicate && !predicate(err)) { + throw err; + } + lastErr = err; + } + } + throw lastErr; +} From 608249745ae3215c51acb6f28a1af61640f0f9e7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 15 Sep 2020 13:19:47 +0100 Subject: [PATCH 034/155] Attempt to fix tests some more --- src/languageHandler.tsx | 15 ++++++++++++--- test/i18n-test/languageHandler-test.js | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index d9feec95b1..e699f8e301 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -27,6 +27,7 @@ import PlatformPeg from "./PlatformPeg"; // @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config import webpackLangJsonUrl from "$webapp/i18n/languages.json"; import { SettingLevel } from "./settings/SettingLevel"; +import {retry} from "./utils/promise"; const i18nFolder = 'i18n/'; @@ -327,7 +328,7 @@ export function setLanguage(preferredLangs: string | string[]) { console.error("Unable to find an appropriate language"); } - return getLanguage(i18nFolder + availLangs[langToUse].fileName); + return getLanguageRetry(i18nFolder + availLangs[langToUse].fileName); }).then((langData) => { counterpart.registerTranslations(langToUse, langData); counterpart.setLocale(langToUse); @@ -336,7 +337,7 @@ export function setLanguage(preferredLangs: string | string[]) { // Set 'en' as fallback language: if (langToUse !== "en") { - return getLanguage(i18nFolder + availLangs['en'].fileName); + return getLanguageRetry(i18nFolder + availLangs['en'].fileName); } }).then((langData) => { if (langData) counterpart.registerTranslations('en', langData); @@ -482,7 +483,15 @@ function weblateToCounterpart(inTrs: object): object { return outTrs; } -function getLanguage(langPath: string): object { +async function getLanguageRetry(langPath: string, num = 3): Promise { + return retry(() => getLanguage(langPath), num, e => { + console.log("Failed to load i18n", langPath); + console.error(e); + return true; // always retry + }); +} + +function getLanguage(langPath: string): Promise { return new Promise((resolve, reject) => { request( { method: "GET", url: langPath }, diff --git a/test/i18n-test/languageHandler-test.js b/test/i18n-test/languageHandler-test.js index 7968186e9e..b9bc955269 100644 --- a/test/i18n-test/languageHandler-test.js +++ b/test/i18n-test/languageHandler-test.js @@ -12,11 +12,11 @@ describe('languageHandler', function() { languageHandler.setMissingEntryGenerator(key => key.split("|", 2)[1]); }); - it('translates a string to german', function() { + it('translates a string to german', function(done) { languageHandler.setLanguage('de').then(function() { const translated = languageHandler._t('Rooms'); expect(translated).toBe('Räume'); - }); + }).then(done); }); it('handles plurals', function() { From 8e871c455ce457596bda265a3e6414dd3edd79b0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 15 Sep 2020 13:24:20 +0100 Subject: [PATCH 035/155] Fix german i18n test which was previously broken due to async --- __mocks__/browser-request.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/__mocks__/browser-request.js b/__mocks__/browser-request.js index 7d231fb9db..391be7c54f 100644 --- a/__mocks__/browser-request.js +++ b/__mocks__/browser-request.js @@ -1,4 +1,5 @@ const en = require("../src/i18n/strings/en_EN"); +const de = require("../src/i18n/strings/de_DE"); module.exports = jest.fn((opts, cb) => { const url = opts.url || opts.uri; @@ -8,9 +9,15 @@ module.exports = jest.fn((opts, cb) => { "fileName": "en_EN.json", "label": "English", }, + "de": { + "fileName": "de_DE.json", + "label": "German", + }, })); } else if (url && url.endsWith("en_EN.json")) { cb(undefined, {status: 200}, JSON.stringify(en)); + } else if (url && url.endsWith("de_DE.json")) { + cb(undefined, {status: 200}, JSON.stringify(de)); } else { cb(true, {status: 404}, ""); } From d643f908b1a9ab1e8cb45d805845c3f14861dca1 Mon Sep 17 00:00:00 2001 From: linsui Date: Tue, 15 Sep 2020 03:57:38 +0000 Subject: [PATCH 036/155] Translated using Weblate (Chinese (Simplified)) Currently translated at 97.5% (2316 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/zh_Hans/ --- src/i18n/strings/zh_Hans.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json index f1a3af31d7..eaf3b0d329 100644 --- a/src/i18n/strings/zh_Hans.json +++ b/src/i18n/strings/zh_Hans.json @@ -2371,5 +2371,18 @@ "Toggle this dialog": "切换此对话框", "End": "End", "The server is not configured to indicate what the problem is (CORS).": "服务器没有配置为提示错误是什么(CORS)。", - "Activate selected button": "激活选择的按钮" + "Activate selected button": "激活选择的按钮", + "End Call": "结束通话", + "Remove the group call from the room?": "是否从聊天室中移除聊天室?", + "You don't have permission to remove the call from the room": "您没有权限从聊天室中移除此通话", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "在纯文本消息之前附加 ( ͡° ͜ʖ ͡°)", + "Group call modified by %(senderName)s": "群通话被 %(senderName)s 修改", + "Group call started by %(senderName)s": "%(senderName)s 发起的群通话", + "Group call ended by %(senderName)s": "%(senderName)s 结束了群通话", + "Unknown App": "未知应用", + "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "社区 v2 原型。需要兼容的主服务器。高度实验性 - 谨慎使用。", + "Cross-signing is ready for use.": "交叉签名已可用。", + "Cross-signing is not set up.": "未设置交叉签名。", + "Backup version:": "备份版本:", + "Algorithm:": "算法:" } From 0e3c478e38d2ef13ef035a464d9dbd577ecde8e5 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Tue, 15 Sep 2020 02:22:58 +0000 Subject: [PATCH 037/155] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index c7dc5555c4..0a2c16343d 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2493,5 +2493,8 @@ "Secret storage:": "秘密儲存空間:", "ready": "準備好", "not ready": "尚未準備好", - "Secure Backup": "安全備份" + "Secure Backup": "安全備份", + "End Call": "結束通話", + "Remove the group call from the room?": "從聊天室中移除群組通話?", + "You don't have permission to remove the call from the room": "您沒有從聊天室移除通話的權限" } From c605c3636b92dbf9962d45dc4f47404d1c6ea826 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Tue, 15 Sep 2020 06:34:18 +0000 Subject: [PATCH 038/155] Translated using Weblate (Swedish) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 8be68c031e..3feee476c6 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -2423,5 +2423,8 @@ "Secret storage:": "Hemlig lagring:", "ready": "klart", "not ready": "inte klart", - "Secure Backup": "Säker säkerhetskopiering" + "Secure Backup": "Säker säkerhetskopiering", + "End Call": "Avsluta samtal", + "Remove the group call from the room?": "Ta bort gruppsamtalet från rummet?", + "You don't have permission to remove the call from the room": "Du har inte behörighet från att ta bort samtalet från rummet" } From 9cc789756b4eabac8cb0e7da90636f6375106af1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 11:26:15 +0100 Subject: [PATCH 039/155] UI Feature Flag: Hide flair --- src/components/structures/MessagePanel.js | 7 +++++- src/components/structures/TimelinePanel.js | 2 ++ .../views/dialogs/UserSettingsDialog.js | 15 +++++++----- .../views/elements/EventTilePreview.tsx | 8 ++++++- src/components/views/elements/ReplyThread.js | 2 ++ src/components/views/rooms/EventTile.js | 7 ++++-- src/components/views/rooms/ReplyPreview.js | 14 +++++++---- .../views/rooms/SearchResultTile.js | 23 ++++++++++++++----- .../tabs/room/GeneralRoomSettingsTab.js | 22 +++++++++++------- src/settings/Settings.ts | 4 ++++ src/settings/UIFeature.ts | 1 + 11 files changed, 76 insertions(+), 29 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 230d136e04..fe7b20a2d9 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -135,6 +135,9 @@ export default class MessagePanel extends React.Component { // whether to use the irc layout useIRCLayout: PropTypes.bool, + + // whether or not to show flair at all + enableFlair: PropTypes.bool, }; // Force props to be loaded for useIRCLayout @@ -579,7 +582,8 @@ export default class MessagePanel extends React.Component { data-scroll-tokens={scrollToken} > - , diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 97f9ba48ed..8bbc66bf40 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -35,6 +35,7 @@ import Timer from '../../utils/Timer'; import shouldHideEvent from '../../shouldHideEvent'; import EditorStateTransfer from '../../utils/EditorStateTransfer'; import {haveTileForEvent} from "../views/rooms/EventTile"; +import {UIFeature} from "../../settings/UIFeature"; const PAGINATE_SIZE = 20; const INITIAL_SIZE = 20; @@ -1446,6 +1447,7 @@ class TimelinePanel extends React.Component { editState={this.state.editState} showReactions={this.props.showReactions} useIRCLayout={this.props.useIRCLayout} + enableFlair={SettingsStore.getValue(UIFeature.Flair)} /> ); } diff --git a/src/components/views/dialogs/UserSettingsDialog.js b/src/components/views/dialogs/UserSettingsDialog.js index ffde03fe31..8cb5a76760 100644 --- a/src/components/views/dialogs/UserSettingsDialog.js +++ b/src/components/views/dialogs/UserSettingsDialog.js @@ -32,6 +32,7 @@ import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab"; import * as sdk from "../../../index"; import SdkConfig from "../../../SdkConfig"; import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab"; +import {UIFeature} from "../../../settings/UIFeature"; export const USER_GENERAL_TAB = "USER_GENERAL_TAB"; export const USER_APPEARANCE_TAB = "USER_APPEARANCE_TAB"; @@ -86,12 +87,14 @@ export default class UserSettingsDialog extends React.Component { "mx_UserSettingsDialog_appearanceIcon", , )); - tabs.push(new Tab( - USER_FLAIR_TAB, - _td("Flair"), - "mx_UserSettingsDialog_flairIcon", - , - )); + if (SettingsStore.getValue(UIFeature.Flair)) { + tabs.push(new Tab( + USER_FLAIR_TAB, + _td("Flair"), + "mx_UserSettingsDialog_flairIcon", + , + )); + } tabs.push(new Tab( USER_NOTIFICATIONS_TAB, _td("Notifications"), diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx index 61e5f5381d..35019a901e 100644 --- a/src/components/views/elements/EventTilePreview.tsx +++ b/src/components/views/elements/EventTilePreview.tsx @@ -21,6 +21,8 @@ import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import * as Avatar from '../../../Avatar'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import EventTile from '../rooms/EventTile'; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; interface IProps { /** @@ -121,7 +123,11 @@ export default class EventTilePreview extends React.Component { }); return
- +
; } } diff --git a/src/components/views/elements/ReplyThread.js b/src/components/views/elements/ReplyThread.js index 70592c72c5..2d17c858a2 100644 --- a/src/components/views/elements/ReplyThread.js +++ b/src/components/views/elements/ReplyThread.js @@ -28,6 +28,7 @@ import escapeHtml from "escape-html"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {Action} from "../../../dispatcher/actions"; import sanitizeHtml from "sanitize-html"; +import {UIFeature} from "../../../settings/UIFeature"; // This component does no cycle detection, simply because the only way to make such a cycle would be to // craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would @@ -366,6 +367,7 @@ export default class ReplyThread extends React.Component { isRedacted={ev.isRedacted()} isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")} useIRCLayout={this.props.useIRCLayout} + enableFlair={SettingsStore.getValue(UIFeature.Flair)} /> ; }); diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index ab9f240f2d..f444fb1f1a 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -206,6 +206,9 @@ export default class EventTile extends React.Component { // whether to use the irc layout useIRCLayout: PropTypes.bool, + + // whether or not to show flair at all + enableFlair: PropTypes.bool, }; static defaultProps = { @@ -736,10 +739,10 @@ export default class EventTile extends React.Component { else if (msgtype === 'm.file') text = _td('%(senderName)s uploaded a file'); sender = ; } else { - sender = ; + sender = ; } } diff --git a/src/components/views/rooms/ReplyPreview.js b/src/components/views/rooms/ReplyPreview.js index de70338245..c7872d95ed 100644 --- a/src/components/views/rooms/ReplyPreview.js +++ b/src/components/views/rooms/ReplyPreview.js @@ -22,6 +22,7 @@ import RoomViewStore from '../../../stores/RoomViewStore'; import SettingsStore from "../../../settings/SettingsStore"; import PropTypes from "prop-types"; import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; +import {UIFeature} from "../../../settings/UIFeature"; function cancelQuoting() { dis.dispatch({ @@ -80,11 +81,14 @@ export default class ReplyPreview extends React.Component { onClick={cancelQuoting} />
- +
; } diff --git a/src/components/views/rooms/SearchResultTile.js b/src/components/views/rooms/SearchResultTile.js index 136bd23729..8b2a9c2d61 100644 --- a/src/components/views/rooms/SearchResultTile.js +++ b/src/components/views/rooms/SearchResultTile.js @@ -19,6 +19,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import {haveTileForEvent} from "./EventTile"; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; export default class SearchResultTile extends React.Component { static propTypes = { @@ -45,18 +47,27 @@ export default class SearchResultTile extends React.Component { const ret = []; const timeline = result.context.getTimeline(); - for (var j = 0; j < timeline.length; j++) { + for (let j = 0; j < timeline.length; j++) { const ev = timeline[j]; - var highlights; + let highlights; const contextual = (j != result.context.getOurEventIndex()); if (!contextual) { highlights = this.props.searchHighlights; } if (haveTileForEvent(ev)) { - ret.push(); + ret.push(( + + )); } } return ( diff --git a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js index 90eb60e632..9b8004d9d6 100644 --- a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js @@ -73,6 +73,18 @@ export default class GeneralRoomSettingsTab extends React.Component { urlPreviewSettings = null; } + let flairSection; + if (SettingsStore.getValue(UIFeature.Flair)) { + flairSection = <> + {_t("Flair")} +
+ +
+ ; + } + return (
{_t("General")}
@@ -87,14 +99,8 @@ export default class GeneralRoomSettingsTab extends React.Component { canonicalAliasEvent={canonicalAliasEv} aliasEvents={aliasEvents} />
{_t("Other")}
- {_t("Flair")} -
- -
- - {urlPreviewSettings} + { flairSection } + { urlPreviewSettings } {_t("Leave room")}
diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 511daf5cc6..c57394d970 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -618,4 +618,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.Flair]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index e7355a98eb..8cbf7c207b 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -17,4 +17,5 @@ limitations under the License. // see settings.md for documentation on conventions export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", + Flair = "UIFeature.flair", } From 8cbd67d6117647a51082b1d2bea4205f6282b72e Mon Sep 17 00:00:00 2001 From: Mirza Arnaut Date: Tue, 15 Sep 2020 22:38:43 +0000 Subject: [PATCH 040/155] Translated using Weblate (Bosnian) Currently translated at 0.2% (4 of 2374 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/bs/ --- src/i18n/strings/bs.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/bs.json b/src/i18n/strings/bs.json index 9e26dfeeb6..dc4ebda993 100644 --- a/src/i18n/strings/bs.json +++ b/src/i18n/strings/bs.json @@ -1 +1,6 @@ -{} \ No newline at end of file +{ + "Dismiss": "Odbaci", + "Create Account": "Otvori račun", + "Sign In": "Prijavite se", + "Explore rooms": "Istražite sobe" +} From c653e7c5a06bfa8ade2efb1c6f0ab2685fd4c78e Mon Sep 17 00:00:00 2001 From: call_xz Date: Tue, 15 Sep 2020 20:59:48 +0000 Subject: [PATCH 041/155] Translated using Weblate (Japanese) Currently translated at 56.6% (1344 of 2374 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ja/ --- src/i18n/strings/ja.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json index 4edf415efb..f5c2b89f8c 100644 --- a/src/i18n/strings/ja.json +++ b/src/i18n/strings/ja.json @@ -1319,7 +1319,7 @@ "Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)": "他のユーザーがあなたのホームサーバー (%(localDomain)s) を通じてこの部屋を見つけられるよう、アドレスを設定しましょう", "Enter recovery key": "リカバリキーを入力", "Verify this login": "このログインを承認", - "Signing In...": "サインインしています...", + "Signing In...": "サインイン中...", "If you've joined lots of rooms, this might take a while": "たくさんの部屋に参加している場合は、時間がかかる可能性があります", "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "このログインを他のセッションで承認し、あなたの認証情報を確認すれば、暗号化されたメッセージへアクセスできるようになります。", "This requires the latest %(brand)s on your other devices:": "最新版の %(brand)s が他のあなたのデバイスで実行されている必要があります:", @@ -1387,5 +1387,15 @@ "Your server": "あなたのサーバー", "Matrix": "Matrix", "Add a new server": "新しいサーバーを追加", - "Server name": "サーバー名" + "Server name": "サーバー名", + "Privacy": "プライバシー", + "Syncing...": "同期中...", + "Log in to your new account.": "新しいアカウントでログインする。", + "Use Recovery Key or Passphrase": "リカバリーキーまたはパスフレーズを使う", + "Use Recovery Key": "リカバリーキーを使う", + "%(brand)s Web": "%(brand)s ウェブ", + "%(brand)s Desktop": "%(brand)s デスクトップ", + "%(brand)s iOS": "%(brand)s iOS", + "%(brand)s Android": "%(brand)s Android", + "Your recovery key": "あなたのリカバリーキー" } From 7a448be1dc59b13a19916fc189123086d5069fbd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 12:14:33 +0100 Subject: [PATCH 042/155] UI Feature Flag: Disable advanced options and tidy up some copy --- .../views/dialogs/RoomSettingsDialog.js | 15 ++++---- .../tabs/user/PreferencesUserSettingsTab.js | 8 ++--- .../tabs/user/SecurityUserSettingsTab.js | 36 +++++++++++-------- src/settings/Settings.ts | 4 +++ src/settings/UIFeature.ts | 1 + 5 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.js index 613708e436..a43b284c42 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.js +++ b/src/components/views/dialogs/RoomSettingsDialog.js @@ -29,6 +29,7 @@ import * as sdk from "../../../index"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import dis from "../../../dispatcher/dispatcher"; import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; export const ROOM_GENERAL_TAB = "ROOM_GENERAL_TAB"; export const ROOM_SECURITY_TAB = "ROOM_SECURITY_TAB"; @@ -96,12 +97,14 @@ export default class RoomSettingsDialog extends React.Component { )); } - tabs.push(new Tab( - ROOM_ADVANCED_TAB, - _td("Advanced"), - "mx_RoomSettingsDialog_warningIcon", - , - )); + if (SettingsStore.getValue(UIFeature.AdvancedSettings)) { + tabs.push(new Tab( + ROOM_ADVANCED_TAB, + _td("Advanced"), + "mx_RoomSettingsDialog_warningIcon", + , + )); + } return tabs; } diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index d5dafe146a..347cfccd56 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -50,10 +50,10 @@ export default class PreferencesUserSettingsTab extends React.Component { 'showAvatarChanges', 'showDisplaynameChanges', 'showImages', + 'Pill.shouldShowPillAvatar', ]; - static ADVANCED_SETTINGS = [ - 'Pill.shouldShowPillAvatar', + static GENERAL_SETTINGS = [ 'TagPanel.enableTagPanel', 'promptBeforeInviteUnknownUsers', // Start automatically after startup (electron-only) @@ -191,8 +191,8 @@ export default class PreferencesUserSettingsTab extends React.Component {
- {_t("Advanced")} - {this._renderGroup(PreferencesUserSettingsTab.ADVANCED_SETTINGS)} + {_t("General")} + {this._renderGroup(PreferencesUserSettingsTab.GENERAL_SETTINGS)} {minimizeToTrayOption} {autoHideMenuOption} {autoLaunchOption} diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index c25ac438e0..6509fece13 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -30,6 +30,8 @@ import dis from "../../../../../dispatcher/dispatcher"; import {privateShouldBeEncrypted} from "../../../../../createRoom"; import {SettingLevel} from "../../../../../settings/SettingLevel"; import SecureBackupPanel from "../../SecureBackupPanel"; +import SettingsStore from "../../../../../settings/SettingsStore"; +import {UIFeature} from "../../../../../settings/UIFeature"; export class IgnoredUser extends React.Component { static propTypes = { @@ -311,15 +313,13 @@ export default class SecurityUserSettingsTab extends React.Component { // can remove this. const CrossSigningPanel = sdk.getComponent('views.settings.CrossSigningPanel'); const crossSigning = ( -
- {_t("Cross-signing")} -
- -
+
+ {_t("Cross-signing")} +
+
- ); - - const E2eAdvancedPanel = sdk.getComponent('views.settings.E2eAdvancedPanel'); +
+ ); let warning; if (!privateShouldBeEncrypted()) { @@ -329,6 +329,19 @@ export default class SecurityUserSettingsTab extends React.Component {
; } + const E2eAdvancedPanel = sdk.getComponent('views.settings.E2eAdvancedPanel'); + let advancedSection; + if (SettingsStore.getValue(UIFeature.AdvancedSettings)) { + advancedSection = <> +
{_t("Advanced")}
+
+ {this._renderIgnoredUsers()} + {this._renderManageInvites()} + +
+ ; + } + return (
{warning} @@ -375,12 +388,7 @@ export default class SecurityUserSettingsTab extends React.Component {
-
{_t("Advanced")}
-
- {this._renderIgnoredUsers()} - {this._renderManageInvites()} - -
+ { advancedSection }
); } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..5fd5eebca4 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -622,4 +622,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.AdvancedSettings]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 99196e5d30..d94b44e2c2 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,5 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", + AdvancedSettings = "UIFeature.advancedSettings", } From 2bea8457e9050782da88fffed0bf3b45b943976c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 12:55:04 +0100 Subject: [PATCH 043/155] UI Feature Flag: Communities --- .../settings/tabs/user/PreferencesUserSettingsTab.js | 9 +++------ src/settings/Settings.ts | 8 ++++++++ src/settings/SettingsStore.ts | 5 +++++ src/settings/UIFeature.ts | 1 + src/settings/controllers/SettingController.ts | 7 +++++++ src/settings/controllers/UIFeatureController.ts | 10 +++++++--- 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index d5dafe146a..b3a5d06707 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -23,7 +23,6 @@ import Field from "../../../elements/Field"; import * as sdk from "../../../../.."; import PlatformPeg from "../../../../../PlatformPeg"; import {SettingLevel} from "../../../../../settings/SettingLevel"; -import {UIFeature} from "../../../../../settings/UIFeature"; export default class PreferencesUserSettingsTab extends React.Component { static ROOM_LIST_SETTINGS = [ @@ -138,12 +137,10 @@ export default class PreferencesUserSettingsTab extends React.Component { }; _renderGroup(settingIds) { - if (!SettingsStore.getValue(UIFeature.URLPreviews)) { - settingIds = settingIds.filter(i => i !== 'urlPreviewsEnabled'); - } - const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag"); - return settingIds.map(i => ); + return settingIds.filter(SettingsStore.isEnabled).map(i => { + return ; + }); } render() { diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index c57394d970..5c4dc2e4d0 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -337,6 +337,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td('Enable Community Filter Panel'), default: true, invertedSettingName: 'TagPanel.disableTagPanel', + // We force the value to true because the invertedSettingName causes it to flip + controller: new UIFeatureController(UIFeature.Communities, true), }, "theme": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, @@ -621,5 +623,11 @@ export const SETTINGS: {[setting: string]: ISetting} = { [UIFeature.Flair]: { supportedLevels: LEVELS_UI_FEATURE, default: true, + // Disable Flair when Communities are disabled + controller: new UIFeatureController(UIFeature.Communities), + }, + [UIFeature.Communities]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, }, }; diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 9e146ad799..498a2d269d 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -257,6 +257,11 @@ export default class SettingsStore { return SETTINGS[settingName].isFeature; } + public static isEnabled(settingName: string) { + if (!SETTINGS[settingName]) return false; + return SETTINGS[settingName].controller ? !SETTINGS[settingName].controller.settingDisabled : true; + } + /** * Gets the value of a setting. The room ID is optional if the setting is not to * be applied to any particular room, otherwise it should be supplied. diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 8cbf7c207b..3bd7e0f25b 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,5 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Flair = "UIFeature.flair", + Communities = "UIFeature.communities", } diff --git a/src/settings/controllers/SettingController.ts b/src/settings/controllers/SettingController.ts index d90eba1e9e..ba78597da7 100644 --- a/src/settings/controllers/SettingController.ts +++ b/src/settings/controllers/SettingController.ts @@ -55,4 +55,11 @@ export default abstract class SettingController { public onChange(level: SettingLevel, roomId: string, newValue: any) { // do nothing by default } + + /** + * Gets whether the setting has been disabled due to this controller. + */ + public get settingDisabled() { + return false; + } } diff --git a/src/settings/controllers/UIFeatureController.ts b/src/settings/controllers/UIFeatureController.ts index ed6598a6e8..2748eec16a 100644 --- a/src/settings/controllers/UIFeatureController.ts +++ b/src/settings/controllers/UIFeatureController.ts @@ -26,7 +26,7 @@ import SettingsStore from "../SettingsStore"; * Settings using this controller are assumed to return `false` when disabled. */ export default class UIFeatureController extends SettingController { - public constructor(private uiFeatureName: string) { + public constructor(private uiFeatureName: string, private forcedValue = false) { super(); } @@ -36,10 +36,14 @@ export default class UIFeatureController extends SettingController { calculatedValue: any, calculatedAtLevel: SettingLevel, ): any { - if (!SettingsStore.getValue(this.uiFeatureName)) { + if (this.settingDisabled) { // per the docs: we force a disabled state when the feature isn't active - return false; + return this.forcedValue; } return null; // no override } + + public get settingDisabled(): boolean { + return !SettingsStore.getValue(this.uiFeatureName); + } } From d1070c05ddd3f7bf3f7c2171f4c5c831466a200b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 13:40:27 +0100 Subject: [PATCH 044/155] UI Feature Flag: Disable VoIP --- src/components/structures/MatrixChat.tsx | 3 +++ .../views/dialogs/UserSettingsDialog.js | 17 +++++++++++------ src/settings/Settings.ts | 5 +++++ src/settings/UIFeature.ts | 1 + 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index dde5dc6fb2..48dc8a79d1 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -79,6 +79,7 @@ import { SettingLevel } from "../../settings/SettingLevel"; import { leaveRoomBehaviour } from "../../utils/membership"; import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog"; import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore"; +import {UIFeature} from "../../settings/UIFeature"; /** constants for MatrixChat.state.view */ export enum Views { @@ -1373,6 +1374,8 @@ export default class MatrixChat extends React.PureComponent { }); }); cli.on('Call.incoming', function(call) { + // Check if the VoIP UI has been disabled + if (!SettingsStore.getValue(UIFeature.Voip)) return; // we dispatch this synchronously to make sure that the event // handlers on the call are set up immediately (so that if // we get an immediate hangup, we don't get a stuck call) diff --git a/src/components/views/dialogs/UserSettingsDialog.js b/src/components/views/dialogs/UserSettingsDialog.js index ffde03fe31..f74f57b970 100644 --- a/src/components/views/dialogs/UserSettingsDialog.js +++ b/src/components/views/dialogs/UserSettingsDialog.js @@ -32,6 +32,7 @@ import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab"; import * as sdk from "../../../index"; import SdkConfig from "../../../SdkConfig"; import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab"; +import {UIFeature} from "../../../settings/UIFeature"; export const USER_GENERAL_TAB = "USER_GENERAL_TAB"; export const USER_APPEARANCE_TAB = "USER_APPEARANCE_TAB"; @@ -104,12 +105,16 @@ export default class UserSettingsDialog extends React.Component { "mx_UserSettingsDialog_preferencesIcon", , )); - tabs.push(new Tab( - USER_VOICE_TAB, - _td("Voice & Video"), - "mx_UserSettingsDialog_voiceIcon", - , - )); + + if (SettingsStore.getValue(UIFeature.Voip)) { + tabs.push(new Tab( + USER_VOICE_TAB, + _td("Voice & Video"), + "mx_UserSettingsDialog_voiceIcon", + , + )); + } + tabs.push(new Tab( USER_SECURITY_TAB, _td("Security & Privacy"), diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..a7250982bf 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -588,6 +588,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { "showCallButtonsInComposer": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, default: true, + controller: new UIFeatureController(UIFeature.Voip), }, "e2ee.manuallyVerifyAllSessions": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, @@ -622,4 +623,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.Voip]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 99196e5d30..ce174ec4b5 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,5 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", + Voip = "UIFeature.voip", } From dfabe79335b9024a9efd68d6a68bec0589b2c15c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 13:44:24 +0100 Subject: [PATCH 045/155] tidy up event handler --- src/components/structures/MatrixChat.tsx | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 48dc8a79d1..7a207dd9a5 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1373,17 +1373,19 @@ export default class MatrixChat extends React.PureComponent { ready: true, }); }); - cli.on('Call.incoming', function(call) { - // Check if the VoIP UI has been disabled - if (!SettingsStore.getValue(UIFeature.Voip)) return; - // we dispatch this synchronously to make sure that the event - // handlers on the call are set up immediately (so that if - // we get an immediate hangup, we don't get a stuck call) - dis.dispatch({ - action: 'incoming_call', - call: call, - }, true); - }); + + if (SettingsStore.getValue(UIFeature.Voip)) { + cli.on('Call.incoming', function(call) { + // we dispatch this synchronously to make sure that the event + // handlers on the call are set up immediately (so that if + // we get an immediate hangup, we don't get a stuck call) + dis.dispatch({ + action: 'incoming_call', + call: call, + }, true); + }); + } + cli.on('Session.logged_out', function(errObj) { if (Lifecycle.isLoggingOut()) return; From d3c84e25f5287f7793f9faa723bf88520d6a861a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 14:45:34 +0100 Subject: [PATCH 046/155] UI Feature Flag: Identity server --- src/components/views/dialogs/InviteDialog.js | 85 ++++++++++++++----- .../tabs/user/GeneralUserSettingsTab.js | 11 ++- src/settings/Settings.ts | 4 + src/settings/UIFeature.ts | 1 + 4 files changed, 76 insertions(+), 25 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 80d8f1fc2c..3347a1381f 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -38,6 +38,8 @@ import {Action} from "../../../dispatcher/actions"; import {DefaultTagID} from "../../../stores/room-list/models"; import RoomListStore from "../../../stores/room-list/RoomListStore"; import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore"; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; // we have a number of types defined from the Matrix spec which can't reasonably be altered here. /* eslint-disable camelcase */ @@ -549,7 +551,7 @@ export default class InviteDialog extends React.PureComponent { if (this.state.filterText.startsWith('@')) { // Assume mxid newMember = new DirectoryMember({user_id: this.state.filterText, display_name: null, avatar_url: null}); - } else { + } else if (SettingsStore.getValue(UIFeature.IdentityServer)) { // Assume email newMember = new ThreepidMember(this.state.filterText); } @@ -734,7 +736,7 @@ export default class InviteDialog extends React.PureComponent { this.setState({tryingIdentityServer: true}); return; } - if (term.indexOf('@') > 0 && Email.looksValid(term)) { + if (term.indexOf('@') > 0 && Email.looksValid(term) && SettingsStore.getValue(UIFeature.IdentityServer)) { // Start off by suggesting the plain email while we try and resolve it // to a real account. this.setState({ @@ -1037,7 +1039,9 @@ export default class InviteDialog extends React.PureComponent { } _renderIdentityServerWarning() { - if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer) { + if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer || + !SettingsStore.getValue(UIFeature.IdentityServer) + ) { return null; } @@ -1086,22 +1090,41 @@ export default class InviteDialog extends React.PureComponent { let buttonText; let goButtonFn; + const identityServersEnabled = SettingsStore.getValue(UIFeature.IdentityServer); + const userId = MatrixClientPeg.get().getUserId(); if (this.props.kind === KIND_DM) { title = _t("Direct Messages"); - helpText = _t( + + if (identityServersEnabled) { + helpText = _t( "Start a conversation with someone using their name, username (like ) or email address.", {}, {userId: () => { - return {userId}; - }}, - ); + return ( + {userId} + ); + }}, + ); + } else { + helpText = _t( + "Start a conversation with someone using their name or username (like ).", + {}, + {userId: () => { + return ( + {userId} + ); + }}, + ); + } + if (CommunityPrototypeStore.instance.getSelectedCommunityId()) { const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName(); - helpText = _t( - "Start a conversation with someone using their name, username (like ) or email address. " + - "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click " + - "here.", + + helpText = + { helpText } {_t( + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, " + + "click here", {communityName}, { userId: () => { return ( @@ -1120,23 +1143,39 @@ export default class InviteDialog extends React.PureComponent { >{sub} ); }, - }, - ); + })} + ; } buttonText = _t("Go"); goButtonFn = this._startDm; } else { // KIND_INVITE title = _t("Invite to this room"); - helpText = _t( - "Invite someone using their name, username (like ), email address or share this room.", - {}, - { - userId: () => - {userId}, - a: (sub) => - {sub}, - }, - ); + + if (identityServersEnabled) { + helpText = _t( + "Invite someone using their name, username (like ), email address or " + + "share this room.", + {}, + { + userId: () => + {userId}, + a: (sub) => + {sub}, + }, + ); + } else { + helpText = _t( + "Invite someone using their name, username (like ) or share this room.", + {}, + { + userId: () => + {userId}, + a: (sub) => + {sub}, + }, + ); + } + buttonText = _t("Invite"); goButtonFn = this._inviteUsers; } diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 42e12077f2..40fd57d311 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -386,14 +386,21 @@ export default class GeneralUserSettingsTab extends React.Component { width="18" height="18" alt={_t("Warning")} /> : null; + let discoverySection; + if (SettingsStore.getValue(UIFeature.IdentityServer)) { + discoverySection = <> +
{discoWarning} {_t("Discovery")}
+ {this._renderDiscoverySection()} + ; + } + return (
{_t("General")}
{this._renderProfileSection()} {this._renderAccountSection()} {this._renderLanguageSection()} -
{discoWarning} {_t("Discovery")}
- {this._renderDiscoverySection()} + { discoverySection } {this._renderIntegrationManagerSection() /* Has its own title */}
{_t("Deactivate account")}
{this._renderManagementSection()} diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..a18d0f2187 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -622,4 +622,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.IdentityServer]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 99196e5d30..4de1d954d1 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,5 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", + IdentityServer = "UIFeature.identityServer", } From 1c44f15d2d4457127852fc82ae8c2c8dc28156db Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 14:54:30 +0100 Subject: [PATCH 047/155] i18n --- src/components/views/dialogs/InviteDialog.js | 6 +++--- src/i18n/strings/en_EN.json | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 3347a1381f..86411c43da 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -1098,9 +1098,9 @@ export default class InviteDialog extends React.PureComponent { if (identityServersEnabled) { helpText = _t( - "Start a conversation with someone using their name, username (like ) or email address.", - {}, - {userId: () => { + "Start a conversation with someone using their name, username (like ) or email address.", + {}, + {userId: () => { return ( {userId} ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4414077005..7c5212444c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -832,8 +832,8 @@ "Account management": "Account management", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", "Deactivate Account": "Deactivate Account", - "General": "General", "Discovery": "Discovery", + "General": "General", "Deactivate account": "Deactivate account", "Legal": "Legal", "Credits": "Credits", @@ -1732,9 +1732,11 @@ "Recently Direct Messaged": "Recently Direct Messaged", "Direct Messages": "Direct Messages", "Start a conversation with someone using their name, username (like ) or email address.": "Start a conversation with someone using their name, username (like ) or email address.", - "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.", + "Start a conversation with someone using their name or username (like ).": "Start a conversation with someone using their name or username (like ).", + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here", "Go": "Go", "Invite someone using their name, username (like ), email address or share this room.": "Invite someone using their name, username (like ), email address or share this room.", + "Invite someone using their name, username (like ) or share this room.": "Invite someone using their name, username (like ) or share this room.", "a new master key signature": "a new master key signature", "a new cross-signing key signature": "a new cross-signing key signature", "a device cross-signing signature": "a device cross-signing signature", From aa25bad68955bdab93f8e83f6cb06cf66dde6c7a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 14:57:46 +0100 Subject: [PATCH 048/155] tidy --- src/components/views/dialogs/InviteDialog.js | 51 ++++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 86411c43da..f66de67a1d 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -1108,9 +1108,9 @@ export default class InviteDialog extends React.PureComponent { ); } else { helpText = _t( - "Start a conversation with someone using their name or username (like ).", - {}, - {userId: () => { + "Start a conversation with someone using their name or username (like ).", + {}, + {userId: () => { return ( {userId} ); @@ -1120,30 +1120,29 @@ export default class InviteDialog extends React.PureComponent { if (CommunityPrototypeStore.instance.getSelectedCommunityId()) { const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName(); - - helpText = - { helpText } {_t( - "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, " + - "click here", + const inviteText = _t("This won't invite them to %(communityName)s. " + + "To invite someone to %(communityName)s, click here", {communityName}, { - userId: () => { - return ( - {userId} - ); - }, - a: (sub) => { - return ( - {sub} - ); - }, - })} + userId: () => { + return ( + {userId} + ); + }, + a: (sub) => { + return ( + {sub} + ); + }, + }); + helpText = + { helpText } {inviteText} ; } buttonText = _t("Go"); From 7bd5e3fa310238ed57a806564a4e8cd8cd9cca6c Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 10 Sep 2020 13:56:07 +0100 Subject: [PATCH 049/155] Move security-related dialogs to a common directory --- res/css/_components.scss | 10 +++++----- .../_AccessSecretStorageDialog.scss | 0 .../_CreateKeyBackupDialog.scss | 0 .../_CreateSecretStorageDialog.scss | 0 .../_KeyBackupFailedDialog.scss | 0 .../_RestoreKeyBackupDialog.scss | 0 src/SecurityManager.js | 7 +++---- .../{keybackup => security}/CreateKeyBackupDialog.js | 0 .../CreateSecretStorageDialog.js | 2 +- .../dialogs/{ => security}/ExportE2eKeysDialog.js | 6 +++--- .../IgnoreRecoveryReminderDialog.js | 0 .../dialogs/{ => security}/ImportE2eKeysDialog.js | 6 +++--- .../NewRecoveryMethodDialog.js | 2 +- .../RecoveryMethodRemovedDialog.js | 0 src/components/structures/MatrixChat.tsx | 4 ++-- src/components/structures/auth/E2eSetup.js | 2 +- src/components/views/dialogs/LogoutDialog.js | 8 ++++---- .../AccessSecretStorageDialog.js | 0 .../ConfirmDestroyCrossSigningDialog.js | 4 ++-- .../RestoreKeyBackupDialog.js | 0 .../dialogs/{ => security}/SetupEncryptionDialog.js | 12 ++++++------ src/components/views/rooms/RoomRecoveryReminder.js | 6 +++--- src/components/views/settings/ChangePassword.js | 2 +- src/components/views/settings/CrossSigningPanel.js | 2 +- src/components/views/settings/SecureBackupPanel.js | 4 ++-- .../settings/tabs/user/SecurityUserSettingsTab.js | 4 ++-- src/toasts/SetupEncryptionToast.ts | 2 +- .../views/dialogs/AccessSecretStorageDialog-test.js | 2 +- 28 files changed, 42 insertions(+), 43 deletions(-) rename res/css/views/dialogs/{secretstorage => security}/_AccessSecretStorageDialog.scss (100%) rename res/css/views/dialogs/{keybackup => security}/_CreateKeyBackupDialog.scss (100%) rename res/css/views/dialogs/{secretstorage => security}/_CreateSecretStorageDialog.scss (100%) rename res/css/views/dialogs/{keybackup => security}/_KeyBackupFailedDialog.scss (100%) rename res/css/views/dialogs/{keybackup => security}/_RestoreKeyBackupDialog.scss (100%) rename src/async-components/views/dialogs/{keybackup => security}/CreateKeyBackupDialog.js (100%) rename src/async-components/views/dialogs/{secretstorage => security}/CreateSecretStorageDialog.js (99%) rename src/async-components/views/dialogs/{ => security}/ExportE2eKeysDialog.js (97%) rename src/async-components/views/dialogs/{keybackup => security}/IgnoreRecoveryReminderDialog.js (100%) rename src/async-components/views/dialogs/{ => security}/ImportE2eKeysDialog.js (97%) rename src/async-components/views/dialogs/{keybackup => security}/NewRecoveryMethodDialog.js (97%) rename src/async-components/views/dialogs/{keybackup => security}/RecoveryMethodRemovedDialog.js (100%) rename src/components/views/dialogs/{secretstorage => security}/AccessSecretStorageDialog.js (100%) rename src/components/views/dialogs/{ => security}/ConfirmDestroyCrossSigningDialog.js (96%) rename src/components/views/dialogs/{keybackup => security}/RestoreKeyBackupDialog.js (100%) rename src/components/views/dialogs/{ => security}/SetupEncryptionDialog.js (80%) diff --git a/res/css/_components.scss b/res/css/_components.scss index 54e7436886..3263e3e28b 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -91,11 +91,11 @@ @import "./views/dialogs/_UploadConfirmDialog.scss"; @import "./views/dialogs/_UserSettingsDialog.scss"; @import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss"; -@import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss"; -@import "./views/dialogs/keybackup/_KeyBackupFailedDialog.scss"; -@import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss"; -@import "./views/dialogs/secretstorage/_AccessSecretStorageDialog.scss"; -@import "./views/dialogs/secretstorage/_CreateSecretStorageDialog.scss"; +@import "./views/dialogs/security/_AccessSecretStorageDialog.scss"; +@import "./views/dialogs/security/_CreateKeyBackupDialog.scss"; +@import "./views/dialogs/security/_CreateSecretStorageDialog.scss"; +@import "./views/dialogs/security/_KeyBackupFailedDialog.scss"; +@import "./views/dialogs/security/_RestoreKeyBackupDialog.scss"; @import "./views/directory/_NetworkDropdown.scss"; @import "./views/elements/_AccessibleButton.scss"; @import "./views/elements/_AddressSelector.scss"; diff --git a/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss b/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss similarity index 100% rename from res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss rename to res/css/views/dialogs/security/_AccessSecretStorageDialog.scss diff --git a/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss b/res/css/views/dialogs/security/_CreateKeyBackupDialog.scss similarity index 100% rename from res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss rename to res/css/views/dialogs/security/_CreateKeyBackupDialog.scss diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/security/_CreateSecretStorageDialog.scss similarity index 100% rename from res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss rename to res/css/views/dialogs/security/_CreateSecretStorageDialog.scss diff --git a/res/css/views/dialogs/keybackup/_KeyBackupFailedDialog.scss b/res/css/views/dialogs/security/_KeyBackupFailedDialog.scss similarity index 100% rename from res/css/views/dialogs/keybackup/_KeyBackupFailedDialog.scss rename to res/css/views/dialogs/security/_KeyBackupFailedDialog.scss diff --git a/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss b/res/css/views/dialogs/security/_RestoreKeyBackupDialog.scss similarity index 100% rename from res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss rename to res/css/views/dialogs/security/_RestoreKeyBackupDialog.scss diff --git a/src/SecurityManager.js b/src/SecurityManager.js index cc7db3ead7..f6b9c993d0 100644 --- a/src/SecurityManager.js +++ b/src/SecurityManager.js @@ -22,6 +22,8 @@ import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey'; import { _t } from './languageHandler'; import {encodeBase64} from "matrix-js-sdk/src/crypto/olmlib"; import { isSecureBackupRequired } from './utils/WellKnownUtils'; +import AccessSecretStorageDialog from './components/views/dialogs/security/AccessSecretStorageDialog'; +import RestoreKeyBackupDialog from './components/views/dialogs/security/RestoreKeyBackupDialog'; // This stores the secret storage private keys in memory for the JS SDK. This is // only meant to act as a cache to avoid prompting the user multiple times @@ -87,8 +89,6 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { return decodeRecoveryKey(recoveryKey); } }; - const AccessSecretStorageDialog = - sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog"); const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "", AccessSecretStorageDialog, /* props= */ @@ -181,7 +181,6 @@ export const crossSigningCallbacks = { export async function promptForBackupPassphrase() { let key; - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, { showSummary: false, keyCallback: k => key = k, }, null, /* priority = */ false, /* static = */ true); @@ -221,7 +220,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f // This dialog calls bootstrap itself after guiding the user through // passphrase creation. const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '', - import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"), + import("./async-components/views/dialogs/security/CreateSecretStorageDialog"), { forceReset, }, diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.js similarity index 100% rename from src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js rename to src/async-components/views/dialogs/security/CreateKeyBackupDialog.js diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js similarity index 99% rename from src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js rename to src/async-components/views/dialogs/security/CreateSecretStorageDialog.js index d4b1a73c3e..3908b7cd4a 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js @@ -30,6 +30,7 @@ import StyledRadioButton from '../../../../components/views/elements/StyledRadio import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; import DialogButtons from "../../../../components/views/elements/DialogButtons"; import InlineSpinner from "../../../../components/views/elements/InlineSpinner"; +import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog"; import { isSecureBackupRequired } from '../../../../utils/WellKnownUtils'; const PHASE_LOADING = 0; @@ -341,7 +342,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { // so let's stash it here, rather than prompting for it twice. const keyCallback = k => this._backupKey = k; - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); const { finished } = Modal.createTrackedDialog( 'Restore Backup', '', RestoreKeyBackupDialog, { diff --git a/src/async-components/views/dialogs/ExportE2eKeysDialog.js b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.js similarity index 97% rename from src/async-components/views/dialogs/ExportE2eKeysDialog.js rename to src/async-components/views/dialogs/security/ExportE2eKeysDialog.js index 406ffd8749..4dd296a8f1 100644 --- a/src/async-components/views/dialogs/ExportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.js @@ -17,11 +17,11 @@ limitations under the License. import FileSaver from 'file-saver'; import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import { _t } from '../../../languageHandler'; +import { _t } from '../../../../languageHandler'; import { MatrixClient } from 'matrix-js-sdk'; -import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption'; -import * as sdk from '../../../index'; +import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption'; +import * as sdk from '../../../../index'; const PHASE_EDIT = 1; const PHASE_EXPORTING = 2; diff --git a/src/async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog.js b/src/async-components/views/dialogs/security/IgnoreRecoveryReminderDialog.js similarity index 100% rename from src/async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog.js rename to src/async-components/views/dialogs/security/IgnoreRecoveryReminderDialog.js diff --git a/src/async-components/views/dialogs/ImportE2eKeysDialog.js b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.js similarity index 97% rename from src/async-components/views/dialogs/ImportE2eKeysDialog.js rename to src/async-components/views/dialogs/security/ImportE2eKeysDialog.js index c2d17f681d..e7bae3578b 100644 --- a/src/async-components/views/dialogs/ImportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.js @@ -18,9 +18,9 @@ import React, {createRef} from 'react'; import PropTypes from 'prop-types'; import { MatrixClient } from 'matrix-js-sdk'; -import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption'; -import * as sdk from '../../../index'; -import { _t } from '../../../languageHandler'; +import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption'; +import * as sdk from '../../../../index'; +import { _t } from '../../../../languageHandler'; function readFileAsArrayBuffer(file) { return new Promise((resolve, reject) => { diff --git a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js b/src/async-components/views/dialogs/security/NewRecoveryMethodDialog.js similarity index 97% rename from src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js rename to src/async-components/views/dialogs/security/NewRecoveryMethodDialog.js index 74552a5c08..9f5045635d 100644 --- a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js +++ b/src/async-components/views/dialogs/security/NewRecoveryMethodDialog.js @@ -22,6 +22,7 @@ import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import dis from "../../../../dispatcher/dispatcher"; import { _t } from "../../../../languageHandler"; import Modal from "../../../../Modal"; +import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog"; import {Action} from "../../../../dispatcher/actions"; export default class NewRecoveryMethodDialog extends React.PureComponent { @@ -41,7 +42,6 @@ export default class NewRecoveryMethodDialog extends React.PureComponent { } onSetupClick = async () => { - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); Modal.createTrackedDialog( 'Restore Backup', '', RestoreKeyBackupDialog, { onFinished: this.props.onFinished, diff --git a/src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js b/src/async-components/views/dialogs/security/RecoveryMethodRemovedDialog.js similarity index 100% rename from src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js rename to src/async-components/views/dialogs/security/RecoveryMethodRemovedDialog.js diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index dde5dc6fb2..95f60be86e 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1496,12 +1496,12 @@ export default class MatrixChat extends React.PureComponent { if (haveNewVersion) { Modal.createTrackedDialogAsync('New Recovery Method', 'New Recovery Method', - import('../../async-components/views/dialogs/keybackup/NewRecoveryMethodDialog'), + import('../../async-components/views/dialogs/security/NewRecoveryMethodDialog'), { newVersionInfo }, ); } else { Modal.createTrackedDialogAsync('Recovery Method Removed', 'Recovery Method Removed', - import('../../async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog'), + import('../../async-components/views/dialogs/security/RecoveryMethodRemovedDialog'), ); } }); diff --git a/src/components/structures/auth/E2eSetup.js b/src/components/structures/auth/E2eSetup.js index 9b390d24cc..91382d594d 100644 --- a/src/components/structures/auth/E2eSetup.js +++ b/src/components/structures/auth/E2eSetup.js @@ -29,7 +29,7 @@ export default class E2eSetup extends React.Component { super(); // awkwardly indented because https://github.com/eslint/eslint/issues/11310 this._createStorageDialogPromise = - import("../../../async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"); + import("../../../async-components/views/dialogs/security/CreateSecretStorageDialog"); } render() { diff --git a/src/components/views/dialogs/LogoutDialog.js b/src/components/views/dialogs/LogoutDialog.js index 930acaa0b8..af36dba2b6 100644 --- a/src/components/views/dialogs/LogoutDialog.js +++ b/src/components/views/dialogs/LogoutDialog.js @@ -20,7 +20,8 @@ import Modal from '../../../Modal'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; import { _t } from '../../../languageHandler'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; +import RestoreKeyBackupDialog from './security/RestoreKeyBackupDialog'; export default class LogoutDialog extends React.Component { defaultProps = { @@ -73,7 +74,7 @@ export default class LogoutDialog extends React.Component { _onExportE2eKeysClicked() { Modal.createTrackedDialogAsync('Export E2E Keys', '', - import('../../../async-components/views/dialogs/ExportE2eKeysDialog'), + import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), { matrixClient: MatrixClientPeg.get(), }, @@ -93,14 +94,13 @@ export default class LogoutDialog extends React.Component { // A key backup exists for this account, but the creating device is not // verified, so restore the backup which will give us the keys from it and // allow us to trust it (ie. upload keys to it) - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); Modal.createTrackedDialog( 'Restore Backup', '', RestoreKeyBackupDialog, null, null, /* priority = */ false, /* static = */ true, ); } else { Modal.createTrackedDialogAsync("Key Backup", "Key Backup", - import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"), + import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog"), null, null, /* priority = */ false, /* static = */ true, ); } diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/security/AccessSecretStorageDialog.js similarity index 100% rename from src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js rename to src/components/views/dialogs/security/AccessSecretStorageDialog.js diff --git a/src/components/views/dialogs/ConfirmDestroyCrossSigningDialog.js b/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.js similarity index 96% rename from src/components/views/dialogs/ConfirmDestroyCrossSigningDialog.js rename to src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.js index 9e1980e98d..abc1586205 100644 --- a/src/components/views/dialogs/ConfirmDestroyCrossSigningDialog.js +++ b/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.js @@ -16,8 +16,8 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {_t} from "../../../languageHandler"; -import * as sdk from "../../../index"; +import {_t} from "../../../../languageHandler"; +import * as sdk from "../../../../index"; export default class ConfirmDestroyCrossSigningDialog extends React.Component { static propTypes = { diff --git a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js b/src/components/views/dialogs/security/RestoreKeyBackupDialog.js similarity index 100% rename from src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js rename to src/components/views/dialogs/security/RestoreKeyBackupDialog.js diff --git a/src/components/views/dialogs/SetupEncryptionDialog.js b/src/components/views/dialogs/security/SetupEncryptionDialog.js similarity index 80% rename from src/components/views/dialogs/SetupEncryptionDialog.js rename to src/components/views/dialogs/security/SetupEncryptionDialog.js index d7723de588..9ce3144534 100644 --- a/src/components/views/dialogs/SetupEncryptionDialog.js +++ b/src/components/views/dialogs/security/SetupEncryptionDialog.js @@ -16,16 +16,16 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import SetupEncryptionBody from '../../structures/auth/SetupEncryptionBody'; -import BaseDialog from './BaseDialog'; -import { _t } from '../../../languageHandler'; -import { SetupEncryptionStore, PHASE_DONE } from '../../../stores/SetupEncryptionStore'; +import SetupEncryptionBody from '../../../structures/auth/SetupEncryptionBody'; +import BaseDialog from '../BaseDialog'; +import { _t } from '../../../../languageHandler'; +import { SetupEncryptionStore, PHASE_DONE } from '../../../../stores/SetupEncryptionStore'; function iconFromPhase(phase) { if (phase === PHASE_DONE) { - return require("../../../../res/img/e2e/verified.svg"); + return require("../../../../../res/img/e2e/verified.svg"); } else { - return require("../../../../res/img/e2e/warning.svg"); + return require("../../../../../res/img/e2e/warning.svg"); } } diff --git a/src/components/views/rooms/RoomRecoveryReminder.js b/src/components/views/rooms/RoomRecoveryReminder.js index 859df6dd1b..552de681c3 100644 --- a/src/components/views/rooms/RoomRecoveryReminder.js +++ b/src/components/views/rooms/RoomRecoveryReminder.js @@ -23,6 +23,7 @@ import Modal from "../../../Modal"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import SettingsStore from "../../../settings/SettingsStore"; import {SettingLevel} from "../../../settings/SettingLevel"; +import RestoreKeyBackupDialog from "../dialogs/security/RestoreKeyBackupDialog"; export default class RoomRecoveryReminder extends React.PureComponent { static propTypes = { @@ -70,14 +71,13 @@ export default class RoomRecoveryReminder extends React.PureComponent { // A key backup exists for this account, but the creating device is not // verified, so restore the backup which will give us the keys from it and // allow us to trust it (ie. upload keys to it) - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); Modal.createTrackedDialog( 'Restore Backup', '', RestoreKeyBackupDialog, null, null, /* priority = */ false, /* static = */ true, ); } else { Modal.createTrackedDialogAsync("Key Backup", "Key Backup", - import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"), + import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog"), null, null, /* priority = */ false, /* static = */ true, ); } @@ -91,7 +91,7 @@ export default class RoomRecoveryReminder extends React.PureComponent { // When you choose "Don't ask again" from the room reminder, we show a // dialog to confirm the choice. Modal.createTrackedDialogAsync("Ignore Recovery Reminder", "Ignore Recovery Reminder", - import("../../../async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog"), + import("../../../async-components/views/dialogs/security/IgnoreRecoveryReminderDialog"), { onDontAskAgain: async () => { await SettingsStore.setValue( diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index 725f04dede..0b62f1fa81 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -184,7 +184,7 @@ export default class ChangePassword extends React.Component { _onExportE2eKeysClicked = () => { Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password', - import('../../../async-components/views/dialogs/ExportE2eKeysDialog'), + import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), { matrixClient: MatrixClientPeg.get(), }, diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index a0ca84645f..fd8fef0544 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -22,6 +22,7 @@ import * as sdk from '../../../index'; import Modal from '../../../Modal'; import Spinner from '../elements/Spinner'; import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog'; +import ConfirmDestroyCrossSigningDialog from '../dialogs/security/ConfirmDestroyCrossSigningDialog'; export default class CrossSigningPanel extends React.PureComponent { constructor(props) { @@ -137,7 +138,6 @@ export default class CrossSigningPanel extends React.PureComponent { } _resetCrossSigning = () => { - const ConfirmDestroyCrossSigningDialog = sdk.getComponent("dialogs.ConfirmDestroyCrossSigningDialog"); Modal.createDialog(ConfirmDestroyCrossSigningDialog, { onFinished: (act) => { if (!act) return; diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 7f0655d54a..f94a4c9590 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -24,7 +24,7 @@ import { isSecureBackupRequired } from '../../../utils/WellKnownUtils'; import Spinner from '../elements/Spinner'; import AccessibleButton from '../elements/AccessibleButton'; import QuestionDialog from '../dialogs/QuestionDialog'; -import RestoreKeyBackupDialog from '../dialogs/keybackup/RestoreKeyBackupDialog'; +import RestoreKeyBackupDialog from '../dialogs/security/RestoreKeyBackupDialog'; import { accessSecretStorage } from '../../../SecurityManager'; export default class SecureBackupPanel extends React.PureComponent { @@ -150,7 +150,7 @@ export default class SecureBackupPanel extends React.PureComponent { _startNewBackup = () => { Modal.createTrackedDialogAsync('Key Backup', 'Key Backup', - import('../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'), + import('../../../async-components/views/dialogs/security/CreateKeyBackupDialog'), { onFinished: () => { this._loadBackupStatus(); diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index c25ac438e0..de03360f2a 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -103,14 +103,14 @@ export default class SecurityUserSettingsTab extends React.Component { _onExportE2eKeysClicked = () => { Modal.createTrackedDialogAsync('Export E2E Keys', '', - import('../../../../../async-components/views/dialogs/ExportE2eKeysDialog'), + import('../../../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), {matrixClient: MatrixClientPeg.get()}, ); }; _onImportE2eKeysClicked = () => { Modal.createTrackedDialogAsync('Import E2E Keys', '', - import('../../../../../async-components/views/dialogs/ImportE2eKeysDialog'), + import('../../../../../async-components/views/dialogs/security/ImportE2eKeysDialog'), {matrixClient: MatrixClientPeg.get()}, ); }; diff --git a/src/toasts/SetupEncryptionToast.ts b/src/toasts/SetupEncryptionToast.ts index 9dbc4acafc..5e3da94eda 100644 --- a/src/toasts/SetupEncryptionToast.ts +++ b/src/toasts/SetupEncryptionToast.ts @@ -18,7 +18,7 @@ import Modal from "../Modal"; import * as sdk from "../index"; import { _t } from "../languageHandler"; import DeviceListener from "../DeviceListener"; -import SetupEncryptionDialog from "../components/views/dialogs/SetupEncryptionDialog"; +import SetupEncryptionDialog from "../components/views/dialogs/security/SetupEncryptionDialog"; import { accessSecretStorage } from "../SecurityManager"; import ToastStore from "../stores/ToastStore"; import GenericToast from "../components/views/toasts/GenericToast"; diff --git a/test/components/views/dialogs/AccessSecretStorageDialog-test.js b/test/components/views/dialogs/AccessSecretStorageDialog-test.js index 5a8dcbf763..7c4b2996c9 100644 --- a/test/components/views/dialogs/AccessSecretStorageDialog-test.js +++ b/test/components/views/dialogs/AccessSecretStorageDialog-test.js @@ -20,7 +20,7 @@ import sdk from '../../../skinned-sdk'; import {MatrixClientPeg} from '../../../../src/MatrixClientPeg'; import { stubClient } from '../../../test-utils'; -const AccessSecretStorageDialog = sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog"); +const AccessSecretStorageDialog = sdk.getComponent("dialogs.security.AccessSecretStorageDialog"); describe("AccessSecretStorageDialog", function() { it("Closes the dialog if _onRecoveryKeyNext is called with a valid key", (done) => { From 3259ab1f250ee0d027a4a5d020f360848a6562db Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 11 Sep 2020 14:09:54 +0100 Subject: [PATCH 050/155] Place cross-signing action buttons on a single row Part of https://github.com/vector-im/element-web/issues/13895 --- .../views/settings/_CrossSigningPanel.scss | 4 ++ .../views/settings/CrossSigningPanel.js | 40 ++++++++++--------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/res/css/views/settings/_CrossSigningPanel.scss b/res/css/views/settings/_CrossSigningPanel.scss index fa9f76a963..12a0e36835 100644 --- a/res/css/views/settings/_CrossSigningPanel.scss +++ b/res/css/views/settings/_CrossSigningPanel.scss @@ -28,4 +28,8 @@ limitations under the License. .mx_CrossSigningPanel_buttonRow { margin: 1em 0; + + :nth-child(n + 1) { + margin-inline-end: 10px; + } } diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index fd8fef0544..5b5ef56024 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -195,29 +195,32 @@ export default class CrossSigningPanel extends React.PureComponent { crossSigningPublicKeysOnDevice ); - let resetButton; - if (keysExistAnywhere) { - resetButton = ( -
- - {_t("Reset")} - -
+ const actions = []; + + // TODO: determine how better to expose this to users in addition to prompts at login/toast + if (!keysExistEverywhere && homeserverSupportsCrossSigning) { + actions.push( + + {_t("Set up")} + , ); } - // TODO: determine how better to expose this to users in addition to prompts at login/toast - let bootstrapButton; - if (!keysExistEverywhere && homeserverSupportsCrossSigning) { - bootstrapButton = ( -
- - {_t("Set up")} - -
+ if (keysExistAnywhere) { + actions.push( + + {_t("Reset")} + , ); } + let actionRow; + if (actions.length) { + actionRow =
+ {actions} +
; + } + return (
{summarisedStatus} @@ -251,8 +254,7 @@ export default class CrossSigningPanel extends React.PureComponent { {errorSection} - {bootstrapButton} - {resetButton} + {actionRow}
); } From 685878a10189043ca09b39daab04ce40a22b7c53 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 11 Sep 2020 14:20:08 +0100 Subject: [PATCH 051/155] Clarify diagnostic about keys in storage Part of https://github.com/vector-im/element-web/issues/13895 --- src/components/views/settings/CrossSigningPanel.js | 2 +- src/i18n/strings/en_EN.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 5b5ef56024..fd5966ca0a 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -233,7 +233,7 @@ export default class CrossSigningPanel extends React.PureComponent { {_t("Cross-signing private keys:")} - {crossSigningPrivateKeysInStorage ? _t("in secret storage") : _t("not found")} + {crossSigningPrivateKeysInStorage ? _t("in secret storage") : _t("not found in storage")} {_t("Master private key:")} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4414077005..76ca0bf738 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -658,6 +658,7 @@ "not found": "not found", "Cross-signing private keys:": "Cross-signing private keys:", "in secret storage": "in secret storage", + "not found in storage": "not found in storage", "Master private key:": "Master private key:", "cached locally": "cached locally", "not found locally": "not found locally", From 7be27e70c964329769d9ceab8185824d5ea975da Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 15 Sep 2020 12:45:08 +0100 Subject: [PATCH 052/155] Add component key to actions array --- src/components/views/settings/CrossSigningPanel.js | 4 ++-- src/components/views/settings/SecureBackupPanel.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index fd5966ca0a..669c2e84d9 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -200,7 +200,7 @@ export default class CrossSigningPanel extends React.PureComponent { // TODO: determine how better to expose this to users in addition to prompts at login/toast if (!keysExistEverywhere && homeserverSupportsCrossSigning) { actions.push( - + {_t("Set up")} , ); @@ -208,7 +208,7 @@ export default class CrossSigningPanel extends React.PureComponent { if (keysExistAnywhere) { actions.push( - + {_t("Reset")} , ); diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index f94a4c9590..7e9fb6cd3d 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -367,14 +367,14 @@ export default class SecureBackupPanel extends React.PureComponent { ; actions.push( - + {restoreButtonCaption} , ); if (!isSecureBackupRequired()) { actions.push( - + {_t("Delete Backup")} , ); @@ -388,7 +388,7 @@ export default class SecureBackupPanel extends React.PureComponent {

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

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

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

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

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

-

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

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

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

-

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

-
-
- - {setupCaption} - - - { _t("Not now") } - - - { _t("Don't ask me again") } - -
-
- ); - } -} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ea558fbd93..727898f1a0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -474,7 +474,6 @@ "Show timestamps in 12 hour format (e.g. 2:30pm)": "Show timestamps in 12 hour format (e.g. 2:30pm)", "Always show message timestamps": "Always show message timestamps", "Autoplay GIFs and videos": "Autoplay GIFs and videos", - "Show a reminder to enable Secure Message Recovery in encrypted rooms": "Show a reminder to enable Secure Message Recovery in encrypted rooms", "Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting", "Show avatars in user and room mentions": "Show avatars in user and room mentions", "Enable big emoji in chat": "Enable big emoji in chat", @@ -1172,12 +1171,6 @@ "%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.", "Try again later, or ask a room admin to check if you have access.": "Try again later, or ask a room admin to check if you have access.", "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.", - "Start using Key Backup": "Start using Key Backup", - "Never lose encrypted messages": "Never lose encrypted messages", - "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", - "Securely back up your keys to avoid losing them. Learn more.": "Securely back up your keys to avoid losing them. Learn more.", - "Not now": "Not now", - "Don't ask me again": "Don't ask me again", "Appearance": "Appearance", "Show rooms with unread messages first": "Show rooms with unread messages first", "Show previews of messages": "Show previews of messages", @@ -1750,6 +1743,7 @@ "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!", "Updating %(brand)s": "Updating %(brand)s", "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", + "Start using Key Backup": "Start using Key Backup", "I don't want my encrypted messages": "I don't want my encrypted messages", "Manually export keys": "Manually export keys", "You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages", @@ -2300,9 +2294,6 @@ "Enter passphrase": "Enter passphrase", "Confirm passphrase": "Confirm passphrase", "Export": "Export", - "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.", - "If you don't want to set this up now, you can later in Settings.": "If you don't want to set this up now, you can later in Settings.", - "Don't ask again": "Don't ask again", "Import room keys": "Import room keys", "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.", "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.", diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..91bc4c2d85 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -281,11 +281,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td('Autoplay GIFs and videos'), default: false, }, - "showRoomRecoveryReminder": { - supportedLevels: LEVELS_ACCOUNT_SETTINGS, - displayName: _td('Show a reminder to enable Secure Message Recovery in encrypted rooms'), - default: true, - }, "enableSyntaxHighlightLanguageDetection": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Enable automatic language detection for syntax highlighting'), From 0d25f62a9adb9e4bb3bae6081f8c7eced3c82fc5 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 12:00:49 +0100 Subject: [PATCH 056/155] Tweak diagnostics for session backup key --- src/components/views/settings/SecureBackupPanel.js | 2 +- src/rageshake/submit-rageshake.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 7e9fb6cd3d..3547efc3f2 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -131,7 +131,7 @@ export default class SecureBackupPanel extends React.PureComponent { const cli = MatrixClientPeg.get(); const secretStorage = cli._crypto._secretStorage; - const backupKeyStored = await cli.isKeyBackupKeyStored(); + const backupKeyStored = !!(await cli.isKeyBackupKeyStored()); const backupKeyFromCache = await cli._crypto.getSessionBackupPrivateKey(); const backupKeyCached = !!(backupKeyFromCache); const backupKeyWellFormed = backupKeyFromCache instanceof Uint8Array; diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts index dd60cde16d..d361f6b0dd 100644 --- a/src/rageshake/submit-rageshake.ts +++ b/src/rageshake/submit-rageshake.ts @@ -112,6 +112,7 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true) { body.append("secret_storage_ready", String(await client.isSecretStorageReady())); body.append("secret_storage_key_in_account", String(!!(await secretStorage.hasKey()))); + body.append("session_backup_key_in_secret_storage", String(!!(await client.isKeyBackupKeyStored()))); const sessionBackupKeyFromCache = await client._crypto.getSessionBackupPrivateKey(); body.append("session_backup_key_cached", String(!!sessionBackupKeyFromCache)); body.append("session_backup_key_well_formed", String(sessionBackupKeyFromCache instanceof Uint8Array)); From 6130d9e8826863b9faa0e763c55fc8dd49fb5ef5 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 12:25:02 +0100 Subject: [PATCH 057/155] Delay encryption setup toasts until encrypted rooms present Part of https://github.com/vector-im/element-web/issues/13895 --- src/DeviceListener.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index 89cf968c6b..df494e6bdd 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -29,7 +29,6 @@ import { hideToast as hideUnverifiedSessionsToast, showToast as showUnverifiedSessionsToast, } from "./toasts/UnverifiedSessionToast"; -import { privateShouldBeEncrypted } from "./createRoom"; import { isSecretStorageBeingAccessed, accessSecretStorage } from "./SecurityManager"; import { isSecureBackupRequired } from './utils/WellKnownUtils'; import { isLoggedIn } from './components/structures/MatrixChat'; @@ -201,9 +200,7 @@ export default class DeviceListener { // If we're in the middle of a secret storage operation, we're likely // modifying the state involved here, so don't add new toasts to setup. if (isSecretStorageBeingAccessed()) return false; - // In a default configuration, show the toasts. If the well-known config causes e2ee default to be false - // then do not show the toasts until user is in at least one encrypted room. - if (privateShouldBeEncrypted()) return true; + // Show setup toasts once the user is in at least one encrypted room. const cli = MatrixClientPeg.get(); return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId)); } @@ -219,8 +216,6 @@ export default class DeviceListener { // (we add a listener on sync to do once check after the initial sync is done) if (!cli.isInitialSyncComplete()) return; - // JRS: This will change again in the next PR which moves secret storage - // later in the process. const crossSigningReady = await cli.isCrossSigningReady(); const secretStorageReady = await cli.isSecretStorageReady(); const allSystemsReady = crossSigningReady && secretStorageReady; From 550a53e49ce127db64767671e5b887d476861e40 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 13:20:45 +0100 Subject: [PATCH 058/155] Check cross-signing cached keys when showing setup button --- src/components/views/settings/CrossSigningPanel.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 669c2e84d9..1c548bd9d8 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -187,12 +187,18 @@ export default class CrossSigningPanel extends React.PureComponent { } const keysExistAnywhere = ( + crossSigningPublicKeysOnDevice || crossSigningPrivateKeysInStorage || - crossSigningPublicKeysOnDevice + masterPrivateKeyCached || + selfSigningPrivateKeyCached || + userSigningPrivateKeyCached ); const keysExistEverywhere = ( + crossSigningPublicKeysOnDevice && crossSigningPrivateKeysInStorage && - crossSigningPublicKeysOnDevice + masterPrivateKeyCached && + selfSigningPrivateKeyCached && + userSigningPrivateKeyCached ); const actions = []; From 7a5b0a964f041ad5f2e515f44166615ffef0ca4d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 13:57:23 +0100 Subject: [PATCH 059/155] Adjust main encryption toast to reference Secure Backup This adjusts the main toast to focus on Secure Backup as suggested in designs. Part of https://github.com/vector-im/element-web/issues/13895 --- res/css/structures/_ToastContainer.scss | 5 +++++ src/i18n/strings/en_EN.json | 7 +++---- src/toasts/SetupEncryptionToast.ts | 18 ++++++++++++++---- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index 544dcbc180..c381668a6a 100644 --- a/res/css/structures/_ToastContainer.scss +++ b/res/css/structures/_ToastContainer.scss @@ -80,6 +80,11 @@ limitations under the License. } } + &.mx_Toast_icon_secure_backup::after { + mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); + background-color: $primary-fg-color; + } + .mx_Toast_title, .mx_Toast_body { grid-column: 2; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 727898f1a0..17d80b7647 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -411,13 +411,12 @@ "Set password": "Set password", "To return to your account in future you need to set a password": "To return to your account in future you need to set a password", "Set Password": "Set Password", - "Set up encryption": "Set up encryption", + "Set up Secure Backup": "Set up Secure Backup", "Encryption upgrade available": "Encryption upgrade available", "Verify this session": "Verify this session", - "Set up": "Set up", "Upgrade": "Upgrade", "Verify": "Verify", - "Verify yourself & others to keep your chats safe": "Verify yourself & others to keep your chats safe", + "Safeguard against losing access to encrypted messages & data": "Safeguard against losing access to encrypted messages & data", "Other users may not trust it": "Other users may not trust it", "New login. Was this you?": "New login. Was this you?", "Verify the new login accessing your account: %(name)s": "Verify the new login accessing your account: %(name)s", @@ -651,6 +650,7 @@ "Cross-signing is ready for use.": "Cross-signing is ready for use.", "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.", "Cross-signing is not set up.": "Cross-signing is not set up.", + "Set up": "Set up", "Reset": "Reset", "Cross-signing public keys:": "Cross-signing public keys:", "in memory": "in memory", @@ -2279,7 +2279,6 @@ "Unable to query secret storage status": "Unable to query secret storage status", "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.", "You can also set up Secure Backup & manage your keys in Settings.": "You can also set up Secure Backup & manage your keys in Settings.", - "Set up Secure Backup": "Set up Secure Backup", "Upgrade your encryption": "Upgrade your encryption", "Set a Security Phrase": "Set a Security Phrase", "Confirm Security Phrase": "Confirm Security Phrase", diff --git a/src/toasts/SetupEncryptionToast.ts b/src/toasts/SetupEncryptionToast.ts index 5e3da94eda..5aa030e497 100644 --- a/src/toasts/SetupEncryptionToast.ts +++ b/src/toasts/SetupEncryptionToast.ts @@ -28,7 +28,7 @@ const TOAST_KEY = "setupencryption"; const getTitle = (kind: Kind) => { switch (kind) { case Kind.SET_UP_ENCRYPTION: - return _t("Set up encryption"); + return _t("Set up Secure Backup"); case Kind.UPGRADE_ENCRYPTION: return _t("Encryption upgrade available"); case Kind.VERIFY_THIS_SESSION: @@ -36,10 +36,20 @@ const getTitle = (kind: Kind) => { } }; +const getIcon = (kind: Kind) => { + switch (kind) { + case Kind.SET_UP_ENCRYPTION: + case Kind.UPGRADE_ENCRYPTION: + return "secure_backup"; + case Kind.VERIFY_THIS_SESSION: + return "verification_warning"; + } +}; + const getSetupCaption = (kind: Kind) => { switch (kind) { case Kind.SET_UP_ENCRYPTION: - return _t("Set up"); + return _t("Continue"); case Kind.UPGRADE_ENCRYPTION: return _t("Upgrade"); case Kind.VERIFY_THIS_SESSION: @@ -51,7 +61,7 @@ const getDescription = (kind: Kind) => { switch (kind) { case Kind.SET_UP_ENCRYPTION: case Kind.UPGRADE_ENCRYPTION: - return _t("Verify yourself & others to keep your chats safe"); + return _t("Safeguard against losing access to encrypted messages & data"); case Kind.VERIFY_THIS_SESSION: return _t("Other users may not trust it"); } @@ -88,7 +98,7 @@ export const showToast = (kind: Kind) => { ToastStore.sharedInstance().addOrReplaceToast({ key: TOAST_KEY, title: getTitle(kind), - icon: "verification_warning", + icon: getIcon(kind), props: { description: getDescription(kind), acceptLabel: getSetupCaption(kind), From feb37878d85f723039d737f8ffdf451706c9b9f4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 15:04:13 +0100 Subject: [PATCH 060/155] tidy --- src/components/views/dialogs/InviteDialog.js | 35 ++++++++++---------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index f66de67a1d..73101056f3 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -1123,24 +1123,25 @@ export default class InviteDialog extends React.PureComponent { const inviteText = _t("This won't invite them to %(communityName)s. " + "To invite someone to %(communityName)s, click here", {communityName}, { - userId: () => { - return ( - {userId} - ); + userId: () => { + return ( + {userId} + ); + }, + a: (sub) => { + return ( + {sub} + ); + }, }, - a: (sub) => { - return ( - {sub} - ); - }, - }); + ); helpText = { helpText } {inviteText} ; From c11abb74e0020610f1af696b463d64a44e9ddaf9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 16:06:17 +0100 Subject: [PATCH 061/155] UI Feature Flag: Share dialog QR code and social icons --- res/css/views/dialogs/_ShareDialog.scss | 5 +- src/components/views/dialogs/ShareDialog.tsx | 53 ++++++++++++-------- src/settings/Settings.ts | 8 +++ src/settings/UIFeature.ts | 2 + 4 files changed, 46 insertions(+), 22 deletions(-) diff --git a/res/css/views/dialogs/_ShareDialog.scss b/res/css/views/dialogs/_ShareDialog.scss index c343b872fd..ce3fdd021f 100644 --- a/res/css/views/dialogs/_ShareDialog.scss +++ b/res/css/views/dialogs/_ShareDialog.scss @@ -71,9 +71,12 @@ limitations under the License. margin-right: 64px; } +.mx_ShareDialog_qrcode_container + .mx_ShareDialog_social_container { + width: 299px; +} + .mx_ShareDialog_social_container { display: inline-block; - width: 299px; } .mx_ShareDialog_social_icon { diff --git a/src/components/views/dialogs/ShareDialog.tsx b/src/components/views/dialogs/ShareDialog.tsx index e849f7efe3..1569977d58 100644 --- a/src/components/views/dialogs/ShareDialog.tsx +++ b/src/components/views/dialogs/ShareDialog.tsx @@ -32,6 +32,8 @@ import {copyPlaintext, selectText} from "../../../utils/strings"; import StyledCheckbox from '../elements/StyledCheckbox'; import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; import { IDialogProps } from "./IDialogProps"; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; const socials = [ { @@ -197,6 +199,35 @@ export default class ShareDialog extends React.PureComponent { const matrixToUrl = this.getUrl(); const encodedUrl = encodeURIComponent(matrixToUrl); + const showQrCode = SettingsStore.getValue(UIFeature.ShareQRCode); + const showSocials = SettingsStore.getValue(UIFeature.ShareSocial); + + let qrSocialSection; + if (showQrCode || showSocials) { + qrSocialSection = <> +
+
+ { showQrCode &&
+ +
} + { showSocials &&
+ { socials.map((social) => ( + + {social.name} + + )) } +
} +
+ ; + } + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return { />
{ checkbox } -
- -
-
- -
-
- { socials.map((social) => ( - - {social.name} - - )) } -
-
+ { qrSocialSection } ; } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..3731125f09 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -622,4 +622,12 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.ShareQRCode]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, + [UIFeature.ShareSocial]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 99196e5d30..c4825dbbba 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,6 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", + ShareQRCode = "UIFeature.shareQrCode", + ShareSocial = "UIFeature.shareSocial", } From a18d0271c370c25706d9174c74bc7089be05d18d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 16:51:10 +0100 Subject: [PATCH 062/155] Adjust tests for Secure Backup toast --- .../src/scenarios/e2e-encryption.js | 2 + .../end-to-end-tests/src/usecases/security.js | 42 +++++++++++++++++++ test/end-to-end-tests/src/usecases/signup.js | 2 + 3 files changed, 46 insertions(+) create mode 100644 test/end-to-end-tests/src/usecases/security.js diff --git a/test/end-to-end-tests/src/scenarios/e2e-encryption.js b/test/end-to-end-tests/src/scenarios/e2e-encryption.js index d31d2c0d57..20e8af2947 100644 --- a/test/end-to-end-tests/src/scenarios/e2e-encryption.js +++ b/test/end-to-end-tests/src/scenarios/e2e-encryption.js @@ -21,6 +21,7 @@ const {receiveMessage} = require('../usecases/timeline'); const {createDm} = require('../usecases/create-room'); const {checkRoomSettings} = require('../usecases/room-settings'); const {startSasVerifcation, acceptSasVerification} = require('../usecases/verify'); +const { setupSecureBackup } = require('../usecases/security'); const assert = require('assert'); module.exports = async function e2eEncryptionScenarios(alice, bob) { @@ -43,4 +44,5 @@ module.exports = async function e2eEncryptionScenarios(alice, bob) { const bobMessage = "You've got to tell me!"; await sendMessage(bob, bobMessage); await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true}); + await setupSecureBackup(alice); }; diff --git a/test/end-to-end-tests/src/usecases/security.js b/test/end-to-end-tests/src/usecases/security.js new file mode 100644 index 0000000000..31540874e9 --- /dev/null +++ b/test/end-to-end-tests/src/usecases/security.js @@ -0,0 +1,42 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +const { acceptToast } = require("./toasts"); + +async function setupSecureBackup(session) { + session.log.step("sets up Secure Backup"); + + await acceptToast(session, "Set up Secure Backup"); + + // Continue with the default (generate a security key) + const xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); + await xsignContButton.click(); + + //ignore the recovery key + //TODO: It's probably important for the tests to know the recovery key + const copyButton = await session.query('.mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn'); + await copyButton.click(); + + //acknowledge that we copied the recovery key to a safe place + const copyContinueButton = await session.query( + '.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary', + ); + await copyContinueButton.click(); + + session.log.done(); +} + +module.exports = { setupSecureBackup }; diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index ef8a259091..e4b2a9a62d 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -15,6 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +const { acceptToast } = require("./toasts"); + const assert = require('assert'); module.exports = async function signup(session, username, password, homeserver) { From b5000b236f09dd4159f1768121d62a801aabe7b1 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 17:12:51 +0100 Subject: [PATCH 063/155] Fix lint error --- test/end-to-end-tests/src/usecases/signup.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index e4b2a9a62d..ef8a259091 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -15,8 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -const { acceptToast } = require("./toasts"); - const assert = require('assert'); module.exports = async function signup(session, username, password, homeserver) { From 815a1559ffa8af51ab9794771a5915549e3cb9e2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 15:05:14 -0600 Subject: [PATCH 064/155] Fix setState() usage in the constructor of RoomDirectory React doesn't like it when we setState() in the constructor --- src/components/structures/RoomDirectory.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index 16ab8edbed..44652e5073 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -70,10 +70,10 @@ export default class RoomDirectory extends React.Component { this.scrollPanel = null; this.protocols = null; - this.setState({protocolsLoading: true}); + this.state.protocolsLoading = true; if (!MatrixClientPeg.get()) { // We may not have a client yet when invoked from welcome page - this.setState({protocolsLoading: false}); + this.state.protocolsLoading = false; return; } @@ -102,14 +102,16 @@ export default class RoomDirectory extends React.Component { }); } else { // We don't use the protocols in the communities v2 prototype experience - this.setState({protocolsLoading: false}); + this.state.protocolsLoading = false; // Grab the profile info async FlairStore.getGroupProfileCached(MatrixClientPeg.get(), this.state.selectedCommunityId).then(profile => { this.setState({communityName: profile.name}); }); } + } + componentDidMount() { this.refreshRoomList(); } From bfbbf44dfcc85e990eb579bd67aa7782d6280d6a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 17:23:37 -0600 Subject: [PATCH 065/155] Add a note to use the desktop builds when seshat isn't available Fixes https://github.com/vector-im/element-web/issues/15184 This is currently temporary design for https://github.com/vector-im/element-web/issues/12896 but does not fix it. --- res/css/_components.scss | 1 + .../views/elements/_DesktopBuildsNotice.scss | 28 ++++ res/css/views/rooms/_SearchBar.scss | 1 + res/img/element-desktop-logo.svg | 157 ++++++++++++++++++ src/SdkConfig.ts | 5 + src/components/structures/FilePanel.js | 4 + src/components/structures/RoomView.tsx | 1 + .../views/elements/DesktopBuildsNotice.tsx | 77 +++++++++ src/components/views/rooms/SearchBar.js | 35 ++-- src/i18n/strings/en_EN.json | 4 + 10 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 res/css/views/elements/_DesktopBuildsNotice.scss create mode 100644 res/img/element-desktop-logo.svg create mode 100644 src/components/views/elements/DesktopBuildsNotice.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 54e7436886..4c83dd7a31 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -100,6 +100,7 @@ @import "./views/elements/_AccessibleButton.scss"; @import "./views/elements/_AddressSelector.scss"; @import "./views/elements/_AddressTile.scss"; +@import "./views/elements/_DesktopBuildsNotice.scss"; @import "./views/elements/_DirectorySearchBox.scss"; @import "./views/elements/_Dropdown.scss"; @import "./views/elements/_EditableItemList.scss"; diff --git a/res/css/views/elements/_DesktopBuildsNotice.scss b/res/css/views/elements/_DesktopBuildsNotice.scss new file mode 100644 index 0000000000..3672595bf1 --- /dev/null +++ b/res/css/views/elements/_DesktopBuildsNotice.scss @@ -0,0 +1,28 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_DesktopBuildsNotice { + text-align: center; + padding: 0 16px; + + > * { + vertical-align: middle; + } + + > img { + margin-right: 8px; + } +} diff --git a/res/css/views/rooms/_SearchBar.scss b/res/css/views/rooms/_SearchBar.scss index fecc8d78d8..d9f730a8b6 100644 --- a/res/css/views/rooms/_SearchBar.scss +++ b/res/css/views/rooms/_SearchBar.scss @@ -68,3 +68,4 @@ limitations under the License. cursor: pointer; } } + diff --git a/res/img/element-desktop-logo.svg b/res/img/element-desktop-logo.svg new file mode 100644 index 0000000000..2031733ce3 --- /dev/null +++ b/res/img/element-desktop-logo.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SdkConfig.ts b/src/SdkConfig.ts index b914aaaf6d..7d7caa2d24 100644 --- a/src/SdkConfig.ts +++ b/src/SdkConfig.ts @@ -33,6 +33,11 @@ export const DEFAULTS: ConfigOptions = { // Default conference domain preferredDomain: "jitsi.riot.im", }, + desktopBuilds: { + available: true, + logo: require("../res/img/element-desktop-logo.svg"), + url: "https://element.io/get-started", + }, }; export default class SdkConfig { diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 6d618d0b9d..4836b0f554 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -25,6 +25,7 @@ import EventIndexPeg from "../../indexing/EventIndexPeg"; import { _t } from '../../languageHandler'; import BaseCard from "../views/right_panel/BaseCard"; import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; +import DesktopBuildsNotice, {WarningKind} from "../views/elements/DesktopBuildsNotice"; /* * Component which shows the filtered file using a TimelinePanel @@ -222,6 +223,8 @@ class FilePanel extends React.Component {

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

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

{ microcopy }

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

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

    - -
    - - {_t("Generate a Security Key")} -
    -
    {_t("We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.")}
    -
    - -
    - - {_t("Enter a Security Phrase")} -
    -
    {_t("Use a secret phrase only you know, and optionally save a Security Key to use for backup.")}
    -
    + {optionKey} + {optionPassphrase}
    Date: Mon, 21 Sep 2020 12:13:41 +0000 Subject: [PATCH 113/155] Translated using Weblate (Dutch) Currently translated at 82.7% (1958 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/nl/ --- src/i18n/strings/nl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index bb0fb5def6..1ec887c364 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -2026,5 +2026,6 @@ "To return to your account in future you need to set a password": "Zonder wachtwoord kunt u later niet tot uw account terugkeren", "Restart": "Herstarten", "People": "Tweegesprekken", - "Set a room address to easily share your room with other people.": "Geef het gesprek een adres om het gemakkelijk met anderen te kunnen delen." + "Set a room address to easily share your room with other people.": "Geef het gesprek een adres om het gemakkelijk met anderen te kunnen delen.", + "Invite people to join %(communityName)s": "Stuur uitnodigingen voor %(communityName)s" } From b7d4a94edd0f7f21d526f5ddb03cd319902b6dde Mon Sep 17 00:00:00 2001 From: XoseM Date: Mon, 21 Sep 2020 08:24:55 +0000 Subject: [PATCH 114/155] Translated using Weblate (Galician) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 51f39758e1..b19cb28420 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2499,5 +2499,12 @@ "Start a conversation with someone using their name or username (like ).": "Inicia unha conversa con alguén usando o seu nome ou nome de usuaria (como ).", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Esto non as convidará a %(communityName)s. Para convidar alguén a %(communityName)s, preme aquí", "Invite someone using their name, username (like ) or share this room.": "Convida a alguén usando o seu nome, nome de usuaria (como ) ou comparte esta sala.", - "Unable to set up keys": "Non se puideron configurar as chaves" + "Unable to set up keys": "Non se puideron configurar as chaves", + "Widgets": "Widgets", + "Edit widgets, bridges & bots": "Editar widgets, pontes e bots", + "Add widgets, bridges & bots": "Engade widgets, pontes e bots", + "You can only pin 2 widgets at a time": "Só podes fixar 2 widgets ó mesmo tempo", + "Minimize widget": "Minimizar widget", + "Maximize widget": "Maximizar widget", + "Your server requires encryption to be enabled in private rooms.": "O servidor require que actives o cifrado nas salas privadas." } From e691f78c513505e1db3068c9d6dc88c4e5629533 Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Mon, 21 Sep 2020 13:48:27 +0000 Subject: [PATCH 115/155] Translated using Weblate (German) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index e211ef6a2a..b21898a561 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -801,7 +801,7 @@ "Muted Users": "Stummgeschaltete Benutzer", "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "Dies wird deinen Account permanent unbenutzbar machen. Du wirst nicht in der Lage sein, dich anzumelden und keiner wird dieselbe Benutzer-ID erneut registrieren können. Alle Räume, in denen der Account ist, werden verlassen und deine Account-Daten werden vom Identitätsserver gelöscht. Diese Aktion ist unumkehrbar.", "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "Standardmäßig werden die von dir gesendeten Nachrichten beim Deaktiveren nicht gelöscht. Wenn du dies von uns möchtest, aktivere das Auswalfeld unten.", - "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Sie Sichtbarkeit der Nachrichten in Matrix ist vergleichbar mit E-Mails: Wenn wir deine Nachrichten vergessen heißt das, dass diese nicht mit neuen oder nicht registrierten Nutzern teilen werden, aber registrierte Nutzer, die bereits zugriff haben, werden Zugriff auf ihre Kopie behalten.", + "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Die Sichtbarkeit der Nachrichten in Matrix ist vergleichbar mit E-Mails: Wenn wir deine Nachrichten vergessen heißt das, dass diese nicht mit neuen oder nicht registrierten Nutzern teilen werden, aber registrierte Nutzer, die bereits zugriff haben, werden Zugriff auf ihre Kopie behalten.", "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Bitte vergesst alle Nachrichten, die ich gesendet habe, wenn mein Account deaktiviert wird. (Warnung: Zukünftige Nutzer werden eine unvollständige Konversation sehen)", "To continue, please enter your password:": "Um fortzufahren, bitte Passwort eingeben:", "Can't leave Server Notices room": "Du kannst den Raum für Server-Notizen nicht verlassen", @@ -937,9 +937,9 @@ "Unable to load key backup status": "Konnte Status der Schlüsselsicherung nicht laden", "Don't ask again": "Nicht erneut fragen", "Set up": "Einrichten", - "Please review and accept all of the homeserver's policies": "Bitte prüfen und akzeptieren Sie alle Richtlinien des Heimservers", + "Please review and accept all of the homeserver's policies": "Bitte prüfe und akzeptiere alle Richtlinien des Heimservers", "Failed to load group members": "Konnte Gruppenmitglieder nicht laden", - "That doesn't look like a valid email address": "Sieht nicht nach einer validen E-Mail-Adresse aus", + "That doesn't look like a valid email address": "Sieht nicht nach einer gültigen E-Mail-Adresse aus", "Unable to load commit detail: %(msg)s": "Konnte Commit-Details nicht laden: %(msg)s", "Checking...": "Überprüfe...", "Unable to load backup status": "Konnte Sicherungsstatus nicht laden", @@ -960,7 +960,7 @@ "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Wenn du die neue Wiederherstellungsmethode nicht festgelegt hast, versucht ein/e Angreifer!n möglicherweise, auf dein Konto zuzugreifen. Ändere dein Kontopasswort und lege sofort eine neue Wiederherstellungsmethode in den Einstellungen fest.", "Set up Secure Messages": "Richte sichere Nachrichten ein", "Go to Settings": "Gehe zu Einstellungen", - "Sign in with single sign-on": "Melden Sie sich mit „Single Sign-On“ an", + "Sign in with single sign-on": "Melde dich mit „Single Sign-On“ an", "Unrecognised address": "Nicht erkannte Adresse", "User %(user_id)s may or may not exist": "Existenz der Benutzer %(user_id)s unsicher", "Prompt before sending invites to potentially invalid matrix IDs": "Nachfragen bevor Einladungen zu möglichen ungültigen Matrix IDs gesendet werden", @@ -1141,11 +1141,11 @@ "Are you sure you want to sign out?": "Bist du sicher, dass du dich abmelden möchtest?", "Manually export keys": "Manueller Schlüssel Export", "Composer": "Nachrichteneingabefeld", - "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Überprüfen Sie diesen Benutzer, um ihn als vertrauenswürdig zu kennzeichnen. Benutzern zu vertrauen gibt Ihnen zusätzliche Sicherheit bei der Verwendung von Ende-zu-Ende-verschlüsselten Nachrichten.", + "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Überprüfe diesen Benutzer, um ihn als vertrauenswürdig zu kennzeichnen. Benutzern zu vertrauen gibt dir zusätzliche Sicherheit bei der Verwendung von Ende-zu-Ende-verschlüsselten Nachrichten.", "I don't want my encrypted messages": "Ich möchte meine verschlüsselten Nachrichten nicht", "You'll lose access to your encrypted messages": "Du wirst den Zugang zu deinen verschlüsselten Nachrichten verlieren", "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "Wenn du Fehler bemerkst oder eine Rückmeldung geben möchtest, teile dies uns auf GitHub mit.", - "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.": "Um doppelte Issues zu vermeiden, schauen Sie bitte zuerst die existierenden Issues an (und fügen Sie ein \"+1\" hinzu), oder erstellen Sie ein neues Issue, wenn Sie keines finden können.", + "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.": "Um doppelte Issues zu vermeiden, schaue bitte zuerst die existierenden Issues an (und füge ein \"+1\" hinzu), oder erstelle ein neues Issue, wenn du kein passendes findest.", "Report bugs & give feedback": "Melde Fehler & gib Rückmeldungen", "Update status": "Aktualisiere Status", "Set status": "Setze Status", @@ -1188,7 +1188,7 @@ "Enter the location of your Modular homeserver. It may use your own domain name or be a subdomain of modular.im.": "Gib die Adresse deines Modular-Heimservers an. Es kann deine eigene Domain oder eine Subdomain von modular.im sein.", "Unable to query for supported registration methods.": "Konnte unterstützte Registrierungsmethoden nicht abrufen.", "Bulk options": "Sammeloptionen", - "Join millions for free on the largest public server": "Schließen Sie sich auf dem größten öffentlichen Server kostenlos Millionen von Menschen an", + "Join millions for free on the largest public server": "Schließe dich kostenlos auf dem größten öffentlichen Server Millionen von Menschen an", "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Fügt ¯\\_(ツ)_/¯ vor einer Klartextnachricht ein", "Changes your display nickname in the current room only": "Ändert den Anzeigenamen ausschließlich für den aktuellen Raum", "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s aktivierte Abzeichen der Gruppen %(groups)s für diesen Raum.", @@ -1445,12 +1445,12 @@ "Recently Direct Messaged": "Kürzlich direkt verschickt", "Go": "Los", "Command Help": "Befehl Hilfe", - "To help us prevent this in future, please send us logs.": "Um uns zu helfen, dies in Zukunft zu vermeiden, senden Sie uns bitte Logs.", + "To help us prevent this in future, please send us logs.": "Um uns zu helfen, dies in Zukunft zu vermeiden, sende uns bitte Logs.", "Notification settings": "Benachrichtigungseinstellungen", "Help": "Hilf uns", "Filter": "Filtern", "Filter rooms…": "Räume filtern…", - "You have %(count)s unread notifications in a prior version of this room.|one": "Sie haben %(count)s ungelesene Benachrichtigungen in einer früheren Version dieses Raumes.", + "You have %(count)s unread notifications in a prior version of this room.|one": "Du hast %(count)s ungelesene Benachrichtigungen in einer früheren Version dieses Raumes.", "Go Back": "Gehe zurück", "Notification Autocomplete": "Benachrichtigung Autovervollständigen", "If disabled, messages from encrypted rooms won't appear in search results.": "Wenn deaktiviert, werden Nachrichten von verschlüsselten Räumen nicht in den Ergebnissen auftauchen.", @@ -1478,19 +1478,19 @@ "%(name)s cancelled": "%(name)s hat abgebrochen", "%(name)s wants to verify": "%(name)s will eine Verifizierung", "Your display name": "Dein Anzeigename", - "Please enter a name for the room": "Bitte geben Sie einen Namen für den Raum ein", + "Please enter a name for the room": "Bitte gib einen Namen für den Raum ein", "This room is private, and can only be joined by invitation.": "Dieser Raum ist privat und kann nur auf Einladung betreten werden.", "Create a private room": "Erstelle einen privaten Raum", "Topic (optional)": "Thema (optional)", - "Make this room public": "Machen Sie diesen Raum öffentlich", + "Make this room public": "Mache diesen Raum öffentlich", "Hide advanced": "Weitere Einstellungen ausblenden", "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)": "Hindere Benutzer auf anderen Matrix-Homeservern daran, diesem Raum beizutreten (Diese Einstellung kann später nicht geändert werden!)", "Session name": "Name der Sitzung", "This will allow you to return to your account after signing out, and sign in on other sessions.": "So kannst du nach der Abmeldung zu deinem Konto zurückkehren und dich bei anderen Sitzungen anmelden.", "Use bots, bridges, widgets and sticker packs": "Benutze Bots, Bridges, Widgets und Sticker-Packs", "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Wenn du dein Passwort änderst, werden alle Ende-zu-Ende-Verschlüsselungsschlüssel für alle deine Sitzungen zurückgesetzt, sodass der verschlüsselte Chat-Verlauf nicht mehr lesbar ist. Richte ein Schlüssel-Backup ein oder exportiere deine Raumschlüssel aus einer anderen Sitzung, bevor du dein Passwort zurücksetzst.", - "You have been logged out of all sessions and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "Sie wurden von allen Sitzungen abgemeldet und erhalten keine Push-Benachrichtigungen mehr. Um die Benachrichtigungen wieder zu aktivieren, melden Sie sich auf jedem Gerät erneut an.", - "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Aktualisieren Sie diese Sitzung, damit sie andere Sitzungen verifizieren kann, indem sie ihnen Zugang zu verschlüsselten Nachrichten gewährt und sie für andere Benutzer als vertrauenswürdig markiert.", + "You have been logged out of all sessions and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "Du wurdest von allen Sitzungen abgemeldet und erhälst keine Push-Benachrichtigungen mehr. Um die Benachrichtigungen wieder zu aktivieren, melde dich auf jedem Gerät erneut an.", + "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Aktualisiere diese Sitzung, damit sie andere Sitzungen verifizieren kann, indem sie dir Zugang zu verschlüsselten Nachrichten gewährt und sie für andere Benutzer als vertrauenswürdig markiert.", "Sign out and remove encryption keys?": "Abmelden und Verschlüsselungsschlüssel entfernen?", "Sign in to your Matrix account on ": "Melde dich bei deinem Matrix-Konto auf an", "Enter your password to sign in and regain access to your account.": "Gib dein Passwort ein, um dich anzumelden und wieder Zugang zu deinem Konto zu erhalten.", From 6fee3d8f4f2aa63615ddffef20a476b0c0776160 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 21 Sep 2020 18:37:08 -0600 Subject: [PATCH 116/155] Spread out the general user settings sections like the other tabs By design request. --- .../views/settings/tabs/user/_GeneralUserSettingsTab.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss index 6c9b89cf5a..8b73e69031 100644 --- a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss @@ -22,6 +22,13 @@ limitations under the License. margin-top: 0; } +// TODO: Make this selector less painful +.mx_GeneralUserSettingsTab_accountSection .mx_SettingsTab_subheading:nth-child(n + 1), +.mx_GeneralUserSettingsTab_discovery .mx_SettingsTab_subheading:nth-child(n + 2), +.mx_SetIdServer .mx_SettingsTab_subheading { + margin-top: 24px; +} + .mx_GeneralUserSettingsTab_accountSection .mx_Spinner, .mx_GeneralUserSettingsTab_discovery .mx_Spinner { // Move the spinner to the left side of the container (default center) From 4f983ad9a1fe84616971c4f2bebcbe1ed1ac514d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 21 Sep 2020 21:00:51 -0600 Subject: [PATCH 117/155] Rework profile sections of user and room settings Mostly by design request. Some is freehand, to be reviewed. --- res/css/views/settings/_AvatarSetting.scss | 87 +++++++++++++++++-- res/css/views/settings/_ProfileSettings.scss | 23 ++++- .../room_settings/RoomProfileSettings.js | 33 +++++-- .../views/settings/AvatarSetting.js | 56 +++++++----- .../views/settings/ProfileSettings.js | 43 +++++++-- .../tabs/user/GeneralUserSettingsTab.js | 1 - src/i18n/strings/en_EN.json | 4 +- 7 files changed, 200 insertions(+), 47 deletions(-) diff --git a/res/css/views/settings/_AvatarSetting.scss b/res/css/views/settings/_AvatarSetting.scss index eddcf9f55a..871b436ba6 100644 --- a/res/css/views/settings/_AvatarSetting.scss +++ b/res/css/views/settings/_AvatarSetting.scss @@ -1,5 +1,5 @@ /* -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,13 +15,55 @@ limitations under the License. */ .mx_AvatarSetting_avatar { - width: $font-88px; - height: $font-88px; - margin-left: 13px; + width: 90px; + height: 90px; + margin-top: 8px; position: relative; + .mx_AvatarSetting_hover { + transition: opacity 0.08s cubic-bezier(.46, .03, .52, .96); // quadratic + + // position to place the hover bg over the entire thing + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + pointer-events: none; // let the pointer fall through the underlying thing + + line-height: 90px; + text-align: center; + + > span { + color: #fff; // hardcoded to contrast with background + position: relative; // tricks the layout engine into putting this on top of the bg + font-weight: 500; + } + + .mx_AvatarSetting_hoverBg { + // absolute position to lazily fill the entire container + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + opacity: 0.5; + background-color: $settings-profile-overlay-placeholder-fg-color; + border-radius: 90px; + } + } + + &.mx_AvatarSetting_avatar_hovering .mx_AvatarSetting_hover { + opacity: 1; + } + + &:not(.mx_AvatarSetting_avatar_hovering) .mx_AvatarSetting_hover { + opacity: 0; + } + & > * { - width: $font-88px; box-sizing: border-box; } @@ -30,7 +72,7 @@ limitations under the License. } .mx_AccessibleButton.mx_AccessibleButton_kind_link_sm { - color: $button-danger-bg-color; + width: 100%; } & > img { @@ -41,8 +83,9 @@ limitations under the License. & > img, .mx_AvatarSetting_avatarPlaceholder { display: block; - height: $font-88px; - border-radius: 4px; + height: 90px; + border-radius: 90px; + cursor: pointer; } .mx_AvatarSetting_avatarPlaceholder::before { @@ -58,6 +101,34 @@ limitations under the License. left: 0; right: 0; } + + .mx_AvatarSetting_avatarPlaceholder ~ .mx_AvatarSetting_uploadButton { + border: 1px solid $settings-profile-overlay-placeholder-fg-color; + } + + .mx_AvatarSetting_uploadButton { + width: 32px; + height: 32px; + border-radius: 32px; + background-color: $settings-profile-placeholder-bg-color; + + position: absolute; + bottom: 0; + right: 0; + } + + .mx_AvatarSetting_uploadButton::before { + content: ""; + display: block; + width: 100%; + height: 100%; + mask-repeat: no-repeat; + mask-position: center; + mask-size: 55%; + background-color: $settings-profile-overlay-placeholder-fg-color; + mask-image: url('$(res)/img/feather-customised/edit.svg'); + } + } .mx_AvatarSetting_avatar .mx_AvatarSetting_avatarPlaceholder { diff --git a/res/css/views/settings/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss index 58624d1597..732cbedf02 100644 --- a/res/css/views/settings/_ProfileSettings.scss +++ b/res/css/views/settings/_ProfileSettings.scss @@ -1,5 +1,5 @@ /* -Copyright 2019 New Vector Ltd +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,6 +20,13 @@ limitations under the License. .mx_ProfileSettings_controls { flex-grow: 1; + margin-right: 54px; + + // We put the header under the controls with some minor styling to cheat + // alignment of the field with the avatar + .mx_SettingsTab_subheading { + margin-top: 0; + } } .mx_ProfileSettings_controls .mx_Field #profileTopic { @@ -41,3 +48,17 @@ limitations under the License. .mx_ProfileSettings_avatarUpload { display: none; } + +.mx_ProfileSettings_profileForm { + @mixin mx_Settings_fullWidthField; + border-bottom: 1px solid $menu-border-color; +} + +.mx_ProfileSettings_buttons { + margin-top: 10px; // 18px is already accounted for by the

    above the buttons + margin-bottom: 28px; + + > .mx_AccessibleButton_kind_link { + padding-left: 0; // to align with left side + } +} diff --git a/src/components/views/room_settings/RoomProfileSettings.js b/src/components/views/room_settings/RoomProfileSettings.js index f657fbf509..621c8b287e 100644 --- a/src/components/views/room_settings/RoomProfileSettings.js +++ b/src/components/views/room_settings/RoomProfileSettings.js @@ -75,6 +75,15 @@ export default class RoomProfileSettings extends React.Component { }); }; + _clearProfile = async (e) => { + e.stopPropagation(); + e.preventDefault(); + + if (!this.state.enableProfileSave) return; + this._removeAvatar(); + this.setState({enableProfileSave: false, displayName: this.state.originalDisplayName}); + }; + _saveProfile = async (e) => { e.stopPropagation(); e.preventDefault(); @@ -150,7 +159,11 @@ export default class RoomProfileSettings extends React.Component { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AvatarSetting = sdk.getComponent('settings.AvatarSetting'); return ( - +

    @@ -169,10 +182,20 @@ export default class RoomProfileSettings extends React.Component { uploadAvatar={this.state.canSetAvatar ? this._uploadAvatar : undefined} removeAvatar={this.state.canSetAvatar ? this._removeAvatar : undefined} />
    - - {_t("Save")} - +
    + + {_t("Cancel")} + + + {_t("Save")} + +
    ); } diff --git a/src/components/views/settings/AvatarSetting.js b/src/components/views/settings/AvatarSetting.js index 888d99ca49..580ebdcdad 100644 --- a/src/components/views/settings/AvatarSetting.js +++ b/src/components/views/settings/AvatarSetting.js @@ -14,25 +14,25 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {useCallback} from "react"; +import React, {useState} from "react"; import PropTypes from "prop-types"; - -import * as sdk from "../../../index"; import {_t} from "../../../languageHandler"; -import Modal from "../../../Modal"; +import AccessibleButton from "../elements/AccessibleButton"; +import classNames from "classnames"; const AvatarSetting = ({avatarUrl, avatarAltText, avatarName, uploadAvatar, removeAvatar}) => { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const [isHovering, setIsHovering] = useState(); + const hoveringProps = { + onMouseEnter: () => setIsHovering(true), + onMouseLeave: () => setIsHovering(false), + }; - const openImageView = useCallback(() => { - const ImageView = sdk.getComponent("elements.ImageView"); - Modal.createDialog(ImageView, { - src: avatarUrl, - name: avatarName, - }, "mx_Dialog_lightbox"); - }, [avatarUrl, avatarName]); - - let avatarElement =
    ; + let avatarElement = ; if (avatarUrl) { avatarElement = ( + onClick={uploadAvatar} + {...hoveringProps} + /> ); } let uploadAvatarBtn; if (uploadAvatar) { // insert an empty div to be the host for a css mask containing the upload.svg - uploadAvatarBtn = - {_t("Upload")} - ; + uploadAvatarBtn = ; } let removeAvatarBtn; @@ -59,10 +63,18 @@ const AvatarSetting = ({avatarUrl, avatarAltText, avatarName, uploadAvatar, remo ; } - return
    - { avatarElement } - { uploadAvatarBtn } - { removeAvatarBtn } + const avatarClasses = classNames({ + "mx_AvatarSetting_avatar": true, + "mx_AvatarSetting_avatar_hovering": isHovering, + }) + return
    + {avatarElement} +
    +
    + {_t("Upload")} +
    + {uploadAvatarBtn} + {removeAvatarBtn}
    ; }; diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.js index 5dbdcd4901..75c0fc0226 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.js @@ -65,6 +65,15 @@ export default class ProfileSettings extends React.Component { }); }; + _clearProfile = async (e) => { + e.stopPropagation(); + e.preventDefault(); + + if (!this.state.enableProfileSave) return; + this._removeAvatar(); + this.setState({enableProfileSave: false, displayName: this.state.originalDisplayName}); + }; + _saveProfile = async (e) => { e.stopPropagation(); e.preventDefault(); @@ -144,18 +153,26 @@ export default class ProfileSettings extends React.Component { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AvatarSetting = sdk.getComponent('settings.AvatarSetting'); return ( -
    +
    + {_t("Profile")} +

    {this.state.userId} {hostingSignup}

    -
    - - {_t("Save")} - +
    + + {_t("Cancel")} + + + {_t("Save")} + +
    ); } diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 54d4928d83..35285351ab 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -221,7 +221,6 @@ export default class GeneralUserSettingsTab extends React.Component { _renderProfileSection() { return (
    - {_t("Profile")}
    ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d7360430ae..22bba60ea4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -624,8 +624,8 @@ "From %(deviceName)s (%(deviceId)s)": "From %(deviceName)s (%(deviceId)s)", "Decline (%(counter)s)": "Decline (%(counter)s)", "Accept to continue:": "Accept to continue:", - "Upload": "Upload", "Remove": "Remove", + "Upload": "Upload", "This bridge was provisioned by .": "This bridge was provisioned by .", "This bridge is managed by .": "This bridge is managed by .", "Workspace: %(networkName)s": "Workspace: %(networkName)s", @@ -722,6 +722,7 @@ "On": "On", "Noisy": "Noisy", "Upgrade to your own domain": "Upgrade to your own domain", + "Profile": "Profile", "Display Name": "Display Name", "Profile picture": "Profile picture", "Save": "Save", @@ -822,7 +823,6 @@ "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", "Success": "Success", "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them": "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them", - "Profile": "Profile", "Email addresses": "Email addresses", "Phone numbers": "Phone numbers", "Set a new account password...": "Set a new account password...", From 3e0cbd7bfe0c55e7b5bc3dab7292774aa92545c3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 21 Sep 2020 21:09:46 -0600 Subject: [PATCH 118/155] Appease the linters --- res/css/views/settings/_AvatarSetting.scss | 1 - src/components/views/settings/AvatarSetting.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/res/css/views/settings/_AvatarSetting.scss b/res/css/views/settings/_AvatarSetting.scss index 871b436ba6..d2c0268a5c 100644 --- a/res/css/views/settings/_AvatarSetting.scss +++ b/res/css/views/settings/_AvatarSetting.scss @@ -128,7 +128,6 @@ limitations under the License. background-color: $settings-profile-overlay-placeholder-fg-color; mask-image: url('$(res)/img/feather-customised/edit.svg'); } - } .mx_AvatarSetting_avatar .mx_AvatarSetting_avatarPlaceholder { diff --git a/src/components/views/settings/AvatarSetting.js b/src/components/views/settings/AvatarSetting.js index 580ebdcdad..84effe3dc0 100644 --- a/src/components/views/settings/AvatarSetting.js +++ b/src/components/views/settings/AvatarSetting.js @@ -66,7 +66,7 @@ const AvatarSetting = ({avatarUrl, avatarAltText, avatarName, uploadAvatar, remo const avatarClasses = classNames({ "mx_AvatarSetting_avatar": true, "mx_AvatarSetting_avatar_hovering": isHovering, - }) + }); return
    {avatarElement}
    From 693dbab54e48f061ac715aa0932dea651780e5f6 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 22 Sep 2020 15:22:39 +0100 Subject: [PATCH 119/155] Add more types and enums --- src/utils/WellKnownUtils.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/utils/WellKnownUtils.ts b/src/utils/WellKnownUtils.ts index 6437d22cb3..69ed39e0ee 100644 --- a/src/utils/WellKnownUtils.ts +++ b/src/utils/WellKnownUtils.ts @@ -19,9 +19,13 @@ import {MatrixClientPeg} from '../MatrixClientPeg'; const E2EE_WK_KEY = "io.element.e2ee"; const E2EE_WK_KEY_DEPRECATED = "im.vector.riot.e2ee"; +/* eslint-disable camelcase */ export interface IE2EEWellKnown { default?: boolean; + secure_backup_required?: boolean; + secure_backup_setup_methods?: SecureBackupSetupMethod[]; } +/* eslint-enable camelcase */ export function getE2EEWellKnown(): IE2EEWellKnown { const clientWellKnown = MatrixClientPeg.get().getClientWellKnown(); @@ -39,18 +43,26 @@ export function isSecureBackupRequired(): boolean { return wellKnown && wellKnown["secure_backup_required"] === true; } -export function getSecureBackupSetupMethods(): string[] { +export enum SecureBackupSetupMethod { + Key = "key", + Passphrase = "passphrase", +} + +export function getSecureBackupSetupMethods(): SecureBackupSetupMethod[] { const wellKnown = getE2EEWellKnown(); if ( !wellKnown || !wellKnown["secure_backup_setup_methods"] || !wellKnown["secure_backup_setup_methods"].length || !( - wellKnown["secure_backup_setup_methods"].includes("key") || - wellKnown["secure_backup_setup_methods"].includes("passphrase") + wellKnown["secure_backup_setup_methods"].includes(SecureBackupSetupMethod.Key) || + wellKnown["secure_backup_setup_methods"].includes(SecureBackupSetupMethod.Passphrase) ) ) { - return ["key", "passphrase"]; + return [ + SecureBackupSetupMethod.Key, + SecureBackupSetupMethod.Passphrase, + ]; } return wellKnown["secure_backup_setup_methods"]; } From 06b616eb1923b4c8dc9a6e3ae5699ecf0b77a4cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Mon, 21 Sep 2020 17:53:32 +0000 Subject: [PATCH 120/155] Translated using Weblate (Estonian) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 2cba9908d1..981ee512c8 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -1930,7 +1930,7 @@ "'%(groupId)s' is not a valid community ID": "'%(groupId)s' ei ole korrektne kogukonna tunnus", "Direct message": "Otsevestlus", "Demote yourself?": "Kas vähendad enda õigusi?", - "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "Kuna sa vähendad enda õigusi, siis sul ei pruugi enam olla võimalik seda muutust tagasi pöörata. Kui sa juhtumisi oled viimane haldusõigustega kasutaja jututoas, siis hiljem on võimatu samu õigusi tagasi saada.", + "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "Kuna sa vähendad enda õigusi, siis sul ei pruugi hiljem olla võimalik seda muutust tagasi pöörata. Kui sa juhtumisi oled viimane haldusõigustega kasutaja jututoas, siis hiljem on võimatu samu õigusi tagasi saada.", "Demote": "Vähenda enda õigusi", "Ban": "Keela ligipääs", "Unban this user?": "Kas taastame selle kasutaja ligipääsu?", From 898b2540df09374511132ff924abe3f73fb8d296 Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Mon, 21 Sep 2020 23:04:50 +0000 Subject: [PATCH 121/155] Translated using Weblate (German) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index b21898a561..9ed6119873 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -818,7 +818,7 @@ "Share Room Message": "Teile Raumnachricht", "Link to selected message": "Link zur ausgewählten Nachricht", "COPY": "KOPIEREN", - "Share Message": "Teile Nachricht", + "Share Message": "Nachricht teilen", "No Audio Outputs detected": "Keine Ton-Ausgabe erkannt", "Audio Output": "Ton-Ausgabe", "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "In verschlüsselten Räumen, wie diesem, ist die Link-Vorschau standardmäßig deaktiviert damit dein Heimserver (auf dem die Vorschau erzeugt wird) keine Informationen über Links in diesem Raum bekommt.", From 81997a94a032a44b1497a001140a91a845afa545 Mon Sep 17 00:00:00 2001 From: random Date: Tue, 22 Sep 2020 14:48:44 +0000 Subject: [PATCH 122/155] Translated using Weblate (Italian) Currently translated at 100.0% (2369 of 2369 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 9730c055ec..dbcf51f926 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2496,5 +2496,18 @@ "Community and user menu": "Menu comunità e utente", "End Call": "Chiudi chiamata", "Remove the group call from the room?": "Rimuovere la chiamata di gruppo dalla stanza?", - "You don't have permission to remove the call from the room": "Non hai l'autorizzazione per rimuovere la chiamata dalla stanza" + "You don't have permission to remove the call from the room": "Non hai l'autorizzazione per rimuovere la chiamata dalla stanza", + "Safeguard against losing access to encrypted messages & data": "Proteggiti dalla perdita dei messaggi e dati crittografati", + "not found in storage": "non trovato nell'archivio", + "Widgets": "Widget", + "Edit widgets, bridges & bots": "Modifica widget, bridge e bot", + "Add widgets, bridges & bots": "Aggiungi widget, bridge e bot", + "You can only pin 2 widgets at a time": "Puoi fissare solo 2 widget alla volta", + "Minimize widget": "Riduci widget", + "Maximize widget": "Espandi widget", + "Your server requires encryption to be enabled in private rooms.": "Il tuo server richiede la crittografia attiva nelle stanze private.", + "Start a conversation with someone using their name or username (like ).": "Inizia una conversazione con qualcuno usando il suo nome o il nome utente (come ).", + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Ciò non lo inviterà in %(communityName)s. Per invitare qualcuno in %(communityName)s, clicca qui", + "Invite someone using their name, username (like ) or share this room.": "Invita qualcuno usando il suo nome, nome utente (come ) o condividi questa stanza.", + "Unable to set up keys": "Impossibile impostare le chiavi" } From 064ae187e29939f3b346031a83b9fcbce7bf9a82 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 22 Sep 2020 18:06:10 +0100 Subject: [PATCH 123/155] Upgrade sanitize-html, set nesting limit This uses the recently added option to allow specifying a nesting limit. Fixes https://github.com/vector-im/element-web/issues/15122 --- package.json | 2 +- src/@types/sanitize-html.ts | 23 ++++++++++++ src/HtmlUtils.tsx | 10 +++-- yarn.lock | 74 +++++++++++++++++++++++++++++++------ 4 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 src/@types/sanitize-html.ts diff --git a/package.json b/package.json index 156cbb1bc8..53b54cbb60 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "react-transition-group": "^4.4.1", "resize-observer-polyfill": "^1.5.1", "rfc4648": "^1.4.0", - "sanitize-html": "^1.27.1", + "sanitize-html": "github:apostrophecms/sanitize-html#3c7f93f2058f696f5359e3e58d464161647226db", "tar-js": "^0.3.0", "text-encoding-utf-8": "^1.0.2", "url": "^0.11.0", diff --git a/src/@types/sanitize-html.ts b/src/@types/sanitize-html.ts new file mode 100644 index 0000000000..188c8f9997 --- /dev/null +++ b/src/@types/sanitize-html.ts @@ -0,0 +1,23 @@ +/* +Copyright 2020 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import sanitizeHtml from 'sanitize-html'; + +export interface IExtendedSanitizeOptions extends sanitizeHtml.IOptions { + // This option only exists in 2.x RCs so far, so not yet present in the + // separate type definition module. + nestingLimit?: number; +} diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index bd314c2e5f..f991d2df5d 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -19,6 +19,7 @@ limitations under the License. import React from 'react'; import sanitizeHtml from 'sanitize-html'; +import { IExtendedSanitizeOptions } from './@types/sanitize-html'; import * as linkify from 'linkifyjs'; import linkifyMatrix from './linkify-matrix'; import _linkifyElement from 'linkifyjs/element'; @@ -151,7 +152,7 @@ export function isUrlPermitted(inputUrl: string) { } } -const transformTags: sanitizeHtml.IOptions["transformTags"] = { // custom to matrix +const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to matrix // add blank targets to all hyperlinks except vector URLs 'a': function(tagName: string, attribs: sanitizeHtml.Attributes) { if (attribs.href) { @@ -224,7 +225,7 @@ const transformTags: sanitizeHtml.IOptions["transformTags"] = { // custom to mat }, }; -const sanitizeHtmlParams: sanitizeHtml.IOptions = { +const sanitizeHtmlParams: IExtendedSanitizeOptions = { allowedTags: [ 'font', // custom to matrix for IRC-style font coloring 'del', // for markdown @@ -245,13 +246,14 @@ const sanitizeHtmlParams: sanitizeHtml.IOptions = { selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'], // URL schemes we permit allowedSchemes: PERMITTED_URL_SCHEMES, - allowProtocolRelative: false, transformTags, + // 50 levels deep "should be enough for anyone" + nestingLimit: 50, }; // this is the same as the above except with less rewriting -const composerSanitizeHtmlParams: sanitizeHtml.IOptions = { +const composerSanitizeHtmlParams: IExtendedSanitizeOptions = { ...sanitizeHtmlParams, transformTags: { 'code': transformTags['code'], diff --git a/yarn.lock b/yarn.lock index efc1f0eae1..ad1057cdcd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2726,6 +2726,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +colorette@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" + integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== + combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -3032,6 +3037,11 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + define-properties@^1.1.2, define-properties@^1.1.3, define-properties@~1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -3430,6 +3440,11 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + escodegen@^1.9.1: version "1.14.2" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.2.tgz#14ab71bf5026c2aa08173afba22c6f3173284a84" @@ -4964,6 +4979,11 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + is-regex@^1.0.3, is-regex@^1.0.4, is-regex@^1.0.5: version "1.1.0" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" @@ -5636,6 +5656,11 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +klona@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" + integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== + known-css-properties@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.11.0.tgz#0da784f115ea77c76b81536d7052e90ee6c86a8a" @@ -5686,6 +5711,14 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +line-column@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/line-column/-/line-column-1.0.2.tgz#d25af2936b6f4849172b312e4792d1d987bc34a2" + integrity sha1-0lryk2tvSEkXKzEuR5LR2Ye8NKI= + dependencies: + isarray "^1.0.0" + isobject "^2.0.0" + linkifyjs@^2.1.9: version "2.1.9" resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-2.1.9.tgz#af06e45a2866ff06c4766582590d098a4d584702" @@ -6093,6 +6126,11 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== +nanoid@^3.1.12: + version "3.1.12" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654" + integrity sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -6772,7 +6810,7 @@ postcss-value-parser@^4.1.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== -postcss@^7.0.1, postcss@^7.0.13, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.30, postcss@^7.0.6, postcss@^7.0.7: +postcss@^7.0.1, postcss@^7.0.13, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.30, postcss@^7.0.6, postcss@^7.0.7: version "7.0.32" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== @@ -6781,6 +6819,16 @@ postcss@^7.0.1, postcss@^7.0.13, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.2 source-map "^0.6.1" supports-color "^6.1.0" +postcss@^8.0.2: + version "8.0.7" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.0.7.tgz#764d26d8bc64a87af6d945238ae6ef36bf6fc32d" + integrity sha512-LTCMGOjmC/CGWV/azk3h34u6TNj1s9p4XleEiW8yA3j+8k+z3mnv5V7yyREvWDKlkel8GxqhjEZJ+JXWTzKPWw== + dependencies: + colorette "^1.2.1" + line-column "^1.0.2" + nanoid "^3.1.12" + source-map "^0.6.1" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -7617,15 +7665,17 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" -sanitize-html@^1.27.1: - version "1.27.1" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.27.1.tgz#ce147951aa3defba13448e2ca8a4e18d8f2e2cd7" - integrity sha512-C+N7E+7ikYaLHdb9lEkQaFOgmj+9ddZ311Ixs/QsBsoLD411/vdLweiFyGqrswUVgLqagOS5NCDxcEPH7trObQ== +"sanitize-html@github:apostrophecms/sanitize-html#3c7f93f2058f696f5359e3e58d464161647226db": + version "2.0.0-rc.3" + resolved "https://codeload.github.com/apostrophecms/sanitize-html/tar.gz/3c7f93f2058f696f5359e3e58d464161647226db" dependencies: + deepmerge "^4.2.2" + escape-string-regexp "^4.0.0" htmlparser2 "^4.1.0" - lodash "^4.17.15" - postcss "^7.0.27" - srcset "^2.0.1" + is-plain-object "^5.0.0" + klona "^2.0.3" + postcss "^8.0.2" + srcset "^3.0.0" sax@^1.2.4: version "1.2.4" @@ -7884,10 +7934,10 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -srcset@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/srcset/-/srcset-2.0.1.tgz#8f842d357487eb797f413d9c309de7a5149df5ac" - integrity sha512-00kZI87TdRKwt+P8jj8UZxbfp7mK2ufxcIMWvhAOZNJTRROimpHeruWrGvCZneiuVDLqdyHefVp748ECTnyUBQ== +srcset@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/srcset/-/srcset-3.0.0.tgz#8afd8b971362dfc129ae9c1a99b3897301ce6441" + integrity sha512-D59vF08Qzu/C4GAOXVgMTLfgryt5fyWo93FZyhEWANo0PokFz/iWdDe13mX3O5TRf6l8vMTqckAfR4zPiaH0yQ== sshpk@^1.7.0: version "1.16.1" From c3c3472ae4b343567372dc286aad9926e04992ea Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 23 Sep 2020 10:06:25 +0100 Subject: [PATCH 124/155] Fix copyright header --- src/@types/sanitize-html.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/@types/sanitize-html.ts b/src/@types/sanitize-html.ts index 188c8f9997..4cada29845 100644 --- a/src/@types/sanitize-html.ts +++ b/src/@types/sanitize-html.ts @@ -1,5 +1,5 @@ /* -Copyright 2020 New Vector Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 1a60f80407e5bcbc9572bd0326ee9ba7a5d24bcb Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 23 Sep 2020 02:14:07 +0000 Subject: [PATCH 125/155] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index fcdb7c9927..5fdf2b7d85 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2509,5 +2509,11 @@ "Minimize widget": "最小化小工具", "Maximize widget": "最大化小工具", "Your server requires encryption to be enabled in private rooms.": "您的伺服器需要在私人聊天室中啟用加密。", - "Unable to set up keys": "無法設定金鑰" + "Unable to set up keys": "無法設定金鑰", + "Use the Desktop app to see all encrypted files": "使用桌面應用程式以檢視所有加密的檔案", + "Use the Desktop app to search encrypted messages": "使用桌面應用程式以搜尋加密訊息", + "This version of %(brand)s does not support viewing some encrypted files": "此版本的 %(brand)s 不支援檢視某些加密檔案", + "This version of %(brand)s does not support searching encrypted messages": "此版本的 %(brand)s 不支援搜尋加密訊息", + "Cannot create rooms in this community": "無法在此社群中建立聊天室", + "You do not have permission to create rooms in this community.": "您沒有在此社群中建立聊天室的權限。" } From e69c5d49cada75626ae2c77cb5510762a7452a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 22 Sep 2020 16:28:45 +0000 Subject: [PATCH 126/155] Translated using Weblate (Estonian) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 981ee512c8..50517165d3 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2506,5 +2506,11 @@ "Minimize widget": "Vähenda vidinat", "Maximize widget": "Suurenda vidinat", "Your server requires encryption to be enabled in private rooms.": "Sinu koduserveri seadistused eeldavad, et mitteavalikud jututoad asutavad läbivat krüptimist.", - "Unable to set up keys": "Krüptovõtmete kasutuselevõtmine ei õnnestu" + "Unable to set up keys": "Krüptovõtmete kasutuselevõtmine ei õnnestu", + "Use the Desktop app to see all encrypted files": "Kõikide krüptitud failide vaatamiseks kasuta Element Desktop rakendust", + "Use the Desktop app to search encrypted messages": "Otsinguks krüptitud sõnumite hulgast kasuta Element Desktop rakendust", + "This version of %(brand)s does not support viewing some encrypted files": "See %(brand)s versioon ei toeta mõnede krüptitud failide vaatatamist", + "This version of %(brand)s does not support searching encrypted messages": "See %(brand)s versioon ei toeta otsingut krüptitud sõnumite seast", + "Cannot create rooms in this community": "Siia kogukonda ei saa jututubasid luua", + "You do not have permission to create rooms in this community.": "Sul pole õigusi luua siin kogukonnas uusi jututubasid." } From f8d46448be42264ac9e37861e4b042d7ef20a3ee Mon Sep 17 00:00:00 2001 From: XoseM Date: Wed, 23 Sep 2020 05:50:24 +0000 Subject: [PATCH 127/155] Translated using Weblate (Galician) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index b19cb28420..ab00d564d0 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2506,5 +2506,11 @@ "You can only pin 2 widgets at a time": "Só podes fixar 2 widgets ó mesmo tempo", "Minimize widget": "Minimizar widget", "Maximize widget": "Maximizar widget", - "Your server requires encryption to be enabled in private rooms.": "O servidor require que actives o cifrado nas salas privadas." + "Your server requires encryption to be enabled in private rooms.": "O servidor require que actives o cifrado nas salas privadas.", + "Use the Desktop app to see all encrypted files": "Usa a app de Escritorio para ver todos os ficheiros cifrados", + "Use the Desktop app to search encrypted messages": "Usa a app de Escritorio para buscar mensaxes cifradas", + "This version of %(brand)s does not support viewing some encrypted files": "Esta versión de %(brand)s non soporta o visionado dalgúns ficheiros cifrados", + "This version of %(brand)s does not support searching encrypted messages": "Esta versión de %(brand)s non soporta a busca de mensaxes cifradas", + "Cannot create rooms in this community": "Non se poden crear salas nesta comunidade", + "You do not have permission to create rooms in this community.": "Non tes permiso para crear salas nesta comunidade." } From 938371efa7ad947bdbc02aac1676a63264deb748 Mon Sep 17 00:00:00 2001 From: Marcelo Filho Date: Tue, 22 Sep 2020 16:35:52 +0000 Subject: [PATCH 128/155] Translated using Weblate (Portuguese (Brazil)) Currently translated at 95.2% (2261 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/pt_BR/ --- src/i18n/strings/pt_BR.json | 38 +++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index 3276952d28..8eee0c0909 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -167,7 +167,7 @@ "This email address was not found": "Este endereço de e-mail não foi encontrado", "The remote side failed to pick up": "A pessoa não atendeu a chamada", "This room is not recognised.": "Esta sala não é reconhecida.", - "This phone number is already in use": "Este número de telefone já está sendo usado", + "This phone number is already in use": "Este número de telefone já está em uso", "To use it, just wait for autocomplete results to load and tab through them.": "Para usar este recurso, aguarde o carregamento dos resultados de autocompletar e então escolha entre as opções.", "%(senderName)s unbanned %(targetName)s.": "%(senderName)s removeu o banimento de %(targetName)s.", "Unable to capture screen": "Não foi possível capturar a imagem da tela", @@ -2295,5 +2295,39 @@ "Send %(count)s invites|other": "Enviar %(count)s convites", "Send %(count)s invites|one": "Enviar %(count)s convite", "Community ID: +:%(domain)s": "ID da comunidade: +:%(domain)s", - "Enter name": "Digitar nome" + "Enter name": "Digitar nome", + "End Call": "Encerrar chamada", + "Remove the group call from the room?": "Remover esta chamada em grupo da sala?", + "You don't have permission to remove the call from the room": "Você não tem permissão para remover a chamada da sala", + "Group call modified by %(senderName)s": "Chamada em grupo modificada por %(senderName)s", + "Group call started by %(senderName)s": "Chamada em grupo iniciada por %(senderName)s", + "Group call ended by %(senderName)s": "Chamada em grupo encerrada por %(senderName)s", + "Unknown App": "App desconhecido", + "eg: @bot:* or example.org": "por exemplo: @bot:* ou exemplo.org", + "Privacy": "Privacidade", + "Room Info": "Informações da sala", + "Widgets": "Widgets", + "Unpin app": "Desafixar app", + "Edit widgets, bridges & bots": "Editar widgets, pontes & bots", + "Add widgets, bridges & bots": "Adicionar widgets, pontes & bots", + "%(count)s people|other": "%(count)s pessoas", + "%(count)s people|one": "%(count)s pessoa", + "Show files": "Mostrar arquivos", + "Room settings": "Configurações da sala", + "Almost there! Is your other session showing the same shield?": "Quase lá! A sua outra sessão está mostrando o mesmo escudo?", + "Take a picture": "Tirar uma foto", + "Minimize widget": "Minimizar widget", + "Maximize widget": "Maximizar widget", + "Use the Desktop app to see all encrypted files": "Use o app para Computador para ver todos os arquivos criptografados", + "Use the Desktop app to search encrypted messages": "Use o app para Computador para buscar mensagens criptografadas", + "This version of %(brand)s does not support viewing some encrypted files": "Esta versão do %(brand)s não permite visualizar alguns arquivos criptografados", + "This version of %(brand)s does not support searching encrypted messages": "Esta versão do %(brand)s não permite buscar mensagens criptografadas", + "Information": "Informação", + "Add another email": "Adicionar outro e-mail", + "Invite people to join %(communityName)s": "Convidar pessoas para entrarem em %(communityName)s", + "There was an error creating your community. The name may be taken or the server is unable to process your request.": "Houve um erro ao criar sua comunidade. Ou o nome dela pode já estar em uso, ou o servidor não foi capaz de processar a sua solicitação.", + "What's the name of your community or team?": "Qual é o nome da sua comunidade ou equipe?", + "Add image (optional)": "Adicionar foto (opcional)", + "An image will help people identify your community.": "Uma foto ajudará as pessoas identificarem a sua comunidade.", + "Preview": "Visualizar" } From 05dac6da3c77a62154d672d150f93047c3187e9a Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Tue, 22 Sep 2020 20:05:45 +0000 Subject: [PATCH 129/155] Translated using Weblate (Russian) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 8ac246af4b..946e8a5c89 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2502,5 +2502,11 @@ "Start a conversation with someone using their name or username (like ).": "Начните разговор с кем-нибудь, используя его имя или имя пользователя (например, ).", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Это не пригласит их в %(communityName)s. Чтобы пригласить кого-нибудь в %(communityName)s, нажмите здесь", "Invite someone using their name, username (like ) or share this room.": "Пригласите кого-нибудь, используя его имя, имя пользователя (например, ) или поделитесь этой комнатой.", - "Unable to set up keys": "Невозможно настроить ключи" + "Unable to set up keys": "Невозможно настроить ключи", + "Use the Desktop app to see all encrypted files": "Используйте настольное приложение, чтобы просмотреть все зашифрованные файлы", + "Use the Desktop app to search encrypted messages": "Используйте настольное приложение для поиска зашифрованных сообщений", + "This version of %(brand)s does not support viewing some encrypted files": "Эта версия %(brand)s не поддерживает просмотр некоторых зашифрованных файлов", + "This version of %(brand)s does not support searching encrypted messages": "Эта версия %(brand)s не поддерживает поиск зашифрованных сообщений", + "Cannot create rooms in this community": "Невозможно создать комнаты в этом сообществе", + "You do not have permission to create rooms in this community.": "У вас нет разрешения на создание комнат в этом сообществе." } From 91617ae0eb723a8744e27174badd77628026edfe Mon Sep 17 00:00:00 2001 From: random Date: Wed, 23 Sep 2020 12:51:20 +0000 Subject: [PATCH 130/155] Translated using Weblate (Italian) Currently translated at 100.0% (2375 of 2375 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index dbcf51f926..3deba88144 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2509,5 +2509,11 @@ "Start a conversation with someone using their name or username (like ).": "Inizia una conversazione con qualcuno usando il suo nome o il nome utente (come ).", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Ciò non lo inviterà in %(communityName)s. Per invitare qualcuno in %(communityName)s, clicca qui", "Invite someone using their name, username (like ) or share this room.": "Invita qualcuno usando il suo nome, nome utente (come ) o condividi questa stanza.", - "Unable to set up keys": "Impossibile impostare le chiavi" + "Unable to set up keys": "Impossibile impostare le chiavi", + "Use the Desktop app to see all encrypted files": "Usa l'app desktop per vedere tutti i file cifrati", + "Use the Desktop app to search encrypted messages": "Usa l'app desktop per cercare i messaggi cifrati", + "This version of %(brand)s does not support viewing some encrypted files": "Questa versione di %(brand)s non supporta la visualizzazione di alcuni file cifrati", + "This version of %(brand)s does not support searching encrypted messages": "Questa versione di %(brand)s non supporta la ricerca di messaggi cifrati", + "Cannot create rooms in this community": "Impossibile creare stanze in questa comunità", + "You do not have permission to create rooms in this community.": "Non hai i permessi per creare stanze in questa comunità." } From ca4b11ec6a4d94fb5fa546edd1431c795d1ddee5 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 23 Sep 2020 15:27:40 +0100 Subject: [PATCH 131/155] Upgrade matrix-js-sdk to 8.4.0-rc.1 --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 53b54cbb60..446810e058 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "is-ip": "^2.0.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.19", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "8.4.0-rc.1", "minimist": "^1.2.5", "pako": "^1.0.11", "parse5": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index ad1057cdcd..fd97a1c854 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5919,9 +5919,10 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "8.3.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b9886d4f3479c041fc6d91ebc88c4a998e9d2e7c" +matrix-js-sdk@8.4.0-rc.1: + version "8.4.0-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-8.4.0-rc.1.tgz#9547e6d0088ec22fc6463c3144aee8c03266c215" + integrity sha512-u5I8OesrGePVj+NoZByXwV4QBujrMPb4BlKWII4VscvVitLoD/iuz9beNvic3esNF8U3ruWVDcOwA0XQIoumQQ== dependencies: "@babel/runtime" "^7.8.3" another-json "^0.2.0" From 65923c3c55fb763ae89dc9f226a7218f94c75368 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 23 Sep 2020 15:32:46 +0100 Subject: [PATCH 132/155] Prepare changelog for v3.5.0-rc.1 --- CHANGELOG.md | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fa9cc29f9..03d066be99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,115 @@ +Changes in [3.5.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.5.0-rc.1) (2020-09-23) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.4.1...v3.5.0-rc.1) + + * Upgrade JS SDK to 8.4.0-rc.1 + * Update from Weblate + [\#5246](https://github.com/matrix-org/matrix-react-sdk/pull/5246) + * Upgrade sanitize-html, set nesting limit + [\#5245](https://github.com/matrix-org/matrix-react-sdk/pull/5245) + * Add a note to use the desktop builds when seshat isn't available + [\#5225](https://github.com/matrix-org/matrix-react-sdk/pull/5225) + * Add some permission checks to the communities v2 prototype + [\#5240](https://github.com/matrix-org/matrix-react-sdk/pull/5240) + * Support HS-preferred Secure Backup setup methods + [\#5242](https://github.com/matrix-org/matrix-react-sdk/pull/5242) + * Only show User Info verify button if the other user has e2ee devices + [\#5234](https://github.com/matrix-org/matrix-react-sdk/pull/5234) + * Fix New Room List arrow key management + [\#5237](https://github.com/matrix-org/matrix-react-sdk/pull/5237) + * Fix Room Directory View & Preview actions for federated joins + [\#5235](https://github.com/matrix-org/matrix-react-sdk/pull/5235) + * Add a UI feature to disable advanced encryption options + [\#5238](https://github.com/matrix-org/matrix-react-sdk/pull/5238) + * UI Feature Flag: Communities + [\#5216](https://github.com/matrix-org/matrix-react-sdk/pull/5216) + * Rename apps back to widgets + [\#5236](https://github.com/matrix-org/matrix-react-sdk/pull/5236) + * Adjust layout and formatting of notifications / files cards + [\#5229](https://github.com/matrix-org/matrix-react-sdk/pull/5229) + * Fix Search Results Tile undefined variable access regression + [\#5232](https://github.com/matrix-org/matrix-react-sdk/pull/5232) + * Fix Cmd/Ctrl+Shift+U for File Upload + [\#5233](https://github.com/matrix-org/matrix-react-sdk/pull/5233) + * Disable the e2ee toggle when creating a room on a server with forced e2e + [\#5231](https://github.com/matrix-org/matrix-react-sdk/pull/5231) + * UI Feature Flag: Disable advanced options and tidy up some copy + [\#5215](https://github.com/matrix-org/matrix-react-sdk/pull/5215) + * UI Feature Flag: 3PIDs + [\#5228](https://github.com/matrix-org/matrix-react-sdk/pull/5228) + * Defer encryption setup until first E2EE room + [\#5219](https://github.com/matrix-org/matrix-react-sdk/pull/5219) + * Tidy devDeps, all the webpack stuff lives in the layer above + [\#5179](https://github.com/matrix-org/matrix-react-sdk/pull/5179) + * UI Feature Flag: Hide flair + [\#5214](https://github.com/matrix-org/matrix-react-sdk/pull/5214) + * UI Feature Flag: Identity server + [\#5218](https://github.com/matrix-org/matrix-react-sdk/pull/5218) + * UI Feature Flag: Share dialog QR code and social icons + [\#5221](https://github.com/matrix-org/matrix-react-sdk/pull/5221) + * UI Feature Flag: Registration, Password Reset, Deactivate + [\#5227](https://github.com/matrix-org/matrix-react-sdk/pull/5227) + * Retry joinRoom up to 5 times in the case of a 504 GATEWAY TIMEOUT + [\#5204](https://github.com/matrix-org/matrix-react-sdk/pull/5204) + * UI Feature Flag: Disable VoIP + [\#5217](https://github.com/matrix-org/matrix-react-sdk/pull/5217) + * Fix setState() usage in the constructor of RoomDirectory + [\#5224](https://github.com/matrix-org/matrix-react-sdk/pull/5224) + * Hide Analytics sections if piwik config is not provided + [\#5211](https://github.com/matrix-org/matrix-react-sdk/pull/5211) + * UI Feature Flag: Disable feedback button + [\#5213](https://github.com/matrix-org/matrix-react-sdk/pull/5213) + * Clean up UserInfo to not show a blank Power Selector for users not in room + [\#5220](https://github.com/matrix-org/matrix-react-sdk/pull/5220) + * Also hide bug reporting prompts from the Error Boundaries + [\#5212](https://github.com/matrix-org/matrix-react-sdk/pull/5212) + * Tactical improvements to 3PID invites + [\#5201](https://github.com/matrix-org/matrix-react-sdk/pull/5201) + * If no bug_report_endpoint_url, hide rageshaking from the App + [\#5210](https://github.com/matrix-org/matrix-react-sdk/pull/5210) + * Introduce a concept of UI features, using it for URL previews at first + [\#5208](https://github.com/matrix-org/matrix-react-sdk/pull/5208) + * Remove defunct "always show encryption icons" setting + [\#5207](https://github.com/matrix-org/matrix-react-sdk/pull/5207) + * Don't show Notifications Prompt Toast if user has master rule enabled + [\#5203](https://github.com/matrix-org/matrix-react-sdk/pull/5203) + * Fix Bridges tab crashing when the room does not have bridges + [\#5206](https://github.com/matrix-org/matrix-react-sdk/pull/5206) + * Don't count widgets which no longer exist towards pinned count + [\#5202](https://github.com/matrix-org/matrix-react-sdk/pull/5202) + * Fix crashes with cannot read isResizing of undefined + [\#5205](https://github.com/matrix-org/matrix-react-sdk/pull/5205) + * Prompt to remove the jitsi widget when pressing the call button + [\#5193](https://github.com/matrix-org/matrix-react-sdk/pull/5193) + * Show verification status in the room summary card + [\#5195](https://github.com/matrix-org/matrix-react-sdk/pull/5195) + * Fix user info scrolling in new card view + [\#5198](https://github.com/matrix-org/matrix-react-sdk/pull/5198) + * Fix sticker picker height + [\#5197](https://github.com/matrix-org/matrix-react-sdk/pull/5197) + * Call jitsi widgets 'group calls' + [\#5191](https://github.com/matrix-org/matrix-react-sdk/pull/5191) + * Don't show 'unpin' for persistent widgets + [\#5194](https://github.com/matrix-org/matrix-react-sdk/pull/5194) + * Split up cross-signing and secure backup settings + [\#5182](https://github.com/matrix-org/matrix-react-sdk/pull/5182) + * Fix onNewScreen to use replace when going from roomId->roomAlias + [\#5185](https://github.com/matrix-org/matrix-react-sdk/pull/5185) + * bring back 1.2M style badge counts rather than 99+ + [\#5192](https://github.com/matrix-org/matrix-react-sdk/pull/5192) + * Run the rageshake command through the bug report dialog + [\#5189](https://github.com/matrix-org/matrix-react-sdk/pull/5189) + * Account for via in pill matching regex + [\#5188](https://github.com/matrix-org/matrix-react-sdk/pull/5188) + * Remove now-unused create-react-class from lockfile + [\#5187](https://github.com/matrix-org/matrix-react-sdk/pull/5187) + * Fixed 1px jump upwards + [\#5163](https://github.com/matrix-org/matrix-react-sdk/pull/5163) + * Always allow widgets when using the local version + [\#5184](https://github.com/matrix-org/matrix-react-sdk/pull/5184) + * Migrate RoomView and RoomContext to Typescript + [\#5175](https://github.com/matrix-org/matrix-react-sdk/pull/5175) + Changes in [3.4.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.4.1) (2020-09-14) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.4.0...v3.4.1) From 9ac3af4176f46558e2458a0b37d82654efe751f0 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 23 Sep 2020 15:32:47 +0100 Subject: [PATCH 133/155] v3.5.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 446810e058..f19c247d0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.4.1", + "version": "3.5.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 1ab2e06887e6c1bb093279cdf44f68561cccdc17 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 23 Sep 2020 12:29:49 -0600 Subject: [PATCH 134/155] Clean up unused variables --- res/themes/dark/css/_dark.scss | 5 +---- res/themes/legacy-dark/css/_legacy-dark.scss | 3 --- res/themes/legacy-light/css/_legacy-light.scss | 3 --- res/themes/light/css/_light.scss | 5 +---- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index a3b03c777e..bb494811d4 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -87,10 +87,7 @@ $dialog-background-bg-color: $header-panel-bg-color; $lightbox-background-bg-color: #000; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: #e7e7e7; -$settings-profile-overlay-bg-color: #000; -$settings-profile-overlay-placeholder-bg-color: transparent; -$settings-profile-overlay-fg-color: #fff; +$settings-profile-placeholder-bg-color: #21262c; $settings-profile-overlay-placeholder-fg-color: #454545; $settings-subsection-fg-color: $text-secondary-color; diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 2741dcebf8..b49e014e70 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -86,9 +86,6 @@ $lightbox-background-bg-color: #000; $settings-grey-fg-color: #a2a2a2; $settings-profile-placeholder-bg-color: #e7e7e7; -$settings-profile-overlay-bg-color: #000; -$settings-profile-overlay-placeholder-bg-color: transparent; -$settings-profile-overlay-fg-color: #fff; $settings-profile-overlay-placeholder-fg-color: #454545; $settings-subsection-fg-color: $text-secondary-color; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 4fd2a3615b..9261dc8240 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -144,9 +144,6 @@ $blockquote-fg-color: #777; $settings-grey-fg-color: #a2a2a2; $settings-profile-placeholder-bg-color: #e7e7e7; -$settings-profile-overlay-bg-color: #000; -$settings-profile-overlay-placeholder-bg-color: transparent; -$settings-profile-overlay-fg-color: #fff; $settings-profile-overlay-placeholder-fg-color: #2e2f32; $settings-subsection-fg-color: #61708b; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 05302a2a80..8dd21b74ec 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -137,10 +137,7 @@ $blockquote-bar-color: #ddd; $blockquote-fg-color: #777; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: #e7e7e7; -$settings-profile-overlay-bg-color: #000; -$settings-profile-overlay-placeholder-bg-color: transparent; -$settings-profile-overlay-fg-color: #fff; +$settings-profile-placeholder-bg-color: #f4f6fa; $settings-profile-overlay-placeholder-fg-color: #2e2f32; $settings-subsection-fg-color: #61708b; From e658d9619b3896a43f9395952a5cce3349f41344 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 23 Sep 2020 12:30:01 -0600 Subject: [PATCH 135/155] Update styles for new colours --- res/css/views/settings/_AvatarSetting.scss | 8 ++------ res/themes/dark/css/_dark.scss | 2 ++ res/themes/legacy-dark/css/_legacy-dark.scss | 2 ++ res/themes/legacy-light/css/_legacy-light.scss | 2 ++ res/themes/light/css/_light.scss | 2 ++ 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/res/css/views/settings/_AvatarSetting.scss b/res/css/views/settings/_AvatarSetting.scss index d2c0268a5c..3576b09888 100644 --- a/res/css/views/settings/_AvatarSetting.scss +++ b/res/css/views/settings/_AvatarSetting.scss @@ -102,15 +102,11 @@ limitations under the License. right: 0; } - .mx_AvatarSetting_avatarPlaceholder ~ .mx_AvatarSetting_uploadButton { - border: 1px solid $settings-profile-overlay-placeholder-fg-color; - } - .mx_AvatarSetting_uploadButton { width: 32px; height: 32px; border-radius: 32px; - background-color: $settings-profile-placeholder-bg-color; + background-color: $settings-profile-button-bg-color; position: absolute; bottom: 0; @@ -125,7 +121,7 @@ limitations under the License. mask-repeat: no-repeat; mask-position: center; mask-size: 55%; - background-color: $settings-profile-overlay-placeholder-fg-color; + background-color: $settings-profile-button-fg-color; mask-image: url('$(res)/img/feather-customised/edit.svg'); } } diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index bb494811d4..331b5f4692 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -89,6 +89,8 @@ $lightbox-background-bg-color: #000; $settings-grey-fg-color: #a2a2a2; $settings-profile-placeholder-bg-color: #21262c; $settings-profile-overlay-placeholder-fg-color: #454545; +$settings-profile-button-bg-color: #e7e7e7; +$settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; $settings-subsection-fg-color: $text-secondary-color; $topleftmenu-color: $text-primary-color; diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index b49e014e70..14ce264bc0 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -87,6 +87,8 @@ $lightbox-background-bg-color: #000; $settings-grey-fg-color: #a2a2a2; $settings-profile-placeholder-bg-color: #e7e7e7; $settings-profile-overlay-placeholder-fg-color: #454545; +$settings-profile-button-bg-color: #e7e7e7; +$settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; $settings-subsection-fg-color: $text-secondary-color; $topleftmenu-color: $text-primary-color; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 9261dc8240..b030fb7423 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -145,6 +145,8 @@ $blockquote-fg-color: #777; $settings-grey-fg-color: #a2a2a2; $settings-profile-placeholder-bg-color: #e7e7e7; $settings-profile-overlay-placeholder-fg-color: #2e2f32; +$settings-profile-button-bg-color: #e7e7e7; +$settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; $settings-subsection-fg-color: #61708b; $voip-decline-color: #f48080; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 8dd21b74ec..140783212d 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -139,6 +139,8 @@ $blockquote-fg-color: #777; $settings-grey-fg-color: #a2a2a2; $settings-profile-placeholder-bg-color: #f4f6fa; $settings-profile-overlay-placeholder-fg-color: #2e2f32; +$settings-profile-button-bg-color: #e7e7e7; +$settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; $settings-subsection-fg-color: #61708b; $voip-decline-color: #f48080; From 5f6dec7d189e93bdc7ed15d0ff9895b89e34cea3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 24 Sep 2020 10:09:34 +0100 Subject: [PATCH 136/155] add comments Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- __mocks__/browser-request.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/__mocks__/browser-request.js b/__mocks__/browser-request.js index 391be7c54f..4c59e8a43a 100644 --- a/__mocks__/browser-request.js +++ b/__mocks__/browser-request.js @@ -1,6 +1,10 @@ const en = require("../src/i18n/strings/en_EN"); const de = require("../src/i18n/strings/de_DE"); +// Mock the browser-request for the languageHandler tests to return +// Fake languages.json containing references to en_EN and de_DE +// en_EN.json +// de_DE.json module.exports = jest.fn((opts, cb) => { const url = opts.url || opts.uri; if (url && url.endsWith("languages.json")) { From aa0e19daf03509b6aa598c19f5e83c50b03604a8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 24 Sep 2020 08:23:06 -0600 Subject: [PATCH 137/155] Make the hover transition a variable --- res/css/_common.scss | 2 ++ res/css/views/settings/_AvatarSetting.scss | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index a22d77f3d3..aafd6e5297 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -18,6 +18,8 @@ limitations under the License. @import "./_font-sizes.scss"; +$hover-transition: 0.08s cubic-bezier(.46, .03, .52, .96); // quadratic + :root { font-size: 10px; } diff --git a/res/css/views/settings/_AvatarSetting.scss b/res/css/views/settings/_AvatarSetting.scss index 3576b09888..52a0ee95d7 100644 --- a/res/css/views/settings/_AvatarSetting.scss +++ b/res/css/views/settings/_AvatarSetting.scss @@ -21,7 +21,7 @@ limitations under the License. position: relative; .mx_AvatarSetting_hover { - transition: opacity 0.08s cubic-bezier(.46, .03, .52, .96); // quadratic + transition: opacity $hover-transition; // position to place the hover bg over the entire thing position: absolute; From a2860e698a333c7d03cb84a91b04ac545d6fe0f1 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 24 Sep 2020 08:26:59 -0600 Subject: [PATCH 138/155] Clean up other unlinted lint issues --- .../views/room_settings/RoomProfileSettings.js | 9 ++++++--- src/components/views/settings/AvatarSetting.js | 2 +- src/components/views/settings/ProfileSettings.js | 9 ++++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/views/room_settings/RoomProfileSettings.js b/src/components/views/room_settings/RoomProfileSettings.js index 621c8b287e..ca09c3093a 100644 --- a/src/components/views/room_settings/RoomProfileSettings.js +++ b/src/components/views/room_settings/RoomProfileSettings.js @@ -161,7 +161,8 @@ export default class RoomProfileSettings extends React.Component { return (
    {_t("Cancel")} {_t("Save")} diff --git a/src/components/views/settings/AvatarSetting.js b/src/components/views/settings/AvatarSetting.js index 84effe3dc0..487c752c38 100644 --- a/src/components/views/settings/AvatarSetting.js +++ b/src/components/views/settings/AvatarSetting.js @@ -21,7 +21,7 @@ import AccessibleButton from "../elements/AccessibleButton"; import classNames from "classnames"; const AvatarSetting = ({avatarUrl, avatarAltText, avatarName, uploadAvatar, removeAvatar}) => { - const [isHovering, setIsHovering] = useState(); + const [isHovering, setIsHovering] = useState(false); const hoveringProps = { onMouseEnter: () => setIsHovering(true), onMouseLeave: () => setIsHovering(false), diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.js index 75c0fc0226..651aa9f48d 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.js @@ -155,7 +155,8 @@ export default class ProfileSettings extends React.Component { return (
    {_t("Cancel")} {_t("Save")} From 8962f7ae9eba8ce59e5962910b884d3f573a2b82 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 24 Sep 2020 16:16:20 +0100 Subject: [PATCH 139/155] Convert CallHandler to typescript and remove the old conference call stuff while we're at it: enough time should have passed since those mistakes that we can move on. The old conference call rooms will still appear for anyone whose account dates back to that time, but they've presumably been appearing in any other matrix client they used too. --- src/@types/global.d.ts | 3 + src/CallHandler.js | 526 ------------------ src/CallHandler.tsx | 482 ++++++++++++++++ src/Rooms.js | 52 -- src/TextForEvent.js | 18 +- src/VectorConferenceHandler.js | 135 ----- src/components/structures/LoggedInView.tsx | 2 - src/components/structures/MatrixChat.tsx | 1 - src/components/structures/RoomView.tsx | 41 +- src/components/views/rooms/AuxPanel.js | 37 -- src/components/views/rooms/MemberList.js | 6 - src/components/views/rooms/MessageComposer.js | 2 +- src/components/views/voip/CallContainer.tsx | 3 +- src/components/views/voip/CallPreview.tsx | 13 +- src/components/views/voip/CallView.tsx | 33 +- src/components/views/voip/IncomingCallBox.tsx | 2 +- 16 files changed, 509 insertions(+), 847 deletions(-) delete mode 100644 src/CallHandler.js create mode 100644 src/CallHandler.tsx delete mode 100644 src/VectorConferenceHandler.js diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index e1111a8a94..c58dca0a09 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -30,6 +30,7 @@ import {Notifier} from "../Notifier"; import type {Renderer} from "react-dom"; import RightPanelStore from "../stores/RightPanelStore"; import WidgetStore from "../stores/WidgetStore"; +import CallHandler from "../CallHandler"; declare global { interface Window { @@ -53,6 +54,7 @@ declare global { mxNotifier: typeof Notifier; mxRightPanelStore: RightPanelStore; mxWidgetStore: WidgetStore; + mxCallHandler: CallHandler; } interface Document { @@ -62,6 +64,7 @@ declare global { interface Navigator { userLanguage?: string; + mediaSession: any; } interface StorageEstimate { diff --git a/src/CallHandler.js b/src/CallHandler.js deleted file mode 100644 index ad40332af5..0000000000 --- a/src/CallHandler.js +++ /dev/null @@ -1,526 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017, 2018 New Vector Ltd -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/* - * Manages a list of all the currently active calls. - * - * This handler dispatches when voip calls are added/updated/removed from this list: - * { - * action: 'call_state' - * room_id: - * } - * - * To know the state of the call, this handler exposes a getter to - * obtain the call for a room: - * var call = CallHandler.getCall(roomId) - * var state = call.call_state; // ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing - * - * This handler listens for and handles the following actions: - * { - * action: 'place_call', - * type: 'voice|video', - * room_id: - * } - * - * { - * action: 'incoming_call' - * call: MatrixCall - * } - * - * { - * action: 'hangup' - * room_id: - * } - * - * { - * action: 'answer' - * room_id: - * } - */ - -import {MatrixClientPeg} from './MatrixClientPeg'; -import PlatformPeg from './PlatformPeg'; -import Modal from './Modal'; -import { _t } from './languageHandler'; -import Matrix from 'matrix-js-sdk'; -import dis from './dispatcher/dispatcher'; -import WidgetUtils from './utils/WidgetUtils'; -import WidgetEchoStore from './stores/WidgetEchoStore'; -import SettingsStore from './settings/SettingsStore'; -import {generateHumanReadableId} from "./utils/NamingUtils"; -import {Jitsi} from "./widgets/Jitsi"; -import {WidgetType} from "./widgets/WidgetType"; -import {SettingLevel} from "./settings/SettingLevel"; -import {base32} from "rfc4648"; - -import QuestionDialog from "./components/views/dialogs/QuestionDialog"; -import ErrorDialog from "./components/views/dialogs/ErrorDialog"; - -global.mxCalls = { - //room_id: MatrixCall -}; -const calls = global.mxCalls; -let ConferenceHandler = null; - -const audioPromises = {}; - -function play(audioId) { - // TODO: Attach an invisible element for this instead - // which listens? - const audio = document.getElementById(audioId); - if (audio) { - const playAudio = async () => { - try { - // This still causes the chrome debugger to break on promise rejection if - // the promise is rejected, even though we're catching the exception. - await audio.play(); - } catch (e) { - // This is usually because the user hasn't interacted with the document, - // or chrome doesn't think so and is denying the request. Not sure what - // we can really do here... - // https://github.com/vector-im/element-web/issues/7657 - console.log("Unable to play audio clip", e); - } - }; - if (audioPromises[audioId]) { - audioPromises[audioId] = audioPromises[audioId].then(()=>{ - audio.load(); - return playAudio(); - }); - } else { - audioPromises[audioId] = playAudio(); - } - } -} - -function pause(audioId) { - // TODO: Attach an invisible element for this instead - // which listens? - const audio = document.getElementById(audioId); - if (audio) { - if (audioPromises[audioId]) { - audioPromises[audioId] = audioPromises[audioId].then(()=>audio.pause()); - } else { - // pause doesn't actually return a promise, but might as well do this for symmetry with play(); - audioPromises[audioId] = audio.pause(); - } - } -} - -function _setCallListeners(call) { - call.on("error", function(err) { - console.error("Call error:", err); - if ( - MatrixClientPeg.get().getTurnServers().length === 0 && - SettingsStore.getValue("fallbackICEServerAllowed") === null - ) { - _showICEFallbackPrompt(); - return; - } - - Modal.createTrackedDialog('Call Failed', '', ErrorDialog, { - title: _t('Call Failed'), - description: err.message, - }); - }); - call.on("hangup", function() { - _setCallState(undefined, call.roomId, "ended"); - }); - // map web rtc states to dummy UI state - // ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing - call.on("state", function(newState, oldState) { - if (newState === "ringing") { - _setCallState(call, call.roomId, "ringing"); - pause("ringbackAudio"); - } else if (newState === "invite_sent") { - _setCallState(call, call.roomId, "ringback"); - play("ringbackAudio"); - } else if (newState === "ended" && oldState === "connected") { - _setCallState(undefined, call.roomId, "ended"); - pause("ringbackAudio"); - play("callendAudio"); - } else if (newState === "ended" && oldState === "invite_sent" && - (call.hangupParty === "remote" || - (call.hangupParty === "local" && call.hangupReason === "invite_timeout") - )) { - _setCallState(call, call.roomId, "busy"); - pause("ringbackAudio"); - play("busyAudio"); - Modal.createTrackedDialog('Call Handler', 'Call Timeout', ErrorDialog, { - title: _t('Call Timeout'), - description: _t('The remote side failed to pick up') + '.', - }); - } else if (oldState === "invite_sent") { - _setCallState(call, call.roomId, "stop_ringback"); - pause("ringbackAudio"); - } else if (oldState === "ringing") { - _setCallState(call, call.roomId, "stop_ringing"); - pause("ringbackAudio"); - } else if (newState === "connected") { - _setCallState(call, call.roomId, "connected"); - pause("ringbackAudio"); - } - }); -} - -function _setCallState(call, roomId, status) { - console.log( - `Call state in ${roomId} changed to ${status} (${call ? call.call_state : "-"})`, - ); - calls[roomId] = call; - - if (status === "ringing") { - play("ringAudio"); - } else if (call && call.call_state === "ringing") { - pause("ringAudio"); - } - - if (call) { - call.call_state = status; - } - dis.dispatch({ - action: 'call_state', - room_id: roomId, - state: status, - }); -} - -function _showICEFallbackPrompt() { - const cli = MatrixClientPeg.get(); - const code = sub => {sub}; - Modal.createTrackedDialog('No TURN servers', '', QuestionDialog, { - title: _t("Call failed due to misconfigured server"), - description:
    -

    {_t( - "Please ask the administrator of your homeserver " + - "(%(homeserverDomain)s) to configure a TURN server in " + - "order for calls to work reliably.", - { homeserverDomain: cli.getDomain() }, { code }, - )}

    -

    {_t( - "Alternatively, you can try to use the public server at " + - "turn.matrix.org, but this will not be as reliable, and " + - "it will share your IP address with that server. You can also manage " + - "this in Settings.", - null, { code }, - )}

    -
    , - button: _t('Try using turn.matrix.org'), - cancelButton: _t('OK'), - onFinished: (allow) => { - SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow); - cli.setFallbackICEServerAllowed(allow); - }, - }, null, true); -} - -function _onAction(payload) { - function placeCall(newCall) { - _setCallListeners(newCall); - if (payload.type === 'voice') { - newCall.placeVoiceCall(); - } else if (payload.type === 'video') { - newCall.placeVideoCall( - payload.remote_element, - payload.local_element, - ); - } else if (payload.type === 'screensharing') { - const screenCapErrorString = PlatformPeg.get().screenCaptureErrorString(); - if (screenCapErrorString) { - _setCallState(undefined, newCall.roomId, "ended"); - console.log("Can't capture screen: " + screenCapErrorString); - Modal.createTrackedDialog('Call Handler', 'Unable to capture screen', ErrorDialog, { - title: _t('Unable to capture screen'), - description: screenCapErrorString, - }); - return; - } - newCall.placeScreenSharingCall( - payload.remote_element, - payload.local_element, - ); - } else { - console.error("Unknown conf call type: %s", payload.type); - } - } - - switch (payload.action) { - case 'place_call': - { - if (callHandler.getAnyActiveCall()) { - Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, { - title: _t('Existing Call'), - description: _t('You are already in a call.'), - }); - return; // don't allow >1 call to be placed. - } - - // if the runtime env doesn't do VoIP, whine. - if (!MatrixClientPeg.get().supportsVoip()) { - Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, { - title: _t('VoIP is unsupported'), - description: _t('You cannot place VoIP calls in this browser.'), - }); - return; - } - - const room = MatrixClientPeg.get().getRoom(payload.room_id); - if (!room) { - console.error("Room %s does not exist.", payload.room_id); - return; - } - - const members = room.getJoinedMembers(); - if (members.length <= 1) { - Modal.createTrackedDialog('Call Handler', 'Cannot place call with self', ErrorDialog, { - description: _t('You cannot place a call with yourself.'), - }); - return; - } else if (members.length === 2) { - console.info("Place %s call in %s", payload.type, payload.room_id); - const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), payload.room_id); - placeCall(call); - } else { // > 2 - dis.dispatch({ - action: "place_conference_call", - room_id: payload.room_id, - type: payload.type, - remote_element: payload.remote_element, - local_element: payload.local_element, - }); - } - } - break; - case 'place_conference_call': - console.info("Place conference call in %s", payload.room_id); - _startCallApp(payload.room_id, payload.type); - break; - case 'incoming_call': - { - if (callHandler.getAnyActiveCall()) { - // ignore multiple incoming calls. in future, we may want a line-1/line-2 setup. - // we avoid rejecting with "busy" in case the user wants to answer it on a different device. - // in future we could signal a "local busy" as a warning to the caller. - // see https://github.com/vector-im/vector-web/issues/1964 - return; - } - - // if the runtime env doesn't do VoIP, stop here. - if (!MatrixClientPeg.get().supportsVoip()) { - return; - } - - const call = payload.call; - _setCallListeners(call); - _setCallState(call, call.roomId, "ringing"); - } - break; - case 'hangup': - if (!calls[payload.room_id]) { - return; // no call to hangup - } - calls[payload.room_id].hangup(); - _setCallState(null, payload.room_id, "ended"); - break; - case 'answer': - if (!calls[payload.room_id]) { - return; // no call to answer - } - calls[payload.room_id].answer(); - _setCallState(calls[payload.room_id], payload.room_id, "connected"); - dis.dispatch({ - action: "view_room", - room_id: payload.room_id, - }); - break; - } -} - -async function _startCallApp(roomId, type) { - dis.dispatch({ - action: 'appsDrawer', - show: true, - }); - - const room = MatrixClientPeg.get().getRoom(roomId); - const currentJitsiWidgets = WidgetUtils.getRoomWidgetsOfType(room, WidgetType.JITSI); - - if (WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentJitsiWidgets, WidgetType.JITSI)) { - Modal.createTrackedDialog('Call already in progress', '', ErrorDialog, { - title: _t('Call in Progress'), - description: _t('A call is currently being placed!'), - }); - return; - } - - if (currentJitsiWidgets.length > 0) { - console.warn( - "Refusing to start conference call widget in " + roomId + - " a conference call widget is already present", - ); - - if (WidgetUtils.canUserModifyWidgets(roomId)) { - Modal.createTrackedDialog('Already have Jitsi Widget', '', QuestionDialog, { - title: _t('End Call'), - description: _t('Remove the group call from the room?'), - button: _t('End Call'), - cancelButton: _t('Cancel'), - onFinished: (endCall) => { - if (endCall) { - WidgetUtils.setRoomWidget(roomId, currentJitsiWidgets[0].getContent()['id']); - } - }, - }); - } else { - Modal.createTrackedDialog('Already have Jitsi Widget', '', ErrorDialog, { - title: _t('Call in Progress'), - description: _t("You don't have permission to remove the call from the room"), - }); - } - return; - } - - const jitsiDomain = Jitsi.getInstance().preferredDomain; - const jitsiAuth = await Jitsi.getInstance().getJitsiAuth(); - let confId; - if (jitsiAuth === 'openidtoken-jwt') { - // Create conference ID from room ID - // For compatibility with Jitsi, use base32 without padding. - // More details here: - // https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification - confId = base32.stringify(Buffer.from(roomId), { pad: false }); - } else { - // Create a random human readable conference ID - confId = `JitsiConference${generateHumanReadableId()}`; - } - - let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl({auth: jitsiAuth}); - - // TODO: Remove URL hacks when the mobile clients eventually support v2 widgets - const parsedUrl = new URL(widgetUrl); - parsedUrl.search = ''; // set to empty string to make the URL class use searchParams instead - parsedUrl.searchParams.set('confId', confId); - widgetUrl = parsedUrl.toString(); - - const widgetData = { - conferenceId: confId, - isAudioOnly: type === 'voice', - domain: jitsiDomain, - auth: jitsiAuth, - }; - - const widgetId = ( - 'jitsi_' + - MatrixClientPeg.get().credentials.userId + - '_' + - Date.now() - ); - - WidgetUtils.setRoomWidget(roomId, widgetId, WidgetType.JITSI, widgetUrl, 'Jitsi', widgetData).then(() => { - console.log('Jitsi widget added'); - }).catch((e) => { - if (e.errcode === 'M_FORBIDDEN') { - Modal.createTrackedDialog('Call Failed', '', ErrorDialog, { - title: _t('Permission Required'), - description: _t("You do not have permission to start a conference call in this room"), - }); - } - console.error(e); - }); -} - -// FIXME: Nasty way of making sure we only register -// with the dispatcher once -if (!global.mxCallHandler) { - dis.register(_onAction); - // add empty handlers for media actions, otherwise the media keys - // end up causing the audio elements with our ring/ringback etc - // audio clips in to play. - if (navigator.mediaSession) { - navigator.mediaSession.setActionHandler('play', function() {}); - navigator.mediaSession.setActionHandler('pause', function() {}); - navigator.mediaSession.setActionHandler('seekbackward', function() {}); - navigator.mediaSession.setActionHandler('seekforward', function() {}); - navigator.mediaSession.setActionHandler('previoustrack', function() {}); - navigator.mediaSession.setActionHandler('nexttrack', function() {}); - } -} - -const callHandler = { - getCallForRoom: function(roomId) { - let call = callHandler.getCall(roomId); - if (call) return call; - - if (ConferenceHandler) { - call = ConferenceHandler.getConferenceCallForRoom(roomId); - } - if (call) return call; - - return null; - }, - - getCall: function(roomId) { - return calls[roomId] || null; - }, - - getAnyActiveCall: function() { - const roomsWithCalls = Object.keys(calls); - for (let i = 0; i < roomsWithCalls.length; i++) { - if (calls[roomsWithCalls[i]] && - calls[roomsWithCalls[i]].call_state !== "ended") { - return calls[roomsWithCalls[i]]; - } - } - return null; - }, - - /** - * The conference handler is a module that deals with implementation-specific - * multi-party calling implementations. Element passes in its own which creates - * a one-to-one call with a freeswitch conference bridge. As of July 2018, - * the de-facto way of conference calling is a Jitsi widget, so this is - * deprecated. It reamins here for two reasons: - * 1. So Element still supports joining existing freeswitch conference calls - * (but doesn't support creating them). After a transition period, we can - * remove support for joining them too. - * 2. To hide the one-to-one rooms that old-style conferencing creates. This - * is much harder to remove: probably either we make Element leave & forget these - * rooms after we remove support for joining freeswitch conferences, or we - * accept that random rooms with cryptic users will suddently appear for - * anyone who's ever used conference calling, or we are stuck with this - * code forever. - * - * @param {object} confHandler The conference handler object - */ - setConferenceHandler: function(confHandler) { - ConferenceHandler = confHandler; - }, - - getConferenceHandler: function() { - return ConferenceHandler; - }, -}; -// Only things in here which actually need to be global are the -// calls list (done separately) and making sure we only register -// with the dispatcher once (which uses this mechanism but checks -// separately). This could be tidied up. -if (global.mxCallHandler === undefined) { - global.mxCallHandler = callHandler; -} - -export default global.mxCallHandler; diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx new file mode 100644 index 0000000000..87b26b135d --- /dev/null +++ b/src/CallHandler.tsx @@ -0,0 +1,482 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017, 2018 New Vector Ltd +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* + * Manages a list of all the currently active calls. + * + * This handler dispatches when voip calls are added/updated/removed from this list: + * { + * action: 'call_state' + * room_id: + * } + * + * To know the state of the call, this handler exposes a getter to + * obtain the call for a room: + * var call = CallHandler.getCall(roomId) + * var state = call.call_state; // ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing + * + * This handler listens for and handles the following actions: + * { + * action: 'place_call', + * type: 'voice|video', + * room_id: + * } + * + * { + * action: 'incoming_call' + * call: MatrixCall + * } + * + * { + * action: 'hangup' + * room_id: + * } + * + * { + * action: 'answer' + * room_id: + * } + */ + +import React from 'react'; + +import {MatrixClientPeg} from './MatrixClientPeg'; +import PlatformPeg from './PlatformPeg'; +import Modal from './Modal'; +import { _t } from './languageHandler'; +import Matrix from 'matrix-js-sdk'; +import dis from './dispatcher/dispatcher'; +import WidgetUtils from './utils/WidgetUtils'; +import WidgetEchoStore from './stores/WidgetEchoStore'; +import SettingsStore from './settings/SettingsStore'; +import {generateHumanReadableId} from "./utils/NamingUtils"; +import {Jitsi} from "./widgets/Jitsi"; +import {WidgetType} from "./widgets/WidgetType"; +import {SettingLevel} from "./settings/SettingLevel"; +import {base32} from "rfc4648"; + +import QuestionDialog from "./components/views/dialogs/QuestionDialog"; +import ErrorDialog from "./components/views/dialogs/ErrorDialog"; + +export default class CallHandler { + private calls = {}; + private audioPromises = {}; + + static sharedInstance() { + if (!window.mxCallHandler) { + window.mxCallHandler = new CallHandler() + } + + return window.mxCallHandler; + } + + constructor() { + dis.register(this.onAction); + // add empty handlers for media actions, otherwise the media keys + // end up causing the audio elements with our ring/ringback etc + // audio clips in to play. + if (navigator.mediaSession) { + navigator.mediaSession.setActionHandler('play', function() {}); + navigator.mediaSession.setActionHandler('pause', function() {}); + navigator.mediaSession.setActionHandler('seekbackward', function() {}); + navigator.mediaSession.setActionHandler('seekforward', function() {}); + navigator.mediaSession.setActionHandler('previoustrack', function() {}); + navigator.mediaSession.setActionHandler('nexttrack', function() {}); + } + } + + getCallForRoom(roomId: string) { + return this.calls[roomId] || null; + } + + getAnyActiveCall() { + const roomsWithCalls = Object.keys(this.calls); + for (let i = 0; i < roomsWithCalls.length; i++) { + if (this.calls[roomsWithCalls[i]] && + this.calls[roomsWithCalls[i]].call_state !== "ended") { + return this.calls[roomsWithCalls[i]]; + } + } + return null; + } + + play(audioId) { + // TODO: Attach an invisible element for this instead + // which listens? + const audio = document.getElementById(audioId) as HTMLMediaElement; + if (audio) { + const playAudio = async () => { + try { + // This still causes the chrome debugger to break on promise rejection if + // the promise is rejected, even though we're catching the exception. + await audio.play(); + } catch (e) { + // This is usually because the user hasn't interacted with the document, + // or chrome doesn't think so and is denying the request. Not sure what + // we can really do here... + // https://github.com/vector-im/element-web/issues/7657 + console.log("Unable to play audio clip", e); + } + }; + if (this.audioPromises[audioId]) { + this.audioPromises[audioId] = this.audioPromises[audioId].then(() => { + audio.load(); + return playAudio(); + }); + } else { + this.audioPromises[audioId] = playAudio(); + } + } + } + + pause(audioId) { + // TODO: Attach an invisible element for this instead + // which listens? + const audio = document.getElementById(audioId) as HTMLMediaElement; + if (audio) { + if (this.audioPromises[audioId]) { + this.audioPromises[audioId] = this.audioPromises[audioId].then(() => audio.pause()); + } else { + // pause doesn't actually return a promise, but might as well do this for symmetry with play(); + this.audioPromises[audioId] = audio.pause(); + } + } + } + + private setCallListeners(call) { + call.on("error", (err) => { + console.error("Call error:", err); + if ( + MatrixClientPeg.get().getTurnServers().length === 0 && + SettingsStore.getValue("fallbackICEServerAllowed") === null + ) { + this.showICEFallbackPrompt(); + return; + } + + Modal.createTrackedDialog('Call Failed', '', ErrorDialog, { + title: _t('Call Failed'), + description: err.message, + }); + }); + call.on("hangup", () => { + this.setCallState(undefined, call.roomId, "ended"); + }); + // map web rtc states to dummy UI state + // ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing + call.on("state", (newState, oldState) => { + if (newState === "ringing") { + this.setCallState(call, call.roomId, "ringing"); + this.pause("ringbackAudio"); + } else if (newState === "invite_sent") { + this.setCallState(call, call.roomId, "ringback"); + this.play("ringbackAudio"); + } else if (newState === "ended" && oldState === "connected") { + this.setCallState(undefined, call.roomId, "ended"); + this.pause("ringbackAudio"); + this.play("callendAudio"); + } else if (newState === "ended" && oldState === "invite_sent" && + (call.hangupParty === "remote" || + (call.hangupParty === "local" && call.hangupReason === "invite_timeout") + )) { + this.setCallState(call, call.roomId, "busy"); + this.pause("ringbackAudio"); + this.play("busyAudio"); + Modal.createTrackedDialog('Call Handler', 'Call Timeout', ErrorDialog, { + title: _t('Call Timeout'), + description: _t('The remote side failed to pick up') + '.', + }); + } else if (oldState === "invite_sent") { + this.setCallState(call, call.roomId, "stop_ringback"); + this.pause("ringbackAudio"); + } else if (oldState === "ringing") { + this.setCallState(call, call.roomId, "stop_ringing"); + this.pause("ringbackAudio"); + } else if (newState === "connected") { + this.setCallState(call, call.roomId, "connected"); + this.pause("ringbackAudio"); + } + }); + } + + private setCallState(call, roomId, status) { + console.log( + `Call state in ${roomId} changed to ${status} (${call ? call.call_state : "-"})`, + ); + this.calls[roomId] = call; + + if (status === "ringing") { + this.play("ringAudio"); + } else if (call && call.call_state === "ringing") { + this.pause("ringAudio"); + } + + if (call) { + call.call_state = status; + } + dis.dispatch({ + action: 'call_state', + room_id: roomId, + state: status, + }); + } + + private showICEFallbackPrompt() { + const cli = MatrixClientPeg.get(); + const code = sub => {sub}; + Modal.createTrackedDialog('No TURN servers', '', QuestionDialog, { + title: _t("Call failed due to misconfigured server"), + description:
    +

    {_t( + "Please ask the administrator of your homeserver " + + "(%(homeserverDomain)s) to configure a TURN server in " + + "order for calls to work reliably.", + { homeserverDomain: cli.getDomain() }, { code }, + )}

    +

    {_t( + "Alternatively, you can try to use the public server at " + + "turn.matrix.org, but this will not be as reliable, and " + + "it will share your IP address with that server. You can also manage " + + "this in Settings.", + null, { code }, + )}

    +
    , + button: _t('Try using turn.matrix.org'), + cancelButton: _t('OK'), + onFinished: (allow) => { + SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow); + cli.setFallbackICEServerAllowed(allow); + }, + }, null, true); + } + + private onAction = (payload) => { + const placeCall = (newCall) => { + this.setCallListeners(newCall); + if (payload.type === 'voice') { + newCall.placeVoiceCall(); + } else if (payload.type === 'video') { + newCall.placeVideoCall( + payload.remote_element, + payload.local_element, + ); + } else if (payload.type === 'screensharing') { + const screenCapErrorString = PlatformPeg.get().screenCaptureErrorString(); + if (screenCapErrorString) { + this.setCallState(undefined, newCall.roomId, "ended"); + console.log("Can't capture screen: " + screenCapErrorString); + Modal.createTrackedDialog('Call Handler', 'Unable to capture screen', ErrorDialog, { + title: _t('Unable to capture screen'), + description: screenCapErrorString, + }); + return; + } + newCall.placeScreenSharingCall( + payload.remote_element, + payload.local_element, + ); + } else { + console.error("Unknown conf call type: %s", payload.type); + } + } + + switch (payload.action) { + case 'place_call': + { + if (this.getAnyActiveCall()) { + Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, { + title: _t('Existing Call'), + description: _t('You are already in a call.'), + }); + return; // don't allow >1 call to be placed. + } + + // if the runtime env doesn't do VoIP, whine. + if (!MatrixClientPeg.get().supportsVoip()) { + Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, { + title: _t('VoIP is unsupported'), + description: _t('You cannot place VoIP calls in this browser.'), + }); + return; + } + + const room = MatrixClientPeg.get().getRoom(payload.room_id); + if (!room) { + console.error("Room %s does not exist.", payload.room_id); + return; + } + + const members = room.getJoinedMembers(); + if (members.length <= 1) { + Modal.createTrackedDialog('Call Handler', 'Cannot place call with self', ErrorDialog, { + description: _t('You cannot place a call with yourself.'), + }); + return; + } else if (members.length === 2) { + console.info("Place %s call in %s", payload.type, payload.room_id); + const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), payload.room_id); + placeCall(call); + } else { // > 2 + dis.dispatch({ + action: "place_conference_call", + room_id: payload.room_id, + type: payload.type, + remote_element: payload.remote_element, + local_element: payload.local_element, + }); + } + } + break; + case 'place_conference_call': + console.info("Place conference call in %s", payload.room_id); + this.startCallApp(payload.room_id, payload.type); + break; + case 'incoming_call': + { + if (this.getAnyActiveCall()) { + // ignore multiple incoming calls. in future, we may want a line-1/line-2 setup. + // we avoid rejecting with "busy" in case the user wants to answer it on a different device. + // in future we could signal a "local busy" as a warning to the caller. + // see https://github.com/vector-im/vector-web/issues/1964 + return; + } + + // if the runtime env doesn't do VoIP, stop here. + if (!MatrixClientPeg.get().supportsVoip()) { + return; + } + + const call = payload.call; + this.setCallListeners(call); + this.setCallState(call, call.roomId, "ringing"); + } + break; + case 'hangup': + if (!this.calls[payload.room_id]) { + return; // no call to hangup + } + this.calls[payload.room_id].hangup(); + this.setCallState(null, payload.room_id, "ended"); + break; + case 'answer': + if (!this.calls[payload.room_id]) { + return; // no call to answer + } + this.calls[payload.room_id].answer(); + this.setCallState(this.calls[payload.room_id], payload.room_id, "connected"); + dis.dispatch({ + action: "view_room", + room_id: payload.room_id, + }); + break; + } + } + + private async startCallApp(roomId, type) { + dis.dispatch({ + action: 'appsDrawer', + show: true, + }); + + const room = MatrixClientPeg.get().getRoom(roomId); + const currentJitsiWidgets = WidgetUtils.getRoomWidgetsOfType(room, WidgetType.JITSI); + + if (WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentJitsiWidgets, WidgetType.JITSI)) { + Modal.createTrackedDialog('Call already in progress', '', ErrorDialog, { + title: _t('Call in Progress'), + description: _t('A call is currently being placed!'), + }); + return; + } + + if (currentJitsiWidgets.length > 0) { + console.warn( + "Refusing to start conference call widget in " + roomId + + " a conference call widget is already present", + ); + + if (WidgetUtils.canUserModifyWidgets(roomId)) { + Modal.createTrackedDialog('Already have Jitsi Widget', '', QuestionDialog, { + title: _t('End Call'), + description: _t('Remove the group call from the room?'), + button: _t('End Call'), + cancelButton: _t('Cancel'), + onFinished: (endCall) => { + if (endCall) { + WidgetUtils.setRoomWidget(roomId, currentJitsiWidgets[0].getContent()['id']); + } + }, + }); + } else { + Modal.createTrackedDialog('Already have Jitsi Widget', '', ErrorDialog, { + title: _t('Call in Progress'), + description: _t("You don't have permission to remove the call from the room"), + }); + } + return; + } + + const jitsiDomain = Jitsi.getInstance().preferredDomain; + const jitsiAuth = await Jitsi.getInstance().getJitsiAuth(); + let confId; + if (jitsiAuth === 'openidtoken-jwt') { + // Create conference ID from room ID + // For compatibility with Jitsi, use base32 without padding. + // More details here: + // https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification + confId = base32.stringify(Buffer.from(roomId), { pad: false }); + } else { + // Create a random human readable conference ID + confId = `JitsiConference${generateHumanReadableId()}`; + } + + let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl({auth: jitsiAuth}); + + // TODO: Remove URL hacks when the mobile clients eventually support v2 widgets + const parsedUrl = new URL(widgetUrl); + parsedUrl.search = ''; // set to empty string to make the URL class use searchParams instead + parsedUrl.searchParams.set('confId', confId); + widgetUrl = parsedUrl.toString(); + + const widgetData = { + conferenceId: confId, + isAudioOnly: type === 'voice', + domain: jitsiDomain, + auth: jitsiAuth, + }; + + const widgetId = ( + 'jitsi_' + + MatrixClientPeg.get().credentials.userId + + '_' + + Date.now() + ); + + WidgetUtils.setRoomWidget(roomId, widgetId, WidgetType.JITSI, widgetUrl, 'Jitsi', widgetData).then(() => { + console.log('Jitsi widget added'); + }).catch((e) => { + if (e.errcode === 'M_FORBIDDEN') { + Modal.createTrackedDialog('Call Failed', '', ErrorDialog, { + title: _t('Permission Required'), + description: _t("You do not have permission to start a conference call in this room"), + }); + } + console.error(e); + }); + } +} diff --git a/src/Rooms.js b/src/Rooms.js index 218e970f35..3da2b9bc14 100644 --- a/src/Rooms.js +++ b/src/Rooms.js @@ -26,58 +26,6 @@ export function getDisplayAliasForRoom(room) { return room.getCanonicalAlias() || room.getAltAliases()[0]; } -/** - * If the room contains only two members including the logged-in user, - * return the other one. Otherwise, return null. - */ -export function getOnlyOtherMember(room, myUserId) { - if (room.currentState.getJoinedMemberCount() === 2) { - return room.getJoinedMembers().filter(function(m) { - return m.userId !== myUserId; - })[0]; - } - - return null; -} - -function _isConfCallRoom(room, myUserId, conferenceHandler) { - if (!conferenceHandler) return false; - - const myMembership = room.getMyMembership(); - if (myMembership != "join") { - return false; - } - - const otherMember = getOnlyOtherMember(room, myUserId); - if (!otherMember) { - return false; - } - - if (conferenceHandler.isConferenceUser(otherMember.userId)) { - return true; - } - - return false; -} - -// Cache whether a room is a conference call. Assumes that rooms will always -// either will or will not be a conference call room. -const isConfCallRoomCache = { - // $roomId: bool -}; - -export function isConfCallRoom(room, myUserId, conferenceHandler) { - if (isConfCallRoomCache[room.roomId] !== undefined) { - return isConfCallRoomCache[room.roomId]; - } - - const result = _isConfCallRoom(room, myUserId, conferenceHandler); - - isConfCallRoomCache[room.roomId] = result; - - return result; -} - export function looksLikeDirectMessageRoom(room, myUserId) { const myMembership = room.getMyMembership(); const me = room.getMember(myUserId); diff --git a/src/TextForEvent.js b/src/TextForEvent.js index a76c1f59e6..f9cda23650 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ import {MatrixClientPeg} from './MatrixClientPeg'; -import CallHandler from './CallHandler'; import { _t } from './languageHandler'; import * as Roles from './Roles'; import {isValid3pidInvite} from "./RoomInvite"; @@ -29,7 +28,6 @@ function textForMemberEvent(ev) { const prevContent = ev.getPrevContent(); const content = ev.getContent(); - const ConferenceHandler = CallHandler.getConferenceHandler(); const reason = content.reason ? (_t('Reason') + ': ' + content.reason) : ''; switch (content.membership) { case 'invite': { @@ -44,11 +42,7 @@ function textForMemberEvent(ev) { return _t('%(targetName)s accepted an invitation.', {targetName}); } } else { - if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { - return _t('%(senderName)s requested a VoIP conference.', {senderName}); - } else { - return _t('%(senderName)s invited %(targetName)s.', {senderName, targetName}); - } + return _t('%(senderName)s invited %(targetName)s.', {senderName, targetName}); } } case 'ban': @@ -85,17 +79,11 @@ function textForMemberEvent(ev) { } } else { if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key); - if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { - return _t('VoIP conference started.'); - } else { - return _t('%(targetName)s joined the room.', {targetName}); - } + return _t('%(targetName)s joined the room.', {targetName}); } case 'leave': if (ev.getSender() === ev.getStateKey()) { - if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { - return _t('VoIP conference finished.'); - } else if (prevContent.membership === "invite") { + if (prevContent.membership === "invite") { return _t('%(targetName)s rejected the invitation.', {targetName}); } else { return _t('%(targetName)s left the room.', {targetName}); diff --git a/src/VectorConferenceHandler.js b/src/VectorConferenceHandler.js deleted file mode 100644 index c10bc659ae..0000000000 --- a/src/VectorConferenceHandler.js +++ /dev/null @@ -1,135 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019 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 {createNewMatrixCall as jsCreateNewMatrixCall, Room} from "matrix-js-sdk"; -import CallHandler from './CallHandler'; -import {MatrixClientPeg} from "./MatrixClientPeg"; - -// FIXME: this is Element specific code, but will be removed shortly when we -// switch over to Jitsi entirely for video conferencing. - -// FIXME: This currently forces Element to try to hit the matrix.org AS for -// conferencing. This is bad because it prevents people running their own ASes -// from being used. This isn't permanent and will be customisable in the future: -// see the proposal at docs/conferencing.md for more info. -const USER_PREFIX = "fs_"; -const DOMAIN = "matrix.org"; - -export function ConferenceCall(matrixClient, groupChatRoomId) { - this.client = matrixClient; - this.groupRoomId = groupChatRoomId; - this.confUserId = getConferenceUserIdForRoom(this.groupRoomId); -} - -ConferenceCall.prototype.setup = function() { - const self = this; - return this._joinConferenceUser().then(function() { - return self._getConferenceUserRoom(); - }).then(function(room) { - // return a call for *this* room to be placed. We also tack on - // confUserId to speed up lookups (else we'd need to loop every room - // looking for a 1:1 room with this conf user ID!) - const call = jsCreateNewMatrixCall(self.client, room.roomId); - call.confUserId = self.confUserId; - call.groupRoomId = self.groupRoomId; - return call; - }); -}; - -ConferenceCall.prototype._joinConferenceUser = function() { - // Make sure the conference user is in the group chat room - const groupRoom = this.client.getRoom(this.groupRoomId); - if (!groupRoom) { - return Promise.reject("Bad group room ID"); - } - const member = groupRoom.getMember(this.confUserId); - if (member && member.membership === "join") { - return Promise.resolve(); - } - return this.client.invite(this.groupRoomId, this.confUserId); -}; - -ConferenceCall.prototype._getConferenceUserRoom = function() { - // Use an existing 1:1 with the conference user; else make one - const rooms = this.client.getRooms(); - let confRoom = null; - for (let i = 0; i < rooms.length; i++) { - const confUser = rooms[i].getMember(this.confUserId); - if (confUser && confUser.membership === "join" && - rooms[i].getJoinedMemberCount() === 2) { - confRoom = rooms[i]; - break; - } - } - if (confRoom) { - return Promise.resolve(confRoom); - } - return this.client.createRoom({ - preset: "private_chat", - invite: [this.confUserId], - }).then(function(res) { - return new Room(res.room_id, null, MatrixClientPeg.get().getUserId()); - }); -}; - -/** - * Check if this user ID is in fact a conference bot. - * @param {string} userId The user ID to check. - * @return {boolean} True if it is a conference bot. - */ -export function isConferenceUser(userId) { - if (userId.indexOf("@" + USER_PREFIX) !== 0) { - return false; - } - const base64part = userId.split(":")[0].substring(1 + USER_PREFIX.length); - if (base64part) { - const decoded = new Buffer(base64part, "base64").toString(); - // ! $STUFF : $STUFF - return /^!.+:.+/.test(decoded); - } - return false; -} - -export function getConferenceUserIdForRoom(roomId) { - // abuse browserify's core node Buffer support (strip padding ='s) - const base64RoomId = new Buffer(roomId).toString("base64").replace(/=/g, ""); - return "@" + USER_PREFIX + base64RoomId + ":" + DOMAIN; -} - -export function createNewMatrixCall(client, roomId) { - const confCall = new ConferenceCall( - client, roomId, - ); - return confCall.setup(); -} - -export function getConferenceCallForRoom(roomId) { - // search for a conference 1:1 call for this group chat room ID - const activeCall = CallHandler.getAnyActiveCall(); - if (activeCall && activeCall.confUserId) { - const thisRoomConfUserId = getConferenceUserIdForRoom( - roomId, - ); - if (thisRoomConfUserId === activeCall.confUserId) { - return activeCall; - } - } - return null; -} - -// TODO: Document this. -export const slot = 'conference'; diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 81b8da2cad..4dc2080895 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -85,7 +85,6 @@ interface IProps { threepidInvite?: IThreepidInvite; roomOobData?: object; currentRoomId: string; - ConferenceHandler?: object; collapseLhs: boolean; config: { piwik: { @@ -637,7 +636,6 @@ class LoggedInView extends React.Component { viaServers={this.props.viaServers} key={this.props.currentRoomId || 'roomview'} disabled={this.props.middleDisabled} - ConferenceHandler={this.props.ConferenceHandler} resizeNotifier={this.props.resizeNotifier} />; break; diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index dcb497f6dc..69309d5efa 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -148,7 +148,6 @@ interface IRoomInfo { interface IProps { // TODO type things better config: Record; serverConfig?: ValidatedServerConfig; - ConferenceHandler?: any; onNewScreen: (screen: string, replaceLast: boolean) => void; enableGuest?: boolean; // the queryParams extracted from the [real] query-string of the URI diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 039d36a8de..11ed5d5783 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -70,7 +70,6 @@ import PinnedEventsPanel from "../views/rooms/PinnedEventsPanel"; import AuxPanel from "../views/rooms/AuxPanel"; import RoomHeader from "../views/rooms/RoomHeader"; import TintableSvg from "../views/elements/TintableSvg"; -import type * as ConferenceHandler from '../../VectorConferenceHandler'; import {XOR} from "../../@types/common"; import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; @@ -85,8 +84,6 @@ if (DEBUG) { } interface IProps { - ConferenceHandler?: ConferenceHandler; - threepidInvite: IThreepidInvite, // Any data about the room that would normally come from the homeserver @@ -182,7 +179,6 @@ export interface IState { matrixClientIsReady: boolean; showUrlPreview?: boolean; e2eStatus?: E2EStatus; - displayConfCallNotification?: boolean; rejecting?: boolean; rejectError?: Error; } @@ -489,8 +485,6 @@ export default class RoomView extends React.Component { callState: callState, }); - this.updateConfCallNotification(); - window.addEventListener('beforeunload', this.onPageUnload); if (this.props.resizeNotifier) { this.props.resizeNotifier.on("middlePanelResized", this.onResize); @@ -724,10 +718,6 @@ export default class RoomView extends React.Component { callState = call.call_state; } - // possibly remove the conf call notification if we're now in - // the conf - this.updateConfCallNotification(); - this.setState({ callState: callState, }); @@ -1024,9 +1014,6 @@ export default class RoomView extends React.Component { // rate limited because a power level change will emit an event for every member in the room. private updateRoomMembers = rateLimitedFunc((dueToMember) => { - // a member state changed in this room - // refresh the conf call notification state - this.updateConfCallNotification(); this.updateDMState(); let memberCountInfluence = 0; @@ -1055,30 +1042,6 @@ export default class RoomView extends React.Component { this.setState({isAlone: joinedOrInvitedMemberCount === 1}); } - private updateConfCallNotification() { - const room = this.state.room; - if (!room || !this.props.ConferenceHandler) { - return; - } - const confMember = room.getMember( - this.props.ConferenceHandler.getConferenceUserIdForRoom(room.roomId), - ); - - if (!confMember) { - return; - } - const confCall = this.props.ConferenceHandler.getConferenceCallForRoom(confMember.roomId); - - // A conf call notification should be displayed if there is an ongoing - // conf call but this cilent isn't a part of it. - this.setState({ - displayConfCallNotification: ( - (!confCall || confCall.call_state === "ended") && - confMember.membership === "join" - ), - }); - } - private updateDMState() { const room = this.state.room; if (room.getMyMembership() != "join") { @@ -1687,7 +1650,7 @@ export default class RoomView extends React.Component { if (!this.state.room) { return null; } - return CallHandler.getCallForRoom(this.state.room.roomId); + return CallHandler.sharedInstance().getCallForRoom(this.state.room.roomId); } // this has to be a proper method rather than an unnamed function, @@ -1940,9 +1903,7 @@ export default class RoomView extends React.Component { room={this.state.room} fullHeight={false} userId={this.context.credentials.userId} - conferenceHandler={this.props.ConferenceHandler} draggingFile={this.state.draggingFile} - displayConfCallNotification={this.state.displayConfCallNotification} maxHeight={this.state.auxPanelMaxHeight} showApps={this.state.showApps} hideAppsDrawer={false} diff --git a/src/components/views/rooms/AuxPanel.js b/src/components/views/rooms/AuxPanel.js index f2211dba5c..b7ed457a74 100644 --- a/src/components/views/rooms/AuxPanel.js +++ b/src/components/views/rooms/AuxPanel.js @@ -39,15 +39,9 @@ export default class AuxPanel extends React.Component { showApps: PropTypes.bool, // Render apps hideAppsDrawer: PropTypes.bool, // Do not display apps drawer and content (may still be rendered) - // Conference Handler implementation - conferenceHandler: PropTypes.object, - // set to true to show the file drop target draggingFile: PropTypes.bool, - // set to true to show the 'active conf call' banner - displayConfCallNotification: PropTypes.bool, - // maxHeight attribute for the aux panel and the video // therein maxHeight: PropTypes.number, @@ -161,39 +155,9 @@ export default class AuxPanel extends React.Component { ); } - let conferenceCallNotification = null; - if (this.props.displayConfCallNotification) { - let supportedText = ''; - let joinNode; - if (!MatrixClientPeg.get().supportsVoip()) { - supportedText = _t(" (unsupported)"); - } else { - joinNode = ( - { _t( - "Join as voice or video.", - {}, - { - 'voiceText': (sub) => { this.onConferenceNotificationClick(event, 'voice');}} href="#">{ sub }, - 'videoText': (sub) => { this.onConferenceNotificationClick(event, 'video');}} href="#">{ sub }, - }, - ) } - ); - } - // XXX: the translation here isn't great: appending ' (unsupported)' is likely to not make sense in many languages, - // but there are translations for this in the languages we do have so I'm leaving it for now. - conferenceCallNotification = ( -
    - { _t("Ongoing conference call%(supportedText)s.", {supportedText: supportedText}) } -   - { joinNode } -
    - ); - } - const callView = ( @@ -276,7 +240,6 @@ export default class AuxPanel extends React.Component { { appsDrawer } { fileDropTarget } { callView } - { conferenceCallNotification } { this.props.children } ); diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 40b3b042b1..ae122a3783 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -24,7 +24,6 @@ import {isValid3pidInvite} from "../../../RoomInvite"; import rate_limited_func from "../../../ratelimitedfunc"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import * as sdk from "../../../index"; -import CallHandler from "../../../CallHandler"; import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore"; import BaseCard from "../right_panel/BaseCard"; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; @@ -233,15 +232,10 @@ export default class MemberList extends React.Component { } roomMembers() { - const ConferenceHandler = CallHandler.getConferenceHandler(); - const allMembers = this.getMembersWithUser(); const filteredAndSortedMembers = allMembers.filter((m) => { return ( m.membership === 'join' || m.membership === 'invite' - ) && ( - !ConferenceHandler || - (ConferenceHandler && !ConferenceHandler.isConferenceUser(m.userId)) ); }); filteredAndSortedMembers.sort(this.memberSort); diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 81c2ae7a33..e6cd686e3c 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -87,7 +87,7 @@ VideoCallButton.propTypes = { function HangupButton(props) { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const onHangupClick = () => { - const call = CallHandler.getCallForRoom(props.roomId); + const call = CallHandler.sharedInstance().getCallForRoom(props.roomId); if (!call) { return; } diff --git a/src/components/views/voip/CallContainer.tsx b/src/components/views/voip/CallContainer.tsx index 18a9c098d6..51925cb147 100644 --- a/src/components/views/voip/CallContainer.tsx +++ b/src/components/views/voip/CallContainer.tsx @@ -17,7 +17,6 @@ limitations under the License. import React from 'react'; import IncomingCallBox from './IncomingCallBox'; import CallPreview from './CallPreview'; -import * as VectorConferenceHandler from '../../../VectorConferenceHandler'; interface IProps { @@ -31,7 +30,7 @@ export default class CallContainer extends React.PureComponent { public render() { return
    - +
    ; } } diff --git a/src/components/views/voip/CallPreview.tsx b/src/components/views/voip/CallPreview.tsx index 4352fc95e4..9acbece8b3 100644 --- a/src/components/views/voip/CallPreview.tsx +++ b/src/components/views/voip/CallPreview.tsx @@ -26,10 +26,6 @@ import PersistentApp from "../elements/PersistentApp"; import SettingsStore from "../../../settings/SettingsStore"; interface IProps { - // A Conference Handler implementation - // Must have a function signature: - // getConferenceCallForRoom(roomId: string): MatrixCall - ConferenceHandler: any; } interface IState { @@ -47,7 +43,7 @@ export default class CallPreview extends React.Component { this.state = { roomId: RoomViewStore.getRoomId(), - activeCall: CallHandler.getAnyActiveCall(), + activeCall: CallHandler.sharedInstance().getAnyActiveCall(), }; } @@ -77,14 +73,14 @@ export default class CallPreview extends React.Component { // may hide the global CallView if the call it is tracking is dead case 'call_state': this.setState({ - activeCall: CallHandler.getAnyActiveCall(), + activeCall: CallHandler.sharedInstance().getAnyActiveCall(), }); break; } }; private onCallViewClick = () => { - const call = CallHandler.getAnyActiveCall(); + const call = CallHandler.sharedInstance().getAnyActiveCall(); if (call) { dis.dispatch({ action: 'view_room', @@ -94,7 +90,7 @@ export default class CallPreview extends React.Component { }; public render() { - const callForRoom = CallHandler.getCallForRoom(this.state.roomId); + const callForRoom = CallHandler.sharedInstance().getCallForRoom(this.state.roomId); const showCall = ( this.state.activeCall && this.state.activeCall.call_state === 'connected' && @@ -106,7 +102,6 @@ export default class CallPreview extends React.Component { ); diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 1d3a62984a..2ab291ae86 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -31,11 +31,6 @@ interface IProps { // room; if not, we will show any active call. room?: Room; - // A Conference Handler implementation - // Must have a function signature: - // getConferenceCallForRoom(roomId: string): MatrixCall - ConferenceHandler?: any; - // maxHeight style attribute for the video panel maxVideoHeight?: number; @@ -96,14 +91,13 @@ export default class CallView extends React.Component { if (this.props.room) { const roomId = this.props.room.roomId; - call = CallHandler.getCallForRoom(roomId) || - (this.props.ConferenceHandler ? this.props.ConferenceHandler.getConferenceCallForRoom(roomId) : null); + call = CallHandler.sharedInstance().getCallForRoom(roomId); if (this.call) { this.setState({ call: call }); } } else { - call = CallHandler.getAnyActiveCall(); + call = CallHandler.sharedInstance().getAnyActiveCall(); // Ignore calls if we can't get the room associated with them. // I think the underlying problem is that the js-sdk sends events // for calls before it has made the rooms available in the store, @@ -115,20 +109,19 @@ export default class CallView extends React.Component { } if (call) { - call.setLocalVideoElement(this.getVideoView().getLocalVideoElement()); - call.setRemoteVideoElement(this.getVideoView().getRemoteVideoElement()); - // always use a separate element for audio stream playback. - // this is to let us move CallView around the DOM without interrupting remote audio - // during playback, by having the audio rendered by a top-level