From 8b914c02d002fdc87efcff21bd55e6774c341f16 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 15 Oct 2024 14:33:13 +0000 Subject: [PATCH 01/38] Upgrade dependency to matrix-js-sdk@34.9.0-rc.0 --- package.json | 2 +- yarn.lock | 28 ++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d8faec40a2..4cbfd47e0f 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "jsrsasign": "^11.0.0", "katex": "^0.16.0", "lodash": "^4.17.21", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "34.9.0-rc.0", "matrix-react-sdk": "github:matrix-org/matrix-react-sdk#develop", "matrix-widget-api": "^1.8.2", "react": "17.0.2", diff --git a/yarn.lock b/yarn.lock index 1215c3e333..654f31a215 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8124,6 +8124,27 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== +matrix-js-sdk@34.9.0-rc.0: + version "34.9.0-rc.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-34.9.0-rc.0.tgz#889bd997c048f7339ccad29c5173eab69ba4705e" + integrity sha512-feljwdWJpXTCBXo1TV030GZMRMBacZJS1Sq1ml+8qVbvrh7cZ71KMuR9oiOivJPnxqPob9oayvzR/YxojZw/uQ== + dependencies: + "@babel/runtime" "^7.12.5" + "@matrix-org/matrix-sdk-crypto-wasm" "^9.0.0" + "@matrix-org/olm" "3.2.15" + another-json "^0.2.0" + bs58 "^6.0.0" + content-type "^1.0.4" + jwt-decode "^4.0.0" + loglevel "^1.7.1" + matrix-events-sdk "0.0.1" + matrix-widget-api "^1.8.2" + oidc-client-ts "^3.0.1" + p-retry "4" + sdp-transform "^2.14.1" + unhomoglyph "^1.0.6" + uuid "10" + "matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "34.7.0" resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b842192a3415dbe91c8a5d13ae7c0fa2b8c239ad" @@ -8151,10 +8172,9 @@ matrix-mock-request@^2.5.0: dependencies: expect "^28.1.0" -matrix-react-sdk@3.113.0: - version "3.113.0" - resolved "https://registry.yarnpkg.com/matrix-react-sdk/-/matrix-react-sdk-3.113.0.tgz#66bb447d6ead469df191a9cd1a412210b18a2c95" - integrity sha512-ZMOcmcCRW1La3dp9Cqyuuk9qUP+OLJ4BCB2zkjwpUbSKO35e1ahFy+dYqtRNFbCz3C2Auzw3ED9kV2uLFkTxLg== +"matrix-react-sdk@github:matrix-org/matrix-react-sdk#develop": + version "3.109.0" + resolved "https://codeload.github.com/matrix-org/matrix-react-sdk/tar.gz/b67a2303f912a8cab5caf089f3ea710850aa252c" dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/analytics-events" "^0.25.0" From 65f0d7930a80bcff6981d6e5eb78ab0096089878 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 15 Oct 2024 14:43:13 +0000 Subject: [PATCH 02/38] Upgrade dependency to matrix-react-sdk@3.114.0-rc.0 --- package.json | 2 +- yarn.lock | 92 ++++++++++++++++++++++++---------------------------- 2 files changed, 44 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index 4cbfd47e0f..ab9e6b0c90 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "katex": "^0.16.0", "lodash": "^4.17.21", "matrix-js-sdk": "34.9.0-rc.0", - "matrix-react-sdk": "github:matrix-org/matrix-react-sdk#develop", + "matrix-react-sdk": "3.114.0-rc.0", "matrix-widget-api": "^1.8.2", "react": "17.0.2", "react-dom": "17.0.2", diff --git a/yarn.lock b/yarn.lock index 654f31a215..da14f39804 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2055,31 +2055,24 @@ resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe" integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== -"@matrix-org/analytics-events@^0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.25.0.tgz#b0b85297dc05a67feaf89cc5d70b80283c988141" - integrity sha512-UCTuMjlJGArMqG9qXGfeNz/XtZDFldwuO+dkqP6Wo1nVdWasoWAOlcimDWQ2JnNFCg+UDZU+HLBdS5juTd6xTg== +"@matrix-org/analytics-events@^0.26.0": + version "0.26.0" + resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.26.0.tgz#7c8f8f924d8313c87951a0e941640ef8ff78f3d6" + integrity sha512-cjKZBejajUG8wPhVygMkBTwTLdEn74luUP6g6RjCUqPR3RYIl3NVi58Zil8CWfRTILb4wVLCPpAvehgXJn1HnQ== -"@matrix-org/emojibase-bindings@^1.1.2": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@matrix-org/emojibase-bindings/-/emojibase-bindings-1.1.3.tgz#d35f0367d586d83261564662b6bb67fda8845d68" - integrity sha512-ljr0kPerx8yUc4JVJz7japebb1ZbtGH4V4cvlO2LYgTg+warjagDwkJ5x+ZUVuTU6MH8x0LrUxmkqVgmSoQyWA== +"@matrix-org/emojibase-bindings@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@matrix-org/emojibase-bindings/-/emojibase-bindings-1.3.3.tgz#cee82a739c0866bf3100b03755647ace1f3ba6ef" + integrity sha512-GwuZdmF+wZT34RKehQYjTzdgba1ju2W3FM4jPJfwqh0jUxVXZLb+6b6dV3lna6/7EDzgGvOMwTwCAolILDwS0g== dependencies: - emojibase "^15.0.0" - emojibase-data "^15.0.0" + emojibase "^15.3.1" + emojibase-data "^15.3.1" "@matrix-org/matrix-sdk-crypto-wasm@^9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-9.0.0.tgz#293fe8fcb9bc4d577c5f6cf2cbffa151c6e11329" integrity sha512-dz4dkYXj6BeOQuw52XQj8dMuhi85pSFhfFeFlNRAO7JdRPhE9CHBrfK8knkZV5Zux5vvf3Ub4E7myoLeJgZoEw== -"@matrix-org/matrix-wysiwyg@2.37.9": - version "2.37.9" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-2.37.9.tgz#4d5667df3c74e11fd01c4b5be920caff72bf2f48" - integrity sha512-Jn2ug6ySX5es5/SV5BVgSBhVPS7GXggwZ/GGWmnJvhnp/IArit4kgUAsNRhQeKebuVvNphwQykCGwyBOn2PLBA== - dependencies: - eslint-plugin-unicorn "^54.0.0" - "@matrix-org/olm@3.2.15": version "3.2.15" resolved "https://registry.yarnpkg.com/@matrix-org/olm/-/olm-3.2.15.tgz#55f3c1b70a21bbee3f9195cecd6846b1083451ec" @@ -3357,7 +3350,7 @@ resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-1.8.0.tgz#bc844cb6b9842c1eb8e5c42f5cedcaf51a49b86f" integrity sha512-PtQMG7kDzwtjw/fLKD63uWP5rJ8cgWc/aXarfEzxYUf9ceWxBajnYOBI2jDqtE3WIUe9uTVBzNEvmOBG/VIgTA== -"@vector-im/compound-web@7.0.0", "@vector-im/compound-web@^5.5.0", "@vector-im/compound-web@^7.0.0": +"@vector-im/compound-web@7.0.0", "@vector-im/compound-web@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.0.0.tgz#2e31711ad6407a667b08ebf67c54f643902d47eb" integrity sha512-ctK+SQdGyaPeylxC2rVePkVfQZK1ftjWc9XbzYoIbZyu4mihgjHgLhd1i02QsNGIAvpxMDxqHjVD8SsrOB2/0g== @@ -3373,6 +3366,13 @@ ts-xor "^1.3.0" vaul "^1.0.0" +"@vector-im/matrix-wysiwyg@2.37.13": + version "2.37.13" + resolved "https://registry.yarnpkg.com/@vector-im/matrix-wysiwyg/-/matrix-wysiwyg-2.37.13.tgz#7b64e7652a196e811c0470dcbff8d36e2229cd46" + integrity sha512-3ilnJBJSzeTfAQXNJJ29EAqa14exrkca3acTPssdoh3WhTKwXyQ0xU8sS50d6gqlAP/Nahw1y2k+PzajXo1SsQ== + dependencies: + eslint-plugin-unicorn "^54.0.0" + "@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": version "1.12.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" @@ -4718,6 +4718,14 @@ css-tree@2.3.1, css-tree@^2.3.1: mdn-data "2.0.30" source-map-js "^1.0.1" +css-tree@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-3.0.0.tgz#079c7b87e465a28cedbc826502f9a227213db0f3" + integrity sha512-o88DVQ6GzsABn1+6+zo2ct801dBO5OASVyxbbvA2W20ue2puSh/VOuqUj90eUeMSX/xqGqBmOKiRQN7tJOuBXw== + dependencies: + mdn-data "2.10.0" + source-map-js "^1.0.1" + css-tree@~2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" @@ -5238,7 +5246,7 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -emojibase-data@^15.0.0: +emojibase-data@^15.3.1: version "15.3.2" resolved "https://registry.yarnpkg.com/emojibase-data/-/emojibase-data-15.3.2.tgz#2742246bfe14f16a7829b42ca156dec09934cf85" integrity sha512-TpDyTDDTdqWIJixV5sTA6OQ0P0JfIIeK2tFRR3q56G9LK65ylAZ7z3KyBXokpvTTJ+mLUXQXbLNyVkjvnTLE+A== @@ -5248,7 +5256,7 @@ emojibase-regex@15.3.2: resolved "https://registry.yarnpkg.com/emojibase-regex/-/emojibase-regex-15.3.2.tgz#5175231715b86d4b437754527288844a6c29318f" integrity sha512-ue6BVeb2qu33l97MkxcOoyMJlg6Tug3eTv2z1at+M9TjvlWKvdmAPvZIDG1JbT2RH3FSyJNLucO5K5H/yxT03w== -emojibase@^15.0.0: +emojibase@^15.3.1: version "15.3.1" resolved "https://registry.yarnpkg.com/emojibase/-/emojibase-15.3.1.tgz#7f6ff5482486f23e59a457de64e974bd35f3c9a3" integrity sha512-GNsjHnG2J3Ktg684Fs/vZR/6XpOSkZPMAv85EHrr6br2RN2cJNwdS4am/3YSK3y+/gOv2kmoK3GGdahXdMxg2g== @@ -8145,26 +8153,6 @@ matrix-js-sdk@34.9.0-rc.0: unhomoglyph "^1.0.6" uuid "10" -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "34.7.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b842192a3415dbe91c8a5d13ae7c0fa2b8c239ad" - dependencies: - "@babel/runtime" "^7.12.5" - "@matrix-org/matrix-sdk-crypto-wasm" "^9.0.0" - "@matrix-org/olm" "3.2.15" - another-json "^0.2.0" - bs58 "^6.0.0" - content-type "^1.0.4" - jwt-decode "^4.0.0" - loglevel "^1.7.1" - matrix-events-sdk "0.0.1" - matrix-widget-api "^1.8.2" - oidc-client-ts "^3.0.1" - p-retry "4" - sdp-transform "^2.14.1" - unhomoglyph "^1.0.6" - uuid "10" - matrix-mock-request@^2.5.0: version "2.6.0" resolved "https://registry.yarnpkg.com/matrix-mock-request/-/matrix-mock-request-2.6.0.tgz#0855c10b250668ce542b697251087be2bcc23f92" @@ -8172,20 +8160,21 @@ matrix-mock-request@^2.5.0: dependencies: expect "^28.1.0" -"matrix-react-sdk@github:matrix-org/matrix-react-sdk#develop": - version "3.109.0" - resolved "https://codeload.github.com/matrix-org/matrix-react-sdk/tar.gz/b67a2303f912a8cab5caf089f3ea710850aa252c" +matrix-react-sdk@3.114.0-rc.0: + version "3.114.0-rc.0" + resolved "https://registry.yarnpkg.com/matrix-react-sdk/-/matrix-react-sdk-3.114.0-rc.0.tgz#fa3f3a50c281c9dd20e067b7ed45fcc0d9023634" + integrity sha512-PKmQrAmygCho+XW6Y2i3FXLL7do/yZ6nTUPuhfuB+2oT8sq3BgZ7yGsNxvFMdXoP8uViPgjFSt0bQCG2YpbtjA== dependencies: "@babel/runtime" "^7.12.5" - "@matrix-org/analytics-events" "^0.25.0" - "@matrix-org/emojibase-bindings" "^1.1.2" - "@matrix-org/matrix-wysiwyg" "2.37.9" + "@matrix-org/analytics-events" "^0.26.0" + "@matrix-org/emojibase-bindings" "^1.3.3" "@matrix-org/react-sdk-module-api" "^2.4.0" "@matrix-org/spec" "^1.7.0" "@sentry/browser" "^8.0.0" "@testing-library/react-hooks" "^8.0.1" "@vector-im/compound-design-tokens" "^1.8.0" - "@vector-im/compound-web" "^5.5.0" + "@vector-im/compound-web" "^7.0.0" + "@vector-im/matrix-wysiwyg" "2.37.13" "@zxcvbn-ts/core" "^3.0.4" "@zxcvbn-ts/language-common" "^3.0.4" "@zxcvbn-ts/language-en" "^3.0.2" @@ -8196,7 +8185,7 @@ matrix-mock-request@^2.5.0: classnames "^2.2.6" commonmark "^0.31.0" counterpart "^0.18.6" - css-tree "^2.3.1" + css-tree "^3.0.0" diff-dom "^5.0.0" diff-match-patch "^1.0.5" emojibase-regex "15.3.2" @@ -8219,7 +8208,7 @@ matrix-mock-request@^2.5.0: maplibre-gl "^2.0.0" matrix-encrypt-attachment "^1.0.3" matrix-events-sdk "0.0.1" - matrix-js-sdk "github:matrix-org/matrix-js-sdk#develop" + matrix-js-sdk "34.9.0-rc.0" matrix-widget-api "^1.9.0" memoize-one "^6.0.0" minimist "^1.2.5" @@ -8274,6 +8263,11 @@ mdn-data@2.0.30: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== +mdn-data@2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.10.0.tgz#701da407f8fbc7a42aa0ba0c149ec897daef8986" + integrity sha512-qq7C3EtK3yJXMwz1zAab65pjl+UhohqMOctTgcqjLOWABqmwj+me02LSsCuEUxnst9X1lCBpoE0WArGKgdGDzw== + mdurl@^1.0.1, mdurl@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" From 3793c6daca5122f37460945d4a70737f56a46101 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 15 Oct 2024 14:57:48 +0000 Subject: [PATCH 03/38] v1.11.82-rc.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ab9e6b0c90..02454cb5bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "element-web", - "version": "1.11.81", + "version": "1.11.82-rc.0", "description": "A feature-rich client for Matrix.org", "author": "New Vector Ltd.", "repository": { From 26cd13ae3c342e8925d09a7ee01c70cc7c36b184 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 22 Oct 2024 11:59:48 +0000 Subject: [PATCH 04/38] Upgrade dependency to matrix-js-sdk@34.9.0 --- package.json | 2 +- yarn.lock | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 02454cb5bd..0d89459215 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "jsrsasign": "^11.0.0", "katex": "^0.16.0", "lodash": "^4.17.21", - "matrix-js-sdk": "34.9.0-rc.0", + "matrix-js-sdk": "34.9.0", "matrix-react-sdk": "3.114.0-rc.0", "matrix-widget-api": "^1.8.2", "react": "17.0.2", diff --git a/yarn.lock b/yarn.lock index da14f39804..b53b4f4cd6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8132,6 +8132,27 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== +matrix-js-sdk@34.9.0: + version "34.9.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-34.9.0.tgz#58ec0d09f0119bfb883c0babdf791694e3d740a3" + integrity sha512-Fm/2uKFgVthk04GcEcXvb+I8PquPyupDCoAipVz+TE4FfVR0gPgem/vxfQPqy7Ejwvx9ClCP2B6giqcQVj1aqQ== + dependencies: + "@babel/runtime" "^7.12.5" + "@matrix-org/matrix-sdk-crypto-wasm" "^9.0.0" + "@matrix-org/olm" "3.2.15" + another-json "^0.2.0" + bs58 "^6.0.0" + content-type "^1.0.4" + jwt-decode "^4.0.0" + loglevel "^1.7.1" + matrix-events-sdk "0.0.1" + matrix-widget-api "^1.8.2" + oidc-client-ts "^3.0.1" + p-retry "4" + sdp-transform "^2.14.1" + unhomoglyph "^1.0.6" + uuid "10" + matrix-js-sdk@34.9.0-rc.0: version "34.9.0-rc.0" resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-34.9.0-rc.0.tgz#889bd997c048f7339ccad29c5173eab69ba4705e" From 6a960204b33bf887d55b65c9ed0d2df88b922312 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 22 Oct 2024 12:07:41 +0000 Subject: [PATCH 05/38] Upgrade dependency to matrix-react-sdk@3.114.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 0d89459215..32c0c8144c 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "katex": "^0.16.0", "lodash": "^4.17.21", "matrix-js-sdk": "34.9.0", - "matrix-react-sdk": "3.114.0-rc.0", + "matrix-react-sdk": "3.114.0", "matrix-widget-api": "^1.8.2", "react": "17.0.2", "react-dom": "17.0.2", diff --git a/yarn.lock b/yarn.lock index b53b4f4cd6..dd408b466a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8181,10 +8181,10 @@ matrix-mock-request@^2.5.0: dependencies: expect "^28.1.0" -matrix-react-sdk@3.114.0-rc.0: - version "3.114.0-rc.0" - resolved "https://registry.yarnpkg.com/matrix-react-sdk/-/matrix-react-sdk-3.114.0-rc.0.tgz#fa3f3a50c281c9dd20e067b7ed45fcc0d9023634" - integrity sha512-PKmQrAmygCho+XW6Y2i3FXLL7do/yZ6nTUPuhfuB+2oT8sq3BgZ7yGsNxvFMdXoP8uViPgjFSt0bQCG2YpbtjA== +matrix-react-sdk@3.114.0: + version "3.114.0" + resolved "https://registry.yarnpkg.com/matrix-react-sdk/-/matrix-react-sdk-3.114.0.tgz#482cdc2c90e5a2bb5ec54a378e557a33a06015a9" + integrity sha512-LeHJx54SksTAKtf53mXEpp9smrw9hiEyhGRJhV9I2ZN1gFewhfit9Ey8tnqCJnag//0giTm+l4RKl86XUiotRA== dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/analytics-events" "^0.26.0" From 1df72ce2d047eb4d8149d56b788e3649b46d180c Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 22 Oct 2024 12:10:29 +0000 Subject: [PATCH 06/38] v1.11.82 --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef18822d38..c78e5379d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,35 @@ +Changes in [1.11.82](https://github.com/element-hq/element-web/releases/tag/v1.11.82) (2024-10-22) +================================================================================================== +## ✹ Features + +* Deduplicate more icons using Compound Design Tokens ([#132](https://github.com/element-hq/matrix-react-sdk/pull/132)). Contributed by @t3chguy. +* Always show link new device flow even if unsupported ([#147](https://github.com/element-hq/matrix-react-sdk/pull/147)). Contributed by @t3chguy. +* Update design of files list in right panel ([#144](https://github.com/element-hq/matrix-react-sdk/pull/144)). Contributed by @t3chguy. +* Remove feature\_dehydration ([#138](https://github.com/element-hq/matrix-react-sdk/pull/138)). Contributed by @florianduros. +* Upgrade emojibase-bindings and remove local handling of emoticon variations ([#127](https://github.com/element-hq/matrix-react-sdk/pull/127)). Contributed by @langleyd. +* Add support for rendering media captions ([#43](https://github.com/element-hq/matrix-react-sdk/pull/43)). Contributed by @tulir. +* Replace composer icons with Compound variants ([#123](https://github.com/element-hq/matrix-react-sdk/pull/123)). Contributed by @t3chguy. +* Tweak default right panel size to be 320px except for maximised widgets at 420px ([#110](https://github.com/element-hq/matrix-react-sdk/pull/110)). Contributed by @t3chguy. +* Add a pinned message badge under a pinned message ([#118](https://github.com/element-hq/matrix-react-sdk/pull/118)). Contributed by @florianduros. +* Ditch right panel tabs and re-add close button ([#99](https://github.com/element-hq/matrix-react-sdk/pull/99)). Contributed by @t3chguy. +* Force verification even for refreshed clients ([#44](https://github.com/element-hq/matrix-react-sdk/pull/44)). Contributed by @dbkr. +* Update emoji text, border and background colour in timeline ([#119](https://github.com/element-hq/matrix-react-sdk/pull/119)). Contributed by @florianduros. +* Disable ICE fallback based on well-known configuration ([#111](https://github.com/element-hq/matrix-react-sdk/pull/111)). Contributed by @t3chguy. +* Remove legacy room header and promote beta room header ([#105](https://github.com/element-hq/matrix-react-sdk/pull/105)). Contributed by @t3chguy. +* Respect `io.element.jitsi` `useFor1To1Calls` in well-known ([#112](https://github.com/element-hq/matrix-react-sdk/pull/112)). Contributed by @t3chguy. +* Use Compound close icon in favour of mishmash of x/close icons ([#108](https://github.com/element-hq/matrix-react-sdk/pull/108)). Contributed by @t3chguy. + +## 🐛 Bug Fixes + +* Correct typo in option documentation ([#28148](https://github.com/element-hq/element-web/pull/28148)). Contributed by @AndrewKvalheim. +* Revert #124 and #135 ([#139](https://github.com/element-hq/matrix-react-sdk/pull/139)). Contributed by @dbkr. +* Add aria-label to e2e icon ([#136](https://github.com/element-hq/matrix-react-sdk/pull/136)). Contributed by @florianduros. +* Fix bell icons on room list hover being black squares ([#135](https://github.com/element-hq/matrix-react-sdk/pull/135)). Contributed by @dbkr. +* Fix vertical overflow on the mobile register screen ([#137](https://github.com/element-hq/matrix-react-sdk/pull/137)). Contributed by @langleyd. +* Allow to unpin redacted event ([#98](https://github.com/element-hq/matrix-react-sdk/pull/98)). Contributed by @florianduros. + + + Changes in [1.11.81](https://github.com/element-hq/element-web/releases/tag/v1.11.81) (2024-10-15) ================================================================================================== This release fixes High severity vulnerability CVE-2024-47771 / GHSA-963w-49j9-gxj6 diff --git a/package.json b/package.json index 32c0c8144c..e20201d9e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "element-web", - "version": "1.11.82-rc.0", + "version": "1.11.82", "description": "A feature-rich client for Matrix.org", "author": "New Vector Ltd.", "repository": { From 9ce515a646fe44135734fbd2b1f12614bcb80780 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 28 Oct 2024 15:20:40 +0000 Subject: [PATCH 07/38] Enable Element Call by default on release instances (#28313) Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> (cherry picked from commit 79c956388f4b4e0feb15d6bc9c4ceabcfb7b9839) Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- element.io/app/config.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/element.io/app/config.json b/element.io/app/config.json index 4dcc75aeeb..771df35091 100644 --- a/element.io/app/config.json +++ b/element.io/app/config.json @@ -46,5 +46,13 @@ "map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx", "setting_defaults": { "RustCrypto.staged_rollout_percent": 60 + }, + "features": { + "feature_video_rooms": true, + "feature_group_calls": true, + "feature_element_call_video_rooms": true + }, + "element_call": { + "url": "https://call.element.io" } } From dabe6722aad768f1a09fa93062feaa5710300130 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 29 Oct 2024 09:23:20 +0000 Subject: [PATCH 08/38] v1.11.83 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c78e5379d1..828c352228 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +Changes in [1.11.83](https://github.com/element-hq/element-web/releases/tag/v1.11.83) (2024-10-29) +================================================================================================== +## ✹ Features + +* Enable Element Call by default on release instances ([#28314](https://github.com/element-hq/element-web/pull/28314)). Contributed by @t3chguy. + + + Changes in [1.11.82](https://github.com/element-hq/element-web/releases/tag/v1.11.82) (2024-10-22) ================================================================================================== ## ✹ Features diff --git a/package.json b/package.json index e20201d9e1..0478590c6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "element-web", - "version": "1.11.82", + "version": "1.11.83", "description": "A feature-rich client for Matrix.org", "author": "New Vector Ltd.", "repository": { From 8ebfaadeed9d63dae92987c15a959810c1af400e Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 29 Oct 2024 12:57:26 +0000 Subject: [PATCH 09/38] Upgrade dependency to matrix-js-sdk@34.10.0-rc.0 --- package.json | 6 +++--- yarn.lock | 38 ++++++++++++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 5b52a30e7c..057d7d0e97 100644 --- a/package.json +++ b/package.json @@ -86,12 +86,12 @@ "@formatjs/intl-segmenter": "^11.5.7", "@matrix-org/analytics-events": "^0.27.0", "@matrix-org/emojibase-bindings": "^1.3.3", - "@vector-im/matrix-wysiwyg": "2.37.13", "@matrix-org/react-sdk-module-api": "^2.4.0", "@matrix-org/spec": "^1.7.0", "@sentry/browser": "^8.0.0", "@vector-im/compound-design-tokens": "^1.8.0", "@vector-im/compound-web": "^7.1.0", + "@vector-im/matrix-wysiwyg": "2.37.13", "@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4", "@zxcvbn-ts/language-en": "^3.0.2", @@ -114,8 +114,8 @@ "highlight.js": "^11.3.1", "html-entities": "^2.0.0", "is-ip": "^3.1.0", - "jsrsasign": "^11.0.0", "js-xxhash": "^4.0.0", + "jsrsasign": "^11.0.0", "jszip": "^3.7.0", "katex": "^0.16.0", "linkify-element": "4.1.3", @@ -126,7 +126,7 @@ "maplibre-gl": "^2.0.0", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "34.10.0-rc.0", "matrix-widget-api": "^1.9.0", "memoize-one": "^6.0.0", "oidc-client-ts": "^3.0.1", diff --git a/yarn.lock b/yarn.lock index f651bff3d7..08be132eef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8250,9 +8250,10 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "34.8.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/66c80949e8421e94e7d3672013fac1943e363f67" +matrix-js-sdk@34.10.0-rc.0: + version "34.10.0-rc.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-34.10.0-rc.0.tgz#5f395d4bfd89fac1dc7517fcffe24903b591d236" + integrity sha512-gIZB5TH4F9hXrAL96LSE0EpAnvH//SV/hnb7/bAFJBKWtjVvZs0sgRFsn/qJmTKQ+fpu6+uucPrQD2hwdrkYBA== dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-wasm" "^9.0.0" @@ -10832,7 +10833,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10936,7 +10946,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -12024,7 +12041,16 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^6.2.0, wrap-ansi@^7.0.0, wrap-ansi@^8.1.0, wrap-ansi@^9.0.0, "wrap-ansi@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^6.2.0, wrap-ansi@^7.0.0, wrap-ansi@^8.1.0, wrap-ansi@^9.0.0, "wrap-ansi@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From 849f2c9818d5490b87b3fc655749644ec67b5e7f Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 29 Oct 2024 13:02:40 +0000 Subject: [PATCH 10/38] v1.11.84-rc.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 057d7d0e97..b2758b7b2d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "element-web", - "version": "1.11.83", + "version": "1.11.84-rc.0", "description": "A feature-rich client for Matrix.org", "author": "New Vector Ltd.", "repository": { From 2d9982f9f087de4010f6984fb5a2958942332d3a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 1 Nov 2024 15:15:04 +0000 Subject: [PATCH 11/38] Remove boilerplate around dispatcher and settings watchers (#28338) * Remove boilerplate around dispatcher and settings watchers Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/DeviceListener.ts | 10 +++------- src/PosthogAnalytics.ts | 2 +- src/Presence.ts | 20 ++++++++----------- src/components/structures/EmbeddedPage.tsx | 4 ++-- src/components/structures/LoggedInView.tsx | 6 +++--- src/components/structures/ThreadView.tsx | 4 ++-- src/components/structures/UserMenu.tsx | 6 +++--- .../views/dialogs/RoomSettingsDialog.tsx | 4 +--- src/components/views/elements/AppTile.tsx | 4 ++-- .../views/messages/DateSeparator.tsx | 2 +- src/components/views/messages/MImageBody.tsx | 2 +- src/components/views/messages/MVideoBody.tsx | 2 +- .../views/right_panel/TimelineCard.tsx | 10 +++------- src/components/views/rooms/AppsDrawer.tsx | 2 +- .../views/rooms/MessageComposer.tsx | 2 +- src/components/views/rooms/RoomList.tsx | 2 +- src/components/views/rooms/RoomSublist.tsx | 2 +- src/components/views/rooms/RoomTile.tsx | 2 +- src/components/views/rooms/Stickerpicker.tsx | 4 +--- .../views/settings/FontScalingPanel.tsx | 4 +--- .../views/settings/IntegrationManager.tsx | 2 +- src/components/views/settings/SetIdServer.tsx | 2 +- .../tabs/user/SecurityUserSettingsTab.tsx | 2 +- src/components/views/voip/LegacyCallView.tsx | 2 +- src/dispatcher/dispatcher.ts | 5 ++++- src/mjolnir/Mjolnir.ts | 18 +++++++---------- src/models/Call.ts | 14 +++++-------- src/settings/SettingsStore.ts | 7 ++++--- src/settings/watchers/FontWatcher.ts | 7 +------ src/settings/watchers/ThemeWatcher.ts | 16 ++++++--------- src/stores/AsyncStore.ts | 2 +- src/stores/AsyncStoreWithClient.ts | 2 +- src/stores/OwnBeaconStore.ts | 2 +- src/stores/ReadyWatchingStore.ts | 10 +++++----- src/stores/widgets/WidgetLayoutStore.ts | 6 +++--- test/unit-tests/DeviceListener-test.ts | 1 + 36 files changed, 81 insertions(+), 111 deletions(-) diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index 3cf153fc58..02e26729d2 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -113,13 +113,9 @@ export default class DeviceListener { this.client.removeListener(ClientEvent.Sync, this.onSync); this.client.removeListener(RoomStateEvent.Events, this.onRoomStateEvents); } - if (this.deviceClientInformationSettingWatcherRef) { - SettingsStore.unwatchSetting(this.deviceClientInformationSettingWatcherRef); - } - if (this.dispatcherRef) { - dis.unregister(this.dispatcherRef); - this.dispatcherRef = undefined; - } + SettingsStore.unwatchSetting(this.deviceClientInformationSettingWatcherRef); + dis.unregister(this.dispatcherRef); + this.dispatcherRef = undefined; this.dismissed.clear(); this.dismissedThisDeviceToast = false; this.keyBackupInfo = null; diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts index 2ffe8b8166..6217d9b7dd 100644 --- a/src/PosthogAnalytics.ts +++ b/src/PosthogAnalytics.ts @@ -326,7 +326,7 @@ export class PosthogAnalytics { if (this.enabled) { this.posthog.reset(); } - if (this.watchSettingRef) SettingsStore.unwatchSetting(this.watchSettingRef); + SettingsStore.unwatchSetting(this.watchSettingRef); this.setAnonymity(Anonymity.Disabled); } diff --git a/src/Presence.ts b/src/Presence.ts index 11a333ce04..af06d4a1d6 100644 --- a/src/Presence.ts +++ b/src/Presence.ts @@ -20,9 +20,9 @@ import { ActionPayload } from "./dispatcher/payloads"; const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins class Presence { - private unavailableTimer: Timer | null = null; - private dispatcherRef: string | null = null; - private state: SetPresence | null = null; + private unavailableTimer?: Timer; + private dispatcherRef?: string; + private state?: SetPresence; /** * Start listening the user activity to evaluate his presence state. @@ -46,14 +46,10 @@ class Presence { * Stop tracking user activity */ public stop(): void { - if (this.dispatcherRef) { - dis.unregister(this.dispatcherRef); - this.dispatcherRef = null; - } - if (this.unavailableTimer) { - this.unavailableTimer.abort(); - this.unavailableTimer = null; - } + dis.unregister(this.dispatcherRef); + this.dispatcherRef = undefined; + this.unavailableTimer?.abort(); + this.unavailableTimer = undefined; } /** @@ -61,7 +57,7 @@ class Presence { * @returns {string} the presence state (see PRESENCE enum) */ public getState(): SetPresence | null { - return this.state; + return this.state ?? null; } private onAction = (payload: ActionPayload): void => { diff --git a/src/components/structures/EmbeddedPage.tsx b/src/components/structures/EmbeddedPage.tsx index 5de1261ecb..5c7e81caf5 100644 --- a/src/components/structures/EmbeddedPage.tsx +++ b/src/components/structures/EmbeddedPage.tsx @@ -38,7 +38,7 @@ export default class EmbeddedPage extends React.PureComponent { public static contextType = MatrixClientContext; public declare context: React.ContextType; private unmounted = false; - private dispatcherRef: string | null = null; + private dispatcherRef?: string; public constructor(props: IProps, context: React.ContextType) { super(props, context); @@ -100,7 +100,7 @@ export default class EmbeddedPage extends React.PureComponent { public componentWillUnmount(): void { this.unmounted = true; - if (this.dispatcherRef !== null) dis.unregister(this.dispatcherRef); + dis.unregister(this.dispatcherRef); } private onAction = (payload: ActionPayload): void => { diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 84c43fc19d..75156cdf60 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -228,9 +228,9 @@ class LoggedInView extends React.Component { this._matrixClient.removeListener(ClientEvent.Sync, this.onSync); this._matrixClient.removeListener(RoomStateEvent.Events, this.onRoomStateEvents); OwnProfileStore.instance.off(UPDATE_EVENT, this.refreshBackgroundImage); - if (this.layoutWatcherRef) SettingsStore.unwatchSetting(this.layoutWatcherRef); - if (this.compactLayoutWatcherRef) SettingsStore.unwatchSetting(this.compactLayoutWatcherRef); - if (this.backgroundImageWatcherRef) SettingsStore.unwatchSetting(this.backgroundImageWatcherRef); + SettingsStore.unwatchSetting(this.layoutWatcherRef); + SettingsStore.unwatchSetting(this.compactLayoutWatcherRef); + SettingsStore.unwatchSetting(this.backgroundImageWatcherRef); this.timezoneProfileUpdateRef?.forEach((s) => SettingsStore.unwatchSetting(s)); this.resizer?.detach(); } diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index e5c1ccb266..8d2a286de1 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -77,7 +77,7 @@ export default class ThreadView extends React.Component { public static contextType = RoomContext; public declare context: React.ContextType; - private dispatcherRef: string | null = null; + private dispatcherRef?: string; private readonly layoutWatcherRef: string; private timelinePanel = createRef(); private card = createRef(); @@ -118,7 +118,7 @@ export default class ThreadView extends React.Component { } public componentWillUnmount(): void { - if (this.dispatcherRef) dis.unregister(this.dispatcherRef); + dis.unregister(this.dispatcherRef); const roomId = this.props.mxEvent.getRoomId(); SettingsStore.unwatchSetting(this.layoutWatcherRef); diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 971e07193b..84bd93cc36 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -121,9 +121,9 @@ export default class UserMenu extends React.Component { } public componentWillUnmount(): void { - if (this.themeWatcherRef) SettingsStore.unwatchSetting(this.themeWatcherRef); - if (this.dndWatcherRef) SettingsStore.unwatchSetting(this.dndWatcherRef); - if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef); + SettingsStore.unwatchSetting(this.themeWatcherRef); + SettingsStore.unwatchSetting(this.dndWatcherRef); + defaultDispatcher.unregister(this.dispatcherRef); OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate); SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate); this.context.voiceBroadcastRecordingsStore.off( diff --git a/src/components/views/dialogs/RoomSettingsDialog.tsx b/src/components/views/dialogs/RoomSettingsDialog.tsx index 2c4656745a..cb804b8e00 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.tsx +++ b/src/components/views/dialogs/RoomSettingsDialog.tsx @@ -80,9 +80,7 @@ class RoomSettingsDialog extends React.Component { } public componentWillUnmount(): void { - if (this.dispatcherRef) { - dis.unregister(this.dispatcherRef); - } + dis.unregister(this.dispatcherRef); MatrixClientPeg.get()?.removeListener(RoomEvent.Name, this.onRoomName); MatrixClientPeg.get()?.removeListener(RoomStateEvent.Events, this.onStateEvent); diff --git a/src/components/views/elements/AppTile.tsx b/src/components/views/elements/AppTile.tsx index 4803ac5272..8f5aa732be 100644 --- a/src/components/views/elements/AppTile.tsx +++ b/src/components/views/elements/AppTile.tsx @@ -340,13 +340,13 @@ export default class AppTile extends React.Component { } // Widget action listeners - if (this.dispatcherRef) dis.unregister(this.dispatcherRef); + dis.unregister(this.dispatcherRef); if (this.props.room) { this.context.off(RoomEvent.MyMembership, this.onMyMembership); } - if (this.allowedWidgetsWatchRef) SettingsStore.unwatchSetting(this.allowedWidgetsWatchRef); + SettingsStore.unwatchSetting(this.allowedWidgetsWatchRef); OwnProfileStore.instance.removeListener(UPDATE_EVENT, this.onUserReady); } diff --git a/src/components/views/messages/DateSeparator.tsx b/src/components/views/messages/DateSeparator.tsx index 10ec7ddad9..7996b3bebe 100644 --- a/src/components/views/messages/DateSeparator.tsx +++ b/src/components/views/messages/DateSeparator.tsx @@ -71,7 +71,7 @@ export default class DateSeparator extends React.Component { } public componentWillUnmount(): void { - if (this.settingWatcherRef) SettingsStore.unwatchSetting(this.settingWatcherRef); + SettingsStore.unwatchSetting(this.settingWatcherRef); } private onContextMenuOpenClick = (e: ButtonEvent): void => { diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index 5c7a553364..332777be5e 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -368,7 +368,7 @@ export default class MImageBody extends React.Component { this.unmounted = true; MatrixClientPeg.get()?.off(ClientEvent.Sync, this.reconnectedListener); this.clearBlurhashTimeout(); - if (this.sizeWatcher) SettingsStore.unwatchSetting(this.sizeWatcher); + SettingsStore.unwatchSetting(this.sizeWatcher); if (this.state.isAnimated && this.state.thumbUrl) { URL.revokeObjectURL(this.state.thumbUrl); } diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index 4900432b8c..4036b9ddec 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -175,7 +175,7 @@ export default class MVideoBody extends React.PureComponent } public componentWillUnmount(): void { - if (this.sizeWatcher) SettingsStore.unwatchSetting(this.sizeWatcher); + SettingsStore.unwatchSetting(this.sizeWatcher); } private videoOnPlay = async (): Promise => { diff --git a/src/components/views/right_panel/TimelineCard.tsx b/src/components/views/right_panel/TimelineCard.tsx index 4f9d1dd917..e0988eeaa5 100644 --- a/src/components/views/right_panel/TimelineCard.tsx +++ b/src/components/views/right_panel/TimelineCard.tsx @@ -100,14 +100,10 @@ export default class TimelineCard extends React.Component { public componentWillUnmount(): void { SdkContextClass.instance.roomViewStore.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate); - if (this.readReceiptsSettingWatcher) { - SettingsStore.unwatchSetting(this.readReceiptsSettingWatcher); - } - if (this.layoutWatcherRef) { - SettingsStore.unwatchSetting(this.layoutWatcherRef); - } + SettingsStore.unwatchSetting(this.readReceiptsSettingWatcher); + SettingsStore.unwatchSetting(this.layoutWatcherRef); - if (this.dispatcherRef) dis.unregister(this.dispatcherRef); + dis.unregister(this.dispatcherRef); } private onRoomViewStoreUpdate = async (_initial?: boolean): Promise => { diff --git a/src/components/views/rooms/AppsDrawer.tsx b/src/components/views/rooms/AppsDrawer.tsx index cba6e6c691..1d768cae35 100644 --- a/src/components/views/rooms/AppsDrawer.tsx +++ b/src/components/views/rooms/AppsDrawer.tsx @@ -82,7 +82,7 @@ export default class AppsDrawer extends React.Component { this.unmounted = true; ScalarMessaging.stopListening(); WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(this.props.room), this.updateApps); - if (this.dispatcherRef) dis.unregister(this.dispatcherRef); + dis.unregister(this.dispatcherRef); if (this.resizeContainer) { this.resizer.detach(); } diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index e44265b947..c8d1573ebc 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -331,7 +331,7 @@ export class MessageComposer extends React.Component { public componentWillUnmount(): void { VoiceRecordingStore.instance.off(UPDATE_EVENT, this.onVoiceStoreUpdate); - if (this.dispatcherRef) dis.unregister(this.dispatcherRef); + dis.unregister(this.dispatcherRef); UIStore.instance.stopTrackingElementDimensions(`MessageComposer${this.instanceId}`); UIStore.instance.removeListener(`MessageComposer${this.instanceId}`, this.onResize); diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index e27b2ca03f..853bebc4fe 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -446,7 +446,7 @@ export default class RoomList extends React.PureComponent { public componentWillUnmount(): void { SpaceStore.instance.off(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms); RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists); - if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef); + defaultDispatcher.unregister(this.dispatcherRef); SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate); } diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx index 12f6e70d31..34961c0853 100644 --- a/src/components/views/rooms/RoomSublist.tsx +++ b/src/components/views/rooms/RoomSublist.tsx @@ -248,7 +248,7 @@ export default class RoomSublist extends React.Component { } public componentWillUnmount(): void { - if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef); + defaultDispatcher.unregister(this.dispatcherRef); RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onListsUpdated); RoomListStore.instance.off(LISTS_LOADING_EVENT, this.onListsLoading); this.tilesRef.current?.removeEventListener("scroll", this.onScrollPrevent); diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index 93fb42f447..de14808f33 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -175,7 +175,7 @@ export class RoomTile extends React.PureComponent { this.onRoomPreviewChanged, ); this.props.room.off(RoomEvent.Name, this.onRoomNameUpdate); - if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef); + defaultDispatcher.unregister(this.dispatcherRef); this.notificationState.off(NotificationStateEvents.Update, this.onNotificationUpdate); this.roomProps.off(PROPERTY_UPDATED, this.onRoomPropertyUpdate); CallStore.instance.off(CallStoreEvent.Call, this.onCallChanged); diff --git a/src/components/views/rooms/Stickerpicker.tsx b/src/components/views/rooms/Stickerpicker.tsx index 6793f90159..ad1b11f368 100644 --- a/src/components/views/rooms/Stickerpicker.tsx +++ b/src/components/views/rooms/Stickerpicker.tsx @@ -141,9 +141,7 @@ export default class Stickerpicker extends React.PureComponent { if (client) client.removeListener(ClientEvent.AccountData, this.updateWidget); RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate); window.removeEventListener("resize", this.onResize); - if (this.dispatcherRef) { - dis.unregister(this.dispatcherRef); - } + dis.unregister(this.dispatcherRef); } public componentDidUpdate(): void { diff --git a/src/components/views/settings/FontScalingPanel.tsx b/src/components/views/settings/FontScalingPanel.tsx index 5cdd9d16bb..f6dedb3ffb 100644 --- a/src/components/views/settings/FontScalingPanel.tsx +++ b/src/components/views/settings/FontScalingPanel.tsx @@ -79,9 +79,7 @@ export default class FontScalingPanel extends React.Component { public componentWillUnmount(): void { this.unmounted = true; - if (this.layoutWatcherRef) { - SettingsStore.unwatchSetting(this.layoutWatcherRef); - } + SettingsStore.unwatchSetting(this.layoutWatcherRef); } /** diff --git a/src/components/views/settings/IntegrationManager.tsx b/src/components/views/settings/IntegrationManager.tsx index 91b3b4633f..3a31a9e9c8 100644 --- a/src/components/views/settings/IntegrationManager.tsx +++ b/src/components/views/settings/IntegrationManager.tsx @@ -52,7 +52,7 @@ export default class IntegrationManager extends React.Component } public componentWillUnmount(): void { - if (this.dispatcherRef) dis.unregister(this.dispatcherRef); + dis.unregister(this.dispatcherRef); document.removeEventListener("keydown", this.onKeyDown); } diff --git a/src/components/views/settings/SetIdServer.tsx b/src/components/views/settings/SetIdServer.tsx index e7a55f1133..8ed6461d0a 100644 --- a/src/components/views/settings/SetIdServer.tsx +++ b/src/components/views/settings/SetIdServer.tsx @@ -101,7 +101,7 @@ export default class SetIdServer extends React.Component { } public componentWillUnmount(): void { - if (this.dispatcherRef) dis.unregister(this.dispatcherRef); + dis.unregister(this.dispatcherRef); } private onAction = (payload: ActionPayload): void => { diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx index 9e15df6e92..ba4b5eb54b 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx @@ -129,7 +129,7 @@ export default class SecurityUserSettingsTab extends React.Component { document.removeEventListener("keydown", this.onNativeKeyDown); this.updateCallListeners(this.props.call, null); - if (this.dispatcherRef) dis.unregister(this.dispatcherRef); + dis.unregister(this.dispatcherRef); } public static getDerivedStateFromProps(props: IProps): Partial { diff --git a/src/dispatcher/dispatcher.ts b/src/dispatcher/dispatcher.ts index 6d8d3a15a8..0c28de0e2b 100644 --- a/src/dispatcher/dispatcher.ts +++ b/src/dispatcher/dispatcher.ts @@ -45,8 +45,11 @@ export class MatrixDispatcher { /** * Removes a callback based on its token. + * @param id The token that was returned by `register`. + * Can be undefined to avoid needing an if around every caller. */ - public unregister(id: DispatchToken): void { + public unregister(id: DispatchToken | undefined): void { + if (!id) return; invariant(this.callbacks.has(id), `Dispatcher.unregister(...): '${id}' does not map to a registered callback.`); this.callbacks.delete(id); } diff --git a/src/mjolnir/Mjolnir.ts b/src/mjolnir/Mjolnir.ts index 8c9d939908..9dacc0d41f 100644 --- a/src/mjolnir/Mjolnir.ts +++ b/src/mjolnir/Mjolnir.ts @@ -22,12 +22,12 @@ import { Action } from "../dispatcher/actions"; // TODO: Move this and related files to the js-sdk or something once finalized. export class Mjolnir { - private static instance: Mjolnir | null = null; + private static instance?: Mjolnir; private _lists: BanList[] = []; // eslint-disable-line @typescript-eslint/naming-convention private _roomIds: string[] = []; // eslint-disable-line @typescript-eslint/naming-convention - private mjolnirWatchRef: string | null = null; - private dispatcherRef: string | null = null; + private mjolnirWatchRef?: string; + private dispatcherRef?: string; public get roomIds(): string[] { return this._roomIds; @@ -61,15 +61,11 @@ export class Mjolnir { } public stop(): void { - if (this.mjolnirWatchRef) { - SettingsStore.unwatchSetting(this.mjolnirWatchRef); - this.mjolnirWatchRef = null; - } + SettingsStore.unwatchSetting(this.mjolnirWatchRef); + this.mjolnirWatchRef = undefined; - if (this.dispatcherRef) { - dis.unregister(this.dispatcherRef); - this.dispatcherRef = null; - } + dis.unregister(this.dispatcherRef); + this.dispatcherRef = undefined; MatrixClientPeg.get()?.removeListener(RoomStateEvent.Events, this.onEvent); } diff --git a/src/models/Call.ts b/src/models/Call.ts index 0238d15914..4beb5fccc1 100644 --- a/src/models/Call.ts +++ b/src/models/Call.ts @@ -643,8 +643,8 @@ export class ElementCall extends Call { public static readonly MEMBER_EVENT_TYPE = new NamespacedValue(null, EventType.GroupCallMemberPrefix); public readonly STUCK_DEVICE_TIMEOUT_MS = 1000 * 60 * 60; // 1 hour - private settingsStoreCallEncryptionWatcher: string | null = null; - private terminationTimer: number | null = null; + private settingsStoreCallEncryptionWatcher?: string; + private terminationTimer?: number; private _layout = Layout.Tile; public get layout(): Layout { return this._layout; @@ -938,13 +938,9 @@ export class ElementCall extends Call { this.session.off(MatrixRTCSessionEvent.MembershipsChanged, this.onMembershipChanged); this.client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionEnded, this.onRTCSessionEnded); - if (this.settingsStoreCallEncryptionWatcher) { - SettingsStore.unwatchSetting(this.settingsStoreCallEncryptionWatcher); - } - if (this.terminationTimer !== null) { - clearTimeout(this.terminationTimer); - this.terminationTimer = null; - } + SettingsStore.unwatchSetting(this.settingsStoreCallEncryptionWatcher); + clearTimeout(this.terminationTimer); + this.terminationTimer = undefined; super.destroy(); } diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 45ba4e3dbb..98ae347a0a 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -192,10 +192,11 @@ export default class SettingsStore { /** * Stops the SettingsStore from watching a setting. This is a no-op if the watcher * provided is not found. - * @param {string} watcherReference The watcher reference (received from #watchSetting) - * to cancel. + * @param watcherReference The watcher reference (received from #watchSetting) to cancel. + * Can be undefined to avoid needing an if around every caller. */ - public static unwatchSetting(watcherReference: string): void { + public static unwatchSetting(watcherReference: string | undefined): void { + if (!watcherReference) return; if (!SettingsStore.watchers.has(watcherReference)) { logger.warn(`Ending non-existent watcher ID ${watcherReference}`); return; diff --git a/src/settings/watchers/FontWatcher.ts b/src/settings/watchers/FontWatcher.ts index 0c7e596530..64a6a27f58 100644 --- a/src/settings/watchers/FontWatcher.ts +++ b/src/settings/watchers/FontWatcher.ts @@ -28,11 +28,7 @@ export class FontWatcher implements IWatcher { */ public static readonly DEFAULT_DELTA = 0; - private dispatcherRef: string | null; - - public constructor() { - this.dispatcherRef = null; - } + private dispatcherRef?: string; public async start(): Promise { this.updateFont(); @@ -148,7 +144,6 @@ export class FontWatcher implements IWatcher { } public stop(): void { - if (!this.dispatcherRef) return; dis.unregister(this.dispatcherRef); } diff --git a/src/settings/watchers/ThemeWatcher.ts b/src/settings/watchers/ThemeWatcher.ts index 74a3158c62..d0f00c52d9 100644 --- a/src/settings/watchers/ThemeWatcher.ts +++ b/src/settings/watchers/ThemeWatcher.ts @@ -18,9 +18,9 @@ import { ActionPayload } from "../../dispatcher/payloads"; import { SettingLevel } from "../SettingLevel"; export default class ThemeWatcher { - private themeWatchRef: string | null; - private systemThemeWatchRef: string | null; - private dispatcherRef: string | null; + private themeWatchRef?: string; + private systemThemeWatchRef?: string; + private dispatcherRef?: string; private preferDark: MediaQueryList; private preferLight: MediaQueryList; @@ -29,10 +29,6 @@ export default class ThemeWatcher { private currentTheme: string; public constructor() { - this.themeWatchRef = null; - this.systemThemeWatchRef = null; - this.dispatcherRef = null; - // we have both here as each may either match or not match, so by having both // we can get the tristate of dark/light/unsupported this.preferDark = (global).matchMedia("(prefers-color-scheme: dark)"); @@ -55,9 +51,9 @@ export default class ThemeWatcher { this.preferDark.removeEventListener("change", this.onChange); this.preferLight.removeEventListener("change", this.onChange); this.preferHighContrast.removeEventListener("change", this.onChange); - if (this.systemThemeWatchRef) SettingsStore.unwatchSetting(this.systemThemeWatchRef); - if (this.themeWatchRef) SettingsStore.unwatchSetting(this.themeWatchRef); - if (this.dispatcherRef) dis.unregister(this.dispatcherRef); + SettingsStore.unwatchSetting(this.systemThemeWatchRef); + SettingsStore.unwatchSetting(this.themeWatchRef); + dis.unregister(this.dispatcherRef); } private onChange = (): void => { diff --git a/src/stores/AsyncStore.ts b/src/stores/AsyncStore.ts index 5697969a1d..4baf807247 100644 --- a/src/stores/AsyncStore.ts +++ b/src/stores/AsyncStore.ts @@ -65,7 +65,7 @@ export abstract class AsyncStore extends EventEmitter { * Stops the store's listening functions, such as the listener to the dispatcher. */ protected stop(): void { - if (this.dispatcherRef) this.dispatcher.unregister(this.dispatcherRef); + this.dispatcher.unregister(this.dispatcherRef); } /** diff --git a/src/stores/AsyncStoreWithClient.ts b/src/stores/AsyncStoreWithClient.ts index 418143a16c..a131614c7c 100644 --- a/src/stores/AsyncStoreWithClient.ts +++ b/src/stores/AsyncStoreWithClient.ts @@ -23,7 +23,7 @@ export abstract class AsyncStoreWithClient extends AsyncStore< const asyncStore = this; // eslint-disable-line @typescript-eslint/no-this-alias this.readyStore = new (class extends ReadyWatchingStore { public get mxClient(): MatrixClient | null { - return this.matrixClient; + return this.matrixClient ?? null; } protected async onReady(): Promise { diff --git a/src/stores/OwnBeaconStore.ts b/src/stores/OwnBeaconStore.ts index f60dae07fe..ccd4bf33a3 100644 --- a/src/stores/OwnBeaconStore.ts +++ b/src/stores/OwnBeaconStore.ts @@ -142,7 +142,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient { this.matrixClient.removeListener(BeaconEvent.Destroy, this.onDestroyBeacon); this.matrixClient.removeListener(RoomStateEvent.Members, this.onRoomStateMembers); } - SettingsStore.unwatchSetting(this.dynamicWatcherRef ?? ""); + SettingsStore.unwatchSetting(this.dynamicWatcherRef); this.clearBeacons(); } diff --git a/src/stores/ReadyWatchingStore.ts b/src/stores/ReadyWatchingStore.ts index a46a09899a..922e8b8393 100644 --- a/src/stores/ReadyWatchingStore.ts +++ b/src/stores/ReadyWatchingStore.ts @@ -16,8 +16,8 @@ import { Action } from "../dispatcher/actions"; import { MatrixDispatcher } from "../dispatcher/dispatcher"; export abstract class ReadyWatchingStore extends EventEmitter implements IDestroyable { - protected matrixClient: MatrixClient | null = null; - private dispatcherRef: string | null = null; + protected matrixClient?: MatrixClient; + private dispatcherRef?: string; public constructor(protected readonly dispatcher: MatrixDispatcher) { super(); @@ -35,7 +35,7 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro } public get mxClient(): MatrixClient | null { - return this.matrixClient; // for external readonly access + return this.matrixClient ?? null; // for external readonly access } public useUnitTestClient(cli: MatrixClient): void { @@ -43,7 +43,7 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro } public destroy(): void { - if (this.dispatcherRef !== null) this.dispatcher.unregister(this.dispatcherRef); + this.dispatcher.unregister(this.dispatcherRef); } protected async onReady(): Promise { @@ -80,7 +80,7 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro } else if (payload.action === "on_client_not_viable" || payload.action === Action.OnLoggedOut) { if (this.matrixClient) { await this.onNotReady(); - this.matrixClient = null; + this.matrixClient = undefined; } } }; diff --git a/src/stores/widgets/WidgetLayoutStore.ts b/src/stores/widgets/WidgetLayoutStore.ts index cefbee0f6b..f0d3cafc83 100644 --- a/src/stores/widgets/WidgetLayoutStore.ts +++ b/src/stores/widgets/WidgetLayoutStore.ts @@ -91,9 +91,9 @@ export class WidgetLayoutStore extends ReadyWatchingStore { this.byRoom = new MapWithDefault(() => new Map()); this.matrixClient?.off(RoomStateEvent.Events, this.updateRoomFromState); - if (this.pinnedRef) SettingsStore.unwatchSetting(this.pinnedRef); - if (this.layoutRef) SettingsStore.unwatchSetting(this.layoutRef); - if (this.dynamicRef) SettingsStore.unwatchSetting(this.dynamicRef); + SettingsStore.unwatchSetting(this.pinnedRef); + SettingsStore.unwatchSetting(this.layoutRef); + SettingsStore.unwatchSetting(this.dynamicRef); WidgetStore.instance.off(UPDATE_EVENT, this.updateFromWidgetStore); } diff --git a/test/unit-tests/DeviceListener-test.ts b/test/unit-tests/DeviceListener-test.ts index 0f3bb68254..0862c6b385 100644 --- a/test/unit-tests/DeviceListener-test.ts +++ b/test/unit-tests/DeviceListener-test.ts @@ -39,6 +39,7 @@ jest.mock("matrix-js-sdk/src/logger"); jest.mock("../../src/dispatcher/dispatcher", () => ({ dispatch: jest.fn(), register: jest.fn(), + unregister: jest.fn(), })); jest.mock("../../src/SecurityManager", () => ({ From 0899165d9e2cd1b34a47b95eaabc5ad78224738e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 1 Nov 2024 17:39:08 +0000 Subject: [PATCH 12/38] Move state update listeners from constructor to componentDidMount (#28341) * Move state update listeners from constructor to componentDidMount Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/AsyncWrapper.tsx | 1 + .../security/CreateSecretStorageDialog.tsx | 9 +- .../dialogs/security/ExportE2eKeysDialog.tsx | 4 + .../dialogs/security/ImportE2eKeysDialog.tsx | 4 + src/components/structures/InteractiveAuth.tsx | 8 +- src/components/structures/LeftPanel.tsx | 8 +- src/components/structures/MatrixChat.tsx | 71 +++++------ src/components/structures/MessagePanel.tsx | 15 +-- .../structures/NonUrgentToastContainer.tsx | 2 + src/components/structures/RoomSearch.tsx | 6 +- src/components/structures/RoomStatusBar.tsx | 2 + src/components/structures/RoomView.tsx | 120 +++++++++--------- src/components/structures/ScrollPanel.tsx | 4 +- src/components/structures/SpaceRoomView.tsx | 7 +- src/components/structures/ThreadView.tsx | 9 +- src/components/structures/TimelinePanel.tsx | 8 +- src/components/structures/ToastContainer.tsx | 7 +- src/components/structures/UploadBar.tsx | 8 +- src/components/structures/UserMenu.tsx | 5 +- .../structures/auth/CompleteSecurity.tsx | 6 +- src/components/structures/auth/Login.tsx | 1 + .../structures/auth/SetupEncryptionBody.tsx | 6 +- .../views/audio_messages/AudioPlayerBase.tsx | 2 + src/components/views/audio_messages/Clock.tsx | 4 - .../views/audio_messages/DurationClock.tsx | 3 + .../views/audio_messages/PlayPauseButton.tsx | 4 - .../views/audio_messages/PlaybackClock.tsx | 3 + .../views/audio_messages/PlaybackWaveform.tsx | 2 + .../views/audio_messages/SeekBar.tsx | 2 + .../auth/InteractiveAuthEntryComponents.tsx | 4 +- src/components/views/auth/LoginWithQRFlow.tsx | 4 - .../GenericElementContextMenu.tsx | 4 - .../context_menus/LegacyCallContextMenu.tsx | 4 - .../views/dialogs/BugReportDialog.tsx | 9 +- .../views/dialogs/CreateRoomDialog.tsx | 17 +-- .../views/dialogs/DeactivateAccountDialog.tsx | 2 + .../views/dialogs/IncomingSasDialog.tsx | 3 + src/components/views/dialogs/InviteDialog.tsx | 1 + src/components/views/dialogs/LogoutDialog.tsx | 5 +- .../dialogs/VerificationRequestDialog.tsx | 3 + src/components/views/elements/AppTile.tsx | 35 ++--- .../elements/DesktopCapturerSourcePicker.tsx | 4 - src/components/views/elements/Dropdown.tsx | 2 + .../views/elements/LinkWithTooltip.tsx | 4 - .../views/elements/PersistedElement.tsx | 17 +-- .../views/elements/PowerSelector.tsx | 1 + src/components/views/elements/ReplyChain.tsx | 1 + .../views/elements/TextWithTooltip.tsx | 4 - .../views/emojipicker/ReactionPicker.tsx | 3 + .../views/messages/DateSeparator.tsx | 2 + src/components/views/messages/MImageBody.tsx | 2 +- .../views/messages/MJitsiWidgetEvent.tsx | 4 - .../views/messages/MLocationBody.tsx | 4 + src/components/views/rooms/AppsDrawer.tsx | 6 +- .../views/rooms/BasicMessageComposer.tsx | 50 ++++---- .../views/rooms/EditMessageComposer.tsx | 4 +- src/components/views/rooms/EventTile.tsx | 1 + src/components/views/rooms/MemberList.tsx | 12 +- .../views/rooms/MessageComposer.tsx | 34 ++--- .../views/rooms/NotificationBadge.tsx | 17 ++- .../views/rooms/RoomBreadcrumbs.tsx | 9 +- src/components/views/rooms/RoomTile.tsx | 3 +- .../views/rooms/SendMessageComposer.tsx | 10 +- .../views/settings/CrossSigningPanel.tsx | 1 + .../views/settings/CryptographyPanel.tsx | 29 +++-- .../views/settings/FontScalingPanel.tsx | 1 + .../views/settings/Notifications.tsx | 22 ++-- .../views/settings/SecureBackupPanel.tsx | 1 + .../tabs/room/AdvancedRoomSettingsTab.tsx | 6 +- .../views/spaces/SpaceTreeLevel.tsx | 2 + src/components/views/voip/LegacyCallView.tsx | 3 +- .../views/settings/CryptographyPanel-test.tsx | 5 +- 72 files changed, 377 insertions(+), 309 deletions(-) diff --git a/src/AsyncWrapper.tsx b/src/AsyncWrapper.tsx index 179d42668e..cec814df17 100644 --- a/src/AsyncWrapper.tsx +++ b/src/AsyncWrapper.tsx @@ -37,6 +37,7 @@ export default class AsyncWrapper extends React.Component { public state: IState = {}; public componentDidMount(): void { + this.unmounted = false; this.props.prom .then((result) => { if (this.unmounted) return; diff --git a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx index 3022aa6b8d..1e87b5b826 100644 --- a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx +++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx @@ -117,8 +117,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent }; } + public componentDidMount(): void { + this.unmounted = false; + } + public componentWillUnmount(): void { this.unmounted = true; } diff --git a/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx index fa41d53a45..d08259f2cb 100644 --- a/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx +++ b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx @@ -64,6 +64,10 @@ export default class ImportE2eKeysDialog extends React.Component }; } + public componentDidMount(): void { + this.unmounted = false; + } + public componentWillUnmount(): void { this.unmounted = true; } diff --git a/src/components/structures/InteractiveAuth.tsx b/src/components/structures/InteractiveAuth.tsx index 91e52a1905..4b0f060952 100644 --- a/src/components/structures/InteractiveAuth.tsx +++ b/src/components/structures/InteractiveAuth.tsx @@ -90,8 +90,8 @@ interface IState { export default class InteractiveAuthComponent extends React.Component, IState> { private readonly authLogic: InteractiveAuth; - private readonly intervalId: number | null = null; private readonly stageComponent = createRef(); + private intervalId: number | null = null; private unmounted = false; @@ -126,15 +126,17 @@ export default class InteractiveAuthComponent extends React.Component { this.authLogic.poll(); }, 2000); } - } - public componentDidMount(): void { this.authLogic .attemptAuth() .then(async (result) => { diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index f8cd0184d4..49d0f570a5 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -67,10 +67,6 @@ export default class LeftPanel extends React.Component { activeSpace: SpaceStore.instance.activeSpace, showBreadcrumbs: LeftPanel.breadcrumbsMode, }; - - BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate); - RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); - SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.updateActiveSpace); } private static get breadcrumbsMode(): BreadcrumbsMode { @@ -78,6 +74,10 @@ export default class LeftPanel extends React.Component { } public componentDidMount(): void { + BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate); + RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); + SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.updateActiveSpace); + if (this.listContainerRef.current) { UIStore.instance.trackElementDimensions("ListContainer", this.listContainerRef.current); // Using the passive option to not block the main thread diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index d0edcccd4f..80a648b5d5 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -231,10 +231,10 @@ export default class MatrixChat extends React.PureComponent { private prevWindowWidth: number; private voiceBroadcastResumer?: VoiceBroadcastResumer; - private readonly loggedInView: React.RefObject; - private readonly dispatcherRef: string; - private readonly themeWatcher: ThemeWatcher; - private readonly fontWatcher: FontWatcher; + private readonly loggedInView = createRef(); + private dispatcherRef?: string; + private themeWatcher?: ThemeWatcher; + private fontWatcher?: FontWatcher; private readonly stores: SdkContextClass; public constructor(props: IProps) { @@ -256,8 +256,6 @@ export default class MatrixChat extends React.PureComponent { ready: false, }; - this.loggedInView = createRef(); - SdkConfig.put(this.props.config); // Used by _viewRoom before getting state from sync @@ -282,32 +280,10 @@ export default class MatrixChat extends React.PureComponent { } this.prevWindowWidth = UIStore.instance.windowWidth || 1000; - UIStore.instance.on(UI_EVENTS.Resize, this.handleResize); - - // For PersistentElement - this.state.resizeNotifier.on("middlePanelResized", this.dispatchTimelineResize); - - RoomNotificationStateStore.instance.on(UPDATE_STATUS_INDICATOR, this.onUpdateStatusIndicator); - - this.dispatcherRef = dis.register(this.onAction); - - this.themeWatcher = new ThemeWatcher(); - this.fontWatcher = new FontWatcher(); - this.themeWatcher.start(); - this.fontWatcher.start(); // object field used for tracking the status info appended to the title tag. // we don't do it as react state as i'm scared about triggering needless react refreshes. this.subTitleStatus = ""; - - initSentry(SdkConfig.get("sentry")); - - if (!checkSessionLockFree()) { - // another instance holds the lock; confirm its theft before proceeding - setTimeout(() => this.setState({ view: Views.CONFIRM_LOCK_THEFT }), 0); - } else { - this.startInitSession(); - } } /** @@ -476,6 +452,29 @@ export default class MatrixChat extends React.PureComponent { } public componentDidMount(): void { + UIStore.instance.on(UI_EVENTS.Resize, this.handleResize); + + // For PersistentElement + this.state.resizeNotifier.on("middlePanelResized", this.dispatchTimelineResize); + + RoomNotificationStateStore.instance.on(UPDATE_STATUS_INDICATOR, this.onUpdateStatusIndicator); + + this.dispatcherRef = dis.register(this.onAction); + + this.themeWatcher = new ThemeWatcher(); + this.fontWatcher = new FontWatcher(); + this.themeWatcher.start(); + this.fontWatcher.start(); + + initSentry(SdkConfig.get("sentry")); + + if (!checkSessionLockFree()) { + // another instance holds the lock; confirm its theft before proceeding + setTimeout(() => this.setState({ view: Views.CONFIRM_LOCK_THEFT }), 0); + } else { + this.startInitSession(); + } + window.addEventListener("resize", this.onWindowResized); } @@ -497,8 +496,8 @@ export default class MatrixChat extends React.PureComponent { public componentWillUnmount(): void { Lifecycle.stopMatrixClient(); dis.unregister(this.dispatcherRef); - this.themeWatcher.stop(); - this.fontWatcher.stop(); + this.themeWatcher?.stop(); + this.fontWatcher?.stop(); UIStore.destroy(); this.state.resizeNotifier.removeListener("middlePanelResized", this.dispatchTimelineResize); window.removeEventListener("resize", this.onWindowResized); @@ -1011,7 +1010,7 @@ export default class MatrixChat extends React.PureComponent { this.setStateForNewView(newState); ThemeController.isLogin = true; - this.themeWatcher.recheck(); + this.themeWatcher?.recheck(); this.notifyNewScreen(isMobileRegistration ? "mobile_register" : "register"); } @@ -1088,7 +1087,7 @@ export default class MatrixChat extends React.PureComponent { }, () => { ThemeController.isLogin = false; - this.themeWatcher.recheck(); + this.themeWatcher?.recheck(); this.notifyNewScreen("room/" + presentedId, replaceLast); }, ); @@ -1113,7 +1112,7 @@ export default class MatrixChat extends React.PureComponent { }); this.notifyNewScreen("welcome"); ThemeController.isLogin = true; - this.themeWatcher.recheck(); + this.themeWatcher?.recheck(); } private viewLogin(otherState?: any): void { @@ -1123,7 +1122,7 @@ export default class MatrixChat extends React.PureComponent { }); this.notifyNewScreen("login"); ThemeController.isLogin = true; - this.themeWatcher.recheck(); + this.themeWatcher?.recheck(); } private viewHome(justRegistered = false): void { @@ -1136,7 +1135,7 @@ export default class MatrixChat extends React.PureComponent { this.setPage(PageType.HomePage); this.notifyNewScreen("home"); ThemeController.isLogin = false; - this.themeWatcher.recheck(); + this.themeWatcher?.recheck(); } private viewUser(userId: string, subAction: string): void { @@ -1357,7 +1356,7 @@ export default class MatrixChat extends React.PureComponent { */ private async onLoggedIn(): Promise { ThemeController.isLogin = false; - this.themeWatcher.recheck(); + this.themeWatcher?.recheck(); StorageManager.tryPersistStorage(); await this.onShowPostLoginScreen(); diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 7383e06f07..b26de2e645 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -240,13 +240,13 @@ export default class MessagePanel extends React.Component { private readReceiptsByUserId: Map = new Map(); private readonly _showHiddenEvents: boolean; - private isMounted = false; + private unmounted = false; private readMarkerNode = createRef(); private whoIsTyping = createRef(); public scrollPanel = createRef(); - private readonly showTypingNotificationsWatcherRef: string; + private showTypingNotificationsWatcherRef?: string; private eventTiles: Record = {}; // A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination. @@ -267,22 +267,21 @@ export default class MessagePanel extends React.Component { // and we check this in a hot code path. This is also cached in our // RoomContext, however we still need a fallback for roomless MessagePanels. this._showHiddenEvents = SettingsStore.getValue("showHiddenEventsInTimeline"); + } + public componentDidMount(): void { + this.unmounted = false; this.showTypingNotificationsWatcherRef = SettingsStore.watchSetting( "showTypingNotifications", null, this.onShowTypingNotificationsChange, ); - } - - public componentDidMount(): void { this.calculateRoomMembersCount(); this.props.room?.currentState.on(RoomStateEvent.Update, this.calculateRoomMembersCount); - this.isMounted = true; } public componentWillUnmount(): void { - this.isMounted = false; + this.unmounted = true; this.props.room?.currentState.off(RoomStateEvent.Update, this.calculateRoomMembersCount); SettingsStore.unwatchSetting(this.showTypingNotificationsWatcherRef); this.readReceiptMap = {}; @@ -441,7 +440,7 @@ export default class MessagePanel extends React.Component { } private isUnmounting = (): boolean => { - return !this.isMounted; + return this.unmounted; }; public get showHiddenEvents(): boolean { diff --git a/src/components/structures/NonUrgentToastContainer.tsx b/src/components/structures/NonUrgentToastContainer.tsx index 2eca6db934..d01bf78959 100644 --- a/src/components/structures/NonUrgentToastContainer.tsx +++ b/src/components/structures/NonUrgentToastContainer.tsx @@ -25,7 +25,9 @@ export default class NonUrgentToastContainer extends React.PureComponent { - private readonly dispatcherRef: string; - - public constructor(props: IProps) { - super(props); + private dispatcherRef?: string; + public componentDidMount(): void { this.dispatcherRef = defaultDispatcher.register(this.onAction); } diff --git a/src/components/structures/RoomStatusBar.tsx b/src/components/structures/RoomStatusBar.tsx index bd236f2286..76f3b0c229 100644 --- a/src/components/structures/RoomStatusBar.tsx +++ b/src/components/structures/RoomStatusBar.tsx @@ -103,6 +103,8 @@ export default class RoomStatusBar extends React.PureComponent { } public componentDidMount(): void { + this.unmounted = false; + const client = this.context; client.on(ClientEvent.Sync, this.onSyncStateChange); client.on(RoomEvent.LocalEchoUpdated, this.onRoomLocalEchoUpdated); diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 486a7fb652..520760713c 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -351,8 +351,8 @@ export class RoomView extends React.Component { private static e2eStatusCache = new Map(); private readonly askToJoinEnabled: boolean; - private readonly dispatcherRef: string; - private settingWatchers: string[]; + private dispatcherRef?: string; + private settingWatchers: string[] = []; private unmounted = false; private permalinkCreators: Record = {}; @@ -418,62 +418,6 @@ export class RoomView extends React.Component { promptAskToJoin: false, viewRoomOpts: { buttons: [] }, }; - - this.dispatcherRef = dis.register(this.onAction); - context.client.on(ClientEvent.Room, this.onRoom); - context.client.on(RoomEvent.Timeline, this.onRoomTimeline); - context.client.on(RoomEvent.TimelineReset, this.onRoomTimelineReset); - context.client.on(RoomEvent.Name, this.onRoomName); - context.client.on(RoomStateEvent.Events, this.onRoomStateEvents); - context.client.on(RoomStateEvent.Update, this.onRoomStateUpdate); - context.client.on(RoomEvent.MyMembership, this.onMyMembership); - context.client.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus); - context.client.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged); - context.client.on(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged); - context.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted); - // Start listening for RoomViewStore updates - context.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate); - - context.rightPanelStore.on(UPDATE_EVENT, this.onRightPanelStoreUpdate); - - WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate); - context.widgetStore.on(UPDATE_EVENT, this.onWidgetStoreUpdate); - - CallStore.instance.on(CallStoreEvent.ConnectedCalls, this.onConnectedCalls); - - this.props.resizeNotifier.on("isResizing", this.onIsResizing); - - this.settingWatchers = [ - SettingsStore.watchSetting("layout", null, (...[, , , value]) => - this.setState({ layout: value as Layout }), - ), - SettingsStore.watchSetting("lowBandwidth", null, (...[, , , value]) => - this.setState({ lowBandwidth: value as boolean }), - ), - SettingsStore.watchSetting("alwaysShowTimestamps", null, (...[, , , value]) => - this.setState({ alwaysShowTimestamps: value as boolean }), - ), - SettingsStore.watchSetting("showTwelveHourTimestamps", null, (...[, , , value]) => - this.setState({ showTwelveHourTimestamps: value as boolean }), - ), - SettingsStore.watchSetting(TimezoneHandler.USER_TIMEZONE_KEY, null, (...[, , , value]) => - this.setState({ userTimezone: value as string }), - ), - SettingsStore.watchSetting("readMarkerInViewThresholdMs", null, (...[, , , value]) => - this.setState({ readMarkerInViewThresholdMs: value as number }), - ), - SettingsStore.watchSetting("readMarkerOutOfViewThresholdMs", null, (...[, , , value]) => - this.setState({ readMarkerOutOfViewThresholdMs: value as number }), - ), - SettingsStore.watchSetting("showHiddenEventsInTimeline", null, (...[, , , value]) => - this.setState({ showHiddenEvents: value as boolean }), - ), - SettingsStore.watchSetting("urlPreviewsEnabled", null, this.onUrlPreviewsEnabledChange), - SettingsStore.watchSetting("urlPreviewsEnabled_e2ee", null, this.onUrlPreviewsEnabledChange), - SettingsStore.watchSetting("feature_dynamic_room_predecessors", null, (...[, , , value]) => - this.setState({ msc3946ProcessDynamicPredecessor: value as boolean }), - ), - ]; } private onIsResizing = (resizing: boolean): void => { @@ -904,6 +848,66 @@ export class RoomView extends React.Component { } public componentDidMount(): void { + this.unmounted = false; + + this.dispatcherRef = dis.register(this.onAction); + if (this.context.client) { + this.context.client.on(ClientEvent.Room, this.onRoom); + this.context.client.on(RoomEvent.Timeline, this.onRoomTimeline); + this.context.client.on(RoomEvent.TimelineReset, this.onRoomTimelineReset); + this.context.client.on(RoomEvent.Name, this.onRoomName); + this.context.client.on(RoomStateEvent.Events, this.onRoomStateEvents); + this.context.client.on(RoomStateEvent.Update, this.onRoomStateUpdate); + this.context.client.on(RoomEvent.MyMembership, this.onMyMembership); + this.context.client.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus); + this.context.client.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged); + this.context.client.on(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged); + this.context.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted); + } + // Start listening for RoomViewStore updates + this.context.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate); + + this.context.rightPanelStore.on(UPDATE_EVENT, this.onRightPanelStoreUpdate); + + WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate); + this.context.widgetStore.on(UPDATE_EVENT, this.onWidgetStoreUpdate); + + CallStore.instance.on(CallStoreEvent.ConnectedCalls, this.onConnectedCalls); + + this.props.resizeNotifier.on("isResizing", this.onIsResizing); + + this.settingWatchers = [ + SettingsStore.watchSetting("layout", null, (...[, , , value]) => + this.setState({ layout: value as Layout }), + ), + SettingsStore.watchSetting("lowBandwidth", null, (...[, , , value]) => + this.setState({ lowBandwidth: value as boolean }), + ), + SettingsStore.watchSetting("alwaysShowTimestamps", null, (...[, , , value]) => + this.setState({ alwaysShowTimestamps: value as boolean }), + ), + SettingsStore.watchSetting("showTwelveHourTimestamps", null, (...[, , , value]) => + this.setState({ showTwelveHourTimestamps: value as boolean }), + ), + SettingsStore.watchSetting(TimezoneHandler.USER_TIMEZONE_KEY, null, (...[, , , value]) => + this.setState({ userTimezone: value as string }), + ), + SettingsStore.watchSetting("readMarkerInViewThresholdMs", null, (...[, , , value]) => + this.setState({ readMarkerInViewThresholdMs: value as number }), + ), + SettingsStore.watchSetting("readMarkerOutOfViewThresholdMs", null, (...[, , , value]) => + this.setState({ readMarkerOutOfViewThresholdMs: value as number }), + ), + SettingsStore.watchSetting("showHiddenEventsInTimeline", null, (...[, , , value]) => + this.setState({ showHiddenEvents: value as boolean }), + ), + SettingsStore.watchSetting("urlPreviewsEnabled", null, this.onUrlPreviewsEnabledChange), + SettingsStore.watchSetting("urlPreviewsEnabled_e2ee", null, this.onUrlPreviewsEnabledChange), + SettingsStore.watchSetting("feature_dynamic_room_predecessors", null, (...[, , , value]) => + this.setState({ msc3946ProcessDynamicPredecessor: value as boolean }), + ), + ]; + this.onRoomViewStoreUpdate(true); const call = this.getCallForRoom(); diff --git a/src/components/structures/ScrollPanel.tsx b/src/components/structures/ScrollPanel.tsx index d072c322ce..b354f6b005 100644 --- a/src/components/structures/ScrollPanel.tsx +++ b/src/components/structures/ScrollPanel.tsx @@ -191,12 +191,12 @@ export default class ScrollPanel extends React.Component { public constructor(props: IProps) { super(props); - this.props.resizeNotifier?.on("middlePanelResizedNoisy", this.onResize); - this.resetScrollState(); } public componentDidMount(): void { + this.unmounted = false; + this.props.resizeNotifier?.on("middlePanelResizedNoisy", this.onResize); this.checkScroll(); } diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 4f0c895233..3ea2a03c1a 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -599,7 +599,7 @@ export default class SpaceRoomView extends React.PureComponent { public static contextType = MatrixClientContext; public declare context: React.ContextType; - private readonly dispatcherRef: string; + private dispatcherRef?: string; public constructor(props: IProps, context: React.ContextType) { super(props, context); @@ -621,12 +621,11 @@ export default class SpaceRoomView extends React.PureComponent { showRightPanel: RightPanelStore.instance.isOpenForRoom(this.props.space.roomId), myMembership: this.props.space.getMyMembership(), }; - - this.dispatcherRef = defaultDispatcher.register(this.onAction); - RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate); } public componentDidMount(): void { + this.dispatcherRef = defaultDispatcher.register(this.onAction); + RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate); this.context.on(RoomEvent.MyMembership, this.onMyMembership); } diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 8d2a286de1..be538a6669 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -78,7 +78,7 @@ export default class ThreadView extends React.Component { public declare context: React.ContextType; private dispatcherRef?: string; - private readonly layoutWatcherRef: string; + private layoutWatcherRef?: string; private timelinePanel = createRef(); private card = createRef(); @@ -91,7 +91,6 @@ export default class ThreadView extends React.Component { this.setEventId(this.props.mxEvent); const thread = this.props.room.getThread(this.eventId) ?? undefined; - this.setupThreadListeners(thread); this.state = { layout: SettingsStore.getValue("layout"), narrow: false, @@ -100,13 +99,15 @@ export default class ThreadView extends React.Component { return ev.isRelation(THREAD_RELATION_TYPE.name) && !ev.status; }), }; + } + + public componentDidMount(): void { + this.setupThreadListeners(this.state.thread); this.layoutWatcherRef = SettingsStore.watchSetting("layout", null, (...[, , , value]) => this.setState({ layout: value as Layout }), ); - } - public componentDidMount(): void { if (this.state.thread) { this.postThreadUpdate(this.state.thread); } diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index 846fc56d17..68b65965f5 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -248,7 +248,7 @@ class TimelinePanel extends React.Component { private lastRMSentEventId: string | null | undefined = undefined; private readonly messagePanel = createRef(); - private readonly dispatcherRef: string; + private dispatcherRef?: string; private timelineWindow?: TimelineWindow; private overlayTimelineWindow?: TimelineWindow; private unmounted = false; @@ -291,6 +291,10 @@ class TimelinePanel extends React.Component { readMarkerInViewThresholdMs: SettingsStore.getValue("readMarkerInViewThresholdMs"), readMarkerOutOfViewThresholdMs: SettingsStore.getValue("readMarkerOutOfViewThresholdMs"), }; + } + + public componentDidMount(): void { + this.unmounted = false; this.dispatcherRef = dis.register(this.onAction); const cli = MatrixClientPeg.safeGet(); @@ -312,9 +316,7 @@ class TimelinePanel extends React.Component { cli.on(ClientEvent.Sync, this.onSync); this.props.timelineSet.room?.on(ThreadEvent.Update, this.onThreadUpdate); - } - public componentDidMount(): void { if (this.props.manageReadReceipts) { this.updateReadReceiptOnUserActivity(); } diff --git a/src/components/structures/ToastContainer.tsx b/src/components/structures/ToastContainer.tsx index 8c572442a0..3e5b4a4474 100644 --- a/src/components/structures/ToastContainer.tsx +++ b/src/components/structures/ToastContainer.tsx @@ -24,12 +24,11 @@ export default class ToastContainer extends React.Component<{}, IState> { toasts: ToastStore.sharedInstance().getToasts(), countSeen: ToastStore.sharedInstance().getCountSeen(), }; + } - // Start listening here rather than in componentDidMount because - // toasts may dismiss themselves in their didMount if they find - // they're already irrelevant by the time they're mounted, and - // our own componentDidMount is too late. + public componentDidMount(): void { ToastStore.sharedInstance().on("update", this.onToastStoreUpdate); + this.onToastStoreUpdate(); } public componentWillUnmount(): void { diff --git a/src/components/structures/UploadBar.tsx b/src/components/structures/UploadBar.tsx index 93ce6d6bf2..01ecae96dc 100644 --- a/src/components/structures/UploadBar.tsx +++ b/src/components/structures/UploadBar.tsx @@ -46,7 +46,7 @@ function isUploadPayload(payload: ActionPayload): payload is UploadPayload { export default class UploadBar extends React.PureComponent { private dispatcherRef: Optional; - private mounted = false; + private unmounted = false; public constructor(props: IProps) { super(props); @@ -57,12 +57,12 @@ export default class UploadBar extends React.PureComponent { } public componentDidMount(): void { + this.unmounted = false; this.dispatcherRef = dis.register(this.onAction); - this.mounted = true; } public componentWillUnmount(): void { - this.mounted = false; + this.unmounted = true; dis.unregister(this.dispatcherRef!); } @@ -83,7 +83,7 @@ export default class UploadBar extends React.PureComponent { } private onAction = (payload: ActionPayload): void => { - if (!this.mounted) return; + if (this.unmounted) return; if (isUploadPayload(payload)) { this.setState(this.calculateState()); } diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 84bd93cc36..b2c7990746 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -96,9 +96,6 @@ export default class UserMenu extends React.Component { selectedSpace: SpaceStore.instance.activeSpaceRoom, showLiveAvatarAddon: this.context.voiceBroadcastRecordingsStore.hasCurrent(), }; - - OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); - SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate); } private get hasHomePage(): boolean { @@ -112,6 +109,8 @@ export default class UserMenu extends React.Component { }; public componentDidMount(): void { + OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); + SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate); this.context.voiceBroadcastRecordingsStore.on( VoiceBroadcastRecordingsStoreEvent.CurrentChanged, this.onCurrentVoiceBroadcastRecordingChanged, diff --git a/src/components/structures/auth/CompleteSecurity.tsx b/src/components/structures/auth/CompleteSecurity.tsx index a74e07692d..ec65a62cef 100644 --- a/src/components/structures/auth/CompleteSecurity.tsx +++ b/src/components/structures/auth/CompleteSecurity.tsx @@ -29,11 +29,15 @@ export default class CompleteSecurity extends React.Component { public constructor(props: IProps) { super(props); const store = SetupEncryptionStore.sharedInstance(); - store.on("update", this.onStoreUpdate); store.start(); this.state = { phase: store.phase, lostKeys: store.lostKeys() }; } + public componentDidMount(): void { + const store = SetupEncryptionStore.sharedInstance(); + store.on("update", this.onStoreUpdate); + } + private onStoreUpdate = (): void => { const store = SetupEncryptionStore.sharedInstance(); this.setState({ phase: store.phase, lostKeys: store.lostKeys() }); diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index abbba0f970..0a14450e63 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -134,6 +134,7 @@ export default class LoginComponent extends React.PureComponent } public componentDidMount(): void { + this.unmounted = false; this.initLoginLogic(this.props.serverConfig); } diff --git a/src/components/structures/auth/SetupEncryptionBody.tsx b/src/components/structures/auth/SetupEncryptionBody.tsx index 666313321a..32528fc7e3 100644 --- a/src/components/structures/auth/SetupEncryptionBody.tsx +++ b/src/components/structures/auth/SetupEncryptionBody.tsx @@ -39,7 +39,6 @@ export default class SetupEncryptionBody extends React.Component public constructor(props: IProps) { super(props); const store = SetupEncryptionStore.sharedInstance(); - store.on("update", this.onStoreUpdate); store.start(); this.state = { phase: store.phase, @@ -52,6 +51,11 @@ export default class SetupEncryptionBody extends React.Component }; } + public componentDidMount(): void { + const store = SetupEncryptionStore.sharedInstance(); + store.on("update", this.onStoreUpdate); + } + private onStoreUpdate = (): void => { const store = SetupEncryptionStore.sharedInstance(); if (store.phase === Phase.Finished) { diff --git a/src/components/views/audio_messages/AudioPlayerBase.tsx b/src/components/views/audio_messages/AudioPlayerBase.tsx index 70e30dccca..601611e422 100644 --- a/src/components/views/audio_messages/AudioPlayerBase.tsx +++ b/src/components/views/audio_messages/AudioPlayerBase.tsx @@ -41,7 +41,9 @@ export default abstract class AudioPlayerBase extends this.state = { playbackPhase: this.props.playback.currentState, }; + } + public componentDidMount(): void { // We don't need to de-register: the class handles this for us internally this.props.playback.on(UPDATE_EVENT, this.onPlaybackUpdate); diff --git a/src/components/views/audio_messages/Clock.tsx b/src/components/views/audio_messages/Clock.tsx index 56661c7a0c..ca72d29a05 100644 --- a/src/components/views/audio_messages/Clock.tsx +++ b/src/components/views/audio_messages/Clock.tsx @@ -27,10 +27,6 @@ export default class Clock extends React.Component { formatFn: formatSeconds, }; - public constructor(props: Props) { - super(props); - } - public shouldComponentUpdate(nextProps: Readonly): boolean { const currentFloor = Math.floor(this.props.seconds); const nextFloor = Math.floor(nextProps.seconds); diff --git a/src/components/views/audio_messages/DurationClock.tsx b/src/components/views/audio_messages/DurationClock.tsx index e495098144..3794ab9a4f 100644 --- a/src/components/views/audio_messages/DurationClock.tsx +++ b/src/components/views/audio_messages/DurationClock.tsx @@ -33,6 +33,9 @@ export default class DurationClock extends React.PureComponent { // member property to track "did we get a duration". durationSeconds: this.props.playback.clockInfo.durationSeconds, }; + } + + public componentDidMount(): void { this.props.playback.clockInfo.liveData.onUpdate(this.onTimeUpdate); } diff --git a/src/components/views/audio_messages/PlayPauseButton.tsx b/src/components/views/audio_messages/PlayPauseButton.tsx index 1053a89eea..1cd2d168b4 100644 --- a/src/components/views/audio_messages/PlayPauseButton.tsx +++ b/src/components/views/audio_messages/PlayPauseButton.tsx @@ -26,10 +26,6 @@ type Props = Omit, "title" | "onClick" | "disabled" | "elemen * to be displayed in reference to a recording. */ export default class PlayPauseButton extends React.PureComponent { - public constructor(props: Props) { - super(props); - } - private onClick = (): void => { // noinspection JSIgnoredPromiseFromCall this.toggleState(); diff --git a/src/components/views/audio_messages/PlaybackClock.tsx b/src/components/views/audio_messages/PlaybackClock.tsx index 8de3cb71e6..b3d736758b 100644 --- a/src/components/views/audio_messages/PlaybackClock.tsx +++ b/src/components/views/audio_messages/PlaybackClock.tsx @@ -43,6 +43,9 @@ export default class PlaybackClock extends React.PureComponent { durationSeconds: this.props.playback.clockInfo.durationSeconds, playbackPhase: PlaybackState.Stopped, // assume not started, so full clock }; + } + + public componentDidMount(): void { this.props.playback.on(UPDATE_EVENT, this.onPlaybackUpdate); this.props.playback.clockInfo.liveData.onUpdate(this.onTimeUpdate); } diff --git a/src/components/views/audio_messages/PlaybackWaveform.tsx b/src/components/views/audio_messages/PlaybackWaveform.tsx index 5f59289879..0f95f7084b 100644 --- a/src/components/views/audio_messages/PlaybackWaveform.tsx +++ b/src/components/views/audio_messages/PlaybackWaveform.tsx @@ -34,7 +34,9 @@ export default class PlaybackWaveform extends React.PureComponent { this.state = { percentage: percentageOf(this.props.playback.timeSeconds, 0, this.props.playback.durationSeconds), }; + } + public componentDidMount(): void { // We don't need to de-register: the class handles this for us internally this.props.playback.liveData.onUpdate(() => this.animationFrameFn.mark()); } diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx index 44ccd3a30e..b1360f5560 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx +++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx @@ -801,7 +801,6 @@ export class SSOAuthEntry extends React.Component extends React.Component { private checkCodeInput = createRef(); - public constructor(props: Props) { - super(props); - } - private handleClick = (type: Click): ((e: React.FormEvent) => Promise) => { return async (e: React.FormEvent): Promise => { e.preventDefault(); diff --git a/src/components/views/context_menus/GenericElementContextMenu.tsx b/src/components/views/context_menus/GenericElementContextMenu.tsx index 42ed8ce5be..afb39d6ebe 100644 --- a/src/components/views/context_menus/GenericElementContextMenu.tsx +++ b/src/components/views/context_menus/GenericElementContextMenu.tsx @@ -20,10 +20,6 @@ interface IProps { * menu. */ export default class GenericElementContextMenu extends React.Component { - public constructor(props: IProps) { - super(props); - } - public componentDidMount(): void { window.addEventListener("resize", this.resize); } diff --git a/src/components/views/context_menus/LegacyCallContextMenu.tsx b/src/components/views/context_menus/LegacyCallContextMenu.tsx index 817b4632e8..e6bb191df8 100644 --- a/src/components/views/context_menus/LegacyCallContextMenu.tsx +++ b/src/components/views/context_menus/LegacyCallContextMenu.tsx @@ -17,10 +17,6 @@ interface IProps extends IContextMenuProps { } export default class LegacyCallContextMenu extends React.Component { - public constructor(props: IProps) { - super(props); - } - public onHoldClick = (): void => { this.props.call.setRemoteOnHold(true); this.props.onFinished(); diff --git a/src/components/views/dialogs/BugReportDialog.tsx b/src/components/views/dialogs/BugReportDialog.tsx index 22a7efacb9..373f30d3ae 100644 --- a/src/components/views/dialogs/BugReportDialog.tsx +++ b/src/components/views/dialogs/BugReportDialog.tsx @@ -64,6 +64,11 @@ export default class BugReportDialog extends React.Component { this.unmounted = false; this.issueRef = React.createRef(); + } + + public componentDidMount(): void { + this.unmounted = false; + this.issueRef.current?.focus(); // Get all of the extra info dumped to the console when someone is about // to send debug logs. Since this is a fire and forget action, we do @@ -76,10 +81,6 @@ export default class BugReportDialog extends React.Component { }); } - public componentDidMount(): void { - this.issueRef.current?.focus(); - } - public componentWillUnmount(): void { this.unmounted = true; } diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index c5a8080e3f..990efdda71 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -113,14 +113,6 @@ export default class CreateRoomDialog extends React.Component { nameIsValid: false, canChangeEncryption: false, }; - - checkUserIsAllowedToChangeEncryption(cli, Preset.PrivateChat).then(({ allowChange, forcedValue }) => - this.setState((state) => ({ - canChangeEncryption: allowChange, - // override with forcedValue if it is set - isEncrypted: forcedValue ?? state.isEncrypted, - })), - ); } private roomCreateOptions(): IOpts { @@ -160,6 +152,15 @@ export default class CreateRoomDialog extends React.Component { } public componentDidMount(): void { + const cli = MatrixClientPeg.safeGet(); + checkUserIsAllowedToChangeEncryption(cli, Preset.PrivateChat).then(({ allowChange, forcedValue }) => + this.setState((state) => ({ + canChangeEncryption: allowChange, + // override with forcedValue if it is set + isEncrypted: forcedValue ?? state.isEncrypted, + })), + ); + // move focus to first field when showing dialog this.nameField.current?.focus(); } diff --git a/src/components/views/dialogs/DeactivateAccountDialog.tsx b/src/components/views/dialogs/DeactivateAccountDialog.tsx index fbcd26d38f..d68c931cc1 100644 --- a/src/components/views/dialogs/DeactivateAccountDialog.tsx +++ b/src/components/views/dialogs/DeactivateAccountDialog.tsx @@ -58,7 +58,9 @@ export default class DeactivateAccountDialog extends React.Component { opponentProfileError: null, sas: null, }; + } + + public componentDidMount(): void { this.props.verifier.on(VerifierEvent.ShowSas, this.onVerifierShowSas); this.props.verifier.on(VerifierEvent.Cancel, this.onVerifierCancel); this.fetchOpponentProfile(); diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index 8e1d49c138..35e04fb12e 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -397,6 +397,7 @@ export default class InviteDialog extends React.PureComponent { this.state = { backupStatus: BackupStatus.LOADING, }; + } - // we can't call setState() immediately, so wait a beat - window.setTimeout(() => this.startLoadBackupStatus(), 0); + public componentDidMount(): void { + this.startLoadBackupStatus(); } /** kick off the asynchronous calls to populate `state.backupStatus` in the background */ diff --git a/src/components/views/dialogs/VerificationRequestDialog.tsx b/src/components/views/dialogs/VerificationRequestDialog.tsx index 50644ccf30..d2ea83f2af 100644 --- a/src/components/views/dialogs/VerificationRequestDialog.tsx +++ b/src/components/views/dialogs/VerificationRequestDialog.tsx @@ -32,6 +32,9 @@ export default class VerificationRequestDialog extends React.Component { this.setState({ verificationRequest: r }); }); diff --git a/src/components/views/elements/AppTile.tsx b/src/components/views/elements/AppTile.tsx index 8f5aa732be..dae452fd5d 100644 --- a/src/components/views/elements/AppTile.tsx +++ b/src/components/views/elements/AppTile.tsx @@ -134,29 +134,20 @@ export default class AppTile extends React.Component { private iframe?: HTMLIFrameElement; // ref to the iframe (callback style) private allowedWidgetsWatchRef?: string; private persistKey: string; - private sgWidget: StopGapWidget | null; + private sgWidget?: StopGapWidget; private dispatcherRef?: string; private unmounted = false; public constructor(props: IProps, context: ContextType) { super(props, context); - // Tiles in miniMode are floating, and therefore not docked - if (!this.props.miniMode) { - ActiveWidgetStore.instance.dockWidget( - this.props.app.id, - isAppWidget(this.props.app) ? this.props.app.roomId : null, - ); - } - // The key used for PersistedElement this.persistKey = getPersistKey(WidgetUtils.getWidgetUid(this.props.app)); try { this.sgWidget = new StopGapWidget(this.props); - this.setupSgListeners(); } catch (e) { logger.log("Failed to construct widget", e); - this.sgWidget = null; + this.sgWidget = undefined; } this.state = this.getNewState(props); @@ -303,6 +294,20 @@ export default class AppTile extends React.Component { } public componentDidMount(): void { + this.unmounted = false; + + // Tiles in miniMode are floating, and therefore not docked + if (!this.props.miniMode) { + ActiveWidgetStore.instance.dockWidget( + this.props.app.id, + isAppWidget(this.props.app) ? this.props.app.roomId : null, + ); + } + + if (this.sgWidget) { + this.setupSgListeners(); + } + // Only fetch IM token on mount if we're showing and have permission to load if (this.sgWidget && this.state.hasPermissionToLoad) { this.startWidget(); @@ -374,7 +379,7 @@ export default class AppTile extends React.Component { this.startWidget(); } catch (e) { logger.error("Failed to construct widget", e); - this.sgWidget = null; + this.sgWidget = undefined; } } @@ -607,7 +612,7 @@ export default class AppTile extends React.Component { }; public render(): React.ReactNode { - let appTileBody: JSX.Element; + let appTileBody: JSX.Element | undefined; // Note that there is advice saying allow-scripts shouldn't be used with allow-same-origin // because that would allow the iframe to programmatically remove the sandbox attribute, but @@ -650,7 +655,7 @@ export default class AppTile extends React.Component { ); - } else if (!this.state.hasPermissionToLoad && this.props.room) { + } else if (!this.state.hasPermissionToLoad && this.props.room && this.sgWidget) { // only possible for room widgets, can assert this.props.room here const isEncrypted = this.context.isRoomEncrypted(this.props.room.roomId); appTileBody = ( @@ -677,7 +682,7 @@ export default class AppTile extends React.Component { ); - } else { + } else if (this.sgWidget) { appTileBody = ( <>
diff --git a/src/components/views/elements/DesktopCapturerSourcePicker.tsx b/src/components/views/elements/DesktopCapturerSourcePicker.tsx index 26b759bcb2..e1f1def836 100644 --- a/src/components/views/elements/DesktopCapturerSourcePicker.tsx +++ b/src/components/views/elements/DesktopCapturerSourcePicker.tsx @@ -41,10 +41,6 @@ export interface ExistingSourceIProps { } export class ExistingSource extends React.Component { - public constructor(props: ExistingSourceIProps) { - super(props); - } - private onClick = (): void => { this.props.onSelect(this.props.source); }; diff --git a/src/components/views/elements/Dropdown.tsx b/src/components/views/elements/Dropdown.tsx index c8802cd880..a7ce84163c 100644 --- a/src/components/views/elements/Dropdown.tsx +++ b/src/components/views/elements/Dropdown.tsx @@ -127,7 +127,9 @@ export default class Dropdown extends React.Component { // the current search query searchQuery: "", }; + } + public componentDidMount(): void { // Listen for all clicks on the document so we can close the // menu when the user clicks somewhere else document.addEventListener("click", this.onDocumentClick, false); diff --git a/src/components/views/elements/LinkWithTooltip.tsx b/src/components/views/elements/LinkWithTooltip.tsx index a9ca2606ae..016297d9f1 100644 --- a/src/components/views/elements/LinkWithTooltip.tsx +++ b/src/components/views/elements/LinkWithTooltip.tsx @@ -15,10 +15,6 @@ interface IProps extends Omit, "tab } export default class LinkWithTooltip extends React.Component { - public constructor(props: IProps) { - super(props); - } - public render(): React.ReactNode { const { children, tooltip, ...props } = this.props; diff --git a/src/components/views/elements/PersistedElement.tsx b/src/components/views/elements/PersistedElement.tsx index 20584f3794..2c87c8e7c6 100644 --- a/src/components/views/elements/PersistedElement.tsx +++ b/src/components/views/elements/PersistedElement.tsx @@ -79,7 +79,7 @@ interface IProps { */ export default class PersistedElement extends React.Component { private resizeObserver: ResizeObserver; - private dispatcherRef: string; + private dispatcherRef?: string; private childContainer?: HTMLDivElement; private child?: HTMLDivElement; @@ -87,13 +87,6 @@ export default class PersistedElement extends React.Component { super(props); this.resizeObserver = new ResizeObserver(this.repositionChild); - // Annoyingly, a resize observer is insufficient, since we also care - // about when the element moves on the screen without changing its - // dimensions. Doesn't look like there's a ResizeObserver equivalent - // for this, so we bodge it by listening for document resize and - // the timeline_resize action. - window.addEventListener("resize", this.repositionChild); - this.dispatcherRef = dis.register(this.onAction); if (this.props.moveRef) this.props.moveRef.current = this.repositionChild; } @@ -132,6 +125,14 @@ export default class PersistedElement extends React.Component { }; public componentDidMount(): void { + // Annoyingly, a resize observer is insufficient, since we also care + // about when the element moves on the screen without changing its + // dimensions. Doesn't look like there's a ResizeObserver equivalent + // for this, so we bodge it by listening for document resize and + // the timeline_resize action. + window.addEventListener("resize", this.repositionChild); + this.dispatcherRef = dis.register(this.onAction); + this.updateChild(); this.renderApp(); } diff --git a/src/components/views/elements/PowerSelector.tsx b/src/components/views/elements/PowerSelector.tsx index b600b2ba96..385d932b87 100644 --- a/src/components/views/elements/PowerSelector.tsx +++ b/src/components/views/elements/PowerSelector.tsx @@ -68,6 +68,7 @@ export default class PowerSelector extends React.C } public componentDidMount(): void { + this.unmounted = false; this.initStateFromProps(); } diff --git a/src/components/views/elements/ReplyChain.tsx b/src/components/views/elements/ReplyChain.tsx index 4eb3707031..71846d6065 100644 --- a/src/components/views/elements/ReplyChain.tsx +++ b/src/components/views/elements/ReplyChain.tsx @@ -89,6 +89,7 @@ export default class ReplyChain extends React.Component { } public componentDidMount(): void { + this.unmounted = false; this.initialize(); this.trySetExpandableQuotes(); } diff --git a/src/components/views/elements/TextWithTooltip.tsx b/src/components/views/elements/TextWithTooltip.tsx index 34346cbe25..b589ce3635 100644 --- a/src/components/views/elements/TextWithTooltip.tsx +++ b/src/components/views/elements/TextWithTooltip.tsx @@ -16,10 +16,6 @@ interface IProps extends HTMLAttributes { } export default class TextWithTooltip extends React.Component { - public constructor(props: IProps) { - super(props); - } - public render(): React.ReactNode { const { className, children, tooltip, tooltipProps } = this.props; diff --git a/src/components/views/emojipicker/ReactionPicker.tsx b/src/components/views/emojipicker/ReactionPicker.tsx index 2c2eb442a0..b62df99e25 100644 --- a/src/components/views/emojipicker/ReactionPicker.tsx +++ b/src/components/views/emojipicker/ReactionPicker.tsx @@ -37,6 +37,9 @@ class ReactionPicker extends React.Component { this.state = { selectedEmojis: new Set(Object.keys(this.getReactions())), }; + } + + public componentDidMount(): void { this.addListeners(); } diff --git a/src/components/views/messages/DateSeparator.tsx b/src/components/views/messages/DateSeparator.tsx index 7996b3bebe..6aed04d8f9 100644 --- a/src/components/views/messages/DateSeparator.tsx +++ b/src/components/views/messages/DateSeparator.tsx @@ -58,7 +58,9 @@ export default class DateSeparator extends React.Component { this.state = { jumpToDateEnabled: SettingsStore.getValue("feature_jump_to_date"), }; + } + public componentDidMount(): void { // We're using a watcher so the date headers in the timeline are updated // when the lab setting is toggled. this.settingWatcherRef = SettingsStore.watchSetting( diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index 332777be5e..d8cac8e28b 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -59,7 +59,7 @@ export default class MImageBody extends React.Component { public static contextType = RoomContext; public declare context: React.ContextType; - private unmounted = true; + private unmounted = false; private image = createRef(); private placeholder = createRef(); private timeout?: number; diff --git a/src/components/views/messages/MJitsiWidgetEvent.tsx b/src/components/views/messages/MJitsiWidgetEvent.tsx index a547a78f94..bec4f56164 100644 --- a/src/components/views/messages/MJitsiWidgetEvent.tsx +++ b/src/components/views/messages/MJitsiWidgetEvent.tsx @@ -21,10 +21,6 @@ interface IProps { } export default class MJitsiWidgetEvent extends React.PureComponent { - public constructor(props: IProps) { - super(props); - } - public render(): React.ReactNode { const url = this.props.mxEvent.getContent()["url"]; const prevUrl = this.props.mxEvent.getPrevContent()["url"]; diff --git a/src/components/views/messages/MLocationBody.tsx b/src/components/views/messages/MLocationBody.tsx index 742587e0a7..b226476fa8 100644 --- a/src/components/views/messages/MLocationBody.tsx +++ b/src/components/views/messages/MLocationBody.tsx @@ -75,6 +75,10 @@ export default class MLocationBody extends React.Component { this.context.on(ClientEvent.Sync, this.reconnectedListener); }; + public componentDidMount(): void { + this.unmounted = false; + } + public componentWillUnmount(): void { this.unmounted = true; this.context.off(ClientEvent.Sync, this.reconnectedListener); diff --git a/src/components/views/rooms/AppsDrawer.tsx b/src/components/views/rooms/AppsDrawer.tsx index 1d768cae35..c02bfe8cf2 100644 --- a/src/components/views/rooms/AppsDrawer.tsx +++ b/src/components/views/rooms/AppsDrawer.tsx @@ -68,11 +68,13 @@ export default class AppsDrawer extends React.Component { }; this.resizer = this.createResizer(); - - this.props.resizeNotifier.on("isResizing", this.onIsResizing); } public componentDidMount(): void { + this.unmounted = false; + + this.props.resizeNotifier.on("isResizing", this.onIsResizing); + ScalarMessaging.startListening(); WidgetLayoutStore.instance.on(WidgetLayoutStore.emissionForRoom(this.props.room), this.updateApps); this.dispatcherRef = dis.register(this.onAction); diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 0add0c1027..5f033de238 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -128,10 +128,10 @@ export default class BasicMessageEditor extends React.Component private lastCaret!: DocumentOffset; private lastSelection: ReturnType | null = null; - private readonly useMarkdownHandle: string; - private readonly emoticonSettingHandle: string; - private readonly shouldShowPillAvatarSettingHandle: string; - private readonly surroundWithHandle: string; + private useMarkdownHandle?: string; + private emoticonSettingHandle?: string; + private shouldShowPillAvatarSettingHandle?: string; + private surroundWithHandle?: string; private readonly historyManager = new HistoryManager(); public constructor(props: IProps) { @@ -145,28 +145,7 @@ export default class BasicMessageEditor extends React.Component const ua = navigator.userAgent.toLowerCase(); this.isSafari = ua.includes("safari/") && !ua.includes("chrome/"); - - this.useMarkdownHandle = SettingsStore.watchSetting( - "MessageComposerInput.useMarkdown", - null, - this.configureUseMarkdown, - ); - this.emoticonSettingHandle = SettingsStore.watchSetting( - "MessageComposerInput.autoReplaceEmoji", - null, - this.configureEmoticonAutoReplace, - ); this.configureEmoticonAutoReplace(); - this.shouldShowPillAvatarSettingHandle = SettingsStore.watchSetting( - "Pill.shouldShowPillAvatar", - null, - this.configureShouldShowPillAvatar, - ); - this.surroundWithHandle = SettingsStore.watchSetting( - "MessageComposerInput.surroundWith", - null, - this.surroundWithSettingChanged, - ); } public componentDidUpdate(prevProps: IProps): void { @@ -737,6 +716,27 @@ export default class BasicMessageEditor extends React.Component } public componentDidMount(): void { + this.useMarkdownHandle = SettingsStore.watchSetting( + "MessageComposerInput.useMarkdown", + null, + this.configureUseMarkdown, + ); + this.emoticonSettingHandle = SettingsStore.watchSetting( + "MessageComposerInput.autoReplaceEmoji", + null, + this.configureEmoticonAutoReplace, + ); + this.shouldShowPillAvatarSettingHandle = SettingsStore.watchSetting( + "Pill.shouldShowPillAvatar", + null, + this.configureShouldShowPillAvatar, + ); + this.surroundWithHandle = SettingsStore.watchSetting( + "MessageComposerInput.surroundWith", + null, + this.surroundWithSettingChanged, + ); + const model = this.props.model; model.setUpdateCallback(this.updateEditorState); const partCreator = model.partCreator; diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index 06f189df59..d62a451b8b 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -124,7 +124,7 @@ class EditMessageComposer extends React.Component; private readonly editorRef = createRef(); - private readonly dispatcherRef: string; + private dispatcherRef?: string; private readonly replyToEvent?: MatrixEvent; private model!: EditorModel; @@ -140,7 +140,9 @@ class EditMessageComposer extends React.Component } public componentDidMount(): void { + this.unmounted = false; this.suppressReadReceiptAnimation = false; const client = MatrixClientPeg.safeGet(); if (!this.props.forExport) { diff --git a/src/components/views/rooms/MemberList.tsx b/src/components/views/rooms/MemberList.tsx index 993a2ba1f1..e503ce2363 100644 --- a/src/components/views/rooms/MemberList.tsx +++ b/src/components/views/rooms/MemberList.tsx @@ -72,7 +72,7 @@ interface IState { export default class MemberList extends React.Component { private readonly showPresence: boolean; - private mounted = false; + private unmounted = false; public static contextType = SDKContext; public declare context: React.ContextType; @@ -82,8 +82,6 @@ export default class MemberList extends React.Component { super(props, context); this.state = this.getMembersState([], []); this.showPresence = context?.memberListStore.isPresenceEnabled() ?? true; - this.mounted = true; - this.listenForMembersChanges(); } private listenForMembersChanges(): void { @@ -102,11 +100,13 @@ export default class MemberList extends React.Component { } public componentDidMount(): void { + this.unmounted = false; + this.listenForMembersChanges(); this.updateListNow(true); } public componentWillUnmount(): void { - this.mounted = false; + this.unmounted = true; const cli = MatrixClientPeg.get(); if (cli) { cli.removeListener(RoomStateEvent.Update, this.onRoomStateUpdate); @@ -205,7 +205,7 @@ export default class MemberList extends React.Component { // XXX: exported for tests public async updateListNow(showLoadingSpinner?: boolean): Promise { - if (!this.mounted) { + if (this.unmounted) { return; } if (showLoadingSpinner) { @@ -215,7 +215,7 @@ export default class MemberList extends React.Component { this.props.roomId, this.props.searchQuery, ); - if (!this.mounted) { + if (this.unmounted) { return; } this.setState({ diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index c8d1573ebc..69139fae5b 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -134,9 +134,6 @@ export class MessageComposer extends React.Component { super(props, context); this.context = context; // otherwise React will only set it prior to render due to type def above - VoiceRecordingStore.instance.on(UPDATE_EVENT, this.onVoiceStoreUpdate); - - window.addEventListener("beforeunload", this.saveWysiwygEditorState); const isWysiwygLabEnabled = SettingsStore.getValue("feature_wysiwyg_composer"); let isRichTextEnabled = true; let initialComposerContent = ""; @@ -145,13 +142,6 @@ export class MessageComposer extends React.Component { if (wysiwygState) { isRichTextEnabled = wysiwygState.isRichText; initialComposerContent = wysiwygState.content; - if (wysiwygState.replyEventId) { - dis.dispatch({ - action: "reply_to_event", - event: this.props.room.findEventById(wysiwygState.replyEventId), - context: this.context.timelineRenderingType, - }); - } } } @@ -171,11 +161,6 @@ export class MessageComposer extends React.Component { }; this.instanceId = instanceCount++; - - SettingsStore.monitorSetting("MessageComposerInput.showStickersButton", null); - SettingsStore.monitorSetting("MessageComposerInput.showPollsButton", null); - SettingsStore.monitorSetting(Features.VoiceBroadcast, null); - SettingsStore.monitorSetting("feature_wysiwyg_composer", null); } private get editorStateKey(): string { @@ -248,6 +233,25 @@ export class MessageComposer extends React.Component { } public componentDidMount(): void { + VoiceRecordingStore.instance.on(UPDATE_EVENT, this.onVoiceStoreUpdate); + + window.addEventListener("beforeunload", this.saveWysiwygEditorState); + if (this.state.isWysiwygLabEnabled) { + const wysiwygState = this.restoreWysiwygEditorState(); + if (wysiwygState?.replyEventId) { + dis.dispatch({ + action: "reply_to_event", + event: this.props.room.findEventById(wysiwygState.replyEventId), + context: this.context.timelineRenderingType, + }); + } + } + + SettingsStore.monitorSetting("MessageComposerInput.showStickersButton", null); + SettingsStore.monitorSetting("MessageComposerInput.showPollsButton", null); + SettingsStore.monitorSetting(Features.VoiceBroadcast, null); + SettingsStore.monitorSetting("feature_wysiwyg_composer", null); + this.dispatcherRef = dis.register(this.onAction); this.waitForOwnMember(); UIStore.instance.trackElementDimensions(`MessageComposer${this.instanceId}`, this.ref.current!); diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx index c4cc418db4..6825ea8e43 100644 --- a/src/components/views/rooms/NotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge.tsx @@ -44,15 +44,23 @@ interface IState { } export default class NotificationBadge extends React.PureComponent, IState> { - private countWatcherRef: string; + private countWatcherRef?: string; public constructor(props: IProps) { super(props); - this.props.notification.on(NotificationStateEvents.Update, this.onNotificationUpdate); this.state = { showCounts: SettingsStore.getValue("Notifications.alwaysShowBadgeCounts", this.roomId), }; + } + + private get roomId(): string | null { + // We should convert this to null for safety with the SettingsStore + return this.props.roomId || null; + } + + public componentDidMount(): void { + this.props.notification.on(NotificationStateEvents.Update, this.onNotificationUpdate); this.countWatcherRef = SettingsStore.watchSetting( "Notifications.alwaysShowBadgeCounts", @@ -61,11 +69,6 @@ export default class NotificationBadge extends React.PureComponent v }; export default class RoomBreadcrumbs extends React.PureComponent { - private isMounted = true; + private unmounted = false; private toolbar = createRef(); public constructor(props: IProps) { @@ -70,17 +70,20 @@ export default class RoomBreadcrumbs extends React.PureComponent doAnimation: true, // technically we want animation on mount, but it won't be perfect skipFirst: false, // render the thing, as boring as it is }; + } + public componentDidMount(): void { + this.unmounted = false; BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate); } public componentWillUnmount(): void { - this.isMounted = false; + this.unmounted = true; BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate); } private onBreadcrumbsUpdate = (): void => { - if (!this.isMounted) return; + if (this.unmounted) return; // We need to trick the CSSTransition component into updating, which means we need to // tell it to not animate, then to animate a moment later. This causes two updates diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index de14808f33..8351c176ff 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -94,7 +94,6 @@ export class RoomTile extends React.PureComponent { // generatePreview() will return nothing if the user has previews disabled messagePreview: null, }; - this.generatePreview(); this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room); this.roomProps = EchoChamber.forRoom(this.props.room); @@ -147,6 +146,8 @@ export class RoomTile extends React.PureComponent { } public componentDidMount(): void { + this.generatePreview(); + // when we're first rendered (or our sublist is expanded) make sure we are visible if we're active if (this.state.selected) { this.scrollIntoView(); diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 776963eb33..a12a09dcb7 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -255,7 +255,7 @@ export class SendMessageComposer extends React.Component(); private model: EditorModel; private currentlyComposedEditorState: SerializedPart[] | null = null; - private dispatcherRef: string; + private dispatcherRef?: string; private sendHistoryManager: SendHistoryManager; public static defaultProps = { @@ -275,15 +275,17 @@ export class SendMessageComposer extends React.Component { } public componentDidMount(): void { + this.unmounted = false; const cli = MatrixClientPeg.safeGet(); cli.on(ClientEvent.AccountData, this.onAccountData); cli.on(CryptoEvent.UserTrustStatusChanged, this.onStatusChanged); diff --git a/src/components/views/settings/CryptographyPanel.tsx b/src/components/views/settings/CryptographyPanel.tsx index 08917d215c..ae0436a9e5 100644 --- a/src/components/views/settings/CryptographyPanel.tsx +++ b/src/components/views/settings/CryptographyPanel.tsx @@ -11,7 +11,6 @@ import { logger } from "matrix-js-sdk/src/logger"; import type ExportE2eKeysDialog from "../../../async-components/views/dialogs/security/ExportE2eKeysDialog"; import type ImportE2eKeysDialog from "../../../async-components/views/dialogs/security/ImportE2eKeysDialog"; -import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { _t } from "../../../languageHandler"; import Modal from "../../../Modal"; import AccessibleButton from "../elements/AccessibleButton"; @@ -20,6 +19,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import SettingsFlag from "../elements/SettingsFlag"; import { SettingLevel } from "../../../settings/SettingLevel"; import SettingsSubsection, { SettingsSubsectionText } from "./shared/SettingsSubsection"; +import MatrixClientContext from "../../../contexts/MatrixClientContext"; interface IProps {} @@ -33,17 +33,24 @@ interface IState { } export default class CryptographyPanel extends React.Component { - public constructor(props: IProps) { + public static contextType = MatrixClientContext; + public declare context: React.ContextType; + + public constructor(props: IProps, context: React.ContextType) { super(props); - const client = MatrixClientPeg.safeGet(); - const crypto = client.getCrypto(); - if (!crypto) { + if (!context.getCrypto()) { this.state = { deviceIdentityKey: null }; } else { this.state = { deviceIdentityKey: undefined }; - crypto - .getOwnDeviceKeys() + } + } + + public componentDidMount(): void { + if (this.state.deviceIdentityKey === undefined) { + this.context + .getCrypto() + ?.getOwnDeviceKeys() .then((keys) => { this.setState({ deviceIdentityKey: keys.ed25519 }); }) @@ -55,7 +62,7 @@ export default class CryptographyPanel extends React.Component { } public render(): React.ReactNode { - const client = MatrixClientPeg.safeGet(); + const client = this.context; const deviceId = client.deviceId; let identityKey = this.state.deviceIdentityKey; if (identityKey === undefined) { @@ -126,7 +133,7 @@ export default class CryptographyPanel extends React.Component { import("../../../async-components/views/dialogs/security/ExportE2eKeysDialog") as unknown as Promise< typeof ExportE2eKeysDialog >, - { matrixClient: MatrixClientPeg.safeGet() }, + { matrixClient: this.context }, ); }; @@ -135,12 +142,12 @@ export default class CryptographyPanel extends React.Component { import("../../../async-components/views/dialogs/security/ImportE2eKeysDialog") as unknown as Promise< typeof ImportE2eKeysDialog >, - { matrixClient: MatrixClientPeg.safeGet() }, + { matrixClient: this.context }, ); }; private updateBlacklistDevicesFlag = (checked: boolean): void => { - const crypto = MatrixClientPeg.safeGet().getCrypto(); + const crypto = this.context.getCrypto(); if (crypto) crypto.globalBlacklistUnverifiedDevices = checked; }; } diff --git a/src/components/views/settings/FontScalingPanel.tsx b/src/components/views/settings/FontScalingPanel.tsx index f6dedb3ffb..b7f7c64a3b 100644 --- a/src/components/views/settings/FontScalingPanel.tsx +++ b/src/components/views/settings/FontScalingPanel.tsx @@ -55,6 +55,7 @@ export default class FontScalingPanel extends React.Component { } public async componentDidMount(): Promise { + this.unmounted = false; // Fetch the current user profile for the message preview const client = MatrixClientPeg.safeGet(); const userId = client.getSafeUserId(); diff --git a/src/components/views/settings/Notifications.tsx b/src/components/views/settings/Notifications.tsx index 78660d4f9d..6890c7b5d3 100644 --- a/src/components/views/settings/Notifications.tsx +++ b/src/components/views/settings/Notifications.tsx @@ -206,7 +206,7 @@ const NotificationActivitySettings = (): JSX.Element => { * The old, deprecated notifications tab view, only displayed if the user has the labs flag disabled. */ export default class Notifications extends React.PureComponent { - private settingWatchers: string[]; + private settingWatchers: string[] = []; public constructor(props: IProps) { super(props); @@ -220,7 +220,17 @@ export default class Notifications extends React.PureComponent { clearingNotifications: false, ruleIdsWithError: {}, }; + } + private get isInhibited(): boolean { + // Caution: The master rule's enabled state is inverted from expectation. When + // the master rule is *enabled* it means all other rules are *disabled* (or + // inhibited). Conversely, when the master rule is *disabled* then all other rules + // are *enabled* (or operate fine). + return !!this.state.masterPushRule?.enabled; + } + + public componentDidMount(): void { this.settingWatchers = [ SettingsStore.watchSetting("notificationsEnabled", null, (...[, , , , value]) => this.setState({ desktopNotifications: value as boolean }), @@ -235,17 +245,7 @@ export default class Notifications extends React.PureComponent { this.setState({ audioNotifications: value as boolean }), ), ]; - } - private get isInhibited(): boolean { - // Caution: The master rule's enabled state is inverted from expectation. When - // the master rule is *enabled* it means all other rules are *disabled* (or - // inhibited). Conversely, when the master rule is *disabled* then all other rules - // are *enabled* (or operate fine). - return !!this.state.masterPushRule?.enabled; - } - - public componentDidMount(): void { // noinspection JSIgnoredPromiseFromCall this.refreshFromServer(); this.refreshFromAccountData(); diff --git a/src/components/views/settings/SecureBackupPanel.tsx b/src/components/views/settings/SecureBackupPanel.tsx index dac7425e3c..6a855c8ea8 100644 --- a/src/components/views/settings/SecureBackupPanel.tsx +++ b/src/components/views/settings/SecureBackupPanel.tsx @@ -83,6 +83,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> { } public componentDidMount(): void { + this.unmounted = false; this.loadBackupStatus(); MatrixClientPeg.safeGet().on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus); diff --git a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx index c32ac5150b..337cead3a3 100644 --- a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx @@ -53,9 +53,11 @@ export default class AdvancedRoomSettingsTab extends React.Component { collapsed, childSpaces: this.childSpaces, }; + } + public componentDidMount(): void { SpaceStore.instance.on(this.props.space.roomId, this.onSpaceUpdate); this.props.space.on(RoomEvent.Name, this.onRoomNameChange); } diff --git a/src/components/views/voip/LegacyCallView.tsx b/src/components/views/voip/LegacyCallView.tsx index 124d078782..aba3d60743 100644 --- a/src/components/views/voip/LegacyCallView.tsx +++ b/src/components/views/voip/LegacyCallView.tsx @@ -110,11 +110,10 @@ export default class LegacyCallView extends React.Component { sidebarFeeds: sidebar, sidebarShown: true, }; - - this.updateCallListeners(null, this.props.call); } public componentDidMount(): void { + this.updateCallListeners(null, this.props.call); this.dispatcherRef = dis.register(this.onAction); document.addEventListener("keydown", this.onNativeKeyDown); } diff --git a/test/unit-tests/components/views/settings/CryptographyPanel-test.tsx b/test/unit-tests/components/views/settings/CryptographyPanel-test.tsx index ce506461ee..0b699c1383 100644 --- a/test/unit-tests/components/views/settings/CryptographyPanel-test.tsx +++ b/test/unit-tests/components/views/settings/CryptographyPanel-test.tsx @@ -14,6 +14,7 @@ import { mocked } from "jest-mock"; import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg"; import * as TestUtils from "../../../../test-utils"; import CryptographyPanel from "../../../../../src/components/views/settings/CryptographyPanel"; +import { withClientContextRenderOptions } from "../../../../test-utils"; describe("CryptographyPanel", () => { it("shows the session ID and key", async () => { @@ -28,7 +29,7 @@ describe("CryptographyPanel", () => { mocked(client.getCrypto()!.getOwnDeviceKeys).mockResolvedValue({ ed25519: sessionKey, curve25519: "1234" }); // When we render the CryptographyPanel - const rendered = render(); + const rendered = render(, withClientContextRenderOptions(client)); // Then it displays info about the user's session const codes = rendered.container.querySelectorAll("code"); @@ -52,7 +53,7 @@ describe("CryptographyPanel", () => { mocked(client.getCrypto()!.getOwnDeviceKeys).mockRejectedValue(new Error("bleh")); // When we render the CryptographyPanel - const rendered = render(); + const rendered = render(, withClientContextRenderOptions(client)); // Then it displays info about the user's session const codes = rendered.container.querySelectorAll("code"); From 8e304713a20d4cb41c0e96b83b26c7062145253f Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Mon, 4 Nov 2024 06:25:18 +0000 Subject: [PATCH 13/38] [create-pull-request] automated change (#28372) Co-authored-by: t3chguy --- src/i18n/strings/nl.json | 43 +++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index a5a1bfd015..35de7da36c 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -1,6 +1,8 @@ { "a11y": { + "emoji_picker": "Emoji kiezer", "jump_first_invite": "Ga naar de eerste uitnodiging.", + "message_composer": "Bericht opsteller", "n_unread_messages": { "other": "%(count)s ongelezen berichten.", "one": "1 ongelezen bericht." @@ -9,22 +11,26 @@ "other": "%(count)s ongelezen berichten, inclusief vermeldingen.", "one": "1 ongelezen vermelding." }, + "recent_rooms": "Recente kamers", "room_name": "Kamer %(name)s", + "room_status_bar": "Kamerstatus balk", + "seek_bar_label": "Audio zoekbalk", "unread_messages": "Ongelezen berichten.", - "user_menu": "Persoonsmenu" + "user_menu": "Gebruikersmenu" }, - "a11y_jump_first_unread_room": "Ga naar het eerste ongelezen kamer.", + "a11y_jump_first_unread_room": "Ga naar de eerste ongelezen kamer", "action": { "accept": "Accepteren", "add": "Toevoegen", - "add_existing_room": "Bestaande kamers toevoegen", + "add_existing_room": "Voeg bestaande kamer toeroom", "add_people": "Personen toevoegen", + "apply": "Toepassen", "approve": "Keur goed", "ask_to_join": "Vraag om toe te treden", "back": "Terug", "call": "Bellen", "cancel": "Annuleer", - "change": "Wijzigen", + "change": "Wijzig", "clear": "Wis", "click": "Klik", "click_to_copy": "Klik om te kopiĂ«ren", @@ -376,12 +382,15 @@ "other": "en %(count)s anderen
", "one": "en één andere
" }, + "android": "Android", "appearance": "Weergave", "application": "Toepassing", "are_you_sure": "Weet je het zeker?", "attachment": "Bijlage", "authentication": "Login bevestigen", "avatar": "Afbeelding", + "beta": "BÈTA", + "camera": "Camera", "cameras": "Camera's", "capabilities": "Mogelijkheden", "copied": "Gekopieerd!", @@ -393,28 +402,38 @@ "device": "Apparaat", "edited": "bewerkt", "email_address": "E-mailadres", + "emoji": "Emoji", "encrypted": "Versleuteld", "encryption_enabled": "Versleuteling ingeschakeld", "error": "Fout", + "faq": "FAQ", "favourites": "Favorieten", "filter_results": "Resultaten filteren", "forward_message": "Bericht doorsturen", "general": "Algemeen", "go_to_settings": "Ga naar instellingen", "guest": "Gast", + "help": "Hulp", "historical": "Historisch", "home": "Thuis", + "homeserver": "Homeserver", "identity_server": "Identiteitsserver", "image": "Afbeelding", "integration_manager": "Integratiebeheerder", + "ios": "iOS", "joined": "Toegetreden", + "labs": "Labs", "legal": "Juridisch", "light": "Helder", + "loading": "Laden...", "location": "Locatie", "low_priority": "Lage prioriteit", + "matrix": "Matrix", "message": "Bericht", "message_layout": "Berichtlayout", "microphone": "Microfoon", + "model": "Model", + "modern": "Modern", "mute": "Dempen", "n_members": { "other": "%(count)s personen", @@ -429,6 +448,7 @@ "no_results_found": "Geen resultaten gevonden", "not_trusted": "Niet vertrouwd", "off": "Uit", + "offline": "Offline", "on": "Aan", "options": "Opties", "orphan_rooms": "Andere kamers", @@ -437,6 +457,7 @@ "preferences": "Voorkeuren", "presence": "Aanwezigheid", "preview_message": "Hey. Jij bent de beste!", + "privacy": "Privacy", "private": "PrivĂ©", "private_room": "PrivĂ© kamer", "private_space": "PrivĂ© Space", @@ -454,10 +475,13 @@ "secure_backup": "Beveiligde back-up", "security": "Beveiliging", "select_all": "Allemaal selecteren", + "server": "Server", "settings": "Instellingen", "setup_secure_messages": "Beveiligde berichten instellen", "show_more": "Meer tonen", "someone": "Iemand", + "space": "Space", + "sticker": "Sticker", "stickerpack": "Stickerpakket", "success": "Klaar", "suggestions": "Suggesties", @@ -465,6 +489,7 @@ "system_alerts": "Systeemmeldingen", "theme": "Thema", "thread": "Draad", + "threads": "Onderwerpen", "timeline": "Tijdslijn", "trusted": "Vertrouwd", "unencrypted": "Onversleuteld", @@ -472,6 +497,7 @@ "unnamed_room": "Naamloze Kamer", "unnamed_space": "Naamloze Space", "unverified": "Niet geverifieerd", + "user": "Gebruiker", "user_avatar": "Profielfoto", "username": "Inlognaam", "verification_cancelled": "Verificatie geannuleerd", @@ -2587,6 +2613,7 @@ "about_minute_ago": "ongeveer een minuut geleden", "date_at_time": "%(date)s om %(time)s", "few_seconds_ago": "enige tellen geleden", + "hours_minutes_seconds_left": "%(hours)su, %(minutes)sm %(seconds)ss over", "in_about_day": "over een dag of zo", "in_about_hour": "over ongeveer een uur", "in_about_minute": "over ongeveer een minuut", @@ -2595,10 +2622,16 @@ "in_n_hours": "over %(num)s uur", "in_n_minutes": "over %(num)s minuten", "left": "%(timeRemaining)s over", + "minutes_seconds_left": "%(minutes)sm %(seconds)ss over", "n_days_ago": "%(num)s dagen geleden", "n_hours_ago": "%(num)s uur geleden", "n_minutes_ago": "%(num)s minuten geleden", - "seconds_left": "%(seconds)s's over" + "seconds_left": "%(seconds)s's over", + "short_days": "%(value)sd", + "short_days_hours_minutes_seconds": "%(days)sd %(hours)su %(minutes)sm %(seconds)ss", + "short_hours": "%(value)sh", + "short_minutes": "%(value)sm", + "short_seconds": "%(value)ss" }, "timeline": { "context_menu": { From 00d46f1c8f6cf145ac9c8f6e9d4136611f1d1a55 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 4 Nov 2024 09:49:20 +0000 Subject: [PATCH 14/38] Remove unused Security customisations module (#28350) Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- docs/customisations.md | 8 ++-- src/customisations/Security.ts | 73 ---------------------------------- 2 files changed, 4 insertions(+), 77 deletions(-) delete mode 100644 src/customisations/Security.ts diff --git a/docs/customisations.md b/docs/customisations.md index b5075b6fce..a6f72ab1ab 100644 --- a/docs/customisations.md +++ b/docs/customisations.md @@ -11,8 +11,8 @@ Customisations will be removed from the codebase in a future release. Element Web and the React SDK support "customisation points" that can be used to easily add custom logic specific to a particular deployment of Element Web. -An example of this is the [security customisations -module](https://github.com/element-hq/element-web/blob/develop/src/customisations/Security.ts). +An example of this is the [media customisations +module](https://github.com/element-hq/element-web/blob/develop/src/customisations/Media.ts). This module in the React SDK only defines some empty functions and their types: it does not do anything by default. @@ -21,14 +21,14 @@ Web so that you can add your own code. Even though the default module is part of the React SDK, you can still override it from the Element Web layer: 1. Copy the default customisation module to - `element-web/src/customisations/YourNameSecurity.ts` + `element-web/src/customisations/YourNameMedia.ts` 2. Edit customisations points and make sure export the ones you actually want to activate 3. Create/add an entry to `customisations.json` next to the webpack config: ```json { - "src/customisations/Security.ts": "src/customisations/YourNameSecurity.ts" + "src/customisations/Media.ts": "src/customisations/YourNameMedia.ts" } ``` diff --git a/src/customisations/Security.ts b/src/customisations/Security.ts deleted file mode 100644 index c1b8b580a9..0000000000 --- a/src/customisations/Security.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2020 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ -import { CryptoCallbacks } from "matrix-js-sdk/src/crypto-api"; - -import { IMatrixClientCreds } from "../MatrixClientPeg"; -import { Kind as SetupEncryptionKind } from "../toasts/SetupEncryptionToast"; - -/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ -function examineLoginResponse(response: any, credentials: IMatrixClientCreds): void { - // E.g. add additional data to the persisted credentials -} - -/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ -function persistCredentials(credentials: IMatrixClientCreds): void { - // E.g. store any additional credential fields -} - -/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ -function createSecretStorageKey(): Uint8Array | null { - // E.g. generate or retrieve secret storage key somehow - return null; -} - -/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ -function getSecretStorageKey(): Uint8Array | null { - // E.g. retrieve secret storage key from some other place - return null; -} - -/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ -function catchAccessSecretStorageError(e: unknown): void { - // E.g. notify the user in some way -} - -/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ -function setupEncryptionNeeded(kind: SetupEncryptionKind): boolean { - // E.g. trigger some kind of setup - return false; -} - -// This interface summarises all available customisation points and also marks -// them all as optional. This allows customisers to only define and export the -// customisations they need while still maintaining type safety. -export interface ISecurityCustomisations { - examineLoginResponse?: typeof examineLoginResponse; - persistCredentials?: typeof persistCredentials; - createSecretStorageKey?: typeof createSecretStorageKey; - getSecretStorageKey?: typeof getSecretStorageKey; - catchAccessSecretStorageError?: typeof catchAccessSecretStorageError; - setupEncryptionNeeded?: typeof setupEncryptionNeeded; - getDehydrationKey?: CryptoCallbacks["getDehydrationKey"]; - - /** - * When false, disables the post-login UI from showing. If there's - * an error during setup, that will be shown to the user. - * - * Note: when this is set to false then the app will assume the user's - * encryption is set up some other way which would circumvent the default - * UI, such as by presenting alternative UI. - */ - SHOW_ENCRYPTION_SETUP_UI?: boolean; // default true -} - -// A real customisation module will define and export one or more of the -// customisation points that make up `ISecurityCustomisations`. -export default { - SHOW_ENCRYPTION_SETUP_UI: true, -} as ISecurityCustomisations; From b1ef099cd6692f2bf2025aca1946432dc404f9d8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 4 Nov 2024 09:49:35 +0000 Subject: [PATCH 15/38] Show developer jump to event button on all events with associations (#28351) Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/context_menus/MessageContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 5cf947092b..d5749658c9 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -507,7 +507,7 @@ export default class MessageContextMenu extends React.Component } let jumpToRelatedEventButton: JSX.Element | undefined; - const relatedEventId = mxEvent.relationEventId; + const relatedEventId = mxEvent.getAssociatedId(); if (relatedEventId && SettingsStore.getValue("developerMode")) { jumpToRelatedEventButton = ( Date: Mon, 4 Nov 2024 09:49:41 +0000 Subject: [PATCH 16/38] Wire up analytics for Legacy/EC/Jitsi voip options (#28348) * Wire up analytics for Legacy/EC/Jitsi voip options Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update @matrix-org/analytics-events Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- package.json | 2 +- src/components/views/rooms/RoomHeader.tsx | 29 ++++++++++--------- src/hooks/room/useRoomCall.tsx | 35 +++++++++++++++-------- src/utils/room/placeCall.ts | 6 +++- yarn.lock | 8 +++--- 5 files changed, 49 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 11232c479c..15bc0aec86 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "dependencies": { "@babel/runtime": "^7.12.5", "@formatjs/intl-segmenter": "^11.5.7", - "@matrix-org/analytics-events": "^0.28.0", + "@matrix-org/analytics-events": "^0.29.0", "@matrix-org/emojibase-bindings": "^1.3.3", "@vector-im/matrix-wysiwyg": "2.37.13", "@matrix-org/react-sdk-module-api": "^2.4.0", diff --git a/src/components/views/rooms/RoomHeader.tsx b/src/components/views/rooms/RoomHeader.tsx index fc6fbcffa7..c2642ea733 100644 --- a/src/components/views/rooms/RoomHeader.tsx +++ b/src/components/views/rooms/RoomHeader.tsx @@ -27,7 +27,7 @@ import { useRoomMemberCount, useRoomMembers } from "../../../hooks/useRoomMember import { _t } from "../../../languageHandler"; import { Flex } from "../../utils/Flex"; import { Box } from "../../utils/Box"; -import { getPlatformCallTypeChildren, getPlatformCallTypeLabel, useRoomCall } from "../../../hooks/room/useRoomCall"; +import { getPlatformCallTypeProps, useRoomCall } from "../../../hooks/room/useRoomCall"; import { useRoomThreadNotifications } from "../../../hooks/room/useRoomThreadNotifications"; import { useGlobalNotificationState } from "../../../hooks/useGlobalNotificationState"; import SdkConfig from "../../../SdkConfig"; @@ -167,18 +167,21 @@ export default function RoomHeader({ side="left" align="start" > - {callOptions.map((option) => ( - videoCallClick(ev, option)} - Icon={VideoCallIcon} - onSelect={() => {} /* Dummy handler since we want the click event.*/} - /> - ))} + {callOptions.map((option) => { + const { label, children } = getPlatformCallTypeProps(option); + return ( + videoCallClick(ev, option)} + Icon={VideoCallIcon} + onSelect={() => {} /* Dummy handler since we want the click event.*/} + /> + ); + })} ) : ( { + +export const getPlatformCallTypeProps = ( + platformCallType: PlatformCallType, +): { + label: string; + children?: ReactNode; + analyticsName: InteractionName; +} => { switch (platformCallType) { case PlatformCallType.ElementCall: - return _t("voip|element_call"); + return { + label: _t("voip|element_call"), + analyticsName: "WebVoipOptionElementCall", + children: , + }; case PlatformCallType.JitsiCall: - return _t("voip|jitsi_call"); + return { + label: _t("voip|jitsi_call"), + analyticsName: "WebVoipOptionJitsi", + }; case PlatformCallType.LegacyCall: - return _t("voip|legacy_call"); - } -}; -export const getPlatformCallTypeChildren = (platformCallType: PlatformCallType): ReactNode => { - switch (platformCallType) { - case PlatformCallType.ElementCall: - return ; - default: - return null; + return { + label: _t("voip|legacy_call"), + analyticsName: "WebVoipOptionLegacy", + }; } }; + const enum State { NoCall, NoOneHere, diff --git a/src/utils/room/placeCall.ts b/src/utils/room/placeCall.ts index d3d8a7ab8e..13b0de23c4 100644 --- a/src/utils/room/placeCall.ts +++ b/src/utils/room/placeCall.ts @@ -10,10 +10,11 @@ import { CallType } from "matrix-js-sdk/src/webrtc/call"; import { Room } from "matrix-js-sdk/src/matrix"; import LegacyCallHandler from "../../LegacyCallHandler"; -import { PlatformCallType } from "../../hooks/room/useRoomCall"; +import { getPlatformCallTypeProps, PlatformCallType } from "../../hooks/room/useRoomCall"; import defaultDispatcher from "../../dispatcher/dispatcher"; import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload"; import { Action } from "../../dispatcher/actions"; +import PosthogTrackers from "../../PosthogTrackers"; /** * Helper to place a call in a room that works with all the legacy modes @@ -27,6 +28,9 @@ export const placeCall = async ( platformCallType: PlatformCallType, skipLobby: boolean, ): Promise => { + const { analyticsName } = getPlatformCallTypeProps(platformCallType); + PosthogTrackers.trackInteraction(analyticsName); + if (platformCallType == PlatformCallType.LegacyCall || platformCallType == PlatformCallType.JitsiCall) { await LegacyCallHandler.instance.placeCall(room.roomId, callType); } else if (platformCallType == PlatformCallType.ElementCall) { diff --git a/yarn.lock b/yarn.lock index dee966cb86..68d76fe4dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1931,10 +1931,10 @@ resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe" integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== -"@matrix-org/analytics-events@^0.28.0": - version "0.28.0" - resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.28.0.tgz#91f501bb25435b9418f785ca850ca858aa6efc76" - integrity sha512-RvvGBYzgJrk2wTRVGk2fWhGM1f69f6nBraRqTiuqlqE2eQd2hZ2onHyRhvhxJeKVC/oNBsvrupObqrrWowXsnQ== +"@matrix-org/analytics-events@^0.29.0": + version "0.29.0" + resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.29.0.tgz#ebadd922f50c0932a5c6af8b5e156f8f839f3236" + integrity sha512-7L+dQJuFt2iIhxhWwvJVp89/UdOfl2P455Ki9Krw0LGCMazPOwo5bc9GUjj/OKXO+eNHq+Qml1+RkYW+8LCk2Q== "@matrix-org/emojibase-bindings@^1.3.3": version "1.3.3" From 38e5eeea00a56d548dee683eaaf9158ce335175d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 4 Nov 2024 11:31:44 +0000 Subject: [PATCH 17/38] Fix markdown escaping wrongly passing html through (#28363) * Fix markdown escaping wrongly passing html through Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add comment Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/Markdown.ts | 5 ++++- test/unit-tests/editor/serialize-test.ts | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Markdown.ts b/src/Markdown.ts index 5e4265bc26..9a346bf2f6 100644 --- a/src/Markdown.ts +++ b/src/Markdown.ts @@ -383,6 +383,9 @@ export default class Markdown { if (isMultiLine(node) && node.next) this.lit("\n\n"); }; - return renderer.render(this.parsed); + // We inhibit the default escape function as we escape the entire output string to correctly handle backslashes + renderer.esc = (input: string) => input; + + return escape(renderer.render(this.parsed)); } } diff --git a/test/unit-tests/editor/serialize-test.ts b/test/unit-tests/editor/serialize-test.ts index e9f905125b..8920bf1747 100644 --- a/test/unit-tests/editor/serialize-test.ts +++ b/test/unit-tests/editor/serialize-test.ts @@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details. import { mocked } from "jest-mock"; import EditorModel from "../../../src/editor/model"; -import { htmlSerializeIfNeeded } from "../../../src/editor/serialize"; +import { htmlSerializeFromMdIfNeeded, htmlSerializeIfNeeded } from "../../../src/editor/serialize"; import { createPartCreator } from "./mock"; import { IConfigOptions } from "../../../src/IConfigOptions"; import SettingsStore from "../../../src/settings/SettingsStore"; @@ -71,6 +71,12 @@ describe("editor/serialize", function () { const html = htmlSerializeIfNeeded(model, {}); expect(html).toBe("*hello* world"); }); + it("escaped markdown should not retain backslashes around other markdown", function () { + const pc = createPartCreator(); + const model = new EditorModel([pc.plain("\\*hello\\* **world**")], pc); + const html = htmlSerializeIfNeeded(model, {}); + expect(html).toBe("*hello* world"); + }); it("escaped markdown should convert HTML entities", function () { const pc = createPartCreator(); const model = new EditorModel([pc.plain("\\*hello\\* world < hey world!")], pc); @@ -153,6 +159,14 @@ describe("editor/serialize", function () { const html = htmlSerializeIfNeeded(model, { forceHTML: true, useMarkdown: false }); expect(html).toBe("hello world"); }); + it("should treat tags not in allowlist as plaintext", () => { + const html = htmlSerializeFromMdIfNeeded("test", {}); + expect(html).toBeUndefined(); + }); + it("should treat tags not in allowlist as plaintext even if escaped", () => { + const html = htmlSerializeFromMdIfNeeded("\\test", {}); + expect(html).toBe("<b>test</b>"); + }); }); describe("feature_latex_maths", () => { From 502cc91dfee611699518e4d2c4708ef624ddc0e6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 4 Nov 2024 11:34:00 +0000 Subject: [PATCH 18/38] Switch ModalManager to the React 18 createRoot API (#28336) * Remove boilerplate around dispatcher and settings watchers Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Move state update listeners from constructor to componentDidMount Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Switch ModalManager to the React 18 createRoot API Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/Modal.tsx | 61 +++++++++---------- .../components/structures/MatrixChat-test.tsx | 6 +- .../structures/auth/ForgotPassword-test.tsx | 36 +++++------ .../dialogs/ConfirmRedactDialog-test.tsx | 6 +- .../views/messages/DateSeparator-test.tsx | 6 +- .../wysiwyg_composer/utils/message-test.ts | 2 +- .../views/settings/JoinRuleSettings-test.tsx | 4 +- .../tabs/user/SessionManagerTab-test.tsx | 1 + .../media/requestMediaPermissions-test.tsx | 4 +- 9 files changed, 66 insertions(+), 60 deletions(-) diff --git a/src/Modal.tsx b/src/Modal.tsx index 9e6a7672be..a2919bdc5f 100644 --- a/src/Modal.tsx +++ b/src/Modal.tsx @@ -8,9 +8,9 @@ Please see LICENSE files in the repository root for full details. */ import React, { StrictMode } from "react"; -import ReactDOM from "react-dom"; +import { createRoot, Root } from "react-dom/client"; import classNames from "classnames"; -import { IDeferred, defer, sleep } from "matrix-js-sdk/src/utils"; +import { IDeferred, defer } from "matrix-js-sdk/src/utils"; import { TypedEventEmitter } from "matrix-js-sdk/src/matrix"; import { Glass, TooltipProvider } from "@vector-im/compound-web"; @@ -69,6 +69,16 @@ type HandlerMap = { type ModalCloseReason = "backgroundClick"; +function getOrCreateContainer(id: string): HTMLDivElement { + let container = document.getElementById(id) as HTMLDivElement | null; + if (!container) { + container = document.createElement("div"); + container.id = id; + document.body.appendChild(container); + } + return container; +} + export class ModalManager extends TypedEventEmitter { private counter = 0; // The modal to prioritise over all others. If this is set, only show @@ -83,28 +93,22 @@ export class ModalManager extends TypedEventEmitter[] = []; - private static getOrCreateContainer(): HTMLElement { - let container = document.getElementById(DIALOG_CONTAINER_ID); - - if (!container) { - container = document.createElement("div"); - container.id = DIALOG_CONTAINER_ID; - document.body.appendChild(container); + private static root?: Root; + private static getOrCreateRoot(): Root { + if (!ModalManager.root) { + const container = getOrCreateContainer(DIALOG_CONTAINER_ID); + ModalManager.root = createRoot(container); } - - return container; + return ModalManager.root; } - private static getOrCreateStaticContainer(): HTMLElement { - let container = document.getElementById(STATIC_DIALOG_CONTAINER_ID); - - if (!container) { - container = document.createElement("div"); - container.id = STATIC_DIALOG_CONTAINER_ID; - document.body.appendChild(container); + private static staticRoot?: Root; + private static getOrCreateStaticRoot(): Root { + if (!ModalManager.staticRoot) { + const container = getOrCreateContainer(STATIC_DIALOG_CONTAINER_ID); + ModalManager.staticRoot = createRoot(container); } - - return container; + return ModalManager.staticRoot; } public constructor() { @@ -389,19 +393,14 @@ export class ModalManager extends TypedEventEmitter { - // TODO: We should figure out how to remove this weird sleep. It also makes testing harder - // - // await next tick because sometimes ReactDOM can race with itself and cause the modal to wrongly stick around - await sleep(0); - if (this.modals.length === 0 && !this.priorityModal && !this.staticModal) { // If there is no modal to render, make all of Element available // to screen reader users again dis.dispatch({ action: "aria_unhide_main_app", }); - ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer()); - ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateStaticContainer()); + ModalManager.getOrCreateRoot().render(<>); + ModalManager.getOrCreateStaticRoot().render(<>); return; } @@ -432,10 +431,10 @@ export class ModalManager extends TypedEventEmitter ); - ReactDOM.render(staticDialog, ModalManager.getOrCreateStaticContainer()); + ModalManager.getOrCreateStaticRoot().render(staticDialog); } else { // This is safe to call repeatedly if we happen to do that - ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateStaticContainer()); + ModalManager.getOrCreateStaticRoot().render(<>); } const modal = this.getCurrentModal(); @@ -461,10 +460,10 @@ export class ModalManager extends TypedEventEmitter ); - setTimeout(() => ReactDOM.render(dialog, ModalManager.getOrCreateContainer()), 0); + ModalManager.getOrCreateRoot().render(dialog); } else { // This is safe to call repeatedly if we happen to do that - ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer()); + ModalManager.getOrCreateRoot().render(<>); } } } diff --git a/test/unit-tests/components/structures/MatrixChat-test.tsx b/test/unit-tests/components/structures/MatrixChat-test.tsx index 7f565d682f..4e04025f55 100644 --- a/test/unit-tests/components/structures/MatrixChat-test.tsx +++ b/test/unit-tests/components/structures/MatrixChat-test.tsx @@ -62,6 +62,7 @@ import { DRAFT_LAST_CLEANUP_KEY } from "../../../../src/DraftCleaner"; import { UIFeature } from "../../../../src/settings/UIFeature"; import AutoDiscoveryUtils from "../../../../src/utils/AutoDiscoveryUtils"; import { ValidatedServerConfig } from "../../../../src/utils/ValidatedServerConfig"; +import Modal from "../../../../src/Modal.tsx"; jest.mock("matrix-js-sdk/src/oidc/authorize", () => ({ completeAuthorizationCodeGrant: jest.fn(), @@ -1514,7 +1515,9 @@ describe("", () => { describe("when key backup failed", () => { it("should show the new recovery method dialog", async () => { + const spy = jest.spyOn(Modal, "createDialogAsync"); jest.mock("../../../../src/async-components/views/dialogs/security/NewRecoveryMethodDialog", () => ({ + __test: true, __esModule: true, default: () => mocked dialog, })); @@ -1526,7 +1529,8 @@ describe("", () => { }); await flushPromises(); mockClient.emit(CryptoEvent.KeyBackupFailed, "error code"); - await waitFor(() => expect(screen.getByText("mocked dialog")).toBeInTheDocument()); + await waitFor(() => expect(spy).toHaveBeenCalledTimes(1)); + expect(await spy.mock.lastCall![0]).toEqual(expect.objectContaining({ __test: true })); }); }); }); diff --git a/test/unit-tests/components/structures/auth/ForgotPassword-test.tsx b/test/unit-tests/components/structures/auth/ForgotPassword-test.tsx index f439605319..db6ce005c0 100644 --- a/test/unit-tests/components/structures/auth/ForgotPassword-test.tsx +++ b/test/unit-tests/components/structures/auth/ForgotPassword-test.tsx @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details. import React from "react"; import { mocked } from "jest-mock"; -import { act, render, RenderResult, screen } from "jest-matrix-react"; +import { act, render, RenderResult, screen, waitFor } from "jest-matrix-react"; import userEvent from "@testing-library/user-event"; import { MatrixClient, createClient } from "matrix-js-sdk/src/matrix"; @@ -47,14 +47,12 @@ describe("", () => { }; const click = async (element: Element): Promise => { - await act(async () => { - await userEvent.click(element, { delay: null }); - }); + await userEvent.click(element, { delay: null }); }; const itShouldCloseTheDialogAndShowThePasswordInput = (): void => { - it("should close the dialog and show the password input", () => { - expect(screen.queryByText("Verify your email to continue")).not.toBeInTheDocument(); + it("should close the dialog and show the password input", async () => { + await waitFor(() => expect(screen.queryByText("Verify your email to continue")).not.toBeInTheDocument()); expect(screen.getByText("Reset your password")).toBeInTheDocument(); }); }; @@ -314,7 +312,7 @@ describe("", () => { }); }); - it("should send the new password and show the click validation link dialog", () => { + it("should send the new password and show the click validation link dialog", async () => { expect(client.setPassword).toHaveBeenCalledWith( { type: "m.login.email.identity", @@ -326,15 +324,15 @@ describe("", () => { testPassword, false, ); - expect(screen.getByText("Verify your email to continue")).toBeInTheDocument(); + await expect( + screen.findByText("Verify your email to continue"), + ).resolves.toBeInTheDocument(); expect(screen.getByText(testEmail)).toBeInTheDocument(); }); describe("and dismissing the dialog by clicking the background", () => { beforeEach(async () => { - await act(async () => { - await userEvent.click(screen.getByTestId("dialog-background"), { delay: null }); - }); + await userEvent.click(await screen.findByTestId("dialog-background"), { delay: null }); await waitEnoughCyclesForModal({ useFakeTimers: true, }); @@ -345,7 +343,7 @@ describe("", () => { describe("and dismissing the dialog", () => { beforeEach(async () => { - await click(screen.getByLabelText("Close dialog")); + await click(await screen.findByLabelText("Close dialog")); await waitEnoughCyclesForModal({ useFakeTimers: true, }); @@ -356,14 +354,16 @@ describe("", () => { describe("and clicking »Re-enter email address«", () => { beforeEach(async () => { - await click(screen.getByText("Re-enter email address")); + await click(await screen.findByText("Re-enter email address")); await waitEnoughCyclesForModal({ useFakeTimers: true, }); }); - it("should close the dialog and go back to the email input", () => { - expect(screen.queryByText("Verify your email to continue")).not.toBeInTheDocument(); + it("should close the dialog and go back to the email input", async () => { + await waitFor(() => + expect(screen.queryByText("Verify your email to continue")).not.toBeInTheDocument(), + ); expect(screen.queryByText("Enter your email to reset password")).toBeInTheDocument(); }); }); @@ -397,11 +397,11 @@ describe("", () => { }); it("should show the sign out warning dialog", async () => { - expect( - screen.getByText( + await expect( + screen.findByText( "Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.", ), - ).toBeInTheDocument(); + ).resolves.toBeInTheDocument(); // confirm dialog await click(screen.getByText("Continue")); diff --git a/test/unit-tests/components/views/dialogs/ConfirmRedactDialog-test.tsx b/test/unit-tests/components/views/dialogs/ConfirmRedactDialog-test.tsx index b6c894c52b..c648f416f9 100644 --- a/test/unit-tests/components/views/dialogs/ConfirmRedactDialog-test.tsx +++ b/test/unit-tests/components/views/dialogs/ConfirmRedactDialog-test.tsx @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details. import { Feature, ServerSupport } from "matrix-js-sdk/src/feature"; import { MatrixClient, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix"; -import { screen } from "jest-matrix-react"; +import { screen, act } from "jest-matrix-react"; import userEvent from "@testing-library/user-event"; import { flushPromises, mkEvent, stubClient } from "../../../../test-utils"; @@ -31,12 +31,12 @@ describe("ConfirmRedactDialog", () => { }; const confirmDeleteVoiceBroadcastStartedEvent = async () => { - createRedactEventDialog({ mxEvent }); + act(() => createRedactEventDialog({ mxEvent })); // double-flush promises required for the dialog to show up await flushPromises(); await flushPromises(); - await userEvent.click(screen.getByTestId("dialog-primary-button")); + await userEvent.click(await screen.findByTestId("dialog-primary-button")); }; beforeEach(() => { diff --git a/test/unit-tests/components/views/messages/DateSeparator-test.tsx b/test/unit-tests/components/views/messages/DateSeparator-test.tsx index 4bdd66b227..25b89e05f2 100644 --- a/test/unit-tests/components/views/messages/DateSeparator-test.tsx +++ b/test/unit-tests/components/views/messages/DateSeparator-test.tsx @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details. import React from "react"; import { mocked } from "jest-mock"; -import { fireEvent, render, screen } from "jest-matrix-react"; +import { fireEvent, render, screen, waitFor } from "jest-matrix-react"; import { TimestampToEventResponse, ConnectionError, HTTPError, MatrixError } from "matrix-js-sdk/src/matrix"; import dispatcher from "../../../../../src/dispatcher/dispatcher"; @@ -291,7 +291,9 @@ describe("DateSeparator", () => { // The submit debug logs option should *NOT* be shown for network errors. // // We have to use `queryBy` so that it can return `null` for something that does not exist. - expect(screen.queryByTestId("jump-to-date-error-submit-debug-logs-button")).not.toBeInTheDocument(); + await waitFor(() => + expect(screen.queryByTestId("jump-to-date-error-submit-debug-logs-button")).not.toBeInTheDocument(), + ); }); }); }); diff --git a/test/unit-tests/components/views/rooms/wysiwyg_composer/utils/message-test.ts b/test/unit-tests/components/views/rooms/wysiwyg_composer/utils/message-test.ts index 4cabd6f666..7c77912206 100644 --- a/test/unit-tests/components/views/rooms/wysiwyg_composer/utils/message-test.ts +++ b/test/unit-tests/components/views/rooms/wysiwyg_composer/utils/message-test.ts @@ -417,7 +417,7 @@ describe("message", () => { expect(mockClient.sendMessage).toHaveBeenCalledTimes(0); expect(mockClient.cancelPendingEvent).toHaveBeenCalledTimes(1); expect(mockCreateRedactEventDialog).toHaveBeenCalledTimes(1); - expect(spyDispatcher).toHaveBeenCalledTimes(0); + expect(spyDispatcher).toHaveBeenCalledTimes(1); }); it("Should do nothing if the content is unmodified", async () => { diff --git a/test/unit-tests/components/views/settings/JoinRuleSettings-test.tsx b/test/unit-tests/components/views/settings/JoinRuleSettings-test.tsx index 55a179b1d1..36dd664ac6 100644 --- a/test/unit-tests/components/views/settings/JoinRuleSettings-test.tsx +++ b/test/unit-tests/components/views/settings/JoinRuleSettings-test.tsx @@ -202,7 +202,7 @@ describe("", () => { await flushPromises(); - expect(within(dialog).getByText("Loading new room")).toBeInTheDocument(); + await expect(within(dialog).findByText("Loading new room")).resolves.toBeInTheDocument(); // "create" our new room, have it come thru sync client.getRoom.mockImplementation((id) => { @@ -250,7 +250,7 @@ describe("", () => { await flushPromises(); - expect(within(dialog).getByText("Loading new room")).toBeInTheDocument(); + await expect(within(dialog).findByText("Loading new room")).resolves.toBeInTheDocument(); // "create" our new room, have it come thru sync client.getRoom.mockImplementation((id) => { diff --git a/test/unit-tests/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/unit-tests/components/views/settings/tabs/user/SessionManagerTab-test.tsx index 32497cb0c8..52c9d3aaa9 100644 --- a/test/unit-tests/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/unit-tests/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -119,6 +119,7 @@ describe("", () => { const mockVerificationRequest = { cancel: jest.fn(), on: jest.fn(), + off: jest.fn(), } as unknown as VerificationRequest; const mockCrypto = mocked({ diff --git a/test/unit-tests/utils/media/requestMediaPermissions-test.tsx b/test/unit-tests/utils/media/requestMediaPermissions-test.tsx index a968b51fab..14dfa15505 100644 --- a/test/unit-tests/utils/media/requestMediaPermissions-test.tsx +++ b/test/unit-tests/utils/media/requestMediaPermissions-test.tsx @@ -19,9 +19,9 @@ describe("requestMediaPermissions", () => { const audioStream = {} as MediaStream; const itShouldLogTheErrorAndShowTheNoMediaPermissionsModal = () => { - it("should log the error and show the »No media permissions« modal", () => { + it("should log the error and show the »No media permissions« modal", async () => { expect(logger.log).toHaveBeenCalledWith("Failed to list userMedia devices", error); - screen.getByText("No media permissions"); + await screen.findByText("No media permissions"); }); }; From 2631b908b628b6247c34f41246b269bf734f9d92 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Mon, 4 Nov 2024 07:46:38 -0500 Subject: [PATCH 19/38] Update the display of decryption failures due to failed trust requirement (#28300) * update the display of decryption failures due to failed trust requirement * add test for not showing shield --- .../e2e/crypto/invisible-crypto.spec.ts | 2 +- .../messages/_DecryptionFailureBody.pcss | 19 +++-------- .../views/messages/DecryptionFailureBody.tsx | 14 +++++--- src/components/views/rooms/EventTile.tsx | 10 +++++- src/i18n/strings/en_EN.json | 4 +-- .../messages/DecryptionFailureBody-test.tsx | 2 +- .../DecryptionFailureBody-test.tsx.snap | 11 ++---- .../components/views/rooms/EventTile-test.tsx | 34 ++++++++++++++++++- 8 files changed, 63 insertions(+), 33 deletions(-) diff --git a/playwright/e2e/crypto/invisible-crypto.spec.ts b/playwright/e2e/crypto/invisible-crypto.spec.ts index c53bacd32c..f207d2c6bb 100644 --- a/playwright/e2e/crypto/invisible-crypto.spec.ts +++ b/playwright/e2e/crypto/invisible-crypto.spec.ts @@ -51,6 +51,6 @@ test.describe("Invisible cryptography", () => { /* should show an error for a message from a previously verified device */ await bobSecondDevice.sendMessage(testRoomId, "test encrypted from user that was previously verified"); const lastTile = page.locator(".mx_EventTile_last"); - await expect(lastTile).toContainText("Verified identity has changed"); + await expect(lastTile).toContainText("Sender's verified identity has changed"); }); }); diff --git a/res/css/views/messages/_DecryptionFailureBody.pcss b/res/css/views/messages/_DecryptionFailureBody.pcss index 64a09be7ef..516e7bcc89 100644 --- a/res/css/views/messages/_DecryptionFailureBody.pcss +++ b/res/css/views/messages/_DecryptionFailureBody.pcss @@ -11,22 +11,11 @@ Please see LICENSE files in the repository root for full details. font-style: italic; } -/* Formatting for the "Verified identity has changed" error */ -.mx_DecryptionFailureVerifiedIdentityChanged > span { - /* Show it in red */ - color: var(--cpd-color-text-critical-primary); - background-color: var(--cpd-color-bg-critical-subtle); - - /* With a red border */ - border: 1px solid var(--cpd-color-border-critical-subtle); - border-radius: $font-16px; - - /* Some space inside the border */ - padding: var(--cpd-space-1x) var(--cpd-space-3x) var(--cpd-space-1x) var(--cpd-space-2x); - - /* some space between the (!) icon and text */ +/* Formatting for errors due to sender trust requirement failures */ +.mx_DecryptionFailureSenderTrustRequirement > span { + /* some space between the (/) icon and text */ display: inline-flex; - gap: var(--cpd-space-2x); + gap: var(--cpd-space-1x); /* Center vertically */ align-items: center; diff --git a/src/components/views/messages/DecryptionFailureBody.tsx b/src/components/views/messages/DecryptionFailureBody.tsx index 81894fa51f..108ec45b03 100644 --- a/src/components/views/messages/DecryptionFailureBody.tsx +++ b/src/components/views/messages/DecryptionFailureBody.tsx @@ -10,7 +10,7 @@ import classNames from "classnames"; import React, { forwardRef, ForwardRefExoticComponent, useContext } from "react"; import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { DecryptionFailureCode } from "matrix-js-sdk/src/crypto-api"; -import { WarningIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; +import { BlockIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { _t } from "../../../languageHandler"; import { IBodyProps } from "./IBodyProps"; @@ -41,7 +41,7 @@ function getErrorMessage(mxEvent: MatrixEvent, isVerified: boolean | undefined): case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED: return ( - + {_t("timeline|decryption_failure|sender_identity_previously_verified")} ); @@ -49,7 +49,12 @@ function getErrorMessage(mxEvent: MatrixEvent, isVerified: boolean | undefined): case DecryptionFailureCode.UNSIGNED_SENDER_DEVICE: // TODO: event should be hidden instead of showing this error. // To be revisited as part of https://github.com/element-hq/element-meta/issues/2449 - return _t("timeline|decryption_failure|sender_unsigned_device"); + return ( + + + {_t("timeline|decryption_failure|sender_unsigned_device")} + + ); } return _t("timeline|decryption_failure|unable_to_decrypt"); } @@ -58,7 +63,8 @@ function getErrorMessage(mxEvent: MatrixEvent, isVerified: boolean | undefined): function errorClassName(mxEvent: MatrixEvent): string | null { switch (mxEvent.decryptionFailureReason) { case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED: - return "mx_DecryptionFailureVerifiedIdentityChanged"; + case DecryptionFailureCode.UNSIGNED_SENDER_DEVICE: + return "mx_DecryptionFailureSenderTrustRequirement"; default: return null; diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 0b600275ef..41b4147473 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -28,6 +28,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { CallErrorCode } from "matrix-js-sdk/src/webrtc/call"; import { CryptoEvent, + DecryptionFailureCode, EventShieldColour, EventShieldReason, UserVerificationStatus, @@ -719,7 +720,14 @@ export class UnwrappedEventTile extends React.Component // event could not be decrypted if (ev.isDecryptionFailure()) { - return ; + switch (ev.decryptionFailureReason) { + // These two errors get icons from DecryptionFailureBody, so we hide the padlock icon + case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED: + case DecryptionFailureCode.UNSIGNED_SENDER_DEVICE: + return null; + default: + return ; + } } if (this.state.shieldColour !== EventShieldColour.NONE) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 6c1d8a8e0b..6e3764d582 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3266,8 +3266,8 @@ "historical_event_no_key_backup": "Historical messages are not available on this device", "historical_event_unverified_device": "You need to verify this device for access to historical messages", "historical_event_user_not_joined": "You don't have access to this message", - "sender_identity_previously_verified": "Verified identity has changed", - "sender_unsigned_device": "Encrypted by a device not verified by its owner.", + "sender_identity_previously_verified": "Sender's verified identity has changed", + "sender_unsigned_device": "Sent from an insecure device.", "unable_to_decrypt": "Unable to decrypt message" }, "disambiguated_profile": "%(displayName)s (%(matrixId)s)", diff --git a/test/unit-tests/components/views/messages/DecryptionFailureBody-test.tsx b/test/unit-tests/components/views/messages/DecryptionFailureBody-test.tsx index c92263f722..94495c4349 100644 --- a/test/unit-tests/components/views/messages/DecryptionFailureBody-test.tsx +++ b/test/unit-tests/components/views/messages/DecryptionFailureBody-test.tsx @@ -129,6 +129,6 @@ describe("DecryptionFailureBody", () => { const { container } = customRender(event); // Then - expect(container).toHaveTextContent("Encrypted by a device not verified by its owner"); + expect(container).toHaveTextContent("Sent from an insecure device"); }); }); diff --git a/test/unit-tests/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap b/test/unit-tests/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap index 67630f2031..a3b9fb205f 100644 --- a/test/unit-tests/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap +++ b/test/unit-tests/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap @@ -23,7 +23,7 @@ exports[`DecryptionFailureBody Should display "Unable to decrypt message" 1`] = exports[`DecryptionFailureBody should handle messages from users who change identities after verification 1`] = `
- - Verified identity has changed + Sender's verified identity has changed
diff --git a/test/unit-tests/components/views/rooms/EventTile-test.tsx b/test/unit-tests/components/views/rooms/EventTile-test.tsx index 7554c83ef0..b2835d15c0 100644 --- a/test/unit-tests/components/views/rooms/EventTile-test.tsx +++ b/test/unit-tests/components/views/rooms/EventTile-test.tsx @@ -19,7 +19,13 @@ import { Room, TweakName, } from "matrix-js-sdk/src/matrix"; -import { CryptoApi, EventEncryptionInfo, EventShieldColour, EventShieldReason } from "matrix-js-sdk/src/crypto-api"; +import { + CryptoApi, + DecryptionFailureCode, + EventEncryptionInfo, + EventShieldColour, + EventShieldReason, +} from "matrix-js-sdk/src/crypto-api"; import { mkEncryptedMatrixEvent } from "matrix-js-sdk/src/testing"; import EventTile, { EventTileProps } from "../../../../../src/components/views/rooms/EventTile"; @@ -350,6 +356,32 @@ describe("EventTile", () => { "mx_EventTile_e2eIcon_decryption_failure", ); }); + + it("should not show a shield for previously-verified users", async () => { + mxEvent = mkEvent({ + type: "m.room.encrypted", + room: room.roomId, + user: "@alice:example.org", + event: true, + content: {}, + }); + + const mockCrypto = { + decryptEvent: async (_ev): Promise => { + throw new Error("can't decrypt"); + }, + } as Parameters[0]; + await mxEvent.attemptDecryption(mockCrypto); + mxEvent["_decryptionFailureReason"] = DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED; + + const { container } = getComponent(); + await act(flushPromises); + + const eventTiles = container.getElementsByClassName("mx_EventTile"); + expect(eventTiles).toHaveLength(1); + + expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(0); + }); }); it("should update the warning when the event is edited", async () => { From d7d96b6b8b859c85aecf7210fa01759b554f4285 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 5 Nov 2024 06:19:43 +0000 Subject: [PATCH 20/38] [create-pull-request] automated change (#28377) Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com> --- playwright/plugins/homeserver/synapse/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index d7c3061b0a..a1c35b274c 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -20,7 +20,7 @@ import { randB64Bytes } from "../../utils/rand"; // Docker tag to use for synapse docker image. // We target a specific digest as every now and then a Synapse update will break our CI. // This digest is updated by the playwright-image-updates.yaml workflow periodically. -const DOCKER_TAG = "develop@sha256:46a04410bafb6db707a5083d7377adbb84144c93dd917b276ae1913b5459e908"; +const DOCKER_TAG = "develop@sha256:df06607d21965639cb7dd72724fd610731c13bed95d3334746f53668a36c6cda"; async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); From c9d9c421bc7e3f2a9d5d5ed05679cb3e8e06a388 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 5 Nov 2024 11:04:14 +0000 Subject: [PATCH 21/38] Move navigator message listener registration to be synchronously attached (#28340) to silence Chrome warning Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/vector/platform/WebPlatform.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vector/platform/WebPlatform.ts b/src/vector/platform/WebPlatform.ts index 53ff14d82f..0bbc7a0a5a 100644 --- a/src/vector/platform/WebPlatform.ts +++ b/src/vector/platform/WebPlatform.ts @@ -54,8 +54,8 @@ export default class WebPlatform extends VectorBasePlatform { return; } - await registration.update(); navigator.serviceWorker.addEventListener("message", this.onServiceWorkerPostMessage.bind(this)); + await registration.update(); } private onServiceWorkerPostMessage(event: MessageEvent): void { From aeabf3b18896ac1eb7ae9757e66ce886120f8309 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 5 Nov 2024 11:34:42 +0000 Subject: [PATCH 22/38] Show message type prefix in thread root & reply previews (#28361) * Extract EventPreview from PinnedMessageBanner Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Show message type prefix in thread root previews Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Show message type prefix in thread reply preview Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/_components.pcss | 1 + res/css/views/rooms/_EventPreview.pcss | 18 +++ res/css/views/rooms/_PinnedMessageBanner.pcss | 8 - src/components/views/rooms/EventPreview.tsx | 138 ++++++++++++++++++ src/components/views/rooms/EventTile.tsx | 4 +- .../views/rooms/PinnedMessageBanner.tsx | 90 +----------- src/components/views/rooms/ThreadSummary.tsx | 35 +---- src/i18n/strings/en_EN.json | 18 +-- test/test-utils/threads.ts | 2 +- .../views/rooms/PinnedMessageBanner-test.tsx | 23 +-- .../PinnedMessageBanner-test.tsx.snap | 32 ++-- 11 files changed, 215 insertions(+), 154 deletions(-) create mode 100644 res/css/views/rooms/_EventPreview.pcss create mode 100644 src/components/views/rooms/EventPreview.tsx diff --git a/res/css/_components.pcss b/res/css/_components.pcss index f52cdbdbdd..12239fac2d 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -282,6 +282,7 @@ @import "./views/rooms/_EmojiButton.pcss"; @import "./views/rooms/_EntityTile.pcss"; @import "./views/rooms/_EventBubbleTile.pcss"; +@import "./views/rooms/_EventPreview.pcss"; @import "./views/rooms/_EventTile.pcss"; @import "./views/rooms/_HistoryTile.pcss"; @import "./views/rooms/_IRCLayout.pcss"; diff --git a/res/css/views/rooms/_EventPreview.pcss b/res/css/views/rooms/_EventPreview.pcss new file mode 100644 index 0000000000..0639c76d98 --- /dev/null +++ b/res/css/views/rooms/_EventPreview.pcss @@ -0,0 +1,18 @@ +/* +* Copyright 2024 New Vector Ltd. +* Copyright 2024 The Matrix.org Foundation C.I.C. +* +* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only +* Please see LICENSE files in the repository root for full details. + */ + +.mx_EventPreview { + font: var(--cpd-font-body-sm-regular); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + .mx_EventPreview_prefix { + font: var(--cpd-font-body-sm-semibold); + } +} diff --git a/res/css/views/rooms/_PinnedMessageBanner.pcss b/res/css/views/rooms/_PinnedMessageBanner.pcss index dd753b7c9e..27c7971833 100644 --- a/res/css/views/rooms/_PinnedMessageBanner.pcss +++ b/res/css/views/rooms/_PinnedMessageBanner.pcss @@ -81,15 +81,7 @@ .mx_PinnedMessageBanner_message { grid-area: message; - font: var(--cpd-font-body-sm-regular); line-height: 20px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - - .mx_PinnedMessageBanner_prefix { - font: var(--cpd-font-body-sm-semibold); - } } .mx_PinnedMessageBanner_redactedMessage { diff --git a/src/components/views/rooms/EventPreview.tsx b/src/components/views/rooms/EventPreview.tsx new file mode 100644 index 0000000000..e02c2f152f --- /dev/null +++ b/src/components/views/rooms/EventPreview.tsx @@ -0,0 +1,138 @@ +/* + * Copyright 2024 New Vector Ltd. + * Copyright 2024 The Matrix.org Foundation C.I.C. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only + * Please see LICENSE files in the repository root for full details. + */ + +import React, { HTMLProps, JSX, useContext, useState } from "react"; +import { IContent, M_POLL_START, MatrixEvent, MatrixEventEvent, MsgType } from "matrix-js-sdk/src/matrix"; +import classNames from "classnames"; + +import { _t } from "../../../languageHandler"; +import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; +import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; +import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import { useTypedEventEmitter } from "../../../hooks/useEventEmitter.ts"; + +/** + * The props for the {@link EventPreview} component. + */ +interface Props extends HTMLProps { + /** + * The event to display the preview for + */ + mxEvent: MatrixEvent; +} + +/** + * A component that displays a preview for the given event. + * Wraps both `useEventPreview` & `EventPreviewTile`. + */ +export function EventPreview({ mxEvent, className, ...props }: Props): JSX.Element | null { + const preview = useEventPreview(mxEvent); + if (!preview) return null; + + return ; +} + +/** + * The props for the {@link EventPreviewTile} component. + */ +interface EventPreviewTileProps extends HTMLProps { + /** + * The preview to display + */ + preview: Preview; +} + +/** + * A component that displays a preview given the output from `useEventPreview`. + */ +export function EventPreviewTile({ + preview: [preview, prefix], + className, + ...props +}: EventPreviewTileProps): JSX.Element | null { + const classes = classNames("mx_EventPreview", className); + if (!prefix) + return ( + + {preview} + + ); + + return ( + + {_t( + "event_preview|preview", + { + prefix, + preview, + }, + { + bold: (sub) => {sub}, + }, + )} + + ); +} + +type Preview = [preview: string, prefix: string | null]; + +/** + * Hooks to generate a preview for the event. + * @param mxEvent + */ +export function useEventPreview(mxEvent: MatrixEvent | undefined): Preview | null { + const cli = useContext(MatrixClientContext); + // track the content as a means to regenerate the preview upon edits & decryption + const [content, setContent] = useState(mxEvent?.getContent()); + useTypedEventEmitter(mxEvent ?? undefined, MatrixEventEvent.Replaced, () => { + setContent(mxEvent!.getContent()); + }); + const awaitDecryption = mxEvent?.shouldAttemptDecryption() || mxEvent?.isBeingDecrypted(); + useTypedEventEmitter(awaitDecryption ? (mxEvent ?? undefined) : undefined, MatrixEventEvent.Decrypted, () => { + setContent(mxEvent!.getContent()); + }); + + return useAsyncMemo( + async () => { + if (!mxEvent || mxEvent.isRedacted() || mxEvent.isDecryptionFailure()) return null; + await cli.decryptEventIfNeeded(mxEvent); + return [ + MessagePreviewStore.instance.generatePreviewForEvent(mxEvent), + getPreviewPrefix(mxEvent.getType(), content?.msgtype as MsgType), + ]; + }, + [mxEvent, content], + null, + ); +} + +/** + * Get the prefix for the preview based on the type and the message type. + * @param type + * @param msgType + */ +function getPreviewPrefix(type: string, msgType: MsgType): string | null { + switch (type) { + case M_POLL_START.name: + return _t("event_preview|prefix|poll"); + default: + } + + switch (msgType) { + case MsgType.Audio: + return _t("event_preview|prefix|audio"); + case MsgType.Image: + return _t("event_preview|prefix|image"); + case MsgType.Video: + return _t("event_preview|prefix|video"); + case MsgType.File: + return _t("event_preview|prefix|file"); + default: + return null; + } +} diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 41b4147473..22da73bef7 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -61,7 +61,6 @@ import { IReadReceiptPosition } from "./ReadReceiptMarker"; import MessageActionBar from "../messages/MessageActionBar"; import ReactionsRow from "../messages/ReactionsRow"; import { getEventDisplayInfo } from "../../../utils/EventRenderingUtils"; -import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import { MediaEventHelper } from "../../../utils/MediaEventHelper"; import { ButtonEvent } from "../elements/AccessibleButton"; @@ -83,6 +82,7 @@ import { EventTileThreadToolbar } from "./EventTile/EventTileThreadToolbar"; import { getLateEventInfo } from "../../structures/grouper/LateEventGrouper"; import PinningUtils from "../../../utils/PinningUtils"; import { PinnedMessageBadge } from "../messages/PinnedMessageBadge"; +import { EventPreview } from "./EventPreview"; export type GetRelationsForEvent = ( eventId: string, @@ -1341,7 +1341,7 @@ export class UnwrappedEventTile extends React.Component ) : this.props.mxEvent.isDecryptionFailure() ? ( ) : ( - MessagePreviewStore.instance.generatePreviewForEvent(this.props.mxEvent) + )}
{this.renderThreadPanelSummary()} diff --git a/src/components/views/rooms/PinnedMessageBanner.tsx b/src/components/views/rooms/PinnedMessageBanner.tsx index 6360870dbf..f44b4417c9 100644 --- a/src/components/views/rooms/PinnedMessageBanner.tsx +++ b/src/components/views/rooms/PinnedMessageBanner.tsx @@ -6,10 +6,10 @@ * Please see LICENSE files in the repository root for full details. */ -import React, { JSX, useEffect, useMemo, useState } from "react"; +import React, { JSX, useEffect, useState } from "react"; import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin-solid"; import { Button } from "@vector-im/compound-web"; -import { M_POLL_START, MatrixEvent, MsgType, Room } from "matrix-js-sdk/src/matrix"; +import { Room } from "matrix-js-sdk/src/matrix"; import classNames from "classnames"; import { usePinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents"; @@ -19,12 +19,12 @@ import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePha import { useEventEmitter } from "../../../hooks/useEventEmitter"; import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; -import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; import dis from "../../../dispatcher/dispatcher"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { Action } from "../../../dispatcher/actions"; import MessageEvent from "../messages/MessageEvent"; import PosthogTrackers from "../../../PosthogTrackers.ts"; +import { EventPreview } from "./EventPreview.tsx"; /** * The props for the {@link PinnedMessageBanner} component. @@ -105,7 +105,11 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan )} )} - + {/* In case of redacted event, we want to display the nice sentence of the message event like in the timeline or in the pinned message list */} {shouldUseMessageEvent && (
@@ -124,84 +128,6 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan ); } -/** - * The props for the {@link EventPreview} component. - */ -interface EventPreviewProps { - /** - * The pinned event to display the preview for - */ - pinnedEvent: MatrixEvent; -} - -/** - * A component that displays a preview for the pinned event. - */ -function EventPreview({ pinnedEvent }: EventPreviewProps): JSX.Element | null { - const preview = useEventPreview(pinnedEvent); - if (!preview) return null; - - const prefix = getPreviewPrefix(pinnedEvent.getType(), pinnedEvent.getContent().msgtype as MsgType); - if (!prefix) - return ( - - {preview} - - ); - - return ( - - {_t( - "room|pinned_message_banner|preview", - { - prefix, - preview, - }, - { - bold: (sub) => {sub}, - }, - )} - - ); -} - -/** - * Hooks to generate a preview for the pinned event. - * @param pinnedEvent - */ -function useEventPreview(pinnedEvent: MatrixEvent | null): string | null { - return useMemo(() => { - if (!pinnedEvent || pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure()) return null; - return MessagePreviewStore.instance.generatePreviewForEvent(pinnedEvent); - }, [pinnedEvent]); -} - -/** - * Get the prefix for the preview based on the type and the message type. - * @param type - * @param msgType - */ -function getPreviewPrefix(type: string, msgType: MsgType): string | null { - switch (type) { - case M_POLL_START.name: - return _t("room|pinned_message_banner|prefix|poll"); - default: - } - - switch (msgType) { - case MsgType.Audio: - return _t("room|pinned_message_banner|prefix|audio"); - case MsgType.Image: - return _t("room|pinned_message_banner|prefix|image"); - case MsgType.Video: - return _t("room|pinned_message_banner|prefix|video"); - case MsgType.File: - return _t("room|pinned_message_banner|prefix|file"); - default: - return null; - } -} - const MAX_INDICATORS = 3; /** diff --git a/src/components/views/rooms/ThreadSummary.tsx b/src/components/views/rooms/ThreadSummary.tsx index ea76dd0d36..4a3032d641 100644 --- a/src/components/views/rooms/ThreadSummary.tsx +++ b/src/components/views/rooms/ThreadSummary.tsx @@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ -import React, { useContext, useState } from "react"; -import { Thread, ThreadEvent, IContent, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix"; +import React, { useContext } from "react"; +import { Thread, ThreadEvent, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { IndicatorIcon } from "@vector-im/compound-web"; import ThreadIconSolid from "@vector-im/compound-design-tokens/assets/web/icons/threads-solid"; @@ -15,17 +15,15 @@ import { _t } from "../../../languageHandler"; import { CardContext } from "../right_panel/context"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import PosthogTrackers from "../../../PosthogTrackers"; -import { useTypedEventEmitter, useTypedEventEmitterState } from "../../../hooks/useEventEmitter"; +import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter"; import RoomContext from "../../../contexts/RoomContext"; -import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; import MemberAvatar from "../avatars/MemberAvatar"; -import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { Action } from "../../../dispatcher/actions"; import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import { useUnreadNotifications } from "../../../hooks/useUnreadNotifications"; import { notificationLevelToIndicator } from "../../../utils/notifications"; +import { EventPreviewTile, useEventPreview } from "./EventPreview.tsx"; interface IProps { mxEvent: MatrixEvent; @@ -75,24 +73,9 @@ interface IPreviewProps { } export const ThreadMessagePreview: React.FC = ({ thread, showDisplayname = false }) => { - const cli = useContext(MatrixClientContext); - const lastReply = useTypedEventEmitterState(thread, ThreadEvent.Update, () => thread.replyToEvent) ?? undefined; - // track the content as a means to regenerate the thread message preview upon edits & decryption - const [content, setContent] = useState(lastReply?.getContent()); - useTypedEventEmitter(lastReply, MatrixEventEvent.Replaced, () => { - setContent(lastReply!.getContent()); - }); - const awaitDecryption = lastReply?.shouldAttemptDecryption() || lastReply?.isBeingDecrypted(); - useTypedEventEmitter(awaitDecryption ? lastReply : undefined, MatrixEventEvent.Decrypted, () => { - setContent(lastReply!.getContent()); - }); + const preview = useEventPreview(lastReply); - const preview = useAsyncMemo(async (): Promise => { - if (!lastReply) return; - await cli.decryptEventIfNeeded(lastReply); - return MessagePreviewStore.instance.generatePreviewForEvent(lastReply); - }, [lastReply, content]); if (!preview || !lastReply) { return null; } @@ -114,14 +97,10 @@ export const ThreadMessagePreview: React.FC = ({ thread, showDisp className="mx_ThreadSummary_content mx_DecryptionFailureBody" title={_t("timeline|decryption_failure|unable_to_decrypt")} > - - {_t("timeline|decryption_failure|unable_to_decrypt")} - + {_t("timeline|decryption_failure|unable_to_decrypt")}
) : ( -
- {preview} -
+ )} ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 6e3764d582..1ad73fff8a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1110,7 +1110,15 @@ "you": "You reacted %(reaction)s to %(message)s" }, "m.sticker": "%(senderName)s: %(stickerName)s", - "m.text": "%(senderName)s: %(message)s" + "m.text": "%(senderName)s: %(message)s", + "prefix": { + "audio": "Audio", + "file": "File", + "image": "Image", + "poll": "Poll", + "video": "Video" + }, + "preview": "%(prefix)s: %(preview)s" }, "export_chat": { "cancelled": "Export Cancelled", @@ -2037,14 +2045,6 @@ "button_view_all": "View all", "description": "This room has pinned messages. Click to view them.", "go_to_message": "View the pinned message in the timeline.", - "prefix": { - "audio": "Audio", - "file": "File", - "image": "Image", - "poll": "Poll", - "video": "Video" - }, - "preview": "%(prefix)s: %(preview)s", "title": "%(index)s of %(length)s Pinned messages" }, "read_topic": "Click to read topic", diff --git a/test/test-utils/threads.ts b/test/test-utils/threads.ts index 1b9e7f0a18..d2459653e5 100644 --- a/test/test-utils/threads.ts +++ b/test/test-utils/threads.ts @@ -84,7 +84,7 @@ export const makeThreadEvents = ({ rootEvent.setUnsigned({ "m.relations": { [RelationType.Thread]: { - latest_event: events[events.length - 1], + latest_event: events[events.length - 1].event, count: length, current_user_participated: [...participantUserIds, authorId].includes(currentUserId!), }, diff --git a/test/unit-tests/components/views/rooms/PinnedMessageBanner-test.tsx b/test/unit-tests/components/views/rooms/PinnedMessageBanner-test.tsx index b2dd4f4c7c..f59b9f3a9f 100644 --- a/test/unit-tests/components/views/rooms/PinnedMessageBanner-test.tsx +++ b/test/unit-tests/components/views/rooms/PinnedMessageBanner-test.tsx @@ -14,7 +14,7 @@ import userEvent from "@testing-library/user-event"; import * as pinnedEventHooks from "../../../../../src/hooks/usePinnedEvents"; import { PinnedMessageBanner } from "../../../../../src/components/views/rooms/PinnedMessageBanner"; import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks"; -import { makePollStartEvent, stubClient } from "../../../../test-utils"; +import { makePollStartEvent, stubClient, withClientContextRenderOptions } from "../../../../test-utils"; import dis from "../../../../../src/dispatcher/dispatcher"; import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore"; import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases"; @@ -76,7 +76,10 @@ describe("", () => { * Render the banner */ function renderBanner() { - return render(); + return render( + , + withClientContextRenderOptions(mockClient), + ); } it("should render nothing when there are no pinned events", async () => { @@ -92,7 +95,7 @@ describe("", () => { const { asFragment } = renderBanner(); - expect(screen.getByText("First pinned message")).toBeVisible(); + await expect(screen.findByText("First pinned message")).resolves.toBeVisible(); expect(screen.queryByRole("button", { name: "View all" })).toBeNull(); expect(asFragment()).toMatchSnapshot(); }); @@ -103,7 +106,7 @@ describe("", () => { const { asFragment } = renderBanner(); - expect(screen.getByText("Second pinned message")).toBeVisible(); + await expect(screen.findByText("Second pinned message")).resolves.toBeVisible(); expect(screen.getByTestId("banner-counter")).toHaveTextContent("2 of 2 Pinned messages"); expect(screen.getAllByTestId("banner-indicator")).toHaveLength(2); expect(screen.queryByRole("button", { name: "View all" })).toBeVisible(); @@ -121,7 +124,7 @@ describe("", () => { const { asFragment } = renderBanner(); - expect(screen.getByText("Fourth pinned message")).toBeVisible(); + await expect(screen.findByText("Fourth pinned message")).resolves.toBeVisible(); expect(screen.getByTestId("banner-counter")).toHaveTextContent("4 of 4 Pinned messages"); expect(screen.getAllByTestId("banner-indicator")).toHaveLength(3); expect(screen.queryByRole("button", { name: "View all" })).toBeVisible(); @@ -143,7 +146,7 @@ describe("", () => { ]); jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2, event3]); rerender(); - expect(screen.getByText("Third pinned message")).toBeVisible(); + await expect(screen.findByText("Third pinned message")).resolves.toBeVisible(); expect(asFragment()).toMatchSnapshot(); }); @@ -152,7 +155,7 @@ describe("", () => { jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2]); renderBanner(); - expect(screen.getByText("Second pinned message")).toBeVisible(); + await expect(screen.findByText("Second pinned message")).resolves.toBeVisible(); await userEvent.click(screen.getByRole("button", { name: "View the pinned message in the timeline." })); expect(screen.getByText("First pinned message")).toBeVisible(); @@ -182,14 +185,14 @@ describe("", () => { ["m.audio", "Audio"], ["m.video", "Video"], ["m.image", "Image"], - ])("should display the %s event type", (msgType, label) => { + ])("should display the %s event type", async (msgType, label) => { const body = `Message with ${msgType} type`; const event = makePinEvent({ content: { body, msgtype: msgType } }); jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event.getId()!]); jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event]); const { asFragment } = renderBanner(); - expect(screen.getByTestId("banner-message")).toHaveTextContent(`${label}: ${body}`); + await expect(screen.findByTestId("banner-message")).resolves.toHaveTextContent(`${label}: ${body}`); expect(asFragment()).toMatchSnapshot(); }); @@ -199,7 +202,7 @@ describe("", () => { jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event]); const { asFragment } = renderBanner(); - expect(screen.getByTestId("banner-message")).toHaveTextContent("Poll: Alice?"); + await expect(screen.findByTestId("banner-message")).resolves.toHaveTextContent("Poll: Alice?"); expect(asFragment()).toMatchSnapshot(); }); diff --git a/test/unit-tests/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap b/test/unit-tests/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap index 53e5f2e2e8..9fdc1404d0 100644 --- a/test/unit-tests/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap @@ -37,12 +37,12 @@ exports[` should display display a poll event 1`] = ` /> Poll: @@ -113,8 +113,9 @@ exports[` should display the last message when the pinned Third pinned message @@ -170,12 +171,12 @@ exports[` should display the m.audio event type 1`] = ` /> Audio: @@ -225,12 +226,12 @@ exports[` should display the m.file event type 1`] = ` /> File: @@ -280,12 +281,12 @@ exports[` should display the m.image event type 1`] = ` /> Image: @@ -335,12 +336,12 @@ exports[` should display the m.video event type 1`] = ` /> Video: @@ -407,8 +408,9 @@ exports[` should render 2 pinned event 1`] = ` Second pinned message @@ -485,8 +487,9 @@ exports[` should render 4 pinned event 1`] = ` Fourth pinned message @@ -542,8 +545,9 @@ exports[` should render a single pinned event 1`] = ` /> First pinned message From 6e4bd564d51a40a1844111dae5458d9bf560fd58 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 5 Nov 2024 13:50:59 +0000 Subject: [PATCH 23/38] Upgrade dependency to matrix-js-sdk@34.10.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index b2758b7b2d..58516eee9c 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "maplibre-gl": "^2.0.0", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", - "matrix-js-sdk": "34.10.0-rc.0", + "matrix-js-sdk": "34.10.0", "matrix-widget-api": "^1.9.0", "memoize-one": "^6.0.0", "oidc-client-ts": "^3.0.1", diff --git a/yarn.lock b/yarn.lock index 08be132eef..3e1c409a24 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8250,10 +8250,10 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -matrix-js-sdk@34.10.0-rc.0: - version "34.10.0-rc.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-34.10.0-rc.0.tgz#5f395d4bfd89fac1dc7517fcffe24903b591d236" - integrity sha512-gIZB5TH4F9hXrAL96LSE0EpAnvH//SV/hnb7/bAFJBKWtjVvZs0sgRFsn/qJmTKQ+fpu6+uucPrQD2hwdrkYBA== +matrix-js-sdk@34.10.0: + version "34.10.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-34.10.0.tgz#39d2b4a10ee9a05dc5b40553e317b65b2b5a5efd" + integrity sha512-+X9euFC1BeDIX06ckPeYD4Bs1yIGzETTSqVsktjZG6FOx6kr8CGIenmcCe4majq0Kj9tpv+p/NFenh59aPdh8Q== dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-wasm" "^9.0.0" From 15984455af25927c21a6734df5de8f59bf9eb972 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 5 Nov 2024 13:55:39 +0000 Subject: [PATCH 24/38] v1.11.84 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 828c352228..1274048c8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +Changes in [1.11.84](https://github.com/element-hq/element-web/releases/tag/v1.11.84) (2024-11-05) +================================================================================================== +## ✹ Features + +* Remove abandoned MSC3886, MSC3903, MSC3906 implementations ([#28274](https://github.com/element-hq/element-web/pull/28274)). Contributed by @t3chguy. +* Update to React 18 ([#24763](https://github.com/element-hq/element-web/pull/24763)). Contributed by @t3chguy. +* Deduplicate icons using Compound ([#28239](https://github.com/element-hq/element-web/pull/28239)). Contributed by @t3chguy. +* Replace legacy Tooltips with Compound tooltips ([#28231](https://github.com/element-hq/element-web/pull/28231)). Contributed by @t3chguy. +* Deduplicate icons using Compound Design Tokens ([#28219](https://github.com/element-hq/element-web/pull/28219)). Contributed by @t3chguy. +* Add reactions to html export ([#28210](https://github.com/element-hq/element-web/pull/28210)). Contributed by @langleyd. +* Remove feature\_dehydration ([#28173](https://github.com/element-hq/element-web/pull/28173)). Contributed by @florianduros. + +## 🐛 Bug Fixes + +* Remove upgrade encryption in `DeviceListener` and `SetupEncryptionToast` ([#28299](https://github.com/element-hq/element-web/pull/28299)). Contributed by @florianduros. +* Fix 'remove alias' button in room settings ([#28269](https://github.com/element-hq/element-web/pull/28269)). Contributed by @Dev-Gurjar. +* Add back unencrypted path in `StopGapWidgetDriver.sendToDevice` ([#28295](https://github.com/element-hq/element-web/pull/28295)). Contributed by @florianduros. +* Fix other devices not being decorated as such ([#28279](https://github.com/element-hq/element-web/pull/28279)). Contributed by @t3chguy. +* Fix pill contrast in invitation dialog ([#28250](https://github.com/element-hq/element-web/pull/28250)). Contributed by @florianduros. +* Close right panel chat when minimising maximised voip widget ([#28241](https://github.com/element-hq/element-web/pull/28241)). Contributed by @t3chguy. +* Fix develop changelog parsing ([#28232](https://github.com/element-hq/element-web/pull/28232)). Contributed by @t3chguy. +* Fix Ctrl+F shortcut not working with minimised room summary card ([#28223](https://github.com/element-hq/element-web/pull/28223)). Contributed by @t3chguy. +* Fix network dropdown missing checkbox \& aria-checked ([#28220](https://github.com/element-hq/element-web/pull/28220)). Contributed by @t3chguy. + + Changes in [1.11.83](https://github.com/element-hq/element-web/releases/tag/v1.11.83) (2024-10-29) ================================================================================================== ## ✹ Features diff --git a/package.json b/package.json index 58516eee9c..65ee24b6f0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "element-web", - "version": "1.11.84-rc.0", + "version": "1.11.84", "description": "A feature-rich client for Matrix.org", "author": "New Vector Ltd.", "repository": { From 0b6ed443903640a15ce77799f6d830fff9a63e21 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 5 Nov 2024 13:59:24 +0000 Subject: [PATCH 25/38] Reset matrix-js-sdk back to develop branch --- package.json | 2 +- yarn.lock | 20 +++++++------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 28f18cc602..cbb8a33480 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "maplibre-gl": "^2.0.0", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", - "matrix-js-sdk": "34.10.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^1.9.0", "memoize-one": "^6.0.0", "oidc-client-ts": "^3.0.1", diff --git a/yarn.lock b/yarn.lock index d051bd7489..343a0f3ef4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8307,10 +8307,9 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -matrix-js-sdk@34.10.0: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "34.10.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-34.10.0.tgz#39d2b4a10ee9a05dc5b40553e317b65b2b5a5efd" - integrity sha512-+X9euFC1BeDIX06ckPeYD4Bs1yIGzETTSqVsktjZG6FOx6kr8CGIenmcCe4majq0Kj9tpv+p/NFenh59aPdh8Q== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/5a1488ebd5552817b1e95265afe7b3baac1231a2" dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-wasm" "^9.0.0" @@ -8326,7 +8325,7 @@ matrix-js-sdk@34.10.0: p-retry "4" sdp-transform "^2.14.1" unhomoglyph "^1.0.6" - uuid "10" + uuid "11" matrix-mock-request@^2.5.0: version "2.6.0" @@ -11702,21 +11701,16 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@10: - version "10.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" - integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== +uuid@11, uuid@^11.0.0: + version "11.0.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.2.tgz#a8d68ba7347d051e7ea716cc8dcbbab634d66875" + integrity sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ== uuid@8.3.2, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -uuid@^11.0.0: - version "11.0.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.2.tgz#a8d68ba7347d051e7ea716cc8dcbbab634d66875" - integrity sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ== - uuid@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" From 24fabfff8933194cf80cf9ea182ed487957718fb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 5 Nov 2024 14:02:25 +0000 Subject: [PATCH 26/38] Fix post release docker check Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0e8c21e786..3a9c29e197 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,7 +49,7 @@ jobs: ref: master repo-token: ${{ secrets.GITHUB_TOKEN }} wait-interval: 10 - check-name: "Docker Buildx (vanilla)" + check-name: "Docker Buildx" allowed-conclusions: success - name: Wait for debian package From 9d79a934bfc85bf76315bfb25deeae09fdd84f28 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 5 Nov 2024 15:41:00 +0000 Subject: [PATCH 27/38] Flatten Vector-override components (#28346) * Flatten Vector-override components Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Improve coverage Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Ie Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- components.json | 6 +- src/BasePlatform.ts | 44 ++- src/components/views/auth/AuthFooter.tsx | 38 ++- src/components/views/auth/AuthHeaderLogo.tsx | 14 +- src/components/views/auth/AuthPage.tsx | 64 +++- .../views/auth/VectorAuthFooter.tsx | 41 --- .../views/auth/VectorAuthHeaderLogo.tsx | 24 -- src/components/views/auth/VectorAuthPage.tsx | 75 ----- src/i18n/strings/en_EN.json | 2 - src/vector/app.tsx | 3 +- src/vector/platform/ElectronPlatform.tsx | 5 +- src/vector/platform/VectorBasePlatform.ts | 80 ----- src/vector/platform/WebPlatform.ts | 5 +- .../components/structures/MatrixChat-test.tsx | 6 +- .../__snapshots__/MatrixChat-test.tsx.snap | 305 +++++++++++------- ...rAuthPage-test.tsx => AuthFooter-test.tsx} | 6 +- ...rLogo-test.tsx => AuthHeaderLogo-test.tsx} | 6 +- .../components/views/auth/AuthPage-test.tsx | 36 +++ .../views/auth/VectorAuthFooter-test.tsx | 24 -- ...test.tsx.snap => AuthFooter-test.tsx.snap} | 2 +- ....tsx.snap => AuthHeaderLogo-test.tsx.snap} | 2 +- ...e-test.tsx.snap => AuthPage-test.tsx.snap} | 2 +- .../vector/platform/WebPlatform-test.ts | 14 + 23 files changed, 395 insertions(+), 409 deletions(-) delete mode 100644 src/components/views/auth/VectorAuthFooter.tsx delete mode 100644 src/components/views/auth/VectorAuthHeaderLogo.tsx delete mode 100644 src/components/views/auth/VectorAuthPage.tsx delete mode 100644 src/vector/platform/VectorBasePlatform.ts rename test/unit-tests/components/views/auth/{VectorAuthPage-test.tsx => AuthFooter-test.tsx} (73%) rename test/unit-tests/components/views/auth/{VectorAuthHeaderLogo-test.tsx => AuthHeaderLogo-test.tsx} (64%) create mode 100644 test/unit-tests/components/views/auth/AuthPage-test.tsx delete mode 100644 test/unit-tests/components/views/auth/VectorAuthFooter-test.tsx rename test/unit-tests/components/views/auth/__snapshots__/{VectorAuthFooter-test.tsx.snap => AuthFooter-test.tsx.snap} (92%) rename test/unit-tests/components/views/auth/__snapshots__/{VectorAuthHeaderLogo-test.tsx.snap => AuthHeaderLogo-test.tsx.snap} (78%) rename test/unit-tests/components/views/auth/__snapshots__/{VectorAuthPage-test.tsx.snap => AuthPage-test.tsx.snap} (95%) diff --git a/components.json b/components.json index cc5046ed69..0967ef424b 100644 --- a/components.json +++ b/components.json @@ -1,5 +1 @@ -{ - "src/components/views/auth/AuthFooter.tsx": "src/components/views/auth/VectorAuthFooter.tsx", - "src/components/views/auth/AuthHeaderLogo.tsx": "src/components/views/auth/VectorAuthHeaderLogo.tsx", - "src/components/views/auth/AuthPage.tsx": "src/components/views/auth/VectorAuthPage.tsx" -} +{} diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 8bbad339c7..0017d00dac 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -31,6 +31,8 @@ import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload"; import { IConfigOptions } from "./IConfigOptions"; import SdkConfig from "./SdkConfig"; import { buildAndEncodePickleKey, encryptPickleKey } from "./utils/tokens/pickling"; +import Favicon from "./favicon.ts"; +import { getVectorConfig } from "./vector/getconfig.ts"; export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url"; export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url"; @@ -66,14 +68,20 @@ const UPDATE_DEFER_KEY = "mx_defer_update"; export default abstract class BasePlatform { protected notificationCount = 0; protected errorDidOccur = false; + protected _favicon?: Favicon; protected constructor() { dis.register(this.onAction); this.startUpdateCheck = this.startUpdateCheck.bind(this); } - public abstract getConfig(): Promise; + public async getConfig(): Promise { + return getVectorConfig(); + } + /** + * Get a sensible default display name for the device Element is running on + */ public abstract getDefaultDeviceDisplayName(): string; protected onAction = (payload: ActionPayload): void => { @@ -89,11 +97,15 @@ export default abstract class BasePlatform { public abstract getHumanReadableName(): string; public setNotificationCount(count: number): void { + if (this.notificationCount === count) return; this.notificationCount = count; + this.updateFavicon(); } public setErrorStatus(errorDidOccur: boolean): void { + if (this.errorDidOccur === errorDidOccur) return; this.errorDidOccur = errorDidOccur; + this.updateFavicon(); } /** @@ -456,4 +468,34 @@ export default abstract class BasePlatform { url.hash = ""; return url; } + + /** + * Delay creating the `Favicon` instance until first use (on the first notification) as + * it uses canvas, which can trigger a permission prompt in Firefox's resist fingerprinting mode. + * See https://github.com/element-hq/element-web/issues/9605. + */ + public get favicon(): Favicon { + if (this._favicon) { + return this._favicon; + } + this._favicon = new Favicon(); + return this._favicon; + } + + private updateFavicon(): void { + let bgColor = "#d00"; + let notif: string | number = this.notificationCount; + + if (this.errorDidOccur) { + notif = notif || "×"; + bgColor = "#f00"; + } + + this.favicon.badge(notif, { bgColor }); + } + + /** + * Begin update polling, if applicable + */ + public startUpdater(): void {} } diff --git a/src/components/views/auth/AuthFooter.tsx b/src/components/views/auth/AuthFooter.tsx index c81617b9db..8d27a04c83 100644 --- a/src/components/views/auth/AuthFooter.tsx +++ b/src/components/views/auth/AuthFooter.tsx @@ -7,18 +7,36 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ -import React from "react"; +import React, { ReactElement } from "react"; +import SdkConfig from "../../../SdkConfig"; import { _t } from "../../../languageHandler"; -export default class AuthFooter extends React.Component { - public render(): React.ReactNode { - return ( - +const AuthFooter = (): ReactElement => { + const brandingConfig = SdkConfig.getObject("branding"); + const links = brandingConfig?.get("auth_footer_links") ?? [ + { text: "Blog", url: "https://element.io/blog" }, + { text: "Twitter", url: "https://twitter.com/element_hq" }, + { text: "GitHub", url: "https://github.com/element-hq/element-web" }, + ]; + + const authFooterLinks: JSX.Element[] = []; + for (const linkEntry of links) { + authFooterLinks.push( + + {linkEntry.text} + , ); } -} + + return ( + + ); +}; + +export default AuthFooter; diff --git a/src/components/views/auth/AuthHeaderLogo.tsx b/src/components/views/auth/AuthHeaderLogo.tsx index 3ff11ba3f2..07cc2f978a 100644 --- a/src/components/views/auth/AuthHeaderLogo.tsx +++ b/src/components/views/auth/AuthHeaderLogo.tsx @@ -1,5 +1,6 @@ /* Copyright 2019-2024 New Vector Ltd. +Copyright 2015, 2016 OpenMarket Ltd SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. @@ -7,8 +8,17 @@ Please see LICENSE files in the repository root for full details. import React from "react"; +import SdkConfig from "../../../SdkConfig"; + export default class AuthHeaderLogo extends React.PureComponent { - public render(): React.ReactNode { - return ; + public render(): React.ReactElement { + const brandingConfig = SdkConfig.getObject("branding"); + const logoUrl = brandingConfig?.get("auth_header_logo_url") ?? "themes/element/img/logos/element-logo.svg"; + + return ( + + ); } } diff --git a/src/components/views/auth/AuthPage.tsx b/src/components/views/auth/AuthPage.tsx index e9beb6d2a0..2782d0a641 100644 --- a/src/components/views/auth/AuthPage.tsx +++ b/src/components/views/auth/AuthPage.tsx @@ -7,15 +7,69 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ -import React, { ReactNode } from "react"; +import React from "react"; +import SdkConfig from "../../../SdkConfig"; import AuthFooter from "./AuthFooter"; -export default class AuthPage extends React.PureComponent<{ children: ReactNode }> { - public render(): React.ReactNode { +export default class AuthPage extends React.PureComponent { + private static welcomeBackgroundUrl?: string; + + // cache the url as a static to prevent it changing without refreshing + private static getWelcomeBackgroundUrl(): string { + if (AuthPage.welcomeBackgroundUrl) return AuthPage.welcomeBackgroundUrl; + + const brandingConfig = SdkConfig.getObject("branding"); + AuthPage.welcomeBackgroundUrl = "themes/element/img/backgrounds/lake.jpg"; + + const configuredUrl = brandingConfig?.get("welcome_background_url"); + if (configuredUrl) { + if (Array.isArray(configuredUrl)) { + const index = Math.floor(Math.random() * configuredUrl.length); + AuthPage.welcomeBackgroundUrl = configuredUrl[index]; + } else { + AuthPage.welcomeBackgroundUrl = configuredUrl; + } + } + + return AuthPage.welcomeBackgroundUrl; + } + + public render(): React.ReactElement { + const pageStyle = { + background: `center/cover fixed url(${AuthPage.getWelcomeBackgroundUrl()})`, + }; + + const modalStyle: React.CSSProperties = { + position: "relative", + background: "initial", + }; + + const blurStyle: React.CSSProperties = { + position: "absolute", + top: 0, + right: 0, + bottom: 0, + left: 0, + filter: "blur(40px)", + background: pageStyle.background, + }; + + const modalContentStyle: React.CSSProperties = { + display: "flex", + zIndex: 1, + background: "rgba(255, 255, 255, 0.59)", + borderRadius: "8px", + }; + return ( -
-
{this.props.children}
+
+
+
+
+ {this.props.children} +
+
); diff --git a/src/components/views/auth/VectorAuthFooter.tsx b/src/components/views/auth/VectorAuthFooter.tsx deleted file mode 100644 index 234c6b127b..0000000000 --- a/src/components/views/auth/VectorAuthFooter.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* -Copyright 2019-2024 New Vector Ltd. -Copyright 2015, 2016 OpenMarket Ltd - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React, { ReactElement } from "react"; - -import SdkConfig from "../../../SdkConfig"; -import { _t } from "../../../languageHandler"; - -const VectorAuthFooter = (): ReactElement => { - const brandingConfig = SdkConfig.getObject("branding"); - const links = brandingConfig?.get("auth_footer_links") ?? [ - { text: "Blog", url: "https://element.io/blog" }, - { text: "Twitter", url: "https://twitter.com/element_hq" }, - { text: "GitHub", url: "https://github.com/element-hq/element-web" }, - ]; - - const authFooterLinks: JSX.Element[] = []; - for (const linkEntry of links) { - authFooterLinks.push( - - {linkEntry.text} - , - ); - } - - return ( - - ); -}; - -export default VectorAuthFooter; diff --git a/src/components/views/auth/VectorAuthHeaderLogo.tsx b/src/components/views/auth/VectorAuthHeaderLogo.tsx deleted file mode 100644 index 3cdf30cafc..0000000000 --- a/src/components/views/auth/VectorAuthHeaderLogo.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* -Copyright 2019-2024 New Vector Ltd. -Copyright 2015, 2016 OpenMarket Ltd - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import * as React from "react"; - -import SdkConfig from "../../../SdkConfig"; - -export default class VectorAuthHeaderLogo extends React.PureComponent { - public render(): React.ReactElement { - const brandingConfig = SdkConfig.getObject("branding"); - const logoUrl = brandingConfig?.get("auth_header_logo_url") ?? "themes/element/img/logos/element-logo.svg"; - - return ( - - ); - } -} diff --git a/src/components/views/auth/VectorAuthPage.tsx b/src/components/views/auth/VectorAuthPage.tsx deleted file mode 100644 index 969cc560a3..0000000000 --- a/src/components/views/auth/VectorAuthPage.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/* -Copyright 2019-2024 New Vector Ltd. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import * as React from "react"; - -import SdkConfig from "../../../SdkConfig"; -import VectorAuthFooter from "./VectorAuthFooter"; - -export default class VectorAuthPage extends React.PureComponent { - private static welcomeBackgroundUrl?: string; - - // cache the url as a static to prevent it changing without refreshing - private static getWelcomeBackgroundUrl(): string { - if (VectorAuthPage.welcomeBackgroundUrl) return VectorAuthPage.welcomeBackgroundUrl; - - const brandingConfig = SdkConfig.getObject("branding"); - VectorAuthPage.welcomeBackgroundUrl = "themes/element/img/backgrounds/lake.jpg"; - - const configuredUrl = brandingConfig?.get("welcome_background_url"); - if (configuredUrl) { - if (Array.isArray(configuredUrl)) { - const index = Math.floor(Math.random() * configuredUrl.length); - VectorAuthPage.welcomeBackgroundUrl = configuredUrl[index]; - } else { - VectorAuthPage.welcomeBackgroundUrl = configuredUrl; - } - } - - return VectorAuthPage.welcomeBackgroundUrl; - } - - public render(): React.ReactElement { - const pageStyle = { - background: `center/cover fixed url(${VectorAuthPage.getWelcomeBackgroundUrl()})`, - }; - - const modalStyle: React.CSSProperties = { - position: "relative", - background: "initial", - }; - - const blurStyle: React.CSSProperties = { - position: "absolute", - top: 0, - right: 0, - bottom: 0, - left: 0, - filter: "blur(40px)", - background: pageStyle.background, - }; - - const modalContentStyle: React.CSSProperties = { - display: "flex", - zIndex: 1, - background: "rgba(255, 255, 255, 0.59)", - borderRadius: "8px", - }; - - return ( -
-
-
-
- {this.props.children} -
-
- -
- ); - } -} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1ad73fff8a..b17233c9d2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -209,7 +209,6 @@ "failed_query_registration_methods": "Unable to query for supported registration methods.", "failed_soft_logout_auth": "Failed to re-authenticate", "failed_soft_logout_homeserver": "Failed to re-authenticate due to a homeserver problem", - "footer_powered_by_matrix": "powered by Matrix", "forgot_password_email_invalid": "The email address doesn't appear to be valid.", "forgot_password_email_required": "The email address linked to your account must be entered.", "forgot_password_prompt": "Forgotten your password?", @@ -3706,7 +3705,6 @@ "truncated_list_n_more": { "other": "And %(count)s more..." }, - "unknown_device": "Unknown device", "unsupported_browser": { "description": "If you continue, some features may stop working and there is a risk that you may lose data in the future. Update your browser to continue using %(brand)s.", "title": "%(brand)s does not support this browser" diff --git a/src/vector/app.tsx b/src/vector/app.tsx index da0f3f3941..426163db0b 100644 --- a/src/vector/app.tsx +++ b/src/vector/app.tsx @@ -27,7 +27,6 @@ import MatrixChat from "../components/structures/MatrixChat"; import { ValidatedServerConfig } from "../utils/ValidatedServerConfig"; import { ModuleRunner } from "../modules/ModuleRunner"; import { parseQs } from "./url_utils"; -import VectorBasePlatform from "./platform/VectorBasePlatform"; import { getInitialScreenAfterLogin, getScreenFromLocation, init as initRouting, onNewScreen } from "./routing"; import { UserFriendlyError } from "../languageHandler"; @@ -64,7 +63,7 @@ export async function loadApp(fragParams: {}, matrixChatRef: React.Ref -Copyright 2016 Aviral Dasgupta -Copyright 2016 OpenMarket Ltd - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import type { IConfigOptions } from "../../IConfigOptions"; -import BasePlatform from "../../BasePlatform"; -import { getVectorConfig } from "../getconfig"; -import Favicon from "../../favicon"; -import { _t } from "../../languageHandler"; - -/** - * Vector-specific extensions to the BasePlatform template - */ -export default abstract class VectorBasePlatform extends BasePlatform { - protected _favicon?: Favicon; - - public async getConfig(): Promise { - return getVectorConfig(); - } - - public getHumanReadableName(): string { - return "Vector Base Platform"; // no translation required: only used for analytics - } - - /** - * Delay creating the `Favicon` instance until first use (on the first notification) as - * it uses canvas, which can trigger a permission prompt in Firefox's resist fingerprinting mode. - * See https://github.com/element-hq/element-web/issues/9605. - */ - public get favicon(): Favicon { - if (this._favicon) { - return this._favicon; - } - this._favicon = new Favicon(); - return this._favicon; - } - - private updateFavicon(): void { - let bgColor = "#d00"; - let notif: string | number = this.notificationCount; - - if (this.errorDidOccur) { - notif = notif || "×"; - bgColor = "#f00"; - } - - this.favicon.badge(notif, { bgColor }); - } - - public setNotificationCount(count: number): void { - if (this.notificationCount === count) return; - super.setNotificationCount(count); - this.updateFavicon(); - } - - public setErrorStatus(errorDidOccur: boolean): void { - if (this.errorDidOccur === errorDidOccur) return; - super.setErrorStatus(errorDidOccur); - this.updateFavicon(); - } - - /** - * Begin update polling, if applicable - */ - public startUpdater(): void {} - - /** - * Get a sensible default display name for the - * device Vector is running on - */ - public getDefaultDeviceDisplayName(): string { - return _t("unknown_device"); - } -} diff --git a/src/vector/platform/WebPlatform.ts b/src/vector/platform/WebPlatform.ts index 0bbc7a0a5a..bb573c89c0 100644 --- a/src/vector/platform/WebPlatform.ts +++ b/src/vector/platform/WebPlatform.ts @@ -11,12 +11,11 @@ import UAParser from "ua-parser-js"; import { logger } from "matrix-js-sdk/src/logger"; import { MatrixClientPeg } from "../../MatrixClientPeg"; -import { UpdateCheckStatus, UpdateStatus } from "../../BasePlatform"; +import BasePlatform, { UpdateCheckStatus, UpdateStatus } from "../../BasePlatform"; import dis from "../../dispatcher/dispatcher"; import { hideToast as hideUpdateToast, showToast as showUpdateToast } from "../../toasts/UpdateToast"; import { Action } from "../../dispatcher/actions"; import { CheckUpdatesPayload } from "../../dispatcher/payloads/CheckUpdatesPayload"; -import VectorBasePlatform from "./VectorBasePlatform"; import { parseQs } from "../url_utils"; import { _t } from "../../languageHandler"; @@ -31,7 +30,7 @@ function getNormalizedAppVersion(version: string): string { return version; } -export default class WebPlatform extends VectorBasePlatform { +export default class WebPlatform extends BasePlatform { private static readonly VERSION = process.env.VERSION!; // baked in by Webpack public constructor() { diff --git a/test/unit-tests/components/structures/MatrixChat-test.tsx b/test/unit-tests/components/structures/MatrixChat-test.tsx index 4e04025f55..4b396b66a9 100644 --- a/test/unit-tests/components/structures/MatrixChat-test.tsx +++ b/test/unit-tests/components/structures/MatrixChat-test.tsx @@ -953,7 +953,7 @@ describe("", () => { const getComponentAndWaitForReady = async (): Promise => { const renderResult = getComponent(); // wait for welcome page chrome render - await screen.findByText("powered by Matrix"); + await screen.findByText("Powered by Matrix"); // go to login page defaultDispatcher.dispatch({ @@ -1480,7 +1480,7 @@ describe("", () => { const getComponentAndWaitForReady = async (): Promise => { const renderResult = getComponent(); // wait for welcome page chrome render - await screen.findByText("powered by Matrix"); + await screen.findByText("Powered by Matrix"); // go to mobile_register page defaultDispatcher.dispatch({ @@ -1500,7 +1500,7 @@ describe("", () => { it("should render welcome screen if mobile registration is not enabled in settings", async () => { await getComponentAndWaitForReady(); - await screen.findByText("powered by Matrix"); + await screen.findByText("Powered by Matrix"); }); it("should render mobile registration", async () => { diff --git a/test/unit-tests/components/structures/__snapshots__/MatrixChat-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/MatrixChat-test.tsx.snap index 71bde418ff..e074958144 100644 --- a/test/unit-tests/components/structures/__snapshots__/MatrixChat-test.tsx.snap +++ b/test/unit-tests/components/structures/__snapshots__/MatrixChat-test.tsx.snap @@ -114,46 +114,56 @@ exports[` Multi-tab lockout waits for other tab to stop during sta >
+
-

