diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000000..c9d11f02c8
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,3 @@
+
+
+
diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml
index 3f82e61280..3c3807e33b 100644
--- a/.github/workflows/develop.yml
+++ b/.github/workflows/develop.yml
@@ -1,4 +1,4 @@
-name: Develop jobs
+name: Develop
on:
push:
branches: [develop]
@@ -11,17 +11,31 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2
- - name: End-to-End tests
- run: ./scripts/ci/end-to-end-tests.sh
+ - name: Prepare End-to-End tests
+ run: ./scripts/ci/prepare-end-to-end-tests.sh
+ - name: Run End-to-End tests
+ run: ./scripts/ci/run-end-to-end-tests.sh
- name: Archive logs
uses: actions/upload-artifact@v2
+ if: ${{ always() }}
with:
path: |
test/end-to-end-tests/logs/**/*
test/end-to-end-tests/synapse/installations/consent/homeserver.log
retention-days: 14
- - name: Archive performance benchmark
- uses: actions/upload-artifact@v2
+ - name: Download previous benchmark data
+ uses: actions/cache@v1
with:
- name: performance-entries.json
- path: test/end-to-end-tests/performance-entries.json
+ path: ./cache
+ key: ${{ runner.os }}-benchmark
+ - name: Store benchmark result
+ uses: matrix-org/github-action-benchmark@jsperfentry-1
+ with:
+ tool: 'jsperformanceentry'
+ output-file-path: test/end-to-end-tests/performance-entries.json
+ fail-on-alert: false
+ comment-on-alert: false
+ # Only temporary to monitor where failures occur
+ alert-comment-cc-users: '@gsouquet'
+ github-token: ${{ secrets.DEPLOY_GH_PAGES }}
+ auto-push: ${{ github.ref == 'refs/heads/develop' }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 94c9530941..0f979b4802 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,133 @@
+Changes in [3.24.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.24.0) (2021-06-21)
+=====================================================================================================
+[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.24.0-rc.1...v3.24.0)
+
+ * Upgrade to JS SDK 12.0.0
+ * [Release] Keep composer reply when scrolling away from a highlighted event
+ [\#6211](https://github.com/matrix-org/matrix-react-sdk/pull/6211)
+ * [Release] Remove stray bullet point in reply preview
+ [\#6210](https://github.com/matrix-org/matrix-react-sdk/pull/6210)
+ * [Release] Stop requesting null next replies from the server
+ [\#6209](https://github.com/matrix-org/matrix-react-sdk/pull/6209)
+
+Changes in [3.24.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.24.0-rc.1) (2021-06-15)
+===============================================================================================================
+[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.23.0...v3.24.0-rc.1)
+
+ * Upgrade to JS SDK 12.0.0-rc.1
+ * Translations update from Weblate
+ [\#6192](https://github.com/matrix-org/matrix-react-sdk/pull/6192)
+ * Disable comment-on-alert for PR coming from a fork
+ [\#6189](https://github.com/matrix-org/matrix-react-sdk/pull/6189)
+ * Add JS benchmark tracking in CI
+ [\#6177](https://github.com/matrix-org/matrix-react-sdk/pull/6177)
+ * Upgrade matrix-react-test-utils for React 17 peer deps
+ [\#6187](https://github.com/matrix-org/matrix-react-sdk/pull/6187)
+ * Fix display name overlaps on the IRC layout
+ [\#6186](https://github.com/matrix-org/matrix-react-sdk/pull/6186)
+ * Small fixes to the spaces experience
+ [\#6184](https://github.com/matrix-org/matrix-react-sdk/pull/6184)
+ * Add footer and privacy note to the start dm dialog
+ [\#6111](https://github.com/matrix-org/matrix-react-sdk/pull/6111)
+ * Format mxids when disambiguation needed
+ [\#5880](https://github.com/matrix-org/matrix-react-sdk/pull/5880)
+ * Move various createRoom types to the js-sdk
+ [\#6183](https://github.com/matrix-org/matrix-react-sdk/pull/6183)
+ * Fix HTML tag for Event Tile when not rendered in a list
+ [\#6175](https://github.com/matrix-org/matrix-react-sdk/pull/6175)
+ * Remove legacy polyfills and unused dependencies
+ [\#6176](https://github.com/matrix-org/matrix-react-sdk/pull/6176)
+ * Fix buggy hovering/selecting of event tiles
+ [\#6173](https://github.com/matrix-org/matrix-react-sdk/pull/6173)
+ * Add room intro warning when e2ee is not enabled
+ [\#5929](https://github.com/matrix-org/matrix-react-sdk/pull/5929)
+ * Migrate end to end tests to GitHub actions
+ [\#6156](https://github.com/matrix-org/matrix-react-sdk/pull/6156)
+ * Fix expanding last collapsed sticky session when zoomed in
+ [\#6171](https://github.com/matrix-org/matrix-react-sdk/pull/6171)
+ * ⚛️ Upgrade to React@17
+ [\#6165](https://github.com/matrix-org/matrix-react-sdk/pull/6165)
+ * Revert refreshStickyHeaders optimisations
+ [\#6168](https://github.com/matrix-org/matrix-react-sdk/pull/6168)
+ * Add logging for which rooms calls are in
+ [\#6170](https://github.com/matrix-org/matrix-react-sdk/pull/6170)
+ * Restore read receipt animation from event to event
+ [\#6169](https://github.com/matrix-org/matrix-react-sdk/pull/6169)
+ * Restore copy button icon when sharing permalink
+ [\#6166](https://github.com/matrix-org/matrix-react-sdk/pull/6166)
+ * Restore Page Up/Down key bindings when focusing the composer
+ [\#6167](https://github.com/matrix-org/matrix-react-sdk/pull/6167)
+ * Timeline rendering optimizations
+ [\#6143](https://github.com/matrix-org/matrix-react-sdk/pull/6143)
+ * Bump css-what from 5.0.0 to 5.0.1
+ [\#6164](https://github.com/matrix-org/matrix-react-sdk/pull/6164)
+ * Bump ws from 6.2.1 to 6.2.2 in /test/end-to-end-tests
+ [\#6145](https://github.com/matrix-org/matrix-react-sdk/pull/6145)
+ * Bump trim-newlines from 3.0.0 to 3.0.1
+ [\#6163](https://github.com/matrix-org/matrix-react-sdk/pull/6163)
+ * Fix upgrade to element home button in top left menu
+ [\#6162](https://github.com/matrix-org/matrix-react-sdk/pull/6162)
+ * Fix unpinning of pinned messages and panel empty state
+ [\#6140](https://github.com/matrix-org/matrix-react-sdk/pull/6140)
+ * Better handling for widgets that fail to load
+ [\#6161](https://github.com/matrix-org/matrix-react-sdk/pull/6161)
+ * Improved forwarding UI
+ [\#5999](https://github.com/matrix-org/matrix-react-sdk/pull/5999)
+ * Fixes for sharing room links
+ [\#6118](https://github.com/matrix-org/matrix-react-sdk/pull/6118)
+ * Fix setting watchers
+ [\#6160](https://github.com/matrix-org/matrix-react-sdk/pull/6160)
+ * Fix Stickerpicker context menu
+ [\#6152](https://github.com/matrix-org/matrix-react-sdk/pull/6152)
+ * Add warning to private space creation flow
+ [\#6155](https://github.com/matrix-org/matrix-react-sdk/pull/6155)
+ * Add prop to alwaysShowTimestamps on TimelinePanel
+ [\#6159](https://github.com/matrix-org/matrix-react-sdk/pull/6159)
+ * Fix notif panel timestamp padding
+ [\#6157](https://github.com/matrix-org/matrix-react-sdk/pull/6157)
+ * Fixes and refactoring for the ImageView
+ [\#6149](https://github.com/matrix-org/matrix-react-sdk/pull/6149)
+ * Fix timestamps
+ [\#6148](https://github.com/matrix-org/matrix-react-sdk/pull/6148)
+ * Make it easier to pan images in the lightbox
+ [\#6147](https://github.com/matrix-org/matrix-react-sdk/pull/6147)
+ * Fix scroll token for EventTile and EventListSummary node type
+ [\#6154](https://github.com/matrix-org/matrix-react-sdk/pull/6154)
+ * Convert bunch of things to Typescript
+ [\#6153](https://github.com/matrix-org/matrix-react-sdk/pull/6153)
+ * Lint the typescript tests
+ [\#6142](https://github.com/matrix-org/matrix-react-sdk/pull/6142)
+ * Fix jumping to bottom without a highlighted event
+ [\#6146](https://github.com/matrix-org/matrix-react-sdk/pull/6146)
+ * Repair event status position in timeline
+ [\#6141](https://github.com/matrix-org/matrix-react-sdk/pull/6141)
+ * Adapt for js-sdk MatrixClient conversion to TS
+ [\#6132](https://github.com/matrix-org/matrix-react-sdk/pull/6132)
+ * Improve pinned messages in Labs
+ [\#6096](https://github.com/matrix-org/matrix-react-sdk/pull/6096)
+ * Map phone number lookup results to their native rooms
+ [\#6136](https://github.com/matrix-org/matrix-react-sdk/pull/6136)
+ * Fix mx_Event containment rules and empty read avatar row
+ [\#6138](https://github.com/matrix-org/matrix-react-sdk/pull/6138)
+ * Improve switch room rendering
+ [\#6079](https://github.com/matrix-org/matrix-react-sdk/pull/6079)
+ * Add CSS containment rules for shorter reflow operations
+ [\#6127](https://github.com/matrix-org/matrix-react-sdk/pull/6127)
+ * ignore hash/fragment when de-duplicating links for url previews
+ [\#6135](https://github.com/matrix-org/matrix-react-sdk/pull/6135)
+ * Clicking jump to bottom resets room hash
+ [\#5823](https://github.com/matrix-org/matrix-react-sdk/pull/5823)
+ * Use passive option for scroll handlers
+ [\#6113](https://github.com/matrix-org/matrix-react-sdk/pull/6113)
+ * Optimise memberSort performance for large list
+ [\#6130](https://github.com/matrix-org/matrix-react-sdk/pull/6130)
+ * Tweak event border radius to match action bar
+ [\#6133](https://github.com/matrix-org/matrix-react-sdk/pull/6133)
+ * Log when we ignore a second call in a room
+ [\#6131](https://github.com/matrix-org/matrix-react-sdk/pull/6131)
+ * Performance monitoring measurements
+ [\#6041](https://github.com/matrix-org/matrix-react-sdk/pull/6041)
+
Changes in [3.23.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.23.0) (2021-06-07)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.23.0-rc.1...v3.23.0)
diff --git a/package.json b/package.json
index 4dc4bda3b2..7956c4b47f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
- "version": "3.23.0",
+ "version": "3.24.0",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
@@ -78,7 +78,7 @@
"katex": "^0.12.0",
"linkifyjs": "^2.1.9",
"lodash": "^4.17.20",
- "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
+ "matrix-js-sdk": "12.0.0",
"matrix-widget-api": "^0.1.0-beta.14",
"minimist": "^1.2.5",
"opus-recorder": "^8.0.3",
@@ -89,7 +89,7 @@
"qrcode": "^1.4.4",
"re-resizable": "^6.9.0",
"react": "^17.0.2",
- "react-beautiful-dnd": "^4.0.1",
+ "react-beautiful-dnd": "^13.1.0",
"react-dom": "^17.0.2",
"react-focus-lock": "^2.5.0",
"react-transition-group": "^4.4.1",
@@ -132,19 +132,20 @@
"@types/pako": "^1.0.1",
"@types/parse5": "^6.0.0",
"@types/qrcode": "^1.3.5",
- "@types/react": "^16.9",
- "@types/react-dom": "^16.9.10",
+ "@types/react": "^17.0.2",
+ "@types/react-beautiful-dnd": "^13.0.0",
+ "@types/react-dom": "^17.0.2",
"@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "^2.3.1",
"@types/zxcvbn": "^4.4.0",
"@typescript-eslint/eslint-plugin": "^4.14.0",
"@typescript-eslint/parser": "^4.14.0",
+ "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.6.3",
"chokidar": "^3.5.1",
"concurrently": "^5.3.0",
"enzyme": "^3.11.0",
- "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
"eslint": "7.18.0",
"eslint-config-matrix-org": "^0.2.0",
"eslint-plugin-babel": "^5.3.1",
@@ -157,7 +158,7 @@
"jest-environment-jsdom-sixteen": "^1.0.3",
"jest-fetch-mock": "^3.0.3",
"matrix-mock-request": "^1.2.3",
- "matrix-react-test-utils": "^0.2.2",
+ "matrix-react-test-utils": "^0.2.3",
"matrix-web-i18n": "github:matrix-org/matrix-web-i18n",
"react-test-renderer": "^17.0.2",
"rimraf": "^3.0.2",
@@ -167,9 +168,6 @@
"typescript": "^4.1.3",
"walk": "^2.3.14"
},
- "resolutions": {
- "**/@types/react": "^16.14"
- },
"jest": {
"testEnvironment": "./__test-utils__/environment.js",
"testMatch": [
diff --git a/res/css/_components.scss b/res/css/_components.scss
index 56403ea190..ec3af8655e 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -123,7 +123,6 @@
@import "./views/elements/_EventListSummary.scss";
@import "./views/elements/_FacePile.scss";
@import "./views/elements/_Field.scss";
-@import "./views/elements/_FormButton.scss";
@import "./views/elements/_ImageView.scss";
@import "./views/elements/_InfoTooltip.scss";
@import "./views/elements/_InlineSpinner.scss";
diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss
index c433ccf275..e64057d16c 100644
--- a/res/css/structures/_SpacePanel.scss
+++ b/res/css/structures/_SpacePanel.scss
@@ -31,7 +31,6 @@ $activeBorderColor: $secondary-fg-color;
// Create another flexbox so the Panel fills the container
display: flex;
flex-direction: column;
- overflow-y: auto;
.mx_SpacePanel_spaceTreeWrapper {
flex: 1;
@@ -69,6 +68,12 @@ $activeBorderColor: $secondary-fg-color;
cursor: pointer;
}
+ .mx_SpaceItem_dragging {
+ .mx_SpaceButton_toggleCollapse {
+ visibility: hidden;
+ }
+ }
+
.mx_SpaceTreeLevel {
display: flex;
flex-direction: column;
diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss
index 09f834a6e3..14e4c01389 100644
--- a/res/css/structures/_ToastContainer.scss
+++ b/res/css/structures/_ToastContainer.scss
@@ -134,8 +134,9 @@ limitations under the License.
.mx_Toast_buttons {
float: right;
display: flex;
+ gap: 5px;
- .mx_FormButton {
+ .mx_AccessibleButton {
min-width: 96px;
box-sizing: border-box;
}
diff --git a/res/css/views/beta/_BetaCard.scss b/res/css/views/beta/_BetaCard.scss
index 3463a653fc..1a8241b65f 100644
--- a/res/css/views/beta/_BetaCard.scss
+++ b/res/css/views/beta/_BetaCard.scss
@@ -19,49 +19,68 @@ limitations under the License.
padding: 24px;
background-color: $settings-profile-placeholder-bg-color;
border-radius: 8px;
- display: flex;
box-sizing: border-box;
- > div {
- .mx_BetaCard_title {
- font-weight: $font-semi-bold;
- font-size: $font-18px;
- line-height: $font-22px;
- color: $primary-fg-color;
- margin: 4px 0 14px;
+ .mx_BetaCard_columns {
+ display: flex;
- .mx_BetaCard_betaPill {
- margin-left: 12px;
+ > div {
+ .mx_BetaCard_title {
+ font-weight: $font-semi-bold;
+ font-size: $font-18px;
+ line-height: $font-22px;
+ color: $primary-fg-color;
+ margin: 4px 0 14px;
+
+ .mx_BetaCard_betaPill {
+ margin-left: 12px;
+ }
+ }
+
+ .mx_BetaCard_caption {
+ font-size: $font-15px;
+ line-height: $font-20px;
+ color: $secondary-fg-color;
+ margin-bottom: 20px;
+ }
+
+ .mx_BetaCard_buttons .mx_AccessibleButton {
+ display: block;
+ margin: 12px 0;
+ padding: 7px 40px;
+ width: auto;
+ }
+
+ .mx_BetaCard_disclaimer {
+ font-size: $font-12px;
+ line-height: $font-15px;
+ color: $secondary-fg-color;
+ margin-top: 20px;
}
}
- .mx_BetaCard_caption {
- font-size: $font-15px;
- line-height: $font-20px;
- color: $secondary-fg-color;
- margin-bottom: 20px;
- }
-
- .mx_AccessibleButton {
- display: block;
- margin: 12px 0;
- padding: 7px 40px;
- width: auto;
- }
-
- .mx_BetaCard_disclaimer {
- font-size: $font-12px;
- line-height: $font-15px;
- color: $secondary-fg-color;
- margin-top: 20px;
+ > img {
+ margin: auto 0 auto 20px;
+ width: 300px;
+ object-fit: contain;
+ height: 100%;
}
}
- > img {
- margin: auto 0 auto 20px;
- width: 300px;
- object-fit: contain;
- height: 100%;
+ .mx_BetaCard_relatedSettings {
+ .mx_SettingsFlag {
+ margin: 16px 0 0;
+ font-size: $font-15px;
+ line-height: $font-24px;
+ color: $primary-fg-color;
+
+ .mx_SettingsFlag_microcopy {
+ margin-top: 4px;
+ font-size: $font-12px;
+ line-height: $font-15px;
+ color: $secondary-fg-color;
+ }
+ }
}
}
diff --git a/res/css/views/context_menus/_TagTileContextMenu.scss b/res/css/views/context_menus/_TagTileContextMenu.scss
index 8929c8906e..d707f4ce7c 100644
--- a/res/css/views/context_menus/_TagTileContextMenu.scss
+++ b/res/css/views/context_menus/_TagTileContextMenu.scss
@@ -38,6 +38,15 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/view-community.svg');
}
+.mx_TagTileContextMenu_moveUp::before {
+ transform: rotate(180deg);
+ mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
+}
+
+.mx_TagTileContextMenu_moveDown::before {
+ mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
+}
+
.mx_TagTileContextMenu_hideCommunity::before {
mask-image: url('$(res)/img/element-icons/hide.svg');
}
diff --git a/res/css/views/elements/_FormButton.scss b/res/css/views/elements/_FormButton.scss
deleted file mode 100644
index eda201ff03..0000000000
--- a/res/css/views/elements/_FormButton.scss
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
-Copyright 2019 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-.mx_FormButton {
- line-height: $font-16px;
- padding: 5px 15px;
- font-size: $font-12px;
- height: min-content;
-
- &:not(:last-child) {
- margin-right: 8px;
- }
-
- &.mx_AccessibleButton_kind_primary {
- color: $accent-color;
- background-color: $accent-bg-color;
- }
-
- &.mx_AccessibleButton_kind_danger {
- color: $notice-primary-color;
- background-color: $notice-primary-bg-color;
- }
-
- &.mx_AccessibleButton_kind_secondary {
- color: $secondary-fg-color;
- border: 1px solid $secondary-fg-color;
- background-color: unset;
- }
-}
diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss
index 87420ae4e7..6632ccddf9 100644
--- a/res/css/views/right_panel/_UserInfo.scss
+++ b/res/css/views/right_panel/_UserInfo.scss
@@ -259,16 +259,6 @@ limitations under the License.
.mx_AccessibleButton.mx_AccessibleButton_hasKind {
padding: 8px 18px;
-
- &.mx_AccessibleButton_kind_primary {
- color: $accent-color;
- background-color: $accent-bg-color;
- }
-
- &.mx_AccessibleButton_kind_danger {
- color: $notice-primary-color;
- background-color: $notice-primary-bg-color;
- }
}
.mx_VerificationShowSas .mx_AccessibleButton,
diff --git a/res/css/views/right_panel/_VerificationPanel.scss b/res/css/views/right_panel/_VerificationPanel.scss
index a8466a1626..12148b09de 100644
--- a/res/css/views/right_panel/_VerificationPanel.scss
+++ b/res/css/views/right_panel/_VerificationPanel.scss
@@ -58,7 +58,7 @@ limitations under the License.
}
.mx_VerificationPanel_reciprocate_section {
- .mx_FormButton {
+ .mx_AccessibleButton {
width: 100%;
box-sizing: border-box;
padding: 10px;
diff --git a/res/css/views/spaces/_SpaceBasicSettings.scss b/res/css/views/spaces/_SpaceBasicSettings.scss
index e6e06e7181..68e8723f11 100644
--- a/res/css/views/spaces/_SpaceBasicSettings.scss
+++ b/res/css/views/spaces/_SpaceBasicSettings.scss
@@ -73,7 +73,7 @@ limitations under the License.
}
}
- .mx_FormButton {
+ .mx_AccessibleButton {
padding: 8px 22px;
margin-left: auto;
display: block;
diff --git a/res/css/views/voip/_CallContainer.scss b/res/css/views/voip/_CallContainer.scss
index 8262075559..168a8bb74b 100644
--- a/res/css/views/voip/_CallContainer.scss
+++ b/res/css/views/voip/_CallContainer.scss
@@ -98,5 +98,29 @@ limitations under the License.
line-height: $font-24px;
}
}
+
+ .mx_IncomingCallBox_iconButton {
+ position: absolute;
+ right: 8px;
+
+ &::before {
+ content: '';
+
+ height: 20px;
+ width: 20px;
+ background-color: $icon-button-color;
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ mask-position: center;
+ }
+ }
+
+ .mx_IncomingCallBox_silence::before {
+ mask-image: url('$(res)/img/voip/silence.svg');
+ }
+
+ .mx_IncomingCallBox_unSilence::before {
+ mask-image: url('$(res)/img/voip/un-silence.svg');
+ }
}
}
diff --git a/res/img/voip/silence.svg b/res/img/voip/silence.svg
new file mode 100644
index 0000000000..332932dfff
--- /dev/null
+++ b/res/img/voip/silence.svg
@@ -0,0 +1,3 @@
+
diff --git a/res/img/voip/un-silence.svg b/res/img/voip/un-silence.svg
new file mode 100644
index 0000000000..c00b366f84
--- /dev/null
+++ b/res/img/voip/un-silence.svg
@@ -0,0 +1,4 @@
+
diff --git a/scripts/ci/Dockerfile b/scripts/ci/Dockerfile
index 3fdd0d7bf6..1d1425c865 100644
--- a/scripts/ci/Dockerfile
+++ b/scripts/ci/Dockerfile
@@ -3,6 +3,6 @@
# docker push vectorim/element-web-ci-e2etests-env:latest
FROM node:14-buster
RUN apt-get update
-RUN apt-get -y install build-essential python3-dev libffi-dev python-pip python-setuptools sqlite3 libssl-dev python-virtualenv libjpeg-dev libxslt1-dev uuid-runtime
+RUN apt-get -y install jq build-essential python3-dev libffi-dev python-pip python-setuptools sqlite3 libssl-dev python-virtualenv libjpeg-dev libxslt1-dev uuid-runtime
# dependencies for chrome (installed by puppeteer)
RUN apt-get -y install gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
diff --git a/scripts/ci/end-to-end-tests.sh b/scripts/ci/prepare-end-to-end-tests.sh
similarity index 65%
rename from scripts/ci/end-to-end-tests.sh
rename to scripts/ci/prepare-end-to-end-tests.sh
index edb8870d8e..147e1f6445 100755
--- a/scripts/ci/end-to-end-tests.sh
+++ b/scripts/ci/prepare-end-to-end-tests.sh
@@ -1,8 +1,4 @@
#!/bin/bash
-#
-# script which is run by the CI build (after `yarn test`).
-#
-# clones element-web develop and runs the tests against our version of react-sdk.
set -ev
@@ -19,7 +15,7 @@ cd element-web
element_web_dir=`pwd`
CI_PACKAGE=true yarn build
cd ..
-# run end to end tests
+# prepare end to end tests
pushd test/end-to-end-tests
ln -s $element_web_dir element/element-web
# PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh
@@ -28,9 +24,4 @@ echo "--- Install synapse & other dependencies"
./install.sh
# install static webserver to server symlinked local copy of element
./element/install-webserver.sh
-rm -r logs || true
-mkdir logs
-echo "+++ Running end-to-end tests"
-TESTS_STARTED=1
-./run.sh --no-sandbox --log-directory logs/
popd
diff --git a/scripts/ci/run-end-to-end-tests.sh b/scripts/ci/run-end-to-end-tests.sh
new file mode 100755
index 0000000000..3c99391fc7
--- /dev/null
+++ b/scripts/ci/run-end-to-end-tests.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+set -ev
+
+handle_error() {
+ EXIT_CODE=$?
+ exit $EXIT_CODE
+}
+
+trap 'handle_error' ERR
+
+# run end to end tests
+pushd test/end-to-end-tests
+rm -r logs || true
+mkdir logs
+echo "--- Running end-to-end tests"
+TESTS_STARTED=1
+./run.sh --no-sandbox --log-directory logs/
+popd
diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh
index fe1f49c361..0990af70ce 100755
--- a/scripts/fetchdep.sh
+++ b/scripts/fetchdep.sh
@@ -22,29 +22,51 @@ clone() {
}
# Try the PR author's branch in case it exists on the deps as well.
-# First we check if BUILDKITE_BRANCH is defined,
-# if it isn't we can assume this is a Netlify build
-if [ -z ${BUILDKITE_BRANCH+x} ]; then
- # Netlify doesn't give us info about the fork so we have to get it from GitHub API
- apiEndpoint="https://api.github.com/repos/matrix-org/matrix-react-sdk/pulls/"
- apiEndpoint+=$REVIEW_ID
- head=$(curl $apiEndpoint | jq -r '.head.label')
-else
+# First we check if GITHUB_HEAD_REF is defined,
+# Then we check if BUILDKITE_BRANCH is defined,
+# if they aren't we can assume this is a Netlify build
+if [ -n "$GITHUB_HEAD_REF" ]; then
+ head=$GITHUB_HEAD_REF
+elif [ -n "$BUILDKITE_BRANCH" ]; then
head=$BUILDKITE_BRANCH
+else
+ # Netlify doesn't give us info about the fork so we have to get it from GitHub API
+ apiEndpoint="https://api.github.com/repos/matrix-org/matrix-react-sdk/pulls/"
+ apiEndpoint+=$REVIEW_ID
+ head=$(curl $apiEndpoint | jq -r '.head.label')
fi
-# If head is set, it will contain either:
+# If head is set, it will contain on Buildkite either:
# * "branch" when the author's branch and target branch are in the same repo
# * "fork:branch" when the author's branch is in their fork or if this is a Netlify build
# We can split on `:` into an array to check.
+# For GitHub Actions we need to inspect GITHUB_REPOSITORY and GITHUB_ACTOR
+# to determine whether the branch is from a fork or not
BRANCH_ARRAY=(${head//:/ })
if [[ "${#BRANCH_ARRAY[@]}" == "1" ]]; then
- clone $deforg $defrepo $BUILDKITE_BRANCH
+
+ if [ -n "$GITHUB_HEAD_REF" ]; then
+ if [[ "$GITHUB_REPOSITORY" == "$deforg"* ]]; then
+ clone $deforg $defrepo $GITHUB_HEAD_REF
+ else
+ REPO_ARRAY=(${GITHUB_REPOSITORY//\// })
+ clone $REPO_ARRAY[0] $defrepo $GITHUB_HEAD_REF
+ fi
+ else
+ clone $deforg $defrepo $BUILDKITE_BRANCH
+ fi
+
elif [[ "${#BRANCH_ARRAY[@]}" == "2" ]]; then
clone ${BRANCH_ARRAY[0]} $defrepo ${BRANCH_ARRAY[1]}
fi
+
# Try the target branch of the push or PR.
-clone $deforg $defrepo $BUILDKITE_PULL_REQUEST_BASE_BRANCH
+if [ -n $GITHUB_BASE_REF ]; then
+ clone $deforg $defrepo $GITHUB_BASE_REF
+elif [ -n $BUILDKITE_PULL_REQUEST_BASE_BRANCH ]; then
+ clone $deforg $defrepo $BUILDKITE_PULL_REQUEST_BASE_BRANCH
+fi
+
# Try HEAD which is the branch name in Netlify (not BRANCH which is pull/xxxx/head for PR builds)
clone $deforg $defrepo $HEAD
# Use the default branch as the last resort.
diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index 22280b8a28..7eff341095 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -44,6 +44,7 @@ import { EventIndexPeg } from "../indexing/EventIndexPeg";
import {VoiceRecordingStore} from "../stores/VoiceRecordingStore";
import PerformanceMonitor from "../performance";
import UIStore from "../stores/UIStore";
+import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
declare global {
interface Window {
@@ -84,6 +85,7 @@ declare global {
mxPerformanceMonitor: PerformanceMonitor;
mxPerformanceEntryNames: any;
mxUIStore: UIStore;
+ mxSetupEncryptionStore?: SetupEncryptionStore;
}
interface Document {
@@ -111,19 +113,6 @@ declare global {
usageDetails?: {[key: string]: number};
}
- export interface ISettledFulfilled {
- status: "fulfilled";
- value: T;
- }
- export interface ISettledRejected {
- status: "rejected";
- reason: any;
- }
-
- interface PromiseConstructor {
- allSettled(promises: Promise[]): Promise | ISettledRejected>>;
- }
-
interface HTMLAudioElement {
type?: string;
// sinkId & setSinkId are experimental and typescript doesn't know about them
diff --git a/src/Avatar.ts b/src/Avatar.ts
index a6499c688e..4c4bd1c265 100644
--- a/src/Avatar.ts
+++ b/src/Avatar.ts
@@ -14,18 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {RoomMember} from "matrix-js-sdk/src/models/room-member";
-import {User} from "matrix-js-sdk/src/models/user";
-import {Room} from "matrix-js-sdk/src/models/room";
+import { RoomMember } from "matrix-js-sdk/src/models/room-member";
+import { User } from "matrix-js-sdk/src/models/user";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { ResizeMethod } from "matrix-js-sdk/src/@types/partials";
import DMRoomMap from './utils/DMRoomMap';
-import {mediaFromMxc} from "./customisations/Media";
+import { mediaFromMxc } from "./customisations/Media";
import SettingsStore from "./settings/SettingsStore";
-export type ResizeMethod = "crop" | "scale";
-
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
-export function avatarUrlForMember(member: RoomMember, width: number, height: number, resizeMethod: ResizeMethod) {
+export function avatarUrlForMember(
+ member: RoomMember,
+ width: number,
+ height: number,
+ resizeMethod: ResizeMethod,
+): string {
let url: string;
if (member?.getMxcAvatarUrl()) {
url = mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod);
@@ -39,7 +43,12 @@ export function avatarUrlForMember(member: RoomMember, width: number, height: nu
return url;
}
-export function avatarUrlForUser(user: User, width: number, height: number, resizeMethod?: ResizeMethod) {
+export function avatarUrlForUser(
+ user: Pick,
+ width: number,
+ height: number,
+ resizeMethod?: ResizeMethod,
+): string | null {
if (!user.avatarUrl) return null;
return mediaFromMxc(user.avatarUrl).getThumbnailOfSourceHttp(width, height, resizeMethod);
}
diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx
index bf7cb3473d..448b1cb780 100644
--- a/src/CallHandler.tsx
+++ b/src/CallHandler.tsx
@@ -99,7 +99,7 @@ const CHECK_PROTOCOLS_ATTEMPTS = 3;
// (and store the ID of their native room)
export const VIRTUAL_ROOM_EVENT_TYPE = 'im.vector.is_virtual_room';
-enum AudioID {
+export enum AudioID {
Ring = 'ringAudio',
Ringback = 'ringbackAudio',
CallEnd = 'callendAudio',
diff --git a/src/Modal.tsx b/src/Modal.tsx
index ce11c571b6..2f2d5a2d52 100644
--- a/src/Modal.tsx
+++ b/src/Modal.tsx
@@ -385,7 +385,7 @@ export class ModalManager {
);
- ReactDOM.render(dialog, ModalManager.getOrCreateContainer());
+ setImmediate(() => ReactDOM.render(dialog, ModalManager.getOrCreateContainer()));
} else {
// This is safe to call repeatedly if we happen to do that
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer());
diff --git a/src/Searching.js b/src/Searching.js
index 2b17aee054..596dd2f3d4 100644
--- a/src/Searching.js
+++ b/src/Searching.js
@@ -468,7 +468,7 @@ function restoreEncryptionInfo(searchResultSlice = []) {
ev.event.curve25519Key,
ev.event.ed25519Key,
);
- ev._forwardingCurve25519KeyChain = ev.event.forwardingCurve25519KeyChain;
+ ev.forwardingCurve25519KeyChain = ev.event.forwardingCurve25519KeyChain;
delete ev.event.curve25519Key;
delete ev.event.ed25519Key;
diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx
index 4a7b37b5e5..bd2133f3d9 100644
--- a/src/SlashCommands.tsx
+++ b/src/SlashCommands.tsx
@@ -17,8 +17,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-
import * as React from 'react';
+import { User } from "matrix-js-sdk/src/models/user";
import * as ContentHelpers from 'matrix-js-sdk/src/content-helpers';
import {MatrixClientPeg} from './MatrixClientPeg';
@@ -150,6 +150,10 @@ function success(promise?: Promise) {
return {promise};
}
+function successSync(value: any) {
+ return success(Promise.resolve(value));
+}
+
/* Disable the "unexpected this" error for these commands - all of the run
* functions are called with `this` bound to the Command instance.
*/
@@ -160,7 +164,7 @@ export const Commands = [
args: '',
description: _td('Sends the given message as a spoiler'),
runFn: function(roomId, message) {
- return success(ContentHelpers.makeHtmlMessage(
+ return successSync(ContentHelpers.makeHtmlMessage(
message,
`${message}`,
));
@@ -176,7 +180,7 @@ export const Commands = [
if (args) {
message = message + ' ' + args;
}
- return success(ContentHelpers.makeTextMessage(message));
+ return successSync(ContentHelpers.makeTextMessage(message));
},
category: CommandCategories.messages,
}),
@@ -189,7 +193,7 @@ export const Commands = [
if (args) {
message = message + ' ' + args;
}
- return success(ContentHelpers.makeTextMessage(message));
+ return successSync(ContentHelpers.makeTextMessage(message));
},
category: CommandCategories.messages,
}),
@@ -202,7 +206,7 @@ export const Commands = [
if (args) {
message = message + ' ' + args;
}
- return success(ContentHelpers.makeTextMessage(message));
+ return successSync(ContentHelpers.makeTextMessage(message));
},
category: CommandCategories.messages,
}),
@@ -215,7 +219,7 @@ export const Commands = [
if (args) {
message = message + ' ' + args;
}
- return success(ContentHelpers.makeTextMessage(message));
+ return successSync(ContentHelpers.makeTextMessage(message));
},
category: CommandCategories.messages,
}),
@@ -224,7 +228,7 @@ export const Commands = [
args: '',
description: _td('Sends a message as plain text, without interpreting it as markdown'),
runFn: function(roomId, messages) {
- return success(ContentHelpers.makeTextMessage(messages));
+ return successSync(ContentHelpers.makeTextMessage(messages));
},
category: CommandCategories.messages,
}),
@@ -233,7 +237,7 @@ export const Commands = [
args: '',
description: _td('Sends a message as html, without interpreting it as markdown'),
runFn: function(roomId, messages) {
- return success(ContentHelpers.makeHtmlMessage(messages, messages));
+ return successSync(ContentHelpers.makeHtmlMessage(messages, messages));
},
category: CommandCategories.messages,
}),
@@ -978,7 +982,7 @@ export const Commands = [
args: '',
runFn: function(roomId, args) {
if (!args) return reject(this.getUserId());
- return success(ContentHelpers.makeHtmlMessage(args, textToHtmlRainbow(args)));
+ return successSync(ContentHelpers.makeHtmlMessage(args, textToHtmlRainbow(args)));
},
category: CommandCategories.messages,
}),
@@ -988,7 +992,7 @@ export const Commands = [
args: '',
runFn: function(roomId, args) {
if (!args) return reject(this.getUserId());
- return success(ContentHelpers.makeHtmlEmote(args, textToHtmlRainbow(args)));
+ return successSync(ContentHelpers.makeHtmlEmote(args, textToHtmlRainbow(args)));
},
category: CommandCategories.messages,
}),
@@ -1015,9 +1019,8 @@ export const Commands = [
const member = MatrixClientPeg.get().getRoom(roomId).getMember(userId);
dis.dispatch({
action: Action.ViewUser,
- // XXX: We should be using a real member object and not assuming what the
- // receiver wants.
- member: member || {userId},
+ // XXX: We should be using a real member object and not assuming what the receiver wants.
+ member: member || { userId } as User,
});
return success();
},
diff --git a/src/VoipUserMapper.ts b/src/VoipUserMapper.ts
index d576a5434c..dacb4262bd 100644
--- a/src/VoipUserMapper.ts
+++ b/src/VoipUserMapper.ts
@@ -24,7 +24,9 @@ import { Room } from 'matrix-js-sdk/src/models/room';
// is sip virtual: there could be others in the future.
export default class VoipUserMapper {
- private virtualRoomIdCache = new Set();
+ // We store mappings of virtual -> native room IDs here until the local echo for the
+ // account data arrives.
+ private virtualToNativeRoomIdCache = new Map();
public static sharedInstance(): VoipUserMapper {
if (window.mxVoipUserMapper === undefined) window.mxVoipUserMapper = new VoipUserMapper();
@@ -49,10 +51,20 @@ export default class VoipUserMapper {
native_room: roomId,
});
+ this.virtualToNativeRoomIdCache.set(virtualRoomId, roomId);
+
return virtualRoomId;
}
public nativeRoomForVirtualRoom(roomId: string): string {
+ const cachedNativeRoomId = this.virtualToNativeRoomIdCache.get(roomId);
+ if (cachedNativeRoomId) {
+ console.log(
+ "Returning native room ID " + cachedNativeRoomId + " for virtual room ID " + roomId + " from cache",
+ );
+ return cachedNativeRoomId;
+ }
+
const virtualRoom = MatrixClientPeg.get().getRoom(roomId);
if (!virtualRoom) return null;
const virtualRoomEvent = virtualRoom.getAccountData(VIRTUAL_ROOM_EVENT_TYPE);
@@ -67,7 +79,7 @@ export default class VoipUserMapper {
public isVirtualRoom(room: Room): boolean {
if (this.nativeRoomForVirtualRoom(room.roomId)) return true;
- if (this.virtualRoomIdCache.has(room.roomId)) return true;
+ if (this.virtualToNativeRoomIdCache.has(room.roomId)) return true;
// also look in the create event for the claimed native room ID, which is the only
// way we can recognise a virtual room we've created when it first arrives down
@@ -110,7 +122,7 @@ export default class VoipUserMapper {
// also put this room in the virtual room ID cache so isVirtualRoom return the right answer
// in however long it takes for the echo of setAccountData to come down the sync
- this.virtualRoomIdCache.add(invitedRoom.roomId);
+ this.virtualToNativeRoomIdCache.set(invitedRoom.roomId, nativeRoom.roomId);
}
}
}
diff --git a/src/accessibility/KeyboardShortcuts.tsx b/src/accessibility/KeyboardShortcuts.tsx
index 2a3e576e31..1cd5408210 100644
--- a/src/accessibility/KeyboardShortcuts.tsx
+++ b/src/accessibility/KeyboardShortcuts.tsx
@@ -57,6 +57,8 @@ export enum Modifiers {
// Meta-modifier: isMac ? CMD : CONTROL
export const CMD_OR_CTRL = isMac ? Modifiers.COMMAND : Modifiers.CONTROL;
+// Meta-key representing the digits [0-9] often found at the top of standard keyboard layouts
+export const DIGITS = "digits";
interface IKeybind {
modifiers?: Modifiers[];
@@ -319,6 +321,7 @@ const alternateKeyName: Record = {
[Key.SPACE]: _td("Space"),
[Key.HOME]: _td("Home"),
[Key.END]: _td("End"),
+ [DIGITS]: _td("[number]"),
};
const keyIcon: Record = {
[Key.ARROW_UP]: "↑",
diff --git a/src/autocomplete/Autocompleter.ts b/src/autocomplete/Autocompleter.ts
index 5409825f45..ea8eddbb8d 100644
--- a/src/autocomplete/Autocompleter.ts
+++ b/src/autocomplete/Autocompleter.ts
@@ -15,8 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {ReactElement} from 'react';
-import Room from 'matrix-js-sdk/src/models/room';
+import { ReactElement } from 'react';
+import { Room } from 'matrix-js-sdk/src/models/room';
+
import CommandProvider from './CommandProvider';
import CommunityProvider from './CommunityProvider';
import DuckDuckGoProvider from './DuckDuckGoProvider';
@@ -24,7 +25,7 @@ import RoomProvider from './RoomProvider';
import UserProvider from './UserProvider';
import EmojiProvider from './EmojiProvider';
import NotifProvider from './NotifProvider';
-import {timeout} from "../utils/promise";
+import { timeout } from "../utils/promise";
import AutocompleteProvider, {ICommand} from "./AutocompleteProvider";
import SettingsStore from "../settings/SettingsStore";
import SpaceProvider from "./SpaceProvider";
diff --git a/src/autocomplete/NotifProvider.tsx b/src/autocomplete/NotifProvider.tsx
index 0bc7ead097..827f4aa885 100644
--- a/src/autocomplete/NotifProvider.tsx
+++ b/src/autocomplete/NotifProvider.tsx
@@ -15,7 +15,8 @@ limitations under the License.
*/
import React from 'react';
-import Room from "matrix-js-sdk/src/models/room";
+import { Room } from "matrix-js-sdk/src/models/room";
+
import AutocompleteProvider from './AutocompleteProvider';
import { _t } from '../languageHandler';
import {MatrixClientPeg} from '../MatrixClientPeg';
diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx
index ad55b19101..5240db6955 100644
--- a/src/autocomplete/RoomProvider.tsx
+++ b/src/autocomplete/RoomProvider.tsx
@@ -17,28 +17,24 @@ limitations under the License.
*/
import React from "react";
-import {uniqBy, sortBy} from "lodash";
-import Room from "matrix-js-sdk/src/models/room";
+import { uniqBy, sortBy } from "lodash";
+import { Room } from "matrix-js-sdk/src/models/room";
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
-import {MatrixClientPeg} from '../MatrixClientPeg';
+import { MatrixClientPeg } from '../MatrixClientPeg';
import QueryMatcher from './QueryMatcher';
-import {PillCompletion} from './Components';
-import {makeRoomPermalink} from "../utils/permalinks/Permalinks";
-import {ICompletion, ISelectionRange} from "./Autocompleter";
+import { PillCompletion } from './Components';
+import { makeRoomPermalink } from "../utils/permalinks/Permalinks";
+import { ICompletion, ISelectionRange } from "./Autocompleter";
import RoomAvatar from '../components/views/avatars/RoomAvatar';
import SettingsStore from "../settings/SettingsStore";
const ROOM_REGEX = /\B#\S*/g;
-function score(query: string, space: string) {
- const index = space.indexOf(query);
- if (index === -1) {
- return Infinity;
- } else {
- return index;
- }
+// Prefer canonical aliases over non-canonical ones
+function canonicalScore(displayedAlias: string, room: Room): number {
+ return displayedAlias === room.getCanonicalAlias() ? 0 : 1;
}
function matcherObject(room: Room, displayedAlias: string, matchName = "") {
@@ -106,7 +102,7 @@ export default class RoomProvider extends AutocompleteProvider {
const matchedString = command[0];
completions = this.matcher.match(matchedString, limit);
completions = sortBy(completions, [
- (c) => score(matchedString, c.displayedAlias),
+ (c) => canonicalScore(c.displayedAlias, c.room),
(c) => c.displayedAlias.length,
]);
completions = uniqBy(completions, (match) => match.room);
diff --git a/src/autocomplete/UserProvider.tsx b/src/autocomplete/UserProvider.tsx
index 3cf43d0b84..687b477133 100644
--- a/src/autocomplete/UserProvider.tsx
+++ b/src/autocomplete/UserProvider.tsx
@@ -20,19 +20,19 @@ limitations under the License.
import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
-import {PillCompletion} from './Components';
+import { PillCompletion } from './Components';
import * as sdk from '../index';
import QueryMatcher from './QueryMatcher';
-import {sortBy} from 'lodash';
-import {MatrixClientPeg} from '../MatrixClientPeg';
+import { sortBy } from 'lodash';
+import { MatrixClientPeg } from '../MatrixClientPeg';
-import MatrixEvent from "matrix-js-sdk/src/models/event";
-import Room from "matrix-js-sdk/src/models/room";
-import RoomMember from "matrix-js-sdk/src/models/room-member";
-import RoomState from "matrix-js-sdk/src/models/room-state";
-import EventTimeline from "matrix-js-sdk/src/models/event-timeline";
-import {makeUserPermalink} from "../utils/permalinks/Permalinks";
-import {ICompletion, ISelectionRange} from "./Autocompleter";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { RoomMember } from "matrix-js-sdk/src/models/room-member";
+import { RoomState } from "matrix-js-sdk/src/models/room-state";
+import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
+import { makeUserPermalink } from "../utils/permalinks/Permalinks";
+import { ICompletion, ISelectionRange } from "./Autocompleter";
const USER_REGEX = /\B@\S*/g;
diff --git a/src/components/structures/AutoHideScrollbar.tsx b/src/components/structures/AutoHideScrollbar.tsx
index 66f998b616..8650224fb3 100644
--- a/src/components/structures/AutoHideScrollbar.tsx
+++ b/src/components/structures/AutoHideScrollbar.tsx
@@ -15,9 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
+import React, { HTMLAttributes } from "react";
-interface IProps {
+interface IProps extends HTMLAttributes {
className?: string;
onScroll?: () => void;
onWheel?: () => void;
@@ -52,14 +52,18 @@ export default class AutoHideScrollbar extends React.Component {
}
public render() {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { className, onScroll, onWheel, style, tabIndex, wrappedRef, children, ...otherProps } = this.props;
+
return (
- { this.props.children }
+ { children }
);
}
}
diff --git a/src/components/structures/GroupFilterPanel.js b/src/components/structures/GroupFilterPanel.js
index 2ff91e4976..f1c28d588a 100644
--- a/src/components/structures/GroupFilterPanel.js
+++ b/src/components/structures/GroupFilterPanel.js
@@ -24,7 +24,6 @@ import * as sdk from '../../index';
import dis from '../../dispatcher/dispatcher';
import { _t } from '../../languageHandler';
-import { Droppable } from 'react-beautiful-dnd';
import classNames from 'classnames';
import MatrixClientContext from "../../contexts/MatrixClientContext";
import AutoHideScrollbar from "./AutoHideScrollbar";
@@ -83,7 +82,7 @@ class GroupFilterPanel extends React.Component {
}
};
- onMouseDown = e => {
+ onClick = e => {
// only dispatch if its not a no-op
if (this.state.selectedTags.length > 0) {
dis.dispatch({action: 'deselect_tags'});
@@ -151,28 +150,15 @@ class GroupFilterPanel extends React.Component {
return
-
- { (provided, snapshot) => (
-
- { this.renderGlobalIcon() }
- { tags }
-
- {createButton}
-
- { provided.placeholder }
-
- ) }
-
+
+ { this.renderGlobalIcon() }
+ { tags }
+
+ { createButton }
+
+
;
}
diff --git a/src/components/structures/IndicatorScrollbar.js b/src/components/structures/IndicatorScrollbar.js
index 51a3b287f0..25dcaeed39 100644
--- a/src/components/structures/IndicatorScrollbar.js
+++ b/src/components/structures/IndicatorScrollbar.js
@@ -185,21 +185,24 @@ export default class IndicatorScrollbar extends React.Component {
};
render() {
+ // eslint-disable-next-line no-unused-vars
+ const { children, trackHorizontalOverflow, verticalScrollsHorizontally, ...otherProps } = this.props;
+
const leftIndicatorStyle = {left: this.state.leftIndicatorOffset};
const rightIndicatorStyle = {right: this.state.rightIndicatorOffset};
- const leftOverflowIndicator = this.props.trackHorizontalOverflow
+ const leftOverflowIndicator = trackHorizontalOverflow
? : null;
- const rightOverflowIndicator = this.props.trackHorizontalOverflow
+ const rightOverflowIndicator = trackHorizontalOverflow
? : null;
return (
{ leftOverflowIndicator }
- { this.props.children }
+ { children }
{ rightOverflowIndicator }
);
}
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index 3091278e3a..e3d6b1ab9c 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -19,7 +19,6 @@ limitations under the License.
import * as React from 'react';
import * as PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk/src/client';
-import { DragDropContext } from 'react-beautiful-dnd';
import {Key} from '../../Keyboard';
import PageTypes from '../../PageTypes';
@@ -30,8 +29,6 @@ import dis from '../../dispatcher/dispatcher';
import { IMatrixClientCreds } from '../../MatrixClientPeg';
import SettingsStore from "../../settings/SettingsStore";
-import TagOrderActions from '../../actions/TagOrderActions';
-import RoomListActions from '../../actions/RoomListActions';
import ResizeHandle from '../views/elements/ResizeHandle';
import {Resizer, CollapseDistributor} from '../../resizer';
import MatrixClientContext from "../../contexts/MatrixClientContext";
@@ -569,50 +566,6 @@ class LoggedInView extends React.Component {
}
};
- _onDragEnd = (result) => {
- // Dragged to an invalid destination, not onto a droppable
- if (!result.destination) {
- return;
- }
-
- const dest = result.destination.droppableId;
-
- if (dest === 'tag-panel-droppable') {
- // Could be "GroupTile +groupId:domain"
- const draggableId = result.draggableId.split(' ').pop();
-
- // Dispatch synchronously so that the GroupFilterPanel receives an
- // optimistic update from GroupFilterOrderStore before the previous
- // state is shown.
- dis.dispatch(TagOrderActions.moveTag(
- this._matrixClient,
- draggableId,
- result.destination.index,
- ), true);
- } else if (dest.startsWith('room-sub-list-droppable_')) {
- this._onRoomTileEndDrag(result);
- }
- };
-
- _onRoomTileEndDrag = (result) => {
- let newTag = result.destination.droppableId.split('_')[1];
- let prevTag = result.source.droppableId.split('_')[1];
- if (newTag === 'undefined') newTag = undefined;
- if (prevTag === 'undefined') prevTag = undefined;
-
- const roomId = result.draggableId.split('_')[1];
-
- const oldIndex = result.source.index;
- const newIndex = result.destination.index;
-
- dis.dispatch(RoomListActions.tagRoom(
- this._matrixClient,
- this._matrixClient.getRoom(roomId),
- prevTag, newTag,
- oldIndex, newIndex,
- ), true);
- };
-
render() {
const RoomView = sdk.getComponent('structures.RoomView');
const UserView = sdk.getComponent('structures.UserView');
@@ -679,17 +632,15 @@ class LoggedInView extends React.Component {
aria-hidden={this.props.hideToSRUsers}
>
-
-
- { SettingsStore.getValue("feature_spaces") ? : null }
-
-
- { pageElement }
-
-
+
+ { SettingsStore.getValue("feature_spaces") ? : null }
+
+
+ { pageElement }
+
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index 16da9321e2..0af2d3d635 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -1953,6 +1953,7 @@ export default class MatrixChat extends React.PureComponent {
// Create and start the client
await Lifecycle.setLoggedIn(credentials);
await this.postLoginSetup();
+
PerformanceMonitor.instance.stop(PerformanceEntryNames.LOGIN);
PerformanceMonitor.instance.stop(PerformanceEntryNames.REGISTER);
};
diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js
index 1fab6c4348..d0a2fbff41 100644
--- a/src/components/structures/MyGroups.js
+++ b/src/components/structures/MyGroups.js
@@ -82,8 +82,7 @@ export default class MyGroups extends React.Component {
{ _t(
- "To set up a filter, drag a community avatar over to the filter panel on " +
- "the far left hand side of the screen. You can click on an avatar in the " +
+ "You can click on an avatar in the " +
"filter panel at any time to see only the rooms and people associated " +
"with that community.",
) }
diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js
index b2f0c70bd7..7d74229421 100644
--- a/src/components/structures/RoomStatusBar.js
+++ b/src/components/structures/RoomStatusBar.js
@@ -41,7 +41,7 @@ export function getUnsentMessages(room) {
}
@replaceableComponent("structures.RoomStatusBar")
-export default class RoomStatusBar extends React.Component {
+export default class RoomStatusBar extends React.PureComponent {
static propTypes = {
// the room this statusbar is representing.
room: PropTypes.object.isRequired,
diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx
index fe90d2f873..885851e8e6 100644
--- a/src/components/structures/RoomView.tsx
+++ b/src/components/structures/RoomView.tsx
@@ -23,8 +23,9 @@ limitations under the License.
import React, { createRef } from 'react';
import classNames from 'classnames';
-import { Room } from "matrix-js-sdk/src/models/room";
+import { IRecommendedVersion, NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { SearchResult } from "matrix-js-sdk/src/models/search-result";
import { EventSubscription } from "fbemitter";
import shouldHideEvent from '../../shouldHideEvent';
@@ -59,7 +60,7 @@ import ScrollPanel from "./ScrollPanel";
import TimelinePanel from "./TimelinePanel";
import ErrorBoundary from "../views/elements/ErrorBoundary";
import RoomPreviewBar from "../views/rooms/RoomPreviewBar";
-import SearchBar from "../views/rooms/SearchBar";
+import SearchBar, { SearchScope } from "../views/rooms/SearchBar";
import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar";
import AuxPanel from "../views/rooms/AuxPanel";
import RoomHeader from "../views/rooms/RoomHeader";
@@ -80,7 +81,6 @@ import { objectHasDiff } from "../../utils/objects";
import SpaceRoomView from "./SpaceRoomView";
import { IOpts } from "../../createRoom";
import { replaceableComponent } from "../../utils/replaceableComponent";
-import { omit } from 'lodash';
import UIStore from "../../stores/UIStore";
const DEBUG = false;
@@ -139,11 +139,11 @@ export interface IState {
draggingFile: boolean;
searching: boolean;
searchTerm?: string;
- searchScope?: "All" | "Room";
+ searchScope?: SearchScope;
searchResults?: XOR<{}, {
count: number;
highlights: string[];
- results: MatrixEvent[];
+ results: SearchResult[];
next_batch: string; // eslint-disable-line camelcase
}>;
searchHighlights?: string[];
@@ -172,11 +172,7 @@ export interface IState {
// We load this later by asking the js-sdk to suggest a version for us.
// This object is the result of Room#getRecommendedVersion()
- upgradeRecommendation?: {
- version: string;
- needsUpgrade: boolean;
- urgent: boolean;
- };
+ upgradeRecommendation?: IRecommendedVersion;
canReact: boolean;
canReply: boolean;
layout: Layout;
@@ -572,16 +568,12 @@ export default class RoomView extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
const hasPropsDiff = objectHasDiff(this.props, nextProps);
- // React only shallow comparison and we only want to trigger
- // a component re-render if a room requires an upgrade
- const newUpgradeRecommendation = nextState.upgradeRecommendation || {}
-
- const state = omit(this.state, ['upgradeRecommendation']);
- const newState = omit(nextState, ['upgradeRecommendation'])
+ const { upgradeRecommendation, ...state } = this.state;
+ const { upgradeRecommendation: newUpgradeRecommendation, ...newState } = nextState;
const hasStateDiff =
- objectHasDiff(state, newState) ||
- (newUpgradeRecommendation.needsUpgrade === true)
+ newUpgradeRecommendation?.needsUpgrade !== upgradeRecommendation?.needsUpgrade ||
+ objectHasDiff(state, newState);
return hasPropsDiff || hasStateDiff;
}
@@ -701,16 +693,11 @@ export default class RoomView extends React.Component {
room_id: this.state.room.roomId,
event_id: this.state.initialEventId,
highlighted: false,
+ replyingToEvent: this.state.replyToEvent,
});
}
}
- private onLayoutChange = () => {
- this.setState({
- layout: SettingsStore.getValue("layout"),
- });
- };
-
private onRightPanelStoreUpdate = () => {
this.setState({
showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
@@ -1276,7 +1263,7 @@ export default class RoomView extends React.Component {
});
}
- private onSearch = (term: string, scope) => {
+ private onSearch = (term: string, scope: SearchScope) => {
this.setState({
searchTerm: term,
searchScope: scope,
@@ -1297,7 +1284,7 @@ export default class RoomView extends React.Component {
this.searchId = new Date().getTime();
let roomId;
- if (scope === "Room") roomId = this.state.room.roomId;
+ if (scope === SearchScope.Room) roomId = this.state.room.roomId;
debuglog("sending search request");
const searchPromise = eventSearch(term, roomId);
@@ -1644,29 +1631,27 @@ export default class RoomView extends React.Component {
let auxPanelMaxHeight = UIStore.instance.windowHeight -
(54 + // height of RoomHeader
36 + // height of the status area
- 51 + // minimum height of the message compmoser
+ 51 + // minimum height of the message composer
120); // amount of desired scrollback
// XXX: this is a bit of a hack and might possibly cause the video to push out the page anyway
// but it's better than the video going missing entirely
if (auxPanelMaxHeight < 50) auxPanelMaxHeight = 50;
- this.setState({auxPanelMaxHeight: auxPanelMaxHeight});
+ if (this.state.auxPanelMaxHeight !== auxPanelMaxHeight) {
+ this.setState({ auxPanelMaxHeight });
+ }
};
private onStatusBarVisible = () => {
- if (this.unmounted) return;
- this.setState({
- statusBarVisible: true,
- });
+ if (this.unmounted || this.state.statusBarVisible) return;
+ this.setState({ statusBarVisible: true });
};
private onStatusBarHidden = () => {
// This is currently not desired as it is annoying if it keeps expanding and collapsing
- if (this.unmounted) return;
- this.setState({
- statusBarVisible: false,
- });
+ if (this.unmounted || !this.state.statusBarVisible) return;
+ this.setState({ statusBarVisible: false });
};
/**
@@ -2069,7 +2054,7 @@ export default class RoomView extends React.Component {
if (!this.state.atEndOfLiveTimeline && !this.state.searchResults) {
const JumpToBottomButton = sdk.getComponent('rooms.JumpToBottomButton');
jumpToBottom = ( 0}
+ highlight={this.state.room.getUnreadNotificationCount(NotificationCountType.Highlight) > 0}
numUnreadMessages={this.state.numUnreadMessages}
onScrollToBottomClick={this.jumpToLiveTimeline}
roomId={this.state.roomId}
diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx
index acbde0b097..4292b60f41 100644
--- a/src/components/structures/SpaceRoomDirectory.tsx
+++ b/src/components/structures/SpaceRoomDirectory.tsx
@@ -14,34 +14,34 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {ReactNode, useMemo, useState} from "react";
-import {Room} from "matrix-js-sdk/src/models/room";
-import {MatrixClient} from "matrix-js-sdk/src/client";
-import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
+import React, { ReactNode, useMemo, useState } from "react";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixClient } from "matrix-js-sdk/src/client";
+import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
import classNames from "classnames";
-import {sortBy} from "lodash";
+import { sortBy } from "lodash";
-import {MatrixClientPeg} from "../../MatrixClientPeg";
+import { MatrixClientPeg } from "../../MatrixClientPeg";
import dis from "../../dispatcher/dispatcher";
-import {_t} from "../../languageHandler";
-import AccessibleButton, {ButtonEvent} from "../views/elements/AccessibleButton";
+import { _t } from "../../languageHandler";
+import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
import BaseDialog from "../views/dialogs/BaseDialog";
import Spinner from "../views/elements/Spinner";
import SearchBox from "./SearchBox";
import RoomAvatar from "../views/avatars/RoomAvatar";
import RoomName from "../views/elements/RoomName";
-import {useAsyncMemo} from "../../hooks/useAsyncMemo";
-import {EnhancedMap} from "../../utils/maps";
+import { useAsyncMemo } from "../../hooks/useAsyncMemo";
+import { EnhancedMap } from "../../utils/maps";
import StyledCheckbox from "../views/elements/StyledCheckbox";
import AutoHideScrollbar from "./AutoHideScrollbar";
import BaseAvatar from "../views/avatars/BaseAvatar";
-import {mediaFromMxc} from "../../customisations/Media";
+import { mediaFromMxc } from "../../customisations/Media";
import InfoTooltip from "../views/elements/InfoTooltip";
import TextWithTooltip from "../views/elements/TextWithTooltip";
-import {useStateToggle} from "../../hooks/useStateToggle";
-import {getOrder} from "../../stores/SpaceStore";
+import { useStateToggle } from "../../hooks/useStateToggle";
+import { getChildOrder } from "../../stores/SpaceStore";
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
-import {linkifyElement} from "../../HtmlUtils";
+import { linkifyElement } from "../../HtmlUtils";
interface IHierarchyProps {
space: Room;
@@ -286,7 +286,7 @@ export const HierarchyLevel = ({
const children = Array.from(relations.get(spaceId)?.values() || []);
const sortedChildren = sortBy(children, ev => {
// XXX: Space Summary API doesn't give the child origin_server_ts but once it does we should use it for sorting
- return getOrder(ev.content.order, null, ev.state_key);
+ return getChildOrder(ev.content.order, null, ev.state_key);
});
const [subspaces, childRooms] = sortedChildren.reduce((result, ev: ISpaceSummaryEvent) => {
const roomId = ev.state_key;
diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx
index 3ccf2e5424..aad770888b 100644
--- a/src/components/structures/SpaceRoomView.tsx
+++ b/src/components/structures/SpaceRoomView.tsx
@@ -59,7 +59,7 @@ import IconizedContextMenu, {
} from "../views/context_menus/IconizedContextMenu";
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
import {BetaPill} from "../views/beta/BetaCard";
-import {USER_LABS_TAB} from "../views/dialogs/UserSettingsDialog";
+import { UserTab } from "../views/dialogs/UserSettingsDialog";
import SettingsStore from "../../settings/SettingsStore";
import dis from "../../dispatcher/dispatcher";
import Modal from "../../Modal";
@@ -166,7 +166,7 @@ const SpaceInfo = ({ space }) => {
const onBetaClick = () => {
defaultDispatcher.dispatch({
action: Action.ViewUserSettings,
- initialTabId: USER_LABS_TAB,
+ initialTabId: UserTab.Labs,
});
};
diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js
index bb62745d98..03d0b5c6d7 100644
--- a/src/components/structures/TimelinePanel.js
+++ b/src/components/structures/TimelinePanel.js
@@ -18,14 +18,14 @@ limitations under the License.
*/
import SettingsStore from "../../settings/SettingsStore";
-import {LayoutPropType} from "../../settings/Layout";
-import React, {createRef} from 'react';
+import { LayoutPropType } from "../../settings/Layout";
+import React, { createRef } from 'react';
import ReactDOM from "react-dom";
import PropTypes from 'prop-types';
-import {EventTimeline} from "matrix-js-sdk/src/models/event-timeline";
-import {TimelineWindow} from "matrix-js-sdk/src/timeline-window";
+import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
+import { TimelineWindow } from "matrix-js-sdk/src/timeline-window";
import { _t } from '../../languageHandler';
-import {MatrixClientPeg} from "../../MatrixClientPeg";
+import { MatrixClientPeg } from "../../MatrixClientPeg";
import RoomContext from "../../contexts/RoomContext";
import UserActivity from "../../UserActivity";
import Modal from "../../Modal";
@@ -35,10 +35,11 @@ import { Key } from '../../Keyboard';
import Timer from '../../utils/Timer';
import shouldHideEvent from '../../shouldHideEvent';
import EditorStateTransfer from '../../utils/EditorStateTransfer';
-import {haveTileForEvent} from "../views/rooms/EventTile";
-import {UIFeature} from "../../settings/UIFeature";
-import {replaceableComponent} from "../../utils/replaceableComponent";
+import { haveTileForEvent } from "../views/rooms/EventTile";
+import { UIFeature } from "../../settings/UIFeature";
+import { replaceableComponent } from "../../utils/replaceableComponent";
import { arrayFastClone } from "../../utils/arrays";
+import { Action } from "../../dispatcher/actions";
const PAGINATE_SIZE = 20;
const INITIAL_SIZE = 20;
@@ -439,21 +440,42 @@ class TimelinePanel extends React.Component {
};
onAction = payload => {
- if (payload.action === 'ignore_state_changed') {
- this.forceUpdate();
- }
- if (payload.action === "edit_event") {
- const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
- this.setState({editState}, () => {
- if (payload.event && this._messagePanel.current) {
- this._messagePanel.current.scrollToEventIfNeeded(
- payload.event.getId(),
- );
+ switch (payload.action) {
+ case "ignore_state_changed":
+ this.forceUpdate();
+ break;
+
+ case "edit_event": {
+ const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
+ this.setState({editState}, () => {
+ if (payload.event && this._messagePanel.current) {
+ this._messagePanel.current.scrollToEventIfNeeded(
+ payload.event.getId(),
+ );
+ }
+ });
+ break;
+ }
+
+ case Action.ComposerInsert: {
+ // re-dispatch to the correct composer
+ if (this.state.editState) {
+ dis.dispatch({
+ ...payload,
+ action: "edit_composer_insert",
+ });
+ } else {
+ dis.dispatch({
+ ...payload,
+ action: "send_composer_insert",
+ });
}
- });
- }
- if (payload.action === "scroll_to_bottom") {
- this.jumpToLiveTimeline();
+ break;
+ }
+
+ case "scroll_to_bottom":
+ this.jumpToLiveTimeline();
+ break;
}
};
diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx
index 6a449cf1a2..3cf0dc5f84 100644
--- a/src/components/structures/UserMenu.tsx
+++ b/src/components/structures/UserMenu.tsx
@@ -26,7 +26,7 @@ import { ActionPayload } from "../../dispatcher/payloads";
import { Action } from "../../dispatcher/actions";
import { _t } from "../../languageHandler";
import { ContextMenuButton } from "./ContextMenu";
-import { USER_NOTIFICATIONS_TAB, USER_SECURITY_TAB } from "../views/dialogs/UserSettingsDialog";
+import { UserTab } from "../views/dialogs/UserSettingsDialog";
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
import FeedbackDialog from "../views/dialogs/FeedbackDialog";
import Modal from "../../Modal";
@@ -408,12 +408,12 @@ export default class UserMenu extends React.Component {
this.onSettingsOpen(e, USER_NOTIFICATIONS_TAB)}
+ onClick={(e) => this.onSettingsOpen(e, UserTab.Notifications)}
/>
this.onSettingsOpen(e, USER_SECURITY_TAB)}
+ onClick={(e) => this.onSettingsOpen(e, UserTab.Security)}
/>
;
title = _t("Verify this login");
- } else if (phase === PHASE_DONE) {
+ } else if (phase === Phase.Done) {
icon = ;
title = _t("Session verified");
- } else if (phase === PHASE_CONFIRM_SKIP) {
+ } else if (phase === Phase.ConfirmSkip) {
icon = ;
title = _t("Are you sure?");
- } else if (phase === PHASE_BUSY) {
+ } else if (phase === Phase.Busy) {
icon = ;
title = _t("Verify this login");
} else {
diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx
index 6feb1e34f7..3a4be6f0d6 100644
--- a/src/components/structures/auth/Registration.tsx
+++ b/src/components/structures/auth/Registration.tsx
@@ -269,7 +269,7 @@ export default class Registration extends React.Component {
);
}
- private onUIAuthFinished = async (success, response, extra) => {
+ private onUIAuthFinished = async (success: boolean, response: any) => {
if (!success) {
let msg = response.message || response.toString();
// can we give a better error message?
diff --git a/src/components/structures/auth/SetupEncryptionBody.js b/src/components/structures/auth/SetupEncryptionBody.js
index 803df19d00..90137e084c 100644
--- a/src/components/structures/auth/SetupEncryptionBody.js
+++ b/src/components/structures/auth/SetupEncryptionBody.js
@@ -21,15 +21,7 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg';
import Modal from '../../../Modal';
import VerificationRequestDialog from '../../views/dialogs/VerificationRequestDialog';
import * as sdk from '../../../index';
-import {
- SetupEncryptionStore,
- PHASE_LOADING,
- PHASE_INTRO,
- PHASE_BUSY,
- PHASE_DONE,
- PHASE_CONFIRM_SKIP,
- PHASE_FINISHED,
-} from '../../../stores/SetupEncryptionStore';
+import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
import {replaceableComponent} from "../../../utils/replaceableComponent";
function keyHasPassphrase(keyInfo) {
@@ -63,7 +55,7 @@ export default class SetupEncryptionBody extends React.Component {
_onStoreUpdate = () => {
const store = SetupEncryptionStore.sharedInstance();
- if (store.phase === PHASE_FINISHED) {
+ if (store.phase === Phase.Finished) {
this.props.onFinished();
return;
}
@@ -136,7 +128,7 @@ export default class SetupEncryptionBody extends React.Component {
onClose={this.props.onFinished}
member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)}
/>;
- } else if (phase === PHASE_INTRO) {
+ } else if (phase === Phase.Intro) {
const store = SetupEncryptionStore.sharedInstance();
let recoveryKeyPrompt;
if (store.keyInfo && keyHasPassphrase(store.keyInfo)) {
@@ -174,7 +166,7 @@ export default class SetupEncryptionBody extends React.Component {
);
- } else if (phase === PHASE_DONE) {
+ } else if (phase === Phase.Done) {
let message;
if (this.state.backupInfo) {
message = {_t(
@@ -200,7 +192,7 @@ export default class SetupEncryptionBody extends React.Component {
);
- } else if (phase === PHASE_CONFIRM_SKIP) {
+ } else if (phase === Phase.ConfirmSkip) {
return (
{_t(
@@ -224,7 +216,7 @@ export default class SetupEncryptionBody extends React.Component {
);
- } else if (phase === PHASE_BUSY || phase === PHASE_LOADING) {
+ } else if (phase === Phase.Busy || phase === Phase.Loading) {
const Spinner = sdk.getComponent('views.elements.Spinner');
return ;
} else {
diff --git a/src/components/views/avatars/BaseAvatar.tsx b/src/components/views/avatars/BaseAvatar.tsx
index 6949c14636..f98f8c88a1 100644
--- a/src/components/views/avatars/BaseAvatar.tsx
+++ b/src/components/views/avatars/BaseAvatar.tsx
@@ -17,16 +17,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {useCallback, useContext, useEffect, useState} from 'react';
+import React, { useCallback, useContext, useEffect, useState } from 'react';
import classNames from 'classnames';
+import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
+
import * as AvatarLogic from '../../../Avatar';
import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from '../elements/AccessibleButton';
import RoomContext from "../../../contexts/RoomContext";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import {useEventEmitter} from "../../../hooks/useEventEmitter";
-import {toPx} from "../../../utils/units";
-import {ResizeMethod} from "../../../Avatar";
+import { useEventEmitter } from "../../../hooks/useEventEmitter";
+import { toPx } from "../../../utils/units";
import { _t } from '../../../languageHandler';
interface IProps {
diff --git a/src/components/views/avatars/GroupAvatar.tsx b/src/components/views/avatars/GroupAvatar.tsx
index 3734ba9504..13dbbfec09 100644
--- a/src/components/views/avatars/GroupAvatar.tsx
+++ b/src/components/views/avatars/GroupAvatar.tsx
@@ -15,10 +15,11 @@ limitations under the License.
*/
import React from 'react';
+import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
+
import BaseAvatar from './BaseAvatar';
-import {replaceableComponent} from "../../../utils/replaceableComponent";
-import {mediaFromMxc} from "../../../customisations/Media";
-import {ResizeMethod} from "../../../Avatar";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { mediaFromMxc } from "../../../customisations/Media";
export interface IProps {
groupId?: string;
diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx
index 3205ca372c..862563a8b4 100644
--- a/src/components/views/avatars/MemberAvatar.tsx
+++ b/src/components/views/avatars/MemberAvatar.tsx
@@ -16,14 +16,14 @@ limitations under the License.
*/
import React from 'react';
-import {RoomMember} from "matrix-js-sdk/src/models/room-member";
+import { RoomMember } from "matrix-js-sdk/src/models/room-member";
+import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
import dis from "../../../dispatcher/dispatcher";
-import {Action} from "../../../dispatcher/actions";
+import { Action } from "../../../dispatcher/actions";
import BaseAvatar from "./BaseAvatar";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
-import {mediaFromMxc} from "../../../customisations/Media";
-import {ResizeMethod} from "../../../Avatar";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { mediaFromMxc } from "../../../customisations/Media";
interface IProps extends Omit, "name" | "idName" | "url"> {
member: RoomMember;
diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx
index 4693d907ba..bd820509c5 100644
--- a/src/components/views/avatars/RoomAvatar.tsx
+++ b/src/components/views/avatars/RoomAvatar.tsx
@@ -13,17 +13,17 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {ComponentProps} from 'react';
-import Room from 'matrix-js-sdk/src/models/room';
+import React, { ComponentProps } from 'react';
+import { Room } from 'matrix-js-sdk/src/models/room';
+import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
import BaseAvatar from './BaseAvatar';
import ImageView from '../elements/ImageView';
-import {MatrixClientPeg} from '../../../MatrixClientPeg';
+import { MatrixClientPeg } from '../../../MatrixClientPeg';
import Modal from '../../../Modal';
import * as Avatar from '../../../Avatar';
-import {ResizeMethod} from "../../../Avatar";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
-import {mediaFromMxc} from "../../../customisations/Media";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { mediaFromMxc } from "../../../customisations/Media";
interface IProps extends Omit, "name" | "idName" | "url" | "onClick"> {
// Room may be left unset here, but if it is,
diff --git a/src/components/views/beta/BetaCard.tsx b/src/components/views/beta/BetaCard.tsx
index 821c448f4f..aa4fe49f63 100644
--- a/src/components/views/beta/BetaCard.tsx
+++ b/src/components/views/beta/BetaCard.tsx
@@ -25,6 +25,7 @@ import TextWithTooltip from "../elements/TextWithTooltip";
import Modal from "../../../Modal";
import BetaFeedbackDialog from "../dialogs/BetaFeedbackDialog";
import SdkConfig from "../../../SdkConfig";
+import SettingsFlag from "../elements/SettingsFlag";
interface IProps {
title?: string;
@@ -66,7 +67,7 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
const info = SettingsStore.getBetaInfo(featureId);
if (!info) return null; // Beta is invalid/disabled
- const { title, caption, disclaimer, image, feedbackLabel, feedbackSubheading } = info;
+ const { title, caption, disclaimer, image, feedbackLabel, feedbackSubheading, extraSettings } = info;
const value = SettingsStore.getValue(featureId);
let feedbackButton;
@@ -82,26 +83,33 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
}
return
-
-
- { titleOverride || _t(title) }
-
-
-
{ _t(caption) }
+
- { feedbackButton }
-
SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value)}
- kind={feedbackButton ? "primary_outline" : "primary"}
- >
- { value ? _t("Leave the beta") : _t("Join the beta") }
-
+
+ { titleOverride || _t(title) }
+
+
+
{ _t(caption) }
+
+ { feedbackButton }
+
SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value)}
+ kind={feedbackButton ? "primary_outline" : "primary"}
+ >
+ { value ? _t("Leave the beta") : _t("Join the beta") }
+
+
+ { disclaimer &&
+ { disclaimer(value) }
+
}
- { disclaimer &&
- { disclaimer(value) }
-
}
+
-

+ { extraSettings &&
+ { extraSettings.map(key => (
+
+ )) }
+
}
;
};
diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js
index adfeeb0968..5a1da1376d 100644
--- a/src/components/views/context_menus/MessageContextMenu.js
+++ b/src/components/views/context_menus/MessageContextMenu.js
@@ -33,6 +33,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard";
import ForwardDialog from "../dialogs/ForwardDialog";
+import { Action } from "../../../dispatcher/actions";
export function canCancel(eventStatus) {
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
@@ -200,7 +201,7 @@ export default class MessageContextMenu extends React.Component {
onQuoteClick = () => {
dis.dispatch({
- action: 'quote',
+ action: Action.ComposerInsert,
event: this.props.mxEvent,
});
this.closeMenu();
diff --git a/src/components/views/context_menus/TagTileContextMenu.js b/src/components/views/context_menus/TagTileContextMenu.js
index 8dea62690c..4e381643ba 100644
--- a/src/components/views/context_menus/TagTileContextMenu.js
+++ b/src/components/views/context_menus/TagTileContextMenu.js
@@ -23,45 +23,70 @@ import TagOrderActions from '../../../actions/TagOrderActions';
import {MenuItem} from "../../structures/ContextMenu";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {replaceableComponent} from "../../../utils/replaceableComponent";
+import GroupFilterOrderStore from "../../../stores/GroupFilterOrderStore";
@replaceableComponent("views.context_menus.TagTileContextMenu")
export default class TagTileContextMenu extends React.Component {
static propTypes = {
tag: PropTypes.string.isRequired,
+ index: PropTypes.number.isRequired,
/* callback called when the menu is dismissed */
onFinished: PropTypes.func.isRequired,
};
static contextType = MatrixClientContext;
- constructor() {
- super();
-
- this._onViewCommunityClick = this._onViewCommunityClick.bind(this);
- this._onRemoveClick = this._onRemoveClick.bind(this);
- }
-
- _onViewCommunityClick() {
+ _onViewCommunityClick = () => {
dis.dispatch({
action: 'view_group',
group_id: this.props.tag,
});
this.props.onFinished();
- }
+ };
- _onRemoveClick() {
+ _onRemoveClick = () => {
dis.dispatch(TagOrderActions.removeTag(this.context, this.props.tag));
this.props.onFinished();
- }
+ };
+
+ _onMoveUp = () => {
+ dis.dispatch(TagOrderActions.moveTag(this.context, this.props.tag, this.props.index - 1));
+ this.props.onFinished();
+ };
+
+ _onMoveDown = () => {
+ dis.dispatch(TagOrderActions.moveTag(this.context, this.props.tag, this.props.index + 1));
+ this.props.onFinished();
+ };
render() {
+ let moveUp;
+ let moveDown;
+ if (this.props.index > 0) {
+ moveUp = (
+
+ );
+ }
+ if (this.props.index < (GroupFilterOrderStore.getOrderedTags() || []).length - 1) {
+ moveDown = (
+
+ );
+ }
+
return
+ { (moveUp || moveDown) ?
: null }
+ { moveUp }
+ { moveDown }
;
}
diff --git a/src/components/views/dialogs/AskInviteAnywayDialog.js b/src/components/views/dialogs/AskInviteAnywayDialog.tsx
similarity index 73%
rename from src/components/views/dialogs/AskInviteAnywayDialog.js
rename to src/components/views/dialogs/AskInviteAnywayDialog.tsx
index e6cd45ba6b..970883aca2 100644
--- a/src/components/views/dialogs/AskInviteAnywayDialog.js
+++ b/src/components/views/dialogs/AskInviteAnywayDialog.tsx
@@ -15,39 +15,41 @@ limitations under the License.
*/
import React from 'react';
-import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
-import {SettingLevel} from "../../../settings/SettingLevel";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { SettingLevel } from "../../../settings/SettingLevel";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+
+interface IProps {
+ unknownProfileUsers: Array<{
+ userId: string;
+ errorText: string;
+ }>;
+ onInviteAnyways: () => void;
+ onGiveUp: () => void;
+ onFinished: (success: boolean) => void;
+}
@replaceableComponent("views.dialogs.AskInviteAnywayDialog")
-export default class AskInviteAnywayDialog extends React.Component {
- static propTypes = {
- unknownProfileUsers: PropTypes.array.isRequired, // [ {userId, errorText}... ]
- onInviteAnyways: PropTypes.func.isRequired,
- onGiveUp: PropTypes.func.isRequired,
- onFinished: PropTypes.func.isRequired,
- };
-
- _onInviteClicked = () => {
+export default class AskInviteAnywayDialog extends React.Component
{
+ private onInviteClicked = (): void => {
this.props.onInviteAnyways();
this.props.onFinished(true);
};
- _onInviteNeverWarnClicked = () => {
+ private onInviteNeverWarnClicked = (): void => {
SettingsStore.setValue("promptBeforeInviteUnknownUsers", null, SettingLevel.ACCOUNT, false);
this.props.onInviteAnyways();
this.props.onFinished(true);
};
- _onGiveUpClicked = () => {
+ private onGiveUpClicked = (): void => {
this.props.onGiveUp();
this.props.onFinished(false);
};
- render() {
+ public render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const errorList = this.props.unknownProfileUsers
@@ -55,11 +57,12 @@ export default class AskInviteAnywayDialog extends React.Component {
return (
+ {/* eslint-disable-next-line */}
{_t("Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?")}
{ errorList }
@@ -67,13 +70,13 @@ export default class AskInviteAnywayDialog extends React.Component {
-
diff --git a/src/components/views/dialogs/BetaFeedbackDialog.tsx b/src/components/views/dialogs/BetaFeedbackDialog.tsx
index 1ae50dd66f..b8ff803627 100644
--- a/src/components/views/dialogs/BetaFeedbackDialog.tsx
+++ b/src/components/views/dialogs/BetaFeedbackDialog.tsx
@@ -29,7 +29,7 @@ import InfoDialog from "./InfoDialog";
import AccessibleButton from "../elements/AccessibleButton";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import {Action} from "../../../dispatcher/actions";
-import {USER_LABS_TAB} from "./UserSettingsDialog";
+import { UserTab } from "./UserSettingsDialog";
interface IProps extends IDialogProps {
featureId: string;
@@ -44,7 +44,12 @@ const BetaFeedbackDialog: React.FC = ({featureId, onFinished}) => {
const sendFeedback = async (ok: boolean) => {
if (!ok) return onFinished(false);
- submitFeedback(SdkConfig.get().bug_report_endpoint_url, info.feedbackLabel, comment, canContact);
+ const extraData = SettingsStore.getBetaInfo(featureId)?.extraSettings.reduce((o, k) => {
+ o[k] = SettingsStore.getValue(k);
+ return o;
+ }, {});
+
+ submitFeedback(SdkConfig.get().bug_report_endpoint_url, info.feedbackLabel, comment, canContact, extraData);
onFinished(true);
Modal.createTrackedDialog("Beta Dialog Sent", featureId, InfoDialog, {
@@ -70,7 +75,7 @@ const BetaFeedbackDialog: React.FC = ({featureId, onFinished}) => {
onFinished(false);
defaultDispatcher.dispatch({
action: Action.ViewUserSettings,
- initialTabId: USER_LABS_TAB,
+ initialTabId: UserTab.Labs,
});
}}>
{ _t("To leave the beta, visit your settings.") }
diff --git a/src/components/views/dialogs/BugReportDialog.js b/src/components/views/dialogs/BugReportDialog.tsx
similarity index 79%
rename from src/components/views/dialogs/BugReportDialog.js
rename to src/components/views/dialogs/BugReportDialog.tsx
index cbe0130649..f938340a50 100644
--- a/src/components/views/dialogs/BugReportDialog.js
+++ b/src/components/views/dialogs/BugReportDialog.tsx
@@ -18,7 +18,6 @@ limitations under the License.
*/
import React from 'react';
-import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import SdkConfig from '../../../SdkConfig';
import Modal from '../../../Modal';
@@ -27,8 +26,27 @@ import sendBugReport, {downloadBugReport} from '../../../rageshake/submit-ragesh
import AccessibleButton from "../elements/AccessibleButton";
import {replaceableComponent} from "../../../utils/replaceableComponent";
+interface IProps {
+ onFinished: (success: boolean) => void;
+ initialText?: string;
+ label?: string;
+}
+
+interface IState {
+ sendLogs: boolean;
+ busy: boolean;
+ err: string;
+ issueUrl: string;
+ text: string;
+ progress: string;
+ downloadBusy: boolean;
+ downloadProgress: string;
+}
+
@replaceableComponent("views.dialogs.BugReportDialog")
-export default class BugReportDialog extends React.Component {
+export default class BugReportDialog extends React.Component {
+ private unmounted: boolean;
+
constructor(props) {
super(props);
this.state = {
@@ -41,25 +59,18 @@ export default class BugReportDialog extends React.Component {
downloadBusy: false,
downloadProgress: null,
};
- this._unmounted = false;
- this._onSubmit = this._onSubmit.bind(this);
- this._onCancel = this._onCancel.bind(this);
- this._onTextChange = this._onTextChange.bind(this);
- this._onIssueUrlChange = this._onIssueUrlChange.bind(this);
- this._onSendLogsChange = this._onSendLogsChange.bind(this);
- this._sendProgressCallback = this._sendProgressCallback.bind(this);
- this._downloadProgressCallback = this._downloadProgressCallback.bind(this);
+ this.unmounted = false;
}
- componentWillUnmount() {
- this._unmounted = true;
+ public componentWillUnmount() {
+ this.unmounted = true;
}
- _onCancel(ev) {
+ private onCancel = (): void => {
this.props.onFinished(false);
}
- _onSubmit(ev) {
+ private onSubmit = (): void => {
if ((!this.state.text || !this.state.text.trim()) && (!this.state.issueUrl || !this.state.issueUrl.trim())) {
this.setState({
err: _t("Please tell us what went wrong or, better, create a GitHub issue that describes the problem."),
@@ -72,15 +83,15 @@ export default class BugReportDialog extends React.Component {
(this.state.issueUrl.length > 0 ? this.state.issueUrl : 'No issue link given');
this.setState({ busy: true, progress: null, err: null });
- this._sendProgressCallback(_t("Preparing to send logs"));
+ this.sendProgressCallback(_t("Preparing to send logs"));
sendBugReport(SdkConfig.get().bug_report_endpoint_url, {
userText,
sendLogs: true,
- progressCallback: this._sendProgressCallback,
+ progressCallback: this.sendProgressCallback,
label: this.props.label,
}).then(() => {
- if (!this._unmounted) {
+ if (!this.unmounted) {
this.props.onFinished(false);
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
// N.B. first param is passed to piwik and so doesn't want i18n
@@ -91,7 +102,7 @@ export default class BugReportDialog extends React.Component {
});
}
}, (err) => {
- if (!this._unmounted) {
+ if (!this.unmounted) {
this.setState({
busy: false,
progress: null,
@@ -101,14 +112,14 @@ export default class BugReportDialog extends React.Component {
});
}
- _onDownload = async (ev) => {
+ private onDownload = async (): Promise => {
this.setState({ downloadBusy: true });
- this._downloadProgressCallback(_t("Preparing to download logs"));
+ this.downloadProgressCallback(_t("Preparing to download logs"));
try {
await downloadBugReport({
sendLogs: true,
- progressCallback: this._downloadProgressCallback,
+ progressCallback: this.downloadProgressCallback,
label: this.props.label,
});
@@ -117,7 +128,7 @@ export default class BugReportDialog extends React.Component {
downloadProgress: null,
});
} catch (err) {
- if (!this._unmounted) {
+ if (!this.unmounted) {
this.setState({
downloadBusy: false,
downloadProgress: _t("Failed to send logs: ") + `${err.message}`,
@@ -126,33 +137,29 @@ export default class BugReportDialog extends React.Component {
}
};
- _onTextChange(ev) {
- this.setState({ text: ev.target.value });
+ private onTextChange = (ev: React.FormEvent): void => {
+ this.setState({ text: ev.currentTarget.value });
}
- _onIssueUrlChange(ev) {
- this.setState({ issueUrl: ev.target.value });
+ private onIssueUrlChange = (ev: React.FormEvent): void => {
+ this.setState({ issueUrl: ev.currentTarget.value });
}
- _onSendLogsChange(ev) {
- this.setState({ sendLogs: ev.target.checked });
- }
-
- _sendProgressCallback(progress) {
- if (this._unmounted) {
+ private sendProgressCallback = (progress: string): void => {
+ if (this.unmounted) {
return;
}
- this.setState({progress: progress});
+ this.setState({ progress });
}
- _downloadProgressCallback(downloadProgress) {
- if (this._unmounted) {
+ private downloadProgressCallback = (downloadProgress: string): void => {
+ if (this.unmounted) {
return;
}
this.setState({ downloadProgress });
}
- render() {
+ public render() {
const Loader = sdk.getComponent("elements.Spinner");
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
@@ -183,7 +190,7 @@ export default class BugReportDialog extends React.Component {
}
return (
-
@@ -213,7 +220,7 @@ export default class BugReportDialog extends React.Component {
-
+
{ _t("Download logs") }
{this.state.downloadProgress && {this.state.downloadProgress} ...}
@@ -223,7 +230,7 @@ export default class BugReportDialog extends React.Component {
type="text"
className="mx_BugReportDialog_field_input"
label={_t("GitHub issue")}
- onChange={this._onIssueUrlChange}
+ onChange={this.onIssueUrlChange}
value={this.state.issueUrl}
placeholder="https://github.com/vector-im/element-web/issues/..."
/>
@@ -232,7 +239,7 @@ export default class BugReportDialog extends React.Component {
element="textarea"
label={_t("Notes")}
rows={5}
- onChange={this._onTextChange}
+ onChange={this.onTextChange}
value={this.state.text}
placeholder={_t(
"If there is additional context that would help in " +
@@ -245,17 +252,12 @@ export default class BugReportDialog extends React.Component {
{error}
);
}
}
-
-BugReportDialog.propTypes = {
- onFinished: PropTypes.func.isRequired,
- initialText: PropTypes.string,
-};
diff --git a/src/components/views/dialogs/ChangelogDialog.js b/src/components/views/dialogs/ChangelogDialog.tsx
similarity index 88%
rename from src/components/views/dialogs/ChangelogDialog.js
rename to src/components/views/dialogs/ChangelogDialog.tsx
index efbeba3977..0ded33cdcb 100644
--- a/src/components/views/dialogs/ChangelogDialog.js
+++ b/src/components/views/dialogs/ChangelogDialog.tsx
@@ -16,21 +16,26 @@ Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
*/
import React from 'react';
-import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import request from 'browser-request';
import { _t } from '../../../languageHandler';
+interface IProps {
+ newVersion: string;
+ version: string;
+ onFinished: (success: boolean) => void;
+}
+
const REPOS = ['vector-im/element-web', 'matrix-org/matrix-react-sdk', 'matrix-org/matrix-js-sdk'];
-export default class ChangelogDialog extends React.Component {
+export default class ChangelogDialog extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
- componentDidMount() {
+ public componentDidMount() {
const version = this.props.newVersion.split('-');
const version2 = this.props.version.split('-');
if (version == null || version2 == null) return;
@@ -49,7 +54,7 @@ export default class ChangelogDialog extends React.Component {
}
}
- _elementsForCommit(commit) {
+ private elementsForCommit(commit): JSX.Element {
return (
@@ -59,7 +64,7 @@ export default class ChangelogDialog extends React.Component {
);
}
- render() {
+ public render() {
const Spinner = sdk.getComponent('views.elements.Spinner');
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
@@ -72,7 +77,7 @@ export default class ChangelogDialog extends React.Component {
msg: this.state[repo],
});
} else {
- content = this.state[repo].map(this._elementsForCommit);
+ content = this.state[repo].map(this.elementsForCommit);
}
return (
@@ -99,9 +104,3 @@ export default class ChangelogDialog extends React.Component {
);
}
}
-
-ChangelogDialog.propTypes = {
- version: PropTypes.string.isRequired,
- newVersion: PropTypes.string.isRequired,
- onFinished: PropTypes.func.isRequired,
-};
diff --git a/src/components/views/dialogs/ConfirmAndWaitRedactDialog.js b/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx
similarity index 89%
rename from src/components/views/dialogs/ConfirmAndWaitRedactDialog.js
rename to src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx
index 37d5510756..ae7b23c2c9 100644
--- a/src/components/views/dialogs/ConfirmAndWaitRedactDialog.js
+++ b/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx
@@ -17,7 +17,17 @@ limitations under the License.
import React from 'react';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+
+interface IProps {
+ redact: () => Promise
;
+ onFinished: (success: boolean) => void;
+}
+
+interface IState {
+ isRedacting: boolean;
+ redactionErrorCode: string | number;
+}
/*
* A dialog for confirming a redaction.
@@ -32,7 +42,7 @@ import {replaceableComponent} from "../../../utils/replaceableComponent";
* To avoid this, we keep the dialog open as long as /redact is in progress.
*/
@replaceableComponent("views.dialogs.ConfirmAndWaitRedactDialog")
-export default class ConfirmAndWaitRedactDialog extends React.PureComponent {
+export default class ConfirmAndWaitRedactDialog extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
@@ -41,7 +51,7 @@ export default class ConfirmAndWaitRedactDialog extends React.PureComponent {
};
}
- onParentFinished = async (proceed) => {
+ public onParentFinished = async (proceed: boolean): Promise => {
if (proceed) {
this.setState({isRedacting: true});
try {
@@ -60,7 +70,7 @@ export default class ConfirmAndWaitRedactDialog extends React.PureComponent {
}
};
- render() {
+ public render() {
if (this.state.isRedacting) {
if (this.state.redactionErrorCode) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
diff --git a/src/components/views/dialogs/ConfirmRedactDialog.js b/src/components/views/dialogs/ConfirmRedactDialog.tsx
similarity index 95%
rename from src/components/views/dialogs/ConfirmRedactDialog.js
rename to src/components/views/dialogs/ConfirmRedactDialog.tsx
index bd63d3acc1..eee05599e8 100644
--- a/src/components/views/dialogs/ConfirmRedactDialog.js
+++ b/src/components/views/dialogs/ConfirmRedactDialog.tsx
@@ -19,11 +19,15 @@ import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {replaceableComponent} from "../../../utils/replaceableComponent";
+interface IProps {
+ onFinished: (success: boolean) => void;
+}
+
/*
* A dialog for confirming a redaction.
*/
@replaceableComponent("views.dialogs.ConfirmRedactDialog")
-export default class ConfirmRedactDialog extends React.Component {
+export default class ConfirmRedactDialog extends React.Component {
render() {
const TextInputDialog = sdk.getComponent('views.dialogs.TextInputDialog');
return (
diff --git a/src/components/views/dialogs/ConfirmUserActionDialog.js b/src/components/views/dialogs/ConfirmUserActionDialog.tsx
similarity index 74%
rename from src/components/views/dialogs/ConfirmUserActionDialog.js
rename to src/components/views/dialogs/ConfirmUserActionDialog.tsx
index 8059b9172a..5cdb4c664b 100644
--- a/src/components/views/dialogs/ConfirmUserActionDialog.js
+++ b/src/components/views/dialogs/ConfirmUserActionDialog.tsx
@@ -15,13 +15,31 @@ limitations under the License.
*/
import React from 'react';
-import PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk/src/client';
+import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import { GroupMemberType } from '../../../groups';
-import {replaceableComponent} from "../../../utils/replaceableComponent";
-import {mediaFromMxc} from "../../../customisations/Media";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { mediaFromMxc } from "../../../customisations/Media";
+
+interface IProps {
+ // matrix-js-sdk (room) member object. Supply either this or 'groupMember'
+ member: RoomMember;
+ // group member object. Supply either this or 'member'
+ groupMember: GroupMemberType;
+ // needed if a group member is specified
+ matrixClient?: MatrixClient,
+ action: string; // eg. 'Ban'
+ title: string; // eg. 'Ban this user?'
+
+ // Whether to display a text field for a reason
+ // If true, the second argument to onFinished will
+ // be the string entered.
+ askReason?: boolean;
+ danger?: boolean;
+ onFinished: (success: boolean, reason?: string) => void;
+}
/*
* A dialog for confirming an operation on another user.
@@ -32,53 +50,23 @@ import {mediaFromMxc} from "../../../customisations/Media";
* Also tweaks the style for 'dangerous' actions (albeit only with colour)
*/
@replaceableComponent("views.dialogs.ConfirmUserActionDialog")
-export default class ConfirmUserActionDialog extends React.Component {
- static propTypes = {
- // matrix-js-sdk (room) member object. Supply either this or 'groupMember'
- member: PropTypes.object,
- // group member object. Supply either this or 'member'
- groupMember: GroupMemberType,
- // needed if a group member is specified
- matrixClient: PropTypes.instanceOf(MatrixClient),
- action: PropTypes.string.isRequired, // eg. 'Ban'
- title: PropTypes.string.isRequired, // eg. 'Ban this user?'
-
- // Whether to display a text field for a reason
- // If true, the second argument to onFinished will
- // be the string entered.
- askReason: PropTypes.bool,
- danger: PropTypes.bool,
- onFinished: PropTypes.func.isRequired,
- };
+export default class ConfirmUserActionDialog extends React.Component {
+ private reasonField: React.RefObject = React.createRef();
static defaultProps = {
danger: false,
askReason: false,
};
- constructor(props) {
- super(props);
-
- this._reasonField = null;
- }
-
- onOk = () => {
- let reason;
- if (this._reasonField) {
- reason = this._reasonField.value;
- }
- this.props.onFinished(true, reason);
+ public onOk = (): void => {
+ this.props.onFinished(true, this.reasonField.current?.value);
};
- onCancel = () => {
+ public onCancel = (): void => {
this.props.onFinished(false);
};
- _collectReasonField = e => {
- this._reasonField = e;
- };
-
- render() {
+ public render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
@@ -92,7 +80,7 @@ export default class ConfirmUserActionDialog extends React.Component {
);
diff --git a/src/components/views/dialogs/CreateGroupDialog.js b/src/components/views/dialogs/CreateGroupDialog.tsx
similarity index 82%
rename from src/components/views/dialogs/CreateGroupDialog.js
rename to src/components/views/dialogs/CreateGroupDialog.tsx
index e6c7a67aca..60e4f5efb8 100644
--- a/src/components/views/dialogs/CreateGroupDialog.js
+++ b/src/components/views/dialogs/CreateGroupDialog.tsx
@@ -15,44 +15,51 @@ limitations under the License.
*/
import React from 'react';
-import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {replaceableComponent} from "../../../utils/replaceableComponent";
-@replaceableComponent("views.dialogs.CreateGroupDialog")
-export default class CreateGroupDialog extends React.Component {
- static propTypes = {
- onFinished: PropTypes.func.isRequired,
- };
+interface IProps {
+ onFinished: (success: boolean) => void;
+}
- state = {
+interface IState {
+ groupName: string;
+ groupId: string;
+ groupIdError: string;
+ creating: boolean;
+ createError: Error;
+}
+
+@replaceableComponent("views.dialogs.CreateGroupDialog")
+export default class CreateGroupDialog extends React.Component {
+ public state = {
groupName: '',
groupId: '',
- groupError: null,
+ groupIdError: '',
creating: false,
createError: null,
};
- _onGroupNameChange = e => {
+ private onGroupNameChange = (e: React.FormEvent): void => {
this.setState({
- groupName: e.target.value,
+ groupName: e.currentTarget.value,
});
};
- _onGroupIdChange = e => {
+ private onGroupIdChange = (e: React.FormEvent): void => {
this.setState({
- groupId: e.target.value,
+ groupId: e.currentTarget.value,
});
};
- _onGroupIdBlur = e => {
- this._checkGroupId();
+ private onGroupIdBlur = (): void => {
+ this.checkGroupId();
};
- _checkGroupId(e) {
+ private checkGroupId() {
let error = null;
if (!this.state.groupId) {
error = _t("Community IDs cannot be empty.");
@@ -67,12 +74,12 @@ export default class CreateGroupDialog extends React.Component {
return error;
}
- _onFormSubmit = e => {
+ private onFormSubmit = (e: React.FormEvent) => {
e.preventDefault();
- if (this._checkGroupId()) return;
+ if (this.checkGroupId()) return;
- const profile = {};
+ const profile: any = {};
if (this.state.groupName !== '') {
profile.name = this.state.groupName;
}
@@ -121,7 +128,7 @@ export default class CreateGroupDialog extends React.Component {
-