diff --git a/babel.config.js b/babel.config.js index b63a90e5ff7..58df067b795 100644 --- a/babel.config.js +++ b/babel.config.js @@ -31,5 +31,7 @@ module.exports = { "@babel/plugin-syntax-dynamic-import", "@babel/plugin-transform-runtime", + ["@babel/plugin-proposal-decorators", { version: "2023-11" }], // only needed by the js-sdk + "@babel/plugin-transform-class-static-block", // only needed by the js-sdk for decorators ], }; diff --git a/package.json b/package.json index 6d19504ada8..6a790633342 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "@types/react-dom": "18.3.5", "oidc-client-ts": "3.1.0", "jwt-decode": "4.0.0", - "caniuse-lite": "1.0.30001697", + "caniuse-lite": "1.0.30001699", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0", "wrap-ansi": "npm:wrap-ansi@^7.0.0" }, @@ -88,11 +88,11 @@ "@matrix-org/emojibase-bindings": "^1.3.4", "@matrix-org/react-sdk-module-api": "^2.4.0", "@matrix-org/spec": "^1.7.0", - "@sentry/browser": "^8.0.0", + "@sentry/browser": "^9.0.0", "@types/png-chunks-extract": "^1.0.2", "@types/react-virtualized": "^9.21.30", - "@vector-im/compound-design-tokens": "^3.0.0", - "@vector-im/compound-web": "^7.6.1", + "@vector-im/compound-design-tokens": "^4.0.0", + "@vector-im/compound-web": "^7.6.4", "@vector-im/matrix-wysiwyg": "2.38.0", "@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4", @@ -129,7 +129,7 @@ "maplibre-gl": "^5.0.0", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", - "matrix-js-sdk": "37.0.0", + "matrix-js-sdk": "37.1.0", "matrix-widget-api": "^1.10.0", "memoize-one": "^6.0.0", "mime": "^4.0.4", @@ -163,9 +163,11 @@ "@babel/core": "^7.12.10", "@babel/eslint-parser": "^7.12.10", "@babel/eslint-plugin": "^7.12.10", + "@babel/plugin-proposal-decorators": "^7.25.9", "@babel/plugin-proposal-export-default-from": "^7.12.1", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-transform-class-properties": "^7.12.1", + "@babel/plugin-transform-class-static-block": "^7.26.0", "@babel/plugin-transform-logical-assignment-operators": "^7.20.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.12.1", "@babel/plugin-transform-numeric-separator": "^7.12.7", @@ -275,7 +277,7 @@ "postcss-preset-env": "^10.0.0", "postcss-scss": "^4.0.4", "postcss-simple-vars": "^7.0.1", - "prettier": "3.4.2", + "prettier": "3.5.1", "process": "^0.11.10", "raw-loader": "^4.0.2", "rimraf": "^6.0.0", diff --git a/res/css/_common.pcss b/res/css/_common.pcss index 92a19bb076e..5cf20b52343 100644 --- a/res/css/_common.pcss +++ b/res/css/_common.pcss @@ -612,18 +612,21 @@ legend { * in the app look the same by being AccessibleButtons, or possibly by having explict button classes. * We should go through and have one consistent set of styles for buttons throughout the app. * For now, I am duplicating the selectors here for mx_Dialog and mx_DialogButtons. - * - * Elements that should not be styled like a dialog button are mentioned in a :not() pseudo-class. - * For the widest browser support, we use multiple :not pseudo-classes instead of :not(.a, .b). */ .mx_Dialog - button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not( - .mx_UserProfileSettings button - ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not( - .mx_EncryptionUserSettingsTab button + button:not( + .mx_EncryptionUserSettingsTab button, + .mx_UserProfileSettings button, + .mx_ShareDialog button, + .mx_UnpinAllDialog button, + .mx_ThemeChoicePanel_CustomTheme button, + .mx_Dialog_nonDialogButton, + .mx_AccessibleButton, + .mx_IdentityServerPicker button, + [class|="maplibregl"] ), +.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton, .mx_AccessibleButton), .mx_Dialog input[type="submit"], -.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton), .mx_Dialog_buttons input[type="submit"] { @mixin mx_DialogButton; margin-left: 0px; @@ -639,32 +642,46 @@ legend { } .mx_Dialog - button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not( - .mx_UserProfileSettings button - ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not( + button:not( + .mx_Dialog_nonDialogButton, + [class|="maplibregl"], + .mx_AccessibleButton, + .mx_UserProfileSettings button, + .mx_ThemeChoicePanel_CustomTheme button, + .mx_UnpinAllDialog button, + .mx_ShareDialog button, .mx_EncryptionUserSettingsTab button ):last-child { margin-right: 0px; } .mx_Dialog - button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not( - .mx_UserProfileSettings button - ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not( + button:not( + .mx_Dialog_nonDialogButton, + [class|="maplibregl"], + .mx_AccessibleButton, + .mx_UserProfileSettings button, + .mx_ThemeChoicePanel_CustomTheme button, + .mx_UnpinAllDialog button, + .mx_ShareDialog button, .mx_EncryptionUserSettingsTab button ):focus, .mx_Dialog input[type="submit"]:focus, -.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):focus, +.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton, .mx_AccessibleButton):focus, .mx_Dialog_buttons input[type="submit"]:focus { filter: brightness($focus-brightness); } -.mx_Dialog button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]), +.mx_Dialog button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton, [class|="maplibregl"]), .mx_Dialog input[type="submit"].mx_Dialog_primary, .mx_Dialog_buttons - button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not( - .mx_UserProfileSettings button - ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not( + button:not( + .mx_Dialog_nonDialogButton, + .mx_AccessibleButton, + .mx_UserProfileSettings button, + .mx_ThemeChoicePanel_CustomTheme button, + .mx_UnpinAllDialog button, + .mx_ShareDialog button, .mx_EncryptionUserSettingsTab button ), .mx_Dialog_buttons input[type="submit"].mx_Dialog_primary { @@ -674,32 +691,43 @@ legend { min-width: 156px; } -.mx_Dialog button.danger:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]), +.mx_Dialog button.danger:not(.mx_Dialog_nonDialogButton, [class|="maplibregl"]), .mx_Dialog input[type="submit"].danger, .mx_Dialog_buttons - button.danger:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(.mx_UserProfileSettings button):not( - .mx_ThemeChoicePanel_CustomTheme button - ):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(.mx_EncryptionUserSettingsTab button), + button.danger:not( + .mx_Dialog_nonDialogButton, + .mx_AccessibleButton, + .mx_UserProfileSettings button, + .mx_ThemeChoicePanel_CustomTheme button, + .mx_UnpinAllDialog button, + .mx_ShareDialog button, + .mx_EncryptionUserSettingsTab button + ), .mx_Dialog_buttons input[type="submit"].danger { background-color: var(--cpd-color-bg-critical-primary); border: solid 1px var(--cpd-color-bg-critical-primary); color: var(--cpd-color-text-on-solid-primary); } -.mx_Dialog button.warning:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]), +.mx_Dialog button.warning:not(.mx_Dialog_nonDialogButton, [class|="maplibregl"]), .mx_Dialog input[type="submit"].warning { border: solid 1px var(--cpd-color-border-critical-subtle); color: var(--cpd-color-text-critical-primary); } .mx_Dialog - button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not( - .mx_UserProfileSettings button - ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not( + button:not( + .mx_Dialog_nonDialogButton, + [class|="maplibregl"], + .mx_AccessibleButton, + .mx_UserProfileSettings button, + .mx_ThemeChoicePanel_CustomTheme button, + .mx_UnpinAllDialog button, + .mx_ShareDialog button, .mx_EncryptionUserSettingsTab button ):disabled, .mx_Dialog input[type="submit"]:disabled, -.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):disabled, +.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton, .mx_AccessibleButton):disabled, .mx_Dialog_buttons input[type="submit"]:disabled { background-color: $light-fg-color; border: solid 1px $light-fg-color; diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 98b8464e6ca..f60c4f96245 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -270,8 +270,9 @@ @import "./views/right_panel/_VerificationPanel.pcss"; @import "./views/right_panel/_WidgetCard.pcss"; @import "./views/room_settings/_AliasSettings.pcss"; -@import "./views/rooms/RoomListView/_RoomListSearch.pcss"; -@import "./views/rooms/RoomListView/_RoomListView.pcss"; +@import "./views/rooms/RoomListPanel/_RoomListHeaderView.pcss"; +@import "./views/rooms/RoomListPanel/_RoomListPanel.pcss"; +@import "./views/rooms/RoomListPanel/_RoomListSearch.pcss"; @import "./views/rooms/_AppsDrawer.pcss"; @import "./views/rooms/_Autocomplete.pcss"; @import "./views/rooms/_AuxPanel.pcss"; @@ -289,6 +290,8 @@ @import "./views/rooms/_IRCLayout.pcss"; @import "./views/rooms/_InvitedIconView.pcss"; @import "./views/rooms/_JumpToBottomButton.pcss"; +@import "./views/rooms/_LegacyRoomList.pcss"; +@import "./views/rooms/_LegacyRoomListHeader.pcss"; @import "./views/rooms/_LinkPreviewGroup.pcss"; @import "./views/rooms/_LinkPreviewWidget.pcss"; @import "./views/rooms/_LiveContentSummary.pcss"; @@ -312,8 +315,6 @@ @import "./views/rooms/_RoomHeader.pcss"; @import "./views/rooms/_RoomInfoLine.pcss"; @import "./views/rooms/_RoomKnocksBar.pcss"; -@import "./views/rooms/_RoomList.pcss"; -@import "./views/rooms/_RoomListHeader.pcss"; @import "./views/rooms/_RoomPreviewBar.pcss"; @import "./views/rooms/_RoomPreviewCard.pcss"; @import "./views/rooms/_RoomSearchAuxPanel.pcss"; @@ -349,7 +350,6 @@ @import "./views/settings/_PowerLevelSelector.pcss"; @import "./views/settings/_RoomProfileSettings.pcss"; @import "./views/settings/_SecureBackupPanel.pcss"; -@import "./views/settings/_SetIdServer.pcss"; @import "./views/settings/_SetIntegrationManager.pcss"; @import "./views/settings/_SettingsFieldset.pcss"; @import "./views/settings/_SettingsHeader.pcss"; @@ -361,8 +361,8 @@ @import "./views/settings/encryption/_AdvancedPanel.pcss"; @import "./views/settings/encryption/_ChangeRecoveryKey.pcss"; @import "./views/settings/encryption/_EncryptionCard.pcss"; +@import "./views/settings/encryption/_EncryptionCardEmphasisedContent.pcss"; @import "./views/settings/encryption/_RecoveryPanelOutOfSync.pcss"; -@import "./views/settings/encryption/_ResetIdentityPanel.pcss"; @import "./views/settings/tabs/_SettingsBanner.pcss"; @import "./views/settings/tabs/_SettingsIndent.pcss"; @import "./views/settings/tabs/_SettingsSection.pcss"; diff --git a/res/css/structures/ErrorView.pcss b/res/css/structures/ErrorView.pcss index cf09ac02afd..ddc510e1882 100644 --- a/res/css/structures/ErrorView.pcss +++ b/res/css/structures/ErrorView.pcss @@ -10,8 +10,9 @@ Please see LICENSE files in the repository root for full details. --cpd-separator-inset: calc(50% - (var(--width) / 2)); --cpd-separator-spacing: var(--cpd-space-8x); - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol"; text-align: center; color: var(--cpd-color-text-primary); width: 100%; diff --git a/res/css/structures/_LeftPanel.pcss b/res/css/structures/_LeftPanel.pcss index b93cb8b9331..7b8a6e273b1 100644 --- a/res/css/structures/_LeftPanel.pcss +++ b/res/css/structures/_LeftPanel.pcss @@ -114,7 +114,7 @@ Please see LICENSE files in the repository root for full details. display: flex; align-items: center; - & + .mx_RoomListHeader { + & + .mx_LegacyRoomListHeader { margin-top: 12px; } @@ -184,7 +184,7 @@ Please see LICENSE files in the repository root for full details. } } - .mx_RoomListHeader:first-child { + .mx_LegacyRoomListHeader:first-child { margin-top: 12px; } diff --git a/res/css/structures/_SpaceHierarchy.pcss b/res/css/structures/_SpaceHierarchy.pcss index 31dad9413f1..02f39a0b72f 100644 --- a/res/css/structures/_SpaceHierarchy.pcss +++ b/res/css/structures/_SpaceHierarchy.pcss @@ -77,7 +77,7 @@ Please see LICENSE files in the repository root for full details. height: 16px; width: 16px; left: 0; - background-image: url("@vector-im/compound-design-tokens/icons/error.svg"); + background-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg"); background-size: cover; background-repeat: no-repeat; } diff --git a/res/css/structures/_SpacePanel.pcss b/res/css/structures/_SpacePanel.pcss index 31dab1ee8dd..664d781829a 100644 --- a/res/css/structures/_SpacePanel.pcss +++ b/res/css/structures/_SpacePanel.pcss @@ -365,7 +365,8 @@ Please see LICENSE files in the repository root for full details. Note the top fade is much smaller because the spaces start close to the top, so otherwise a large gradient suddenly appears when you scroll down. */ - mask-image: linear-gradient(to bottom, transparent, black 16px), + mask-image: + linear-gradient(to bottom, transparent, black 16px), linear-gradient( to top, transparent, diff --git a/res/css/structures/_SplashPage.pcss b/res/css/structures/_SplashPage.pcss index 6f976ba5753..26eec65a939 100644 --- a/res/css/structures/_SplashPage.pcss +++ b/res/css/structures/_SplashPage.pcss @@ -16,7 +16,8 @@ Please see LICENSE files in the repository root for full details. position: absolute; z-index: -1; opacity: 0.6; - background-image: radial-gradient( + background-image: + radial-gradient( 53.85% 66.75% at 87.55% 0%, hsla(250deg, 76%, 71%, 0.261) 0%, hsla(250deg, 100%, 88%, 0) 100% diff --git a/res/css/views/context_menus/_MessageContextMenu.pcss b/res/css/views/context_menus/_MessageContextMenu.pcss index f365c4a293c..9fc454f3286 100644 --- a/res/css/views/context_menus/_MessageContextMenu.pcss +++ b/res/css/views/context_menus/_MessageContextMenu.pcss @@ -29,7 +29,7 @@ Please see LICENSE files in the repository root for full details. } .mx_MessageContextMenu_iconReport::before { - mask-image: url("@vector-im/compound-design-tokens/icons/error.svg"); + mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg"); } .mx_MessageContextMenu_iconLink::before { diff --git a/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss index da71b4462b0..83b9fe96b45 100644 --- a/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss +++ b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss @@ -21,7 +21,7 @@ Please see LICENSE files in the repository root for full details. &.mx_AccessSecretStorageDialog_resetBadge::before { /* The image isn't capable of masking, so we use a background instead. */ - background-image: url("@vector-im/compound-design-tokens/icons/error.svg"); + background-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg"); background-size: 24px; background-color: transparent; } @@ -120,7 +120,7 @@ Please see LICENSE files in the repository root for full details. width: 16px; left: 0; top: 2px; /* alignment */ - background-image: url("@vector-im/compound-design-tokens/icons/error.svg"); + background-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg"); background-size: contain; } diff --git a/res/css/views/elements/_InfoTooltip.pcss b/res/css/views/elements/_InfoTooltip.pcss index 5229b7d9f59..a214f0bf835 100644 --- a/res/css/views/elements/_InfoTooltip.pcss +++ b/res/css/views/elements/_InfoTooltip.pcss @@ -29,5 +29,5 @@ Please see LICENSE files in the repository root for full details. } .mx_InfoTooltip_icon_warning::before { - mask-image: url("@vector-im/compound-design-tokens/icons/error.svg"); + mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg"); } diff --git a/res/css/views/rooms/RoomListPanel/_RoomListHeaderView.pcss b/res/css/views/rooms/RoomListPanel/_RoomListHeaderView.pcss new file mode 100644 index 00000000000..8ce4655e58f --- /dev/null +++ b/res/css/views/rooms/RoomListPanel/_RoomListHeaderView.pcss @@ -0,0 +1,39 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +.mx_RoomListHeaderView { + height: 60px; + padding: 0 var(--cpd-space-3x); + + .mx_RoomListHeaderView_title { + min-width: 0; + + h1 { + all: unset; + font: var(--cpd-font-heading-sm-semibold); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + + button { + color: var(--cpd-color-icon-secondary); + } + + .mx_SpaceMenu_button { + svg { + transition: transform 0.1s linear; + } + } + + .mx_SpaceMenu_button[aria-expanded="true"] { + svg { + transform: rotate(180deg); + } + } +} diff --git a/res/css/views/rooms/RoomListView/_RoomListView.pcss b/res/css/views/rooms/RoomListPanel/_RoomListPanel.pcss similarity index 94% rename from res/css/views/rooms/RoomListView/_RoomListView.pcss rename to res/css/views/rooms/RoomListPanel/_RoomListPanel.pcss index 117810e6b70..eb1f6e5fe5b 100644 --- a/res/css/views/rooms/RoomListView/_RoomListView.pcss +++ b/res/css/views/rooms/RoomListPanel/_RoomListPanel.pcss @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -.mx_RoomListView { +.mx_RoomListPanel { background-color: var(--cpd-color-bg-canvas-default); height: 100%; border-right: 1px solid var(--cpd-color-bg-subtle-primary); diff --git a/res/css/views/rooms/RoomListView/_RoomListSearch.pcss b/res/css/views/rooms/RoomListPanel/_RoomListSearch.pcss similarity index 87% rename from res/css/views/rooms/RoomListView/_RoomListSearch.pcss rename to res/css/views/rooms/RoomListPanel/_RoomListSearch.pcss index 55b1def58a6..f175ab3976d 100644 --- a/res/css/views/rooms/RoomListView/_RoomListSearch.pcss +++ b/res/css/views/rooms/RoomListPanel/_RoomListSearch.pcss @@ -9,7 +9,7 @@ /* From figma, this should be aligned with the room header */ height: 64px; box-sizing: border-box; - border-bottom: 1px solid var(--cpd-color-bg-subtle-primary); + border-bottom: var(--cpd-border-width-1) solid var(--cpd-color-bg-subtle-primary); padding: 0 var(--cpd-space-3x); svg { @@ -31,7 +31,7 @@ } } - .mx_RoomListSearch_explore:hover { + .mx_RoomListSearch_button:hover { svg { fill: var(--cpd-color-icon-primary); } diff --git a/res/css/views/rooms/_EventTile.pcss b/res/css/views/rooms/_EventTile.pcss index 5268f143da7..2c96f07b90f 100644 --- a/res/css/views/rooms/_EventTile.pcss +++ b/res/css/views/rooms/_EventTile.pcss @@ -685,6 +685,7 @@ $left-gutter: 64px; line-height: inherit !important; background-color: inherit; color: inherit; /* inherit the colour from the dark or light theme by default (but not for code blocks) */ + flex: 1; pre, code { diff --git a/res/css/views/rooms/_RoomList.pcss b/res/css/views/rooms/_LegacyRoomList.pcss similarity index 71% rename from res/css/views/rooms/_RoomList.pcss rename to res/css/views/rooms/_LegacyRoomList.pcss index 74e2e86ed1d..acf162b7a2f 100644 --- a/res/css/views/rooms/_RoomList.pcss +++ b/res/css/views/rooms/_LegacyRoomList.pcss @@ -6,31 +6,31 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -.mx_RoomList { +.mx_LegacyRoomList { padding-right: 7px; /* width of the scrollbar, to line things up */ } -.mx_RoomList_iconPlus::before { +.mx_LegacyRoomList_iconPlus::before { mask-image: url("$(res)/img/element-icons/roomlist/plus-circle.svg"); } -.mx_RoomList_iconNewRoom::before { +.mx_LegacyRoomList_iconNewRoom::before { mask-image: url("$(res)/img/element-icons/roomlist/hash-plus.svg"); } -.mx_RoomList_iconNewVideoRoom::before { +.mx_LegacyRoomList_iconNewVideoRoom::before { mask-image: url("$(res)/img/element-icons/roomlist/hash-video.svg"); } -.mx_RoomList_iconAddExistingRoom::before { +.mx_LegacyRoomList_iconAddExistingRoom::before { mask-image: url("$(res)/img/element-icons/roomlist/hash.svg"); } -.mx_RoomList_iconExplore::before { +.mx_LegacyRoomList_iconExplore::before { mask-image: url("$(res)/img/element-icons/roomlist/hash-search.svg"); } -.mx_RoomList_iconDialpad::before { +.mx_LegacyRoomList_iconDialpad::before { mask-image: url("$(res)/img/element-icons/roomlist/dialpad.svg"); } -.mx_RoomList_iconStartChat::before { +.mx_LegacyRoomList_iconStartChat::before { mask-image: url("@vector-im/compound-design-tokens/icons/user-add-solid.svg"); } -.mx_RoomList_iconInvite::before { +.mx_LegacyRoomList_iconInvite::before { mask-image: url("$(res)/img/element-icons/room/share.svg"); } diff --git a/res/css/views/rooms/_RoomListHeader.pcss b/res/css/views/rooms/_LegacyRoomListHeader.pcss similarity index 84% rename from res/css/views/rooms/_RoomListHeader.pcss rename to res/css/views/rooms/_LegacyRoomListHeader.pcss index 16d75833606..573343531ab 100644 --- a/res/css/views/rooms/_RoomListHeader.pcss +++ b/res/css/views/rooms/_LegacyRoomListHeader.pcss @@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -.mx_RoomListHeader { +.mx_LegacyRoomListHeader { display: flex; align-items: center; - .mx_RoomListHeader_contextLessTitle, - .mx_RoomListHeader_contextMenuButton { + .mx_LegacyRoomListHeader_contextLessTitle, + .mx_LegacyRoomListHeader_contextMenuButton { font: var(--cpd-font-heading-sm-semibold); font-weight: var(--cpd-font-weight-semibold); padding: 1px 24px 1px 4px; @@ -24,7 +24,7 @@ Please see LICENSE files in the repository root for full details. user-select: none; } - .mx_RoomListHeader_contextMenuButton { + .mx_LegacyRoomListHeader_contextMenuButton { border-radius: 6px; &:hover { @@ -54,7 +54,7 @@ Please see LICENSE files in the repository root for full details. } } - .mx_RoomListHeader_plusButton { + .mx_LegacyRoomListHeader_plusButton { /* elecord, hide primary plus button */ display: none; @@ -91,21 +91,21 @@ Please see LICENSE files in the repository root for full details. } } -.mx_RoomListHeader_iconInvite::before { +.mx_LegacyRoomListHeader_iconInvite::before { mask-image: url("$(res)/img/element-icons/room/invite.svg"); } -.mx_RoomListHeader_iconStartChat::before { +.mx_LegacyRoomListHeader_iconStartChat::before { mask-image: url("@vector-im/compound-design-tokens/icons/user-add-solid.svg"); } -.mx_RoomListHeader_iconNewRoom::before { +.mx_LegacyRoomListHeader_iconNewRoom::before { mask-image: url("$(res)/img/element-icons/roomlist/hash-plus.svg"); } -.mx_RoomListHeader_iconNewVideoRoom::before { +.mx_LegacyRoomListHeader_iconNewVideoRoom::before { mask-image: url("$(res)/img/element-icons/roomlist/hash-video.svg"); } -.mx_RoomListHeader_iconExplore::before { +.mx_LegacyRoomListHeader_iconExplore::before { mask-image: url("$(res)/img/element-icons/roomlist/hash-search.svg"); } -.mx_RoomListHeader_iconPlus::before { +.mx_LegacyRoomListHeader_iconPlus::before { mask-image: url("@vector-im/compound-design-tokens/icons/plus.svg"); } diff --git a/res/css/views/rooms/_RoomHeader.pcss b/res/css/views/rooms/_RoomHeader.pcss index f00dbeccc2c..320a482c74f 100644 --- a/res/css/views/rooms/_RoomHeader.pcss +++ b/res/css/views/rooms/_RoomHeader.pcss @@ -59,6 +59,7 @@ Please see LICENSE files in the repository root for full details. .mx_RoomHeader_icon { flex-shrink: 0; + padding: var(--cpd-space-1x); } .mx_RoomHeader .mx_FacePile { diff --git a/res/css/views/settings/_SetIdServer.pcss b/res/css/views/settings/_SetIdServer.pcss deleted file mode 100644 index 377292451ff..00000000000 --- a/res/css/views/settings/_SetIdServer.pcss +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2019-2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -.mx_SetIdServer { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: $spacing-8; - - .mx_Field { - width: 100%; - margin: 0; - } -} - -.mx_SetIdServer_tooltip { - max-width: var(--SettingsTab_tooltip-max-width); -} diff --git a/res/css/views/settings/encryption/_EncryptionCardEmphasisedContent.pcss b/res/css/views/settings/encryption/_EncryptionCardEmphasisedContent.pcss new file mode 100644 index 00000000000..6b18fcff650 --- /dev/null +++ b/res/css/views/settings/encryption/_EncryptionCardEmphasisedContent.pcss @@ -0,0 +1,13 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +.mx_EncryptionCard_emphasisedContent { + span { + font: var(--cpd-font-body-md-medium); + text-align: center; + } +} diff --git a/res/css/views/settings/encryption/_ResetIdentityPanel.pcss b/res/css/views/settings/encryption/_ResetIdentityPanel.pcss deleted file mode 100644 index 1933351337e..00000000000 --- a/res/css/views/settings/encryption/_ResetIdentityPanel.pcss +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -.mx_ResetIdentityPanel { - .mx_ResetIdentityPanel_content { - display: flex; - flex-direction: column; - gap: var(--cpd-space-3x); - - > span { - font: var(--cpd-font-body-md-medium); - text-align: center; - } - } -} diff --git a/res/themes/dark/css/_dark.pcss b/res/themes/dark/css/_dark.pcss index d5272922823..f0b5c74f5d1 100644 --- a/res/themes/dark/css/_dark.pcss +++ b/res/themes/dark/css/_dark.pcss @@ -309,11 +309,8 @@ body { /* Splash Page Gradient */ .mx_SplashPage::before { - background-image: radial-gradient( - 53.85% 66.75% at 87.55% 0%, - hsla(0deg, 0%, 11%, 0.15) 0%, - hsla(250deg, 100%, 88%, 0) 100% - ), + background-image: + radial-gradient(53.85% 66.75% at 87.55% 0%, hsla(0deg, 0%, 11%, 0.15) 0%, hsla(250deg, 100%, 88%, 0) 100%), radial-gradient(41.93% 41.93% at 0% 0%, hsla(0deg, 0%, 38%, 0.28) 0%, hsla(250deg, 100%, 88%, 0) 100%), radial-gradient(100% 100% at 0% 0%, hsla(250deg, 100%, 88%, 0.3) 0%, hsla(0deg, 100%, 86%, 0) 100%), radial-gradient(106.35% 96.26% at 100% 0%, hsla(25deg, 100%, 88%, 0.4) 0%, hsla(167deg, 76%, 82%, 0) 100%) !important; diff --git a/res/themes/legacy-light/css/_legacy-light.pcss b/res/themes/legacy-light/css/_legacy-light.pcss index 32ca7d3d1a0..eea7197d9fc 100644 --- a/res/themes/legacy-light/css/_legacy-light.pcss +++ b/res/themes/legacy-light/css/_legacy-light.pcss @@ -10,11 +10,13 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: "Nunito", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", - sans-serif, "Noto Color Emoji"; +$font-family: + "Nunito", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", sans-serif, + "Noto Color Emoji"; -$monospace-font-family: "Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier", - monospace, "Noto Color Emoji"; +$monospace-font-family: + "Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier", monospace, + "Noto Color Emoji"; /* unified palette */ /* try to use these colors when possible */ diff --git a/res/themes/light/css/_light.pcss b/res/themes/light/css/_light.pcss index fde64a31c4f..1dcdc540e94 100644 --- a/res/themes/light/css/_light.pcss +++ b/res/themes/light/css/_light.pcss @@ -10,11 +10,13 @@ /* Noto Color Emoji contains digits, in fixed-width, therefore causing digits in flowed text to stand out. TODO: Consider putting all emoji fonts to the end rather than the front. */ -$font-family: "Inter", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", - sans-serif, "Noto Color Emoji"; +$font-family: + "Inter", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", sans-serif, + "Noto Color Emoji"; -$monospace-font-family: "Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier", - monospace, "Noto Color Emoji"; +$monospace-font-family: + "Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier", monospace, + "Noto Color Emoji"; /* Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A120 */ /* ******************** */ diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index b779952a861..cf46be41fa3 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -15,9 +15,10 @@ import { type SyncState, ClientStoppedError, } from "matrix-js-sdk/src/matrix"; -import { logger as baseLogger } from "matrix-js-sdk/src/logger"; +import { logger as baseLogger, LogSpan } from "matrix-js-sdk/src/logger"; import { CryptoEvent, type KeyBackupInfo } from "matrix-js-sdk/src/crypto-api"; import { type CryptoSessionStateChange } from "@matrix-org/analytics-events/types/typescript/CryptoSessionStateChange"; +import { secureRandomString } from "matrix-js-sdk/src/randomstring"; import { PosthogAnalytics } from "./PosthogAnalytics"; import dis from "./dispatcher/dispatcher"; @@ -48,6 +49,11 @@ import { asyncSomeParallel } from "./utils/arrays.ts"; const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000; +// Unfortunately named account data key used by Element X to indicate that the user +// has chosen to disable server side key backups. We need to set and honour this +// to prevent Element X from automatically turning key backup back on. +const BACKUP_DISABLED_ACCOUNT_DATA_KEY = "m.org.matrix.custom.backup_disabled"; + const logger = baseLogger.getChild("DeviceListener:"); export default class DeviceListener { @@ -91,6 +97,7 @@ export default class DeviceListener { this.client.on(ClientEvent.AccountData, this.onAccountData); this.client.on(ClientEvent.Sync, this.onSync); this.client.on(RoomStateEvent.Events, this.onRoomStateEvents); + this.client.on(ClientEvent.ToDeviceEvent, this.onToDeviceEvent); this.shouldRecordClientInformation = SettingsStore.getValue("deviceClientInformationOptIn"); // only configurable in config, so we don't need to watch the value this.enableBulkUnverifiedSessionsReminder = SettingsStore.getValue(UIFeature.BulkUnverifiedSessionsReminder); @@ -113,6 +120,7 @@ export default class DeviceListener { this.client.removeListener(ClientEvent.AccountData, this.onAccountData); this.client.removeListener(ClientEvent.Sync, this.onSync); this.client.removeListener(RoomStateEvent.Events, this.onRoomStateEvents); + this.client.removeListener(ClientEvent.ToDeviceEvent, this.onToDeviceEvent); } SettingsStore.unwatchSetting(this.deviceClientInformationSettingWatcherRef); dis.unregister(this.dispatcherRef); @@ -220,6 +228,11 @@ export default class DeviceListener { this.updateClientInformation(); }; + private onToDeviceEvent = (event: MatrixEvent): void => { + // Receiving a 4S secret can mean we are in sync where we were not before. + if (event.getType() === EventType.SecretSend) this.recheck(); + }; + /** * Fetch the key backup information from the server. * @@ -268,18 +281,29 @@ export default class DeviceListener { private async doRecheck(): Promise { if (!this.running || !this.client) return; // we have been stopped + const logSpan = new LogSpan(logger, "check_" + secureRandomString(4)); + const cli = this.client; // cross-signing support was added to Matrix in MSC1756, which landed in spec v1.1 - if (!(await cli.isVersionSupported("v1.1"))) return; + if (!(await cli.isVersionSupported("v1.1"))) { + logSpan.debug("cross-signing not supported"); + return; + } const crypto = cli.getCrypto(); - if (!crypto) return; + if (!crypto) { + logSpan.debug("crypto not enabled"); + return; + } // don't recheck until the initial sync is complete: lots of account data events will fire // while the initial sync is processing and we don't need to recheck on each one of them // (we add a listener on sync to do once check after the initial sync is done) - if (!cli.isInitialSyncComplete()) return; + if (!cli.isInitialSyncComplete()) { + logSpan.debug("initial sync not yet complete"); + return; + } const crossSigningReady = await crypto.isCrossSigningReady(); const secretStorageReady = await crypto.isSecretStorageReady(); @@ -301,6 +325,7 @@ export default class DeviceListener { await this.reportCryptoSessionStateToAnalytics(cli); if (this.dismissedThisDeviceToast || allSystemsReady) { + logSpan.info("No toast needed"); hideSetupEncryptionToast(); this.checkKeyBackupStatus(); @@ -311,25 +336,33 @@ export default class DeviceListener { if (!crossSigningReady) { // This account is legacy and doesn't have cross-signing set up at all. // Prompt the user to set it up. - logger.info("Cross-signing not ready: showing SET_UP_ENCRYPTION toast"); + logSpan.info("Cross-signing not ready: showing SET_UP_ENCRYPTION toast"); showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION); } else if (!isCurrentDeviceTrusted) { // cross signing is ready but the current device is not trusted: prompt the user to verify - logger.info("Current device not verified: showing VERIFY_THIS_SESSION toast"); + logSpan.info("Current device not verified: showing VERIFY_THIS_SESSION toast"); showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION); } else if (!allCrossSigningSecretsCached) { // cross signing ready & device trusted, but we are missing secrets from our local cache. // prompt the user to enter their recovery key. - logger.info("Some secrets not cached: showing KEY_STORAGE_OUT_OF_SYNC toast"); + logSpan.info( + "Some secrets not cached: showing KEY_STORAGE_OUT_OF_SYNC toast", + crossSigningStatus.privateKeysCachedLocally, + ); showSetupEncryptionToast(SetupKind.KEY_STORAGE_OUT_OF_SYNC); } else if (defaultKeyId === null) { - // the user just hasn't set up 4S yet: prompt them to do so - logger.info("No default 4S key: showing SET_UP_RECOVERY toast"); - showSetupEncryptionToast(SetupKind.SET_UP_RECOVERY); + // the user just hasn't set up 4S yet: prompt them to do so (unless they've explicitly said no to key storage) + const disabledEvent = cli.getAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY); + if (!disabledEvent?.getContent().disabled) { + logSpan.info("No default 4S key: showing SET_UP_RECOVERY toast"); + showSetupEncryptionToast(SetupKind.SET_UP_RECOVERY); + } else { + logSpan.info("No default 4S key but backup disabled: no toast needed"); + } } else { // some other condition... yikes! Show the 'set up encryption' toast: this is what we previously did // in 'other' situations. Possibly we should consider prompting for a full reset in this case? - logger.warn("Couldn't match encryption state to a known case: showing 'setup encryption' prompt", { + logSpan.warn("Couldn't match encryption state to a known case: showing 'setup encryption' prompt", { crossSigningReady, secretStorageReady, allCrossSigningSecretsCached, @@ -338,6 +371,8 @@ export default class DeviceListener { }); showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION); } + } else { + logSpan.info("Not yet ready, but shouldShowSetupEncryptionToast==false"); } // This needs to be done after awaiting on getUserDeviceInfo() above, so @@ -370,9 +405,9 @@ export default class DeviceListener { } } - logger.debug("Old unverified sessions: " + Array.from(oldUnverifiedDeviceIds).join(",")); - logger.debug("New unverified sessions: " + Array.from(newUnverifiedDeviceIds).join(",")); - logger.debug("Currently showing toasts for: " + Array.from(this.displayingToastsForDeviceIds).join(",")); + logSpan.debug("Old unverified sessions: " + Array.from(oldUnverifiedDeviceIds).join(",")); + logSpan.debug("New unverified sessions: " + Array.from(newUnverifiedDeviceIds).join(",")); + logSpan.debug("Currently showing toasts for: " + Array.from(this.displayingToastsForDeviceIds).join(",")); const isBulkUnverifiedSessionsReminderSnoozed = isBulkUnverifiedDeviceReminderSnoozed(); @@ -397,7 +432,7 @@ export default class DeviceListener { // ...and hide any we don't need any more for (const deviceId of this.displayingToastsForDeviceIds) { if (!newUnverifiedDeviceIds.has(deviceId)) { - logger.debug("Hiding unverified session toast for " + deviceId); + logSpan.debug("Hiding unverified session toast for " + deviceId); hideUnverifiedSessionsToast(deviceId); } } diff --git a/src/IConfigOptions.ts b/src/IConfigOptions.ts index 2eebd9c7603..67b7515eac6 100644 --- a/src/IConfigOptions.ts +++ b/src/IConfigOptions.ts @@ -71,7 +71,7 @@ export interface IConfigOptions { url: string; // download url url_macos?: string; url_win64?: string; - url_win32?: string; + url_win64arm?: string; url_linux?: string; }; mobile_builds: { diff --git a/src/SdkConfig.ts b/src/SdkConfig.ts index 33d3f98683e..a41b890b19e 100644 --- a/src/SdkConfig.ts +++ b/src/SdkConfig.ts @@ -59,7 +59,7 @@ export const DEFAULTS: DeepReadonly = { url: "https://element.io/download", url_macos: "https://packages.element.io/desktop/install/macos/Element.dmg", url_win64: "https://packages.element.io/desktop/install/win32/x64/Element%20Setup.exe", - url_win32: "https://packages.element.io/desktop/install/win32/ia32/Element%20Setup.exe", + url_win64arm: "https://packages.element.io/desktop/install/win32/arm64/Element%20Setup.exe", url_linux: "https://element.io/download#linux", }, mobile_builds: { diff --git a/src/async-components/structures/ErrorView.tsx b/src/async-components/structures/ErrorView.tsx index 90c03c12dd9..57cf048403b 100644 --- a/src/async-components/structures/ErrorView.tsx +++ b/src/async-components/structures/ErrorView.tsx @@ -80,9 +80,9 @@ const MobileAppLinks: React.FC<{ const DesktopAppLinks: React.FC<{ macOsUrl?: string; win64Url?: string; - win32Url?: string; + win64ArmUrl?: string; linuxUrl?: string; -}> = ({ macOsUrl, win64Url, win32Url, linuxUrl }) => { +}> = ({ macOsUrl, win64Url, win64ArmUrl, linuxUrl }) => { return ( {macOsUrl && ( @@ -92,12 +92,12 @@ const DesktopAppLinks: React.FC<{ )} {win64Url && ( )} - {win32Url && ( - )} {linuxUrl && ( @@ -127,7 +127,7 @@ export const UnsupportedBrowserView: React.FC<{ config.desktop_builds?.available && (config.desktop_builds?.url_macos || config.desktop_builds?.url_win64 || - config.desktop_builds?.url_win32 || + config.desktop_builds?.url_win64arm || config.desktop_builds?.url_linux); const hasMobileBuilds = Boolean( config.mobile_builds?.ios || config.mobile_builds?.android || config.mobile_builds?.fdroid, @@ -157,7 +157,7 @@ export const UnsupportedBrowserView: React.FC<{ diff --git a/src/components/structures/HomePage.tsx b/src/components/structures/HomePage.tsx index 720a78cf626..01c00427525 100644 --- a/src/components/structures/HomePage.tsx +++ b/src/components/structures/HomePage.tsx @@ -27,7 +27,7 @@ import EmbeddedPage from "./EmbeddedPage"; const onClickSendDm = (ev: ButtonEvent): void => { PosthogTrackers.trackInteraction("WebHomeCreateChatButton", ev); - dis.dispatch({ action: "view_create_chat" }); + dis.dispatch({ action: Action.CreateChat }); }; const onClickExplore = (ev: ButtonEvent): void => { @@ -37,7 +37,7 @@ const onClickExplore = (ev: ButtonEvent): void => { const onClickNewRoom = (ev: ButtonEvent): void => { PosthogTrackers.trackInteraction("WebHomeCreateRoomButton", ev); - dis.dispatch({ action: "view_create_room" }); + dis.dispatch({ action: Action.CreateRoom }); }; interface IProps { diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 82040eefab4..f168c390705 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -23,7 +23,7 @@ import { MetaSpace, type SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/sp import { getKeyBindingsManager } from "../../KeyBindingsManager"; import UIStore from "../../stores/UIStore"; import { type IState as IRovingTabIndexState } from "../../accessibility/RovingTabIndex"; -import RoomListHeader from "../views/rooms/RoomListHeader"; +import LegacyRoomListHeader from "../views/rooms/LegacyRoomListHeader"; import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore"; import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; @@ -37,7 +37,7 @@ import PosthogTrackers from "../../PosthogTrackers"; import type PageType from "../../PageTypes"; import { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation"; import SettingsStore from "../../settings/SettingsStore"; -import { RoomListView } from "../views/rooms/RoomListView"; +import { RoomListPanel } from "../views/rooms/RoomListPanel"; interface IProps { isMinimized: boolean; @@ -390,7 +390,7 @@ export default class LeftPanel extends React.Component { return (
- +
); @@ -415,7 +415,7 @@ export default class LeftPanel extends React.Component {
{shouldShowComponent(UIComponent.FilterContainer) && this.renderSearchDialExplore()} {this.renderBreadcrumbs()} - {!this.props.isMinimized && } + {!this.props.isMinimized && }