diff --git a/.dockerignore b/.dockerignore index ced02564a8..70377fccdd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,3 +7,6 @@ karma-reports/ .idea/ .tmp/ config.json* +# Exclude the playwright directory as much as we can as the snapshots are huge and we bind mount it in +playwright/ +!playwright/docker-entrypoint.sh diff --git a/.eslintrc.js b/.eslintrc.js index cf034d1468..f4d92f1cf2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -198,6 +198,8 @@ module.exports = { "@typescript-eslint/ban-ts-comment": "off", // We're okay with assertion errors when we ask for them "@typescript-eslint/no-non-null-assertion": "off", + // We do this sometimes to brand interfaces + "@typescript-eslint/no-empty-object-type": "off", }, }, // temporary override for offending icon require files diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index dd793898ec..7252c27b5f 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -10,7 +10,7 @@ on: jobs: backport: name: Backport - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 # Only react to merged PRs for security reasons. # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. if: > diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 84bb3d263d..55f5c1f4a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,15 +18,15 @@ jobs: fail-fast: false matrix: image: - - ubuntu-latest - - windows-latest - - macos-latest + - ubuntu-24.04 + - windows-2022 + - macos-14 isDevelop: - ${{ github.event_name == 'push' && github.ref_name == 'develop' }} - # Skip the ubuntu-latest build for the develop branch as the dedicated CD build_develop workflow handles that + # Skip the ubuntu-24.04 build for the develop branch as the dedicated CD build_develop workflow handles that exclude: - isDevelop: true - image: ubuntu-latest + image: ubuntu-24.04 runs-on: ${{ matrix.image }} defaults: run: diff --git a/.github/workflows/build_debian.yaml b/.github/workflows/build_debian.yaml index 254b99d261..319dccd9f2 100644 --- a/.github/workflows/build_debian.yaml +++ b/.github/workflows/build_debian.yaml @@ -7,7 +7,7 @@ jobs: build: name: Build package environment: packages.element.io - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: R2_INCOMING_BUCKET: ${{ vars.R2_INCOMING_BUCKET }} R2_URL: ${{ vars.CF_R2_S3_API }} diff --git a/.github/workflows/build_develop.yml b/.github/workflows/build_develop.yml index 2407d2e6f1..b4c96c4eef 100644 --- a/.github/workflows/build_develop.yml +++ b/.github/workflows/build_develop.yml @@ -14,7 +14,7 @@ jobs: name: "Build & Deploy develop.element.io" # Only respect triggers from our develop branch, ignore that of forks if: github.repository == 'element-hq/element-web' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 environment: develop env: R2_BUCKET: "element-web-develop" diff --git a/.github/workflows/dockerhub.yaml b/.github/workflows/dockerhub.yaml index c8bec61fea..2fdc66fb33 100644 --- a/.github/workflows/dockerhub.yaml +++ b/.github/workflows/dockerhub.yaml @@ -13,7 +13,7 @@ permissions: jobs: buildx: name: Docker Buildx - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 environment: dockerhub strategy: fail-fast: false diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e0bf8a6b8e..c3f08deb1d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,7 +17,7 @@ concurrency: jobs: build: name: GitHub Pages - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Fetch element-desktop uses: actions/checkout@v4 @@ -99,7 +99,7 @@ jobs: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: build steps: - name: Deploy to GitHub Pages diff --git a/.github/workflows/issue_closed.yml b/.github/workflows/issue_closed.yml index ddcd3d12bd..191f345cc9 100644 --- a/.github/workflows/issue_closed.yml +++ b/.github/workflows/issue_closed.yml @@ -7,7 +7,7 @@ on: jobs: tidy: name: Tidy closed issues - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/github-script@v7 id: main diff --git a/.github/workflows/pending-reviews.yaml b/.github/workflows/pending-reviews.yaml index f22483547d..499da6a9b3 100644 --- a/.github/workflows/pending-reviews.yaml +++ b/.github/workflows/pending-reviews.yaml @@ -9,7 +9,7 @@ concurrency: ${{ github.workflow }} jobs: bot: name: Pending reviews bot - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 environment: Matrix env: URL: "https://github.com/pulls?q=is%3Apr+is%3Aopen+repo%3Amatrix-org%2Fmatrix-js-sdk+repo%3Amatrix-org%2Fmatrix-react-sdk+repo%3Aelement-hq%2Felement-web+repo%3Aelement-hq%2Felement-desktop+review-requested%3A%40me+sort%3Aupdated-desc+" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 799c439f73..0e8c21e786 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: notify-downstream: name: Trigger release drafter downstream needs: release - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Notify element-desktop repo that element-web release has completed to re-trigger release-drafter uses: benc-uk/workflow-dispatch@e2e5e9a103e331dad343f381a29e654aea3cf8fc # v1 @@ -41,7 +41,7 @@ jobs: check: name: Post release checks needs: release - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Wait for dockerhub uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork diff --git a/.github/workflows/release_prepare.yml b/.github/workflows/release_prepare.yml index f88e10493d..ce088a9327 100644 --- a/.github/workflows/release_prepare.yml +++ b/.github/workflows/release_prepare.yml @@ -19,7 +19,7 @@ on: default: true jobs: prepare: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout Element Desktop uses: actions/checkout@v4 diff --git a/.github/workflows/static_analysis.yaml b/.github/workflows/static_analysis.yaml index 470e072892..536f78e18d 100644 --- a/.github/workflows/static_analysis.yaml +++ b/.github/workflows/static_analysis.yaml @@ -19,7 +19,7 @@ env: jobs: ts_lint: name: "Typescript Syntax Check" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -81,7 +81,7 @@ jobs: js_lint: name: "ESLint" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -99,7 +99,7 @@ jobs: style_lint: name: "Style Lint" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -117,7 +117,7 @@ jobs: workflow_lint: name: "Workflow Lint" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -135,7 +135,7 @@ jobs: analyse_dead_code: name: "Analyse Dead Code" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/triage-assigned.yml b/.github/workflows/triage-assigned.yml index 98112d556f..81d1dff80f 100644 --- a/.github/workflows/triage-assigned.yml +++ b/.github/workflows/triage-assigned.yml @@ -6,7 +6,7 @@ on: jobs: web-app-team: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: | contains(github.event.issue.assignees.*.login, 't3chguy') || contains(github.event.issue.assignees.*.login, 'andybalaam') || diff --git a/.github/workflows/triage-incoming.yml b/.github/workflows/triage-incoming.yml index 47ed03efe3..e63017dc3b 100644 --- a/.github/workflows/triage-incoming.yml +++ b/.github/workflows/triage-incoming.yml @@ -6,7 +6,7 @@ on: jobs: automate-project-columns: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/add-to-project@main with: diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index 0ee40b987f..0112f180c1 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -11,7 +11,7 @@ on: jobs: apply_Z-Labs_label: name: Add Z-Labs label for features behind labs flags - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > contains(github.event.issue.labels.*.name, 'A-Maths') || contains(github.event.issue.labels.*.name, 'A-Location-Sharing') || @@ -37,7 +37,7 @@ jobs: apply_Help-Wanted_label: name: Add "Help Wanted" label to all "good first issue" and Hacktoberfest - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > contains(github.event.issue.labels.*.name, 'good first issue') || contains(github.event.issue.labels.*.name, 'Hacktoberfest') @@ -54,7 +54,7 @@ jobs: move_needs_info_issues: name: X-Needs-Info issues to Need info column on triage board - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > contains(github.event.issue.labels.*.name, 'X-Needs-Info') steps: @@ -77,7 +77,7 @@ jobs: move_flakey_test_issues: name: Z-Flaky-Test issues to Sized for maintainer column on triage board - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > contains(github.event.issue.labels.*.name, 'Z-Flaky-Test') steps: @@ -100,7 +100,7 @@ jobs: add_priority_design_issues_to_project: name: P1 X-Needs-Design to Design project board - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > contains(github.event.issue.labels.*.name, 'X-Needs-Design') && (contains(github.event.issue.labels.*.name, 'S-Critical') && @@ -117,7 +117,7 @@ jobs: add_product_issues: name: X-Needs-Product to product project board - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > contains(github.event.issue.labels.*.name, 'X-Needs-Product') steps: @@ -128,7 +128,7 @@ jobs: Search_issues_to_board: name: Search issues to project board - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > contains(github.event.issue.labels.*.name, 'A-New-Search-Experience') steps: @@ -139,7 +139,7 @@ jobs: voip: name: Add labelled issues to VoIP project board - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > contains(github.event.issue.labels.*.name, 'Team: VoIP') steps: @@ -150,7 +150,7 @@ jobs: verticals_feature: name: Add labelled issues to Verticals Feature project - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > contains(github.event.issue.labels.*.name, 'Team: Verticals Feature') steps: @@ -161,7 +161,7 @@ jobs: tech_debt: name: Add labelled issues to tech debt project - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > contains(github.event.issue.labels.*.name, 'A-Developer-Experience') || contains(github.event.issue.labels.*.name, 'A-Documentation') || diff --git a/.github/workflows/triage-move-review-requests.yml b/.github/workflows/triage-move-review-requests.yml index 9d2924c1a9..72d9786a4a 100644 --- a/.github/workflows/triage-move-review-requests.yml +++ b/.github/workflows/triage-move-review-requests.yml @@ -6,7 +6,7 @@ on: jobs: add_design_pr_to_project: name: Move PRs asking for design review to the design board - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: octokit/graphql-action@v2.x id: find_team_members @@ -73,7 +73,7 @@ jobs: add_product_pr_to_project: name: Move PRs asking for design review to the design board - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: octokit/graphql-action@v2.x id: find_team_members diff --git a/.github/workflows/triage-stale-flaky-tests.yml b/.github/workflows/triage-stale-flaky-tests.yml index ab6693675f..d339a136cd 100644 --- a/.github/workflows/triage-stale-flaky-tests.yml +++ b/.github/workflows/triage-stale-flaky-tests.yml @@ -4,7 +4,7 @@ on: - cron: "30 1 * * *" jobs: close: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: actions: write issues: write diff --git a/.github/workflows/triage-unlabelled.yml b/.github/workflows/triage-unlabelled.yml index 59d1bf471d..1cd1c80afc 100644 --- a/.github/workflows/triage-unlabelled.yml +++ b/.github/workflows/triage-unlabelled.yml @@ -7,7 +7,7 @@ on: jobs: Move_Unabeled_Issue_On_Project_Board: name: Move no longer X-Needs-Info issues to Triaged - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > ${{ !contains(github.event.issue.labels.*.name, 'X-Needs-Info') }} @@ -44,7 +44,7 @@ jobs: remove_Z-Labs_label: name: Remove Z-Labs label when features behind labs flags are removed - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: > !(contains(github.event.issue.labels.*.name, 'A-Maths') || contains(github.event.issue.labels.*.name, 'A-Message-Pinning') || diff --git a/.github/workflows/update-jitsi.yml b/.github/workflows/update-jitsi.yml index 71405177d2..68dbf22e63 100644 --- a/.github/workflows/update-jitsi.yml +++ b/.github/workflows/update-jitsi.yml @@ -6,7 +6,7 @@ on: - cron: "0 3 * * 0" # 3am every Sunday jobs: update: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/update-topics.yaml b/.github/workflows/update-topics.yaml index b24c84ad26..a984fc4f03 100644 --- a/.github/workflows/update-topics.yaml +++ b/.github/workflows/update-topics.yaml @@ -18,7 +18,7 @@ concurrency: ${{ github.workflow }} jobs: bot: name: Release topic update - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 environment: Matrix steps: - uses: actions/github-script@v7 @@ -56,7 +56,7 @@ jobs: headers, body: "{}", }); - + let res = await fetch(apiUrl, { method: "GET", headers, @@ -69,7 +69,7 @@ jobs: const data = await res.json(); console.log(roomId, "got event", data); - + const topic = data.topic.replace(regex, releaseTopic); if (topic === data.topic) { console.log(roomId, "nothing to do"); @@ -89,7 +89,7 @@ jobs: }), headers, }); - + if (res.ok) { console.log(roomId, "topic updated:", topic); } else { diff --git a/package.json b/package.json index b57457d59f..482067aa22 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "test:playwright:open": "yarn test:playwright --ui", "test:playwright:screenshots": "yarn test:playwright:screenshots:build && yarn test:playwright:screenshots:run", "test:playwright:screenshots:build": "docker build playwright -t element-web-playwright", - "test:playwright:screenshots:run": "docker run --rm --network host -e BASE_URL -e CI -v $(pwd):/work/ -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/:/tmp/ -it element-web-playwright", + "test:playwright:screenshots:run": "docker run --rm --network host -e BASE_URL -e CI -v $(pwd):/work/ -v $(node -e 'console.log(require(`path`).dirname(require.resolve(`matrix-js-sdk/package.json`)))'):/work/node_modules/matrix-js-sdk -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/:/tmp/ -it element-web-playwright", "coverage": "yarn test --coverage", "analyse:unused-exports": "ts-node ./scripts/analyse_unused_exports.ts", "analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp", @@ -77,7 +77,7 @@ "@types/react-dom": "17.0.25", "@types/react": "17.0.83", "@types/seedrandom": "3.0.8", - "oidc-client-ts": "3.0.1", + "oidc-client-ts": "3.1.0", "jwt-decode": "4.0.0", "@vector-im/compound-design-tokens": "1.8.0", "@vector-im/compound-web": "7.0.0", @@ -115,7 +115,7 @@ "emojibase-regex": "15.3.2", "escape-html": "^1.0.3", "file-saver": "^2.0.5", - "filesize": "10.1.4", + "filesize": "10.1.6", "github-markdown-css": "^5.5.1", "glob-to-regexp": "^0.4.1", "highlight.js": "^11.3.1", @@ -142,7 +142,7 @@ "png-chunks-extract": "^1.0.0", "posthog-js": "1.157.2", "qrcode": "1.5.4", - "re-resizable": "^6.9.0", + "re-resizable": "6.9.17", "react": "17.0.2", "react-beautiful-dnd": "^13.1.0", "react-blurhash": "^0.3.0", @@ -151,7 +151,7 @@ "react-transition-group": "^4.4.1", "rfc4648": "^1.4.0", "sanitize-filename": "^1.6.3", - "sanitize-html": "2.13.0", + "sanitize-html": "2.13.1", "tar-js": "^0.3.0", "temporal-polyfill": "^0.2.5", "ua-parser-js": "^1.0.2", @@ -186,6 +186,7 @@ "@playwright/test": "^1.40.1", "@principalstudio/html-webpack-inject-preload": "^1.2.7", "@sentry/webpack-plugin": "^2.7.1", + "@stylistic/eslint-plugin": "^2.9.0", "@svgr/webpack": "^8.0.0", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", @@ -223,8 +224,8 @@ "@types/tar-js": "^0.3.5", "@types/ua-parser-js": "^0.7.36", "@types/uuid": "^10.0.0", - "@typescript-eslint/eslint-plugin": "^7.0.0", - "@typescript-eslint/parser": "^7.0.0", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", "axe-core": "4.10.0", "babel-jest": "^29.0.0", "babel-loader": "^9.0.0", @@ -246,7 +247,7 @@ "eslint-plugin-import": "^2.25.4", "eslint-plugin-jest": "^28.0.0", "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-matrix-org": "1.2.1", + "eslint-plugin-matrix-org": "^2.0.2", "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-hooks": "^4.3.0", "eslint-plugin-unicorn": "^56.0.0", diff --git a/playwright.config.ts b/playwright.config.ts index b0fd1ef99e..1c1d380042 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -26,6 +26,7 @@ export default defineConfig({ command: process.env.CI ? "npx serve -p 8080 -L ./webapp" : "yarn start", url: `${baseURL}/config.json`, reuseExistingServer: true, + timeout: (process.env.CI ? 30 : 120) * 1000, }, testDir: "playwright/e2e", outputDir: "playwright/test-results", diff --git a/playwright/e2e/accessibility/keyboard-navigation.spec.ts b/playwright/e2e/accessibility/keyboard-navigation.spec.ts index 18983bde01..87cee4e05c 100644 --- a/playwright/e2e/accessibility/keyboard-navigation.spec.ts +++ b/playwright/e2e/accessibility/keyboard-navigation.spec.ts @@ -15,6 +15,9 @@ test.describe("Landmark navigation tests", () => { }); test("without any rooms", async ({ page, homeserver, app, user }) => { + // sometimes the space button doesn't appear right away + await expect(page.locator(".mx_SpaceButton_active")).toBeVisible(); + /** * Without any rooms, there is no tile in the roomlist to be focused. * So the next landmark in the list should be focused instead. diff --git a/playwright/e2e/chat-export/html-export.spec.ts b/playwright/e2e/chat-export/html-export.spec.ts index 0e12e8d93e..9a66a4907a 100644 --- a/playwright/e2e/chat-export/html-export.spec.ts +++ b/playwright/e2e/chat-export/html-export.spec.ts @@ -96,7 +96,10 @@ test.describe("HTML Export", () => { // Send a bunch of messages to populate the room for (let i = 1; i < 10; i++) { - await app.client.sendMessage(room.roomId, { body: `Testing ${i}`, msgtype: "m.text" }); + const respone = await app.client.sendMessage(room.roomId, { body: `Testing ${i}`, msgtype: "m.text" }); + if (i == 1) { + await app.client.reactToMessage(room.roomId, null, respone.event_id, "🙃"); + } } // Wait for all the messages to be displayed diff --git a/playwright/e2e/login/login.spec.ts b/playwright/e2e/login/login.spec.ts index 696ecb183c..e1307f7402 100644 --- a/playwright/e2e/login/login.spec.ts +++ b/playwright/e2e/login/login.spec.ts @@ -217,7 +217,7 @@ test.describe("Login", () => { const h1 = await page.getByRole("heading", { name: "Verify this device", level: 1 }); await expect(h1).toBeVisible(); - expect(h1.locator(".mx_CompleteSecurity_skip")).not.toBeVisible(); + await expect(h1.locator(".mx_CompleteSecurity_skip")).toHaveCount(0); }); }); }); diff --git a/playwright/e2e/read-receipts/index.ts b/playwright/e2e/read-receipts/index.ts index 2db4bcbecf..49a0195fd7 100644 --- a/playwright/e2e/read-receipts/index.ts +++ b/playwright/e2e/read-receipts/index.ts @@ -69,7 +69,7 @@ export class MessageBuilder { /** * Map of message content -> event. */ - messages = new Map>>(); + messages = new Map>>(); /** * Utility to find a MatrixEvent by its body content @@ -222,14 +222,7 @@ export class MessageBuilder { threadId: !ev.isThreadRoot ? ev.threadRootId : undefined, })); const roomId = await room.evaluate((room) => room.roomId); - - await bot.sendEvent(roomId, threadId ?? null, "m.reaction", { - "m.relates_to": { - rel_type: "m.annotation", - event_id: id, - key: reaction, - }, - }); + await bot.reactToMessage(roomId, threadId, id, reaction); } })(this); } diff --git a/playwright/e2e/settings/account-user-settings-tab.spec.ts b/playwright/e2e/settings/account-user-settings-tab.spec.ts index ce54995d45..5492094f93 100644 --- a/playwright/e2e/settings/account-user-settings-tab.spec.ts +++ b/playwright/e2e/settings/account-user-settings-tab.spec.ts @@ -140,4 +140,51 @@ test.describe("Account user settings tab", () => { await expect(page.locator(".mx_UserMenu .mx_BaseAvatar").getByText("A")).toBeVisible(); // Alice await expect(page.locator(".mx_RoomView_wrapper .mx_BaseAvatar").getByText("A")).toBeVisible(); // Alice }); + + // ported to a playwright test because the jest test was very flakey for no obvious reason + test("should display an error if the code is incorrect when adding a phone number", async ({ uut, page }) => { + const dummyUrl = "https://nowhere.dummy/_matrix/client/unstable/add_threepid/msisdn/submit_token"; + + await page.route( + `**/_matrix/client/v3/account/3pid/msisdn/requestToken`, + async (route) => { + await route.fulfill({ + json: { + success: true, + sid: "1", + msisdn: "447700900000", + intl_fmt: "+44 7700 900000", + submit_url: dummyUrl, + }, + }); + }, + { times: 1 }, + ); + + await page.route( + dummyUrl, + async (route) => { + await route.fulfill({ + status: 400, + json: { + errcode: "M_THREEPID_AUTH_FAILED", + error: "That code is definitely wrong", + }, + }); + }, + { times: 1 }, + ); + + const phoneSection = page.getByTestId("mx_AccountPhoneNumbers"); + await phoneSection.getByRole("textbox", { name: "Phone Number" }).fill("07700900000"); + await phoneSection.getByRole("button", { name: "Add" }).click(); + + await phoneSection + .getByRole("textbox", { name: "Verification code" }) + .fill("A small eurasian field mouse dancing the paso doble"); + + await phoneSection.getByRole("button", { name: "Continue" }).click(); + + await expect(page.getByRole("heading", { name: "Unable to verify phone number." })).toBeVisible(); + }); }); diff --git a/playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts b/playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts index 4c8f3b3c09..4f3b75b5ba 100644 --- a/playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts +++ b/playwright/e2e/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts @@ -74,7 +74,7 @@ test.describe("Appearance user settings tab", () => { await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-custom-theme-added.png"); await util.removeCustomTheme(); - await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-custom-theme.png"); + await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-custom-theme-removed.png"); }); }); }); diff --git a/playwright/e2e/spaces/threads-activity-centre/index.ts b/playwright/e2e/spaces/threads-activity-centre/index.ts index bf05fc34c1..f6ac7f95d6 100644 --- a/playwright/e2e/spaces/threads-activity-centre/index.ts +++ b/playwright/e2e/spaces/threads-activity-centre/index.ts @@ -70,7 +70,7 @@ export class MessageBuilder { /** * Map of message content -> event. */ - messages = new Map>>(); + messages = new Map>>(); /** * Utility to find a MatrixEvent by its body content diff --git a/playwright/pages/client.ts b/playwright/pages/client.ts index 7d62f42ae0..2dfe7484f5 100644 --- a/playwright/pages/client.ts +++ b/playwright/pages/client.ts @@ -143,6 +143,29 @@ export class Client { ); } + /** + * Send a reaction to to a message + * @param roomId ID of the room to send the reaction into + * @param threadId ID of the thread to send into or null for main timeline + * @param eventId Event ID of the message you are reacting to + * @param reaction The reaction text to send + * @returns + */ + public async reactToMessage( + roomId: string, + threadId: string | null, + eventId: string, + reaction: string, + ): Promise { + return this.sendEvent(roomId, threadId ?? null, "m.reaction", { + "m.relates_to": { + rel_type: "m.annotation", + event_id: eventId, + key: reaction, + }, + }); + } + /** * Create a room with given options. * @param options the options to apply when creating the room diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index fd1aa44068..cb4882f1b9 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -20,7 +20,7 @@ import { randB64Bytes } from "../../utils/rand"; // Docker tag to use for synapse docker image. // We target a specific digest as every now and then a Synapse update will break our CI. // This digest is updated by the playwright-image-updates.yaml workflow periodically. -const DOCKER_TAG = "develop@sha256:fd6ba2d8471a0807e1bccef4124b22d17f0058f2cf9285066fdd94d8c631964a"; +const DOCKER_TAG = "develop@sha256:47c62aa9507a24820190eef547861c0d278cc83fe90329c46b9f4329eed88ef4"; async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); diff --git a/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png b/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png index 9d301e7919..6a490c2157 100644 Binary files a/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png and b/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png differ diff --git a/playwright/snapshots/permalinks/permalinks.spec.ts/permalink-rendering-linux.png b/playwright/snapshots/permalinks/permalinks.spec.ts/permalink-rendering-linux.png index 1c88bb4c1f..0ec66fa4ef 100644 Binary files a/playwright/snapshots/permalinks/permalinks.spec.ts/permalink-rendering-linux.png and b/playwright/snapshots/permalinks/permalinks.spec.ts/permalink-rendering-linux.png differ diff --git a/playwright/snapshots/polls/polls.spec.ts/Polls-Timeline-tile-no-votes-linux.png b/playwright/snapshots/polls/polls.spec.ts/Polls-Timeline-tile-no-votes-linux.png index afe3331d2e..7e1195c82d 100644 Binary files a/playwright/snapshots/polls/polls.spec.ts/Polls-Timeline-tile-no-votes-linux.png and b/playwright/snapshots/polls/polls.spec.ts/Polls-Timeline-tile-no-votes-linux.png differ diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png index 76a9308b35..bceaa4a283 100644 Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png differ diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-removed-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-removed-linux.png new file mode 100644 index 0000000000..cdafb6e9ee Binary files /dev/null and b/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/theme-panel-custom-theme-removed-linux.png differ diff --git a/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png b/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png index 4652833628..d8e7d62819 100644 Binary files a/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png and b/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png index 4745d81d22..6e8ffeee8e 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/event-line-inline-start-margin-irc-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png index 273be0a269..d69fca4738 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-and-messages-irc-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png index 594ac521e0..5701a63fcf 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-bubble-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png index 1c83d343d0..c2e8afbd7b 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-emote-irc-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png index 4745d81d22..6e8ffeee8e 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-irc-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png index c69649ed0e..1defab7208 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-modern-layout-linux.png differ diff --git a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png index 78ecc64c71..2015220013 100644 Binary files a/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png and b/playwright/snapshots/timeline/timeline.spec.ts/expanded-gels-redaction-placeholder-linux.png differ diff --git a/res/css/structures/_GenericDropdownMenu.pcss b/res/css/structures/_GenericDropdownMenu.pcss index f75516ce7e..bf0098b4ed 100644 --- a/res/css/structures/_GenericDropdownMenu.pcss +++ b/res/css/structures/_GenericDropdownMenu.pcss @@ -99,10 +99,10 @@ Please see LICENSE files in the repository root for full details. &[aria-checked="true"]::before { content: ""; - width: 12px; - height: 12px; - margin-left: -20px; - margin-right: 8px; + width: 16px; + height: 16px; + margin-left: -22px; + margin-right: 6px; mask-image: url("@vector-im/compound-design-tokens/icons/check.svg"); mask-size: 100%; mask-repeat: no-repeat; diff --git a/res/css/views/elements/_ImageView.pcss b/res/css/views/elements/_ImageView.pcss index d2355378a5..03041fe255 100644 --- a/res/css/views/elements/_ImageView.pcss +++ b/res/css/views/elements/_ImageView.pcss @@ -130,6 +130,7 @@ $button-gap: 24px; .mx_ImageView_button_more::before { mask-image: url("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg"); + mask-size: 28px; } .mx_ImageView_button_close { diff --git a/res/css/views/messages/_MessageActionBar.pcss b/res/css/views/messages/_MessageActionBar.pcss index b8333defdf..4fe68f08d0 100644 --- a/res/css/views/messages/_MessageActionBar.pcss +++ b/res/css/views/messages/_MessageActionBar.pcss @@ -122,4 +122,8 @@ Please see LICENSE files in the repository root for full details. --MessageActionBar-icon-size: 12px; } } + + .mx_MessageActionBar_optionsButton { + --MessageActionBar-icon-size: 22px; + } } diff --git a/res/css/views/rooms/_MessageComposer.pcss b/res/css/views/rooms/_MessageComposer.pcss index 8acbb0be40..d176a0d627 100644 --- a/res/css/views/rooms/_MessageComposer.pcss +++ b/res/css/views/rooms/_MessageComposer.pcss @@ -278,6 +278,7 @@ Please see LICENSE files in the repository root for full details. .mx_MessageComposer_buttonMenu::before { mask-image: url("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg"); + mask-size: 24px; } .mx_MessageComposer_sendMessage { @@ -295,7 +296,7 @@ Please see LICENSE files in the repository root for full details. top: 8px; left: 9px; - mask-image: url("@vector-im/compound-design-tokens/icons/send-solid.svg"); + mask-image: url("$(res)/img/element-icons/send-message.svg"); mask-repeat: no-repeat; mask-size: contain; mask-position: center; diff --git a/src/@types/common.ts b/src/@types/common.ts index d0ab03221d..1331ba92b5 100644 --- a/src/@types/common.ts +++ b/src/@types/common.ts @@ -35,6 +35,7 @@ export type Defaultize = P extends any Partial>> : never; +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ export type DeepReadonly = T extends (infer R)[] ? DeepReadonlyArray : T extends Function @@ -42,6 +43,7 @@ export type DeepReadonly = T extends (infer R)[] : T extends object ? DeepReadonlyObject : T; +/* eslint-enable @typescript-eslint/no-unsafe-function-type */ interface DeepReadonlyArray extends ReadonlyArray> {} diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 20e9e7add3..8bbad339c7 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -129,7 +129,7 @@ export default abstract class BasePlatform { try { const [version, deferUntil] = JSON.parse(localStorage.getItem(UPDATE_DEFER_KEY)!); return newVersion !== version || Date.now() > deferUntil; - } catch (e) { + } catch { return true; } } @@ -380,7 +380,7 @@ export default abstract class BasePlatform { try { await idbSave("pickleKey", [userId, deviceId], data); - } catch (e) { + } catch { return null; } return encodeUnpaddedBase64(randomArray); diff --git a/src/DraftCleaner.ts b/src/DraftCleaner.ts index eebc7b87d0..48a3523f61 100644 --- a/src/DraftCleaner.ts +++ b/src/DraftCleaner.ts @@ -48,7 +48,7 @@ function shouldCleanupDrafts(): boolean { return true; } return Date.now() > parsedTimestamp + DRAFT_CLEANUP_PERIOD; - } catch (error) { + } catch { return true; } } diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 8b0b73c692..3146a9eb07 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -52,7 +52,7 @@ export const EMOJI_REGEX = (() => { // want the app to completely crash on older platforms. We use the // constructor here to avoid a syntax error on such platforms. return new RegExp("\\p{RGI_Emoji}(?!\\uFE0E)(?:(? { const BIGEMOJI_REGEX = (() => { try { return new RegExp(`^(${EMOJI_REGEX.source})+$`, "iv"); - } catch (_e) { + } catch { // Fall back, just like for EMOJI_REGEX return /(?!)/; } @@ -120,7 +120,7 @@ export function isUrlPermitted(inputUrl: string): boolean { try { // URL parser protocol includes the trailing colon return PERMITTED_URL_SCHEMES.includes(new URL(inputUrl).protocol.slice(0, -1)); - } catch (e) { + } catch { return false; } } diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx index ae14b31aed..a06480e9cd 100644 --- a/src/LegacyCallHandler.tsx +++ b/src/LegacyCallHandler.tsx @@ -827,7 +827,7 @@ export default class LegacyCallHandler extends EventEmitter { try { this.addCallForRoom(roomId, call); - } catch (e) { + } catch { Modal.createDialog(ErrorDialog, { title: _t("voip|already_in_call"), description: _t("voip|already_in_call_person"), diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index 0ba2ec65d7..9804ab5d82 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -211,7 +211,7 @@ class MatrixClientPegClass implements IMatrixClientPeg { const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time")!, 10); const diff = Date.now() - registrationTime; return diff / 36e5 <= hours; - } catch (e) { + } catch { return false; } } @@ -220,7 +220,7 @@ class MatrixClientPegClass implements IMatrixClientPeg { try { const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time")!, 10); return timestamp.getTime() <= registrationTime; - } catch (e) { + } catch { return false; } } diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts index 78d53e959d..28e37282a5 100644 --- a/src/PosthogAnalytics.ts +++ b/src/PosthogAnalytics.ts @@ -236,7 +236,7 @@ export class PosthogAnalytics { let appVersion: string | undefined; try { appVersion = await platform?.getAppVersion(); - } catch (e) { + } catch { // this happens if no version is set i.e. in dev appVersion = "unknown"; } diff --git a/src/Presence.ts b/src/Presence.ts index 3326dac96f..11a333ce04 100644 --- a/src/Presence.ts +++ b/src/Presence.ts @@ -36,7 +36,7 @@ class Presence { try { await this.unavailableTimer.finished(); this.setState(SetPresence.Unavailable); - } catch (e) { + } catch { /* aborted, stop got called */ } } diff --git a/src/RoomNotifs.ts b/src/RoomNotifs.ts index cc027c9682..7c281d0d2d 100644 --- a/src/RoomNotifs.ts +++ b/src/RoomNotifs.ts @@ -44,7 +44,7 @@ export function getRoomNotifsState(client: MatrixClient, roomId: string): RoomNo let roomRule: IPushRule | undefined; try { roomRule = client.getRoomPushRule("global", roomId); - } catch (err) { + } catch { // Possible that the client doesn't have pushRules yet. If so, it // hasn't started either, so indicate that this room is not notifying. return null; diff --git a/src/ScalarMessaging.ts b/src/ScalarMessaging.ts index d8092953f3..cac5b561a0 100644 --- a/src/ScalarMessaging.ts +++ b/src/ScalarMessaging.ts @@ -855,14 +855,14 @@ const onMessage = function (event: MessageEvent): void { try { if (!openManagerUrl) openManagerUrl = IntegrationManagers.sharedInstance().getPrimaryManager()?.uiUrl; configUrl = new URL(openManagerUrl!); - } catch (e) { + } catch { // No integrations UI URL, ignore silently. return; } let eventOriginUrl: URL; try { eventOriginUrl = new URL(event.origin); - } catch (e) { + } catch { return; } // TODO -- Scalar postMessage API should be namespaced with event.data.api field diff --git a/src/SdkConfig.ts b/src/SdkConfig.ts index abf9933e2b..cbec03381a 100644 --- a/src/SdkConfig.ts +++ b/src/SdkConfig.ts @@ -42,6 +42,7 @@ export const DEFAULTS: DeepReadonly = { // be preferred over their config. desktopBuilds: { available: true, + // eslint-disable-next-line @typescript-eslint/no-require-imports logo: require("../res/img/element-desktop-logo.svg").default, url: "https://element.io/get-started", }, diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 7375cba568..02c2b33849 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -727,7 +727,7 @@ export const Commands = [ isEnabled: (cli) => !isCurrentLocalRoom(cli), runFn: function (cli, roomId) { try { - cli.forceDiscardSession(roomId); + cli.getCrypto()?.forceDiscardSession(roomId); } catch (e) { return reject(e instanceof Error ? e.message : e); } diff --git a/src/SlidingSyncManager.ts b/src/SlidingSyncManager.ts index 578a175749..eeac10b603 100644 --- a/src/SlidingSyncManager.ts +++ b/src/SlidingSyncManager.ts @@ -252,7 +252,7 @@ export class SlidingSyncManager { try { // wait until the next sync before returning as RoomView may need to know the current state await p; - } catch (err) { + } catch { logger.warn("SlidingSync setRoomVisible:", roomId, visible, "failed to confirm transaction"); } return roomId; @@ -305,7 +305,7 @@ export class SlidingSyncManager { } else { await this.slidingSync!.setListRanges(SlidingSyncManager.ListSearch, ranges); } - } catch (err) { + } catch { // do nothing, as we reject only when we get interrupted but that's fine as the next // request will include our data } finally { @@ -357,7 +357,7 @@ export class SlidingSyncManager { } const clientWellKnown = await AutoDiscovery.findClientConfig(clientDomain); proxyUrl = clientWellKnown?.["org.matrix.msc3575.proxy"]?.url; - } catch (e) { + } catch { // Either client.getDomain() is null so we've shorted out, or is invalid so `AutoDiscovery.findClientConfig` has thrown } diff --git a/src/UserActivity.ts b/src/UserActivity.ts index 61f3c93ca9..a1a090ca0e 100644 --- a/src/UserActivity.ts +++ b/src/UserActivity.ts @@ -213,7 +213,7 @@ export default class UserActivity { attachedTimers.forEach((t) => t.start()); try { await timeout.finished(); - } catch (_e) { + } catch { /* aborted */ } attachedTimers.forEach((t) => t.abort()); diff --git a/src/async-components/views/dialogs/security/NewRecoveryMethodDialog.tsx b/src/async-components/views/dialogs/security/NewRecoveryMethodDialog.tsx index ba08bcad23..ac18039749 100644 --- a/src/async-components/views/dialogs/security/NewRecoveryMethodDialog.tsx +++ b/src/async-components/views/dialogs/security/NewRecoveryMethodDialog.tsx @@ -7,10 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ -import React from "react"; -import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api"; +import React, { JSX, useEffect, useState } from "react"; -import { MatrixClientPeg } from "../../../../MatrixClientPeg"; import dis from "../../../../dispatcher/dispatcher"; import { _t } from "../../../../languageHandler"; import Modal from "../../../../Modal"; @@ -18,81 +16,73 @@ import RestoreKeyBackupDialog from "../../../../components/views/dialogs/securit import { Action } from "../../../../dispatcher/actions"; import DialogButtons from "../../../../components/views/elements/DialogButtons"; import BaseDialog from "../../../../components/views/dialogs/BaseDialog"; +import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext.tsx"; -interface IProps { - newVersionInfo: KeyBackupInfo; +/** + * Properties for {@link NewRecoveryMethodDialog}. + */ +interface NewRecoveryMethodDialogProps { + /** + * Callback when the dialog is dismissed. + */ onFinished(): void; } -export default class NewRecoveryMethodDialog extends React.PureComponent { - private onOkClick = (): void => { - this.props.onFinished(); - }; +// Export as default instead of a named export so that it can be dynamically imported with `Modal.createDialogAsync` - private onGoToSettingsClick = (): void => { - this.props.onFinished(); - dis.fire(Action.ViewUserSettings); - }; +/** + * Dialog to inform the user that a new recovery method has been detected. + */ +export default function NewRecoveryMethodDialog({ onFinished }: NewRecoveryMethodDialogProps): JSX.Element { + const matrixClient = useMatrixClientContext(); + const [isKeyBackupEnabled, setIsKeyBackupEnabled] = useState(false); + useEffect(() => { + const checkBackupEnabled = async (): Promise => { + const crypto = matrixClient.getCrypto(); + setIsKeyBackupEnabled(Boolean(crypto && (await crypto.getActiveSessionBackupVersion()) !== null)); + }; - private onSetupClick = async (): Promise => { - Modal.createDialog( - RestoreKeyBackupDialog, - { - onFinished: this.props.onFinished, - }, - undefined, - /* priority = */ false, - /* static = */ true, - ); - }; + checkBackupEnabled(); + }, [matrixClient]); - public render(): React.ReactNode { - const title = ( - - {_t("encryption|new_recovery_method_detected|title")} - - ); - - const newMethodDetected =

