diff --git a/CHANGELOG.md b/CHANGELOG.md index b8e23bda8a..c6a0f92e41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Changes in [3.64.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.64.1) (2023-01-18) +===================================================================================================== + +## 🐛 Bug Fixes + * Fix crash in older browsers (replace .at() with array.length-1) ([\#9933](https://github.com/matrix-org/matrix-react-sdk/pull/9933)). Fixes matrix-org/element-web-rageshakes#19281. + Changes in [3.64.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.64.0) (2023-01-18) ===================================================================================================== diff --git a/cypress/e2e/crypto/crypto.spec.ts b/cypress/e2e/crypto/crypto.spec.ts index 716eff8ddb..306e94cb97 100644 --- a/cypress/e2e/crypto/crypto.spec.ts +++ b/cypress/e2e/crypto/crypto.spec.ts @@ -20,6 +20,7 @@ import type { ISasEvent } from "matrix-js-sdk/src/crypto/verification/SAS"; import type { CypressBot } from "../../support/bot"; import { HomeserverInstance } from "../../plugins/utils/homeserver"; import Chainable = Cypress.Chainable; +import { UserCredentials } from "../../support/login"; type EmojiMapping = [emoji: string, name: string]; interface CryptoTestContext extends Mocha.Context { @@ -154,11 +155,15 @@ const verify = function (this: CryptoTestContext) { }; describe("Cryptography", function () { + let aliceCredentials: UserCredentials; + beforeEach(function () { cy.startHomeserver("default") .as("homeserver") .then((homeserver: HomeserverInstance) => { - cy.initTestUser(homeserver, "Alice", undefined, "alice_"); + cy.initTestUser(homeserver, "Alice", undefined, "alice_").then((credentials) => { + aliceCredentials = credentials; + }); cy.getBot(homeserver, { displayName: "Bob", autoAcceptInvites: false, userIdPrefix: "bob_" }).as("bob"); }); }); @@ -183,7 +188,7 @@ describe("Cryptography", function () { }); it("creating a DM should work, being e2e-encrypted / user verification", function (this: CryptoTestContext) { - cy.bootstrapCrossSigning(); + cy.bootstrapCrossSigning(aliceCredentials); startDMWithBob.call(this); // send first message cy.get(".mx_BasicMessageComposer_input").click().should("have.focus").type("Hey!{enter}"); @@ -194,7 +199,7 @@ describe("Cryptography", function () { }); it("should allow verification when there is no existing DM", function (this: CryptoTestContext) { - cy.bootstrapCrossSigning(); + cy.bootstrapCrossSigning(aliceCredentials); autoJoin(this.bob); // we need to have a room with the other user present, so we can open the verification panel @@ -212,7 +217,7 @@ describe("Cryptography", function () { }); it("should show the correct shield on edited e2e events", function (this: CryptoTestContext) { - cy.bootstrapCrossSigning(); + cy.bootstrapCrossSigning(aliceCredentials); // bob has a second, not cross-signed, device cy.loginBot(this.homeserver, this.bob.getUserId(), this.bob.__cypress_password, {}).as("bobSecondDevice"); diff --git a/cypress/e2e/crypto/decryption-failure.spec.ts b/cypress/e2e/crypto/decryption-failure.spec.ts index 6cc0a69e3c..5f0a9056ad 100644 --- a/cypress/e2e/crypto/decryption-failure.spec.ts +++ b/cypress/e2e/crypto/decryption-failure.spec.ts @@ -105,15 +105,9 @@ describe("Decryption Failure Bar", () => { "and there are other verified devices or backups", () => { let otherDevice: MatrixClient | undefined; - cy.loginBot(homeserver, testUser.username, testUser.password, {}) + cy.loginBot(homeserver, testUser.username, testUser.password, { bootstrapCrossSigning: true }) .then(async (cli) => { otherDevice = cli; - await otherDevice.bootstrapCrossSigning({ - authUploadDeviceSigningKeys: async (makeRequest) => { - await makeRequest({}); - }, - setupNewCrossSigning: true, - }); }) .then(() => { cy.botSendMessage(bot, roomId, "test"); @@ -169,15 +163,11 @@ describe("Decryption Failure Bar", () => { "should prompt the user to reset keys, if this device isn't verified " + "and there are no other verified devices or backups", () => { - cy.loginBot(homeserver, testUser.username, testUser.password, {}).then(async (cli) => { - await cli.bootstrapCrossSigning({ - authUploadDeviceSigningKeys: async (makeRequest) => { - await makeRequest({}); - }, - setupNewCrossSigning: true, - }); - await cli.logout(true); - }); + cy.loginBot(homeserver, testUser.username, testUser.password, { bootstrapCrossSigning: true }).then( + async (cli) => { + await cli.logout(true); + }, + ); cy.botSendMessage(bot, roomId, "test"); cy.wait(5000); diff --git a/cypress/support/bot.ts b/cypress/support/bot.ts index 745ec4002c..9cb5e472de 100644 --- a/cypress/support/bot.ts +++ b/cypress/support/bot.ts @@ -150,7 +150,14 @@ function setupBotClient( if (opts.bootstrapCrossSigning) { await cli.bootstrapCrossSigning({ authUploadDeviceSigningKeys: async (func) => { - await func({}); + await func({ + type: "m.login.password", + identifier: { + type: "m.id.user", + user: credentials.userId, + }, + password: credentials.password, + }); }, }); } diff --git a/cypress/support/client.ts b/cypress/support/client.ts index 1195a6f303..c56608fadc 100644 --- a/cypress/support/client.ts +++ b/cypress/support/client.ts @@ -22,6 +22,7 @@ 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; +import { UserCredentials } from "./login"; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace @@ -119,7 +120,7 @@ declare global { /** * Boostraps cross-signing. */ - bootstrapCrossSigning(): Chainable; + bootstrapCrossSigning(credendtials: UserCredentials): Chainable; /** * Joins the given room by alias or ID * @param roomIdOrAlias the id or alias of the room to join @@ -210,11 +211,18 @@ Cypress.Commands.add("setAvatarUrl", (url: string): Chainable<{}> => { }); }); -Cypress.Commands.add("bootstrapCrossSigning", () => { +Cypress.Commands.add("bootstrapCrossSigning", (credentials: UserCredentials) => { cy.window({ log: false }).then((win) => { win.mxMatrixClientPeg.matrixClient.bootstrapCrossSigning({ authUploadDeviceSigningKeys: async (func) => { - await func({}); + await func({ + type: "m.login.password", + identifier: { + type: "m.id.user", + user: credentials.userId, + }, + password: credentials.password, + }); }, }); }); diff --git a/cypress/support/homeserver.ts b/cypress/support/homeserver.ts index 8510e3640a..3026c94b06 100644 --- a/cypress/support/homeserver.ts +++ b/cypress/support/homeserver.ts @@ -76,6 +76,7 @@ export interface Credentials { userId: string; deviceId: string; homeServer: string; + password: string; } function registerUser( @@ -120,6 +121,7 @@ function registerUser( accessToken: response.body.access_token, userId: response.body.user_id, deviceId: response.body.device_id, + password: password, })); } diff --git a/package.json b/package.json index a9121dc123..cb17d44351 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.64.0", + "version": "3.64.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { diff --git a/res/css/_spacing.pcss b/res/css/_spacing.pcss index 40c470c26b..63197f2321 100644 --- a/res/css/_spacing.pcss +++ b/res/css/_spacing.pcss @@ -16,6 +16,7 @@ limitations under the License. /* 1rem :: 10px */ +$spacing-2: 2px; $spacing-4: 4px; $spacing-8: 8px; $spacing-12: 12px; diff --git a/res/css/views/rooms/_EventTile.pcss b/res/css/views/rooms/_EventTile.pcss index da77396469..5d55e8bf34 100644 --- a/res/css/views/rooms/_EventTile.pcss +++ b/res/css/views/rooms/_EventTile.pcss @@ -548,7 +548,19 @@ $left-gutter: 64px; pre, code { font-family: $monospace-font-family !important; - background-color: $codeblock-background-color; + background-color: $system; + } + + code:not(pre *) { + background-color: $inlinecode-background-color; + border: 1px solid $inlinecode-border-color; + border-radius: 4px; + // The horizontal padding is added by gfm.css .markdown-body + padding: $spacing-2 0; + // Avoid inline code blocks to be sticked when on multiple lines + line-height: $font-22px; + // Avoid the border to be glued to the other words + margin-right: $spacing-2; } code { @@ -566,6 +578,8 @@ $left-gutter: 64px; background: transparent; } + border: 1px solid $quinary-content; + code { white-space: pre; /* we want code blocks to be scrollable and not wrap */ @@ -744,6 +758,8 @@ $left-gutter: 64px; .mx_EventTile_collapsedCodeBlock { max-height: 30vh; + padding-top: $spacing-12; + padding-bottom: $spacing-12; } /* Inserted adjacent to
 blocks, (See TextualBody) */
