diff --git a/.github/workflows/static_analysis.yaml b/.github/workflows/static_analysis.yaml
index 0b8fcfa5c82..999f19c2589 100644
--- a/.github/workflows/static_analysis.yaml
+++ b/.github/workflows/static_analysis.yaml
@@ -52,6 +52,8 @@ jobs:
- "--noImplicitAny"
steps:
- uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
- name: Install Deps
run: "scripts/ci/layered.sh"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bfd402f79ee..3b8428ec020 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,50 @@
+Changes in [3.67.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.67.0) (2023-02-28)
+=====================================================================================================
+
+## ✨ Features
+ * Fix block code styling in rich text editor ([\#10246](https://github.com/matrix-org/matrix-react-sdk/pull/10246)). Contributed by @alunturner.
+ * Poll history: fetch more poll history ([\#10235](https://github.com/matrix-org/matrix-react-sdk/pull/10235)). Contributed by @kerryarchibald.
+ * Sort short/exact emoji matches before longer incomplete matches ([\#10212](https://github.com/matrix-org/matrix-react-sdk/pull/10212)). Fixes vector-im/element-web#23210. Contributed by @grimhilt.
+ * Poll history: detail screen ([\#10172](https://github.com/matrix-org/matrix-react-sdk/pull/10172)). Contributed by @kerryarchibald.
+ * Provide a more detailed error message than "No known servers" ([\#6048](https://github.com/matrix-org/matrix-react-sdk/pull/6048)). Fixes vector-im/element-web#13247. Contributed by @aaronraimist.
+ * Say when a call was answered from a different device ([\#10224](https://github.com/matrix-org/matrix-react-sdk/pull/10224)).
+ * Widget permissions customizations using module api ([\#10121](https://github.com/matrix-org/matrix-react-sdk/pull/10121)). Contributed by @maheichyk.
+ * Fix copy button icon overlapping with copyable text ([\#10227](https://github.com/matrix-org/matrix-react-sdk/pull/10227)). Contributed by @Adesh-Pandey.
+ * Support joining non-peekable rooms via the module API ([\#10154](https://github.com/matrix-org/matrix-react-sdk/pull/10154)). Contributed by @maheichyk.
+ * The "new login" toast does now display the same device information as in the settings. "No" does now open the device settings. "Yes, it was me" dismisses the toast. ([\#10200](https://github.com/matrix-org/matrix-react-sdk/pull/10200)).
+ * Do not prompt for a password when doing a „reset all“ after login ([\#10208](https://github.com/matrix-org/matrix-react-sdk/pull/10208)).
+ * Display "The sender has blocked you from receiving this message" error message instead of "Unable to decrypt message" ([\#10202](https://github.com/matrix-org/matrix-react-sdk/pull/10202)). Contributed by @florianduros.
+ * Polls: show warning about undecryptable relations ([\#10179](https://github.com/matrix-org/matrix-react-sdk/pull/10179)). Contributed by @kerryarchibald.
+ * Poll history: fetch last 30 days of polls ([\#10157](https://github.com/matrix-org/matrix-react-sdk/pull/10157)). Contributed by @kerryarchibald.
+ * Poll history - ended polls list items ([\#10119](https://github.com/matrix-org/matrix-react-sdk/pull/10119)). Contributed by @kerryarchibald.
+ * Remove threads labs flag and the ability to disable threads ([\#9878](https://github.com/matrix-org/matrix-react-sdk/pull/9878)). Fixes vector-im/element-web#24365.
+ * Show a success dialog after setting up the key backup ([\#10177](https://github.com/matrix-org/matrix-react-sdk/pull/10177)). Fixes vector-im/element-web#24487.
+ * Release Sign in with QR out of labs ([\#10066](https://github.com/matrix-org/matrix-react-sdk/pull/10066)). Contributed by @hughns.
+ * Hide indent button in rte ([\#10149](https://github.com/matrix-org/matrix-react-sdk/pull/10149)). Contributed by @alunturner.
+ * Add option to find own location in map views ([\#10083](https://github.com/matrix-org/matrix-react-sdk/pull/10083)).
+ * Render poll end events in timeline ([\#10027](https://github.com/matrix-org/matrix-react-sdk/pull/10027)). Contributed by @kerryarchibald.
+
+## 🐛 Bug Fixes
+ * Use the room avatar as a placeholder in calls ([\#10231](https://github.com/matrix-org/matrix-react-sdk/pull/10231)).
+ * Fix calls showing as 'connecting' after hangup ([\#10223](https://github.com/matrix-org/matrix-react-sdk/pull/10223)).
+ * Stop access token overflowing the box ([\#10069](https://github.com/matrix-org/matrix-react-sdk/pull/10069)). Fixes vector-im/element-web#24023. Contributed by @sbjaj33.
+ * Prevent multiple Jitsi calls started at the same time ([\#10183](https://github.com/matrix-org/matrix-react-sdk/pull/10183)). Fixes vector-im/element-web#23009.
+ * Make localization keys compatible with agglutinative and/or SOV type languages ([\#10159](https://github.com/matrix-org/matrix-react-sdk/pull/10159)). Contributed by @luixxiul.
+ * Add link to next file in the export ([\#10190](https://github.com/matrix-org/matrix-react-sdk/pull/10190)). Fixes vector-im/element-web#20272. Contributed by @grimhilt.
+ * Ended poll tiles: add ended the poll message ([\#10193](https://github.com/matrix-org/matrix-react-sdk/pull/10193)). Fixes vector-im/element-web#24579. Contributed by @kerryarchibald.
+ * Fix accidentally inverted condition for room ordering ([\#10178](https://github.com/matrix-org/matrix-react-sdk/pull/10178)). Fixes vector-im/element-web#24527. Contributed by @justjanne.
+ * Re-focus the composer on dialogue quit ([\#10007](https://github.com/matrix-org/matrix-react-sdk/pull/10007)). Fixes vector-im/element-web#22832. Contributed by @Ashu999.
+ * Try to resolve emails before creating a DM ([\#10164](https://github.com/matrix-org/matrix-react-sdk/pull/10164)).
+ * Disable poll response loading test ([\#10168](https://github.com/matrix-org/matrix-react-sdk/pull/10168)). Contributed by @justjanne.
+ * Fix email lookup in invite dialog ([\#10150](https://github.com/matrix-org/matrix-react-sdk/pull/10150)). Fixes vector-im/element-web#23353.
+ * Remove duplicate white space characters from translation keys ([\#10152](https://github.com/matrix-org/matrix-react-sdk/pull/10152)). Contributed by @luixxiul.
+ * Fix the caption of new sessions manager on Labs settings page for localization ([\#10143](https://github.com/matrix-org/matrix-react-sdk/pull/10143)). Contributed by @luixxiul.
+ * Prevent start another DM with a user if one already exists ([\#10127](https://github.com/matrix-org/matrix-react-sdk/pull/10127)). Fixes vector-im/element-web#23138.
+ * Remove white space characters before the horizontal ellipsis ([\#10130](https://github.com/matrix-org/matrix-react-sdk/pull/10130)). Contributed by @luixxiul.
+ * Fix Selectable Text on 'Delete All' and 'Retry All' Buttons ([\#10128](https://github.com/matrix-org/matrix-react-sdk/pull/10128)). Fixes vector-im/element-web#23232. Contributed by @akshattchhabra.
+ * Correctly Identify emoticons ([\#10108](https://github.com/matrix-org/matrix-react-sdk/pull/10108)). Fixes vector-im/element-web#19472. Contributed by @adarsh-sgh.
+ * Remove a redundant white space ([\#10129](https://github.com/matrix-org/matrix-react-sdk/pull/10129)). Contributed by @luixxiul.
+
Changes in [3.66.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.66.0) (2023-02-14)
=====================================================================================================
diff --git a/cypress/e2e/crypto/crypto.spec.ts b/cypress/e2e/crypto/crypto.spec.ts
index 306e94cb97d..7ffd290862a 100644
--- a/cypress/e2e/crypto/crypto.spec.ts
+++ b/cypress/e2e/crypto/crypto.spec.ts
@@ -183,6 +183,10 @@ describe("Cryptography", function () {
cy.contains(".mx_Dialog_primary:not([disabled])", "Continue").click();
cy.contains(".mx_Dialog_title", "Setting up keys").should("exist");
cy.contains(".mx_Dialog_title", "Setting up keys").should("not.exist");
+
+ cy.contains("Secure Backup successful").should("exist");
+ cy.contains("Done").click();
+ cy.contains("Secure Backup successful").should("not.exist");
});
return;
});
diff --git a/cypress/e2e/crypto/decryption-failure.spec.ts b/cypress/e2e/crypto/decryption-failure.spec.ts
index b9e3265b767..13e3c56abab 100644
--- a/cypress/e2e/crypto/decryption-failure.spec.ts
+++ b/cypress/e2e/crypto/decryption-failure.spec.ts
@@ -181,12 +181,14 @@ describe("Decryption Failure Bar", () => {
cy.contains(".mx_DecryptionFailureBar_button", "Reset").click();
+ // Set up key backup
cy.get(".mx_Dialog").within(() => {
cy.contains(".mx_Dialog_primary", "Continue").click();
cy.get(".mx_CreateSecretStorageDialog_recoveryKey code").invoke("text").as("securityKey");
// Clicking download instead of Copy because of https://github.com/cypress-io/cypress/issues/2851
cy.contains(".mx_AccessibleButton", "Download").click();
cy.contains(".mx_Dialog_primary:not([disabled])", "Continue").click();
+ cy.contains("Done").click();
});
cy.get(".mx_DecryptionFailureBar .mx_DecryptionFailureBar_message_headline").should(
diff --git a/cypress/e2e/location/location.spec.ts b/cypress/e2e/location/location.spec.ts
index 0d512705a08..b716fe543b4 100644
--- a/cypress/e2e/location/location.spec.ts
+++ b/cypress/e2e/location/location.spec.ts
@@ -27,7 +27,7 @@ describe("Location sharing", () => {
};
const submitShareLocation = (): void => {
- cy.get('[data-test-id="location-picker-submit-button"]').click();
+ cy.get('[data-testid="location-picker-submit-button"]').click();
};
beforeEach(() => {
diff --git a/cypress/e2e/polls/polls.spec.ts b/cypress/e2e/polls/polls.spec.ts
index 51d169d61bd..07a14533c70 100644
--- a/cypress/e2e/polls/polls.spec.ts
+++ b/cypress/e2e/polls/polls.spec.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2022 The Matrix.org Foundation C.I.C.
+Copyright 2022 - 2023 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.
@@ -54,12 +54,12 @@ describe("Polls", () => {
};
const getPollOption = (pollId: string, optionText: string): Chainable => {
- return getPollTile(pollId).contains(".mx_MPollBody_option .mx_StyledRadioButton", optionText);
+ return getPollTile(pollId).contains(".mx_PollOption .mx_StyledRadioButton", optionText);
};
const expectPollOptionVoteCount = (pollId: string, optionText: string, votes: number): void => {
getPollOption(pollId, optionText).within(() => {
- cy.get(".mx_MPollBody_optionVoteCount").should("contain", `${votes} vote`);
+ cy.get(".mx_PollOption_optionVoteCount").should("contain", `${votes} vote`);
});
};
@@ -83,7 +83,6 @@ describe("Polls", () => {
};
beforeEach(() => {
- cy.enableLabsFeature("feature_threadenabled");
cy.window().then((win) => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
});
diff --git a/cypress/e2e/threads/threads.spec.ts b/cypress/e2e/threads/threads.spec.ts
index 84778d4be76..d946ad34dac 100644
--- a/cypress/e2e/threads/threads.spec.ts
+++ b/cypress/e2e/threads/threads.spec.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2022 The Matrix.org Foundation C.I.C.
+Copyright 2022 - 2023 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.
@@ -19,17 +19,10 @@ limitations under the License.
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";
-function markWindowBeforeReload(): void {
- // mark our window object to "know" when it gets reloaded
- cy.window().then((w) => (w.beforeReload = true));
-}
-
describe("Threads", () => {
let homeserver: HomeserverInstance;
beforeEach(() => {
- // Default threads to ON for this spec
- cy.enableLabsFeature("feature_threadenabled");
cy.window().then((win) => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
});
@@ -44,35 +37,6 @@ describe("Threads", () => {
cy.stopHomeserver(homeserver);
});
- it("should reload when enabling threads beta", () => {
- markWindowBeforeReload();
-
- // Turn off
- cy.openUserSettings("Labs").within(() => {
- // initially the new property is there
- cy.window().should("have.prop", "beforeReload", true);
-
- cy.leaveBeta("Threaded messages");
- cy.wait(1000);
- // after reload the property should be gone
- cy.window().should("not.have.prop", "beforeReload");
- });
-
- cy.get(".mx_MatrixChat", { timeout: 15000 }); // wait for the app
- markWindowBeforeReload();
-
- // Turn on
- cy.openUserSettings("Labs").within(() => {
- // initially the new property is there
- cy.window().should("have.prop", "beforeReload", true);
-
- cy.joinBeta("Threaded messages");
- cy.wait(1000);
- // after reload the property should be gone
- cy.window().should("not.have.prop", "beforeReload");
- });
- });
-
it("should be usable for a conversation", () => {
let bot: MatrixClient;
cy.getBot(homeserver, {
diff --git a/package.json b/package.json
index 2c717e99f1d..3642474ee7d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
- "version": "3.66.0",
+ "version": "3.67.0",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
@@ -57,7 +57,7 @@
"dependencies": {
"@babel/runtime": "^7.12.5",
"@matrix-org/analytics-events": "^0.4.0",
- "@matrix-org/matrix-wysiwyg": "^0.23.0",
+ "@matrix-org/matrix-wysiwyg": "^1.1.1",
"@matrix-org/react-sdk-module-api": "^0.0.3",
"@sentry/browser": "^7.0.0",
"@sentry/tracing": "^7.0.0",
@@ -93,7 +93,7 @@
"maplibre-gl": "^2.0.0",
"matrix-encrypt-attachment": "^1.0.3",
"matrix-events-sdk": "0.0.1",
- "matrix-js-sdk": "23.3.0",
+ "matrix-js-sdk": "23.4.0",
"matrix-widget-api": "^1.1.1",
"minimist": "^1.2.5",
"opus-recorder": "^8.0.3",
@@ -112,7 +112,7 @@
"react-transition-group": "^4.4.1",
"rfc4648": "^1.4.0",
"sanitize-filename": "^1.6.3",
- "sanitize-html": "^2.3.2",
+ "sanitize-html": "2.8.0",
"tar-js": "^0.3.0",
"ua-parser-js": "^1.0.2",
"url": "^0.11.0",
@@ -153,22 +153,24 @@
"@types/escape-html": "^1.0.1",
"@types/file-saver": "^2.0.3",
"@types/flux": "^3.1.9",
- "@types/fs-extra": "^9.0.13",
+ "@types/fs-extra": "^11.0.0",
"@types/geojson": "^7946.0.8",
+ "@types/glob-to-regexp": "^0.4.1",
"@types/jest": "^29.2.1",
- "@types/katex": "^0.14.0",
+ "@types/katex": "^0.16.0",
"@types/lodash": "^4.14.168",
"@types/modernizr": "^3.5.3",
"@types/node": "^16",
+ "@types/node-fetch": "^2.6.2",
"@types/pako": "^2.0.0",
"@types/parse5": "^6.0.0",
"@types/qrcode": "^1.3.5",
"@types/react": "17.0.49",
"@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "17.0.17",
- "@types/react-test-renderer": "^17.0.1",
"@types/react-transition-group": "^4.4.0",
- "@types/sanitize-html": "^2.3.1",
+ "@types/sanitize-html": "2.8.0",
+ "@types/tar-js": "^0.3.2",
"@types/ua-parser-js": "^0.7.36",
"@types/zxcvbn": "^4.4.0",
"@typescript-eslint/eslint-plugin": "^5.35.1",
@@ -210,7 +212,6 @@
"postcss-scss": "^4.0.4",
"prettier": "2.8.0",
"raw-loader": "^4.0.2",
- "react-test-renderer": "^17.0.2",
"rimraf": "^3.0.2",
"stylelint": "^14.9.1",
"stylelint-config-prettier": "^9.0.4",
diff --git a/res/css/_components.pcss b/res/css/_components.pcss
index 671de3ed868..f1f15287d5e 100644
--- a/res/css/_components.pcss
+++ b/res/css/_components.pcss
@@ -18,7 +18,9 @@
@import "./components/views/beacon/_StyledLiveBeaconIcon.pcss";
@import "./components/views/context_menus/_KebabContextMenu.pcss";
@import "./components/views/dialogs/polls/_PollListItem.pcss";
+@import "./components/views/dialogs/polls/_PollListItemEnded.pcss";
@import "./components/views/elements/_FilterDropdown.pcss";
+@import "./components/views/elements/_FilterTabGroup.pcss";
@import "./components/views/elements/_LearnMore.pcss";
@import "./components/views/location/_EnableLiveShare.pcss";
@import "./components/views/location/_LiveDurationDropdown.pcss";
@@ -32,6 +34,7 @@
@import "./components/views/messages/_MBeaconBody.pcss";
@import "./components/views/messages/shared/_MediaProcessingError.pcss";
@import "./components/views/pips/_WidgetPip.pcss";
+@import "./components/views/polls/_PollOption.pcss";
@import "./components/views/settings/devices/_CurrentDeviceSection.pcss";
@import "./components/views/settings/devices/_DeviceDetailHeading.pcss";
@import "./components/views/settings/devices/_DeviceDetails.pcss";
@@ -48,6 +51,7 @@
@import "./components/views/spaces/_QuickThemeSwitcher.pcss";
@import "./components/views/typography/_Caption.pcss";
@import "./compound/_Icon.pcss";
+@import "./compound/_SuccessDialog.pcss";
@import "./structures/_AutoHideScrollbar.pcss";
@import "./structures/_AutocompleteInput.pcss";
@import "./structures/_BackdropPanel.pcss";
@@ -236,6 +240,7 @@
@import "./views/messages/_MLocationBody.pcss";
@import "./views/messages/_MNoticeBody.pcss";
@import "./views/messages/_MPollBody.pcss";
+@import "./views/messages/_MPollEndBody.pcss";
@import "./views/messages/_MStickerBody.pcss";
@import "./views/messages/_MTextBody.pcss";
@import "./views/messages/_MVideoBody.pcss";
diff --git a/res/css/components/views/dialogs/polls/_PollListItemEnded.pcss b/res/css/components/views/dialogs/polls/_PollListItemEnded.pcss
new file mode 100644
index 00000000000..6518052ab61
--- /dev/null
+++ b/res/css/components/views/dialogs/polls/_PollListItemEnded.pcss
@@ -0,0 +1,60 @@
+/*
+Copyright 2023 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_PollListItemEnded {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ color: $primary-content;
+}
+
+.mx_PollListItemEnded_title {
+ display: grid;
+ justify-content: left;
+ align-items: center;
+ grid-gap: $spacing-8;
+ grid-template-columns: min-content 1fr min-content;
+ grid-template-rows: auto;
+}
+
+.mx_PollListItemEnded_icon {
+ height: 14px;
+ width: 14px;
+ color: $quaternary-content;
+ padding-left: $spacing-8;
+}
+
+.mx_PollListItemEnded_date {
+ font-size: $font-12px;
+ color: $secondary-content;
+}
+
+.mx_PollListItemEnded_question {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.mx_PollListItemEnded_answers {
+ display: grid;
+ grid-gap: $spacing-8;
+ margin-top: $spacing-12;
+}
+
+.mx_PollListItemEnded_voteCount {
+ // 6px to match PollOption padding
+ margin: $spacing-8 0 0 6px;
+}
diff --git a/res/css/components/views/elements/_FilterTabGroup.pcss b/res/css/components/views/elements/_FilterTabGroup.pcss
new file mode 100644
index 00000000000..bbf1a279ad4
--- /dev/null
+++ b/res/css/components/views/elements/_FilterTabGroup.pcss
@@ -0,0 +1,46 @@
+/*
+Copyright 2023 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_FilterTabGroup {
+ color: $primary-content;
+ label {
+ margin-right: $spacing-12;
+ cursor: pointer;
+ span {
+ display: inline-block;
+ line-height: $font-24px;
+ }
+ }
+ input[type="radio"] {
+ appearance: none;
+ margin: 0;
+ padding: 0;
+
+ &:focus,
+ &:hover {
+ & + span {
+ color: $secondary-content;
+ }
+ }
+
+ &:checked + span {
+ color: $accent;
+ font-weight: $font-semi-bold;
+ // underline
+ box-shadow: 0 1.5px 0 0 currentColor;
+ }
+ }
+}
diff --git a/res/css/components/views/polls/_PollOption.pcss b/res/css/components/views/polls/_PollOption.pcss
new file mode 100644
index 00000000000..da4c66d6cf1
--- /dev/null
+++ b/res/css/components/views/polls/_PollOption.pcss
@@ -0,0 +1,108 @@
+/*
+Copyright 2023 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_PollOption {
+ border: 1px solid $quinary-content;
+ border-radius: 8px;
+ padding: 6px 12px;
+ background-color: $background;
+
+ .mx_StyledRadioButton_content,
+ .mx_PollOption_endedOption {
+ padding-top: 2px;
+ margin-right: 0px;
+ }
+
+ .mx_StyledRadioButton_spacer {
+ display: none;
+ }
+}
+
+.mx_PollOption,
+/* label has cursor: default in user-agent stylesheet */
+/* override */
+.mx_PollOption_live-option {
+ cursor: pointer;
+}
+
+.mx_PollOption_content {
+ display: flex;
+ justify-content: space-between;
+}
+
+.mx_PollOption_optionVoteCount {
+ color: $secondary-content;
+ font-size: $font-12px;
+ white-space: nowrap;
+}
+
+.mx_PollOption_winnerIcon {
+ height: 12px;
+ width: 12px;
+ color: $accent;
+ margin-right: $spacing-4;
+ vertical-align: middle;
+}
+
+.mx_PollOption_checked {
+ border-color: $accent;
+
+ .mx_PollOption_popularityBackground {
+ .mx_PollOption_popularityAmount {
+ background-color: $accent;
+ }
+ }
+
+ // override checked radio button styling
+ // to show checkmark instead
+ .mx_StyledRadioButton_checked {
+ input[type="radio"] + div {
+ border-width: 2px;
+ border-color: $accent;
+ background-color: $accent;
+ background-image: url("$(res)/img/element-icons/check-white.svg");
+ background-size: 12px;
+ background-repeat: no-repeat;
+ background-position: center;
+
+ div {
+ visibility: hidden;
+ }
+ }
+ }
+}
+
+/* options not actionable in these states */
+.mx_PollOption_checked,
+.mx_PollOption_ended {
+ pointer-events: none;
+}
+
+.mx_PollOption_popularityBackground {
+ width: 100%;
+ height: 8px;
+ margin-right: 12px;
+ border-radius: 8px;
+ background-color: $system;
+ margin-top: $spacing-8;
+
+ .mx_PollOption_popularityAmount {
+ width: 0%;
+ height: 8px;
+ border-radius: 8px;
+ background-color: $quaternary-content;
+ }
+}
diff --git a/res/css/compound/_Icon.pcss b/res/css/compound/_Icon.pcss
index 4a1d832675d..e12006a32e3 100644
--- a/res/css/compound/_Icon.pcss
+++ b/res/css/compound/_Icon.pcss
@@ -29,10 +29,22 @@ limitations under the License.
color: $accent;
}
+.mx_Icon_bg-accent-light {
+ background-color: rgba($accent, 0.1);
+}
+
.mx_Icon_alert {
color: $alert;
}
+.mx_Icon_circle-40 {
+ border-radius: 20px;
+ flex: 0 0 40px;
+ height: 40px;
+ padding: 0 12px;
+ width: 40px;
+}
+
.mx_Icon_8 {
flex: 0 0 8px;
height: 8px;
diff --git a/res/css/compound/_SuccessDialog.pcss b/res/css/compound/_SuccessDialog.pcss
new file mode 100644
index 00000000000..61f98a97df7
--- /dev/null
+++ b/res/css/compound/_SuccessDialog.pcss
@@ -0,0 +1,48 @@
+/*
+Copyright 2023 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_SuccessDialog {
+ text-align: center;
+
+ .mx_Icon {
+ mask-border: $spacing-16;
+ }
+
+ .mx_Dialog_header {
+ margin: 0 0 $spacing-16;
+ padding: 0;
+ }
+
+ .mx_Dialog_title {
+ margin: 0;
+ }
+
+ .mx_Dialog_content {
+ color: $secondary-content;
+ margin: 0 0 $spacing-40;
+ }
+
+ .mx_Dialog_buttons {
+ .mx_Dialog_buttons_row {
+ justify-content: center;
+
+ button.mx_Dialog_primary {
+ height: 48px;
+ min-width: 328px;
+ }
+ }
+ }
+}
diff --git a/res/css/structures/_RoomStatusBar.pcss b/res/css/structures/_RoomStatusBar.pcss
index 40969bf0c2c..0c826bc5b1c 100644
--- a/res/css/structures/_RoomStatusBar.pcss
+++ b/res/css/structures/_RoomStatusBar.pcss
@@ -115,6 +115,7 @@ limitations under the License.
padding-left: 30px; /* 18px for the icon, 2px margin to text, 10px regular padding */
display: inline-block;
position: relative;
+ user-select: none;
&:nth-child(2) {
border-left: 1px solid $resend-button-divider-color;
diff --git a/res/css/views/dialogs/polls/_PollHistoryList.pcss b/res/css/views/dialogs/polls/_PollHistoryList.pcss
index 6a0a003ce1e..ee6f0254f71 100644
--- a/res/css/views/dialogs/polls/_PollHistoryList.pcss
+++ b/res/css/views/dialogs/polls/_PollHistoryList.pcss
@@ -32,6 +32,10 @@ limitations under the License.
grid-gap: $spacing-20;
padding-right: $spacing-64;
margin: $spacing-32 0;
+
+ &.mx_PollHistoryList_list_ENDED {
+ grid-gap: $spacing-32;
+ }
}
.mx_PollHistoryList_noResults {
@@ -42,3 +46,14 @@ limitations under the License.
justify-content: center;
color: $secondary-content;
}
+
+.mx_PollHistoryList_loading {
+ color: $secondary-content;
+ text-align: center;
+
+ // center in all free space
+ // when there are no results
+ &.mx_PollHistoryList_noResultsYet {
+ margin: auto auto;
+ }
+}
diff --git a/res/css/views/dialogs/security/_CreateSecretStorageDialog.pcss b/res/css/views/dialogs/security/_CreateSecretStorageDialog.pcss
index 2c624e835a2..5dc40898623 100644
--- a/res/css/views/dialogs/security/_CreateSecretStorageDialog.pcss
+++ b/res/css/views/dialogs/security/_CreateSecretStorageDialog.pcss
@@ -23,6 +23,14 @@ limitations under the License.
/* never asked. */
width: 560px;
+ &.mx_SuccessDialog {
+ padding: 56px; /* 80px from design - 24px wrapper padding */
+
+ .mx_Dialog_title {
+ margin-bottom: $spacing-16;
+ }
+ }
+
.mx_SettingsFlag {
display: flex;
}
diff --git a/res/css/views/elements/_CopyableText.pcss b/res/css/views/elements/_CopyableText.pcss
index e6b3b1ebf92..8e1d3f3cfd7 100644
--- a/res/css/views/elements/_CopyableText.pcss
+++ b/res/css/views/elements/_CopyableText.pcss
@@ -38,9 +38,12 @@ limitations under the License.
cursor: pointer;
margin-left: 20px;
display: block;
+ /* If the copy button is used within a scrollable div, make it stick to the right while scrolling */
+ position: sticky;
+ right: 0;
/* center to first line */
- position: relative;
top: 0.15em;
+ background-color: $background;
&::before {
content: "";
diff --git a/res/css/views/messages/_MPollBody.pcss b/res/css/views/messages/_MPollBody.pcss
index ed355be103c..e7f3118d571 100644
--- a/res/css/views/messages/_MPollBody.pcss
+++ b/res/css/views/messages/_MPollBody.pcss
@@ -47,109 +47,6 @@ limitations under the License.
mask-image: url("$(res)/img/element-icons/room/composer/poll.svg");
}
- .mx_MPollBody_option {
- border: 1px solid $quinary-content;
- border-radius: 8px;
- margin-bottom: 16px;
- padding: 6px 12px;
- max-width: 550px;
- background-color: $background;
-
- .mx_StyledRadioButton,
- .mx_MPollBody_endedOption {
- margin-bottom: 8px;
- }
-
- .mx_StyledRadioButton_content,
- .mx_MPollBody_endedOption {
- padding-top: 2px;
- margin-right: 0px;
- }
-
- .mx_StyledRadioButton_spacer {
- display: none;
- }
-
- .mx_MPollBody_optionDescription {
- display: flex;
- justify-content: space-between;
-
- .mx_MPollBody_optionVoteCount {
- color: $secondary-content;
- font-size: $font-12px;
- white-space: nowrap;
- margin-left: 8px;
- }
- }
-
- .mx_MPollBody_popularityBackground {
- width: 100%;
- height: 8px;
- margin-right: 12px;
- border-radius: 8px;
- background-color: $system;
-
- .mx_MPollBody_popularityAmount {
- width: 0%;
- height: 8px;
- border-radius: 8px;
- background-color: $quaternary-content;
- }
- }
- }
-
- .mx_MPollBody_option:last-child {
- margin-bottom: 8px;
- }
-
- .mx_MPollBody_option_checked {
- border-color: $accent;
-
- .mx_MPollBody_popularityBackground {
- .mx_MPollBody_popularityAmount {
- background-color: $accent;
- }
- }
- }
-
- /* options not actionable in these states */
- .mx_MPollBody_option_checked,
- .mx_MPollBody_option_ended {
- pointer-events: none;
- }
-
- .mx_StyledRadioButton_checked,
- .mx_MPollBody_endedOptionWinner {
- input[type="radio"] + div {
- border-width: 2px;
- border-color: $accent;
- background-color: $accent;
- background-image: url("$(res)/img/element-icons/check-white.svg");
- background-size: 12px;
- background-repeat: no-repeat;
- background-position: center;
-
- div {
- visibility: hidden;
- }
- }
- }
-
- .mx_MPollBody_endedOptionWinner .mx_MPollBody_optionDescription .mx_MPollBody_optionVoteCount::before {
- content: "";
- position: relative;
- display: inline-block;
- margin-right: 4px;
- top: 2px;
- height: 12px;
- width: 12px;
- background-color: $accent;
- mask-repeat: no-repeat;
- mask-size: contain;
- mask-position: center;
- mask-image: url("$(res)/img/element-icons/trophy.svg");
- }
-
.mx_MPollBody_totalVotes {
display: flex;
flex-direction: inline;
@@ -169,9 +66,9 @@ limitations under the License.
pointer-events: none;
}
-.mx_MPollBody_option,
-/* label has cursor: default in user-agent stylesheet */
-/* override */
-.mx_MPollBody_live-option {
- cursor: pointer;
+.mx_MPollBody_allOptions {
+ display: grid;
+ grid-gap: $spacing-16;
+ margin-bottom: $spacing-8;
+ max-width: 550px;
}
diff --git a/res/css/views/messages/_MPollEndBody.pcss b/res/css/views/messages/_MPollEndBody.pcss
new file mode 100644
index 00000000000..db302655043
--- /dev/null
+++ b/res/css/views/messages/_MPollEndBody.pcss
@@ -0,0 +1,22 @@
+/*
+Copyright 2023 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_MPollEndBody_icon {
+ height: 14px;
+ margin-right: $spacing-8;
+ vertical-align: middle;
+ color: $secondary-content;
+}
diff --git a/res/css/views/settings/tabs/user/_HelpUserSettingsTab.pcss b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.pcss
index c03de9f36ce..5a61bcd2dab 100644
--- a/res/css/views/settings/tabs/user/_HelpUserSettingsTab.pcss
+++ b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.pcss
@@ -28,4 +28,9 @@ limitations under the License.
margin-bottom: $spacing-16;
}
}
+
+ /* prevent the access token from overflowing the text box */
+ div .mx_CopyableText {
+ overflow: scroll;
+ }
}
diff --git a/res/img/element-icons/check.svg b/res/img/element-icons/check.svg
new file mode 100644
index 00000000000..afbd40cf109
--- /dev/null
+++ b/res/img/element-icons/check.svg
@@ -0,0 +1,20 @@
+
+
diff --git a/res/img/element-icons/room/composer/poll.svg b/res/img/element-icons/room/composer/poll.svg
index e843e36c70a..75e74fd60aa 100644
--- a/res/img/element-icons/room/composer/poll.svg
+++ b/res/img/element-icons/room/composer/poll.svg
@@ -1,5 +1,5 @@
diff --git a/res/img/element-icons/trophy.svg b/res/img/element-icons/trophy.svg
index e392cb0a79c..99f4831b573 100644
--- a/res/img/element-icons/trophy.svg
+++ b/res/img/element-icons/trophy.svg
@@ -1,3 +1,3 @@
diff --git a/scripts/ci/install-deps.sh b/scripts/ci/install-deps.sh
index 7121e7a36ce..544045c7ed4 100755
--- a/scripts/ci/install-deps.sh
+++ b/scripts/ci/install-deps.sh
@@ -11,6 +11,7 @@ set -ex
scripts/fetchdep.sh matrix-org matrix-js-sdk
pushd matrix-js-sdk
+[ -n "$JS_SDK_GITHUB_BASE_REF" ] && git fetch --depth 1 origin $JS_SDK_GITHUB_BASE_REF && git checkout $JS_SDK_GITHUB_BASE_REF
yarn link
yarn install --pure-lockfile $@
popd
diff --git a/scripts/ci/layered.sh b/scripts/ci/layered.sh
index bb002bd3abf..cd3dc7442cb 100755
--- a/scripts/ci/layered.sh
+++ b/scripts/ci/layered.sh
@@ -16,6 +16,7 @@ set -ex
# Set up the js-sdk first
scripts/fetchdep.sh matrix-org matrix-js-sdk
pushd matrix-js-sdk
+[ -n "$JS_SDK_GITHUB_BASE_REF" ] && git fetch --depth 1 origin $JS_SDK_GITHUB_BASE_REF && git checkout $JS_SDK_GITHUB_BASE_REF
yarn link
yarn install --pure-lockfile
popd
diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index f1e72ca2fc4..9d3b64fd6af 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -218,7 +218,7 @@ declare global {
processorCtor: (new (options?: AudioWorkletNodeOptions) => AudioWorkletProcessor) & {
parameterDescriptors?: AudioParamDescriptor[];
},
- );
+ ): void;
// eslint-disable-next-line no-var
var grecaptcha:
diff --git a/src/@types/opus-recorder.d.ts b/src/@types/opus-recorder.d.ts
new file mode 100644
index 00000000000..a964278aa1d
--- /dev/null
+++ b/src/@types/opus-recorder.d.ts
@@ -0,0 +1,65 @@
+/*
+Copyright 2023 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.
+*/
+
+declare module "opus-recorder/dist/recorder.min.js" {
+ export default class Recorder {
+ public static isRecordingSupported(): boolean;
+
+ public constructor(config: {
+ bufferLength?: number;
+ encoderApplication?: number;
+ encoderFrameSize?: number;
+ encoderPath?: string;
+ encoderSampleRate?: number;
+ encoderBitRate?: number;
+ maxFramesPerPage?: number;
+ mediaTrackConstraints?: boolean;
+ monitorGain?: number;
+ numberOfChannels?: number;
+ recordingGain?: number;
+ resampleQuality?: number;
+ streamPages?: boolean;
+ wavBitDepth?: number;
+ sourceNode?: MediaStreamAudioSourceNode;
+ encoderComplexity?: number;
+ });
+
+ public ondataavailable?(data: ArrayBuffer): void;
+
+ public readonly encodedSamplePosition: number;
+
+ public start(): Promise;
+
+ public stop(): Promise;
+
+ public close(): void;
+ }
+}
+
+declare module "opus-recorder/dist/encoderWorker.min.js" {
+ const path: string;
+ export default path;
+}
+
+declare module "opus-recorder/dist/waveWorker.min.js" {
+ const path: string;
+ export default path;
+}
+
+declare module "opus-recorder/dist/decoderWorker.min.js" {
+ const path: string;
+ export default path;
+}
diff --git a/src/AddThreepid.ts b/src/AddThreepid.ts
index b6ed0d57387..5d8d947854d 100644
--- a/src/AddThreepid.ts
+++ b/src/AddThreepid.ts
@@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix";
+import { IAuthData, IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "./MatrixClientPeg";
import Modal from "./Modal";
@@ -29,6 +29,12 @@ function getIdServerDomain(): string {
return MatrixClientPeg.get().idBaseUrl.split("://")[1];
}
+export type Binding = {
+ bind: boolean;
+ label: string;
+ errorTitle: string;
+};
+
/**
* Allows a user to add a third party identifier to their homeserver and,
* optionally, the identity servers.
@@ -178,7 +184,7 @@ export default class AddThreepid {
* with a "message" property which contains a human-readable message detailing why
* the request failed.
*/
- public async checkEmailLinkClicked(): Promise {
+ public async checkEmailLinkClicked(): Promise<[boolean, IAuthData | Error | null]> {
try {
if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) {
if (this.bind) {
@@ -220,16 +226,19 @@ export default class AddThreepid {
continueKind: "primary",
},
};
- const { finished } = Modal.createDialog(InteractiveAuthDialog, {
- title: _t("Add Email Address"),
- matrixClient: MatrixClientPeg.get(),
- authData: e.data,
- makeRequest: this.makeAddThreepidOnlyRequest,
- aestheticsForStagePhases: {
- [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
- [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
+ const { finished } = Modal.createDialog<[boolean, IAuthData | Error | null]>(
+ InteractiveAuthDialog,
+ {
+ title: _t("Add Email Address"),
+ matrixClient: MatrixClientPeg.get(),
+ authData: e.data,
+ makeRequest: this.makeAddThreepidOnlyRequest,
+ aestheticsForStagePhases: {
+ [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
+ [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
+ },
},
- });
+ );
return finished;
}
}
diff --git a/src/AsyncWrapper.tsx b/src/AsyncWrapper.tsx
index 226f5b692bc..e4e12bedfe5 100644
--- a/src/AsyncWrapper.tsx
+++ b/src/AsyncWrapper.tsx
@@ -42,10 +42,7 @@ interface IState {
export default class AsyncWrapper extends React.Component {
private unmounted = false;
- public state = {
- component: null,
- error: null,
- };
+ public state: IState = {};
public componentDidMount(): void {
// XXX: temporary logging to try to diagnose
@@ -77,7 +74,7 @@ export default class AsyncWrapper extends React.Component {
this.props.onFinished(false);
};
- public render(): JSX.Element {
+ public render(): React.ReactNode {
if (this.state.component) {
const Component = this.state.component;
return ;
diff --git a/src/Avatar.ts b/src/Avatar.ts
index d02e4b8e281..5036b8f2561 100644
--- a/src/Avatar.ts
+++ b/src/Avatar.ts
@@ -138,7 +138,7 @@ export function getInitialLetter(name: string): string | undefined {
}
export function avatarUrlForRoom(
- room: Room,
+ room: Room | null,
width: number,
height: number,
resizeMethod?: ResizeMethod,
diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts
index 46f964995af..7280b6bb3d7 100644
--- a/src/BasePlatform.ts
+++ b/src/BasePlatform.ts
@@ -193,11 +193,11 @@ export default abstract class BasePlatform {
public displayNotification(
title: string,
msg: string,
- avatarUrl: string,
+ avatarUrl: string | null,
room: Room,
ev?: MatrixEvent,
): Notification {
- const notifBody = {
+ const notifBody: NotificationOptions = {
body: msg,
silent: true, // we play our own sounds
};
diff --git a/src/ContentMessages.ts b/src/ContentMessages.ts
index 85ca90067d5..e05858bb166 100644
--- a/src/ContentMessages.ts
+++ b/src/ContentMessages.ts
@@ -89,7 +89,7 @@ async function loadImageElement(imageFile: File): Promise<{
// check for hi-dpi PNGs and fudge display resolution as needed.
// this is mainly needed for macOS screencaps
- let parsePromise: Promise;
+ let parsePromise = Promise.resolve(false);
if (imageFile.type === "image/png") {
// in practice macOS happens to order the chunks so they fall in
// the first 0x1000 bytes (thanks to a massive ICC header).
@@ -101,7 +101,7 @@ async function loadImageElement(imageFile: File): Promise<{
const chunks = extractPngChunks(buffer);
for (const chunk of chunks) {
if (chunk.name === "pHYs") {
- if (chunk.data.byteLength !== PHYS_HIDPI.length) return;
+ if (chunk.data.byteLength !== PHYS_HIDPI.length) return false;
return chunk.data.every((val, i) => val === PHYS_HIDPI[i]);
}
}
@@ -199,10 +199,10 @@ function loadVideoElement(videoFile: File): Promise {
reject(e);
};
- let dataUrl = ev.target.result as string;
+ let dataUrl = ev.target?.result as string;
// Chrome chokes on quicktime but likes mp4, and `file.type` is
// read only, so do this horrible hack to unbreak quicktime
- if (dataUrl.startsWith("data:video/quicktime;")) {
+ if (dataUrl?.startsWith("data:video/quicktime;")) {
dataUrl = dataUrl.replace("data:video/quicktime;", "data:video/mp4;");
}
@@ -258,7 +258,7 @@ function readFileAsArrayBuffer(file: File | Blob): Promise {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function (e): void {
- resolve(e.target.result as ArrayBuffer);
+ resolve(e.target?.result as ArrayBuffer);
};
reader.onerror = function (e): void {
reject(e);
@@ -329,7 +329,7 @@ export async function uploadFile(
export default class ContentMessages {
private inprogress: RoomUpload[] = [];
- private mediaConfig: IMediaConfig = null;
+ private mediaConfig: IMediaConfig | null = null;
public sendStickerContentToRoom(
url: string,
@@ -377,8 +377,8 @@ export default class ContentMessages {
modal.close();
}
- const tooBigFiles = [];
- const okFiles = [];
+ const tooBigFiles: File[] = [];
+ const okFiles: File[] = [];
for (const file of files) {
if (this.isFileSizeAcceptable(file)) {
@@ -420,7 +420,14 @@ export default class ContentMessages {
}
promBefore = doMaybeLocalRoomAction(roomId, (actualRoomId) =>
- this.sendContentToRoom(file, actualRoomId, relation, matrixClient, replyToEvent, loopPromiseBefore),
+ this.sendContentToRoom(
+ file,
+ actualRoomId,
+ relation,
+ matrixClient,
+ replyToEvent ?? undefined,
+ loopPromiseBefore,
+ ),
);
}
@@ -584,7 +591,7 @@ export default class ContentMessages {
}
private ensureMediaConfigFetched(matrixClient: MatrixClient): Promise {
- if (this.mediaConfig !== null) return;
+ if (this.mediaConfig !== null) return Promise.resolve();
logger.log("[Media Config] Fetching");
return matrixClient
diff --git a/src/DateUtils.ts b/src/DateUtils.ts
index c279c1ad1b2..a6a8caa9468 100644
--- a/src/DateUtils.ts
+++ b/src/DateUtils.ts
@@ -175,7 +175,10 @@ function withinCurrentYear(prevDate: Date, nextDate: Date): boolean {
return prevDate.getFullYear() === nextDate.getFullYear();
}
-export function wantsDateSeparator(prevEventDate: Date | undefined, nextEventDate: Date | undefined): boolean {
+export function wantsDateSeparator(
+ prevEventDate: Date | null | undefined,
+ nextEventDate: Date | null | undefined,
+): boolean {
if (!nextEventDate || !prevEventDate) {
return false;
}
diff --git a/src/DecryptionFailureTracker.ts b/src/DecryptionFailureTracker.ts
index 7329c665bc2..256fe245eef 100644
--- a/src/DecryptionFailureTracker.ts
+++ b/src/DecryptionFailureTracker.ts
@@ -138,7 +138,7 @@ export class DecryptionFailureTracker {
return;
}
if (err) {
- this.addDecryptionFailure(new DecryptionFailure(e.getId(), err.code));
+ this.addDecryptionFailure(new DecryptionFailure(e.getId()!, err.code));
} else {
// Could be an event in the failures, remove it
this.removeDecryptionFailuresForEvent(e);
@@ -146,7 +146,7 @@ export class DecryptionFailureTracker {
}
public addVisibleEvent(e: MatrixEvent): void {
- const eventId = e.getId();
+ const eventId = e.getId()!;
if (this.trackedEvents.has(eventId)) {
return;
@@ -154,7 +154,7 @@ export class DecryptionFailureTracker {
this.visibleEvents.add(eventId);
if (this.failures.has(eventId) && !this.visibleFailures.has(eventId)) {
- this.visibleFailures.set(eventId, this.failures.get(eventId));
+ this.visibleFailures.set(eventId, this.failures.get(eventId)!);
}
}
@@ -172,7 +172,7 @@ export class DecryptionFailureTracker {
}
public removeDecryptionFailuresForEvent(e: MatrixEvent): void {
- const eventId = e.getId();
+ const eventId = e.getId()!;
this.failures.delete(eventId);
this.visibleFailures.delete(eventId);
}
@@ -193,8 +193,8 @@ export class DecryptionFailureTracker {
* Clear state and stop checking for and tracking failures.
*/
public stop(): void {
- clearInterval(this.checkInterval);
- clearInterval(this.trackInterval);
+ if (this.checkInterval) clearInterval(this.checkInterval);
+ if (this.trackInterval) clearInterval(this.trackInterval);
this.failures = new Map();
this.visibleEvents = new Set();
diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts
index eccfc1c0e3e..039adc27cd1 100644
--- a/src/DeviceListener.ts
+++ b/src/DeviceListener.ts
@@ -51,7 +51,7 @@ import { isBulkUnverifiedDeviceReminderSnoozed } from "./utils/device/snoozeBulk
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
export default class DeviceListener {
- private dispatcherRef: string;
+ private dispatcherRef: string | null;
// device IDs for which the user has dismissed the verify toast ('Later')
private dismissed = new Set();
// has the user dismissed any of the various nag toasts to setup encryption on this device?
@@ -152,7 +152,7 @@ export default class DeviceListener {
private ensureDeviceIdsAtStartPopulated(): void {
if (this.ourDeviceIdsAtStart === null) {
const cli = MatrixClientPeg.get();
- this.ourDeviceIdsAtStart = new Set(cli.getStoredDevicesForUser(cli.getUserId()).map((d) => d.deviceId));
+ this.ourDeviceIdsAtStart = new Set(cli.getStoredDevicesForUser(cli.getUserId()!).map((d) => d.deviceId));
}
}
@@ -162,7 +162,7 @@ export default class DeviceListener {
// devicesAtStart list to the devices that we see after the fetch.
if (initialFetch) return;
- const myUserId = MatrixClientPeg.get().getUserId();
+ const myUserId = MatrixClientPeg.get().getUserId()!;
if (users.includes(myUserId)) this.ensureDeviceIdsAtStartPopulated();
// No need to do a recheck here: we just need to get a snapshot of our devices
@@ -170,7 +170,7 @@ export default class DeviceListener {
};
private onDevicesUpdated = (users: string[]): void => {
- if (!users.includes(MatrixClientPeg.get().getUserId())) return;
+ if (!users.includes(MatrixClientPeg.get().getUserId()!)) return;
this.recheck();
};
@@ -225,7 +225,7 @@ export default class DeviceListener {
// The server doesn't tell us when key backup is set up, so we poll
// & cache the result
- private async getKeyBackupInfo(): Promise {
+ private async getKeyBackupInfo(): Promise {
const now = new Date().getTime();
if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
@@ -265,10 +265,10 @@ export default class DeviceListener {
this.checkKeyBackupStatus();
} else if (this.shouldShowSetupEncryptionToast()) {
// make sure our keys are finished downloading
- await cli.downloadKeys([cli.getUserId()]);
+ await cli.downloadKeys([cli.getUserId()!]);
// cross signing isn't enabled - nag to enable it
// There are 3 different toasts for:
- if (!cli.getCrossSigningId() && cli.getStoredCrossSigningForUser(cli.getUserId())) {
+ if (!cli.getCrossSigningId() && cli.getStoredCrossSigningForUser(cli.getUserId()!)) {
// Cross-signing on account but this device doesn't trust the master key (verify this session)
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
this.checkKeyBackupStatus();
@@ -310,13 +310,13 @@ export default class DeviceListener {
// as long as cross-signing isn't ready,
// you can't see or dismiss any device toasts
if (crossSigningReady) {
- const devices = cli.getStoredDevicesForUser(cli.getUserId());
+ const devices = cli.getStoredDevicesForUser(cli.getUserId()!);
for (const device of devices) {
if (device.deviceId === cli.deviceId) continue;
const deviceTrust = await cli.checkDeviceTrust(cli.getUserId()!, device.deviceId!);
if (!deviceTrust.isCrossSigningVerified() && !this.dismissed.has(device.deviceId)) {
- if (this.ourDeviceIdsAtStart.has(device.deviceId)) {
+ if (this.ourDeviceIdsAtStart?.has(device.deviceId)) {
oldUnverifiedDeviceIds.add(device.deviceId);
} else {
newUnverifiedDeviceIds.add(device.deviceId);
diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx
index cf7cb285394..b41f39cec3b 100644
--- a/src/HtmlUtils.tsx
+++ b/src/HtmlUtils.tsx
@@ -204,7 +204,7 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = {
attribs.style += "height: 100%;";
}
- attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height);
+ attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height)!;
return { tagName, attribs };
},
"code": function (tagName: string, attribs: sanitizeHtml.Attributes) {
@@ -228,7 +228,7 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = {
// Sanitise and transform data-mx-color and data-mx-bg-color to their CSS
// equivalents
- const customCSSMapper = {
+ const customCSSMapper: Record = {
"data-mx-color": "color",
"data-mx-bg-color": "background-color",
// $customAttributeKey: $cssAttributeKey
@@ -352,7 +352,7 @@ const topicSanitizeHtmlParams: IExtendedSanitizeOptions = {
};
abstract class BaseHighlighter {
- public constructor(public highlightClass: string, public highlightLink: string) {}
+ public constructor(public highlightClass: string, public highlightLink?: string) {}
/**
* apply the highlights to a section of text
@@ -504,7 +504,7 @@ function formatEmojis(message: string, isHtmlMessage: boolean): (JSX.Element | s
export function bodyToHtml(content: IContent, highlights: Optional, opts: IOptsReturnString): string;
export function bodyToHtml(content: IContent, highlights: Optional, opts: IOptsReturnNode): ReactNode;
export function bodyToHtml(content: IContent, highlights: Optional, opts: IOpts = {}): ReactNode | string {
- const isFormattedBody = content.format === "org.matrix.custom.html" && !!content.formatted_body;
+ const isFormattedBody = content.format === "org.matrix.custom.html" && typeof content.formatted_body === "string";
let bodyHasEmoji = false;
let isHtmlMessage = false;
@@ -514,7 +514,7 @@ export function bodyToHtml(content: IContent, highlights: Optional, op
}
let strippedBody: string;
- let safeBody: string; // safe, sanitised HTML, preferred over `strippedBody` which is fully plaintext
+ let safeBody: string | undefined; // safe, sanitised HTML, preferred over `strippedBody` which is fully plaintext
let isAllHtmlEmoji = false;
try {
@@ -530,7 +530,7 @@ export function bodyToHtml(content: IContent, highlights: Optional, op
if (opts.stripReplyFallback && formattedBody) formattedBody = stripHTMLReply(formattedBody);
strippedBody = opts.stripReplyFallback ? stripPlainReply(plainBody) : plainBody;
- bodyHasEmoji = mightContainEmoji(isFormattedBody ? formattedBody : plainBody);
+ bodyHasEmoji = mightContainEmoji(isFormattedBody ? formattedBody! : plainBody);
const highlighter = safeHighlights?.length
? new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink)
@@ -544,11 +544,11 @@ export function bodyToHtml(content: IContent, highlights: Optional, op
// by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure.
sanitizeParams.textFilter = function (safeText) {
- return highlighter.applyHighlights(safeText, safeHighlights).join("");
+ return highlighter.applyHighlights(safeText, safeHighlights!).join("");
};
}
- safeBody = sanitizeHtml(formattedBody, sanitizeParams);
+ safeBody = sanitizeHtml(formattedBody!, sanitizeParams);
const phtml = cheerio.load(safeBody, {
// @ts-ignore: The `_useHtmlParser2` internal option is the
// simplest way to both parse and render using `htmlparser2`.
@@ -594,7 +594,7 @@ export function bodyToHtml(content: IContent, highlights: Optional, op
safeBody = formatEmojis(safeBody, true).join("");
}
} else if (highlighter) {
- safeBody = highlighter.applyHighlights(plainBody, safeHighlights).join("");
+ safeBody = highlighter.applyHighlights(plainBody, safeHighlights!).join("");
}
} finally {
delete sanitizeParams.textFilter;
@@ -616,7 +616,7 @@ export function bodyToHtml(content: IContent, highlights: Optional, op
contentBodyTrimmed = contentBodyTrimmed.replace(EMOJI_SEPARATOR_REGEX, "");
const match = BIGEMOJI_REGEX.exec(contentBodyTrimmed);
- const matched = match && match[0] && match[0].length === contentBodyTrimmed.length;
+ const matched = match?.[0]?.length === contentBodyTrimmed.length;
emojiBody =
(matched || isAllHtmlEmoji) &&
(strippedBody === safeBody || // replies have the html fallbacks, account for that here
@@ -630,7 +630,7 @@ export function bodyToHtml(content: IContent, highlights: Optional, op
"markdown-body": isHtmlMessage && !emojiBody,
});
- let emojiBodyElements: JSX.Element[];
+ let emojiBodyElements: JSX.Element[] | undefined;
if (!safeBody && bodyHasEmoji) {
emojiBodyElements = formatEmojis(strippedBody, false) as JSX.Element[];
}
@@ -659,7 +659,7 @@ export function topicToHtml(
allowExtendedHtml = false,
): ReactNode {
if (!SettingsStore.getValue("feature_html_topic")) {
- htmlTopic = null;
+ htmlTopic = undefined;
}
let isFormattedTopic = !!htmlTopic;
@@ -667,10 +667,10 @@ export function topicToHtml(
let safeTopic = "";
try {
- topicHasEmoji = mightContainEmoji(isFormattedTopic ? htmlTopic : topic);
+ topicHasEmoji = mightContainEmoji(isFormattedTopic ? htmlTopic! : topic);
if (isFormattedTopic) {
- safeTopic = sanitizeHtml(htmlTopic, allowExtendedHtml ? sanitizeHtmlParams : topicSanitizeHtmlParams);
+ safeTopic = sanitizeHtml(htmlTopic!, allowExtendedHtml ? sanitizeHtmlParams : topicSanitizeHtmlParams);
if (topicHasEmoji) {
safeTopic = formatEmojis(safeTopic, true).join("");
}
@@ -679,7 +679,7 @@ export function topicToHtml(
isFormattedTopic = false; // Fall back to plain-text topic
}
- let emojiBodyElements: ReturnType;
+ let emojiBodyElements: ReturnType | undefined;
if (!isFormattedTopic && topicHasEmoji) {
emojiBodyElements = formatEmojis(topic, false);
}
diff --git a/src/IConfigOptions.ts b/src/IConfigOptions.ts
index 8234f5bc757..1db73cc0745 100644
--- a/src/IConfigOptions.ts
+++ b/src/IConfigOptions.ts
@@ -169,10 +169,18 @@ export interface IConfigOptions {
inline?: {
left?: string;
right?: string;
+ pattern?: {
+ tex?: string;
+ latex?: string;
+ };
};
display?: {
left?: string;
right?: string;
+ pattern?: {
+ tex?: string;
+ latex?: string;
+ };
};
};
diff --git a/src/IdentityAuthClient.tsx b/src/IdentityAuthClient.tsx
index 293a3c19a6b..12f42a3add1 100644
--- a/src/IdentityAuthClient.tsx
+++ b/src/IdentityAuthClient.tsx
@@ -67,7 +67,7 @@ export default class IdentityAuthClient {
window.localStorage.setItem("mx_is_access_token", this.accessToken);
}
- private readToken(): string {
+ private readToken(): string | null {
if (this.tempClient) return null; // temporary client: ignore
return window.localStorage.getItem("mx_is_access_token");
}
@@ -77,13 +77,13 @@ export default class IdentityAuthClient {
}
// Returns a promise that resolves to the access_token string from the IS
- public async getAccessToken({ check = true } = {}): Promise {
+ public async getAccessToken({ check = true } = {}): Promise {
if (!this.authEnabled) {
// The current IS doesn't support authentication
return null;
}
- let token = this.accessToken;
+ let token: string | null = this.accessToken;
if (!token) {
token = this.readToken();
}
diff --git a/src/Keyboard.ts b/src/Keyboard.ts
index 9d4d3f61521..7b1ea4031be 100644
--- a/src/Keyboard.ts
+++ b/src/Keyboard.ts
@@ -16,6 +16,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import React from "react";
+
export const Key = {
HOME: "Home",
END: "End",
@@ -76,7 +78,7 @@ export const Key = {
export const IS_MAC = navigator.platform.toUpperCase().includes("MAC");
-export function isOnlyCtrlOrCmdKeyEvent(ev: KeyboardEvent): boolean {
+export function isOnlyCtrlOrCmdKeyEvent(ev: React.KeyboardEvent | KeyboardEvent): boolean {
if (IS_MAC) {
return ev.metaKey && !ev.altKey && !ev.ctrlKey && !ev.shiftKey;
} else {
diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx
index 82e5cac996c..34fffffc505 100644
--- a/src/LegacyCallHandler.tsx
+++ b/src/LegacyCallHandler.tsx
@@ -158,9 +158,9 @@ export default class LegacyCallHandler extends EventEmitter {
private transferees = new Map(); // callId (target) -> call (transferee)
private audioPromises = new Map>();
private audioElementsWithListeners = new Map();
- private supportsPstnProtocol = null;
- private pstnSupportPrefixed = null; // True if the server only support the prefixed pstn protocol
- private supportsSipNativeVirtual = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native
+ private supportsPstnProtocol: boolean | null = null;
+ private pstnSupportPrefixed: boolean | null = null; // True if the server only support the prefixed pstn protocol
+ private supportsSipNativeVirtual: boolean | null = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native
// Map of the asserted identity users after we've looked them up using the API.
// We need to be be able to determine the mapped room synchronously, so we
@@ -181,20 +181,20 @@ export default class LegacyCallHandler extends EventEmitter {
* Gets the user-facing room associated with a call (call.roomId may be the call "virtual room"
* if a voip_mxid_translate_pattern is set in the config)
*/
- public roomIdForCall(call: MatrixCall): string {
+ public roomIdForCall(call?: MatrixCall): string | null {
if (!call) return null;
// check asserted identity: if we're not obeying asserted identity,
// this map will never be populated, but we check anyway for sanity
if (this.shouldObeyAssertedfIdentity()) {
- const nativeUser = this.assertedIdentityNativeUsers[call.callId];
+ const nativeUser = this.assertedIdentityNativeUsers.get(call.callId);
if (nativeUser) {
const room = findDMForUser(MatrixClientPeg.get(), nativeUser);
if (room) return room.roomId;
}
}
- return VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(call.roomId) || call.roomId;
+ return VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(call.roomId) ?? call.roomId ?? null;
}
public start(): void {
@@ -282,7 +282,7 @@ export default class LegacyCallHandler extends EventEmitter {
}
public unSilenceCall(callId: string): void {
- if (this.isForcedSilent) return;
+ if (this.isForcedSilent()) return;
this.silencedCalls.delete(callId);
this.emit(LegacyCallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
this.play(AudioID.Ring);
@@ -341,14 +341,14 @@ export default class LegacyCallHandler extends EventEmitter {
}
private shouldObeyAssertedfIdentity(): boolean {
- return SdkConfig.getObject("voip")?.get("obey_asserted_identity");
+ return !!SdkConfig.getObject("voip")?.get("obey_asserted_identity");
}
- public getSupportsPstnProtocol(): boolean {
+ public getSupportsPstnProtocol(): boolean | null {
return this.supportsPstnProtocol;
}
- public getSupportsVirtualRooms(): boolean {
+ public getSupportsVirtualRooms(): boolean | null {
return this.supportsSipNativeVirtual;
}
@@ -414,7 +414,7 @@ export default class LegacyCallHandler extends EventEmitter {
cli.prepareToEncrypt(cli.getRoom(call.roomId));
};
- public getCallById(callId: string): MatrixCall {
+ public getCallById(callId: string): MatrixCall | null {
for (const call of this.calls.values()) {
if (call.callId === callId) return call;
}
@@ -435,7 +435,7 @@ export default class LegacyCallHandler extends EventEmitter {
}
public getAllActiveCalls(): MatrixCall[] {
- const activeCalls = [];
+ const activeCalls: MatrixCall[] = [];
for (const call of this.calls.values()) {
if (call.state !== CallState.Ended && call.state !== CallState.Ringing) {
@@ -446,7 +446,7 @@ export default class LegacyCallHandler extends EventEmitter {
}
public getAllActiveCallsNotInRoom(notInThisRoomId: string): MatrixCall[] {
- const callsNotInThatRoom = [];
+ const callsNotInThatRoom: MatrixCall[] = [];
for (const [roomId, call] of this.calls.entries()) {
if (roomId !== notInThisRoomId && call.state !== CallState.Ended) {
@@ -466,8 +466,8 @@ export default class LegacyCallHandler extends EventEmitter {
return this.getAllActiveCallsNotInRoom(roomId);
}
- public getTransfereeForCallId(callId: string): MatrixCall {
- return this.transferees[callId];
+ public getTransfereeForCallId(callId: string): MatrixCall | undefined {
+ return this.transferees.get(callId);
}
public play(audioId: AudioID): void {
@@ -547,7 +547,7 @@ export default class LegacyCallHandler extends EventEmitter {
const mappedRoomId = this.roomIdForCall(call);
const callForThisRoom = this.getCallForRoom(mappedRoomId);
- return callForThisRoom && call.callId === callForThisRoom.callId;
+ return !!callForThisRoom && call.callId === callForThisRoom.callId;
}
private setCallListeners(call: MatrixCall): void {
@@ -610,7 +610,7 @@ export default class LegacyCallHandler extends EventEmitter {
return;
}
- const newAssertedIdentity = call.getRemoteAssertedIdentity().id;
+ const newAssertedIdentity = call.getRemoteAssertedIdentity()?.id;
let newNativeAssertedIdentity = newAssertedIdentity;
if (newAssertedIdentity) {
const response = await this.sipNativeLookup(newAssertedIdentity);
@@ -621,7 +621,7 @@ export default class LegacyCallHandler extends EventEmitter {
logger.log(`Asserted identity ${newAssertedIdentity} mapped to ${newNativeAssertedIdentity}`);
if (newNativeAssertedIdentity) {
- this.assertedIdentityNativeUsers[call.callId] = newNativeAssertedIdentity;
+ this.assertedIdentityNativeUsers.set(call.callId, newNativeAssertedIdentity);
// If we don't already have a room with this user, make one. This will be slightly odd
// if they called us because we'll be inviting them, but there's not much we can do about
@@ -642,7 +642,7 @@ export default class LegacyCallHandler extends EventEmitter {
});
}
- private onCallStateChanged = (newState: CallState, oldState: CallState, call: MatrixCall): void => {
+ private onCallStateChanged = (newState: CallState, oldState: CallState | null, call: MatrixCall): void => {
if (!this.matchesCallForThisRoom(call)) return;
const mappedRoomId = this.roomIdForCall(call);
@@ -830,7 +830,7 @@ export default class LegacyCallHandler extends EventEmitter {
"turn.matrix.org, but this will not be as reliable, and " +
"it will share your IP address with that server. You can also manage " +
"this in Settings.",
- null,
+ undefined,
{ code },
)}
@@ -843,7 +843,7 @@ export default class LegacyCallHandler extends EventEmitter {
cli.setFallbackICEServerAllowed(allow);
},
},
- null,
+ undefined,
true,
);
}
@@ -882,7 +882,7 @@ export default class LegacyCallHandler extends EventEmitter {
title,
description,
},
- null,
+ undefined,
true,
);
}
@@ -917,7 +917,7 @@ export default class LegacyCallHandler extends EventEmitter {
return;
}
if (transferee) {
- this.transferees[call.callId] = transferee;
+ this.transferees.set(call.callId, transferee);
}
this.setCallListeners(call);
diff --git a/src/Login.ts b/src/Login.ts
index 6475a9f5c93..73e6366956f 100644
--- a/src/Login.ts
+++ b/src/Login.ts
@@ -29,20 +29,17 @@ interface ILoginOptions {
}
export default class Login {
- private hsUrl: string;
- private isUrl: string;
- private fallbackHsUrl: string;
- private flows: Array;
- private defaultDeviceDisplayName: string;
- private tempClient: MatrixClient;
-
- public constructor(hsUrl: string, isUrl: string, fallbackHsUrl?: string, opts?: ILoginOptions) {
- this.hsUrl = hsUrl;
- this.isUrl = isUrl;
- this.fallbackHsUrl = fallbackHsUrl;
- this.flows = [];
+ private flows: Array = [];
+ private readonly defaultDeviceDisplayName?: string;
+ private tempClient: MatrixClient | null = null; // memoize
+
+ public constructor(
+ private hsUrl: string,
+ private isUrl: string,
+ private fallbackHsUrl: string | null,
+ opts: ILoginOptions,
+ ) {
this.defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
- this.tempClient = null; // memoize
}
public getHomeserverUrl(): string {
@@ -91,12 +88,12 @@ export default class Login {
}
public loginViaPassword(
- username: string,
- phoneCountry: string,
- phoneNumber: string,
+ username: string | undefined,
+ phoneCountry: string | undefined,
+ phoneNumber: string | undefined,
password: string,
): Promise {
- const isEmail = username.indexOf("@") > 0;
+ const isEmail = !!username && username.indexOf("@") > 0;
let identifier;
if (phoneCountry && phoneNumber) {
@@ -127,7 +124,7 @@ export default class Login {
};
const tryFallbackHs = (originalError: Error): Promise => {
- return sendLoginRequest(this.fallbackHsUrl, this.isUrl, "m.login.password", loginParams).catch(
+ return sendLoginRequest(this.fallbackHsUrl!, this.isUrl, "m.login.password", loginParams).catch(
(fallbackError) => {
logger.log("fallback HS login failed", fallbackError);
// throw the original error
@@ -136,13 +133,13 @@ export default class Login {
);
};
- let originalLoginError = null;
+ let originalLoginError: Error | null = null;
return sendLoginRequest(this.hsUrl, this.isUrl, "m.login.password", loginParams)
.catch((error) => {
originalLoginError = error;
if (error.httpStatus === 403) {
if (this.fallbackHsUrl) {
- return tryFallbackHs(originalLoginError);
+ return tryFallbackHs(originalLoginError!);
}
}
throw originalLoginError;
diff --git a/src/Markdown.ts b/src/Markdown.ts
index b082e659d01..e493649e6d0 100644
--- a/src/Markdown.ts
+++ b/src/Markdown.ts
@@ -141,7 +141,7 @@ export default class Markdown {
*/
private repairLinks(parsed: commonmark.Node): commonmark.Node {
const walker = parsed.walker();
- let event: commonmark.NodeWalkingStep = null;
+ let event: commonmark.NodeWalkingStep | null = null;
let text = "";
let isInPara = false;
let previousNode: commonmark.Node | null = null;
@@ -289,7 +289,7 @@ export default class Markdown {
// However, if it's a blockquote, adds a p tag anyway
// in order to avoid deviation to commonmark and unexpected
// results when parsing the formatted HTML.
- if (node.parent.type === "block_quote" || isMultiLine(node)) {
+ if (node.parent?.type === "block_quote" || isMultiLine(node)) {
realParagraph.call(this, node, entering);
}
};
diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts
index 19a9eb5fde8..69a2263fc59 100644
--- a/src/MatrixClientPeg.ts
+++ b/src/MatrixClientPeg.ts
@@ -2,7 +2,7 @@
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd.
Copyright 2017, 2018, 2019 New Vector Ltd
-Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
+Copyright 2019 - 2023 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.
@@ -218,7 +218,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
opts.pendingEventOrdering = PendingEventOrdering.Detached;
opts.lazyLoadMembers = true;
opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours
- opts.threadSupport = SettingsStore.getValue("feature_threadenabled");
+ opts.threadSupport = true;
if (SettingsStore.getValue("feature_sliding_sync")) {
const proxyUrl = SettingsStore.getValue("feature_sliding_sync_proxy_url");
diff --git a/src/MediaDeviceHandler.ts b/src/MediaDeviceHandler.ts
index 329741fe511..fcb066e92fe 100644
--- a/src/MediaDeviceHandler.ts
+++ b/src/MediaDeviceHandler.ts
@@ -37,7 +37,7 @@ export enum MediaDeviceHandlerEvent {
}
export default class MediaDeviceHandler extends EventEmitter {
- private static internalInstance;
+ private static internalInstance?: MediaDeviceHandler;
public static get instance(): MediaDeviceHandler {
if (!MediaDeviceHandler.internalInstance) {
@@ -67,7 +67,7 @@ export default class MediaDeviceHandler extends EventEmitter {
public static async getDevices(): Promise {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
- const output = {
+ const output: Record = {
[MediaDeviceKindEnum.AudioOutput]: [],
[MediaDeviceKindEnum.AudioInput]: [],
[MediaDeviceKindEnum.VideoInput]: [],
diff --git a/src/Modal.tsx b/src/Modal.tsx
index 1b21f74b5e5..3b21c47d3d0 100644
--- a/src/Modal.tsx
+++ b/src/Modal.tsx
@@ -33,7 +33,7 @@ export interface IModal {
beforeClosePromise?: Promise;
closeReason?: string;
onBeforeClose?(reason?: string): Promise;
- onFinished(...args: T): void;
+ onFinished?(...args: T): void;
close(...args: T): void;
hidden?: boolean;
}
@@ -68,11 +68,11 @@ export class ModalManager extends TypedEventEmitter = null;
+ private priorityModal: IModal | null = null;
// The modal to keep open underneath other modals if possible. Useful
// for cases like Settings where the modal should remain open while the
// user is prompted for more information/errors.
- private staticModal: IModal = null;
+ private staticModal: IModal | null = null;
// A list of the modals we have stacked up, with the most recent at [0]
// Neither the static nor priority modal will be in this list.
private modals: IModal[] = [];
@@ -144,17 +144,14 @@ export class ModalManager extends TypedEventEmitter["close"];
onFinishedProm: IHandle["finished"];
} {
- const modal: IModal = {
- onFinished: props ? props.onFinished : null,
- onBeforeClose: options.onBeforeClose,
- beforeClosePromise: null,
- closeReason: null,
+ const modal = {
+ onFinished: props?.onFinished,
+ onBeforeClose: options?.onBeforeClose,
className,
// these will be set below but we need an object reference to pass to getCloseFn before we can do that
elem: null,
- close: null,
- };
+ } as IModal;
// never call this from onFinished() otherwise it will loop
const [closeDialog, onFinishedProm] = this.getCloseFn(modal, props);
@@ -173,7 +170,7 @@ export class ModalManager extends TypedEventEmitter(
modal: IModal,
- props: IProps,
+ props?: IProps,
): [IHandle["close"], IHandle["finished"]] {
const deferred = defer();
return [
@@ -183,13 +180,13 @@ export class ModalManager extends TypedEventEmitter= 0) {
this.modals.splice(i, 1);
@@ -317,7 +314,7 @@ export class ModalManager extends TypedEventEmitter {
diff --git a/src/NodeAnimator.tsx b/src/NodeAnimator.tsx
index 24b4e85ae37..b8c3f855ac4 100644
--- a/src/NodeAnimator.tsx
+++ b/src/NodeAnimator.tsx
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
+import React, { ReactInstance } from "react";
import ReactDom from "react-dom";
interface IChildProps {
@@ -41,7 +41,7 @@ interface IProps {
* automatic positional animation, look at react-shuffle or similar libraries.
*/
export default class NodeAnimator extends React.Component {
- private nodes = {};
+ private nodes: Record = {};
private children: { [key: string]: React.DetailedReactHTMLElement };
public static defaultProps: Partial = {
startStyles: [],
@@ -65,7 +65,7 @@ export default class NodeAnimator extends React.Component {
*/
private applyStyles(node: HTMLElement, styles: React.CSSProperties): void {
Object.entries(styles).forEach(([property, value]) => {
- node.style[property] = value;
+ node.style[property as keyof Omit] = value;
});
}
@@ -120,7 +120,7 @@ export default class NodeAnimator extends React.Component {
this.nodes[k] = node;
}
- public render(): JSX.Element {
+ public render(): React.ReactNode {
return <>{Object.values(this.children)}>;
}
}
diff --git a/src/Notifier.ts b/src/Notifier.ts
index 42909a2632e..4e34083f3f6 100644
--- a/src/Notifier.ts
+++ b/src/Notifier.ts
@@ -68,7 +68,7 @@ Override both the content body and the TextForEvent handler for specific msgtype
This is useful when the content body contains fallback text that would explain that the client can't handle a particular
type of tile.
*/
-const msgTypeHandlers = {
+const msgTypeHandlers: Record string | null> = {
[MsgType.KeyVerificationRequest]: (event: MatrixEvent) => {
const name = (event.sender || {}).name;
return _t("%(name)s is requesting verification", { name });
@@ -95,22 +95,26 @@ const msgTypeHandlers = {
},
};
-export const Notifier = {
- notifsByRoom: {},
+class NotifierClass {
+ private notifsByRoom: Record = {};
// A list of event IDs that we've received but need to wait until
// they're decrypted until we decide whether to notify for them
// or not
- pendingEncryptedEventIds: [],
+ private pendingEncryptedEventIds: string[] = [];
- notificationMessageForEvent: function (ev: MatrixEvent): string {
+ private toolbarHidden?: boolean;
+ private isSyncing?: boolean;
+
+ public notificationMessageForEvent(ev: MatrixEvent): string {
if (msgTypeHandlers.hasOwnProperty(ev.getContent().msgtype)) {
return msgTypeHandlers[ev.getContent().msgtype](ev);
}
return TextForEvent.textForEvent(ev);
- },
+ }
- _displayPopupNotification: function (ev: MatrixEvent, room: Room): void {
+ // XXX: exported for tests
+ public displayPopupNotification(ev: MatrixEvent, room: Room): void {
const plaf = PlatformPeg.get();
const cli = MatrixClientPeg.get();
if (!plaf) {
@@ -152,7 +156,7 @@ export const Notifier = {
msg = "";
}
- let avatarUrl = null;
+ let avatarUrl: string | null = null;
if (ev.sender && !SettingsStore.getValue("lowBandwidth")) {
avatarUrl = Avatar.avatarUrlForMember(ev.sender, 40, 40, "crop");
}
@@ -162,12 +166,17 @@ export const Notifier = {
// if displayNotification returns non-null, the platform supports
// clearing notifications later, so keep track of this.
if (notif) {
- if (this.notifsByRoom[ev.getRoomId()] === undefined) this.notifsByRoom[ev.getRoomId()] = [];
- this.notifsByRoom[ev.getRoomId()].push(notif);
+ if (this.notifsByRoom[ev.getRoomId()!] === undefined) this.notifsByRoom[ev.getRoomId()!] = [];
+ this.notifsByRoom[ev.getRoomId()!].push(notif);
}
- },
-
- getSoundForRoom: function (roomId: string) {
+ }
+
+ public getSoundForRoom(roomId: string): {
+ url: string;
+ name: string;
+ type: string;
+ size: string;
+ } | null {
// We do no caching here because the SDK caches setting
// and the browser will cache the sound.
const content = SettingsStore.getValue("notificationSound", roomId);
@@ -193,9 +202,10 @@ export const Notifier = {
type: content.type,
size: content.size,
};
- },
+ }
- _playAudioNotification: async function (ev: MatrixEvent, room: Room): Promise {
+ // XXX: Exported for tests
+ public async playAudioNotification(ev: MatrixEvent, room: Room): Promise {
const cli = MatrixClientPeg.get();
if (localNotificationsAreSilenced(cli)) {
return;
@@ -209,7 +219,7 @@ export const Notifier = {
sound ? `audio[src='${sound.url}']` : "#messageAudio",
);
let audioElement = selector;
- if (!selector) {
+ if (!audioElement) {
if (!sound) {
logger.error("No audio element or sound to play for notification");
return;
@@ -224,39 +234,32 @@ export const Notifier = {
} catch (ex) {
logger.warn("Caught error when trying to fetch room notification sound:", ex);
}
- },
+ }
- start: function (this: typeof Notifier) {
- // do not re-bind in the case of repeated call
- this.boundOnEvent = this.boundOnEvent || this.onEvent.bind(this);
- this.boundOnSyncStateChange = this.boundOnSyncStateChange || this.onSyncStateChange.bind(this);
- this.boundOnRoomReceipt = this.boundOnRoomReceipt || this.onRoomReceipt.bind(this);
- this.boundOnEventDecrypted = this.boundOnEventDecrypted || this.onEventDecrypted.bind(this);
-
- MatrixClientPeg.get().on(RoomEvent.Timeline, this.boundOnEvent);
- MatrixClientPeg.get().on(RoomEvent.Receipt, this.boundOnRoomReceipt);
- MatrixClientPeg.get().on(MatrixEventEvent.Decrypted, this.boundOnEventDecrypted);
- MatrixClientPeg.get().on(ClientEvent.Sync, this.boundOnSyncStateChange);
+ public start(): void {
+ MatrixClientPeg.get().on(RoomEvent.Timeline, this.onEvent);
+ MatrixClientPeg.get().on(RoomEvent.Receipt, this.onRoomReceipt);
+ MatrixClientPeg.get().on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
+ MatrixClientPeg.get().on(ClientEvent.Sync, this.onSyncStateChange);
this.toolbarHidden = false;
this.isSyncing = false;
- },
+ }
- stop: function (this: typeof Notifier) {
+ public stop(): void {
if (MatrixClientPeg.get()) {
- MatrixClientPeg.get().removeListener(RoomEvent.Timeline, this.boundOnEvent);
- MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.boundOnRoomReceipt);
- MatrixClientPeg.get().removeListener(MatrixEventEvent.Decrypted, this.boundOnEventDecrypted);
- MatrixClientPeg.get().removeListener(ClientEvent.Sync, this.boundOnSyncStateChange);
+ MatrixClientPeg.get().removeListener(RoomEvent.Timeline, this.onEvent);
+ MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.onRoomReceipt);
+ MatrixClientPeg.get().removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted);
+ MatrixClientPeg.get().removeListener(ClientEvent.Sync, this.onSyncStateChange);
}
this.isSyncing = false;
- },
+ }
- supportsDesktopNotifications: function () {
- const plaf = PlatformPeg.get();
- return plaf && plaf.supportsNotifications();
- },
+ public supportsDesktopNotifications(): boolean {
+ return PlatformPeg.get()?.supportsNotifications() ?? false;
+ }
- setEnabled: function (enable: boolean, callback?: () => void) {
+ public setEnabled(enable: boolean, callback?: () => void): void {
const plaf = PlatformPeg.get();
if (!plaf) return;
@@ -320,31 +323,30 @@ export const Notifier = {
// set the notifications_hidden flag, as the user has knowingly interacted
// with the setting we shouldn't nag them any further
this.setPromptHidden(true);
- },
+ }
- isEnabled: function () {
+ public isEnabled(): boolean {
return this.isPossible() && SettingsStore.getValue("notificationsEnabled");
- },
+ }
- isPossible: function () {
+ public isPossible(): boolean {
const plaf = PlatformPeg.get();
- if (!plaf) return false;
- if (!plaf.supportsNotifications()) return false;
+ if (!plaf?.supportsNotifications()) return false;
if (!plaf.maySendNotifications()) return false;
return true; // possible, but not necessarily enabled
- },
+ }
- isBodyEnabled: function () {
+ public isBodyEnabled(): boolean {
return this.isEnabled() && SettingsStore.getValue("notificationBodyEnabled");
- },
+ }
- isAudioEnabled: function () {
+ public isAudioEnabled(): boolean {
// We don't route Audio via the HTML Notifications API so it is possible regardless of other things
return SettingsStore.getValue("audioNotificationsEnabled");
- },
+ }
- setPromptHidden: function (this: typeof Notifier, hidden: boolean, persistent = true) {
+ public setPromptHidden(hidden: boolean, persistent = true): void {
this.toolbarHidden = hidden;
hideNotificationsToast();
@@ -353,9 +355,9 @@ export const Notifier = {
if (persistent && global.localStorage) {
global.localStorage.setItem("notifications_hidden", String(hidden));
}
- },
+ }
- shouldShowPrompt: function () {
+ public shouldShowPrompt(): boolean {
const client = MatrixClientPeg.get();
if (!client) {
return false;
@@ -366,25 +368,21 @@ export const Notifier = {
this.supportsDesktopNotifications() &&
!isPushNotifyDisabled() &&
!this.isEnabled() &&
- !this._isPromptHidden()
+ !this.isPromptHidden()
);
- },
+ }
- _isPromptHidden: function (this: typeof Notifier) {
+ private isPromptHidden(): boolean {
// Check localStorage for any such meta data
if (global.localStorage) {
return global.localStorage.getItem("notifications_hidden") === "true";
}
- return this.toolbarHidden;
- },
+ return !!this.toolbarHidden;
+ }
- onSyncStateChange: function (
- this: typeof Notifier,
- state: SyncState,
- prevState?: SyncState,
- data?: ISyncStateData,
- ) {
+ // XXX: Exported for tests
+ public onSyncStateChange = (state: SyncState, prevState: SyncState | null, data?: ISyncStateData): void => {
if (state === SyncState.Syncing) {
this.isSyncing = true;
} else if (state === SyncState.Stopped || state === SyncState.Error) {
@@ -395,16 +393,15 @@ export const Notifier = {
if (![SyncState.Stopped, SyncState.Error].includes(state) && !data?.fromCache) {
createLocalNotificationSettingsIfNeeded(MatrixClientPeg.get());
}
- },
+ };
- onEvent: function (
- this: typeof Notifier,
+ private onEvent = (
ev: MatrixEvent,
room: Room | undefined,
toStartOfTimeline: boolean | undefined,
removed: boolean,
data: IRoomTimelineData,
- ) {
+ ): void => {
if (!data.liveEvent) return; // only notify for new things, not old.
if (!this.isSyncing) return; // don't alert for any messages initially
if (ev.getSender() === MatrixClientPeg.get().getUserId()) return;
@@ -414,7 +411,7 @@ export const Notifier = {
// If it's an encrypted event and the type is still 'm.room.encrypted',
// it hasn't yet been decrypted, so wait until it is.
if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) {
- this.pendingEncryptedEventIds.push(ev.getId());
+ this.pendingEncryptedEventIds.push(ev.getId()!);
// don't let the list fill up indefinitely
while (this.pendingEncryptedEventIds.length > MAX_PENDING_ENCRYPTED) {
this.pendingEncryptedEventIds.shift();
@@ -422,22 +419,22 @@ export const Notifier = {
return;
}
- this._evaluateEvent(ev);
- },
+ this.evaluateEvent(ev);
+ };
- onEventDecrypted: function (ev: MatrixEvent) {
+ private onEventDecrypted = (ev: MatrixEvent): void => {
// 'decrypted' means the decryption process has finished: it may have failed,
// in which case it might decrypt soon if the keys arrive
if (ev.isDecryptionFailure()) return;
- const idx = this.pendingEncryptedEventIds.indexOf(ev.getId());
+ const idx = this.pendingEncryptedEventIds.indexOf(ev.getId()!);
if (idx === -1) return;
this.pendingEncryptedEventIds.splice(idx, 1);
- this._evaluateEvent(ev);
- },
+ this.evaluateEvent(ev);
+ };
- onRoomReceipt: function (ev: MatrixEvent, room: Room) {
+ private onRoomReceipt = (ev: MatrixEvent, room: Room): void => {
if (room.getUnreadNotificationCount() === 0) {
// ideally we would clear each notification when it was read,
// but we have no way, given a read receipt, to know whether
@@ -453,13 +450,13 @@ export const Notifier = {
}
delete this.notifsByRoom[room.roomId];
}
- },
+ };
- _evaluateEvent: function (ev: MatrixEvent) {
+ // XXX: exported for tests
+ public evaluateEvent(ev: MatrixEvent): void {
// Mute notifications for broadcast info events
if (ev.getType() === VoiceBroadcastInfoEventType) return;
-
- let roomId = ev.getRoomId();
+ let roomId = ev.getRoomId()!;
if (LegacyCallHandler.instance.getSupportsVirtualRooms()) {
// Attempt to translate a virtual room to a native one
const nativeRoomId = VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(roomId);
@@ -477,7 +474,7 @@ export const Notifier = {
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
if (actions?.notify) {
- this._performCustomEventHandling(ev);
+ this.performCustomEventHandling(ev);
const store = SdkContextClass.instance.roomViewStore;
const isViewingRoom = store.getRoomId() === room.roomId;
@@ -492,33 +489,34 @@ export const Notifier = {
}
if (this.isEnabled()) {
- this._displayPopupNotification(ev, room);
+ this.displayPopupNotification(ev, room);
}
if (actions.tweaks.sound && this.isAudioEnabled()) {
- PlatformPeg.get().loudNotification(ev, room);
- this._playAudioNotification(ev, room);
+ PlatformPeg.get()?.loudNotification(ev, room);
+ this.playAudioNotification(ev, room);
}
}
- },
+ }
/**
* Some events require special handling such as showing in-app toasts
*/
- _performCustomEventHandling: function (ev: MatrixEvent) {
+ private performCustomEventHandling(ev: MatrixEvent): void {
if (ElementCall.CALL_EVENT_TYPE.names.includes(ev.getType()) && SettingsStore.getValue("feature_group_calls")) {
ToastStore.sharedInstance().addOrReplaceToast({
- key: getIncomingCallToastKey(ev.getStateKey()),
+ key: getIncomingCallToastKey(ev.getStateKey()!),
priority: 100,
component: IncomingCallToast,
bodyClassName: "mx_IncomingCallToast",
props: { callEvent: ev },
});
}
- },
-};
+ }
+}
if (!window.mxNotifier) {
- window.mxNotifier = Notifier;
+ window.mxNotifier = new NotifierClass();
}
export default window.mxNotifier;
+export const Notifier: NotifierClass = window.mxNotifier;
diff --git a/src/PasswordReset.ts b/src/PasswordReset.ts
index 7dbc8a54060..851aa95df13 100644
--- a/src/PasswordReset.ts
+++ b/src/PasswordReset.ts
@@ -146,7 +146,7 @@ export default class PasswordReset {
err.message = _t("Failed to verify email address: make sure you clicked the link in the email");
} else if (err.httpStatus === 404) {
err.message = _t(
- "Your email address does not appear to be associated with a Matrix ID on this Homeserver.",
+ "Your email address does not appear to be associated with a Matrix ID on this homeserver.",
);
} else if (err.httpStatus) {
err.message += ` (Status ${err.httpStatus})`;
diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts
index c8a99ab4264..5c5805af938 100644
--- a/src/PosthogAnalytics.ts
+++ b/src/PosthogAnalytics.ts
@@ -132,8 +132,8 @@ export class PosthogAnalytics {
private anonymity = Anonymity.Disabled;
// set true during the constructor if posthog config is present, otherwise false
private readonly enabled: boolean = false;
- private static _instance = null;
- private platformSuperProperties = {};
+ private static _instance: PosthogAnalytics | null = null;
+ private platformSuperProperties: Properties = {};
public static readonly ANALYTICS_EVENT_TYPE = "im.vector.analytics";
private propertiesForNextEvent: Partial> = {};
private userPropertyCache: UserProperties = {};
@@ -238,11 +238,11 @@ export class PosthogAnalytics {
}
}
- private static async getPlatformProperties(): Promise {
+ private static async getPlatformProperties(): Promise> {
const platform = PlatformPeg.get();
- let appVersion: string;
+ let appVersion: string | undefined;
try {
- appVersion = await platform.getAppVersion();
+ appVersion = await platform?.getAppVersion();
} catch (e) {
// this happens if no version is set i.e. in dev
appVersion = "unknown";
@@ -250,7 +250,7 @@ export class PosthogAnalytics {
return {
appVersion,
- appPlatform: platform.getHumanReadableName(),
+ appPlatform: platform?.getHumanReadableName(),
};
}
@@ -411,7 +411,7 @@ export class PosthogAnalytics {
// All other scenarios should not track a user before they have given
// explicit consent that they are ok with their analytics data being collected
const options: IPostHogEventOptions = {};
- const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time"), 10);
+ const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time")!, 10);
if (!isNaN(registrationTime)) {
options.timestamp = new Date(registrationTime);
}
diff --git a/src/PosthogTrackers.ts b/src/PosthogTrackers.ts
index 8a8b02965ce..a82b78c1dd2 100644
--- a/src/PosthogTrackers.ts
+++ b/src/PosthogTrackers.ts
@@ -120,7 +120,7 @@ export class PosthogScreenTracker extends PureComponent<{ screenName: ScreenName
PosthogTrackers.instance.clearOverride(this.props.screenName);
}
- public render(): JSX.Element {
+ public render(): React.ReactNode {
return null; // no need to render anything, we just need to hook into the React lifecycle
}
}
diff --git a/src/Presence.ts b/src/Presence.ts
index c13cc32b60f..02d2ef0e7e0 100644
--- a/src/Presence.ts
+++ b/src/Presence.ts
@@ -33,9 +33,9 @@ enum State {
}
class Presence {
- private unavailableTimer: Timer = null;
- private dispatcherRef: string = null;
- private state: State = null;
+ private unavailableTimer: Timer | null = null;
+ private dispatcherRef: string | null = null;
+ private state: State | null = null;
/**
* Start listening the user activity to evaluate his presence state.
@@ -73,14 +73,14 @@ class Presence {
* Get the current presence state.
* @returns {string} the presence state (see PRESENCE enum)
*/
- public getState(): State {
+ public getState(): State | null {
return this.state;
}
private onAction = (payload: ActionPayload): void => {
if (payload.action === "user_activity") {
this.setState(State.Online);
- this.unavailableTimer.restart();
+ this.unavailableTimer?.restart();
}
};
diff --git a/src/Resend.ts b/src/Resend.ts
index bc62c62efab..17e39a7e296 100644
--- a/src/Resend.ts
+++ b/src/Resend.ts
@@ -46,7 +46,7 @@ export default class Resend {
}
public static resend(event: MatrixEvent): Promise {
- const room = MatrixClientPeg.get().getRoom(event.getRoomId());
+ const room = MatrixClientPeg.get().getRoom(event.getRoomId())!;
return MatrixClientPeg.get()
.resendEvent(event, room)
.then(
diff --git a/src/RoomAliasCache.ts b/src/RoomAliasCache.ts
index c318db2d3f6..f565d8d2d3d 100644
--- a/src/RoomAliasCache.ts
+++ b/src/RoomAliasCache.ts
@@ -30,6 +30,6 @@ export function storeRoomAliasInCache(alias: string, id: string): void {
aliasToIDMap.set(alias, id);
}
-export function getCachedRoomIDForAlias(alias: string): string {
+export function getCachedRoomIDForAlias(alias: string): string | undefined {
return aliasToIDMap.get(alias);
}
diff --git a/src/RoomInvite.tsx b/src/RoomInvite.tsx
index 582eb360f81..c92ebcc55e2 100644
--- a/src/RoomInvite.tsx
+++ b/src/RoomInvite.tsx
@@ -112,7 +112,7 @@ export function inviteUsersToRoom(
): Promise {
return inviteMultipleToRoom(roomId, userIds, sendSharedHistoryKeys, progressCallback)
.then((result) => {
- const room = MatrixClientPeg.get().getRoom(roomId);
+ const room = MatrixClientPeg.get().getRoom(roomId)!;
showAnyInviteErrors(result.states, room, result.inviter);
})
.catch((err) => {
@@ -142,7 +142,7 @@ export function showAnyInviteErrors(
});
return false;
} else {
- const errorList = [];
+ const errorList: string[] = [];
for (const addr of failedUsers) {
if (states[addr] === "error") {
const reason = inviter.getErrorText(addr);
@@ -173,16 +173,19 @@ export function showAnyInviteErrors(
{name}
- {user.userId}
+ {user?.userId}
{inviter.getErrorText(addr)}
diff --git a/src/RoomNotifs.ts b/src/RoomNotifs.ts
index c98f5685083..9e94d0895d6 100644
--- a/src/RoomNotifs.ts
+++ b/src/RoomNotifs.ts
@@ -48,7 +48,7 @@ export function getRoomNotifsState(client: MatrixClient, roomId: string): RoomNo
}
// for everything else, look at the room rule.
- let roomRule = null;
+ let roomRule: IPushRule | undefined;
try {
roomRule = client.getRoomPushRule("global", roomId);
} catch (err) {
@@ -108,7 +108,7 @@ export function getUnreadNotificationCount(room: Room, type: NotificationCountTy
function setRoomNotifsStateMuted(roomId: string): Promise {
const cli = MatrixClientPeg.get();
- const promises = [];
+ const promises: Promise[] = [];
// delete the room rule
const roomRule = cli.getRoomPushRule("global", roomId);
@@ -139,7 +139,7 @@ function setRoomNotifsStateMuted(roomId: string): Promise {
function setRoomNotifsStateUnmuted(roomId: string, newState: RoomNotifState): Promise {
const cli = MatrixClientPeg.get();
- const promises = [];
+ const promises: Promise[] = [];
const overrideMuteRule = findOverrideMuteRule(roomId);
if (overrideMuteRule) {
diff --git a/src/Rooms.ts b/src/Rooms.ts
index 12fed5aac1a..25bbb0e1778 100644
--- a/src/Rooms.ts
+++ b/src/Rooms.ts
@@ -30,13 +30,13 @@ import AliasCustomisations from "./customisations/Alias";
* @param {Object} room The room object
* @returns {string} A display alias for the given room
*/
-export function getDisplayAliasForRoom(room: Room): string | undefined {
+export function getDisplayAliasForRoom(room: Room): string | null {
return getDisplayAliasForAliasSet(room.getCanonicalAlias(), room.getAltAliases());
}
// The various display alias getters should all feed through this one path so
// there's a single place to change the logic.
-export function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: string[]): string {
+export function getDisplayAliasForAliasSet(canonicalAlias: string | null, altAliases: string[]): string | null {
if (AliasCustomisations.getDisplayAliasForAliasSet) {
return AliasCustomisations.getDisplayAliasForAliasSet(canonicalAlias, altAliases);
}
@@ -46,7 +46,7 @@ export function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: s
export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise {
let newTarget;
if (isDirect) {
- const guessedUserId = guessDMRoomTargetId(room, MatrixClientPeg.get().getUserId());
+ const guessedUserId = guessDMRoomTargetId(room, MatrixClientPeg.get().getUserId()!);
newTarget = guessedUserId;
} else {
newTarget = null;
@@ -119,7 +119,7 @@ function guessDMRoomTargetId(room: Room, myUserId: string): string {
if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) {
oldestUser = user;
- oldestTs = user.events.member.getTs();
+ oldestTs = user.events.member?.getTs();
}
}
if (oldestUser) return oldestUser.userId;
@@ -130,7 +130,7 @@ function guessDMRoomTargetId(room: Room, myUserId: string): string {
if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) {
oldestUser = user;
- oldestTs = user.events.member.getTs();
+ oldestTs = user.events.member?.getTs();
}
}
diff --git a/src/ScalarAuthClient.ts b/src/ScalarAuthClient.ts
index f65d03ab5cb..8400a8f03fe 100644
--- a/src/ScalarAuthClient.ts
+++ b/src/ScalarAuthClient.ts
@@ -32,8 +32,8 @@ const imApiVersion = "1.1";
// TODO: Generify the name of this class and all components within - it's not just for Scalar.
export default class ScalarAuthClient {
- private scalarToken: string;
- private termsInteractionCallback: TermsInteractionCallback;
+ private scalarToken: string | null;
+ private termsInteractionCallback?: TermsInteractionCallback;
private isDefaultManager: boolean;
public constructor(private apiUrl: string, private uiUrl: string) {
@@ -59,7 +59,7 @@ export default class ScalarAuthClient {
}
}
- private readTokenFromStore(): string {
+ private readTokenFromStore(): string | null {
let token = window.localStorage.getItem("mx_scalar_token_at_" + this.apiUrl);
if (!token && this.isDefaultManager) {
token = window.localStorage.getItem("mx_scalar_token");
@@ -67,7 +67,7 @@ export default class ScalarAuthClient {
return token;
}
- private readToken(): string {
+ private readToken(): string | null {
if (this.scalarToken) return this.scalarToken;
return this.readTokenFromStore();
}
@@ -256,7 +256,7 @@ export default class ScalarAuthClient {
}
}
- public getScalarInterfaceUrlForRoom(room: Room, screen: string, id: string): string {
+ public getScalarInterfaceUrlForRoom(room: Room, screen?: string, id?: string): string {
const roomId = room.roomId;
const roomName = room.name;
let url = this.uiUrl;
diff --git a/src/ScalarMessaging.ts b/src/ScalarMessaging.ts
index fec671eab43..aebffad18b7 100644
--- a/src/ScalarMessaging.ts
+++ b/src/ScalarMessaging.ts
@@ -358,7 +358,7 @@ function inviteUser(event: MessageEvent, roomId: string, userId: string): v
if (room) {
// if they are already invited or joined we can resolve immediately.
const member = room.getMember(userId);
- if (member && ["join", "invite"].includes(member.membership)) {
+ if (member && ["join", "invite"].includes(member.membership!)) {
sendResponse(event, {
success: true,
});
@@ -389,7 +389,7 @@ function kickUser(event: MessageEvent, roomId: string, userId: string): voi
if (room) {
// if they are already not in the room we can resolve immediately.
const member = room.getMember(userId);
- if (!member || getEffectiveMembership(member.membership) === EffectiveMembership.Leave) {
+ if (!member || getEffectiveMembership(member.membership!) === EffectiveMembership.Leave) {
sendResponse(event, {
success: true,
});
@@ -472,7 +472,7 @@ function setWidget(event: MessageEvent, roomId: string | null): void {
} else {
// Room widget
if (!roomId) {
- sendError(event, _t("Missing roomId."), null);
+ sendError(event, _t("Missing roomId."));
return;
}
WidgetUtils.setRoomWidget(
@@ -675,7 +675,7 @@ function canSendEvent(event: MessageEvent, roomId: string): void {
sendError(event, _t("You are not in this room."));
return;
}
- const me = client.credentials.userId;
+ const me = client.credentials.userId!;
let canSend = false;
if (isState) {
diff --git a/src/Searching.ts b/src/Searching.ts
index df54695326e..90798f480ed 100644
--- a/src/Searching.ts
+++ b/src/Searching.ts
@@ -34,7 +34,7 @@ const SEARCH_LIMIT = 10;
async function serverSideSearch(
term: string,
- roomId: string = undefined,
+ roomId?: string,
abortSignal?: AbortSignal,
): Promise<{ response: ISearchResponse; query: ISearchRequestBody }> {
const client = MatrixClientPeg.get();
@@ -67,7 +67,7 @@ async function serverSideSearch(
async function serverSideSearchProcess(
term: string,
- roomId: string = undefined,
+ roomId?: string,
abortSignal?: AbortSignal,
): Promise {
const client = MatrixClientPeg.get();
@@ -158,7 +158,7 @@ async function combinedSearch(searchTerm: string, abortSignal?: AbortSignal): Pr
async function localSearch(
searchTerm: string,
- roomId: string = undefined,
+ roomId?: string,
processResult = true,
): Promise<{ response: IResultRoomEvents; query: ISearchArgs }> {
const eventIndex = EventIndexPeg.get();
@@ -195,7 +195,7 @@ export interface ISeshatSearchResults extends ISearchResults {
serverSideNextBatch?: string;
}
-async function localSearchProcess(searchTerm: string, roomId: string = undefined): Promise {
+async function localSearchProcess(searchTerm: string, roomId?: string): Promise {
const emptyResult = {
results: [],
highlights: [],
@@ -244,7 +244,7 @@ async function localPagination(searchResult: ISeshatSearchResults): Promise {
+function eventIndexSearch(term: string, roomId?: string, abortSignal?: AbortSignal): Promise {
let searchPromise: Promise;
if (roomId !== undefined) {
@@ -643,11 +639,7 @@ export function searchPagination(searchResult: ISearchResults): Promise {
+export default function eventSearch(term: string, roomId?: string, abortSignal?: AbortSignal): Promise {
const eventIndex = EventIndexPeg.get();
if (eventIndex === null) {
diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts
index 20db6594b01..2a56de87513 100644
--- a/src/SecurityManager.ts
+++ b/src/SecurityManager.ts
@@ -102,14 +102,14 @@ async function getSecretStorageKey({
}): Promise<[string, Uint8Array]> {
const cli = MatrixClientPeg.get();
let keyId = await cli.getDefaultSecretStorageKeyId();
- let keyInfo: ISecretStorageKeyInfo;
+ let keyInfo!: ISecretStorageKeyInfo;
if (keyId) {
// use the default SSSS key if set
keyInfo = keyInfos[keyId];
if (!keyInfo) {
// if the default key is not available, pretend the default key
// isn't set
- keyId = undefined;
+ keyId = null;
}
}
if (!keyId) {
@@ -156,7 +156,7 @@ async function getSecretStorageKey({
return MatrixClientPeg.get().checkSecretStorageKey(key, keyInfo);
},
},
- /* className= */ null,
+ /* className= */ undefined,
/* isPriorityModal= */ false,
/* isStaticModal= */ false,
/* options= */ {
@@ -182,7 +182,7 @@ async function getSecretStorageKey({
export async function getDehydrationKey(
keyInfo: ISecretStorageKeyInfo,
- checkFunc: (Uint8Array) => void,
+ checkFunc: (data: Uint8Array) => void,
): Promise {
const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.();
if (keyFromCustomisations) {
@@ -196,7 +196,7 @@ export async function getDehydrationKey(
/* props= */
{
keyInfo,
- checkPrivateKey: async (input): Promise => {
+ checkPrivateKey: async (input: KeyParams): Promise => {
const key = await inputToKey(input);
try {
checkFunc(key);
@@ -206,7 +206,7 @@ export async function getDehydrationKey(
}
},
},
- /* className= */ null,
+ /* className= */ undefined,
/* isPriorityModal= */ false,
/* isStaticModal= */ false,
/* options= */ {
@@ -243,7 +243,7 @@ async function onSecretRequested(
requestId: string,
name: string,
deviceTrust: DeviceTrustLevel,
-): Promise {
+): Promise {
logger.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust);
const client = MatrixClientPeg.get();
if (userId !== client.getUserId()) {
@@ -259,19 +259,19 @@ async function onSecretRequested(
name === "m.cross_signing.user_signing"
) {
const callbacks = client.getCrossSigningCacheCallbacks();
- if (!callbacks.getCrossSigningKeyCache) return;
+ if (!callbacks?.getCrossSigningKeyCache) return;
const keyId = name.replace("m.cross_signing.", "");
const key = await callbacks.getCrossSigningKeyCache(keyId);
if (!key) {
logger.log(`${keyId} requested by ${deviceId}, but not found in cache`);
}
- return key && encodeBase64(key);
+ return key ? encodeBase64(key) : undefined;
} else if (name === "m.megolm_backup.v1") {
- const key = await client.crypto.getSessionBackupPrivateKey();
+ const key = await client.crypto?.getSessionBackupPrivateKey();
if (!key) {
logger.log(`session backup key requested by ${deviceId}, but not found in cache`);
}
- return key && encodeBase64(key);
+ return key ? encodeBase64(key) : undefined;
}
logger.warn("onSecretRequested didn't recognise the secret named ", name);
}
@@ -284,15 +284,15 @@ export const crossSigningCallbacks: ICryptoCallbacks = {
};
export async function promptForBackupPassphrase(): Promise {
- let key: Uint8Array;
+ let key!: Uint8Array;
const { finished } = Modal.createDialog(
RestoreKeyBackupDialog,
{
showSummary: false,
- keyCallback: (k) => (key = k),
+ keyCallback: (k: Uint8Array) => (key = k),
},
- null,
+ undefined,
/* priority = */ false,
/* static = */ true,
);
@@ -338,7 +338,7 @@ export async function accessSecretStorage(func = async (): Promise => {},
{
forceReset,
},
- null,
+ undefined,
/* priority = */ false,
/* static = */ true,
/* options = */ {
diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx
index 3434090d8ea..66f673445dd 100644
--- a/src/SlashCommands.tsx
+++ b/src/SlashCommands.tsx
@@ -81,7 +81,8 @@ const singleMxcUpload = async (): Promise => {
const fileSelector = document.createElement("input");
fileSelector.setAttribute("type", "file");
fileSelector.onchange = (ev: HTMLInputEvent) => {
- const file = ev.target.files[0];
+ const file = ev.target.files?.[0];
+ if (!file) return;
Modal.createDialog(UploadConfirmDialog, {
file,
@@ -111,7 +112,7 @@ export const CommandCategories = {
export type RunResult = XOR<{ error: Error | ITranslatableError }, { promise: Promise }>;
-type RunFn = (this: Command, roomId: string, args: string) => RunResult;
+type RunFn = (this: Command, roomId: string, args?: string) => RunResult;
interface ICommandOpts {
command: string;
@@ -159,7 +160,7 @@ export class Command {
return this.getCommand() + " " + this.args;
}
- public run(roomId: string, threadId: string, args: string): RunResult {
+ public run(roomId: string, threadId: string | null, args?: string): RunResult {
// if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me`
if (!this.runFn) {
return reject(newTranslatableError("Command error: Unable to handle slash command."));
@@ -304,7 +305,7 @@ export const Commands = [
if (args) {
const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId);
- if (!room.currentState.mayClientSendStateEvent("m.room.tombstone", cli)) {
+ if (!room?.currentState.mayClientSendStateEvent("m.room.tombstone", cli)) {
return reject(
newTranslatableError("You do not have the required permissions to use this command."),
);
@@ -313,7 +314,7 @@ export const Commands = [
const { finished } = Modal.createDialog(
RoomUpgradeWarningDialog,
{ roomId: roomId, targetVersion: args },
- /*className=*/ null,
+ /*className=*/ undefined,
/*isPriority=*/ false,
/*isStatic=*/ true,
);
@@ -395,12 +396,12 @@ export const Commands = [
runFn: function (roomId, args) {
if (args) {
const cli = MatrixClientPeg.get();
- const ev = cli.getRoom(roomId).currentState.getStateEvents("m.room.member", cli.getUserId());
+ const ev = cli.getRoom(roomId)?.currentState.getStateEvents("m.room.member", cli.getUserId()!);
const content = {
...(ev ? ev.getContent() : { membership: "join" }),
displayname: args,
};
- return success(cli.sendStateEvent(roomId, "m.room.member", content, cli.getUserId()));
+ return success(cli.sendStateEvent(roomId, "m.room.member", content, cli.getUserId()!));
}
return reject(this.getUsage());
},
@@ -413,7 +414,7 @@ export const Commands = [
description: _td("Changes the avatar of the current room"),
isEnabled: () => !isCurrentLocalRoom(),
runFn: function (roomId, args) {
- let promise = Promise.resolve(args);
+ let promise = Promise.resolve(args ?? null);
if (!args) {
promise = singleMxcUpload();
}
@@ -436,9 +437,9 @@ export const Commands = [
runFn: function (roomId, args) {
const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId);
- const userId = cli.getUserId();
+ const userId = cli.getUserId()!;
- let promise = Promise.resolve(args);
+ let promise = Promise.resolve(args ?? null);
if (!args) {
promise = singleMxcUpload();
}
@@ -446,7 +447,7 @@ export const Commands = [
return success(
promise.then((url) => {
if (!url) return;
- const ev = room.currentState.getStateEvents("m.room.member", userId);
+ const ev = room?.currentState.getStateEvents("m.room.member", userId);
const content = {
...(ev ? ev.getContent() : { membership: "join" }),
avatar_url: url,
@@ -463,7 +464,7 @@ export const Commands = [
args: "[]",
description: _td("Changes your avatar in all rooms"),
runFn: function (roomId, args) {
- let promise = Promise.resolve(args);
+ let promise = Promise.resolve(args ?? null);
if (!args) {
promise = singleMxcUpload();
}
@@ -496,7 +497,7 @@ export const Commands = [
);
}
- const content: MRoomTopicEventContent = room.currentState.getStateEvents("m.room.topic", "")?.getContent();
+ const content = room.currentState.getStateEvents("m.room.topic", "")?.getContent();
const topic = !!content
? ContentHelpers.parseTopicContent(content)
: { text: _t("This room has no topic.") };
@@ -697,11 +698,8 @@ export const Commands = [
}
if (viaServers) {
- // For the join
- dispatch["opts"] = {
- // These are passed down to the js-sdk's /join call
- viaServers: viaServers,
- };
+ // For the join, these are passed down to the js-sdk's /join call
+ dispatch["opts"] = { viaServers };
// For if the join fails (rejoin button)
dispatch["via_servers"] = viaServers;
@@ -877,7 +875,8 @@ export const Commands = [
const cli = MatrixClientPeg.get();
const room = cli.getRoom(SdkContextClass.instance.roomViewStore.getRoomId());
return (
- room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()) && !isLocalRoom(room)
+ !!room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()!) &&
+ !isLocalRoom(room)
);
},
runFn: function (roomId, args) {
@@ -919,7 +918,8 @@ export const Commands = [
const cli = MatrixClientPeg.get();
const room = cli.getRoom(SdkContextClass.instance.roomViewStore.getRoomId());
return (
- room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()) && !isLocalRoom(room)
+ !!room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()!) &&
+ !isLocalRoom(room)
);
},
runFn: function (roomId, args) {
@@ -935,7 +935,7 @@ export const Commands = [
}
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
- if (!powerLevelEvent.getContent().users[args]) {
+ if (!powerLevelEvent?.getContent().users[args]) {
return reject(newTranslatableError("Could not find user in room"));
}
return success(cli.setPowerLevel(roomId, args, undefined, powerLevelEvent));
@@ -1042,7 +1042,7 @@ export const Commands = [
throw newTranslatableError("Session already verified!");
} else {
throw newTranslatableError(
- "WARNING: Session already verified, but keys do NOT MATCH!",
+ "WARNING: session already verified, but keys do NOT MATCH!",
);
}
}
@@ -1116,9 +1116,9 @@ export const Commands = [
MatrixClientPeg.get().forceDiscardSession(roomId);
return success(
- room.getEncryptionTargetMembers().then((members) => {
+ room?.getEncryptionTargetMembers().then((members) => {
// noinspection JSIgnoredPromiseFromCall
- MatrixClientPeg.get().crypto.ensureOlmSessionsForUsers(
+ MatrixClientPeg.get().crypto?.ensureOlmSessionsForUsers(
members.map((m) => m.userId),
true,
);
@@ -1170,7 +1170,7 @@ export const Commands = [
return reject(this.getUsage());
}
- const member = MatrixClientPeg.get().getRoom(roomId).getMember(userId);
+ 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.
@@ -1200,7 +1200,7 @@ export const Commands = [
description: _td("Switches to this room's virtual room, if it has one"),
category: CommandCategories.advanced,
isEnabled(): boolean {
- return LegacyCallHandler.instance.getSupportsVirtualRooms() && !isCurrentLocalRoom();
+ return !!LegacyCallHandler.instance.getSupportsVirtualRooms() && !isCurrentLocalRoom();
},
runFn: (roomId) => {
return success(
@@ -1390,7 +1390,7 @@ export function parseCommandString(input: string): { cmd?: string; args?: string
const bits = input.match(/^(\S+?)(?:[ \n]+((.|\n)*))?$/);
let cmd: string;
- let args: string;
+ let args: string | undefined;
if (bits) {
cmd = bits[1].substring(1).toLowerCase();
args = bits[2];
@@ -1415,7 +1415,7 @@ interface ICmd {
export function getCommand(input: string): ICmd {
const { cmd, args } = parseCommandString(input);
- if (CommandMap.has(cmd) && CommandMap.get(cmd).isEnabled()) {
+ if (cmd && CommandMap.has(cmd) && CommandMap.get(cmd)!.isEnabled()) {
return {
cmd: CommandMap.get(cmd),
args,
diff --git a/src/Terms.ts b/src/Terms.ts
index bb18a18cf7e..f66f543887c 100644
--- a/src/Terms.ts
+++ b/src/Terms.ts
@@ -52,11 +52,13 @@ export type Policies = {
[policy: string]: Policy;
};
+export type ServicePolicyPair = {
+ policies: Policies;
+ service: Service;
+};
+
export type TermsInteractionCallback = (
- policiesAndServicePairs: {
- service: Service;
- policies: Policies;
- }[],
+ policiesAndServicePairs: ServicePolicyPair[],
agreedUrls: string[],
extraClassNames?: string,
) => Promise;
@@ -117,9 +119,9 @@ export async function startTermsFlow(
// but then they'd assume they can un-check the boxes to un-agree to a policy,
// but that is not a thing the API supports, so probably best to just show
// things they've not agreed to yet.
- const unagreedPoliciesAndServicePairs = [];
+ const unagreedPoliciesAndServicePairs: ServicePolicyPair[] = [];
for (const { service, policies } of policiesAndServicePairs) {
- const unagreedPolicies = {};
+ const unagreedPolicies: Policies = {};
for (const [policyName, policy] of Object.entries(policies)) {
let policyAgreed = false;
for (const lang of Object.keys(policy)) {
diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx
index 7f874f8a898..790ce2571b1 100644
--- a/src/TextForEvent.tsx
+++ b/src/TextForEvent.tsx
@@ -33,7 +33,7 @@ import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases";
import defaultDispatcher from "./dispatcher/dispatcher";
import { MatrixClientPeg } from "./MatrixClientPeg";
import { ROOM_SECURITY_TAB } from "./components/views/dialogs/RoomSettingsDialog";
-import AccessibleButton from "./components/views/elements/AccessibleButton";
+import AccessibleButton, { ButtonEvent } from "./components/views/elements/AccessibleButton";
import RightPanelStore from "./stores/right-panel/RightPanelStore";
import { highlightEvent, isLocationEvent } from "./utils/EventUtils";
import { ElementCall } from "./models/Call";
@@ -43,12 +43,12 @@ import { getSenderName } from "./utils/event/getSenderName";
function getRoomMemberDisplayname(event: MatrixEvent, userId = event.getSender()): string {
const client = MatrixClientPeg.get();
const roomId = event.getRoomId();
- const member = client.getRoom(roomId)?.getMember(userId);
+ const member = client.getRoom(roomId)?.getMember(userId!);
return member?.name || member?.rawDisplayName || userId || _t("Someone");
}
function textForCallEvent(event: MatrixEvent): () => string {
- const roomName = MatrixClientPeg.get().getRoom(event.getRoomId()!).name;
+ const roomName = MatrixClientPeg.get().getRoom(event.getRoomId()!)?.name;
const isSupported = MatrixClientPeg.get().supportsVoip();
return isSupported
@@ -60,7 +60,7 @@ function textForCallEvent(event: MatrixEvent): () => string {
// any text to display at all. For this reason they return deferred values
// to avoid the expense of looking up translations when they're not needed.
-function textForCallInviteEvent(event: MatrixEvent): () => string | null {
+function textForCallInviteEvent(event: MatrixEvent): (() => string) | null {
const senderName = getSenderName(event);
// FIXME: Find a better way to determine this from the event?
const isVoice = !event.getContent().offer?.sdp?.includes("m=video");
@@ -78,9 +78,11 @@ function textForCallInviteEvent(event: MatrixEvent): () => string | null {
} else if (!isVoice && !isSupported) {
return () => _t("%(senderName)s placed a video call. (not supported by this browser)", { senderName });
}
+
+ return null;
}
-function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean): () => string | null {
+function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean): (() => string) | null {
// XXX: SYJS-16 "sender is sometimes null for join messages"
const senderName = ev.sender?.name || getRoomMemberDisplayname(ev);
const targetName = ev.target?.name || getRoomMemberDisplayname(ev, ev.getStateKey());
@@ -118,20 +120,20 @@ function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents
// We're taking the display namke directly from the event content here so we need
// to strip direction override chars which the js-sdk would normally do when
// calculating the display name
- oldDisplayName: removeDirectionOverrideChars(prevContent.displayname),
- displayName: removeDirectionOverrideChars(content.displayname),
+ oldDisplayName: removeDirectionOverrideChars(prevContent.displayname!),
+ displayName: removeDirectionOverrideChars(content.displayname!),
});
} else if (!prevContent.displayname && content.displayname) {
return () =>
_t("%(senderName)s set their display name to %(displayName)s", {
senderName: ev.getSender(),
- displayName: removeDirectionOverrideChars(content.displayname),
+ displayName: removeDirectionOverrideChars(content.displayname!),
});
} else if (prevContent.displayname && !content.displayname) {
return () =>
_t("%(senderName)s removed their display name (%(oldDisplayName)s)", {
senderName,
- oldDisplayName: removeDirectionOverrideChars(prevContent.displayname),
+ oldDisplayName: removeDirectionOverrideChars(prevContent.displayname!),
});
} else if (prevContent.avatar_url && !content.avatar_url) {
return () => _t("%(senderName)s removed their profile picture", { senderName });
@@ -187,9 +189,11 @@ function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents
return null;
}
}
+
+ return null;
}
-function textForTopicEvent(ev: MatrixEvent): () => string | null {
+function textForTopicEvent(ev: MatrixEvent): (() => string) | null {
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
return () =>
_t('%(senderDisplayName)s changed the topic to "%(topic)s".', {
@@ -198,12 +202,12 @@ function textForTopicEvent(ev: MatrixEvent): () => string | null {
});
}
-function textForRoomAvatarEvent(ev: MatrixEvent): () => string | null {
+function textForRoomAvatarEvent(ev: MatrixEvent): (() => string) | null {
const senderDisplayName = ev?.sender?.name || ev.getSender();
return () => _t("%(senderDisplayName)s changed the room avatar.", { senderDisplayName });
}
-function textForRoomNameEvent(ev: MatrixEvent): () => string | null {
+function textForRoomNameEvent(ev: MatrixEvent): (() => string) | null {
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
if (!ev.getContent().name || ev.getContent().name.trim().length === 0) {
@@ -224,7 +228,7 @@ function textForRoomNameEvent(ev: MatrixEvent): () => string | null {
});
}
-function textForTombstoneEvent(ev: MatrixEvent): () => string | null {
+function textForTombstoneEvent(ev: MatrixEvent): (() => string) | null {
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
return () => _t("%(senderDisplayName)s upgraded this room.", { senderDisplayName });
}
@@ -281,7 +285,7 @@ function textForJoinRulesEvent(ev: MatrixEvent, allowJSX: boolean): () => Render
}
}
-function textForGuestAccessEvent(ev: MatrixEvent): () => string | null {
+function textForGuestAccessEvent(ev: MatrixEvent): (() => string) | null {
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
switch (ev.getContent().guest_access) {
case GuestAccess.CanJoin:
@@ -298,7 +302,7 @@ function textForGuestAccessEvent(ev: MatrixEvent): () => string | null {
}
}
-function textForServerACLEvent(ev: MatrixEvent): () => string | null {
+function textForServerACLEvent(ev: MatrixEvent): (() => string) | null {
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
const prevContent = ev.getPrevContent();
const current = ev.getContent();
@@ -308,7 +312,7 @@ function textForServerACLEvent(ev: MatrixEvent): () => string | null {
allow_ip_literals: prevContent.allow_ip_literals !== false,
};
- let getText = null;
+ let getText: () => string;
if (prev.deny.length === 0 && prev.allow.length === 0) {
getText = () => _t("%(senderDisplayName)s set the server ACLs for this room.", { senderDisplayName });
} else {
@@ -328,7 +332,7 @@ function textForServerACLEvent(ev: MatrixEvent): () => string | null {
return getText;
}
-function textForMessageEvent(ev: MatrixEvent): () => string | null {
+function textForMessageEvent(ev: MatrixEvent): (() => string) | null {
if (isLocationEvent(ev)) {
return textForLocationEvent(ev);
}
@@ -354,14 +358,14 @@ function textForMessageEvent(ev: MatrixEvent): () => string | null {
};
}
-function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null {
+function textForCanonicalAliasEvent(ev: MatrixEvent): (() => string) | null {
const senderName = getSenderName(ev);
const oldAlias = ev.getPrevContent().alias;
const oldAltAliases = ev.getPrevContent().alt_aliases || [];
const newAlias = ev.getContent().alias;
const newAltAliases = ev.getContent().alt_aliases || [];
- const removedAltAliases = oldAltAliases.filter((alias) => !newAltAliases.includes(alias));
- const addedAltAliases = newAltAliases.filter((alias) => !oldAltAliases.includes(alias));
+ const removedAltAliases = oldAltAliases.filter((alias: string) => !newAltAliases.includes(alias));
+ const addedAltAliases = newAltAliases.filter((alias: string) => !oldAltAliases.includes(alias));
if (!removedAltAliases.length && !addedAltAliases.length) {
if (newAlias) {
@@ -414,7 +418,7 @@ function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null {
});
}
-function textForThreePidInviteEvent(event: MatrixEvent): () => string | null {
+function textForThreePidInviteEvent(event: MatrixEvent): (() => string) | null {
const senderName = getSenderName(event);
if (!isValid3pidInvite(event)) {
@@ -432,7 +436,7 @@ function textForThreePidInviteEvent(event: MatrixEvent): () => string | null {
});
}
-function textForHistoryVisibilityEvent(event: MatrixEvent): () => string | null {
+function textForHistoryVisibilityEvent(event: MatrixEvent): (() => string) | null {
const senderName = getSenderName(event);
switch (event.getContent().history_visibility) {
case HistoryVisibility.Invited:
@@ -463,7 +467,7 @@ function textForHistoryVisibilityEvent(event: MatrixEvent): () => string | null
}
// Currently will only display a change if a user's power level is changed
-function textForPowerEvent(event: MatrixEvent): () => string | null {
+function textForPowerEvent(event: MatrixEvent): (() => string) | null {
const senderName = getSenderName(event);
if (!event.getPrevContent()?.users || !event.getContent()?.users) {
return null;
@@ -528,20 +532,20 @@ const onPinnedMessagesClick = (): void => {
RightPanelStore.instance.setCard({ phase: RightPanelPhases.PinnedMessages }, false);
};
-function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Renderable {
+function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): (() => Renderable) | null {
if (!SettingsStore.getValue("feature_pinning")) return null;
const senderName = getSenderName(event);
- const roomId = event.getRoomId();
+ const roomId = event.getRoomId()!;
- const pinned = event.getContent().pinned ?? [];
- const previouslyPinned = event.getPrevContent().pinned ?? [];
+ const pinned = event.getContent<{ pinned: string[] }>().pinned ?? [];
+ const previouslyPinned: string[] = event.getPrevContent().pinned ?? [];
const newlyPinned = pinned.filter((item) => previouslyPinned.indexOf(item) < 0);
const newlyUnpinned = previouslyPinned.filter((item) => pinned.indexOf(item) < 0);
if (newlyPinned.length === 1 && newlyUnpinned.length === 0) {
// A single message was pinned, include a link to that message.
if (allowJSX) {
- const messageId = newlyPinned.pop();
+ const messageId = newlyPinned.pop()!;
return () => (
@@ -550,7 +554,10 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
{ senderName },
{
a: (sub) => (
- highlightEvent(roomId, messageId)}>
+ highlightEvent(roomId, messageId)}
+ >
{sub}
),
@@ -571,7 +578,7 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
if (newlyUnpinned.length === 1 && newlyPinned.length === 0) {
// A single message was unpinned, include a link to that message.
if (allowJSX) {
- const messageId = newlyUnpinned.pop();
+ const messageId = newlyUnpinned.pop()!;
return () => (
@@ -580,7 +587,10 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
{ senderName },
{
a: (sub) => (
- highlightEvent(roomId, messageId)}>
+ highlightEvent(roomId, messageId)}
+ >
{sub}
),
@@ -619,7 +629,7 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
return () => _t("%(senderName)s changed the pinned messages for the room.", { senderName });
}
-function textForWidgetEvent(event: MatrixEvent): () => string | null {
+function textForWidgetEvent(event: MatrixEvent): (() => string) | null {
const senderName = getSenderName(event);
const { name: prevName, type: prevType, url: prevUrl } = event.getPrevContent();
const { name, type, url } = event.getContent() || {};
@@ -655,12 +665,12 @@ function textForWidgetEvent(event: MatrixEvent): () => string | null {
}
}
-function textForWidgetLayoutEvent(event: MatrixEvent): () => string | null {
+function textForWidgetLayoutEvent(event: MatrixEvent): (() => string) | null {
const senderName = getSenderName(event);
return () => _t("%(senderName)s has updated the room layout", { senderName });
}
-function textForMjolnirEvent(event: MatrixEvent): () => string | null {
+function textForMjolnirEvent(event: MatrixEvent): (() => string) | null {
const senderName = getSenderName(event);
const { entity: prevEntity } = event.getPrevContent();
const { entity, recommendation, reason } = event.getContent();
@@ -789,7 +799,7 @@ function textForMjolnirEvent(event: MatrixEvent): () => string | null {
);
}
-export function textForLocationEvent(event: MatrixEvent): () => string | null {
+export function textForLocationEvent(event: MatrixEvent): () => string {
return () =>
_t("%(senderName)s has shared their location", {
senderName: getSenderName(event),
@@ -811,7 +821,7 @@ function textForRedactedPollAndMessageEvent(ev: MatrixEvent): string {
return message;
}
-function textForPollStartEvent(event: MatrixEvent): () => string | null {
+function textForPollStartEvent(event: MatrixEvent): (() => string) | null {
return () => {
let message = "";
@@ -830,7 +840,7 @@ function textForPollStartEvent(event: MatrixEvent): () => string | null {
};
}
-function textForPollEndEvent(event: MatrixEvent): () => string | null {
+function textForPollEndEvent(event: MatrixEvent): (() => string) | null {
return () =>
_t("%(senderName)s has ended a poll", {
senderName: getSenderName(event),
@@ -840,7 +850,7 @@ function textForPollEndEvent(event: MatrixEvent): () => string | null {
type Renderable = string | React.ReactNode | null;
interface IHandlers {
- [type: string]: (ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean) => () => Renderable;
+ [type: string]: (ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean) => (() => Renderable) | null;
}
const handlers: IHandlers = {
diff --git a/src/Unread.ts b/src/Unread.ts
index 6e7218cad12..1fd7ac449fa 100644
--- a/src/Unread.ts
+++ b/src/Unread.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
+Copyright 2015 - 2023 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.
diff --git a/src/UserActivity.ts b/src/UserActivity.ts
index 9217aca3c0e..ae6417d4f4d 100644
--- a/src/UserActivity.ts
+++ b/src/UserActivity.ts
@@ -168,7 +168,7 @@ export default class UserActivity {
return this.activeRecentlyTimeout.isRunning();
}
- private onPageVisibilityChanged = (e): void => {
+ private onPageVisibilityChanged = (e: Event): void => {
if (this.document.visibilityState === "hidden") {
this.activeNowTimeout.abort();
this.activeRecentlyTimeout.abort();
@@ -182,11 +182,12 @@ export default class UserActivity {
this.activeRecentlyTimeout.abort();
};
- private onUserActivity = (event: MouseEvent): void => {
+ // XXX: exported for tests
+ public onUserActivity = (event: Event): void => {
// ignore anything if the window isn't focused
if (!this.document.hasFocus()) return;
- if (event.screenX && event.type === "mousemove") {
+ if (event.type === "mousemove" && this.isMouseEvent(event)) {
if (event.screenX === this.lastScreenX && event.screenY === this.lastScreenY) {
// mouse hasn't actually moved
return;
@@ -223,4 +224,8 @@ export default class UserActivity {
}
attachedTimers.forEach((t) => t.abort());
}
+
+ private isMouseEvent(event: Event): event is MouseEvent {
+ return event.type.startsWith("mouse");
+ }
}
diff --git a/src/VoipUserMapper.ts b/src/VoipUserMapper.ts
index 7a163350db8..0214ab9cbed 100644
--- a/src/VoipUserMapper.ts
+++ b/src/VoipUserMapper.ts
@@ -38,7 +38,7 @@ export default class VoipUserMapper {
return window.mxVoipUserMapper;
}
- private async userToVirtualUser(userId: string): Promise {
+ private async userToVirtualUser(userId: string): Promise {
const results = await LegacyCallHandler.instance.sipVirtualLookup(userId);
if (results.length === 0 || !results[0].fields.lookup_success) return null;
return results[0].userid;
@@ -59,11 +59,11 @@ export default class VoipUserMapper {
if (!virtualUser) return null;
const virtualRoomId = await ensureVirtualRoomExists(MatrixClientPeg.get(), virtualUser, roomId);
- MatrixClientPeg.get().setRoomAccountData(virtualRoomId, VIRTUAL_ROOM_EVENT_TYPE, {
+ MatrixClientPeg.get().setRoomAccountData(virtualRoomId!, VIRTUAL_ROOM_EVENT_TYPE, {
native_room: roomId,
});
- this.virtualToNativeRoomIdCache.set(virtualRoomId, roomId);
+ this.virtualToNativeRoomIdCache.set(virtualRoomId!, roomId);
return virtualRoomId;
}
@@ -72,9 +72,9 @@ export default class VoipUserMapper {
* Gets the ID of the virtual room for a room, or null if the room has no
* virtual room
*/
- public async getVirtualRoomForRoom(roomId: string): Promise {
+ public async getVirtualRoomForRoom(roomId: string): Promise {
const virtualUser = await this.getVirtualUserForRoom(roomId);
- if (!virtualUser) return null;
+ if (!virtualUser) return undefined;
return findDMForUser(MatrixClientPeg.get(), virtualUser);
}
@@ -121,8 +121,12 @@ export default class VoipUserMapper {
if (!LegacyCallHandler.instance.getSupportsVirtualRooms()) return;
const inviterId = invitedRoom.getDMInviter();
+ if (!inviterId) {
+ logger.error("Could not find DM inviter for room id: " + invitedRoom.roomId);
+ }
+
logger.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
- const result = await LegacyCallHandler.instance.sipNativeLookup(inviterId);
+ const result = await LegacyCallHandler.instance.sipNativeLookup(inviterId!);
if (result.length === 0) {
return;
}
@@ -141,11 +145,11 @@ export default class VoipUserMapper {
// (possibly we should only join if we've also joined the native room, then we'd also have
// to make sure we joined virtual rooms on joining a native one)
MatrixClientPeg.get().joinRoom(invitedRoom.roomId);
- }
- // 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.virtualToNativeRoomIdCache.set(invitedRoom.roomId, nativeRoom.roomId);
+ // 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.virtualToNativeRoomIdCache.set(invitedRoom.roomId, nativeRoom.roomId);
+ }
}
}
}
diff --git a/src/WhoIsTyping.ts b/src/WhoIsTyping.ts
index 01e7b2e4f7d..d4a43636cea 100644
--- a/src/WhoIsTyping.ts
+++ b/src/WhoIsTyping.ts
@@ -21,11 +21,11 @@ import { MatrixClientPeg } from "./MatrixClientPeg";
import { _t } from "./languageHandler";
export function usersTypingApartFromMeAndIgnored(room: Room): RoomMember[] {
- return usersTyping(room, [MatrixClientPeg.get().getUserId()].concat(MatrixClientPeg.get().getIgnoredUsers()));
+ return usersTyping(room, [MatrixClientPeg.get().getUserId()!].concat(MatrixClientPeg.get().getIgnoredUsers()));
}
export function usersTypingApartFromMe(room: Room): RoomMember[] {
- return usersTyping(room, [MatrixClientPeg.get().getUserId()]);
+ return usersTyping(room, [MatrixClientPeg.get().getUserId()!]);
}
/**
@@ -36,7 +36,7 @@ export function usersTypingApartFromMe(room: Room): RoomMember[] {
* @returns {RoomMember[]} list of user objects who are typing.
*/
export function usersTyping(room: Room, exclude: string[] = []): RoomMember[] {
- const whoIsTyping = [];
+ const whoIsTyping: RoomMember[] = [];
const memberKeys = Object.keys(room.currentState.members);
for (const userId of memberKeys) {
diff --git a/src/accessibility/KeyboardShortcutUtils.ts b/src/accessibility/KeyboardShortcutUtils.ts
index bb42f7c1ce9..8ba866be3fa 100644
--- a/src/accessibility/KeyboardShortcutUtils.ts
+++ b/src/accessibility/KeyboardShortcutUtils.ts
@@ -27,6 +27,7 @@ import {
KEYBOARD_SHORTCUTS,
MAC_ONLY_SHORTCUTS,
} from "./KeyboardShortcuts";
+import { IBaseSetting } from "../settings/Settings";
/**
* This function gets the keyboard shortcuts that should be presented in the UI
@@ -103,7 +104,7 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
return true;
})
.reduce((o, key) => {
- o[key] = KEYBOARD_SHORTCUTS[key];
+ o[key as KeyBindingAction] = KEYBOARD_SHORTCUTS[key as KeyBindingAction];
return o;
}, {} as IKeyboardShortcuts);
};
@@ -112,7 +113,10 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
* Gets keyboard shortcuts that should be presented to the user in the UI.
*/
export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
- const entries = [...Object.entries(getUIOnlyShortcuts()), ...Object.entries(getKeyboardShortcuts())];
+ const entries = [...Object.entries(getUIOnlyShortcuts()), ...Object.entries(getKeyboardShortcuts())] as [
+ KeyBindingAction,
+ IBaseSetting,
+ ][];
return entries.reduce((acc, [key, value]) => {
acc[key] = value;
@@ -120,11 +124,11 @@ export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
}, {} as IKeyboardShortcuts);
};
-export const getKeyboardShortcutValue = (name: string): KeyCombo | undefined => {
+export const getKeyboardShortcutValue = (name: KeyBindingAction): KeyCombo | undefined => {
return getKeyboardShortcutsForUI()[name]?.default;
};
-export const getKeyboardShortcutDisplayName = (name: string): string | undefined => {
+export const getKeyboardShortcutDisplayName = (name: KeyBindingAction): string | undefined => {
const keyboardShortcutDisplayName = getKeyboardShortcutsForUI()[name]?.displayName;
- return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName);
+ return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName as string);
};
diff --git a/src/accessibility/KeyboardShortcuts.ts b/src/accessibility/KeyboardShortcuts.ts
index 0e536ac1497..3011a5b5bd7 100644
--- a/src/accessibility/KeyboardShortcuts.ts
+++ b/src/accessibility/KeyboardShortcuts.ts
@@ -156,10 +156,8 @@ export enum KeyBindingAction {
type KeyboardShortcutSetting = IBaseSetting;
-export type IKeyboardShortcuts = {
- // TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
- [k in KeyBindingAction]?: KeyboardShortcutSetting;
-};
+// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
+export type IKeyboardShortcuts = Partial>;
export interface ICategory {
categoryLabel?: string;
diff --git a/src/accessibility/RovingTabIndex.tsx b/src/accessibility/RovingTabIndex.tsx
index 605ffb1f5b5..b449b10710f 100644
--- a/src/accessibility/RovingTabIndex.tsx
+++ b/src/accessibility/RovingTabIndex.tsx
@@ -25,6 +25,7 @@ import React, {
Reducer,
Dispatch,
RefObject,
+ ReactNode,
} from "react";
import { getKeyBindingsManager } from "../KeyBindingsManager";
@@ -158,8 +159,8 @@ interface IProps {
handleHomeEnd?: boolean;
handleUpDown?: boolean;
handleLeftRight?: boolean;
- children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent) });
- onKeyDown?(ev: React.KeyboardEvent, state: IState);
+ children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent): void }): ReactNode;
+ onKeyDown?(ev: React.KeyboardEvent, state: IState): void;
}
export const findSiblingElement = (
diff --git a/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx b/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx
index ee3a0e4d368..e8e69865d78 100644
--- a/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx
+++ b/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx
@@ -25,7 +25,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
interface IProps extends React.ComponentProps {
label?: string;
- onChange(); // we handle keyup/down ourselves so lose the ChangeEvent
+ onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent
onClose(): void; // gets called after onChange on KeyBindingAction.ActivateSelectedButton
}
diff --git a/src/accessibility/context_menu/StyledMenuItemRadio.tsx b/src/accessibility/context_menu/StyledMenuItemRadio.tsx
index 2fe87384340..7a394a3d1f9 100644
--- a/src/accessibility/context_menu/StyledMenuItemRadio.tsx
+++ b/src/accessibility/context_menu/StyledMenuItemRadio.tsx
@@ -25,7 +25,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
interface IProps extends React.ComponentProps {
label?: string;
- onChange(); // we handle keyup/down ourselves so lose the ChangeEvent
+ onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent
onClose(): void; // gets called after onChange on KeyBindingAction.Enter
}
diff --git a/src/accessibility/roving/RovingAccessibleButton.tsx b/src/accessibility/roving/RovingAccessibleButton.tsx
index 3968ef6d6bd..71818c6cda1 100644
--- a/src/accessibility/roving/RovingAccessibleButton.tsx
+++ b/src/accessibility/roving/RovingAccessibleButton.tsx
@@ -30,7 +30,7 @@ export const RovingAccessibleButton: React.FC = ({ inputRef, onFocus, ..
return (
{
+ onFocus={(event: React.FocusEvent) => {
onFocusInternal();
onFocus?.(event);
}}
diff --git a/src/accessibility/roving/RovingAccessibleTooltipButton.tsx b/src/accessibility/roving/RovingAccessibleTooltipButton.tsx
index f30225f0f72..f06cc934bbc 100644
--- a/src/accessibility/roving/RovingAccessibleTooltipButton.tsx
+++ b/src/accessibility/roving/RovingAccessibleTooltipButton.tsx
@@ -31,7 +31,7 @@ export const RovingAccessibleTooltipButton: React.FC = ({ inputRef, onFo
return (
{
+ onFocus={(event: React.FocusEvent) => {
onFocusInternal();
onFocus?.(event);
}}
diff --git a/src/accessibility/roving/RovingTabIndexWrapper.tsx b/src/accessibility/roving/RovingTabIndexWrapper.tsx
index b549f18119e..4208d47499f 100644
--- a/src/accessibility/roving/RovingTabIndexWrapper.tsx
+++ b/src/accessibility/roving/RovingTabIndexWrapper.tsx
@@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
+import React, { ReactElement } from "react";
import { useRovingTabIndex } from "../RovingTabIndex";
import { FocusHandler, Ref } from "./types";
interface IProps {
inputRef?: Ref;
- children(renderProps: { onFocus: FocusHandler; isActive: boolean; ref: Ref });
+ children(renderProps: { onFocus: FocusHandler; isActive: boolean; ref: Ref }): ReactElement;
}
// Wrapper to allow use of useRovingTabIndex outside of React Functional Components.
diff --git a/src/actions/RoomListActions.ts b/src/actions/RoomListActions.ts
index 637b57071e4..49351757ca7 100644
--- a/src/actions/RoomListActions.ts
+++ b/src/actions/RoomListActions.ts
@@ -54,7 +54,7 @@ export default class RoomListActions {
oldIndex: number | null,
newIndex: number | null,
): AsyncActionPayload {
- let metaData = null;
+ let metaData: Parameters[2] | null = null;
// Is the tag ordered manually?
const store = RoomListStore.instance;
@@ -81,7 +81,7 @@ export default class RoomListActions {
return asyncAction(
"RoomListActions.tagRoom",
() => {
- const promises = [];
+ const promises: Promise[] = [];
const roomId = room.roomId;
// Evil hack to get DMs behaving
@@ -120,7 +120,7 @@ export default class RoomListActions {
if (newTag && newTag !== DefaultTagID.DM && (hasChangedSubLists || metaData)) {
// metaData is the body of the PUT to set the tag, so it must
// at least be an empty object.
- metaData = metaData || {};
+ metaData = metaData || ({} as typeof metaData);
const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function (err) {
logger.error("Failed to add tag " + newTag + " to room: " + err);
diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx
index 63a132077fa..5393ae3fc6e 100644
--- a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx
+++ b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx
@@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
+import React, { ChangeEvent } from "react";
+import { Room } from "matrix-js-sdk/src/models/room";
import { _t } from "../../../../languageHandler";
import SdkConfig from "../../../../SdkConfig";
@@ -27,6 +28,7 @@ import Field from "../../../../components/views/elements/Field";
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
import DialogButtons from "../../../../components/views/elements/DialogButtons";
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
+import { IIndexStats } from "../../../../indexing/BaseEventIndexManager";
interface IProps extends IDialogProps {}
@@ -43,7 +45,7 @@ interface IState {
* Allows the user to introspect the event index state and disable it.
*/
export default class ManageEventIndexDialog extends React.Component {
- public constructor(props) {
+ public constructor(props: IProps) {
super(props);
this.state = {
@@ -56,9 +58,9 @@ export default class ManageEventIndexDialog extends React.Component => {
+ public updateCurrentRoom = async (room: Room): Promise => {
const eventIndex = EventIndexPeg.get();
- let stats;
+ let stats: IIndexStats;
try {
stats = await eventIndex.getStats();
@@ -136,12 +138,12 @@ export default class ManageEventIndexDialog extends React.Component {
- this.setState({ crawlerSleepTime: e.target.value });
+ private onCrawlerSleepTimeChange = (e: ChangeEvent): void => {
+ this.setState({ crawlerSleepTime: parseInt(e.target.value, 10) });
SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value);
};
- public render(): JSX.Element {
+ public render(): React.ReactNode {
const brand = SdkConfig.get().brand;
let crawlerState;
diff --git a/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx
index a75b41f602b..a9327f2a467 100644
--- a/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx
+++ b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx
@@ -239,7 +239,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent
{_t(
- "Warning: You should only set up key backup from a trusted computer.",
+ "Warning: you should only set up key backup from a trusted computer.",
{},
{ b: (sub) => {sub} },
)}
@@ -327,7 +327,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent
{_t(
- "Enter a security phrase only you know, as it's used to safeguard your data. " +
+ "Enter a Security Phrase only you know, as it's used to safeguard your data. " +
"To be secure, you shouldn't re-use your account password.",
)}