}>;
- queue: any;
- }
-
- interface Chainable {
- chainerId: string;
}
}
}
-const chainStart = Symbol("chainStart");
-
/**
* @description Returns a single Chainable that resolves when all of the Chainables pass.
* @param {Cypress.Chainable[]} commands - List of Cypress.Chainable to resolve.
* @returns {Cypress.Chainable} Cypress when all Chainables are resolved.
*/
cy.all = function all(commands): Cypress.Chainable {
- const chain = cy.wrap(null, { log: false });
- const stopCommand = Cypress._.find(cy.queue.get(), {
- attributes: { chainerId: chain.chainerId },
+ const resultArray = [];
+
+ // as each command completes, store the result in the corresponding location of resultArray.
+ for (let i = 0; i < commands.length; i++) {
+ commands[i].then((val) => {
+ resultArray[i] = val;
+ });
+ }
+
+ // add an entry to the log which, when clicked, will write the results to the console.
+ Cypress.log({
+ name: "all",
+ consoleProps: () => ({ Results: resultArray }),
});
- const startCommand = Cypress._.find(cy.queue.get(), {
- attributes: { chainerId: commands[0].chainerId },
- });
- const p = chain.then(() => {
- return cy.wrap(
- // @see https://lodash.com/docs/4.17.15#lodash
- Cypress._(commands)
- .map((cmd) => {
- return cmd[chainStart]
- ? cmd[chainStart].attributes
- : Cypress._.find(cy.queue.get(), {
- attributes: { chainerId: cmd.chainerId },
- }).attributes;
- })
- .concat(stopCommand.attributes)
- .slice(1)
- .map((cmd) => {
- return cmd.prev.get("subject");
- })
- .value(),
- );
- });
- p[chainStart] = startCommand;
- return p;
+
+ // return a chainable which wraps the resultArray. Although this doesn't have a direct dependency on the input
+ // commands, cypress won't process it until the commands that precede it on the command queue (which must include
+ // the input commands) have passed.
+ return cy.wrap(resultArray, { log: false });
};
// Needed to make this file a module
diff --git a/package.json b/package.json
index 09682dc292..17d2c761ff 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
"package.json",
".stylelintrc.js"
],
- "main": "./lib/index.ts",
+ "main": "./src/index.ts",
"matrix_src_main": "./src/index.ts",
"matrix_lib_main": "./lib/index.ts",
"matrix_lib_typings": "./lib/index.d.ts",
@@ -56,8 +56,8 @@
},
"dependencies": {
"@babel/runtime": "^7.12.5",
- "@matrix-org/analytics-events": "^0.3.0",
- "@matrix-org/matrix-wysiwyg": "^0.14.0",
+ "@matrix-org/analytics-events": "^0.4.0",
+ "@matrix-org/matrix-wysiwyg": "^0.20.0",
"@matrix-org/react-sdk-module-api": "^0.0.3",
"@sentry/browser": "^7.0.0",
"@sentry/tracing": "^7.0.0",
@@ -177,7 +177,7 @@
"babel-jest": "^29.0.0",
"blob-polyfill": "^7.0.0",
"chokidar": "^3.5.1",
- "cypress": "^11.0.0",
+ "cypress": "^12.0.0",
"cypress-axe": "^1.0.0",
"cypress-multi-reporters": "^1.6.1",
"cypress-real-events": "^1.7.1",
@@ -259,6 +259,5 @@
"outputDirectory": "coverage",
"outputName": "jest-sonar-report.xml",
"relativePaths": true
- },
- "typings": "./lib/index.d.ts"
+ }
}
diff --git a/res/css/_components.pcss b/res/css/_components.pcss
index 9cd446ecbc..fe50417c00 100644
--- a/res/css/_components.pcss
+++ b/res/css/_components.pcss
@@ -379,5 +379,6 @@
@import "./voice-broadcast/atoms/_PlaybackControlButton.pcss";
@import "./voice-broadcast/atoms/_VoiceBroadcastControl.pcss";
@import "./voice-broadcast/atoms/_VoiceBroadcastHeader.pcss";
+@import "./voice-broadcast/atoms/_VoiceBroadcastRecordingConnectionError.pcss";
@import "./voice-broadcast/atoms/_VoiceBroadcastRoomSubtitle.pcss";
@import "./voice-broadcast/molecules/_VoiceBroadcastBody.pcss";
diff --git a/res/css/_spacing.pcss b/res/css/_spacing.pcss
index 40c470c26b..63197f2321 100644
--- a/res/css/_spacing.pcss
+++ b/res/css/_spacing.pcss
@@ -16,6 +16,7 @@ limitations under the License.
/* 1rem :: 10px */
+$spacing-2: 2px;
$spacing-4: 4px;
$spacing-8: 8px;
$spacing-12: 12px;
diff --git a/res/css/views/context_menus/_IconizedContextMenu.pcss b/res/css/views/context_menus/_IconizedContextMenu.pcss
index 677d4b2bb0..e6600620ac 100644
--- a/res/css/views/context_menus/_IconizedContextMenu.pcss
+++ b/res/css/views/context_menus/_IconizedContextMenu.pcss
@@ -84,7 +84,7 @@ limitations under the License.
align-items: center;
&:hover,
- &:focus {
+ &:focus-visible {
background-color: $menu-selected-color;
}
diff --git a/res/css/views/dialogs/_UserSettingsDialog.pcss b/res/css/views/dialogs/_UserSettingsDialog.pcss
index 118c057b83..41d39f8b79 100644
--- a/res/css/views/dialogs/_UserSettingsDialog.pcss
+++ b/res/css/views/dialogs/_UserSettingsDialog.pcss
@@ -49,6 +49,10 @@ limitations under the License.
mask-image: url("$(res)/img/element-icons/security.svg");
}
+.mx_UserSettingsDialog_sessionsIcon::before {
+ mask-image: url("$(res)/img/element-icons/settings/devices.svg");
+}
+
.mx_UserSettingsDialog_helpIcon::before {
mask-image: url("$(res)/img/element-icons/settings/help.svg");
}
diff --git a/res/css/views/elements/_ImageView.pcss b/res/css/views/elements/_ImageView.pcss
index 969e34924b..f67f8a1262 100644
--- a/res/css/views/elements/_ImageView.pcss
+++ b/res/css/views/elements/_ImageView.pcss
@@ -67,6 +67,8 @@ $button-gap: 24px;
flex-direction: row;
align-items: center;
color: $lightbox-fg-color;
+ flex-grow: 1;
+ flex-basis: 0;
}
.mx_ImageView_info {
@@ -82,6 +84,9 @@ $button-gap: 24px;
.mx_ImageView_title {
color: $lightbox-fg-color;
font-size: $font-12px;
+ flex-grow: 1;
+ flex-basis: 0;
+ text-align: center;
}
.mx_ImageView_toolbar {
@@ -89,6 +94,9 @@ $button-gap: 24px;
pointer-events: initial;
display: flex;
align-items: center;
+ flex-grow: 1;
+ flex-basis: 0;
+ justify-content: flex-end;
gap: calc($button-gap - ($button-size - $icon-size));
}
diff --git a/res/css/views/rooms/_EventTile.pcss b/res/css/views/rooms/_EventTile.pcss
index 1169f51388..5d55e8bf34 100644
--- a/res/css/views/rooms/_EventTile.pcss
+++ b/res/css/views/rooms/_EventTile.pcss
@@ -548,7 +548,19 @@ $left-gutter: 64px;
pre,
code {
font-family: $monospace-font-family !important;
- background-color: $codeblock-background-color;
+ background-color: $system;
+ }
+
+ code:not(pre *) {
+ background-color: $inlinecode-background-color;
+ border: 1px solid $inlinecode-border-color;
+ border-radius: 4px;
+ // The horizontal padding is added by gfm.css .markdown-body
+ padding: $spacing-2 0;
+ // Avoid inline code blocks to be sticked when on multiple lines
+ line-height: $font-22px;
+ // Avoid the border to be glued to the other words
+ margin-right: $spacing-2;
}
code {
@@ -566,6 +578,8 @@ $left-gutter: 64px;
background: transparent;
}
+ border: 1px solid $quinary-content;
+
code {
white-space: pre; /* we want code blocks to be scrollable and not wrap */
@@ -619,6 +633,17 @@ $left-gutter: 64px;
ul ol {
list-style-type: revert;
}
+
+ /* Make list type disc to match rich text editor */
+ > ul {
+ list-style-type: disc;
+ }
+
+ /* Remove top and bottom margin for better consecutive list display */
+ > :is(ol, ul) {
+ margin-top: 0;
+ margin-bottom: 0;
+ }
}
}
@@ -733,6 +758,8 @@ $left-gutter: 64px;
.mx_EventTile_collapsedCodeBlock {
max-height: 30vh;
+ padding-top: $spacing-12;
+ padding-bottom: $spacing-12;
}
/* Inserted adjacent to blocks, (See TextualBody) */
@@ -873,6 +900,7 @@ $left-gutter: 64px;
&::before {
inset: 0;
+ pointer-events: none; /* ensures the title for the sender name can be correctly displayed */
}
/* Display notification dot */
@@ -916,8 +944,14 @@ $left-gutter: 64px;
inset: $padding auto auto $padding;
}
+ .mx_EventTile_details {
+ overflow: hidden;
+ }
+
.mx_DisambiguatedProfile {
display: inline-flex;
+ align-items: center;
+ flex: 1;
.mx_DisambiguatedProfile_displayName,
.mx_DisambiguatedProfile_mxid {
@@ -968,8 +1002,11 @@ $left-gutter: 64px;
.mx_MessageTimestamp {
font-size: $font-12px;
- max-width: var(--MessageTimestamp-max-width);
+ width: unset; /* Cancel the default width */
+ overflow: hidden; /* ensure correct overflow behavior */
+ text-overflow: ellipsis;
position: initial;
+ margin-left: auto; /* to ensure it's end-aligned even if it's the only element of its parent */
}
&:hover {
@@ -1297,7 +1334,7 @@ $left-gutter: 64px;
.mx_EventTile_details {
display: flex;
- width: -webkit-fill-available;
+ width: stretch;
align-items: center;
justify-content: space-between;
gap: $spacing-8;
diff --git a/res/css/views/rooms/_ReplyTile.pcss b/res/css/views/rooms/_ReplyTile.pcss
index 1e70b47956..1a2e1a3814 100644
--- a/res/css/views/rooms/_ReplyTile.pcss
+++ b/res/css/views/rooms/_ReplyTile.pcss
@@ -32,11 +32,12 @@ limitations under the License.
grid-template:
"sender" auto
"message" auto
- / auto;
+ / 100%;
text-decoration: none;
color: $secondary-content;
transition: color ease 0.15s;
gap: 2px;
+ max-width: 100%; // avoid overflow with wide content
&:hover {
color: $primary-content;
diff --git a/res/css/views/rooms/_RoomHeader.pcss b/res/css/views/rooms/_RoomHeader.pcss
index f1636055b1..c6ab2add22 100644
--- a/res/css/views/rooms/_RoomHeader.pcss
+++ b/res/css/views/rooms/_RoomHeader.pcss
@@ -108,7 +108,7 @@ limitations under the License.
display: flex;
user-select: none;
- &:not(.mx_RoomHeader_name--textonly):hover {
+ &:hover {
background-color: $quinary-content;
}
diff --git a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss
index d48476cfd7..cc805e1ac1 100644
--- a/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss
+++ b/res/css/views/rooms/wysiwyg_composer/components/_Editor.pcss
@@ -25,6 +25,7 @@ limitations under the License.
}
.mx_WysiwygComposer_Editor_content {
+ line-height: $font-22px;
white-space: pre-wrap;
word-wrap: break-word;
outline: none;
@@ -35,6 +36,52 @@ limitations under the License.
.caretNode {
user-select: all;
}
+
+ ul,
+ ol {
+ margin-top: 0;
+ margin-bottom: 0;
+ padding-inline-start: $spacing-28;
+ }
+
+ blockquote {
+ color: #777;
+ border-left: 2px solid $blockquote-bar-color;
+ border-radius: 2px;
+ padding: 0 10px;
+
+ margin-block-start: 0;
+ margin-block-end: 0;
+ margin-inline-start: 0;
+ margin-inline-end: 0;
+ }
+
+ // model output always includes a linebreak but we do not want the user
+ // to see it when writing input in lists
+ :is(ol, ul, pre, blockquote) + br:last-of-type {
+ display: none;
+ }
+
+ > pre {
+ font-size: $font-15px;
+ line-height: $font-24px;
+
+ margin-top: 0;
+ margin-bottom: 0;
+ padding: $spacing-8 $spacing-12;
+
+ background-color: $inlinecode-background-color;
+ border: 1px solid $inlinecode-border-color;
+ border-radius: 2px;
+ }
+
+ code {
+ font-family: $monospace-font-family !important;
+ background-color: $inlinecode-background-color;
+ border: 1px solid $inlinecode-border-color;
+ border-radius: 4px;
+ padding: $spacing-2;
+ }
}
.mx_WysiwygComposer_Editor_content_placeholder::before {
diff --git a/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss b/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss
index fa8078279f..8e3dd22c99 100644
--- a/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss
+++ b/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss
@@ -50,6 +50,12 @@ limitations under the License.
}
}
+ .mx_FormattingButtons_disabled {
+ .mx_FormattingButtons_Icon {
+ color: $quinary-content;
+ }
+ }
+
.mx_FormattingButtons_Icon {
--size: 16px;
height: var(--size);
diff --git a/res/css/views/voip/_CallDuration.pcss b/res/css/views/voip/_CallDuration.pcss
index c8dc07ef67..49524c88c6 100644
--- a/res/css/views/voip/_CallDuration.pcss
+++ b/res/css/views/voip/_CallDuration.pcss
@@ -17,4 +17,5 @@ limitations under the License.
.mx_CallDuration {
color: $secondary-content;
font-size: $font-12px;
+ white-space: nowrap;
}
diff --git a/res/css/views/voip/_CallView.pcss b/res/css/views/voip/_CallView.pcss
index 3e214a5b7b..72c5dc1839 100644
--- a/res/css/views/voip/_CallView.pcss
+++ b/res/css/views/voip/_CallView.pcss
@@ -160,7 +160,7 @@ limitations under the License.
content: "";
display: inline-block;
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
- mask-size: $size;
+ mask-size: 20px;
mask-position: center;
background-color: $call-primary-content;
height: 100%;
@@ -181,7 +181,7 @@ limitations under the License.
.mx_CallView_deviceButton {
&.mx_CallView_deviceButton_audio::before {
mask-image: url("$(res)/img/element-icons/Mic-off.svg");
- mask-size: 14px;
+ mask-size: 18px;
}
&.mx_CallView_deviceButton_video::before {
diff --git a/res/css/voice-broadcast/atoms/_VoiceBroadcastRecordingConnectionError.pcss b/res/css/voice-broadcast/atoms/_VoiceBroadcastRecordingConnectionError.pcss
new file mode 100644
index 0000000000..bf2e535096
--- /dev/null
+++ b/res/css/voice-broadcast/atoms/_VoiceBroadcastRecordingConnectionError.pcss
@@ -0,0 +1,26 @@
+/*
+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_VoiceBroadcastRecordingConnectionError {
+ align-items: center;
+ color: $alert;
+ display: flex;
+ gap: $spacing-12;
+
+ svg path {
+ fill: $alert;
+ }
+}
diff --git a/res/img/element-icons/room/composer/bulleted_list.svg b/res/img/element-icons/room/composer/bulleted_list.svg
new file mode 100644
index 0000000000..828bb8ab03
--- /dev/null
+++ b/res/img/element-icons/room/composer/bulleted_list.svg
@@ -0,0 +1,3 @@
+
diff --git a/res/img/element-icons/room/composer/code_block.svg b/res/img/element-icons/room/composer/code_block.svg
new file mode 100644
index 0000000000..dd0be2aefc
--- /dev/null
+++ b/res/img/element-icons/room/composer/code_block.svg
@@ -0,0 +1,3 @@
+
diff --git a/res/img/element-icons/room/composer/numbered_list.svg b/res/img/element-icons/room/composer/numbered_list.svg
new file mode 100644
index 0000000000..46a5438f3f
--- /dev/null
+++ b/res/img/element-icons/room/composer/numbered_list.svg
@@ -0,0 +1,3 @@
+
diff --git a/res/img/element-icons/room/composer/quote.svg b/res/img/element-icons/room/composer/quote.svg
new file mode 100644
index 0000000000..82cc2d2875
--- /dev/null
+++ b/res/img/element-icons/room/composer/quote.svg
@@ -0,0 +1,6 @@
+
diff --git a/res/img/element-icons/settings/devices.svg b/res/img/element-icons/settings/devices.svg
new file mode 100644
index 0000000000..4d2f171993
--- /dev/null
+++ b/res/img/element-icons/settings/devices.svg
@@ -0,0 +1,5 @@
+
diff --git a/res/themes/dark/css/_dark.pcss b/res/themes/dark/css/_dark.pcss
index 6fd88d63a9..882ef1f005 100644
--- a/res/themes/dark/css/_dark.pcss
+++ b/res/themes/dark/css/_dark.pcss
@@ -224,6 +224,8 @@ $composer-shadow-color: rgba(0, 0, 0, 0.28);
$breadcrumb-placeholder-bg-color: #272c35;
$theme-button-bg-color: #e3e8f0;
$resend-button-divider-color: rgba($header-panel-text-primary-color, 0.74);
+$inlinecode-border-color: $quinary-content;
+$inlinecode-background-color: $system;
$codeblock-background-color: #2a3039;
$scrollbar-thumb-color: rgba(255, 255, 255, 0.2);
$selected-color: $room-highlight-color;
diff --git a/res/themes/legacy-dark/css/_legacy-dark.pcss b/res/themes/legacy-dark/css/_legacy-dark.pcss
index ed1ff5793b..03088f216b 100644
--- a/res/themes/legacy-dark/css/_legacy-dark.pcss
+++ b/res/themes/legacy-dark/css/_legacy-dark.pcss
@@ -190,6 +190,8 @@ $appearance-tab-border-color: $room-highlight-color;
$composer-shadow-color: tranparent;
$codeblock-background-color: #2a3039;
+$inlinecode-border-color: #2a3039;
+$inlinecode-background-color: #2a3039;
/* Bubble tiles */
$eventbubble-self-bg: #14322e;
diff --git a/res/themes/legacy-light/css/_legacy-light.pcss b/res/themes/legacy-light/css/_legacy-light.pcss
index ae52b078a7..dd152f368e 100644
--- a/res/themes/legacy-light/css/_legacy-light.pcss
+++ b/res/themes/legacy-light/css/_legacy-light.pcss
@@ -290,6 +290,8 @@ $appearance-tab-border-color: $input-darker-bg-color;
$composer-shadow-color: tranparent;
$codeblock-background-color: $header-panel-bg-color;
+$inlinecode-border-color: $header-panel-bg-color;
+$inlinecode-background-color: $header-panel-bg-color;
/* Bubble tiles */
$eventbubble-self-bg: #f0fbf8;
diff --git a/res/themes/light/css/_light.pcss b/res/themes/light/css/_light.pcss
index 4a1ae7e53a..6d59801779 100644
--- a/res/themes/light/css/_light.pcss
+++ b/res/themes/light/css/_light.pcss
@@ -295,6 +295,8 @@ $composer-shadow-color: rgba(0, 0, 0, 0.04);
$breadcrumb-placeholder-bg-color: #e8eef5;
$theme-button-bg-color: $quinary-content;
$resend-button-divider-color: $input-darker-bg-color;
+$inlinecode-border-color: $quinary-content;
+$inlinecode-background-color: $system;
$codeblock-background-color: $header-panel-bg-color;
$scrollbar-thumb-color: rgba(0, 0, 0, 0.2);
$selected-color: $secondary-accent-color;
diff --git a/src/@types/diff-dom.d.ts b/src/@types/diff-dom.d.ts
index 5998b0e404..bf9150a697 100644
--- a/src/@types/diff-dom.d.ts
+++ b/src/@types/diff-dom.d.ts
@@ -20,10 +20,10 @@ declare module "diff-dom" {
name: string;
text?: string;
route: number[];
- value: string;
- element: unknown;
- oldValue: string;
- newValue: string;
+ value: HTMLElement | string;
+ element: HTMLElement | string;
+ oldValue: HTMLElement | string;
+ newValue: HTMLElement | string;
}
interface IOpts {}
diff --git a/src/@types/polyfill.ts b/src/@types/polyfill.ts
index d24d2f4463..6434512e75 100644
--- a/src/@types/polyfill.ts
+++ b/src/@types/polyfill.ts
@@ -15,7 +15,7 @@ limitations under the License.
*/
// This is intended to fix re-resizer because of its unguarded `instanceof TouchEvent` checks.
-export function polyfillTouchEvent() {
+export function polyfillTouchEvent(): void {
// Firefox doesn't have touch events without touch devices being present, so create a fake
// one we can rely on lying about.
if (!window.TouchEvent) {
diff --git a/src/AsyncWrapper.tsx b/src/AsyncWrapper.tsx
index f6f7edd2c2..226f5b692b 100644
--- a/src/AsyncWrapper.tsx
+++ b/src/AsyncWrapper.tsx
@@ -47,7 +47,7 @@ export default class AsyncWrapper extends React.Component {
error: null,
};
- public componentDidMount() {
+ public componentDidMount(): void {
// XXX: temporary logging to try to diagnose
// https://github.com/vector-im/element-web/issues/3148
logger.log("Starting load of AsyncWrapper for modal");
@@ -69,15 +69,15 @@ export default class AsyncWrapper extends React.Component {
});
}
- public componentWillUnmount() {
+ public componentWillUnmount(): void {
this.unmounted = true;
}
- private onWrapperCancelClick = () => {
+ private onWrapperCancelClick = (): void => {
this.props.onFinished(false);
};
- public render() {
+ public render(): JSX.Element {
if (this.state.component) {
const Component = this.state.component;
return ;
diff --git a/src/Avatar.ts b/src/Avatar.ts
index 30a53e74a0..8a3f10a22c 100644
--- a/src/Avatar.ts
+++ b/src/Avatar.ts
@@ -137,7 +137,12 @@ export function getInitialLetter(name: string): string {
return split(name, "", 1)[0].toUpperCase();
}
-export function avatarUrlForRoom(room: Room, width: number, height: number, resizeMethod?: ResizeMethod) {
+export function avatarUrlForRoom(
+ room: Room,
+ width: number,
+ height: number,
+ resizeMethod?: ResizeMethod,
+): string | null {
if (!room) return null; // null-guard
if (room.getMxcAvatarUrl()) {
diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts
index 46a406271c..22d274ffb1 100644
--- a/src/BasePlatform.ts
+++ b/src/BasePlatform.ts
@@ -272,7 +272,7 @@ export default abstract class BasePlatform {
return null;
}
- public setLanguage(preferredLangs: string[]) {}
+ public setLanguage(preferredLangs: string[]): void {}
public setSpellCheckEnabled(enabled: boolean): void {}
@@ -280,7 +280,7 @@ export default abstract class BasePlatform {
return null;
}
- public setSpellCheckLanguages(preferredLangs: string[]) {}
+ public setSpellCheckLanguages(preferredLangs: string[]): void {}
public getSpellCheckLanguages(): Promise | null {
return null;
diff --git a/src/BlurhashEncoder.ts b/src/BlurhashEncoder.ts
index 56e137cc01..01f84421b6 100644
--- a/src/BlurhashEncoder.ts
+++ b/src/BlurhashEncoder.ts
@@ -40,7 +40,7 @@ export class BlurhashEncoder {
this.worker.onmessage = this.onMessage;
}
- private onMessage = (ev: MessageEvent) => {
+ private onMessage = (ev: MessageEvent): void => {
const { seq, blurhash } = ev.data;
const deferred = this.pendingDeferredMap.get(seq);
if (deferred) {
diff --git a/src/ContentMessages.ts b/src/ContentMessages.ts
index a2fe27d0a9..1381e9431e 100644
--- a/src/ContentMessages.ts
+++ b/src/ContentMessages.ts
@@ -68,16 +68,20 @@ interface IMediaConfig {
* @param {File} imageFile The file to load in an image element.
* @return {Promise} A promise that resolves with the html image element.
*/
-async function loadImageElement(imageFile: File) {
+async function loadImageElement(imageFile: File): Promise<{
+ width: number;
+ height: number;
+ img: HTMLImageElement;
+}> {
// Load the file into an html element
const img = new Image();
const objectUrl = URL.createObjectURL(imageFile);
const imgPromise = new Promise((resolve, reject) => {
- img.onload = function () {
+ img.onload = function (): void {
URL.revokeObjectURL(objectUrl);
resolve(img);
};
- img.onerror = function (e) {
+ img.onerror = function (e): void {
reject(e);
};
});
@@ -185,13 +189,13 @@ function loadVideoElement(videoFile: File): Promise {
const reader = new FileReader();
- reader.onload = function (ev) {
+ reader.onload = function (ev): void {
// Wait until we have enough data to thumbnail the first frame.
- video.onloadeddata = async function () {
+ video.onloadeddata = async function (): Promise {
resolve(video);
video.pause();
};
- video.onerror = function (e) {
+ video.onerror = function (e): void {
reject(e);
};
@@ -206,7 +210,7 @@ function loadVideoElement(videoFile: File): Promise {
video.load();
video.play();
};
- reader.onerror = function (e) {
+ reader.onerror = function (e): void {
reject(e);
};
reader.readAsDataURL(videoFile);
@@ -253,10 +257,10 @@ function infoForVideoFile(
function readFileAsArrayBuffer(file: File | Blob): Promise {
return new Promise((resolve, reject) => {
const reader = new FileReader();
- reader.onload = function (e) {
+ reader.onload = function (e): void {
resolve(e.target.result as ArrayBuffer);
};
- reader.onerror = function (e) {
+ reader.onerror = function (e): void {
reject(e);
};
reader.readAsArrayBuffer(file);
@@ -461,7 +465,7 @@ export default class ContentMessages {
matrixClient: MatrixClient,
replyToEvent: MatrixEvent | undefined,
promBefore?: Promise,
- ) {
+ ): Promise {
const fileName = file.name || _t("Attachment");
const content: Omit & { info: Partial } = {
body: fileName,
@@ -491,7 +495,7 @@ export default class ContentMessages {
this.inprogress.push(upload);
dis.dispatch({ action: Action.UploadStarted, upload });
- function onProgress(progress: UploadProgress) {
+ function onProgress(progress: UploadProgress): void {
upload.onProgress(progress);
dis.dispatch({ action: Action.UploadProgress, upload });
}
@@ -568,7 +572,7 @@ export default class ContentMessages {
}
}
- private isFileSizeAcceptable(file: File) {
+ private isFileSizeAcceptable(file: File): boolean {
if (
this.mediaConfig !== null &&
this.mediaConfig["m.upload.size"] !== undefined &&
@@ -599,7 +603,7 @@ export default class ContentMessages {
});
}
- public static sharedInstance() {
+ public static sharedInstance(): ContentMessages {
if (window.mxContentMessages === undefined) {
window.mxContentMessages = new ContentMessages();
}
diff --git a/src/DateUtils.ts b/src/DateUtils.ts
index 1dab03121e..5973a7c5f2 100644
--- a/src/DateUtils.ts
+++ b/src/DateUtils.ts
@@ -188,7 +188,7 @@ export function wantsDateSeparator(prevEventDate: Date, nextEventDate: Date): bo
return prevEventDate.getDay() !== nextEventDate.getDay();
}
-export function formatFullDateNoDay(date: Date) {
+export function formatFullDateNoDay(date: Date): string {
return _t("%(date)s at %(time)s", {
date: date.toLocaleDateString().replace(/\//g, "-"),
time: date.toLocaleTimeString().replace(/:/g, "-"),
@@ -205,7 +205,7 @@ export function formatFullDateNoDayISO(date: Date): string {
return date.toISOString();
}
-export function formatFullDateNoDayNoTime(date: Date) {
+export function formatFullDateNoDayNoTime(date: Date): string {
return date.getFullYear() + "/" + pad(date.getMonth() + 1) + "/" + pad(date.getDate());
}
diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts
index 3d33ff0fda..be48717415 100644
--- a/src/DeviceListener.ts
+++ b/src/DeviceListener.ts
@@ -19,6 +19,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
import { ClientEvent, EventType, RoomStateEvent } from "matrix-js-sdk/src/matrix";
import { SyncState } from "matrix-js-sdk/src/sync";
+import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
import { MatrixClientPeg } from "./MatrixClientPeg";
import dis from "./dispatcher/dispatcher";
@@ -56,7 +57,7 @@ export default class DeviceListener {
// has the user dismissed any of the various nag toasts to setup encryption on this device?
private dismissedThisDeviceToast = false;
// cache of the key backup info
- private keyBackupInfo: object = null;
+ private keyBackupInfo: IKeyBackupInfo | null = null;
private keyBackupFetchedAt: number = null;
private keyBackupStatusChecked = false;
// We keep a list of our own device IDs so we can batch ones that were already
@@ -70,12 +71,12 @@ export default class DeviceListener {
private enableBulkUnverifiedSessionsReminder = true;
private deviceClientInformationSettingWatcherRef: string | undefined;
- public static sharedInstance() {
+ public static sharedInstance(): DeviceListener {
if (!window.mxDeviceListener) window.mxDeviceListener = new DeviceListener();
return window.mxDeviceListener;
}
- public start() {
+ public start(): void {
this.running = true;
MatrixClientPeg.get().on(CryptoEvent.WillUpdateDevices, this.onWillUpdateDevices);
MatrixClientPeg.get().on(CryptoEvent.DevicesUpdated, this.onDevicesUpdated);
@@ -98,7 +99,7 @@ export default class DeviceListener {
this.updateClientInformation();
}
- public stop() {
+ public stop(): void {
this.running = false;
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener(CryptoEvent.WillUpdateDevices, this.onWillUpdateDevices);
@@ -134,7 +135,7 @@ export default class DeviceListener {
*
* @param {String[]} deviceIds List of device IDs to dismiss notifications for
*/
- public async dismissUnverifiedSessions(deviceIds: Iterable) {
+ public async dismissUnverifiedSessions(deviceIds: Iterable): Promise {
logger.log("Dismissing unverified sessions: " + Array.from(deviceIds).join(","));
for (const d of deviceIds) {
this.dismissed.add(d);
@@ -143,19 +144,19 @@ export default class DeviceListener {
this.recheck();
}
- public dismissEncryptionSetup() {
+ public dismissEncryptionSetup(): void {
this.dismissedThisDeviceToast = true;
this.recheck();
}
- private ensureDeviceIdsAtStartPopulated() {
+ private ensureDeviceIdsAtStartPopulated(): void {
if (this.ourDeviceIdsAtStart === null) {
const cli = MatrixClientPeg.get();
this.ourDeviceIdsAtStart = new Set(cli.getStoredDevicesForUser(cli.getUserId()).map((d) => d.deviceId));
}
}
- private onWillUpdateDevices = async (users: string[], initialFetch?: boolean) => {
+ private onWillUpdateDevices = async (users: string[], initialFetch?: boolean): Promise => {
// If we didn't know about *any* devices before (ie. it's fresh login),
// then they are all pre-existing devices, so ignore this and set the
// devicesAtStart list to the devices that we see after the fetch.
@@ -168,26 +169,26 @@ export default class DeviceListener {
// before we download any new ones.
};
- private onDevicesUpdated = (users: string[]) => {
+ private onDevicesUpdated = (users: string[]): void => {
if (!users.includes(MatrixClientPeg.get().getUserId())) return;
this.recheck();
};
- private onDeviceVerificationChanged = (userId: string) => {
+ private onDeviceVerificationChanged = (userId: string): void => {
if (userId !== MatrixClientPeg.get().getUserId()) return;
this.recheck();
};
- private onUserTrustStatusChanged = (userId: string) => {
+ private onUserTrustStatusChanged = (userId: string): void => {
if (userId !== MatrixClientPeg.get().getUserId()) return;
this.recheck();
};
- private onCrossSingingKeysChanged = () => {
+ private onCrossSingingKeysChanged = (): void => {
this.recheck();
};
- private onAccountData = (ev: MatrixEvent) => {
+ private onAccountData = (ev: MatrixEvent): void => {
// User may have:
// * migrated SSSS to symmetric
// * uploaded keys to secret storage
@@ -202,13 +203,13 @@ export default class DeviceListener {
}
};
- private onSync = (state: SyncState, prevState?: SyncState) => {
+ private onSync = (state: SyncState, prevState?: SyncState): void => {
if (state === "PREPARED" && prevState === null) {
this.recheck();
}
};
- private onRoomStateEvents = (ev: MatrixEvent) => {
+ private onRoomStateEvents = (ev: MatrixEvent): void => {
if (ev.getType() !== EventType.RoomEncryption) return;
// If a room changes to encrypted, re-check as it may be our first
@@ -216,7 +217,7 @@ export default class DeviceListener {
this.recheck();
};
- private onAction = ({ action }: ActionPayload) => {
+ private onAction = ({ action }: ActionPayload): void => {
if (action !== Action.OnLoggedIn) return;
this.recheck();
this.updateClientInformation();
@@ -224,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() {
+ 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();
@@ -233,7 +234,7 @@ export default class DeviceListener {
return this.keyBackupInfo;
}
- private shouldShowSetupEncryptionToast() {
+ private shouldShowSetupEncryptionToast(): boolean {
// If we're in the middle of a secret storage operation, we're likely
// modifying the state involved here, so don't add new toasts to setup.
if (isSecretStorageBeingAccessed()) return false;
@@ -242,7 +243,7 @@ export default class DeviceListener {
return cli && cli.getRooms().some((r) => cli.isRoomEncrypted(r.roomId));
}
- private async recheck() {
+ private async recheck(): Promise {
if (!this.running) return; // we have been stopped
const cli = MatrixClientPeg.get();
@@ -359,7 +360,7 @@ export default class DeviceListener {
this.displayingToastsForDeviceIds = newUnverifiedDeviceIds;
}
- private checkKeyBackupStatus = async () => {
+ private checkKeyBackupStatus = async (): Promise => {
if (this.keyBackupStatusChecked) {
return;
}
@@ -388,7 +389,7 @@ export default class DeviceListener {
}
};
- private updateClientInformation = async () => {
+ private updateClientInformation = async (): Promise => {
try {
if (this.shouldRecordClientInformation) {
await recordClientInformation(MatrixClientPeg.get(), SdkConfig.get(), PlatformPeg.get());
diff --git a/src/Editing.ts b/src/Editing.ts
index 57e58cc2a7..e331a3dca1 100644
--- a/src/Editing.ts
+++ b/src/Editing.ts
@@ -16,5 +16,6 @@ limitations under the License.
import { TimelineRenderingType } from "./contexts/RoomContext";
-export const editorRoomKey = (roomId: string, context: TimelineRenderingType) => `mx_edit_room_${roomId}_${context}`;
-export const editorStateKey = (eventId: string) => `mx_edit_state_${eventId}`;
+export const editorRoomKey = (roomId: string, context: TimelineRenderingType): string =>
+ `mx_edit_room_${roomId}_${context}`;
+export const editorStateKey = (eventId: string): string => `mx_edit_state_${eventId}`;
diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx
index 19da52f45b..3e67e42256 100644
--- a/src/HtmlUtils.tsx
+++ b/src/HtmlUtils.tsx
@@ -449,9 +449,9 @@ export interface IOptsReturnString extends IOpts {
returnString: true;
}
-const emojiToHtmlSpan = (emoji: string) =>
+const emojiToHtmlSpan = (emoji: string): string =>
`${emoji}`;
-const emojiToJsxSpan = (emoji: string, key: number) => (
+const emojiToJsxSpan = (emoji: string, key: number): JSX.Element => (
{emoji}
@@ -505,7 +505,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 = {}) {
+export function bodyToHtml(content: IContent, highlights: Optional, opts: IOpts = {}): ReactNode | string {
const isFormattedBody = content.format === "org.matrix.custom.html" && !!content.formatted_body;
let bodyHasEmoji = false;
let isHtmlMessage = false;
diff --git a/src/IConfigOptions.ts b/src/IConfigOptions.ts
index fffa3fbb9f..8234f5bc75 100644
--- a/src/IConfigOptions.ts
+++ b/src/IConfigOptions.ts
@@ -99,7 +99,7 @@ export interface IConfigOptions {
features?: Record; //
bug_report_endpoint_url?: string; // omission disables bug reporting
- uisi_autorageshake_app?: string;
+ uisi_autorageshake_app?: string; // defaults to "element-auto-uisi"
sentry?: {
dsn: string;
environment?: string; // "production", etc
diff --git a/src/ImageUtils.ts b/src/ImageUtils.ts
index 1618aa8f4c..42db71ebab 100644
--- a/src/ImageUtils.ts
+++ b/src/ImageUtils.ts
@@ -28,7 +28,7 @@ limitations under the License.
* consume in the timeline, when performing scroll offset calculations
* (e.g. scroll locking)
*/
-export function thumbHeight(fullWidth: number, fullHeight: number, thumbWidth: number, thumbHeight: number) {
+export function thumbHeight(fullWidth: number, fullHeight: number, thumbWidth: number, thumbHeight: number): number {
if (!fullWidth || !fullHeight) {
// Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even
// log this because it's spammy
diff --git a/src/Keyboard.ts b/src/Keyboard.ts
index 3a425cef31..9d4d3f6152 100644
--- a/src/Keyboard.ts
+++ b/src/Keyboard.ts
@@ -76,7 +76,7 @@ export const Key = {
export const IS_MAC = navigator.platform.toUpperCase().includes("MAC");
-export function isOnlyCtrlOrCmdKeyEvent(ev) {
+export function isOnlyCtrlOrCmdKeyEvent(ev: 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 d81bd73661..82e5cac996 100644
--- a/src/LegacyCallHandler.tsx
+++ b/src/LegacyCallHandler.tsx
@@ -169,7 +169,7 @@ export default class LegacyCallHandler extends EventEmitter {
private silencedCalls = new Set(); // callIds
- public static get instance() {
+ public static get instance(): LegacyCallHandler {
if (!window.mxLegacyCallHandler) {
window.mxLegacyCallHandler = new LegacyCallHandler();
}
@@ -456,7 +456,7 @@ export default class LegacyCallHandler extends EventEmitter {
return callsNotInThatRoom;
}
- public getAllActiveCallsForPip(roomId: string) {
+ public getAllActiveCallsForPip(roomId: string): MatrixCall[] {
const room = MatrixClientPeg.get().getRoom(roomId);
if (WidgetLayoutStore.instance.hasMaximisedWidget(room)) {
// This checks if there is space for the call view in the aux panel
@@ -478,7 +478,7 @@ export default class LegacyCallHandler extends EventEmitter {
const audio = document.getElementById(audioId) as HTMLMediaElement;
if (audio) {
this.addEventListenersForAudioElement(audio);
- const playAudio = async () => {
+ const playAudio = async (): Promise => {
try {
if (audio.muted) {
logger.error(
@@ -524,7 +524,7 @@ export default class LegacyCallHandler extends EventEmitter {
// TODO: Attach an invisible element for this instead
// which listens?
const audio = document.getElementById(audioId) as HTMLMediaElement;
- const pauseAudio = () => {
+ const pauseAudio = (): void => {
logger.debug(`${logPrefix} pausing audio`);
// pause doesn't return a promise, so just do it
audio.pause();
@@ -600,7 +600,7 @@ export default class LegacyCallHandler extends EventEmitter {
this.setCallListeners(newCall);
this.setCallState(newCall, newCall.state);
});
- call.on(CallEvent.AssertedIdentityChanged, async () => {
+ call.on(CallEvent.AssertedIdentityChanged, async (): Promise => {
if (!this.matchesCallForThisRoom(call)) return;
logger.log(`Call ID ${call.callId} got new asserted identity:`, call.getRemoteAssertedIdentity());
@@ -808,7 +808,7 @@ export default class LegacyCallHandler extends EventEmitter {
private showICEFallbackPrompt(): void {
const cli = MatrixClientPeg.get();
- const code = (sub) => {sub}
;
+ const code = (sub: string): JSX.Element => {sub}
;
Modal.createDialog(
QuestionDialog,
{
diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts
index db6d15c188..30aab429fb 100644
--- a/src/Lifecycle.ts
+++ b/src/Lifecycle.ts
@@ -219,7 +219,7 @@ export function attemptTokenLogin(
})
.then(function (creds) {
logger.log("Logged in with token");
- return clearStorage().then(async () => {
+ return clearStorage().then(async (): Promise => {
await persistCredentials(creds);
// remember that we just logged in
sessionStorage.setItem("mx_fresh_login", String(true));
@@ -406,7 +406,7 @@ async function pickleKeyToAesKey(pickleKey: string): Promise {
);
}
-async function abortLogin() {
+async function abortLogin(): Promise {
const signOut = await showStorageEvictedDialog();
if (signOut) {
await clearStorage();
diff --git a/src/Livestream.ts b/src/Livestream.ts
index d339045c94..563136983b 100644
--- a/src/Livestream.ts
+++ b/src/Livestream.ts
@@ -20,14 +20,14 @@ import { MatrixClientPeg } from "./MatrixClientPeg";
import SdkConfig from "./SdkConfig";
import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions";
-export function getConfigLivestreamUrl() {
+export function getConfigLivestreamUrl(): string | undefined {
return SdkConfig.get("audio_stream_url");
}
// Dummy rtmp URL used to signal that we want a special audio-only stream
const AUDIOSTREAM_DUMMY_URL = "rtmp://audiostream.dummy/";
-async function createLiveStream(roomId: string) {
+async function createLiveStream(roomId: string): Promise {
const openIdToken = await MatrixClientPeg.get().getOpenIdToken();
const url = getConfigLivestreamUrl() + "/createStream";
@@ -47,7 +47,7 @@ async function createLiveStream(roomId: string) {
return respBody["stream_id"];
}
-export async function startJitsiAudioLivestream(widgetMessaging: ClientWidgetApi, roomId: string) {
+export async function startJitsiAudioLivestream(widgetMessaging: ClientWidgetApi, roomId: string): Promise {
const streamId = await createLiveStream(roomId);
await widgetMessaging.transport.send(ElementWidgetActions.StartLiveStream, {
diff --git a/src/Login.ts b/src/Login.ts
index ec769e8cb3..90f8f5d0eb 100644
--- a/src/Login.ts
+++ b/src/Login.ts
@@ -122,7 +122,7 @@ export default class Login {
initial_device_display_name: this.defaultDeviceDisplayName,
};
- const tryFallbackHs = (originalError) => {
+ const tryFallbackHs = (originalError: Error): Promise => {
return sendLoginRequest(this.fallbackHsUrl, this.isUrl, "m.login.password", loginParams).catch(
(fallbackError) => {
logger.log("fallback HS login failed", fallbackError);
diff --git a/src/Markdown.ts b/src/Markdown.ts
index 404da0ca8d..a32126117d 100644
--- a/src/Markdown.ts
+++ b/src/Markdown.ts
@@ -56,7 +56,7 @@ function isMultiLine(node: commonmark.Node): boolean {
return par.firstChild != par.lastChild;
}
-function getTextUntilEndOrLinebreak(node: commonmark.Node) {
+function getTextUntilEndOrLinebreak(node: commonmark.Node): string {
let currentNode = node;
let text = "";
while (currentNode !== null && currentNode.type !== "softbreak" && currentNode.type !== "linebreak") {
@@ -137,7 +137,7 @@ export default class Markdown {
* See: https://github.com/vector-im/element-web/issues/4674
* @param parsed
*/
- private repairLinks(parsed: commonmark.Node) {
+ private repairLinks(parsed: commonmark.Node): commonmark.Node {
const walker = parsed.walker();
let event: commonmark.NodeWalkingStep = null;
let text = "";
diff --git a/src/Modal.tsx b/src/Modal.tsx
index 290c082344..1b21f74b5e 100644
--- a/src/Modal.tsx
+++ b/src/Modal.tsx
@@ -77,7 +77,7 @@ export class ModalManager extends TypedEventEmitter[] = [];
- private static getOrCreateContainer() {
+ private static getOrCreateContainer(): HTMLElement {
let container = document.getElementById(DIALOG_CONTAINER_ID);
if (!container) {
@@ -89,7 +89,7 @@ export class ModalManager extends TypedEventEmitter 0;
+ public hasDialogs(): boolean {
+ return !!this.priorityModal || !!this.staticModal || this.modals.length > 0;
}
public createDialog(
Element: React.ComponentType,
...rest: ParametersWithoutFirst
- ) {
+ ): IHandle {
return this.createDialogAsync(Promise.resolve(Element), ...rest);
}
public appendDialog(
Element: React.ComponentType,
...rest: ParametersWithoutFirst
- ) {
+ ): IHandle {
return this.appendDialogAsync(Promise.resolve(Element), ...rest);
}
- public closeCurrentModal(reason: string) {
+ public closeCurrentModal(reason: string): void {
const modal = this.getCurrentModal();
if (!modal) {
return;
@@ -139,7 +139,11 @@ export class ModalManager extends TypedEventEmitter,
className?: string,
options?: IOptions,
- ) {
+ ): {
+ modal: IModal;
+ closeDialog: IHandle["close"];
+ onFinishedProm: IHandle["finished"];
+ } {
const modal: IModal = {
onFinished: props ? props.onFinished : null,
onBeforeClose: options.onBeforeClose,
@@ -173,7 +177,7 @@ export class ModalManager extends TypedEventEmitter["close"], IHandle["finished"]] {
const deferred = defer();
return [
- async (...args: T) => {
+ async (...args: T): Promise => {
if (modal.beforeClosePromise) {
await modal.beforeClosePromise;
} else if (modal.onBeforeClose) {
@@ -302,7 +306,7 @@ export class ModalManager extends TypedEventEmitter {
+ private onBackgroundClick = (): void => {
const modal = this.getCurrentModal();
if (!modal) {
return;
@@ -320,7 +324,7 @@ export class ModalManager extends TypedEventEmitter {
// await next tick because sometimes ReactDOM can race with itself and cause the modal to wrongly stick around
await sleep(0);
diff --git a/src/Notifier.ts b/src/Notifier.ts
index 7e1f9eb0a4..42909a2632 100644
--- a/src/Notifier.ts
+++ b/src/Notifier.ts
@@ -50,7 +50,8 @@ import { localNotificationsAreSilenced, createLocalNotificationSettingsIfNeeded
import { getIncomingCallToastKey, IncomingCallToast } from "./toasts/IncomingCallToast";
import ToastStore from "./stores/ToastStore";
import { ElementCall } from "./models/Call";
-import { VoiceBroadcastChunkEventType } from "./voice-broadcast";
+import { VoiceBroadcastChunkEventType, VoiceBroadcastInfoEventType } from "./voice-broadcast";
+import { getSenderName } from "./utils/event/getSenderName";
/*
* Dispatches:
@@ -80,9 +81,16 @@ const msgTypeHandlers = {
},
[MsgType.Audio]: (event: MatrixEvent): string | null => {
if (event.getContent()?.[VoiceBroadcastChunkEventType]) {
- // mute broadcast chunks
+ if (event.getContent()?.[VoiceBroadcastChunkEventType]?.sequence === 1) {
+ // Show a notification for the first broadcast chunk.
+ // At this point a user received something to listen to.
+ return _t("%(senderName)s started a voice broadcast", { senderName: getSenderName(event) });
+ }
+
+ // Mute other broadcast chunks
return null;
}
+
return TextForEvent.textForEvent(event);
},
};
@@ -448,6 +456,9 @@ export const Notifier = {
},
_evaluateEvent: function (ev: MatrixEvent) {
+ // Mute notifications for broadcast info events
+ if (ev.getType() === VoiceBroadcastInfoEventType) return;
+
let roomId = ev.getRoomId();
if (LegacyCallHandler.instance.getSupportsVirtualRooms()) {
// Attempt to translate a virtual room to a native one
diff --git a/src/PasswordReset.ts b/src/PasswordReset.ts
index f6661a35f1..7dbc8a5406 100644
--- a/src/PasswordReset.ts
+++ b/src/PasswordReset.ts
@@ -104,6 +104,10 @@ export default class PasswordReset {
);
}
+ public setLogoutDevices(logoutDevices: boolean): void {
+ this.logoutDevices = logoutDevices;
+ }
+
public async setNewPassword(password: string): Promise {
this.password = password;
await this.checkEmailLinkClicked();
diff --git a/src/PlatformPeg.ts b/src/PlatformPeg.ts
index ab45f1ab1a..cc7bb8dc17 100644
--- a/src/PlatformPeg.ts
+++ b/src/PlatformPeg.ts
@@ -32,13 +32,13 @@ import { PlatformSetPayload } from "./dispatcher/payloads/PlatformSetPayload";
* object.
*/
export class PlatformPeg {
- private platform: BasePlatform = null;
+ private platform: BasePlatform | null = null;
/**
* Returns the current Platform object for the application.
* This should be an instance of a class extending BasePlatform.
*/
- public get() {
+ public get(): BasePlatform | null {
return this.platform;
}
@@ -46,7 +46,7 @@ export class PlatformPeg {
* Sets the current platform handler object to use for the application.
* @param {BasePlatform} platform an instance of a class extending BasePlatform.
*/
- public set(platform: BasePlatform) {
+ public set(platform: BasePlatform): void {
this.platform = platform;
defaultDispatcher.dispatch({
action: Action.PlatformSet,
diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts
index 3e1773be29..c8a99ab426 100644
--- a/src/PosthogAnalytics.ts
+++ b/src/PosthogAnalytics.ts
@@ -175,7 +175,7 @@ export class PosthogAnalytics {
this.onLayoutUpdated();
}
- private onLayoutUpdated = () => {
+ private onLayoutUpdated = (): void => {
let layout: UserProperties["WebLayout"];
switch (SettingsStore.getValue("layout")) {
@@ -195,7 +195,7 @@ export class PosthogAnalytics {
this.setProperty("WebLayout", layout);
};
- private onAction = (payload: ActionPayload) => {
+ private onAction = (payload: ActionPayload): void => {
if (payload.action !== Action.SettingUpdated) return;
const settingsPayload = payload as SettingUpdatedPayload;
if (["layout", "useCompactLayout"].includes(settingsPayload.settingName)) {
@@ -232,7 +232,7 @@ export class PosthogAnalytics {
return properties;
};
- private registerSuperProperties(properties: Properties) {
+ private registerSuperProperties(properties: Properties): void {
if (this.enabled) {
this.posthog.register(properties);
}
@@ -255,7 +255,7 @@ export class PosthogAnalytics {
}
// eslint-disable-nextline no-unused-varsx
- private capture(eventName: string, properties: Properties, options?: IPostHogEventOptions) {
+ private capture(eventName: string, properties: Properties, options?: IPostHogEventOptions): void {
if (!this.enabled) {
return;
}
diff --git a/src/PosthogTrackers.ts b/src/PosthogTrackers.ts
index 09c7225a3d..8a8b02965c 100644
--- a/src/PosthogTrackers.ts
+++ b/src/PosthogTrackers.ts
@@ -107,20 +107,20 @@ export default class PosthogTrackers {
}
export class PosthogScreenTracker extends PureComponent<{ screenName: ScreenName }> {
- public componentDidMount() {
+ public componentDidMount(): void {
PosthogTrackers.instance.trackOverride(this.props.screenName);
}
- public componentDidUpdate() {
+ public componentDidUpdate(): void {
// We do not clear the old override here so that we do not send the non-override screen as a transition
PosthogTrackers.instance.trackOverride(this.props.screenName);
}
- public componentWillUnmount() {
+ public componentWillUnmount(): void {
PosthogTrackers.instance.clearOverride(this.props.screenName);
}
- public render() {
+ public render(): JSX.Element {
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 3684d6f779..c13cc32b60 100644
--- a/src/Presence.ts
+++ b/src/Presence.ts
@@ -41,7 +41,7 @@ class Presence {
* Start listening the user activity to evaluate his presence state.
* Any state change will be sent to the homeserver.
*/
- public async start() {
+ public async start(): Promise {
this.unavailableTimer = new Timer(UNAVAILABLE_TIME_MS);
// the user_activity_start action starts the timer
this.dispatcherRef = dis.register(this.onAction);
@@ -58,7 +58,7 @@ class Presence {
/**
* Stop tracking user activity
*/
- public stop() {
+ public stop(): void {
if (this.dispatcherRef) {
dis.unregister(this.dispatcherRef);
this.dispatcherRef = null;
@@ -73,11 +73,11 @@ class Presence {
* Get the current presence state.
* @returns {string} the presence state (see PRESENCE enum)
*/
- public getState() {
+ public getState(): State {
return this.state;
}
- private onAction = (payload: ActionPayload) => {
+ private onAction = (payload: ActionPayload): void => {
if (payload.action === "user_activity") {
this.setState(State.Online);
this.unavailableTimer.restart();
@@ -89,7 +89,7 @@ class Presence {
* If the state has changed, the homeserver will be notified.
* @param {string} newState the new presence state (see PRESENCE enum)
*/
- private async setState(newState: State) {
+ private async setState(newState: State): Promise {
if (newState === this.state) {
return;
}
diff --git a/src/ScalarAuthClient.ts b/src/ScalarAuthClient.ts
index baa6e6f632..a775eb3d4e 100644
--- a/src/ScalarAuthClient.ts
+++ b/src/ScalarAuthClient.ts
@@ -49,7 +49,7 @@ export default class ScalarAuthClient {
this.isDefaultManager = apiUrl === configApiUrl && configUiUrl === uiUrl;
}
- private writeTokenToStore() {
+ private writeTokenToStore(): void {
window.localStorage.setItem("mx_scalar_token_at_" + this.apiUrl, this.scalarToken);
if (this.isDefaultManager) {
// We remove the old token from storage to migrate upwards. This is safe
@@ -72,7 +72,7 @@ export default class ScalarAuthClient {
return this.readTokenFromStore();
}
- public setTermsInteractionCallback(callback) {
+ public setTermsInteractionCallback(callback: TermsInteractionCallback): void {
this.termsInteractionCallback = callback;
}
diff --git a/src/ScalarMessaging.ts b/src/ScalarMessaging.ts
index b1912c484a..fec671eab4 100644
--- a/src/ScalarMessaging.ts
+++ b/src/ScalarMessaging.ts
@@ -711,7 +711,7 @@ function returnStateEvent(event: MessageEvent, roomId: string, eventType: s
sendResponse(event, stateEvent.getContent());
}
-async function getOpenIdToken(event: MessageEvent) {
+async function getOpenIdToken(event: MessageEvent): Promise {
try {
const tokenObject = await MatrixClientPeg.get().getOpenIdToken();
sendResponse(event, tokenObject);
@@ -728,7 +728,7 @@ async function sendEvent(
content?: IContent;
}>,
roomId: string,
-) {
+): Promise {
const eventType = event.data.type;
const stateKey = event.data.state_key;
const content = event.data.content;
@@ -786,7 +786,7 @@ async function readEvents(
limit?: number;
}>,
roomId: string,
-) {
+): Promise {
const eventType = event.data.type;
const stateKey = event.data.state_key;
const limit = event.data.limit;
diff --git a/src/SdkConfig.ts b/src/SdkConfig.ts
index 75cc5ef059..5aaab6e2f4 100644
--- a/src/SdkConfig.ts
+++ b/src/SdkConfig.ts
@@ -27,6 +27,8 @@ export const DEFAULTS: IConfigOptions = {
integrations_ui_url: "https://scalar.vector.im/",
integrations_rest_url: "https://scalar.vector.im/api",
bug_report_endpoint_url: null,
+ uisi_autorageshake_app: "element-auto-uisi",
+
jitsi: {
preferred_domain: "meet.element.io",
},
@@ -56,7 +58,7 @@ export default class SdkConfig {
private static instance: IConfigOptions;
private static fallback: SnakedObject;
- private static setInstance(i: IConfigOptions) {
+ private static setInstance(i: IConfigOptions): void {
SdkConfig.instance = i;
SdkConfig.fallback = new SnakedObject(i);
@@ -90,18 +92,18 @@ export default class SdkConfig {
return val === undefined ? undefined : null;
}
- public static put(cfg: Partial) {
+ public static put(cfg: Partial): void {
SdkConfig.setInstance({ ...DEFAULTS, ...cfg });
}
/**
* Resets the config to be completely empty.
*/
- public static unset() {
+ public static unset(): void {
SdkConfig.setInstance({}); // safe to cast - defaults will be applied
}
- public static add(cfg: Partial) {
+ public static add(cfg: Partial): void {
SdkConfig.put({ ...SdkConfig.get(), ...cfg });
}
}
diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts
index ed72417ecc..20db6594b0 100644
--- a/src/SecurityManager.ts
+++ b/src/SecurityManager.ts
@@ -86,7 +86,7 @@ async function confirmToDismiss(): Promise {
type KeyParams = { passphrase: string; recoveryKey: string };
function makeInputToKey(keyInfo: ISecretStorageKeyInfo): (keyParams: KeyParams) => Promise {
- return async ({ passphrase, recoveryKey }) => {
+ return async ({ passphrase, recoveryKey }): Promise => {
if (passphrase) {
return deriveKey(passphrase, keyInfo.passphrase.salt, keyInfo.passphrase.iterations);
} else {
@@ -151,7 +151,7 @@ async function getSecretStorageKey({
/* props= */
{
keyInfo,
- checkPrivateKey: async (input: KeyParams) => {
+ checkPrivateKey: async (input: KeyParams): Promise => {
const key = await inputToKey(input);
return MatrixClientPeg.get().checkSecretStorageKey(key, keyInfo);
},
@@ -160,7 +160,7 @@ async function getSecretStorageKey({
/* isPriorityModal= */ false,
/* isStaticModal= */ false,
/* options= */ {
- onBeforeClose: async (reason) => {
+ onBeforeClose: async (reason): Promise => {
if (reason === "backgroundClick") {
return confirmToDismiss();
}
@@ -196,7 +196,7 @@ export async function getDehydrationKey(
/* props= */
{
keyInfo,
- checkPrivateKey: async (input) => {
+ checkPrivateKey: async (input): Promise => {
const key = await inputToKey(input);
try {
checkFunc(key);
@@ -210,7 +210,7 @@ export async function getDehydrationKey(
/* isPriorityModal= */ false,
/* isStaticModal= */ false,
/* options= */ {
- onBeforeClose: async (reason) => {
+ onBeforeClose: async (reason): Promise => {
if (reason === "backgroundClick") {
return confirmToDismiss();
}
@@ -324,7 +324,7 @@ export async function promptForBackupPassphrase(): Promise {
* bootstrapped. Optional.
* @param {bool} [forceReset] Reset secret storage even if it's already set up
*/
-export async function accessSecretStorage(func = async () => {}, forceReset = false) {
+export async function accessSecretStorage(func = async (): Promise => {}, forceReset = false): Promise {
const cli = MatrixClientPeg.get();
secretStorageBeingAccessed = true;
try {
@@ -342,7 +342,7 @@ export async function accessSecretStorage(func = async () => {}, forceReset = fa
/* priority = */ false,
/* static = */ true,
/* options = */ {
- onBeforeClose: async (reason) => {
+ onBeforeClose: async (reason): Promise => {
// If Secure Backup is required, you cannot leave the modal.
if (reason === "backgroundClick") {
return !isSecureBackupRequired();
@@ -357,7 +357,7 @@ export async function accessSecretStorage(func = async () => {}, forceReset = fa
}
} else {
await cli.bootstrapCrossSigning({
- authUploadDeviceSigningKeys: async (makeRequest) => {
+ authUploadDeviceSigningKeys: async (makeRequest): Promise => {
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
title: _t("Setting up keys"),
matrixClient: cli,
diff --git a/src/SendHistoryManager.ts b/src/SendHistoryManager.ts
index 1fd8f839bd..e95641d4c9 100644
--- a/src/SendHistoryManager.ts
+++ b/src/SendHistoryManager.ts
@@ -60,7 +60,7 @@ export default class SendHistoryManager {
};
}
- public save(editorModel: EditorModel, replyEvent?: MatrixEvent) {
+ public save(editorModel: EditorModel, replyEvent?: MatrixEvent): void {
const item = SendHistoryManager.createItem(editorModel, replyEvent);
this.history.push(item);
this.currentIndex = this.history.length;
diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx
index 910c077525..9b23bd4138 100644
--- a/src/SlashCommands.tsx
+++ b/src/SlashCommands.tsx
@@ -85,7 +85,7 @@ const singleMxcUpload = async (): Promise => {
Modal.createDialog(UploadConfirmDialog, {
file,
- onFinished: async (shouldContinue) => {
+ onFinished: async (shouldContinue): Promise => {
if (shouldContinue) {
const { content_uri: uri } = await MatrixClientPeg.get().uploadContent(file);
resolve(uri);
@@ -151,11 +151,11 @@ export class Command {
this.analyticsName = opts.analyticsName;
}
- public getCommand() {
+ public getCommand(): string {
return `/${this.command}`;
}
- public getCommandWithArgs() {
+ public getCommandWithArgs(): string {
return this.getCommand() + " " + this.args;
}
@@ -184,7 +184,7 @@ export class Command {
return this.runFn(roomId, args);
}
- public getUsage() {
+ public getUsage(): string {
return _t("Usage") + ": " + this.getCommandWithArgs();
}
@@ -193,15 +193,15 @@ export class Command {
}
}
-function reject(error) {
+function reject(error?: any): RunResult {
return { error };
}
-function success(promise?: Promise) {
+function success(promise?: Promise): RunResult {
return { promise };
}
-function successSync(value: any) {
+function successSync(value: any): RunResult {
return success(Promise.resolve(value));
}
@@ -319,7 +319,7 @@ export const Commands = [
);
return success(
- finished.then(async ([resp]) => {
+ finished.then(async ([resp]): Promise => {
if (!resp?.continue) return;
await upgradeRoom(room, args, resp.invite);
}),
@@ -338,7 +338,7 @@ export const Commands = [
runFn: function (roomId, args) {
if (args) {
return success(
- (async () => {
+ (async (): Promise => {
const unixTimestamp = Date.parse(args);
if (!unixTimestamp) {
throw newTranslatableError(
@@ -501,7 +501,9 @@ export const Commands = [
? ContentHelpers.parseTopicContent(content)
: { text: _t("This room has no topic.") };
- const ref = (e) => e && linkifyElement(e);
+ const ref = (e): void => {
+ if (e) linkifyElement(e);
+ };
const body = topicToHtml(topic.text, topic.html, ref, true);
Modal.createDialog(InfoDialog, {
@@ -1028,7 +1030,7 @@ export const Commands = [
const fingerprint = matches[3];
return success(
- (async () => {
+ (async (): Promise => {
const device = cli.getStoredDevice(userId, deviceId);
if (!device) {
throw newTranslatableError("Unknown (user, session) pair: (%(userId)s, %(deviceId)s)", {
@@ -1205,7 +1207,7 @@ export const Commands = [
},
runFn: (roomId) => {
return success(
- (async () => {
+ (async (): Promise => {
const room = await VoipUserMapper.sharedInstance().getVirtualRoomForRoom(roomId);
if (!room) throw newTranslatableError("No virtual room for this room");
dis.dispatch({
@@ -1231,7 +1233,7 @@ export const Commands = [
}
return success(
- (async () => {
+ (async (): Promise => {
if (isPhoneNumber) {
const results = await LegacyCallHandler.instance.pstnLookup(userId);
if (!results || results.length === 0 || !results[0].userid) {
@@ -1265,7 +1267,7 @@ export const Commands = [
const [userId, msg] = matches.slice(1);
if (userId && userId.startsWith("@") && userId.includes(":")) {
return success(
- (async () => {
+ (async (): Promise => {
const cli = MatrixClientPeg.get();
const roomId = await ensureDMExists(cli, userId);
dis.dispatch({
diff --git a/src/SlidingSyncManager.ts b/src/SlidingSyncManager.ts
index f9ce5ed2a4..4a6c113253 100644
--- a/src/SlidingSyncManager.ts
+++ b/src/SlidingSyncManager.ts
@@ -119,12 +119,10 @@ export class SlidingSyncManager {
public slidingSync: SlidingSync;
private client: MatrixClient;
- private listIdToIndex: Record;
private configureDefer: IDeferred;
public constructor() {
- this.listIdToIndex = {};
this.configureDefer = defer();
}
@@ -134,13 +132,18 @@ export class SlidingSyncManager {
public configure(client: MatrixClient, proxyUrl: string): SlidingSync {
this.client = client;
- this.listIdToIndex = {};
// by default use the encrypted subscription as that gets everything, which is a safer
// default than potentially missing member events.
- this.slidingSync = new SlidingSync(proxyUrl, [], ENCRYPTED_SUBSCRIPTION, client, SLIDING_SYNC_TIMEOUT_MS);
+ this.slidingSync = new SlidingSync(
+ proxyUrl,
+ new Map(),
+ ENCRYPTED_SUBSCRIPTION,
+ client,
+ SLIDING_SYNC_TIMEOUT_MS,
+ );
this.slidingSync.addCustomSubscription(UNENCRYPTED_SUBSCRIPTION_NAME, UNENCRYPTED_SUBSCRIPTION);
// set the space list
- this.slidingSync.setList(this.getOrAllocateListIndex(SlidingSyncManager.ListSpaces), {
+ this.slidingSync.setList(SlidingSyncManager.ListSpaces, {
ranges: [[0, 20]],
sort: ["by_name"],
slow_get_all_rooms: true,
@@ -173,47 +176,16 @@ export class SlidingSyncManager {
return this.slidingSync;
}
- public listIdForIndex(index: number): string | null {
- for (const listId in this.listIdToIndex) {
- if (this.listIdToIndex[listId] === index) {
- return listId;
- }
- }
- return null;
- }
-
- /**
- * Allocate or retrieve the list index for an arbitrary list ID. For example SlidingSyncManager.ListSpaces
- * @param listId A string which represents the list.
- * @returns The index to use when registering lists or listening for callbacks.
- */
- public getOrAllocateListIndex(listId: string): number {
- let index = this.listIdToIndex[listId];
- if (index === undefined) {
- // assign next highest index
- index = -1;
- for (const id in this.listIdToIndex) {
- const listIndex = this.listIdToIndex[id];
- if (listIndex > index) {
- index = listIndex;
- }
- }
- index++;
- this.listIdToIndex[listId] = index;
- }
- return index;
- }
-
/**
* Ensure that this list is registered.
- * @param listIndex The list index to register
+ * @param listKey The list key to register
* @param updateArgs The fields to update on the list.
* @returns The complete list request params
*/
- public async ensureListRegistered(listIndex: number, updateArgs: PartialSlidingSyncRequest): Promise {
- logger.debug("ensureListRegistered:::", listIndex, updateArgs);
+ public async ensureListRegistered(listKey: string, updateArgs: PartialSlidingSyncRequest): Promise {
+ logger.debug("ensureListRegistered:::", listKey, updateArgs);
await this.configureDefer.promise;
- let list = this.slidingSync.getList(listIndex);
+ let list = this.slidingSync.getListParams(listKey);
if (!list) {
list = {
ranges: [[0, 20]],
@@ -252,14 +224,14 @@ export class SlidingSyncManager {
try {
// if we only have range changes then call a different function so we don't nuke the list from before
if (updateArgs.ranges && Object.keys(updateArgs).length === 1) {
- await this.slidingSync.setListRanges(listIndex, updateArgs.ranges);
+ await this.slidingSync.setListRanges(listKey, updateArgs.ranges);
} else {
- await this.slidingSync.setList(listIndex, list);
+ await this.slidingSync.setList(listKey, list);
}
} catch (err) {
logger.debug("ensureListRegistered: update failed txn_id=", err);
}
- return this.slidingSync.getList(listIndex);
+ return this.slidingSync.getListParams(listKey)!;
}
public async setRoomVisible(roomId: string, visible: boolean): Promise {
@@ -302,9 +274,8 @@ export class SlidingSyncManager {
* @param batchSize The number of rooms to return in each request.
* @param gapBetweenRequestsMs The number of milliseconds to wait between requests.
*/
- public async startSpidering(batchSize: number, gapBetweenRequestsMs: number) {
+ public async startSpidering(batchSize: number, gapBetweenRequestsMs: number): Promise {
await sleep(gapBetweenRequestsMs); // wait a bit as this is called on first render so let's let things load
- const listIndex = this.getOrAllocateListIndex(SlidingSyncManager.ListSearch);
let startIndex = batchSize;
let hasMore = true;
let firstTime = true;
@@ -316,7 +287,7 @@ export class SlidingSyncManager {
[startIndex, endIndex],
];
if (firstTime) {
- await this.slidingSync.setList(listIndex, {
+ await this.slidingSync.setList(SlidingSyncManager.ListSearch, {
// e.g [0,19] [20,39] then [0,19] [40,59]. We keep [0,20] constantly to ensure
// any changes to the list whilst spidering are caught.
ranges: ranges,
@@ -342,15 +313,17 @@ export class SlidingSyncManager {
},
});
} else {
- await this.slidingSync.setListRanges(listIndex, ranges);
+ await this.slidingSync.setListRanges(SlidingSyncManager.ListSearch, ranges);
}
- // gradually request more over time
- await sleep(gapBetweenRequestsMs);
} catch (err) {
// do nothing, as we reject only when we get interrupted but that's fine as the next
// request will include our data
+ } finally {
+ // gradually request more over time, even on errors.
+ await sleep(gapBetweenRequestsMs);
}
- hasMore = endIndex + 1 < this.slidingSync.getListData(listIndex)?.joinedCount;
+ const listData = this.slidingSync.getListData(SlidingSyncManager.ListSearch)!;
+ hasMore = endIndex + 1 < listData.joinedCount;
startIndex += batchSize;
firstTime = false;
}
diff --git a/src/Terms.ts b/src/Terms.ts
index 101a778a94..bb18a18cf7 100644
--- a/src/Terms.ts
+++ b/src/Terms.ts
@@ -75,7 +75,7 @@ export type TermsInteractionCallback = (
export async function startTermsFlow(
services: Service[],
interactionCallback: TermsInteractionCallback = dialogTermsInteractionCallback,
-) {
+): Promise {
const termsPromises = services.map((s) => MatrixClientPeg.get().getTerms(s.serviceType, s.baseUrl));
/*
@@ -176,7 +176,7 @@ export async function startTermsFlow(
urlsForService,
);
});
- return Promise.all(agreePromises);
+ await Promise.all(agreePromises);
}
export async function dialogTermsInteractionCallback(
diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx
index 8be6cd4a40..7f874f8a89 100644
--- a/src/TextForEvent.tsx
+++ b/src/TextForEvent.tsx
@@ -20,7 +20,8 @@ import { logger } from "matrix-js-sdk/src/logger";
import { removeDirectionOverrideChars } from "matrix-js-sdk/src/utils";
import { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@types/partials";
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
-import { M_POLL_START, M_POLL_END, PollStartEvent } from "matrix-events-sdk";
+import { M_POLL_START, M_POLL_END } from "matrix-js-sdk/src/@types/polls";
+import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
import { _t } from "./languageHandler";
import * as Roles from "./Roles";
@@ -228,7 +229,7 @@ function textForTombstoneEvent(ev: MatrixEvent): () => string | null {
return () => _t("%(senderDisplayName)s upgraded this room.", { senderDisplayName });
}
-const onViewJoinRuleSettingsClick = () => {
+const onViewJoinRuleSettingsClick = (): void => {
defaultDispatcher.dispatch({
action: "open_room_settings",
initial_tab_id: ROOM_SECURITY_TAB,
diff --git a/src/UserActivity.ts b/src/UserActivity.ts
index 0e163564e0..9217aca3c0 100644
--- a/src/UserActivity.ts
+++ b/src/UserActivity.ts
@@ -50,7 +50,7 @@ export default class UserActivity {
this.activeRecentlyTimeout = new Timer(RECENTLY_ACTIVE_THRESHOLD_MS);
}
- public static sharedInstance() {
+ public static sharedInstance(): UserActivity {
if (window.mxUserActivity === undefined) {
window.mxUserActivity = new UserActivity(window, document);
}
@@ -66,7 +66,7 @@ export default class UserActivity {
* later on when the user does become active.
* @param {Timer} timer the timer to use
*/
- public timeWhileActiveNow(timer: Timer) {
+ public timeWhileActiveNow(timer: Timer): void {
this.timeWhile(timer, this.attachedActiveNowTimers);
if (this.userActiveNow()) {
timer.start();
@@ -82,14 +82,14 @@ export default class UserActivity {
* later on when the user does become active.
* @param {Timer} timer the timer to use
*/
- public timeWhileActiveRecently(timer: Timer) {
+ public timeWhileActiveRecently(timer: Timer): void {
this.timeWhile(timer, this.attachedActiveRecentlyTimers);
if (this.userActiveRecently()) {
timer.start();
}
}
- private timeWhile(timer: Timer, attachedTimers: Timer[]) {
+ private timeWhile(timer: Timer, attachedTimers: Timer[]): void {
// important this happens first
const index = attachedTimers.indexOf(timer);
if (index === -1) {
@@ -113,7 +113,7 @@ export default class UserActivity {
/**
* Start listening to user activity
*/
- public start() {
+ public start(): void {
this.document.addEventListener("mousedown", this.onUserActivity);
this.document.addEventListener("mousemove", this.onUserActivity);
this.document.addEventListener("keydown", this.onUserActivity);
@@ -133,7 +133,7 @@ export default class UserActivity {
/**
* Stop tracking user activity
*/
- public stop() {
+ public stop(): void {
this.document.removeEventListener("mousedown", this.onUserActivity);
this.document.removeEventListener("mousemove", this.onUserActivity);
this.document.removeEventListener("keydown", this.onUserActivity);
@@ -152,7 +152,7 @@ export default class UserActivity {
* user's attention at any given moment.
* @returns {boolean} true if user is currently 'active'
*/
- public userActiveNow() {
+ public userActiveNow(): boolean {
return this.activeNowTimeout.isRunning();
}
@@ -164,11 +164,11 @@ export default class UserActivity {
* (or they may have gone to make tea and left the window focused).
* @returns {boolean} true if user has been active recently
*/
- public userActiveRecently() {
+ public userActiveRecently(): boolean {
return this.activeRecentlyTimeout.isRunning();
}
- private onPageVisibilityChanged = (e) => {
+ private onPageVisibilityChanged = (e): void => {
if (this.document.visibilityState === "hidden") {
this.activeNowTimeout.abort();
this.activeRecentlyTimeout.abort();
@@ -177,12 +177,12 @@ export default class UserActivity {
}
};
- private onWindowBlurred = () => {
+ private onWindowBlurred = (): void => {
this.activeNowTimeout.abort();
this.activeRecentlyTimeout.abort();
};
- private onUserActivity = (event: MouseEvent) => {
+ private onUserActivity = (event: MouseEvent): void => {
// ignore anything if the window isn't focused
if (!this.document.hasFocus()) return;
@@ -214,7 +214,7 @@ export default class UserActivity {
}
};
- private static async runTimersUntilTimeout(attachedTimers: Timer[], timeout: Timer) {
+ private static async runTimersUntilTimeout(attachedTimers: Timer[], timeout: Timer): Promise {
attachedTimers.forEach((t) => t.start());
try {
await timeout.finished();
diff --git a/src/accessibility/RovingTabIndex.tsx b/src/accessibility/RovingTabIndex.tsx
index beb6eee004..e90aed87a9 100644
--- a/src/accessibility/RovingTabIndex.tsx
+++ b/src/accessibility/RovingTabIndex.tsx
@@ -87,7 +87,7 @@ interface IAction {
};
}
-export const reducer = (state: IState, action: IAction) => {
+export const reducer: Reducer = (state: IState, action: IAction) => {
switch (action.type) {
case Type.Register: {
if (!state.activeRef) {
diff --git a/src/accessibility/Toolbar.tsx b/src/accessibility/Toolbar.tsx
index 22ef018241..3b5fbb0943 100644
--- a/src/accessibility/Toolbar.tsx
+++ b/src/accessibility/Toolbar.tsx
@@ -26,7 +26,7 @@ interface IProps extends Omit, "onKeyDown"> {}
// https://www.w3.org/TR/wai-aria-practices-1.1/#toolbar
// All buttons passed in children must use RovingTabIndex to set `onFocus`, `isActive`, `ref`
const Toolbar: React.FC = ({ children, ...props }) => {
- const onKeyDown = (ev: React.KeyboardEvent) => {
+ const onKeyDown = (ev: React.KeyboardEvent): void => {
const target = ev.target as HTMLElement;
// Don't interfere with input default keydown behaviour
if (target.tagName === "INPUT") return;
diff --git a/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx b/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx
index 867d3aeaab..ee3a0e4d36 100644
--- a/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx
+++ b/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx
@@ -33,7 +33,7 @@ interface IProps extends React.ComponentProps {
export const StyledMenuItemCheckbox: React.FC = ({ children, label, onChange, onClose, ...props }) => {
const [onFocus, isActive, ref] = useRovingTabIndex();
- const onKeyDown = (e: React.KeyboardEvent) => {
+ const onKeyDown = (e: React.KeyboardEvent): void => {
let handled = true;
const action = getKeyBindingsManager().getAccessibilityAction(e);
@@ -55,7 +55,7 @@ export const StyledMenuItemCheckbox: React.FC = ({ children, label, onCh
e.preventDefault();
}
};
- const onKeyUp = (e: React.KeyboardEvent) => {
+ const onKeyUp = (e: React.KeyboardEvent): void => {
const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) {
case KeyBindingAction.Space:
diff --git a/src/accessibility/context_menu/StyledMenuItemRadio.tsx b/src/accessibility/context_menu/StyledMenuItemRadio.tsx
index 6bbf5a1106..2fe8738434 100644
--- a/src/accessibility/context_menu/StyledMenuItemRadio.tsx
+++ b/src/accessibility/context_menu/StyledMenuItemRadio.tsx
@@ -33,7 +33,7 @@ interface IProps extends React.ComponentProps {
export const StyledMenuItemRadio: React.FC = ({ children, label, onChange, onClose, ...props }) => {
const [onFocus, isActive, ref] = useRovingTabIndex();
- const onKeyDown = (e: React.KeyboardEvent) => {
+ const onKeyDown = (e: React.KeyboardEvent): void => {
let handled = true;
const action = getKeyBindingsManager().getAccessibilityAction(e);
@@ -55,7 +55,7 @@ export const StyledMenuItemRadio: React.FC = ({ children, label, onChang
e.preventDefault();
}
};
- const onKeyUp = (e: React.KeyboardEvent) => {
+ const onKeyUp = (e: React.KeyboardEvent): void => {
const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) {
case KeyBindingAction.Enter:
diff --git a/src/actions/actionCreators.ts b/src/actions/actionCreators.ts
index 0341f03cac..b6eb263fb9 100644
--- a/src/actions/actionCreators.ts
+++ b/src/actions/actionCreators.ts
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { AsyncActionPayload } from "../dispatcher/payloads";
+import { AsyncActionFn, AsyncActionPayload } from "../dispatcher/payloads";
/**
* Create an action thunk that will dispatch actions indicating the current
@@ -45,7 +45,7 @@ import { AsyncActionPayload } from "../dispatcher/payloads";
* `fn`.
*/
export function asyncAction(id: string, fn: () => Promise, pendingFn: () => any | null): AsyncActionPayload {
- const helper = (dispatch) => {
+ const helper: AsyncActionFn = (dispatch) => {
dispatch({
action: id + ".pending",
request: typeof pendingFn === "function" ? pendingFn() : undefined,
diff --git a/src/actions/handlers/viewUserDeviceSettings.ts b/src/actions/handlers/viewUserDeviceSettings.ts
index e1dc7b3f26..4525ba104d 100644
--- a/src/actions/handlers/viewUserDeviceSettings.ts
+++ b/src/actions/handlers/viewUserDeviceSettings.ts
@@ -22,7 +22,7 @@ import defaultDispatcher from "../../dispatcher/dispatcher";
* Redirect to the correct device manager section
* Based on the labs setting
*/
-export const viewUserDeviceSettings = (isNewDeviceManagerEnabled: boolean) => {
+export const viewUserDeviceSettings = (isNewDeviceManagerEnabled: boolean): void => {
defaultDispatcher.dispatch({
action: Action.ViewUserSettings,
initialTabId: isNewDeviceManagerEnabled ? UserTab.SessionManager : UserTab.Security,
diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx
index 98bbb55069..63a132077f 100644
--- a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx
+++ b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx
@@ -56,7 +56,7 @@ export default class ManageEventIndexDialog extends React.Component {
+ public updateCurrentRoom = async (room): Promise => {
const eventIndex = EventIndexPeg.get();
let stats;
@@ -131,17 +131,17 @@ export default class ManageEventIndexDialog extends React.Component {
+ private onDisable = async (): Promise => {
const DisableEventIndexDialog = (await import("./DisableEventIndexDialog")).default;
Modal.createDialog(DisableEventIndexDialog, null, null, /* priority = */ false, /* static = */ true);
};
- private onCrawlerSleepTimeChange = (e) => {
+ private onCrawlerSleepTimeChange = (e): void => {
this.setState({ crawlerSleepTime: e.target.value });
SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value);
};
- public render() {
+ public render(): JSX.Element {
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 f9df9b09a4..a75b41f602 100644
--- a/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx
+++ b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx
@@ -125,7 +125,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
+ await accessSecretStorage(async (): Promise => {
info = await MatrixClientPeg.get().prepareKeyBackupVersion(null /* random key */, {
secureSecretStorage: true,
});
diff --git a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx
index 0c976ec599..b595a60a2e 100644
--- a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx
+++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx
@@ -350,7 +350,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent this.recoveryKey,
keyBackupInfo: this.state.backupInfo,
setupNewKeyBackup: !this.state.backupInfo,
- getKeyBackupPassphrase: async () => {
+ getKeyBackupPassphrase: async (): Promise => {
// We may already have the backup key if we earlier went
// through the restore backup path, so pass it along
// rather than prompting again.
@@ -383,7 +383,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent => {
// It's possible we'll need the backup key later on for bootstrapping,
// so let's stash it here, rather than prompting for it twice.
- const keyCallback = (k) => (this.backupKey = k);
+ const keyCallback = (k: Uint8Array): void => {
+ this.backupKey = k;
+ };
const { finished } = Modal.createDialog(
RestoreKeyBackupDialog,
@@ -420,7 +422,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
+ private onPassPhraseNextClick = async (e: React.FormEvent): Promise => {
e.preventDefault();
if (!this.passphraseField.current) return; // unmounting
@@ -434,7 +436,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
+ private onPassPhraseConfirmNextClick = async (e: React.FormEvent): Promise => {
e.preventDefault();
if (this.state.passPhrase !== this.state.passPhraseConfirm) return;
diff --git a/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx
index 1998c7c7ed..c8a561e7da 100644
--- a/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx
+++ b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx
@@ -121,7 +121,7 @@ export default class ExportE2eKeysDialog extends React.Component
return false;
};
- private onPassphraseChange = (ev: React.ChangeEvent, phrase: AnyPassphrase) => {
+ private onPassphraseChange = (ev: React.ChangeEvent, phrase: AnyPassphrase): void => {
this.setState({
[phrase]: ev.target.value,
} as Pick);
diff --git a/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx
index b546ffc91a..079271b021 100644
--- a/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx
+++ b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx
@@ -91,7 +91,7 @@ export default class ImportE2eKeysDialog extends React.Component
return false;
};
- private startImport(file: File, passphrase: string) {
+ private startImport(file: File, passphrase: string): Promise {
this.setState({
errStr: null,
phase: Phase.Importing,
diff --git a/src/audio/ManagedPlayback.ts b/src/audio/ManagedPlayback.ts
index 5db07671f1..c33d032b68 100644
--- a/src/audio/ManagedPlayback.ts
+++ b/src/audio/ManagedPlayback.ts
@@ -30,7 +30,7 @@ export class ManagedPlayback extends Playback {
return super.play();
}
- public destroy() {
+ public destroy(): void {
this.manager.destroyPlaybackInstance(this);
super.destroy();
}
diff --git a/src/audio/Playback.ts b/src/audio/Playback.ts
index d5971ad73c..e1ab1a1c59 100644
--- a/src/audio/Playback.ts
+++ b/src/audio/Playback.ts
@@ -145,7 +145,7 @@ export class Playback extends EventEmitter implements IDestroyable, PlaybackInte
return true; // we don't ever care if the event had listeners, so just return "yes"
}
- public destroy() {
+ public destroy(): void {
// Dev note: It's critical that we call stop() during cleanup to ensure that downstream callers
// are aware of the final clock position before the user triggered an unload.
// noinspection JSIgnoredPromiseFromCall - not concerned about being called async here
@@ -159,7 +159,7 @@ export class Playback extends EventEmitter implements IDestroyable, PlaybackInte
}
}
- public async prepare() {
+ public async prepare(): Promise {
// don't attempt to decode the media again
// AudioContext.decodeAudioData detaches the array buffer `this.buf`
// meaning it cannot be re-read
@@ -190,7 +190,7 @@ export class Playback extends EventEmitter implements IDestroyable, PlaybackInte
this.context.decodeAudioData(
this.buf,
(b) => resolve(b),
- async (e) => {
+ async (e): Promise => {
try {
// This error handler is largely for Safari as well, which doesn't support Opus/Ogg
// very well.
@@ -232,12 +232,12 @@ export class Playback extends EventEmitter implements IDestroyable, PlaybackInte
this.emit(PlaybackState.Stopped); // signal that we're not decoding anymore
}
- private onPlaybackEnd = async () => {
+ private onPlaybackEnd = async (): Promise => {
await this.context.suspend();
this.emit(PlaybackState.Stopped);
};
- public async play() {
+ public async play(): Promise {
// We can't restart a buffer source, so we need to create a new one if we hit the end
if (this.state === PlaybackState.Stopped) {
this.disconnectSource();
@@ -256,13 +256,13 @@ export class Playback extends EventEmitter implements IDestroyable, PlaybackInte
this.emit(PlaybackState.Playing);
}
- private disconnectSource() {
+ private disconnectSource(): void {
if (this.element) return; // leave connected, we can (and must) re-use it
this.source?.disconnect();
this.source?.removeEventListener("ended", this.onPlaybackEnd);
}
- private makeNewSourceBuffer() {
+ private makeNewSourceBuffer(): void {
if (this.element && this.source) return; // leave connected, we can (and must) re-use it
if (this.element) {
@@ -276,22 +276,22 @@ export class Playback extends EventEmitter implements IDestroyable, PlaybackInte
this.source.connect(this.context.destination);
}
- public async pause() {
+ public async pause(): Promise {
await this.context.suspend();
this.emit(PlaybackState.Paused);
}
- public async stop() {
+ public async stop(): Promise {
await this.onPlaybackEnd();
this.clock.flagStop();
}
- public async toggle() {
+ public async toggle(): Promise {
if (this.isPlaying) await this.pause();
else await this.play();
}
- public async skipTo(timeSeconds: number) {
+ public async skipTo(timeSeconds: number): Promise {
// Dev note: this function talks a lot about clock desyncs. There is a clock running
// independently to the audio context and buffer so that accurate human-perceptible
// time can be exposed. The PlaybackClock class has more information, but the short
diff --git a/src/audio/PlaybackClock.ts b/src/audio/PlaybackClock.ts
index 4f2d8f14aa..1a6d1a2e5d 100644
--- a/src/audio/PlaybackClock.ts
+++ b/src/audio/PlaybackClock.ts
@@ -89,7 +89,7 @@ export class PlaybackClock implements IDestroyable {
return this.observable;
}
- private checkTime = (force = false) => {
+ private checkTime = (force = false): void => {
const now = this.timeSeconds; // calculated dynamically
if (this.lastCheck !== now || force) {
this.observable.update([now, this.durationSeconds]);
@@ -102,7 +102,7 @@ export class PlaybackClock implements IDestroyable {
* The placeholders will be overridden once known.
* @param {MatrixEvent} event The event to use for placeholders.
*/
- public populatePlaceholdersFrom(event: MatrixEvent) {
+ public populatePlaceholdersFrom(event: MatrixEvent): void {
const durationMs = Number(event.getContent()["info"]?.["duration"]);
if (Number.isFinite(durationMs)) this.placeholderDuration = durationMs / 1000;
}
@@ -112,11 +112,11 @@ export class PlaybackClock implements IDestroyable {
* This is to ensure the clock isn't skewed into thinking it is ~0.5s into
* a clip when the duration is set.
*/
- public flagLoadTime() {
+ public flagLoadTime(): void {
this.clipStart = this.context.currentTime;
}
- public flagStart() {
+ public flagStart(): void {
if (this.stopped) {
this.clipStart = this.context.currentTime;
this.stopped = false;
@@ -128,7 +128,7 @@ export class PlaybackClock implements IDestroyable {
}
}
- public flagStop() {
+ public flagStop(): void {
this.stopped = true;
// Reset the clock time now so that the update going out will trigger components
@@ -136,13 +136,13 @@ export class PlaybackClock implements IDestroyable {
this.clipStart = this.context.currentTime;
}
- public syncTo(contextTime: number, clipTime: number) {
+ public syncTo(contextTime: number, clipTime: number): void {
this.clipStart = contextTime - clipTime;
this.stopped = false; // count as a mid-stream pause (if we were stopped)
this.checkTime(true);
}
- public destroy() {
+ public destroy(): void {
this.observable.close();
if (this.timerId) clearInterval(this.timerId);
}
diff --git a/src/audio/PlaybackManager.ts b/src/audio/PlaybackManager.ts
index 01a597d58f..0cc52e7f0e 100644
--- a/src/audio/PlaybackManager.ts
+++ b/src/audio/PlaybackManager.ts
@@ -38,13 +38,13 @@ export class PlaybackManager {
* instances are paused.
* @param playback Optional. The playback to leave untouched.
*/
- public pauseAllExcept(playback?: Playback) {
+ public pauseAllExcept(playback?: Playback): void {
this.instances
.filter((p) => p !== playback && p.currentState === PlaybackState.Playing)
.forEach((p) => p.pause());
}
- public destroyPlaybackInstance(playback: ManagedPlayback) {
+ public destroyPlaybackInstance(playback: ManagedPlayback): void {
this.instances = this.instances.filter((p) => p !== playback);
}
diff --git a/src/audio/PlaybackQueue.ts b/src/audio/PlaybackQueue.ts
index 1ea8a85fa6..7c521b9ca6 100644
--- a/src/audio/PlaybackQueue.ts
+++ b/src/audio/PlaybackQueue.ts
@@ -75,28 +75,28 @@ export class PlaybackQueue {
return queue;
}
- private persistClocks() {
+ private persistClocks(): void {
localStorage.setItem(
`mx_voice_message_clocks_${this.room.roomId}`,
JSON.stringify(Array.from(this.clockStates.entries())),
);
}
- private loadClocks() {
+ private loadClocks(): void {
const val = localStorage.getItem(`mx_voice_message_clocks_${this.room.roomId}`);
if (!!val) {
this.clockStates = new Map(JSON.parse(val));
}
}
- public unsortedEnqueue(mxEvent: MatrixEvent, playback: Playback) {
+ public unsortedEnqueue(mxEvent: MatrixEvent, playback: Playback): void {
// We don't ever detach our listeners: we expect the Playback to clean up for us
this.playbacks.set(mxEvent.getId(), playback);
playback.on(UPDATE_EVENT, (state) => this.onPlaybackStateChange(playback, mxEvent, state));
playback.clockInfo.liveData.onUpdate((clock) => this.onPlaybackClock(playback, mxEvent, clock));
}
- private onPlaybackStateChange(playback: Playback, mxEvent: MatrixEvent, newState: PlaybackState) {
+ private onPlaybackStateChange(playback: Playback, mxEvent: MatrixEvent, newState: PlaybackState): void {
// Remember where the user got to in playback
const wasLastPlaying = this.currentPlaybackId === mxEvent.getId();
if (newState === PlaybackState.Stopped && this.clockStates.has(mxEvent.getId()) && !wasLastPlaying) {
@@ -210,7 +210,7 @@ export class PlaybackQueue {
}
}
- private onPlaybackClock(playback: Playback, mxEvent: MatrixEvent, clocks: number[]) {
+ private onPlaybackClock(playback: Playback, mxEvent: MatrixEvent, clocks: number[]): void {
if (playback.currentState === PlaybackState.Decoding) return; // ignore pre-ready values
if (playback.currentState !== PlaybackState.Stopped) {
diff --git a/src/audio/RecorderWorklet.ts b/src/audio/RecorderWorklet.ts
index 5079ec58a9..0c0cc56cd6 100644
--- a/src/audio/RecorderWorklet.ts
+++ b/src/audio/RecorderWorklet.ts
@@ -43,7 +43,7 @@ class MxVoiceWorklet extends AudioWorkletProcessor {
private nextAmplitudeSecond = 0;
private amplitudeIndex = 0;
- public process(inputs, outputs, parameters) {
+ public process(inputs, outputs, parameters): boolean {
const currentSecond = roundTimeToTargetFreq(currentTime);
// We special case the first ping because there's a fairly good chance that we'll miss the zeroth
// update. Firefox for instance takes 0.06 seconds (roughly) to call this function for the first
diff --git a/src/audio/VoiceMessageRecording.ts b/src/audio/VoiceMessageRecording.ts
index f27fc36135..7d5c491261 100644
--- a/src/audio/VoiceMessageRecording.ts
+++ b/src/audio/VoiceMessageRecording.ts
@@ -141,7 +141,7 @@ export class VoiceMessageRecording implements IDestroyable {
this.voiceRecording.destroy();
}
- private onDataAvailable = (data: ArrayBuffer) => {
+ private onDataAvailable = (data: ArrayBuffer): void => {
const buf = new Uint8Array(data);
this.buffer = concat(this.buffer, buf);
};
@@ -153,6 +153,6 @@ export class VoiceMessageRecording implements IDestroyable {
}
}
-export const createVoiceMessageRecording = (matrixClient: MatrixClient) => {
+export const createVoiceMessageRecording = (matrixClient: MatrixClient): VoiceMessageRecording => {
return new VoiceMessageRecording(matrixClient, new VoiceRecording());
};
diff --git a/src/audio/VoiceRecording.ts b/src/audio/VoiceRecording.ts
index 20434d998d..32fcb5a97a 100644
--- a/src/audio/VoiceRecording.ts
+++ b/src/audio/VoiceRecording.ts
@@ -110,7 +110,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
return !MediaDeviceHandler.getAudioNoiseSuppression();
}
- private async makeRecorder() {
+ private async makeRecorder(): Promise {
try {
this.recorderStream = await navigator.mediaDevices.getUserMedia({
audio: {
@@ -212,14 +212,14 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
return !!Recorder.isRecordingSupported();
}
- private onAudioProcess = (ev: AudioProcessingEvent) => {
+ private onAudioProcess = (ev: AudioProcessingEvent): void => {
this.processAudioUpdate(ev.playbackTime);
// We skip the functionality of the worklet regarding waveform calculations: we
// should get that information pretty quick during the playback info.
};
- private processAudioUpdate = (timeSeconds: number) => {
+ private processAudioUpdate = (timeSeconds: number): void => {
if (!this.recording) return;
this.observable.update({
@@ -260,7 +260,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
/**
* {@link https://github.com/chris-rudmin/opus-recorder#instance-fields ref for recorderSeconds}
*/
- public get recorderSeconds() {
+ public get recorderSeconds(): number {
return this.recorder.encodedSamplePosition / 48000;
}
@@ -279,7 +279,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
}
public async stop(): Promise {
- return Singleflight.for(this, "stop").do(async () => {
+ return Singleflight.for(this, "stop").do(async (): Promise => {
if (!this.recording) {
throw new Error("No recording to stop");
}
@@ -307,7 +307,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
});
}
- public destroy() {
+ public destroy(): void {
// noinspection JSIgnoredPromiseFromCall - not concerned about stop() being called async here
this.stop();
this.removeAllListeners();
diff --git a/src/autocomplete/AutocompleteProvider.tsx b/src/autocomplete/AutocompleteProvider.tsx
index e76c4f1903..546e052f58 100644
--- a/src/autocomplete/AutocompleteProvider.tsx
+++ b/src/autocomplete/AutocompleteProvider.tsx
@@ -22,7 +22,7 @@ import { TimelineRenderingType } from "../contexts/RoomContext";
import type { ICompletion, ISelectionRange } from "./Autocompleter";
export interface ICommand {
- command: string | null;
+ command: RegExpExecArray | null;
range: {
start: number;
end: number;
@@ -59,7 +59,7 @@ export default abstract class AutocompleteProvider {
}
}
- public destroy() {
+ public destroy(): void {
// stub
}
@@ -70,7 +70,7 @@ export default abstract class AutocompleteProvider {
* @param {boolean} force True if the user is forcing completion
* @return {object} { command, range } where both objects fields are null if no match
*/
- public getCurrentCommand(query: string, selection: ISelectionRange, force = false) {
+ public getCurrentCommand(query: string, selection: ISelectionRange, force = false): ICommand {
let commandRegex = this.commandRegex;
if (force && this.shouldForceComplete()) {
@@ -83,7 +83,7 @@ export default abstract class AutocompleteProvider {
commandRegex.lastIndex = 0;
- let match;
+ let match: RegExpExecArray;
while ((match = commandRegex.exec(query)) !== null) {
const start = match.index;
const end = start + match[0].length;
diff --git a/src/autocomplete/Autocompleter.ts b/src/autocomplete/Autocompleter.ts
index 67a40db158..b609f265f1 100644
--- a/src/autocomplete/Autocompleter.ts
+++ b/src/autocomplete/Autocompleter.ts
@@ -69,7 +69,7 @@ export default class Autocompleter {
});
}
- public destroy() {
+ public destroy(): void {
this.providers.forEach((p) => {
p.destroy();
});
@@ -88,7 +88,7 @@ export default class Autocompleter {
*/
// list of results from each provider, each being a list of completions or null if it times out
const completionsList: ICompletion[][] = await Promise.all(
- this.providers.map(async (provider) => {
+ this.providers.map(async (provider): Promise => {
return timeout(
provider.getCompletions(query, selection, force, limit),
null,
diff --git a/src/autocomplete/CommandProvider.tsx b/src/autocomplete/CommandProvider.tsx
index 68850a9a15..caafe98f08 100644
--- a/src/autocomplete/CommandProvider.tsx
+++ b/src/autocomplete/CommandProvider.tsx
@@ -100,7 +100,7 @@ export default class CommandProvider extends AutocompleteProvider {
});
}
- public getName() {
+ public getName(): string {
return "*️⃣ " + _t("Commands");
}
diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx
index 821edb4a3e..cc25068db8 100644
--- a/src/autocomplete/EmojiProvider.tsx
+++ b/src/autocomplete/EmojiProvider.tsx
@@ -55,7 +55,7 @@ const SORTED_EMOJI: ISortedEmoji[] = EMOJI.sort((a, b) => {
_orderBy: index,
}));
-function score(query, space) {
+function score(query: string, space: string): number {
const index = space.indexOf(query);
if (index === -1) {
return Infinity;
@@ -154,7 +154,7 @@ export default class EmojiProvider extends AutocompleteProvider {
return [];
}
- public getName() {
+ public getName(): string {
return "😃 " + _t("Emoji");
}
diff --git a/src/autocomplete/NotifProvider.tsx b/src/autocomplete/NotifProvider.tsx
index 28f01c178a..5efe0e86f6 100644
--- a/src/autocomplete/NotifProvider.tsx
+++ b/src/autocomplete/NotifProvider.tsx
@@ -65,7 +65,7 @@ export default class NotifProvider extends AutocompleteProvider {
return [];
}
- public getName() {
+ public getName(): string {
return "❗️ " + _t("Room Notification");
}
diff --git a/src/autocomplete/QueryMatcher.ts b/src/autocomplete/QueryMatcher.ts
index 23545075bc..1f7b5a5a7f 100644
--- a/src/autocomplete/QueryMatcher.ts
+++ b/src/autocomplete/QueryMatcher.ts
@@ -61,7 +61,7 @@ export default class QueryMatcher {
}
}
- public setObjects(objects: T[]) {
+ public setObjects(objects: T[]): void {
this._items = new Map();
for (const object of objects) {
diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx
index a225676898..bf102d55bc 100644
--- a/src/autocomplete/RoomProvider.tsx
+++ b/src/autocomplete/RoomProvider.tsx
@@ -37,7 +37,15 @@ function canonicalScore(displayedAlias: string, room: Room): number {
return displayedAlias === room.getCanonicalAlias() ? 0 : 1;
}
-function matcherObject(room: Room, displayedAlias: string, matchName = "") {
+function matcherObject(
+ room: Room,
+ displayedAlias: string,
+ matchName = "",
+): {
+ room: Room;
+ matchName: string;
+ displayedAlias: string;
+} {
return {
room,
matchName,
@@ -46,7 +54,7 @@ function matcherObject(room: Room, displayedAlias: string, matchName = "") {
}
export default class RoomProvider extends AutocompleteProvider {
- protected matcher: QueryMatcher;
+ protected matcher: QueryMatcher>;
public constructor(room: Room, renderingType?: TimelineRenderingType) {
super({ commandRegex: ROOM_REGEX, renderingType });
@@ -55,7 +63,7 @@ export default class RoomProvider extends AutocompleteProvider {
});
}
- protected getRooms() {
+ protected getRooms(): Room[] {
const cli = MatrixClientPeg.get();
// filter out spaces here as they get their own autocomplete provider
@@ -68,7 +76,6 @@ export default class RoomProvider extends AutocompleteProvider {
force = false,
limit = -1,
): Promise {
- let completions = [];
const { command, range } = this.getCurrentCommand(query, selection, force);
if (command) {
// the only reason we need to do this is because Fuse only matches on properties
@@ -96,15 +103,15 @@ export default class RoomProvider extends AutocompleteProvider {
this.matcher.setObjects(matcherObjects);
const matchedString = command[0];
- completions = this.matcher.match(matchedString, limit);
+ let completions = this.matcher.match(matchedString, limit);
completions = sortBy(completions, [
(c) => canonicalScore(c.displayedAlias, c.room),
(c) => c.displayedAlias.length,
]);
completions = uniqBy(completions, (match) => match.room);
- completions = completions
- .map((room) => {
- return {
+ return completions
+ .map(
+ (room): ICompletion => ({
completion: room.displayedAlias,
completionId: room.room.roomId,
type: "room",
@@ -116,14 +123,14 @@ export default class RoomProvider extends AutocompleteProvider {
),
range,
- };
- })
+ }),
+ )
.filter((completion) => !!completion.completion && completion.completion.length > 0);
}
- return completions;
+ return [];
}
- public getName() {
+ public getName(): string {
return _t("Rooms");
}
diff --git a/src/autocomplete/SpaceProvider.tsx b/src/autocomplete/SpaceProvider.tsx
index 14f9e2c375..bef3b57354 100644
--- a/src/autocomplete/SpaceProvider.tsx
+++ b/src/autocomplete/SpaceProvider.tsx
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { Room } from "matrix-js-sdk/src/models/room";
import React from "react";
import { _t } from "../languageHandler";
@@ -21,13 +22,13 @@ import { MatrixClientPeg } from "../MatrixClientPeg";
import RoomProvider from "./RoomProvider";
export default class SpaceProvider extends RoomProvider {
- protected getRooms() {
+ protected getRooms(): Room[] {
return MatrixClientPeg.get()
.getVisibleRooms()
.filter((r) => r.isSpaceRoom());
}
- public getName() {
+ public getName(): string {
return _t("Spaces");
}
diff --git a/src/autocomplete/UserProvider.tsx b/src/autocomplete/UserProvider.tsx
index 551a7bc141..65de4b1bb4 100644
--- a/src/autocomplete/UserProvider.tsx
+++ b/src/autocomplete/UserProvider.tsx
@@ -64,7 +64,7 @@ export default class UserProvider extends AutocompleteProvider {
MatrixClientPeg.get().on(RoomStateEvent.Update, this.onRoomStateUpdate);
}
- public destroy() {
+ public destroy(): void {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener(RoomEvent.Timeline, this.onRoomTimeline);
MatrixClientPeg.get().removeListener(RoomStateEvent.Update, this.onRoomStateUpdate);
@@ -77,7 +77,7 @@ export default class UserProvider extends AutocompleteProvider {
toStartOfTimeline: boolean,
removed: boolean,
data: IRoomTimelineData,
- ) => {
+ ): void => {
if (!room) return; // notification timeline, we'll get this event again with a room specific timeline
if (removed) return;
if (room.roomId !== this.room.roomId) return;
@@ -93,7 +93,7 @@ export default class UserProvider extends AutocompleteProvider {
this.onUserSpoke(ev.sender);
};
- private onRoomStateUpdate = (state: RoomState) => {
+ private onRoomStateUpdate = (state: RoomState): void => {
// ignore updates in other rooms
if (state.roomId !== this.room.roomId) return;
@@ -150,7 +150,7 @@ export default class UserProvider extends AutocompleteProvider {
return _t("Users");
}
- private makeUsers() {
+ private makeUsers(): void {
const events = this.room.getLiveTimeline().getEvents();
const lastSpoken = {};
@@ -167,7 +167,7 @@ export default class UserProvider extends AutocompleteProvider {
this.matcher.setObjects(this.users);
}
- public onUserSpoke(user: RoomMember) {
+ public onUserSpoke(user: RoomMember): void {
if (!this.users) return;
if (!user) return;
if (user.userId === MatrixClientPeg.get().credentials.userId) return;
diff --git a/src/components/structures/AutoHideScrollbar.tsx b/src/components/structures/AutoHideScrollbar.tsx
index 719be59f6c..90fda3fe21 100644
--- a/src/components/structures/AutoHideScrollbar.tsx
+++ b/src/components/structures/AutoHideScrollbar.tsx
@@ -22,7 +22,7 @@ type DynamicHtmlElementProps =
JSX.IntrinsicElements[T] extends HTMLAttributes<{}> ? DynamicElementProps : DynamicElementProps<"div">;
type DynamicElementProps = Partial>;
-export type IProps = DynamicHtmlElementProps & {
+export type IProps = Omit, "onScroll"> & {
element?: T;
className?: string;
onScroll?: (event: Event) => void;
@@ -39,7 +39,7 @@ export default class AutoHideScrollbar ex
public readonly containerRef: React.RefObject = React.createRef();
- public componentDidMount() {
+ public componentDidMount(): void {
if (this.containerRef.current && this.props.onScroll) {
// Using the passive option to not block the main thread
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners
@@ -49,13 +49,13 @@ export default class AutoHideScrollbar ex
this.props.wrappedRef?.(this.containerRef.current);
}
- public componentWillUnmount() {
+ public componentWillUnmount(): void {
if (this.containerRef.current && this.props.onScroll) {
this.containerRef.current.removeEventListener("scroll", this.props.onScroll);
}
}
- public render() {
+ public render(): JSX.Element {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { element, className, onScroll, tabIndex, wrappedRef, children, ...otherProps } = this.props;
diff --git a/src/components/structures/AutocompleteInput.tsx b/src/components/structures/AutocompleteInput.tsx
index 2d09a247c6..9f16211022 100644
--- a/src/components/structures/AutocompleteInput.tsx
+++ b/src/components/structures/AutocompleteInput.tsx
@@ -52,11 +52,11 @@ export const AutocompleteInput: React.FC = ({
const editorContainerRef = useRef(null);
const editorRef = useRef(null);
- const focusEditor = () => {
+ const focusEditor = (): void => {
editorRef?.current?.focus();
};
- const onQueryChange = async (e: ChangeEvent) => {
+ const onQueryChange = async (e: ChangeEvent): Promise => {
const value = e.target.value.trim();
setQuery(value);
@@ -74,11 +74,11 @@ export const AutocompleteInput: React.FC = ({
setSuggestions(matches);
};
- const onClickInputArea = () => {
+ const onClickInputArea = (): void => {
focusEditor();
};
- const onKeyDown = (e: KeyboardEvent) => {
+ const onKeyDown = (e: KeyboardEvent): void => {
const hasModifiers = e.ctrlKey || e.shiftKey || e.metaKey;
// when the field is empty and the user hits backspace remove the right-most target
@@ -87,7 +87,7 @@ export const AutocompleteInput: React.FC = ({
}
};
- const toggleSelection = (completion: ICompletion) => {
+ const toggleSelection = (completion: ICompletion): void => {
const newSelection = [...selection];
const index = selection.findIndex((selection) => selection.completionId === completion.completionId);
@@ -101,7 +101,7 @@ export const AutocompleteInput: React.FC = ({
focusEditor();
};
- const removeSelection = (completion: ICompletion) => {
+ const removeSelection = (completion: ICompletion): void => {
const newSelection = [...selection];
const index = selection.findIndex((selection) => selection.completionId === completion.completionId);
diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx
index 6dbdc4a7eb..978dd07be9 100644
--- a/src/components/structures/ContextMenu.tsx
+++ b/src/components/structures/ContextMenu.tsx
@@ -64,7 +64,7 @@ export enum ChevronFace {
None = "none",
}
-export interface IProps extends IPosition {
+export interface MenuProps extends IPosition {
menuWidth?: number;
menuHeight?: number;
@@ -77,7 +77,9 @@ export interface IProps extends IPosition {
menuPaddingRight?: number;
zIndex?: number;
+}
+export interface IProps extends MenuProps {
// If true, insert an invisible screen-sized element behind the menu that when clicked will close it.
hasBackground?: boolean;
// whether this context menu should be focus managed. If false it must handle itself
@@ -128,21 +130,21 @@ export default class ContextMenu extends React.PureComponent {
this.initialFocus = document.activeElement as HTMLElement;
}
- public componentDidMount() {
+ public componentDidMount(): void {
Modal.on(ModalManagerEvent.Opened, this.onModalOpen);
}
- public componentWillUnmount() {
+ public componentWillUnmount(): void {
Modal.off(ModalManagerEvent.Opened, this.onModalOpen);
// return focus to the thing which had it before us
this.initialFocus.focus();
}
- private onModalOpen = () => {
+ private onModalOpen = (): void => {
this.props.onFinished?.();
};
- private collectContextMenuRect = (element: HTMLDivElement) => {
+ private collectContextMenuRect = (element: HTMLDivElement): void => {
// We don't need to clean up when unmounting, so ignore
if (!element) return;
@@ -159,7 +161,7 @@ export default class ContextMenu extends React.PureComponent {
});
};
- private onContextMenu = (e) => {
+ private onContextMenu = (e: React.MouseEvent): void => {
if (this.props.onFinished) {
this.props.onFinished();
@@ -184,20 +186,20 @@ export default class ContextMenu extends React.PureComponent {
}
};
- private onContextMenuPreventBubbling = (e) => {
+ private onContextMenuPreventBubbling = (e: React.MouseEvent): void => {
// stop propagation so that any context menu handlers don't leak out of this context menu
// but do not inhibit the default browser menu
e.stopPropagation();
};
// Prevent clicks on the background from going through to the component which opened the menu.
- private onFinished = (ev: React.MouseEvent) => {
+ private onFinished = (ev: React.MouseEvent): void => {
ev.stopPropagation();
ev.preventDefault();
this.props.onFinished?.();
};
- private onClick = (ev: React.MouseEvent) => {
+ private onClick = (ev: React.MouseEvent): void => {
// Don't allow clicks to escape the context menu wrapper
ev.stopPropagation();
@@ -208,7 +210,7 @@ export default class ContextMenu extends React.PureComponent {
// We now only handle closing the ContextMenu in this keyDown handler.
// All of the item/option navigation is delegated to RovingTabIndex.
- private onKeyDown = (ev: React.KeyboardEvent) => {
+ private onKeyDown = (ev: React.KeyboardEvent): void => {
ev.stopPropagation(); // prevent keyboard propagating out of the context menu, we're focus-locked
const action = getKeyBindingsManager().getAccessibilityAction(ev);
@@ -243,7 +245,7 @@ export default class ContextMenu extends React.PureComponent {
}
};
- protected renderMenu(hasBackground = this.props.hasBackground) {
+ protected renderMenu(hasBackground = this.props.hasBackground): JSX.Element {
const position: Partial> = {};
const {
top,
@@ -501,17 +503,13 @@ export const toLeftOrRightOf = (elementRect: DOMRect, chevronOffset = 12): ToRig
return toRightOf(elementRect, chevronOffset);
};
-export type AboveLeftOf = IPosition & {
- chevronFace: ChevronFace;
-};
-
// Placement method for to position context menu right-aligned and flowing to the left of elementRect,
// and either above or below: wherever there is more space (maybe this should be aboveOrBelowLeftOf?)
export const aboveLeftOf = (
elementRect: Pick,
chevronFace = ChevronFace.None,
vPadding = 0,
-): AboveLeftOf => {
+): MenuProps => {
const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
const buttonRight = elementRect.right + window.scrollX;
@@ -535,7 +533,7 @@ export const aboveRightOf = (
elementRect: Pick,
chevronFace = ChevronFace.None,
vPadding = 0,
-): AboveLeftOf => {
+): MenuProps => {
const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
const buttonLeft = elementRect.left + window.scrollX;
@@ -555,11 +553,11 @@ export const aboveRightOf = (
// Placement method for to position context menu right-aligned and flowing to the left of elementRect
// and always above elementRect
-export const alwaysAboveLeftOf = (
+export const alwaysMenuProps = (
elementRect: Pick,
chevronFace = ChevronFace.None,
vPadding = 0,
-) => {
+): IPosition & { chevronFace: ChevronFace } => {
const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
const buttonRight = elementRect.right + window.scrollX;
@@ -578,7 +576,7 @@ export const alwaysAboveRightOf = (
elementRect: Pick,
chevronFace = ChevronFace.None,
vPadding = 0,
-) => {
+): IPosition & { chevronFace: ChevronFace } => {
const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
const buttonLeft = elementRect.left + window.scrollX;
@@ -607,12 +605,12 @@ export const useContextMenu = (inputRef?: RefObject
}
const [isOpen, setIsOpen] = useState(false);
- const open = (ev?: SyntheticEvent) => {
+ const open = (ev?: SyntheticEvent): void => {
ev?.preventDefault();
ev?.stopPropagation();
setIsOpen(true);
};
- const close = (ev?: SyntheticEvent) => {
+ const close = (ev?: SyntheticEvent): void => {
ev?.preventDefault();
ev?.stopPropagation();
setIsOpen(false);
@@ -622,8 +620,11 @@ export const useContextMenu = (inputRef?: RefObject
};
// XXX: Deprecated, used only for dynamic Tooltips. Avoid using at all costs.
-export function createMenu(ElementClass, props) {
- const onFinished = function (...args) {
+export function createMenu(
+ ElementClass: typeof React.Component,
+ props: Record,
+): { close: (...args: any[]) => void } {
+ const onFinished = function (...args): void {
ReactDOM.unmountComponentAtNode(getOrCreateContainer());
props?.onFinished?.apply(null, args);
};
diff --git a/src/components/structures/EmbeddedPage.tsx b/src/components/structures/EmbeddedPage.tsx
index d531e4fcc4..e3cacf0114 100644
--- a/src/components/structures/EmbeddedPage.tsx
+++ b/src/components/structures/EmbeddedPage.tsx
@@ -60,7 +60,7 @@ export default class EmbeddedPage extends React.PureComponent {
return sanitizeHtml(_t(s));
}
- private async fetchEmbed() {
+ private async fetchEmbed(): Promise {
let res: Response;
try {
diff --git a/src/components/structures/FileDropTarget.tsx b/src/components/structures/FileDropTarget.tsx
index ce24bb3783..e8a8fa5e28 100644
--- a/src/components/structures/FileDropTarget.tsx
+++ b/src/components/structures/FileDropTarget.tsx
@@ -37,7 +37,7 @@ const FileDropTarget: React.FC = ({ parent, onFileDrop }) => {
useEffect(() => {
if (!parent || parent.ondrop) return;
- const onDragEnter = (ev: DragEvent) => {
+ const onDragEnter = (ev: DragEvent): void => {
ev.stopPropagation();
ev.preventDefault();
@@ -55,7 +55,7 @@ const FileDropTarget: React.FC = ({ parent, onFileDrop }) => {
}));
};
- const onDragLeave = (ev: DragEvent) => {
+ const onDragLeave = (ev: DragEvent): void => {
ev.stopPropagation();
ev.preventDefault();
@@ -65,7 +65,7 @@ const FileDropTarget: React.FC = ({ parent, onFileDrop }) => {
}));
};
- const onDragOver = (ev: DragEvent) => {
+ const onDragOver = (ev: DragEvent): void => {
ev.stopPropagation();
ev.preventDefault();
@@ -79,7 +79,7 @@ const FileDropTarget: React.FC = ({ parent, onFileDrop }) => {
}
};
- const onDrop = (ev: DragEvent) => {
+ const onDrop = (ev: DragEvent): void => {
ev.stopPropagation();
ev.preventDefault();
onFileDrop(ev.dataTransfer);
diff --git a/src/components/structures/FilePanel.tsx b/src/components/structures/FilePanel.tsx
index 6efa4f857a..4390dcf36e 100644
--- a/src/components/structures/FilePanel.tsx
+++ b/src/components/structures/FilePanel.tsx
@@ -223,7 +223,7 @@ class FilePanel extends React.Component {
}
}
- public render() {
+ public render(): JSX.Element {
if (MatrixClientPeg.get().isGuest()) {
return (
diff --git a/src/components/structures/GenericErrorPage.tsx b/src/components/structures/GenericErrorPage.tsx
index 4179abe7fd..4261d9b2f4 100644
--- a/src/components/structures/GenericErrorPage.tsx
+++ b/src/components/structures/GenericErrorPage.tsx
@@ -22,7 +22,7 @@ interface IProps {
}
export default class GenericErrorPage extends React.PureComponent {
- public render() {
+ public render(): JSX.Element {
return (
diff --git a/src/components/structures/HomePage.tsx b/src/components/structures/HomePage.tsx
index 54aa635fe7..13fc132516 100644
--- a/src/components/structures/HomePage.tsx
+++ b/src/components/structures/HomePage.tsx
@@ -33,17 +33,17 @@ import MiniAvatarUploader, { AVATAR_SIZE } from "../views/elements/MiniAvatarUpl
import PosthogTrackers from "../../PosthogTrackers";
import EmbeddedPage from "./EmbeddedPage";
-const onClickSendDm = (ev: ButtonEvent) => {
+const onClickSendDm = (ev: ButtonEvent): void => {
PosthogTrackers.trackInteraction("WebHomeCreateChatButton", ev);
dis.dispatch({ action: "view_create_chat" });
};
-const onClickExplore = (ev: ButtonEvent) => {
+const onClickExplore = (ev: ButtonEvent): void => {
PosthogTrackers.trackInteraction("WebHomeExploreRoomsButton", ev);
dis.fire(Action.ViewRoomDirectory);
};
-const onClickNewRoom = (ev: ButtonEvent) => {
+const onClickNewRoom = (ev: ButtonEvent): void => {
PosthogTrackers.trackInteraction("WebHomeCreateRoomButton", ev);
dis.dispatch({ action: "view_create_room" });
};
@@ -52,12 +52,17 @@ interface IProps {
justRegistered?: boolean;
}
-const getOwnProfile = (userId: string) => ({
+const getOwnProfile = (
+ userId: string,
+): {
+ displayName: string;
+ avatarUrl: string;
+} => ({
displayName: OwnProfileStore.instance.displayName || userId,
avatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(AVATAR_SIZE),
});
-const UserWelcomeTop = () => {
+const UserWelcomeTop: React.FC = () => {
const cli = useContext(MatrixClientContext);
const userId = cli.getUserId();
const [ownProfile, setOwnProfile] = useState(getOwnProfile(userId));
diff --git a/src/components/structures/HostSignupAction.tsx b/src/components/structures/HostSignupAction.tsx
index 7d4652e3ee..757a7360fb 100644
--- a/src/components/structures/HostSignupAction.tsx
+++ b/src/components/structures/HostSignupAction.tsx
@@ -28,7 +28,7 @@ interface IProps {
interface IState {}
export default class HostSignupAction extends React.PureComponent {
- private openDialog = async () => {
+ private openDialog = async (): Promise => {
this.props.onClick?.();
await HostSignupStore.instance.setHostSignupActive(true);
};
diff --git a/src/components/structures/InteractiveAuth.tsx b/src/components/structures/InteractiveAuth.tsx
index b5582323bf..99be8705a4 100644
--- a/src/components/structures/InteractiveAuth.tsx
+++ b/src/components/structures/InteractiveAuth.tsx
@@ -130,7 +130,7 @@ export default class InteractiveAuthComponent extends React.Component {
@@ -155,7 +155,7 @@ export default class InteractiveAuthComponent extends React.Component {
return SettingsStore.getValue("feature_breadcrumbs_v2") ? BreadcrumbsMode.Labs : BreadcrumbsMode.Legacy;
}
- public componentDidMount() {
+ public componentDidMount(): void {
UIStore.instance.trackElementDimensions("ListContainer", this.listContainerRef.current);
UIStore.instance.on("ListContainer", this.refreshStickyHeaders);
// Using the passive option to not block the main thread
@@ -97,7 +97,7 @@ export default class LeftPanel extends React.Component {
this.listContainerRef.current?.addEventListener("scroll", this.onScroll, { passive: true });
}
- public componentWillUnmount() {
+ public componentWillUnmount(): void {
BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate);
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
@@ -112,25 +112,25 @@ export default class LeftPanel extends React.Component {
}
}
- private updateActiveSpace = (activeSpace: SpaceKey) => {
+ private updateActiveSpace = (activeSpace: SpaceKey): void => {
this.setState({ activeSpace });
};
- private onDialPad = () => {
+ private onDialPad = (): void => {
dis.fire(Action.OpenDialPad);
};
- private onExplore = (ev: ButtonEvent) => {
+ private onExplore = (ev: ButtonEvent): void => {
dis.fire(Action.ViewRoomDirectory);
PosthogTrackers.trackInteraction("WebLeftPanelExploreRoomsButton", ev);
};
- private refreshStickyHeaders = () => {
+ private refreshStickyHeaders = (): void => {
if (!this.listContainerRef.current) return; // ignore: no headers to sticky
this.handleStickyHeaders(this.listContainerRef.current);
};
- private onBreadcrumbsUpdate = () => {
+ private onBreadcrumbsUpdate = (): void => {
const newVal = LeftPanel.breadcrumbsMode;
if (newVal !== this.state.showBreadcrumbs) {
this.setState({ showBreadcrumbs: newVal });
@@ -141,7 +141,7 @@ export default class LeftPanel extends React.Component {
}
};
- private handleStickyHeaders(list: HTMLDivElement) {
+ private handleStickyHeaders(list: HTMLDivElement): void {
if (this.isDoingStickyHeaders) return;
this.isDoingStickyHeaders = true;
window.requestAnimationFrame(() => {
@@ -150,7 +150,7 @@ export default class LeftPanel extends React.Component {
});
}
- private doStickyHeaders(list: HTMLDivElement) {
+ private doStickyHeaders(list: HTMLDivElement): void {
const topEdge = list.scrollTop;
const bottomEdge = list.offsetHeight + list.scrollTop;
const sublists = list.querySelectorAll(".mx_RoomSublist:not(.mx_RoomSublist_hidden)");
@@ -282,20 +282,20 @@ export default class LeftPanel extends React.Component {
}
}
- private onScroll = (ev: Event) => {
+ private onScroll = (ev: Event): void => {
const list = ev.target as HTMLDivElement;
this.handleStickyHeaders(list);
};
- private onFocus = (ev: React.FocusEvent) => {
+ private onFocus = (ev: React.FocusEvent): void => {
this.focusedElement = ev.target;
};
- private onBlur = () => {
+ private onBlur = (): void => {
this.focusedElement = null;
};
- private onKeyDown = (ev: React.KeyboardEvent, state?: IRovingTabIndexState) => {
+ private onKeyDown = (ev: React.KeyboardEvent, state?: IRovingTabIndexState): void => {
if (!this.focusedElement) return;
const action = getKeyBindingsManager().getRoomListAction(ev);
diff --git a/src/components/structures/LegacyCallEventGrouper.ts b/src/components/structures/LegacyCallEventGrouper.ts
index 9a4d82a9f8..5365352921 100644
--- a/src/components/structures/LegacyCallEventGrouper.ts
+++ b/src/components/structures/LegacyCallEventGrouper.ts
@@ -142,7 +142,7 @@ export default class LegacyCallEventGrouper extends EventEmitter {
return [...this.events][0]?.getRoomId();
}
- private onSilencedCallsChanged = () => {
+ private onSilencedCallsChanged = (): void => {
const newState = LegacyCallHandler.instance.isCallSilenced(this.callId);
this.emit(LegacyCallEventGrouperEvent.SilencedChanged, newState);
};
@@ -163,20 +163,20 @@ export default class LegacyCallEventGrouper extends EventEmitter {
LegacyCallHandler.instance.placeCall(this.roomId, this.isVoice ? CallType.Voice : CallType.Video);
};
- public toggleSilenced = () => {
+ public toggleSilenced = (): void => {
const silenced = LegacyCallHandler.instance.isCallSilenced(this.callId);
silenced
? LegacyCallHandler.instance.unSilenceCall(this.callId)
: LegacyCallHandler.instance.silenceCall(this.callId);
};
- private setCallListeners() {
+ private setCallListeners(): void {
if (!this.call) return;
this.call.addListener(CallEvent.State, this.setState);
this.call.addListener(CallEvent.LengthChanged, this.onLengthChanged);
}
- private setState = () => {
+ private setState = (): void => {
if (CONNECTING_STATES.includes(this.call?.state)) {
this.state = CallState.Connecting;
} else if (SUPPORTED_STATES.includes(this.call?.state)) {
@@ -190,7 +190,7 @@ export default class LegacyCallEventGrouper extends EventEmitter {
this.emit(LegacyCallEventGrouperEvent.StateChanged, this.state);
};
- private setCall = () => {
+ private setCall = (): void => {
if (this.call) return;
this.call = LegacyCallHandler.instance.getCallById(this.callId);
@@ -198,7 +198,7 @@ export default class LegacyCallEventGrouper extends EventEmitter {
this.setState();
};
- public add(event: MatrixEvent) {
+ public add(event: MatrixEvent): void {
if (this.events.has(event)) return; // nothing to do
this.events.add(event);
this.setCall();
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index 6e18f8a6f7..242bbdc028 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -159,7 +159,7 @@ class LoggedInView extends React.Component {
this.resizeHandler = React.createRef();
}
- public componentDidMount() {
+ public componentDidMount(): void {
document.addEventListener("keydown", this.onNativeKeyDown, false);
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallState, this.onCallState);
@@ -191,7 +191,7 @@ class LoggedInView extends React.Component {
this.refreshBackgroundImage();
}
- public componentWillUnmount() {
+ public componentWillUnmount(): void {
document.removeEventListener("keydown", this.onNativeKeyDown, false);
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.onCallState);
this._matrixClient.removeListener(ClientEvent.AccountData, this.onAccountData);
@@ -221,14 +221,14 @@ class LoggedInView extends React.Component {
this.setState({ backgroundImage });
};
- public canResetTimelineInRoom = (roomId: string) => {
+ public canResetTimelineInRoom = (roomId: string): boolean => {
if (!this._roomView.current) {
return true;
}
return this._roomView.current.canResetTimeline();
};
- private createResizer() {
+ private createResizer(): Resizer {
let panelSize;
let panelCollapsed;
const collapseConfig: ICollapseConfig = {
@@ -268,7 +268,7 @@ class LoggedInView extends React.Component {
return resizer;
}
- private loadResizerPreferences() {
+ private loadResizerPreferences(): void {
let lhsSize = parseInt(window.localStorage.getItem("mx_lhs_size"), 10);
if (isNaN(lhsSize)) {
lhsSize = 350;
@@ -276,13 +276,13 @@ class LoggedInView extends React.Component {
this.resizer.forHandleWithId("lp-resizer").resize(lhsSize);
}
- private onAccountData = (event: MatrixEvent) => {
+ private onAccountData = (event: MatrixEvent): void => {
if (event.getType() === "m.ignored_user_list") {
dis.dispatch({ action: "ignore_state_changed" });
}
};
- private onCompactLayoutChanged = () => {
+ private onCompactLayoutChanged = (): void => {
this.setState({
useCompactLayout: SettingsStore.getValue("useCompactLayout"),
});
@@ -311,13 +311,13 @@ class LoggedInView extends React.Component {
}
};
- private onUsageLimitDismissed = () => {
+ private onUsageLimitDismissed = (): void => {
this.setState({
usageLimitDismissed: true,
});
};
- private calculateServerLimitToast(syncError: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit) {
+ private calculateServerLimitToast(syncError: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit): void {
const error = (syncError?.error as MatrixError)?.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
if (error) {
usageLimitEventContent = (syncError?.error as MatrixError).data as IUsageLimit;
@@ -337,9 +337,9 @@ class LoggedInView extends React.Component {
}
}
- private updateServerNoticeEvents = async () => {
+ private updateServerNoticeEvents = async (): Promise => {
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
- if (!serverNoticeList) return [];
+ if (!serverNoticeList) return;
const events = [];
let pinnedEventTs = 0;
@@ -379,7 +379,7 @@ class LoggedInView extends React.Component {
});
};
- private onPaste = (ev: ClipboardEvent) => {
+ private onPaste = (ev: ClipboardEvent): void => {
const element = ev.target as HTMLElement;
const inputableElement = getInputableElement(element);
if (inputableElement === document.activeElement) return; // nothing to do
@@ -422,13 +422,13 @@ class LoggedInView extends React.Component {
We also listen with a native listener on the document to get keydown events when no element is focused.
Bubbling is irrelevant here as the target is the body element.
*/
- private onReactKeyDown = (ev) => {
+ private onReactKeyDown = (ev): void => {
// events caught while bubbling up on the root element
// of this component, so something must be focused.
this.onKeyDown(ev);
};
- private onNativeKeyDown = (ev) => {
+ private onNativeKeyDown = (ev): void => {
// only pass this if there is no focused element.
// if there is, onKeyDown will be called by the
// react keydown handler that respects the react bubbling order.
@@ -437,7 +437,7 @@ class LoggedInView extends React.Component {
}
};
- private onKeyDown = (ev) => {
+ private onKeyDown = (ev): void => {
let handled = false;
const roomAction = getKeyBindingsManager().getRoomAction(ev);
@@ -615,13 +615,13 @@ class LoggedInView extends React.Component {
* dispatch a page-up/page-down/etc to the appropriate component
* @param {Object} ev The key event
*/
- private onScrollKeyPressed = (ev) => {
+ private onScrollKeyPressed = (ev): void => {
if (this._roomView.current) {
this._roomView.current.handleScrollKey(ev);
}
};
- public render() {
+ public render(): JSX.Element {
let pageElement;
switch (this.props.page_type) {
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index 536626f270..429adb6f50 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -134,7 +134,7 @@ import { ValidatedServerConfig } from "../../utils/ValidatedServerConfig";
import { isLocalRoom } from "../../utils/localRoom/isLocalRoom";
import { SdkContextClass, SDKContext } from "../../contexts/SDKContext";
import { viewUserDeviceSettings } from "../../actions/handlers/viewUserDeviceSettings";
-import { VoiceBroadcastResumer } from "../../voice-broadcast";
+import { cleanUpBroadcasts, VoiceBroadcastResumer } from "../../voice-broadcast";
import GenericToast from "../views/toasts/GenericToast";
import { Linkify } from "../views/elements/Linkify";
import RovingSpotlightDialog, { Filter } from "../views/dialogs/spotlight/SpotlightDialog";
@@ -216,7 +216,7 @@ export default class MatrixChat extends React.PureComponent {
realQueryParams: {},
startingFragmentQueryParams: {},
config: {},
- onTokenLoginCompleted: () => {},
+ onTokenLoginCompleted: (): void => {},
};
private firstSyncComplete = false;
@@ -317,7 +317,7 @@ export default class MatrixChat extends React.PureComponent {
this.props.realQueryParams,
this.props.defaultDeviceDisplayName,
this.getFragmentAfterLogin(),
- ).then(async (loggedIn) => {
+ ).then(async (loggedIn): Promise => {
if (this.props.realQueryParams?.loginToken) {
// remove the loginToken from the URL regardless
this.props.onTokenLoginCompleted();
@@ -353,7 +353,7 @@ export default class MatrixChat extends React.PureComponent {
initSentry(SdkConfig.get("sentry"));
}
- private async postLoginSetup() {
+ private async postLoginSetup(): Promise {
const cli = MatrixClientPeg.get();
const cryptoEnabled = cli.isCryptoEnabled();
if (!cryptoEnabled) {
@@ -367,7 +367,7 @@ export default class MatrixChat extends React.PureComponent {
// as a proxy to figure out if it's worth prompting the user to verify
// from another device.
promisesList.push(
- (async () => {
+ (async (): Promise => {
crossSigningIsSetUp = await cli.userHasCrossSigningKeys();
})(),
);
@@ -417,7 +417,7 @@ export default class MatrixChat extends React.PureComponent {
window.addEventListener("resize", this.onWindowResized);
}
- public componentDidUpdate(prevProps, prevState) {
+ public componentDidUpdate(prevProps, prevState): void {
if (this.shouldTrackPageChange(prevState, this.state)) {
const durationMs = this.stopPageChangeTimer();
PosthogTrackers.instance.trackPageChange(this.state.view, this.state.page_type, durationMs);
@@ -428,7 +428,7 @@ export default class MatrixChat extends React.PureComponent {
}
}
- public componentWillUnmount() {
+ public componentWillUnmount(): void {
Lifecycle.stopMatrixClient();
dis.unregister(this.dispatcherRef);
this.themeWatcher.stop();
@@ -477,7 +477,7 @@ export default class MatrixChat extends React.PureComponent {
}
}
- private getServerProperties() {
+ private getServerProperties(): { serverConfig: ValidatedServerConfig } {
let props = this.state.serverConfig;
if (!props) props = this.props.serverConfig; // for unit tests
if (!props) props = SdkConfig.get("validated_server_config");
@@ -513,11 +513,11 @@ export default class MatrixChat extends React.PureComponent {
// to try logging out.
}
- private startPageChangeTimer() {
+ private startPageChangeTimer(): void {
PerformanceMonitor.instance.start(PerformanceEntryNames.PAGE_CHANGE);
}
- private stopPageChangeTimer() {
+ private stopPageChangeTimer(): number | null {
const perfMonitor = PerformanceMonitor.instance;
perfMonitor.stop(PerformanceEntryNames.PAGE_CHANGE);
@@ -591,9 +591,10 @@ export default class MatrixChat extends React.PureComponent {
break;
case "logout":
LegacyCallHandler.instance.hangupAllCalls();
- Promise.all([...CallStore.instance.activeCalls].map((call) => call.disconnect())).finally(() =>
- Lifecycle.logout(),
- );
+ Promise.all([
+ ...[...CallStore.instance.activeCalls].map((call) => call.disconnect()),
+ cleanUpBroadcasts(this.stores),
+ ]).finally(() => Lifecycle.logout());
break;
case "require_registration":
startAnyRegistrationFlow(payload as any);
@@ -876,13 +877,13 @@ export default class MatrixChat extends React.PureComponent {
}
};
- private setPage(pageType: PageType) {
+ private setPage(pageType: PageType): void {
this.setState({
page_type: pageType,
});
}
- private async startRegistration(params: { [key: string]: string }) {
+ private async startRegistration(params: { [key: string]: string }): Promise {
const newState: Partial = {
view: Views.REGISTER,
};
@@ -916,7 +917,7 @@ export default class MatrixChat extends React.PureComponent {
}
// switch view to the given room
- private async viewRoom(roomInfo: ViewRoomPayload) {
+ private async viewRoom(roomInfo: ViewRoomPayload): Promise {
this.focusComposer = true;
if (roomInfo.room_alias) {
@@ -987,12 +988,14 @@ export default class MatrixChat extends React.PureComponent {
roomJustCreatedOpts: roomInfo.justCreatedOpts,
},
() => {
+ ThemeController.isLogin = false;
+ this.themeWatcher.recheck();
this.notifyNewScreen("room/" + presentedId, replaceLast);
},
);
}
- private viewSomethingBehindModal() {
+ private viewSomethingBehindModal(): void {
if (this.state.view !== Views.LOGGED_IN) {
this.viewWelcome();
return;
@@ -1002,7 +1005,7 @@ export default class MatrixChat extends React.PureComponent {
}
}
- private viewWelcome() {
+ private viewWelcome(): void {
if (shouldUseLoginForWelcome(SdkConfig.get())) {
return this.viewLogin();
}
@@ -1014,7 +1017,7 @@ export default class MatrixChat extends React.PureComponent