diff --git a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss
index 7ff3f11a50..21db71a774 100644
--- a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss
+++ b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss
@@ -46,9 +46,30 @@ limitations under the License.
 
         // model output always includes a linebreak but we do not want the user
         // to see it when writing input in lists
-        :is(ol, ul) + br:last-of-type {
+        :is(ol, ul, pre) + br:last-of-type {
             display: none;
         }
+
+        > pre {
+            font-size: $font-15px;
+            line-height: $font-24px;
+
+            margin-top: 0;
+            margin-bottom: 0;
+            padding: $spacing-8 $spacing-12;
+
+            background-color: $inlinecode-background-color;
+            border: 1px solid $inlinecode-border-color;
+            border-radius: 2px;
+        }
+
+        code {
+            font-family: $monospace-font-family !important;
+            background-color: $inlinecode-background-color;
+            border: 1px solid $inlinecode-border-color;
+            border-radius: 4px;
+            padding: $spacing-2;
+        }
     }
 
     .mx_WysiwygComposer_Editor_content_placeholder::before {
diff --git a/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss b/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss
index fa8078279f..8e3dd22c99 100644
--- a/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss
+++ b/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss
@@ -50,6 +50,12 @@ limitations under the License.
         }
     }
 
+    .mx_FormattingButtons_disabled {
+        .mx_FormattingButtons_Icon {
+            color: $quinary-content;
+        }
+    }
+
     .mx_FormattingButtons_Icon {
         --size: 16px;
         height: var(--size);
diff --git a/res/img/element-icons/room/composer/code_block.svg b/res/img/element-icons/room/composer/code_block.svg
new file mode 100644
index 0000000000..dd0be2aefc
--- /dev/null
+++ b/res/img/element-icons/room/composer/code_block.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/res/themes/dark/css/_dark.pcss b/res/themes/dark/css/_dark.pcss
index 6fd88d63a9..882ef1f005 100644
--- a/res/themes/dark/css/_dark.pcss
+++ b/res/themes/dark/css/_dark.pcss
@@ -224,6 +224,8 @@ $composer-shadow-color: rgba(0, 0, 0, 0.28);
 $breadcrumb-placeholder-bg-color: #272c35;
 $theme-button-bg-color: #e3e8f0;
 $resend-button-divider-color: rgba($header-panel-text-primary-color, 0.74);
+$inlinecode-border-color: $quinary-content;
+$inlinecode-background-color: $system;
 $codeblock-background-color: #2a3039;
 $scrollbar-thumb-color: rgba(255, 255, 255, 0.2);
 $selected-color: $room-highlight-color;
diff --git a/res/themes/legacy-dark/css/_legacy-dark.pcss b/res/themes/legacy-dark/css/_legacy-dark.pcss
index ed1ff5793b..03088f216b 100644
--- a/res/themes/legacy-dark/css/_legacy-dark.pcss
+++ b/res/themes/legacy-dark/css/_legacy-dark.pcss
@@ -190,6 +190,8 @@ $appearance-tab-border-color: $room-highlight-color;
 $composer-shadow-color: tranparent;
 
 $codeblock-background-color: #2a3039;
+$inlinecode-border-color: #2a3039;
+$inlinecode-background-color: #2a3039;
 
 /* Bubble tiles */
 $eventbubble-self-bg: #14322e;
diff --git a/res/themes/legacy-light/css/_legacy-light.pcss b/res/themes/legacy-light/css/_legacy-light.pcss
index ae52b078a7..dd152f368e 100644
--- a/res/themes/legacy-light/css/_legacy-light.pcss
+++ b/res/themes/legacy-light/css/_legacy-light.pcss
@@ -290,6 +290,8 @@ $appearance-tab-border-color: $input-darker-bg-color;
 $composer-shadow-color: tranparent;
 
 $codeblock-background-color: $header-panel-bg-color;
+$inlinecode-border-color: $header-panel-bg-color;
+$inlinecode-background-color: $header-panel-bg-color;
 
 /* Bubble tiles */
 $eventbubble-self-bg: #f0fbf8;
diff --git a/res/themes/light/css/_light.pcss b/res/themes/light/css/_light.pcss
index 4a1ae7e53a..6d59801779 100644
--- a/res/themes/light/css/_light.pcss
+++ b/res/themes/light/css/_light.pcss
@@ -295,6 +295,8 @@ $composer-shadow-color: rgba(0, 0, 0, 0.04);
 $breadcrumb-placeholder-bg-color: #e8eef5;
 $theme-button-bg-color: $quinary-content;
 $resend-button-divider-color: $input-darker-bg-color;
+$inlinecode-border-color: $quinary-content;
+$inlinecode-background-color: $system;
 $codeblock-background-color: $header-panel-bg-color;
 $scrollbar-thumb-color: rgba(0, 0, 0, 0.2);
 $selected-color: $secondary-accent-color;
diff --git a/src/Unread.ts b/src/Unread.ts
index cbd30b2bb8..6e7218cad1 100644
--- a/src/Unread.ts
+++ b/src/Unread.ts
@@ -85,7 +85,7 @@ export function doesRoomOrThreadHaveUnreadMessages(roomOrThread: Room | Thread):
     //             https://github.com/vector-im/element-web/issues/2427
     // ...and possibly some of the others at
     //             https://github.com/vector-im/element-web/issues/3363
-    if (roomOrThread.timeline.at(-1)?.getSender() === myUserId) {
+    if (roomOrThread.timeline[roomOrThread.timeline.length - 1]?.getSender() === myUserId) {
         return false;
     }
 
diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx
index 2c8f922e39..94399bf88c 100644
--- a/src/components/structures/auth/ForgotPassword.tsx
+++ b/src/components/structures/auth/ForgotPassword.tsx
@@ -262,6 +262,8 @@ export default class ForgotPassword extends React.Component {
 
         try {
             await this.reset.setNewPassword(this.state.password);
+            this.setState({ phase: Phase.Done });
+            return;
         } catch (err: any) {
             if (err.httpStatus !== 401) {
                 // 401 = waiting for email verification, else unknown error
diff --git a/src/components/views/rooms/wysiwyg_composer/components/FormattingButtons.tsx b/src/components/views/rooms/wysiwyg_composer/components/FormattingButtons.tsx
index 7c1601b441..c80857b9f1 100644
--- a/src/components/views/rooms/wysiwyg_composer/components/FormattingButtons.tsx
+++ b/src/components/views/rooms/wysiwyg_composer/components/FormattingButtons.tsx
@@ -15,7 +15,7 @@ limitations under the License.
 */
 
 import React, { MouseEventHandler, ReactNode } from "react";
-import { FormattingFunctions, AllActionStates } from "@matrix-org/matrix-wysiwyg";
+import { FormattingFunctions, AllActionStates, ActionState } from "@matrix-org/matrix-wysiwyg";
 import classNames from "classnames";
 
 import { Icon as BoldIcon } from "../../../../../../res/img/element-icons/room/composer/bold.svg";
@@ -26,6 +26,7 @@ import { Icon as InlineCodeIcon } from "../../../../../../res/img/element-icons/
 import { Icon as LinkIcon } from "../../../../../../res/img/element-icons/room/composer/link.svg";
 import { Icon as BulletedListIcon } from "../../../../../../res/img/element-icons/room/composer/bulleted_list.svg";
 import { Icon as NumberedListIcon } from "../../../../../../res/img/element-icons/room/composer/numbered_list.svg";
+import { Icon as CodeBlockIcon } from "../../../../../../res/img/element-icons/room/composer/code_block.svg";
 import AccessibleTooltipButton from "../../../elements/AccessibleTooltipButton";
 import { Alignment } from "../../../elements/Tooltip";
 import { KeyboardShortcut } from "../../../settings/KeyboardShortcut";
@@ -53,21 +54,23 @@ function Tooltip({ label, keyCombo }: TooltipProps): JSX.Element {
 
 interface ButtonProps extends TooltipProps {
     icon: ReactNode;
-    isActive: boolean;
+    actionState: ActionState;
     onClick: MouseEventHandler;
 }
 
-function Button({ label, keyCombo, onClick, isActive, icon }: ButtonProps): JSX.Element {
+function Button({ label, keyCombo, onClick, actionState, icon }: ButtonProps): JSX.Element {
     return (
          void}
             title={label}
             className={classNames("mx_FormattingButtons_Button", {
-                mx_FormattingButtons_active: isActive,
-                mx_FormattingButtons_Button_hover: !isActive,
+                mx_FormattingButtons_active: actionState === "reversed",
+                mx_FormattingButtons_Button_hover: actionState === "enabled",
+                mx_FormattingButtons_disabled: actionState === "disabled",
             })}
             tooltip={keyCombo && }
+            forceHide={actionState === "disabled"}
             alignment={Alignment.Top}
         >
             {icon}
@@ -85,53 +88,59 @@ export function FormattingButtons({ composer, actionStates }: FormattingButtonsP
     return (