{_t("encryption|new_recovery_method_detected|description_1")}

; - - const hackWarning = ( - {_t("encryption|new_recovery_method_detected|warning")} - ); - - let content: JSX.Element | undefined; - if (MatrixClientPeg.safeGet().getKeyBackupEnabled()) { - content = ( -
- {newMethodDetected} -

{_t("encryption|new_recovery_method_detected|description_2")}

- {hackWarning} - -
- ); + function onClick(): void { + if (isKeyBackupEnabled) { + onFinished(); } else { - content = ( -
- {newMethodDetected} - {hackWarning} - -
+ Modal.createDialog( + RestoreKeyBackupDialog, + { + onFinished, + }, + undefined, + false, + true, ); } - - return ( - - {content} - - ); } + + return ( + + {_t("encryption|new_recovery_method_detected|title")} + + } + > +

{_t("encryption|new_recovery_method_detected|description_1")}

+ {isKeyBackupEnabled &&

{_t("encryption|new_recovery_method_detected|description_2")}

} + {_t("encryption|new_recovery_method_detected|warning")} + { + onFinished(); + dis.fire(Action.ViewUserSettings); + }} + /> +
+ ); } diff --git a/src/components/structures/FileDropTarget.tsx b/src/components/structures/FileDropTarget.tsx index b815d9aa62..5bc16e2ee4 100644 --- a/src/components/structures/FileDropTarget.tsx +++ b/src/components/structures/FileDropTarget.tsx @@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details. import React, { useEffect, useState } from "react"; import { _t } from "../../languageHandler"; +import UploadBigSvg from "../../../res/img/upload-big.svg"; interface IProps { parent: HTMLElement | null; @@ -106,11 +107,7 @@ const FileDropTarget: React.FC = ({ parent, onFileDrop }) => { if (state.dragging) { return (
- + {_t("room|drop_file_prompt")}
); diff --git a/src/components/structures/GenericDropdownMenu.tsx b/src/components/structures/GenericDropdownMenu.tsx index 3e3d415371..b323d9b8ad 100644 --- a/src/components/structures/GenericDropdownMenu.tsx +++ b/src/components/structures/GenericDropdownMenu.tsx @@ -106,6 +106,10 @@ type IProps = WithKeyFunction & { AdditionalOptions?: FunctionComponent; }; +function calculateKey(value: T, toKey: ((key: T) => Key) | undefined): Key { + return toKey ? toKey(value) : (value as Key); +} + export function GenericDropdownMenu({ value, onChange, @@ -119,23 +123,24 @@ export function GenericDropdownMenu({ }: IProps): JSX.Element { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); + const valueKey = calculateKey(value, toKey); const selected: GenericDropdownMenuItem | undefined = options .flatMap((it) => (isGenericDropdownMenuGroup(it) ? [it, ...it.options] : [it])) - .find((option) => (toKey ? toKey(option.key) === toKey(value) : option.key === value)); + .find((option) => calculateKey(option.key, toKey) === valueKey); let contextMenuOptions: JSX.Element; if (options && isGenericDropdownMenuGroupArray(options)) { contextMenuOptions = ( <> {options.map((group) => ( {group.options.map((option) => ( { @@ -144,7 +149,7 @@ export function GenericDropdownMenu({ onClose?.(ev); }} adornment={option.adornment} - isSelected={option === selected} + isSelected={calculateKey(option.key, toKey) === valueKey} /> ))} @@ -156,7 +161,7 @@ export function GenericDropdownMenu({ <> {options.map((option) => ( { @@ -165,7 +170,7 @@ export function GenericDropdownMenu({ onClose?.(ev); }} adornment={option.adornment} - isSelected={option === selected} + isSelected={calculateKey(option.key, toKey) === valueKey} /> ))} diff --git a/src/components/structures/LegacyCallEventGrouper.ts b/src/components/structures/LegacyCallEventGrouper.ts index d5c5a5a0fe..3eefbdbb87 100644 --- a/src/components/structures/LegacyCallEventGrouper.ts +++ b/src/components/structures/LegacyCallEventGrouper.ts @@ -161,9 +161,11 @@ export default class LegacyCallEventGrouper extends EventEmitter { public toggleSilenced = (): void => { const silenced = LegacyCallHandler.instance.isCallSilenced(this.callId); - silenced - ? LegacyCallHandler.instance.unSilenceCall(this.callId) - : LegacyCallHandler.instance.silenceCall(this.callId); + if (silenced) { + LegacyCallHandler.instance.unSilenceCall(this.callId); + } else { + LegacyCallHandler.instance.silenceCall(this.callId); + } }; private setCallListeners(): void { diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 8b23455967..0d7251c864 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -982,7 +982,7 @@ export default class MatrixChat extends React.PureComponent { try { const config = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(params.hs_url); newState.serverConfig = config; - } catch (err) { + } catch { logger.warn("Failed to load hs_url param:", params.hs_url); } } else if (params.client_secret && params.session_id && params.hs_url && params.is_url && params.sid) { @@ -1631,8 +1631,12 @@ export default class MatrixChat extends React.PureComponent { cli.on(CryptoEvent.KeyBackupFailed, async (errcode): Promise => { let haveNewVersion: boolean | undefined; let newVersionInfo: KeyBackupInfo | null = null; + const keyBackupEnabled = Boolean( + cli.getCrypto() && (await cli.getCrypto()?.getActiveSessionBackupVersion()) !== null, + ); + // if key backup is still enabled, there must be a new backup in place - if (cli.getKeyBackupEnabled()) { + if (keyBackupEnabled) { haveNewVersion = true; } else { // otherwise check the server to see if there's a new one @@ -1650,7 +1654,6 @@ export default class MatrixChat extends React.PureComponent { import( "../../async-components/views/dialogs/security/NewRecoveryMethodDialog" ) as unknown as Promise, - { newVersionInfo: newVersionInfo! }, ); } else { Modal.createDialogAsync( diff --git a/src/components/structures/RoomStatusBar.tsx b/src/components/structures/RoomStatusBar.tsx index 2ac6c8d01b..bd236f2286 100644 --- a/src/components/structures/RoomStatusBar.tsx +++ b/src/components/structures/RoomStatusBar.tsx @@ -17,7 +17,7 @@ import { SyncState, SyncStateData, } from "matrix-js-sdk/src/matrix"; -import WarningIcon from "@vector-im/compound-design-tokens/assets/web/icons/warning"; +import { WarningIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { _t, _td } from "../../languageHandler"; import Resend from "../../Resend"; diff --git a/src/components/structures/SpaceHierarchy.tsx b/src/components/structures/SpaceHierarchy.tsx index dc16d248af..d12f66e9c7 100644 --- a/src/components/structures/SpaceHierarchy.tsx +++ b/src/components/structures/SpaceHierarchy.tsx @@ -691,7 +691,7 @@ const ManageButtons: React.FC = ({ hierarchy, selected, set hierarchy.removeRelation(parentId, childId); } - } catch (e) { + } catch { setError(_t("space|failed_remove_rooms")); } setRemoving(false); @@ -724,7 +724,7 @@ const ManageButtons: React.FC = ({ hierarchy, selected, set // mutate the local state to save us having to refetch the world existingContent.suggested = content.suggested; } - } catch (e) { + } catch { setError("Failed to update some suggestions. Try again later"); } setSaving(false); diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index f4c9b639ac..b55709e8c2 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -973,7 +973,7 @@ class TimelinePanel extends React.Component { UserActivity.sharedInstance().timeWhileActiveRecently(this.readMarkerActivityTimer); try { await this.readMarkerActivityTimer.finished(); - } catch (e) { + } catch { continue; /* aborted */ } // outside of try/catch to not swallow errors @@ -988,7 +988,7 @@ class TimelinePanel extends React.Component { UserActivity.sharedInstance().timeWhileActiveNow(this.readReceiptActivityTimer); try { await this.readReceiptActivityTimer.finished(); - } catch (e) { + } catch { continue; /* aborted */ } // outside of try/catch to not swallow errors diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 4bdb6e227b..971e07193b 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -44,6 +44,7 @@ import { Icon as LiveIcon } from "../../../res/img/compound/live-8px.svg"; import { VoiceBroadcastRecording, VoiceBroadcastRecordingsStoreEvent } from "../../voice-broadcast"; import { SDKContext } from "../../contexts/SDKContext"; import { shouldShowFeedback } from "../../utils/Feedback"; +import DarkLightModeSvg from "../../../res/img/element-icons/roomlist/dark-light-mode.svg"; interface IProps { isPanelCollapsed: boolean; @@ -414,12 +415,7 @@ export default class UserMenu extends React.Component { : _t("user_menu|switch_theme_dark") } > - + {topSection} diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 0e5cfb0f38..e0a9318e9a 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -274,7 +274,7 @@ export default class ForgotPassword extends React.Component { await this.reset.setNewPassword(this.state.password); this.setState({ phase: Phase.Done }); modal.close(); - } catch (e) { + } catch { // Email not confirmed, yet. Retry after a while. await sleep(emailCheckInterval); } diff --git a/src/components/structures/static-page-vars.ts b/src/components/structures/static-page-vars.ts index 8cd2fd4a21..aaabfafa6d 100644 --- a/src/components/structures/static-page-vars.ts +++ b/src/components/structures/static-page-vars.ts @@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details. */ // We're importing via require specifically so the svg becomes a URI rather than a DOM element. -// eslint-disable-next-line @typescript-eslint/no-var-requires +// eslint-disable-next-line @typescript-eslint/no-require-imports const matrixSvg = require("../../../res/img/matrix.svg").default; /** diff --git a/src/components/views/avatars/WidgetAvatar.tsx b/src/components/views/avatars/WidgetAvatar.tsx index 171b1b78b4..bbb673a295 100644 --- a/src/components/views/avatars/WidgetAvatar.tsx +++ b/src/components/views/avatars/WidgetAvatar.tsx @@ -20,6 +20,7 @@ interface IProps extends Omit, "name" | "url" | " } const WidgetAvatar: React.FC = ({ app, className, size = "20px", ...props }) => { + /* eslint-disable @typescript-eslint/no-require-imports */ let iconUrls = [require("../../../../res/img/element-icons/room/default_app.svg").default]; // heuristics for some better icons until Widgets support their own icons if (app.type.includes("jitsi")) { @@ -31,6 +32,7 @@ const WidgetAvatar: React.FC = ({ app, className, size = "20px", ...prop } else if (app.type.includes("clock")) { iconUrls = [require("../../../../res/img/element-icons/room/default_clock.svg").default]; } + /* eslint-enable @typescript-eslint/no-require-imports */ return ( = ({ if (error) { footer = ( <> - +
diff --git a/src/components/views/dialogs/ChangelogDialog.tsx b/src/components/views/dialogs/ChangelogDialog.tsx index a33b2413ee..ae3c157c78 100644 --- a/src/components/views/dialogs/ChangelogDialog.tsx +++ b/src/components/views/dialogs/ChangelogDialog.tsx @@ -15,8 +15,8 @@ import Spinner from "../elements/Spinner"; import Heading from "../typography/Heading"; interface IProps { - newVersion: string; - version: string; + newVersion: DevelopVersionString; + version: DevelopVersionString; onFinished: (success: boolean) => void; } @@ -32,6 +32,28 @@ interface Commit { const REPOS = ["element-hq/element-web", "matrix-org/matrix-js-sdk"] as const; +export type DevelopVersionString = string & { _developVersionString: never }; + +/* + * Parse a version string is compatible with the Changelog dialog ([element-version]-js-[js-sdk-version]) + */ +export function parseVersion(version: string): Record<(typeof REPOS)[number], string> | null { + const parts = version.split("-"); + if (parts.length === 3 && parts[1] === "js") { + const obj: Record = {}; + for (let i = 0; i < REPOS.length; i++) { + const commit = parts[2 * i]; + obj[REPOS[i]] = commit; + } + return obj; + } + return null; +} + +export function checkVersion(version: string): version is DevelopVersionString { + return parseVersion(version) !== null; +} + export default class ChangelogDialog extends React.Component { public constructor(props: IProps) { super(props); @@ -58,14 +80,11 @@ export default class ChangelogDialog extends React.Component { } public componentDidMount(): void { - const version = this.props.newVersion.split("-"); - const version2 = this.props.version.split("-"); - if (version == null || version2 == null) return; - // parse versions of form: [vectorversion]-react-[react-sdk-version]-js-[js-sdk-version] - for (let i = 0; i < REPOS.length; i++) { - const oldVersion = version2[2 * i]; - const newVersion = version[2 * i]; - this.fetchChanges(REPOS[i], oldVersion, newVersion); + const commits = parseVersion(this.props.version)!; + const newCommits = parseVersion(this.props.newVersion)!; + + for (const repo of REPOS) { + this.fetchChanges(repo, commits[repo], newCommits[repo]); } } diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index b8a7f9d500..6e9df055cd 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -23,6 +23,8 @@ import { TimelineEvents, } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; +// eslint-disable-next-line no-restricted-imports +import OverflowHorizontalSvg from "@vector-im/compound-design-tokens/icons/overflow-horizontal.svg"; import { _t } from "../../../languageHandler"; import dis from "../../../dispatcher/dispatcher"; @@ -104,7 +106,7 @@ const Entry: React.FC> = ({ room, type, content, matrixClient: try { await cli.sendEvent(room.roomId, type, content); setSendState(SendState.Sent); - } catch (e) { + } catch { setSendState(SendState.Failed); } }; @@ -278,13 +280,7 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr return ( - } + avatarJsx={} name={text} showPresence={false} onClick={() => setTruncateAt(totalCount)} diff --git a/src/components/views/dialogs/ModalWidgetDialog.tsx b/src/components/views/dialogs/ModalWidgetDialog.tsx index 28f06927b5..90f330c625 100644 --- a/src/components/views/dialogs/ModalWidgetDialog.tsx +++ b/src/components/views/dialogs/ModalWidgetDialog.tsx @@ -33,6 +33,7 @@ import { arrayFastClone } from "../../../utils/arrays"; import { ElementWidget } from "../../../stores/widgets/StopGapWidget"; import { ELEMENT_CLIENT_ID } from "../../../identifiers"; import SettingsStore from "../../../settings/SettingsStore"; +import WarningBadgeSvg from "../../../../res/img/element-icons/warning-badge.svg"; interface IProps { widgetDefinition: IModalWidgetOpenRequestData; @@ -185,12 +186,7 @@ export default class ModalWidgetDialog extends React.PureComponent
- + {_t("widget|modal_data_warning", { widgetDomain: parsed.hostname, })} diff --git a/src/components/views/dialogs/ShareDialog.tsx b/src/components/views/dialogs/ShareDialog.tsx index 3369850b84..f9382227e4 100644 --- a/src/components/views/dialogs/ShareDialog.tsx +++ b/src/components/views/dialogs/ShareDialog.tsx @@ -21,11 +21,12 @@ import BaseDialog from "./BaseDialog"; import CopyableText from "../elements/CopyableText"; import { XOR } from "../../../@types/common"; +/* eslint-disable @typescript-eslint/no-require-imports */ const socials = [ { name: "Facebook", img: require("../../../../res/img/social/facebook.png"), - url: (url: String) => `https://www.facebook.com/sharer/sharer.php?u=${url}`, + url: (url: string) => `https://www.facebook.com/sharer/sharer.php?u=${url}`, }, { name: "Twitter", @@ -52,6 +53,7 @@ const socials = [ url: (url: string) => `mailto:?body=${url}`, }, ]; +/* eslint-enable @typescript-eslint/no-require-imports */ interface BaseProps { /** diff --git a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx index 412d88c704..50b728f4da 100644 --- a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx +++ b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx @@ -107,7 +107,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent = (props) => { return (
- +
{props.errorMsg || "Error"} diff --git a/src/components/views/elements/Pill.tsx b/src/components/views/elements/Pill.tsx index d668c6530f..14093587ad 100644 --- a/src/components/views/elements/Pill.tsx +++ b/src/components/views/elements/Pill.tsx @@ -10,8 +10,7 @@ import React, { ReactElement } from "react"; import classNames from "classnames"; import { Room, RoomMember } from "matrix-js-sdk/src/matrix"; import { Tooltip } from "@vector-im/compound-web"; -import UserIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-profile-solid"; -import LinkIcon from "@vector-im/compound-design-tokens/assets/web/icons/link"; +import { LinkIcon, UserSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; @@ -63,7 +62,7 @@ const PillMemberAvatar: React.FC<{ if (member) { return