From 1f9db5d41d1cba9f918237409aeb71d1d15f82e5 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Mon, 13 Feb 2023 09:49:30 +0000 Subject: [PATCH 1/5] Use '_t' for caption to have strings localized (#10143) Signed-off-by: Suguru Hirahara --- src/settings/Settings.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index fc4c772d69f7..a8b2b45aff96 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -486,9 +486,9 @@ export const SETTINGS: { [setting: string]: ISetting } = { title: _td("New session manager"), caption: () => ( <> -

{_td("Have greater visibility and control over all your sessions.")}

+

{_t("Have greater visibility and control over all your sessions.")}

- {_td( + {_t( "Our new sessions manager provides better visibility of all your sessions, " + "and greater control over them including the ability to remotely toggle push notifications.", )} From 986b7cbb616c5e18c25dc7704f64500242f35d81 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Mon, 13 Feb 2023 10:11:22 +0000 Subject: [PATCH 2/5] Remove ineffective white space characters from VerifyEmailModal.tsx (#10141) Signed-off-by: Suguru Hirahara --- .../structures/auth/forgot-password/VerifyEmailModal.tsx | 4 ++-- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/auth/forgot-password/VerifyEmailModal.tsx b/src/components/structures/auth/forgot-password/VerifyEmailModal.tsx index 1f63d51bd973..d1de4ba4b3f7 100644 --- a/src/components/structures/auth/forgot-password/VerifyEmailModal.tsx +++ b/src/components/structures/auth/forgot-password/VerifyEmailModal.tsx @@ -52,8 +52,8 @@ export const VerifyEmailModal: React.FC = ({

{_t("Verify your email to continue")}

{_t( - `We need to know it’s you before resetting your password. - Click the link in the email we just sent to %(email)s`, + "We need to know it’s you before resetting your password. " + + "Click the link in the email we just sent to %(email)s", { email, }, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 46207ded9ef3..3e09c3a140f1 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3584,7 +3584,7 @@ "The email address doesn't appear to be valid.": "The email address doesn't appear to be valid.", "Sign in instead": "Sign in instead", "Verify your email to continue": "Verify your email to continue", - "We need to know it’s you before resetting your password.\n Click the link in the email we just sent to %(email)s": "We need to know it’s you before resetting your password.\n Click the link in the email we just sent to %(email)s", + "We need to know it’s you before resetting your password. Click the link in the email we just sent to %(email)s": "We need to know it’s you before resetting your password. Click the link in the email we just sent to %(email)s", "Commands": "Commands", "Command Autocomplete": "Command Autocomplete", "Emoji Autocomplete": "Emoji Autocomplete", From ac7f69216e700b0160d4c7f804dbece1603b9f18 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Mon, 13 Feb 2023 11:31:22 +0000 Subject: [PATCH 3/5] Remove duplicate translated string (#10139) --- src/SlashCommands.tsx | 2 +- .../views/dialogs/security/CreateKeyBackupDialog.tsx | 2 +- src/components/structures/auth/SoftLogout.tsx | 2 +- .../views/dialogs/security/RestoreKeyBackupDialog.tsx | 2 +- src/components/views/rooms/RoomUpgradeWarningBar.tsx | 2 +- .../views/settings/tabs/room/AdvancedRoomSettingsTab.tsx | 2 +- src/i18n/strings/en_EN.json | 7 +++---- 7 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 3434090d8ea5..1ffe43fec739 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -1042,7 +1042,7 @@ export const Commands = [ throw newTranslatableError("Session already verified!"); } else { throw newTranslatableError( - "WARNING: Session already verified, but keys do NOT MATCH!", + "WARNING: session already verified, but keys do NOT MATCH!", ); } } diff --git a/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx index a75b41f602bd..36fd23a7fc3c 100644 --- a/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx +++ b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx @@ -239,7 +239,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent

{_t( - "Warning: You should only set up key backup from a trusted computer.", + "Warning: you should only set up key backup from a trusted computer.", {}, { b: (sub) => {sub} }, )} diff --git a/src/components/structures/auth/SoftLogout.tsx b/src/components/structures/auth/SoftLogout.tsx index d6ad4bfb1652..253e89aa04e8 100644 --- a/src/components/structures/auth/SoftLogout.tsx +++ b/src/components/structures/auth/SoftLogout.tsx @@ -339,7 +339,7 @@ export default class SoftLogout extends React.Component {

{_t("Clear personal data")}

{_t( - "Warning: Your personal data (including encryption keys) is still stored " + + "Warning: your personal data (including encryption keys) is still stored " + "in this session. Clear it if you're finished using this session, or want to sign " + "in to another account.", )} diff --git a/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx b/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx index bc386cb97637..0d1286a29ba7 100644 --- a/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx +++ b/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx @@ -480,7 +480,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent

{_t( - "Warning: You should only set up key backup " + "from a trusted computer.", + "Warning: you should only set up key backup " + "from a trusted computer.", {}, { b: (sub) => {sub} }, )} diff --git a/src/components/views/rooms/RoomUpgradeWarningBar.tsx b/src/components/views/rooms/RoomUpgradeWarningBar.tsx index 51152b130d2a..95bef9fbaa43 100644 --- a/src/components/views/rooms/RoomUpgradeWarningBar.tsx +++ b/src/components/views/rooms/RoomUpgradeWarningBar.tsx @@ -81,7 +81,7 @@ export default class RoomUpgradeWarningBar extends React.PureComponent

{_t( - "Warning: Upgrading a room will not automatically migrate room members " + + "Warning: upgrading a room will not automatically migrate room members " + "to the new version of the room. We'll post a link to the new room in the old " + "version of the room - room members will have to click this link to join the new room.", {}, diff --git a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx index d9acba8524a4..42aae829c3a8 100644 --- a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx @@ -113,7 +113,7 @@ export default class AdvancedRoomSettingsTab extends React.Component

{_t( - "Warning: Upgrading a room will not automatically migrate room members " + + "Warning: upgrading a room will not automatically migrate room members " + "to the new version of the room. We'll post a link to the new room in the old " + "version of the room - room members will have to click this link to join the new room.", {}, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3e09c3a140f1..363a1bdc324a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -458,7 +458,7 @@ "Verifies a user, session, and pubkey tuple": "Verifies a user, session, and pubkey tuple", "Unknown (user, session) pair: (%(userId)s, %(deviceId)s)": "Unknown (user, session) pair: (%(userId)s, %(deviceId)s)", "Session already verified!": "Session already verified!", - "WARNING: Session already verified, but keys do NOT MATCH!": "WARNING: Session already verified, but keys do NOT MATCH!", + "WARNING: session already verified, but keys do NOT MATCH!": "WARNING: session already verified, but keys do NOT MATCH!", "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!", "Verified key": "Verified key", "The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.", @@ -1669,7 +1669,7 @@ "Voice processing": "Voice processing", "Connection": "Connection", "This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers", - "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.", + "Warning: upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Warning: upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.", "Upgrade this space to the recommended room version": "Upgrade this space to the recommended room version", "Upgrade this room to the recommended room version": "Upgrade this room to the recommended room version", "View older version of %(spaceName)s.": "View older version of %(spaceName)s.", @@ -3141,7 +3141,6 @@ "Enter Security Key": "Enter Security Key", "This looks like a valid Security Key!": "This looks like a valid Security Key!", "Not a valid Security Key": "Not a valid Security Key", - "Warning: You should only set up key backup from a trusted computer.": "Warning: You should only set up key backup from a trusted computer.", "Access your secure message history and set up secure messaging by entering your Security Key.": "Access your secure message history and set up secure messaging by entering your Security Key.", "If you've forgotten your Security Key you can ": "If you've forgotten your Security Key you can ", "There are no active polls in this room": "There are no active polls in this room", @@ -3571,7 +3570,7 @@ "You cannot sign in to your account. Please contact your homeserver admin for more information.": "You cannot sign in to your account. Please contact your homeserver admin for more information.", "You're signed out": "You're signed out", "Clear personal data": "Clear personal data", - "Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.": "Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.", + "Warning: your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.": "Warning: your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.", "Follow the instructions sent to %(email)s": "Follow the instructions sent to %(email)s", "Wrong email address?": "Wrong email address?", "Re-enter email address": "Re-enter email address", From 61a63e47f46fb597577fbb86d607f67ccdc47ed2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 13 Feb 2023 11:39:16 +0000 Subject: [PATCH 4/5] Comply with noImplicitAny (#9940) * Stash noImplicitAny work * Stash * Fix imports * Iterate * Fix tests * Delint * Fix tests --- package.json | 3 + src/@types/global.d.ts | 2 +- src/@types/opus-recorder.d.ts | 65 +++++++ src/AddThreepid.ts | 31 ++-- src/AsyncWrapper.tsx | 5 +- src/BasePlatform.ts | 2 +- src/HtmlUtils.tsx | 2 +- src/IConfigOptions.ts | 8 + src/Keyboard.ts | 4 +- src/LegacyCallHandler.tsx | 16 +- src/Login.ts | 8 +- src/MediaDeviceHandler.ts | 4 +- src/NodeAnimator.tsx | 6 +- src/Notifier.ts | 160 +++++++++--------- src/PosthogAnalytics.ts | 4 +- src/SecurityManager.ts | 6 +- src/SlashCommands.tsx | 7 +- src/Terms.ts | 14 +- src/TextForEvent.tsx | 22 ++- src/UserActivity.ts | 5 +- src/accessibility/KeyboardShortcutUtils.ts | 14 +- src/accessibility/KeyboardShortcuts.ts | 6 +- src/accessibility/RovingTabIndex.tsx | 5 +- .../context_menu/StyledMenuItemCheckbox.tsx | 2 +- .../context_menu/StyledMenuItemRadio.tsx | 2 +- .../roving/RovingAccessibleButton.tsx | 2 +- .../roving/RovingAccessibleTooltipButton.tsx | 2 +- .../roving/RovingTabIndexWrapper.tsx | 4 +- src/actions/RoomListActions.ts | 6 +- .../eventindex/ManageEventIndexDialog.tsx | 14 +- .../security/CreateSecretStorageDialog.tsx | 4 +- .../dialogs/security/ExportE2eKeysDialog.tsx | 10 +- .../dialogs/security/ImportE2eKeysDialog.tsx | 4 +- src/audio/RecorderWorklet.ts | 6 +- src/audio/VoiceRecording.ts | 1 - src/audio/compat.ts | 2 +- src/autocomplete/EmojiProvider.tsx | 10 +- src/autocomplete/UserProvider.tsx | 11 +- src/components/structures/ContextMenu.tsx | 14 +- src/components/structures/FilePanel.tsx | 4 +- .../structures/GenericDropdownMenu.tsx | 14 +- src/components/structures/InteractiveAuth.tsx | 4 +- src/components/structures/LeftPanel.tsx | 2 +- src/components/structures/LoggedInView.tsx | 26 ++- src/components/structures/MatrixChat.tsx | 11 +- src/components/structures/MessagePanel.tsx | 36 ++-- .../structures/NonUrgentToastContainer.tsx | 4 +- .../structures/NotificationPanel.tsx | 2 +- src/components/structures/RightPanel.tsx | 2 +- src/components/structures/RoomStatusBar.tsx | 9 +- .../RoomStatusBarUnsentMessages.tsx | 4 +- src/components/structures/RoomView.tsx | 25 ++- src/components/structures/ScrollPanel.tsx | 12 +- src/components/structures/SpaceHierarchy.tsx | 2 +- src/components/structures/SpaceRoomView.tsx | 2 +- src/components/structures/ThreadView.tsx | 4 +- src/components/structures/TimelinePanel.tsx | 6 +- src/components/structures/ToastContainer.tsx | 4 +- src/components/structures/UploadBar.tsx | 2 +- src/components/structures/auth/Login.tsx | 44 +++-- .../structures/auth/Registration.tsx | 42 ++--- .../structures/auth/SetupEncryptionBody.tsx | 2 +- src/components/structures/auth/SoftLogout.tsx | 8 +- .../views/audio_messages/DurationClock.tsx | 2 +- .../audio_messages/LiveRecordingClock.tsx | 4 +- .../audio_messages/LiveRecordingWaveform.tsx | 4 +- .../views/audio_messages/PlayPauseButton.tsx | 2 +- .../views/audio_messages/PlaybackClock.tsx | 2 +- .../views/audio_messages/PlaybackWaveform.tsx | 2 +- .../views/audio_messages/SeekBar.tsx | 2 +- src/components/views/auth/CountryDropdown.tsx | 2 +- .../auth/InteractiveAuthEntryComponents.tsx | 20 ++- src/components/views/auth/LoginWithQR.tsx | 2 +- src/components/views/auth/LoginWithQRFlow.tsx | 2 +- .../views/auth/PassphraseConfirmField.tsx | 4 +- src/components/views/auth/PassphraseField.tsx | 4 +- src/components/views/auth/PasswordLogin.tsx | 52 +++--- .../views/auth/RegistrationForm.tsx | 38 +++-- src/components/views/avatars/BaseAvatar.tsx | 4 +- .../context_menus/DialpadContextMenu.tsx | 6 +- .../context_menus/LegacyCallContextMenu.tsx | 2 +- src/components/views/dialogs/BaseDialog.tsx | 4 +- .../views/dialogs/BugReportDialog.tsx | 2 +- .../views/dialogs/BulkRedactDialog.tsx | 3 +- .../views/dialogs/ChangelogDialog.tsx | 22 ++- .../dialogs/ConfirmAndWaitRedactDialog.tsx | 2 +- .../views/dialogs/CreateRoomDialog.tsx | 2 +- .../views/dialogs/CreateSubspaceDialog.tsx | 4 +- .../views/dialogs/DeactivateAccountDialog.tsx | 22 ++- .../views/dialogs/DevtoolsDialog.tsx | 2 +- src/components/views/dialogs/ErrorDialog.tsx | 3 - src/components/views/dialogs/ExportDialog.tsx | 25 ++- .../views/dialogs/ForwardDialog.tsx | 2 +- .../views/dialogs/HostSignupDialog.tsx | 2 +- .../views/dialogs/InteractiveAuthDialog.tsx | 20 +-- src/components/views/dialogs/InviteDialog.tsx | 42 ++--- src/components/views/dialogs/LogoutDialog.tsx | 2 +- .../dialogs/MessageEditHistoryDialog.tsx | 4 +- .../views/dialogs/ModalWidgetDialog.tsx | 2 +- .../dialogs/RegistrationEmailPromptDialog.tsx | 4 +- .../views/dialogs/ReportEventDialog.tsx | 4 +- .../views/dialogs/RoomSettingsDialog.tsx | 3 +- .../dialogs/RoomUpgradeWarningDialog.tsx | 6 +- .../views/dialogs/ServerPickerDialog.tsx | 8 +- src/components/views/dialogs/ShareDialog.tsx | 16 +- .../views/dialogs/SlashCommandHelpDialog.tsx | 4 +- src/components/views/dialogs/TermsDialog.tsx | 5 +- .../views/dialogs/UploadConfirmDialog.tsx | 2 +- .../views/dialogs/UserSettingsDialog.tsx | 2 +- .../dialogs/VerificationRequestDialog.tsx | 2 +- .../WidgetCapabilitiesPromptDialog.tsx | 15 +- .../views/dialogs/devtools/Event.tsx | 4 +- .../views/dialogs/devtools/FilteredList.tsx | 4 +- .../dialogs/devtools/SettingExplorer.tsx | 6 +- .../security/AccessSecretStorageDialog.tsx | 2 +- .../security/CreateCrossSigningDialog.tsx | 3 +- .../security/RestoreKeyBackupDialog.tsx | 12 +- .../dialogs/spotlight/SpotlightDialog.tsx | 4 +- src/components/views/elements/AppTile.tsx | 8 +- src/components/views/elements/Dropdown.tsx | 2 +- .../views/elements/EditableItemList.tsx | 20 +-- .../views/elements/EffectsOverlay.tsx | 6 +- .../views/elements/ErrorBoundary.tsx | 2 +- .../views/elements/EventListSummary.tsx | 26 +-- src/components/views/elements/Field.tsx | 20 +-- src/components/views/elements/ImageView.tsx | 2 +- .../views/elements/InteractiveTooltip.tsx | 4 +- .../views/elements/InviteReason.tsx | 2 +- .../views/elements/LabelledToggleSwitch.tsx | 16 +- src/components/views/elements/Measured.tsx | 2 +- .../views/elements/PersistentApp.tsx | 4 +- src/components/views/elements/Pill.tsx | 8 +- .../views/elements/RoomAliasField.tsx | 2 +- .../views/elements/RoomFacePile.tsx | 2 +- .../views/elements/SearchWarning.tsx | 4 +- .../elements/SpellCheckLanguagesDropdown.tsx | 4 +- .../views/elements/StyledRadioGroup.tsx | 6 +- src/components/views/elements/Tooltip.tsx | 4 +- .../views/elements/TooltipButton.tsx | 2 +- .../views/elements/TruncatedList.tsx | 2 +- .../views/emojipicker/QuickReactions.tsx | 2 +- .../views/emojipicker/ReactionPicker.tsx | 4 +- .../views/location/LocationShareMenu.tsx | 2 +- .../views/messages/DateSeparator.tsx | 10 +- .../views/messages/EventTileBubble.tsx | 4 +- src/components/views/messages/MAudioBody.tsx | 5 +- src/components/views/messages/MFileBody.tsx | 8 +- .../views/messages/MJitsiWidgetEvent.tsx | 2 +- .../messages/MKeyVerificationRequest.tsx | 6 +- src/components/views/messages/MVideoBody.tsx | 2 +- .../views/messages/ReactionsRow.tsx | 4 +- .../messages/ReactionsRowButtonTooltip.tsx | 3 +- src/components/views/messages/TextualBody.tsx | 2 +- .../views/messages/TileErrorBoundary.tsx | 2 +- .../views/messages/ViewSourceEvent.tsx | 2 +- .../views/right_panel/HeaderButtons.tsx | 3 +- .../views/room_settings/AliasSettings.tsx | 19 ++- .../room_settings/RoomPublishSetting.tsx | 6 +- src/components/views/rooms/AppsDrawer.tsx | 10 +- src/components/views/rooms/Autocomplete.tsx | 2 +- src/components/views/rooms/AuxPanel.tsx | 8 +- .../views/rooms/BasicMessageComposer.tsx | 8 +- .../views/rooms/EditMessageComposer.tsx | 6 +- src/components/views/rooms/EntityTile.tsx | 10 +- src/components/views/rooms/EventTile.tsx | 4 +- .../views/rooms/LinkPreviewWidget.tsx | 2 +- src/components/views/rooms/MemberList.tsx | 6 +- src/components/views/rooms/MemberTile.tsx | 17 +- .../views/rooms/MessageComposer.tsx | 4 +- .../views/rooms/NotificationBadge.tsx | 2 +- .../views/rooms/PinnedEventTile.tsx | 1 + src/components/views/rooms/PresenceLabel.tsx | 1 - src/components/views/rooms/RoomHeader.tsx | 3 +- src/components/views/rooms/RoomList.tsx | 15 +- src/components/views/rooms/RoomPreviewBar.tsx | 6 +- .../views/rooms/RoomUpgradeWarningBar.tsx | 2 +- .../views/rooms/SearchResultTile.tsx | 2 +- .../views/rooms/SendMessageComposer.tsx | 5 +- src/components/views/rooms/Stickerpicker.tsx | 4 +- .../views/rooms/ThirdPartyMemberInfo.tsx | 4 +- .../views/rooms/WhoIsTypingTile.tsx | 4 +- .../views/settings/ChangePassword.tsx | 17 +- .../views/settings/CrossSigningPanel.tsx | 2 +- .../views/settings/CryptographyPanel.tsx | 2 +- .../views/settings/EventIndexPanel.tsx | 2 +- .../views/settings/Notifications.tsx | 2 +- src/components/views/settings/SetIdServer.tsx | 6 +- .../views/settings/SpellCheckSettings.tsx | 12 +- .../views/settings/account/EmailAddresses.tsx | 4 +- .../views/settings/account/PhoneNumbers.tsx | 2 +- .../settings/discovery/EmailAddresses.tsx | 6 +- .../views/settings/discovery/PhoneNumbers.tsx | 6 +- .../tabs/room/AdvancedRoomSettingsTab.tsx | 4 +- .../tabs/room/RolesRoomSettingsTab.tsx | 2 +- .../tabs/room/SecurityRoomSettingsTab.tsx | 2 +- .../tabs/user/AppearanceUserSettingsTab.tsx | 4 +- .../tabs/user/GeneralUserSettingsTab.tsx | 10 +- .../tabs/user/HelpUserSettingsTab.tsx | 8 +- .../tabs/user/KeyboardUserSettingsTab.tsx | 4 +- .../tabs/user/MjolnirUserSettingsTab.tsx | 12 +- .../tabs/user/PreferencesUserSettingsTab.tsx | 2 +- .../views/spaces/SpaceBasicSettings.tsx | 6 +- .../views/spaces/SpaceCreateMenu.tsx | 13 +- .../views/spaces/SpaceTreeLevel.tsx | 2 +- .../views/terms/InlineTermsAgreement.tsx | 5 +- .../views/toasts/GenericExpiringToast.tsx | 2 +- src/components/views/toasts/GenericToast.tsx | 4 +- .../views/toasts/VerificationRequestToast.tsx | 2 +- src/components/views/voip/DialPadModal.tsx | 12 +- src/components/views/voip/LegacyCallView.tsx | 2 +- src/components/views/voip/VideoFeed.tsx | 2 +- src/createRoom.ts | 4 +- src/dispatcher/payloads/ViewRoomPayload.ts | 2 + src/editor/deserialize.ts | 2 +- src/editor/history.ts | 2 +- src/editor/model.ts | 4 +- src/editor/parts.ts | 8 +- src/editor/render.ts | 8 +- src/editor/serialize.ts | 6 +- src/emoji.ts | 2 +- src/hooks/spotlight/useRecentSearches.ts | 2 +- src/hooks/useEventEmitter.ts | 2 +- src/indexing/EventIndex.ts | 2 +- .../IntegrationManagerInstance.ts | 3 +- src/integrations/IntegrationManagers.ts | 8 +- src/linkify-matrix.ts | 4 +- src/modules/ModuleComponents.tsx | 4 +- src/modules/ProxiedModuleApi.ts | 3 +- src/notifications/ContentRules.ts | 6 +- src/notifications/StandardActions.ts | 4 +- .../VectorPushRulesDefinitions.ts | 6 +- src/rageshake/rageshake.ts | 73 ++++---- src/rageshake/submit-rageshake.ts | 6 +- src/sentry.ts | 10 +- src/settings/Settings.tsx | 7 +- src/settings/SettingsStore.ts | 29 ++-- .../MatrixClientBackedSettingsHandler.ts | 2 +- .../handlers/PlatformSettingsHandler.ts | 2 +- src/shouldHideEvent.ts | 4 +- src/stores/AsyncStore.ts | 2 +- src/stores/BreadcrumbsStore.ts | 2 +- src/stores/LifecycleStore.ts | 4 +- src/stores/RoomViewStore.tsx | 13 +- src/stores/local-echo/EchoTransaction.ts | 2 +- src/stores/local-echo/GenericEchoChamber.ts | 2 +- src/stores/local-echo/RoomEchoChamber.ts | 9 +- .../list-ordering/ImportanceAlgorithm.ts | 38 +++-- .../list-ordering/NaturalAlgorithm.ts | 2 +- src/stores/widgets/StopGapWidgetDriver.ts | 9 +- src/stores/widgets/WidgetLayoutStore.ts | 21 ++- src/stores/widgets/WidgetPermissionStore.ts | 5 +- src/theme.ts | 52 +++--- src/utils/AutoDiscoveryUtils.tsx | 15 +- src/utils/DMRoomMap.ts | 2 +- src/utils/DecryptFile.ts | 4 +- src/utils/FileDownloader.ts | 2 +- src/utils/FormattingUtils.ts | 8 +- src/utils/MultiInviter.ts | 2 +- src/utils/ReactUtils.tsx | 6 +- src/utils/Reply.ts | 10 +- src/utils/SnakedObject.ts | 2 +- src/utils/Timer.ts | 2 +- src/utils/UserInteractiveAuth.ts | 2 +- src/utils/ValidatedServerConfig.ts | 2 +- src/utils/WidgetUtils.ts | 4 +- src/utils/beacon/geolocation.ts | 4 +- src/utils/createMatrixClient.ts | 2 +- src/utils/exportUtils/exportUtils.ts | 4 + src/utils/image-media.ts | 6 +- src/utils/location/map.ts | 2 +- src/utils/membership.ts | 7 +- src/utils/objects.ts | 2 +- src/utils/permalinks/Permalinks.ts | 6 +- src/utils/pillify.tsx | 2 +- src/widgets/CapabilityText.tsx | 27 +-- test/Notifier-test.ts | 48 +++--- test/PosthogAnalytics-test.ts | 4 +- test/ScalarAuthClient-test.ts | 3 +- test/TextForEvent-test.ts | 70 ++++---- test/UserActivity-test.ts | 32 ++-- .../KeyboardShortcutUtils-test.ts | 2 +- test/accessibility/RovingTabIndex-test.tsx | 6 +- .../structures/MessagePanel-test.tsx | 10 +- .../components/structures/ThreadView-test.tsx | 4 +- .../audio_messages/RecordingPlayback-test.tsx | 4 +- .../views/avatars/MemberAvatar-test.tsx | 4 +- .../views/beacon/DialogSidebar-test.tsx | 4 +- .../AccessSecretStorageDialog-test.tsx | 4 +- .../views/dialogs/ExportDialog-test.tsx | 29 ++-- .../views/dialogs/ForwardDialog-test.tsx | 14 +- .../views/dialogs/UserSettingsDialog-test.tsx | 5 +- .../views/elements/EventListSummary-test.tsx | 10 +- .../views/elements/StyledRadioGroup-test.tsx | 8 +- .../views/elements/TooltipTarget-test.tsx | 2 +- .../location/LiveDurationDropdown-test.tsx | 8 +- .../views/location/LocationPicker-test.tsx | 11 +- .../location/LocationViewDialog-test.tsx | 2 +- .../views/messages/DateSeparator-test.tsx | 2 +- .../views/messages/MBeaconBody-test.tsx | 6 +- .../views/messages/MLocationBody-test.tsx | 8 +- .../views/messages/MVideoBody-test.tsx | 8 +- .../views/messages/MessageActionBar-test.tsx | 2 +- .../views/messages/TextualBody-test.tsx | 24 +-- .../right_panel/PinnedMessagesCard-test.tsx | 8 +- .../views/rooms/MemberList-test.tsx | 38 +++-- .../views/rooms/RoomHeader-test.tsx | 5 +- .../views/rooms/RoomPreviewBar-test.tsx | 14 +- .../components/PlainTextComposer-test.tsx | 4 +- .../views/settings/KeyboardShortcut-test.tsx | 2 +- .../views/settings/Notifications-test.tsx | 17 +- .../settings/devices/DeviceDetails-test.tsx | 5 +- .../devices/FilteredDeviceList-test.tsx | 5 +- .../settings/devices/LoginWithQR-test.tsx | 2 +- .../settings/devices/deleteDevices-test.tsx | 4 +- .../tabs/room/VoipRoomSettingsTab-test.tsx | 2 +- .../user/KeyboardUserSettingsTab-test.tsx | 8 +- .../tabs/user/SessionManagerTab-test.tsx | 2 +- .../views/spaces/SpacePanel-test.tsx | 8 +- .../SpaceSettingsVisibilityTab-test.tsx | 8 +- test/editor/deserialize-test.ts | 9 +- test/editor/mock.ts | 4 +- test/editor/position-test.ts | 3 +- test/hooks/useDebouncedCallback-test.tsx | 4 +- test/hooks/useLatestResult-test.tsx | 8 +- test/hooks/useProfileInfo-test.tsx | 10 +- test/hooks/usePublicRoomDirectory-test.tsx | 5 +- test/hooks/useUserDirectory-test.tsx | 6 +- test/i18n-test/languageHandler-test.tsx | 6 +- test/linkify-matrix-test.ts | 2 +- test/settings/SettingsStore-test.ts | 2 +- .../IncompatibleController-test.ts | 8 +- test/setup/setupLanguage.ts | 6 +- test/setup/setupManualMocks.ts | 5 +- test/stores/OwnBeaconStore-test.ts | 13 +- test/stores/SpaceStore-test.ts | 16 +- test/stores/VoiceRecordingStore-test.ts | 2 +- test/stores/room-list/SpaceWatcher-test.ts | 8 +- .../algorithms/RecentAlgorithm-test.ts | 17 +- .../filters/SpaceFilterCondition-test.ts | 8 +- .../widgets/WidgetPermissionStore-test.ts | 2 +- test/test-utils/beacon.ts | 4 +- test/test-utils/composer.ts | 12 +- test/test-utils/utilities.ts | 4 +- test/test-utils/wrappers.tsx | 2 +- test/theme-test.ts | 26 ++- test/utils/DateUtils-test.ts | 2 +- test/utils/MegolmExportEncryption-test.ts | 6 +- test/utils/MultiInviter-test.ts | 2 +- test/utils/ShieldUtils-test.ts | 10 +- test/utils/beacon/geolocation-test.ts | 19 ++- test/utils/export-test.tsx | 2 +- test/utils/local-room-test.ts | 2 +- test/utils/location/isSelfLocation-test.ts | 8 +- test/utils/membership-test.ts | 11 +- test/utils/notifications-test.ts | 8 +- test/utils/permalinks/Permalinks-test.ts | 2 +- test/voice-broadcast/utils/test-utils.ts | 4 +- tsconfig.json | 8 +- yarn.lock | 27 +++ 359 files changed, 1622 insertions(+), 1354 deletions(-) create mode 100644 src/@types/opus-recorder.d.ts diff --git a/package.json b/package.json index b87477951113..ebc99caa1502 100644 --- a/package.json +++ b/package.json @@ -154,11 +154,13 @@ "@types/flux": "^3.1.9", "@types/fs-extra": "^11.0.0", "@types/geojson": "^7946.0.8", + "@types/glob-to-regexp": "^0.4.1", "@types/jest": "^29.2.1", "@types/katex": "^0.14.0", "@types/lodash": "^4.14.168", "@types/modernizr": "^3.5.3", "@types/node": "^16", + "@types/node-fetch": "^2.6.2", "@types/pako": "^2.0.0", "@types/parse5": "^6.0.0", "@types/qrcode": "^1.3.5", @@ -168,6 +170,7 @@ "@types/react-test-renderer": "^17.0.1", "@types/react-transition-group": "^4.4.0", "@types/sanitize-html": "^2.3.1", + "@types/tar-js": "^0.3.2", "@types/ua-parser-js": "^0.7.36", "@types/zxcvbn": "^4.4.0", "@typescript-eslint/eslint-plugin": "^5.35.1", diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index f1e72ca2fc49..9d3b64fd6af6 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -218,7 +218,7 @@ declare global { processorCtor: (new (options?: AudioWorkletNodeOptions) => AudioWorkletProcessor) & { parameterDescriptors?: AudioParamDescriptor[]; }, - ); + ): void; // eslint-disable-next-line no-var var grecaptcha: diff --git a/src/@types/opus-recorder.d.ts b/src/@types/opus-recorder.d.ts new file mode 100644 index 000000000000..a964278aa1d5 --- /dev/null +++ b/src/@types/opus-recorder.d.ts @@ -0,0 +1,65 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +declare module "opus-recorder/dist/recorder.min.js" { + export default class Recorder { + public static isRecordingSupported(): boolean; + + public constructor(config: { + bufferLength?: number; + encoderApplication?: number; + encoderFrameSize?: number; + encoderPath?: string; + encoderSampleRate?: number; + encoderBitRate?: number; + maxFramesPerPage?: number; + mediaTrackConstraints?: boolean; + monitorGain?: number; + numberOfChannels?: number; + recordingGain?: number; + resampleQuality?: number; + streamPages?: boolean; + wavBitDepth?: number; + sourceNode?: MediaStreamAudioSourceNode; + encoderComplexity?: number; + }); + + public ondataavailable?(data: ArrayBuffer): void; + + public readonly encodedSamplePosition: number; + + public start(): Promise; + + public stop(): Promise; + + public close(): void; + } +} + +declare module "opus-recorder/dist/encoderWorker.min.js" { + const path: string; + export default path; +} + +declare module "opus-recorder/dist/waveWorker.min.js" { + const path: string; + export default path; +} + +declare module "opus-recorder/dist/decoderWorker.min.js" { + const path: string; + export default path; +} diff --git a/src/AddThreepid.ts b/src/AddThreepid.ts index b6ed0d57387b..5d8d947854d5 100644 --- a/src/AddThreepid.ts +++ b/src/AddThreepid.ts @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix"; +import { IAuthData, IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix"; import { MatrixClientPeg } from "./MatrixClientPeg"; import Modal from "./Modal"; @@ -29,6 +29,12 @@ function getIdServerDomain(): string { return MatrixClientPeg.get().idBaseUrl.split("://")[1]; } +export type Binding = { + bind: boolean; + label: string; + errorTitle: string; +}; + /** * Allows a user to add a third party identifier to their homeserver and, * optionally, the identity servers. @@ -178,7 +184,7 @@ export default class AddThreepid { * with a "message" property which contains a human-readable message detailing why * the request failed. */ - public async checkEmailLinkClicked(): Promise { + public async checkEmailLinkClicked(): Promise<[boolean, IAuthData | Error | null]> { try { if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) { if (this.bind) { @@ -220,16 +226,19 @@ export default class AddThreepid { continueKind: "primary", }, }; - const { finished } = Modal.createDialog(InteractiveAuthDialog, { - title: _t("Add Email Address"), - matrixClient: MatrixClientPeg.get(), - authData: e.data, - makeRequest: this.makeAddThreepidOnlyRequest, - aestheticsForStagePhases: { - [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, - [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, + const { finished } = Modal.createDialog<[boolean, IAuthData | Error | null]>( + InteractiveAuthDialog, + { + title: _t("Add Email Address"), + matrixClient: MatrixClientPeg.get(), + authData: e.data, + makeRequest: this.makeAddThreepidOnlyRequest, + aestheticsForStagePhases: { + [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, + [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, + }, }, - }); + ); return finished; } } diff --git a/src/AsyncWrapper.tsx b/src/AsyncWrapper.tsx index 226f5b692bc9..9d0ba11b0628 100644 --- a/src/AsyncWrapper.tsx +++ b/src/AsyncWrapper.tsx @@ -42,10 +42,7 @@ interface IState { export default class AsyncWrapper extends React.Component { private unmounted = false; - public state = { - component: null, - error: null, - }; + public state: IState = {}; public componentDidMount(): void { // XXX: temporary logging to try to diagnose diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 46f964995af8..7676305c4f76 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -197,7 +197,7 @@ export default abstract class BasePlatform { room: Room, ev?: MatrixEvent, ): Notification { - const notifBody = { + const notifBody: NotificationOptions = { body: msg, silent: true, // we play our own sounds }; diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index f2452327e501..a6b999fa2730 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -228,7 +228,7 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // Sanitise and transform data-mx-color and data-mx-bg-color to their CSS // equivalents - const customCSSMapper = { + const customCSSMapper: Record = { "data-mx-color": "color", "data-mx-bg-color": "background-color", // $customAttributeKey: $cssAttributeKey diff --git a/src/IConfigOptions.ts b/src/IConfigOptions.ts index 8234f5bc7577..1db73cc07456 100644 --- a/src/IConfigOptions.ts +++ b/src/IConfigOptions.ts @@ -169,10 +169,18 @@ export interface IConfigOptions { inline?: { left?: string; right?: string; + pattern?: { + tex?: string; + latex?: string; + }; }; display?: { left?: string; right?: string; + pattern?: { + tex?: string; + latex?: string; + }; }; }; diff --git a/src/Keyboard.ts b/src/Keyboard.ts index 9d4d3f615218..7b1ea4031beb 100644 --- a/src/Keyboard.ts +++ b/src/Keyboard.ts @@ -16,6 +16,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import React from "react"; + export const Key = { HOME: "Home", END: "End", @@ -76,7 +78,7 @@ export const Key = { export const IS_MAC = navigator.platform.toUpperCase().includes("MAC"); -export function isOnlyCtrlOrCmdKeyEvent(ev: KeyboardEvent): boolean { +export function isOnlyCtrlOrCmdKeyEvent(ev: React.KeyboardEvent | KeyboardEvent): boolean { if (IS_MAC) { return ev.metaKey && !ev.altKey && !ev.ctrlKey && !ev.shiftKey; } else { diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx index 82e5cac996c9..c3ca2566462b 100644 --- a/src/LegacyCallHandler.tsx +++ b/src/LegacyCallHandler.tsx @@ -158,9 +158,9 @@ export default class LegacyCallHandler extends EventEmitter { private transferees = new Map(); // callId (target) -> call (transferee) private audioPromises = new Map>(); private audioElementsWithListeners = new Map(); - private supportsPstnProtocol = null; - private pstnSupportPrefixed = null; // True if the server only support the prefixed pstn protocol - private supportsSipNativeVirtual = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native + private supportsPstnProtocol: boolean | null = null; + private pstnSupportPrefixed: boolean | null = null; // True if the server only support the prefixed pstn protocol + private supportsSipNativeVirtual: boolean | null = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native // Map of the asserted identity users after we've looked them up using the API. // We need to be be able to determine the mapped room synchronously, so we @@ -187,7 +187,7 @@ export default class LegacyCallHandler extends EventEmitter { // check asserted identity: if we're not obeying asserted identity, // this map will never be populated, but we check anyway for sanity if (this.shouldObeyAssertedfIdentity()) { - const nativeUser = this.assertedIdentityNativeUsers[call.callId]; + const nativeUser = this.assertedIdentityNativeUsers.get(call.callId); if (nativeUser) { const room = findDMForUser(MatrixClientPeg.get(), nativeUser); if (room) return room.roomId; @@ -466,8 +466,8 @@ export default class LegacyCallHandler extends EventEmitter { return this.getAllActiveCallsNotInRoom(roomId); } - public getTransfereeForCallId(callId: string): MatrixCall { - return this.transferees[callId]; + public getTransfereeForCallId(callId: string): MatrixCall | undefined { + return this.transferees.get(callId); } public play(audioId: AudioID): void { @@ -621,7 +621,7 @@ export default class LegacyCallHandler extends EventEmitter { logger.log(`Asserted identity ${newAssertedIdentity} mapped to ${newNativeAssertedIdentity}`); if (newNativeAssertedIdentity) { - this.assertedIdentityNativeUsers[call.callId] = newNativeAssertedIdentity; + this.assertedIdentityNativeUsers.set(call.callId, newNativeAssertedIdentity); // If we don't already have a room with this user, make one. This will be slightly odd // if they called us because we'll be inviting them, but there's not much we can do about @@ -917,7 +917,7 @@ export default class LegacyCallHandler extends EventEmitter { return; } if (transferee) { - this.transferees[call.callId] = transferee; + this.transferees.set(call.callId, transferee); } this.setCallListeners(call); diff --git a/src/Login.ts b/src/Login.ts index 6475a9f5c938..dbcdfe954e2d 100644 --- a/src/Login.ts +++ b/src/Login.ts @@ -91,12 +91,12 @@ export default class Login { } public loginViaPassword( - username: string, - phoneCountry: string, - phoneNumber: string, + username: string | undefined, + phoneCountry: string | undefined, + phoneNumber: string | undefined, password: string, ): Promise { - const isEmail = username.indexOf("@") > 0; + const isEmail = username?.indexOf("@") > 0; let identifier; if (phoneCountry && phoneNumber) { diff --git a/src/MediaDeviceHandler.ts b/src/MediaDeviceHandler.ts index 329741fe511a..fcb066e92fe8 100644 --- a/src/MediaDeviceHandler.ts +++ b/src/MediaDeviceHandler.ts @@ -37,7 +37,7 @@ export enum MediaDeviceHandlerEvent { } export default class MediaDeviceHandler extends EventEmitter { - private static internalInstance; + private static internalInstance?: MediaDeviceHandler; public static get instance(): MediaDeviceHandler { if (!MediaDeviceHandler.internalInstance) { @@ -67,7 +67,7 @@ export default class MediaDeviceHandler extends EventEmitter { public static async getDevices(): Promise { try { const devices = await navigator.mediaDevices.enumerateDevices(); - const output = { + const output: Record = { [MediaDeviceKindEnum.AudioOutput]: [], [MediaDeviceKindEnum.AudioInput]: [], [MediaDeviceKindEnum.VideoInput]: [], diff --git a/src/NodeAnimator.tsx b/src/NodeAnimator.tsx index 24b4e85ae375..be2a3442e925 100644 --- a/src/NodeAnimator.tsx +++ b/src/NodeAnimator.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactInstance } from "react"; import ReactDom from "react-dom"; interface IChildProps { @@ -41,7 +41,7 @@ interface IProps { * automatic positional animation, look at react-shuffle or similar libraries. */ export default class NodeAnimator extends React.Component { - private nodes = {}; + private nodes: Record = {}; private children: { [key: string]: React.DetailedReactHTMLElement }; public static defaultProps: Partial = { startStyles: [], @@ -65,7 +65,7 @@ export default class NodeAnimator extends React.Component { */ private applyStyles(node: HTMLElement, styles: React.CSSProperties): void { Object.entries(styles).forEach(([property, value]) => { - node.style[property] = value; + node.style[property as keyof Omit] = value; }); } diff --git a/src/Notifier.ts b/src/Notifier.ts index 42909a2632e2..0faa53334102 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -68,7 +68,7 @@ Override both the content body and the TextForEvent handler for specific msgtype This is useful when the content body contains fallback text that would explain that the client can't handle a particular type of tile. */ -const msgTypeHandlers = { +const msgTypeHandlers: Record string> = { [MsgType.KeyVerificationRequest]: (event: MatrixEvent) => { const name = (event.sender || {}).name; return _t("%(name)s is requesting verification", { name }); @@ -95,22 +95,26 @@ const msgTypeHandlers = { }, }; -export const Notifier = { - notifsByRoom: {}, +class NotifierClass { + private notifsByRoom: Record = {}; // A list of event IDs that we've received but need to wait until // they're decrypted until we decide whether to notify for them // or not - pendingEncryptedEventIds: [], + private pendingEncryptedEventIds: string[] = []; - notificationMessageForEvent: function (ev: MatrixEvent): string { + private toolbarHidden?: boolean; + private isSyncing?: boolean; + + public notificationMessageForEvent(ev: MatrixEvent): string { if (msgTypeHandlers.hasOwnProperty(ev.getContent().msgtype)) { return msgTypeHandlers[ev.getContent().msgtype](ev); } return TextForEvent.textForEvent(ev); - }, + } - _displayPopupNotification: function (ev: MatrixEvent, room: Room): void { + // XXX: exported for tests + public displayPopupNotification(ev: MatrixEvent, room: Room): void { const plaf = PlatformPeg.get(); const cli = MatrixClientPeg.get(); if (!plaf) { @@ -165,9 +169,14 @@ export const Notifier = { if (this.notifsByRoom[ev.getRoomId()] === undefined) this.notifsByRoom[ev.getRoomId()] = []; this.notifsByRoom[ev.getRoomId()].push(notif); } - }, - - getSoundForRoom: function (roomId: string) { + } + + public getSoundForRoom(roomId: string): { + url: string; + name: string; + type: string; + size: string; + } | null { // We do no caching here because the SDK caches setting // and the browser will cache the sound. const content = SettingsStore.getValue("notificationSound", roomId); @@ -193,9 +202,10 @@ export const Notifier = { type: content.type, size: content.size, }; - }, + } - _playAudioNotification: async function (ev: MatrixEvent, room: Room): Promise { + // XXX: Exported for tests + public async playAudioNotification(ev: MatrixEvent, room: Room): Promise { const cli = MatrixClientPeg.get(); if (localNotificationsAreSilenced(cli)) { return; @@ -224,39 +234,32 @@ export const Notifier = { } catch (ex) { logger.warn("Caught error when trying to fetch room notification sound:", ex); } - }, + } - start: function (this: typeof Notifier) { - // do not re-bind in the case of repeated call - this.boundOnEvent = this.boundOnEvent || this.onEvent.bind(this); - this.boundOnSyncStateChange = this.boundOnSyncStateChange || this.onSyncStateChange.bind(this); - this.boundOnRoomReceipt = this.boundOnRoomReceipt || this.onRoomReceipt.bind(this); - this.boundOnEventDecrypted = this.boundOnEventDecrypted || this.onEventDecrypted.bind(this); - - MatrixClientPeg.get().on(RoomEvent.Timeline, this.boundOnEvent); - MatrixClientPeg.get().on(RoomEvent.Receipt, this.boundOnRoomReceipt); - MatrixClientPeg.get().on(MatrixEventEvent.Decrypted, this.boundOnEventDecrypted); - MatrixClientPeg.get().on(ClientEvent.Sync, this.boundOnSyncStateChange); + public start(): void { + MatrixClientPeg.get().on(RoomEvent.Timeline, this.onEvent); + MatrixClientPeg.get().on(RoomEvent.Receipt, this.onRoomReceipt); + MatrixClientPeg.get().on(MatrixEventEvent.Decrypted, this.onEventDecrypted); + MatrixClientPeg.get().on(ClientEvent.Sync, this.onSyncStateChange); this.toolbarHidden = false; this.isSyncing = false; - }, + } - stop: function (this: typeof Notifier) { + public stop(): void { if (MatrixClientPeg.get()) { - MatrixClientPeg.get().removeListener(RoomEvent.Timeline, this.boundOnEvent); - MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.boundOnRoomReceipt); - MatrixClientPeg.get().removeListener(MatrixEventEvent.Decrypted, this.boundOnEventDecrypted); - MatrixClientPeg.get().removeListener(ClientEvent.Sync, this.boundOnSyncStateChange); + MatrixClientPeg.get().removeListener(RoomEvent.Timeline, this.onEvent); + MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.onRoomReceipt); + MatrixClientPeg.get().removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted); + MatrixClientPeg.get().removeListener(ClientEvent.Sync, this.onSyncStateChange); } this.isSyncing = false; - }, + } - supportsDesktopNotifications: function () { - const plaf = PlatformPeg.get(); - return plaf && plaf.supportsNotifications(); - }, + public supportsDesktopNotifications(): boolean { + return PlatformPeg.get()?.supportsNotifications() ?? false; + } - setEnabled: function (enable: boolean, callback?: () => void) { + public setEnabled(enable: boolean, callback?: () => void): void { const plaf = PlatformPeg.get(); if (!plaf) return; @@ -320,31 +323,30 @@ export const Notifier = { // set the notifications_hidden flag, as the user has knowingly interacted // with the setting we shouldn't nag them any further this.setPromptHidden(true); - }, + } - isEnabled: function () { + public isEnabled(): boolean { return this.isPossible() && SettingsStore.getValue("notificationsEnabled"); - }, + } - isPossible: function () { + public isPossible(): boolean { const plaf = PlatformPeg.get(); - if (!plaf) return false; - if (!plaf.supportsNotifications()) return false; + if (!plaf?.supportsNotifications()) return false; if (!plaf.maySendNotifications()) return false; return true; // possible, but not necessarily enabled - }, + } - isBodyEnabled: function () { + public isBodyEnabled(): boolean { return this.isEnabled() && SettingsStore.getValue("notificationBodyEnabled"); - }, + } - isAudioEnabled: function () { + public isAudioEnabled(): boolean { // We don't route Audio via the HTML Notifications API so it is possible regardless of other things return SettingsStore.getValue("audioNotificationsEnabled"); - }, + } - setPromptHidden: function (this: typeof Notifier, hidden: boolean, persistent = true) { + public setPromptHidden(hidden: boolean, persistent = true): void { this.toolbarHidden = hidden; hideNotificationsToast(); @@ -353,9 +355,9 @@ export const Notifier = { if (persistent && global.localStorage) { global.localStorage.setItem("notifications_hidden", String(hidden)); } - }, + } - shouldShowPrompt: function () { + public shouldShowPrompt(): boolean { const client = MatrixClientPeg.get(); if (!client) { return false; @@ -366,25 +368,21 @@ export const Notifier = { this.supportsDesktopNotifications() && !isPushNotifyDisabled() && !this.isEnabled() && - !this._isPromptHidden() + !this.isPromptHidden() ); - }, + } - _isPromptHidden: function (this: typeof Notifier) { + private isPromptHidden(): boolean { // Check localStorage for any such meta data if (global.localStorage) { return global.localStorage.getItem("notifications_hidden") === "true"; } return this.toolbarHidden; - }, + } - onSyncStateChange: function ( - this: typeof Notifier, - state: SyncState, - prevState?: SyncState, - data?: ISyncStateData, - ) { + // XXX: Exported for tests + public onSyncStateChange = (state: SyncState, prevState?: SyncState, data?: ISyncStateData): void => { if (state === SyncState.Syncing) { this.isSyncing = true; } else if (state === SyncState.Stopped || state === SyncState.Error) { @@ -395,16 +393,15 @@ export const Notifier = { if (![SyncState.Stopped, SyncState.Error].includes(state) && !data?.fromCache) { createLocalNotificationSettingsIfNeeded(MatrixClientPeg.get()); } - }, + }; - onEvent: function ( - this: typeof Notifier, + private onEvent = ( ev: MatrixEvent, room: Room | undefined, toStartOfTimeline: boolean | undefined, removed: boolean, data: IRoomTimelineData, - ) { + ): void => { if (!data.liveEvent) return; // only notify for new things, not old. if (!this.isSyncing) return; // don't alert for any messages initially if (ev.getSender() === MatrixClientPeg.get().getUserId()) return; @@ -422,10 +419,10 @@ export const Notifier = { return; } - this._evaluateEvent(ev); - }, + this.evaluateEvent(ev); + }; - onEventDecrypted: function (ev: MatrixEvent) { + private onEventDecrypted = (ev: MatrixEvent): void => { // 'decrypted' means the decryption process has finished: it may have failed, // in which case it might decrypt soon if the keys arrive if (ev.isDecryptionFailure()) return; @@ -434,10 +431,10 @@ export const Notifier = { if (idx === -1) return; this.pendingEncryptedEventIds.splice(idx, 1); - this._evaluateEvent(ev); - }, + this.evaluateEvent(ev); + }; - onRoomReceipt: function (ev: MatrixEvent, room: Room) { + private onRoomReceipt = (ev: MatrixEvent, room: Room): void => { if (room.getUnreadNotificationCount() === 0) { // ideally we would clear each notification when it was read, // but we have no way, given a read receipt, to know whether @@ -453,12 +450,12 @@ export const Notifier = { } delete this.notifsByRoom[room.roomId]; } - }, + }; - _evaluateEvent: function (ev: MatrixEvent) { + // XXX: exported for tests + public evaluateEvent(ev: MatrixEvent): void { // Mute notifications for broadcast info events if (ev.getType() === VoiceBroadcastInfoEventType) return; - let roomId = ev.getRoomId(); if (LegacyCallHandler.instance.getSupportsVirtualRooms()) { // Attempt to translate a virtual room to a native one @@ -477,7 +474,7 @@ export const Notifier = { const actions = MatrixClientPeg.get().getPushActionsForEvent(ev); if (actions?.notify) { - this._performCustomEventHandling(ev); + this.performCustomEventHandling(ev); const store = SdkContextClass.instance.roomViewStore; const isViewingRoom = store.getRoomId() === room.roomId; @@ -492,19 +489,19 @@ export const Notifier = { } if (this.isEnabled()) { - this._displayPopupNotification(ev, room); + this.displayPopupNotification(ev, room); } if (actions.tweaks.sound && this.isAudioEnabled()) { PlatformPeg.get().loudNotification(ev, room); - this._playAudioNotification(ev, room); + this.playAudioNotification(ev, room); } } - }, + } /** * Some events require special handling such as showing in-app toasts */ - _performCustomEventHandling: function (ev: MatrixEvent) { + private performCustomEventHandling(ev: MatrixEvent): void { if (ElementCall.CALL_EVENT_TYPE.names.includes(ev.getType()) && SettingsStore.getValue("feature_group_calls")) { ToastStore.sharedInstance().addOrReplaceToast({ key: getIncomingCallToastKey(ev.getStateKey()), @@ -514,11 +511,12 @@ export const Notifier = { props: { callEvent: ev }, }); } - }, -}; + } +} if (!window.mxNotifier) { - window.mxNotifier = Notifier; + window.mxNotifier = new NotifierClass(); } export default window.mxNotifier; +export const Notifier: NotifierClass = window.mxNotifier; diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts index c8a99ab42640..173fdb0b19b8 100644 --- a/src/PosthogAnalytics.ts +++ b/src/PosthogAnalytics.ts @@ -132,8 +132,8 @@ export class PosthogAnalytics { private anonymity = Anonymity.Disabled; // set true during the constructor if posthog config is present, otherwise false private readonly enabled: boolean = false; - private static _instance = null; - private platformSuperProperties = {}; + private static _instance: PosthogAnalytics | null = null; + private platformSuperProperties: Properties = {}; public static readonly ANALYTICS_EVENT_TYPE = "im.vector.analytics"; private propertiesForNextEvent: Partial> = {}; private userPropertyCache: UserProperties = {}; diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts index 20db6594b017..bc1c4bb4b986 100644 --- a/src/SecurityManager.ts +++ b/src/SecurityManager.ts @@ -182,7 +182,7 @@ async function getSecretStorageKey({ export async function getDehydrationKey( keyInfo: ISecretStorageKeyInfo, - checkFunc: (Uint8Array) => void, + checkFunc: (data: Uint8Array) => void, ): Promise { const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.(); if (keyFromCustomisations) { @@ -196,7 +196,7 @@ export async function getDehydrationKey( /* props= */ { keyInfo, - checkPrivateKey: async (input): Promise => { + checkPrivateKey: async (input: KeyParams): Promise => { const key = await inputToKey(input); try { checkFunc(key); @@ -290,7 +290,7 @@ export async function promptForBackupPassphrase(): Promise { RestoreKeyBackupDialog, { showSummary: false, - keyCallback: (k) => (key = k), + keyCallback: (k: Uint8Array) => (key = k), }, null, /* priority = */ false, diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 1ffe43fec739..43350e44d0b6 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -697,11 +697,8 @@ export const Commands = [ } if (viaServers) { - // For the join - dispatch["opts"] = { - // These are passed down to the js-sdk's /join call - viaServers: viaServers, - }; + // For the join, these are passed down to the js-sdk's /join call + dispatch["opts"] = { viaServers }; // For if the join fails (rejoin button) dispatch["via_servers"] = viaServers; diff --git a/src/Terms.ts b/src/Terms.ts index bb18a18cf7e1..f66f543887cd 100644 --- a/src/Terms.ts +++ b/src/Terms.ts @@ -52,11 +52,13 @@ export type Policies = { [policy: string]: Policy; }; +export type ServicePolicyPair = { + policies: Policies; + service: Service; +}; + export type TermsInteractionCallback = ( - policiesAndServicePairs: { - service: Service; - policies: Policies; - }[], + policiesAndServicePairs: ServicePolicyPair[], agreedUrls: string[], extraClassNames?: string, ) => Promise; @@ -117,9 +119,9 @@ export async function startTermsFlow( // but then they'd assume they can un-check the boxes to un-agree to a policy, // but that is not a thing the API supports, so probably best to just show // things they've not agreed to yet. - const unagreedPoliciesAndServicePairs = []; + const unagreedPoliciesAndServicePairs: ServicePolicyPair[] = []; for (const { service, policies } of policiesAndServicePairs) { - const unagreedPolicies = {}; + const unagreedPolicies: Policies = {}; for (const [policyName, policy] of Object.entries(policies)) { let policyAgreed = false; for (const lang of Object.keys(policy)) { diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx index 7f874f8a898c..a515576748b9 100644 --- a/src/TextForEvent.tsx +++ b/src/TextForEvent.tsx @@ -33,7 +33,7 @@ import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases"; import defaultDispatcher from "./dispatcher/dispatcher"; import { MatrixClientPeg } from "./MatrixClientPeg"; import { ROOM_SECURITY_TAB } from "./components/views/dialogs/RoomSettingsDialog"; -import AccessibleButton from "./components/views/elements/AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "./components/views/elements/AccessibleButton"; import RightPanelStore from "./stores/right-panel/RightPanelStore"; import { highlightEvent, isLocationEvent } from "./utils/EventUtils"; import { ElementCall } from "./models/Call"; @@ -308,7 +308,7 @@ function textForServerACLEvent(ev: MatrixEvent): () => string | null { allow_ip_literals: prevContent.allow_ip_literals !== false, }; - let getText = null; + let getText: () => string = null; if (prev.deny.length === 0 && prev.allow.length === 0) { getText = () => _t("%(senderDisplayName)s set the server ACLs for this room.", { senderDisplayName }); } else { @@ -360,8 +360,8 @@ function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null { const oldAltAliases = ev.getPrevContent().alt_aliases || []; const newAlias = ev.getContent().alias; const newAltAliases = ev.getContent().alt_aliases || []; - const removedAltAliases = oldAltAliases.filter((alias) => !newAltAliases.includes(alias)); - const addedAltAliases = newAltAliases.filter((alias) => !oldAltAliases.includes(alias)); + const removedAltAliases = oldAltAliases.filter((alias: string) => !newAltAliases.includes(alias)); + const addedAltAliases = newAltAliases.filter((alias: string) => !oldAltAliases.includes(alias)); if (!removedAltAliases.length && !addedAltAliases.length) { if (newAlias) { @@ -533,8 +533,8 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render const senderName = getSenderName(event); const roomId = event.getRoomId(); - const pinned = event.getContent().pinned ?? []; - const previouslyPinned = event.getPrevContent().pinned ?? []; + const pinned = event.getContent<{ pinned: string[] }>().pinned ?? []; + const previouslyPinned: string[] = event.getPrevContent().pinned ?? []; const newlyPinned = pinned.filter((item) => previouslyPinned.indexOf(item) < 0); const newlyUnpinned = previouslyPinned.filter((item) => pinned.indexOf(item) < 0); @@ -550,7 +550,10 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render { senderName }, { a: (sub) => ( - highlightEvent(roomId, messageId)}> + highlightEvent(roomId, messageId)} + > {sub} ), @@ -580,7 +583,10 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render { senderName }, { a: (sub) => ( - highlightEvent(roomId, messageId)}> + highlightEvent(roomId, messageId)} + > {sub} ), diff --git a/src/UserActivity.ts b/src/UserActivity.ts index 44c8abb2c7f6..ae6417d4f4d8 100644 --- a/src/UserActivity.ts +++ b/src/UserActivity.ts @@ -168,7 +168,7 @@ export default class UserActivity { return this.activeRecentlyTimeout.isRunning(); } - private onPageVisibilityChanged = (e): void => { + private onPageVisibilityChanged = (e: Event): void => { if (this.document.visibilityState === "hidden") { this.activeNowTimeout.abort(); this.activeRecentlyTimeout.abort(); @@ -182,7 +182,8 @@ export default class UserActivity { this.activeRecentlyTimeout.abort(); }; - private onUserActivity = (event: Event): void => { + // XXX: exported for tests + public onUserActivity = (event: Event): void => { // ignore anything if the window isn't focused if (!this.document.hasFocus()) return; diff --git a/src/accessibility/KeyboardShortcutUtils.ts b/src/accessibility/KeyboardShortcutUtils.ts index bb42f7c1ce9a..8ba866be3fab 100644 --- a/src/accessibility/KeyboardShortcutUtils.ts +++ b/src/accessibility/KeyboardShortcutUtils.ts @@ -27,6 +27,7 @@ import { KEYBOARD_SHORTCUTS, MAC_ONLY_SHORTCUTS, } from "./KeyboardShortcuts"; +import { IBaseSetting } from "../settings/Settings"; /** * This function gets the keyboard shortcuts that should be presented in the UI @@ -103,7 +104,7 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => { return true; }) .reduce((o, key) => { - o[key] = KEYBOARD_SHORTCUTS[key]; + o[key as KeyBindingAction] = KEYBOARD_SHORTCUTS[key as KeyBindingAction]; return o; }, {} as IKeyboardShortcuts); }; @@ -112,7 +113,10 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => { * Gets keyboard shortcuts that should be presented to the user in the UI. */ export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => { - const entries = [...Object.entries(getUIOnlyShortcuts()), ...Object.entries(getKeyboardShortcuts())]; + const entries = [...Object.entries(getUIOnlyShortcuts()), ...Object.entries(getKeyboardShortcuts())] as [ + KeyBindingAction, + IBaseSetting, + ][]; return entries.reduce((acc, [key, value]) => { acc[key] = value; @@ -120,11 +124,11 @@ export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => { }, {} as IKeyboardShortcuts); }; -export const getKeyboardShortcutValue = (name: string): KeyCombo | undefined => { +export const getKeyboardShortcutValue = (name: KeyBindingAction): KeyCombo | undefined => { return getKeyboardShortcutsForUI()[name]?.default; }; -export const getKeyboardShortcutDisplayName = (name: string): string | undefined => { +export const getKeyboardShortcutDisplayName = (name: KeyBindingAction): string | undefined => { const keyboardShortcutDisplayName = getKeyboardShortcutsForUI()[name]?.displayName; - return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName); + return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName as string); }; diff --git a/src/accessibility/KeyboardShortcuts.ts b/src/accessibility/KeyboardShortcuts.ts index 0e536ac1497d..3011a5b5bd79 100644 --- a/src/accessibility/KeyboardShortcuts.ts +++ b/src/accessibility/KeyboardShortcuts.ts @@ -156,10 +156,8 @@ export enum KeyBindingAction { type KeyboardShortcutSetting = IBaseSetting; -export type IKeyboardShortcuts = { - // TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager - [k in KeyBindingAction]?: KeyboardShortcutSetting; -}; +// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager +export type IKeyboardShortcuts = Partial>; export interface ICategory { categoryLabel?: string; diff --git a/src/accessibility/RovingTabIndex.tsx b/src/accessibility/RovingTabIndex.tsx index 605ffb1f5b53..b449b10710f3 100644 --- a/src/accessibility/RovingTabIndex.tsx +++ b/src/accessibility/RovingTabIndex.tsx @@ -25,6 +25,7 @@ import React, { Reducer, Dispatch, RefObject, + ReactNode, } from "react"; import { getKeyBindingsManager } from "../KeyBindingsManager"; @@ -158,8 +159,8 @@ interface IProps { handleHomeEnd?: boolean; handleUpDown?: boolean; handleLeftRight?: boolean; - children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent) }); - onKeyDown?(ev: React.KeyboardEvent, state: IState); + children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent): void }): ReactNode; + onKeyDown?(ev: React.KeyboardEvent, state: IState): void; } export const findSiblingElement = ( diff --git a/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx b/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx index ee3a0e4d368b..e8e69865d78c 100644 --- a/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx +++ b/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx @@ -25,7 +25,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager"; interface IProps extends React.ComponentProps { label?: string; - onChange(); // we handle keyup/down ourselves so lose the ChangeEvent + onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent onClose(): void; // gets called after onChange on KeyBindingAction.ActivateSelectedButton } diff --git a/src/accessibility/context_menu/StyledMenuItemRadio.tsx b/src/accessibility/context_menu/StyledMenuItemRadio.tsx index 2fe873843406..7a394a3d1f9a 100644 --- a/src/accessibility/context_menu/StyledMenuItemRadio.tsx +++ b/src/accessibility/context_menu/StyledMenuItemRadio.tsx @@ -25,7 +25,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager"; interface IProps extends React.ComponentProps { label?: string; - onChange(); // we handle keyup/down ourselves so lose the ChangeEvent + onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent onClose(): void; // gets called after onChange on KeyBindingAction.Enter } diff --git a/src/accessibility/roving/RovingAccessibleButton.tsx b/src/accessibility/roving/RovingAccessibleButton.tsx index 3968ef6d6bd4..71818c6cda15 100644 --- a/src/accessibility/roving/RovingAccessibleButton.tsx +++ b/src/accessibility/roving/RovingAccessibleButton.tsx @@ -30,7 +30,7 @@ export const RovingAccessibleButton: React.FC = ({ inputRef, onFocus, .. return ( { + onFocus={(event: React.FocusEvent) => { onFocusInternal(); onFocus?.(event); }} diff --git a/src/accessibility/roving/RovingAccessibleTooltipButton.tsx b/src/accessibility/roving/RovingAccessibleTooltipButton.tsx index f30225f0f72f..f06cc934bbc3 100644 --- a/src/accessibility/roving/RovingAccessibleTooltipButton.tsx +++ b/src/accessibility/roving/RovingAccessibleTooltipButton.tsx @@ -31,7 +31,7 @@ export const RovingAccessibleTooltipButton: React.FC = ({ inputRef, onFo return ( { + onFocus={(event: React.FocusEvent) => { onFocusInternal(); onFocus?.(event); }} diff --git a/src/accessibility/roving/RovingTabIndexWrapper.tsx b/src/accessibility/roving/RovingTabIndexWrapper.tsx index b549f18119e4..4208d47499f2 100644 --- a/src/accessibility/roving/RovingTabIndexWrapper.tsx +++ b/src/accessibility/roving/RovingTabIndexWrapper.tsx @@ -14,14 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactElement } from "react"; import { useRovingTabIndex } from "../RovingTabIndex"; import { FocusHandler, Ref } from "./types"; interface IProps { inputRef?: Ref; - children(renderProps: { onFocus: FocusHandler; isActive: boolean; ref: Ref }); + children(renderProps: { onFocus: FocusHandler; isActive: boolean; ref: Ref }): ReactElement; } // Wrapper to allow use of useRovingTabIndex outside of React Functional Components. diff --git a/src/actions/RoomListActions.ts b/src/actions/RoomListActions.ts index 637b57071e44..49351757ca7b 100644 --- a/src/actions/RoomListActions.ts +++ b/src/actions/RoomListActions.ts @@ -54,7 +54,7 @@ export default class RoomListActions { oldIndex: number | null, newIndex: number | null, ): AsyncActionPayload { - let metaData = null; + let metaData: Parameters[2] | null = null; // Is the tag ordered manually? const store = RoomListStore.instance; @@ -81,7 +81,7 @@ export default class RoomListActions { return asyncAction( "RoomListActions.tagRoom", () => { - const promises = []; + const promises: Promise[] = []; const roomId = room.roomId; // Evil hack to get DMs behaving @@ -120,7 +120,7 @@ export default class RoomListActions { if (newTag && newTag !== DefaultTagID.DM && (hasChangedSubLists || metaData)) { // metaData is the body of the PUT to set the tag, so it must // at least be an empty object. - metaData = metaData || {}; + metaData = metaData || ({} as typeof metaData); const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function (err) { logger.error("Failed to add tag " + newTag + " to room: " + err); diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx index 63a132077fa5..f93e097a9d80 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ChangeEvent } from "react"; +import { Room } from "matrix-js-sdk/src/models/room"; import { _t } from "../../../../languageHandler"; import SdkConfig from "../../../../SdkConfig"; @@ -27,6 +28,7 @@ import Field from "../../../../components/views/elements/Field"; import BaseDialog from "../../../../components/views/dialogs/BaseDialog"; import DialogButtons from "../../../../components/views/elements/DialogButtons"; import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps"; +import { IIndexStats } from "../../../../indexing/BaseEventIndexManager"; interface IProps extends IDialogProps {} @@ -43,7 +45,7 @@ interface IState { * Allows the user to introspect the event index state and disable it. */ export default class ManageEventIndexDialog extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { @@ -56,9 +58,9 @@ export default class ManageEventIndexDialog extends React.Component => { + public updateCurrentRoom = async (room: Room): Promise => { const eventIndex = EventIndexPeg.get(); - let stats; + let stats: IIndexStats; try { stats = await eventIndex.getStats(); @@ -136,8 +138,8 @@ export default class ManageEventIndexDialog extends React.Component { - this.setState({ crawlerSleepTime: e.target.value }); + private onCrawlerSleepTimeChange = (e: ChangeEvent): void => { + this.setState({ crawlerSleepTime: parseInt(e.target.value, 10) }); SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value); }; diff --git a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx index b595a60a2e07..41b91fba1dce 100644 --- a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx +++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx @@ -20,7 +20,7 @@ import FileSaver from "file-saver"; import { logger } from "matrix-js-sdk/src/logger"; import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup"; import { TrustInfo } from "matrix-js-sdk/src/crypto/backup"; -import { CrossSigningKeys } from "matrix-js-sdk/src/matrix"; +import { CrossSigningKeys, UIAFlow } from "matrix-js-sdk/src/matrix"; import { IRecoveryKey } from "matrix-js-sdk/src/crypto/api"; import { CryptoEvent } from "matrix-js-sdk/src/crypto"; @@ -206,7 +206,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { + const canUploadKeysWithPasswordOnly = error.data.flows.some((f: UIAFlow) => { return f.stages.length === 1 && f.stages[0] === "m.login.password"; }); this.setState({ diff --git a/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx index c8a561e7dae8..7bfdf400cf76 100644 --- a/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx +++ b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx @@ -16,7 +16,7 @@ limitations under the License. */ import FileSaver from "file-saver"; -import React from "react"; +import React, { ChangeEvent } from "react"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { logger } from "matrix-js-sdk/src/logger"; @@ -163,7 +163,9 @@ export default class ExportE2eKeysDialog extends React.Component this.onPassphraseChange(e, "passphrase1")} + onChange={(e: ChangeEvent) => + this.onPassphraseChange(e, "passphrase1") + } autoFocus={true} size={64} type="password" @@ -174,7 +176,9 @@ export default class ExportE2eKeysDialog extends React.Component this.onPassphraseChange(e, "passphrase2")} + onChange={(e: ChangeEvent) => + this.onPassphraseChange(e, "passphrase2") + } size={64} type="password" disabled={disableForm} diff --git a/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx index 079271b02132..e49c090a75ab 100644 --- a/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx +++ b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx @@ -73,9 +73,9 @@ export default class ImportE2eKeysDialog extends React.Component } private onFormChange = (): void => { - const files = this.file.current.files || []; + const files = this.file.current.files; this.setState({ - enableSubmit: this.state.passphrase !== "" && files.length > 0, + enableSubmit: this.state.passphrase !== "" && !!files?.length, }); }; diff --git a/src/audio/RecorderWorklet.ts b/src/audio/RecorderWorklet.ts index 0c0cc56cd68e..3cb9cf03d286 100644 --- a/src/audio/RecorderWorklet.ts +++ b/src/audio/RecorderWorklet.ts @@ -43,7 +43,11 @@ class MxVoiceWorklet extends AudioWorkletProcessor { private nextAmplitudeSecond = 0; private amplitudeIndex = 0; - public process(inputs, outputs, parameters): boolean { + public process( + inputs: Float32Array[][], + outputs: Float32Array[][], + parameters: Record, + ): 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/VoiceRecording.ts b/src/audio/VoiceRecording.ts index 32fcb5a97ab2..f8f9f01b6676 100644 --- a/src/audio/VoiceRecording.ts +++ b/src/audio/VoiceRecording.ts @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// @ts-ignore import Recorder from "opus-recorder/dist/recorder.min.js"; import encoderPath from "opus-recorder/dist/encoderWorker.min.js"; import { SimpleObservable } from "matrix-widget-api"; diff --git a/src/audio/compat.ts b/src/audio/compat.ts index ab63f644a19b..ce0fc30816d5 100644 --- a/src/audio/compat.ts +++ b/src/audio/compat.ts @@ -69,7 +69,7 @@ export function decodeOgg(audioBuffer: ArrayBuffer): Promise { command: "encode", buffers: ev.data, }, - ev.data.map((b) => b.buffer), + ev.data.map((b: Float32Array) => b.buffer), ); }; diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx index cc25068db86d..6b55dbf857b7 100644 --- a/src/autocomplete/EmojiProvider.tsx +++ b/src/autocomplete/EmojiProvider.tsx @@ -19,7 +19,7 @@ limitations under the License. */ import React from "react"; -import { uniq, sortBy } from "lodash"; +import { uniq, sortBy, ListIteratee } from "lodash"; import EMOTICON_REGEX from "emojibase-regex/emoticon"; import { Room } from "matrix-js-sdk/src/models/room"; @@ -55,7 +55,11 @@ const SORTED_EMOJI: ISortedEmoji[] = EMOJI.sort((a, b) => { _orderBy: index, })); -function score(query: string, space: string): number { +function score(query: string, space: string[] | string): number { + if (Array.isArray(space)) { + return Math.min(...space.map((s) => score(query, s))); + } + const index = space.indexOf(query); if (index === -1) { return Infinity; @@ -113,7 +117,7 @@ export default class EmojiProvider extends AutocompleteProvider { // Do second match with shouldMatchWordsOnly in order to match against 'name' completions = completions.concat(this.nameMatcher.match(matchedString)); - let sorters = []; + let sorters: ListIteratee[] = []; // make sure that emoticons come first sorters.push((c) => score(matchedString, c.emoji.emoticon || "")); diff --git a/src/autocomplete/UserProvider.tsx b/src/autocomplete/UserProvider.tsx index 65de4b1bb4fb..5e8d25f4c839 100644 --- a/src/autocomplete/UserProvider.tsx +++ b/src/autocomplete/UserProvider.tsx @@ -110,17 +110,14 @@ export default class UserProvider extends AutocompleteProvider { // lazy-load user list into matcher if (!this.users) this.makeUsers(); - let completions = []; const { command, range } = this.getCurrentCommand(rawQuery, selection, force); - if (!command) return completions; - - const fullMatch = command[0]; + const fullMatch = command?.[0]; // Don't search if the query is a single "@" if (fullMatch && fullMatch !== "@") { // Don't include the '@' in our search query - it's only used as a way to trigger completion const query = fullMatch.startsWith("@") ? fullMatch.substring(1) : fullMatch; - completions = this.matcher.match(query, limit).map((user) => { + return this.matcher.match(query, limit).map((user) => { const description = UserIdentifierCustomisations.getDisplayUserIdentifier(user.userId, { roomId: this.room.roomId, withDisplayName: true, @@ -143,7 +140,7 @@ export default class UserProvider extends AutocompleteProvider { }; }); } - return completions; + return []; } public getName(): string { @@ -152,7 +149,7 @@ export default class UserProvider extends AutocompleteProvider { private makeUsers(): void { const events = this.room.getLiveTimeline().getEvents(); - const lastSpoken = {}; + const lastSpoken: Record = {}; for (const event of events) { lastSpoken[event.getSender()] = event.getTs(); diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index 978dd07be916..118e1d8d9540 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -99,9 +99,9 @@ export interface IProps extends MenuProps { closeOnInteraction?: boolean; // Function to be called on menu close - onFinished(); + onFinished(): void; // on resize callback - windowResize?(); + windowResize?(): void; } interface IState { @@ -119,8 +119,8 @@ export default class ContextMenu extends React.PureComponent { managed: true, }; - public constructor(props, context) { - super(props, context); + public constructor(props: IProps) { + super(props); this.state = { contextMenuElem: null, @@ -387,13 +387,13 @@ export default class ContextMenu extends React.PureComponent { menuStyle["paddingRight"] = menuPaddingRight; } - const wrapperStyle = {}; + const wrapperStyle: CSSProperties = {}; if (!isNaN(Number(zIndex))) { menuStyle["zIndex"] = zIndex + 1; wrapperStyle["zIndex"] = zIndex; } - let background; + let background: JSX.Element; if (hasBackground) { background = (

, ): { close: (...args: any[]) => void } { - const onFinished = function (...args): void { + const onFinished = function (...args: any[]): void { ReactDOM.unmountComponentAtNode(getOrCreateContainer()); props?.onFinished?.apply(null, args); }; diff --git a/src/components/structures/FilePanel.tsx b/src/components/structures/FilePanel.tsx index 4390dcf36edd..1f6c1d258378 100644 --- a/src/components/structures/FilePanel.tsx +++ b/src/components/structures/FilePanel.tsx @@ -43,7 +43,7 @@ interface IProps { } interface IState { - timelineSet: EventTimelineSet; + timelineSet: EventTimelineSet | null; narrow: boolean; } @@ -59,7 +59,7 @@ class FilePanel extends React.Component { public noRoom: boolean; private card = createRef(); - public state = { + public state: IState = { timelineSet: null, narrow: false, }; diff --git a/src/components/structures/GenericDropdownMenu.tsx b/src/components/structures/GenericDropdownMenu.tsx index 6b727c5140db..98dfbf0851f5 100644 --- a/src/components/structures/GenericDropdownMenu.tsx +++ b/src/components/structures/GenericDropdownMenu.tsx @@ -79,6 +79,12 @@ export function GenericDropdownMenuGroup({ ); } +function isGenericDropdownMenuGroupArray( + items: readonly GenericDropdownMenuItem[], +): items is GenericDropdownMenuGroup[] { + return isGenericDropdownMenuGroup(items[0]); +} + function isGenericDropdownMenuGroup(item: GenericDropdownMenuItem): item is GenericDropdownMenuGroup { return "options" in item; } @@ -123,19 +129,19 @@ export function GenericDropdownMenu({ .flatMap((it) => (isGenericDropdownMenuGroup(it) ? [it, ...it.options] : [it])) .find((option) => (toKey ? toKey(option.key) === toKey(value) : option.key === value)); let contextMenuOptions: JSX.Element; - if (options && isGenericDropdownMenuGroup(options[0])) { + if (options && isGenericDropdownMenuGroupArray(options)) { contextMenuOptions = ( <> {options.map((group) => ( {group.options.map((option) => ( { @@ -156,7 +162,7 @@ export function GenericDropdownMenu({ <> {options.map((option) => ( { diff --git a/src/components/structures/InteractiveAuth.tsx b/src/components/structures/InteractiveAuth.tsx index 99be8705a4d9..f11b23905238 100644 --- a/src/components/structures/InteractiveAuth.tsx +++ b/src/components/structures/InteractiveAuth.tsx @@ -80,7 +80,7 @@ interface IProps { // Called when the stage changes, or the stage's phase changes. First // argument is the stage, second is the phase. Some stages do not have // phases and will be counted as 0 (numeric). - onStagePhaseChange?(stage: string, phase: string | number): void; + onStagePhaseChange?(stage: AuthType, phase: number): void; } interface IState { @@ -99,7 +99,7 @@ export default class InteractiveAuthComponent extends React.Component { private listContainerRef = createRef(); private roomListRef = createRef(); - private focusedElement = null; + private focusedElement: Element = null; private isDoingStickyHeaders = false; public constructor(props: IProps) { diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 242bbdc0280b..1cffd6b6f431 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -136,8 +136,8 @@ class LoggedInView extends React.Component { protected backgroundImageWatcherRef: string; protected resizer: Resizer; - public constructor(props, context) { - super(props, context); + public constructor(props: IProps) { + super(props); this.state = { syncErrorData: undefined, @@ -229,8 +229,8 @@ class LoggedInView extends React.Component { }; private createResizer(): Resizer { - let panelSize; - let panelCollapsed; + let panelSize: number; + let panelCollapsed: boolean; const collapseConfig: ICollapseConfig = { // TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel toggleSize: 206 - 50, @@ -341,7 +341,7 @@ class LoggedInView extends React.Component { const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice]; if (!serverNoticeList) return; - const events = []; + const events: MatrixEvent[] = []; let pinnedEventTs = 0; for (const room of serverNoticeList) { const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", ""); @@ -369,7 +369,7 @@ class LoggedInView extends React.Component { e.getContent()["server_notice_type"] === "m.server_notice.usage_limit_reached" ); }); - const usageLimitEventContent = usageLimitEvent && usageLimitEvent.getContent(); + const usageLimitEventContent = usageLimitEvent?.getContent(); this.calculateServerLimitToast(this.state.syncErrorData, usageLimitEventContent); this.setState({ usageLimitEventContent, @@ -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): void => { + private onReactKeyDown = (ev: React.KeyboardEvent): void => { // events caught while bubbling up on the root element // of this component, so something must be focused. this.onKeyDown(ev); }; - private onNativeKeyDown = (ev): void => { + private onNativeKeyDown = (ev: KeyboardEvent): 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): void => { + private onKeyDown = (ev: React.KeyboardEvent | KeyboardEvent): void => { let handled = false; const roomAction = getKeyBindingsManager().getRoomAction(ev); @@ -571,7 +571,7 @@ class LoggedInView extends React.Component { ) { dis.dispatch({ action: Action.SwitchSpace, - num: ev.code.slice(5), // Cut off the first 5 characters - "Digit" + num: parseInt(ev.code.slice(5), 10), // Cut off the first 5 characters - "Digit" }); handled = true; } @@ -615,10 +615,8 @@ 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): void => { - if (this._roomView.current) { - this._roomView.current.handleScrollKey(ev); - } + private onScrollKeyPressed = (ev: React.KeyboardEvent | KeyboardEvent): void => { + this._roomView.current?.handleScrollKey(ev); }; public render(): JSX.Element { diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 7fdda93eb616..8bc29eb33af3 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -419,7 +419,7 @@ export default class MatrixChat extends React.PureComponent { window.addEventListener("resize", this.onWindowResized); } - public componentDidUpdate(prevProps, prevState): void { + public componentDidUpdate(prevProps: IProps, prevState: IState): void { if (this.shouldTrackPageChange(prevState, this.state)) { const durationMs = this.stopPageChangeTimer(); PosthogTrackers.instance.trackPageChange(this.state.view, this.state.page_type, durationMs); @@ -544,12 +544,11 @@ export default class MatrixChat extends React.PureComponent { if (state.view === undefined) { throw new Error("setStateForNewView with no view!"); } - const newState = { - currentUserId: null, + this.setState({ + currentUserId: undefined, justRegistered: false, - }; - Object.assign(newState, state); - this.setState(newState); + ...state, + } as IState); } private onAction = (payload: ActionPayload): void => { diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 2dd432cb9282..c5dc2491994b 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef, KeyboardEvent, ReactNode, TransitionEvent } from "react"; +import React, { createRef, ReactNode, TransitionEvent } from "react"; import ReactDOM from "react-dom"; import classNames from "classnames"; import { Room } from "matrix-js-sdk/src/models/room"; import { EventType } from "matrix-js-sdk/src/@types/event"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { logger } from "matrix-js-sdk/src/logger"; import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon"; @@ -34,7 +34,7 @@ import SettingsStore from "../../settings/SettingsStore"; import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; import { Layout } from "../../settings/enums/Layout"; import { _t } from "../../languageHandler"; -import EventTile, { UnwrappedEventTile, GetRelationsForEvent, IReadReceiptProps } from "../views/rooms/EventTile"; +import EventTile, { GetRelationsForEvent, IReadReceiptProps, UnwrappedEventTile } from "../views/rooms/EventTile"; import { hasText } from "../../TextForEvent"; import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer"; import DMRoomMap from "../../utils/DMRoomMap"; @@ -272,7 +272,7 @@ export default class MessagePanel extends React.Component { // A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination. public grouperKeyMap = new WeakMap(); - public constructor(props, context) { + public constructor(props: IProps, context: React.ContextType) { super(props, context); this.state = { @@ -308,7 +308,7 @@ export default class MessagePanel extends React.Component { SettingsStore.unwatchSetting(this.showTypingNotificationsWatcherRef); } - public componentDidUpdate(prevProps, prevState): void { + public componentDidUpdate(prevProps: IProps, prevState: IState): void { if (prevProps.layout !== this.props.layout) { this.calculateRoomMembersCount(); } @@ -410,17 +410,13 @@ export default class MessagePanel extends React.Component { /* jump to the top of the content. */ public scrollToTop(): void { - if (this.scrollPanel.current) { - this.scrollPanel.current.scrollToTop(); - } + this.scrollPanel.current?.scrollToTop(); } /* jump to the bottom of the content. */ public scrollToBottom(): void { - if (this.scrollPanel.current) { - this.scrollPanel.current.scrollToBottom(); - } + this.scrollPanel.current?.scrollToBottom(); } /** @@ -428,10 +424,8 @@ export default class MessagePanel extends React.Component { * * @param {KeyboardEvent} ev: the keyboard event to handle */ - public handleScrollKey(ev: KeyboardEvent): void { - if (this.scrollPanel.current) { - this.scrollPanel.current.handleScrollKey(ev); - } + public handleScrollKey(ev: React.KeyboardEvent | KeyboardEvent): void { + this.scrollPanel.current?.handleScrollKey(ev); } /* jump to the given event id. @@ -752,7 +746,7 @@ export default class MessagePanel extends React.Component { const readReceipts = this.readReceiptsByEvent[eventId]; let isLastSuccessful = false; - const isSentState = (s): boolean => !s || s === "sent"; + const isSentState = (s: EventStatus): boolean => !s || s === EventStatus.SENT; const isSent = isSentState(mxEv.getAssociatedStatus()); const hasNextEvent = nextEvent && this.shouldShowEvent(nextEvent); if (!hasNextEvent && isSent) { @@ -869,8 +863,14 @@ export default class MessagePanel extends React.Component { // should be shown next to that event. If a hidden event has read receipts, // they are folded into the receipts of the last shown event. private getReadReceiptsByShownEvent(): Record { - const receiptsByEvent = {}; - const receiptsByUserId = {}; + const receiptsByEvent: Record = {}; + const receiptsByUserId: Record< + string, + { + lastShownEventId: string; + receipt: IReadReceiptProps; + } + > = {}; let lastShownEventId; for (const event of this.props.events) { diff --git a/src/components/structures/NonUrgentToastContainer.tsx b/src/components/structures/NonUrgentToastContainer.tsx index 813522ffcb2d..508812ae1648 100644 --- a/src/components/structures/NonUrgentToastContainer.tsx +++ b/src/components/structures/NonUrgentToastContainer.tsx @@ -27,8 +27,8 @@ interface IState { } export default class NonUrgentToastContainer extends React.PureComponent { - public constructor(props, context) { - super(props, context); + public constructor(props: IProps) { + super(props); this.state = { toasts: NonUrgentToastStore.instance.components, diff --git a/src/components/structures/NotificationPanel.tsx b/src/components/structures/NotificationPanel.tsx index ac351399d49f..7a72ea67f7d5 100644 --- a/src/components/structures/NotificationPanel.tsx +++ b/src/components/structures/NotificationPanel.tsx @@ -43,7 +43,7 @@ export default class NotificationPanel extends React.PureComponent(); - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 3748ee0ec748..60138dd4428e 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -63,7 +63,7 @@ export default class RightPanel extends React.Component { public static contextType = MatrixClientContext; public context!: React.ContextType; - public constructor(props, context) { + public constructor(props: IProps, context: React.ContextType) { super(props, context); this.state = { diff --git a/src/components/structures/RoomStatusBar.tsx b/src/components/structures/RoomStatusBar.tsx index f370091a8af5..3a4af77441c9 100644 --- a/src/components/structures/RoomStatusBar.tsx +++ b/src/components/structures/RoomStatusBar.tsx @@ -14,10 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactNode } from "react"; import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { SyncState, ISyncStateData } from "matrix-js-sdk/src/sync"; import { Room } from "matrix-js-sdk/src/models/room"; +import { MatrixError } from "matrix-js-sdk/src/matrix"; import { _t, _td } from "../../languageHandler"; import Resend from "../../Resend"; @@ -192,10 +193,10 @@ export default class RoomStatusBar extends React.PureComponent { private getUnsentMessageContent(): JSX.Element { const unsentMessages = this.state.unsentMessages; - let title; + let title: ReactNode; - let consentError = null; - let resourceLimitError = null; + let consentError: MatrixError | null = null; + let resourceLimitError: MatrixError | null = null; for (const m of unsentMessages) { if (m.error && m.error.errcode === "M_CONSENT_NOT_GIVEN") { consentError = m.error; diff --git a/src/components/structures/RoomStatusBarUnsentMessages.tsx b/src/components/structures/RoomStatusBarUnsentMessages.tsx index 3ce300f0eb45..38dbae281e72 100644 --- a/src/components/structures/RoomStatusBarUnsentMessages.tsx +++ b/src/components/structures/RoomStatusBarUnsentMessages.tsx @@ -14,13 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactElement } from "react"; +import React, { ReactElement, ReactNode } from "react"; import { StaticNotificationState } from "../../stores/notifications/StaticNotificationState"; import NotificationBadge from "../views/rooms/NotificationBadge"; interface RoomStatusBarUnsentMessagesProps { - title: string; + title: ReactNode; description?: string; notificationState: StaticNotificationState; buttons: ReactElement; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 221d2151efd0..6e2b72cf9f03 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -33,6 +33,7 @@ import { CryptoEvent } from "matrix-js-sdk/src/crypto"; import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread"; import { HistoryVisibility } from "matrix-js-sdk/src/@types/partials"; import { ISearchResults } from "matrix-js-sdk/src/@types/search"; +import { IRoomTimelineData } from "matrix-js-sdk/src/models/event-timeline-set"; import shouldHideEvent from "../../shouldHideEvent"; import { _t } from "../../languageHandler"; @@ -49,7 +50,7 @@ import RoomScrollStateStore, { ScrollState } from "../../stores/RoomScrollStateS import WidgetEchoStore from "../../stores/WidgetEchoStore"; import SettingsStore from "../../settings/SettingsStore"; import { Layout } from "../../settings/enums/Layout"; -import AccessibleButton from "../views/elements/AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton"; import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; import { E2EStatus, shieldStatusForRoom } from "../../utils/ShieldUtils"; import { Action } from "../../dispatcher/actions"; @@ -856,7 +857,7 @@ export class RoomView extends React.Component { window.addEventListener("beforeunload", this.onPageUnload); } - public shouldComponentUpdate(nextProps, nextState): boolean { + public shouldComponentUpdate(nextProps: IRoomProps, nextState: IRoomState): boolean { const hasPropsDiff = objectHasDiff(this.props, nextProps); const { upgradeRecommendation, ...state } = this.state; @@ -958,7 +959,7 @@ export class RoomView extends React.Component { }); }; - private onPageUnload = (event): string => { + private onPageUnload = (event: BeforeUnloadEvent): string => { if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) { return (event.returnValue = _t("You seem to be uploading files, are you sure you want to quit?")); } else if (this.getCallForRoom() && this.state.callState !== "ended") { @@ -966,7 +967,7 @@ export class RoomView extends React.Component { } }; - private onReactKeyDown = (ev): void => { + private onReactKeyDown = (ev: React.KeyboardEvent): void => { let handled = false; const action = getKeyBindingsManager().getRoomAction(ev); @@ -1130,7 +1131,13 @@ export class RoomView extends React.Component { createRoomFromLocalRoom(this.context.client, this.state.room as LocalRoom); } - private onRoomTimeline = (ev: MatrixEvent, room: Room | null, toStartOfTimeline: boolean, removed, data): void => { + private onRoomTimeline = ( + ev: MatrixEvent, + room: Room | null, + toStartOfTimeline: boolean, + removed: boolean, + data?: IRoomTimelineData, + ): void => { if (this.unmounted) return; // ignore events for other rooms or the notification timeline set @@ -1150,7 +1157,7 @@ export class RoomView extends React.Component { // ignore anything but real-time updates at the end of the room: // updates from pagination will happen when the paginate completes. - if (toStartOfTimeline || !data || !data.liveEvent) return; + if (toStartOfTimeline || !data?.liveEvent) return; // no point handling anything while we're waiting for the join to finish: // we'll only be showing a spinner. @@ -1702,7 +1709,7 @@ export class RoomView extends React.Component { }; // update the read marker to match the read-receipt - private forgetReadMarker = (ev): void => { + private forgetReadMarker = (ev: ButtonEvent): void => { ev.stopPropagation(); this.messagePanel.forgetReadMarker(); }; @@ -1775,7 +1782,7 @@ export class RoomView extends React.Component { * * We pass it down to the scroll panel. */ - public handleScrollKey = (ev): void => { + public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => { let panel: ScrollPanel | TimelinePanel; if (this.searchResultsPanel.current) { panel = this.searchResultsPanel.current; @@ -1798,7 +1805,7 @@ export class RoomView extends React.Component { // this has to be a proper method rather than an unnamed function, // otherwise react calls it with null on each update. - private gatherTimelinePanelRef = (r): void => { + private gatherTimelinePanelRef = (r?: TimelinePanel): void => { this.messagePanel = r; }; diff --git a/src/components/structures/ScrollPanel.tsx b/src/components/structures/ScrollPanel.tsx index f51cba66a329..7779f97a5472 100644 --- a/src/components/structures/ScrollPanel.tsx +++ b/src/components/structures/ScrollPanel.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef, CSSProperties, ReactNode, KeyboardEvent } from "react"; +import React, { createRef, CSSProperties, ReactNode } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import SettingsStore from "../../settings/SettingsStore"; @@ -195,8 +195,8 @@ export default class ScrollPanel extends React.Component { private heightUpdateInProgress: boolean; private divScroll: HTMLDivElement; - public constructor(props, context) { - super(props, context); + public constructor(props: IProps) { + super(props); this.props.resizeNotifier?.on("middlePanelResizedNoisy", this.onResize); @@ -440,9 +440,9 @@ export default class ScrollPanel extends React.Component { // pagination. // // If backwards is true, we unpaginate (remove) tiles from the back (top). - let tile; + let tile: HTMLElement; for (let i = 0; i < tiles.length; i++) { - tile = tiles[backwards ? i : tiles.length - 1 - i]; + tile = tiles[backwards ? i : tiles.length - 1 - i] as HTMLElement; // Subtract height of tile as if it were unpaginated excessHeight -= tile.clientHeight; //If removing the tile would lead to future pagination, break before setting scroll token @@ -587,7 +587,7 @@ export default class ScrollPanel extends React.Component { * Scroll up/down in response to a scroll key * @param {object} ev the keyboard event */ - public handleScrollKey = (ev: KeyboardEvent): void => { + public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => { const roomAction = getKeyBindingsManager().getRoomAction(ev); switch (roomAction) { case KeyBindingAction.ScrollUp: diff --git a/src/components/structures/SpaceHierarchy.tsx b/src/components/structures/SpaceHierarchy.tsx index 60a80bc25f0b..03401119b0c8 100644 --- a/src/components/structures/SpaceHierarchy.tsx +++ b/src/components/structures/SpaceHierarchy.tsx @@ -280,7 +280,7 @@ const Tile: React.FC = ({ ); if (showChildren) { - const onChildrenKeyDown = (e): void => { + const onChildrenKeyDown = (e: React.KeyboardEvent): void => { const action = getKeyBindingsManager().getAccessibilityAction(e); switch (action) { case KeyBindingAction.ArrowLeft: diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 96ff90936d5c..6d28106ddb1f 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -318,7 +318,7 @@ const SpaceSetupFirstRooms: React.FC<{ label={_t("Room name")} placeholder={placeholders[i]} value={roomNames[i]} - onChange={(ev) => setRoomName(i, ev.target.value)} + onChange={(ev: React.ChangeEvent) => setRoomName(i, ev.target.value)} autoFocus={i === 2} disabled={busy} autoComplete="off" diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 8d553b554976..ae93080937d8 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -137,7 +137,7 @@ export default class ThreadView extends React.Component { }); } - public componentDidUpdate(prevProps): void { + public componentDidUpdate(prevProps: IProps): void { if (prevProps.mxEvent !== this.props.mxEvent) { this.setupThread(this.props.mxEvent); } @@ -316,7 +316,7 @@ export default class ThreadView extends React.Component { }; private get threadRelation(): IEventRelation { - const relation = { + const relation: IEventRelation = { rel_type: THREAD_RELATION_TYPE.name, event_id: this.state.thread?.id, is_falling_back: true, diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index d0f0f9e876b5..f49a3ea896ce 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -1316,7 +1316,7 @@ class TimelinePanel extends React.Component { * * We pass it down to the scroll panel. */ - public handleScrollKey = (ev: React.KeyboardEvent): void => { + public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => { if (!this.messagePanel.current) return; // jump to the live timeline on ctrl-end, rather than the end of the @@ -1977,9 +1977,9 @@ class TimelinePanel extends React.Component { * * @return An event ID list for every timeline in every timelineSet */ -function serializeEventIdsFromTimelineSets(timelineSets): { [key: string]: string[] }[] { +function serializeEventIdsFromTimelineSets(timelineSets: EventTimelineSet[]): { [key: string]: string[] }[] { const serializedEventIdsInTimelineSet = timelineSets.map((timelineSet) => { - const timelineMap = {}; + const timelineMap: Record = {}; const timelines = timelineSet.getTimelines(); const liveTimeline = timelineSet.getLiveTimeline(); diff --git a/src/components/structures/ToastContainer.tsx b/src/components/structures/ToastContainer.tsx index c4d121d12a6b..378c33b51365 100644 --- a/src/components/structures/ToastContainer.tsx +++ b/src/components/structures/ToastContainer.tsx @@ -25,8 +25,8 @@ interface IState { } export default class ToastContainer extends React.Component<{}, IState> { - public constructor(props, context) { - super(props, context); + public constructor(props: {}) { + super(props); this.state = { toasts: ToastStore.sharedInstance().getToasts(), countSeen: ToastStore.sharedInstance().getCountSeen(), diff --git a/src/components/structures/UploadBar.tsx b/src/components/structures/UploadBar.tsx index 9d256b6aa5d6..c928c42ec44c 100644 --- a/src/components/structures/UploadBar.tsx +++ b/src/components/structures/UploadBar.tsx @@ -57,7 +57,7 @@ export default class UploadBar extends React.PureComponent { private dispatcherRef: Optional; private mounted = false; - public constructor(props) { + public constructor(props: IProps) { super(props); // Set initial state to any available upload in this room - we might be mounting diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index 4cbe0f5bc603..b0303245f9a3 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -37,7 +37,7 @@ import SSOButtons from "../../views/elements/SSOButtons"; import ServerPicker from "../../views/elements/ServerPicker"; import AuthBody from "../../views/auth/AuthBody"; import AuthHeader from "../../views/auth/AuthHeader"; -import AccessibleButton from "../../views/elements/AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton"; import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig"; // These are used in several places, and come from the js-sdk's autodiscovery @@ -101,6 +101,11 @@ interface IState { serverDeadError?: ReactNode; } +type OnPasswordLogin = { + (username: string, phoneCountry: undefined, phoneNumber: undefined, password: string): Promise; + (username: undefined, phoneCountry: string, phoneNumber: string, password: string): Promise; +}; + /* * A wire component which glues together login UI components and Login logic */ @@ -110,7 +115,7 @@ export default class LoginComponent extends React.PureComponent private readonly stepRendererMap: Record ReactNode>; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { @@ -152,7 +157,7 @@ export default class LoginComponent extends React.PureComponent this.unmounted = true; } - public componentDidUpdate(prevProps): void { + public componentDidUpdate(prevProps: IProps): void { if ( prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl || prevProps.serverConfig.isUrl !== this.props.serverConfig.isUrl @@ -164,7 +169,12 @@ export default class LoginComponent extends React.PureComponent public isBusy = (): boolean => this.state.busy || this.props.busy; - public onPasswordLogin = async (username, phoneCountry, phoneNumber, password): Promise => { + public onPasswordLogin: OnPasswordLogin = async ( + username: string | undefined, + phoneCountry: string | undefined, + phoneNumber: string | undefined, + password: string, + ): Promise => { if (!this.state.serverIsAlive) { this.setState({ busy: true }); // Do a quick liveliness check on the URLs @@ -207,10 +217,10 @@ export default class LoginComponent extends React.PureComponent if (this.unmounted) { return; } - let errorText; + let errorText: ReactNode; // Some error strings only apply for logging in - const usingEmail = username.indexOf("@") > 0; + const usingEmail = username?.indexOf("@") > 0; if (error.httpStatus === 400 && usingEmail) { errorText = _t("This homeserver does not support login using email address."); } else if (error.errcode === "M_RESOURCE_LIMIT_EXCEEDED") { @@ -264,11 +274,11 @@ export default class LoginComponent extends React.PureComponent ); }; - public onUsernameChanged = (username): void => { - this.setState({ username: username }); + public onUsernameChanged = (username: string): void => { + this.setState({ username }); }; - public onUsernameBlur = async (username): Promise => { + public onUsernameBlur = async (username: string): Promise => { const doWellknownLookup = username[0] === "@"; this.setState({ username: username, @@ -315,23 +325,21 @@ export default class LoginComponent extends React.PureComponent } }; - public onPhoneCountryChanged = (phoneCountry): void => { - this.setState({ phoneCountry: phoneCountry }); + public onPhoneCountryChanged = (phoneCountry: string): void => { + this.setState({ phoneCountry }); }; - public onPhoneNumberChanged = (phoneNumber): void => { - this.setState({ - phoneNumber: phoneNumber, - }); + public onPhoneNumberChanged = (phoneNumber: string): void => { + this.setState({ phoneNumber }); }; - public onRegisterClick = (ev): void => { + public onRegisterClick = (ev: ButtonEvent): void => { ev.preventDefault(); ev.stopPropagation(); this.props.onRegisterClick(); }; - public onTryRegisterClick = (ev): void => { + public onTryRegisterClick = (ev: ButtonEvent): void => { const hasPasswordFlow = this.state.flows?.find((flow) => flow.type === "m.login.password"); const ssoFlow = this.state.flows?.find((flow) => flow.type === "m.login.sso" || flow.type === "m.login.cas"); // If has no password flow but an SSO flow guess that the user wants to register with SSO. @@ -540,7 +548,7 @@ export default class LoginComponent extends React.PureComponent ); }; - private renderSsoStep = (loginType): JSX.Element => { + private renderSsoStep = (loginType: "cas" | "sso"): JSX.Element => { const flow = this.state.flows.find((flow) => flow.type === "m.login." + loginType) as ISSOFlow; return ( diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index aac39334a0db..7cfebc6d0ade 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -14,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { AuthType, createClient, IAuthData, IInputs } from "matrix-js-sdk/src/matrix"; +import { AuthType, createClient, IAuthData, IInputs, MatrixError } from "matrix-js-sdk/src/matrix"; import React, { Fragment, ReactNode } from "react"; -import { IRequestTokenResponse, MatrixClient } from "matrix-js-sdk/src/client"; +import { IRegisterRequestParams, IRequestTokenResponse, MatrixClient } from "matrix-js-sdk/src/client"; import classNames from "classnames"; import { logger } from "matrix-js-sdk/src/logger"; import { ISSOFlow, SSOAction } from "matrix-js-sdk/src/@types/auth"; @@ -125,7 +125,7 @@ export default class Registration extends React.Component { // `replaceClient` tracks latest serverConfig to spot when it changes under the async method which fetches flows private latestServerConfig: ValidatedServerConfig; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { @@ -166,7 +166,7 @@ export default class Registration extends React.Component { } }; - public componentDidUpdate(prevProps): void { + public componentDidUpdate(prevProps: IProps): void { if ( prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl || prevProps.serverConfig.isUrl !== this.props.serverConfig.isUrl @@ -307,7 +307,7 @@ export default class Registration extends React.Component { if (!success) { let errorText: ReactNode = (response as Error).message || (response as Error).toString(); // can we give a better error message? - if (response.errcode === "M_RESOURCE_LIMIT_EXCEEDED") { + if (response instanceof MatrixError && response.errcode === "M_RESOURCE_LIMIT_EXCEEDED") { const errorTop = messageForResourceLimitError(response.data.limit_type, response.data.admin_contact, { "monthly_active_user": _td("This homeserver has hit its Monthly Active User limit."), "hs_blocked": _td("This homeserver has been blocked by its administrator."), @@ -326,17 +326,17 @@ export default class Registration extends React.Component {

{errorDetail}

); - } else if (response.required_stages && response.required_stages.includes(AuthType.Msisdn)) { + } else if ((response as IAuthData).required_stages?.includes(AuthType.Msisdn)) { let msisdnAvailable = false; - for (const flow of response.available_flows) { + for (const flow of (response as IAuthData).available_flows) { msisdnAvailable = msisdnAvailable || flow.stages.includes(AuthType.Msisdn); } if (!msisdnAvailable) { errorText = _t("This server does not support authentication with a phone number."); } - } else if (response.errcode === "M_USER_IN_USE") { + } else if (response instanceof MatrixError && response.errcode === "M_USER_IN_USE") { errorText = _t("Someone already has that username, please try another."); - } else if (response.errcode === "M_THREEPID_IN_USE") { + } else if (response instanceof MatrixError && response.errcode === "M_THREEPID_IN_USE") { errorText = _t("That e-mail address or phone number is already in use."); } @@ -348,11 +348,11 @@ export default class Registration extends React.Component { return; } - MatrixClientPeg.setJustRegisteredUserId(response.user_id); + MatrixClientPeg.setJustRegisteredUserId((response as IAuthData).user_id); - const newState = { + const newState: Partial = { doingUIAuth: false, - registeredUsername: response.user_id, + registeredUsername: (response as IAuthData).user_id, differentLoggedInUserId: null, completedNoSignin: false, // we're still busy until we get unmounted: don't show the registration form again @@ -365,8 +365,10 @@ export default class Registration extends React.Component { // starting the registration process. This isn't perfect since it's possible // the user had a separate guest session they didn't actually mean to replace. const [sessionOwner, sessionIsGuest] = await Lifecycle.getStoredSessionOwner(); - if (sessionOwner && !sessionIsGuest && sessionOwner !== response.user_id) { - logger.log(`Found a session for ${sessionOwner} but ${response.user_id} has just registered.`); + if (sessionOwner && !sessionIsGuest && sessionOwner !== (response as IAuthData).user_id) { + logger.log( + `Found a session for ${sessionOwner} but ${(response as IAuthData).user_id} has just registered.`, + ); newState.differentLoggedInUserId = sessionOwner; } @@ -383,7 +385,7 @@ export default class Registration extends React.Component { // as the client that started registration may be gone by the time we've verified the email, and only the client // that verified the email is guaranteed to exist, we'll always do the login in that client. const hasEmail = Boolean(this.state.formVals.email); - const hasAccessToken = Boolean(response.access_token); + const hasAccessToken = Boolean((response as IAuthData).access_token); debuglog("Registration: ui auth finished:", { hasEmail, hasAccessToken }); // don’t log in if we found a session for a different user if (!hasEmail && hasAccessToken && !newState.differentLoggedInUserId) { @@ -391,11 +393,11 @@ export default class Registration extends React.Component { // the email, not the client that started the registration flow await this.props.onLoggedIn( { - userId: response.user_id, - deviceId: response.device_id, + userId: (response as IAuthData).user_id, + deviceId: (response as IAuthData).device_id, homeserverUrl: this.state.matrixClient.getHomeserverUrl(), identityServerUrl: this.state.matrixClient.getIdentityServerUrl(), - accessToken: response.access_token, + accessToken: (response as IAuthData).access_token, }, this.state.formVals.password, ); @@ -406,7 +408,7 @@ export default class Registration extends React.Component { newState.completedNoSignin = true; } - this.setState(newState); + this.setState(newState as IState); }; private setupPushers(): Promise { @@ -455,7 +457,7 @@ export default class Registration extends React.Component { }; private makeRegisterRequest = (auth: IAuthData | null): Promise => { - const registerParams = { + const registerParams: IRegisterRequestParams = { username: this.state.formVals.username, password: this.state.formVals.password, initial_device_display_name: this.props.defaultDeviceDisplayName, diff --git a/src/components/structures/auth/SetupEncryptionBody.tsx b/src/components/structures/auth/SetupEncryptionBody.tsx index 8102cbcfab7b..4eec31a226bb 100644 --- a/src/components/structures/auth/SetupEncryptionBody.tsx +++ b/src/components/structures/auth/SetupEncryptionBody.tsx @@ -45,7 +45,7 @@ interface IState { } export default class SetupEncryptionBody extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); const store = SetupEncryptionStore.sharedInstance(); store.on("update", this.onStoreUpdate); diff --git a/src/components/structures/auth/SoftLogout.tsx b/src/components/structures/auth/SoftLogout.tsx index 253e89aa04e8..e3cb98bd8601 100644 --- a/src/components/structures/auth/SoftLogout.tsx +++ b/src/components/structures/auth/SoftLogout.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ChangeEvent, SyntheticEvent } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { Optional } from "matrix-events-sdk"; import { ISSOFlow, LoginFlow, SSOAction } from "matrix-js-sdk/src/@types/auth"; @@ -44,7 +44,7 @@ enum LoginView { Unsupported, } -const STATIC_FLOWS_TO_VIEWS = { +const STATIC_FLOWS_TO_VIEWS: Record = { "m.login.password": LoginView.Password, "m.login.cas": LoginView.CAS, "m.login.sso": LoginView.SSO, @@ -133,7 +133,7 @@ export default class SoftLogout extends React.Component { this.setState({ flows, loginView: chosenView }); } - private onPasswordChange = (ev): void => { + private onPasswordChange = (ev: ChangeEvent): void => { this.setState({ password: ev.target.value }); }; @@ -141,7 +141,7 @@ export default class SoftLogout extends React.Component { dis.dispatch({ action: "start_password_recovery" }); }; - private onPasswordLogin = async (ev): Promise => { + private onPasswordLogin = async (ev: SyntheticEvent): Promise => { ev.preventDefault(); ev.stopPropagation(); diff --git a/src/components/views/audio_messages/DurationClock.tsx b/src/components/views/audio_messages/DurationClock.tsx index 975fb79a361f..805c8f4f834c 100644 --- a/src/components/views/audio_messages/DurationClock.tsx +++ b/src/components/views/audio_messages/DurationClock.tsx @@ -31,7 +31,7 @@ interface IState { * A clock which shows a clip's maximum duration. */ export default class DurationClock extends React.PureComponent { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/audio_messages/LiveRecordingClock.tsx b/src/components/views/audio_messages/LiveRecordingClock.tsx index 1a3f30b10870..50dd272a0d86 100644 --- a/src/components/views/audio_messages/LiveRecordingClock.tsx +++ b/src/components/views/audio_messages/LiveRecordingClock.tsx @@ -34,12 +34,12 @@ interface IState { */ export default class LiveRecordingClock extends React.PureComponent { private seconds = 0; - private scheduledUpdate = new MarkedExecution( + private scheduledUpdate: MarkedExecution = new MarkedExecution( () => this.updateClock(), () => requestAnimationFrame(() => this.scheduledUpdate.trigger()), ); - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { seconds: 0, diff --git a/src/components/views/audio_messages/LiveRecordingWaveform.tsx b/src/components/views/audio_messages/LiveRecordingWaveform.tsx index 36ca2b4e1354..050a01a21e14 100644 --- a/src/components/views/audio_messages/LiveRecordingWaveform.tsx +++ b/src/components/views/audio_messages/LiveRecordingWaveform.tsx @@ -39,12 +39,12 @@ export default class LiveRecordingWaveform extends React.PureComponent this.updateWaveform(), () => requestAnimationFrame(() => this.scheduledUpdate.trigger()), ); - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { waveform: arraySeed(0, RECORDING_PLAYBACK_SAMPLES), diff --git a/src/components/views/audio_messages/PlayPauseButton.tsx b/src/components/views/audio_messages/PlayPauseButton.tsx index eb31c01c3337..a865f0aeef25 100644 --- a/src/components/views/audio_messages/PlayPauseButton.tsx +++ b/src/components/views/audio_messages/PlayPauseButton.tsx @@ -35,7 +35,7 @@ interface IProps extends Omit { - public constructor(props) { + public constructor(props: IProps) { super(props); } diff --git a/src/components/views/audio_messages/PlaybackClock.tsx b/src/components/views/audio_messages/PlaybackClock.tsx index e225bf1150ba..c5e569ce9216 100644 --- a/src/components/views/audio_messages/PlaybackClock.tsx +++ b/src/components/views/audio_messages/PlaybackClock.tsx @@ -39,7 +39,7 @@ interface IState { * A clock for a playback of a recording. */ export default class PlaybackClock extends React.PureComponent { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/audio_messages/PlaybackWaveform.tsx b/src/components/views/audio_messages/PlaybackWaveform.tsx index fe86ef3326ed..32c34db6b29c 100644 --- a/src/components/views/audio_messages/PlaybackWaveform.tsx +++ b/src/components/views/audio_messages/PlaybackWaveform.tsx @@ -34,7 +34,7 @@ interface IState { * A waveform which shows the waveform of a previously recorded recording */ export default class PlaybackWaveform extends React.PureComponent { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/audio_messages/SeekBar.tsx b/src/components/views/audio_messages/SeekBar.tsx index 85fef772784f..4af84325e58b 100644 --- a/src/components/views/audio_messages/SeekBar.tsx +++ b/src/components/views/audio_messages/SeekBar.tsx @@ -46,7 +46,7 @@ export default class SeekBar extends React.PureComponent { // We use an animation frame request to avoid overly spamming prop updates, even if we aren't // really using anything demanding on the CSS front. - private animationFrameFn = new MarkedExecution( + private animationFrameFn: MarkedExecution = new MarkedExecution( () => this.doUpdate(), () => requestAnimationFrame(() => this.animationFrameFn.trigger()), ); diff --git a/src/components/views/auth/CountryDropdown.tsx b/src/components/views/auth/CountryDropdown.tsx index ae155696c024..4b4396ebb5e3 100644 --- a/src/components/views/auth/CountryDropdown.tsx +++ b/src/components/views/auth/CountryDropdown.tsx @@ -21,7 +21,7 @@ import SdkConfig from "../../../SdkConfig"; import { _t } from "../../../languageHandler"; import Dropdown from "../elements/Dropdown"; -const COUNTRIES_BY_ISO2 = {}; +const COUNTRIES_BY_ISO2: Record = {}; for (const c of COUNTRIES) { COUNTRIES_BY_ISO2[c.iso2] = c; } diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx index 4a995e4d06b8..2d11ff3fab83 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx +++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx @@ -100,7 +100,7 @@ interface IPasswordAuthEntryState { export class PasswordAuthEntry extends React.Component { public static LOGIN_TYPE = AuthType.Password; - public constructor(props) { + public constructor(props: IAuthEntryProps) { super(props); this.state = { @@ -264,7 +264,7 @@ interface ITermsAuthEntryState { export class TermsAuthEntry extends React.Component { public static LOGIN_TYPE = AuthType.Terms; - public constructor(props) { + public constructor(props: ITermsAuthEntryProps) { super(props); // example stageParams: @@ -288,8 +288,12 @@ export class TermsAuthEntry extends React.Component = {}; + const pickedPolicies: { + id: string; + name: string; + url: string; + }[] = []; for (const policyId of Object.keys(allPolicies)) { const policy = allPolicies[policyId]; @@ -325,7 +329,7 @@ export class TermsAuthEntry extends React.Component = {}; for (const policy of this.state.policies) { let checked = this.state.toggledPolicies[policy.id]; if (policy.id === policyId) checked = !checked; @@ -484,7 +488,7 @@ export class EmailIdentityAuthEntry extends React.Component< { a: (text: string) => ( - null} disabled> + {text} @@ -555,7 +559,7 @@ export class MsisdnAuthEntry extends React.Component { private popupWindow: Window; private fallbackButton = createRef(); - public constructor(props) { + public constructor(props: IAuthEntryProps) { super(props); // we have to make the user click a button, as browsers will block diff --git a/src/components/views/auth/LoginWithQR.tsx b/src/components/views/auth/LoginWithQR.tsx index 74e69c96b7d2..4ca07323f7a6 100644 --- a/src/components/views/auth/LoginWithQR.tsx +++ b/src/components/views/auth/LoginWithQR.tsx @@ -75,7 +75,7 @@ interface IState { * This uses the unstable feature of MSC3906: https://github.com/matrix-org/matrix-spec-proposals/pull/3906 */ export default class LoginWithQR extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/auth/LoginWithQRFlow.tsx b/src/components/views/auth/LoginWithQRFlow.tsx index be79d8d2f387..b84250c97ef0 100644 --- a/src/components/views/auth/LoginWithQRFlow.tsx +++ b/src/components/views/auth/LoginWithQRFlow.tsx @@ -41,7 +41,7 @@ interface IProps { * This uses the unstable feature of MSC3906: https://github.com/matrix-org/matrix-spec-proposals/pull/3906 */ export default class LoginWithQRFlow extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); } diff --git a/src/components/views/auth/PassphraseConfirmField.tsx b/src/components/views/auth/PassphraseConfirmField.tsx index 36b6bdb7fbb9..1d002310fa55 100644 --- a/src/components/views/auth/PassphraseConfirmField.tsx +++ b/src/components/views/auth/PassphraseConfirmField.tsx @@ -30,8 +30,8 @@ interface IProps extends Omit { labelRequired?: string; labelInvalid?: string; - onChange(ev: React.FormEvent); - onValidate?(result: IValidationResult); + onChange(ev: React.FormEvent): void; + onValidate?(result: IValidationResult): void; } class PassphraseConfirmField extends PureComponent { diff --git a/src/components/views/auth/PassphraseField.tsx b/src/components/views/auth/PassphraseField.tsx index f8e82d68bf19..f9cbc8fe0634 100644 --- a/src/components/views/auth/PassphraseField.tsx +++ b/src/components/views/auth/PassphraseField.tsx @@ -36,8 +36,8 @@ interface IProps extends Omit { labelStrongPassword?: string; labelAllowedButUnsafe?: string; - onChange(ev: React.FormEvent); - onValidate?(result: IValidationResult); + onChange(ev: React.FormEvent): void; + onValidate?(result: IValidationResult): void; } class PassphraseField extends PureComponent { diff --git a/src/components/views/auth/PasswordLogin.tsx b/src/components/views/auth/PasswordLogin.tsx index 164860ab416d..3100b36a8314 100644 --- a/src/components/views/auth/PasswordLogin.tsx +++ b/src/components/views/auth/PasswordLogin.tsx @@ -14,17 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { SyntheticEvent } from "react"; import classNames from "classnames"; import { _t } from "../../../languageHandler"; import SdkConfig from "../../../SdkConfig"; import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig"; -import AccessibleButton from "../elements/AccessibleButton"; -import withValidation, { IValidationResult } from "../elements/Validation"; +import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; +import withValidation, { IFieldState, IValidationResult } from "../elements/Validation"; import Field from "../elements/Field"; import CountryDropdown from "./CountryDropdown"; import EmailField from "./EmailField"; +import { PhoneNumberCountryDefinition } from "../../../phonenumber"; // For validating phone numbers without country codes const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/; @@ -51,7 +52,7 @@ interface IProps { interface IState { fieldValid: Partial>; loginType: LoginField.Email | LoginField.MatrixId | LoginField.Phone; - password: ""; + password: string; } const enum LoginField { @@ -66,6 +67,10 @@ const enum LoginField { * The email/username/phone fields are fully-controlled, the password field is not. */ export default class PasswordLogin extends React.PureComponent { + private [LoginField.Email]: Field; + private [LoginField.Phone]: Field; + private [LoginField.MatrixId]: Field; + public static defaultProps = { onUsernameChanged: function () {}, onUsernameBlur: function () {}, @@ -75,7 +80,7 @@ export default class PasswordLogin extends React.PureComponent { disableSubmit: false, }; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { // Field error codes by field ID @@ -85,13 +90,13 @@ export default class PasswordLogin extends React.PureComponent { }; } - private onForgotPasswordClick = (ev): void => { + private onForgotPasswordClick = (ev: ButtonEvent): void => { ev.preventDefault(); ev.stopPropagation(); this.props.onForgotPasswordClick(); }; - private onSubmitForm = async (ev): Promise => { + private onSubmitForm = async (ev: SyntheticEvent): Promise => { ev.preventDefault(); const allFieldsValid = await this.verifyFieldsBeforeSubmit(); @@ -99,47 +104,40 @@ export default class PasswordLogin extends React.PureComponent { return; } - let username = ""; // XXX: Synapse breaks if you send null here: - let phoneCountry = null; - let phoneNumber = null; - switch (this.state.loginType) { case LoginField.Email: case LoginField.MatrixId: - username = this.props.username; + this.props.onSubmit(this.props.username, undefined, undefined, this.state.password); break; case LoginField.Phone: - phoneCountry = this.props.phoneCountry; - phoneNumber = this.props.phoneNumber; + this.props.onSubmit(undefined, this.props.phoneCountry, this.props.phoneNumber, this.state.password); break; } - - this.props.onSubmit(username, phoneCountry, phoneNumber, this.state.password); }; - private onUsernameChanged = (ev): void => { + private onUsernameChanged = (ev: React.ChangeEvent): void => { this.props.onUsernameChanged(ev.target.value); }; - private onUsernameBlur = (ev): void => { + private onUsernameBlur = (ev: React.FocusEvent): void => { this.props.onUsernameBlur(ev.target.value); }; - private onLoginTypeChange = (ev): void => { - const loginType = ev.target.value; + private onLoginTypeChange = (ev: React.ChangeEvent): void => { + const loginType = ev.target.value as IState["loginType"]; this.setState({ loginType }); this.props.onUsernameChanged(""); // Reset because email and username use the same state }; - private onPhoneCountryChanged = (country): void => { + private onPhoneCountryChanged = (country: PhoneNumberCountryDefinition): void => { this.props.onPhoneCountryChanged(country.iso2); }; - private onPhoneNumberChanged = (ev): void => { + private onPhoneNumberChanged = (ev: React.ChangeEvent): void => { this.props.onPhoneNumberChanged(ev.target.value); }; - private onPasswordChanged = (ev): void => { + private onPasswordChanged = (ev: React.ChangeEvent): void => { this.setState({ password: ev.target.value }); }; @@ -151,7 +149,7 @@ export default class PasswordLogin extends React.PureComponent { activeElement.blur(); } - const fieldIDsInDisplayOrder = [this.state.loginType, LoginField.Password]; + const fieldIDsInDisplayOrder: LoginField[] = [this.state.loginType, LoginField.Password]; // Run all fields with stricter validation that no longer allows empty // values for required fields. @@ -221,7 +219,7 @@ export default class PasswordLogin extends React.PureComponent { ], }); - private onUsernameValidate = async (fieldState): Promise => { + private onUsernameValidate = async (fieldState: IFieldState): Promise => { const result = await this.validateUsernameRules(fieldState); this.markFieldValid(LoginField.MatrixId, result.valid); return result; @@ -248,7 +246,7 @@ export default class PasswordLogin extends React.PureComponent { ], }); - private onPhoneNumberValidate = async (fieldState): Promise => { + private onPhoneNumberValidate = async (fieldState: IFieldState): Promise => { const result = await this.validatePhoneNumberRules(fieldState); this.markFieldValid(LoginField.Password, result.valid); return result; @@ -266,7 +264,7 @@ export default class PasswordLogin extends React.PureComponent { ], }); - private onPasswordValidate = async (fieldState): Promise => { + private onPasswordValidate = async (fieldState: IFieldState): Promise => { const result = await this.validatePasswordRules(fieldState); this.markFieldValid(LoginField.Password, result.valid); return result; diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index 3f41cb61273c..4d2cd9214c63 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -15,18 +15,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { BaseSyntheticEvent } from "react"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { logger } from "matrix-js-sdk/src/logger"; import { MatrixError } from "matrix-js-sdk/src/matrix"; import * as Email from "../../../email"; -import { looksValid as phoneNumberLooksValid } from "../../../phonenumber"; +import { looksValid as phoneNumberLooksValid, PhoneNumberCountryDefinition } from "../../../phonenumber"; import Modal from "../../../Modal"; import { _t, _td } from "../../../languageHandler"; import SdkConfig from "../../../SdkConfig"; import { SAFE_LOCALPART_REGEX } from "../../../Registration"; -import withValidation, { IValidationResult } from "../elements/Validation"; +import withValidation, { IFieldState, IValidationResult } from "../elements/Validation"; import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig"; import EmailField from "./EmailField"; import PassphraseField from "./PassphraseField"; @@ -95,12 +95,18 @@ interface IState { * A pure UI component which displays a registration form. */ export default class RegistrationForm extends React.PureComponent { + private [RegistrationField.Email]: Field; + private [RegistrationField.Password]: Field; + private [RegistrationField.PasswordConfirm]: Field; + private [RegistrationField.Username]: Field; + private [RegistrationField.PhoneNumber]: Field; + public static defaultProps = { onValidationChange: logger.error, canSubmit: true, }; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { @@ -115,7 +121,9 @@ export default class RegistrationForm extends React.PureComponent => { + private onSubmit = async ( + ev: BaseSyntheticEvent, + ): Promise => { ev.preventDefault(); ev.persist(); @@ -152,7 +160,9 @@ export default class RegistrationForm extends React.PureComponent, + ): void { PosthogAnalytics.instance.setAuthenticationType("Password"); const email = this.state.email.trim(); @@ -248,7 +258,7 @@ export default class RegistrationForm extends React.PureComponent { + private onEmailChange = (ev: React.ChangeEvent): void => { this.setState({ email: ev.target.value.trim(), }); @@ -277,7 +287,7 @@ export default class RegistrationForm extends React.PureComponent { + private onPasswordChange = (ev: React.ChangeEvent): void => { this.setState({ password: ev.target.value, }); @@ -287,7 +297,7 @@ export default class RegistrationForm extends React.PureComponent { + private onPasswordConfirmChange = (ev: React.ChangeEvent): void => { this.setState({ passwordConfirm: ev.target.value, }); @@ -297,19 +307,19 @@ export default class RegistrationForm extends React.PureComponent { + private onPhoneCountryChange = (newVal: PhoneNumberCountryDefinition): void => { this.setState({ phoneCountry: newVal.iso2, }); }; - private onPhoneNumberChange = (ev): void => { + private onPhoneNumberChange = (ev: React.ChangeEvent): void => { this.setState({ phoneNumber: ev.target.value, }); }; - private onPhoneNumberValidate = async (fieldState): Promise => { + private onPhoneNumberValidate = async (fieldState: IFieldState): Promise => { const result = await this.validatePhoneNumberRules(fieldState); this.markFieldValid(RegistrationField.PhoneNumber, result.valid); return result; @@ -334,13 +344,13 @@ export default class RegistrationForm extends React.PureComponent { + private onUsernameChange = (ev: React.ChangeEvent): void => { this.setState({ username: ev.target.value, }); }; - private onUsernameValidate = async (fieldState): Promise => { + private onUsernameValidate = async (fieldState: IFieldState): Promise => { const result = await this.validateUsernameRules(fieldState); this.markFieldValid(RegistrationField.Username, result.valid); return result; diff --git a/src/components/views/avatars/BaseAvatar.tsx b/src/components/views/avatars/BaseAvatar.tsx index 025cb9d2711a..469c5fbb2abe 100644 --- a/src/components/views/avatars/BaseAvatar.tsx +++ b/src/components/views/avatars/BaseAvatar.tsx @@ -48,7 +48,7 @@ interface IProps { tabIndex?: number; } -const calculateUrls = (url: string, urls: string[], lowBandwidth: boolean): string[] => { +const calculateUrls = (url?: string, urls?: string[], lowBandwidth = false): string[] => { // work out the full set of urls to try to load. This is formed like so: // imageUrls: [ props.url, ...props.urls ] @@ -66,7 +66,7 @@ const calculateUrls = (url: string, urls: string[], lowBandwidth: boolean): stri return Array.from(new Set(_urls)); }; -const useImageUrl = ({ url, urls }): [string, () => void] => { +const useImageUrl = ({ url, urls }: { url?: string; urls?: string[] }): [string, () => void] => { // Since this is a hot code path and the settings store can be slow, we // use the cached lowBandwidth value from the room context if it exists const roomContext = useContext(RoomContext); diff --git a/src/components/views/context_menus/DialpadContextMenu.tsx b/src/components/views/context_menus/DialpadContextMenu.tsx index b0af3f4cc7e4..9d8c5c8e90a3 100644 --- a/src/components/views/context_menus/DialpadContextMenu.tsx +++ b/src/components/views/context_menus/DialpadContextMenu.tsx @@ -34,7 +34,7 @@ interface IState { export default class DialpadContextMenu extends React.Component { private numberEntryFieldRef: React.RefObject = createRef(); - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { @@ -58,14 +58,14 @@ export default class DialpadContextMenu extends React.Component this.props.onFinished(); }; - public onKeyDown = (ev): void => { + public onKeyDown = (ev: React.KeyboardEvent): void => { // Prevent Backspace and Delete keys from functioning in the entry field if (ev.code === "Backspace" || ev.code === "Delete") { ev.preventDefault(); } }; - public onChange = (ev): void => { + public onChange = (ev: React.ChangeEvent): void => { this.setState({ value: ev.target.value }); }; diff --git a/src/components/views/context_menus/LegacyCallContextMenu.tsx b/src/components/views/context_menus/LegacyCallContextMenu.tsx index 8e4efa28e47c..4de776ff0666 100644 --- a/src/components/views/context_menus/LegacyCallContextMenu.tsx +++ b/src/components/views/context_menus/LegacyCallContextMenu.tsx @@ -26,7 +26,7 @@ interface IProps extends IContextMenuProps { } export default class LegacyCallContextMenu extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); } diff --git a/src/components/views/dialogs/BaseDialog.tsx b/src/components/views/dialogs/BaseDialog.tsx index 2eb5234caa94..1f16821ee5ad 100644 --- a/src/components/views/dialogs/BaseDialog.tsx +++ b/src/components/views/dialogs/BaseDialog.tsx @@ -88,7 +88,7 @@ export default class BaseDialog extends React.Component { fixedWidth: true, }; - public constructor(props) { + public constructor(props: IProps) { super(props); this.matrixClient = MatrixClientPeg.get(); @@ -132,7 +132,7 @@ export default class BaseDialog extends React.Component { headerImage = ; } - const lockProps = { + const lockProps: Record = { "onKeyDown": this.onKeyDown, "role": "dialog", // This should point to a node describing the dialog. diff --git a/src/components/views/dialogs/BugReportDialog.tsx b/src/components/views/dialogs/BugReportDialog.tsx index 7b389a3e9b69..e3ae127024a0 100644 --- a/src/components/views/dialogs/BugReportDialog.tsx +++ b/src/components/views/dialogs/BugReportDialog.tsx @@ -54,7 +54,7 @@ interface IState { export default class BugReportDialog extends React.Component { private unmounted: boolean; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { sendLogs: true, diff --git a/src/components/views/dialogs/BulkRedactDialog.tsx b/src/components/views/dialogs/BulkRedactDialog.tsx index 9c503d24fe00..e1f9a11cd22e 100644 --- a/src/components/views/dialogs/BulkRedactDialog.tsx +++ b/src/components/views/dialogs/BulkRedactDialog.tsx @@ -21,6 +21,7 @@ import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { Room } from "matrix-js-sdk/src/models/room"; import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline"; import { EventType } from "matrix-js-sdk/src/@types/event"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { _t } from "../../../languageHandler"; import dis from "../../../dispatcher/dispatcher"; @@ -42,7 +43,7 @@ const BulkRedactDialog: React.FC = (props) => { const [keepStateEvents, setKeepStateEvents] = useState(true); let timeline = room.getLiveTimeline(); - let eventsToRedact = []; + let eventsToRedact: MatrixEvent[] = []; while (timeline) { eventsToRedact = [ ...eventsToRedact, diff --git a/src/components/views/dialogs/ChangelogDialog.tsx b/src/components/views/dialogs/ChangelogDialog.tsx index 696c3616bd19..1d7c3e196af3 100644 --- a/src/components/views/dialogs/ChangelogDialog.tsx +++ b/src/components/views/dialogs/ChangelogDialog.tsx @@ -27,16 +27,26 @@ interface IProps { onFinished: (success: boolean) => void; } -const REPOS = ["vector-im/element-web", "matrix-org/matrix-react-sdk", "matrix-org/matrix-js-sdk"]; +type State = Partial>; + +interface Commit { + sha: string; + html_url: string; + commit: { + message: string; + }; +} + +const REPOS = ["vector-im/element-web", "matrix-org/matrix-react-sdk", "matrix-org/matrix-js-sdk"] as const; -export default class ChangelogDialog extends React.Component { - public constructor(props) { +export default class ChangelogDialog extends React.Component { + public constructor(props: IProps) { super(props); this.state = {}; } - private async fetchChanges(repo: string, oldVersion: string, newVersion: string): Promise { + private async fetchChanges(repo: typeof REPOS[number], oldVersion: string, newVersion: string): Promise { const url = `https://riot.im/github/repos/${repo}/compare/${oldVersion}...${newVersion}`; try { @@ -66,7 +76,7 @@ export default class ChangelogDialog extends React.Component { } } - private elementsForCommit(commit): JSX.Element { + private elementsForCommit(commit: Commit): JSX.Element { return (
  • @@ -86,7 +96,7 @@ export default class ChangelogDialog extends React.Component { msg: this.state[repo], }); } else { - content = this.state[repo].map(this.elementsForCommit); + content = (this.state[repo] as Commit[]).map(this.elementsForCommit); } return (
    diff --git a/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx b/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx index a9cf28d91a39..1d8e48807378 100644 --- a/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx +++ b/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx @@ -45,7 +45,7 @@ interface IState { * To avoid this, we keep the dialog open as long as /redact is in progress. */ export default class ConfirmAndWaitRedactDialog extends React.PureComponent { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { isRedacting: false, diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index 0fcb6e1353ef..c13f7921ab81 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -62,7 +62,7 @@ export default class CreateRoomDialog extends React.Component { private nameField = createRef(); private aliasField = createRef(); - public constructor(props) { + public constructor(props: IProps) { super(props); this.supportsRestricted = !!this.props.parentSpace; diff --git a/src/components/views/dialogs/CreateSubspaceDialog.tsx b/src/components/views/dialogs/CreateSubspaceDialog.tsx index 96d8eec1b8dd..aaf14452e3a7 100644 --- a/src/components/views/dialogs/CreateSubspaceDialog.tsx +++ b/src/components/views/dialogs/CreateSubspaceDialog.tsx @@ -21,7 +21,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "../../../languageHandler"; import BaseDialog from "./BaseDialog"; -import AccessibleButton from "../elements/AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { BetaPill } from "../beta/BetaCard"; import Field from "../elements/Field"; @@ -54,7 +54,7 @@ const CreateSubspaceDialog: React.FC = ({ space, onAddExistingSpaceClick } const [joinRule, setJoinRule] = useState(defaultJoinRule); - const onCreateSubspaceClick = async (e): Promise => { + const onCreateSubspaceClick = async (e: ButtonEvent): Promise => { e.preventDefault(); if (busy) return; diff --git a/src/components/views/dialogs/DeactivateAccountDialog.tsx b/src/components/views/dialogs/DeactivateAccountDialog.tsx index ad9f657baa1c..8876038ec406 100644 --- a/src/components/views/dialogs/DeactivateAccountDialog.tsx +++ b/src/components/views/dialogs/DeactivateAccountDialog.tsx @@ -28,6 +28,16 @@ import BaseDialog from "./BaseDialog"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import { Action } from "../../../dispatcher/actions"; +type DialogAesthetics = Partial<{ + [x in AuthType]: { + [x: number]: { + body: string; + continueText?: string; + continueKind?: string; + }; + }; +}>; + interface IProps { onFinished: (success: boolean) => void; } @@ -46,7 +56,7 @@ interface IState { } export default class DeactivateAccountDialog extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { @@ -65,7 +75,7 @@ export default class DeactivateAccountDialog extends React.Component { + private onStagePhaseChange = (stage: AuthType, phase: number): void => { const dialogAesthetics = { [SSOAuthEntry.PHASE_PREAUTH]: { body: _t("Confirm your account deactivation by using Single Sign On to prove your identity."), @@ -80,7 +90,7 @@ export default class DeactivateAccountDialog extends React.Component = ({ roomId, onFinished }) => { {Object.entries(Tools).map(([category, tools]) => (
    -

    {_t(categoryLabels[category])}

    +

    {_t(categoryLabels[category as unknown as Category])}

    {tools.map(([label, tool]) => { const onClick = (): void => { setTool([label, tool]); diff --git a/src/components/views/dialogs/ErrorDialog.tsx b/src/components/views/dialogs/ErrorDialog.tsx index d0d4b22f6e08..298398b0781a 100644 --- a/src/components/views/dialogs/ErrorDialog.tsx +++ b/src/components/views/dialogs/ErrorDialog.tsx @@ -46,9 +46,6 @@ interface IState { export default class ErrorDialog extends React.Component { public static defaultProps = { focus: true, - title: null, - description: null, - button: null, }; private onClick = (): void => { diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx index 6b7df64cb537..58b84436a500 100644 --- a/src/components/views/dialogs/ExportDialog.tsx +++ b/src/components/views/dialogs/ExportDialog.tsx @@ -25,7 +25,14 @@ import DialogButtons from "../elements/DialogButtons"; import Field from "../elements/Field"; import StyledRadioGroup from "../elements/StyledRadioGroup"; import StyledCheckbox from "../elements/StyledCheckbox"; -import { ExportFormat, ExportType, textForFormat, textForType } from "../../../utils/exportUtils/exportUtils"; +import { + ExportFormat, + ExportFormatKey, + ExportType, + ExportTypeKey, + textForFormat, + textForType, +} from "../../../utils/exportUtils/exportUtils"; import withValidation, { IFieldState, IValidationResult } from "../elements/Validation"; import HTMLExporter from "../../../utils/exportUtils/HtmlExport"; import JSONExporter from "../../../utils/exportUtils/JSONExport"; @@ -237,15 +244,15 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { setExporter(null); }; - const exportFormatOptions = Object.keys(ExportFormat).map((format) => ({ - value: ExportFormat[format], - label: textForFormat(ExportFormat[format]), + const exportFormatOptions = Object.values(ExportFormat).map((format) => ({ + value: format, + label: textForFormat(format), })); - const exportTypeOptions = Object.keys(ExportType).map((type) => { + const exportTypeOptions = Object.values(ExportType).map((type) => { return ( - ); }); @@ -332,7 +339,7 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { setExportFormat(ExportFormat[key])} + onChange={(key: ExportFormatKey) => setExportFormat(ExportFormat[key])} definitions={exportFormatOptions} /> @@ -347,7 +354,7 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { element="select" value={exportType} onChange={(e) => { - setExportType(ExportType[e.target.value]); + setExportType(ExportType[e.target.value as ExportTypeKey]); }} > {exportTypeOptions} diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 561bbd0b1c6e..0bd462c65a44 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -243,7 +243,7 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr } const [truncateAt, setTruncateAt] = useState(20); - function overflowTile(overflowCount, totalCount): JSX.Element { + function overflowTile(overflowCount: number, totalCount: number): JSX.Element { const text = _t("and %(count)s others...", { count: overflowCount }); return ( => { + private onAccountDetailsDialogFinished = async (result: boolean): Promise => { if (result) { return this.sendAccountDetails(); } diff --git a/src/components/views/dialogs/InteractiveAuthDialog.tsx b/src/components/views/dialogs/InteractiveAuthDialog.tsx index 3ebd9d4eacf8..00eb40278fd1 100644 --- a/src/components/views/dialogs/InteractiveAuthDialog.tsx +++ b/src/components/views/dialogs/InteractiveAuthDialog.tsx @@ -18,7 +18,7 @@ limitations under the License. import React from "react"; import { MatrixClient } from "matrix-js-sdk/src/client"; -import { IAuthData } from "matrix-js-sdk/src/interactive-auth"; +import { AuthType, IAuthData } from "matrix-js-sdk/src/interactive-auth"; import { _t } from "../../../languageHandler"; import AccessibleButton from "../elements/AccessibleButton"; @@ -27,8 +27,8 @@ import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents"; import BaseDialog from "./BaseDialog"; import { IDialogProps } from "./IDialogProps"; -interface IDialogAesthetics { - [x: string]: { +type DialogAesthetics = Partial<{ + [x in AuthType]: { [x: number]: { title: string; body: string; @@ -36,7 +36,7 @@ interface IDialogAesthetics { continueKind: string; }; }; -} +}>; export interface InteractiveAuthDialogProps extends IDialogProps { // matrix client to use for UI auth requests @@ -71,15 +71,15 @@ export interface InteractiveAuthDialogProps extends IDialogProps { // } // // Default is defined in _getDefaultDialogAesthetics() - aestheticsForStagePhases?: IDialogAesthetics; + aestheticsForStagePhases?: DialogAesthetics; } interface IState { authError: Error; // See _onUpdateStagePhase() - uiaStage: number | string; - uiaStagePhase: number | string; + uiaStage: AuthType | null; + uiaStagePhase: number | null; } export default class InteractiveAuthDialog extends React.Component { @@ -95,7 +95,7 @@ export default class InteractiveAuthDialog extends React.Component { + private onUpdateStagePhase = (newStage: AuthType, newPhase: number): void => { // We copy the stage and stage phase params into state for title selection in render() this.setState({ uiaStage: newStage, uiaStagePhase: newPhase }); }; diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index ff28f6636a11..4cb29c61d70c 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef, ReactNode } from "react"; +import React, { createRef, ReactNode, SyntheticEvent } from "react"; import classNames from "classnames"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { Room } from "matrix-js-sdk/src/models/room"; @@ -92,7 +92,7 @@ enum TabId { } class DMUserTile extends React.PureComponent { - private onRemove = (e): void => { + private onRemove = (e: ButtonEvent): void => { // Stop the browser from highlighting text e.preventDefault(); e.stopPropagation(); @@ -139,7 +139,7 @@ interface IDMRoomTileProps { } class DMRoomTile extends React.PureComponent { - private onClick = (e): void => { + private onClick = (e: ButtonEvent): void => { // Stop the browser from highlighting text e.preventDefault(); e.stopPropagation(); @@ -271,6 +271,10 @@ interface InviteRoomProps extends BaseProps { roomId: string; } +function isRoomInvite(props: Props): props is InviteRoomProps { + return props.kind === KIND_INVITE; +} + interface InviteCallProps extends BaseProps { kind: typeof KIND_CALL_TRANSFER; @@ -311,7 +315,7 @@ export default class InviteDialog extends React.PureComponent = createRef(); private unmounted = false; - public constructor(props) { + public constructor(props: Props) { super(props); if (props.kind === KIND_INVITE && !props.roomId) { @@ -321,7 +325,7 @@ export default class InviteDialog extends React.PureComponent alreadyInvited.add(m.userId)); @@ -361,7 +365,7 @@ export default class InviteDialog extends React.PureComponent { + private onConsultFirstChange = (ev: React.ChangeEvent): void => { this.setState({ consultFirst: ev.target.checked }); }; @@ -538,11 +542,11 @@ export default class InviteDialog extends React.PureComponent { + private onKeyDown = (e: React.KeyboardEvent): void => { if (this.state.busy) return; let handled = false; - const value = e.target.value.trim(); + const value = e.currentTarget.value.trim(); const action = getKeyBindingsManager().getAccessibilityAction(e); switch (action) { @@ -692,7 +696,7 @@ export default class InviteDialog extends React.PureComponent { + private updateFilter = (e: React.ChangeEvent): void => { const term = e.target.value; this.setState({ filterText: term }); @@ -750,7 +754,7 @@ export default class InviteDialog extends React.PureComponent => { + private onPaste = async (e: React.ClipboardEvent): Promise => { if (this.state.filterText) { // if the user has already typed something, just let them // paste normally. @@ -825,7 +829,7 @@ export default class InviteDialog extends React.PureComponent { + private onClickInputArea = (e: React.MouseEvent): void => { // Stop the browser from highlighting text e.preventDefault(); e.stopPropagation(); @@ -835,7 +839,7 @@ export default class InviteDialog extends React.PureComponent { + private onUseDefaultIdentityServerClick = (e: ButtonEvent): void => { e.preventDefault(); // Update the IS in account data. Actually using it may trigger terms. @@ -844,7 +848,7 @@ export default class InviteDialog extends React.PureComponent { + private onManageSettingsClick = (e: ButtonEvent): void => { e.preventDefault(); dis.fire(Action.ViewUserSettings); this.props.onFinished(false); @@ -864,8 +868,8 @@ export default class InviteDialog extends React.PureComponent { + private onDialFormSubmit = (ev: SyntheticEvent): void => { ev.preventDefault(); this.transferCall(); }; - private onDialChange = (ev): void => { + private onDialChange = (ev: React.ChangeEvent): void => { this.setState({ dialPadValue: ev.currentTarget.value }); }; @@ -1066,9 +1070,9 @@ export default class InviteDialog extends React.PureComponent { + private async onLinkClick(e: React.MouseEvent): Promise { e.preventDefault(); - selectText(e.target); + selectText(e.currentTarget); } private get screenName(): ScreenName { diff --git a/src/components/views/dialogs/LogoutDialog.tsx b/src/components/views/dialogs/LogoutDialog.tsx index 6e4fb1b2e9ff..dfb4a5fb3752 100644 --- a/src/components/views/dialogs/LogoutDialog.tsx +++ b/src/components/views/dialogs/LogoutDialog.tsx @@ -45,7 +45,7 @@ export default class LogoutDialog extends React.Component { onFinished: function () {}, }; - public constructor(props) { + public constructor(props: IProps) { super(props); const cli = MatrixClientPeg.get(); diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.tsx b/src/components/views/dialogs/MessageEditHistoryDialog.tsx index 8775b4eb5c33..2e3d0aca05c7 100644 --- a/src/components/views/dialogs/MessageEditHistoryDialog.tsx +++ b/src/components/views/dialogs/MessageEditHistoryDialog.tsx @@ -121,8 +121,8 @@ export default class MessageEditHistoryDialog extends React.PureComponent b.disabled).map((b) => b.id), }; - public constructor(props) { + public constructor(props: IProps) { super(props); this.widget = new ElementWidget({ diff --git a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx index e3be5410148e..1e6c9901ee3d 100644 --- a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx +++ b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import * as React from "react"; -import { useRef, useState } from "react"; +import { SyntheticEvent, useRef, useState } from "react"; import { _t, _td } from "../../../languageHandler"; import { IDialogProps } from "./IDialogProps"; @@ -32,7 +32,7 @@ const RegistrationEmailPromptDialog: React.FC = ({ onFinished }) => { const [email, setEmail] = useState(""); const fieldRef = useRef(); - const onSubmit = async (e): Promise => { + const onSubmit = async (e: SyntheticEvent): Promise => { e.preventDefault(); if (email) { const valid = await fieldRef.current.validate({}); diff --git a/src/components/views/dialogs/ReportEventDialog.tsx b/src/components/views/dialogs/ReportEventDialog.tsx index 28ae30b6f1eb..00e430b2c3a8 100644 --- a/src/components/views/dialogs/ReportEventDialog.tsx +++ b/src/components/views/dialogs/ReportEventDialog.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ChangeEvent } from "react"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { logger } from "matrix-js-sdk/src/logger"; @@ -189,7 +189,7 @@ export default class ReportEventDialog extends React.Component { }; // The user has written down a freeform description of the abuse. - private onReasonChange = ({ target: { value: reason } }): void => { + private onReasonChange = ({ target: { value: reason } }: ChangeEvent): void => { this.setState({ reason }); }; diff --git a/src/components/views/dialogs/RoomSettingsDialog.tsx b/src/components/views/dialogs/RoomSettingsDialog.tsx index 68616cf11793..67b46b371e42 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.tsx +++ b/src/components/views/dialogs/RoomSettingsDialog.tsx @@ -33,6 +33,7 @@ import { UIFeature } from "../../../settings/UIFeature"; import BaseDialog from "./BaseDialog"; import { Action } from "../../../dispatcher/actions"; import { VoipRoomSettingsTab } from "../settings/tabs/room/VoipRoomSettingsTab"; +import { ActionPayload } from "../../../dispatcher/payloads"; export const ROOM_GENERAL_TAB = "ROOM_GENERAL_TAB"; export const ROOM_VOIP_TAB = "ROOM_VOIP_TAB"; @@ -74,7 +75,7 @@ export default class RoomSettingsDialog extends React.Component MatrixClientPeg.get().removeListener(RoomEvent.Name, this.onRoomName); } - private onAction = (payload): void => { + private onAction = (payload: ActionPayload): void => { // When view changes below us, close the room settings // whilst the modal is open this can only be triggered when someone hits Leave Room if (payload.action === Action.ViewHomePage) { diff --git a/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx b/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx index a62c1467dc5e..c3b2cd19bf00 100644 --- a/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx +++ b/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode } from "react"; +import React, { ReactNode, SyntheticEvent } from "react"; import { EventType } from "matrix-js-sdk/src/@types/event"; import { JoinRule } from "matrix-js-sdk/src/@types/partials"; @@ -53,7 +53,7 @@ export default class RoomUpgradeWarningDialog extends React.Component { + private openBugReportDialog = (e: SyntheticEvent): void => { e.preventDefault(); e.stopPropagation(); diff --git a/src/components/views/dialogs/ServerPickerDialog.tsx b/src/components/views/dialogs/ServerPickerDialog.tsx index 31c7a0065983..7c567dadf64d 100644 --- a/src/components/views/dialogs/ServerPickerDialog.tsx +++ b/src/components/views/dialogs/ServerPickerDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef } from "react"; +import React, { ChangeEvent, createRef, SyntheticEvent } from "react"; import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery"; import { logger } from "matrix-js-sdk/src/logger"; @@ -45,7 +45,7 @@ export default class ServerPickerDialog extends React.PureComponent(); private validatedConf: ValidatedServerConfig; - public constructor(props) { + public constructor(props: IProps) { super(props); const config = SdkConfig.get(); @@ -75,7 +75,7 @@ export default class ServerPickerDialog extends React.PureComponent { + private onHomeserverChange = (ev: ChangeEvent): void => { this.setState({ otherHomeserver: ev.target.value }); }; @@ -149,7 +149,7 @@ export default class ServerPickerDialog extends React.PureComponent => this.validate(fieldState); - private onSubmit = async (ev): Promise => { + private onSubmit = async (ev: SyntheticEvent): Promise => { ev.preventDefault(); const valid = await this.fieldRef.current.validate({ allowEmpty: false }); diff --git a/src/components/views/dialogs/ShareDialog.tsx b/src/components/views/dialogs/ShareDialog.tsx index b3761a49b69e..9a78b35b2f0d 100644 --- a/src/components/views/dialogs/ShareDialog.tsx +++ b/src/components/views/dialogs/ShareDialog.tsx @@ -36,12 +36,12 @@ const socials = [ { name: "Facebook", img: require("../../../../res/img/social/facebook.png"), - url: (url) => `https://www.facebook.com/sharer/sharer.php?u=${url}`, + url: (url: String) => `https://www.facebook.com/sharer/sharer.php?u=${url}`, }, { name: "Twitter", img: require("../../../../res/img/social/twitter-2.png"), - url: (url) => `https://twitter.com/home?status=${url}`, + url: (url: string) => `https://twitter.com/home?status=${url}`, }, /* // icon missing name: 'Google Plus', @@ -50,17 +50,17 @@ const socials = [ },*/ { name: "LinkedIn", img: require("../../../../res/img/social/linkedin.png"), - url: (url) => `https://www.linkedin.com/shareArticle?mini=true&url=${url}`, + url: (url: string) => `https://www.linkedin.com/shareArticle?mini=true&url=${url}`, }, { name: "Reddit", img: require("../../../../res/img/social/reddit.png"), - url: (url) => `https://www.reddit.com/submit?url=${url}`, + url: (url: string) => `https://www.reddit.com/submit?url=${url}`, }, { name: "email", img: require("../../../../res/img/social/email-1.png"), - url: (url) => `mailto:?body=${url}`, + url: (url: string) => `mailto:?body=${url}`, }, ]; @@ -75,7 +75,7 @@ interface IState { } export default class ShareDialog extends React.PureComponent { - public constructor(props) { + public constructor(props: IProps) { super(props); let permalinkCreator: RoomPermalinkCreator = null; @@ -91,9 +91,9 @@ export default class ShareDialog extends React.PureComponent { }; } - public static onLinkClick(e): void { + public static onLinkClick(e: React.MouseEvent): void { e.preventDefault(); - selectText(e.target); + selectText(e.currentTarget); } private onLinkSpecificEventCheckboxClick = (): void => { diff --git a/src/components/views/dialogs/SlashCommandHelpDialog.tsx b/src/components/views/dialogs/SlashCommandHelpDialog.tsx index f12143418c4b..1324babbebed 100644 --- a/src/components/views/dialogs/SlashCommandHelpDialog.tsx +++ b/src/components/views/dialogs/SlashCommandHelpDialog.tsx @@ -17,14 +17,14 @@ limitations under the License. import React from "react"; import { _t } from "../../../languageHandler"; -import { CommandCategories, Commands } from "../../../SlashCommands"; +import { Command, CommandCategories, Commands } from "../../../SlashCommands"; import { IDialogProps } from "./IDialogProps"; import InfoDialog from "./InfoDialog"; interface IProps extends IDialogProps {} const SlashCommandHelpDialog: React.FC = ({ onFinished }) => { - const categories = {}; + const categories: Record = {}; Commands.forEach((cmd) => { if (!cmd.isEnabled()) return; if (!categories[cmd.category]) { diff --git a/src/components/views/dialogs/TermsDialog.tsx b/src/components/views/dialogs/TermsDialog.tsx index 819e14be7c35..fd420d436c2b 100644 --- a/src/components/views/dialogs/TermsDialog.tsx +++ b/src/components/views/dialogs/TermsDialog.tsx @@ -21,6 +21,7 @@ import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types"; import { _t, pickBestLanguage } from "../../../languageHandler"; import DialogButtons from "../elements/DialogButtons"; import BaseDialog from "./BaseDialog"; +import { ServicePolicyPair } from "../../../Terms"; interface ITermsCheckboxProps { onChange: (url: string, checked: boolean) => void; @@ -43,7 +44,7 @@ interface ITermsDialogProps { * Array of [Service, policies] pairs, where policies is the response from the * /terms endpoint for that service */ - policiesAndServicePairs: any[]; + policiesAndServicePairs: ServicePolicyPair[]; /** * urls that the user has already agreed to @@ -63,7 +64,7 @@ interface IState { } export default class TermsDialog extends React.PureComponent { - public constructor(props) { + public constructor(props: ITermsDialogProps) { super(props); this.state = { // url -> boolean diff --git a/src/components/views/dialogs/UploadConfirmDialog.tsx b/src/components/views/dialogs/UploadConfirmDialog.tsx index 9b22b33b67f8..688ee03b8111 100644 --- a/src/components/views/dialogs/UploadConfirmDialog.tsx +++ b/src/components/views/dialogs/UploadConfirmDialog.tsx @@ -39,7 +39,7 @@ export default class UploadConfirmDialog extends React.Component { totalFiles: 1, }; - public constructor(props) { + public constructor(props: IProps) { super(props); // Create a fresh `Blob` for previewing (even though `File` already is diff --git a/src/components/views/dialogs/UserSettingsDialog.tsx b/src/components/views/dialogs/UserSettingsDialog.tsx index 7b33ca58da3c..aca4a1b37b92 100644 --- a/src/components/views/dialogs/UserSettingsDialog.tsx +++ b/src/components/views/dialogs/UserSettingsDialog.tsx @@ -50,7 +50,7 @@ interface IState { export default class UserSettingsDialog extends React.Component { private settingsWatchers: string[] = []; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/dialogs/VerificationRequestDialog.tsx b/src/components/views/dialogs/VerificationRequestDialog.tsx index 25e7e835bd07..b8b1e2ed8b84 100644 --- a/src/components/views/dialogs/VerificationRequestDialog.tsx +++ b/src/components/views/dialogs/VerificationRequestDialog.tsx @@ -35,7 +35,7 @@ interface IState { } export default class VerificationRequestDialog extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { verificationRequest: this.props.verificationRequest, diff --git a/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx b/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx index 4c3acb89600c..4ede87b1ebe6 100644 --- a/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx +++ b/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx @@ -33,13 +33,12 @@ interface IProps extends IDialogProps { widgetKind: WidgetKind; // TODO: Refactor into the Widget class } -interface IBooleanStates { - // @ts-ignore - TS wants a string key, but we know better - [capability: Capability]: boolean; -} +type BooleanStates = Partial<{ + [capability in Capability]: boolean; +}>; interface IState { - booleanStates: IBooleanStates; + booleanStates: BooleanStates; rememberSelection: boolean; } @@ -52,7 +51,7 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent< const parsedEvents = WidgetEventCapability.findEventCapabilities(this.props.requestedCapabilities); parsedEvents.forEach((e) => this.eventPermissionsMap.set(e.raw, e)); - const states: IBooleanStates = {}; + const states: BooleanStates = {}; this.props.requestedCapabilities.forEach((c) => (states[c] = true)); this.state = { @@ -71,7 +70,7 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent< this.setState({ rememberSelection: newVal }); }; - private onSubmit = async (ev): Promise => { + private onSubmit = async (): Promise => { this.closeAndTryRemember( Object.entries(this.state.booleanStates) .filter(([_, isSelected]) => isSelected) @@ -79,7 +78,7 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent< ); }; - private onReject = async (ev): Promise => { + private onReject = async (): Promise => { this.closeAndTryRemember([]); // nothing was approved }; diff --git a/src/components/views/dialogs/devtools/Event.tsx b/src/components/views/dialogs/devtools/Event.tsx index 6af242b68b78..ce7b1e236174 100644 --- a/src/components/views/dialogs/devtools/Event.tsx +++ b/src/components/views/dialogs/devtools/Event.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useContext, useMemo, useRef, useState } from "react"; +import React, { ChangeEvent, useContext, useMemo, useRef, useState } from "react"; import { IContent, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { _t, _td } from "../../../../languageHandler"; @@ -87,7 +87,7 @@ export const EventEditor: React.FC = ({ fieldDefs, defaultCon type="text" autoComplete="on" value={fieldData[i]} - onChange={(ev) => + onChange={(ev: ChangeEvent) => setFieldData((data) => { data[i] = ev.target.value; return [...data]; diff --git a/src/components/views/dialogs/devtools/FilteredList.tsx b/src/components/views/dialogs/devtools/FilteredList.tsx index 46d3f9566145..11ee0156714b 100644 --- a/src/components/views/dialogs/devtools/FilteredList.tsx +++ b/src/components/views/dialogs/devtools/FilteredList.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useEffect, useState } from "react"; +import React, { ChangeEvent, useEffect, useState } from "react"; import { _t } from "../../../../languageHandler"; import Field from "../../elements/Field"; @@ -72,7 +72,7 @@ const FilteredList: React.FC = ({ children, query, onChange }) => { type="text" autoComplete="off" value={query} - onChange={(ev) => onChange(ev.target.value)} + onChange={(ev: ChangeEvent) => onChange(ev.target.value)} className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query" // force re-render so that autoFocus is applied when this component is re-used key={children?.[0]?.key ?? ""} diff --git a/src/components/views/dialogs/devtools/SettingExplorer.tsx b/src/components/views/dialogs/devtools/SettingExplorer.tsx index 72876f35511e..c0801cd062bb 100644 --- a/src/components/views/dialogs/devtools/SettingExplorer.tsx +++ b/src/components/views/dialogs/devtools/SettingExplorer.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useContext, useMemo, useState } from "react"; +import React, { ChangeEvent, useContext, useMemo, useState } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "../../../../languageHandler"; @@ -74,7 +74,7 @@ const CanEditLevelField: React.FC = ({ setting, roomId, }; function renderExplicitSettingValues(setting: string, roomId: string): string { - const vals = {}; + const vals: Record = {}; for (const level of LEVEL_ORDER) { try { vals[level] = SettingsStore.getValueAt(level, setting, roomId, true, true); @@ -283,7 +283,7 @@ const SettingsList: React.FC = ({ onBack, onView, onEdit }) type="text" autoComplete="off" value={query} - onChange={(ev) => setQuery(ev.target.value)} + onChange={(ev: ChangeEvent) => setQuery(ev.target.value)} className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query" /> diff --git a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx index d7154b3aa2fb..8964b189929c 100644 --- a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx +++ b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx @@ -62,7 +62,7 @@ interface IState { export default class AccessSecretStorageDialog extends React.PureComponent { private fileUpload = React.createRef(); - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx b/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx index b69fc9cbb320..0b6438df72a6 100644 --- a/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx +++ b/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx @@ -18,6 +18,7 @@ limitations under the License. import React from "react"; import { CrossSigningKeys } from "matrix-js-sdk/src/client"; import { logger } from "matrix-js-sdk/src/logger"; +import { UIAFlow } from "matrix-js-sdk/src/matrix"; import { MatrixClientPeg } from "../../../../MatrixClientPeg"; import { _t } from "../../../../languageHandler"; @@ -82,7 +83,7 @@ export default class CreateCrossSigningDialog extends React.PureComponent { + const canUploadKeysWithPasswordOnly = error.data.flows.some((f: UIAFlow) => { return f.stages.length === 1 && f.stages[0] === "m.login.password"; }); this.setState({ diff --git a/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx b/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx index 0d1286a29ba7..66a9314ea3db 100644 --- a/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx +++ b/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ChangeEvent } from "react"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { IKeyBackupInfo, IKeyBackupRestoreResult } from "matrix-js-sdk/src/crypto/keybackup"; import { ISecretStorageKeyInfo } from "matrix-js-sdk/src/crypto/api"; @@ -81,7 +81,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { + private progressCallback = (data: IState["progress"]): void => { this.setState({ progress: data, }); @@ -128,7 +128,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent => {}, /* forceReset = */ true); }; - private onRecoveryKeyChange = (e): void => { + private onRecoveryKeyChange = (e: ChangeEvent): void => { this.setState({ recoveryKey: e.target.value, recoveryKeyValid: MatrixClientPeg.get().isValidRecoveryKey(e.target.value), @@ -213,7 +213,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { + private onPassPhraseChange = (e: ChangeEvent): void => { this.setState({ passPhrase: e.target.value, }); @@ -247,7 +247,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { + private async restoreWithCachedKey(backupInfo?: IKeyBackupInfo): Promise { if (!backupInfo) return false; try { const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithCache( diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index bf2556956827..0102935f28f0 100644 --- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx +++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx @@ -77,7 +77,7 @@ import BaseAvatar from "../../avatars/BaseAvatar"; import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar"; import { SearchResultAvatar } from "../../avatars/SearchResultAvatar"; import { NetworkDropdown } from "../../directory/NetworkDropdown"; -import AccessibleButton from "../../elements/AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton"; import LabelledCheckbox from "../../elements/LabelledCheckbox"; import Spinner from "../../elements/Spinner"; import NotificationBadge from "../../rooms/NotificationBadge"; @@ -625,7 +625,7 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n const showViewButton = clientRoom?.getMyMembership() === "join" || result.publicRoom.world_readable || cli.isGuest(); - const listener = (ev): void => { + const listener = (ev: ButtonEvent): void => { const { publicRoom } = result; viewRoom( { diff --git a/src/components/views/elements/AppTile.tsx b/src/components/views/elements/AppTile.tsx index b75de2ee4497..de0b1a17e14c 100644 --- a/src/components/views/elements/AppTile.tsx +++ b/src/components/views/elements/AppTile.tsx @@ -18,7 +18,7 @@ limitations under the License. */ import url from "url"; -import React, { ContextType, createRef, MutableRefObject, ReactNode } from "react"; +import React, { ContextType, createRef, CSSProperties, MutableRefObject, ReactNode } from "react"; import classNames from "classnames"; import { MatrixCapabilities } from "matrix-widget-api"; import { Room, RoomEvent } from "matrix-js-sdk/src/models/room"; @@ -81,7 +81,7 @@ interface IProps { // Is this an instance of a user widget userWidget: boolean; // sets the pointer-events property on the iframe - pointerEvents?: string; + pointerEvents?: CSSProperties["pointerEvents"]; widgetPageTitle?: string; showLayoutButtons?: boolean; // Handle to manually notify the PersistedElement that it needs to move @@ -562,9 +562,9 @@ export default class AppTile extends React.Component { "microphone; camera; encrypted-media; autoplay; display-capture; clipboard-write; " + "clipboard-read;"; const appTileBodyClass = "mx_AppTileBody" + (this.props.miniMode ? "_mini " : " "); - const appTileBodyStyles = {}; + const appTileBodyStyles: CSSProperties = {}; if (this.props.pointerEvents) { - appTileBodyStyles["pointerEvents"] = this.props.pointerEvents; + appTileBodyStyles.pointerEvents = this.props.pointerEvents; } const loadingElement = ( diff --git a/src/components/views/elements/Dropdown.tsx b/src/components/views/elements/Dropdown.tsx index c7abf5246c74..a41aa5321da5 100644 --- a/src/components/views/elements/Dropdown.tsx +++ b/src/components/views/elements/Dropdown.tsx @@ -373,7 +373,7 @@ export default class Dropdown extends React.Component { ); } - const dropdownClasses = { + const dropdownClasses: Record = { mx_Dropdown: true, mx_Dropdown_disabled: this.props.disabled, }; diff --git a/src/components/views/elements/EditableItemList.tsx b/src/components/views/elements/EditableItemList.tsx index 9cc5371ff56a..2d2997660469 100644 --- a/src/components/views/elements/EditableItemList.tsx +++ b/src/components/views/elements/EditableItemList.tsx @@ -14,11 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ChangeEvent } from "react"; import { _t } from "../../../languageHandler"; import Field from "./Field"; -import AccessibleButton from "./AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "./AccessibleButton"; interface IItemProps { index?: number; @@ -35,21 +35,21 @@ export class EditableItem extends React.Component { verifyRemove: false, }; - private onRemove = (e): void => { + private onRemove = (e: ButtonEvent): void => { e.stopPropagation(); e.preventDefault(); this.setState({ verifyRemove: true }); }; - private onDontRemove = (e): void => { + private onDontRemove = (e: ButtonEvent): void => { e.stopPropagation(); e.preventDefault(); this.setState({ verifyRemove: false }); }; - private onActuallyRemove = (e): void => { + private onActuallyRemove = (e: ButtonEvent): void => { e.stopPropagation(); e.preventDefault(); @@ -105,19 +105,19 @@ interface IProps { } export default class EditableItemList

    extends React.PureComponent { - protected onItemAdded = (e): void => { + protected onItemAdded = (e: ButtonEvent): void => { e.stopPropagation(); e.preventDefault(); - if (this.props.onItemAdded) this.props.onItemAdded(this.props.newItem); + this.props.onItemAdded?.(this.props.newItem); }; protected onItemRemoved = (index: number): void => { - if (this.props.onItemRemoved) this.props.onItemRemoved(index); + this.props.onItemRemoved?.(index); }; - protected onNewItemChanged = (e): void => { - if (this.props.onNewItemChanged) this.props.onNewItemChanged(e.target.value); + protected onNewItemChanged = (e: ChangeEvent): void => { + this.props.onNewItemChanged?.(e.target.value); }; protected renderNewItemField(): JSX.Element { diff --git a/src/components/views/elements/EffectsOverlay.tsx b/src/components/views/elements/EffectsOverlay.tsx index 423ae62d19ab..cf11350c2b90 100644 --- a/src/components/views/elements/EffectsOverlay.tsx +++ b/src/components/views/elements/EffectsOverlay.tsx @@ -32,13 +32,13 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) => { const lazyLoadEffectModule = async (name: string): Promise => { if (!name) return null; - let effect: ICanvasEffect | null = effectsRef.current[name] || null; + let effect: ICanvasEffect | null = effectsRef.current.get(name) || null; if (effect === null) { const options = CHAT_EFFECTS.find((e) => e.command === name)?.options; try { const { default: Effect } = await import(`../../../effects/${name}`); effect = new Effect(options); - effectsRef.current[name] = effect; + effectsRef.current.set(name, effect); } catch (err) { logger.warn(`Unable to load effect module at '../../../effects/${name}.`, err); } @@ -70,7 +70,7 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) => { // eslint-disable-next-line react-hooks/exhaustive-deps const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored for (const effect in currentEffects) { - const effectModule: ICanvasEffect = currentEffects[effect]; + const effectModule: ICanvasEffect = currentEffects.get(effect); if (effectModule && effectModule.isRunning) { effectModule.stop(); } diff --git a/src/components/views/elements/ErrorBoundary.tsx b/src/components/views/elements/ErrorBoundary.tsx index 87b81f3280c6..760304147851 100644 --- a/src/components/views/elements/ErrorBoundary.tsx +++ b/src/components/views/elements/ErrorBoundary.tsx @@ -34,7 +34,7 @@ interface IState { * catch exceptions during rendering in the component tree below them. */ export default class ErrorBoundary extends React.PureComponent<{}, IState> { - public constructor(props) { + public constructor(props: {}) { super(props); this.state = { diff --git a/src/components/views/elements/EventListSummary.tsx b/src/components/views/elements/EventListSummary.tsx index 60288fb2f56a..2a55eec8e1c6 100644 --- a/src/components/views/elements/EventListSummary.tsx +++ b/src/components/views/elements/EventListSummary.tsx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ComponentProps } from "react"; +import React, { ComponentProps, ReactNode } from "react"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { EventType } from "matrix-js-sdk/src/@types/event"; @@ -161,7 +161,15 @@ export default class EventListSummary extends React.Component { * @returns {string[]} an array of transitions. */ private static getCanonicalTransitions(transitions: TransitionType[]): TransitionType[] { - const modMap = { + const modMap: Partial< + Record< + TransitionType, + { + after: TransitionType; + newTransition: TransitionType; + } + > + > = { [TransitionType.Joined]: { after: TransitionType.Left, newTransition: TransitionType.JoinedAndLeft, @@ -170,10 +178,6 @@ export default class EventListSummary extends React.Component { after: TransitionType.Joined, newTransition: TransitionType.LeftAndJoined, }, - // $currentTransition : { - // 'after' : $nextTransition, - // 'newTransition' : 'new_transition_type', - // }, }; const res: TransitionType[] = []; @@ -237,15 +241,11 @@ export default class EventListSummary extends React.Component { * @param {number} repeats the number of times the transition was repeated in a row. * @returns {string} the written Human Readable equivalent of the transition. */ - private static getDescriptionForTransition( - t: TransitionType, - userCount: number, - count: number, - ): string | JSX.Element { + private static getDescriptionForTransition(t: TransitionType, userCount: number, count: number): ReactNode | null { // The empty interpolations 'severalUsers' and 'oneUser' // are there only to show translators to non-English languages // that the verb is conjugated to plural or singular Subject. - let res = null; + let res: ReactNode | undefined; switch (t) { case TransitionType.Joined: res = @@ -377,7 +377,7 @@ export default class EventListSummary extends React.Component { break; } - return res; + return res ?? null; } private static getTransitionSequence(events: IUserEvents[]): TransitionType[] { diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx index b22c1d2b27f7..bdb542519519 100644 --- a/src/components/views/elements/Field.tsx +++ b/src/components/views/elements/Field.tsx @@ -145,7 +145,7 @@ export default class Field extends React.PureComponent { }); }, VALIDATION_THROTTLE_MS); - public constructor(props) { + public constructor(props: PropShapes) { super(props); this.state = { valid: undefined, @@ -165,7 +165,7 @@ export default class Field extends React.PureComponent { }); } - private onFocus = (ev): void => { + private onFocus = (ev: React.FocusEvent): void => { this.setState({ focused: true, }); @@ -175,22 +175,18 @@ export default class Field extends React.PureComponent { }); } // Parent component may have supplied its own `onFocus` as well - if (this.props.onFocus) { - this.props.onFocus(ev); - } + this.props.onFocus?.(ev); }; - private onChange = (ev): void => { + private onChange = (ev: React.ChangeEvent): void => { if (this.props.validateOnChange) { this.validateOnChange(); } // Parent component may have supplied its own `onChange` as well - if (this.props.onChange) { - this.props.onChange(ev); - } + this.props.onChange?.(ev); }; - private onBlur = (ev): void => { + private onBlur = (ev: React.FocusEvent): void => { this.setState({ focused: false, }); @@ -200,9 +196,7 @@ export default class Field extends React.PureComponent { }); } // Parent component may have supplied its own `onBlur` as well - if (this.props.onBlur) { - this.props.onBlur(ev); - } + this.props.onBlur?.(ev); }; public async validate({ focused, allowEmpty = true }: IValidateOpts): Promise { diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index b6dff90b9c13..ac5a78566e67 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -91,7 +91,7 @@ interface IState { } export default class ImageView extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); const { thumbnailInfo } = this.props; diff --git a/src/components/views/elements/InteractiveTooltip.tsx b/src/components/views/elements/InteractiveTooltip.tsx index 33819f8ed6a9..1986de02433a 100644 --- a/src/components/views/elements/InteractiveTooltip.tsx +++ b/src/components/views/elements/InteractiveTooltip.tsx @@ -308,8 +308,8 @@ export default class InteractiveTooltip extends React.Component side: Direction.Top, }; - public constructor(props, context) { - super(props, context); + public constructor(props: IProps) { + super(props); this.state = { contentRect: null, diff --git a/src/components/views/elements/InviteReason.tsx b/src/components/views/elements/InviteReason.tsx index 08e0ceca318a..2bdbb98b214f 100644 --- a/src/components/views/elements/InviteReason.tsx +++ b/src/components/views/elements/InviteReason.tsx @@ -30,7 +30,7 @@ interface IState { } export default class InviteReason extends React.PureComponent { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { // We hide the reason for invitation by default, since it can be a diff --git a/src/components/views/elements/LabelledToggleSwitch.tsx b/src/components/views/elements/LabelledToggleSwitch.tsx index ce4db91117f2..83a1e66f8a01 100644 --- a/src/components/views/elements/LabelledToggleSwitch.tsx +++ b/src/components/views/elements/LabelledToggleSwitch.tsx @@ -22,22 +22,24 @@ import { Caption } from "../typography/Caption"; interface IProps { // The value for the toggle switch - value: boolean; + "value": boolean; // The translated label for the switch - label: string; + "label": string; // The translated caption for the switch - caption?: string; + "caption"?: string; // Tooltip to display - tooltip?: string; + "tooltip"?: string; // Whether or not to disable the toggle switch - disabled?: boolean; + "disabled"?: boolean; // True to put the toggle in front of the label // Default false. - toggleInFront?: boolean; + "toggleInFront"?: boolean; // Additional class names to append to the switch. Optional. - className?: string; + "className"?: string; // The function to call when the value changes onChange(checked: boolean): void; + + "data-testid"?: string; } export default class LabelledToggleSwitch extends React.PureComponent { diff --git a/src/components/views/elements/Measured.tsx b/src/components/views/elements/Measured.tsx index 9445973b1d41..2f7862c9220b 100644 --- a/src/components/views/elements/Measured.tsx +++ b/src/components/views/elements/Measured.tsx @@ -32,7 +32,7 @@ export default class Measured extends React.PureComponent { breakpoint: 500, }; - public constructor(props) { + public constructor(props: IProps) { super(props); this.instanceId = Measured.instanceCount++; diff --git a/src/components/views/elements/PersistentApp.tsx b/src/components/views/elements/PersistentApp.tsx index f692f74aa88c..67ad09018d51 100644 --- a/src/components/views/elements/PersistentApp.tsx +++ b/src/components/views/elements/PersistentApp.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ContextType, MutableRefObject } from "react"; +import React, { ContextType, CSSProperties, MutableRefObject } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import WidgetUtils from "../../../utils/WidgetUtils"; @@ -26,7 +26,7 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; interface IProps { persistentWidgetId: string; persistentRoomId: string; - pointerEvents?: string; + pointerEvents?: CSSProperties["pointerEvents"]; movePersistedElement: MutableRefObject<(() => void) | undefined>; } diff --git a/src/components/views/elements/Pill.tsx b/src/components/views/elements/Pill.tsx index 8f905f5b591f..e5df681cc71e 100644 --- a/src/components/views/elements/Pill.tsx +++ b/src/components/views/elements/Pill.tsx @@ -20,6 +20,7 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { logger } from "matrix-js-sdk/src/logger"; import { MatrixClient } from "matrix-js-sdk/src/client"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import dis from "../../../dispatcher/dispatcher"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; @@ -30,6 +31,7 @@ import Tooltip, { Alignment } from "./Tooltip"; import RoomAvatar from "../avatars/RoomAvatar"; import MemberAvatar from "../avatars/MemberAvatar"; import { objectHasDiff } from "../../../utils/objects"; +import { ButtonEvent } from "./AccessibleButton"; export enum PillType { UserMention = "TYPE_USER_MENTION", @@ -180,7 +182,7 @@ export default class Pill extends React.Component { }); }; - private doProfileLookup(userId: string, member): void { + private doProfileLookup(userId: string, member: RoomMember): void { MatrixClientPeg.get() .getProfileInfo(userId) .then((resp) => { @@ -196,7 +198,7 @@ export default class Pill extends React.Component { getDirectionalContent: function () { return this.getContent(); }, - }; + } as MatrixEvent; this.setState({ member }); }) .catch((err) => { @@ -204,7 +206,7 @@ export default class Pill extends React.Component { }); } - private onUserPillClicked = (e): void => { + private onUserPillClicked = (e: ButtonEvent): void => { e.preventDefault(); dis.dispatch({ action: Action.ViewUser, diff --git a/src/components/views/elements/RoomAliasField.tsx b/src/components/views/elements/RoomAliasField.tsx index 7ba6be8588be..f03b40173a7e 100644 --- a/src/components/views/elements/RoomAliasField.tsx +++ b/src/components/views/elements/RoomAliasField.tsx @@ -44,7 +44,7 @@ export default class RoomAliasField extends React.PureComponent private fieldRef = createRef(); - public constructor(props, context) { + public constructor(props: IProps, context: React.ContextType) { super(props, context); this.state = { diff --git a/src/components/views/elements/RoomFacePile.tsx b/src/components/views/elements/RoomFacePile.tsx index 7149c7e0ce52..fc4792c2e0d0 100644 --- a/src/components/views/elements/RoomFacePile.tsx +++ b/src/components/views/elements/RoomFacePile.tsx @@ -42,7 +42,7 @@ const RoomFacePile: FC = ({ room, onlyKnownUsers = true, numShown = DEFA const count = members.length; // sort users with an explicit avatar first - const iteratees = [(member) => (member.getMxcAvatarUrl() ? 0 : 1)]; + const iteratees = [(member: RoomMember) => (member.getMxcAvatarUrl() ? 0 : 1)]; if (onlyKnownUsers) { members = members.filter(isKnownMember); } else { diff --git a/src/components/views/elements/SearchWarning.tsx b/src/components/views/elements/SearchWarning.tsx index 0737ff3c1b6f..fec5eee37f1a 100644 --- a/src/components/views/elements/SearchWarning.tsx +++ b/src/components/views/elements/SearchWarning.tsx @@ -23,7 +23,7 @@ import SdkConfig from "../../../SdkConfig"; import dis from "../../../dispatcher/dispatcher"; import { Action } from "../../../dispatcher/actions"; import { UserTab } from "../dialogs/UserTab"; -import AccessibleButton from "./AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "./AccessibleButton"; export enum WarningKind { Files, @@ -49,7 +49,7 @@ export default function SearchWarning({ isRoomEncrypted, kind }: IProps): JSX.El a: (sub) => ( { + onClick={(evt: ButtonEvent) => { evt.preventDefault(); dis.dispatch({ action: Action.ViewUserSettings, diff --git a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx index 78644dc962b6..a930ec409bc8 100644 --- a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx +++ b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx @@ -33,7 +33,7 @@ function languageMatchesSearchQuery(query: string, language: Languages[0]): bool interface SpellCheckLanguagesDropdownIProps { className: string; value: string; - onOptionChange(language: string); + onOptionChange(language: string): void; } interface SpellCheckLanguagesDropdownIState { @@ -45,7 +45,7 @@ export default class SpellCheckLanguagesDropdown extends React.Component< SpellCheckLanguagesDropdownIProps, SpellCheckLanguagesDropdownIState > { - public constructor(props) { + public constructor(props: SpellCheckLanguagesDropdownIProps) { super(props); this.onSearchChange = this.onSearchChange.bind(this); diff --git a/src/components/views/elements/StyledRadioGroup.tsx b/src/components/views/elements/StyledRadioGroup.tsx index 8ad41c47053c..3a09b3feff13 100644 --- a/src/components/views/elements/StyledRadioGroup.tsx +++ b/src/components/views/elements/StyledRadioGroup.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode } from "react"; +import React, { ChangeEvent, ReactNode } from "react"; import classNames from "classnames"; import StyledRadioButton from "./StyledRadioButton"; @@ -47,8 +47,8 @@ function StyledRadioGroup({ disabled, onChange, }: IProps): JSX.Element { - const _onChange = (e): void => { - onChange(e.target.value); + const _onChange = (e: ChangeEvent): void => { + onChange(e.target.value as T); }; return ( diff --git a/src/components/views/elements/Tooltip.tsx b/src/components/views/elements/Tooltip.tsx index 9b927d81891f..5e65824454a2 100644 --- a/src/components/views/elements/Tooltip.tsx +++ b/src/components/views/elements/Tooltip.tsx @@ -68,7 +68,7 @@ export default class Tooltip extends React.PureComponent { alignment: Alignment.Natural, }; - public constructor(props) { + public constructor(props: ITooltipProps) { super(props); this.state = {}; @@ -92,7 +92,7 @@ export default class Tooltip extends React.PureComponent { this.updatePosition(); } - public componentDidUpdate(prevProps): void { + public componentDidUpdate(prevProps: ITooltipProps): void { if (objectHasDiff(prevProps, this.props)) { this.updatePosition(); } diff --git a/src/components/views/elements/TooltipButton.tsx b/src/components/views/elements/TooltipButton.tsx index 415e25cf9e0e..ceb547a9ce32 100644 --- a/src/components/views/elements/TooltipButton.tsx +++ b/src/components/views/elements/TooltipButton.tsx @@ -24,7 +24,7 @@ interface IProps { } export default class TooltipButton extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); } diff --git a/src/components/views/elements/TruncatedList.tsx b/src/components/views/elements/TruncatedList.tsx index 45a621eaf355..4f5284d54ff2 100644 --- a/src/components/views/elements/TruncatedList.tsx +++ b/src/components/views/elements/TruncatedList.tsx @@ -40,7 +40,7 @@ interface IProps { export default class TruncatedList extends React.Component { public static defaultProps = { truncateAt: 2, - createOverflowElement(overflowCount, totalCount) { + createOverflowElement(overflowCount: number, totalCount: number) { return

    {_t("And %(count)s more...", { count: overflowCount })}
    ; }, }; diff --git a/src/components/views/emojipicker/QuickReactions.tsx b/src/components/views/emojipicker/QuickReactions.tsx index be6d6a569688..bb0a9a12d0d4 100644 --- a/src/components/views/emojipicker/QuickReactions.tsx +++ b/src/components/views/emojipicker/QuickReactions.tsx @@ -40,7 +40,7 @@ interface IState { } class QuickReactions extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { hover: null, diff --git a/src/components/views/emojipicker/ReactionPicker.tsx b/src/components/views/emojipicker/ReactionPicker.tsx index 023328cedb5b..e113ab895036 100644 --- a/src/components/views/emojipicker/ReactionPicker.tsx +++ b/src/components/views/emojipicker/ReactionPicker.tsx @@ -50,7 +50,7 @@ class ReactionPicker extends React.Component { this.addListeners(); } - public componentDidUpdate(prevProps): void { + public componentDidUpdate(prevProps: IProps): void { if (prevProps.reactions !== this.props.reactions) { this.addListeners(); this.onReactionsChange(); @@ -78,7 +78,7 @@ class ReactionPicker extends React.Component { return {}; } const userId = MatrixClientPeg.get().getUserId(); - const myAnnotations = this.props.reactions.getAnnotationsBySender()[userId] || []; + const myAnnotations = this.props.reactions.getAnnotationsBySender()[userId] || new Set(); return Object.fromEntries( [...myAnnotations] .filter((event) => !event.isRedacted()) diff --git a/src/components/views/location/LocationShareMenu.tsx b/src/components/views/location/LocationShareMenu.tsx index 31664a872fa1..5968d50c472e 100644 --- a/src/components/views/location/LocationShareMenu.tsx +++ b/src/components/views/location/LocationShareMenu.tsx @@ -38,7 +38,7 @@ type Props = Omit & { relation?: IEventRelation; }; -const getEnabledShareTypes = (relation): LocationShareType[] => { +const getEnabledShareTypes = (relation?: IEventRelation): LocationShareType[] => { const enabledShareTypes = [LocationShareType.Own]; // live locations cannot have a relation diff --git a/src/components/views/messages/DateSeparator.tsx b/src/components/views/messages/DateSeparator.tsx index 183bfffd15e9..1f8f166bf5d5 100644 --- a/src/components/views/messages/DateSeparator.tsx +++ b/src/components/views/messages/DateSeparator.tsx @@ -53,10 +53,10 @@ interface IState { } export default class DateSeparator extends React.Component { - private settingWatcherRef = null; + private settingWatcherRef?: string; - public constructor(props, context) { - super(props, context); + public constructor(props: IProps) { + super(props); this.state = { jumpToDateEnabled: SettingsStore.getValue("feature_jump_to_date"), }; @@ -116,7 +116,7 @@ export default class DateSeparator extends React.Component { } } - private pickDate = async (inputTimestamp): Promise => { + private pickDate = async (inputTimestamp: number | string | Date): Promise => { const unixTimestamp = new Date(inputTimestamp).getTime(); const cli = MatrixClientPeg.get(); @@ -175,7 +175,7 @@ export default class DateSeparator extends React.Component { this.closeMenu(); }; - private onDatePicked = (dateString): void => { + private onDatePicked = (dateString: string): void => { this.pickDate(dateString); this.closeMenu(); }; diff --git a/src/components/views/messages/EventTileBubble.tsx b/src/components/views/messages/EventTileBubble.tsx index 90b1811e1996..db60d706f243 100644 --- a/src/components/views/messages/EventTileBubble.tsx +++ b/src/components/views/messages/EventTileBubble.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { forwardRef, ReactNode, ReactChildren } from "react"; +import React, { forwardRef, ReactNode, ReactChild } from "react"; import classNames from "classnames"; interface IProps { @@ -22,7 +22,7 @@ interface IProps { title: string; timestamp?: JSX.Element; subtitle?: ReactNode; - children?: ReactChildren; + children?: ReactChild; } const EventTileBubble = forwardRef( diff --git a/src/components/views/messages/MAudioBody.tsx b/src/components/views/messages/MAudioBody.tsx index 2a78e79e7fb9..188f4526a9c7 100644 --- a/src/components/views/messages/MAudioBody.tsx +++ b/src/components/views/messages/MAudioBody.tsx @@ -16,6 +16,7 @@ limitations under the License. import React from "react"; import { logger } from "matrix-js-sdk/src/logger"; +import { IContent } from "matrix-js-sdk/src/matrix"; import { Playback } from "../../../audio/Playback"; import InlineSpinner from "../elements/InlineSpinner"; @@ -66,8 +67,8 @@ export default class MAudioBody extends React.PureComponent // We should have a buffer to work with now: let's set it up // Note: we don't actually need a waveform to render an audio event, but voice messages do. - const content = this.props.mxEvent.getContent(); - const waveform = content?.["org.matrix.msc1767.audio"]?.waveform?.map((p) => p / 1024); + const content = this.props.mxEvent.getContent(); + const waveform = content?.["org.matrix.msc1767.audio"]?.waveform?.map((p: number) => p / 1024); // We should have a buffer to work with now: let's set it up const playback = PlaybackManager.instance.createPlaybackInstance(buffer, waveform); diff --git a/src/components/views/messages/MFileBody.tsx b/src/components/views/messages/MFileBody.tsx index e68fe24341a5..f0a80a345b46 100644 --- a/src/components/views/messages/MFileBody.tsx +++ b/src/components/views/messages/MFileBody.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef } from "react"; +import React, { AllHTMLAttributes, createRef } from "react"; import { filesize } from "filesize"; import { logger } from "matrix-js-sdk/src/logger"; @@ -30,7 +30,7 @@ import { FileDownloader } from "../../../utils/FileDownloader"; import TextWithTooltip from "../elements/TextWithTooltip"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; -export let DOWNLOAD_ICON_URL; // cached copy of the download.svg asset for the sandboxed iframe later on +export let DOWNLOAD_ICON_URL: string; // cached copy of the download.svg asset for the sandboxed iframe later on async function cacheDownloadIcon(): Promise { if (DOWNLOAD_ICON_URL) return; // cached already @@ -155,7 +155,7 @@ export default class MFileBody extends React.Component { }); } - public componentDidUpdate(prevProps, prevState): void { + public componentDidUpdate(prevProps: IProps, prevState: IState): void { if (this.props.onHeightChanged && !prevState.decryptedBlob && this.state.decryptedBlob) { this.props.onHeightChanged(); } @@ -295,7 +295,7 @@ export default class MFileBody extends React.Component { ); } else if (contentUrl) { - const downloadProps = { + const downloadProps: AllHTMLAttributes = { target: "_blank", rel: "noreferrer noopener", diff --git a/src/components/views/messages/MJitsiWidgetEvent.tsx b/src/components/views/messages/MJitsiWidgetEvent.tsx index 0796b6830e78..bb3a21c4b4d9 100644 --- a/src/components/views/messages/MJitsiWidgetEvent.tsx +++ b/src/components/views/messages/MJitsiWidgetEvent.tsx @@ -29,7 +29,7 @@ interface IProps { } export default class MJitsiWidgetEvent extends React.PureComponent { - public constructor(props) { + public constructor(props: IProps) { super(props); } diff --git a/src/components/views/messages/MKeyVerificationRequest.tsx b/src/components/views/messages/MKeyVerificationRequest.tsx index 2a115572144a..d12f252750bb 100644 --- a/src/components/views/messages/MKeyVerificationRequest.tsx +++ b/src/components/views/messages/MKeyVerificationRequest.tsx @@ -122,9 +122,9 @@ export default class MKeyVerificationRequest extends React.Component { return null; } - let title; - let subtitle; - let stateNode; + let title: string; + let subtitle: string; + let stateNode: JSX.Element; if (!request.canAccept) { let stateLabel; diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index e94705f97bbf..abad590cbf7c 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -47,7 +47,7 @@ export default class MVideoBody extends React.PureComponent private videoRef = React.createRef(); private sizeWatcher: string; - public constructor(props) { + public constructor(props: IBodyProps) { super(props); this.state = { diff --git a/src/components/views/messages/ReactionsRow.tsx b/src/components/views/messages/ReactionsRow.tsx index 92612383fc9a..3bb6260d701a 100644 --- a/src/components/views/messages/ReactionsRow.tsx +++ b/src/components/views/messages/ReactionsRow.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { SyntheticEvent } from "react"; import classNames from "classnames"; import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event"; import { Relations, RelationsEvent } from "matrix-js-sdk/src/models/relations"; @@ -52,7 +52,7 @@ const ReactButton: React.FC = ({ mxEvent, reactions }) => { })} title={_t("Add reaction")} onClick={openMenu} - onContextMenu={(e) => { + onContextMenu={(e: SyntheticEvent): void => { e.preventDefault(); openMenu(); }} diff --git a/src/components/views/messages/ReactionsRowButtonTooltip.tsx b/src/components/views/messages/ReactionsRowButtonTooltip.tsx index b875b7e27a12..600502e7ddc4 100644 --- a/src/components/views/messages/ReactionsRowButtonTooltip.tsx +++ b/src/components/views/messages/ReactionsRowButtonTooltip.tsx @@ -35,6 +35,7 @@ interface IProps { export default class ReactionsRowButtonTooltip extends React.PureComponent { public static contextType = MatrixClientContext; + public context!: React.ContextType; public render(): JSX.Element { const { content, reactionEvents, mxEvent, visible } = this.props; @@ -42,7 +43,7 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent { public static contextType = RoomContext; public context!: React.ContextType; - public constructor(props) { + public constructor(props: IBodyProps) { super(props); this.state = { diff --git a/src/components/views/messages/TileErrorBoundary.tsx b/src/components/views/messages/TileErrorBoundary.tsx index 12cda9f9482c..0c221a793350 100644 --- a/src/components/views/messages/TileErrorBoundary.tsx +++ b/src/components/views/messages/TileErrorBoundary.tsx @@ -37,7 +37,7 @@ interface IState { } export default class TileErrorBoundary extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/messages/ViewSourceEvent.tsx b/src/components/views/messages/ViewSourceEvent.tsx index 5f265a2c0d13..344ddd344eed 100644 --- a/src/components/views/messages/ViewSourceEvent.tsx +++ b/src/components/views/messages/ViewSourceEvent.tsx @@ -31,7 +31,7 @@ interface IState { } export default class ViewSourceEvent extends React.PureComponent { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/right_panel/HeaderButtons.tsx b/src/components/views/right_panel/HeaderButtons.tsx index 7a6eea6f23c3..75542e15337e 100644 --- a/src/components/views/right_panel/HeaderButtons.tsx +++ b/src/components/views/right_panel/HeaderButtons.tsx @@ -26,6 +26,7 @@ import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePha import { IRightPanelCardState } from "../../../stores/right-panel/RightPanelStoreIPanelState"; import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { NotificationColor } from "../../../stores/notifications/NotificationColor"; +import { ActionPayload } from "../../../dispatcher/payloads"; export enum HeaderKind { Room = "room", @@ -67,7 +68,7 @@ export default abstract class HeaderButtons

    extends React.Component): void { const rps = RightPanelStore.instance; diff --git a/src/components/views/room_settings/AliasSettings.tsx b/src/components/views/room_settings/AliasSettings.tsx index 9c6b1cf2dc16..d540ac61ad4d 100644 --- a/src/components/views/room_settings/AliasSettings.tsx +++ b/src/components/views/room_settings/AliasSettings.tsx @@ -15,8 +15,9 @@ limitations under the License. */ import React, { ChangeEvent, ContextType, createRef, SyntheticEvent } from "react"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { IContent, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { logger } from "matrix-js-sdk/src/logger"; +import { EventType } from "matrix-js-sdk/src/@types/event"; import EditableItemList from "../elements/EditableItemList"; import { _t } from "../../../languageHandler"; @@ -52,7 +53,7 @@ class EditableAliasesList extends EditableItemList { }; protected renderNewItemField(): JSX.Element { - const onChange = (alias: string): void => this.onNewItemChanged({ target: { value: alias } }); + const onChange = (alias: string): void => this.props.onNewItemChanged?.(alias); return (
    { canSetCanonicalAlias: false, }; - public constructor(props, context: ContextType) { + public constructor(props: IProps, context: ContextType) { super(props, context); - const state = { + const state: IState = { altAliases: [], // [ #alias:domain.tld, ... ] localAliases: [], // [ #alias:my-hs.tld, ... ] canonicalAlias: null, // #canonical:domain.tld @@ -140,7 +141,7 @@ export default class AliasSettings extends React.Component { try { const mxClient = this.context; - let localAliases = []; + let localAliases: string[] = []; const response = await mxClient.getLocalAliases(this.props.roomId); if (Array.isArray(response?.aliases)) { localAliases = response.aliases; @@ -164,14 +165,14 @@ export default class AliasSettings extends React.Component { updatingCanonicalAlias: true, }); - const eventContent = { + const eventContent: IContent = { alt_aliases: this.state.altAliases, }; if (alias) eventContent["alias"] = alias; this.context - .sendStateEvent(this.props.roomId, "m.room.canonical_alias", eventContent, "") + .sendStateEvent(this.props.roomId, EventType.RoomCanonicalAlias, eventContent, "") .catch((err) => { logger.error(err); Modal.createDialog(ErrorDialog, { @@ -195,7 +196,7 @@ export default class AliasSettings extends React.Component { updatingCanonicalAlias: true, }); - const eventContent = {}; + const eventContent: IContent = {}; if (this.state.canonicalAlias) { eventContent["alias"] = this.state.canonicalAlias; @@ -205,7 +206,7 @@ export default class AliasSettings extends React.Component { } this.context - .sendStateEvent(this.props.roomId, "m.room.canonical_alias", eventContent, "") + .sendStateEvent(this.props.roomId, EventType.RoomCanonicalAlias, eventContent, "") .then(() => { this.setState({ altAliases, diff --git a/src/components/views/room_settings/RoomPublishSetting.tsx b/src/components/views/room_settings/RoomPublishSetting.tsx index 673afccb77c6..d20ea9f92ea9 100644 --- a/src/components/views/room_settings/RoomPublishSetting.tsx +++ b/src/components/views/room_settings/RoomPublishSetting.tsx @@ -33,15 +33,15 @@ interface IState { } export default class RoomPublishSetting extends React.PureComponent { - public constructor(props, context) { - super(props, context); + public constructor(props: IProps) { + super(props); this.state = { isRoomPublished: false, }; } - private onRoomPublishChange = (e): void => { + private onRoomPublishChange = (): void => { const valueBefore = this.state.isRoomPublished; const newValue = !valueBefore; this.setState({ isRoomPublished: newValue }); diff --git a/src/components/views/rooms/AppsDrawer.tsx b/src/components/views/rooms/AppsDrawer.tsx index 25c8684c9c8d..38f119718e0d 100644 --- a/src/components/views/rooms/AppsDrawer.tsx +++ b/src/components/views/rooms/AppsDrawer.tsx @@ -45,8 +45,7 @@ interface IProps { } interface IState { - // @ts-ignore - TS wants a string key, but we know better - apps: { [id: Container]: IApp[] }; + apps: Partial<{ [id in Container]: IApp[] }>; resizingVertical: boolean; // true when changing the height of the apps drawer resizingHorizontal: boolean; // true when changing the distribution of the width between widgets resizing: boolean; @@ -203,10 +202,9 @@ export default class AppsDrawer extends React.Component { break; } }; - // @ts-ignore - TS wants a string key, but we know better - private getApps = (): { [id: Container]: IApp[] } => { - // @ts-ignore - const appsDict: { [id: Container]: IApp[] } = {}; + + private getApps = (): Partial<{ [id in Container]: IApp[] }> => { + const appsDict: Partial<{ [id in Container]: IApp[] }> = {}; appsDict[Container.Top] = WidgetLayoutStore.instance.getContainerWidgets(this.props.room, Container.Top); appsDict[Container.Center] = WidgetLayoutStore.instance.getContainerWidgets(this.props.room, Container.Center); return appsDict; diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index b88f6dbdb794..5f7f88d01c82 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -57,7 +57,7 @@ export default class Autocomplete extends React.PureComponent { public static contextType = RoomContext; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 8492f575199d..ccb36091af11 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -55,7 +55,7 @@ export default class AuxPanel extends React.Component { showApps: true, }; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { @@ -76,7 +76,7 @@ export default class AuxPanel extends React.Component { } } - public shouldComponentUpdate(nextProps, nextState): boolean { + public shouldComponentUpdate(nextProps: IProps, nextState: IState): boolean { return objectHasDiff(this.props, nextProps) || objectHasDiff(this.state, nextState); } @@ -146,9 +146,9 @@ export default class AuxPanel extends React.Component { ); } - let stateViews = null; + let stateViews: JSX.Element | null = null; if (this.state.counters && SettingsStore.getValue("feature_state_counters")) { - const counters = []; + const counters: JSX.Element[] = []; this.state.counters.forEach((counter, idx) => { const title = counter.title; diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index eaf97a39f405..00daed475cb0 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -32,7 +32,7 @@ import { } from "../../../editor/operations"; import { getCaretOffsetAndText, getRangeForSelection } from "../../../editor/dom"; import Autocomplete, { generateCompletionDomId } from "../rooms/Autocomplete"; -import { getAutoCompleteCreator, Part, Type } from "../../../editor/parts"; +import { getAutoCompleteCreator, Part, SerializedPart, Type } from "../../../editor/parts"; import { parseEvent, parsePlainTextMessage } from "../../../editor/deserialize"; import { renderModel } from "../../../editor/render"; import SettingsStore from "../../../settings/SettingsStore"; @@ -107,7 +107,7 @@ interface IProps { initialCaret?: DocumentOffset; disabled?: boolean; - onChange?(); + onChange?(): void; onPaste?(event: ClipboardEvent, model: EditorModel): boolean; } @@ -140,7 +140,7 @@ export default class BasicMessageEditor extends React.Component private readonly surroundWithHandle: string; private readonly historyManager = new HistoryManager(); - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { showPillAvatar: SettingsStore.getValue("Pill.shouldShowPillAvatar"), @@ -367,7 +367,7 @@ export default class BasicMessageEditor extends React.Component let parts: Part[]; if (partsText) { const serializedTextParts = JSON.parse(partsText); - parts = serializedTextParts.map((p) => partCreator.deserializePart(p)); + parts = serializedTextParts.map((p: SerializedPart) => partCreator.deserializePart(p)); } else { parts = parsePlainTextMessage(plainText, partCreator, { shouldEscape: false }); } diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index a5e11564372c..af734d25a7c5 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -29,7 +29,7 @@ import { getCaretOffsetAndText } from "../../../editor/dom"; import { htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand } from "../../../editor/serialize"; import { findEditableEvent } from "../../../utils/EventUtils"; import { parseEvent } from "../../../editor/deserialize"; -import { CommandPartCreator, Part, PartCreator } from "../../../editor/parts"; +import { CommandPartCreator, Part, PartCreator, SerializedPart } from "../../../editor/parts"; import EditorStateTransfer from "../../../utils/EditorStateTransfer"; import BasicMessageComposer, { REGEX_EMOTICON } from "./BasicMessageComposer"; import { CommandCategories } from "../../../SlashCommands"; @@ -59,7 +59,7 @@ function getHtmlReplyFallback(mxEvent: MatrixEvent): string { } function getTextReplyFallback(mxEvent: MatrixEvent): string { - const body = mxEvent.getContent().body; + const body: string = mxEvent.getContent().body; const lines = body.split("\n").map((l) => l.trim()); if (lines.length > 2 && lines[0].startsWith("> ") && lines[1].length === 0) { return `${lines[0]}\n\n`; @@ -253,7 +253,7 @@ class EditMessageComposer extends React.Component partCreator.deserializePart(p)); + const parts: Part[] = serializedParts.map((p: SerializedPart) => partCreator.deserializePart(p)); return parts; } catch (e) { logger.error("Error parsing editing state: ", e); diff --git a/src/components/views/rooms/EntityTile.tsx b/src/components/views/rooms/EntityTile.tsx index bfd5cece66d9..7bcc60541d91 100644 --- a/src/components/views/rooms/EntityTile.tsx +++ b/src/components/views/rooms/EntityTile.tsx @@ -35,13 +35,15 @@ const PowerLabel: Record = { [PowerStatus.Moderator]: _td("Mod"), }; -const PRESENCE_CLASS = { +export type PresenceState = "offline" | "online" | "unavailable"; + +const PRESENCE_CLASS: Record = { offline: "mx_EntityTile_offline", online: "mx_EntityTile_online", unavailable: "mx_EntityTile_unavailable", }; -function presenceClassForMember(presenceState: string, lastActiveAgo: number, showPresence: boolean): string { +function presenceClassForMember(presenceState: PresenceState, lastActiveAgo: number, showPresence: boolean): string { if (showPresence === false) { return "mx_EntityTile_online_beenactive"; } @@ -67,7 +69,7 @@ interface IProps { title?: string; avatarJsx?: JSX.Element; // className?: string; - presenceState?: string; + presenceState?: PresenceState; presenceLastActiveAgo?: number; presenceLastTs?: number; presenceCurrentlyActive?: boolean; @@ -104,7 +106,7 @@ export default class EntityTile extends React.PureComponent { } public render(): JSX.Element { - const mainClassNames = { + const mainClassNames: Record = { mx_EntityTile: true, mx_EntityTile_noHover: this.props.suppressOnHover, }; diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index d6c2052d089c..e0780251c56b 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -617,8 +617,8 @@ export class UnwrappedEventTile extends React.Component } private propsEqual(objA: EventTileProps, objB: EventTileProps): boolean { - const keysA = Object.keys(objA); - const keysB = Object.keys(objB); + const keysA = Object.keys(objA) as Array; + const keysB = Object.keys(objB) as Array; if (keysA.length !== keysB.length) { return false; diff --git a/src/components/views/rooms/LinkPreviewWidget.tsx b/src/components/views/rooms/LinkPreviewWidget.tsx index 23bd19ff4dc8..3de8d68a8945 100644 --- a/src/components/views/rooms/LinkPreviewWidget.tsx +++ b/src/components/views/rooms/LinkPreviewWidget.tsx @@ -37,7 +37,7 @@ interface IProps { export default class LinkPreviewWidget extends React.Component { private image = createRef(); - private onImageClick = (ev): void => { + private onImageClick = (ev: React.MouseEvent): void => { const p = this.props.preview; if (ev.button != 0 || ev.metaKey) return; ev.preventDefault(); diff --git a/src/components/views/rooms/MemberList.tsx b/src/components/views/rooms/MemberList.tsx index 396e09398676..0d48d0b26751 100644 --- a/src/components/views/rooms/MemberList.tsx +++ b/src/components/views/rooms/MemberList.tsx @@ -68,7 +68,8 @@ interface IState { } export default class MemberList extends React.Component { - private showPresence = true; + // XXX: exported for tests + public showPresence = true; private mounted = false; public static contextType = SDKContext; @@ -195,7 +196,8 @@ export default class MemberList extends React.Component { { leading: true, trailing: true }, ); - private async updateListNow(showLoadingSpinner: boolean): Promise { + // XXX: exported for tests + public async updateListNow(showLoadingSpinner?: boolean): Promise { if (!this.mounted) { return; } diff --git a/src/components/views/rooms/MemberTile.tsx b/src/components/views/rooms/MemberTile.tsx index 94b4771007f7..de544cd929d2 100644 --- a/src/components/views/rooms/MemberTile.tsx +++ b/src/components/views/rooms/MemberTile.tsx @@ -28,10 +28,11 @@ import dis from "../../../dispatcher/dispatcher"; import { _t } from "../../../languageHandler"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { Action } from "../../../dispatcher/actions"; -import EntityTile, { PowerStatus } from "./EntityTile"; +import EntityTile, { PowerStatus, PresenceState } from "./EntityTile"; import MemberAvatar from "./../avatars/MemberAvatar"; import DisambiguatedProfile from "../messages/DisambiguatedProfile"; import UserIdentifierCustomisations from "../../../customisations/UserIdentifier"; +import { E2EState } from "./E2EIcon"; interface IProps { member: RoomMember; @@ -40,7 +41,7 @@ interface IProps { interface IState { isRoomEncrypted: boolean; - e2eStatus: string; + e2eStatus: E2EState; } export default class MemberTile extends React.Component { @@ -51,7 +52,7 @@ export default class MemberTile extends React.Component { showPresence: true, }; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { @@ -121,7 +122,7 @@ export default class MemberTile extends React.Component { const userTrust = cli.checkUserTrust(userId); if (!userTrust.isCrossSigningVerified()) { this.setState({ - e2eStatus: userTrust.wasCrossSigningVerified() ? "warning" : "normal", + e2eStatus: userTrust.wasCrossSigningVerified() ? E2EState.Warning : E2EState.Normal, }); return; } @@ -138,7 +139,7 @@ export default class MemberTile extends React.Component { return isMe ? !deviceTrust.isCrossSigningVerified() : !deviceTrust.isVerified(); }); this.setState({ - e2eStatus: anyDeviceUnverified ? "warning" : "verified", + e2eStatus: anyDeviceUnverified ? E2EState.Warning : E2EState.Verified, }); } @@ -186,7 +187,7 @@ export default class MemberTile extends React.Component { public render(): JSX.Element { const member = this.props.member; const name = this.getDisplayName(); - const presenceState = member.user ? member.user.presence : null; + const presenceState = member.user?.presence ?? null; const av =