From b0cea630a3c5c8478607864628f299eeeea17508 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 28 Jun 2022 16:10:33 +0100 Subject: [PATCH 001/162] Upgrade matrix-js-sdk to 19.0.0-rc.1 --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 740333f8b5f..bde7504f218 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "maplibre-gl": "^1.15.2", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "^0.0.1-beta.7", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "19.0.0-rc.1", "matrix-widget-api": "^0.1.0-beta.18", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 46bc76b3c69..8da5fadffc6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6961,9 +6961,10 @@ matrix-events-sdk@^0.0.1-beta.7: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1-beta.7.tgz#5ffe45eba1f67cc8d7c2377736c728b322524934" integrity sha512-9jl4wtWanUFSy2sr2lCjErN/oC8KTAtaeaozJtrgot1JiQcEI4Rda9OLgQ7nLKaqb4Z/QUx/fR3XpDzm5Jy1JA== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "18.1.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/cb5b2e14703ecab96f89ce6945e60de75a1c4f54" +matrix-js-sdk@19.0.0-rc.1: + version "19.0.0-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-19.0.0-rc.1.tgz#6a1454330246e232e58964d198eef90d2bdce1ee" + integrity sha512-XHeI7RhcrbNByFPD45yM7SeF6ZckPUpRiQOcGD8rGUFDxwmbEyjIdjqZf8YJeKedaGsLPMxztDw17jT6QJSWyg== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From e3044faedca0559fa2669a001bdf3ce46781da2a Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 28 Jun 2022 16:12:44 +0100 Subject: [PATCH 002/162] Prepare changelog for v3.48.0-rc.1 --- CHANGELOG.md | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b2b3ec2979..59d02f714b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,107 @@ +Changes in [3.48.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.48.0-rc.1) (2022-06-28) +=============================================================================================================== + +## 🚨 BREAKING CHANGES + * Remove Piwik support ([\#8835](https://github.com/matrix-org/matrix-react-sdk/pull/8835)). + +## ✨ Features + * Move New Search Experience out of beta ([\#8859](https://github.com/matrix-org/matrix-react-sdk/pull/8859)). Contributed by @justjanne. + * Switch video rooms to spotlight layout when in PiP mode ([\#8912](https://github.com/matrix-org/matrix-react-sdk/pull/8912)). Fixes vector-im/element-web#22574. + * Live location sharing - render message deleted tile for redacted beacons ([\#8905](https://github.com/matrix-org/matrix-react-sdk/pull/8905)). Contributed by @kerryarchibald. + * Improve view source dialog style ([\#8883](https://github.com/matrix-org/matrix-react-sdk/pull/8883)). Fixes vector-im/element-web#22636. Contributed by @luixxiul. + * Improve integration manager dialog style ([\#8888](https://github.com/matrix-org/matrix-react-sdk/pull/8888)). Fixes vector-im/element-web#22642. Contributed by @luixxiul. + * Implement MSC3827: Filtering of `/publicRooms` by room type ([\#8866](https://github.com/matrix-org/matrix-react-sdk/pull/8866)). Fixes vector-im/element-web#22578. + * Show chat panel when opening a video room with unread messages ([\#8812](https://github.com/matrix-org/matrix-react-sdk/pull/8812)). Fixes vector-im/element-web#22527. + * Live location share - forward latest location ([\#8860](https://github.com/matrix-org/matrix-react-sdk/pull/8860)). Contributed by @kerryarchibald. + * Allow integration managers to validate user identity after opening ([\#8782](https://github.com/matrix-org/matrix-react-sdk/pull/8782)). Contributed by @Half-Shot. + * Create a common header on right panel cards on BaseCard ([\#8808](https://github.com/matrix-org/matrix-react-sdk/pull/8808)). Contributed by @luixxiul. + * Integrate searching public rooms and people into the new search experience ([\#8707](https://github.com/matrix-org/matrix-react-sdk/pull/8707)). Fixes vector-im/element-web#21354 and vector-im/element-web#19349. Contributed by @justjanne. + * Bring back waveform for voice messages and retain seeking ([\#8843](https://github.com/matrix-org/matrix-react-sdk/pull/8843)). Fixes vector-im/element-web#21904. + * Improve colors in settings ([\#7283](https://github.com/matrix-org/matrix-react-sdk/pull/7283)). + * Keep draft in composer when a slash command syntax errors ([\#8811](https://github.com/matrix-org/matrix-react-sdk/pull/8811)). Fixes vector-im/element-web#22384. + * Release video rooms as a beta feature ([\#8431](https://github.com/matrix-org/matrix-react-sdk/pull/8431)). + * Clarify logout key backup warning dialog. Contributed by @notramo. ([\#8741](https://github.com/matrix-org/matrix-react-sdk/pull/8741)). Fixes vector-im/element-web#15565. Contributed by @MadLittleMods. + * Slightly improve the look of the `Message edits` dialog ([\#8763](https://github.com/matrix-org/matrix-react-sdk/pull/8763)). Fixes vector-im/element-web#22410. + * Add support for MD / HTML in room topics ([\#8215](https://github.com/matrix-org/matrix-react-sdk/pull/8215)). Fixes vector-im/element-web#5180. Contributed by @Johennes. + * Live location share - link to timeline tile from share warning ([\#8752](https://github.com/matrix-org/matrix-react-sdk/pull/8752)). Contributed by @kerryarchibald. + * Improve composer visiblity ([\#8578](https://github.com/matrix-org/matrix-react-sdk/pull/8578)). Fixes vector-im/element-web#22072 and vector-im/element-web#17362. + * Makes the avatar of the user menu non-draggable ([\#8765](https://github.com/matrix-org/matrix-react-sdk/pull/8765)). Contributed by @luixxiul. + * Improve widget buttons behaviour and layout ([\#8734](https://github.com/matrix-org/matrix-react-sdk/pull/8734)). Contributed by @weeman1337. + * Use AccessibleButton for 'Reset All' link button on SetupEncryptionBody ([\#8730](https://github.com/matrix-org/matrix-react-sdk/pull/8730)). Contributed by @luixxiul. + * Adjust message timestamp position on TimelineCard in non-bubble layouts ([\#8745](https://github.com/matrix-org/matrix-react-sdk/pull/8745)). Fixes vector-im/element-web#22426. Contributed by @luixxiul. + * Use AccessibleButton for 'In reply to' link button on ReplyChain ([\#8726](https://github.com/matrix-org/matrix-react-sdk/pull/8726)). Fixes vector-im/element-web#22407. Contributed by @luixxiul. + * Live location share - enable reply and react to tiles ([\#8721](https://github.com/matrix-org/matrix-react-sdk/pull/8721)). Contributed by @kerryarchibald. + * Change dash to em dash issues fixed ([\#8455](https://github.com/matrix-org/matrix-react-sdk/pull/8455)). Fixes vector-im/element-web#21895. Contributed by @goelesha. + +## 🐛 Bug Fixes + * Correct issue with tab order in new search experience ([\#8919](https://github.com/matrix-org/matrix-react-sdk/pull/8919)). Fixes vector-im/element-web#22670. Contributed by @justjanne. + * Clicking location replies now redirects to the replied event instead of opening the map ([\#8918](https://github.com/matrix-org/matrix-react-sdk/pull/8918)). Fixes vector-im/element-web#22667. Contributed by @weeman1337. + * Keep clicks on pills within the app ([\#8917](https://github.com/matrix-org/matrix-react-sdk/pull/8917)). Fixes vector-im/element-web#22653. + * Don't overlap tile bubbles with timestamps in modern layout ([\#8908](https://github.com/matrix-org/matrix-react-sdk/pull/8908)). Fixes vector-im/element-web#22425. + * Connect to Jitsi unmuted by default ([\#8909](https://github.com/matrix-org/matrix-react-sdk/pull/8909)). + * Maximize width value of display name on TimelineCard with IRC/modern layout ([\#8904](https://github.com/matrix-org/matrix-react-sdk/pull/8904)). Fixes vector-im/element-web#22651. Contributed by @luixxiul. + * Align the avatar and the display name on TimelineCard ([\#8900](https://github.com/matrix-org/matrix-react-sdk/pull/8900)). Contributed by @luixxiul. + * Remove inline margin from reactions row on IRC layout ([\#8891](https://github.com/matrix-org/matrix-react-sdk/pull/8891)). Fixes vector-im/element-web#22644. Contributed by @luixxiul. + * Align "From a thread" on search result panel on IRC layout ([\#8892](https://github.com/matrix-org/matrix-react-sdk/pull/8892)). Fixes vector-im/element-web#22645. Contributed by @luixxiul. + * Display description of E2E advanced panel as subsection text ([\#8889](https://github.com/matrix-org/matrix-react-sdk/pull/8889)). Contributed by @luixxiul. + * Remove inline end margin from images on file panel ([\#8886](https://github.com/matrix-org/matrix-react-sdk/pull/8886)). Fixes vector-im/element-web#22640. Contributed by @luixxiul. + * Disable option to `Quote` when we don't have sufficient permissions ([\#8893](https://github.com/matrix-org/matrix-react-sdk/pull/8893)). Fixes vector-im/element-web#22643. + * Add padding to font scaling loader for message bubble layout ([\#8875](https://github.com/matrix-org/matrix-react-sdk/pull/8875)). Fixes vector-im/element-web#22626. Contributed by @luixxiul. + * Set 100% max-width to display name on reply tiles ([\#8867](https://github.com/matrix-org/matrix-react-sdk/pull/8867)). Fixes vector-im/element-web#22615. Contributed by @luixxiul. + * Fix alignment of pill letter ([\#8874](https://github.com/matrix-org/matrix-react-sdk/pull/8874)). Fixes vector-im/element-web#22622. Contributed by @luixxiul. + * Move the beta pill to the right side and display the pill on video room only ([\#8873](https://github.com/matrix-org/matrix-react-sdk/pull/8873)). Fixes vector-im/element-web#22619 and vector-im/element-web#22620. Contributed by @luixxiul. + * Stop using absolute property to place beta pill on RoomPreviewCard ([\#8872](https://github.com/matrix-org/matrix-react-sdk/pull/8872)). Fixes vector-im/element-web#22617. Contributed by @luixxiul. + * Make the pill text single line ([\#8744](https://github.com/matrix-org/matrix-react-sdk/pull/8744)). Fixes vector-im/element-web#22427. Contributed by @luixxiul. + * Hide overflow of public room description on spotlight dialog result ([\#8870](https://github.com/matrix-org/matrix-react-sdk/pull/8870)). Contributed by @luixxiul. + * Fix position of message action bar on the info tile on TimelineCard in message bubble layout ([\#8865](https://github.com/matrix-org/matrix-react-sdk/pull/8865)). Fixes vector-im/element-web#22614. Contributed by @luixxiul. + * Remove inline start margin from display name on reply tiles on TimelineCard ([\#8864](https://github.com/matrix-org/matrix-react-sdk/pull/8864)). Fixes vector-im/element-web#22613. Contributed by @luixxiul. + * Improve homeserver dropdown dialog styling ([\#8850](https://github.com/matrix-org/matrix-react-sdk/pull/8850)). Fixes vector-im/element-web#22552. Contributed by @justjanne. + * Fix crash when drawing blurHash for portrait videos PSB-139 ([\#8855](https://github.com/matrix-org/matrix-react-sdk/pull/8855)). Fixes vector-im/element-web#22597. Contributed by @andybalaam. + * Fix grid blowout on pinned event tiles ([\#8816](https://github.com/matrix-org/matrix-react-sdk/pull/8816)). Fixes vector-im/element-web#22543. Contributed by @luixxiul. + * Fix temporary sync errors if there's weird settings stored in account data ([\#8857](https://github.com/matrix-org/matrix-react-sdk/pull/8857)). + * Fix reactions row overflow and gap between reactions ([\#8813](https://github.com/matrix-org/matrix-react-sdk/pull/8813)). Fixes vector-im/element-web#22093. Contributed by @luixxiul. + * Fix issues with the Create new room button in Spotlight ([\#8851](https://github.com/matrix-org/matrix-react-sdk/pull/8851)). Contributed by @justjanne. + * Remove margin from E2E icon between avatar and hidden event ([\#8584](https://github.com/matrix-org/matrix-react-sdk/pull/8584)). Fixes vector-im/element-web#22186. Contributed by @luixxiul. + * Fix waveform on a message bubble ([\#8852](https://github.com/matrix-org/matrix-react-sdk/pull/8852)). Contributed by @luixxiul. + * Location sharing maps are now loaded after reconnection ([\#8848](https://github.com/matrix-org/matrix-react-sdk/pull/8848)). Fixes vector-im/element-web#20993. Contributed by @weeman1337. + * Update the avatar mask so it doesn’t cut off spaces’ avatars anymore ([\#8849](https://github.com/matrix-org/matrix-react-sdk/pull/8849)). Contributed by @justjanne. + * Add a bit of safety around timestamp handling for threads ([\#8845](https://github.com/matrix-org/matrix-react-sdk/pull/8845)). + * Remove top margin from event tile on a narrow viewport ([\#8814](https://github.com/matrix-org/matrix-react-sdk/pull/8814)). Contributed by @luixxiul. + * Fix keyboard shortcuts on settings tab being wrapped ([\#8825](https://github.com/matrix-org/matrix-react-sdk/pull/8825)). Fixes vector-im/element-web#22547. Contributed by @luixxiul. + * Add try-catch around blurhash loading ([\#8830](https://github.com/matrix-org/matrix-react-sdk/pull/8830)). + * Prevent new composer from overflowing from non-breakable text ([\#8829](https://github.com/matrix-org/matrix-react-sdk/pull/8829)). Fixes vector-im/element-web#22507. Contributed by @justjanne. + * Use common subheading on sidebar user settings tab ([\#8823](https://github.com/matrix-org/matrix-react-sdk/pull/8823)). Contributed by @luixxiul. + * Fix clickable area of advanced toggle on appearance user settings tab ([\#8820](https://github.com/matrix-org/matrix-react-sdk/pull/8820)). Fixes vector-im/element-web#22546. Contributed by @luixxiul. + * Disable redacting reactions if we don't have sufficient permissions ([\#8767](https://github.com/matrix-org/matrix-react-sdk/pull/8767)). Fixes vector-im/element-web#22262. + * Update the live timeline when the JS SDK resets it ([\#8806](https://github.com/matrix-org/matrix-react-sdk/pull/8806)). Fixes vector-im/element-web#22421. + * Fix flex blowout on image reply ([\#8809](https://github.com/matrix-org/matrix-react-sdk/pull/8809)). Fixes vector-im/element-web#22509 and vector-im/element-web#22510. Contributed by @luixxiul. + * Enable background color on hover for chat panel and thread panel ([\#8644](https://github.com/matrix-org/matrix-react-sdk/pull/8644)). Fixes vector-im/element-web#22273. Contributed by @luixxiul. + * Fix #20026: send read marker as soon as we change it ([\#8802](https://github.com/matrix-org/matrix-react-sdk/pull/8802)). Fixes vector-im/element-web#20026. Contributed by @andybalaam. + * Allow AppTiles to shrink as much as necessary ([\#8805](https://github.com/matrix-org/matrix-react-sdk/pull/8805)). Fixes vector-im/element-web#22499. + * Make widgets in video rooms immutable again ([\#8803](https://github.com/matrix-org/matrix-react-sdk/pull/8803)). Fixes vector-im/element-web#22497. + * Use MessageActionBar style declarations on pinned message card ([\#8757](https://github.com/matrix-org/matrix-react-sdk/pull/8757)). Fixes vector-im/element-web#22444. Contributed by @luixxiul. + * Expire video member events after 1 hour ([\#8776](https://github.com/matrix-org/matrix-react-sdk/pull/8776)). + * Name lists on invite dialog ([\#8046](https://github.com/matrix-org/matrix-react-sdk/pull/8046)). Fixes vector-im/element-web#21400 and vector-im/element-web#19463. Contributed by @luixxiul. + * Live location share - show loading UI for beacons with start timestamp in the future ([\#8775](https://github.com/matrix-org/matrix-react-sdk/pull/8775)). Fixes vector-im/element-web#22437. Contributed by @kerryarchibald. + * Fix scroll jump issue with the composer ([\#8788](https://github.com/matrix-org/matrix-react-sdk/pull/8788)). Fixes vector-im/element-web#22464. + * Fix the incorrect nesting of download button on MessageActionBar ([\#8785](https://github.com/matrix-org/matrix-react-sdk/pull/8785)). Contributed by @luixxiul. + * Revert link color change in composer ([\#8784](https://github.com/matrix-org/matrix-react-sdk/pull/8784)). Fixes vector-im/element-web#22468. + * Fix 'Logout' inline link on the splash screen ([\#8770](https://github.com/matrix-org/matrix-react-sdk/pull/8770)). Fixes vector-im/element-web#22449. Contributed by @luixxiul. + * Fix disappearing widget poput button when changing the widget layout ([\#8754](https://github.com/matrix-org/matrix-react-sdk/pull/8754)). Contributed by @weeman1337. + * Reduce gutter with the new read receipt UI ([\#8736](https://github.com/matrix-org/matrix-react-sdk/pull/8736)). Fixes vector-im/element-web#21890. + * Add ellipsis effect to hidden beacon status ([\#8755](https://github.com/matrix-org/matrix-react-sdk/pull/8755)). Fixes vector-im/element-web#22441. Contributed by @luixxiul. + * Make the pill on the basic message composer compatible with display name in RTL languages ([\#8758](https://github.com/matrix-org/matrix-react-sdk/pull/8758)). Fixes vector-im/element-web#22445. Contributed by @luixxiul. + * Prevent the banner text from being selected, replacing the spacing values with the variable ([\#8756](https://github.com/matrix-org/matrix-react-sdk/pull/8756)). Fixes vector-im/element-web#22442. Contributed by @luixxiul. + * Ensure the first device on a newly-registered account gets cross-signed properly ([\#8750](https://github.com/matrix-org/matrix-react-sdk/pull/8750)). Fixes vector-im/element-web#21977. Contributed by @duxovni. + * Hide live location option in threads composer ([\#8746](https://github.com/matrix-org/matrix-react-sdk/pull/8746)). Fixes vector-im/element-web#22424. Contributed by @kerryarchibald. + * Make sure MessageTimestamp is not hidden by EventTile_line on TimelineCard ([\#8748](https://github.com/matrix-org/matrix-react-sdk/pull/8748)). Contributed by @luixxiul. + * Make PiP motion smoother and react to window resizes correctly ([\#8747](https://github.com/matrix-org/matrix-react-sdk/pull/8747)). Fixes vector-im/element-web#22292. + * Prevent Invite and DevTools dialogs from being cut off ([\#8646](https://github.com/matrix-org/matrix-react-sdk/pull/8646)). Fixes vector-im/element-web#20911 and undefined/matrix-react-sdk#8165. Contributed by @justjanne. + * Squish event bubble tiles less ([\#8740](https://github.com/matrix-org/matrix-react-sdk/pull/8740)). + * Use random widget IDs for video rooms ([\#8739](https://github.com/matrix-org/matrix-react-sdk/pull/8739)). Fixes vector-im/element-web#22417. + * Fix read avatars overflow from the right chat panel with a maximized widget on bubble message layout ([\#8470](https://github.com/matrix-org/matrix-react-sdk/pull/8470)). Contributed by @luixxiul. + * Fix `CallView` crash ([\#8735](https://github.com/matrix-org/matrix-react-sdk/pull/8735)). Fixes vector-im/element-web#22394. + Changes in [3.47.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.47.0) (2022-06-14) ===================================================================================================== From d8b6011bf680833c0a2480517e9714161b55b6ec Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 28 Jun 2022 16:12:45 +0100 Subject: [PATCH 003/162] v3.48.0-rc.1 --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index bde7504f218..4c3c33e4f34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.47.0", + "version": "3.48.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -22,7 +22,7 @@ "README.md", "package.json" ], - "main": "./src/index.ts", + "main": "./lib/index.ts", "matrix_src_main": "./src/index.ts", "matrix_lib_main": "./lib/index.ts", "matrix_lib_typings": "./lib/index.d.ts", @@ -246,5 +246,6 @@ "jestSonar": { "reportPath": "coverage", "sonar56x": true - } + }, + "typings": "./lib/index.d.ts" } From 383bc50f1a59afb2ce220948c3fa4f8d17f3f7fb Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Thu, 30 Jun 2022 11:56:52 +0200 Subject: [PATCH 004/162] Make invite dialogue fixed height (#8945) --- res/css/views/dialogs/_InviteDialog.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/res/css/views/dialogs/_InviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss index 6fd3d0510fb..2b0475bc361 100644 --- a/res/css/views/dialogs/_InviteDialog.scss +++ b/res/css/views/dialogs/_InviteDialog.scss @@ -189,7 +189,7 @@ limitations under the License. // Prevent the dialog from jumping around randomly when elements change. display: flex; flex-direction: column; - max-height: 600px; + height: 600px; overflow: hidden; .mx_InviteDialog_addressBar { @@ -197,6 +197,7 @@ limitations under the License. } .mx_InviteDialog_userSections { + flex-grow: 1; padding-inline-end: 0; .mx_InviteDialog_section { @@ -209,7 +210,7 @@ limitations under the License. .mx_InviteDialog_content { display: flex; flex-direction: column; - flex-shrink: 1; + flex-grow: 1; overflow: hidden; } From 933ced504349576c2039ed163e2e04ee43b4116b Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Sat, 2 Jul 2022 00:07:26 +0900 Subject: [PATCH 005/162] Apply mx_EventTile_isEditing .mx_MessageTimestamp globally (#8956) --- res/css/views/rooms/_EventTile.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index cd9c434bd0f..fa9838ac7af 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -54,6 +54,10 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss } } + &.mx_EventTile_isEditing .mx_MessageTimestamp { + visibility: hidden; + } + .mx_EventTile_avatar { cursor: pointer; user-select: none; @@ -253,10 +257,6 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss padding-top: 0px !important; } - &.mx_EventTile_isEditing .mx_MessageTimestamp { - visibility: hidden; - } - .mx_MessageTimestamp { left: 0px; text-align: center; From 0d100cbbce0d3ea4b3c8e9503befd1a5837503c2 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Fri, 1 Jul 2022 21:01:18 +0200 Subject: [PATCH 006/162] Render HTML topics in rooms on space home (#8939) * Render HTML topics in rooms on space home Signed-off-by: Johannes Marbach * Add type annotations Signed-off-by: Johannes Marbach * Remove superfluous conditional check Signed-off-by: Johannes Marbach --- src/components/structures/SpaceHierarchy.tsx | 25 +++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/components/structures/SpaceHierarchy.tsx b/src/components/structures/SpaceHierarchy.tsx index 9d7c737c5f2..cb0432efb5d 100644 --- a/src/components/structures/SpaceHierarchy.tsx +++ b/src/components/structures/SpaceHierarchy.tsx @@ -18,6 +18,7 @@ import React, { Dispatch, KeyboardEvent, KeyboardEventHandler, + ReactElement, ReactNode, SetStateAction, useCallback, @@ -50,7 +51,7 @@ import TextWithTooltip from "../views/elements/TextWithTooltip"; import { useStateToggle } from "../../hooks/useStateToggle"; import { getChildOrder } from "../../stores/spaces/SpaceStore"; import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; -import { linkifyElement } from "../../HtmlUtils"; +import { linkifyElement, topicToHtml } from "../../HtmlUtils"; import { useDispatcher } from "../../hooks/useDispatcher"; import { Action } from "../../dispatcher/actions"; import { IState, RovingTabIndexProvider, useRovingTabIndex } from "../../accessibility/RovingTabIndex"; @@ -65,6 +66,7 @@ import { JoinRoomReadyPayload } from "../../dispatcher/payloads/JoinRoomReadyPay import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; import { getKeyBindingsManager } from "../../KeyBindingsManager"; import { Alignment } from "../views/elements/Tooltip"; +import { getTopic } from "../../hooks/room/useTopic"; interface IProps { space: Room; @@ -122,7 +124,7 @@ const Tile: React.FC = ({ }); }; - let button; + let button: ReactElement; if (busy) { button = = ({ ; } - let checkbox; + let checkbox: ReactElement | undefined; if (onToggleClick) { if (hasPermissions) { checkbox = ; @@ -168,7 +170,7 @@ const Tile: React.FC = ({ } } - let avatar; + let avatar: ReactElement; if (joinedRoom) { avatar = ; } else { @@ -186,19 +188,22 @@ const Tile: React.FC = ({ description += " · " + _t("%(count)s rooms", { count: numChildRooms }); } - const topic = joinedRoom?.currentState?.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || room.topic; - if (topic) { - description += " · " + topic; + let topic: ReactNode | string | null; + if (joinedRoom) { + const topicObj = getTopic(joinedRoom); + topic = topicToHtml(topicObj?.text, topicObj?.html); + } else { + topic = room.topic; } - let joinedSection; + let joinedSection: ReactElement | undefined; if (joinedRoom) { joinedSection =
{ _t("Joined") }
; } - let suggestedSection; + let suggestedSection: ReactElement | undefined; if (suggested && (!joinedRoom || hasPermissions)) { suggestedSection = { _t("Suggested") } @@ -226,6 +231,8 @@ const Tile: React.FC = ({ }} > { description } + { topic && " ¡ " } + { topic }
From 9328dca3fdd3fc01c25f870a9d680967dad4d5f5 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Sat, 2 Jul 2022 19:39:33 +0900 Subject: [PATCH 007/162] Consider cascading order of style rules on _EventTile.scss (#8968) --- res/css/views/rooms/_EventTile.scss | 94 ++++++++++++++--------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index fa9838ac7af..e34347621f8 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -27,37 +27,6 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss flex-shrink: 0; - &.mx_EventTile_highlight, - &.mx_EventTile_highlight .markdown-body { - color: $alert; - } - - &.mx_EventTile_bubbleContainer { - display: grid; - grid-template-columns: 1fr 100px; - - .mx_EventTile_line { - margin-right: 0; - grid-column: 1 / 3; - padding: 0 !important; // override default padding of mx_EventTile_line so that we can be centered - } - - .mx_EventTile_msgOption { - grid-column: 2; - } - - &:hover { - .mx_EventTile_line { - // To avoid bubble events being highlighted - background-color: inherit !important; - } - } - } - - &.mx_EventTile_isEditing .mx_MessageTimestamp { - visibility: hidden; - } - .mx_EventTile_avatar { cursor: pointer; user-select: none; @@ -137,15 +106,39 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss } } - &[data-layout=irc], - &[data-layout=group] { - &.mx_EventTile_highlight, - &.mx_EventTile_highlight .markdown-body { + &.mx_EventTile_highlight, + &.mx_EventTile_highlight .markdown-body { + color: $alert; + } + + &.mx_EventTile_bubbleContainer { + display: grid; + grid-template-columns: 1fr 100px; + + .mx_EventTile_line { + margin-right: 0; + grid-column: 1 / 3; + padding: 0 !important; // override default padding of mx_EventTile_line so that we can be centered + } + + .mx_EventTile_msgOption { + grid-column: 2; + } + + &:hover { .mx_EventTile_line { - background-color: $event-highlight-bg-color; + // To avoid bubble events being highlighted + background-color: inherit !important; } } + } + &.mx_EventTile_isEditing .mx_MessageTimestamp { + visibility: hidden; + } + + &[data-layout=irc], + &[data-layout=group] { .mx_EventTile_e2eIcon { position: absolute; } @@ -169,20 +162,16 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss .mx_EventTile_reply { margin-right: 10px; } - } - &[data-layout=group] { - > .mx_DisambiguatedProfile { - line-height: $font-20px; - margin-left: $left-gutter; - max-width: calc(100% - $left-gutter); - } - - > .mx_EventTile_avatar { - position: absolute; - z-index: 9; + &.mx_EventTile_highlight, + &.mx_EventTile_highlight .markdown-body { + .mx_EventTile_line { + background-color: $event-highlight-bg-color; + } } + } + &[data-layout=group] { .mx_EventTile_avatar { top: 14px; left: $spacing-8; @@ -216,6 +205,17 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss .mx_ReactionsRow { margin: $spacing-4 64px; } + + > .mx_DisambiguatedProfile { + line-height: $font-20px; + margin-left: $left-gutter; + max-width: calc(100% - $left-gutter); + } + + > .mx_EventTile_avatar { + position: absolute; + z-index: 9; + } } } From 37d8cfbc505bfba688b4101843cbb70a8c3de952 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Sun, 3 Jul 2022 00:51:57 +0900 Subject: [PATCH 008/162] Fix read receipts group position on TimelineCard in compact modern/group layout (#8971) --- res/css/views/right_panel/_TimelineCard.scss | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/res/css/views/right_panel/_TimelineCard.scss b/res/css/views/right_panel/_TimelineCard.scss index 255daf37033..63048511d51 100644 --- a/res/css/views/right_panel/_TimelineCard.scss +++ b/res/css/views/right_panel/_TimelineCard.scss @@ -50,6 +50,8 @@ limitations under the License. &[data-layout=irc], &[data-layout=group] { + --TimelineCard_ReadReceiptGroup-inset-block-start: -6px; + &.mx_EventTile_info .mx_EventTile_line, .mx_EventTile_line { padding: var(--BaseCard_EventTile_line-padding-block) var(--BaseCard_EventTile-spacing-inline); @@ -97,6 +99,10 @@ limitations under the License. margin-inline-end: $spacing-8; // See: var(--ThreadView_group_spacing-end) for ReactionsRow on _EventTile.scss } + .mx_ReadReceiptGroup { + top: var(--TimelineCard_ReadReceiptGroup-inset-block-start); + } + .mx_ThreadSummary { margin-inline-end: 0; max-width: min(calc(100% - 36px), 600px); @@ -110,6 +116,15 @@ limitations under the License. } } + &[data-layout=group] { + // Read receipt group on compact modern layout + // This is required because mx_TimelineCard is a child element wrapped by mx_MatrixChat_useCompactLayout, + // which specifies the default position of mx_ReadReceiptGroup on compact modern layout. + .mx_MatrixChat_useCompactLayout & .mx_ReadReceiptGroup { + top: var(--TimelineCard_ReadReceiptGroup-inset-block-start); + } + } + &[data-layout=bubble] { &::before { z-index: auto; // enable background color on hover @@ -141,10 +156,6 @@ limitations under the License. } } - .mx_ReadReceiptGroup { - top: -6px; - } - .mx_WhoIsTypingTile { margin-left: -12px; // undo padding on the message list } From 0909bfeb380bd4d426327233f115608361e513e8 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Mon, 4 Jul 2022 15:10:46 +0900 Subject: [PATCH 009/162] Improve LinkPreviewWidget (#8881) * Use shorthand for margin values Signed-off-by: Suguru Hirahara * Merge style declarations Signed-off-by: Suguru Hirahara * Merge style declarations Signed-off-by: Suguru Hirahara * Include mx_MatrixChat_useCompactLayout in mx_LinkPreviewWidget Signed-off-by: Suguru Hirahara * Include in mx_LinkPreviewWidget Signed-off-by: Suguru Hirahara * yarn run lint:style --fix Signed-off-by: Suguru Hirahara * Use spacing variables Signed-off-by: Suguru Hirahara * Use logical variables Signed-off-by: Suguru Hirahara * Prevent flex children blowout Signed-off-by: Suguru Hirahara * Wrap caption Signed-off-by: Suguru Hirahara * yarn run lint:style --fix Signed-off-by: Suguru Hirahara * Set row-gap between image and caption Signed-off-by: Suguru Hirahara * Set column-gap between the caption and close button Signed-off-by: Suguru Hirahara * Rename the class name Signed-off-by: Suguru Hirahara * Decrease inline spacing Signed-off-by: Suguru Hirahara --- res/css/views/rooms/_LinkPreviewWidget.scss | 87 ++++++++++--------- .../views/rooms/LinkPreviewWidget.tsx | 22 ++--- 2 files changed, 60 insertions(+), 49 deletions(-) diff --git a/res/css/views/rooms/_LinkPreviewWidget.scss b/res/css/views/rooms/_LinkPreviewWidget.scss index 1546164710e..e274994a532 100644 --- a/res/css/views/rooms/_LinkPreviewWidget.scss +++ b/res/css/views/rooms/_LinkPreviewWidget.scss @@ -15,53 +15,62 @@ limitations under the License. */ .mx_LinkPreviewWidget { - margin-top: 15px; - margin-right: 15px; - margin-bottom: 15px; + margin: $spacing-16 0 $spacing-16 auto; display: flex; - border-left: 2px solid $preview-widget-bar-color; + column-gap: $spacing-4; + border-inline-start: 2px solid $preview-widget-bar-color; border-radius: 2px; color: $info-plinth-fg-color; -} -.mx_LinkPreviewWidget_image { - flex: 0 0 100px; - margin-left: 15px; - text-align: center; - cursor: pointer; -} + .mx_MatrixChat_useCompactLayout & { + margin-top: 6px; + margin-bottom: 6px; + } -.mx_LinkPreviewWidget_caption { - margin-left: 15px; - flex: 1 1 auto; - overflow: hidden; // cause it to wrap rather than clip -} + // Exclude mx_LinkPreviewGroup_hide from wrapping + .mx_LinkPreviewWidget_wrapImageCaption { + display: flex; + flex-wrap: wrap; + row-gap: $spacing-8; -.mx_LinkPreviewWidget_title { - font-weight: bold; - white-space: normal; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; + .mx_LinkPreviewWidget_image, + .mx_LinkPreviewWidget_caption { + margin-inline-start: $spacing-16; + min-width: 0; // Prevent blowout + } - .mx_LinkPreviewWidget_siteName { - font-weight: normal; - } -} + .mx_LinkPreviewWidget_image { + flex: 0 0 100px; + text-align: center; + cursor: pointer; + } -.mx_LinkPreviewWidget_description { - margin-top: 8px; - white-space: normal; - word-wrap: break-word; - display: -webkit-box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; -} + .mx_LinkPreviewWidget_caption { + flex: 1; + overflow: hidden; // cause it to wrap rather than clip + } -.mx_MatrixChat_useCompactLayout { - .mx_LinkPreviewWidget { - margin-top: 6px; - margin-bottom: 6px; + .mx_LinkPreviewWidget_title, + .mx_LinkPreviewWidget_description { + display: -webkit-box; + -webkit-box-orient: vertical; + overflow: hidden; + white-space: normal; + } + + .mx_LinkPreviewWidget_title { + font-weight: bold; + -webkit-line-clamp: 2; + + .mx_LinkPreviewWidget_siteName { + font-weight: normal; + } + } + + .mx_LinkPreviewWidget_description { + margin-top: $spacing-8; + word-wrap: break-word; + -webkit-line-clamp: 3; + } } } diff --git a/src/components/views/rooms/LinkPreviewWidget.tsx b/src/components/views/rooms/LinkPreviewWidget.tsx index d14c504dd8c..cb28739f179 100644 --- a/src/components/views/rooms/LinkPreviewWidget.tsx +++ b/src/components/views/rooms/LinkPreviewWidget.tsx @@ -120,16 +120,18 @@ export default class LinkPreviewWidget extends React.Component { return (
- { img } -
-
- { p["og:title"] } - { p["og:site_name"] && - { (" - " + p["og:site_name"]) } - } -
-
- { description } +
+ { img } +
+
+ { p["og:title"] } + { p["og:site_name"] && + { (" - " + p["og:site_name"]) } + } +
+
+ { description } +
{ this.props.children } From 4b17307d792aae5eadf2a95394b9e4e6cc80ac04 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Mon, 4 Jul 2022 15:20:18 +0900 Subject: [PATCH 010/162] Tidy up _FilePanel.scss (#8953) * Include in mx_FilePanel Signed-off-by: Suguru Hirahara * Include mx_RoomView_MessageList * and mx_EventTile * Signed-off-by: Suguru Hirahara * yarn run lint:style --fix Signed-off-by: Suguru Hirahara * &:not([data-layout=bubble]) Signed-off-by: Suguru Hirahara * yarn run lint:style --fix Signed-off-by: Suguru Hirahara * Variables, logical properties, etc. Signed-off-by: Suguru Hirahara * Cancel the left stroke of the event tile Signed-off-by: Suguru Hirahara * Add comment to indicate that text-align of MessageTimestamp is not effective Signed-off-by: Suguru Hirahara --- res/css/structures/_FilePanel.scss | 171 +++++++++++++++-------------- 1 file changed, 91 insertions(+), 80 deletions(-) diff --git a/res/css/structures/_FilePanel.scss b/res/css/structures/_FilePanel.scss index d863702d5c5..3119162e268 100644 --- a/res/css/structures/_FilePanel.scss +++ b/res/css/structures/_FilePanel.scss @@ -19,101 +19,112 @@ limitations under the License. flex: 1 1 0; overflow-y: auto; display: flex; -} -.mx_FilePanel .mx_RoomView_messageListWrapper { - flex-direction: row; - align-items: center; - justify-content: center; -} + .mx_RoomView_messageListWrapper { + flex-direction: row; + align-items: center; + justify-content: center; + } -.mx_FilePanel .mx_RoomView_MessageList { - width: 100%; -} + .mx_RoomView_MessageList { + width: 100%; -.mx_FilePanel .mx_RoomView_MessageList h2 { - display: none; -} + h2 { + display: none; + } + } -/* FIXME: rather than having EventTile's default CSS be for MessagePanel, + /* FIXME: rather than having EventTile's default CSS be for MessagePanel, we should make EventTile a base CSS class and customise it specifically for usage in {Message,File,Notification}Panel. */ -.mx_FilePanel .mx_EventTile_avatar { - display: none; -} - -/* Overrides for the attachment body tiles */ - -.mx_FilePanel .mx_EventTile:not([data-layout=bubble]) { - word-break: break-word; - margin-top: 10px; - padding-top: 0; - - .mx_EventTile_line { - padding-left: 0; + .mx_EventTile_avatar { + display: none; } -} - -.mx_FilePanel .mx_EventTile .mx_MFileBody { - line-height: 2.4rem; -} - -.mx_FilePanel .mx_EventTile .mx_MFileBody_download { - padding-top: 8px; - display: flex; - font-size: $font-14px; - color: $event-timestamp-color; -} -.mx_FilePanel .mx_EventTile .mx_MFileBody_downloadLink { - flex: 1 1 auto; - color: $light-fg-color; -} - -.mx_FilePanel .mx_EventTile .mx_MImageBody_size { - flex: 1 0 0; - font-size: $font-14px; - text-align: right; - white-space: nowrap; -} - -/* Overides for the sender details line */ - -.mx_FilePanel .mx_EventTile_senderDetails { - display: flex; - margin-top: -2px; -} + .mx_EventTile { + /* Overrides for the attachment body tiles */ + &:not([data-layout=bubble]) { + word-break: break-word; + margin-top: 10px; + padding-top: 0; + + .mx_EventTile_line { + padding-inline-start: 0; + } + + &:hover { + &.mx_EventTile_verified, + &.mx_EventTile_unverified, + &.mx_EventTile_unknown { + .mx_EventTile_line { + box-shadow: none; + } + } + } + } + + .mx_MFileBody { + line-height: 2.4rem; + } + + .mx_MFileBody_download { + padding-top: $spacing-8; + display: flex; + font-size: $font-14px; + color: $event-timestamp-color; + } + + .mx_MFileBody_downloadLink { + flex: 1 1 auto; + color: $light-fg-color; + } + + .mx_MImageBody_size { + flex: 1 0 0; + font-size: $font-14px; + text-align: right; + white-space: nowrap; + } + + .mx_DisambiguatedProfile { + flex: 1 1 auto; + line-height: initial; + opacity: 1.0; + color: $event-timestamp-color; + } + + .mx_MessageTimestamp { + flex: 1 0 0; + text-align: right; // FIXME: .mx_EventTile:not([data-layout=bubble]) .mx_MessageTimestamp + visibility: visible; + position: initial; + font-size: $font-14px; + opacity: 1.0; + } + } -.mx_FilePanel .mx_EventTile_senderDetailsLink { - text-decoration: none; -} + /* Overides for the sender details line */ -.mx_FilePanel .mx_EventTile .mx_DisambiguatedProfile { - flex: 1 1 auto; - line-height: initial; - opacity: 1.0; - color: $event-timestamp-color; -} + .mx_EventTile_senderDetails { + display: flex; + margin-top: -2px; + } -.mx_FilePanel .mx_EventTile .mx_MessageTimestamp { - flex: 1 0 0; - text-align: right; - visibility: visible; - position: initial; - font-size: $font-14px; - opacity: 1.0; -} + .mx_EventTile_senderDetailsLink { + text-decoration: none; + } -/* Overrides for the wrappers around the body tile */ + /* Overrides for the wrappers around the body tile */ -.mx_FilePanel .mx_EventTile_line { - margin-right: 0px; - padding-left: 0px; -} + .mx_EventTile_line { + margin-inline-end: 0; + padding-inline-start: 0; -.mx_FilePanel .mx_EventTile_selected .mx_EventTile_line { - padding-left: 0px; + .mx_EventTile_selected & { + padding-inline-start: 0; + } + } } .mx_FilePanel_empty::before { From 7cf0f2d66d1c39c4ae3d655b5969ffde6f1129a4 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Mon, 4 Jul 2022 15:27:22 +0900 Subject: [PATCH 011/162] Add top padding to EventTilePreview loader (#8977) Signed-off-by: Suguru Hirahara --- res/css/views/elements/_EventTilePreview.scss | 6 ++++-- res/css/views/settings/_FontScalingPanel.scss | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/res/css/views/elements/_EventTilePreview.scss b/res/css/views/elements/_EventTilePreview.scss index 1f2df50f28e..67bed16ea8e 100644 --- a/res/css/views/elements/_EventTilePreview.scss +++ b/res/css/views/elements/_EventTilePreview.scss @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_FontScalingPanel_preview.mx_EventTilePreview_loader { - padding: 9px 0; +.mx_FontScalingPanel { + .mx_FontScalingPanel_preview.mx_EventTilePreview_loader { + padding: var(--FontScalingPanel_preview-padding-block) 0; + } } diff --git a/res/css/views/settings/_FontScalingPanel.scss b/res/css/views/settings/_FontScalingPanel.scss index a4f58a00047..7eb1c5a2964 100644 --- a/res/css/views/settings/_FontScalingPanel.scss +++ b/res/css/views/settings/_FontScalingPanel.scss @@ -23,9 +23,11 @@ limitations under the License. } .mx_FontScalingPanel_preview { + --FontScalingPanel_preview-padding-block: 9px; + border: 1px solid $quinary-content; border-radius: 10px; - padding: 0 $spacing-16 9px $spacing-16; + padding: 0 $spacing-16 var(--FontScalingPanel_preview-padding-block) $spacing-16; pointer-events: none; display: flow-root; From 352df7ddc7ed69311488545cb8aeb61d6b1f283b Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Mon, 4 Jul 2022 15:37:31 +0900 Subject: [PATCH 012/162] Move mx_EventTile_contextual out of mx_EventTile:not([data-layout=bubble]) (#8974) * Move mx_EventTile_contextual out of mx_EventTile:not([data-layout=bubble]) Signed-off-by: Suguru Hirahara * Empty commit Signed-off-by: Suguru Hirahara --- res/css/views/rooms/_EventBubbleTile.scss | 5 ----- res/css/views/rooms/_EventTile.scss | 10 ++++++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index 1717c611e2a..52c115891a4 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -44,11 +44,6 @@ limitations under the License. display: none; } } - - // Mirror rough designs for "greyed out" text - &.mx_EventTile_contextual .mx_EventTile_line { - opacity: 0.4; - } } } diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index e34347621f8..29968ed41fc 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -106,6 +106,12 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss } } + .mx_RoomView_searchResultsPanel & { + &.mx_EventTile_contextual { + opacity: 0.4; + } + } + &.mx_EventTile_highlight, &.mx_EventTile_highlight .markdown-body { color: $alert; @@ -299,10 +305,6 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss } } - &.mx_EventTile_contextual { - opacity: 0.4; - } - .mx_EventTile_msgOption { float: right; text-align: right; From a4701ccff1a9a9232842a903cb4e9e150d9064bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 4 Jul 2022 09:11:06 +0200 Subject: [PATCH 013/162] Dismiss the search dialogue when starting a DM (#8967) --- src/components/views/dialogs/spotlight/SpotlightDialog.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index f633ca66ddb..0bdb5b1cc9c 100644 --- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx +++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx @@ -529,6 +529,7 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n key={`${Section[result.section]}-${result.member.userId}`} onClick={() => { startDm(cli, [result.member]); + onFinished(); }} > From 6543f6a7d17326e92da3b5214486c19fea67580f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 4 Jul 2022 09:11:28 +0200 Subject: [PATCH 014/162] Link to cypress tests rather than the old E2E tests (#8970) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d44d9ff6e1b..775487f42bc 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,6 @@ Now the yarn commands should work as normal. ### End-to-End tests Make sure you've got your Element development server running (by doing `yarn -start` in element-web), and then in this project, run `yarn run e2etests`. See -[`test/end-to-end-tests/README.md`](https://github.com/matrix-org/matrix-react-sdk/blob/develop/test/end-to-end-tests/README.md) +start` in element-web), and then in this project, run `yarn run test:cypress`. See +[`docs/cypress.md`](https://github.com/matrix-org/matrix-react-sdk/blob/develop/docs/cypress.md) for more information. From 0026e0462b3f459b3ab75068d8254252039630c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 4 Jul 2022 11:09:08 +0200 Subject: [PATCH 015/162] Fix resizing room topic (#8966) --- res/css/views/settings/_ProfileSettings.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/settings/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss index a9e80880aac..f543a12f29f 100644 --- a/res/css/views/settings/_ProfileSettings.scss +++ b/res/css/views/settings/_ProfileSettings.scss @@ -39,8 +39,8 @@ limitations under the License. resize: vertical; } - &.mx_ProfileSettings_profile_controls_topic--room { - height: 4em; + &.mx_ProfileSettings_profile_controls_topic--room textarea { + min-height: 4em; } } From ed9207104686e7933ffe23c9b1933297e6833a3c Mon Sep 17 00:00:00 2001 From: Kerry Date: Mon, 4 Jul 2022 16:05:55 +0200 Subject: [PATCH 016/162] Live location share - open latest location in map site (#8981) * move getForwardableBeacon to beacon utils * move event transform type up * add helper to get shareable-as-locaion events * use getShareableLocationEvent in MessageContextMenu * test opening in maplink * fix bad copy pasted tests --- .../context_menus/MessageContextMenu.tsx | 11 +-- src/events/forward/getForwardableEvent.ts | 7 +- src/events/forward/types.ts | 2 +- src/events/index.ts | 18 ++++ .../location/getShareableLocationEvent.ts | 36 ++++++++ src/events/types.ts | 19 ++++ .../beacon/getShareableLocation.ts} | 15 ++-- .../context_menus/MessageContextMenu-test.tsx | 45 +++++++++- .../forward/getForwardableEvent-test.ts | 87 +++++++++++++++++++ .../getShareableLocationEvent-test.ts | 87 +++++++++++++++++++ 10 files changed, 310 insertions(+), 17 deletions(-) create mode 100644 src/events/index.ts create mode 100644 src/events/location/getShareableLocationEvent.ts create mode 100644 src/events/types.ts rename src/{events/forward/getForwardableBeacon.ts => utils/beacon/getShareableLocation.ts} (69%) create mode 100644 test/events/forward/getForwardableEvent-test.ts create mode 100644 test/events/location/getShareableLocationEvent-test.ts diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 197092ca69c..11e173975dd 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -35,7 +35,6 @@ import { canPinEvent, editEvent, isContentActionable, - isLocationEvent, } from '../../../utils/EventUtils'; import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from './IconizedContextMenu'; import { ReadPinsEventId } from "../right_panel/types"; @@ -58,6 +57,7 @@ import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwa import { OpenReportEventDialogPayload } from "../../../dispatcher/payloads/OpenReportEventDialogPayload"; import { createMapSiteLinkFromEvent } from '../../../utils/location'; import { getForwardableEvent } from '../../../events/forward/getForwardableEvent'; +import { getShareableLocationEvent } from '../../../events/location/getShareableLocationEvent'; interface IProps extends IPosition { chevronFace: ChevronFace; @@ -145,10 +145,6 @@ export default class MessageContextMenu extends React.Component return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId()); } - private canOpenInMapSite(mxEvent: MatrixEvent): boolean { - return isLocationEvent(mxEvent); - } - private canEndPoll(mxEvent: MatrixEvent): boolean { return ( M_POLL_START.matches(mxEvent.getType()) && @@ -369,8 +365,9 @@ export default class MessageContextMenu extends React.Component } let openInMapSiteButton: JSX.Element; - if (this.canOpenInMapSite(mxEvent)) { - const mapSiteLink = createMapSiteLinkFromEvent(mxEvent); + const shareableLocationEvent = getShareableLocationEvent(mxEvent, cli); + if (shareableLocationEvent) { + const mapSiteLink = createMapSiteLinkFromEvent(shareableLocationEvent); openInMapSiteButton = ( MatrixEvent | null; +export type ActionableEventTransformFunction = (event: MatrixEvent, cli: MatrixClient) => MatrixEvent | null; diff --git a/src/events/index.ts b/src/events/index.ts new file mode 100644 index 00000000000..67ebedbb4d3 --- /dev/null +++ b/src/events/index.ts @@ -0,0 +1,18 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export { getForwardableEvent } from './forward/getForwardableEvent'; +export { getShareableLocationEvent } from './location/getShareableLocationEvent'; diff --git a/src/events/location/getShareableLocationEvent.ts b/src/events/location/getShareableLocationEvent.ts new file mode 100644 index 00000000000..09b84dbf8fe --- /dev/null +++ b/src/events/location/getShareableLocationEvent.ts @@ -0,0 +1,36 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon"; +import { MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix"; + +import { getShareableLocationEventForBeacon } from "../../utils/beacon/getShareableLocation"; +import { isLocationEvent } from "../../utils/EventUtils"; + +/** + * Get event that is shareable as a location + * If an event does not have a shareable location, return null + */ +export const getShareableLocationEvent = (event: MatrixEvent, cli: MatrixClient): MatrixEvent | null => { + if (isLocationEvent(event)) { + return event; + } + + if (M_BEACON_INFO.matches(event.getType())) { + return getShareableLocationEventForBeacon(event, cli); + } + return null; +}; diff --git a/src/events/types.ts b/src/events/types.ts new file mode 100644 index 00000000000..f30b3144814 --- /dev/null +++ b/src/events/types.ts @@ -0,0 +1,19 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; + +export type ActionableEventTransformFunction = (event: MatrixEvent, cli: MatrixClient) => MatrixEvent | null; diff --git a/src/events/forward/getForwardableBeacon.ts b/src/utils/beacon/getShareableLocation.ts similarity index 69% rename from src/events/forward/getForwardableBeacon.ts rename to src/utils/beacon/getShareableLocation.ts index 600937dce62..b2a63db0607 100644 --- a/src/events/forward/getForwardableBeacon.ts +++ b/src/utils/beacon/getShareableLocation.ts @@ -14,15 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix"; - -import { ForwardableEventTransformFunction } from "./types"; +import { + MatrixClient, + MatrixEvent, + getBeaconInfoIdentifier, +} from "matrix-js-sdk/src/matrix"; /** - * Live location beacons should forward their latest location as a static pin location - * If the beacon is not live, or doesn't have a location forwarding is not allowed + * Beacons should only have shareable locations (open in external mapping tool, forward) + * when they are live and have a location + * If not live, returns null */ -export const getForwardableBeaconEvent: ForwardableEventTransformFunction = (event, cli) => { +export const getShareableLocationEventForBeacon = (event: MatrixEvent, cli: MatrixClient): MatrixEvent | null => { const room = cli.getRoom(event.getRoomId()); const beacon = room.currentState.beacons?.get(getBeaconInfoIdentifier(event)); const latestLocationEvent = beacon?.latestLocationEvent; diff --git a/test/components/views/context_menus/MessageContextMenu-test.tsx b/test/components/views/context_menus/MessageContextMenu-test.tsx index 2b56f9a629b..9a8699b2784 100644 --- a/test/components/views/context_menus/MessageContextMenu-test.tsx +++ b/test/components/views/context_menus/MessageContextMenu-test.tsx @@ -36,7 +36,7 @@ import { IRoomState } from "../../../../src/components/structures/RoomView"; import { canEditContent } from "../../../../src/utils/EventUtils"; import { copyPlaintext, getSelectedText } from "../../../../src/utils/strings"; import MessageContextMenu from "../../../../src/components/views/context_menus/MessageContextMenu"; -import { makeBeaconEvent, makeBeaconInfoEvent, stubClient } from '../../../test-utils'; +import { makeBeaconEvent, makeBeaconInfoEvent, makeLocationEvent, stubClient } from '../../../test-utils'; import dispatcher from '../../../../src/dispatcher/dispatcher'; import SettingsStore from '../../../../src/settings/SettingsStore'; import { ReadPinsEventId } from '../../../../src/components/views/right_panel/types'; @@ -308,6 +308,49 @@ describe('MessageContextMenu', () => { }); }); + describe('open as map link', () => { + it('does not allow opening a plain message in open street maps', () => { + const eventContent = MessageEvent.from("hello"); + const menu = createMenuWithContent(eventContent); + expect(menu.find('a[aria-label="Open in OpenStreetMap"]')).toHaveLength(0); + }); + + it('does not allow opening a beacon that does not have a shareable location event', () => { + const deadBeaconEvent = makeBeaconInfoEvent('@alice', roomId, { isLive: false }); + const beacon = new Beacon(deadBeaconEvent); + const beacons = new Map(); + beacons.set(getBeaconInfoIdentifier(deadBeaconEvent), beacon); + const menu = createMenu(deadBeaconEvent, {}, {}, beacons); + expect(menu.find('a[aria-label="Open in OpenStreetMap"]')).toHaveLength(0); + }); + + it('allows opening a location event in open street map', () => { + const locationEvent = makeLocationEvent('geo:50,50'); + const menu = createMenu(locationEvent); + // exists with a href with the lat/lon from the location event + expect( + menu.find('a[aria-label="Open in OpenStreetMap"]').at(0).props().href, + ).toEqual('https://www.openstreetmap.org/?mlat=50&mlon=50#map=16/50/50'); + }); + + it('allows opening a beacon that has a shareable location event', () => { + const liveBeaconEvent = makeBeaconInfoEvent('@alice', roomId, { isLive: true }); + const beaconLocation = makeBeaconEvent( + '@alice', { beaconInfoId: liveBeaconEvent.getId(), geoUri: 'geo:51,41' }, + ); + const beacon = new Beacon(liveBeaconEvent); + // @ts-ignore illegally set private prop + beacon._latestLocationEvent = beaconLocation; + const beacons = new Map(); + beacons.set(getBeaconInfoIdentifier(liveBeaconEvent), beacon); + const menu = createMenu(liveBeaconEvent, {}, {}, beacons); + // exists with a href with the lat/lon from the location event + expect( + menu.find('a[aria-label="Open in OpenStreetMap"]').at(0).props().href, + ).toEqual('https://www.openstreetmap.org/?mlat=51&mlon=41#map=16/51/41'); + }); + }); + describe("right click", () => { it('copy button does work as expected', () => { const text = "hello"; diff --git a/test/events/forward/getForwardableEvent-test.ts b/test/events/forward/getForwardableEvent-test.ts new file mode 100644 index 00000000000..2985f527bc1 --- /dev/null +++ b/test/events/forward/getForwardableEvent-test.ts @@ -0,0 +1,87 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { + EventType, + MatrixEvent, + MsgType, +} from "matrix-js-sdk/src/matrix"; + +import { getForwardableEvent } from "../../../src/events"; +import { + getMockClientWithEventEmitter, + makeBeaconEvent, + makeBeaconInfoEvent, + makePollStartEvent, + makeRoomWithBeacons, +} from "../../test-utils"; + +describe('getForwardableEvent()', () => { + const userId = '@alice:server.org'; + const roomId = '!room:server.org'; + const client = getMockClientWithEventEmitter({ + getRoom: jest.fn(), + }); + + it('returns the event for a room message', () => { + const alicesMessageEvent = new MatrixEvent({ + type: EventType.RoomMessage, + sender: userId, + room_id: roomId, + content: { + msgtype: MsgType.Text, + body: 'Hello', + }, + }); + + expect(getForwardableEvent(alicesMessageEvent, client)).toBe(alicesMessageEvent); + }); + + it('returns null for a poll start event', () => { + const pollStartEvent = makePollStartEvent('test?', userId); + + expect(getForwardableEvent(pollStartEvent, client)).toBe(null); + }); + + describe('beacons', () => { + it('returns null for a beacon that is not live', () => { + const notLiveBeacon = makeBeaconInfoEvent(userId, roomId, { isLive: false }); + makeRoomWithBeacons(roomId, client, [notLiveBeacon]); + + expect(getForwardableEvent(notLiveBeacon, client)).toBe(null); + }); + + it('returns null for a live beacon that does not have a location', () => { + const liveBeacon = makeBeaconInfoEvent(userId, roomId, { isLive: true }); + makeRoomWithBeacons(roomId, client, [liveBeacon]); + + expect(getForwardableEvent(liveBeacon, client)).toBe(null); + }); + + it('returns the latest location event for a live beacon with location', () => { + const liveBeacon = makeBeaconInfoEvent(userId, roomId, { isLive: true }, 'id'); + const locationEvent = makeBeaconEvent(userId, { + beaconInfoId: liveBeacon.getId(), + geoUri: 'geo:52,42', + // make sure its in live period + timestamp: Date.now() + 1, + }); + makeRoomWithBeacons(roomId, client, [liveBeacon], [locationEvent]); + + expect(getForwardableEvent(liveBeacon, client)).toBe(locationEvent); + }); + }); +}); diff --git a/test/events/location/getShareableLocationEvent-test.ts b/test/events/location/getShareableLocationEvent-test.ts new file mode 100644 index 00000000000..fe2d83174ca --- /dev/null +++ b/test/events/location/getShareableLocationEvent-test.ts @@ -0,0 +1,87 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { + EventType, + MatrixEvent, + MsgType, +} from "matrix-js-sdk/src/matrix"; + +import { getShareableLocationEvent } from "../../../src/events"; +import { + getMockClientWithEventEmitter, + makeBeaconEvent, + makeBeaconInfoEvent, + makeLocationEvent, + makeRoomWithBeacons, +} from "../../test-utils"; + +describe('getShareableLocationEvent()', () => { + const userId = '@alice:server.org'; + const roomId = '!room:server.org'; + const client = getMockClientWithEventEmitter({ + getRoom: jest.fn(), + }); + + it('returns null for a non-location event', () => { + const alicesMessageEvent = new MatrixEvent({ + type: EventType.RoomMessage, + sender: userId, + room_id: roomId, + content: { + msgtype: MsgType.Text, + body: 'Hello', + }, + }); + + expect(getShareableLocationEvent(alicesMessageEvent, client)).toBe(null); + }); + + it('returns the event for a location event', () => { + const locationEvent = makeLocationEvent('geo:52,42'); + + expect(getShareableLocationEvent(locationEvent, client)).toBe(locationEvent); + }); + + describe('beacons', () => { + it('returns null for a beacon that is not live', () => { + const notLiveBeacon = makeBeaconInfoEvent(userId, roomId, { isLive: false }); + makeRoomWithBeacons(roomId, client, [notLiveBeacon]); + + expect(getShareableLocationEvent(notLiveBeacon, client)).toBe(null); + }); + + it('returns null for a live beacon that does not have a location', () => { + const liveBeacon = makeBeaconInfoEvent(userId, roomId, { isLive: true }); + makeRoomWithBeacons(roomId, client, [liveBeacon]); + + expect(getShareableLocationEvent(liveBeacon, client)).toBe(null); + }); + + it('returns the latest location event for a live beacon with location', () => { + const liveBeacon = makeBeaconInfoEvent(userId, roomId, { isLive: true }, 'id'); + const locationEvent = makeBeaconEvent(userId, { + beaconInfoId: liveBeacon.getId(), + geoUri: 'geo:52,42', + // make sure its in live period + timestamp: Date.now() + 1, + }); + makeRoomWithBeacons(roomId, client, [liveBeacon], [locationEvent]); + + expect(getShareableLocationEvent(liveBeacon, client)).toBe(locationEvent); + }); + }); +}); From 32eb853bbb4eea71e284a2498f412e7d441fc092 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Mon, 4 Jul 2022 19:00:45 +0000 Subject: [PATCH 017/162] Fix clickable area of general event list summary toggle (#8979) * Fix toggle of expanded generic event list summary on bubble layout Signed-off-by: Suguru Hirahara * Margin settings Signed-off-by: Suguru Hirahara * Remove redundant declarations Signed-off-by: Suguru Hirahara * Remove redundant declaration Signed-off-by: Suguru Hirahara --- res/css/views/elements/_GenericEventListSummary.scss | 11 ++++------- .../views/elements/GenericEventListSummary.tsx | 7 ++++++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/res/css/views/elements/_GenericEventListSummary.scss b/res/css/views/elements/_GenericEventListSummary.scss index 2924af12cfc..243506a447b 100644 --- a/res/css/views/elements/_GenericEventListSummary.scss +++ b/res/css/views/elements/_GenericEventListSummary.scss @@ -67,14 +67,16 @@ limitations under the License. } .mx_GenericEventListSummary_toggle { - margin: 0 55px 0 5px; + margin-block: 0; + margin-inline-end: 55px; &[aria-expanded=false] { order: 9; + margin-inline-start: 5px; } &[aria-expanded=true] { - text-align: right; + margin-inline-start: auto; // reduce clickable area } } @@ -109,11 +111,6 @@ limitations under the License. cursor: pointer; } -.mx_GenericEventListSummary_toggle { - color: $accent; - cursor: pointer; -} - .mx_GenericEventListSummary_line { border-bottom: 1px solid $primary-hairline-color; margin-left: 63px; diff --git a/src/components/views/elements/GenericEventListSummary.tsx b/src/components/views/elements/GenericEventListSummary.tsx index 5a61cb04009..ddd6bdb9ec5 100644 --- a/src/components/views/elements/GenericEventListSummary.tsx +++ b/src/components/views/elements/GenericEventListSummary.tsx @@ -119,7 +119,12 @@ const GenericEventListSummary: React.FC = ({ data-layout={layout} data-testid={testId} > - + { expanded ? _t('collapse') : _t('expand') } { body } From 0bf5d54041cb3a6cac03d1b9e4e1a2212d1cf2c5 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Mon, 4 Jul 2022 19:07:50 +0000 Subject: [PATCH 018/162] Improve style rules for thread summary (#8868) * Use mixin ThreadSummaryIcon Signed-off-by: Suguru Hirahara * Tidy mx_ThreadSummary Signed-off-by: Suguru Hirahara * Move style blocks from _EventTile.scss to _ThreadSummary.scss Signed-off-by: Suguru Hirahara * Merge mx_ThreadSummaryIcon::before Signed-off-by: Suguru Hirahara * From threads amount to replies amount Signed-off-by: Suguru Hirahara * Remove obsolete declaration and class names mixin ThreadSummaryIcon has "background-color: $secondary-content !important" Signed-off-by: Suguru Hirahara * Move mx_ThreadPanel_replies::before from _ThreadSummary to _EventTile.scss Signed-off-by: Suguru Hirahara * Rename mx_ThreadSummaryIcon to mx_ThreadSummary_icon Signed-off-by: Suguru Hirahara * Use variables and remove obsolete one Signed-off-by: Suguru Hirahara * Merge style rules, renaming a variable Signed-off-by: Suguru Hirahara * Include mx_MessagePanel_narrow in mx_ThreadSummary Signed-off-by: Suguru Hirahara * Remove a redundant declaration Signed-off-by: Suguru Hirahara * Use a variable Signed-off-by: Suguru Hirahara * Include mx_ThreadSummary_sender and mx_ThreadSummary_content in mx_ThreadSummary Expected according to tests Signed-off-by: Suguru Hirahara * Remove a variable used only once Signed-off-by: Suguru Hirahara * Ensure the same line-height is applied Signed-off-by: Suguru Hirahara * Remove !important Signed-off-by: Suguru Hirahara --- res/css/_common.scss | 11 +-- res/css/views/right_panel/_ThreadPanel.scss | 8 +- res/css/views/rooms/_EventTile.scss | 40 ++------- res/css/views/rooms/_ThreadSummary.scss | 95 +++++++++++++------- src/components/views/rooms/EventTile.tsx | 4 +- src/components/views/rooms/ThreadSummary.tsx | 2 +- 6 files changed, 81 insertions(+), 79 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index 1e8daf3ba30..e04bf616f93 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -775,25 +775,22 @@ legend { } } -@define-mixin ThreadsAmount { - $threadInfoLineHeight: calc(2 * $font-12px); - +@define-mixin ThreadRepliesAmount { color: $secondary-content; font-weight: $font-semi-bold; - line-height: $threadInfoLineHeight; white-space: nowrap; position: relative; padding: 0 $spacing-12 0 $spacing-8; } -@define-mixin ThreadInfoIcon { +@define-mixin ThreadSummaryIcon { content: ""; display: inline-block; mask-image: url('$(res)/img/element-icons/thread-summary.svg'); mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; height: 18px; min-width: 18px; background-color: $secondary-content !important; - mask-repeat: no-repeat; - mask-size: contain; } diff --git a/res/css/views/right_panel/_ThreadPanel.scss b/res/css/views/right_panel/_ThreadPanel.scss index ca0b4409cae..d83b51272e9 100644 --- a/res/css/views/right_panel/_ThreadPanel.scss +++ b/res/css/views/right_panel/_ThreadPanel.scss @@ -208,15 +208,9 @@ limitations under the License. border-radius: 50%; &::after { - content: ""; + @mixin ThreadSummaryIcon; width: inherit; height: inherit; - mask-image: url('$(res)/img/element-icons/thread-summary.svg'); - mask-position: center; - display: inline-block; - mask-repeat: no-repeat; - mask-size: contain; - background-color: $secondary-content; } } diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 29968ed41fc..89f25f5d61e 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -16,7 +16,6 @@ limitations under the License. */ $left-gutter: 64px; -$threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss .mx_EventTile { --EventTile_content-margin-inline-end: 34px; // TODO: Use a spacing variable @@ -24,6 +23,7 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss --EventTile_group_line-spacing-block-end: 3px; --EventTile_group_line-spacing-inline-start: $left-gutter; --EventTile_group_line-line-height: $font-22px; + --EventTile_ThreadSummary-line-height: calc(2 * $font-12px); flex-shrink: 0; @@ -204,7 +204,7 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss } .mx_ThreadSummary, - .mx_ThreadSummaryIcon { + .mx_ThreadSummary_icon { margin-left: $left-gutter; } @@ -484,7 +484,7 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss .mx_EventTile[data-layout=group] { .mx_ThreadSummary, - .mx_ThreadSummaryIcon, + .mx_ThreadSummary_icon, .mx_EventTile_line { /* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */ margin-right: 80px; @@ -722,33 +722,6 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss } } -.mx_ThreadPanel_replies::before, -.mx_ThreadSummaryIcon::before, -.mx_ThreadSummary::before { - @mixin ThreadInfoIcon; - background-color: $secondary-content !important; -} - -.mx_ThreadSummaryIcon { - display: inline-block; - font-size: $font-12px; - color: $secondary-content !important; - margin-top: 8px; - margin-bottom: 8px; - - &::before { - vertical-align: middle; - margin-right: 8px; - margin-top: -2px; - } -} - -.mx_MessagePanel_narrow .mx_ThreadSummary { - min-width: initial; - max-width: 100%; // prevent overflow - width: initial; -} - .mx_EventTile[data-shape=ThreadsList] { --topOffset: $spacing-12; --leftOffset: 48px; @@ -864,8 +837,13 @@ $threadInfoLineHeight: calc(2 * $font-12px); // See: _commons.scss align-items: center; position: relative; + &::before { + @mixin ThreadSummaryIcon; + } + .mx_ThreadPanel_replies_amount { - @mixin ThreadsAmount; + @mixin ThreadRepliesAmount; + line-height: var(--EventTile_ThreadSummary-line-height); font-size: $font-12px; // Same font size as the counter on the main panel } } diff --git a/res/css/views/rooms/_ThreadSummary.scss b/res/css/views/rooms/_ThreadSummary.scss index 11ff6cdbbe3..a1821c5a591 100644 --- a/res/css/views/rooms/_ThreadSummary.scss +++ b/res/css/views/rooms/_ThreadSummary.scss @@ -14,26 +14,35 @@ See the License for the specific language governing permissions and limitations under the License. */ -$left-gutter: 64px; // From _EventTile.scss -$threadSummaryLineHeight: calc(2 * $font-12px); +.mx_ThreadSummary, +.mx_ThreadSummary_content, +.mx_ThreadSummary_icon { + font-size: $font-12px; +} + +.mx_ThreadSummary, +.mx_ThreadSummary_content { + color: $secondary-content; +} + +.mx_ThreadSummary, +.mx_ThreadSummary_icon { + margin-top: $spacing-8; +} .mx_ThreadSummary { min-width: 267px; - max-width: min(calc(100% - $left-gutter), 600px); // leave space on both left & right gutters + max-width: min(calc(100% - var(--EventTile_group_line-spacing-inline-start)), 600px); // leave space on both left & right gutters width: fit-content; height: 40px; position: relative; background-color: $system; - padding-left: $spacing-12; + padding-inline: $spacing-12 $spacing-16; display: flex; align-items: center; + justify-content: flex-start; border-radius: 8px; - padding-right: $spacing-16; - margin-top: $spacing-8; - font-size: $font-12px; - color: $secondary-content; box-sizing: border-box; - justify-content: flex-start; clear: both; overflow: hidden; border: 1px solid $system; // always render a border so the hover effect doesn't require a re-layout @@ -70,7 +79,6 @@ $threadSummaryLineHeight: calc(2 * $font-12px); &:hover, &:focus { - cursor: pointer; border-color: $quinary-content; .mx_ThreadSummary_chevron { @@ -80,34 +88,59 @@ $threadSummaryLineHeight: calc(2 * $font-12px); } &::before { + @mixin ThreadSummaryIcon; align-self: center; // v-align the threads icon } -} -// XXX: these classes are re-used outside of the component -.mx_ThreadSummary_ThreadsAmount { - @mixin ThreadsAmount; -} + .mx_ThreadSummary_sender, + .mx_ThreadSummary_content, + .mx_ThreadSummary_replies_amount { + line-height: var(--EventTile_ThreadSummary-line-height); + } -.mx_ThreadSummary_sender { - font-weight: $font-semi-bold; - line-height: $threadSummaryLineHeight; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; -} + .mx_ThreadSummary_sender, + .mx_ThreadSummary_content { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } -.mx_ThreadSummary_content { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - margin-left: $spacing-4; - font-size: $font-12px; - line-height: $threadSummaryLineHeight; - color: $secondary-content; - flex: 1; + .mx_ThreadSummary_sender { + font-weight: $font-semi-bold; + } + + .mx_ThreadSummary_content { + margin-left: $spacing-4; + flex: 1; + } + + .mx_ThreadSummary_replies_amount { + @mixin ThreadRepliesAmount; + } + + .mx_MessagePanel_narrow & { + min-width: initial; + max-width: 100%; // prevent overflow + width: initial; + } } .mx_ThreadSummary_avatar { margin-inline-end: $spacing-8; } + +.mx_ThreadSummary_icon { + display: inline-block; + margin-bottom: $spacing-8; + + &::before { + @mixin ThreadSummaryIcon; + vertical-align: middle; + margin-inline-end: $spacing-8; + margin-top: -2px; + } + + a& { + color: $secondary-content; + } +} diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index bd2d7fb961f..3c5e419d94a 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -533,14 +533,14 @@ export class UnwrappedEventTile extends React.Component { if (this.context.timelineRenderingType === TimelineRenderingType.Search && this.props.mxEvent.threadRootId) { if (this.props.highlightLink) { return ( - + { _t("From a thread") } ); } return ( -

{ _t("From a thread") }

+

{ _t("From a thread") }

); } } diff --git a/src/components/views/rooms/ThreadSummary.tsx b/src/components/views/rooms/ThreadSummary.tsx index 56ed1f9ff87..968727022f9 100644 --- a/src/components/views/rooms/ThreadSummary.tsx +++ b/src/components/views/rooms/ThreadSummary.tsx @@ -58,7 +58,7 @@ const ThreadSummary = ({ mxEvent, thread }: IProps) => { }} aria-label={_t("Open thread")} > - + { countSection } From 88afd552d866cf0d82eda52652a152b84f673189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 4 Jul 2022 21:37:48 +0200 Subject: [PATCH 019/162] Delabs `Show current avatar and name for users in message history` (#8764) Co-authored-by: Travis Ralston --- cypress/global.d.ts | 2 + .../integration/14-timeline/timeline.spec.ts | 145 ++++++++++++++++++ cypress/support/client.ts | 92 ++++++++++- cypress/support/settings.ts | 55 +++++++ src/components/views/avatars/MemberAvatar.tsx | 4 +- .../views/messages/SenderProfile.tsx | 2 +- .../tabs/user/PreferencesUserSettingsTab.tsx | 1 + src/settings/Settings.tsx | 6 +- 8 files changed, 299 insertions(+), 8 deletions(-) create mode 100644 cypress/integration/14-timeline/timeline.spec.ts diff --git a/cypress/global.d.ts b/cypress/global.d.ts index 2cbfff94c25..18f4314d1c1 100644 --- a/cypress/global.d.ts +++ b/cypress/global.d.ts @@ -31,11 +31,13 @@ import type { } from "matrix-js-sdk/src/matrix"; import type { MatrixDispatcher } from "../src/dispatcher/dispatcher"; import type PerformanceMonitor from "../src/performance"; +import type SettingsStore from "../src/settings/SettingsStore"; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface ApplicationWindow { + mxSettingsStore: typeof SettingsStore; mxMatrixClientPeg: { matrixClient?: MatrixClient; }; diff --git a/cypress/integration/14-timeline/timeline.spec.ts b/cypress/integration/14-timeline/timeline.spec.ts new file mode 100644 index 00000000000..22861c8fd70 --- /dev/null +++ b/cypress/integration/14-timeline/timeline.spec.ts @@ -0,0 +1,145 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/// + +import { MessageEvent } from "matrix-events-sdk"; + +import type { ISendEventResponse } from "matrix-js-sdk/src/@types/requests"; +import type { EventType } from "matrix-js-sdk/src/@types/event"; +import type { MatrixClient } from "matrix-js-sdk/src/client"; +import { SynapseInstance } from "../../plugins/synapsedocker"; +import { SettingLevel } from "../../../src/settings/SettingLevel"; +import Chainable = Cypress.Chainable; + +// The avatar size used in the timeline +const AVATAR_SIZE = 30; +// The resize method used in the timeline +const AVATAR_RESIZE_METHOD = "crop"; + +const ROOM_NAME = "Test room"; +const OLD_AVATAR = "avatar_image1"; +const NEW_AVATAR = "avatar_image2"; +const OLD_NAME = "Alan"; +const NEW_NAME = "Alan (away)"; + +const getEventTilesWithBodies = (): Chainable => { + return cy.get(".mx_EventTile").filter((_i, e) => e.getElementsByClassName("mx_EventTile_body").length > 0); +}; + +const expectDisplayName = (e: JQuery, displayName: string): void => { + expect(e.find(".mx_DisambiguatedProfile_displayName").text()).to.equal(displayName); +}; + +const expectAvatar = (e: JQuery, avatarUrl: string): void => { + cy.getClient().then((cli: MatrixClient) => { + expect(e.find(".mx_BaseAvatar_image").attr("src")).to.equal( + // eslint-disable-next-line no-restricted-properties + cli.mxcUrlToHttp(avatarUrl, AVATAR_SIZE, AVATAR_SIZE, AVATAR_RESIZE_METHOD), + ); + }); +}; + +const sendEvent = (roomId: string): Chainable => { + return cy.sendEvent( + roomId, + null, + "m.room.message" as EventType, + MessageEvent.from("Message").serialize().content, + ); +}; + +describe("Timeline", () => { + let synapse: SynapseInstance; + + let roomId: string; + + let oldAvatarUrl: string; + let newAvatarUrl: string; + + describe("useOnlyCurrentProfiles", () => { + beforeEach(() => { + cy.startSynapse("default").then(data => { + synapse = data; + cy.initTestUser(synapse, OLD_NAME).then(() => + cy.window({ log: false }).then(() => { + cy.createRoom({ name: ROOM_NAME }).then(_room1Id => { + roomId = _room1Id; + }); + }), + ).then(() => { + cy.uploadContent(OLD_AVATAR).then((url) => { + oldAvatarUrl = url; + cy.setAvatarUrl(url); + }); + }).then(() => { + cy.uploadContent(NEW_AVATAR).then((url) => { + newAvatarUrl = url; + }); + }); + }); + }); + + afterEach(() => { + cy.stopSynapse(synapse); + }); + + it("should show historical profiles if disabled", () => { + cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, false); + sendEvent(roomId); + cy.setDisplayName("Alan (away)"); + cy.setAvatarUrl(newAvatarUrl); + // XXX: If we send the second event too quickly, there won't be + // enough time for the client to register the profile change + cy.wait(500); + sendEvent(roomId); + cy.viewRoomByName(ROOM_NAME); + + const events = getEventTilesWithBodies(); + + events.should("have.length", 2); + events.each((e, i) => { + if (i === 0) { + expectDisplayName(e, OLD_NAME); + expectAvatar(e, oldAvatarUrl); + } else if (i === 1) { + expectDisplayName(e, NEW_NAME); + expectAvatar(e, newAvatarUrl); + } + }); + }); + + it("should not show historical profiles if enabled", () => { + cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, true); + sendEvent(roomId); + cy.setDisplayName(NEW_NAME); + cy.setAvatarUrl(newAvatarUrl); + // XXX: If we send the second event too quickly, there won't be + // enough time for the client to register the profile change + cy.wait(500); + sendEvent(roomId); + cy.viewRoomByName(ROOM_NAME); + + const events = getEventTilesWithBodies(); + + events.should("have.length", 2); + events.each((e) => { + expectDisplayName(e, NEW_NAME); + expectAvatar(e, newAvatarUrl); + }); + }); + }); +}); diff --git a/cypress/support/client.ts b/cypress/support/client.ts index c4577760a8b..8f9b14e851e 100644 --- a/cypress/support/client.ts +++ b/cypress/support/client.ts @@ -16,9 +16,12 @@ limitations under the License. /// -import type { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests"; +import type { FileType, UploadContentResponseType } from "matrix-js-sdk/src/http-api"; +import type { IAbortablePromise } from "matrix-js-sdk/src/@types/partials"; +import type { ICreateRoomOpts, ISendEventResponse, IUploadOpts } from "matrix-js-sdk/src/@types/requests"; import type { MatrixClient } from "matrix-js-sdk/src/client"; import type { Room } from "matrix-js-sdk/src/models/room"; +import type { IContent } from "matrix-js-sdk/src/models/event"; import Chainable = Cypress.Chainable; declare global { @@ -53,6 +56,64 @@ declare global { * @param data The data to store. */ setAccountData(type: string, data: object): Chainable<{}>; + /** + * @param {string} roomId + * @param {string} threadId + * @param {string} eventType + * @param {Object} content + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ + sendEvent( + roomId: string, + threadId: string | null, + eventType: string, + content: IContent + ): Chainable; + /** + * @param {string} name + * @param {module:client.callback} callback Optional. + * @return {Promise} Resolves: {} an empty object. + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ + setDisplayName(name: string): Chainable<{}>; + /** + * @param {string} url + * @param {module:client.callback} callback Optional. + * @return {Promise} Resolves: {} an empty object. + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ + setAvatarUrl(url: string): Chainable<{}>; + /** + * Upload a file to the media repository on the homeserver. + * + * @param {object} file The object to upload. On a browser, something that + * can be sent to XMLHttpRequest.send (typically a File). Under node.js, + * a a Buffer, String or ReadStream. + */ + uploadContent( + file: FileType, + opts?: O, + ): IAbortablePromise>; + /** + * Turn an MXC URL into an HTTP one. This method is experimental and + * may change. + * @param {string} mxcUrl The MXC URL + * @param {Number} width The desired width of the thumbnail. + * @param {Number} height The desired height of the thumbnail. + * @param {string} resizeMethod The thumbnail resize method to use, either + * "crop" or "scale". + * @param {Boolean} allowDirectLinks If true, return any non-mxc URLs + * directly. Fetching such URLs will leak information about the user to + * anyone they share a room with. If false, will return null for such URLs. + * @return {?string} the avatar URL or null. + */ + mxcUrlToHttp( + mxcUrl: string, + width?: number, + height?: number, + resizeMethod?: string, + allowDirectLinks?: boolean, + ): string | null; /** * Gets the list of DMs with a given user * @param userId The ID of the user @@ -120,6 +181,35 @@ Cypress.Commands.add("setAccountData", (type: string, data: object): Chainable<{ }); }); +Cypress.Commands.add("sendEvent", ( + roomId: string, + threadId: string | null, + eventType: string, + content: IContent, +): Chainable => { + return cy.getClient().then(async (cli: MatrixClient) => { + return cli.sendEvent(roomId, threadId, eventType, content); + }); +}); + +Cypress.Commands.add("setDisplayName", (name: string): Chainable<{}> => { + return cy.getClient().then(async (cli: MatrixClient) => { + return cli.setDisplayName(name); + }); +}); + +Cypress.Commands.add("uploadContent", (file: FileType): Chainable<{}> => { + return cy.getClient().then(async (cli: MatrixClient) => { + return cli.uploadContent(file); + }); +}); + +Cypress.Commands.add("setAvatarUrl", (url: string): Chainable<{}> => { + return cy.getClient().then(async (cli: MatrixClient) => { + return cli.setAvatarUrl(url); + }); +}); + Cypress.Commands.add("bootstrapCrossSigning", () => { cy.window({ log: false }).then(win => { win.mxMatrixClientPeg.matrixClient.bootstrapCrossSigning({ diff --git a/cypress/support/settings.ts b/cypress/support/settings.ts index aed0631354b..a44f3f06d25 100644 --- a/cypress/support/settings.ts +++ b/cypress/support/settings.ts @@ -17,11 +17,17 @@ limitations under the License. /// import Chainable = Cypress.Chainable; +import type { SettingLevel } from "../../src/settings/SettingLevel"; +import type SettingsStore from "../../src/settings/SettingsStore"; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface Chainable { + /** + * Returns the SettingsStore + */ + getSettingsStore(): Chainable; // XXX: Importing SettingsStore causes a bunch of type lint errors /** * Open the top left user menu, returning a handle to the resulting context menu. */ @@ -63,10 +69,59 @@ declare global { * @param name the name of the beta to leave. */ leaveBeta(name: string): Chainable>; + + /** + * Sets the value for a setting. The room ID is optional if the + * setting is not being set for a particular room, otherwise it + * should be supplied. The value may be null to indicate that the + * level should no longer have an override. + * @param {string} settingName The name of the setting to change. + * @param {String} roomId The room ID to change the value in, may be + * null. + * @param {SettingLevel} level The level to change the value at. + * @param {*} value The new value of the setting, may be null. + * @return {Promise} Resolves when the setting has been changed. + */ + setSettingValue(name: string, roomId: string, level: SettingLevel, value: any): Chainable; + + /** + * Gets the value of a setting. The room ID is optional if the + * setting is not to be applied to any particular room, otherwise it + * should be supplied. + * @param {string} settingName The name of the setting to read the + * value of. + * @param {String} roomId The room ID to read the setting value in, + * may be null. + * @param {boolean} excludeDefault True to disable using the default + * value. + * @return {*} The value, or null if not found + */ + getSettingValue(name: string, roomId?: string): Chainable; } } } +Cypress.Commands.add("getSettingsStore", (): Chainable => { + return cy.window({ log: false }).then(win => win.mxSettingsStore); +}); + +Cypress.Commands.add("setSettingValue", ( + name: string, + roomId: string, + level: SettingLevel, + value: any, +): Chainable => { + return cy.getSettingsStore().then(async (store: typeof SettingsStore) => { + return store.setValue(name, roomId, level, value); + }); +}); + +Cypress.Commands.add("getSettingValue", (name: string, roomId?: string): Chainable => { + return cy.getSettingsStore().then((store: typeof SettingsStore) => { + return store.getValue(name, roomId); + }); +}); + Cypress.Commands.add("openUserMenu", (): Chainable> => { cy.get('[aria-label="User menu"]').click(); return cy.get(".mx_ContextualMenu"); diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx index 76dc9e69621..48664394731 100644 --- a/src/components/views/avatars/MemberAvatar.tsx +++ b/src/components/views/avatars/MemberAvatar.tsx @@ -42,7 +42,7 @@ interface IProps extends Omit, "name" | pushUserOnClick?: boolean; title?: string; style?: any; - forceHistorical?: boolean; // true to deny `feature_use_only_current_profiles` usage. Default false. + forceHistorical?: boolean; // true to deny `useOnlyCurrentProfiles` usage. Default false. hideTitle?: boolean; } @@ -72,7 +72,7 @@ export default class MemberAvatar extends React.PureComponent { private static getState(props: IProps): IState { let member = props.member; - if (member && !props.forceHistorical && SettingsStore.getValue("feature_use_only_current_profiles")) { + if (member && !props.forceHistorical && SettingsStore.getValue("useOnlyCurrentProfiles")) { const room = MatrixClientPeg.get().getRoom(member.roomId); if (room) { member = room.getMember(member.userId); diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx index da7d1206d11..db44cfeb04d 100644 --- a/src/components/views/messages/SenderProfile.tsx +++ b/src/components/views/messages/SenderProfile.tsx @@ -38,7 +38,7 @@ export default class SenderProfile extends React.PureComponent { const msgtype = mxEvent.getContent().msgtype; let member = mxEvent.sender; - if (SettingsStore.getValue("feature_use_only_current_profiles")) { + if (SettingsStore.getValue("useOnlyCurrentProfiles")) { const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId()); if (room) { member = room.getMember(mxEvent.getSender()); diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index e72be3404bc..06883703bdf 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -89,6 +89,7 @@ export default class PreferencesUserSettingsTab extends React.Component Date: Tue, 5 Jul 2022 07:01:06 +0000 Subject: [PATCH 020/162] Move mx_EventTile_msgOption out of mx_EventTile:not([data-layout=bubble]) (#8973) * Move mx_EventTile_msgOption out of mx_EventTile:not([data-layout=bubble]) Signed-off-by: Suguru Hirahara * Decrease specificity on _IRCLayout.scss Signed-off-by: Suguru Hirahara --- res/css/views/rooms/_EventTile.scss | 32 ++++++++++++++--------------- res/css/views/rooms/_IRCLayout.scss | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 89f25f5d61e..abbe53d0b26 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -169,6 +169,22 @@ $left-gutter: 64px; margin-right: 10px; } + .mx_EventTile_msgOption { + float: right; + text-align: right; + position: relative; + width: 90px; + + /* Hack to stop the height of this pushing the messages apart. + Replaces margin-top: -6px. This interacts better with a read + marker being in between. Content overflows. */ + height: 1px; + + a { + text-decoration: none; + } + } + &.mx_EventTile_highlight, &.mx_EventTile_highlight .markdown-body { .mx_EventTile_line { @@ -305,22 +321,6 @@ $left-gutter: 64px; } } - .mx_EventTile_msgOption { - float: right; - text-align: right; - position: relative; - width: 90px; - - /* Hack to stop the height of this pushing the messages apart. - Replaces margin-top: -6px. This interacts better with a read - marker being in between. Content overflows. */ - height: 1px; - - a { - text-decoration: none; - } - } - &:hover.mx_EventTile_verified .mx_EventTile_line { box-shadow: inset calc(50px + $selected-message-border-width) 0 0 -50px $e2e-verified-color; } diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss index 4794b6d1490..dd4df6e296c 100644 --- a/res/css/views/rooms/_IRCLayout.scss +++ b/res/css/views/rooms/_IRCLayout.scss @@ -39,7 +39,7 @@ $irc-line-height: $font-18px; margin-right: $right-padding; } - > .mx_EventTile_msgOption { + .mx_EventTile_msgOption { order: 5; flex-shrink: 0; From 08c607718d268e37f8b5373473a7907bbee17511 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Tue, 5 Jul 2022 11:14:24 +0100 Subject: [PATCH 021/162] Attempt to set the language when setting up Cypress tests (#8964) --- cypress/support/login.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cypress/support/login.ts b/cypress/support/login.ts index 50be88ae670..da72ec69a46 100644 --- a/cypress/support/login.ts +++ b/cypress/support/login.ts @@ -82,6 +82,9 @@ Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: str win.localStorage.setItem("mx_is_guest", "false"); win.localStorage.setItem("mx_has_pickle_key", "false"); win.localStorage.setItem("mx_has_access_token", "true"); + + // Ensure the language is set to a consistent value + win.localStorage.setItem("mx_local_settings", '{"language":"en"}'); }); return cy.visit("/").then(() => { From fd3d65993c9d8e56c79db8ef7b0153dd32ad67cd Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Tue, 5 Jul 2022 11:02:29 +0000 Subject: [PATCH 022/162] Improve mx_MatrixChat_useCompactLayout on _EventTile.scss (#8965) --- res/css/views/rooms/_EventTile.scss | 45 ++++++++++++++++------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index abbe53d0b26..0d927e9903f 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -1071,15 +1071,25 @@ $left-gutter: 64px; // Override :not([data-layout="bubble"]) &[data-layout=group] { --MatrixChat_useCompactLayout_group-padding-top: $spacing-4; + --MatrixChat_useCompactLayout-top-avatar: 2px; + --MatrixChat_useCompactLayout-top-e2eIcon: 3px; + --MatrixChat_useCompactLayout_line-spacing-block: 0px; padding-top: var(--MatrixChat_useCompactLayout_group-padding-top); + .mx_EventTile_line, + .mx_EventTile_reply { + padding-block: var(--MatrixChat_useCompactLayout_line-spacing-block); + } + &.mx_EventTile_info { padding-top: 0; // same as the padding for non-compact .mx_EventTile.mx_EventTile_info font-size: $font-13px; + .mx_EventTile_e2eIcon, .mx_EventTile_avatar { - top: $spacing-4; + top: 0; + margin-block: var(--MatrixChat_useCompactLayout_line-spacing-block); } .mx_EventTile_line, @@ -1091,39 +1101,34 @@ $left-gutter: 64px; &.mx_EventTile_emote { padding-top: $spacing-8; // add a bit more space for emotes so that avatars don't collide - &.mx_EventTile_continuation { - padding-top: 0; - - .mx_EventTile_line, - .mx_EventTile_reply { - padding-top: 0; - padding-bottom: 0; - } - } - .mx_EventTile_avatar { - top: 2px; + top: var(--MatrixChat_useCompactLayout-top-avatar); } .mx_EventTile_line, .mx_EventTile_reply { - padding-top: 0; padding-bottom: 1px; } + + &.mx_EventTile_continuation { + .mx_EventTile_line, + .mx_EventTile_reply { + padding-bottom: var(--MatrixChat_useCompactLayout_line-spacing-block); + } + } } - .mx_EventTile_avatar { - top: 2px; + // Cascading - apply zero padding to every element including mx_EventTile_emote + &.mx_EventTile_continuation { + padding-top: var(--MatrixChat_useCompactLayout_line-spacing-block); } - .mx_EventTile_line, - .mx_EventTile_reply { - padding-top: 0; - padding-bottom: 0; + .mx_EventTile_avatar { + top: var(--MatrixChat_useCompactLayout-top-avatar); } .mx_EventTile_e2eIcon { - top: 3px; + top: var(--MatrixChat_useCompactLayout-top-e2eIcon); } .mx_DisambiguatedProfile { From 74a059b52067494c78b1cd3f6cb8d1c594362131 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Tue, 5 Jul 2022 11:17:11 +0000 Subject: [PATCH 023/162] Move mx_EventTile_continuation out of mx_EventTile:not([data-layout=bubble]) (#8941) * Move mx_EventTile_continuation out of mx_EventTile:not([data-layout=bubble]) Signed-off-by: Suguru Hirahara * Manage mx_EventTile_continuation declarations on one file Signed-off-by: Suguru Hirahara --- res/css/views/rooms/_EventBubbleTile.scss | 4 ---- res/css/views/rooms/_EventTile.scss | 22 ++++++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index 52c115891a4..08acc7b3b32 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -73,10 +73,6 @@ limitations under the License. margin-right: 0; } - &.mx_EventTile_continuation { - margin-top: 2px; - } - &.mx_EventTile_highlight { &::before { background-color: $event-highlight-bg-color; diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 0d927e9903f..c7e5f220840 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -191,6 +191,10 @@ $left-gutter: 64px; background-color: $event-highlight-bg-color; } } + + &.mx_EventTile_continuation .mx_EventTile_line { + clear: both; + } } &[data-layout=group] { @@ -238,6 +242,16 @@ $left-gutter: 64px; position: absolute; z-index: 9; } + + &.mx_EventTile_continuation { + padding-top: 0px !important; + } + } + + &[data-layout=bubble] { + &.mx_EventTile_continuation { + margin-top: 2px; + } } } @@ -275,19 +289,11 @@ $left-gutter: 64px; } } - &.mx_EventTile_continuation { - padding-top: 0px !important; - } - .mx_MessageTimestamp { left: 0px; text-align: center; } - &.mx_EventTile_continuation .mx_EventTile_line { - clear: both; - } - /* this is used for the tile for the event which is selected via the URL. * TODO: ultimately we probably want some transition on here. */ From a009f8001a65ff8462fd91ccc38396094949fe29 Mon Sep 17 00:00:00 2001 From: sha-265 <4103710+sha-265@users.noreply.github.com> Date: Tue, 5 Jul 2022 14:37:35 +0300 Subject: [PATCH 024/162] Add bidirectonal isolation for pills (#8985) --- src/components/views/elements/Pill.tsx | 6 ++---- test/components/views/messages/TextualBody-test.tsx | 6 +++--- .../views/messages/__snapshots__/TextualBody-test.tsx.snap | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/views/elements/Pill.tsx b/src/components/views/elements/Pill.tsx index f87e7c020d9..ab5313c210c 100644 --- a/src/components/views/elements/Pill.tsx +++ b/src/components/views/elements/Pill.tsx @@ -256,7 +256,7 @@ export default class Pill extends React.Component { tip = ; } - return + return { this.props.inMessage ? { onClick={onClick} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave} - dir="auto" > { avatar } { linkText } @@ -274,13 +273,12 @@ export default class Pill extends React.Component { className={classes} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave} - dir="auto" > { avatar } { linkText } { tip } } - ; + ; } else { // Deliberately render nothing if the URL isn't recognised return null; diff --git a/test/components/views/messages/TextualBody-test.tsx b/test/components/views/messages/TextualBody-test.tsx index 23c9fa206a0..d4b1b2aeadf 100644 --- a/test/components/views/messages/TextualBody-test.tsx +++ b/test/components/views/messages/TextualBody-test.tsx @@ -329,13 +329,13 @@ describe("", () => { const content = wrapper.find(".mx_EventTile_body"); expect(content.html()).toBe( '' + - 'A ' + - 'room name with vias', + 'room name with vias', ); }); diff --git a/test/components/views/messages/__snapshots__/TextualBody-test.tsx.snap b/test/components/views/messages/__snapshots__/TextualBody-test.tsx.snap index 783f72a697b..7537766df9d 100644 --- a/test/components/views/messages/__snapshots__/TextualBody-test.tsx.snap +++ b/test/components/views/messages/__snapshots__/TextualBody-test.tsx.snap @@ -14,4 +14,4 @@ exports[` renders formatted m.text correctly pills do not appear " `; -exports[` renders formatted m.text correctly pills get injected correctly into the DOM 1`] = `"Hey \\"\\"Member"`; +exports[` renders formatted m.text correctly pills get injected correctly into the DOM 1`] = `"Hey \\"\\"Member"`; From c197930e97b117b161c759f17f38c18f4d0484f5 Mon Sep 17 00:00:00 2001 From: Element Translate Bot Date: Tue, 5 Jul 2022 14:42:53 +0200 Subject: [PATCH 025/162] Translations update from Weblate (#8927) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (French) Currently translated at 100.0% (3425 of 3425 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fr/ * Translated using Weblate (Czech) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/uk/ * Translated using Weblate (Estonian) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/id/ * Translated using Weblate (Swedish) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sv/ * Translated using Weblate (Finnish) Currently translated at 91.1% (3122 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fi/ * Translated using Weblate (Slovak) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sk/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/zh_Hant/ * Translated using Weblate (French) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fr/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/hu/ * Translated using Weblate (Dutch) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/nl/ * Translated using Weblate (Italian) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/it/ * Translated using Weblate (Czech) Currently translated at 99.9% (3425 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ * Translated using Weblate (Estonian) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/et/ * Translated using Weblate (Czech) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ * Translated using Weblate (Swedish) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sv/ * Translated using Weblate (Italian) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/it/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (3427 of 3427 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/uk/ * Translated using Weblate (Spanish) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/es/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/zh_Hant/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sk/ * Translated using Weblate (Czech) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ * Translated using Weblate (Galician) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/gl/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/uk/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (3429 of 3429 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/uk/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (3429 of 3429 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/id/ * Translated using Weblate (Estonian) Currently translated at 100.0% (3429 of 3429 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/et/ * Translated using Weblate (Slovak) Currently translated at 100.0% (3429 of 3429 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sk/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (3429 of 3429 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/zh_Hant/ * Translated using Weblate (Czech) Currently translated at 100.0% (3429 of 3429 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (3438 of 3438 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/zh_Hant/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (3438 of 3438 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/uk/ * Translated using Weblate (Czech) Currently translated at 100.0% (3438 of 3438 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ * Translated using Weblate (Estonian) Currently translated at 100.0% (3438 of 3438 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/et/ * Translated using Weblate (Galician) Currently translated at 100.0% (3438 of 3438 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/gl/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (3438 of 3438 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (3438 of 3438 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sk/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (3439 of 3439 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/uk/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (3439 of 3439 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/id/ * Translated using Weblate (Czech) Currently translated at 100.0% (3439 of 3439 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ * Translated using Weblate (Slovak) Currently translated at 100.0% (3439 of 3439 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sk/ * Translated using Weblate (Estonian) Currently translated at 100.0% (3439 of 3439 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/et/ * Translated using Weblate (Spanish) Currently translated at 100.0% (3439 of 3439 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/es/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (3439 of 3439 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/zh_Hant/ * Translated using Weblate (Galician) Currently translated at 100.0% (3439 of 3439 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/gl/ * Translated using Weblate (German) Currently translated at 97.5% (3335 of 3418 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ * Translated using Weblate (Czech) Currently translated at 99.7% (3422 of 3429 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ * Translated using Weblate (Czech) Currently translated at 100.0% (3429 of 3429 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ * Translated using Weblate (Estonian) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/et/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/zh_Hant/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/uk/ * Translated using Weblate (Galician) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/gl/ * Translated using Weblate (Greek) Currently translated at 98.2% (3367 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/el/ * Translated using Weblate (Italian) Currently translated at 99.7% (3421 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/it/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sk/ * Translated using Weblate (Arabic) Currently translated at 40.1% (1377 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/ar/ * Translated using Weblate (Spanish) Currently translated at 99.8% (3424 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/es/ * Translated using Weblate (French) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fr/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/uk/ * Translated using Weblate (Dutch) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/nl/ * Translated using Weblate (Estonian) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/et/ * Translated using Weblate (Italian) Currently translated at 100.0% (3428 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/it/ * Translated using Weblate (Icelandic) Currently translated at 88.5% (3037 of 3428 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/is/ * Translated using Weblate (French) Currently translated at 99.8% (3428 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fr/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (3432 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/uk/ * Translated using Weblate (Estonian) Currently translated at 100.0% (3432 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/et/ * Translated using Weblate (Spanish) Currently translated at 100.0% (3432 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/es/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (3432 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/uk/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (3432 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/id/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (3432 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/hu/ * Translated using Weblate (Albanian) Currently translated at 99.0% (3398 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sq/ * Translated using Weblate (Russian) Currently translated at 90.8% (3119 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/ru/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (3432 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/zh_Hant/ * Translated using Weblate (Dutch) Currently translated at 100.0% (3432 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/nl/ * Translated using Weblate (Slovak) Currently translated at 100.0% (3432 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sk/ * Translated using Weblate (Russian) Currently translated at 91.2% (3132 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/ru/ * Translated using Weblate (Russian) Currently translated at 92.1% (3161 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/ru/ * Translated using Weblate (Czech) Currently translated at 100.0% (3432 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ * Translated using Weblate (Italian) Currently translated at 100.0% (3432 of 3432 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/it/ Co-authored-by: Weblate Co-authored-by: Glandos Co-authored-by: waclaw66 Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: LinAGKar Co-authored-by: Jiri Grönroos Co-authored-by: Jozef Gaal Co-authored-by: Jeff Huang Co-authored-by: Szimszon Co-authored-by: Johan Smits Co-authored-by: random Co-authored-by: iaiz Co-authored-by: Xose M Co-authored-by: Benoit Marty Co-authored-by: Theo Co-authored-by: AhmedRN Co-authored-by: Sveinn í Felli Co-authored-by: Jean-Luc KABORE-TURQUIN Co-authored-by: Besnik Bleta Co-authored-by: Nui Harime Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- src/i18n/strings/pl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/pl.json b/src/i18n/strings/pl.json index ea2f6b0117b..5fc2a535a5a 100644 --- a/src/i18n/strings/pl.json +++ b/src/i18n/strings/pl.json @@ -2019,5 +2019,6 @@ "Can't see what you're looking for?": "Nie możesz znaleźć czego szukasz?", "Sticker": "Naklejka", "Rooms outside of a space": "Pokoje poza przestrzenią", - "Autoplay videos": "Auto odtwarzanie filmów" + "Autoplay videos": "Auto odtwarzanie filmów", + "Where this page includes identifiable information, such as a room, user ID, that data is removed before being sent to the server.": "Jeśli ta strona zawiera informacje umożliwiające identyfikację, takie jak pokój, identyfikator użytkownika, dane te są usuwane przed wysłaniem na serwer." } From 517494d6195f323a2b49474ae404f831c0c41f1b Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 5 Jul 2022 14:12:58 +0100 Subject: [PATCH 026/162] Upgrade matrix-js-sdk to 19.0.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4c3c33e4f34..f9050639e77 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "maplibre-gl": "^1.15.2", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "^0.0.1-beta.7", - "matrix-js-sdk": "19.0.0-rc.1", + "matrix-js-sdk": "19.0.0", "matrix-widget-api": "^0.1.0-beta.18", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 8da5fadffc6..bf62d553810 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6961,10 +6961,10 @@ matrix-events-sdk@^0.0.1-beta.7: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1-beta.7.tgz#5ffe45eba1f67cc8d7c2377736c728b322524934" integrity sha512-9jl4wtWanUFSy2sr2lCjErN/oC8KTAtaeaozJtrgot1JiQcEI4Rda9OLgQ7nLKaqb4Z/QUx/fR3XpDzm5Jy1JA== -matrix-js-sdk@19.0.0-rc.1: - version "19.0.0-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-19.0.0-rc.1.tgz#6a1454330246e232e58964d198eef90d2bdce1ee" - integrity sha512-XHeI7RhcrbNByFPD45yM7SeF6ZckPUpRiQOcGD8rGUFDxwmbEyjIdjqZf8YJeKedaGsLPMxztDw17jT6QJSWyg== +matrix-js-sdk@19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-19.0.0.tgz#c4be8365d08126976a1a3de053fe12b5dd7d4a0d" + integrity sha512-UWFEhV3XlBRY/9dLKlFgBzd9vynN+U4ratE0BVvM3Zw8FQa+a8rrDwJRIpJnWoxDB7IjJ188A0TxFqzxkQoEBQ== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From 0259c27acecca4abfc5ba884fda59c2f0fd701d2 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 5 Jul 2022 14:14:46 +0100 Subject: [PATCH 027/162] Prepare changelog for v3.48.0 --- CHANGELOG.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59d02f714b3..e7f94db4214 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ -Changes in [3.48.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.48.0-rc.1) (2022-06-28) -=============================================================================================================== +Changes in [3.48.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.48.0) (2022-07-05) +===================================================================================================== ## 🚨 BREAKING CHANGES * Remove Piwik support ([\#8835](https://github.com/matrix-org/matrix-react-sdk/pull/8835)). @@ -26,7 +26,7 @@ Changes in [3.48.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases * Live location share - link to timeline tile from share warning ([\#8752](https://github.com/matrix-org/matrix-react-sdk/pull/8752)). Contributed by @kerryarchibald. * Improve composer visiblity ([\#8578](https://github.com/matrix-org/matrix-react-sdk/pull/8578)). Fixes vector-im/element-web#22072 and vector-im/element-web#17362. * Makes the avatar of the user menu non-draggable ([\#8765](https://github.com/matrix-org/matrix-react-sdk/pull/8765)). Contributed by @luixxiul. - * Improve widget buttons behaviour and layout ([\#8734](https://github.com/matrix-org/matrix-react-sdk/pull/8734)). Contributed by @weeman1337. + * Improve widget buttons behaviour and layout ([\#8734](https://github.com/matrix-org/matrix-react-sdk/pull/8734)). * Use AccessibleButton for 'Reset All' link button on SetupEncryptionBody ([\#8730](https://github.com/matrix-org/matrix-react-sdk/pull/8730)). Contributed by @luixxiul. * Adjust message timestamp position on TimelineCard in non-bubble layouts ([\#8745](https://github.com/matrix-org/matrix-react-sdk/pull/8745)). Fixes vector-im/element-web#22426. Contributed by @luixxiul. * Use AccessibleButton for 'In reply to' link button on ReplyChain ([\#8726](https://github.com/matrix-org/matrix-react-sdk/pull/8726)). Fixes vector-im/element-web#22407. Contributed by @luixxiul. @@ -34,8 +34,9 @@ Changes in [3.48.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases * Change dash to em dash issues fixed ([\#8455](https://github.com/matrix-org/matrix-react-sdk/pull/8455)). Fixes vector-im/element-web#21895. Contributed by @goelesha. ## 🐛 Bug Fixes + * Make invite dialogue fixed height ([\#8945](https://github.com/matrix-org/matrix-react-sdk/pull/8945)). * Correct issue with tab order in new search experience ([\#8919](https://github.com/matrix-org/matrix-react-sdk/pull/8919)). Fixes vector-im/element-web#22670. Contributed by @justjanne. - * Clicking location replies now redirects to the replied event instead of opening the map ([\#8918](https://github.com/matrix-org/matrix-react-sdk/pull/8918)). Fixes vector-im/element-web#22667. Contributed by @weeman1337. + * Clicking location replies now redirects to the replied event instead of opening the map ([\#8918](https://github.com/matrix-org/matrix-react-sdk/pull/8918)). Fixes vector-im/element-web#22667. * Keep clicks on pills within the app ([\#8917](https://github.com/matrix-org/matrix-react-sdk/pull/8917)). Fixes vector-im/element-web#22653. * Don't overlap tile bubbles with timestamps in modern layout ([\#8908](https://github.com/matrix-org/matrix-react-sdk/pull/8908)). Fixes vector-im/element-web#22425. * Connect to Jitsi unmuted by default ([\#8909](https://github.com/matrix-org/matrix-react-sdk/pull/8909)). @@ -63,7 +64,7 @@ Changes in [3.48.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases * Fix issues with the Create new room button in Spotlight ([\#8851](https://github.com/matrix-org/matrix-react-sdk/pull/8851)). Contributed by @justjanne. * Remove margin from E2E icon between avatar and hidden event ([\#8584](https://github.com/matrix-org/matrix-react-sdk/pull/8584)). Fixes vector-im/element-web#22186. Contributed by @luixxiul. * Fix waveform on a message bubble ([\#8852](https://github.com/matrix-org/matrix-react-sdk/pull/8852)). Contributed by @luixxiul. - * Location sharing maps are now loaded after reconnection ([\#8848](https://github.com/matrix-org/matrix-react-sdk/pull/8848)). Fixes vector-im/element-web#20993. Contributed by @weeman1337. + * Location sharing maps are now loaded after reconnection ([\#8848](https://github.com/matrix-org/matrix-react-sdk/pull/8848)). Fixes vector-im/element-web#20993. * Update the avatar mask so it doesn’t cut off spaces’ avatars anymore ([\#8849](https://github.com/matrix-org/matrix-react-sdk/pull/8849)). Contributed by @justjanne. * Add a bit of safety around timestamp handling for threads ([\#8845](https://github.com/matrix-org/matrix-react-sdk/pull/8845)). * Remove top margin from event tile on a narrow viewport ([\#8814](https://github.com/matrix-org/matrix-react-sdk/pull/8814)). Contributed by @luixxiul. @@ -87,7 +88,7 @@ Changes in [3.48.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases * Fix the incorrect nesting of download button on MessageActionBar ([\#8785](https://github.com/matrix-org/matrix-react-sdk/pull/8785)). Contributed by @luixxiul. * Revert link color change in composer ([\#8784](https://github.com/matrix-org/matrix-react-sdk/pull/8784)). Fixes vector-im/element-web#22468. * Fix 'Logout' inline link on the splash screen ([\#8770](https://github.com/matrix-org/matrix-react-sdk/pull/8770)). Fixes vector-im/element-web#22449. Contributed by @luixxiul. - * Fix disappearing widget poput button when changing the widget layout ([\#8754](https://github.com/matrix-org/matrix-react-sdk/pull/8754)). Contributed by @weeman1337. + * Fix disappearing widget poput button when changing the widget layout ([\#8754](https://github.com/matrix-org/matrix-react-sdk/pull/8754)). * Reduce gutter with the new read receipt UI ([\#8736](https://github.com/matrix-org/matrix-react-sdk/pull/8736)). Fixes vector-im/element-web#21890. * Add ellipsis effect to hidden beacon status ([\#8755](https://github.com/matrix-org/matrix-react-sdk/pull/8755)). Fixes vector-im/element-web#22441. Contributed by @luixxiul. * Make the pill on the basic message composer compatible with display name in RTL languages ([\#8758](https://github.com/matrix-org/matrix-react-sdk/pull/8758)). Fixes vector-im/element-web#22445. Contributed by @luixxiul. From 5a1ed59a9d244b38d75f5a56745eb6382480e110 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 5 Jul 2022 14:14:47 +0100 Subject: [PATCH 028/162] v3.48.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f9050639e77..a0019d090ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.48.0-rc.1", + "version": "3.48.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From d80dd8f02fe76e8e34a9ba55cae1bd47e323c02a Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 5 Jul 2022 14:16:29 +0100 Subject: [PATCH 029/162] Resetting package fields for development --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 20d7a2abea5..f7776eb3b85 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "README.md", "package.json" ], - "main": "./lib/index.ts", + "main": "./src/index.ts", "matrix_src_main": "./src/index.ts", "matrix_lib_main": "./lib/index.ts", "matrix_lib_typings": "./lib/index.d.ts", @@ -246,6 +246,5 @@ "jestSonar": { "reportPath": "coverage", "sonar56x": true - }, - "typings": "./lib/index.d.ts" + } } From bdc05ec2682748ab26daae74096f15fe8db52294 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 5 Jul 2022 14:16:38 +0100 Subject: [PATCH 030/162] Reset matrix-js-sdk back to develop branch --- package.json | 2 +- yarn.lock | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index f7776eb3b85..6ad4c4c0367 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "maplibre-gl": "^1.15.2", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "^0.0.1-beta.7", - "matrix-js-sdk": "19.0.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^0.1.0-beta.18", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index bf62d553810..26a80252e6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6961,10 +6961,9 @@ matrix-events-sdk@^0.0.1-beta.7: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1-beta.7.tgz#5ffe45eba1f67cc8d7c2377736c728b322524934" integrity sha512-9jl4wtWanUFSy2sr2lCjErN/oC8KTAtaeaozJtrgot1JiQcEI4Rda9OLgQ7nLKaqb4Z/QUx/fR3XpDzm5Jy1JA== -matrix-js-sdk@19.0.0: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "19.0.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-19.0.0.tgz#c4be8365d08126976a1a3de053fe12b5dd7d4a0d" - integrity sha512-UWFEhV3XlBRY/9dLKlFgBzd9vynN+U4ratE0BVvM3Zw8FQa+a8rrDwJRIpJnWoxDB7IjJ188A0TxFqzxkQoEBQ== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/85a96c6467c24b031ee25d5fbb8d9fe62edbbe49" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From f45e8ad486eaed3a5897bda7e96eba8415abcfbe Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Tue, 5 Jul 2022 14:24:26 +0000 Subject: [PATCH 031/162] Improve security room settings tab style rules (#8844) * Fix position of 'Show Advanced' on security room settings tab Signed-off-by: Suguru Hirahara * Use flex to ensure alignment of the warning icon and message Signed-off-by: Suguru Hirahara * Remove an obsolete style block Signed-off-by: Suguru Hirahara * yarn run lint:style --fix Signed-off-by: Suguru Hirahara --- .../tabs/room/_SecurityRoomSettingsTab.scss | 22 +++++-------------- .../tabs/room/SecurityRoomSettingsTab.tsx | 8 +++---- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss b/res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss index c1426534902..cf3e16bc044 100644 --- a/res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss +++ b/res/css/views/settings/tabs/room/_SecurityRoomSettingsTab.scss @@ -16,24 +16,12 @@ limitations under the License. .mx_SecurityRoomSettingsTab { .mx_SettingsTab_showAdvanced { - padding: 0; - margin-bottom: 16px; + margin-bottom: $spacing-16; } -} - -.mx_SecurityRoomSettingsTab_warning { - display: block; - img { - vertical-align: middle; - margin-right: 5px; - margin-left: 3px; - margin-bottom: 5px; + .mx_SecurityRoomSettingsTab_warning { + display: flex; + align-items: center; + column-gap: $spacing-4; } } - -.mx_SecurityRoomSettingsTab_encryptionSection { - padding-bottom: 24px; - border-bottom: 1px solid $quinary-content; - margin-bottom: 32px; -} diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx index abbd836e905..14a937c5f68 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx @@ -355,7 +355,7 @@ export default class SecurityRoomSettingsTab extends React.Component + return <> -
; + ; } render() { @@ -391,7 +391,7 @@ export default class SecurityRoomSettingsTab extends React.Component +
{ this.state.showAdvancedSection && this.renderAdvanced() } - +
); } From 6f10cd64042c27c2dab9c76d1c1a03a88b4eb12c Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Tue, 5 Jul 2022 16:02:55 +0000 Subject: [PATCH 032/162] Fix font size of MessageTimestamp on TimelineCard (#8950) Signed-off-by: Suguru Hirahara --- res/css/views/right_panel/_TimelineCard.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/right_panel/_TimelineCard.scss b/res/css/views/right_panel/_TimelineCard.scss index 63048511d51..6d608182cb5 100644 --- a/res/css/views/right_panel/_TimelineCard.scss +++ b/res/css/views/right_panel/_TimelineCard.scss @@ -93,6 +93,7 @@ limitations under the License. .mx_MessageTimestamp { inset-inline: auto 0; + font-size: $font-12px; } .mx_ReactionsRow { From 8311bdd2638f8576f287a42cc0d9339fe9b6be04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 5 Jul 2022 18:53:18 +0200 Subject: [PATCH 033/162] Fix the size of the clickable area of images (#8987) --- res/css/views/rooms/_EventTile.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index c7e5f220840..9e6d415557d 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -150,8 +150,6 @@ $left-gutter: 64px; } .mx_MImageBody { - margin-right: var(--EventTile_content-margin-inline-end); - .mx_MImageBody_thumbnail_container { justify-content: flex-start; min-height: $font-44px; From 2dd683a42ff3fe7648f1c6d504439f8c8b15ffd4 Mon Sep 17 00:00:00 2001 From: Robin Date: Tue, 5 Jul 2022 14:22:36 -0400 Subject: [PATCH 034/162] Adjust encryption copy when creating a video room (#8989) * Adjust encryption copy when creating a video room * Adjust wording --- src/components/views/dialogs/CreateRoomDialog.tsx | 12 +++++++----- src/i18n/strings/en_EN.json | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index 7d1028bba79..8425a7269cc 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -222,7 +222,7 @@ export default class CreateRoomDialog extends React.Component { render() { const isVideoRoom = this.props.type === RoomType.ElementVideo; - let aliasField; + let aliasField: JSX.Element; if (this.state.joinRule === JoinRule.Public) { const domain = MatrixClientPeg.get().getDomain(); aliasField = ( @@ -274,12 +274,14 @@ export default class CreateRoomDialog extends React.Component {

; } - let e2eeSection; + let e2eeSection: JSX.Element; if (this.state.joinRule !== JoinRule.Public) { - let microcopy; + let microcopy: string; if (privateShouldBeEncrypted()) { if (this.state.canChangeEncryption) { - microcopy = _t("You can't disable this later. Bridges & most bots won't work yet."); + microcopy = isVideoRoom + ? _t("You can't disable this later. The room will be encrypted but the embedded call will not.") + : _t("You can't disable this later. Bridges & most bots won't work yet."); } else { microcopy = _t("Your server requires encryption to be enabled in private rooms."); } @@ -312,7 +314,7 @@ export default class CreateRoomDialog extends React.Component { ); } - let title; + let title: string; if (isVideoRoom) { title = _t("Create a video room"); } else if (this.props.parentSpace) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 906d2358d1b..444c871a2b6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2445,6 +2445,7 @@ "Anyone will be able to find and join this room, not just members of .": "Anyone will be able to find and join this room, not just members of .", "Anyone will be able to find and join this room.": "Anyone will be able to find and join this room.", "Only people invited will be able to find and join this room.": "Only people invited will be able to find and join this room.", + "You can't disable this later. The room will be encrypted but the embedded call will not.": "You can't disable this later. The room will be encrypted but the embedded call will not.", "You can't disable this later. Bridges & most bots won't work yet.": "You can't disable this later. Bridges & most bots won't work yet.", "Your server requires encryption to be enabled in private rooms.": "Your server requires encryption to be enabled in private rooms.", "Enable end-to-end encryption": "Enable end-to-end encryption", From 7f5bb61a79b20c8642bb5aeec5869dc658bc5147 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 5 Jul 2022 20:26:44 +0200 Subject: [PATCH 035/162] Support a module API surface for custom functionality (#8246) * Early implementation of module API surface + functions for ILAG module * Wire up dialog functions and ILAG-needed surface * Ensure component renders for modules get overridden * Respond to changes from module API interface * Use a real module-api dependency * Update for new Dialogs interface * Add support for getConfigValue from module API * Update the remainder of the module API interface * Docs & cleanup * Add some unit tests around module stuff Needs end-to-end tests still. * Appease early linters * Break import cycles by not directly depending on Lifecycle * Appease the linter * Fix bad merge --- package.json | 1 + src/Lifecycle.ts | 5 + .../views/dialogs/ModuleUiDialog.tsx | 66 ++++++ src/components/views/rooms/RoomPreviewBar.tsx | 30 ++- src/dispatcher/actions.ts | 5 + .../payloads/OverwriteLoginPayload.ts | 25 +++ src/i18n/strings/en_EN.json | 1 + src/languageHandler.tsx | 53 +++-- src/modules/AppModule.ts | 45 +++++ src/modules/ModuleComponents.tsx | 40 ++++ src/modules/ModuleFactory.ts | 20 ++ src/modules/ModuleRunner.ts | 85 ++++++++ src/modules/ProxiedModuleApi.ts | 191 ++++++++++++++++++ src/stores/widgets/StopGapWidgetDriver.ts | 8 +- src/utils/permalinks/navigator.ts | 30 +++ test/modules/AppModule-test.ts | 36 ++++ test/modules/MockModule.ts | 45 +++++ test/modules/ModuleComponents-test.tsx | 41 ++++ test/modules/ModuleRunner-test.ts | 54 +++++ test/modules/ProxiedModuleApi-test.ts | 79 ++++++++ .../ModuleComponents-test.tsx.snap | 66 ++++++ yarn.lock | 14 ++ 22 files changed, 906 insertions(+), 34 deletions(-) create mode 100644 src/components/views/dialogs/ModuleUiDialog.tsx create mode 100644 src/dispatcher/payloads/OverwriteLoginPayload.ts create mode 100644 src/modules/AppModule.ts create mode 100644 src/modules/ModuleComponents.tsx create mode 100644 src/modules/ModuleFactory.ts create mode 100644 src/modules/ModuleRunner.ts create mode 100644 src/modules/ProxiedModuleApi.ts create mode 100644 src/utils/permalinks/navigator.ts create mode 100644 test/modules/AppModule-test.ts create mode 100644 test/modules/MockModule.ts create mode 100644 test/modules/ModuleComponents-test.tsx create mode 100644 test/modules/ModuleRunner-test.ts create mode 100644 test/modules/ProxiedModuleApi-test.ts create mode 100644 test/modules/__snapshots__/ModuleComponents-test.tsx.snap diff --git a/package.json b/package.json index 6ad4c4c0367..8b1aa4a4cad 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "dependencies": { "@babel/runtime": "^7.12.5", "@matrix-org/analytics-events": "^0.1.1", + "@matrix-org/react-sdk-module-api": "^0.0.3", "@sentry/browser": "^6.11.0", "@sentry/tracing": "^6.11.0", "@testing-library/react": "^12.1.5", diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index 4915d17e3f9..915bc85dd72 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -63,6 +63,7 @@ import VideoChannelStore from "./stores/VideoChannelStore"; import { fixStuckDevices } from "./utils/VideoChannelUtils"; import { Action } from "./dispatcher/actions"; import AbstractLocalStorageSettingsHandler from "./settings/handlers/AbstractLocalStorageSettingsHandler"; +import { OverwriteLoginPayload } from "./dispatcher/payloads/OverwriteLoginPayload"; const HOMESERVER_URL_KEY = "mx_hs_url"; const ID_SERVER_URL_KEY = "mx_is_url"; @@ -71,6 +72,10 @@ dis.register((payload) => { if (payload.action === Action.TriggerLogout) { // noinspection JSIgnoredPromiseFromCall - we don't care if it fails onLoggedOut(); + } else if (payload.action === Action.OverwriteLogin) { + const typed = payload; + // noinspection JSIgnoredPromiseFromCall - we don't care if it fails + doSetLoggedIn(typed.credentials, true); } }); diff --git a/src/components/views/dialogs/ModuleUiDialog.tsx b/src/components/views/dialogs/ModuleUiDialog.tsx new file mode 100644 index 00000000000..44109038bd3 --- /dev/null +++ b/src/components/views/dialogs/ModuleUiDialog.tsx @@ -0,0 +1,66 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, { createRef } from "react"; +import { DialogContent, DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent"; +import { logger } from "matrix-js-sdk/src/logger"; + +import ScrollableBaseModal, { IScrollableBaseState } from "./ScrollableBaseModal"; +import { IDialogProps } from "./IDialogProps"; +import { _t } from "../../../languageHandler"; + +interface IProps extends IDialogProps { + contentFactory: (props: DialogProps, ref: React.Ref) => React.ReactNode; + contentProps: DialogProps; + title: string; +} + +interface IState extends IScrollableBaseState { + // nothing special +} + +export class ModuleUiDialog extends ScrollableBaseModal { + private contentRef = createRef(); + + public constructor(props: IProps) { + super(props); + + this.state = { + title: this.props.title, + canSubmit: true, + actionLabel: _t("OK"), + }; + } + + protected async submit() { + try { + const model = await this.contentRef.current.trySubmit(); + this.props.onFinished(true, model); + } catch (e) { + logger.error("Error during submission of module dialog:", e); + } + } + + protected cancel(): void { + this.props.onFinished(false); + } + + protected renderContent(): React.ReactNode { + return
+ { this.props.contentFactory(this.props.contentProps, this.contentRef) } +
; + } +} diff --git a/src/components/views/rooms/RoomPreviewBar.tsx b/src/components/views/rooms/RoomPreviewBar.tsx index e0e1f13f059..6c724440cfa 100644 --- a/src/components/views/rooms/RoomPreviewBar.tsx +++ b/src/components/views/rooms/RoomPreviewBar.tsx @@ -21,6 +21,10 @@ import { EventType, RoomType } from "matrix-js-sdk/src/@types/event"; import { IJoinRuleEventContent, JoinRule } from "matrix-js-sdk/src/@types/partials"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import classNames from 'classnames'; +import { + RoomPreviewOpts, + RoomViewLifecycle, +} from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle"; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import dis from '../../../dispatcher/dispatcher'; @@ -34,6 +38,7 @@ import AccessibleButton from "../elements/AccessibleButton"; import RoomAvatar from "../avatars/RoomAvatar"; import SettingsStore from "../../../settings/SettingsStore"; import { UIFeature } from "../../../settings/UIFeature"; +import { ModuleRunner } from "../../../modules/ModuleRunner"; const MemberEventHtmlReasonField = "io.element.html_reason"; @@ -313,13 +318,26 @@ export default class RoomPreviewBar extends React.Component { break; } case MessageCase.NotLoggedIn: { - title = _t("Join the conversation with an account"); - if (SettingsStore.getValue(UIFeature.Registration)) { - primaryActionLabel = _t("Sign Up"); - primaryActionHandler = this.onRegisterClick; + const opts: RoomPreviewOpts = { canJoin: false }; + if (this.props.room?.roomId) { + ModuleRunner.instance + .invoke(RoomViewLifecycle.PreviewRoomNotLoggedIn, opts, this.props.room.roomId); + } + if (opts.canJoin) { + title = _t("Join the room to participate"); + primaryActionLabel = _t("Join"); + primaryActionHandler = () => { + ModuleRunner.instance.invoke(RoomViewLifecycle.JoinFromRoomPreview, this.props.room.roomId); + }; + } else { + title = _t("Join the conversation with an account"); + if (SettingsStore.getValue(UIFeature.Registration)) { + primaryActionLabel = _t("Sign Up"); + primaryActionHandler = this.onRegisterClick; + } + secondaryActionLabel = _t("Sign In"); + secondaryActionHandler = this.onLoginClick; } - secondaryActionLabel = _t("Sign In"); - secondaryActionHandler = this.onLoginClick; if (this.props.previewLoading) { footer = (
diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 8f46c9398fb..3d1ac9969b6 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -316,6 +316,11 @@ export enum Action { */ OnLoggedIn = "on_logged_in", + /** + * Overwrites the existing login with fresh session credentials. Use with a OverwriteLoginPayload. + */ + OverwriteLogin = "overwrite_login", + /** * Fired when the PlatformPeg gets a new platform set upon it, should only happen once per app load lifecycle. * Fires with the PlatformSetPayload. diff --git a/src/dispatcher/payloads/OverwriteLoginPayload.ts b/src/dispatcher/payloads/OverwriteLoginPayload.ts new file mode 100644 index 00000000000..ec5b83c1de7 --- /dev/null +++ b/src/dispatcher/payloads/OverwriteLoginPayload.ts @@ -0,0 +1,25 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; +import { IMatrixClientCreds } from "../../MatrixClientPeg"; + +export interface OverwriteLoginPayload extends ActionPayload { + action: Action.OverwriteLogin; + + credentials: IMatrixClientCreds; +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 444c871a2b6..693299f9219 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1805,6 +1805,7 @@ "Joining …": "Joining …", "Loading …": "Loading …", "Rejecting invite …": "Rejecting invite …", + "Join the room to participate": "Join the room to participate", "Join the conversation with an account": "Join the conversation with an account", "Sign Up": "Sign Up", "Loading preview": "Loading preview", diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 1d3fef16668..2caf5c1639e 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -28,6 +28,7 @@ import PlatformPeg from "./PlatformPeg"; import { SettingLevel } from "./settings/SettingLevel"; import { retry } from "./utils/promise"; import SdkConfig from "./SdkConfig"; +import { ModuleRunner } from "./modules/ModuleRunner"; // @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config import webpackLangJsonUrl from "$webapp/i18n/languages.json"; @@ -609,15 +610,40 @@ export class CustomTranslationOptions { } } +function doRegisterTranslations(customTranslations: ICustomTranslations) { + // We convert the operator-friendly version into something counterpart can + // consume. + const langs: { + // same structure, just flipped key order + [lang: string]: { + [str: string]: string; + }; + } = {}; + for (const [str, translations] of Object.entries(customTranslations)) { + for (const [lang, newStr] of Object.entries(translations)) { + if (!langs[lang]) langs[lang] = {}; + langs[lang][str] = newStr; + } + } + + // Finally, tell counterpart about our translations + for (const [lang, translations] of Object.entries(langs)) { + counterpart.registerTranslations(lang, translations); + } +} + /** - * If a custom translations file is configured, it will be parsed and registered. - * If no customization is made, or the file can't be parsed, no action will be - * taken. + * Any custom modules with translations to load are parsed first, followed by an + * optionally defined translations file in the config. If no customization is made, + * or the file can't be parsed, no action will be taken. * * This function should be called *after* registering other translations data to * ensure it overrides strings properly. */ export async function registerCustomTranslations() { + const moduleTranslations = ModuleRunner.instance.allTranslations; + doRegisterTranslations(moduleTranslations); + const lookupUrl = SdkConfig.get().custom_translations_url; if (!lookupUrl) return; // easy - nothing to do @@ -639,25 +665,8 @@ export async function registerCustomTranslations() { // If the (potentially cached) json is invalid, don't use it. if (!json) return; - // We convert the operator-friendly version into something counterpart can - // consume. - const langs: { - // same structure, just flipped key order - [lang: string]: { - [str: string]: string; - }; - } = {}; - for (const [str, translations] of Object.entries(json)) { - for (const [lang, newStr] of Object.entries(translations)) { - if (!langs[lang]) langs[lang] = {}; - langs[lang][str] = newStr; - } - } - - // Finally, tell counterpart about our translations - for (const [lang, translations] of Object.entries(langs)) { - counterpart.registerTranslations(lang, translations); - } + // Finally, register it. + doRegisterTranslations(json); } catch (e) { // We consume all exceptions because it's considered non-fatal for custom // translations to break. Most failures will be during initial development diff --git a/src/modules/AppModule.ts b/src/modules/AppModule.ts new file mode 100644 index 00000000000..b5ccf5f63fb --- /dev/null +++ b/src/modules/AppModule.ts @@ -0,0 +1,45 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule"; + +import { ModuleFactory } from "./ModuleFactory"; +import { ProxiedModuleApi } from "./ProxiedModuleApi"; + +/** + * Wraps a module factory into a usable module. Acts as a simple container + * for the constructs needed to operate a module. + */ +export class AppModule { + /** + * The module instance. + */ + public readonly module: RuntimeModule; + + /** + * The API instance used by the module. + */ + public readonly api = new ProxiedModuleApi(); + + /** + * Converts a factory into an AppModule. The factory will be called + * immediately. + * @param factory The module factory. + */ + public constructor(factory: ModuleFactory) { + this.module = factory(this.api); + } +} diff --git a/src/modules/ModuleComponents.tsx b/src/modules/ModuleComponents.tsx new file mode 100644 index 00000000000..c9c7a90b2c4 --- /dev/null +++ b/src/modules/ModuleComponents.tsx @@ -0,0 +1,40 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { TextInputField } from "@matrix-org/react-sdk-module-api/lib/components/TextInputField"; +import { Spinner as ModuleSpinner } from "@matrix-org/react-sdk-module-api/lib/components/Spinner"; +import React from "react"; + +import Field from "../components/views/elements/Field"; +import Spinner from "../components/views/elements/Spinner"; + +// Here we define all the render factories for the module API components. This file should be +// imported by the ModuleRunner to load them into the call stack at runtime. +// +// If a new component is added to the module API, it should be added here too. +// +// Don't forget to add a test to ensure the renderFactory is overridden! See ModuleComponents-test.tsx + +TextInputField.renderFactory = (props) => ( + props.onChange(e.target.value)} + label={props.label} + autoComplete="off" + /> +); +ModuleSpinner.renderFactory = () => ; diff --git a/src/modules/ModuleFactory.ts b/src/modules/ModuleFactory.ts new file mode 100644 index 00000000000..947c14cb012 --- /dev/null +++ b/src/modules/ModuleFactory.ts @@ -0,0 +1,20 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule"; +import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi"; + +export type ModuleFactory = (api: ModuleApi) => RuntimeModule; diff --git a/src/modules/ModuleRunner.ts b/src/modules/ModuleRunner.ts new file mode 100644 index 00000000000..e573852a880 --- /dev/null +++ b/src/modules/ModuleRunner.ts @@ -0,0 +1,85 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations"; +import { AnyLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/types"; + +import { AppModule } from "./AppModule"; +import { ModuleFactory } from "./ModuleFactory"; +import "./ModuleComponents"; + +/** + * Handles and coordinates the operation of modules. + */ +export class ModuleRunner { + public static readonly instance = new ModuleRunner(); + + private modules: AppModule[] = []; + + private constructor() { + // we only want one instance + } + + /** + * Resets the runner, clearing all known modules. + * + * Intended for test usage only. + */ + public reset() { + this.modules = []; + } + + /** + * All custom translations from all registered modules. + */ + public get allTranslations(): TranslationStringsObject { + const merged: TranslationStringsObject = {}; + + for (const module of this.modules) { + const i18n = module.api.translations; + if (!i18n) continue; + + for (const [lang, strings] of Object.entries(i18n)) { + if (!merged[lang]) merged[lang] = {}; + for (const [str, val] of Object.entries(strings)) { + merged[lang][str] = val; + } + } + } + + return merged; + } + + /** + * Registers a factory which creates a module for later loading. The factory + * will be called immediately. + * @param factory The module factory. + */ + public registerModule(factory: ModuleFactory) { + this.modules.push(new AppModule(factory)); + } + + /** + * Invokes a lifecycle event, notifying registered modules. + * @param lifecycleEvent The lifecycle event. + * @param args The arguments for the lifecycle event. + */ + public invoke(lifecycleEvent: AnyLifecycle, ...args: any[]): void { + for (const module of this.modules) { + module.module.emit(lifecycleEvent, ...args); + } + } +} diff --git a/src/modules/ProxiedModuleApi.ts b/src/modules/ProxiedModuleApi.ts new file mode 100644 index 00000000000..008a09527d7 --- /dev/null +++ b/src/modules/ProxiedModuleApi.ts @@ -0,0 +1,191 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi"; +import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations"; +import { Optional } from "matrix-events-sdk"; +import { DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent"; +import React from "react"; +import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo"; +import { PlainSubstitution } from "@matrix-org/react-sdk-module-api/lib/types/translations"; +import * as Matrix from "matrix-js-sdk/src/matrix"; + +import Modal from "../Modal"; +import { _t } from "../languageHandler"; +import { ModuleUiDialog } from "../components/views/dialogs/ModuleUiDialog"; +import SdkConfig from "../SdkConfig"; +import PlatformPeg from "../PlatformPeg"; +import dispatcher from "../dispatcher/dispatcher"; +import { navigateToPermalink } from "../utils/permalinks/navigator"; +import { parsePermalink } from "../utils/permalinks/Permalinks"; +import { MatrixClientPeg } from "../MatrixClientPeg"; +import { getCachedRoomIDForAlias } from "../RoomAliasCache"; +import { Action } from "../dispatcher/actions"; +import { OverwriteLoginPayload } from "../dispatcher/payloads/OverwriteLoginPayload"; + +/** + * Glue between the `ModuleApi` interface and the react-sdk. Anticipates one instance + * to be assigned to a single module. + */ +export class ProxiedModuleApi implements ModuleApi { + private cachedTranslations: Optional; + + /** + * All custom translations used by the associated module. + */ + public get translations(): Optional { + return this.cachedTranslations; + } + + /** + * @override + */ + public registerTranslations(translations: TranslationStringsObject): void { + this.cachedTranslations = translations; + } + + /** + * @override + */ + public translateString(s: string, variables?: Record): string { + return _t(s, variables); + } + + /** + * @override + */ + public openDialog< + M extends object, + P extends DialogProps = DialogProps, + C extends React.Component = React.Component, + >( + title: string, + body: (props: P, ref: React.RefObject) => React.ReactNode, + ): Promise<{ didOkOrSubmit: boolean, model: M }> { + return new Promise<{ didOkOrSubmit: boolean, model: M }>((resolve) => { + Modal.createDialog(ModuleUiDialog, { + title: title, + contentFactory: body, + contentProps: { + moduleApi: this, + }, + }, "mx_CompoundDialog").finished.then(([didOkOrSubmit, model]) => { + resolve({ didOkOrSubmit, model }); + }); + }); + } + + /** + * @override + */ + public async registerSimpleAccount( + username: string, + password: string, + displayName?: string, + ): Promise { + const hsUrl = SdkConfig.get("validated_server_config").hsUrl; + const client = Matrix.createClient({ baseUrl: hsUrl }); + const deviceName = SdkConfig.get("default_device_display_name") + || PlatformPeg.get().getDefaultDeviceDisplayName(); + const req = { + username, + password, + initial_device_display_name: deviceName, + auth: undefined, + inhibit_login: false, + }; + const creds = await (client.registerRequest(req).catch(resp => client.registerRequest({ + ...req, + auth: { + session: resp.data.session, + type: "m.login.dummy", + }, + }))); + + if (displayName) { + const profileClient = Matrix.createClient({ + baseUrl: hsUrl, + userId: creds.user_id, + deviceId: creds.device_id, + accessToken: creds.access_token, + }); + await profileClient.setDisplayName(displayName); + } + + return { + homeserverUrl: hsUrl, + userId: creds.user_id, + deviceId: creds.device_id, + accessToken: creds.access_token, + }; + } + + /** + * @override + */ + public async overwriteAccountAuth(accountInfo: AccountAuthInfo): Promise { + dispatcher.dispatch({ + action: Action.OverwriteLogin, + credentials: { + ...accountInfo, + guest: false, + }, + }, true); // require to be sync to match inherited interface behaviour + } + + /** + * @override + */ + public async navigatePermalink(uri: string, andJoin?: boolean): Promise { + navigateToPermalink(uri); + + const parts = parsePermalink(uri); + if (parts.roomIdOrAlias && andJoin) { + let roomId = parts.roomIdOrAlias; + let servers = parts.viaServers; + if (roomId.startsWith("#")) { + roomId = getCachedRoomIDForAlias(parts.roomIdOrAlias); + if (!roomId) { + // alias resolution failed + const result = await MatrixClientPeg.get().getRoomIdForAlias(parts.roomIdOrAlias); + roomId = result.room_id; + if (!servers) servers = result.servers; // use provided servers first, if available + } + } + dispatcher.dispatch({ + action: Action.ViewRoom, + room_id: roomId, + via_servers: servers, + }); + + if (andJoin) { + dispatcher.dispatch({ + action: Action.JoinRoom, + }); + } + } + } + + /** + * @override + */ + public getConfigValue(namespace: string, key: string): T { + // Force cast to `any` because the namespace won't be known to the SdkConfig types + const maybeObj = SdkConfig.get(namespace as any); + if (!maybeObj || !(typeof maybeObj === "object")) return undefined; + return maybeObj[key]; + } +} diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index 3fcc10283eb..3b617e6f314 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -46,10 +46,10 @@ import { WidgetType } from "../../widgets/WidgetType"; import { CHAT_EFFECTS } from "../../effects"; import { containsEmoji } from "../../effects/utils"; import dis from "../../dispatcher/dispatcher"; -import { tryTransformPermalinkToLocalHref } from "../../utils/permalinks/Permalinks"; import SettingsStore from "../../settings/SettingsStore"; import { RoomViewStore } from "../RoomViewStore"; import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities"; +import { navigateToPermalink } from "../../utils/permalinks/navigator"; // TODO: Purge this from the universe @@ -280,10 +280,6 @@ export class StopGapWidgetDriver extends WidgetDriver { } public async navigate(uri: string): Promise { - const localUri = tryTransformPermalinkToLocalHref(uri); - if (!localUri || localUri === uri) { // parse failure can lead to an unmodified URL - throw new Error("Failed to transform URI"); - } - window.location.hash = localUri; // it'll just be a fragment + navigateToPermalink(uri); } } diff --git a/src/utils/permalinks/navigator.ts b/src/utils/permalinks/navigator.ts new file mode 100644 index 00000000000..ffa4678dbea --- /dev/null +++ b/src/utils/permalinks/navigator.ts @@ -0,0 +1,30 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { tryTransformPermalinkToLocalHref } from "./Permalinks"; + +/** + * Converts a permalink to a local HREF and navigates accordingly. Throws if the permalink + * cannot be transformed. + * @param uri The permalink to navigate to. + */ +export function navigateToPermalink(uri: string): void { + const localUri = tryTransformPermalinkToLocalHref(uri); + if (!localUri || localUri === uri) { // parse failure can lead to an unmodified URL + throw new Error("Failed to transform URI"); + } + window.location.hash = localUri; // it'll just be a fragment +} diff --git a/test/modules/AppModule-test.ts b/test/modules/AppModule-test.ts new file mode 100644 index 00000000000..6fdf16d6945 --- /dev/null +++ b/test/modules/AppModule-test.ts @@ -0,0 +1,36 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MockModule } from "./MockModule"; +import { AppModule } from "../../src/modules/AppModule"; + +describe("AppModule", () => { + describe("constructor", () => { + it("should call the factory immediately", () => { + let module: MockModule; + const appModule = new AppModule((api) => { + if (module) { + throw new Error("State machine error: Factory called twice"); + } + module = new MockModule(api); + return module; + }); + expect(appModule.module).toBeDefined(); + expect(appModule.module).toBe(module); + expect(appModule.api).toBeDefined(); + }); + }); +}); diff --git a/test/modules/MockModule.ts b/test/modules/MockModule.ts new file mode 100644 index 00000000000..64964379893 --- /dev/null +++ b/test/modules/MockModule.ts @@ -0,0 +1,45 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule"; +import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi"; + +import { ModuleRunner } from "../../src/modules/ModuleRunner"; + +export class MockModule extends RuntimeModule { + public get apiInstance(): ModuleApi { + return this.moduleApi; + } + + public constructor(moduleApi: ModuleApi) { + super(moduleApi); + } +} + +export function registerMockModule(): MockModule { + let module: MockModule; + ModuleRunner.instance.registerModule(api => { + if (module) { + throw new Error("State machine error: ModuleRunner created the module twice"); + } + module = new MockModule(api); + return module; + }); + if (!module) { + throw new Error("State machine error: ModuleRunner did not create module"); + } + return module; +} diff --git a/test/modules/ModuleComponents-test.tsx b/test/modules/ModuleComponents-test.tsx new file mode 100644 index 00000000000..3bb39a9012a --- /dev/null +++ b/test/modules/ModuleComponents-test.tsx @@ -0,0 +1,41 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import { mount } from "enzyme"; +import { TextInputField } from "@matrix-org/react-sdk-module-api/lib/components/TextInputField"; +import { Spinner as ModuleSpinner } from "@matrix-org/react-sdk-module-api/lib/components/Spinner"; + +import "../../src/modules/ModuleRunner"; + +describe("Module Components", () => { + // Note: we're not testing to see if there's components that are missing a renderFactory() + // but rather that the renderFactory() for components we do know about is actually defined + // and working. + // + // We do this by deliberately not importing the ModuleComponents file itself, relying on the + // ModuleRunner import to do its job (as per documentation in ModuleComponents). + + it("should override the factory for a TextInputField", () => { + const component = mount( {}} />); + expect(component).toMatchSnapshot(); + }); + + it("should override the factory for a ModuleSpinner", () => { + const component = mount(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/test/modules/ModuleRunner-test.ts b/test/modules/ModuleRunner-test.ts new file mode 100644 index 00000000000..400d9705192 --- /dev/null +++ b/test/modules/ModuleRunner-test.ts @@ -0,0 +1,54 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { RoomPreviewOpts, RoomViewLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle"; + +import { MockModule, registerMockModule } from "./MockModule"; +import { ModuleRunner } from "../../src/modules/ModuleRunner"; + +describe("ModuleRunner", () => { + afterEach(() => { + ModuleRunner.instance.reset(); + }); + + // Translations implicitly tested by ProxiedModuleApi integration tests. + + describe("invoke", () => { + it("should invoke to every registered module", async () => { + const module1 = registerMockModule(); + const module2 = registerMockModule(); + + const wrapEmit = (module: MockModule) => new Promise((resolve) => { + module.on(RoomViewLifecycle.PreviewRoomNotLoggedIn, (val1, val2) => { + resolve([val1, val2]); + }); + }); + const promises = Promise.all([ + wrapEmit(module1), + wrapEmit(module2), + ]); + + const roomId = "!room:example.org"; + const opts: RoomPreviewOpts = { canJoin: false }; + ModuleRunner.instance.invoke(RoomViewLifecycle.PreviewRoomNotLoggedIn, opts, roomId); + const results = await promises; + expect(results).toEqual([ + [opts, roomId], // module 1 + [opts, roomId], // module 2 + ]); + }); + }); +}); diff --git a/test/modules/ProxiedModuleApi-test.ts b/test/modules/ProxiedModuleApi-test.ts new file mode 100644 index 00000000000..80890acfb1d --- /dev/null +++ b/test/modules/ProxiedModuleApi-test.ts @@ -0,0 +1,79 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations"; + +import { ProxiedModuleApi } from "../../src/modules/ProxiedModuleApi"; +import { stubClient } from "../test-utils"; +import { setLanguage } from "../../src/languageHandler"; +import { ModuleRunner } from "../../src/modules/ModuleRunner"; +import { registerMockModule } from "./MockModule"; + +describe("ProxiedApiModule", () => { + afterEach(() => { + ModuleRunner.instance.reset(); + }); + + // Note: Remainder is implicitly tested from end-to-end tests of modules. + + describe("translations", () => { + it("should cache translations", () => { + const api = new ProxiedModuleApi(); + expect(api.translations).toBeFalsy(); + + const translations: TranslationStringsObject = { + ["custom string"]: { + "en": "custom string", + "fr": "custom french string", + }, + }; + api.registerTranslations(translations); + expect(api.translations).toBe(translations); + }); + + describe("integration", () => { + it("should translate strings using translation system", async () => { + // Test setup + stubClient(); + + // Set up a module to pull translations through + const module = registerMockModule(); + const en = "custom string"; + const de = "custom german string"; + const enVars = "custom variable %(var)s"; + const varVal = "string"; + const deVars = "custom german variable %(var)s"; + const deFull = `custom german variable ${varVal}`; + expect(module.apiInstance).toBeInstanceOf(ProxiedModuleApi); + module.apiInstance.registerTranslations({ + [en]: { + "en": en, + "de": de, + }, + [enVars]: { + "en": enVars, + "de": deVars, + }, + }); + await setLanguage("de"); // calls `registerCustomTranslations()` for us + + // See if we can pull the German string out + expect(module.apiInstance.translateString(en)).toEqual(de); + expect(module.apiInstance.translateString(enVars, { var: varVal })).toEqual(deFull); + }); + }); + }); +}); diff --git a/test/modules/__snapshots__/ModuleComponents-test.tsx.snap b/test/modules/__snapshots__/ModuleComponents-test.tsx.snap new file mode 100644 index 00000000000..4dbad141d17 --- /dev/null +++ b/test/modules/__snapshots__/ModuleComponents-test.tsx.snap @@ -0,0 +1,66 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Module Components should override the factory for a ModuleSpinner 1`] = ` + + +
+
+
+ + +`; + +exports[`Module Components should override the factory for a TextInputField 1`] = ` + + +
+ + +
+
+
+`; diff --git a/yarn.lock b/yarn.lock index 26a80252e6d..a7131a96e0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1111,6 +1111,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.17.9": + version "7.18.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4" + integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.16.7", "@babel/template@^7.3.3": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" @@ -1572,6 +1579,13 @@ version "3.2.8" resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz#8d53636d045e1776e2a2ec6613e57330dd9ce856" +"@matrix-org/react-sdk-module-api@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@matrix-org/react-sdk-module-api/-/react-sdk-module-api-0.0.3.tgz#a7ac1b18a72d18d08290b81fa33b0d8d00a77d2b" + integrity sha512-jQmLhVIanuX0g7Jx1OIqlzs0kp72PfSpv3umi55qVPYcAPQmO252AUs0vncatK8O4e013vohdnNhly19a/kmLQ== + dependencies: + "@babel/runtime" "^7.17.9" + "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": version "2.1.8-no-fsevents.3" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b" From 434f39fa0f05a054e51aea44a7aabbfe362295c0 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Wed, 6 Jul 2022 06:33:40 +0000 Subject: [PATCH 036/162] Fix text flow of thread summary content on threads list (#8991) --- res/css/views/rooms/_EventTile.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 9e6d415557d..a0489d6b38c 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -850,6 +850,12 @@ $left-gutter: 64px; line-height: var(--EventTile_ThreadSummary-line-height); font-size: $font-12px; // Same font size as the counter on the main panel } + + .mx_ThreadSummary_content { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } } } From afcf6ced052b74df0a5d85c0bb6518f42a0846eb Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Wed, 6 Jul 2022 06:42:30 +0000 Subject: [PATCH 037/162] Include mx_RoomView_timeline_rr_enabled style rules in mx_EventTile[data-layout=group] (#8986) --- res/css/views/rooms/_EventTile.scss | 36 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index a0489d6b38c..4fc28d0bce7 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -241,6 +241,23 @@ $left-gutter: 64px; z-index: 9; } + .mx_RoomView_timeline_rr_enabled & { + $inline-end-margin: 80px; // TODO: Use a spacing variable + + .mx_ThreadSummary, + .mx_ThreadSummary_icon, + .mx_EventTile_line { + margin-right: $inline-end-margin; + min-height: $font-14px; + } + + .mx_ThreadSummary { + max-width: min(calc(100% - $left-gutter - $inline-end-margin), 600px); // leave space on both left & right gutters + } + + // on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter + } + &.mx_EventTile_continuation { padding-top: 0px !important; } @@ -484,25 +501,6 @@ $left-gutter: 64px; } } -.mx_RoomView_timeline_rr_enabled { - .mx_EventTile[data-layout=group] { - - .mx_ThreadSummary, - .mx_ThreadSummary_icon, - .mx_EventTile_line { - /* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */ - margin-right: 80px; - min-height: $font-14px; - } - - .mx_ThreadSummary { - max-width: min(calc(100% - $left-gutter - 80px), 600px); // leave space on both left & right gutters - } - } - - // on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter -} - .mx_EventTile_bigEmoji { font-size: 48px; line-height: 57px; From 530b51a5ac4d8b96fcf34b0608024f27c03d2062 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Wed, 6 Jul 2022 07:47:01 +0000 Subject: [PATCH 038/162] Remove obsolete style blocks - search results in bubble layout (#8990) * Remove an obsolete style block - mx_SenderProfile Signed-off-by: Suguru Hirahara * Display own avatars as they no longer overlap Signed-off-by: Suguru Hirahara * Empty commit Signed-off-by: Suguru Hirahara --- res/css/views/rooms/_EventBubbleTile.scss | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index 08acc7b3b32..21fcdf00755 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -31,22 +31,6 @@ limitations under the License. margin-right: 60px; } -.mx_RoomView_searchResultsPanel { - .mx_EventTile[data-layout=bubble] { - .mx_SenderProfile { - // Group layout adds a 64px left margin, which we really don't want on search results - margin-left: 0; - } - - &[data-self=true] { - // The avatars end up overlapping, so just hide them - .mx_EventTile_avatar { - display: none; - } - } - } -} - .mx_EventTile[data-layout=bubble] { --EventTile_bubble-margin-inline-start: 49px; --EventTile_bubble-margin-inline-end: 60px; From 6f21a155a4009b5aa8ddfe1cc7b397561ae756de Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Wed, 6 Jul 2022 11:43:30 +0200 Subject: [PATCH 039/162] Add option to display tooltip on link hover (#8394) * Add option to display tooltip on link hover This makes it possible for platforms like Electron apps, which lack a built-in URL preview in the status bar, to enable tooltip previews of links. Relates to: vector-im/element-web#6532 Signed-off-by: Johannes Marbach * Gracefully handle missing platform * Use public access modifier Co-authored-by: Travis Ralston * Use exact inequality Co-authored-by: Travis Ralston * Document getAbsoluteUrl * Appease the linter * Clarify performance impact in comment Co-authored-by: Travis Ralston * Use URL instead of anchor element hack * Wrap anchor in tooltip target and only allow focus on anchor * Use optional chaining Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> * Use double quotes for consistency * Accumulate and unmount tooltips and extract tooltipify.tsx * Fix indentation * Blur tooltip target on click * Remove space * Mention platform flag in comment * Add (simplistic) tests * Fix lint errors * Fix lint errors ... for real * Replace snapshot tests with structural assertions * Add missing semicolon * Add tooltips in link previews * Fix copyright Co-authored-by: Travis Ralston Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- src/BasePlatform.ts | 8 ++ .../views/elements/LinkWithTooltip.tsx | 44 ++++++++++ .../views/elements/TextWithTooltip.tsx | 4 +- .../views/messages/EditHistoryMessage.tsx | 12 +++ src/components/views/messages/TextualBody.tsx | 4 + .../views/rooms/LinkPreviewWidget.tsx | 11 ++- src/utils/tooltipify.tsx | 85 +++++++++++++++++++ test/utils/tooltipify-test.tsx | 58 +++++++++++++ 8 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 src/components/views/elements/LinkWithTooltip.tsx create mode 100644 src/utils/tooltipify.tsx create mode 100644 test/utils/tooltipify-test.tsx diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 3c83229755f..232b44d7c96 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -231,6 +231,14 @@ export default abstract class BasePlatform { } } + /** + * Returns true if the platform requires URL previews in tooltips, otherwise false. + * @returns {boolean} whether the platform requires URL previews in tooltips + */ + public needsUrlTooltips(): boolean { + return false; + } + /** * Returns a promise that resolves to a string representing the current version of the application. */ diff --git a/src/components/views/elements/LinkWithTooltip.tsx b/src/components/views/elements/LinkWithTooltip.tsx new file mode 100644 index 00000000000..b171df797f1 --- /dev/null +++ b/src/components/views/elements/LinkWithTooltip.tsx @@ -0,0 +1,44 @@ +/* + Copyright 2022 The Matrix.org Foundation C.I.C. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import React from 'react'; + +import TextWithTooltip from './TextWithTooltip'; + +interface IProps extends Omit, "tabIndex" | "onClick" > {} + +export default class LinkWithTooltip extends React.Component { + constructor(props: IProps) { + super(props); + } + + public render(): JSX.Element { + const { children, tooltip, ...props } = this.props; + + return ( + (e.target as HTMLElement).blur()} // Force tooltip to hide on clickout + {...props} + > + { children } + + ); + } +} diff --git a/src/components/views/elements/TextWithTooltip.tsx b/src/components/views/elements/TextWithTooltip.tsx index c8fa5376b87..33eacbeebed 100644 --- a/src/components/views/elements/TextWithTooltip.tsx +++ b/src/components/views/elements/TextWithTooltip.tsx @@ -14,12 +14,12 @@ limitations under the License. */ -import React from 'react'; +import React, { HTMLAttributes } from 'react'; import classNames from 'classnames'; import TooltipTarget from './TooltipTarget'; -interface IProps { +interface IProps extends HTMLAttributes { class?: string; tooltipClass?: string; tooltip: React.ReactNode; diff --git a/src/components/views/messages/EditHistoryMessage.tsx b/src/components/views/messages/EditHistoryMessage.tsx index 51c39be9b24..e3c11c61a47 100644 --- a/src/components/views/messages/EditHistoryMessage.tsx +++ b/src/components/views/messages/EditHistoryMessage.tsx @@ -22,6 +22,7 @@ import * as HtmlUtils from '../../../HtmlUtils'; import { editBodyDiffToHtml } from '../../../utils/MessageDiffUtils'; import { formatTime } from '../../../DateUtils'; import { pillifyLinks, unmountPills } from '../../../utils/pillify'; +import { tooltipifyLinks, unmountTooltips } from '../../../utils/tooltipify'; import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import Modal from '../../../Modal'; @@ -52,6 +53,7 @@ interface IState { export default class EditHistoryMessage extends React.PureComponent { private content = createRef(); private pills: Element[] = []; + private tooltips: Element[] = []; constructor(props: IProps) { super(props); @@ -93,12 +95,21 @@ export default class EditHistoryMessage extends React.PureComponent { private unmounted = false; private pills: Element[] = []; + private tooltips: Element[] = []; static contextType = RoomContext; public context!: React.ContextType; @@ -91,6 +93,7 @@ export default class TextualBody extends React.Component { // we should be pillify them here by doing the linkifying BEFORE the pillifying. pillifyLinks([this.contentRef.current], this.props.mxEvent, this.pills); HtmlUtils.linkifyElement(this.contentRef.current); + tooltipifyLinks([this.contentRef.current], this.pills, this.tooltips); this.calculateUrlPreview(); if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") { @@ -283,6 +286,7 @@ export default class TextualBody extends React.Component { componentWillUnmount() { this.unmounted = true; unmountPills(this.pills); + unmountTooltips(this.tooltips); } shouldComponentUpdate(nextProps, nextState) { diff --git a/src/components/views/rooms/LinkPreviewWidget.tsx b/src/components/views/rooms/LinkPreviewWidget.tsx index cb28739f179..af0b8a022e6 100644 --- a/src/components/views/rooms/LinkPreviewWidget.tsx +++ b/src/components/views/rooms/LinkPreviewWidget.tsx @@ -25,6 +25,8 @@ import Modal from "../../../Modal"; import * as ImageUtils from "../../../ImageUtils"; import { mediaFromMxc } from "../../../customisations/Media"; import ImageView from '../elements/ImageView'; +import LinkWithTooltip from '../elements/LinkWithTooltip'; +import PlatformPeg from '../../../PlatformPeg'; interface IProps { link: string; @@ -118,13 +120,20 @@ export default class LinkPreviewWidget extends React.Component { // opaque string. This does not allow any HTML to be injected into the DOM. const description = AllHtmlEntities.decode(p["og:description"] || ""); + const anchor = { p["og:title"] }; + const needsTooltip = PlatformPeg.get()?.needsUrlTooltips() && this.props.link !== p["og:title"].trim(); + return (
{ img }
- { p["og:title"] } + { needsTooltip ? + { anchor } + : anchor } { p["og:site_name"] && { (" - " + p["og:site_name"]) } } diff --git a/src/utils/tooltipify.tsx b/src/utils/tooltipify.tsx new file mode 100644 index 00000000000..ec698aa198b --- /dev/null +++ b/src/utils/tooltipify.tsx @@ -0,0 +1,85 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import ReactDOM from 'react-dom'; + +import PlatformPeg from "../PlatformPeg"; +import LinkWithTooltip from "../components/views/elements/LinkWithTooltip"; + +/** + * If the platform enabled needsUrlTooltips, recurses depth-first through a DOM tree, adding tooltip previews + * for link elements. Otherwise, does nothing. + * + * @param {Element[]} rootNodes - a list of sibling DOM nodes to traverse to try + * to add tooltips. + * @param {Element[]} ignoredNodes: a list of nodes to not recurse into. + * @param {Element[]} containers: an accumulator of the DOM nodes which contain + * React components that have been mounted by this function. The initial caller + * should pass in an empty array to seed the accumulator. + */ +export function tooltipifyLinks(rootNodes: ArrayLike, ignoredNodes: Element[], containers: Element[]) { + if (!PlatformPeg.get()?.needsUrlTooltips()) { + return; + } + + let node = rootNodes[0]; + + while (node) { + let tooltipified = false; + + if (ignoredNodes.indexOf(node) >= 0) { + node = node.nextSibling as Element; + continue; + } + + if (node.tagName === "A" && node.getAttribute("href") + && node.getAttribute("href") !== node.textContent.trim() + ) { + const container = document.createElement("span"); + const href = node.getAttribute("href"); + + const tooltip = + + ; + + ReactDOM.render(tooltip, container); + node.parentNode.replaceChild(container, node); + containers.push(container); + tooltipified = true; + } + + if (node.childNodes?.length && !tooltipified) { + tooltipifyLinks(node.childNodes as NodeListOf, ignoredNodes, containers); + } + + node = node.nextSibling as Element; + } +} + +/** + * Unmount tooltip containers created by tooltipifyLinks. + * + * It's critical to call this after tooltipifyLinks, otherwise + * tooltips will leak. + * + * @param {Element[]} containers - array of tooltip containers to unmount + */ +export function unmountTooltips(containers: Element[]) { + for (const container of containers) { + ReactDOM.unmountComponentAtNode(container); + } +} diff --git a/test/utils/tooltipify-test.tsx b/test/utils/tooltipify-test.tsx new file mode 100644 index 00000000000..b94c829faf3 --- /dev/null +++ b/test/utils/tooltipify-test.tsx @@ -0,0 +1,58 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { tooltipifyLinks } from '../../src/utils/tooltipify'; +import PlatformPeg from '../../src/PlatformPeg'; +import BasePlatform from '../../src/BasePlatform'; + +describe('tooltipify', () => { + jest.spyOn(PlatformPeg, 'get') + .mockReturnValue({ needsUrlTooltips: () => true } as unknown as BasePlatform); + + it('does nothing for empty element', () => { + const component = mount(
); + const root = component.getDOMNode(); + const originalHtml = root.outerHTML; + const containers: Element[] = []; + tooltipifyLinks([root], [], containers); + expect(containers).toHaveLength(0); + expect(root.outerHTML).toEqual(originalHtml); + }); + + it('wraps single anchor', () => { + const component = mount(); + const root = component.getDOMNode(); + const containers: Element[] = []; + tooltipifyLinks([root], [], containers); + expect(containers).toHaveLength(1); + const anchor = root.querySelector(".mx_TextWithTooltip_target a"); + expect(anchor?.getAttribute("href")).toEqual("/foo"); + expect(anchor?.innerHTML).toEqual("click"); + }); + + it('ignores node', () => { + const component = mount(); + const root = component.getDOMNode(); + const originalHtml = root.outerHTML; + const containers: Element[] = []; + tooltipifyLinks([root], [root.children[0]], containers); + expect(containers).toHaveLength(0); + expect(root.outerHTML).toEqual(originalHtml); + }); +}); From 2706f14456ab78cbcef92af4654e6026b94e6582 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Wed, 6 Jul 2022 10:16:24 +0000 Subject: [PATCH 040/162] Remove 100% min-width hack from mx_GenericEventListSummary[data-layout=bubble][data-expanded=false] (#8992) --- .../elements/_GenericEventListSummary.scss | 7 +++--- res/css/views/rooms/_EventBubbleTile.scss | 24 +++++++++++-------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/res/css/views/elements/_GenericEventListSummary.scss b/res/css/views/elements/_GenericEventListSummary.scss index 243506a447b..a3bbd13663d 100644 --- a/res/css/views/elements/_GenericEventListSummary.scss +++ b/res/css/views/elements/_GenericEventListSummary.scss @@ -43,8 +43,8 @@ limitations under the License. &[data-expanded=false] { display: flex; align-items: center; - justify-content: flex-start; - padding: 0 49px; // Align with left edge of bubble tiles + justify-content: space-between; + column-gap: 5px; } // ideally we'd use display=contents here for the layout to all work regardless of the *ELS but @@ -68,15 +68,14 @@ limitations under the License. .mx_GenericEventListSummary_toggle { margin-block: 0; - margin-inline-end: 55px; &[aria-expanded=false] { order: 9; - margin-inline-start: 5px; } &[aria-expanded=true] { margin-inline-start: auto; // reduce clickable area + margin-inline-end: var(--EventTile_bubble-margin-inline-end); // as the parent has zero margin } } diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index 21fcdf00755..9e87412ca53 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -28,12 +28,16 @@ limitations under the License. --gutterSize: 11px; --cornerRadius: 12px; --maxWidth: 70%; - margin-right: 60px; -} -.mx_EventTile[data-layout=bubble] { + // For both event tile and event list summary --EventTile_bubble-margin-inline-start: 49px; --EventTile_bubble-margin-inline-end: 60px; + + margin-inline-start: var(--EventTile_bubble-margin-inline-start); + margin-inline-end: var(--EventTile_bubble-margin-inline-end); +} + +.mx_EventTile[data-layout=bubble] { --EventTile_bubble_line-margin-inline-start: -9px; --EventTile_bubble_line-margin-inline-end: -12px; --EventTile_bubble_gap-inline: 5px; @@ -579,6 +583,13 @@ limitations under the License. display: flex; align-items: center; justify-content: flex-start; + + .mx_EventTile_line, + .mx_EventTile_info { + min-width: 100%; + // Preserve alignment with left edge of text in bubbles + margin: 0; + } } .mx_EventTile.mx_EventTile_bubbleContainer[data-layout=bubble], @@ -594,13 +605,6 @@ limitations under the License. margin-inline-end: var(--EventTile_bubble_gap-inline); // Same spacing between E2E icon and a hidden event } - .mx_EventTile_line, - .mx_EventTile_info { - min-width: 100%; - // Preserve alignment with left edge of text in bubbles - margin: 0; - } - .mx_EventTile_e2eIcon { margin-inline-start: 0; // mx_EventTile_avatar has margin-inline-end, so margin is not needed here align-self: center; From 5349f301da19b72dbc275d003fd25bc2dd985769 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Wed, 6 Jul 2022 10:44:23 +0000 Subject: [PATCH 041/162] Stop using :not() pseudo class for mx_GenericEventListSummary (#8944) * Stop using :not() pseudo class for mx_GenericEventListSummary Signed-off-by: Suguru Hirahara * Use a CSS variable for mx_GenericEventListSummary on _EventTile.scss - icon-width Signed-off-by: Suguru Hirahara * Use a CSS variable for mx_GenericEventListSummary on _EventTile.scss - right-padding Signed-off-by: Suguru Hirahara * Move declarations for EventTile_line of GenericEventListSummary for IRC layout from _IRCLayout.scss to _EventTile.scss Signed-off-by: Suguru Hirahara * Move mx_EventTile_line out of mx_GenericEventListSummary:not([data-layout=bubble]) Signed-off-by: Suguru Hirahara * Move common style rules up Signed-off-by: Suguru Hirahara * Set zero inline start padding to mx_EventTile_line of info tile of mx_GenericEventListSummary_unstyledList on IRC layout There should not be spacing between avatars and info tile line on IRC Layout Signed-off-by: Suguru Hirahara * Apply the rule to group/modern layout only Signed-off-by: Suguru Hirahara * Apply the inline start padding to modern/group layout only Overriding $left-gutter is not necessary for IRC layout Signed-off-by: Suguru Hirahara * Merge the style block for mx_EventTile_info .mx_EventTile_line Signed-off-by: Suguru Hirahara * Remove padding from info event tile line from mx_GenericEventListSummary on IRC layout Signed-off-by: Suguru Hirahara * Add spacing between avatar and a single info event tile line on IRC layout Signed-off-by: Suguru Hirahara * Stop using :not() pseudo class for mx_GenericEventListSummary on TimelineCard Signed-off-by: Suguru Hirahara * Fix padding of line with redacted body text Signed-off-by: Suguru Hirahara --- res/css/views/right_panel/_TimelineCard.scss | 13 +++--- res/css/views/rooms/_EventTile.scss | 45 ++++++++++++++++---- res/css/views/rooms/_IRCLayout.scss | 17 +++----- 3 files changed, 52 insertions(+), 23 deletions(-) diff --git a/res/css/views/right_panel/_TimelineCard.scss b/res/css/views/right_panel/_TimelineCard.scss index 6d608182cb5..d236ff46b11 100644 --- a/res/css/views/right_panel/_TimelineCard.scss +++ b/res/css/views/right_panel/_TimelineCard.scss @@ -149,11 +149,14 @@ limitations under the License. } } - .mx_GenericEventListSummary:not([data-layout=bubble]) { - .mx_EventTile_line, - > .mx_GenericEventListSummary_unstyledList > .mx_EventTile_info .mx_EventTile_avatar ~ .mx_EventTile_line { - padding-inline-start: var(--BaseCard_EventTile-spacing-inline); - padding-inline-end: var(--BaseCard_EventTile-spacing-inline); + .mx_GenericEventListSummary { + &[data-layout=irc], + &[data-layout=group] { + .mx_EventTile_line, + .mx_GenericEventListSummary_unstyledList > .mx_EventTile_info .mx_EventTile_avatar ~ .mx_EventTile_line { + padding-inline-start: var(--BaseCard_EventTile-spacing-inline); + padding-inline-end: var(--BaseCard_EventTile-spacing-inline); + } } } diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 4fc28d0bce7..9dc6b090b47 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -261,6 +261,10 @@ $left-gutter: 64px; &.mx_EventTile_continuation { padding-top: 0px !important; } + + &.mx_EventTile_info .mx_EventTile_line { + padding-left: calc($left-gutter + 20px); // override padding-left $left-gutter + } } &[data-layout=bubble] { @@ -366,17 +370,42 @@ $left-gutter: 64px; } } -.mx_GenericEventListSummary:not([data-layout=bubble]) .mx_EventTile_line { - padding-left: $left-gutter; +.mx_GenericEventListSummary { + &[data-layout=irc], + &[data-layout=group] { + .mx_EventTile_line .mx_RedactedBody { + line-height: 1; // remove spacing between lines + } + } - .mx_RedactedBody { - line-height: 1; // remove spacing between lines + &[data-layout=irc] { + .mx_EventTile_info .mx_EventTile_line { + padding-left: 0; // Override .mx_EventTile:not([data-layout="bubble"]).mx_EventTile_info .mx_EventTile_line + } + + .mx_EventTile_line .mx_RedactedBody { + padding-left: 24px; // 25px - 1px + + &::before { + left: var(--right-padding); + } + } + + // Apply only collapsed events block + > .mx_EventTile_line { + padding-left: calc(var(--name-width) + var(--icon-width) + $MessageTimestamp_width + 3 * var(--right-padding)); // 15 px of padding + } } -} -.mx_EventTile:not([data-layout=bubble]).mx_EventTile_info .mx_EventTile_line, -.mx_GenericEventListSummary:not([data-layout=bubble]) > .mx_GenericEventListSummary_unstyledList > .mx_EventTile_info .mx_EventTile_avatar ~ .mx_EventTile_line { - padding-left: calc($left-gutter + 20px); // override padding-left $left-gutter + &[data-layout=group] { + .mx_EventTile_line { + padding-left: $left-gutter; + } + + .mx_GenericEventListSummary_unstyledList > .mx_EventTile_info .mx_EventTile_avatar ~ .mx_EventTile_line { + padding-left: calc($left-gutter + 20px); // override padding-left $left-gutter + } + } } .mx_EventTile_content { diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss index dd4df6e296c..7a6a618324a 100644 --- a/res/css/views/rooms/_IRCLayout.scss +++ b/res/css/views/rooms/_IRCLayout.scss @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -$icon-width: 14px; -$right-padding: 5px; $irc-line-height: $font-18px; .mx_IRCLayout { --name-width: 70px; + --icon-width: 14px; + --right-padding: 5px; line-height: $irc-line-height !important; @@ -36,7 +36,7 @@ $irc-line-height: $font-18px; padding-top: 0; > * { - margin-right: $right-padding; + margin-right: var(--right-padding); } .mx_EventTile_msgOption { @@ -157,7 +157,7 @@ $irc-line-height: $font-18px; .mx_EventTile_emote { .mx_EventTile_avatar { - margin-left: calc(var(--name-width) + $icon-width + $right-padding); + margin-left: calc(var(--name-width) + var(--icon-width) + var(--right-padding)); } } @@ -165,18 +165,15 @@ $irc-line-height: $font-18px; margin: 0; } - .mx_GenericEventListSummary > .mx_EventTile_line { - padding-left: calc(var(--name-width) + $icon-width + $MessageTimestamp_width + 3 * $right-padding); // 15 px of padding - } - .mx_EventTile.mx_EventTile_info { .mx_EventTile_avatar { - left: calc(var(--name-width) + 10px + $icon-width); + left: calc(var(--name-width) + 10px + var(--icon-width)); top: 0; + margin-right: var(--right-padding); } .mx_EventTile_line { - left: calc(var(--name-width) + 10px + $icon-width); + left: calc(var(--name-width) + 10px + var(--icon-width)); } .mx_TextualEvent { From 2ebb5eb957529e9b6812e9977204f0cf513d0094 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Wed, 6 Jul 2022 10:45:18 +0000 Subject: [PATCH 042/162] Move mx_MessageTimestamp out of mx_EventTile:not([data-layout=bubble]) (#8976) * Move mx_MessageTimestamp out of mx_EventTile:not([data-layout=bubble]) Signed-off-by: Suguru Hirahara * Ensure left: 0 and text-align: center for mx_MessageTimestamp on MessageEditHistoryDialog Signed-off-by: Suguru Hirahara * Apply text-align to MessageTimestamp on IRC layout Signed-off-by: Suguru Hirahara * Apply text-align to MessageTimestamp on modern/group layout Signed-off-by: Suguru Hirahara * Apply left: 0 to modern/group layout only Signed-off-by: Suguru Hirahara * Move mx_MessageTimestamp out of mx_EventTile:not([data-layout=bubble]) Signed-off-by: Suguru Hirahara * Remove an obvious comment Signed-off-by: Suguru Hirahara * Cascading order Signed-off-by: Suguru Hirahara * Remove an obsolete comment Signed-off-by: Suguru Hirahara --- res/css/structures/_FilePanel.scss | 2 +- .../dialogs/_MessageEditHistoryDialog.scss | 3 ++ res/css/views/rooms/_EventTile.scss | 36 +++++++++++-------- res/css/views/rooms/_IRCLayout.scss | 4 --- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/res/css/structures/_FilePanel.scss b/res/css/structures/_FilePanel.scss index 3119162e268..cb791ebfcbc 100644 --- a/res/css/structures/_FilePanel.scss +++ b/res/css/structures/_FilePanel.scss @@ -96,7 +96,7 @@ limitations under the License. .mx_MessageTimestamp { flex: 1 0 0; - text-align: right; // FIXME: .mx_EventTile:not([data-layout=bubble]) .mx_MessageTimestamp + text-align: right; visibility: visible; position: initial; font-size: $font-14px; diff --git a/res/css/views/dialogs/_MessageEditHistoryDialog.scss b/res/css/views/dialogs/_MessageEditHistoryDialog.scss index db9c33bba07..cee4888a610 100644 --- a/res/css/views/dialogs/_MessageEditHistoryDialog.scss +++ b/res/css/views/dialogs/_MessageEditHistoryDialog.scss @@ -51,11 +51,14 @@ limitations under the License. text-decoration: underline; } + // Emulate mx_EventTile[data-layout=group] .mx_EventTile { padding-top: 0 !important; // Override mx_EventTile:not([data-layout=bubble]) .mx_MessageTimestamp { position: absolute; + left: 0; + text-align: center; } .mx_EventTile_line { diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 9dc6b090b47..f9479674363 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -195,16 +195,18 @@ $left-gutter: 64px; } } + &[data-layout=irc] { + .mx_MessageTimestamp { + text-align: right; + } + } + &[data-layout=group] { .mx_EventTile_avatar { top: 14px; left: $spacing-8; } - .mx_MessageTimestamp { - position: absolute; // for modern layout - } - .mx_EventTile_line, .mx_EventTile_reply { padding-top: var(--EventTile_group_line-spacing-block-start); @@ -221,6 +223,12 @@ $left-gutter: 64px; margin-right: 10px; } + .mx_MessageTimestamp { + position: absolute; + left: 0; + text-align: center; + } + .mx_ThreadSummary, .mx_ThreadSummary_icon { margin-left: $left-gutter; @@ -308,11 +316,6 @@ $left-gutter: 64px; } } - .mx_MessageTimestamp { - left: 0px; - text-align: center; - } - /* this is used for the tile for the event which is selected via the URL. * TODO: ultimately we probably want some transition on here. */ @@ -957,11 +960,14 @@ $left-gutter: 64px; } } - &:not([data-layout=bubble]) { + &[data-layout=irc], + &[data-layout=group] { .mx_MessageTimestamp { top: 2px; // Align with avatar } + } + &:not([data-layout=bubble]) { .mx_EventTile_avatar { left: calc($MessageTimestamp_width + 14px - 4px); // 14px: avatar width, 4px: align with text z-index: 9; // position above the hover styling @@ -1067,11 +1073,6 @@ $left-gutter: 64px; } } - .mx_MessageTimestamp { - top: 2px; // Align with mx_EventTile_content - position: absolute; // for IRC layout - } - .mx_EventTile_senderDetails { display: flex; align-items: center; @@ -1091,6 +1092,11 @@ $left-gutter: 64px; } } } + + .mx_MessageTimestamp { + position: absolute; // for IRC layout + top: 2px; // Align with mx_EventTile_content + } } } diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss index 7a6a618324a..566338f966b 100644 --- a/res/css/views/rooms/_IRCLayout.scss +++ b/res/css/views/rooms/_IRCLayout.scss @@ -115,10 +115,6 @@ $irc-line-height: $font-18px; } } - .mx_MessageTimestamp { - text-align: right; - } - .mx_EventTile_e2eIcon { padding: 0; From afa8b01601b1c9288f907fcf60f2dd3179940d15 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Wed, 6 Jul 2022 10:48:52 +0000 Subject: [PATCH 043/162] Fix headings margin on security user settings tab (#8826) * Apply kind=link to 'Learn more' link on security user settings tab Signed-off-by: Suguru Hirahara * Remove specific margin setting from heading on security user settings tab Signed-off-by: Suguru Hirahara --- res/css/views/settings/tabs/_SettingsTab.scss | 6 ------ .../settings/tabs/user/_SecurityUserSettingsTab.scss | 3 --- .../settings/tabs/user/SecurityUserSettingsTab.tsx | 11 ++++++----- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss index 49f4282ec5c..9d437443cf1 100644 --- a/res/css/views/settings/tabs/_SettingsTab.scss +++ b/res/css/views/settings/tabs/_SettingsTab.scss @@ -92,12 +92,6 @@ limitations under the License. } } -.mx_SettingsTab_linkBtn { - cursor: pointer; - color: $accent; - word-break: break-all; -} - .mx_SettingsTab_toggleWithDescription { margin-top: $spacing-24; } diff --git a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss index ddcf7c11ac8..3dad2a49a16 100644 --- a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss @@ -27,9 +27,6 @@ limitations under the License. } .mx_SecurityUserSettingsTab { - .mx_SettingsTab_heading { - margin-bottom: 22px; - } .mx_SettingsTab_section { .mx_AccessibleButton_kind_link { font-size: inherit; diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx index a0ae47b8c6f..5a950d65355 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx @@ -306,11 +306,12 @@ export default class SecurityUserSettingsTab extends React.Component -

- - { _t("Learn more") } - -

+ + { _t("Learn more") } +
{ PosthogAnalytics.instance.isEnabled() && ( Date: Wed, 6 Jul 2022 13:29:26 +0200 Subject: [PATCH 044/162] Task - replace img icons with svg components (#8963) * add role=presentation to backdrop panle image * replace img icons with svg components in InviteDialog * replace img icon with svg component * img icons to svg icons in MImageBody * remove log * img icon to svg in SecurityRoomSettingsTab * use shared error message for media message tiles * remove nbsp * dont snapshot test entire rtl render response * use aria-describedby for uploadconfirm preview * use aria-labelledby instead --- res/css/_components.scss | 1 + .../shared/_MediaProcessingError.scss | 20 +++++++++++ src/components/structures/BackdropPanel.tsx | 2 ++ src/components/views/dialogs/InviteDialog.tsx | 10 +++--- .../views/dialogs/UploadConfirmDialog.tsx | 15 +++++--- src/components/views/messages/MAudioBody.tsx | 6 ++-- src/components/views/messages/MImageBody.tsx | 6 ++-- src/components/views/messages/MVideoBody.tsx | 6 ++-- .../views/messages/MVoiceMessageBody.tsx | 6 ++-- .../messages/shared/MediaProcessingError.tsx | 33 ++++++++++++++++++ .../tabs/room/SecurityRoomSettingsTab.tsx | 3 +- .../shared/MediaProcessingError-test.tsx | 34 +++++++++++++++++++ .../MediaProcessingError-test.tsx.snap | 16 +++++++++ 13 files changed, 135 insertions(+), 23 deletions(-) create mode 100644 res/css/components/views/messages/shared/_MediaProcessingError.scss create mode 100644 src/components/views/messages/shared/MediaProcessingError.tsx create mode 100644 test/components/views/messages/shared/MediaProcessingError-test.tsx create mode 100644 test/components/views/messages/shared/__snapshots__/MediaProcessingError-test.tsx.snap diff --git a/res/css/_components.scss b/res/css/_components.scss index 74f5feedf39..d13b0f5a58a 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -26,6 +26,7 @@ @import "./components/views/location/_ShareType.scss"; @import "./components/views/location/_ZoomButtons.scss"; @import "./components/views/messages/_MBeaconBody.scss"; +@import "./components/views/messages/shared/_MediaProcessingError.scss"; @import "./components/views/spaces/_QuickThemeSwitcher.scss"; @import "./structures/_AutoHideScrollbar.scss"; @import "./structures/_BackdropPanel.scss"; diff --git a/res/css/components/views/messages/shared/_MediaProcessingError.scss b/res/css/components/views/messages/shared/_MediaProcessingError.scss new file mode 100644 index 00000000000..97b82ad0f19 --- /dev/null +++ b/res/css/components/views/messages/shared/_MediaProcessingError.scss @@ -0,0 +1,20 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_MediaProcessingError_Icon { + margin-right: $spacing-4; + vertical-align: text-top; +} diff --git a/src/components/structures/BackdropPanel.tsx b/src/components/structures/BackdropPanel.tsx index 08f6c337389..4247188d677 100644 --- a/src/components/structures/BackdropPanel.tsx +++ b/src/components/structures/BackdropPanel.tsx @@ -36,6 +36,8 @@ export const BackdropPanel: React.FC = ({ backgroundImage, blurMultiplie } return
diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index 478a0f8d505..28caf66ec08 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -21,6 +21,8 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import { logger } from "matrix-js-sdk/src/logger"; +import { Icon as InfoIcon } from "../../../../res/img/element-icons/info.svg"; +import { Icon as EmailPillAvatarIcon } from "../../../../res/img/icon-email-pill-avatar.svg"; import { _t, _td } from "../../../languageHandler"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { makeRoomPermalink, makeUserPermalink } from "../../../utils/permalinks/Permalinks"; @@ -186,8 +188,7 @@ class DMRoomTile extends React.PureComponent { const avatarSize = 36; const avatar = (this.props.member as ThreepidMember).isEmail - ? @@ -1152,10 +1153,7 @@ export default class InviteDialog extends React.PureComponent - + { " " + _t("Invited people will be able to read old messages.") }

; } diff --git a/src/components/views/dialogs/UploadConfirmDialog.tsx b/src/components/views/dialogs/UploadConfirmDialog.tsx index c705da1ac3c..bd68171240e 100644 --- a/src/components/views/dialogs/UploadConfirmDialog.tsx +++ b/src/components/views/dialogs/UploadConfirmDialog.tsx @@ -18,6 +18,7 @@ limitations under the License. import React from 'react'; import filesize from "filesize"; +import { Icon as FileIcon } from '../../../../res/img/feather-customised/files.svg'; import { _t } from '../../../languageHandler'; import { getBlobSafeMimeType } from '../../../utils/blobs'; import BaseDialog from "./BaseDialog"; @@ -80,11 +81,16 @@ export default class UploadConfirmDialog extends React.Component { title = _t('Upload files'); } + const fileId = `mx-uploadconfirmdialog-${this.props.file.name}`; let preview: JSX.Element; let placeholder: JSX.Element; if (this.mimeType.startsWith("image/")) { preview = ( - + ); } else if (this.mimeType.startsWith("video/")) { preview = ( @@ -92,9 +98,10 @@ export default class UploadConfirmDialog extends React.Component { ); } else { placeholder = ( - ); } @@ -118,7 +125,7 @@ export default class UploadConfirmDialog extends React.Component {
{ preview &&
{ preview }
} -
+
{ placeholder } { this.props.file.name } ({ filesize(this.props.file.size) })
diff --git a/src/components/views/messages/MAudioBody.tsx b/src/components/views/messages/MAudioBody.tsx index 9d20c667e87..5841c03bb39 100644 --- a/src/components/views/messages/MAudioBody.tsx +++ b/src/components/views/messages/MAudioBody.tsx @@ -28,6 +28,7 @@ import { PlaybackManager } from "../../../audio/PlaybackManager"; import { isVoiceMessage } from "../../../utils/EventUtils"; import { PlaybackQueue } from "../../../audio/PlaybackQueue"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; +import MediaProcessingError from "./shared/MediaProcessingError"; interface IState { error?: Error; @@ -93,10 +94,9 @@ export default class MAudioBody extends React.PureComponent public render() { if (this.state.error) { return ( - - + { _t("Error processing audio message") } - + ); } diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index 371b88db412..87d7e419e35 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -38,6 +38,7 @@ import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContex import { blobIsAnimated, mayBeAnimated } from '../../../utils/Image'; import { presentableTextForFile } from "../../../utils/FileUtils"; import { createReconnectedListener } from '../../../utils/connection'; +import MediaProcessingError from './shared/MediaProcessingError'; enum Placeholder { NoImage, @@ -552,10 +553,9 @@ export default class MImageBody extends React.Component { if (this.state.error) { return ( -
- + { _t("Error decrypting image") } -
+ ); } diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index c46f64a3011..fefedc6775e 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -28,6 +28,7 @@ import { IBodyProps } from "./IBodyProps"; import MFileBody from "./MFileBody"; import { ImageSize, suggestedSize as suggestedVideoSize } from "../../../settings/enums/ImageSize"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; +import MediaProcessingError from './shared/MediaProcessingError'; interface IState { decryptedUrl?: string; @@ -244,10 +245,9 @@ export default class MVideoBody extends React.PureComponent if (this.state.error !== null) { return ( - - + { _t("Error decrypting video") } - + ); } diff --git a/src/components/views/messages/MVoiceMessageBody.tsx b/src/components/views/messages/MVoiceMessageBody.tsx index 65521225ba4..7bcefffdb92 100644 --- a/src/components/views/messages/MVoiceMessageBody.tsx +++ b/src/components/views/messages/MVoiceMessageBody.tsx @@ -21,16 +21,16 @@ import { _t } from "../../../languageHandler"; import RecordingPlayback from "../audio_messages/RecordingPlayback"; import MAudioBody from "./MAudioBody"; import MFileBody from "./MFileBody"; +import MediaProcessingError from "./shared/MediaProcessingError"; export default class MVoiceMessageBody extends MAudioBody { // A voice message is an audio file but rendered in a special way. public render() { if (this.state.error) { return ( - - + { _t("Error processing voice message") } - + ); } diff --git a/src/components/views/messages/shared/MediaProcessingError.tsx b/src/components/views/messages/shared/MediaProcessingError.tsx new file mode 100644 index 00000000000..4584a6b55a5 --- /dev/null +++ b/src/components/views/messages/shared/MediaProcessingError.tsx @@ -0,0 +1,33 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; + +import { Icon as WarningIcon } from '../../../../../res/img/warning.svg'; + +interface Props { + className?: string; + children: React.ReactNode; +} + +const MediaProcessingError: React.FC = ({ className, children }) => ( + + + { children } + +); + +export default MediaProcessingError; diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx index 14a937c5f68..361b82b0e24 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx @@ -21,6 +21,7 @@ import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; import { EventType } from 'matrix-js-sdk/src/@types/event'; import { logger } from "matrix-js-sdk/src/logger"; +import { Icon as WarningIcon } from "../../../../../../res/img/warning.svg"; import { _t } from "../../../../../languageHandler"; import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import Modal from "../../../../../Modal"; @@ -231,7 +232,7 @@ export default class SecurityRoomSettingsTab extends React.Component - + { _t("To link to this room, please add an address.") } diff --git a/test/components/views/messages/shared/MediaProcessingError-test.tsx b/test/components/views/messages/shared/MediaProcessingError-test.tsx new file mode 100644 index 00000000000..114e56f5112 --- /dev/null +++ b/test/components/views/messages/shared/MediaProcessingError-test.tsx @@ -0,0 +1,34 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { render } from '@testing-library/react'; + +import MediaProcessingError from '../../../../../src/components/views/messages/shared/MediaProcessingError'; + +describe('', () => { + const defaultProps = { + className: 'test-classname', + children: 'Something went wrong', + }; + const getComponent = (props = {}) => + render(); + + it('renders', () => { + const { container } = getComponent(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/test/components/views/messages/shared/__snapshots__/MediaProcessingError-test.tsx.snap b/test/components/views/messages/shared/__snapshots__/MediaProcessingError-test.tsx.snap new file mode 100644 index 00000000000..6b50edce75b --- /dev/null +++ b/test/components/views/messages/shared/__snapshots__/MediaProcessingError-test.tsx.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders 1`] = ` +
+ +
+ Something went wrong + +
+`; From e65409861aac1b6d7b7d3f2d1eb7cfe16ea7bacd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 6 Jul 2022 14:07:10 +0200 Subject: [PATCH 045/162] Don't show the same user twice in Spotlight (#8978) Co-authored-by: Janne Mareike Koschinski --- .../12-spotlight/spotlight.spec.ts | 32 +++++++++++++++++++ .../dialogs/spotlight/SpotlightDialog.tsx | 26 ++++++++++++--- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/cypress/integration/12-spotlight/spotlight.spec.ts b/cypress/integration/12-spotlight/spotlight.spec.ts index e5507af7e62..9b2f290cfa2 100644 --- a/cypress/integration/12-spotlight/spotlight.spec.ts +++ b/cypress/integration/12-spotlight/spotlight.spec.ts @@ -55,6 +55,7 @@ declare global { roomHeaderName( options?: Partial ): Chainable>; + startDM(name: string): Chainable; } } } @@ -109,6 +110,20 @@ Cypress.Commands.add("roomHeaderName", ( return cy.get(".mx_RoomHeader_nametext", options); }); +Cypress.Commands.add("startDM", (name: string) => { + cy.openSpotlightDialog().within(() => { + cy.spotlightFilter(Filter.People); + cy.spotlightSearch().clear().type(name); + cy.get(".mx_Spinner").should("not.exist"); + cy.spotlightResults().should("have.length", 1); + cy.spotlightResults().eq(0).should("contain", name); + cy.spotlightResults().eq(0).click(); + }).then(() => { + cy.roomHeaderName().should("contain", name); + cy.get(".mx_RoomSublist[aria-label=People]").should("contain", name); + }); +}); + describe("Spotlight", () => { let synapse: SynapseInstance; @@ -319,6 +334,23 @@ describe("Spotlight", () => { }); }); + it("should close spotlight after starting a DM", () => { + cy.startDM(bot1Name); + cy.get(".mx_SpotlightDialog").should("have.length", 0); + }); + + it("should show the same user only once", () => { + cy.startDM(bot1Name); + cy.visit("/#/home"); + + cy.openSpotlightDialog().within(() => { + cy.spotlightFilter(Filter.People); + cy.spotlightSearch().clear().type(bot1Name); + cy.get(".mx_Spinner").should("not.exist"); + cy.spotlightResults().should("have.length", 1); + }); + }); + it("should be able to navigate results via keyboard", () => { cy.openSpotlightDialog().within(() => { cy.spotlightFilter(Filter.People); diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index 0bdb5b1cc9c..6013b5721f3 100644 --- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx +++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx @@ -320,8 +320,25 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n ); const possibleResults = useMemo( () => { - const roomMembers = findVisibleRoomMembers(cli); - const roomMemberIds = new Set(roomMembers.map(item => item.userId)); + const roomResults = findVisibleRooms(cli).map(toRoomResult); + // If we already have a DM with the user we're looking for, we will + // show that DM instead of the user themselves + const alreadyAddedUserIds = roomResults.reduce((userIds, result) => { + const userId = DMRoomMap.shared().getUserIdForRoomId(result.room.roomId); + if (!userId) return userIds; + if (result.room.getJoinedMemberCount() > 2) return userIds; + userIds.add(userId); + return userIds; + }, new Set()); + const userResults = []; + for (const user of [...findVisibleRoomMembers(cli), ...users]) { + // Make sure we don't have any user more than once + if (alreadyAddedUserIds.has(user.userId)) continue; + alreadyAddedUserIds.add(user.userId); + + userResults.push(toMemberResult(user)); + } + return [ ...SpaceStore.instance.enabledMetaSpaces.map(spaceKey => ({ section: Section.Spaces, @@ -335,9 +352,8 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n SpaceStore.instance.setActiveSpace(spaceKey); }, })), - ...findVisibleRooms(cli).map(toRoomResult), - ...roomMembers.map(toMemberResult), - ...users.filter(item => !roomMemberIds.has(item.userId)).map(toMemberResult), + ...roomResults, + ...userResults, ...(profile ? [new DirectoryMember(profile)] : []).map(toMemberResult), ...publicRooms.map(toPublicRoomResult), ].filter(result => filter === null || result.filter.includes(filter)); From 60faf6d025a3a3e49bb997964936f90bdd88f1b0 Mon Sep 17 00:00:00 2001 From: Kerry Date: Wed, 6 Jul 2022 16:34:33 +0200 Subject: [PATCH 046/162] Live location share - tiles without tile server (PSG-591) (#8962) * live location without map POC * styles * force map tiles to show no map for test build * check latestlocationstate exists * just use loading style map fallback when cant display map * style map error for tile view * set pointer cursor when map error is clickable * test mbeaconbody with map display error, lint * lint more good * remove changes for first attempt tile * make maperror test id more accurate * fussy import ordering * PR tweaks --- .../views/beacon/_OwnBeaconStatus.scss | 4 + .../components/views/location/_MapError.scss | 40 +++- .../views/messages/_MBeaconBody.scss | 34 ++- .../views/beacon/BeaconViewDialog.tsx | 25 +- src/components/views/location/MapError.tsx | 45 +++- src/components/views/location/MapFallback.tsx | 1 - src/components/views/messages/MBeaconBody.tsx | 44 +++- .../views/location/LocationPicker-test.tsx | 6 +- .../views/location/MapError-test.tsx | 33 ++- .../__snapshots__/MapError-test.tsx.snap | 132 +++++------ .../views/messages/MBeaconBody-test.tsx | 224 ++++++++++-------- .../__snapshots__/MBeaconBody-test.tsx.snap | 35 +++ 12 files changed, 421 insertions(+), 202 deletions(-) create mode 100644 test/components/views/messages/__snapshots__/MBeaconBody-test.tsx.snap diff --git a/res/css/components/views/beacon/_OwnBeaconStatus.scss b/res/css/components/views/beacon/_OwnBeaconStatus.scss index aa01b6269a4..cc22fd7a0cc 100644 --- a/res/css/components/views/beacon/_OwnBeaconStatus.scss +++ b/res/css/components/views/beacon/_OwnBeaconStatus.scss @@ -14,6 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_OwnBeaconStatus_button { + margin-left: $spacing-8; +} + .mx_EventTile[data-layout="bubble"] .mx_OwnBeaconStatus_button { // align to top to make room for timestamp // in bubble view diff --git a/res/css/components/views/location/_MapError.scss b/res/css/components/views/location/_MapError.scss index 83d6316ec44..7dc8a684d11 100644 --- a/res/css/components/views/location/_MapError.scss +++ b/res/css/components/views/location/_MapError.scss @@ -18,9 +18,41 @@ limitations under the License. padding: 100px $spacing-32 0; text-align: center; - p { - margin: $spacing-16 0 $spacing-32; + --mx-map-error-icon-color: $secondary-content; + --mx-map-error-icon-size: 58px; +} + +.mx_MapError.mx_MapError_isMinimised { + height: 100%; + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + padding: $spacing-24; + background-color: $panels; + font-size: $font-12px; + line-height: $font-16px; + + --mx-map-error-icon-color: $alert; + --mx-map-error-icon-size: 26px; + + .mx_MapError_message { + margin: 0; + max-width: 275px; } + + .mx_MapError_heading { + padding-top: $spacing-8; + // override h3 heading size + font-size: inherit !important; + font-weight: normal !important; + } +} + +.mx_MapError_message { + margin: $spacing-16 0 $spacing-32; } .mx_MapError_heading { @@ -28,9 +60,9 @@ limitations under the License. } .mx_MapError_icon { - height: 58px; + height: var(--mx-map-error-icon-size); path { - fill: $secondary-content; + fill: var(--mx-map-error-icon-color); } } diff --git a/res/css/components/views/messages/_MBeaconBody.scss b/res/css/components/views/messages/_MBeaconBody.scss index aed1cb44d3d..446b7e6e3fe 100644 --- a/res/css/components/views/messages/_MBeaconBody.scss +++ b/res/css/components/views/messages/_MBeaconBody.scss @@ -24,6 +24,29 @@ limitations under the License. overflow: hidden; } +.mx_MBeaconBody.mx_MBeaconBody_withoutMap { + height: auto; + + .mx_MBeaconBody_chin { + position: relative; + background-color: transparent; + } +} + +.mx_MBeaconBody_withoutMapContent { + background-color: $panels; + border-radius: 4px; +} + +.mx_MBeaconBody_withoutMapInfoLastUpdated { + // 48px lines up with icon in BeaconStatus + margin-top: -$spacing-8; + padding: 0 $spacing-8 $spacing-8 48px; + + color: $tertiary-content; + font-size: $font-10px; +} + .mx_MBeaconBody_map { height: 100%; width: 100%; @@ -32,11 +55,18 @@ limitations under the License. cursor: pointer; } -.mx_MBeaconBody_mapFallback { +.mx_MBeaconBody_mapFallback, +.mx_MBeaconBody_mapError { // pushes spinner/icon up // to appear more centered with the footer - padding-bottom: 50px; + padding-bottom: 50px !important; +} +.mx_MBeaconBody_mapErrorInteractive { + cursor: pointer; +} + +.mx_MBeaconBody_mapFallback { cursor: default; } diff --git a/src/components/views/beacon/BeaconViewDialog.tsx b/src/components/views/beacon/BeaconViewDialog.tsx index f3e2fd12a17..af21c64339d 100644 --- a/src/components/views/beacon/BeaconViewDialog.tsx +++ b/src/components/views/beacon/BeaconViewDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { MatrixClient } from 'matrix-js-sdk/src/client'; import { Beacon, @@ -38,6 +38,8 @@ import DialogSidebar from './DialogSidebar'; import DialogOwnBeaconStatus from './DialogOwnBeaconStatus'; import BeaconStatusTooltip from './BeaconStatusTooltip'; import MapFallback from '../location/MapFallback'; +import { MapError } from '../location/MapError'; +import { LocationShareError } from '../../../utils/location'; interface IProps extends IDialogProps { roomId: Room['roomId']; @@ -83,6 +85,15 @@ const BeaconViewDialog: React.FC = ({ const { bounds, centerGeoUri } = useInitialMapPosition(liveBeacons, focusBeacon); + const [mapDisplayError, setMapDisplayError] = useState(); + + // automatically open the sidebar if there is no map to see + useEffect(() => { + if (mapDisplayError) { + setSidebarOpen(true); + } + }, [mapDisplayError]); + return ( = ({ fixedWidth={false} > - { !!liveBeacons?.length ? { @@ -109,7 +121,14 @@ const BeaconViewDialog: React.FC = ({ } - : + } + { mapDisplayError && + + } + { !liveBeacons?.length && !mapDisplayError && void; +export interface MapErrorProps { error: LocationShareError; + onFinished?: () => void; + isMinimised?: boolean; + className?: string; + onClick?: () => void; } -export const MapError: React.FC = ({ - onFinished, error, -}) => (
- - { _t("Unable to load map") } -

- { getLocationShareErrorMessage(error) } -

- { _t("OK") } -
); +export const MapError: React.FC = ({ + error, + isMinimised, + className, + onFinished, + onClick, +}) => ( +
+ + { _t('Unable to load map') } +

+ { getLocationShareErrorMessage(error) } +

+ { onFinished && + + { _t('OK') } + + } +
+); diff --git a/src/components/views/location/MapFallback.tsx b/src/components/views/location/MapFallback.tsx index 75545d5e0fd..702090d5dde 100644 --- a/src/components/views/location/MapFallback.tsx +++ b/src/components/views/location/MapFallback.tsx @@ -30,7 +30,6 @@ interface Props extends React.HTMLAttributes { const MapFallback: React.FC = ({ className, isLoading, children, ...rest }) => { return
- { /*
*/ } { isLoading ? : } { children }
; diff --git a/src/components/views/messages/MBeaconBody.tsx b/src/components/views/messages/MBeaconBody.tsx index fda27633ec8..a5022317d27 100644 --- a/src/components/views/messages/MBeaconBody.tsx +++ b/src/components/views/messages/MBeaconBody.tsx @@ -26,17 +26,19 @@ import { import { BeaconLocationState } from 'matrix-js-sdk/src/content-helpers'; import { randomString } from 'matrix-js-sdk/src/randomstring'; import { M_BEACON } from 'matrix-js-sdk/src/@types/beacon'; +import classNames from 'classnames'; import MatrixClientContext from '../../../contexts/MatrixClientContext'; import { useEventEmitterState } from '../../../hooks/useEventEmitter'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; import { isBeaconWaitingToStart, useBeacon } from '../../../utils/beacon'; -import { isSelfLocation } from '../../../utils/location'; +import { isSelfLocation, LocationShareError } from '../../../utils/location'; import { BeaconDisplayStatus, getBeaconDisplayStatus } from '../beacon/displayStatus'; import BeaconStatus from '../beacon/BeaconStatus'; import OwnBeaconStatus from '../beacon/OwnBeaconStatus'; import Map from '../location/Map'; +import { MapError } from '../location/MapError'; import MapFallback from '../location/MapFallback'; import SmartMarker from '../location/SmartMarker'; import { GetRelationsForEvent } from '../rooms/EventTile'; @@ -136,7 +138,16 @@ const MBeaconBody: React.FC = React.forwardRef(({ mxEvent, getRelati const matrixClient = useContext(MatrixClientContext); const [error, setError] = useState(); - const displayStatus = getBeaconDisplayStatus(isLive, latestLocationState, error, waitingToStart); + const isMapDisplayError = error?.message === LocationShareError.MapStyleUrlNotConfigured || + error?.message === LocationShareError.MapStyleUrlNotReachable; + const displayStatus = getBeaconDisplayStatus( + isLive, + latestLocationState, + // if we are unable to display maps because it is not configured for the server + // don't display an error + isMapDisplayError ? undefined : error, + waitingToStart, + ); const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined; const isOwnBeacon = beacon?.beaconInfoOwner === matrixClient.getUserId(); @@ -152,6 +163,7 @@ const MBeaconBody: React.FC = React.forwardRef(({ mxEvent, getRelati roomId: mxEvent.getRoomId(), matrixClient, focusBeacon: beacon, + isMapDisplayError, }, "mx_BeaconViewDialog_wrapper", false, // isPriority @@ -160,8 +172,11 @@ const MBeaconBody: React.FC = React.forwardRef(({ mxEvent, getRelati }; return ( -
- { displayStatus === BeaconDisplayStatus.Active ? +
+ { (displayStatus === BeaconDisplayStatus.Active && !isMapDisplayError) ? = React.forwardRef(({ mxEvent, getRelati /> } - : + : isMapDisplayError ? + : + } { isOwnBeacon ? { wrapper.setProps({}); }); - expect(findByTestId(wrapper, 'location-picker-error').find('p').text()).toEqual( + expect(findByTestId(wrapper, 'map-rendering-error').find('p').text()).toEqual( "This homeserver is not configured correctly to display maps, " + "or the configured map server may be unreachable.", ); @@ -115,7 +115,7 @@ describe("LocationPicker", () => { const wrapper = getComponent(); wrapper.setProps({}); - expect(findByTestId(wrapper, 'location-picker-error').find('p').text()).toEqual( + expect(findByTestId(wrapper, 'map-rendering-error').find('p').text()).toEqual( "This homeserver is not configured to display maps.", ); }); @@ -130,7 +130,7 @@ describe("LocationPicker", () => { const wrapper = getComponent(); wrapper.setProps({}); - expect(findByTestId(wrapper, 'location-picker-error').find('p').text()).toEqual( + expect(findByTestId(wrapper, 'map-rendering-error').find('p').text()).toEqual( "This homeserver is not configured correctly to display maps, " + "or the configured map server may be unreachable.", ); diff --git a/test/components/views/location/MapError-test.tsx b/test/components/views/location/MapError-test.tsx index 2fa9cf3cd0d..27a42dd95a2 100644 --- a/test/components/views/location/MapError-test.tsx +++ b/test/components/views/location/MapError-test.tsx @@ -15,28 +15,45 @@ limitations under the License. */ import React from 'react'; -import { mount } from 'enzyme'; +import { render, RenderResult } from '@testing-library/react'; -import { MapError } from '../../../../src/components/views/location/MapError'; +import { MapError, MapErrorProps } from '../../../../src/components/views/location/MapError'; import { LocationShareError } from '../../../../src/utils/location'; describe('', () => { const defaultProps = { onFinished: jest.fn(), error: LocationShareError.MapStyleUrlNotConfigured, + className: 'test', }; - const getComponent = (props = {}) => - mount(); + const getComponent = (props: Partial = {}): RenderResult => + render(); it('renders correctly for MapStyleUrlNotConfigured', () => { - const component = getComponent(); - expect(component).toMatchSnapshot(); + const { container } = getComponent(); + expect(container).toMatchSnapshot(); }); it('renders correctly for MapStyleUrlNotReachable', () => { - const component = getComponent({ + const { container } = getComponent({ error: LocationShareError.MapStyleUrlNotReachable, }); - expect(component).toMatchSnapshot(); + expect(container).toMatchSnapshot(); + }); + + it('does not render button when onFinished falsy', () => { + const { queryByText } = getComponent({ + error: LocationShareError.MapStyleUrlNotReachable, + onFinished: undefined, + }); + // no button + expect(queryByText('OK')).toBeFalsy(); + }); + + it('applies class when isMinimised is truthy', () => { + const { container } = getComponent({ + isMinimised: true, + }); + expect(container).toMatchSnapshot(); }); }); diff --git a/test/components/views/location/__snapshots__/MapError-test.tsx.snap b/test/components/views/location/__snapshots__/MapError-test.tsx.snap index 726e9454938..4fdef3e5d3d 100644 --- a/test/components/views/location/__snapshots__/MapError-test.tsx.snap +++ b/test/components/views/location/__snapshots__/MapError-test.tsx.snap @@ -1,95 +1,91 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[` applies class when isMinimised is truthy 1`] = ` +
+
+
+

+ Unable to load map +

+

+ This homeserver is not configured to display maps. +

+ +
+
+`; + exports[` renders correctly for MapStyleUrlNotConfigured 1`] = ` - +
- + Unable to load map + +

-

- Unable to load map -

-
-

This homeserver is not configured to display maps.

- - - + OK +
- +
`; exports[` renders correctly for MapStyleUrlNotReachable 1`] = ` - +
- + Unable to load map + +

-

- Unable to load map -

-
-

This homeserver is not configured correctly to display maps, or the configured map server may be unreachable.

- - - + OK +
- +
`; diff --git a/test/components/views/messages/MBeaconBody-test.tsx b/test/components/views/messages/MBeaconBody-test.tsx index c38e145a9f9..6a564dfdc4c 100644 --- a/test/components/views/messages/MBeaconBody-test.tsx +++ b/test/components/views/messages/MBeaconBody-test.tsx @@ -33,6 +33,7 @@ import { getMockClientWithEventEmitter, makeBeaconEvent, makeBeaconInfoEvent, + makeRoomWithBeacons, makeRoomWithStateEvents, } from '../../../test-utils'; import { RoomPermalinkCreator } from '../../../../src/utils/permalinks/Permalinks'; @@ -40,6 +41,9 @@ import { MediaEventHelper } from '../../../../src/utils/MediaEventHelper'; import MatrixClientContext from '../../../../src/contexts/MatrixClientContext'; import Modal from '../../../../src/Modal'; import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils'; +import { MapError } from '../../../../src/components/views/location/MapError'; +import * as mapUtilHooks from '../../../../src/utils/location/useMap'; +import { LocationShareError } from '../../../../src/utils/location'; describe('', () => { // 14.03.2022 16:15 @@ -94,112 +98,116 @@ describe('', () => { jest.clearAllMocks(); }); - it('renders stopped beacon UI for an explicitly stopped beacon', () => { - const beaconInfoEvent = makeBeaconInfoEvent(aliceId, - roomId, - { isLive: false }, - '$alice-room1-1', - ); - makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient }); - const component = getComponent({ mxEvent: beaconInfoEvent }); - expect(component.text()).toEqual("Live location ended"); - }); + const testBeaconStatuses = () => { + it('renders stopped beacon UI for an explicitly stopped beacon', () => { + const beaconInfoEvent = makeBeaconInfoEvent(aliceId, + roomId, + { isLive: false }, + '$alice-room1-1', + ); + makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient }); + const component = getComponent({ mxEvent: beaconInfoEvent }); + expect(component.text()).toEqual("Live location ended"); + }); - it('renders stopped beacon UI for an expired beacon', () => { - const beaconInfoEvent = makeBeaconInfoEvent(aliceId, - roomId, - // puts this beacons live period in the past - { isLive: true, timestamp: now - 600000, timeout: 500 }, - '$alice-room1-1', - ); - makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient }); - const component = getComponent({ mxEvent: beaconInfoEvent }); - expect(component.text()).toEqual("Live location ended"); - }); + it('renders stopped beacon UI for an expired beacon', () => { + const beaconInfoEvent = makeBeaconInfoEvent(aliceId, + roomId, + // puts this beacons live period in the past + { isLive: true, timestamp: now - 600000, timeout: 500 }, + '$alice-room1-1', + ); + makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient }); + const component = getComponent({ mxEvent: beaconInfoEvent }); + expect(component.text()).toEqual("Live location ended"); + }); - it('renders loading beacon UI for a beacon that has not started yet', () => { - const beaconInfoEvent = makeBeaconInfoEvent( - aliceId, - roomId, - // puts this beacons start timestamp in the future - { isLive: true, timestamp: now + 60000, timeout: 500 }, - '$alice-room1-1', - ); - makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient }); - const component = getComponent({ mxEvent: beaconInfoEvent }); - expect(component.text()).toEqual("Loading live location..."); - }); + it('renders loading beacon UI for a beacon that has not started yet', () => { + const beaconInfoEvent = makeBeaconInfoEvent( + aliceId, + roomId, + // puts this beacons start timestamp in the future + { isLive: true, timestamp: now + 60000, timeout: 500 }, + '$alice-room1-1', + ); + makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient }); + const component = getComponent({ mxEvent: beaconInfoEvent }); + expect(component.text()).toEqual("Loading live location..."); + }); - it('does not open maximised map when on click when beacon is stopped', () => { - const beaconInfoEvent = makeBeaconInfoEvent(aliceId, - roomId, - // puts this beacons live period in the past - { isLive: true, timestamp: now - 600000, timeout: 500 }, - '$alice-room1-1', - ); - makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient }); - const component = getComponent({ mxEvent: beaconInfoEvent }); - act(() => { - component.find('.mx_MBeaconBody_map').at(0).simulate('click'); + it('does not open maximised map when on click when beacon is stopped', () => { + const beaconInfoEvent = makeBeaconInfoEvent(aliceId, + roomId, + // puts this beacons live period in the past + { isLive: true, timestamp: now - 600000, timeout: 500 }, + '$alice-room1-1', + ); + makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient }); + const component = getComponent({ mxEvent: beaconInfoEvent }); + act(() => { + component.find('.mx_MBeaconBody_map').at(0).simulate('click'); + }); + + expect(modalSpy).not.toHaveBeenCalled(); }); - expect(modalSpy).not.toHaveBeenCalled(); - }); + it('renders stopped UI when a beacon event is not the latest beacon for a user', () => { + const aliceBeaconInfo1 = makeBeaconInfoEvent( + aliceId, + roomId, + // this one is a little older + { isLive: true, timestamp: now - 500 }, + '$alice-room1-1', + ); + aliceBeaconInfo1.event.origin_server_ts = now - 500; + const aliceBeaconInfo2 = makeBeaconInfoEvent( + aliceId, + roomId, + { isLive: true }, + '$alice-room1-2', + ); - it('renders stopped UI when a beacon event is not the latest beacon for a user', () => { - const aliceBeaconInfo1 = makeBeaconInfoEvent( - aliceId, - roomId, - // this one is a little older - { isLive: true, timestamp: now - 500 }, - '$alice-room1-1', - ); - aliceBeaconInfo1.event.origin_server_ts = now - 500; - const aliceBeaconInfo2 = makeBeaconInfoEvent( - aliceId, - roomId, - { isLive: true }, - '$alice-room1-2', - ); + makeRoomWithStateEvents([aliceBeaconInfo1, aliceBeaconInfo2], { roomId, mockClient }); - makeRoomWithStateEvents([aliceBeaconInfo1, aliceBeaconInfo2], { roomId, mockClient }); + const component = getComponent({ mxEvent: aliceBeaconInfo1 }); + // beacon1 has been superceded by beacon2 + expect(component.text()).toEqual("Live location ended"); + }); - const component = getComponent({ mxEvent: aliceBeaconInfo1 }); - // beacon1 has been superceded by beacon2 - expect(component.text()).toEqual("Live location ended"); - }); + it('renders stopped UI when a beacon event is replaced', () => { + const aliceBeaconInfo1 = makeBeaconInfoEvent( + aliceId, + roomId, + // this one is a little older + { isLive: true, timestamp: now - 500 }, + '$alice-room1-1', + ); + aliceBeaconInfo1.event.origin_server_ts = now - 500; + const aliceBeaconInfo2 = makeBeaconInfoEvent( + aliceId, + roomId, + { isLive: true }, + '$alice-room1-2', + ); - it('renders stopped UI when a beacon event is replaced', () => { - const aliceBeaconInfo1 = makeBeaconInfoEvent( - aliceId, - roomId, - // this one is a little older - { isLive: true, timestamp: now - 500 }, - '$alice-room1-1', - ); - aliceBeaconInfo1.event.origin_server_ts = now - 500; - const aliceBeaconInfo2 = makeBeaconInfoEvent( - aliceId, - roomId, - { isLive: true }, - '$alice-room1-2', - ); + const room = makeRoomWithStateEvents([aliceBeaconInfo1], { roomId, mockClient }); + const component = getComponent({ mxEvent: aliceBeaconInfo1 }); - const room = makeRoomWithStateEvents([aliceBeaconInfo1], { roomId, mockClient }); - const component = getComponent({ mxEvent: aliceBeaconInfo1 }); + const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo1)); + // update alice's beacon with a new edition + // beacon instance emits + act(() => { + beaconInstance.update(aliceBeaconInfo2); + }); - const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo1)); - // update alice's beacon with a new edition - // beacon instance emits - act(() => { - beaconInstance.update(aliceBeaconInfo2); - }); + component.setProps({}); - component.setProps({}); + // beacon1 has been superceded by beacon2 + expect(component.text()).toEqual("Live location ended"); + }); + }; - // beacon1 has been superceded by beacon2 - expect(component.text()).toEqual("Live location ended"); - }); + testBeaconStatuses(); describe('on liveness change', () => { it('renders stopped UI when a beacon stops being live', () => { @@ -458,4 +466,34 @@ describe('', () => { ); }); }); + + describe('when map display is not configured', () => { + beforeEach(() => { + // mock map utils to raise MapStyleUrlNotConfigured error + jest.spyOn(mapUtilHooks, 'useMap').mockImplementation( + ({ onError }) => { + onError(new Error(LocationShareError.MapStyleUrlNotConfigured)); + return mockMap; + }); + }); + + it('renders maps unavailable error for a live beacon with location', () => { + const beaconInfoEvent = makeBeaconInfoEvent(aliceId, + roomId, + { isLive: true }, + '$alice-room1-1', + ); + const location1 = makeBeaconEvent( + aliceId, { beaconInfoId: beaconInfoEvent.getId(), geoUri: 'geo:51,41', timestamp: now + 1 }, + ); + + makeRoomWithBeacons(roomId, mockClient, [beaconInfoEvent], [location1]); + + const component = getComponent({ mxEvent: beaconInfoEvent }); + expect(component.find(MapError)).toMatchSnapshot(); + }); + + // test that statuses display as expected with a map display error + testBeaconStatuses(); + }); }); diff --git a/test/components/views/messages/__snapshots__/MBeaconBody-test.tsx.snap b/test/components/views/messages/__snapshots__/MBeaconBody-test.tsx.snap new file mode 100644 index 00000000000..a9acf277c6d --- /dev/null +++ b/test/components/views/messages/__snapshots__/MBeaconBody-test.tsx.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` when map display is not configured renders maps unavailable error for a live beacon with location 1`] = ` + +
+
+ +

+ Unable to load map +

+
+

+ This homeserver is not configured to display maps. +

+
+ +`; From 58b81f604adbc39be300c9c964bce847b3e552d4 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Thu, 7 Jul 2022 06:38:19 +0000 Subject: [PATCH 047/162] Ensure timestamp on generic event list summary is not hidden from TimelineCard (#9000) Signed-off-by: Suguru Hirahara --- res/css/views/right_panel/_TimelineCard.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/right_panel/_TimelineCard.scss b/res/css/views/right_panel/_TimelineCard.scss index d236ff46b11..35692ec2404 100644 --- a/res/css/views/right_panel/_TimelineCard.scss +++ b/res/css/views/right_panel/_TimelineCard.scss @@ -155,7 +155,7 @@ limitations under the License. .mx_EventTile_line, .mx_GenericEventListSummary_unstyledList > .mx_EventTile_info .mx_EventTile_avatar ~ .mx_EventTile_line { padding-inline-start: var(--BaseCard_EventTile-spacing-inline); - padding-inline-end: var(--BaseCard_EventTile-spacing-inline); + padding-inline-end: $MessageTimestamp_width; // ensure timestamp is not hidden } } } From ff6f4dc8eb5b97aac787295fba62642e3b64be4b Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Thu, 7 Jul 2022 07:54:56 +0000 Subject: [PATCH 048/162] Move style rules of EventTile on hover out of mx_EventTile:not([data-layout=bubble]) (#8997) * Move ':hover.mx_EventTile_verified .mx_EventTile_line' etc out of mx_EventTile:not([data-layout=bubble]) Signed-off-by: Suguru Hirahara * Use variables Signed-off-by: Suguru Hirahara * Merge mx_EventTile_line on hover Signed-off-by: Suguru Hirahara * Apply inline start padding effect on hovering info tile text line to modern/group layout only Signed-off-by: Suguru Hirahara --- res/css/views/rooms/_EventTile.scss | 49 ++++++++++++++++++----------- res/css/views/rooms/_IRCLayout.scss | 1 - 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index f9479674363..dd86338fc1e 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -193,6 +193,28 @@ $left-gutter: 64px; &.mx_EventTile_continuation .mx_EventTile_line { clear: both; } + + &:hover { + // TODO: Adjust the values for IRC layout + --EventTile_hover_box-shadow-offset-x: calc(50px + $selected-message-border-width); + --EventTile_hover_box-shadow-spread-radius: -50px; + + .mx_EventTile_line { + background-color: $event-selected-color; + } + + &.mx_EventTile_verified .mx_EventTile_line { + box-shadow: inset var(--EventTile_hover_box-shadow-offset-x) 0 0 var(--EventTile_hover_box-shadow-spread-radius) $e2e-verified-color; + } + + &.mx_EventTile_unverified .mx_EventTile_line { + box-shadow: inset var(--EventTile_hover_box-shadow-offset-x) 0 0 var(--EventTile_hover_box-shadow-spread-radius) $e2e-unverified-color; + } + + &.mx_EventTile_unknown .mx_EventTile_line { + box-shadow: inset var(--EventTile_hover_box-shadow-offset-x) 0 0 var(--EventTile_hover_box-shadow-spread-radius) $e2e-unknown-color; + } + } } &[data-layout=irc] { @@ -273,6 +295,14 @@ $left-gutter: 64px; &.mx_EventTile_info .mx_EventTile_line { padding-left: calc($left-gutter + 20px); // override padding-left $left-gutter } + + &:hover { + &.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line, + &.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line, + &.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line { + padding-left: calc($left-gutter + 18px + $selected-message-border-width); + } + } } &[data-layout=bubble] { @@ -329,7 +359,6 @@ $left-gutter: 64px; padding-left: calc($left-gutter + 18px); } - &.mx_EventTile:hover .mx_EventTile_line, &.mx_EventTile.mx_EventTile_actionBarFocused .mx_EventTile_line, &.mx_EventTile.focus-visible:focus-within .mx_EventTile_line { background-color: $event-selected-color; @@ -349,24 +378,6 @@ $left-gutter: 64px; } } - &:hover.mx_EventTile_verified .mx_EventTile_line { - box-shadow: inset calc(50px + $selected-message-border-width) 0 0 -50px $e2e-verified-color; - } - - &:hover.mx_EventTile_unverified .mx_EventTile_line { - box-shadow: inset calc(50px + $selected-message-border-width) 0 0 -50px $e2e-unverified-color; - } - - &:hover.mx_EventTile_unknown .mx_EventTile_line { - box-shadow: inset calc(50px + $selected-message-border-width) 0 0 -50px $e2e-unknown-color; - } - - &:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line, - &:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line, - &:hover.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line { - padding-left: calc($left-gutter + 18px + $selected-message-border-width); - } - /* End to end encryption stuff */ &:hover .mx_EventTile_e2eIcon { opacity: 1; diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss index 566338f966b..b039e38cc52 100644 --- a/res/css/views/rooms/_IRCLayout.scss +++ b/res/css/views/rooms/_IRCLayout.scss @@ -181,7 +181,6 @@ $irc-line-height: $font-18px; .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line, .mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line, .mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line { - padding-left: 0; border-left: 0; } From e3fda73b2b3b5e81452e4acc511e1e242d31d5ff Mon Sep 17 00:00:00 2001 From: Kerry Date: Thu, 7 Jul 2022 10:11:12 +0200 Subject: [PATCH 049/162] Remove dead AddressSelector code (#8999) * remove unused AddressSelector * 18n --- res/css/_components.scss | 2 - res/css/views/elements/_AddressSelector.scss | 45 ----- res/css/views/elements/_AddressTile.scss | 138 ------------- res/themes/legacy-dark/css/_legacy-dark.scss | 2 +- .../legacy-light/css/_legacy-light.scss | 2 +- .../views/elements/AddressSelector.tsx | 184 ------------------ src/components/views/elements/AddressTile.tsx | 140 ------------- src/i18n/strings/en_EN.json | 1 - 8 files changed, 2 insertions(+), 512 deletions(-) delete mode 100644 res/css/views/elements/_AddressSelector.scss delete mode 100644 res/css/views/elements/_AddressTile.scss delete mode 100644 src/components/views/elements/AddressSelector.tsx delete mode 100644 src/components/views/elements/AddressTile.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index d13b0f5a58a..c5aafe282c0 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -144,8 +144,6 @@ @import "./views/dialogs/security/_RestoreKeyBackupDialog.scss"; @import "./views/directory/_NetworkDropdown.scss"; @import "./views/elements/_AccessibleButton.scss"; -@import "./views/elements/_AddressSelector.scss"; -@import "./views/elements/_AddressTile.scss"; @import "./views/elements/_CopyableText.scss"; @import "./views/elements/_DesktopCapturerSourcePicker.scss"; @import "./views/elements/_DialPadBackspaceButton.scss"; diff --git a/res/css/views/elements/_AddressSelector.scss b/res/css/views/elements/_AddressSelector.scss deleted file mode 100644 index b066d62543e..00000000000 --- a/res/css/views/elements/_AddressSelector.scss +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_AddressSelector { - position: absolute; - background-color: $background; - width: 485px; - max-height: 116px; - overflow-y: auto; - border-radius: 3px; - border: solid 1px $accent; - cursor: pointer; - z-index: 1; -} - -.mx_AddressSelector.mx_AddressSelector_empty { - display: none; -} - -.mx_AddressSelector_addressListElement .mx_AddressTile { - background-color: $background; - border: solid 1px $background; -} - -.mx_AddressSelector_addressListElement.mx_AddressSelector_selected { - background-color: $selected-color; -} - -.mx_AddressSelector_addressListElement.mx_AddressSelector_selected .mx_AddressTile { - background-color: $selected-color; - border: solid 1px $selected-color; -} diff --git a/res/css/views/elements/_AddressTile.scss b/res/css/views/elements/_AddressTile.scss deleted file mode 100644 index 7e646b1cf63..00000000000 --- a/res/css/views/elements/_AddressTile.scss +++ /dev/null @@ -1,138 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_AddressTile { - display: inline-block; - border-radius: 3px; - background-color: rgba(74, 73, 74, 0.1); - border: solid 1px $input-border-color; - line-height: $font-26px; - color: $primary-content; - font-size: $font-14px; - font-weight: normal; - margin-right: 4px; -} - -.mx_AddressTile.mx_AddressTile_error { - background-color: rgba(255, 0, 100, 0.1); - color: $alert; - border-color: $alert; -} - -.mx_AddressTile_network { - display: inline-block; - position: relative; - padding-left: 2px; - padding-right: 4px; - vertical-align: middle; -} - -.mx_AddressTile_avatar { - display: inline-block; - position: relative; - padding-left: 2px; - padding-right: 7px; - vertical-align: middle; -} - -.mx_AddressTile_mx { - display: inline-block; - margin: 0; - border: 0; - padding: 0; -} - -.mx_AddressTile_name { - display: inline-block; - padding-right: 4px; - font-weight: 600; - overflow: hidden; - height: 26px; - vertical-align: middle; -} - -.mx_AddressTile_name.mx_AddressTile_justified { - width: 180px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - vertical-align: middle; -} - -.mx_AddressTile_id { - display: inline-block; - padding-right: 11px; -} - -.mx_AddressTile_id.mx_AddressTile_justified { - width: 200px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - vertical-align: middle; -} - -.mx_AddressTile_unknownMx { - display: inline-block; - font-weight: 600; - padding-right: 11px; -} - -.mx_AddressTile_unknownMxl.mx_AddressTile_justified { - width: 380px; /* name + id width */ - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - vertical-align: middle; -} - -.mx_AddressTile_email { - display: inline-block; - font-weight: 600; - padding-right: 11px; -} - -.mx_AddressTile_email.mx_AddressTile_justified { - width: 200px; /* same as id width */ - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - vertical-align: middle; -} - -.mx_AddressTile_unknown { - display: inline-block; - padding-right: 11px; -} - -.mx_AddressTile_unknown.mx_AddressTile_justified { - width: 380px; /* name + id width */ - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - vertical-align: middle; -} - -.mx_AddressTile_dismiss { - display: inline-block; - padding-right: 11px; - padding-left: 1px; - cursor: pointer; -} - -.mx_AddressTile_dismiss object { - pointer-events: none; -} diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index f09f1465234..2eff623edff 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -37,7 +37,7 @@ $info-plinth-fg-color: #888; $spacePanel-bg-color: $base-color; $inverted-bg-color: $spacePanel-bg-color; -// used by AddressSelector +// used by Autocomplete $selected-color: $room-highlight-color; // selected for hoverover & selected event tiles diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 0e3af16fced..829786f9f07 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -52,7 +52,7 @@ $inverted-bg-color: $spacePanel-bg-color; // used by RoomDropTarget $droptarget-bg-color: rgba(255, 255, 255, 0.5); -// used by AddressSelector +// used by Autocomplete $selected-color: $secondary-accent-color; // selected for hoverover & selected event tiles diff --git a/src/components/views/elements/AddressSelector.tsx b/src/components/views/elements/AddressSelector.tsx deleted file mode 100644 index a084e690983..00000000000 --- a/src/components/views/elements/AddressSelector.tsx +++ /dev/null @@ -1,184 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017 Vector Creations Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React, { createRef } from 'react'; -import classNames from 'classnames'; - -import { IUserAddress } from '../../../UserAddress'; -import AddressTile from './AddressTile'; - -interface IProps { - onSelected: (index: number) => void; - - // List of the addresses to display - addressList: IUserAddress[]; - // Whether to show the address on the address tiles - showAddress?: boolean; - truncateAt: number; - selected?: number; - - // Element to put as a header on top of the list - header?: JSX.Element; -} - -interface IState { - selected: number; - hover: boolean; -} - -export default class AddressSelector extends React.Component { - private scrollElement = createRef(); - private addressListElement = createRef(); - - constructor(props: IProps) { - super(props); - - this.state = { - selected: this.props.selected === undefined ? 0 : this.props.selected, - hover: false, - }; - } - - // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - UNSAFE_componentWillReceiveProps(props: IProps) { // eslint-disable-line - // Make sure the selected item isn't outside the list bounds - const selected = this.state.selected; - const maxSelected = this.maxSelected(props.addressList); - if (selected > maxSelected) { - this.setState({ selected: maxSelected }); - } - } - - componentDidUpdate() { - // As the user scrolls with the arrow keys keep the selected item - // at the top of the window. - if (this.scrollElement.current && this.props.addressList.length > 0 && !this.state.hover) { - const elementHeight = this.addressListElement.current.getBoundingClientRect().height; - this.scrollElement.current.scrollTop = (this.state.selected * elementHeight) - elementHeight; - } - } - - public moveSelectionTop = (): void => { - if (this.state.selected > 0) { - this.setState({ - selected: 0, - hover: false, - }); - } - }; - - public moveSelectionUp = (): void => { - if (this.state.selected > 0) { - this.setState({ - selected: this.state.selected - 1, - hover: false, - }); - } - }; - - public moveSelectionDown = (): void => { - if (this.state.selected < this.maxSelected(this.props.addressList)) { - this.setState({ - selected: this.state.selected + 1, - hover: false, - }); - } - }; - - public chooseSelection = (): void => { - this.selectAddress(this.state.selected); - }; - - private onClick = (index: number): void => { - this.selectAddress(index); - }; - - private onMouseEnter = (index: number): void => { - this.setState({ - selected: index, - hover: true, - }); - }; - - private onMouseLeave = (): void => { - this.setState({ hover: false }); - }; - - private selectAddress = (index: number): void => { - // Only try to select an address if one exists - if (this.props.addressList.length !== 0) { - this.props.onSelected(index); - this.setState({ hover: false }); - } - }; - - private createAddressListTiles(): JSX.Element[] { - const maxSelected = this.maxSelected(this.props.addressList); - const addressList = []; - - // Only create the address elements if there are address - if (this.props.addressList.length > 0) { - for (let i = 0; i <= maxSelected; i++) { - const classes = classNames({ - "mx_AddressSelector_addressListElement": true, - "mx_AddressSelector_selected": this.state.selected === i, - }); - - // NOTE: Defaulting to "vector" as the network, until the network backend stuff is done. - // Saving the addressListElement so we can use it to work out, in the componentDidUpdate - // method, how far to scroll when using the arrow keys - addressList.push( -
- -
, - ); - } - } - return addressList; - } - - private maxSelected(list: IUserAddress[]): number { - const listSize = list.length === 0 ? 0 : list.length - 1; - const maxSelected = listSize > (this.props.truncateAt - 1) ? (this.props.truncateAt - 1) : listSize; - return maxSelected; - } - - render() { - const classes = classNames({ - "mx_AddressSelector": true, - "mx_AddressSelector_empty": this.props.addressList.length === 0, - }); - - return ( -
- { this.props.header } - { this.createAddressListTiles() } -
- ); - } -} diff --git a/src/components/views/elements/AddressTile.tsx b/src/components/views/elements/AddressTile.tsx deleted file mode 100644 index 8aafa0381be..00000000000 --- a/src/components/views/elements/AddressTile.tsx +++ /dev/null @@ -1,140 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import classNames from 'classnames'; - -import { _t } from '../../../languageHandler'; -import { mediaFromMxc } from "../../../customisations/Media"; -import { IUserAddress } from '../../../UserAddress'; -import BaseAvatar from '../avatars/BaseAvatar'; -import EmailUserIcon from "../../../../res/img/icon-email-user.svg"; - -interface IProps { - address: IUserAddress; - canDismiss?: boolean; - onDismissed?: () => void; - justified?: boolean; - showAddress?: boolean; -} - -export default class AddressTile extends React.Component { - static defaultProps: Partial = { - canDismiss: false, - onDismissed: function() {}, // NOP - justified: false, - }; - - render() { - const address = this.props.address; - const name = address.displayName || address.address; - - const imgUrls = []; - const isMatrixAddress = ['mx-user-id', 'mx-room-id'].includes(address.addressType); - - if (isMatrixAddress && address.avatarMxc) { - imgUrls.push(mediaFromMxc(address.avatarMxc).getSquareThumbnailHttp(25)); - } else if (address.addressType === 'email') { - imgUrls.push(EmailUserIcon); - } - - const nameClasses = classNames({ - "mx_AddressTile_name": true, - "mx_AddressTile_justified": this.props.justified, - }); - - let info; - let error = false; - if (isMatrixAddress && address.isKnown) { - const idClasses = classNames({ - "mx_AddressTile_id": true, - "mx_AddressTile_justified": this.props.justified, - }); - - info = ( -
-
{ name }
- { - this.props.showAddress - ?
{ address.address }
- :
- } -
- ); - } else if (isMatrixAddress) { - const unknownMxClasses = classNames({ - "mx_AddressTile_unknownMx": true, - "mx_AddressTile_justified": this.props.justified, - }); - - info = ( -
{ this.props.address.address }
- ); - } else if (address.addressType === "email") { - const emailClasses = classNames({ - "mx_AddressTile_email": true, - "mx_AddressTile_justified": this.props.justified, - }); - - let nameNode = null; - if (address.displayName) { - nameNode =
{ address.displayName }
; - } - - info = ( -
-
{ address.address }
- { nameNode } -
- ); - } else { - error = true; - const unknownClasses = classNames({ - "mx_AddressTile_unknown": true, - "mx_AddressTile_justified": this.props.justified, - }); - - info = ( -
{ _t("Unknown Address") }
- ); - } - - const classes = classNames({ - "mx_AddressTile": true, - "mx_AddressTile_error": error, - }); - - let dismiss; - if (this.props.canDismiss) { - dismiss = ( -
- -
- ); - } - - return ( -
-
- -
- { info } - { dismiss } -
- ); - } -} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 693299f9219..e0b1749c036 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2212,7 +2212,6 @@ "Categories": "Categories", "Quick Reactions": "Quick Reactions", "Cancel search": "Cancel search", - "Unknown Address": "Unknown Address", "Any of the following data may be shared:": "Any of the following data may be shared:", "Your display name": "Your display name", "Your avatar URL": "Your avatar URL", From b1fb609ab3a413da30e06d8993c12dcf0f4cb660 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Thu, 7 Jul 2022 09:24:24 +0000 Subject: [PATCH 050/162] Specify spacing around EventTileBubble for each layout (#9001) * Apply margin to EventTileBubble on each layout Use a variable to ensure alignment of EventTileBubble on IRC layout Signed-off-by: Suguru Hirahara * Improve style rules Signed-off-by: Suguru Hirahara * Apply the same block margin to every layout Signed-off-by: Suguru Hirahara --- res/css/views/messages/_EventTileBubble.scss | 26 +++++++------------ res/css/views/rooms/_EventTile.scss | 27 ++++++++++++++++++++ res/css/views/rooms/_IRCLayout.scss | 4 +-- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/res/css/views/messages/_EventTileBubble.scss b/res/css/views/messages/_EventTileBubble.scss index 5b8925687a9..6813fec6665 100644 --- a/res/css/views/messages/_EventTileBubble.scss +++ b/res/css/views/messages/_EventTileBubble.scss @@ -16,51 +16,43 @@ limitations under the License. .mx_EventTileBubble { background-color: $dark-panel-bg-color; - padding: 10px; + padding: 10px; // TODO: Use a spacing variable border-radius: 8px; - margin: 10px auto; // Reserve space for external timestamps, but also cap the width max-width: min(calc(100% - 2 * $MessageTimestamp_width), 600px); box-sizing: border-box; display: grid; grid-template-columns: 24px minmax(0, 1fr) min-content min-content; - .mx_EventTile[data-layout=bubble] & { - // Timestamps are inside the tile, so the width can be less constrained - max-width: 600px; - } - - &::before, &::after { + &::before, + &::after { position: relative; grid-column: 1; grid-row: 1 / 3; width: 16px; height: 16px; content: ""; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; mask-repeat: no-repeat; mask-position: center; mask-size: contain; - margin-top: 4px; + margin-top: $spacing-4; } - .mx_EventTileBubble_title, .mx_EventTileBubble_subtitle { + .mx_EventTileBubble_title, + .mx_EventTileBubble_subtitle { + grid-column: 2; overflow-wrap: break-word; } .mx_EventTileBubble_title { font-weight: 600; font-size: $font-15px; - grid-column: 2; grid-row: 1; } .mx_EventTileBubble_subtitle { font-size: $font-12px; - grid-column: 2; grid-row: 2; } @@ -68,6 +60,6 @@ limitations under the License. grid-column: 4; grid-row: 1 / 3; align-self: center; - margin-left: 16px; + margin-left: $spacing-16; } } diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index dd86338fc1e..8396c3ee19f 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -73,6 +73,10 @@ $left-gutter: 64px; } } + .mx_EventTileBubble { + margin-block: 10px; // TODO: Use a spacing variable + } + .mx_MImageBody { .mx_MImageBody_thumbnail_container { display: flex; @@ -217,10 +221,28 @@ $left-gutter: 64px; } } + &[data-layout=bubble], + &[data-layout=group] { + .mx_EventTileBubble { + margin-inline: auto; + } + } + &[data-layout=irc] { + --EventTile_irc_line_info-inset-inline-start: calc(var(--name-width) + 10px + var(--icon-width)); + .mx_MessageTimestamp { text-align: right; } + + .mx_EventTileBubble { + position: relative; + left: var(--EventTile_irc_line_info-inset-inline-start); + } + + .mx_ReplyTile .mx_EventTileBubble { + left: unset; // Cancel the value specified above for the tile inside ReplyTile + } } &[data-layout=group] { @@ -306,6 +328,11 @@ $left-gutter: 64px; } &[data-layout=bubble] { + .mx_EventTileBubble { + // Timestamps are inside the tile, so the width can be less constrained + max-width: 600px; + } + &.mx_EventTile_continuation { margin-top: 2px; } diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss index b039e38cc52..e6b6fcbfb35 100644 --- a/res/css/views/rooms/_IRCLayout.scss +++ b/res/css/views/rooms/_IRCLayout.scss @@ -163,13 +163,13 @@ $irc-line-height: $font-18px; .mx_EventTile.mx_EventTile_info { .mx_EventTile_avatar { - left: calc(var(--name-width) + 10px + var(--icon-width)); + left: var(--EventTile_irc_line_info-inset-inline-start); top: 0; margin-right: var(--right-padding); } .mx_EventTile_line { - left: calc(var(--name-width) + 10px + var(--icon-width)); + left: var(--EventTile_irc_line_info-inset-inline-start); } .mx_TextualEvent { From 56258bcdb6b28b60e759ce475806c9af4fab3629 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Thu, 7 Jul 2022 12:01:37 +0000 Subject: [PATCH 051/162] Improve _GenericEventListSummary.scss (#9005) * Include mx_BaseAvatar Signed-off-by: Suguru Hirahara * Include style blocks of mx_MatrixChat_useCompactLayout Signed-off-by: Suguru Hirahara * yarn run lint:style --fix Signed-off-by: Suguru Hirahara * Set top padding to avatars on group layout only Signed-off-by: Suguru Hirahara * Remove a redundant declaration for bubble layout Signed-off-by: Suguru Hirahara * Set the same margin value to mx_GenericEventListSummary_avatars on every layout Signed-off-by: Suguru Hirahara * Remove margin-top from mx_GenericEventListSummary_toggle on IRC layout Signed-off-by: Suguru Hirahara * Remove block margin from mx_GenericEventListSummary_toggle on both IRC layout and modern layout Signed-off-by: Suguru Hirahara * Set spacing to mx_GenericEventListSummary instead of its child elements Signed-off-by: Suguru Hirahara * Apply the margin to every layout Signed-off-by: Suguru Hirahara * Move general rules up Signed-off-by: Suguru Hirahara * Apply block margin to mx_GenericEventListSummary_toggle of every layout Signed-off-by: Suguru Hirahara * Apply top margin to modern layout Signed-off-by: Suguru Hirahara * Include mx_MatrixChat_useCompactLayout Signed-off-by: Suguru Hirahara * Set top margin to mx_GenericEventListSummary insted of toggle Signed-off-by: Suguru Hirahara * Use a spacing variable Signed-off-by: Suguru Hirahara * Remove a redundant declaration Signed-off-by: Suguru Hirahara * Add a comment Signed-off-by: Suguru Hirahara * Apply display flex as a default value Signed-off-by: Suguru Hirahara --- .../elements/_GenericEventListSummary.scss | 89 ++++++++----------- 1 file changed, 38 insertions(+), 51 deletions(-) diff --git a/res/css/views/elements/_GenericEventListSummary.scss b/res/css/views/elements/_GenericEventListSummary.scss index a3bbd13663d..11963af423d 100644 --- a/res/css/views/elements/_GenericEventListSummary.scss +++ b/res/css/views/elements/_GenericEventListSummary.scss @@ -17,31 +17,45 @@ limitations under the License. .mx_GenericEventListSummary { position: relative; + .mx_GenericEventListSummary_avatars { + margin-right: $spacing-8; + } + &[data-layout=irc], &[data-layout=group] { .mx_GenericEventListSummary_toggle { float: right; - margin: 8px 10px 0 0; - } - - .mx_GenericEventListSummary_avatars { - padding-top: $spacing-8; + margin-inline: 0 10px; } } - &[data-layout=irc] { - .mx_GenericEventListSummary_avatars { - padding: 0; - margin: 0 9px 0 0; - } + &[data-layout=group] { + margin-top: $spacing-8; } &[data-layout=bubble] { --maxWidth: 70%; + display: flex; margin-left: calc(var(--avatarSize) + var(--gutterSize)); + .mx_GenericEventListSummary_toggle { + margin-block: 0; + + &[aria-expanded=false] { + order: 9; // TODO: Remove + } + + &[aria-expanded=true] { + margin-inline-start: auto; // reduce clickable area + margin-inline-end: var(--EventTile_bubble-margin-inline-end); // as the parent has zero margin + } + } + + .mx_GenericEventListSummary_line { + display: none; + } + &[data-expanded=false] { - display: flex; align-items: center; justify-content: space-between; column-gap: 5px; @@ -50,7 +64,6 @@ limitations under the License. // ideally we'd use display=contents here for the layout to all work regardless of the *ELS but // that breaks ScrollPanel's reliance upon offsetTop so we have to have a bit more finesse. &[data-expanded=true] { - display: flex; flex-direction: column; margin: 0; } @@ -65,26 +78,22 @@ limitations under the License. background: transparent; } } + } - .mx_GenericEventListSummary_toggle { - margin-block: 0; - - &[aria-expanded=false] { - order: 9; - } + .mx_MatrixChat_useCompactLayout & { + font-size: $font-13px; + margin-top: $spacing-4; - &[aria-expanded=true] { - margin-inline-start: auto; // reduce clickable area - margin-inline-end: var(--EventTile_bubble-margin-inline-end); // as the parent has zero margin - } + .mx_EventTile_line { + line-height: $font-20px; } .mx_GenericEventListSummary_line { - display: none; + line-height: $font-22px; } - .mx_GenericEventListSummary_avatars { - padding-top: 0; + .mx_TextualEvent.mx_GenericEventListSummary_summary { + font-size: $font-13px; } } } @@ -101,13 +110,12 @@ limitations under the License. .mx_GenericEventListSummary_avatars { display: inline-block; - margin-right: 8px; line-height: $font-12px; -} -.mx_GenericEventListSummary_avatars .mx_BaseAvatar { - margin-right: -4px; - cursor: pointer; + .mx_BaseAvatar { + margin-right: -4px; + cursor: pointer; + } } .mx_GenericEventListSummary_line { @@ -115,24 +123,3 @@ limitations under the License. margin-left: 63px; line-height: $font-30px; } - -.mx_MatrixChat_useCompactLayout { - .mx_GenericEventListSummary { - font-size: $font-13px; - .mx_EventTile_line { - line-height: $font-20px; - } - } - - .mx_GenericEventListSummary_line { - line-height: $font-22px; - } - - .mx_GenericEventListSummary_toggle { - margin-top: 3px; - } - - .mx_TextualEvent.mx_GenericEventListSummary_summary { - font-size: $font-13px; - } -} From bd8949872df162ef902b036d74bff61da62ed0c8 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Thu, 7 Jul 2022 13:08:32 +0000 Subject: [PATCH 052/162] Fix lost padding of event tile info line (#9009) Signed-off-by: Suguru Hirahara --- res/css/views/rooms/_EventTile.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 8396c3ee19f..257170636b4 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -365,7 +365,7 @@ $left-gutter: 64px; } .mx_EventTile_line { - padding: 3px 0 2px; // Align with mx_EventTile_avatar and mx_EventTile_e2eIcon + padding-block: 3px 2px; // Align with mx_EventTile_avatar and mx_EventTile_e2eIcon .mx_MessageTimestamp { top: 0; From 644b8415912afb9c5eed54859a444a2ee7224117 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 8 Jul 2022 00:32:38 -0600 Subject: [PATCH 053/162] Replace MSC3244 support with in-client room version checking (#9018) * Replace MSC3244 support with in-client room version checking * Fix irrelevant ternary * It helps to use Jest correctly --- .../views/dialogs/CreateRoomDialog.tsx | 3 +- .../views/dialogs/CreateSubspaceDialog.tsx | 9 +-- .../views/settings/JoinRuleSettings.tsx | 7 +-- src/createRoom.ts | 27 +++++---- src/stores/spaces/SpaceStore.ts | 12 +--- src/utils/PreferredRoomVersions.ts | 55 +++++++++++++++++++ test/PreferredRoomVersions-test.ts | 46 ++++++++++++++++ 7 files changed, 121 insertions(+), 38 deletions(-) create mode 100644 src/utils/PreferredRoomVersions.ts create mode 100644 test/PreferredRoomVersions-test.ts diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index 8425a7269cc..2217af93879 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -30,7 +30,6 @@ import RoomAliasField from "../elements/RoomAliasField"; import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import DialogButtons from "../elements/DialogButtons"; import BaseDialog from "../dialogs/BaseDialog"; -import SpaceStore from "../../../stores/spaces/SpaceStore"; import JoinRuleDropdown from "../elements/JoinRuleDropdown"; import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; @@ -66,7 +65,7 @@ export default class CreateRoomDialog extends React.Component { constructor(props) { super(props); - this.supportsRestricted = this.props.parentSpace && !!SpaceStore.instance.restrictedJoinRuleSupport?.preferred; + this.supportsRestricted = !!this.props.parentSpace; let joinRule = JoinRule.Invite; if (this.props.defaultPublic) { diff --git a/src/components/views/dialogs/CreateSubspaceDialog.tsx b/src/components/views/dialogs/CreateSubspaceDialog.tsx index a44d16dd40f..c371f64f333 100644 --- a/src/components/views/dialogs/CreateSubspaceDialog.tsx +++ b/src/components/views/dialogs/CreateSubspaceDialog.tsx @@ -26,7 +26,6 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { BetaPill } from "../beta/BetaCard"; import Field from "../elements/Field"; import RoomAliasField from "../elements/RoomAliasField"; -import SpaceStore from "../../../stores/spaces/SpaceStore"; import { createSpace, SpaceCreateForm } from "../spaces/SpaceCreateMenu"; import { SubspaceSelector } from "./AddExistingToSpaceDialog"; import JoinRuleDropdown from "../elements/JoinRuleDropdown"; @@ -48,14 +47,10 @@ const CreateSubspaceDialog: React.FC = ({ space, onAddExistingSpaceClick const [avatar, setAvatar] = useState(null); const [topic, setTopic] = useState(""); - const supportsRestricted = !!SpaceStore.instance.restrictedJoinRuleSupport?.preferred; - const spaceJoinRule = space.getJoinRule(); - let defaultJoinRule = JoinRule.Invite; + let defaultJoinRule = JoinRule.Restricted; if (spaceJoinRule === JoinRule.Public) { defaultJoinRule = JoinRule.Public; - } else if (supportsRestricted) { - defaultJoinRule = JoinRule.Restricted; } const [joinRule, setJoinRule] = useState(defaultJoinRule); @@ -150,7 +145,7 @@ const CreateSubspaceDialog: React.FC = ({ space, onAddExistingSpaceClick label={_t("Space visibility")} labelInvite={_t("Private space (invite only)")} labelPublic={_t("Public space")} - labelRestricted={supportsRestricted ? _t("Visible to space members") : undefined} + labelRestricted={_t("Visible to space members")} width={478} value={joinRule} onChange={setJoinRule} diff --git a/src/components/views/settings/JoinRuleSettings.tsx b/src/components/views/settings/JoinRuleSettings.tsx index c7423d1b24e..49295f0e83b 100644 --- a/src/components/views/settings/JoinRuleSettings.tsx +++ b/src/components/views/settings/JoinRuleSettings.tsx @@ -35,6 +35,7 @@ import dis from "../../../dispatcher/dispatcher"; import { ROOM_SECURITY_TAB } from "../dialogs/RoomSettingsDialog"; import { Action } from "../../../dispatcher/actions"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import { doesRoomVersionSupport, PreferredRoomVersions } from "../../../utils/PreferredRoomVersions"; interface IProps { room: Room; @@ -48,11 +49,9 @@ interface IProps { const JoinRuleSettings = ({ room, promptUpgrade, aliasWarning, onError, beforeChange, closeSettingsFn }: IProps) => { const cli = room.client; - const restrictedRoomCapabilities = SpaceStore.instance.restrictedJoinRuleSupport; - const roomSupportsRestricted = Array.isArray(restrictedRoomCapabilities?.support) - && restrictedRoomCapabilities.support.includes(room.getVersion()); + const roomSupportsRestricted = doesRoomVersionSupport(room.getVersion(), PreferredRoomVersions.RestrictedRooms); const preferredRestrictionVersion = !roomSupportsRestricted && promptUpgrade - ? restrictedRoomCapabilities?.preferred + ? PreferredRoomVersions.RestrictedRooms : undefined; const disabled = !room.currentState.mayClientSendStateEvent(EventType.RoomJoinRules, cli); diff --git a/src/createRoom.ts b/src/createRoom.ts index 3e258d28cba..3f85be9f003 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -45,6 +45,7 @@ import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload"; import { findDMForUser } from "./utils/direct-messages"; import { privateShouldBeEncrypted } from "./utils/rooms"; import { waitForMember } from "./utils/membership"; +import { PreferredRoomVersions } from "./utils/PreferredRoomVersions"; // we define a number of interfaces which take their names from the js-sdk /* eslint-disable camelcase */ @@ -191,20 +192,18 @@ export default async function createRoom(opts: IOpts): Promise { } if (opts.joinRule === JoinRule.Restricted) { - if (SpaceStore.instance.restrictedJoinRuleSupport?.preferred) { - createOpts.room_version = SpaceStore.instance.restrictedJoinRuleSupport.preferred; - - createOpts.initial_state.push({ - type: EventType.RoomJoinRules, - content: { - "join_rule": JoinRule.Restricted, - "allow": [{ - "type": RestrictedAllowType.RoomMembership, - "room_id": opts.parentSpace.roomId, - }], - }, - }); - } + createOpts.room_version = PreferredRoomVersions.RestrictedRooms; + + createOpts.initial_state.push({ + type: EventType.RoomJoinRules, + content: { + "join_rule": JoinRule.Restricted, + "allow": [{ + "type": RestrictedAllowType.RoomMembership, + "room_id": opts.parentSpace.roomId, + }], + }, + }); } } diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index ec4556f48c2..4fd785602b9 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -18,7 +18,7 @@ import { ListIteratee, Many, sortBy } from "lodash"; import { EventType, RoomType } from "matrix-js-sdk/src/@types/event"; import { Room, RoomEvent } from "matrix-js-sdk/src/models/room"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { ClientEvent, IRoomCapability } from "matrix-js-sdk/src/client"; +import { ClientEvent } from "matrix-js-sdk/src/client"; import { logger } from "matrix-js-sdk/src/logger"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; @@ -132,7 +132,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient { private _suggestedRooms: ISuggestedRoom[] = []; private _invitedSpaces = new Set(); private spaceOrderLocalEchoMap = new Map(); - private _restrictedJoinRuleSupport?: IRoomCapability; // The following properties are set by onReady as they live in account_data private _allRoomsInHome = false; private _enabledMetaSpaces: MetaSpace[] = []; @@ -210,10 +209,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } } - public get restrictedJoinRuleSupport(): IRoomCapability { - return this._restrictedJoinRuleSupport; - } - /** * Sets the active space, updates room list filters, * optionally switches the user's room back to where they were when they last viewed that space. @@ -1066,11 +1061,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.matrixClient.on(RoomStateEvent.Members, this.onRoomStateMembers); this.matrixClient.on(ClientEvent.AccountData, this.onAccountData); - this.matrixClient.getCapabilities().then(capabilities => { - this._restrictedJoinRuleSupport = capabilities - ?.["m.room_versions"]?.["org.matrix.msc3244.room_capabilities"]?.["restricted"]; - }); - const oldMetaSpaces = this._enabledMetaSpaces; const enabledMetaSpaces = SettingsStore.getValue("Spaces.enabledMetaSpaces"); this._enabledMetaSpaces = metaSpaceOrder.filter(k => enabledMetaSpaces[k]); diff --git a/src/utils/PreferredRoomVersions.ts b/src/utils/PreferredRoomVersions.ts new file mode 100644 index 00000000000..2dc269da6c2 --- /dev/null +++ b/src/utils/PreferredRoomVersions.ts @@ -0,0 +1,55 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/** + * The preferred room versions for various features within the app. The + * room versions here are selected based on the client's support for the + * possible room versions in combination with server support in the + * ecosystem. + * + * Loosely follows https://spec.matrix.org/latest/rooms/#feature-matrix + */ +export class PreferredRoomVersions { + /** + * The room version to use when creating "restricted" rooms. + */ + public static readonly RestrictedRooms = "9"; + + private constructor() { + // readonly, static, class + } +} + +/** + * Determines if a room version supports the given feature using heuristics + * for how Matrix works. + * @param roomVer The room version to check support within. + * @param featureVer The room version of the feature. Should be from PreferredRoomVersions. + * @see PreferredRoomVersions + */ +export function doesRoomVersionSupport(roomVer: string, featureVer: string): boolean { + // Assumption: all unstable room versions don't support the feature. Calling code can check for unstable + // room versions explicitly if it wants to. The spec reserves [0-9] and `.` for its room versions. + if (!roomVer.match(/[\d.]+/)) { + return false; + } + + // Dev note: While the spec says room versions are not linear, we can make reasonable assumptions + // until the room versions prove themselves to be non-linear in the spec. We should see this coming + // from a mile away and can course-correct this function if needed. + return Number(roomVer) >= Number(featureVer); +} + diff --git a/test/PreferredRoomVersions-test.ts b/test/PreferredRoomVersions-test.ts new file mode 100644 index 00000000000..caaf3fe5ae0 --- /dev/null +++ b/test/PreferredRoomVersions-test.ts @@ -0,0 +1,46 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { doesRoomVersionSupport, PreferredRoomVersions } from "../src/utils/PreferredRoomVersions"; + +describe("doesRoomVersionSupport", () => { + it("should detect unstable as unsupported", () => { + expect(doesRoomVersionSupport("org.example.unstable", "1")).toBe(false); + expect(doesRoomVersionSupport("1.2-beta", "1")).toBe(false); + }); + + it("should detect support properly", () => { + expect(doesRoomVersionSupport("1", "2")).toBe(false); // older + expect(doesRoomVersionSupport("2", "2")).toBe(true); // exact + expect(doesRoomVersionSupport("3", "2")).toBe(true); // newer + }); + + it("should handle decimal versions", () => { + expect(doesRoomVersionSupport("1.1", "2.2")).toBe(false); // older + expect(doesRoomVersionSupport("2.1", "2.2")).toBe(false); // exact-ish + expect(doesRoomVersionSupport("2.2", "2.2")).toBe(true); // exact + expect(doesRoomVersionSupport("2.3", "2.2")).toBe(true); // exact-ish + expect(doesRoomVersionSupport("3.1", "2.2")).toBe(true); // newer + }); + + it("should detect restricted rooms in v9 and v10", () => { + // Dev note: we consider it a feature that v8 rooms have to upgrade considering the bug in v8. + // https://spec.matrix.org/v1.3/rooms/v8/#redactions + expect(doesRoomVersionSupport("8", PreferredRoomVersions.RestrictedRooms)).toBe(false); + expect(doesRoomVersionSupport("9", PreferredRoomVersions.RestrictedRooms)).toBe(true); + expect(doesRoomVersionSupport("10", PreferredRoomVersions.RestrictedRooms)).toBe(true); + }); +}); From 4844cc14bdb88c55b3b037f5fa6bd96940bc0012 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Fri, 8 Jul 2022 06:39:20 +0000 Subject: [PATCH 054/162] Remove padding from event info line for hidden events on ThreadView (#9019) Signed-off-by: Suguru Hirahara --- res/css/views/rooms/_EventTile.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 257170636b4..f2c0391fd28 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -968,8 +968,6 @@ $left-gutter: 64px; // handling for hidden events (e.g reactions) in the thread view &.mx_EventTile_info { - padding-top: 0; - .mx_EventTile_avatar { position: absolute; top: 1.5px; // Align with hidden event content @@ -1000,6 +998,8 @@ $left-gutter: 64px; &[data-layout=irc], &[data-layout=group] { + padding-top: 0; + .mx_MessageTimestamp { top: 2px; // Align with avatar } From 39816f67e4c2079d6e3172eb07fdfcc86e692b99 Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski Date: Fri, 8 Jul 2022 10:50:06 +0200 Subject: [PATCH 055/162] Fix Shortcut prompt for Search showing in minimized Roomlist (#9014) --- res/css/structures/_LeftPanel.scss | 32 ----------- res/css/structures/_RoomSearch.scss | 68 +++++++++++++----------- src/components/structures/LeftPanel.tsx | 13 +---- src/components/structures/RoomSearch.tsx | 8 +-- 4 files changed, 41 insertions(+), 80 deletions(-) diff --git a/res/css/structures/_LeftPanel.scss b/res/css/structures/_LeftPanel.scss index 7f66279158f..fbf8341592d 100644 --- a/res/css/structures/_LeftPanel.scss +++ b/res/css/structures/_LeftPanel.scss @@ -122,38 +122,6 @@ $roomListCollapsedWidth: 68px; margin-top: 12px; } - .mx_RoomSearch_shortcutPrompt { - border-radius: 6px; - background-color: $panel-actions; - padding: 2px 4px; - user-select: none; - font-size: $font-12px; - line-height: $font-15px; - font-weight: $font-semi-bold; - color: $light-fg-color; - margin-right: 6px; - } - - .mx_RoomSearch_focused, .mx_RoomSearch_hasQuery { - .mx_RoomSearch_shortcutPrompt { - display: none; - } - - & + .mx_LeftPanel_exploreButton, - & + .mx_LeftPanel_recentsButton { - // Cheaty way to return the occupied space to the filter input - flex-basis: 0; - margin: 0; - width: 0; - - // Don't forget to hide the masked ::before icon, - // using display:none or visibility:hidden would break accessibility - &::before { - content: none; - } - } - } - .mx_LeftPanel_dialPadButton { width: 32px; height: 32px; diff --git a/res/css/structures/_RoomSearch.scss b/res/css/structures/_RoomSearch.scss index 4437480bc80..87531376006 100644 --- a/res/css/structures/_RoomSearch.scss +++ b/res/css/structures/_RoomSearch.scss @@ -17,6 +17,7 @@ limitations under the License. // Note: this component expects to be contained within a flexbox .mx_RoomSearch { flex: 1; + min-width: 0; border-radius: 8px; background-color: $panel-actions; // keep border thickness consistent to prevent movement @@ -28,6 +29,8 @@ limitations under the License. display: flex; align-items: center; + cursor: pointer; + .mx_RoomSearch_icon { width: 16px; height: 16px; @@ -36,11 +39,35 @@ limitations under the License. background-color: $secondary-content; margin-left: 7px; margin-bottom: 2px; + flex-shrink: 0; } .mx_RoomSearch_spotlightTriggerText { font-size: $font-12px; line-height: $font-16px; + color: $tertiary-content; + flex: 1; + min-width: 0; + // the following rules are to match that of a real input field + overflow: hidden; + margin: 9px; + font-weight: $font-semi-bold; + } + + .mx_RoomSearch_shortcutPrompt { + border-radius: 6px; + background-color: $panel-actions; + padding: 2px 4px; + user-select: none; + font-size: $font-12px; + line-height: $font-15px; + font-family: inherit; + font-weight: $font-semi-bold; + color: $light-fg-color; + margin-right: 6px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } &.mx_RoomSearch_minimized { @@ -55,44 +82,25 @@ limitations under the License. align-self: center; } - &:hover { - background-color: $tertiary-content; - - .mx_RoomSearch_icon { - background-color: $background; - } + .mx_RoomSearch_shortcutPrompt { + display: none; } } - &.mx_RoomSearch_spotlightTrigger { - cursor: pointer; - min-width: 0; + &:hover { + background-color: $tertiary-content; .mx_RoomSearch_spotlightTriggerText { - color: $tertiary-content; - flex: 1; - min-width: 0; - // the following rules are to match that of a real input field - overflow: hidden; - margin: 9px; - font-weight: $font-semi-bold; + color: $background; } - &:hover { - background-color: $tertiary-content; - - .mx_RoomSearch_spotlightTriggerText { - color: $background; - } - - .mx_RoomSearch_shortcutPrompt { - background-color: $background; - color: $secondary-content; - } + .mx_RoomSearch_shortcutPrompt { + background-color: $background; + color: $secondary-content; + } - .mx_RoomSearch_icon { - background-color: $background; - } + .mx_RoomSearch_icon { + background-color: $background; } } } diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index b7cdec14347..ab8620a26df 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -304,14 +304,6 @@ export default class LeftPanel extends React.Component { } }; - private selectRoom = () => { - const firstRoom = this.listContainerRef.current.querySelector(".mx_RoomTile"); - if (firstRoom) { - firstRoom.click(); - return true; // to get the field to clear - } - }; - private renderBreadcrumbs(): React.ReactNode { if (this.state.showBreadcrumbs === BreadcrumbsMode.Legacy && !this.props.isMinimized) { return ( @@ -357,10 +349,7 @@ export default class LeftPanel extends React.Component { onBlur={this.onBlur} onKeyDown={this.onKeyDown} > - + { dialPadButton } { rightButton } diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 9af39a5cc3d..4cc36e17943 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -28,10 +28,6 @@ import AccessibleButton from "../views/elements/AccessibleButton"; interface IProps { isMinimized: boolean; - /** - * @returns true if a room has been selected and the search field should be cleared - */ - onSelectRoom(): boolean; } export default class RoomSearch extends React.PureComponent { @@ -67,9 +63,9 @@ export default class RoomSearch extends React.PureComponent {
); - const shortcutPrompt =
+ const shortcutPrompt = { IS_MAC ? "⌘ K" : _t(ALTERNATE_KEY_NAME[Key.CONTROL]) + " K" } -
; + ; return { icon } From b1f8d36a69413e09aeb24d3cef1b4d116491e51a Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Fri, 8 Jul 2022 08:58:49 +0000 Subject: [PATCH 056/162] Re-add margin to tiles based on EventTileBubble (#9015) * Re-add margin to HistoryTile, which is based on EventTileBubble Signed-off-by: Suguru Hirahara * Readd margin to the other tiles based on EventTileBubble Signed-off-by: Suguru Hirahara * Default margin is not required for mx_MJitsiWidgetEvent as it appears inside EventTile Signed-off-by: Suguru Hirahara * Cancel left value for cryptoEvent on IRC layout Signed-off-by: Suguru Hirahara --- res/css/views/messages/_CreateEvent.scss | 4 +++- res/css/views/messages/_EventTileBubble.scss | 2 ++ res/css/views/messages/_MJitsiWidgetEvent.scss | 2 +- res/css/views/messages/_common_CryptoEvent.scss | 4 +++- res/css/views/rooms/_EventTile.scss | 6 +++++- res/css/views/rooms/_HistoryTile.scss | 10 +++++++--- 6 files changed, 21 insertions(+), 7 deletions(-) diff --git a/res/css/views/messages/_CreateEvent.scss b/res/css/views/messages/_CreateEvent.scss index b3f1ad4e4d7..db6e0244425 100644 --- a/res/css/views/messages/_CreateEvent.scss +++ b/res/css/views/messages/_CreateEvent.scss @@ -14,7 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_CreateEvent { +.mx_EventTileBubble.mx_CreateEvent { + margin: var(--EventTileBubble_margin-block) auto; + &::before { background-color: $header-panel-text-primary-color; mask-image: url('$(res)/img/element-icons/chat-bubbles.svg'); diff --git a/res/css/views/messages/_EventTileBubble.scss b/res/css/views/messages/_EventTileBubble.scss index 6813fec6665..4135dc09f2f 100644 --- a/res/css/views/messages/_EventTileBubble.scss +++ b/res/css/views/messages/_EventTileBubble.scss @@ -15,6 +15,8 @@ limitations under the License. */ .mx_EventTileBubble { + --EventTileBubble_margin-block: 10px; // TODO: Use a spacing variable + background-color: $dark-panel-bg-color; padding: 10px; // TODO: Use a spacing variable border-radius: 8px; diff --git a/res/css/views/messages/_MJitsiWidgetEvent.scss b/res/css/views/messages/_MJitsiWidgetEvent.scss index 6360d089fa0..0d811a74b0d 100644 --- a/res/css/views/messages/_MJitsiWidgetEvent.scss +++ b/res/css/views/messages/_MJitsiWidgetEvent.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_MJitsiWidgetEvent { +.mx_EventTileBubble.mx_MJitsiWidgetEvent { &::before { background-color: $header-panel-text-primary-color; // XXX: Variable abuse mask-image: url('$(res)/img/element-icons/call/video-call.svg'); diff --git a/res/css/views/messages/_common_CryptoEvent.scss b/res/css/views/messages/_common_CryptoEvent.scss index 5938f4fcb9d..5753a8bf79f 100644 --- a/res/css/views/messages/_common_CryptoEvent.scss +++ b/res/css/views/messages/_common_CryptoEvent.scss @@ -14,7 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_cryptoEvent { +.mx_EventTileBubble.mx_cryptoEvent { + margin: var(--EventTileBubble_margin-block) auto; + // white infill for the transparency &.mx_cryptoEvent_icon::before { background-color: #ffffff; diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index f2c0391fd28..e8d404d2321 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -74,7 +74,7 @@ $left-gutter: 64px; } .mx_EventTileBubble { - margin-block: 10px; // TODO: Use a spacing variable + margin-block: var(--EventTileBubble_margin-block); } .mx_MImageBody { @@ -238,6 +238,10 @@ $left-gutter: 64px; .mx_EventTileBubble { position: relative; left: var(--EventTile_irc_line_info-inset-inline-start); + + &.mx_cryptoEvent { + left: unset; + } } .mx_ReplyTile .mx_EventTileBubble { diff --git a/res/css/views/rooms/_HistoryTile.scss b/res/css/views/rooms/_HistoryTile.scss index 48f5a4ce2e4..1cd9dd062f3 100644 --- a/res/css/views/rooms/_HistoryTile.scss +++ b/res/css/views/rooms/_HistoryTile.scss @@ -14,7 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_HistoryTile::before { - background-color: $header-panel-text-primary-color; - mask-image: url('$(res)/img/element-icons/hide.svg'); +.mx_EventTileBubble.mx_HistoryTile { + margin: var(--EventTileBubble_margin-block) auto; + + &::before { + background-color: $header-panel-text-primary-color; + mask-image: url('$(res)/img/element-icons/hide.svg'); + } } From eff0395771c0181e2db389421855b1e59554a50c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 8 Jul 2022 10:06:29 +0100 Subject: [PATCH 057/162] Update cypress.yaml (#9007) --- .github/workflows/cypress.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cypress.yaml b/.github/workflows/cypress.yaml index c0f6a4ba762..8b344f52744 100644 --- a/.github/workflows/cypress.yaml +++ b/.github/workflows/cypress.yaml @@ -69,7 +69,7 @@ jobs: echo "PERCY_ENABLE=0" >> $GITHUB_ENV - name: Run Cypress tests - uses: cypress-io/github-action@v2 + uses: cypress-io/github-action@v4.1.1 with: # The built-in Electron runner seems to grind to a halt trying # to run the tests, so use chrome. From 4fb11968fa0233125a067923e9f507fea44d2aa5 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Fri, 8 Jul 2022 09:38:06 +0000 Subject: [PATCH 058/162] Move style declarations out of :not([data-layout=bubble]) in ThreadView (#9020) * Move mx_EventTile out of mx_EventTile:not([data-layout=bubble]) in mx_ThreadView Signed-off-by: Suguru Hirahara * Use a logical property Signed-off-by: Suguru Hirahara * Remove a redundant declaration Signed-off-by: Suguru Hirahara * Apply zero inline start padding to group layout only Signed-off-by: Suguru Hirahara * Reorder style blocks; irc/group before bubble Signed-off-by: Suguru Hirahara * Move styles for mx_EventTile_avatar of info event lines on ThreadView out of :not() pseudo class Signed-off-by: Suguru Hirahara * Move styles in mx_GenericEventListSummary on ThreadView out of :not() pseudo class Signed-off-by: Suguru Hirahara * Shorthand for inline margin Signed-off-by: Suguru Hirahara * yarn run lint:style --fix Signed-off-by: Suguru Hirahara --- res/css/views/rooms/_EventTile.scss | 101 ++++++++++++++-------------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index e8d404d2321..a91ae5d12b3 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -326,7 +326,7 @@ $left-gutter: 64px; &.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line, &.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line, &.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line { - padding-left: calc($left-gutter + 18px + $selected-message-border-width); + padding-inline-start: calc($left-gutter + 18px + $selected-message-border-width); } } } @@ -957,8 +957,7 @@ $left-gutter: 64px; } .mx_EventTile_line { - padding-top: var(--BaseCard_EventTile_line-padding-block); - padding-bottom: var(--BaseCard_EventTile_line-padding-block); + padding-block: var(--BaseCard_EventTile_line-padding-block); } .mx_EventTile_line, @@ -1004,16 +1003,14 @@ $left-gutter: 64px; &[data-layout=group] { padding-top: 0; - .mx_MessageTimestamp { - top: 2px; // Align with avatar - } - } - - &:not([data-layout=bubble]) { .mx_EventTile_avatar { left: calc($MessageTimestamp_width + 14px - 4px); // 14px: avatar width, 4px: align with text z-index: 9; // position above the hover styling } + + .mx_MessageTimestamp { + top: 2px; // Align with avatar + } } &[data-layout=bubble] { @@ -1023,19 +1020,11 @@ $left-gutter: 64px; } } - &:not([data-layout=bubble]) { - padding-top: $spacing-16; - - &:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line, - &:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line, - &:hover.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line { - padding-inline-start: 0; // Override - } + &[data-layout=irc], + &[data-layout=group] { + padding-block-start: $spacing-16; .mx_EventTile_line { - padding-top: var(--BaseCard_EventTile_line-padding-block); - padding-bottom: var(--BaseCard_EventTile_line-padding-block); - .mx_EventTile_content { &.mx_EditMessageComposer { padding-inline-start: 0; // align start of first letter with that of the event body @@ -1044,34 +1033,6 @@ $left-gutter: 64px; } } - &[data-layout=bubble] { - margin-inline-start: var(--BaseCard_EventTile-spacing-inline); - margin-inline-end: var(--BaseCard_EventTile-spacing-inline); - - &::before { - inset-inline: calc(-1 * var(--BaseCard_EventTile-spacing-inline)); - z-index: auto; // enable background color on hover - } - - .mx_ReactionsRow { - position: relative; // display on hover - } - - .mx_EventTile_line.mx_EventTile_mediaLine { - padding-block: 0; - padding-inline-start: 0; - max-width: var(--EventBubbleTile_line-max-width); - } - - &[data-self=true] { - align-items: flex-end; - - .mx_EventTile_line.mx_EventTile_mediaLine { - margin: 0 var(--EventTile_bubble_line-margin-inline-end) 0 0; // align with normal messages - } - } - } - &[data-layout=group] { width: 100%; @@ -1139,13 +1100,51 @@ $left-gutter: 64px; position: absolute; // for IRC layout top: 2px; // Align with mx_EventTile_content } + + &:hover { + &.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line, + &.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line, + &.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line { + padding-inline-start: 0; + } + } + } + + &[data-layout=bubble] { + margin-inline: var(--BaseCard_EventTile-spacing-inline); + + &::before { + inset-inline: calc(-1 * var(--BaseCard_EventTile-spacing-inline)); + z-index: auto; // enable background color on hover + } + + .mx_ReactionsRow { + position: relative; // display on hover + } + + .mx_EventTile_line.mx_EventTile_mediaLine { + padding-block: 0; + padding-inline-start: 0; + max-width: var(--EventBubbleTile_line-max-width); + } + + &[data-self=true] { + align-items: flex-end; + + .mx_EventTile_line.mx_EventTile_mediaLine { + margin: 0 var(--EventTile_bubble_line-margin-inline-end) 0 0; // align with normal messages + } + } } } .mx_GenericEventListSummary { - &:not([data-layout=bubble]) > .mx_EventTile_line { - padding-inline-start: var(--ThreadView_group_spacing-start); // align summary text with message text - padding-inline-end: var(--ThreadView_group_spacing-end); // align summary text with message text + &[data-layout=irc], + &[data-layout=group] { + > .mx_EventTile_line { + padding-inline-start: var(--ThreadView_group_spacing-start); // align summary text with message text + padding-inline-end: var(--ThreadView_group_spacing-end); // align summary text with message text + } } } } From cc64e8eaceaa7b1c707af168afd6e7046a730bb7 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Fri, 8 Jul 2022 11:51:37 +0200 Subject: [PATCH 059/162] Unbreak URL preview for formatted links with tooltips (#9022) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Unbreak URL preview for formatted links with tooltips Fixes: vector-im/element-web#22764 Signed-off-by: Johannes Marbach * Flip back the flag default value 🤦 --- src/components/views/messages/TextualBody.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/TextualBody.tsx b/src/components/views/messages/TextualBody.tsx index 39334e9d465..ce58886c88e 100644 --- a/src/components/views/messages/TextualBody.tsx +++ b/src/components/views/messages/TextualBody.tsx @@ -93,9 +93,14 @@ export default class TextualBody extends React.Component { // we should be pillify them here by doing the linkifying BEFORE the pillifying. pillifyLinks([this.contentRef.current], this.props.mxEvent, this.pills); HtmlUtils.linkifyElement(this.contentRef.current); - tooltipifyLinks([this.contentRef.current], this.pills, this.tooltips); + this.calculateUrlPreview(); + // tooltipifyLinks AFTER calculateUrlPreview because the DOM inside the tooltip + // container is empty before the internal component has mounted so calculateUrlPreview + // won't find any anchors + tooltipifyLinks([this.contentRef.current], this.pills, this.tooltips); + if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") { // Handle expansion and add buttons const pres = (ReactDOM.findDOMNode(this) as Element).getElementsByTagName("pre"); From 7fb48d24e4ea977e8c2aaf5ddd7c0010bdef7acd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 8 Jul 2022 13:14:13 +0100 Subject: [PATCH 060/162] Upgrade to Cypress 10 (#9008) * Upgrade to Cypress 10 * Remove stale comment --- cypress.config.ts | 33 ++++ cypress.json | 13 -- .../1-register/register.spec.ts | 0 .../10-user-view/user-view.spec.ts | 0 .../11-room-directory/room-directory.spec.ts | 0 .../12-spotlight/spotlight.spec.ts | 0 .../pills-click-in-app.spec.ts | 0 .../2-login/consent.spec.ts | 0 .../2-login/login.spec.ts | 0 .../3-user-menu/user-menu.spec.ts | 0 .../4-create-room/create-room.spec.ts | 0 .../5-threads/threads.spec.ts | 0 .../6-spaces/spaces.spec.ts | 0 .../7-crypto/crypto.spec.ts | 0 .../8-update/update.spec.ts | 0 .../9-widgets/stickers.spec.ts | 0 .../integration/14-timeline/timeline.spec.ts | 145 ------------------ cypress/support/{index.ts => e2e.ts} | 0 package.json | 2 +- yarn.lock | 8 +- 20 files changed, 38 insertions(+), 163 deletions(-) create mode 100644 cypress.config.ts delete mode 100644 cypress.json rename cypress/{integration => e2e}/1-register/register.spec.ts (100%) rename cypress/{integration => e2e}/10-user-view/user-view.spec.ts (100%) rename cypress/{integration => e2e}/11-room-directory/room-directory.spec.ts (100%) rename cypress/{integration => e2e}/12-spotlight/spotlight.spec.ts (100%) rename cypress/{integration => e2e}/13-regression-tests/pills-click-in-app.spec.ts (100%) rename cypress/{integration => e2e}/2-login/consent.spec.ts (100%) rename cypress/{integration => e2e}/2-login/login.spec.ts (100%) rename cypress/{integration => e2e}/3-user-menu/user-menu.spec.ts (100%) rename cypress/{integration => e2e}/4-create-room/create-room.spec.ts (100%) rename cypress/{integration => e2e}/5-threads/threads.spec.ts (100%) rename cypress/{integration => e2e}/6-spaces/spaces.spec.ts (100%) rename cypress/{integration => e2e}/7-crypto/crypto.spec.ts (100%) rename cypress/{integration => e2e}/8-update/update.spec.ts (100%) rename cypress/{integration => e2e}/9-widgets/stickers.spec.ts (100%) delete mode 100644 cypress/integration/14-timeline/timeline.spec.ts rename cypress/support/{index.ts => e2e.ts} (100%) diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 00000000000..9236ee2931d --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,33 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { defineConfig } from 'cypress'; + +export default defineConfig({ + videoUploadOnPasses: false, + projectId: 'ppvnzg', + experimentalInteractiveRunEvents: true, + defaultCommandTimeout: 10000, + chromeWebSecurity: false, + e2e: { + setupNodeEvents(on, config) { + return require('./cypress/plugins/index.ts').default(on, config); + }, + baseUrl: 'http://localhost:8080', + experimentalSessionAndOrigin: true, + specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}', + }, +}); diff --git a/cypress.json b/cypress.json deleted file mode 100644 index 66a45fcc797..00000000000 --- a/cypress.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "baseUrl": "http://localhost:8080", - "videoUploadOnPasses": false, - "projectId": "ppvnzg", - "experimentalSessionAndOrigin": true, - "experimentalInteractiveRunEvents": true, - "retries": { - "runMode": 2, - "openMode": 0 - }, - "defaultCommandTimeout": 10000, - "chromeWebSecurity": false -} diff --git a/cypress/integration/1-register/register.spec.ts b/cypress/e2e/1-register/register.spec.ts similarity index 100% rename from cypress/integration/1-register/register.spec.ts rename to cypress/e2e/1-register/register.spec.ts diff --git a/cypress/integration/10-user-view/user-view.spec.ts b/cypress/e2e/10-user-view/user-view.spec.ts similarity index 100% rename from cypress/integration/10-user-view/user-view.spec.ts rename to cypress/e2e/10-user-view/user-view.spec.ts diff --git a/cypress/integration/11-room-directory/room-directory.spec.ts b/cypress/e2e/11-room-directory/room-directory.spec.ts similarity index 100% rename from cypress/integration/11-room-directory/room-directory.spec.ts rename to cypress/e2e/11-room-directory/room-directory.spec.ts diff --git a/cypress/integration/12-spotlight/spotlight.spec.ts b/cypress/e2e/12-spotlight/spotlight.spec.ts similarity index 100% rename from cypress/integration/12-spotlight/spotlight.spec.ts rename to cypress/e2e/12-spotlight/spotlight.spec.ts diff --git a/cypress/integration/13-regression-tests/pills-click-in-app.spec.ts b/cypress/e2e/13-regression-tests/pills-click-in-app.spec.ts similarity index 100% rename from cypress/integration/13-regression-tests/pills-click-in-app.spec.ts rename to cypress/e2e/13-regression-tests/pills-click-in-app.spec.ts diff --git a/cypress/integration/2-login/consent.spec.ts b/cypress/e2e/2-login/consent.spec.ts similarity index 100% rename from cypress/integration/2-login/consent.spec.ts rename to cypress/e2e/2-login/consent.spec.ts diff --git a/cypress/integration/2-login/login.spec.ts b/cypress/e2e/2-login/login.spec.ts similarity index 100% rename from cypress/integration/2-login/login.spec.ts rename to cypress/e2e/2-login/login.spec.ts diff --git a/cypress/integration/3-user-menu/user-menu.spec.ts b/cypress/e2e/3-user-menu/user-menu.spec.ts similarity index 100% rename from cypress/integration/3-user-menu/user-menu.spec.ts rename to cypress/e2e/3-user-menu/user-menu.spec.ts diff --git a/cypress/integration/4-create-room/create-room.spec.ts b/cypress/e2e/4-create-room/create-room.spec.ts similarity index 100% rename from cypress/integration/4-create-room/create-room.spec.ts rename to cypress/e2e/4-create-room/create-room.spec.ts diff --git a/cypress/integration/5-threads/threads.spec.ts b/cypress/e2e/5-threads/threads.spec.ts similarity index 100% rename from cypress/integration/5-threads/threads.spec.ts rename to cypress/e2e/5-threads/threads.spec.ts diff --git a/cypress/integration/6-spaces/spaces.spec.ts b/cypress/e2e/6-spaces/spaces.spec.ts similarity index 100% rename from cypress/integration/6-spaces/spaces.spec.ts rename to cypress/e2e/6-spaces/spaces.spec.ts diff --git a/cypress/integration/7-crypto/crypto.spec.ts b/cypress/e2e/7-crypto/crypto.spec.ts similarity index 100% rename from cypress/integration/7-crypto/crypto.spec.ts rename to cypress/e2e/7-crypto/crypto.spec.ts diff --git a/cypress/integration/8-update/update.spec.ts b/cypress/e2e/8-update/update.spec.ts similarity index 100% rename from cypress/integration/8-update/update.spec.ts rename to cypress/e2e/8-update/update.spec.ts diff --git a/cypress/integration/9-widgets/stickers.spec.ts b/cypress/e2e/9-widgets/stickers.spec.ts similarity index 100% rename from cypress/integration/9-widgets/stickers.spec.ts rename to cypress/e2e/9-widgets/stickers.spec.ts diff --git a/cypress/integration/14-timeline/timeline.spec.ts b/cypress/integration/14-timeline/timeline.spec.ts deleted file mode 100644 index 22861c8fd70..00000000000 --- a/cypress/integration/14-timeline/timeline.spec.ts +++ /dev/null @@ -1,145 +0,0 @@ -/* -Copyright 2022 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/// - -import { MessageEvent } from "matrix-events-sdk"; - -import type { ISendEventResponse } from "matrix-js-sdk/src/@types/requests"; -import type { EventType } from "matrix-js-sdk/src/@types/event"; -import type { MatrixClient } from "matrix-js-sdk/src/client"; -import { SynapseInstance } from "../../plugins/synapsedocker"; -import { SettingLevel } from "../../../src/settings/SettingLevel"; -import Chainable = Cypress.Chainable; - -// The avatar size used in the timeline -const AVATAR_SIZE = 30; -// The resize method used in the timeline -const AVATAR_RESIZE_METHOD = "crop"; - -const ROOM_NAME = "Test room"; -const OLD_AVATAR = "avatar_image1"; -const NEW_AVATAR = "avatar_image2"; -const OLD_NAME = "Alan"; -const NEW_NAME = "Alan (away)"; - -const getEventTilesWithBodies = (): Chainable => { - return cy.get(".mx_EventTile").filter((_i, e) => e.getElementsByClassName("mx_EventTile_body").length > 0); -}; - -const expectDisplayName = (e: JQuery, displayName: string): void => { - expect(e.find(".mx_DisambiguatedProfile_displayName").text()).to.equal(displayName); -}; - -const expectAvatar = (e: JQuery, avatarUrl: string): void => { - cy.getClient().then((cli: MatrixClient) => { - expect(e.find(".mx_BaseAvatar_image").attr("src")).to.equal( - // eslint-disable-next-line no-restricted-properties - cli.mxcUrlToHttp(avatarUrl, AVATAR_SIZE, AVATAR_SIZE, AVATAR_RESIZE_METHOD), - ); - }); -}; - -const sendEvent = (roomId: string): Chainable => { - return cy.sendEvent( - roomId, - null, - "m.room.message" as EventType, - MessageEvent.from("Message").serialize().content, - ); -}; - -describe("Timeline", () => { - let synapse: SynapseInstance; - - let roomId: string; - - let oldAvatarUrl: string; - let newAvatarUrl: string; - - describe("useOnlyCurrentProfiles", () => { - beforeEach(() => { - cy.startSynapse("default").then(data => { - synapse = data; - cy.initTestUser(synapse, OLD_NAME).then(() => - cy.window({ log: false }).then(() => { - cy.createRoom({ name: ROOM_NAME }).then(_room1Id => { - roomId = _room1Id; - }); - }), - ).then(() => { - cy.uploadContent(OLD_AVATAR).then((url) => { - oldAvatarUrl = url; - cy.setAvatarUrl(url); - }); - }).then(() => { - cy.uploadContent(NEW_AVATAR).then((url) => { - newAvatarUrl = url; - }); - }); - }); - }); - - afterEach(() => { - cy.stopSynapse(synapse); - }); - - it("should show historical profiles if disabled", () => { - cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, false); - sendEvent(roomId); - cy.setDisplayName("Alan (away)"); - cy.setAvatarUrl(newAvatarUrl); - // XXX: If we send the second event too quickly, there won't be - // enough time for the client to register the profile change - cy.wait(500); - sendEvent(roomId); - cy.viewRoomByName(ROOM_NAME); - - const events = getEventTilesWithBodies(); - - events.should("have.length", 2); - events.each((e, i) => { - if (i === 0) { - expectDisplayName(e, OLD_NAME); - expectAvatar(e, oldAvatarUrl); - } else if (i === 1) { - expectDisplayName(e, NEW_NAME); - expectAvatar(e, newAvatarUrl); - } - }); - }); - - it("should not show historical profiles if enabled", () => { - cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, true); - sendEvent(roomId); - cy.setDisplayName(NEW_NAME); - cy.setAvatarUrl(newAvatarUrl); - // XXX: If we send the second event too quickly, there won't be - // enough time for the client to register the profile change - cy.wait(500); - sendEvent(roomId); - cy.viewRoomByName(ROOM_NAME); - - const events = getEventTilesWithBodies(); - - events.should("have.length", 2); - events.each((e) => { - expectDisplayName(e, NEW_NAME); - expectAvatar(e, newAvatarUrl); - }); - }); - }); -}); diff --git a/cypress/support/index.ts b/cypress/support/e2e.ts similarity index 100% rename from cypress/support/index.ts rename to cypress/support/e2e.ts diff --git a/package.json b/package.json index 8b1aa4a4cad..71051a1538a 100644 --- a/package.json +++ b/package.json @@ -170,7 +170,7 @@ "babel-jest": "^26.6.3", "blob-polyfill": "^6.0.20211015", "chokidar": "^3.5.1", - "cypress": "^9.6.1", + "cypress": "^10.3.0", "cypress-real-events": "^1.7.0", "enzyme": "^3.11.0", "enzyme-to-json": "^3.6.2", diff --git a/yarn.lock b/yarn.lock index a7131a96e0f..10b1f6c3518 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3651,10 +3651,10 @@ cypress-real-events@^1.7.0: resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.7.0.tgz#ad6a78de33af3af0e6437f5c713e30691c44472c" integrity sha512-iyXp07j0V9sG3YClVDcvHN2DAQDgr+EjTID82uWDw6OZBlU3pXEBqTMNYqroz3bxlb0k+F74U81aZwzMNaKyew== -cypress@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.6.1.tgz#a7d6b5a53325b3dc4960181f5800a5ade0f085eb" - integrity sha512-ECzmV7pJSkk+NuAhEw6C3D+RIRATkSb2VAHXDY6qGZbca/F9mv5pPsj2LO6Ty6oIFVBTrwCyL9agl28MtJMe2g== +cypress@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-10.3.0.tgz#fae8d32f0822fcfb938e79c7c31ef344794336ae" + integrity sha512-txkQWKzvBVnWdCuKs5Xc08gjpO89W2Dom2wpZgT9zWZT5jXxqPIxqP/NC1YArtkpmp3fN5HW8aDjYBizHLUFvg== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" From cc8d78d971668f4ac1bf07ef538cf8664d97fb70 Mon Sep 17 00:00:00 2001 From: Kerry Date: Fri, 8 Jul 2022 17:08:05 +0200 Subject: [PATCH 061/162] Location sharing - add localised strings to map (#9025) * add localised strings to map * fussy import ordering * remove unused scale control strings from override --- src/i18n/strings/en_EN.json | 12 ++++++++++-- src/utils/location/map.ts | 13 +++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index e0b1749c036..d27644cb643 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -726,6 +726,16 @@ "Unknown App": "Unknown App", "This homeserver is not configured to display maps.": "This homeserver is not configured to display maps.", "This homeserver is not configured correctly to display maps, or the configured map server may be unreachable.": "This homeserver is not configured correctly to display maps, or the configured map server may be unreachable.", + "Toggle attribution": "Toggle attribution", + "Map feedback": "Map feedback", + "Enter fullscreen": "Enter fullscreen", + "Exit fullscreen": "Exit fullscreen", + "Find my location": "Find my location", + "Location not available": "Location not available", + "Mapbox logo": "Mapbox logo", + "Reset bearing to north": "Reset bearing to north", + "Zoom in": "Zoom in", + "Zoom out": "Zoom out", "Are you sure you want to exit during this export?": "Are you sure you want to exit during this export?", "Generating a ZIP": "Generating a ZIP", "Fetched %(count)s events out of %(total)s|other": "Fetched %(count)s events out of %(total)s", @@ -2198,8 +2208,6 @@ "My live location": "My live location", "Drop a Pin": "Drop a Pin", "What location type do you want to share?": "What location type do you want to share?", - "Zoom in": "Zoom in", - "Zoom out": "Zoom out", "Frequently Used": "Frequently Used", "Smileys & People": "Smileys & People", "Animals & Nature": "Animals & Nature", diff --git a/src/utils/location/map.ts b/src/utils/location/map.ts index 5c5ab34e18c..7dc8522271c 100644 --- a/src/utils/location/map.ts +++ b/src/utils/location/map.ts @@ -19,6 +19,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { M_LOCATION } from "matrix-js-sdk/src/@types/location"; import { logger } from "matrix-js-sdk/src/logger"; +import { _t } from "../../languageHandler"; import { parseGeoUri } from "./parseGeoUri"; import { findMapStyleUrl } from "./findMapStyleUrl"; import { LocationShareError } from "./LocationShareErrors"; @@ -37,6 +38,18 @@ export const createMap = ( zoom: 15, interactive, attributionControl: false, + locale: { + 'AttributionControl.ToggleAttribution': _t('Toggle attribution'), + 'AttributionControl.MapFeedback': _t('Map feedback'), + 'FullscreenControl.Enter': _t('Enter fullscreen'), + 'FullscreenControl.Exit': _t('Exit fullscreen'), + 'GeolocateControl.FindMyLocation': _t('Find my location'), + 'GeolocateControl.LocationNotAvailable': _t('Location not available'), + 'LogoControl.Title': _t('Mapbox logo'), + 'NavigationControl.ResetBearing': _t('Reset bearing to north'), + 'NavigationControl.ZoomIn': _t('Zoom in'), + 'NavigationControl.ZoomOut': _t('Zoom out'), + }, }); map.addControl(new maplibregl.AttributionControl(), 'top-right'); From 483ea9bf77a7cb3ebd6ba27552e820a6ecea6736 Mon Sep 17 00:00:00 2001 From: Ankur Date: Sat, 9 Jul 2022 02:10:52 +0530 Subject: [PATCH 062/162] Trim whitespace in email field (#9027) Signed-off-by: ankur12-1610 --- src/components/views/auth/RegistrationForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index e4a283c5511..113c09aabaf 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -251,7 +251,7 @@ export default class RegistrationForm extends React.PureComponent { this.setState({ - email: ev.target.value, + email: ev.target.value.trim(), }); }; From 03ce8ae3237c03921c0eb6b4519f558a17b12814 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Sat, 9 Jul 2022 11:29:45 +0000 Subject: [PATCH 063/162] Enable search strings highlight on bubble layout (#9032) * Move mx_EventTile_searchHighlight out of mx_EventTile:not([data-layout=bubble]) to enable it on bubble layout Signed-off-by: Suguru Hirahara * Use a logical property Signed-off-by: Suguru Hirahara --- res/css/views/rooms/_EventTile.scss | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index a91ae5d12b3..02b7fa55f41 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -73,6 +73,19 @@ $left-gutter: 64px; } } + .mx_EventTile_searchHighlight { + background-color: $accent; + color: $accent-fg-color; + border-radius: 5px; + padding-inline: 2px; + cursor: pointer; + + a { + background-color: $accent; + color: $accent-fg-color; + } + } + .mx_EventTileBubble { margin-block: var(--EventTileBubble_margin-block); } @@ -395,20 +408,6 @@ $left-gutter: 64px; background-color: $event-selected-color; } - .mx_EventTile_searchHighlight { - background-color: $accent; - color: $accent-fg-color; - border-radius: 5px; - padding-left: 2px; - padding-right: 2px; - cursor: pointer; - - a { - background-color: $accent; - color: $accent-fg-color; - } - } - /* End to end encryption stuff */ &:hover .mx_EventTile_e2eIcon { opacity: 1; From 8641a5210bc1c960e27e33a66fa36126b65beefa Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Mon, 11 Jul 2022 07:33:37 +0200 Subject: [PATCH 064/162] Add LocalRoom (#9023) --- src/models/LocalRoom.ts | 61 +++++ .../room-list/filters/VisibilityProvider.ts | 6 + src/utils/local-room.ts | 153 +++++++++++ test/models/LocalRoom-test.ts | 90 +++++++ .../filters/VisibilityProvider-test.ts | 124 +++++++++ test/utils/local-room-test.ts | 241 ++++++++++++++++++ 6 files changed, 675 insertions(+) create mode 100644 src/models/LocalRoom.ts create mode 100644 src/utils/local-room.ts create mode 100644 test/models/LocalRoom-test.ts create mode 100644 test/stores/room-list/filters/VisibilityProvider-test.ts create mode 100644 test/utils/local-room-test.ts diff --git a/src/models/LocalRoom.ts b/src/models/LocalRoom.ts new file mode 100644 index 00000000000..ee58dd408fa --- /dev/null +++ b/src/models/LocalRoom.ts @@ -0,0 +1,61 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixClient, Room, PendingEventOrdering } from "matrix-js-sdk/src/matrix"; + +import { Member } from "../utils/direct-messages"; + +export const LOCAL_ROOM_ID_PREFIX = 'local+'; + +export enum LocalRoomState { + NEW, // new local room; only known to the client + CREATING, // real room is being created + CREATED, // real room has been created via API; events applied + ERROR, // error during room creation +} + +/** + * A local room that only exists client side. + * Its main purpose is to be used for temporary rooms when creating a DM. + */ +export class LocalRoom extends Room { + /** Whether the actual room should be encrypted. */ + encrypted = false; + /** If the actual room has been created, this holds its ID. */ + actualRoomId: string; + /** DM chat partner */ + targets: Member[] = []; + /** Callbacks that should be invoked after the actual room has been created. */ + afterCreateCallbacks: Function[] = []; + state: LocalRoomState = LocalRoomState.NEW; + + constructor(roomId: string, client: MatrixClient, myUserId: string) { + super(roomId, client, myUserId, { pendingEventOrdering: PendingEventOrdering.Detached }); + this.name = this.getDefaultRoomName(myUserId); + } + + public get isNew(): boolean { + return this.state === LocalRoomState.NEW; + } + + public get isCreated(): boolean { + return this.state === LocalRoomState.CREATED; + } + + public get isError(): boolean { + return this.state === LocalRoomState.ERROR; + } +} diff --git a/src/stores/room-list/filters/VisibilityProvider.ts b/src/stores/room-list/filters/VisibilityProvider.ts index 24f49ccf92c..26bfcd78ea9 100644 --- a/src/stores/room-list/filters/VisibilityProvider.ts +++ b/src/stores/room-list/filters/VisibilityProvider.ts @@ -18,6 +18,7 @@ import { Room } from "matrix-js-sdk/src/models/room"; import CallHandler from "../../../CallHandler"; import { RoomListCustomisations } from "../../../customisations/RoomList"; +import { LocalRoom } from "../../../models/LocalRoom"; import VoipUserMapper from "../../../VoipUserMapper"; export class VisibilityProvider { @@ -54,6 +55,11 @@ export class VisibilityProvider { return false; } + if (room instanceof LocalRoom) { + // local rooms shouldn't show up anywhere + return false; + } + const isVisibleFn = RoomListCustomisations.isRoomVisible; if (isVisibleFn) { return isVisibleFn(room); diff --git a/src/utils/local-room.ts b/src/utils/local-room.ts new file mode 100644 index 00000000000..29fab7206d1 --- /dev/null +++ b/src/utils/local-room.ts @@ -0,0 +1,153 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { logger } from "matrix-js-sdk/src/logger"; +import { ClientEvent, EventType, MatrixClient } from "matrix-js-sdk/src/matrix"; + +import defaultDispatcher from "../dispatcher/dispatcher"; +import { MatrixClientPeg } from "../MatrixClientPeg"; +import { LocalRoom, LocalRoomState, LOCAL_ROOM_ID_PREFIX } from "../models/LocalRoom"; +import * as thisModule from "./local-room"; + +/** + * Does a room action: + * For non-local rooms it calls fn directly. + * For local rooms it adds the callback function to the room's afterCreateCallbacks and + * dispatches a "local_room_event". + * + * @async + * @template T + * @param {string} roomId Room ID of the target room + * @param {(actualRoomId: string) => Promise} fn Callback to be called directly or collected at the local room + * @param {MatrixClient} [client] + * @returns {Promise} Promise that gets resolved after the callback has finished + */ +export async function doMaybeLocalRoomAction( + roomId: string, + fn: (actualRoomId: string) => Promise, + client?: MatrixClient, +): Promise { + if (roomId.startsWith(LOCAL_ROOM_ID_PREFIX)) { + client = client ?? MatrixClientPeg.get(); + const room = client.getRoom(roomId) as LocalRoom; + + if (room.isCreated) { + return fn(room.actualRoomId); + } + + return new Promise((resolve, reject) => { + room.afterCreateCallbacks.push((newRoomId: string) => { + fn(newRoomId).then(resolve).catch(reject); + }); + defaultDispatcher.dispatch({ + action: "local_room_event", + roomId: room.roomId, + }); + }); + } + + return fn(roomId); +} + +/** + * Tests whether a room created based on a local room is ready. + */ +export function isRoomReady( + client: MatrixClient, + localRoom: LocalRoom, +): boolean { + // not ready if no actual room id exists + if (!localRoom.actualRoomId) return false; + + const room = client.getRoom(localRoom.actualRoomId); + // not ready if the room does not exist + if (!room) return false; + + // not ready if not all members joined/invited + if (room.getInvitedAndJoinedMemberCount() !== 1 + localRoom.targets?.length) return false; + + const roomHistoryVisibilityEvents = room.currentState.getStateEvents(EventType.RoomHistoryVisibility); + // not ready if the room history has not been configured + if (roomHistoryVisibilityEvents.length === 0) return false; + + const roomEncryptionEvents = room.currentState.getStateEvents(EventType.RoomEncryption); + // not ready if encryption has not been configured (applies only to encrypted rooms) + if (localRoom.encrypted === true && roomEncryptionEvents.length === 0) return false; + + return true; +} + +/** + * Waits until a room is ready and then applies the after-create local room callbacks. + * Also implements a stopgap timeout after that a room is assumed to be ready. + * + * @see isRoomReady + * @async + * @param {MatrixClient} client + * @param {LocalRoom} localRoom + * @returns {Promise} Resolved to the actual room id + */ +export async function waitForRoomReadyAndApplyAfterCreateCallbacks( + client: MatrixClient, + localRoom: LocalRoom, +): Promise { + if (thisModule.isRoomReady(client, localRoom)) { + return applyAfterCreateCallbacks(localRoom, localRoom.actualRoomId).then(() => { + localRoom.state = LocalRoomState.CREATED; + client.emit(ClientEvent.Room, localRoom); + return Promise.resolve(localRoom.actualRoomId); + }); + } + + return new Promise((resolve) => { + const finish = () => { + if (checkRoomStateIntervalHandle) clearInterval(checkRoomStateIntervalHandle); + if (stopgapTimeoutHandle) clearTimeout(stopgapTimeoutHandle); + + applyAfterCreateCallbacks(localRoom, localRoom.actualRoomId).then(() => { + localRoom.state = LocalRoomState.CREATED; + client.emit(ClientEvent.Room, localRoom); + resolve(localRoom.actualRoomId); + }); + }; + + const stopgapFinish = () => { + logger.warn(`Assuming local room ${localRoom.roomId} is ready after hitting timeout`); + finish(); + }; + + const checkRoomStateIntervalHandle = setInterval(() => { + if (thisModule.isRoomReady(client, localRoom)) finish(); + }, 500); + const stopgapTimeoutHandle = setTimeout(stopgapFinish, 5000); + }); +} + +/** + * Applies the after-create callback of a local room. + * + * @async + * @param {LocalRoom} localRoom + * @param {string} roomId + * @returns {Promise} Resolved after all callbacks have been called + */ +async function applyAfterCreateCallbacks(localRoom: LocalRoom, roomId: string): Promise { + for (const afterCreateCallback of localRoom.afterCreateCallbacks) { + await afterCreateCallback(roomId); + } + + localRoom.afterCreateCallbacks = []; +} diff --git a/test/models/LocalRoom-test.ts b/test/models/LocalRoom-test.ts new file mode 100644 index 00000000000..00ba11eb0b7 --- /dev/null +++ b/test/models/LocalRoom-test.ts @@ -0,0 +1,90 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixClient } from "matrix-js-sdk/src/matrix"; + +import { LocalRoom, LocalRoomState, LOCAL_ROOM_ID_PREFIX } from "../../src/models/LocalRoom"; +import { createTestClient } from "../test-utils"; + +const stateTestData = [ + { + name: "NEW", + state: LocalRoomState.NEW, + isNew: true, + isCreated: false, + isError: false, + }, + { + name: "CREATING", + state: LocalRoomState.CREATING, + isNew: false, + isCreated: false, + isError: false, + }, + { + name: "CREATED", + state: LocalRoomState.CREATED, + isNew: false, + isCreated: true, + isError: false, + }, + { + name: "ERROR", + state: LocalRoomState.ERROR, + isNew: false, + isCreated: false, + isError: true, + }, +]; + +describe("LocalRoom", () => { + let room: LocalRoom; + let client: MatrixClient; + + beforeEach(() => { + client = createTestClient(); + room = new LocalRoom(LOCAL_ROOM_ID_PREFIX + "test", client, "@test:localhost"); + }); + + it("should not raise an error on getPendingEvents (implicitly check for pendingEventOrdering: detached)", () => { + room.getPendingEvents(); + expect(true).toBe(true); + }); + + it("should not have after create callbacks", () => { + expect(room.afterCreateCallbacks).toHaveLength(0); + }); + + stateTestData.forEach((stateTestDatum) => { + describe(`in state ${stateTestDatum.name}`, () => { + beforeEach(() => { + room.state = stateTestDatum.state; + }); + + it(`isNew should return ${stateTestDatum.isNew}`, () => { + expect(room.isNew).toBe(stateTestDatum.isNew); + }); + + it(`isCreated should return ${stateTestDatum.isCreated}`, () => { + expect(room.isCreated).toBe(stateTestDatum.isCreated); + }); + + it(`isError should return ${stateTestDatum.isError}`, () => { + expect(room.isError).toBe(stateTestDatum.isError); + }); + }); + }); +}); diff --git a/test/stores/room-list/filters/VisibilityProvider-test.ts b/test/stores/room-list/filters/VisibilityProvider-test.ts new file mode 100644 index 00000000000..596e815d364 --- /dev/null +++ b/test/stores/room-list/filters/VisibilityProvider-test.ts @@ -0,0 +1,124 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { mocked } from "jest-mock"; +import { Room } from "matrix-js-sdk/src/matrix"; + +import { VisibilityProvider } from "../../../../src/stores/room-list/filters/VisibilityProvider"; +import CallHandler from "../../../../src/CallHandler"; +import VoipUserMapper from "../../../../src/VoipUserMapper"; +import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../../../src/models/LocalRoom"; +import { RoomListCustomisations } from "../../../../src/customisations/RoomList"; +import { createTestClient } from "../../../test-utils"; + +jest.mock("../../../../src/VoipUserMapper", () => ({ + sharedInstance: jest.fn(), +})); + +jest.mock("../../../../src/CallHandler", () => ({ + instance: { + getSupportsVirtualRooms: jest.fn(), + }, +})); + +jest.mock("../../../../src/customisations/RoomList", () => ({ + RoomListCustomisations: { + isRoomVisible: jest.fn(), + }, +})); + +const createRoom = (isSpaceRoom = false): Room => { + return { + isSpaceRoom: () => isSpaceRoom, + } as unknown as Room; +}; + +const createLocalRoom = (): LocalRoom => { + const room = new LocalRoom(LOCAL_ROOM_ID_PREFIX + "test", createTestClient(), "@test:example.com"); + room.isSpaceRoom = () => false; + return room; +}; + +describe("VisibilityProvider", () => { + let mockVoipUserMapper: VoipUserMapper; + + beforeEach(() => { + mockVoipUserMapper = { + onNewInvitedRoom: jest.fn(), + isVirtualRoom: jest.fn(), + } as unknown as VoipUserMapper; + mocked(VoipUserMapper.sharedInstance).mockReturnValue(mockVoipUserMapper); + }); + + describe("instance", () => { + it("should return an instance", () => { + const visibilityProvider = VisibilityProvider.instance; + expect(visibilityProvider).toBeInstanceOf(VisibilityProvider); + expect(VisibilityProvider.instance).toBe(visibilityProvider); + }); + }); + + describe("onNewInvitedRoom", () => { + it("should call onNewInvitedRoom on VoipUserMapper.sharedInstance", async () => { + const room = {} as unknown as Room; + await VisibilityProvider.instance.onNewInvitedRoom(room); + expect(mockVoipUserMapper.onNewInvitedRoom).toHaveBeenCalledWith(room); + }); + }); + + describe("isRoomVisible", () => { + describe("for a virtual room", () => { + beforeEach(() => { + mocked(CallHandler.instance.getSupportsVirtualRooms).mockReturnValue(true); + mocked(mockVoipUserMapper.isVirtualRoom).mockReturnValue(true); + }); + + it("should return return false", () => { + const room = createRoom(); + expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(false); + expect(mockVoipUserMapper.isVirtualRoom).toHaveBeenCalledWith(room); + }); + }); + + it("should return false without room", () => { + expect(VisibilityProvider.instance.isRoomVisible()).toBe(false); + }); + + it("should return false for a space room", () => { + const room = createRoom(true); + expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(false); + }); + + it("should return false for a local room", () => { + const room = createLocalRoom(); + expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(false); + }); + + it("should return false if visibility customisation returns false", () => { + mocked(RoomListCustomisations.isRoomVisible).mockReturnValue(false); + const room = createRoom(); + expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(false); + expect(RoomListCustomisations.isRoomVisible).toHaveBeenCalledWith(room); + }); + + it("should return true if visibility customisation returns true", () => { + mocked(RoomListCustomisations.isRoomVisible).mockReturnValue(true); + const room = createRoom(); + expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(true); + expect(RoomListCustomisations.isRoomVisible).toHaveBeenCalledWith(room); + }); + }); +}); diff --git a/test/utils/local-room-test.ts b/test/utils/local-room-test.ts new file mode 100644 index 00000000000..de752694c80 --- /dev/null +++ b/test/utils/local-room-test.ts @@ -0,0 +1,241 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { mocked } from "jest-mock"; +import { EventType, MatrixClient, Room } from "matrix-js-sdk/src/matrix"; + +import { LocalRoom, LocalRoomState, LOCAL_ROOM_ID_PREFIX } from "../../src/models/LocalRoom"; +import * as localRoomModule from "../../src/utils/local-room"; +import defaultDispatcher from "../../src/dispatcher/dispatcher"; +import { createTestClient, makeMembershipEvent, mkEvent } from "../test-utils"; +import { DirectoryMember } from "../../src/utils/direct-messages"; + +describe("local-room", () => { + const userId1 = "@user1:example.com"; + const member1 = new DirectoryMember({ user_id: userId1 }); + const userId2 = "@user2:example.com"; + let room1: Room; + let localRoom: LocalRoom; + let client: MatrixClient; + + beforeEach(() => { + client = createTestClient(); + room1 = new Room("!room1:example.com", client, userId1); + room1.getMyMembership = () => "join"; + localRoom = new LocalRoom(LOCAL_ROOM_ID_PREFIX + "test", client, "@test:example.com"); + mocked(client.getRoom).mockImplementation((roomId: string) => { + if (roomId === localRoom.roomId) { + return localRoom; + } + return null; + }); + }); + + describe("doMaybeLocalRoomAction", () => { + let callback: jest.Mock; + + beforeEach(() => { + callback = jest.fn(); + callback.mockReturnValue(Promise.resolve()); + localRoom.actualRoomId = "@new:example.com"; + }); + + it("should invoke the callback for a non-local room", () => { + localRoomModule.doMaybeLocalRoomAction("!room:example.com", callback, client); + expect(callback).toHaveBeenCalled(); + }); + + it("should invoke the callback with the new room ID for a created room", () => { + localRoom.state = LocalRoomState.CREATED; + localRoomModule.doMaybeLocalRoomAction(localRoom.roomId, callback, client); + expect(callback).toHaveBeenCalledWith(localRoom.actualRoomId); + }); + + describe("for a local room", () => { + let prom; + + beforeEach(() => { + jest.spyOn(defaultDispatcher, "dispatch"); + prom = localRoomModule.doMaybeLocalRoomAction(localRoom.roomId, callback, client); + }); + + it("dispatch a local_room_event", () => { + expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ + action: "local_room_event", + roomId: localRoom.roomId, + }); + }); + + it("should resolve the promise after invoking the callback", async () => { + localRoom.afterCreateCallbacks.forEach((callback) => { + callback(localRoom.actualRoomId); + }); + await prom; + }); + }); + }); + + describe("isRoomReady", () => { + beforeEach(() => { + localRoom.targets = [member1]; + }); + + it("should return false if the room has no actual room id", () => { + expect(localRoomModule.isRoomReady(client, localRoom)).toBe(false); + }); + + describe("for a room with an actual room id", () => { + beforeEach(() => { + localRoom.actualRoomId = room1.roomId; + mocked(client.getRoom).mockReturnValue(null); + }); + + it("it should return false", () => { + expect(localRoomModule.isRoomReady(client, localRoom)).toBe(false); + }); + + describe("and the room is known to the client", () => { + beforeEach(() => { + mocked(client.getRoom).mockImplementation((roomId: string) => { + if (roomId === room1.roomId) return room1; + }); + }); + + it("it should return false", () => { + expect(localRoomModule.isRoomReady(client, localRoom)).toBe(false); + }); + + describe("and all members have been invited or joined", () => { + beforeEach(() => { + room1.currentState.setStateEvents([ + makeMembershipEvent(room1.roomId, userId1, "join"), + makeMembershipEvent(room1.roomId, userId2, "invite"), + ]); + }); + + it("it should return false", () => { + expect(localRoomModule.isRoomReady(client, localRoom)).toBe(false); + }); + + describe("and a RoomHistoryVisibility event", () => { + beforeEach(() => { + room1.currentState.setStateEvents([mkEvent({ + user: userId1, + event: true, + type: EventType.RoomHistoryVisibility, + room: room1.roomId, + content: {}, + })]); + }); + + it("it should return true", () => { + expect(localRoomModule.isRoomReady(client, localRoom)).toBe(true); + }); + + describe("and an encrypted room", () => { + beforeEach(() => { + localRoom.encrypted = true; + }); + + it("it should return false", () => { + expect(localRoomModule.isRoomReady(client, localRoom)).toBe(false); + }); + + describe("and a room encryption state event", () => { + beforeEach(() => { + room1.currentState.setStateEvents([mkEvent({ + user: userId1, + event: true, + type: EventType.RoomEncryption, + room: room1.roomId, + content: {}, + })]); + }); + + it("it should return true", () => { + expect(localRoomModule.isRoomReady(client, localRoom)).toBe(true); + }); + }); + }); + }); + }); + }); + }); + }); + + describe("waitForRoomReadyAndApplyAfterCreateCallbacks", () => { + let localRoomCallbackRoomId: string; + + beforeEach(() => { + localRoom.actualRoomId = room1.roomId; + localRoom.afterCreateCallbacks.push((roomId: string) => { + localRoomCallbackRoomId = roomId; + return Promise.resolve(); + }); + jest.useFakeTimers(); + }); + + describe("for an immediate ready room", () => { + beforeEach(() => { + jest.spyOn(localRoomModule, "isRoomReady").mockReturnValue(true); + }); + + it("should invoke the callbacks, set the room state to created and return the actual room id", async () => { + const result = await localRoomModule.waitForRoomReadyAndApplyAfterCreateCallbacks(client, localRoom); + expect(localRoom.state).toBe(LocalRoomState.CREATED); + expect(localRoomCallbackRoomId).toBe(room1.roomId); + expect(result).toBe(room1.roomId); + }); + }); + + describe("for a room running into the create timeout", () => { + beforeEach(() => { + jest.spyOn(localRoomModule, "isRoomReady").mockReturnValue(false); + }); + + it("should invoke the callbacks, set the room state to created and return the actual room id", (done) => { + const prom = localRoomModule.waitForRoomReadyAndApplyAfterCreateCallbacks(client, localRoom); + jest.advanceTimersByTime(5000); + prom.then((roomId: string) => { + expect(localRoom.state).toBe(LocalRoomState.CREATED); + expect(localRoomCallbackRoomId).toBe(room1.roomId); + expect(roomId).toBe(room1.roomId); + expect(jest.getTimerCount()).toBe(0); + done(); + }); + }); + }); + + describe("for a room that is ready after a while", () => { + beforeEach(() => { + jest.spyOn(localRoomModule, "isRoomReady").mockReturnValue(false); + }); + + it("should invoke the callbacks, set the room state to created and return the actual room id", (done) => { + const prom = localRoomModule.waitForRoomReadyAndApplyAfterCreateCallbacks(client, localRoom); + jest.spyOn(localRoomModule, "isRoomReady").mockReturnValue(true); + jest.advanceTimersByTime(500); + prom.then((roomId: string) => { + expect(localRoom.state).toBe(LocalRoomState.CREATED); + expect(localRoomCallbackRoomId).toBe(room1.roomId); + expect(roomId).toBe(room1.roomId); + expect(jest.getTimerCount()).toBe(0); + done(); + }); + }); + }); + }); +}); From d1928d2cb3a19a18256eedc5e6f68bae0da1c670 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Mon, 11 Jul 2022 05:46:31 +0000 Subject: [PATCH 065/162] Remove obsolete opacity value for E2E icons (#8975) * Remove obsolete opacity value for E2E icons Signed-off-by: Suguru Hirahara * The opacity declaration is not required after all Signed-off-by: Suguru Hirahara --- res/css/views/rooms/_EventTile.scss | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 02b7fa55f41..ad865a40f46 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -407,11 +407,6 @@ $left-gutter: 64px; &.mx_EventTile.focus-visible:focus-within .mx_EventTile_line { background-color: $event-selected-color; } - - /* End to end encryption stuff */ - &:hover .mx_EventTile_e2eIcon { - opacity: 1; - } } .mx_GenericEventListSummary { @@ -588,7 +583,6 @@ $left-gutter: 64px; width: 14px; height: 14px; display: block; - opacity: 0.2; background-repeat: no-repeat; background-size: contain; @@ -610,11 +604,6 @@ $left-gutter: 64px; mask-size: 80%; } - &.mx_EventTile_e2eIcon_warning, - &.mx_EventTile_e2eIcon_normal { - opacity: 1; - } - &.mx_EventTile_e2eIcon_warning::after { mask-image: url('$(res)/img/e2e/warning.svg'); background-color: $alert; From 19e514d83cc107ae7ef9372e9b6782a312e45cbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 11 Jul 2022 07:52:44 +0200 Subject: [PATCH 066/162] Remove dead code (#9035) --- src/HtmlUtils.tsx | 27 -------- src/Keyboard.ts | 8 --- src/RoomNotifs.ts | 32 ---------- src/Rooms.ts | 19 ------ src/ScalarMessaging.ts | 4 -- src/UserAddress.ts | 18 ------ src/components/structures/ContextMenu.tsx | 1 - .../dialogs/AnalyticsLearnMoreDialog.tsx | 4 +- .../views/location/shareLocation.ts | 13 ---- .../views/rooms/CollapsibleButton.tsx | 2 - .../views/settings/KeyboardShortcut.tsx | 2 - .../views/spaces/SpaceTreeLevel.tsx | 2 - src/effects/ICanvasEffect.ts | 10 --- src/events/forward/types.ts | 19 ------ src/events/types.ts | 19 ------ src/linkify-matrix.ts | 6 -- src/notifications/ContentRules.ts | 3 - src/settings/SettingsStore.ts | 2 - src/stores/room-list/previews/utils.ts | 9 --- src/utils/Receipt.ts | 38 ----------- src/utils/drawable.ts | 36 ----------- src/utils/iterables.ts | 6 +- src/utils/maps.ts | 14 +--- src/utils/units.ts | 8 --- .../views/location/LocationButton-test.tsx | 31 --------- test/test-utils/threads.ts | 8 +-- test/utils/iterables-test.ts | 13 +--- test/utils/maps-test.ts | 64 +------------------ 28 files changed, 6 insertions(+), 412 deletions(-) delete mode 100644 src/events/forward/types.ts delete mode 100644 src/events/types.ts delete mode 100644 src/utils/Receipt.ts delete mode 100644 src/utils/drawable.ts delete mode 100644 test/components/views/location/LocationButton-test.tsx diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 630bda8787a..c2fe3e31c9d 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -109,33 +109,6 @@ export function unicodeToShortcode(char: string): string { return shortcodes?.length ? `:${shortcodes[0]}:` : ''; } -export function processHtmlForSending(html: string): string { - const contentDiv = document.createElement('div'); - contentDiv.innerHTML = html; - - if (contentDiv.children.length === 0) { - return contentDiv.innerHTML; - } - - let contentHTML = ""; - for (let i = 0; i < contentDiv.children.length; i++) { - const element = contentDiv.children[i]; - if (element.tagName.toLowerCase() === 'p') { - contentHTML += element.innerHTML; - // Don't add a
for the last

- if (i !== contentDiv.children.length - 1) { - contentHTML += '
'; - } - } else { - const temp = document.createElement('div'); - temp.appendChild(element.cloneNode(true)); - contentHTML += temp.innerHTML; - } - } - - return contentHTML; -} - /* * Given an untrusted HTML string, return a React node with an sanitized version * of that HTML. diff --git a/src/Keyboard.ts b/src/Keyboard.ts index efecd791fd8..7d41f9c77d1 100644 --- a/src/Keyboard.ts +++ b/src/Keyboard.ts @@ -83,11 +83,3 @@ export function isOnlyCtrlOrCmdKeyEvent(ev) { return ev.ctrlKey && !ev.altKey && !ev.metaKey && !ev.shiftKey; } } - -export function isOnlyCtrlOrCmdIgnoreShiftKeyEvent(ev) { - if (IS_MAC) { - return ev.metaKey && !ev.altKey && !ev.ctrlKey; - } else { - return ev.ctrlKey && !ev.altKey && !ev.metaKey; - } -} diff --git a/src/RoomNotifs.ts b/src/RoomNotifs.ts index 97e4785104b..08c15970c56 100644 --- a/src/RoomNotifs.ts +++ b/src/RoomNotifs.ts @@ -35,38 +35,6 @@ export enum RoomNotifState { Mute = 'mute', } -export const BADGE_STATES = [RoomNotifState.AllMessages, RoomNotifState.AllMessagesLoud]; -export const MENTION_BADGE_STATES = [...BADGE_STATES, RoomNotifState.MentionsOnly]; - -export function shouldShowNotifBadge(roomNotifState: RoomNotifState): boolean { - return BADGE_STATES.includes(roomNotifState); -} - -export function shouldShowMentionBadge(roomNotifState: RoomNotifState): boolean { - return MENTION_BADGE_STATES.includes(roomNotifState); -} - -export function aggregateNotificationCount(rooms: Room[]): {count: number, highlight: boolean} { - return rooms.reduce<{count: number, highlight: boolean}>((result, room) => { - const roomNotifState = getRoomNotifsState(room.roomId); - const highlight = room.getUnreadNotificationCount(NotificationCountType.Highlight) > 0; - // use helper method to include highlights in the previous version of the room - const notificationCount = getUnreadNotificationCount(room); - - const notifBadges = notificationCount > 0 && shouldShowNotifBadge(roomNotifState); - const mentionBadges = highlight && shouldShowMentionBadge(roomNotifState); - const badges = notifBadges || mentionBadges; - - if (badges) { - result.count += notificationCount; - if (highlight) { - result.highlight = true; - } - } - return result; - }, { count: 0, highlight: false }); -} - export function getRoomNotifsState(roomId: string): RoomNotifState { if (MatrixClientPeg.get().isGuest()) return RoomNotifState.AllMessages; diff --git a/src/Rooms.ts b/src/Rooms.ts index 707b44305be..57a4bf522ea 100644 --- a/src/Rooms.ts +++ b/src/Rooms.ts @@ -44,25 +44,6 @@ export function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: s return canonicalAlias || altAliases?.[0]; } -export function looksLikeDirectMessageRoom(room: Room, myUserId: string): boolean { - const myMembership = room.getMyMembership(); - const me = room.getMember(myUserId); - - if (myMembership == "join" || myMembership === "ban" || (me && me.isKicked())) { - // Used to split rooms via tags - const tagNames = Object.keys(room.tags); - // Used for 1:1 direct messages - // Show 1:1 chats in separate "Direct Messages" section as long as they haven't - // been moved to a different tag section - const totalMemberCount = room.currentState.getJoinedMemberCount() + - room.currentState.getInvitedMemberCount(); - if (totalMemberCount === 2 && !tagNames.length) { - return true; - } - } - return false; -} - export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise { let newTarget; if (isDirect) { diff --git a/src/ScalarMessaging.ts b/src/ScalarMessaging.ts index bf629bb711b..d2d11c71cfa 100644 --- a/src/ScalarMessaging.ts +++ b/src/ScalarMessaging.ts @@ -752,7 +752,3 @@ export function stopListening(): void { logger.error(e); } } - -export function setOpenManagerUrl(url: string): void { - openManagerUrl = url; -} diff --git a/src/UserAddress.ts b/src/UserAddress.ts index 248814aa019..b6e37ce99d4 100644 --- a/src/UserAddress.ts +++ b/src/UserAddress.ts @@ -24,24 +24,6 @@ export enum AddressType { MatrixRoomId = "mx-room-id", } -export const addressTypes = [AddressType.Email, AddressType.MatrixRoomId, AddressType.MatrixUserId]; - -// PropType definition for an object describing -// an address that can be invited to a room (which -// could be a third party identifier or a matrix ID) -// along with some additional information about the -// address / target. -export interface IUserAddress { - addressType: AddressType; - address: string; - displayName?: string; - avatarMxc?: string; - // true if the address is known to be a valid address (eg. is a real - // user we've seen) or false otherwise (eg. is just an address the - // user has entered) - isKnown?: boolean; -} - export function getAddressType(inputText: string): AddressType | null { if (emailRegex.test(inputText)) { return AddressType.Email; diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index 695d6ec2a7b..dc64dd23518 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -570,7 +570,6 @@ export function createMenu(ElementClass, props) { // re-export the semantic helper components for simplicity export { ContextMenuButton } from "../../accessibility/context_menu/ContextMenuButton"; export { ContextMenuTooltipButton } from "../../accessibility/context_menu/ContextMenuTooltipButton"; -export { MenuGroup } from "../../accessibility/context_menu/MenuGroup"; export { MenuItem } from "../../accessibility/context_menu/MenuItem"; export { MenuItemCheckbox } from "../../accessibility/context_menu/MenuItemCheckbox"; export { MenuItemRadio } from "../../accessibility/context_menu/MenuItemRadio"; diff --git a/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx b/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx index 788d12932bf..7a9fb14d460 100644 --- a/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx +++ b/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx @@ -37,7 +37,7 @@ interface IProps { hasCancel?: boolean; } -const AnalyticsLearnMoreDialog: React.FC = ({ +export const AnalyticsLearnMoreDialog: React.FC = ({ onFinished, analyticsOwner, privacyPolicyUrl, @@ -105,5 +105,3 @@ export const showDialog = (props: Omit; }; - -export default CollapsibleButton; diff --git a/src/components/views/settings/KeyboardShortcut.tsx b/src/components/views/settings/KeyboardShortcut.tsx index 3e4f65b8c58..0f35ea0e7ac 100644 --- a/src/components/views/settings/KeyboardShortcut.tsx +++ b/src/components/views/settings/KeyboardShortcut.tsx @@ -63,5 +63,3 @@ export const KeyboardShortcut: React.FC = ({ value }) =>

; }; - -export default KeyboardShortcut; diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index b038dbb7e46..80d678d3b61 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -371,5 +371,3 @@ const SpaceTreeLevel: React.FC = ({ }) } ; }; - -export default SpaceTreeLevel; diff --git a/src/effects/ICanvasEffect.ts b/src/effects/ICanvasEffect.ts index 9bf3e9293d2..36681d7cf99 100644 --- a/src/effects/ICanvasEffect.ts +++ b/src/effects/ICanvasEffect.ts @@ -14,16 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -/** - * Defines the constructor of a canvas based room effect - */ -export interface ICanvasEffectConstructable { - /** - * @param {{[key:string]:any}} options? Optional animation options - * @returns ICanvasEffect Returns a new instance of the canvas effect - */ - new(options?: { [key: string]: any }): ICanvasEffect; -} /** * Defines the interface of a canvas based room effect diff --git a/src/events/forward/types.ts b/src/events/forward/types.ts deleted file mode 100644 index f30b3144814..00000000000 --- a/src/events/forward/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* -Copyright 2022 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; - -export type ActionableEventTransformFunction = (event: MatrixEvent, cli: MatrixClient) => MatrixEvent | null; diff --git a/src/events/types.ts b/src/events/types.ts deleted file mode 100644 index f30b3144814..00000000000 --- a/src/events/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* -Copyright 2022 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; - -export type ActionableEventTransformFunction = (event: MatrixEvent, cli: MatrixClient) => MatrixEvent | null; diff --git a/src/linkify-matrix.ts b/src/linkify-matrix.ts index 7935f5d0377..b626756f7c6 100644 --- a/src/linkify-matrix.ts +++ b/src/linkify-matrix.ts @@ -21,7 +21,6 @@ import linkifyElement from 'linkify-element'; import linkifyString from 'linkify-string'; import { RoomMember } from 'matrix-js-sdk/src/models/room-member'; -import { baseUrl } from "./utils/permalinks/MatrixToPermalinkConstructor"; import { parsePermalink, tryTransformEntityToPermalink, @@ -144,11 +143,6 @@ export const ELEMENT_URL_PATTERN = "(?:app|beta|staging|develop)\\.element\\.io/" + ")(#.*)"; -export const MATRIXTO_URL_PATTERN = "^(?:https?://)?(?:www\\.)?matrix\\.to/#/(([#@!+]).*)"; -export const MATRIXTO_MD_LINK_PATTERN = - '\\[([^\\]]*)\\]\\((?:https?://)?(?:www\\.)?matrix\\.to/#/([#@!+][^\\)]*)\\)'; -export const MATRIXTO_BASE_URL= baseUrl; - export const options = { events: function(href: string, type: Type | string): Partial { switch (type) { diff --git a/src/notifications/ContentRules.ts b/src/notifications/ContentRules.ts index 244657b6207..69cc5507526 100644 --- a/src/notifications/ContentRules.ts +++ b/src/notifications/ContentRules.ts @@ -24,9 +24,6 @@ export interface IContentRules { externalRules: IAnnotatedPushRule[]; } -export const SCOPE = "global"; -export const KIND = "content"; - export class ContentRules { /** * Extract the keyword rules from a list of rules, and parse them diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index c1c488bf9ec..b6f63d4e9c6 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -99,8 +99,6 @@ interface IHandlerMap { [level: SettingLevel]: SettingsHandler; } -export type LabsFeatureState = "labs" | "disable" | "enable" | string; - /** * Controls and manages application settings by providing varying levels at which the * setting value may be specified. The levels are then used to determine what the setting diff --git a/src/stores/room-list/previews/utils.ts b/src/stores/room-list/previews/utils.ts index 440b9433ba1..dd6b63f8bcb 100644 --- a/src/stores/room-list/previews/utils.ts +++ b/src/stores/room-list/previews/utils.ts @@ -27,11 +27,6 @@ export function isSelf(event: MatrixEvent): boolean { return event.getSender() === selfUserId; } -export function isSelfTarget(event: MatrixEvent): boolean { - const selfUserId = MatrixClientPeg.get().getUserId(); - return event.getStateKey() === selfUserId; -} - export function shouldPrefixMessagesIn(roomId: string, tagId: TagID): boolean { if (tagId !== DefaultTagID.DM) return true; @@ -44,7 +39,3 @@ export function shouldPrefixMessagesIn(roomId: string, tagId: TagID): boolean { export function getSenderName(event: MatrixEvent): string { return event.sender ? event.sender.name : event.getSender(); } - -export function getTargetName(event: MatrixEvent): string { - return event.target ? event.target.name : event.getStateKey(); -} diff --git a/src/utils/Receipt.ts b/src/utils/Receipt.ts deleted file mode 100644 index 4b1c0ffbfba..00000000000 --- a/src/utils/Receipt.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; - -/** - * Given MatrixEvent containing receipts, return the first - * read receipt from the given user ID, or null if no such - * receipt exists. - * - * @param {Object} receiptEvent A Matrix Event - * @param {string} userId A user ID - * @returns {Object} Read receipt - */ -export function findReadReceiptFromUserId(receiptEvent: MatrixEvent, userId: string): object | null { - const receiptKeys = Object.keys(receiptEvent.getContent()); - for (let i = 0; i < receiptKeys.length; ++i) { - const rcpt = receiptEvent.getContent()[receiptKeys[i]]; - if (rcpt[ReceiptType.Read]?.[userId]) return rcpt; - if (rcpt[ReceiptType.ReadPrivate]?.[userId]) return rcpt; - } - - return null; -} diff --git a/src/utils/drawable.ts b/src/utils/drawable.ts deleted file mode 100644 index 5c95fb3889c..00000000000 --- a/src/utils/drawable.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright 2021 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/** - * Fetch an image using the best available method based on browser compatibility - * @param url the URL of the image to fetch - * @returns a canvas drawable object - */ -export async function getDrawable(url: string): Promise { - if ('createImageBitmap' in window) { - const response = await fetch(url); - const blob = await response.blob(); - return createImageBitmap(blob); - } else { - return new Promise((resolve, reject) => { - const img = document.createElement("img"); - img.crossOrigin = "anonymous"; - img.onload = () => resolve(img); - img.onerror = (e) => reject(e); - img.src = url; - }); - } -} diff --git a/src/utils/iterables.ts b/src/utils/iterables.ts index 99bc3aba5f2..5fb8967a346 100644 --- a/src/utils/iterables.ts +++ b/src/utils/iterables.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -import { arrayDiff, arrayUnion, arrayIntersection } from "./arrays"; - -export function iterableUnion(a: Iterable, b: Iterable): Iterable { - return arrayUnion(Array.from(a), Array.from(b)); -} +import { arrayDiff, arrayIntersection } from "./arrays"; export function iterableIntersection(a: Iterable, b: Iterable): Iterable { return arrayIntersection(Array.from(a), Array.from(b)); diff --git a/src/utils/maps.ts b/src/utils/maps.ts index 7483d678d07..2afbc16bc55 100644 --- a/src/utils/maps.ts +++ b/src/utils/maps.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { arrayDiff, arrayUnion, arrayIntersection } from "./arrays"; +import { arrayDiff, arrayIntersection } from "./arrays"; /** * Determines the keys added, changed, and removed between two Maps. @@ -33,18 +33,6 @@ export function mapDiff(a: Map, b: Map): { changed: K[], added return { changed: changes, added: keyDiff.added, removed: keyDiff.removed }; } -/** - * Gets all the key changes (added, removed, or value difference) between two Maps. - * Triple equals is used to compare values, not in-depth tree checking. - * @param a The first Map. Must be defined. - * @param b The second Map. Must be defined. - * @returns The keys which have been added, removed, or changed between the two Maps. - */ -export function mapKeyChanges(a: Map, b: Map): K[] { - const diff = mapDiff(a, b); - return arrayUnion(diff.removed, diff.added, diff.changed); -} - /** * A Map with added utility. */ diff --git a/src/utils/units.ts b/src/utils/units.ts index 03775f4c210..12e99638233 100644 --- a/src/utils/units.ts +++ b/src/utils/units.ts @@ -14,14 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -/* Simple utils for formatting style values - */ - -// converts a pixel value to rem. -export function toRem(pixelValue: number): string { - return pixelValue / 10 + "rem"; -} - export function toPx(pixelValue: number): string { return pixelValue + "px"; } diff --git a/test/components/views/location/LocationButton-test.tsx b/test/components/views/location/LocationButton-test.tsx deleted file mode 100644 index 2a247d472d9..00000000000 --- a/test/components/views/location/LocationButton-test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright 2021 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { textForLocation } from "../../../../src/components/views/location/shareLocation"; - -describe("LocationButton", () => { - describe("textForLocation", () => { - it("with no description, simply dumps URI and date", () => { - expect(textForLocation("geo:43.2,54.6", 12345, null)).toBe( - "Location geo:43.2,54.6 at 1970-01-01T00:00:12.345Z"); - }); - - it("with a description, includes that in the text", () => { - expect(textForLocation("geo:12,43,3;u=2", 54321, "Me!")).toBe( - 'Location "Me!" geo:12,43,3;u=2 at 1970-01-01T00:00:54.321Z'); - }); - }); -}); diff --git a/test/test-utils/threads.ts b/test/test-utils/threads.ts index 8c389d41e18..3b2c6352569 100644 --- a/test/test-utils/threads.ts +++ b/test/test-utils/threads.ts @@ -14,8 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; -import { Thread } from "matrix-js-sdk/src/models/thread"; +import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; import { mkMessage, MessageEventProps } from "./test-utils"; @@ -89,8 +88,3 @@ export const makeThreadEvents = ({ return { rootEvent, events }; }; - -export const makeThread = (client: MatrixClient, room: Room, props: MakeThreadEventsProps): Thread => { - const { rootEvent, events } = makeThreadEvents(props); - return new Thread(rootEvent.getId(), rootEvent, { initialEvents: events, room, client }); -}; diff --git a/test/utils/iterables-test.ts b/test/utils/iterables-test.ts index 4f2664809e5..fe0bd61149f 100644 --- a/test/utils/iterables-test.ts +++ b/test/utils/iterables-test.ts @@ -14,20 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { iterableDiff, iterableUnion, iterableIntersection } from "../../src/utils/iterables"; +import { iterableDiff, iterableIntersection } from "../../src/utils/iterables"; describe('iterables', () => { - describe('iterableUnion', () => { - it('should return the union array', () => { - const a = [1, 2, 3]; - const b = [1, 2, 4]; // note diff - const result = iterableUnion(a, b); - expect(result).toBeDefined(); - expect(result).toHaveLength(4); - expect(result).toEqual([1, 2, 3, 4]); - }); - }); - describe('iterableIntersection', () => { it('should return the intersection', () => { const a = [1, 2, 3]; diff --git a/test/utils/maps-test.ts b/test/utils/maps-test.ts index 097f3ef9c98..aea444b2ecd 100644 --- a/test/utils/maps-test.ts +++ b/test/utils/maps-test.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { EnhancedMap, mapDiff, mapKeyChanges } from "../../src/utils/maps"; +import { EnhancedMap, mapDiff } from "../../src/utils/maps"; describe('maps', () => { describe('mapDiff', () => { @@ -116,68 +116,6 @@ describe('maps', () => { }); }); - describe('mapKeyChanges', () => { - it('should indicate no changes for unchanged pointers', () => { - const a = new Map([[1, 1], [2, 2], [3, 3]]); - const result = mapKeyChanges(a, a); - expect(result).toBeDefined(); - expect(result).toHaveLength(0); - }); - - it('should indicate no changes for unchanged maps with different pointers', () => { - const a = new Map([[1, 1], [2, 2], [3, 3]]); - const b = new Map([[1, 1], [2, 2], [3, 3]]); - const result = mapKeyChanges(a, b); - expect(result).toBeDefined(); - expect(result).toHaveLength(0); - }); - - it('should indicate changes for added properties', () => { - const a = new Map([[1, 1], [2, 2], [3, 3]]); - const b = new Map([[1, 1], [2, 2], [3, 3], [4, 4]]); - const result = mapKeyChanges(a, b); - expect(result).toBeDefined(); - expect(result).toHaveLength(1); - expect(result).toEqual([4]); - }); - - it('should indicate changes for removed properties', () => { - const a = new Map([[1, 1], [2, 2], [3, 3], [4, 4]]); - const b = new Map([[1, 1], [2, 2], [3, 3]]); - const result = mapKeyChanges(a, b); - expect(result).toBeDefined(); - expect(result).toHaveLength(1); - expect(result).toEqual([4]); - }); - - it('should indicate changes for changed properties', () => { - const a = new Map([[1, 1], [2, 2], [3, 3], [4, 4]]); - const b = new Map([[1, 1], [2, 2], [3, 3], [4, 55]]); - const result = mapKeyChanges(a, b); - expect(result).toBeDefined(); - expect(result).toHaveLength(1); - expect(result).toEqual([4]); - }); - - it('should indicate changes for properties with different pointers', () => { - const a = new Map([[1, {}]]); // {} always creates a new object - const b = new Map([[1, {}]]); - const result = mapKeyChanges(a, b); - expect(result).toBeDefined(); - expect(result).toHaveLength(1); - expect(result).toEqual([1]); - }); - - it('should indicate changes for changed, added, and removed properties', () => { - const a = new Map([[1, 1], [2, 2], [3, 3]]); - const b = new Map([[1, 1], [2, 8], [4, 4]]); // note change - const result = mapKeyChanges(a, b); - expect(result).toBeDefined(); - expect(result).toHaveLength(3); - expect(result).toEqual([3, 4, 2]); // order irrelevant, but the test cares - }); - }); - describe('EnhancedMap', () => { // Most of these tests will make sure it implements the Map class From 8c67984f50f985aa481df24778078030efa39001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 11 Jul 2022 09:56:20 +0200 Subject: [PATCH 067/162] Remove `mock` from `KeyboardShortcuts.ts` (#9034) --- src/accessibility/KeyboardShortcuts.ts | 11 +-- .../KeyboardShortcutUtils-test.ts | 91 +++++++++++-------- 2 files changed, 55 insertions(+), 47 deletions(-) diff --git a/src/accessibility/KeyboardShortcuts.ts b/src/accessibility/KeyboardShortcuts.ts index 022706f2961..1a7f45404cf 100644 --- a/src/accessibility/KeyboardShortcuts.ts +++ b/src/accessibility/KeyboardShortcuts.ts @@ -1,5 +1,6 @@ /* Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2022 The Matrix.org Foundation C.I.C. Copyright 2021 - 2022 Ĺ imon Brandner Licensed under the Apache License, Version 2.0 (the "License"); @@ -712,13 +713,3 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = { }, }, }; - -// For tests -export function mock({ keyboardShortcuts, macOnlyShortcuts, desktopShortcuts }): void { - Object.keys(KEYBOARD_SHORTCUTS).forEach((k) => delete KEYBOARD_SHORTCUTS[k]); - if (keyboardShortcuts) Object.assign(KEYBOARD_SHORTCUTS, keyboardShortcuts); - MAC_ONLY_SHORTCUTS.splice(0, MAC_ONLY_SHORTCUTS.length); - if (macOnlyShortcuts) macOnlyShortcuts.forEach((e) => MAC_ONLY_SHORTCUTS.push(e)); - DESKTOP_SHORTCUTS.splice(0, DESKTOP_SHORTCUTS.length); - if (desktopShortcuts) desktopShortcuts.forEach((e) => DESKTOP_SHORTCUTS.push(e)); -} diff --git a/test/accessibility/KeyboardShortcutUtils-test.ts b/test/accessibility/KeyboardShortcutUtils-test.ts index 39f5bc3910c..497067caece 100644 --- a/test/accessibility/KeyboardShortcutUtils-test.ts +++ b/test/accessibility/KeyboardShortcutUtils-test.ts @@ -1,5 +1,6 @@ /* Copyright 2022 Ĺ imon Brandner +Copyright 2022 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,60 +15,76 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { - KEYBOARD_SHORTCUTS, - mock, -} from "../../src/accessibility/KeyboardShortcuts"; -import { getKeyboardShortcuts, getKeyboardShortcutsForUI } from "../../src/accessibility/KeyboardShortcutUtils"; import { mockPlatformPeg, unmockPlatformPeg } from "../test-utils"; +const PATH_TO_KEYBOARD_SHORTCUTS = "../../src/accessibility/KeyboardShortcuts"; +const PATH_TO_KEYBOARD_SHORTCUT_UTILS = "../../src/accessibility/KeyboardShortcutUtils"; + +const mockKeyboardShortcuts = (override) => { + jest.doMock(PATH_TO_KEYBOARD_SHORTCUTS, () => { + const original = jest.requireActual(PATH_TO_KEYBOARD_SHORTCUTS); + return { + ...original, + ...override, + }; + }); +}; +const getFile = async () => await import(PATH_TO_KEYBOARD_SHORTCUTS); +const getUtils = async () => await import(PATH_TO_KEYBOARD_SHORTCUT_UTILS); + describe("KeyboardShortcutUtils", () => { - afterEach(() => { + beforeEach(() => { unmockPlatformPeg(); + jest.resetModules(); }); it("doesn't change KEYBOARD_SHORTCUTS when getting shortcuts", async () => { - mock({ - keyboardShortcuts: { + mockKeyboardShortcuts({ + KEYBOARD_SHORTCUTS: { "Keybind1": {}, "Keybind2": {}, }, - macOnlyShortcuts: ["Keybind1"], - desktopShortcuts: ["Keybind2"], + MAC_ONLY_SHORTCUTS: ["Keybind1"], + DESKTOP_SHORTCUTS: ["Keybind2"], }); mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) }); - const copyKeyboardShortcuts = Object.assign({}, KEYBOARD_SHORTCUTS); + const utils = await getUtils(); + const file = await getFile(); + const copyKeyboardShortcuts = Object.assign({}, file.KEYBOARD_SHORTCUTS); - getKeyboardShortcuts(); - expect(KEYBOARD_SHORTCUTS).toEqual(copyKeyboardShortcuts); - getKeyboardShortcutsForUI(); - expect(KEYBOARD_SHORTCUTS).toEqual(copyKeyboardShortcuts); + utils.getKeyboardShortcuts(); + expect(file.KEYBOARD_SHORTCUTS).toEqual(copyKeyboardShortcuts); + utils.getKeyboardShortcutsForUI(); + expect(file.KEYBOARD_SHORTCUTS).toEqual(copyKeyboardShortcuts); }); - it("correctly filters shortcuts", async () => { - mock({ - keyboardShortcuts: { - "Keybind1": {}, - "Keybind2": {}, - "Keybind3": { "controller": { settingDisabled: true } }, - "Keybind4": {}, - }, - macOnlyShortcuts: ["Keybind1"], - desktopShortcuts: ["Keybind2"], - + describe("correctly filters shortcuts", () => { + it("when on web and not on macOS ", async () => { + mockKeyboardShortcuts({ + KEYBOARD_SHORTCUTS: { + "Keybind1": {}, + "Keybind2": {}, + "Keybind3": { "controller": { settingDisabled: true } }, + "Keybind4": {}, + }, + MAC_ONLY_SHORTCUTS: ["Keybind1"], + DESKTOP_SHORTCUTS: ["Keybind2"], + }); + mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) }); + expect((await getUtils()).getKeyboardShortcuts()).toEqual({ "Keybind4": {} }); }); - mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) }); - expect(getKeyboardShortcuts()).toEqual({ "Keybind4": {} }); - mock({ - keyboardShortcuts: { - "Keybind1": {}, - "Keybind2": {}, - }, - macOnlyShortcuts: undefined, - desktopShortcuts: ["Keybind2"], + it("when on desktop", async () => { + mockKeyboardShortcuts({ + KEYBOARD_SHORTCUTS: { + "Keybind1": {}, + "Keybind2": {}, + }, + MAC_ONLY_SHORTCUTS: [], + DESKTOP_SHORTCUTS: ["Keybind2"], + }); + mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(true) }); + expect((await getUtils()).getKeyboardShortcuts()).toEqual({ "Keybind1": {}, "Keybind2": {} }); }); - mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(true) }); - expect(getKeyboardShortcuts()).toEqual({ "Keybind1": {}, "Keybind2": {} }); }); }); From 32c008b3f06be10e2fad5c18f692c682272d7ab1 Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski Date: Mon, 11 Jul 2022 12:00:40 +0200 Subject: [PATCH 068/162] Add additional metadata to feedback submitted through spotlight dialog (#9024) --- src/components/views/dialogs/FeedbackDialog.tsx | 7 +++++-- src/components/views/dialogs/spotlight/SpotlightDialog.tsx | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/views/dialogs/FeedbackDialog.tsx b/src/components/views/dialogs/FeedbackDialog.tsx index b46d10fa94e..1f5a1a97a3c 100644 --- a/src/components/views/dialogs/FeedbackDialog.tsx +++ b/src/components/views/dialogs/FeedbackDialog.tsx @@ -33,7 +33,9 @@ const existingIssuesUrl = "https://github.com/vector-im/element-web/issues" + "?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc"; const newIssueUrl = "https://github.com/vector-im/element-web/issues/new/choose"; -interface IProps extends IDialogProps {} +interface IProps extends IDialogProps { + feature?: string; +} const FeedbackDialog: React.FC = (props: IProps) => { const feedbackRef = useRef(); @@ -55,7 +57,8 @@ const FeedbackDialog: React.FC = (props: IProps) => { const onFinished = (sendFeedback: boolean): void => { if (hasFeedback && sendFeedback) { if (rageshakeUrl) { - submitFeedback(rageshakeUrl, "feedback", comment, canContact); + const label = props.feature ? `${props.feature}-feedback` : "feedback"; + submitFeedback(rageshakeUrl, label, comment, canContact); } Modal.createDialog(InfoDialog, { diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index 6013b5721f3..341244dad02 100644 --- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx +++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx @@ -1004,7 +1004,9 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n }; const openFeedback = SdkConfig.get().bug_report_endpoint_url ? () => { - Modal.createDialog(FeedbackDialog); + Modal.createDialog(FeedbackDialog, { + feature: "spotlight", + }); } : null; const activeDescendant = rovingContext.state.activeRef?.current?.id; From 375ff265dbaf818484aab3f4645dc0293faa4a0f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 11 Jul 2022 12:23:06 +0100 Subject: [PATCH 069/162] Update cypress.md (#9039) --- docs/cypress.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cypress.md b/docs/cypress.md index 1f3882a7e79..be5c63d97c0 100644 --- a/docs/cypress.md +++ b/docs/cypress.md @@ -32,7 +32,7 @@ This will run the Cypress tests once, non-interactively. You can also run individual tests this way too, as you'd expect: ``` -yarn run test:cypress --spec cypress/integration/1-register/register.spec.ts +yarn run test:cypress --spec cypress/e2e/1-register/register.spec.ts ``` Cypress also has its own UI that you can use to run and debug the tests. @@ -44,7 +44,7 @@ yarn run test:cypress:open ## How the Tests Work Everything Cypress-related lives in the `cypress/` subdirectory of react-sdk -as is typical for Cypress tests. Likewise, tests live in `cypress/integration`. +as is typical for Cypress tests. Likewise, tests live in `cypress/e2e`. `cypress/plugins/synapsedocker` contains a Cypress plugin that starts instances of Synapse in Docker containers. These synapses are what Element-web runs against From a9d689650218ae11c00eef1000bbaedf14cc5002 Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski Date: Mon, 11 Jul 2022 13:34:23 +0200 Subject: [PATCH 070/162] Correct accessibility labels for unread rooms in spotlight (#9003) * Correct accessibility labels for unread rooms in spotlight * Improve public room result accessibility * Improve room result accessibility --- .../views/dialogs/ForwardDialog.tsx | 8 +- .../spotlight/PublicRoomResultDetails.tsx | 15 +- .../dialogs/spotlight/SpotlightDialog.tsx | 194 ++++++++++++++---- .../views/rooms/RecentlyViewedButton.tsx | 8 +- .../RoomContextDetails.tsx} | 24 ++- src/i18n/strings/en_EN.json | 4 + src/utils/i18n-helpers.ts | 51 ++--- 7 files changed, 206 insertions(+), 98 deletions(-) rename src/components/views/{dialogs/spotlight/RoomResultDetails.tsx => rooms/RoomContextDetails.tsx} (53%) diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 2d3de09eecf..8e23e3bd950 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -49,9 +49,9 @@ import BaseAvatar from "../avatars/BaseAvatar"; import { Action } from "../../../dispatcher/actions"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { ButtonEvent } from "../elements/AccessibleButton"; -import { roomContextDetailsText } from "../../../utils/i18n-helpers"; import { isLocationEvent } from "../../../utils/EventUtils"; import { isSelfLocation, locationEventGeoUri } from "../../../utils/location"; +import { RoomContextDetails } from "../rooms/RoomContextDetails"; const AVATAR_SIZE = 30; @@ -130,8 +130,6 @@ const Entry: React.FC = ({ room, type, content, matrixClient: cli, />; } - const detailsText = roomContextDetailsText(room); - return
= ({ room, type, content, matrixClient: cli, > { room.name } - { detailsText && - { detailsText } - } + MAX_NAME_LENGTH) { name = `${name.substring(0, MAX_NAME_LENGTH)}...`; @@ -41,12 +48,12 @@ export function PublicRoomResultDetails({ room }: { room: IPublicRoomsChunkRoom return (
- { name } - + { name } + { room.canonical_alias ?? room.room_id }
-
+
{ _t("%(count)s Members", { count: room.num_joined_members, diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index 341244dad02..f14d29d8cad 100644 --- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx +++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx @@ -14,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { WebSearch as WebSearchEvent } from "@matrix-org/analytics-events/types/typescript/WebSearch"; import classNames from "classnames"; import { capitalize, sum } from "lodash"; -import { WebSearch as WebSearchEvent } from "@matrix-org/analytics-events/types/typescript/WebSearch"; import { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces"; import { IPublicRoomsChunkRoom, MatrixClient, RoomMember, RoomType } from "matrix-js-sdk/src/matrix"; import { Room } from "matrix-js-sdk/src/models/room"; @@ -50,6 +50,7 @@ import { useDebouncedCallback } from "../../../../hooks/spotlight/useDebouncedCa import { useRecentSearches } from "../../../../hooks/spotlight/useRecentSearches"; import { useProfileInfo } from "../../../../hooks/useProfileInfo"; import { usePublicRoomDirectory } from "../../../../hooks/usePublicRoomDirectory"; +import { useFeatureEnabled } from "../../../../hooks/useSettings"; import { useSpaceResults } from "../../../../hooks/useSpaceResults"; import { useUserDirectory } from "../../../../hooks/useUserDirectory"; import { getKeyBindingsManager } from "../../../../KeyBindingsManager"; @@ -63,6 +64,7 @@ import SdkConfig from "../../../../SdkConfig"; import { SettingLevel } from "../../../../settings/SettingLevel"; import SettingsStore from "../../../../settings/SettingsStore"; import { BreadcrumbsStore } from "../../../../stores/BreadcrumbsStore"; +import { RoomNotificationState } from "../../../../stores/notifications/RoomNotificationState"; import { RoomNotificationStateStore } from "../../../../stores/notifications/RoomNotificationStateStore"; import { RecentAlgorithm } from "../../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm"; import { RoomViewStore } from "../../../../stores/RoomViewStore"; @@ -78,6 +80,7 @@ import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar"; import { SearchResultAvatar } from "../../avatars/SearchResultAvatar"; import { NetworkDropdown } from "../../directory/NetworkDropdown"; import AccessibleButton from "../../elements/AccessibleButton"; +import LabelledCheckbox from "../../elements/LabelledCheckbox"; import Spinner from "../../elements/Spinner"; import NotificationBadge from "../../rooms/NotificationBadge"; import BaseDialog from "../BaseDialog"; @@ -85,10 +88,8 @@ import FeedbackDialog from "../FeedbackDialog"; import { IDialogProps } from "../IDialogProps"; import { Option } from "./Option"; import { PublicRoomResultDetails } from "./PublicRoomResultDetails"; -import { RoomResultDetails } from "./RoomResultDetails"; +import { RoomContextDetails } from "../../rooms/RoomContextDetails"; import { TooltipOption } from "./TooltipOption"; -import LabelledCheckbox from "../../elements/LabelledCheckbox"; -import { useFeatureEnabled } from "../../../../hooks/useSettings"; const MAX_RECENT_SEARCHES = 10; const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons @@ -259,6 +260,22 @@ const findVisibleRoomMembers = (cli: MatrixClient, filterDMs = true) => { ).filter(it => it.userId !== cli.getUserId()); }; +const roomAriaUnreadLabel = (room: Room, notification: RoomNotificationState): string | undefined => { + if (notification.hasMentions) { + return _t("%(count)s unread messages including mentions.", { + count: notification.count, + }); + } else if (notification.hasUnreadCount) { + return _t("%(count)s unread messages.", { + count: notification.count, + }); + } else if (notification.isUnread) { + return _t("Unread messages."); + } else { + return undefined; + } +}; + interface IDirectoryOpts { limit: number; query: string; @@ -523,6 +540,12 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n if (trimmedQuery || filter !== null) { const resultMapper = (result: Result): JSX.Element => { if (isRoomResult(result)) { + const notification = RoomNotificationStateStore.instance.getRoomState(result.room); + const unreadLabel = roomAriaUnreadLabel(result.room, notification); + const ariaProperties = { + "aria-label": unreadLabel ? `${result.room.name} ${unreadLabel}` : result.room.name, + "aria-details": `mx_SpotlightDialog_button_result_${result.room.roomId}_details`, + }; return ( ); } @@ -547,10 +579,17 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n startDm(cli, [result.member]); onFinished(); }} + aria-label={result.member instanceof RoomMember + ? result.member.rawDisplayName + : result.member.name} + aria-describedby={`mx_SpotlightDialog_button_result_${result.member.userId}_details`} > { result.member instanceof RoomMember ? result.member.rawDisplayName : result.member.name } -
+
{ result.member.userId }
@@ -575,6 +614,9 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n > { _t(clientRoom ? "View" : "Join") } } + aria-labelledby={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_name`} + aria-describedby={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_alias`} + aria-details={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_details`} > = ({ initialText = "", initialFilter = n width={AVATAR_SIZE} height={AVATAR_SIZE} /> - + ); } @@ -608,8 +655,13 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n let peopleSection: JSX.Element; if (results[Section.People].length) { peopleSection = ( -
-

{ _t("Recent Conversations") }

+
+

+ { _t("Recent Conversations") } +

{ results[Section.People].slice(0, SECTION_LIMIT).map(resultMapper) }
@@ -620,8 +672,13 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n let suggestionsSection: JSX.Element; if (results[Section.Suggestions].length && filter === Filter.People) { suggestionsSection = ( -
-

{ _t("Suggestions") }

+
+

+ { _t("Suggestions") } +

{ results[Section.Suggestions].slice(0, SECTION_LIMIT).map(resultMapper) }
@@ -632,8 +689,13 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n let roomsSection: JSX.Element; if (results[Section.Rooms].length) { roomsSection = ( -
-

{ _t("Rooms") }

+
+

+ { _t("Rooms") } +

{ results[Section.Rooms].slice(0, SECTION_LIMIT).map(resultMapper) }
@@ -644,8 +706,13 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n let spacesSection: JSX.Element; if (results[Section.Spaces].length) { spacesSection = ( -
-

{ _t("Spaces you're in") }

+
+

+ { _t("Spaces you're in") } +

{ results[Section.Spaces].slice(0, SECTION_LIMIT).map(resultMapper) }
@@ -656,9 +723,14 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n let publicRoomsSection: JSX.Element; if (filter === Filter.PublicRooms) { publicRoomsSection = ( -
+
-

{ _t("Suggestions") }

+

+ { _t("Suggestions") } +

{ exploringPublicSpacesEnabled && <> = ({ initialText = "", initialFilter = n let spaceRoomsSection: JSX.Element; if (spaceResults.length && activeSpace && filter === null) { spaceRoomsSection = ( -
-

{ _t("Other rooms in %(spaceName)s", { spaceName: activeSpace.name }) }

+
+

+ { _t("Other rooms in %(spaceName)s", { spaceName: activeSpace.name }) } +

{ spaceResults.slice(0, SECTION_LIMIT).map((room: IHierarchyRoom): JSX.Element => (