diff --git a/.github/workflows/cypress.yaml b/.github/workflows/cypress.yaml index 57e6a7837e..cbb5347173 100644 --- a/.github/workflows/cypress.yaml +++ b/.github/workflows/cypress.yaml @@ -79,8 +79,8 @@ jobs: strategy: fail-fast: false matrix: - # Run 3 instances in Parallel - runner: [1, 2, 3] + # Run 4 instances in Parallel + runner: [1, 2, 3, 4] steps: - uses: actions/checkout@v2 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index edb088cd64..2765dbe450 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,53 @@ +Changes in [3.59.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.59.0) (2022-10-25) +===================================================================================================== + +## ✨ Features + * Include a file-safe room name and ISO date in chat exports ([\#9440](https://github.com/matrix-org/matrix-react-sdk/pull/9440)). Fixes vector-im/element-web#21812 and vector-im/element-web#19724. + * Room call banner ([\#9378](https://github.com/matrix-org/matrix-react-sdk/pull/9378)). Fixes vector-im/element-web#23453. Contributed by @toger5. + * Device manager - spinners while devices are signing out ([\#9433](https://github.com/matrix-org/matrix-react-sdk/pull/9433)). Fixes vector-im/element-web#15865. + * Device manager - silence call ringers when local notifications are silenced ([\#9420](https://github.com/matrix-org/matrix-react-sdk/pull/9420)). + * Pass the current language to Element Call ([\#9427](https://github.com/matrix-org/matrix-react-sdk/pull/9427)). + * Hide screen-sharing button in Element Call on desktop ([\#9423](https://github.com/matrix-org/matrix-react-sdk/pull/9423)). + * Add reply support to WysiwygComposer ([\#9422](https://github.com/matrix-org/matrix-react-sdk/pull/9422)). Contributed by @florianduros. + * Disconnect other connected devices (of the same user) when joining an Element call ([\#9379](https://github.com/matrix-org/matrix-react-sdk/pull/9379)). + * Device manager - device tile main click target ([\#9409](https://github.com/matrix-org/matrix-react-sdk/pull/9409)). + * Add formatting buttons to the rich text editor ([\#9410](https://github.com/matrix-org/matrix-react-sdk/pull/9410)). Contributed by @florianduros. + * Device manager - current session context menu ([\#9386](https://github.com/matrix-org/matrix-react-sdk/pull/9386)). + * Remove piwik config fallback for privacy policy URL ([\#9390](https://github.com/matrix-org/matrix-react-sdk/pull/9390)). + * Add the first step to integrate the matrix wysiwyg composer ([\#9374](https://github.com/matrix-org/matrix-react-sdk/pull/9374)). Contributed by @florianduros. + * Device manager - UA parsing tweaks ([\#9382](https://github.com/matrix-org/matrix-react-sdk/pull/9382)). + * Device manager - remove client information events when disabling setting ([\#9384](https://github.com/matrix-org/matrix-react-sdk/pull/9384)). + * Add Element Call participant limit ([\#9358](https://github.com/matrix-org/matrix-react-sdk/pull/9358)). + * Add Element Call room settings ([\#9347](https://github.com/matrix-org/matrix-react-sdk/pull/9347)). + * Device manager - render extended device information ([\#9360](https://github.com/matrix-org/matrix-react-sdk/pull/9360)). + * New group call experience: Room header and PiP designs ([\#9351](https://github.com/matrix-org/matrix-react-sdk/pull/9351)). + * Pass language to Jitsi Widget ([\#9346](https://github.com/matrix-org/matrix-react-sdk/pull/9346)). Contributed by @Fox32. + * Add notifications and toasts for Element Call calls ([\#9337](https://github.com/matrix-org/matrix-react-sdk/pull/9337)). + * Device manager - device type icon ([\#9355](https://github.com/matrix-org/matrix-react-sdk/pull/9355)). + * Delete the remainder of groups ([\#9357](https://github.com/matrix-org/matrix-react-sdk/pull/9357)). Fixes vector-im/element-web#22770. + * Device manager - display client information in device details ([\#9315](https://github.com/matrix-org/matrix-react-sdk/pull/9315)). + +## 🐛 Bug Fixes + * Send Content-Type: application/json header for integration manager /register API ([\#9490](https://github.com/matrix-org/matrix-react-sdk/pull/9490)). Fixes vector-im/element-web#23580. + * Device manager - put client/browser device metadata in correct section ([\#9447](https://github.com/matrix-org/matrix-react-sdk/pull/9447)). + * update the room unread notification counter when the server changes the value without any related read receipt ([\#9438](https://github.com/matrix-org/matrix-react-sdk/pull/9438)). + * Don't show call banners in video rooms ([\#9441](https://github.com/matrix-org/matrix-react-sdk/pull/9441)). + * Prevent useContextMenu isOpen from being true if the button ref goes away ([\#9418](https://github.com/matrix-org/matrix-react-sdk/pull/9418)). Fixes matrix-org/element-web-rageshakes#15637. + * Automatically focus the WYSIWYG composer when you enter a room ([\#9412](https://github.com/matrix-org/matrix-react-sdk/pull/9412)). + * Improve the tooltips on the call lobby join button ([\#9428](https://github.com/matrix-org/matrix-react-sdk/pull/9428)). + * Pass the homeserver's base URL to Element Call ([\#9429](https://github.com/matrix-org/matrix-react-sdk/pull/9429)). Fixes vector-im/element-web#23301. + * Better accommodate long room names in call toasts ([\#9426](https://github.com/matrix-org/matrix-react-sdk/pull/9426)). + * Hide virtual widgets from the room info panel ([\#9424](https://github.com/matrix-org/matrix-react-sdk/pull/9424)). Fixes vector-im/element-web#23494. + * Inhibit clicking on sender avatar in threads list ([\#9417](https://github.com/matrix-org/matrix-react-sdk/pull/9417)). Fixes vector-im/element-web#23482. + * Correct the dir parameter of MSC3715 ([\#9391](https://github.com/matrix-org/matrix-react-sdk/pull/9391)). Contributed by @dhenneke. + * Use a more correct subset of users in `/remakeolm` developer command ([\#9402](https://github.com/matrix-org/matrix-react-sdk/pull/9402)). + * use correct default for notification silencing ([\#9388](https://github.com/matrix-org/matrix-react-sdk/pull/9388)). Fixes vector-im/element-web#23456. + * Device manager - eagerly create `m.local_notification_settings` events ([\#9353](https://github.com/matrix-org/matrix-react-sdk/pull/9353)). + * Close incoming Element call toast when viewing the call lobby ([\#9375](https://github.com/matrix-org/matrix-react-sdk/pull/9375)). + * Always allow enabling sending read receipts ([\#9367](https://github.com/matrix-org/matrix-react-sdk/pull/9367)). Fixes vector-im/element-web#23433. + * Fixes (vector-im/element-web/issues/22609) where the white theme is not applied when `white -> dark -> white` sequence is done. ([\#9320](https://github.com/matrix-org/matrix-react-sdk/pull/9320)). Contributed by @florianduros. + * Fix applying programmatically set height for "top" room layout ([\#9339](https://github.com/matrix-org/matrix-react-sdk/pull/9339)). Contributed by @Fox32. + Changes in [3.58.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.58.1) (2022-10-11) ===================================================================================================== diff --git a/cypress/e2e/composer/composer.spec.ts b/cypress/e2e/composer/composer.spec.ts index f3fc374cf0..6fe562e12a 100644 --- a/cypress/e2e/composer/composer.spec.ts +++ b/cypress/e2e/composer/composer.spec.ts @@ -64,6 +64,21 @@ describe("Composer", () => { cy.contains('.mx_EventTile_body strong', 'bold message'); }); + it("should allow user to input emoji via graphical picker", () => { + cy.getComposer(false).within(() => { + cy.get('[aria-label="Emoji"]').click(); + }); + + cy.get('[data-testid="mx_EmojiPicker"]').within(() => { + cy.contains(".mx_EmojiPicker_item", "😇").click(); + }); + + cy.get(".mx_ContextualMenu_background").click(); // Close emoji picker + cy.get('div[contenteditable=true]').type("{enter}"); // Send message + + cy.contains(".mx_EventTile_body", "😇"); + }); + describe("when Ctrl+Enter is required to send", () => { beforeEach(() => { cy.setSettingValue("MessageComposerInput.ctrlEnterToSend", null, SettingLevel.ACCOUNT, true); diff --git a/cypress/e2e/settings/device-management.spec.ts b/cypress/e2e/settings/device-management.spec.ts index 1709475e17..da4d12e35d 100644 --- a/cypress/e2e/settings/device-management.spec.ts +++ b/cypress/e2e/settings/device-management.spec.ts @@ -78,6 +78,7 @@ describe("Device manager", () => { cy.get('.mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem .mx_Checkbox').last().click(); // sign out from list selection action buttons cy.get('[data-testid="sign-out-selection-cta"]').click(); + cy.get('[data-testid="dialog-primary-button"]').click(); // list updated after sign out cy.get('.mx_FilteredDeviceList_list').find('.mx_FilteredDeviceList_listItem').should('have.length', 1); // security recommendation count updated @@ -106,6 +107,8 @@ describe("Device manager", () => { // sign out using the device details sign out cy.get('[data-testid="device-detail-sign-out-cta"]').click(); }); + // confirm the signout + cy.get('[data-testid="dialog-primary-button"]').click(); // no other sessions or security recommendations sections when only one session cy.contains('Other sessions').should('not.exist'); diff --git a/cypress/plugins/sliding-sync/index.ts b/cypress/plugins/sliding-sync/index.ts index 61a62aad13..608ada8dbf 100644 --- a/cypress/plugins/sliding-sync/index.ts +++ b/cypress/plugins/sliding-sync/index.ts @@ -77,7 +77,7 @@ async function proxyStart(synapse: SynapseInstance): Promise { const port = await getFreePort(); console.log(new Date(), "starting proxy container..."); const containerId = await dockerRun({ - image: "ghcr.io/matrix-org/sliding-sync-proxy:v0.4.0", + image: "ghcr.io/matrix-org/sliding-sync-proxy:v0.6.0", containerName: "react-sdk-cypress-sliding-sync-proxy", params: [ "--rm", diff --git a/package.json b/package.json index f0ab2c266b..9d7d7baa65 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.58.1", + "version": "3.59.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -57,7 +57,7 @@ "dependencies": { "@babel/runtime": "^7.12.5", "@matrix-org/analytics-events": "^0.2.0", - "@matrix-org/matrix-wysiwyg": "^0.3.0", + "@matrix-org/matrix-wysiwyg": "^0.3.2", "@matrix-org/react-sdk-module-api": "^0.0.3", "@sentry/browser": "^6.11.0", "@sentry/tracing": "^6.11.0", diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 819afe64a4..cc7c6a2e2a 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -4,7 +4,6 @@ @import "./_font-sizes.pcss"; @import "./_font-weights.pcss"; @import "./_spacing.pcss"; -@import "./compound/_Icon.pcss"; @import "./components/views/beacon/_BeaconListItem.pcss"; @import "./components/views/beacon/_BeaconStatus.pcss"; @import "./components/views/beacon/_BeaconStatusTooltip.pcss"; @@ -19,6 +18,7 @@ @import "./components/views/beacon/_StyledLiveBeaconIcon.pcss"; @import "./components/views/context_menus/_KebabContextMenu.pcss"; @import "./components/views/elements/_FilterDropdown.pcss"; +@import "./components/views/elements/_LearnMore.pcss"; @import "./components/views/location/_EnableLiveShare.pcss"; @import "./components/views/location/_LiveDurationDropdown.pcss"; @import "./components/views/location/_LocationShareMenu.pcss"; @@ -44,6 +44,7 @@ @import "./components/views/settings/shared/_SettingsSubsectionHeading.pcss"; @import "./components/views/spaces/_QuickThemeSwitcher.pcss"; @import "./components/views/typography/_Caption.pcss"; +@import "./compound/_Icon.pcss"; @import "./structures/_AutoHideScrollbar.pcss"; @import "./structures/_BackdropPanel.pcss"; @import "./structures/_CompatibilityPage.pcss"; @@ -299,8 +300,10 @@ @import "./views/rooms/_TopUnreadMessagesBar.pcss"; @import "./views/rooms/_VoiceRecordComposerTile.pcss"; @import "./views/rooms/_WhoIsTypingTile.pcss"; -@import "./views/rooms/wysiwyg_composer/_FormattingButtons.pcss"; -@import "./views/rooms/wysiwyg_composer/_WysiwygComposer.pcss"; +@import "./views/rooms/wysiwyg_composer/_EditWysiwygComposer.pcss"; +@import "./views/rooms/wysiwyg_composer/_SendWysiwygComposer.pcss"; +@import "./views/rooms/wysiwyg_composer/components/_Editor.pcss"; +@import "./views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss"; @import "./views/settings/_AvatarSetting.pcss"; @import "./views/settings/_CrossSigningPanel.pcss"; @import "./views/settings/_CryptographyPanel.pcss"; @@ -371,6 +374,4 @@ @import "./voice-broadcast/atoms/_PlaybackControlButton.pcss"; @import "./voice-broadcast/atoms/_VoiceBroadcastControl.pcss"; @import "./voice-broadcast/atoms/_VoiceBroadcastHeader.pcss"; -@import "./voice-broadcast/molecules/_VoiceBroadcastPlaybackBody.pcss"; -@import "./voice-broadcast/molecules/_VoiceBroadcastRecordingBody.pcss"; -@import "./voice-broadcast/molecules/_VoiceBroadcastRecordingPip.pcss"; +@import "./voice-broadcast/molecules/_VoiceBroadcastBody.pcss"; diff --git a/res/css/voice-broadcast/molecules/_VoiceBroadcastPlaybackBody.pcss b/res/css/components/views/elements/_LearnMore.pcss similarity index 71% rename from res/css/voice-broadcast/molecules/_VoiceBroadcastPlaybackBody.pcss rename to res/css/components/views/elements/_LearnMore.pcss index 11921e1f95..97f3b4c527 100644 --- a/res/css/voice-broadcast/molecules/_VoiceBroadcastPlaybackBody.pcss +++ b/res/css/components/views/elements/_LearnMore.pcss @@ -14,14 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_VoiceBroadcastPlaybackBody { - background-color: $quinary-content; - border-radius: 8px; - display: inline-block; - padding: 12px; -} - -.mx_VoiceBroadcastPlaybackBody_controls { - display: flex; - justify-content: center; +.mx_LearnMore_button { + margin-left: $spacing-4; } diff --git a/res/css/views/dialogs/_SpotlightDialog.pcss b/res/css/views/dialogs/_SpotlightDialog.pcss index cf6917a475..8602ca9a43 100644 --- a/res/css/views/dialogs/_SpotlightDialog.pcss +++ b/res/css/views/dialogs/_SpotlightDialog.pcss @@ -247,7 +247,7 @@ limitations under the License. } &.mx_SpotlightDialog_result_multiline { - align-items: start; + align-items: flex-start; .mx_AccessibleButton { padding: $spacing-4 $spacing-20; diff --git a/res/css/views/elements/_UseCaseSelection.pcss b/res/css/views/elements/_UseCaseSelection.pcss index 3daf15772f..2b907e7b67 100644 --- a/res/css/views/elements/_UseCaseSelection.pcss +++ b/res/css/views/elements/_UseCaseSelection.pcss @@ -65,7 +65,7 @@ limitations under the License. .mx_UseCaseSelection_skip { display: flex; flex-direction: column; - align-self: start; + align-self: flex-start; } } diff --git a/res/css/views/rooms/_EventTile.pcss b/res/css/views/rooms/_EventTile.pcss index 35cd87b136..55702c787b 100644 --- a/res/css/views/rooms/_EventTile.pcss +++ b/res/css/views/rooms/_EventTile.pcss @@ -426,7 +426,7 @@ $left-gutter: 64px; } &.mx_EventTile_selected .mx_EventTile_line { - // TODO: check if this would be necessary + /* TODO: check if this would be necessary; */ padding-inline-start: calc(var(--EventTile_group_line-spacing-inline-start) + 20px); } } @@ -894,15 +894,22 @@ $left-gutter: 64px; } /* Display notification dot */ - &[data-notification]::before { + &[data-notification]::before, + .mx_NotificationBadge { + position: absolute; $notification-inset-block-start: 14px; /* 14px: align the dot with the timestamp row */ - width: $notification-dot-size; - height: $notification-dot-size; + /* !important to fix overly specific CSS selector applied on mx_NotificationBadge */ + width: $notification-dot-size !important; + height: $notification-dot-size !important; border-radius: 50%; inset: $notification-inset-block-start $spacing-8 auto auto; } + .mx_NotificationBadge_count { + display: none; + } + &[data-notification="total"]::before { background-color: $room-icon-unread-color; } @@ -1301,7 +1308,8 @@ $left-gutter: 64px; } } - &[data-shape="ThreadsList"][data-notification]::before { + &[data-shape="ThreadsList"][data-notification]::before, + .mx_NotificationBadge { /* stylelint-disable-next-line declaration-colon-space-after */ inset-block-start: calc($notification-inset-block-start - var(--MatrixChat_useCompactLayout_group-padding-top)); diff --git a/res/css/views/rooms/_MessageComposer.pcss b/res/css/views/rooms/_MessageComposer.pcss index 4cddf31084..4d22f60a12 100644 --- a/res/css/views/rooms/_MessageComposer.pcss +++ b/res/css/views/rooms/_MessageComposer.pcss @@ -240,7 +240,7 @@ limitations under the License. */ .mx_MessageComposer_wysiwyg { .mx_MessageComposer_e2eIcon.mx_E2EIcon,.mx_MessageComposer_button, .mx_MessageComposer_sendMessage { - margin-top: 22px; + margin-top: 28px; } } @@ -264,6 +264,14 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/composer/emoji.svg'); } +.mx_MessageComposer_plain_text::before { + mask-image: url('$(res)/img/element-icons/room/composer/plain_text.svg'); +} + +.mx_MessageComposer_rich_text::before { + mask-image: url('$(res)/img/element-icons/room/composer/rich_text.svg'); +} + .mx_MessageComposer_location::before { mask-image: url('$(res)/img/element-icons/room/composer/location.svg'); } diff --git a/res/css/views/rooms/wysiwyg_composer/_EditWysiwygComposer.pcss b/res/css/views/rooms/wysiwyg_composer/_EditWysiwygComposer.pcss new file mode 100644 index 0000000000..73e5fef6e9 --- /dev/null +++ b/res/css/views/rooms/wysiwyg_composer/_EditWysiwygComposer.pcss @@ -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. +*/ + +.mx_EditWysiwygComposer { + --EditWysiwygComposer-padding-inline: 3px; + + display: flex; + flex-direction: column; + max-width: 100%; /* disable overflow */ + width: auto; + gap: 8px; + padding: 8px var(--EditWysiwygComposer-padding-inline); + + .mx_WysiwygComposer_content { + border-radius: 4px; + border: solid 1px $primary-hairline-color; + background-color: $background; + max-height: 200px; + padding: 3px 6px; + + &:focus { + border-color: rgba($accent, 0.5); /* Only ever used here */ + } + } + + .mx_EditWysiwygComposer_buttons { + display: flex; + flex-flow: row wrap-reverse; /* display "Save" over "Cancel" */ + justify-content: flex-end; + gap: 5px; + margin-inline-start: auto; + + .mx_AccessibleButton { + flex: 1; + box-sizing: border-box; + min-width: 100px; /* magic number to align the edge of the button with the input area */ + } + } + + .mx_FormattingButtons_Button { + &:first-child { + margin-left: 0px; + } + } +} diff --git a/res/css/views/rooms/wysiwyg_composer/_WysiwygComposer.pcss b/res/css/views/rooms/wysiwyg_composer/_SendWysiwygComposer.pcss similarity index 98% rename from res/css/views/rooms/wysiwyg_composer/_WysiwygComposer.pcss rename to res/css/views/rooms/wysiwyg_composer/_SendWysiwygComposer.pcss index 133b66388e..a00f8c7e11 100644 --- a/res/css/views/rooms/wysiwyg_composer/_WysiwygComposer.pcss +++ b/res/css/views/rooms/wysiwyg_composer/_SendWysiwygComposer.pcss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_WysiwygComposer { +.mx_SendWysiwygComposer { flex: 1; display: flex; flex-direction: column; diff --git a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss new file mode 100644 index 0000000000..6a6b68af7c --- /dev/null +++ b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss @@ -0,0 +1,37 @@ +/* +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_WysiwygComposer_container { + position: relative; + + @keyframes visualbell { + from { background-color: $visual-bell-bg-color; } + to { background-color: $background; } + } + + .mx_WysiwygComposer_content { + white-space: pre-wrap; + word-wrap: break-word; + outline: none; + overflow-x: hidden; + + /* Force caret nodes to be selected in full so that they can be */ + /* navigated through in a single keypress */ + .caretNode { + user-select: all; + } + } +} diff --git a/res/css/views/rooms/wysiwyg_composer/_FormattingButtons.pcss b/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss similarity index 97% rename from res/css/views/rooms/wysiwyg_composer/_FormattingButtons.pcss rename to res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss index 36f84ae5f1..cd0ac38e0e 100644 --- a/res/css/views/rooms/wysiwyg_composer/_FormattingButtons.pcss +++ b/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss @@ -16,7 +16,7 @@ limitations under the License. .mx_FormattingButtons { display: flex; - justify-content: start; + justify-content: flex-start; .mx_FormattingButtons_Button { --size: 28px; @@ -45,7 +45,7 @@ limitations under the License. left: 6px; height: 16px; width: 16px; - background-color: $icon-button-color; + background-color: $tertiary-content; mask-repeat: no-repeat; mask-size: contain; mask-position: center; diff --git a/res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingPip.pcss b/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss similarity index 84% rename from res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingPip.pcss rename to res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss index 11534a4797..37606f993c 100644 --- a/res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingPip.pcss +++ b/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss @@ -14,22 +14,26 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_VoiceBroadcastRecordingPip { - background-color: $system; +.mx_VoiceBroadcastBody { + background-color: $quinary-content; border-radius: 8px; - box-shadow: 0 2px 8px 0 #0000004a; display: inline-block; padding: $spacing-12; } -.mx_VoiceBroadcastRecordingPip_divider { +.mx_VoiceBroadcastBody--pip { + background-color: $system; + box-shadow: 0 2px 8px 0 #0000004a; +} + +.mx_VoiceBroadcastBody_divider { background-color: $quinary-content; border: 0; height: 1px; margin: $spacing-12 0; } -.mx_VoiceBroadcastRecordingPip_controls { +.mx_VoiceBroadcastBody_controls { display: flex; justify-content: space-around; } diff --git a/res/img/element-icons/room/composer/plain_text.svg b/res/img/element-icons/room/composer/plain_text.svg new file mode 100644 index 0000000000..d2da9d2551 --- /dev/null +++ b/res/img/element-icons/room/composer/plain_text.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/res/img/element-icons/room/composer/rich_text.svg b/res/img/element-icons/room/composer/rich_text.svg new file mode 100644 index 0000000000..7ff47fe085 --- /dev/null +++ b/res/img/element-icons/room/composer/rich_text.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/Login.ts b/src/Login.ts index c36f5770b9..4dc96bc17d 100644 --- a/src/Login.ts +++ b/src/Login.ts @@ -24,13 +24,6 @@ import { ILoginParams, LoginFlow } from "matrix-js-sdk/src/@types/auth"; import { IMatrixClientCreds } from "./MatrixClientPeg"; import SecurityCustomisations from "./customisations/Security"; -export { - IdentityProviderBrand, - IIdentityProvider, - ISSOFlow, - LoginFlow, -} from "matrix-js-sdk/src/@types/auth"; - interface ILoginOptions { defaultDeviceDisplayName?: string; } diff --git a/src/Notifier.ts b/src/Notifier.ts index cc84acb2fa..b75b821ae8 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -435,7 +435,16 @@ export const Notifier = { if (actions?.notify) { this._performCustomEventHandling(ev); - if (SdkContextClass.instance.roomViewStore.getRoomId() === room.roomId && + const store = SdkContextClass.instance.roomViewStore; + const isViewingRoom = store.getRoomId() === room.roomId; + const threadId: string | undefined = ev.getId() !== ev.threadRootId + ? ev.threadRootId + : undefined; + const isViewingThread = store.getThreadId() === threadId; + + const isViewingEventTimeline = isViewingRoom && (!threadId || isViewingThread); + + if (isViewingEventTimeline && UserActivity.sharedInstance().userActiveRecently() && !Modal.hasDialogs() ) { diff --git a/src/Roles.ts b/src/Roles.ts index ae0d316d30..77c50fe64c 100644 --- a/src/Roles.ts +++ b/src/Roles.ts @@ -16,7 +16,7 @@ limitations under the License. import { _t } from './languageHandler'; -export function levelRoleMap(usersDefault: number) { +export function levelRoleMap(usersDefault: number): Record { return { undefined: _t('Default'), 0: _t('Restricted'), diff --git a/src/RoomNotifs.ts b/src/RoomNotifs.ts index 08c15970c5..6c1e07e66b 100644 --- a/src/RoomNotifs.ts +++ b/src/RoomNotifs.ts @@ -78,15 +78,23 @@ export function setRoomNotifsState(roomId: string, newState: RoomNotifState): Pr } } -export function getUnreadNotificationCount(room: Room, type: NotificationCountType = null): number { - let notificationCount = room.getUnreadNotificationCount(type); +export function getUnreadNotificationCount( + room: Room, + type: NotificationCountType, + threadId?: string, +): number { + let notificationCount = (!!threadId + ? room.getThreadUnreadNotificationCount(threadId, type) + : room.getUnreadNotificationCount(type)); // Check notification counts in the old room just in case there's some lost // there. We only go one level down to avoid performance issues, and theory // is that 1st generation rooms will have already been read by the 3rd generation. const createEvent = room.currentState.getStateEvents(EventType.RoomCreate, ""); - if (createEvent && createEvent.getContent()['predecessor']) { - const oldRoomId = createEvent.getContent()['predecessor']['room_id']; + const predecessor = createEvent?.getContent().predecessor; + // Exclude threadId, as the same thread can't continue over a room upgrade + if (!threadId && predecessor) { + const oldRoomId = predecessor.room_id; const oldRoom = MatrixClientPeg.get().getRoom(oldRoomId); if (oldRoom) { // We only ever care if there's highlights in the old room. No point in diff --git a/src/ScalarAuthClient.ts b/src/ScalarAuthClient.ts index 5dacd07973..3ee1e7c15d 100644 --- a/src/ScalarAuthClient.ts +++ b/src/ScalarAuthClient.ts @@ -190,6 +190,9 @@ export default class ScalarAuthClient { const res = await fetch(scalarRestUrl, { method: "POST", body: JSON.stringify(openidTokenObject), + headers: { + "Content-Type": "application/json", + }, }); if (!res.ok) { diff --git a/src/SlidingSyncManager.ts b/src/SlidingSyncManager.ts index 0e5736465e..c41e6a78e3 100644 --- a/src/SlidingSyncManager.ts +++ b/src/SlidingSyncManager.ts @@ -63,6 +63,15 @@ const DEFAULT_ROOM_SUBSCRIPTION_INFO = { required_state: [ ["*", "*"], // all events ], + include_old_rooms: { + timeline_limit: 0, + required_state: [ // state needed to handle space navigation and tombstone chains + [EventType.RoomCreate, ""], + [EventType.RoomTombstone, ""], + [EventType.SpaceChild, "*"], + [EventType.SpaceParent, "*"], + ], + }, }; export type PartialSlidingSyncRequest = { @@ -121,6 +130,16 @@ export class SlidingSyncManager { [EventType.SpaceParent, "*"], // all space parents [EventType.RoomMember, this.client.getUserId()!], // lets the client calculate that we are in fact in the room ], + include_old_rooms: { + timeline_limit: 0, + required_state: [ + [EventType.RoomCreate, ""], + [EventType.RoomTombstone, ""], // lets JS SDK hide rooms which are dead + [EventType.SpaceChild, "*"], // all space children + [EventType.SpaceParent, "*"], // all space parents + [EventType.RoomMember, this.client.getUserId()!], // lets the client calculate that we are in fact in the room + ], + }, filters: { room_types: ["m.space"], }, @@ -176,7 +195,7 @@ export class SlidingSyncManager { list = { ranges: [[0, 20]], sort: [ - "by_highlight_count", "by_notification_count", "by_recency", + "by_notification_level", "by_recency", ], timeline_limit: 1, // most recent message display: though this seems to only be needed for favourites? required_state: [ @@ -187,6 +206,16 @@ export class SlidingSyncManager { [EventType.RoomCreate, ""], // for isSpaceRoom checks [EventType.RoomMember, this.client.getUserId()], // lets the client calculate that we are in fact in the room ], + include_old_rooms: { + timeline_limit: 0, + required_state: [ + [EventType.RoomCreate, ""], + [EventType.RoomTombstone, ""], // lets JS SDK hide rooms which are dead + [EventType.SpaceChild, "*"], // all space children + [EventType.SpaceParent, "*"], // all space parents + [EventType.RoomMember, this.client.getUserId()!], // lets the client calculate that we are in fact in the room + ], + }, }; list = Object.assign(list, updateArgs); } else { diff --git a/src/Unread.ts b/src/Unread.ts index 1804ddefb7..60ef9ca19e 100644 --- a/src/Unread.ts +++ b/src/Unread.ts @@ -23,7 +23,6 @@ import { MatrixClientPeg } from "./MatrixClientPeg"; import shouldHideEvent from './shouldHideEvent'; import { haveRendererForEvent } from "./events/EventTileFactory"; import SettingsStore from "./settings/SettingsStore"; -import { RoomNotificationStateStore } from "./stores/notifications/RoomNotificationStateStore"; /** * Returns true if this event arriving in a room should affect the room's @@ -77,11 +76,6 @@ export function doesRoomHaveUnreadMessages(room: Room): boolean { if (room.timeline.length && room.timeline[room.timeline.length - 1].getSender() === myUserId) { return false; } - } else { - const threadState = RoomNotificationStateStore.instance.getThreadsRoomState(room); - if (threadState.color > 0) { - return true; - } } // if the read receipt relates to an event is that part of a thread diff --git a/src/audio/VoiceRecording.ts b/src/audio/VoiceRecording.ts index 0e18756fe5..99f878868d 100644 --- a/src/audio/VoiceRecording.ts +++ b/src/audio/VoiceRecording.ts @@ -60,6 +60,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable { private recorderProcessor: ScriptProcessorNode; private recording = false; private observable: SimpleObservable; + private targetMaxLength: number | null = TARGET_MAX_LENGTH; public amplitudes: number[] = []; // at each second mark, generated private liveWaveform = new FixedRollingArray(RECORDING_PLAYBACK_SAMPLES, 0); public onDataAvailable: (data: ArrayBuffer) => void; @@ -83,6 +84,10 @@ export class VoiceRecording extends EventEmitter implements IDestroyable { return true; // we don't ever care if the event had listeners, so just return "yes" } + public disableMaxLength(): void { + this.targetMaxLength = null; + } + private async makeRecorder() { try { this.recorderStream = await navigator.mediaDevices.getUserMedia({ @@ -203,6 +208,12 @@ export class VoiceRecording extends EventEmitter implements IDestroyable { // In testing, recorder time and worker time lag by about 400ms, which is roughly the // time needed to encode a sample/frame. // + + if (!this.targetMaxLength) { + // skip time checks if max length has been disabled + return; + } + const secondsLeft = TARGET_MAX_LENGTH - this.recorderSeconds; if (secondsLeft < 0) { // go over to make sure we definitely capture that last frame // noinspection JSIgnoredPromiseFromCall - we aren't concerned with it overlapping diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 223eb0a6db..56c396750f 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -835,6 +835,13 @@ export default class MessagePanel extends React.Component { : room; const receipts: IReadReceiptProps[] = []; + + if (!receiptDestination) { + logger.debug("Discarding request, could not find the receiptDestination for event: " + + this.context.threadId); + return receipts; + } + receiptDestination.getReceiptsForEvent(event).forEach((r) => { if ( !r.userId || diff --git a/src/components/structures/RoomStatusBar.tsx b/src/components/structures/RoomStatusBar.tsx index d46ad12b50..e703252546 100644 --- a/src/components/structures/RoomStatusBar.tsx +++ b/src/components/structures/RoomStatusBar.tsx @@ -34,10 +34,12 @@ const STATUS_BAR_HIDDEN = 0; const STATUS_BAR_EXPANDED = 1; const STATUS_BAR_EXPANDED_LARGE = 2; -export function getUnsentMessages(room: Room): MatrixEvent[] { +export function getUnsentMessages(room: Room, threadId?: string): MatrixEvent[] { if (!room) { return []; } return room.getPendingEvents().filter(function(ev) { - return ev.status === EventStatus.NOT_SENT; + const isNotSent = ev.status === EventStatus.NOT_SENT; + const belongsToTheThread = threadId === ev.threadRootId; + return isNotSent && (!threadId || belongsToTheThread); }); } diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 8350b5e734..3463bcd304 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -18,9 +18,6 @@ import React, { createRef, KeyboardEvent } from 'react'; import { Thread, THREAD_RELATION_TYPE, ThreadEvent } from 'matrix-js-sdk/src/models/thread'; import { Room, RoomEvent } from 'matrix-js-sdk/src/models/room'; import { IEventRelation, MatrixEvent } from 'matrix-js-sdk/src/models/event'; -import { TimelineWindow } from 'matrix-js-sdk/src/timeline-window'; -import { Direction } from 'matrix-js-sdk/src/models/event-timeline'; -import { IRelationsRequestOpts } from 'matrix-js-sdk/src/@types/requests'; import { logger } from 'matrix-js-sdk/src/logger'; import classNames from 'classnames'; @@ -55,6 +52,7 @@ import Spinner from "../views/elements/Spinner"; import { ComposerInsertPayload, ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload"; import Heading from '../views/typography/Heading'; import { SdkContextClass } from '../../contexts/SDKContext'; +import { ThreadPayload } from '../../dispatcher/payloads/ThreadPayload'; interface IProps { room: Room; @@ -132,6 +130,11 @@ export default class ThreadView extends React.Component { metricsTrigger: undefined, // room doesn't change }); } + + dis.dispatch({ + action: Action.ViewThread, + thread_id: null, + }); } public componentDidUpdate(prevProps) { @@ -225,11 +228,13 @@ export default class ThreadView extends React.Component { }; private async postThreadUpdate(thread: Thread): Promise { + dis.dispatch({ + action: Action.ViewThread, + thread_id: thread.id, + }); thread.emit(ThreadEvent.ViewThread); - await thread.fetchInitialEvents(); this.updateThreadRelation(); - this.nextBatch = thread.liveTimeline.getPaginationToken(Direction.Backward); - this.timelinePanel.current?.refreshTimeline(); + this.timelinePanel.current?.refreshTimeline(this.props.initialEvent?.getId()); } private setupThreadListeners(thread?: Thread | undefined, oldThread?: Thread | undefined): void { @@ -283,40 +288,6 @@ export default class ThreadView extends React.Component { } }; - private nextBatch: string | undefined | null = null; - - private onPaginationRequest = async ( - timelineWindow: TimelineWindow | null, - direction = Direction.Backward, - limit = 20, - ): Promise => { - if (!Thread.hasServerSideSupport && timelineWindow) { - timelineWindow.extend(direction, limit); - return true; - } - - const opts: IRelationsRequestOpts = { - limit, - }; - - if (this.nextBatch) { - opts.from = this.nextBatch; - } - - let nextBatch: string | null | undefined = null; - if (this.state.thread) { - const response = await this.state.thread.fetchEvents(opts); - nextBatch = response.nextBatch; - this.nextBatch = nextBatch; - } - - // Advances the marker on the TimelineWindow to define the correct - // window of events to display on screen - timelineWindow?.extend(direction, limit); - - return !!nextBatch; - }; - private onFileDrop = (dataTransfer: DataTransfer) => { const roomId = this.props.mxEvent.getRoomId(); if (roomId) { @@ -399,7 +370,6 @@ export default class ThreadView extends React.Component { highlightedEventId={highlightedEventId} eventScrollIntoView={this.props.initialEventScrollIntoView} onEventScrolledIntoView={this.resetJumpToEvent} - onPaginationRequest={this.onPaginationRequest} /> ; } else { diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index 7ddeca11bc..fe7ecc8248 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -1409,24 +1409,28 @@ class TimelinePanel extends React.Component { // quite slow. So we detect that situation and shortcut straight to // calling _reloadEvents and updating the state. - const timeline = this.props.timelineSet.getTimelineForEvent(eventId); - if (timeline) { - // This is a hot-path optimization by skipping a promise tick - // by repeating a no-op sync branch in TimelineSet.getTimelineForEvent & MatrixClient.getEventTimeline - this.timelineWindow.load(eventId, INITIAL_SIZE); // in this branch this method will happen in sync time + // This is a hot-path optimization by skipping a promise tick + // by repeating a no-op sync branch in + // TimelineSet.getTimelineForEvent & MatrixClient.getEventTimeline + if (this.props.timelineSet.getTimelineForEvent(eventId)) { + // if we've got an eventId, and the timeline exists, we can skip + // the promise tick. + this.timelineWindow.load(eventId, INITIAL_SIZE); + // in this branch this method will happen in sync time onLoaded(); - } else { - const prom = this.timelineWindow.load(eventId, INITIAL_SIZE); - this.buildLegacyCallEventGroupers(); - this.setState({ - events: [], - liveEvents: [], - canBackPaginate: false, - canForwardPaginate: false, - timelineLoading: true, - }); - prom.then(onLoaded, onError); + return; } + + const prom = this.timelineWindow.load(eventId, INITIAL_SIZE); + this.buildLegacyCallEventGroupers(); + this.setState({ + events: [], + liveEvents: [], + canBackPaginate: false, + canForwardPaginate: false, + timelineLoading: true, + }); + prom.then(onLoaded, onError); } // handle the completion of a timeline load or localEchoUpdate, by @@ -1443,8 +1447,8 @@ class TimelinePanel extends React.Component { } // Force refresh the timeline before threads support pending events - public refreshTimeline(): void { - this.loadTimeline(); + public refreshTimeline(eventId?: string): void { + this.loadTimeline(eventId, undefined, undefined, false); this.reloadEvents(); } diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index c9fc7e001d..7c1564c9d9 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -18,9 +18,10 @@ import React, { ReactNode } from 'react'; import { ConnectionError, MatrixError } from "matrix-js-sdk/src/http-api"; import classNames from "classnames"; import { logger } from "matrix-js-sdk/src/logger"; +import { ISSOFlow, LoginFlow } from "matrix-js-sdk/src/@types/auth"; import { _t, _td } from '../../../languageHandler'; -import Login, { ISSOFlow, LoginFlow } from '../../../Login'; +import Login from '../../../Login'; import SdkConfig from '../../../SdkConfig'; import { messageForResourceLimitError } from '../../../utils/ErrorUtils'; import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils"; diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index b5770110f6..c155b5acc2 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -19,6 +19,7 @@ import React, { Fragment, ReactNode } from 'react'; import { MatrixClient } from "matrix-js-sdk/src/client"; import classNames from "classnames"; import { logger } from "matrix-js-sdk/src/logger"; +import { ISSOFlow } from "matrix-js-sdk/src/@types/auth"; import { _t, _td } from '../../../languageHandler'; import { messageForResourceLimitError } from '../../../utils/ErrorUtils'; @@ -26,7 +27,7 @@ import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils"; import * as Lifecycle from '../../../Lifecycle'; import { IMatrixClientCreds, MatrixClientPeg } from "../../../MatrixClientPeg"; import AuthPage from "../../views/auth/AuthPage"; -import Login, { ISSOFlow } from "../../../Login"; +import Login from "../../../Login"; import dis from "../../../dispatcher/dispatcher"; import SSOButtons from "../../views/elements/SSOButtons"; import ServerPicker from '../../views/elements/ServerPicker'; diff --git a/src/components/structures/auth/SoftLogout.tsx b/src/components/structures/auth/SoftLogout.tsx index 64dcdce645..7b946070ca 100644 --- a/src/components/structures/auth/SoftLogout.tsx +++ b/src/components/structures/auth/SoftLogout.tsx @@ -17,13 +17,14 @@ limitations under the License. import React from 'react'; import { logger } from "matrix-js-sdk/src/logger"; import { Optional } from "matrix-events-sdk"; +import { ISSOFlow, LoginFlow } from "matrix-js-sdk/src/@types/auth"; import { _t } from '../../../languageHandler'; import dis from '../../../dispatcher/dispatcher'; import * as Lifecycle from '../../../Lifecycle'; import Modal from '../../../Modal'; import { MatrixClientPeg } from "../../../MatrixClientPeg"; -import { ISSOFlow, LoginFlow, sendLoginRequest } from "../../../Login"; +import { sendLoginRequest } from "../../../Login"; import AuthPage from "../../views/auth/AuthPage"; import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY } from "../../../BasePlatform"; import SSOButtons from "../../views/elements/SSOButtons"; diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx index f50232fb22..c09b598cee 100644 --- a/src/components/views/avatars/RoomAvatar.tsx +++ b/src/components/views/avatars/RoomAvatar.tsx @@ -16,11 +16,10 @@ limitations under the License. import React, { ComponentProps } from 'react'; import { Room } from 'matrix-js-sdk/src/models/room'; -import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; import classNames from "classnames"; -import { EventType } from "matrix-js-sdk/src/@types/event"; +import { EventType, RoomType } from "matrix-js-sdk/src/@types/event"; import BaseAvatar from './BaseAvatar'; import ImageView from '../elements/ImageView'; @@ -39,11 +38,7 @@ interface IProps extends Omit, "name" | "idNam oobData?: IOOBData & { roomId?: string; }; - width?: number; - height?: number; - resizeMethod?: ResizeMethod; viewAvatarOnClick?: boolean; - className?: string; onClick?(): void; } @@ -72,10 +67,7 @@ export default class RoomAvatar extends React.Component { } public componentWillUnmount() { - const cli = MatrixClientPeg.get(); - if (cli) { - cli.removeListener(RoomStateEvent.Events, this.onRoomStateEvents); - } + MatrixClientPeg.get()?.removeListener(RoomStateEvent.Events, this.onRoomStateEvents); } public static getDerivedStateFromProps(nextProps: IProps): IState { @@ -133,7 +125,7 @@ export default class RoomAvatar extends React.Component { public render() { const { room, oobData, viewAvatarOnClick, onClick, className, ...otherProps } = this.props; - const roomName = room ? room.name : oobData.name; + const roomName = room?.name ?? oobData.name; // If the room is a DM, we use the other user's ID for the color hash // in order to match the room avatar with their avatar const idName = room ? (DMRoomMap.shared().getUserIdForRoomId(room.roomId) ?? room.roomId) : oobData.roomId; @@ -142,7 +134,7 @@ export default class RoomAvatar extends React.Component { = ({ latestLocationState }) => { return <> public render(): JSX.Element { const cli = MatrixClientPeg.get(); const me = cli.getUserId(); - const { mxEvent, rightClick, link, eventTileOps, reactions, collapseReplyChain } = this.props; + const { + mxEvent, rightClick, link, eventTileOps, reactions, collapseReplyChain, + ...other + } = this.props; + delete other.getRelationsForEvent; + delete other.permalinkCreator; + const eventStatus = mxEvent.status; const unsentReactionsCount = this.getUnsentReactions().length; const contentActionable = isContentActionable(mxEvent); @@ -747,7 +753,7 @@ export default class MessageContextMenu extends React.Component return ( = ({ initialText = "", initialFilter = n shouldPeek: result.publicRoom.world_readable || cli.isGuest(), }, true, ev.type !== "click"); }; + return (