- Hello -

-
-
-
- +
+ -
@@ -162,12 +172,33 @@ exports[` Multi-tab lockout waits for other tab to stop during sta class="mx_AuthFooter" role="contentinfo" > + + Blog + + + Twitter + + + GitHub + - powered by Matrix + Powered by Matrix
@@ -201,116 +232,150 @@ exports[` with a soft-logged-out session should show the soft-logo >
+
-
- -
-
-
-

- You're signed out -

-

- Sign in -

-
-
-

- Enter your password to sign in and regain access to your account. -

-
- - -
-
- Sign in -
- -
-
-

- Clear personal data -

-

- 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. -

-
+
- Clear all data +
-
+
+

+ You're signed out +

+

+ Sign in +

+
+
+

+ Enter your password to sign in and regain access to your account. +

+
+ + +
+
+ Sign in +
+ +
+
+

+ Clear personal data +

+

+ 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. +

+
+
+ Clear all data +
+
+
+
diff --git a/test/unit-tests/components/views/auth/VectorAuthPage-test.tsx b/test/unit-tests/components/views/auth/AuthFooter-test.tsx similarity index 73% rename from test/unit-tests/components/views/auth/VectorAuthPage-test.tsx rename to test/unit-tests/components/views/auth/AuthFooter-test.tsx index 2c5cb461b1..f8d0d8fd5e 100644 --- a/test/unit-tests/components/views/auth/VectorAuthPage-test.tsx +++ b/test/unit-tests/components/views/auth/AuthFooter-test.tsx @@ -9,16 +9,16 @@ Please see LICENSE files in the repository root for full details. import * as React from "react"; import { render } from "jest-matrix-react"; -import VectorAuthPage from "../../../../../src/components/views/auth/VectorAuthPage"; +import AuthFooter from "../../../../../src/components/views/auth/AuthFooter"; import { setupLanguageMock } from "../../../../setup/setupLanguage"; -describe("", () => { +describe("", () => { beforeEach(() => { setupLanguageMock(); }); it("should match snapshot", () => { - const { asFragment } = render(); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/test/unit-tests/components/views/auth/VectorAuthHeaderLogo-test.tsx b/test/unit-tests/components/views/auth/AuthHeaderLogo-test.tsx similarity index 64% rename from test/unit-tests/components/views/auth/VectorAuthHeaderLogo-test.tsx rename to test/unit-tests/components/views/auth/AuthHeaderLogo-test.tsx index 6b3839a5b1..ce187805e4 100644 --- a/test/unit-tests/components/views/auth/VectorAuthHeaderLogo-test.tsx +++ b/test/unit-tests/components/views/auth/AuthHeaderLogo-test.tsx @@ -9,11 +9,11 @@ Please see LICENSE files in the repository root for full details. import * as React from "react"; import { render } from "jest-matrix-react"; -import VectorAuthHeaderLogo from "../../../../../src/components/views/auth/VectorAuthHeaderLogo"; +import AuthHeaderLogo from "../../../../../src/components/views/auth/AuthHeaderLogo"; -describe("", () => { +describe("", () => { it("should match snapshot", () => { - const { asFragment } = render(); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/test/unit-tests/components/views/auth/AuthPage-test.tsx b/test/unit-tests/components/views/auth/AuthPage-test.tsx new file mode 100644 index 0000000000..836b08f20b --- /dev/null +++ b/test/unit-tests/components/views/auth/AuthPage-test.tsx @@ -0,0 +1,36 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2022 The Matrix.org Foundation C.I.C. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only +Please see LICENSE files in the repository root for full details. +*/ + +import * as React from "react"; +import { render } from "jest-matrix-react"; + +import AuthPage from "../../../../../src/components/views/auth/AuthPage"; +import { setupLanguageMock } from "../../../../setup/setupLanguage"; +import SdkConfig from "../../../../../src/SdkConfig.ts"; + +describe("", () => { + beforeEach(() => { + setupLanguageMock(); + SdkConfig.reset(); + // @ts-ignore private access + AuthPage.welcomeBackgroundUrl = undefined; + }); + + it("should match snapshot", () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should use configured background url", () => { + SdkConfig.add({ branding: { welcome_background_url: ["https://example.com/image.png"] } }); + const { container } = render(); + expect(container.querySelector(".mx_AuthPage")).toHaveStyle({ + background: "center/cover fixed url(https://example.com/image.png)", + }); + }); +}); diff --git a/test/unit-tests/components/views/auth/VectorAuthFooter-test.tsx b/test/unit-tests/components/views/auth/VectorAuthFooter-test.tsx deleted file mode 100644 index ebd2a6ffe4..0000000000 --- a/test/unit-tests/components/views/auth/VectorAuthFooter-test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import * as React from "react"; -import { render } from "jest-matrix-react"; - -import VectorAuthFooter from "../../../../../src/components/views/auth/VectorAuthFooter"; -import { setupLanguageMock } from "../../../../setup/setupLanguage"; - -describe("", () => { - beforeEach(() => { - setupLanguageMock(); - }); - - it("should match snapshot", () => { - const { asFragment } = render(); - expect(asFragment()).toMatchSnapshot(); - }); -}); diff --git a/test/unit-tests/components/views/auth/__snapshots__/VectorAuthFooter-test.tsx.snap b/test/unit-tests/components/views/auth/__snapshots__/AuthFooter-test.tsx.snap similarity index 92% rename from test/unit-tests/components/views/auth/__snapshots__/VectorAuthFooter-test.tsx.snap rename to test/unit-tests/components/views/auth/__snapshots__/AuthFooter-test.tsx.snap index d1a16c081b..f1321ece2a 100644 --- a/test/unit-tests/components/views/auth/__snapshots__/VectorAuthFooter-test.tsx.snap +++ b/test/unit-tests/components/views/auth/__snapshots__/AuthFooter-test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` should match snapshot 1`] = ` +exports[` should match snapshot 1`] = `