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 6410bd28fa..3c3807e33b 100644
--- a/.github/workflows/develop.yml
+++ b/.github/workflows/develop.yml
@@ -11,10 +11,13 @@ 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/**/*
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 d8c26098ca..f232d4301b 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",
@@ -139,12 +139,12 @@
"@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",
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/_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/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 204ccab2b7..32454b9530 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..0c6b63dd33 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 {
diff --git a/src/Avatar.ts b/src/Avatar.ts
index a6499c688e..8ea0b0c9fa 100644
--- a/src/Avatar.ts
+++ b/src/Avatar.ts
@@ -14,18 +14,23 @@ 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 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 +44,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/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/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx
index ad55b19101..f784a57821 100644
--- a/src/autocomplete/RoomProvider.tsx
+++ b/src/autocomplete/RoomProvider.tsx
@@ -32,13 +32,9 @@ 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/RoomView.tsx b/src/components/structures/RoomView.tsx
index c9645515bf..a4338e832a 100644
--- a/src/components/structures/RoomView.tsx
+++ b/src/components/structures/RoomView.tsx
@@ -25,6 +25,7 @@ import React, { createRef } from 'react';
import classNames from 'classnames';
import { 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";
@@ -138,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[];
@@ -1266,7 +1267,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,
@@ -1287,7 +1288,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);
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/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/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 635f743c76..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;
@@ -75,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 {
- ;
}
}
-
diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts
index 300eed2b98..6438ecc0f2 100644
--- a/src/dispatcher/actions.ts
+++ b/src/dispatcher/actions.ts
@@ -159,4 +159,14 @@ export enum Action {
* Fired when joining a room failed
*/
JoinRoomError = "join_room_error",
+
+ /**
+ * Inserts content into the active composer. Should be used with ComposerInsertPayload
+ */
+ ComposerInsert = "composer_insert",
+
+ /**
+ * Switches space. Should be used with SwitchSpacePayload.
+ */
+ SwitchSpace = "switch_space",
}
diff --git a/src/dispatcher/payloads/ComposerInsertPayload.ts b/src/dispatcher/payloads/ComposerInsertPayload.ts
new file mode 100644
index 0000000000..9702855432
--- /dev/null
+++ b/src/dispatcher/payloads/ComposerInsertPayload.ts
@@ -0,0 +1,42 @@
+/*
+Copyright 2021 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.
+*/
+
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+
+import { ActionPayload } from "../payloads";
+import { Action } from "../actions";
+
+interface IBaseComposerInsertPayload extends ActionPayload {
+ action: Action.ComposerInsert,
+}
+
+interface IComposerInsertMentionPayload extends IBaseComposerInsertPayload {
+ userId: string;
+}
+
+interface IComposerInsertQuotePayload extends IBaseComposerInsertPayload {
+ event: MatrixEvent;
+}
+
+interface IComposerInsertPlaintextPayload extends IBaseComposerInsertPayload {
+ text: string;
+}
+
+export type ComposerInsertPayload =
+ IComposerInsertMentionPayload |
+ IComposerInsertQuotePayload |
+ IComposerInsertPlaintextPayload;
+
diff --git a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts
index 430fad6145..a4dcb8cfe1 100644
--- a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts
+++ b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts
@@ -17,6 +17,7 @@ limitations under the License.
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
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 { RightPanelPhases } from "../../stores/RightPanelStorePhases";
import { ActionPayload } from "../payloads";
import { Action } from "../actions";
@@ -29,7 +30,7 @@ export interface SetRightPanelPhasePayload extends ActionPayload {
}
export interface SetRightPanelPhaseRefireParams {
- member?: RoomMember;
+ member?: RoomMember | User;
verificationRequest?: VerificationRequest;
groupId?: string;
groupRoomId?: string;
diff --git a/src/dispatcher/payloads/SwitchSpacePayload.ts b/src/dispatcher/payloads/SwitchSpacePayload.ts
new file mode 100644
index 0000000000..04eb744334
--- /dev/null
+++ b/src/dispatcher/payloads/SwitchSpacePayload.ts
@@ -0,0 +1,27 @@
+/*
+Copyright 2021 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.
+*/
+
+import { ActionPayload } from "../payloads";
+import { Action } from "../actions";
+
+export interface SwitchSpacePayload extends ActionPayload {
+ action: Action.SwitchSpace;
+
+ /**
+ * The number of the space to switch to, 1-indexed, 0 is Home.
+ */
+ num: number;
+}
diff --git a/src/editor/model.ts b/src/editor/model.ts
index f1b6f90957..7925a43164 100644
--- a/src/editor/model.ts
+++ b/src/editor/model.ts
@@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {diffAtCaret, diffDeletion, IDiff} from "./diff";
-import DocumentPosition, {IPosition} from "./position";
+import { diffAtCaret, diffDeletion, IDiff } from "./diff";
+import DocumentPosition, { IPosition } from "./position";
import Range from "./range";
-import {SerializedPart, Part, PartCreator} from "./parts";
-import AutocompleteWrapperModel, {ICallback} from "./autocomplete";
+import { SerializedPart, Part, PartCreator } from "./parts";
+import AutocompleteWrapperModel, { ICallback } from "./autocomplete";
import DocumentOffset from "./offset";
-import {Caret} from "./caret";
+import { Caret } from "./caret";
/**
* @callback ModelCallback
@@ -390,7 +390,7 @@ export default class EditorModel {
return addLen;
}
- positionForOffset(totalOffset: number, atPartEnd: boolean) {
+ positionForOffset(totalOffset: number, atPartEnd = false) {
let currentOffset = 0;
const index = this._parts.findIndex(part => {
const partLen = part.text.length;
diff --git a/src/hooks/useAccountData.ts b/src/hooks/useAccountData.ts
index fe71ed9ecd..0384b3bf77 100644
--- a/src/hooks/useAccountData.ts
+++ b/src/hooks/useAccountData.ts
@@ -21,11 +21,11 @@ import {Room} from "matrix-js-sdk/src/models/room";
import {useEventEmitter} from "./useEventEmitter";
-const tryGetContent = (ev?: MatrixEvent) => ev ? ev.getContent() : undefined;
+const tryGetContent = (ev?: MatrixEvent) => ev ? ev.getContent() : undefined;
// Hook to simplify listening to Matrix account data
export const useAccountData = (cli: MatrixClient, eventType: string) => {
- const [value, setValue] = useState(() => tryGetContent(cli.getAccountData(eventType)));
+ const [value, setValue] = useState(() => tryGetContent(cli.getAccountData(eventType)));
const handler = useCallback((event) => {
if (event.getType() !== eventType) return;
@@ -38,7 +38,7 @@ export const useAccountData = (cli: MatrixClient, eventType: strin
// Hook to simplify listening to Matrix room account data
export const useRoomAccountData = (room: Room, eventType: string) => {
- const [value, setValue] = useState(() => tryGetContent(room.getAccountData(eventType)));
+ const [value, setValue] = useState(() => tryGetContent(room.getAccountData(eventType)));
const handler = useCallback((event) => {
if (event.getType() !== eventType) return;
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 17160a6b89..a94b608f2b 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -784,6 +784,7 @@
"%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s",
"%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s",
"Change notification settings": "Change notification settings",
+ "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators",
"Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features.": "Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features.",
"Spaces": "Spaces",
"Spaces are a new way to group rooms and people.": "Spaces are a new way to group rooms and people.",
@@ -909,6 +910,8 @@
"Incoming voice call": "Incoming voice call",
"Incoming video call": "Incoming video call",
"Incoming call": "Incoming call",
+ "Sound on": "Sound on",
+ "Silence call": "Silence call",
"Decline": "Decline",
"Accept": "Accept",
"Pause": "Pause",
@@ -1935,6 +1938,7 @@
"Error loading Widget": "Error loading Widget",
"Error - Mixed content": "Error - Mixed content",
"Popout widget": "Popout widget",
+ "Message search initialisation failed, check your settings for more information": "Message search initialisation failed, check your settings for more information",
"Use the Desktop app to see all encrypted files": "Use the Desktop app to see all encrypted files",
"Use the Desktop app to search encrypted messages": "Use the Desktop app to search encrypted messages",
"This version of %(brand)s does not support viewing some encrypted files": "This version of %(brand)s does not support viewing some encrypted files",
@@ -2320,9 +2324,23 @@
"Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account.": "Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account.",
"Email (optional)": "Email (optional)",
"Please fill why you're reporting.": "Please fill why you're reporting.",
+ "What this user is writing is wrong.\nThis will be reported to the room moderators.": "What this user is writing is wrong.\nThis will be reported to the room moderators.",
+ "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.": "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.",
+ "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.": "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.",
+ "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.": "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.": "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.",
+ "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.": "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.",
+ "Please pick a nature and describe what makes this message abusive.": "Please pick a nature and describe what makes this message abusive.",
+ "Report Content": "Report Content",
+ "Disagree": "Disagree",
+ "Toxic Behaviour": "Toxic Behaviour",
+ "Illegal Content": "Illegal Content",
+ "Spam or propaganda": "Spam or propaganda",
+ "Report the entire room": "Report the entire room",
+ "Send report": "Send report",
"Report Content to Your Homeserver Administrator": "Report Content to Your Homeserver Administrator",
"Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.",
- "Send report": "Send report",
"Room Settings - %(roomName)s": "Room Settings - %(roomName)s",
"Failed to upgrade room": "Failed to upgrade room",
"The room upgrade could not be completed": "The room upgrade could not be completed",
@@ -2489,7 +2507,6 @@
"Share Message": "Share Message",
"Source URL": "Source URL",
"Collapse Reply Thread": "Collapse Reply Thread",
- "Report Content": "Report Content",
"Clear status": "Clear status",
"Update status": "Update status",
"Set status": "Set status",
@@ -2995,5 +3012,6 @@
"Esc": "Esc",
"Enter": "Enter",
"Space": "Space",
- "End": "End"
+ "End": "End",
+ "[number]": "[number]"
}
diff --git a/src/indexing/EventIndex.ts b/src/indexing/EventIndex.ts
index b6289969bd..c36f96f368 100644
--- a/src/indexing/EventIndex.ts
+++ b/src/indexing/EventIndex.ts
@@ -300,7 +300,7 @@ export default class EventIndex extends EventEmitter {
}
private eventToJson(ev: MatrixEvent) {
- const jsonEvent = ev.toJSON();
+ const jsonEvent: any = ev.toJSON();
const e = ev.isEncrypted() ? jsonEvent.decrypted : jsonEvent;
if (ev.isEncrypted()) {
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index f22882abc4..6ed5e0c3d8 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -135,6 +135,13 @@ export interface ISetting {
}
export const SETTINGS: {[setting: string]: ISetting} = {
+ "feature_report_to_moderators": {
+ isFeature: true,
+ displayName: _td("Report to moderators prototype. " +
+ "In rooms that support moderation, the `report` button will let you report abuse to room moderators"),
+ supportedLevels: LEVELS_FEATURE,
+ default: false,
+ },
"feature_spaces": {
isFeature: true,
displayName: _td("Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. " +
diff --git a/src/stores/SetupEncryptionStore.js b/src/stores/SetupEncryptionStore.ts
similarity index 73%
rename from src/stores/SetupEncryptionStore.js
rename to src/stores/SetupEncryptionStore.ts
index b768ae69df..88385d0399 100644
--- a/src/stores/SetupEncryptionStore.js
+++ b/src/stores/SetupEncryptionStore.ts
@@ -15,29 +15,42 @@ limitations under the License.
*/
import EventEmitter from 'events';
+import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
+import { IKeyBackupVersion } from "matrix-js-sdk/src/crypto/keybackup";
+import { ISecretStorageKeyInfo } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from '../MatrixClientPeg';
import { accessSecretStorage, AccessCancelledError } from '../SecurityManager';
import { PHASE_DONE as VERIF_PHASE_DONE } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
-export const PHASE_LOADING = 0;
-export const PHASE_INTRO = 1;
-export const PHASE_BUSY = 2;
-export const PHASE_DONE = 3; //final done stage, but still showing UX
-export const PHASE_CONFIRM_SKIP = 4;
-export const PHASE_FINISHED = 5; //UX can be closed
+export enum Phase {
+ Loading = 0,
+ Intro = 1,
+ Busy = 2,
+ Done = 3, // final done stage, but still showing UX
+ ConfirmSkip = 4,
+ Finished = 5, // UX can be closed
+}
export class SetupEncryptionStore extends EventEmitter {
- static sharedInstance() {
- if (!global.mx_SetupEncryptionStore) global.mx_SetupEncryptionStore = new SetupEncryptionStore();
- return global.mx_SetupEncryptionStore;
+ private started: boolean;
+ public phase: Phase;
+ public verificationRequest: VerificationRequest;
+ public backupInfo: IKeyBackupVersion;
+ public keyId: string;
+ public keyInfo: ISecretStorageKeyInfo;
+ public hasDevicesToVerifyAgainst: boolean;
+
+ public static sharedInstance() {
+ if (!window.mxSetupEncryptionStore) window.mxSetupEncryptionStore = new SetupEncryptionStore();
+ return window.mxSetupEncryptionStore;
}
- start() {
- if (this._started) {
+ public start(): void {
+ if (this.started) {
return;
}
- this._started = true;
- this.phase = PHASE_LOADING;
+ this.started = true;
+ this.phase = Phase.Loading;
this.verificationRequest = null;
this.backupInfo = null;
@@ -48,34 +61,34 @@ export class SetupEncryptionStore extends EventEmitter {
const cli = MatrixClientPeg.get();
cli.on("crypto.verification.request", this.onVerificationRequest);
- cli.on('userTrustStatusChanged', this._onUserTrustStatusChanged);
+ cli.on('userTrustStatusChanged', this.onUserTrustStatusChanged);
const requestsInProgress = cli.getVerificationRequestsToDeviceInProgress(cli.getUserId());
if (requestsInProgress.length) {
// If there are multiple, we take the most recent. Equally if the user sends another request from
// another device after this screen has been shown, we'll switch to the new one, so this
// generally doesn't support multiple requests.
- this._setActiveVerificationRequest(requestsInProgress[requestsInProgress.length - 1]);
+ this.setActiveVerificationRequest(requestsInProgress[requestsInProgress.length - 1]);
}
this.fetchKeyInfo();
}
- stop() {
- if (!this._started) {
+ public stop(): void {
+ if (!this.started) {
return;
}
- this._started = false;
+ this.started = false;
if (this.verificationRequest) {
this.verificationRequest.off("change", this.onVerificationRequestChange);
}
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("crypto.verification.request", this.onVerificationRequest);
- MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged);
+ MatrixClientPeg.get().removeListener('userTrustStatusChanged', this.onUserTrustStatusChanged);
}
}
- async fetchKeyInfo() {
+ public async fetchKeyInfo(): Promise {
const cli = MatrixClientPeg.get();
const keys = await cli.isSecretStored('m.cross_signing.master', false);
if (keys === null || Object.keys(keys).length === 0) {
@@ -97,15 +110,15 @@ export class SetupEncryptionStore extends EventEmitter {
if (!this.hasDevicesToVerifyAgainst && !this.keyInfo) {
// skip before we can even render anything.
- this.phase = PHASE_FINISHED;
+ this.phase = Phase.Finished;
} else {
- this.phase = PHASE_INTRO;
+ this.phase = Phase.Intro;
}
this.emit("update");
}
- async usePassPhrase() {
- this.phase = PHASE_BUSY;
+ public async usePassPhrase(): Promise {
+ this.phase = Phase.Busy;
this.emit("update");
const cli = MatrixClientPeg.get();
try {
@@ -120,7 +133,7 @@ export class SetupEncryptionStore extends EventEmitter {
// passphase cached for that work. This dialog itself will only wait
// on the first trust check, and the key backup restore will happen
// in the background.
- await new Promise((resolve, reject) => {
+ await new Promise((resolve: (value?: unknown) => void, reject: (reason?: any) => void) => {
accessSecretStorage(async () => {
await cli.checkOwnCrossSigningTrust();
resolve();
@@ -134,7 +147,7 @@ export class SetupEncryptionStore extends EventEmitter {
});
if (cli.getCrossSigningId()) {
- this.phase = PHASE_DONE;
+ this.phase = Phase.Done;
this.emit("update");
}
} catch (e) {
@@ -142,25 +155,25 @@ export class SetupEncryptionStore extends EventEmitter {
console.log(e);
}
// this will throw if the user hits cancel, so ignore
- this.phase = PHASE_INTRO;
+ this.phase = Phase.Intro;
this.emit("update");
}
}
- _onUserTrustStatusChanged = (userId) => {
+ private onUserTrustStatusChanged = (userId: string) => {
if (userId !== MatrixClientPeg.get().getUserId()) return;
const publicKeysTrusted = MatrixClientPeg.get().getCrossSigningId();
if (publicKeysTrusted) {
- this.phase = PHASE_DONE;
+ this.phase = Phase.Done;
this.emit("update");
}
}
- onVerificationRequest = (request) => {
- this._setActiveVerificationRequest(request);
+ public onVerificationRequest = (request: VerificationRequest): void => {
+ this.setActiveVerificationRequest(request);
}
- onVerificationRequestChange = () => {
+ public onVerificationRequestChange = (): void => {
if (this.verificationRequest.cancelled) {
this.verificationRequest.off("change", this.onVerificationRequestChange);
this.verificationRequest = null;
@@ -172,34 +185,34 @@ export class SetupEncryptionStore extends EventEmitter {
// cross signing to be ready to use, so wait for the user trust status to
// change (or change to DONE if it's already ready).
const publicKeysTrusted = MatrixClientPeg.get().getCrossSigningId();
- this.phase = publicKeysTrusted ? PHASE_DONE : PHASE_BUSY;
+ this.phase = publicKeysTrusted ? Phase.Done : Phase.Busy;
this.emit("update");
}
}
- skip() {
- this.phase = PHASE_CONFIRM_SKIP;
+ public skip(): void {
+ this.phase = Phase.ConfirmSkip;
this.emit("update");
}
- skipConfirm() {
- this.phase = PHASE_FINISHED;
+ public skipConfirm(): void {
+ this.phase = Phase.Finished;
this.emit("update");
}
- returnAfterSkip() {
- this.phase = PHASE_INTRO;
+ public returnAfterSkip(): void {
+ this.phase = Phase.Intro;
this.emit("update");
}
- done() {
- this.phase = PHASE_FINISHED;
+ public done(): void {
+ this.phase = Phase.Finished;
this.emit("update");
// async - ask other clients for keys, if necessary
MatrixClientPeg.get().crypto.cancelAndResendAllOutgoingKeyRequests();
}
- async _setActiveVerificationRequest(request) {
+ private async setActiveVerificationRequest(request: VerificationRequest): Promise {
if (request.otherUserId !== MatrixClientPeg.get().getUserId()) return;
if (this.verificationRequest) {
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 2b5a25e707..dfdaa73f19 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -33,6 +33,7 @@ import { EnhancedMap, mapDiff } from "../utils/maps";
import { setHasDiff } from "../utils/sets";
import { ISpaceSummaryEvent, ISpaceSummaryRoom } from "../components/structures/SpaceRoomDirectory";
import RoomViewStore from "./RoomViewStore";
+import { Action } from "../dispatcher/actions";
import { arrayHasDiff } from "../utils/arrays";
import { objectDiff } from "../utils/objects";
@@ -653,6 +654,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
this.setActiveSpace(null, false);
}
break;
+ case Action.SwitchSpace:
+ if (payload.num === 0) {
+ this.setActiveSpace(null);
+ } else if (this.spacePanelSpaces.length >= payload.num) {
+ this.setActiveSpace(this.spacePanelSpaces[payload.num - 1]);
+ }
}
}
diff --git a/src/stores/WidgetEchoStore.ts b/src/stores/WidgetEchoStore.ts
index 09120d6108..0b0be50541 100644
--- a/src/stores/WidgetEchoStore.ts
+++ b/src/stores/WidgetEchoStore.ts
@@ -16,8 +16,8 @@ limitations under the License.
import EventEmitter from 'events';
import { IWidget } from 'matrix-widget-api';
-import MatrixEvent from "matrix-js-sdk/src/models/event";
-import {WidgetType} from "../widgets/WidgetType";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { WidgetType } from "../widgets/WidgetType";
/**
* Acts as a place to get & set widget state, storing local echo state and
diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts
index 397d637125..6dcaf7abd7 100644
--- a/src/stores/widgets/StopGapWidget.ts
+++ b/src/stores/widgets/StopGapWidget.ts
@@ -51,7 +51,7 @@ import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
import {getCustomTheme} from "../../theme";
import CountlyAnalytics from "../../CountlyAnalytics";
import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities";
-import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { MatrixEvent, IEvent } from "matrix-js-sdk/src/models/event";
import { ELEMENT_CLIENT_ID } from "../../identifiers";
import { getUserLanguage } from "../../languageHandler";
@@ -415,7 +415,7 @@ export class StopGapWidget extends EventEmitter {
private feedEvent(ev: MatrixEvent) {
if (!this.messaging) return;
- const raw = ev.event;
+ const raw = ev.event as IEvent;
this.messaging.feedEvent(raw).catch(e => {
console.error("Error sending event to widget: ", e);
});
diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts
index 25e81c47a2..5218e4a0bc 100644
--- a/src/stores/widgets/StopGapWidgetDriver.ts
+++ b/src/stores/widgets/StopGapWidgetDriver.ts
@@ -145,7 +145,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
return {roomId, eventId: r.event_id};
}
- public async readRoomEvents(eventType: string, msgtype: string | undefined, limit: number): Promise {
+ public async readRoomEvents(eventType: string, msgtype: string | undefined, limit: number): Promise