Merge branch 'develop' of https://github.com/vector-im/element-web into t3chguy/dedup-icons-17oct

# Conflicts:
#	res/css/structures/_GenericDropdownMenu.pcss
#	res/css/views/elements/_ImageView.pcss
#	res/css/views/rooms/_MessageComposer.pcss
#	src/components/structures/RoomStatusBar.tsx
#	src/components/views/elements/Pill.tsx
#	src/components/views/messages/MessageActionBar.tsx
#	src/components/views/rooms/RoomKnocksBar.tsx
#	src/components/views/rooms/RoomTileSubtitle.tsx
#	src/components/views/settings/tabs/room/PeopleRoomSettingsTab.tsx
This commit is contained in:
Michael Telatynski
2024-10-18 15:17:07 +01:00
224 changed files with 1057 additions and 1064 deletions
+3
View File
@@ -7,3 +7,6 @@ karma-reports/
.idea/ .idea/
.tmp/ .tmp/
config.json* 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
+2
View File
@@ -198,6 +198,8 @@ module.exports = {
"@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-ts-comment": "off",
// We're okay with assertion errors when we ask for them // We're okay with assertion errors when we ask for them
"@typescript-eslint/no-non-null-assertion": "off", "@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 // temporary override for offending icon require files
+1 -1
View File
@@ -10,7 +10,7 @@ on:
jobs: jobs:
backport: backport:
name: Backport name: Backport
runs-on: ubuntu-latest runs-on: ubuntu-24.04
# Only react to merged PRs for security reasons. # Only react to merged PRs for security reasons.
# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
if: > if: >
+5 -5
View File
@@ -18,15 +18,15 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
image: image:
- ubuntu-latest - ubuntu-24.04
- windows-latest - windows-2022
- macos-latest - macos-14
isDevelop: isDevelop:
- ${{ github.event_name == 'push' && github.ref_name == 'develop' }} - ${{ 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: exclude:
- isDevelop: true - isDevelop: true
image: ubuntu-latest image: ubuntu-24.04
runs-on: ${{ matrix.image }} runs-on: ${{ matrix.image }}
defaults: defaults:
run: run:
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
build: build:
name: Build package name: Build package
environment: packages.element.io environment: packages.element.io
runs-on: ubuntu-latest runs-on: ubuntu-24.04
env: env:
R2_INCOMING_BUCKET: ${{ vars.R2_INCOMING_BUCKET }} R2_INCOMING_BUCKET: ${{ vars.R2_INCOMING_BUCKET }}
R2_URL: ${{ vars.CF_R2_S3_API }} R2_URL: ${{ vars.CF_R2_S3_API }}
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
name: "Build & Deploy develop.element.io" name: "Build & Deploy develop.element.io"
# Only respect triggers from our develop branch, ignore that of forks # Only respect triggers from our develop branch, ignore that of forks
if: github.repository == 'element-hq/element-web' if: github.repository == 'element-hq/element-web'
runs-on: ubuntu-latest runs-on: ubuntu-24.04
environment: develop environment: develop
env: env:
R2_BUCKET: "element-web-develop" R2_BUCKET: "element-web-develop"
+1 -1
View File
@@ -13,7 +13,7 @@ permissions:
jobs: jobs:
buildx: buildx:
name: Docker Buildx name: Docker Buildx
runs-on: ubuntu-latest runs-on: ubuntu-24.04
environment: dockerhub environment: dockerhub
strategy: strategy:
fail-fast: false fail-fast: false
+2 -2
View File
@@ -17,7 +17,7 @@ concurrency:
jobs: jobs:
build: build:
name: GitHub Pages name: GitHub Pages
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: Fetch element-desktop - name: Fetch element-desktop
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -99,7 +99,7 @@ jobs:
environment: environment:
name: github-pages name: github-pages
url: ${{ steps.deployment.outputs.page_url }} url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: build needs: build
steps: steps:
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
+1 -1
View File
@@ -7,7 +7,7 @@ on:
jobs: jobs:
tidy: tidy:
name: Tidy closed issues name: Tidy closed issues
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/github-script@v7 - uses: actions/github-script@v7
id: main id: main
+1 -1
View File
@@ -9,7 +9,7 @@ concurrency: ${{ github.workflow }}
jobs: jobs:
bot: bot:
name: Pending reviews bot name: Pending reviews bot
runs-on: ubuntu-latest runs-on: ubuntu-24.04
environment: Matrix environment: Matrix
env: 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+" 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+"
+2 -2
View File
@@ -27,7 +27,7 @@ jobs:
notify-downstream: notify-downstream:
name: Trigger release drafter downstream name: Trigger release drafter downstream
needs: release needs: release
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: Notify element-desktop repo that element-web release has completed to re-trigger release-drafter - name: Notify element-desktop repo that element-web release has completed to re-trigger release-drafter
uses: benc-uk/workflow-dispatch@e2e5e9a103e331dad343f381a29e654aea3cf8fc # v1 uses: benc-uk/workflow-dispatch@e2e5e9a103e331dad343f381a29e654aea3cf8fc # v1
@@ -41,7 +41,7 @@ jobs:
check: check:
name: Post release checks name: Post release checks
needs: release needs: release
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: Wait for dockerhub - name: Wait for dockerhub
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
+1 -1
View File
@@ -19,7 +19,7 @@ on:
default: true default: true
jobs: jobs:
prepare: prepare:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: Checkout Element Desktop - name: Checkout Element Desktop
uses: actions/checkout@v4 uses: actions/checkout@v4
+5 -5
View File
@@ -19,7 +19,7 @@ env:
jobs: jobs:
ts_lint: ts_lint:
name: "Typescript Syntax Check" name: "Typescript Syntax Check"
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -81,7 +81,7 @@ jobs:
js_lint: js_lint:
name: "ESLint" name: "ESLint"
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -99,7 +99,7 @@ jobs:
style_lint: style_lint:
name: "Style Lint" name: "Style Lint"
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -117,7 +117,7 @@ jobs:
workflow_lint: workflow_lint:
name: "Workflow Lint" name: "Workflow Lint"
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -135,7 +135,7 @@ jobs:
analyse_dead_code: analyse_dead_code:
name: "Analyse Dead Code" name: "Analyse Dead Code"
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
+1 -1
View File
@@ -6,7 +6,7 @@ on:
jobs: jobs:
web-app-team: web-app-team:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: | if: |
contains(github.event.issue.assignees.*.login, 't3chguy') || contains(github.event.issue.assignees.*.login, 't3chguy') ||
contains(github.event.issue.assignees.*.login, 'andybalaam') || contains(github.event.issue.assignees.*.login, 'andybalaam') ||
+1 -1
View File
@@ -6,7 +6,7 @@ on:
jobs: jobs:
automate-project-columns: automate-project-columns:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/add-to-project@main - uses: actions/add-to-project@main
with: with:
+10 -10
View File
@@ -11,7 +11,7 @@ on:
jobs: jobs:
apply_Z-Labs_label: apply_Z-Labs_label:
name: Add Z-Labs label for features behind labs flags name: Add Z-Labs label for features behind labs flags
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: > if: >
contains(github.event.issue.labels.*.name, 'A-Maths') || contains(github.event.issue.labels.*.name, 'A-Maths') ||
contains(github.event.issue.labels.*.name, 'A-Location-Sharing') || contains(github.event.issue.labels.*.name, 'A-Location-Sharing') ||
@@ -37,7 +37,7 @@ jobs:
apply_Help-Wanted_label: apply_Help-Wanted_label:
name: Add "Help Wanted" label to all "good first issue" and Hacktoberfest name: Add "Help Wanted" label to all "good first issue" and Hacktoberfest
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: > if: >
contains(github.event.issue.labels.*.name, 'good first issue') || contains(github.event.issue.labels.*.name, 'good first issue') ||
contains(github.event.issue.labels.*.name, 'Hacktoberfest') contains(github.event.issue.labels.*.name, 'Hacktoberfest')
@@ -54,7 +54,7 @@ jobs:
move_needs_info_issues: move_needs_info_issues:
name: X-Needs-Info issues to Need info column on triage board name: X-Needs-Info issues to Need info column on triage board
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: > if: >
contains(github.event.issue.labels.*.name, 'X-Needs-Info') contains(github.event.issue.labels.*.name, 'X-Needs-Info')
steps: steps:
@@ -77,7 +77,7 @@ jobs:
move_flakey_test_issues: move_flakey_test_issues:
name: Z-Flaky-Test issues to Sized for maintainer column on triage board name: Z-Flaky-Test issues to Sized for maintainer column on triage board
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: > if: >
contains(github.event.issue.labels.*.name, 'Z-Flaky-Test') contains(github.event.issue.labels.*.name, 'Z-Flaky-Test')
steps: steps:
@@ -100,7 +100,7 @@ jobs:
add_priority_design_issues_to_project: add_priority_design_issues_to_project:
name: P1 X-Needs-Design to Design project board name: P1 X-Needs-Design to Design project board
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: > if: >
contains(github.event.issue.labels.*.name, 'X-Needs-Design') && contains(github.event.issue.labels.*.name, 'X-Needs-Design') &&
(contains(github.event.issue.labels.*.name, 'S-Critical') && (contains(github.event.issue.labels.*.name, 'S-Critical') &&
@@ -117,7 +117,7 @@ jobs:
add_product_issues: add_product_issues:
name: X-Needs-Product to product project board name: X-Needs-Product to product project board
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: > if: >
contains(github.event.issue.labels.*.name, 'X-Needs-Product') contains(github.event.issue.labels.*.name, 'X-Needs-Product')
steps: steps:
@@ -128,7 +128,7 @@ jobs:
Search_issues_to_board: Search_issues_to_board:
name: Search issues to project board name: Search issues to project board
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: > if: >
contains(github.event.issue.labels.*.name, 'A-New-Search-Experience') contains(github.event.issue.labels.*.name, 'A-New-Search-Experience')
steps: steps:
@@ -139,7 +139,7 @@ jobs:
voip: voip:
name: Add labelled issues to VoIP project board name: Add labelled issues to VoIP project board
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: > if: >
contains(github.event.issue.labels.*.name, 'Team: VoIP') contains(github.event.issue.labels.*.name, 'Team: VoIP')
steps: steps:
@@ -150,7 +150,7 @@ jobs:
verticals_feature: verticals_feature:
name: Add labelled issues to Verticals Feature project name: Add labelled issues to Verticals Feature project
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: > if: >
contains(github.event.issue.labels.*.name, 'Team: Verticals Feature') contains(github.event.issue.labels.*.name, 'Team: Verticals Feature')
steps: steps:
@@ -161,7 +161,7 @@ jobs:
tech_debt: tech_debt:
name: Add labelled issues to tech debt project name: Add labelled issues to tech debt project
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: > if: >
contains(github.event.issue.labels.*.name, 'A-Developer-Experience') || contains(github.event.issue.labels.*.name, 'A-Developer-Experience') ||
contains(github.event.issue.labels.*.name, 'A-Documentation') || contains(github.event.issue.labels.*.name, 'A-Documentation') ||
@@ -6,7 +6,7 @@ on:
jobs: jobs:
add_design_pr_to_project: add_design_pr_to_project:
name: Move PRs asking for design review to the design board name: Move PRs asking for design review to the design board
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: octokit/graphql-action@v2.x - uses: octokit/graphql-action@v2.x
id: find_team_members id: find_team_members
@@ -73,7 +73,7 @@ jobs:
add_product_pr_to_project: add_product_pr_to_project:
name: Move PRs asking for design review to the design board name: Move PRs asking for design review to the design board
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: octokit/graphql-action@v2.x - uses: octokit/graphql-action@v2.x
id: find_team_members id: find_team_members
@@ -4,7 +4,7 @@ on:
- cron: "30 1 * * *" - cron: "30 1 * * *"
jobs: jobs:
close: close:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
permissions: permissions:
actions: write actions: write
issues: write issues: write
+2 -2
View File
@@ -7,7 +7,7 @@ on:
jobs: jobs:
Move_Unabeled_Issue_On_Project_Board: Move_Unabeled_Issue_On_Project_Board:
name: Move no longer X-Needs-Info issues to Triaged name: Move no longer X-Needs-Info issues to Triaged
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: > if: >
${{ ${{
!contains(github.event.issue.labels.*.name, 'X-Needs-Info') }} !contains(github.event.issue.labels.*.name, 'X-Needs-Info') }}
@@ -44,7 +44,7 @@ jobs:
remove_Z-Labs_label: remove_Z-Labs_label:
name: Remove Z-Labs label when features behind labs flags are removed name: Remove Z-Labs label when features behind labs flags are removed
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: > if: >
!(contains(github.event.issue.labels.*.name, 'A-Maths') || !(contains(github.event.issue.labels.*.name, 'A-Maths') ||
contains(github.event.issue.labels.*.name, 'A-Message-Pinning') || contains(github.event.issue.labels.*.name, 'A-Message-Pinning') ||
+1 -1
View File
@@ -6,7 +6,7 @@ on:
- cron: "0 3 * * 0" # 3am every Sunday - cron: "0 3 * * 0" # 3am every Sunday
jobs: jobs:
update: update:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
+4 -4
View File
@@ -18,7 +18,7 @@ concurrency: ${{ github.workflow }}
jobs: jobs:
bot: bot:
name: Release topic update name: Release topic update
runs-on: ubuntu-latest runs-on: ubuntu-24.04
environment: Matrix environment: Matrix
steps: steps:
- uses: actions/github-script@v7 - uses: actions/github-script@v7
@@ -56,7 +56,7 @@ jobs:
headers, headers,
body: "{}", body: "{}",
}); });
let res = await fetch(apiUrl, { let res = await fetch(apiUrl, {
method: "GET", method: "GET",
headers, headers,
@@ -69,7 +69,7 @@ jobs:
const data = await res.json(); const data = await res.json();
console.log(roomId, "got event", data); console.log(roomId, "got event", data);
const topic = data.topic.replace(regex, releaseTopic); const topic = data.topic.replace(regex, releaseTopic);
if (topic === data.topic) { if (topic === data.topic) {
console.log(roomId, "nothing to do"); console.log(roomId, "nothing to do");
@@ -89,7 +89,7 @@ jobs:
}), }),
headers, headers,
}); });
if (res.ok) { if (res.ok) {
console.log(roomId, "topic updated:", topic); console.log(roomId, "topic updated:", topic);
} else { } else {
+9 -8
View File
@@ -67,7 +67,7 @@
"test:playwright:open": "yarn test:playwright --ui", "test:playwright:open": "yarn test:playwright --ui",
"test:playwright:screenshots": "yarn test:playwright:screenshots:build && yarn test:playwright:screenshots:run", "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: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", "coverage": "yarn test --coverage",
"analyse:unused-exports": "ts-node ./scripts/analyse_unused_exports.ts", "analyse:unused-exports": "ts-node ./scripts/analyse_unused_exports.ts",
"analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp", "analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp",
@@ -77,7 +77,7 @@
"@types/react-dom": "17.0.25", "@types/react-dom": "17.0.25",
"@types/react": "17.0.83", "@types/react": "17.0.83",
"@types/seedrandom": "3.0.8", "@types/seedrandom": "3.0.8",
"oidc-client-ts": "3.0.1", "oidc-client-ts": "3.1.0",
"jwt-decode": "4.0.0", "jwt-decode": "4.0.0",
"@vector-im/compound-design-tokens": "1.8.0", "@vector-im/compound-design-tokens": "1.8.0",
"@vector-im/compound-web": "7.0.0", "@vector-im/compound-web": "7.0.0",
@@ -115,7 +115,7 @@
"emojibase-regex": "15.3.2", "emojibase-regex": "15.3.2",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"filesize": "10.1.4", "filesize": "10.1.6",
"github-markdown-css": "^5.5.1", "github-markdown-css": "^5.5.1",
"glob-to-regexp": "^0.4.1", "glob-to-regexp": "^0.4.1",
"highlight.js": "^11.3.1", "highlight.js": "^11.3.1",
@@ -142,7 +142,7 @@
"png-chunks-extract": "^1.0.0", "png-chunks-extract": "^1.0.0",
"posthog-js": "1.157.2", "posthog-js": "1.157.2",
"qrcode": "1.5.4", "qrcode": "1.5.4",
"re-resizable": "^6.9.0", "re-resizable": "6.9.17",
"react": "17.0.2", "react": "17.0.2",
"react-beautiful-dnd": "^13.1.0", "react-beautiful-dnd": "^13.1.0",
"react-blurhash": "^0.3.0", "react-blurhash": "^0.3.0",
@@ -151,7 +151,7 @@
"react-transition-group": "^4.4.1", "react-transition-group": "^4.4.1",
"rfc4648": "^1.4.0", "rfc4648": "^1.4.0",
"sanitize-filename": "^1.6.3", "sanitize-filename": "^1.6.3",
"sanitize-html": "2.13.0", "sanitize-html": "2.13.1",
"tar-js": "^0.3.0", "tar-js": "^0.3.0",
"temporal-polyfill": "^0.2.5", "temporal-polyfill": "^0.2.5",
"ua-parser-js": "^1.0.2", "ua-parser-js": "^1.0.2",
@@ -186,6 +186,7 @@
"@playwright/test": "^1.40.1", "@playwright/test": "^1.40.1",
"@principalstudio/html-webpack-inject-preload": "^1.2.7", "@principalstudio/html-webpack-inject-preload": "^1.2.7",
"@sentry/webpack-plugin": "^2.7.1", "@sentry/webpack-plugin": "^2.7.1",
"@stylistic/eslint-plugin": "^2.9.0",
"@svgr/webpack": "^8.0.0", "@svgr/webpack": "^8.0.0",
"@testing-library/dom": "^9.0.0", "@testing-library/dom": "^9.0.0",
"@testing-library/jest-dom": "^6.0.0", "@testing-library/jest-dom": "^6.0.0",
@@ -223,8 +224,8 @@
"@types/tar-js": "^0.3.5", "@types/tar-js": "^0.3.5",
"@types/ua-parser-js": "^0.7.36", "@types/ua-parser-js": "^0.7.36",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^7.0.0", "@typescript-eslint/parser": "^8.0.0",
"axe-core": "4.10.0", "axe-core": "4.10.0",
"babel-jest": "^29.0.0", "babel-jest": "^29.0.0",
"babel-loader": "^9.0.0", "babel-loader": "^9.0.0",
@@ -246,7 +247,7 @@
"eslint-plugin-import": "^2.25.4", "eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^28.0.0", "eslint-plugin-jest": "^28.0.0",
"eslint-plugin-jsx-a11y": "^6.5.1", "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": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0", "eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-unicorn": "^56.0.0", "eslint-plugin-unicorn": "^56.0.0",
+1
View File
@@ -26,6 +26,7 @@ export default defineConfig({
command: process.env.CI ? "npx serve -p 8080 -L ./webapp" : "yarn start", command: process.env.CI ? "npx serve -p 8080 -L ./webapp" : "yarn start",
url: `${baseURL}/config.json`, url: `${baseURL}/config.json`,
reuseExistingServer: true, reuseExistingServer: true,
timeout: (process.env.CI ? 30 : 120) * 1000,
}, },
testDir: "playwright/e2e", testDir: "playwright/e2e",
outputDir: "playwright/test-results", outputDir: "playwright/test-results",
@@ -15,6 +15,9 @@ test.describe("Landmark navigation tests", () => {
}); });
test("without any rooms", async ({ page, homeserver, app, user }) => { 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. * Without any rooms, there is no tile in the roomlist to be focused.
* So the next landmark in the list should be focused instead. * So the next landmark in the list should be focused instead.
@@ -96,7 +96,10 @@ test.describe("HTML Export", () => {
// Send a bunch of messages to populate the room // Send a bunch of messages to populate the room
for (let i = 1; i < 10; i++) { 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 // Wait for all the messages to be displayed
+1 -1
View File
@@ -217,7 +217,7 @@ test.describe("Login", () => {
const h1 = await page.getByRole("heading", { name: "Verify this device", level: 1 }); const h1 = await page.getByRole("heading", { name: "Verify this device", level: 1 });
await expect(h1).toBeVisible(); await expect(h1).toBeVisible();
expect(h1.locator(".mx_CompleteSecurity_skip")).not.toBeVisible(); await expect(h1.locator(".mx_CompleteSecurity_skip")).toHaveCount(0);
}); });
}); });
}); });
+2 -9
View File
@@ -69,7 +69,7 @@ export class MessageBuilder {
/** /**
* Map of message content -> event. * Map of message content -> event.
*/ */
messages = new Map<String, Promise<JSHandle<MatrixEvent>>>(); messages = new Map<string, Promise<JSHandle<MatrixEvent>>>();
/** /**
* Utility to find a MatrixEvent by its body content * Utility to find a MatrixEvent by its body content
@@ -222,14 +222,7 @@ export class MessageBuilder {
threadId: !ev.isThreadRoot ? ev.threadRootId : undefined, threadId: !ev.isThreadRoot ? ev.threadRootId : undefined,
})); }));
const roomId = await room.evaluate((room) => room.roomId); const roomId = await room.evaluate((room) => room.roomId);
await bot.reactToMessage(roomId, threadId, id, reaction);
await bot.sendEvent(roomId, threadId ?? null, "m.reaction", {
"m.relates_to": {
rel_type: "m.annotation",
event_id: id,
key: reaction,
},
});
} }
})(this); })(this);
} }
@@ -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_UserMenu .mx_BaseAvatar").getByText("A")).toBeVisible(); // Alice
await expect(page.locator(".mx_RoomView_wrapper .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();
});
}); });
@@ -74,7 +74,7 @@ test.describe("Appearance user settings tab", () => {
await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-custom-theme-added.png"); await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-custom-theme-added.png");
await util.removeCustomTheme(); await util.removeCustomTheme();
await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-custom-theme.png"); await expect(util.getThemePanel()).toMatchScreenshot("theme-panel-custom-theme-removed.png");
}); });
}); });
}); });
@@ -70,7 +70,7 @@ export class MessageBuilder {
/** /**
* Map of message content -> event. * Map of message content -> event.
*/ */
messages = new Map<String, Promise<JSHandle<MatrixEvent>>>(); messages = new Map<string, Promise<JSHandle<MatrixEvent>>>();
/** /**
* Utility to find a MatrixEvent by its body content * Utility to find a MatrixEvent by its body content
+23
View File
@@ -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<ISendEventResponse> {
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. * Create a room with given options.
* @param options the options to apply when creating the room * @param options the options to apply when creating the room
@@ -20,7 +20,7 @@ import { randB64Bytes } from "../../utils/rand";
// Docker tag to use for synapse docker image. // 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. // 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. // 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<Omit<HomeserverConfig, "dockerUrl">> { async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> {
const templateDir = path.join(__dirname, "templates", opts.template); const templateDir = path.join(__dirname, "templates", opts.template);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

+4 -4
View File
@@ -99,10 +99,10 @@ Please see LICENSE files in the repository root for full details.
&[aria-checked="true"]::before { &[aria-checked="true"]::before {
content: ""; content: "";
width: 12px; width: 16px;
height: 12px; height: 16px;
margin-left: -20px; margin-left: -22px;
margin-right: 8px; margin-right: 6px;
mask-image: url("@vector-im/compound-design-tokens/icons/check.svg"); mask-image: url("@vector-im/compound-design-tokens/icons/check.svg");
mask-size: 100%; mask-size: 100%;
mask-repeat: no-repeat; mask-repeat: no-repeat;
+1
View File
@@ -130,6 +130,7 @@ $button-gap: 24px;
.mx_ImageView_button_more::before { .mx_ImageView_button_more::before {
mask-image: url("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg"); mask-image: url("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg");
mask-size: 28px;
} }
.mx_ImageView_button_close { .mx_ImageView_button_close {
@@ -122,4 +122,8 @@ Please see LICENSE files in the repository root for full details.
--MessageActionBar-icon-size: 12px; --MessageActionBar-icon-size: 12px;
} }
} }
.mx_MessageActionBar_optionsButton {
--MessageActionBar-icon-size: 22px;
}
} }
+2 -1
View File
@@ -278,6 +278,7 @@ Please see LICENSE files in the repository root for full details.
.mx_MessageComposer_buttonMenu::before { .mx_MessageComposer_buttonMenu::before {
mask-image: url("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg"); mask-image: url("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg");
mask-size: 24px;
} }
.mx_MessageComposer_sendMessage { .mx_MessageComposer_sendMessage {
@@ -295,7 +296,7 @@ Please see LICENSE files in the repository root for full details.
top: 8px; top: 8px;
left: 9px; 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-repeat: no-repeat;
mask-size: contain; mask-size: contain;
mask-position: center; mask-position: center;
+2
View File
@@ -35,6 +35,7 @@ export type Defaultize<P, D> = P extends any
Partial<Pick<D, Exclude<keyof D, keyof P>>> Partial<Pick<D, Exclude<keyof D, keyof P>>>
: never; : never;
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
export type DeepReadonly<T> = T extends (infer R)[] export type DeepReadonly<T> = T extends (infer R)[]
? DeepReadonlyArray<R> ? DeepReadonlyArray<R>
: T extends Function : T extends Function
@@ -42,6 +43,7 @@ export type DeepReadonly<T> = T extends (infer R)[]
: T extends object : T extends object
? DeepReadonlyObject<T> ? DeepReadonlyObject<T>
: T; : T;
/* eslint-enable @typescript-eslint/no-unsafe-function-type */
interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {} interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}
+2 -2
View File
@@ -129,7 +129,7 @@ export default abstract class BasePlatform {
try { try {
const [version, deferUntil] = JSON.parse(localStorage.getItem(UPDATE_DEFER_KEY)!); const [version, deferUntil] = JSON.parse(localStorage.getItem(UPDATE_DEFER_KEY)!);
return newVersion !== version || Date.now() > deferUntil; return newVersion !== version || Date.now() > deferUntil;
} catch (e) { } catch {
return true; return true;
} }
} }
@@ -380,7 +380,7 @@ export default abstract class BasePlatform {
try { try {
await idbSave("pickleKey", [userId, deviceId], data); await idbSave("pickleKey", [userId, deviceId], data);
} catch (e) { } catch {
return null; return null;
} }
return encodeUnpaddedBase64(randomArray); return encodeUnpaddedBase64(randomArray);
+1 -1
View File
@@ -48,7 +48,7 @@ function shouldCleanupDrafts(): boolean {
return true; return true;
} }
return Date.now() > parsedTimestamp + DRAFT_CLEANUP_PERIOD; return Date.now() > parsedTimestamp + DRAFT_CLEANUP_PERIOD;
} catch (error) { } catch {
return true; return true;
} }
} }
+3 -3
View File
@@ -52,7 +52,7 @@ export const EMOJI_REGEX = (() => {
// want the app to completely crash on older platforms. We use the // want the app to completely crash on older platforms. We use the
// constructor here to avoid a syntax error on such platforms. // constructor here to avoid a syntax error on such platforms.
return new RegExp("\\p{RGI_Emoji}(?!\\uFE0E)(?:(?<!\\uFE0F)\\uFE0F)?|[\\u{1f1e6}-\\u{1f1ff}]", "v"); return new RegExp("\\p{RGI_Emoji}(?!\\uFE0E)(?:(?<!\\uFE0F)\\uFE0F)?|[\\u{1f1e6}-\\u{1f1ff}]", "v");
} catch (_e) { } catch {
// v mode not supported; fall back to matching nothing // v mode not supported; fall back to matching nothing
return /(?!)/; return /(?!)/;
} }
@@ -61,7 +61,7 @@ export const EMOJI_REGEX = (() => {
const BIGEMOJI_REGEX = (() => { const BIGEMOJI_REGEX = (() => {
try { try {
return new RegExp(`^(${EMOJI_REGEX.source})+$`, "iv"); return new RegExp(`^(${EMOJI_REGEX.source})+$`, "iv");
} catch (_e) { } catch {
// Fall back, just like for EMOJI_REGEX // Fall back, just like for EMOJI_REGEX
return /(?!)/; return /(?!)/;
} }
@@ -120,7 +120,7 @@ export function isUrlPermitted(inputUrl: string): boolean {
try { try {
// URL parser protocol includes the trailing colon // URL parser protocol includes the trailing colon
return PERMITTED_URL_SCHEMES.includes(new URL(inputUrl).protocol.slice(0, -1)); return PERMITTED_URL_SCHEMES.includes(new URL(inputUrl).protocol.slice(0, -1));
} catch (e) { } catch {
return false; return false;
} }
} }
+1 -1
View File
@@ -827,7 +827,7 @@ export default class LegacyCallHandler extends EventEmitter {
try { try {
this.addCallForRoom(roomId, call); this.addCallForRoom(roomId, call);
} catch (e) { } catch {
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("voip|already_in_call"), title: _t("voip|already_in_call"),
description: _t("voip|already_in_call_person"), description: _t("voip|already_in_call_person"),
+2 -2
View File
@@ -211,7 +211,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time")!, 10); const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time")!, 10);
const diff = Date.now() - registrationTime; const diff = Date.now() - registrationTime;
return diff / 36e5 <= hours; return diff / 36e5 <= hours;
} catch (e) { } catch {
return false; return false;
} }
} }
@@ -220,7 +220,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
try { try {
const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time")!, 10); const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time")!, 10);
return timestamp.getTime() <= registrationTime; return timestamp.getTime() <= registrationTime;
} catch (e) { } catch {
return false; return false;
} }
} }
+1 -1
View File
@@ -236,7 +236,7 @@ export class PosthogAnalytics {
let appVersion: string | undefined; let appVersion: string | undefined;
try { try {
appVersion = await platform?.getAppVersion(); appVersion = await platform?.getAppVersion();
} catch (e) { } catch {
// this happens if no version is set i.e. in dev // this happens if no version is set i.e. in dev
appVersion = "unknown"; appVersion = "unknown";
} }
+1 -1
View File
@@ -36,7 +36,7 @@ class Presence {
try { try {
await this.unavailableTimer.finished(); await this.unavailableTimer.finished();
this.setState(SetPresence.Unavailable); this.setState(SetPresence.Unavailable);
} catch (e) { } catch {
/* aborted, stop got called */ /* aborted, stop got called */
} }
} }
+1 -1
View File
@@ -44,7 +44,7 @@ export function getRoomNotifsState(client: MatrixClient, roomId: string): RoomNo
let roomRule: IPushRule | undefined; let roomRule: IPushRule | undefined;
try { try {
roomRule = client.getRoomPushRule("global", roomId); roomRule = client.getRoomPushRule("global", roomId);
} catch (err) { } catch {
// Possible that the client doesn't have pushRules yet. If so, it // Possible that the client doesn't have pushRules yet. If so, it
// hasn't started either, so indicate that this room is not notifying. // hasn't started either, so indicate that this room is not notifying.
return null; return null;
+2 -2
View File
@@ -855,14 +855,14 @@ const onMessage = function (event: MessageEvent<any>): void {
try { try {
if (!openManagerUrl) openManagerUrl = IntegrationManagers.sharedInstance().getPrimaryManager()?.uiUrl; if (!openManagerUrl) openManagerUrl = IntegrationManagers.sharedInstance().getPrimaryManager()?.uiUrl;
configUrl = new URL(openManagerUrl!); configUrl = new URL(openManagerUrl!);
} catch (e) { } catch {
// No integrations UI URL, ignore silently. // No integrations UI URL, ignore silently.
return; return;
} }
let eventOriginUrl: URL; let eventOriginUrl: URL;
try { try {
eventOriginUrl = new URL(event.origin); eventOriginUrl = new URL(event.origin);
} catch (e) { } catch {
return; return;
} }
// TODO -- Scalar postMessage API should be namespaced with event.data.api field // TODO -- Scalar postMessage API should be namespaced with event.data.api field
+1
View File
@@ -42,6 +42,7 @@ export const DEFAULTS: DeepReadonly<IConfigOptions> = {
// be preferred over their config. // be preferred over their config.
desktopBuilds: { desktopBuilds: {
available: true, available: true,
// eslint-disable-next-line @typescript-eslint/no-require-imports
logo: require("../res/img/element-desktop-logo.svg").default, logo: require("../res/img/element-desktop-logo.svg").default,
url: "https://element.io/get-started", url: "https://element.io/get-started",
}, },
+1 -1
View File
@@ -727,7 +727,7 @@ export const Commands = [
isEnabled: (cli) => !isCurrentLocalRoom(cli), isEnabled: (cli) => !isCurrentLocalRoom(cli),
runFn: function (cli, roomId) { runFn: function (cli, roomId) {
try { try {
cli.forceDiscardSession(roomId); cli.getCrypto()?.forceDiscardSession(roomId);
} catch (e) { } catch (e) {
return reject(e instanceof Error ? e.message : e); return reject(e instanceof Error ? e.message : e);
} }
+3 -3
View File
@@ -252,7 +252,7 @@ export class SlidingSyncManager {
try { try {
// wait until the next sync before returning as RoomView may need to know the current state // wait until the next sync before returning as RoomView may need to know the current state
await p; await p;
} catch (err) { } catch {
logger.warn("SlidingSync setRoomVisible:", roomId, visible, "failed to confirm transaction"); logger.warn("SlidingSync setRoomVisible:", roomId, visible, "failed to confirm transaction");
} }
return roomId; return roomId;
@@ -305,7 +305,7 @@ export class SlidingSyncManager {
} else { } else {
await this.slidingSync!.setListRanges(SlidingSyncManager.ListSearch, ranges); 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 // do nothing, as we reject only when we get interrupted but that's fine as the next
// request will include our data // request will include our data
} finally { } finally {
@@ -357,7 +357,7 @@ export class SlidingSyncManager {
} }
const clientWellKnown = await AutoDiscovery.findClientConfig(clientDomain); const clientWellKnown = await AutoDiscovery.findClientConfig(clientDomain);
proxyUrl = clientWellKnown?.["org.matrix.msc3575.proxy"]?.url; 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 // Either client.getDomain() is null so we've shorted out, or is invalid so `AutoDiscovery.findClientConfig` has thrown
} }
+1 -1
View File
@@ -213,7 +213,7 @@ export default class UserActivity {
attachedTimers.forEach((t) => t.start()); attachedTimers.forEach((t) => t.start());
try { try {
await timeout.finished(); await timeout.finished();
} catch (_e) { } catch {
/* aborted */ /* aborted */
} }
attachedTimers.forEach((t) => t.abort()); attachedTimers.forEach((t) => t.abort());
@@ -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. Please see LICENSE files in the repository root for full details.
*/ */
import React from "react"; import React, { JSX, useEffect, useState } from "react";
import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
import dis from "../../../../dispatcher/dispatcher"; import dis from "../../../../dispatcher/dispatcher";
import { _t } from "../../../../languageHandler"; import { _t } from "../../../../languageHandler";
import Modal from "../../../../Modal"; import Modal from "../../../../Modal";
@@ -18,81 +16,73 @@ import RestoreKeyBackupDialog from "../../../../components/views/dialogs/securit
import { Action } from "../../../../dispatcher/actions"; import { Action } from "../../../../dispatcher/actions";
import DialogButtons from "../../../../components/views/elements/DialogButtons"; import DialogButtons from "../../../../components/views/elements/DialogButtons";
import BaseDialog from "../../../../components/views/dialogs/BaseDialog"; 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; onFinished(): void;
} }
export default class NewRecoveryMethodDialog extends React.PureComponent<IProps> { // Export as default instead of a named export so that it can be dynamically imported with `Modal.createDialogAsync`
private onOkClick = (): void => {
this.props.onFinished();
};
private onGoToSettingsClick = (): void => { /**
this.props.onFinished(); * Dialog to inform the user that a new recovery method has been detected.
dis.fire(Action.ViewUserSettings); */
}; export default function NewRecoveryMethodDialog({ onFinished }: NewRecoveryMethodDialogProps): JSX.Element {
const matrixClient = useMatrixClientContext();
const [isKeyBackupEnabled, setIsKeyBackupEnabled] = useState(false);
useEffect(() => {
const checkBackupEnabled = async (): Promise<void> => {
const crypto = matrixClient.getCrypto();
setIsKeyBackupEnabled(Boolean(crypto && (await crypto.getActiveSessionBackupVersion()) !== null));
};
private onSetupClick = async (): Promise<void> => { checkBackupEnabled();
Modal.createDialog( }, [matrixClient]);
RestoreKeyBackupDialog,
{
onFinished: this.props.onFinished,
},
undefined,
/* priority = */ false,
/* static = */ true,
);
};
public render(): React.ReactNode { function onClick(): void {
const title = ( if (isKeyBackupEnabled) {
<span className="mx_KeyBackupFailedDialog_title"> onFinished();
{_t("encryption|new_recovery_method_detected|title")}
</span>
);
const newMethodDetected = <p>{_t("encryption|new_recovery_method_detected|description_1")}</p>;
const hackWarning = (
<strong className="warning">{_t("encryption|new_recovery_method_detected|warning")}</strong>
);
let content: JSX.Element | undefined;
if (MatrixClientPeg.safeGet().getKeyBackupEnabled()) {
content = (
<div>
{newMethodDetected}
<p>{_t("encryption|new_recovery_method_detected|description_2")}</p>
{hackWarning}
<DialogButtons
primaryButton={_t("action|ok")}
onPrimaryButtonClick={this.onOkClick}
cancelButton={_t("common|go_to_settings")}
onCancel={this.onGoToSettingsClick}
/>
</div>
);
} else { } else {
content = ( Modal.createDialog(
<div> RestoreKeyBackupDialog,
{newMethodDetected} {
{hackWarning} onFinished,
<DialogButtons },
primaryButton={_t("common|setup_secure_messages")} undefined,
onPrimaryButtonClick={this.onSetupClick} false,
cancelButton={_t("common|go_to_settings")} true,
onCancel={this.onGoToSettingsClick}
/>
</div>
); );
} }
return (
<BaseDialog className="mx_KeyBackupFailedDialog" onFinished={this.props.onFinished} title={title}>
{content}
</BaseDialog>
);
} }
return (
<BaseDialog
className="mx_KeyBackupFailedDialog"
onFinished={onFinished}
title={
<span className="mx_KeyBackupFailedDialog_title">
{_t("encryption|new_recovery_method_detected|title")}
</span>
}
>
<p>{_t("encryption|new_recovery_method_detected|description_1")}</p>
{isKeyBackupEnabled && <p>{_t("encryption|new_recovery_method_detected|description_2")}</p>}
<strong className="warning">{_t("encryption|new_recovery_method_detected|warning")}</strong>
<DialogButtons
primaryButton={_t("common|setup_secure_messages")}
onPrimaryButtonClick={onClick}
cancelButton={_t("common|go_to_settings")}
onCancel={() => {
onFinished();
dis.fire(Action.ViewUserSettings);
}}
/>
</BaseDialog>
);
} }
+2 -5
View File
@@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
import UploadBigSvg from "../../../res/img/upload-big.svg";
interface IProps { interface IProps {
parent: HTMLElement | null; parent: HTMLElement | null;
@@ -106,11 +107,7 @@ const FileDropTarget: React.FC<IProps> = ({ parent, onFileDrop }) => {
if (state.dragging) { if (state.dragging) {
return ( return (
<div className="mx_FileDropTarget"> <div className="mx_FileDropTarget">
<img <img src={UploadBigSvg} className="mx_FileDropTarget_image" alt="" />
src={require("../../../res/img/upload-big.svg").default}
className="mx_FileDropTarget_image"
alt=""
/>
{_t("room|drop_file_prompt")} {_t("room|drop_file_prompt")}
</div> </div>
); );
@@ -106,6 +106,10 @@ type IProps<T> = WithKeyFunction<T> & {
AdditionalOptions?: FunctionComponent<AdditionalOptionsProps>; AdditionalOptions?: FunctionComponent<AdditionalOptionsProps>;
}; };
function calculateKey<T>(value: T, toKey: ((key: T) => Key) | undefined): Key {
return toKey ? toKey(value) : (value as Key);
}
export function GenericDropdownMenu<T>({ export function GenericDropdownMenu<T>({
value, value,
onChange, onChange,
@@ -119,23 +123,24 @@ export function GenericDropdownMenu<T>({
}: IProps<T>): JSX.Element { }: IProps<T>): JSX.Element {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu<HTMLElement>(); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu<HTMLElement>();
const valueKey = calculateKey(value, toKey);
const selected: GenericDropdownMenuItem<T> | undefined = options const selected: GenericDropdownMenuItem<T> | undefined = options
.flatMap((it) => (isGenericDropdownMenuGroup(it) ? [it, ...it.options] : [it])) .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; let contextMenuOptions: JSX.Element;
if (options && isGenericDropdownMenuGroupArray(options)) { if (options && isGenericDropdownMenuGroupArray(options)) {
contextMenuOptions = ( contextMenuOptions = (
<> <>
{options.map((group) => ( {options.map((group) => (
<GenericDropdownMenuGroup <GenericDropdownMenuGroup
key={toKey?.(group.key) ?? (group.key as Key)} key={calculateKey(group.key, toKey)}
label={group.label} label={group.label}
description={group.description} description={group.description}
adornment={group.adornment} adornment={group.adornment}
> >
{group.options.map((option) => ( {group.options.map((option) => (
<GenericDropdownMenuOption <GenericDropdownMenuOption
key={toKey?.(option.key) ?? (option.key as Key)} key={calculateKey(option.key, toKey)}
label={option.label} label={option.label}
description={option.description} description={option.description}
onClick={(ev: ButtonEvent) => { onClick={(ev: ButtonEvent) => {
@@ -144,7 +149,7 @@ export function GenericDropdownMenu<T>({
onClose?.(ev); onClose?.(ev);
}} }}
adornment={option.adornment} adornment={option.adornment}
isSelected={option === selected} isSelected={calculateKey(option.key, toKey) === valueKey}
/> />
))} ))}
</GenericDropdownMenuGroup> </GenericDropdownMenuGroup>
@@ -156,7 +161,7 @@ export function GenericDropdownMenu<T>({
<> <>
{options.map((option) => ( {options.map((option) => (
<GenericDropdownMenuOption <GenericDropdownMenuOption
key={toKey?.(option.key) ?? (option.key as Key)} key={calculateKey(option.key, toKey)}
label={option.label} label={option.label}
description={option.description} description={option.description}
onClick={(ev: ButtonEvent) => { onClick={(ev: ButtonEvent) => {
@@ -165,7 +170,7 @@ export function GenericDropdownMenu<T>({
onClose?.(ev); onClose?.(ev);
}} }}
adornment={option.adornment} adornment={option.adornment}
isSelected={option === selected} isSelected={calculateKey(option.key, toKey) === valueKey}
/> />
))} ))}
</> </>
@@ -161,9 +161,11 @@ export default class LegacyCallEventGrouper extends EventEmitter {
public toggleSilenced = (): void => { public toggleSilenced = (): void => {
const silenced = LegacyCallHandler.instance.isCallSilenced(this.callId); const silenced = LegacyCallHandler.instance.isCallSilenced(this.callId);
silenced if (silenced) {
? LegacyCallHandler.instance.unSilenceCall(this.callId) LegacyCallHandler.instance.unSilenceCall(this.callId);
: LegacyCallHandler.instance.silenceCall(this.callId); } else {
LegacyCallHandler.instance.silenceCall(this.callId);
}
}; };
private setCallListeners(): void { private setCallListeners(): void {
+6 -3
View File
@@ -982,7 +982,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
try { try {
const config = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(params.hs_url); const config = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(params.hs_url);
newState.serverConfig = config; newState.serverConfig = config;
} catch (err) { } catch {
logger.warn("Failed to load hs_url param:", params.hs_url); 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) { } 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<IProps, IState> {
cli.on(CryptoEvent.KeyBackupFailed, async (errcode): Promise<void> => { cli.on(CryptoEvent.KeyBackupFailed, async (errcode): Promise<void> => {
let haveNewVersion: boolean | undefined; let haveNewVersion: boolean | undefined;
let newVersionInfo: KeyBackupInfo | null = null; 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 key backup is still enabled, there must be a new backup in place
if (cli.getKeyBackupEnabled()) { if (keyBackupEnabled) {
haveNewVersion = true; haveNewVersion = true;
} else { } else {
// otherwise check the server to see if there's a new one // otherwise check the server to see if there's a new one
@@ -1650,7 +1654,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
import( import(
"../../async-components/views/dialogs/security/NewRecoveryMethodDialog" "../../async-components/views/dialogs/security/NewRecoveryMethodDialog"
) as unknown as Promise<typeof NewRecoveryMethodDialog>, ) as unknown as Promise<typeof NewRecoveryMethodDialog>,
{ newVersionInfo: newVersionInfo! },
); );
} else { } else {
Modal.createDialogAsync( Modal.createDialogAsync(
+1 -1
View File
@@ -17,7 +17,7 @@ import {
SyncState, SyncState,
SyncStateData, SyncStateData,
} from "matrix-js-sdk/src/matrix"; } 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 { _t, _td } from "../../languageHandler";
import Resend from "../../Resend"; import Resend from "../../Resend";
+2 -2
View File
@@ -691,7 +691,7 @@ const ManageButtons: React.FC<IManageButtonsProps> = ({ hierarchy, selected, set
hierarchy.removeRelation(parentId, childId); hierarchy.removeRelation(parentId, childId);
} }
} catch (e) { } catch {
setError(_t("space|failed_remove_rooms")); setError(_t("space|failed_remove_rooms"));
} }
setRemoving(false); setRemoving(false);
@@ -724,7 +724,7 @@ const ManageButtons: React.FC<IManageButtonsProps> = ({ hierarchy, selected, set
// mutate the local state to save us having to refetch the world // mutate the local state to save us having to refetch the world
existingContent.suggested = content.suggested; existingContent.suggested = content.suggested;
} }
} catch (e) { } catch {
setError("Failed to update some suggestions. Try again later"); setError("Failed to update some suggestions. Try again later");
} }
setSaving(false); setSaving(false);
+2 -2
View File
@@ -973,7 +973,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
UserActivity.sharedInstance().timeWhileActiveRecently(this.readMarkerActivityTimer); UserActivity.sharedInstance().timeWhileActiveRecently(this.readMarkerActivityTimer);
try { try {
await this.readMarkerActivityTimer.finished(); await this.readMarkerActivityTimer.finished();
} catch (e) { } catch {
continue; /* aborted */ continue; /* aborted */
} }
// outside of try/catch to not swallow errors // outside of try/catch to not swallow errors
@@ -988,7 +988,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
UserActivity.sharedInstance().timeWhileActiveNow(this.readReceiptActivityTimer); UserActivity.sharedInstance().timeWhileActiveNow(this.readReceiptActivityTimer);
try { try {
await this.readReceiptActivityTimer.finished(); await this.readReceiptActivityTimer.finished();
} catch (e) { } catch {
continue; /* aborted */ continue; /* aborted */
} }
// outside of try/catch to not swallow errors // outside of try/catch to not swallow errors
+2 -6
View File
@@ -44,6 +44,7 @@ import { Icon as LiveIcon } from "../../../res/img/compound/live-8px.svg";
import { VoiceBroadcastRecording, VoiceBroadcastRecordingsStoreEvent } from "../../voice-broadcast"; import { VoiceBroadcastRecording, VoiceBroadcastRecordingsStoreEvent } from "../../voice-broadcast";
import { SDKContext } from "../../contexts/SDKContext"; import { SDKContext } from "../../contexts/SDKContext";
import { shouldShowFeedback } from "../../utils/Feedback"; import { shouldShowFeedback } from "../../utils/Feedback";
import DarkLightModeSvg from "../../../res/img/element-icons/roomlist/dark-light-mode.svg";
interface IProps { interface IProps {
isPanelCollapsed: boolean; isPanelCollapsed: boolean;
@@ -414,12 +415,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
: _t("user_menu|switch_theme_dark") : _t("user_menu|switch_theme_dark")
} }
> >
<img <img src={DarkLightModeSvg} role="presentation" alt="" width={16} />
src={require("../../../res/img/element-icons/roomlist/dark-light-mode.svg").default}
role="presentation"
alt=""
width={16}
/>
</RovingAccessibleButton> </RovingAccessibleButton>
</div> </div>
{topSection} {topSection}
@@ -274,7 +274,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
await this.reset.setNewPassword(this.state.password); await this.reset.setNewPassword(this.state.password);
this.setState({ phase: Phase.Done }); this.setState({ phase: Phase.Done });
modal.close(); modal.close();
} catch (e) { } catch {
// Email not confirmed, yet. Retry after a while. // Email not confirmed, yet. Retry after a while.
await sleep(emailCheckInterval); await sleep(emailCheckInterval);
} }
@@ -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. // 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; const matrixSvg = require("../../../res/img/matrix.svg").default;
/** /**
@@ -20,6 +20,7 @@ interface IProps extends Omit<ComponentProps<BaseAvatarType>, "name" | "url" | "
} }
const WidgetAvatar: React.FC<IProps> = ({ app, className, size = "20px", ...props }) => { const WidgetAvatar: React.FC<IProps> = ({ app, className, size = "20px", ...props }) => {
/* eslint-disable @typescript-eslint/no-require-imports */
let iconUrls = [require("../../../../res/img/element-icons/room/default_app.svg").default]; let iconUrls = [require("../../../../res/img/element-icons/room/default_app.svg").default];
// heuristics for some better icons until Widgets support their own icons // heuristics for some better icons until Widgets support their own icons
if (app.type.includes("jitsi")) { if (app.type.includes("jitsi")) {
@@ -31,6 +32,7 @@ const WidgetAvatar: React.FC<IProps> = ({ app, className, size = "20px", ...prop
} else if (app.type.includes("clock")) { } else if (app.type.includes("clock")) {
iconUrls = [require("../../../../res/img/element-icons/room/default_clock.svg").default]; iconUrls = [require("../../../../res/img/element-icons/room/default_clock.svg").default];
} }
/* eslint-enable @typescript-eslint/no-require-imports */
return ( return (
<BaseAvatar <BaseAvatar
@@ -34,6 +34,7 @@ import LazyRenderList from "../elements/LazyRenderList";
import { useSettingValue } from "../../../hooks/useSettings"; import { useSettingValue } from "../../../hooks/useSettings";
import { filterBoolean } from "../../../utils/arrays"; import { filterBoolean } from "../../../utils/arrays";
import { NonEmptyArray } from "../../../@types/common"; import { NonEmptyArray } from "../../../@types/common";
import WarningBadgeSvg from "../../../../res/img/element-icons/warning-badge.svg";
// These values match CSS // These values match CSS
const ROW_HEIGHT = 32 + 12; const ROW_HEIGHT = 32 + 12;
@@ -228,12 +229,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
if (error) { if (error) {
footer = ( footer = (
<> <>
<img <img src={WarningBadgeSvg} height="24" width="24" alt="" />
src={require("../../../../res/img/element-icons/warning-badge.svg").default}
height="24"
width="24"
alt=""
/>
<span className="mx_AddExistingToSpaceDialog_error"> <span className="mx_AddExistingToSpaceDialog_error">
<div className="mx_AddExistingToSpaceDialog_errorHeading"> <div className="mx_AddExistingToSpaceDialog_errorHeading">
@@ -15,8 +15,8 @@ import Spinner from "../elements/Spinner";
import Heading from "../typography/Heading"; import Heading from "../typography/Heading";
interface IProps { interface IProps {
newVersion: string; newVersion: DevelopVersionString;
version: string; version: DevelopVersionString;
onFinished: (success: boolean) => void; onFinished: (success: boolean) => void;
} }
@@ -32,6 +32,28 @@ interface Commit {
const REPOS = ["element-hq/element-web", "matrix-org/matrix-js-sdk"] as const; 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<string, string> = {};
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<IProps, State> { export default class ChangelogDialog extends React.Component<IProps, State> {
public constructor(props: IProps) { public constructor(props: IProps) {
super(props); super(props);
@@ -58,14 +80,11 @@ export default class ChangelogDialog extends React.Component<IProps, State> {
} }
public componentDidMount(): void { public componentDidMount(): void {
const version = this.props.newVersion.split("-"); const commits = parseVersion(this.props.version)!;
const version2 = this.props.version.split("-"); const newCommits = parseVersion(this.props.newVersion)!;
if (version == null || version2 == null) return;
// parse versions of form: [vectorversion]-react-[react-sdk-version]-js-[js-sdk-version] for (const repo of REPOS) {
for (let i = 0; i < REPOS.length; i++) { this.fetchChanges(repo, commits[repo], newCommits[repo]);
const oldVersion = version2[2 * i];
const newVersion = version[2 * i];
this.fetchChanges(REPOS[i], oldVersion, newVersion);
} }
} }
@@ -23,6 +23,8 @@ import {
TimelineEvents, TimelineEvents,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types"; 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 { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
@@ -104,7 +106,7 @@ const Entry: React.FC<IEntryProps<any>> = ({ room, type, content, matrixClient:
try { try {
await cli.sendEvent(room.roomId, type, content); await cli.sendEvent(room.roomId, type, content);
setSendState(SendState.Sent); setSendState(SendState.Sent);
} catch (e) { } catch {
setSendState(SendState.Failed); setSendState(SendState.Failed);
} }
}; };
@@ -278,13 +280,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
return ( return (
<EntityTile <EntityTile
className="mx_EntityTile_ellipsis" className="mx_EntityTile_ellipsis"
avatarJsx={ avatarJsx={<BaseAvatar url={OverflowHorizontalSvg} name="..." size="36px" />}
<BaseAvatar
url={require("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg").default}
name="..."
size="36px"
/>
}
name={text} name={text}
showPresence={false} showPresence={false}
onClick={() => setTruncateAt(totalCount)} onClick={() => setTruncateAt(totalCount)}
@@ -33,6 +33,7 @@ import { arrayFastClone } from "../../../utils/arrays";
import { ElementWidget } from "../../../stores/widgets/StopGapWidget"; import { ElementWidget } from "../../../stores/widgets/StopGapWidget";
import { ELEMENT_CLIENT_ID } from "../../../identifiers"; import { ELEMENT_CLIENT_ID } from "../../../identifiers";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import WarningBadgeSvg from "../../../../res/img/element-icons/warning-badge.svg";
interface IProps { interface IProps {
widgetDefinition: IModalWidgetOpenRequestData; widgetDefinition: IModalWidgetOpenRequestData;
@@ -185,12 +186,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
onFinished={this.props.onFinished} onFinished={this.props.onFinished}
> >
<div className="mx_ModalWidgetDialog_warning"> <div className="mx_ModalWidgetDialog_warning">
<img <img src={WarningBadgeSvg} height="16" width="16" alt="" />
src={require("../../../../res/img/element-icons/warning-badge.svg").default}
height="16"
width="16"
alt=""
/>
{_t("widget|modal_data_warning", { {_t("widget|modal_data_warning", {
widgetDomain: parsed.hostname, widgetDomain: parsed.hostname,
})} })}
+3 -1
View File
@@ -21,11 +21,12 @@ import BaseDialog from "./BaseDialog";
import CopyableText from "../elements/CopyableText"; import CopyableText from "../elements/CopyableText";
import { XOR } from "../../../@types/common"; import { XOR } from "../../../@types/common";
/* eslint-disable @typescript-eslint/no-require-imports */
const socials = [ const socials = [
{ {
name: "Facebook", name: "Facebook",
img: require("../../../../res/img/social/facebook.png"), 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", name: "Twitter",
@@ -52,6 +53,7 @@ const socials = [
url: (url: string) => `mailto:?body=${url}`, url: (url: string) => `mailto:?body=${url}`,
}, },
]; ];
/* eslint-enable @typescript-eslint/no-require-imports */
interface BaseProps { interface BaseProps {
/** /**
@@ -107,7 +107,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
recoveryKeyValid: true, recoveryKeyValid: true,
recoveryKeyCorrect: correct, recoveryKeyCorrect: correct,
}); });
} catch (e) { } catch {
this.setState({ this.setState({
recoveryKeyValid: false, recoveryKeyValid: false,
recoveryKeyCorrect: false, recoveryKeyCorrect: false,
@@ -127,7 +127,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
try { try {
decodeRecoveryKey(recoveryKey); decodeRecoveryKey(recoveryKey);
return true; return true;
} catch (e) { } catch {
return false; return false;
} }
} }
@@ -15,8 +15,10 @@ import { SetupEncryptionStore, Phase } from "../../../../stores/SetupEncryptionS
function iconFromPhase(phase?: Phase): string { function iconFromPhase(phase?: Phase): string {
if (phase === Phase.Done) { if (phase === Phase.Done) {
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require("../../../../../res/img/e2e/verified-deprecated.svg").default; return require("../../../../../res/img/e2e/verified-deprecated.svg").default;
} else { } else {
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require("../../../../../res/img/e2e/warning-deprecated.svg").default; return require("../../../../../res/img/e2e/warning-deprecated.svg").default;
} }
} }
+3 -1
View File
@@ -8,6 +8,8 @@ Please see LICENSE files in the repository root for full details.
import React from "react"; import React from "react";
import WarningSvg from "../../../../res/img/warning.svg";
interface IProps { interface IProps {
errorMsg?: string; errorMsg?: string;
} }
@@ -16,7 +18,7 @@ const AppWarning: React.FC<IProps> = (props) => {
return ( return (
<div className="mx_AppWarning"> <div className="mx_AppWarning">
<div> <div>
<img src={require("../../../../res/img/warning.svg").default} alt="" /> <img src={WarningSvg} alt="" />
</div> </div>
<div> <div>
<span>{props.errorMsg || "Error"}</span> <span>{props.errorMsg || "Error"}</span>
+2 -3
View File
@@ -10,8 +10,7 @@ import React, { ReactElement } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { Room, RoomMember } from "matrix-js-sdk/src/matrix"; import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
import { Tooltip } from "@vector-im/compound-web"; import { Tooltip } from "@vector-im/compound-web";
import UserIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-profile-solid"; import { LinkIcon, UserSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import LinkIcon from "@vector-im/compound-design-tokens/assets/web/icons/link";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
@@ -63,7 +62,7 @@ const PillMemberAvatar: React.FC<{
if (member) { if (member) {
return <MemberAvatar member={member} size="16px" aria-hidden="true" hideTitle />; return <MemberAvatar member={member} size="16px" aria-hidden="true" hideTitle />;
} }
return <UserIcon className="mx_Pill_UserIcon mx_BaseAvatar" />; return <UserSolidIcon className="mx_Pill_UserIcon mx_BaseAvatar" />;
}; };
export interface PillProps { export interface PillProps {
+2 -2
View File
@@ -140,7 +140,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
const inReplyToEventId = getParentEventId(ev); const inReplyToEventId = getParentEventId(ev);
if (!inReplyToEventId) return null; if (!inReplyToEventId) return null;
return await this.getEvent(inReplyToEventId); return await this.getEvent(inReplyToEventId);
} catch (e) { } catch {
return null; return null;
} }
} }
@@ -154,7 +154,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
// ask the client to fetch the event we want using the context API, only interface to do so is to ask // ask the client to fetch the event we want using the context API, only interface to do so is to ask
// for a timeline with that event, but once it is loaded we can use findEventById to look up the ev map // for a timeline with that event, but once it is loaded we can use findEventById to look up the ev map
await this.matrixClient.getEventTimeline(this.room.getUnfilteredTimelineSet(), eventId); await this.matrixClient.getEventTimeline(this.room.getUnfilteredTimelineSet(), eventId);
} catch (e) { } catch {
// if it fails catch the error and return early, there's no point trying to find the event in this case. // if it fails catch the error and return early, there's no point trying to find the event in this case.
// Return null as it is falsy and thus should be treated as an error (as the event cannot be resolved). // Return null as it is falsy and thus should be treated as an error (as the event cannot be resolved).
return null; return null;
@@ -33,6 +33,7 @@ interface ISSOButtonProps extends IProps {
const getIcon = (brand: IdentityProviderBrand | string): string | null => { const getIcon = (brand: IdentityProviderBrand | string): string | null => {
switch (brand) { switch (brand) {
/* eslint-disable @typescript-eslint/no-require-imports */
case IdentityProviderBrand.Apple: case IdentityProviderBrand.Apple:
return require(`../../../../res/img/element-icons/brands/apple.svg`).default; return require(`../../../../res/img/element-icons/brands/apple.svg`).default;
case IdentityProviderBrand.Facebook: case IdentityProviderBrand.Facebook:
@@ -47,6 +48,7 @@ const getIcon = (brand: IdentityProviderBrand | string): string | null => {
return require(`../../../../res/img/element-icons/brands/twitter.svg`).default; return require(`../../../../res/img/element-icons/brands/twitter.svg`).default;
default: default:
return null; return null;
/* eslint-enable @typescript-eslint/no-require-imports */
} }
}; };
+5 -5
View File
@@ -66,8 +66,8 @@ const useMapWithStyle = ({
throw new Error("Invalid geo URI"); throw new Error("Invalid geo URI");
} }
map.setCenter({ lon: coords.longitude, lat: coords.latitude }); map.setCenter({ lon: coords.longitude, lat: coords.latitude });
} catch (_error) { } catch (e) {
logger.error("Could not set map center"); logger.error("Could not set map center", e);
} }
} }
}, [map, centerGeoUri]); }, [map, centerGeoUri]);
@@ -80,8 +80,8 @@ const useMapWithStyle = ({
[bounds.east, bounds.north], [bounds.east, bounds.north],
); );
map.fitBounds(lngLatBounds, { padding: 100, maxZoom: 15 }); map.fitBounds(lngLatBounds, { padding: 100, maxZoom: 15 });
} catch (_error) { } catch (e) {
logger.error("Invalid map bounds"); logger.error("Invalid map bounds", e);
} }
} }
}, [map, bounds]); }, [map, bounds]);
@@ -170,7 +170,7 @@ const MapComponent: React.FC<MapProps> = ({
return; return;
} }
onClick && onClick(); onClick?.();
}; };
return ( return (
@@ -55,7 +55,7 @@ export default class LegacyCallEvent extends React.PureComponent<IProps, IState>
this.props.callEventGrouper.addListener(LegacyCallEventGrouperEvent.LengthChanged, this.onLengthChanged); this.props.callEventGrouper.addListener(LegacyCallEventGrouperEvent.LengthChanged, this.onLengthChanged);
this.resizeObserver = new ResizeObserver(this.resizeObserverCallback); this.resizeObserver = new ResizeObserver(this.resizeObserverCallback);
this.wrapperElement.current && this.resizeObserver.observe(this.wrapperElement.current); if (this.wrapperElement.current) this.resizeObserver.observe(this.wrapperElement.current);
} }
public componentWillUnmount(): void { public componentWillUnmount(): void {
+1 -1
View File
@@ -27,7 +27,7 @@ export let DOWNLOAD_ICON_URL: string; // cached copy of the download.svg asset f
async function cacheDownloadIcon(): Promise<void> { async function cacheDownloadIcon(): Promise<void> {
if (DOWNLOAD_ICON_URL) return; // cached already if (DOWNLOAD_ICON_URL) return; // cached already
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-require-imports
const svg = await fetch(require("@vector-im/compound-design-tokens/icons/download.svg").default).then((r) => const svg = await fetch(require("@vector-im/compound-design-tokens/icons/download.svg").default).then((r) =>
r.text(), r.text(),
); );
@@ -11,6 +11,7 @@ import { MediaEventContent } from "matrix-js-sdk/src/types";
import MImageBody from "./MImageBody"; import MImageBody from "./MImageBody";
import { BLURHASH_FIELD } from "../../../utils/image-media"; import { BLURHASH_FIELD } from "../../../utils/image-media";
import IconsShowStickersSvg from "../../../../res/img/icons-show-stickers.svg";
export default class MStickerBody extends MImageBody { export default class MStickerBody extends MImageBody {
// Mostly empty to prevent default behaviour of MImageBody // Mostly empty to prevent default behaviour of MImageBody
@@ -44,7 +45,7 @@ export default class MStickerBody extends MImageBody {
aria-hidden aria-hidden
alt="" alt=""
className="mx_MStickerBody_placeholder" className="mx_MStickerBody_placeholder"
src={require("../../../../res/img/icons-show-stickers.svg").default} src={IconsShowStickersSvg}
width="80" width="80"
height="80" height="80"
onMouseEnter={this.onImageEnter} onMouseEnter={this.onImageEnter}
@@ -21,16 +21,18 @@ import {
EventType, EventType,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import classNames from "classnames"; import classNames from "classnames";
import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin"; import {
import UnpinIcon from "@vector-im/compound-design-tokens/assets/web/icons/unpin"; PinIcon,
import ThreadIcon from "@vector-im/compound-design-tokens/assets/web/icons/threads"; UnpinIcon,
import ReplyIcon from "@vector-im/compound-design-tokens/assets/web/icons/reply"; OverflowHorizontalIcon,
import ContextMenuIcon from "@vector-im/compound-design-tokens/assets/web/icons/overflow-horizontal"; ReplyIcon,
import ResendIcon from "@vector-im/compound-design-tokens/assets/web/icons/restart"; DeleteIcon,
import TrashcanIcon from "@vector-im/compound-design-tokens/assets/web/icons/delete"; } from "@vector-im/compound-design-tokens/assets/web/icons";
import { Icon as EditIcon } from "../../../../res/img/element-icons/room/message-bar/edit.svg"; import { Icon as EditIcon } from "../../../../res/img/element-icons/room/message-bar/edit.svg";
import { Icon as EmojiIcon } from "../../../../res/img/element-icons/room/message-bar/emoji.svg"; import { Icon as EmojiIcon } from "../../../../res/img/element-icons/room/message-bar/emoji.svg";
import { Icon as ResendIcon } from "../../../../res/img/element-icons/retry.svg";
import { Icon as ThreadIcon } from "../../../../res/img/element-icons/message/thread.svg";
import { Icon as ExpandMessageIcon } from "../../../../res/img/element-icons/expand-message.svg"; import { Icon as ExpandMessageIcon } from "../../../../res/img/element-icons/expand-message.svg";
import { Icon as CollapseMessageIcon } from "../../../../res/img/element-icons/collapse-message.svg"; import { Icon as CollapseMessageIcon } from "../../../../res/img/element-icons/collapse-message.svg";
import type { Relations } from "matrix-js-sdk/src/matrix"; import type { Relations } from "matrix-js-sdk/src/matrix";
@@ -130,7 +132,7 @@ const OptionsButton: React.FC<IOptionsButtonProps> = ({
tabIndex={isActive ? 0 : -1} tabIndex={isActive ? 0 : -1}
placement="left" placement="left"
> >
<ContextMenuIcon /> <OverflowHorizontalIcon />
</ContextMenuTooltipButton> </ContextMenuTooltipButton>
{contextMenu} {contextMenu}
</React.Fragment> </React.Fragment>
@@ -454,7 +456,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
key="cancel" key="cancel"
placement="left" placement="left"
> >
<TrashcanIcon /> <DeleteIcon />
</RovingAccessibleButton> </RovingAccessibleButton>
); );
@@ -21,6 +21,8 @@ import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePha
import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import ErrorDialog from "../dialogs/ErrorDialog"; import ErrorDialog from "../dialogs/ErrorDialog";
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import WarningDeprecatedSvg from "../../../../res/img/e2e/warning-deprecated.svg";
import WarningSvg from "../../../../res/img/e2e/warning.svg";
// cancellation codes which constitute a key mismatch // cancellation codes which constitute a key mismatch
const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"]; const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"];
@@ -79,7 +81,7 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
) { ) {
isShowingMismatchModal.current = true; isShowingMismatchModal.current = true;
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
headerImage: require("../../../../res/img/e2e/warning-deprecated.svg").default, headerImage: WarningDeprecatedSvg,
title: _t("encryption|messages_not_secure|title"), title: _t("encryption|messages_not_secure|title"),
description: ( description: (
<div> <div>
@@ -118,7 +120,7 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
setRequesting(false); setRequesting(false);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
headerImage: require("../../../../res/img/e2e/warning.svg").default, headerImage: WarningSvg,
title: _t("encryption|verification|error_starting_title"), title: _t("encryption|verification|error_starting_title"),
description: _t("encryption|verification|error_starting_description"), description: _t("encryption|verification|error_starting_description"),
}); });
@@ -1356,7 +1356,7 @@ export const useDevices = (userId: string): IDevice[] | undefined | null => {
disambiguateDevices(devices); disambiguateDevices(devices);
setDevices(devices); setDevices(devices);
} catch (err) { } catch {
setDevices(null); setDevices(null);
} }
} }
+3 -7
View File
@@ -27,6 +27,8 @@ import { KnownMembership } from "matrix-js-sdk/src/types";
import { throttle } from "lodash"; import { throttle } from "lodash";
import { Button, Tooltip } from "@vector-im/compound-web"; import { Button, Tooltip } from "@vector-im/compound-web";
import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add-solid"; import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add-solid";
// eslint-disable-next-line no-restricted-imports
import OverflowHorizontalSvg from "@vector-im/compound-design-tokens/icons/overflow-horizontal.svg";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
@@ -237,13 +239,7 @@ export default class MemberList extends React.Component<IProps, IState> {
return ( return (
<EntityTile <EntityTile
className="mx_EntityTile_ellipsis" className="mx_EntityTile_ellipsis"
avatarJsx={ avatarJsx={<BaseAvatar url={OverflowHorizontalSvg} name="..." size="36px" />}
<BaseAvatar
url={require("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg").default}
name="..."
size="36px"
/>
}
name={text} name={text}
showPresence={false} showPresence={false}
onClick={onClick} onClick={onClick}
@@ -57,6 +57,7 @@ import { VoiceBroadcastInfoState } from "../../../voice-broadcast";
import { createCantStartVoiceMessageBroadcastDialog } from "../dialogs/CantStartVoiceMessageBroadcastDialog"; import { createCantStartVoiceMessageBroadcastDialog } from "../dialogs/CantStartVoiceMessageBroadcastDialog";
import { UIFeature } from "../../../settings/UIFeature"; import { UIFeature } from "../../../settings/UIFeature";
import { formatTimeLeft } from "../../../DateUtils"; import { formatTimeLeft } from "../../../DateUtils";
import RoomReplacedSvg from "../../../../res/img/room_replaced.svg";
// The prefix used when persisting editor drafts to localstorage. // The prefix used when persisting editor drafts to localstorage.
export const WYSIWYG_EDITOR_STATE_STORAGE_PREFIX = "mx_wysiwyg_state_"; export const WYSIWYG_EDITOR_STATE_STORAGE_PREFIX = "mx_wysiwyg_state_";
@@ -619,7 +620,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
aria-hidden aria-hidden
alt="" alt=""
className="mx_MessageComposer_roomReplaced_icon" className="mx_MessageComposer_roomReplaced_icon"
src={require("../../../../res/img/room_replaced.svg").default} src={RoomReplacedSvg}
/> />
<span className="mx_MessageComposer_roomReplaced_header"> <span className="mx_MessageComposer_roomReplaced_header">
{_t("composer|room_upgraded_notice")} {_t("composer|room_upgraded_notice")}
+1 -1
View File
@@ -29,7 +29,7 @@ const RoomInfoLine: FC<IProps> = ({ room }) => {
if (room.getMyMembership() !== KnownMembership.Invite) return null; if (room.getMyMembership() !== KnownMembership.Invite) return null;
try { try {
return await room.client.getRoomSummary(room.roomId); return await room.client.getRoomSummary(room.roomId);
} catch (e) { } catch {
return null; return null;
} }
}, [room]); }, [room]);
+1 -2
View File
@@ -9,8 +9,7 @@ Please see LICENSE files in the repository root for full details.
import { EventTimeline, JoinRule, MatrixError, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix"; import { EventTimeline, JoinRule, MatrixError, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
import React, { ReactElement, ReactNode, useCallback, useState, VFC } from "react"; import React, { ReactElement, ReactNode, useCallback, useState, VFC } from "react";
import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close"; import { CloseIcon, CheckIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter"; import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react"; import React from "react";
import classNames from "classnames"; import classNames from "classnames";
import ThreadIcon from "@vector-im/compound-design-tokens/assets/web/icons/threads-solid"; import { ThreadsIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { MessagePreview } from "../../../stores/room-list/MessagePreviewStore"; import { MessagePreview } from "../../../stores/room-list/MessagePreviewStore";
import { Call } from "../../../models/Call"; import { Call } from "../../../models/Call";
@@ -49,7 +49,7 @@ export const RoomTileSubtitle: React.FC<Props> = ({
"mx_RoomTile_subtitle--thread-reply": messagePreview.isThreadReply, "mx_RoomTile_subtitle--thread-reply": messagePreview.isThreadReply,
}); });
const icon = messagePreview.isThreadReply ? <ThreadIcon className="mx_Icon mx_Icon_12" /> : null; const icon = messagePreview.isThreadReply ? <ThreadsIcon className="mx_Icon mx_Icon_12" /> : null;
return ( return (
<div className={className} id={messagePreviewId(roomId)} title={messagePreview.text}> <div className={className} id={messagePreviewId(roomId)} title={messagePreview.text}>
+3 -1
View File
@@ -202,11 +202,13 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
}; };
private defaultStickerpickerContent(): JSX.Element { private defaultStickerpickerContent(): JSX.Element {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const imgSrc = require("../../../../res/img/stickerpack-placeholder.png");
return ( return (
<AccessibleButton onClick={this.launchManageIntegrations} className="mx_Stickers_contentPlaceholder"> <AccessibleButton onClick={this.launchManageIntegrations} className="mx_Stickers_contentPlaceholder">
<p>{_t("stickers|empty")}</p> <p>{_t("stickers|empty")}</p>
<p className="mx_Stickers_addLink">{_t("stickers|empty_add_prompt")}</p> <p className="mx_Stickers_addLink">{_t("stickers|empty_add_prompt")}</p>
<img src={require("../../../../res/img/stickerpack-placeholder.png")} alt="" /> <img src={imgSrc} alt="" />
</AccessibleButton> </AccessibleButton>
); );
} }

Some files were not shown because too many files have changed in this diff Show More