diff --git a/.babelrc b/.babelrc index 3fb847ad18..abe7e1ef3f 100644 --- a/.babelrc +++ b/.babelrc @@ -13,7 +13,6 @@ ], "transform-class-properties", "transform-object-rest-spread", - "transform-async-to-bluebird", "transform-runtime", "add-module-exports", "syntax-dynamic-import" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c46530fad..bc2341863a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,116 @@ +Changes in [1.7.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.7.3) (2019-11-25) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.7.3-rc.2...v1.7.3) + + * No changes since rc.2 + +Changes in [1.7.3-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.7.3-rc.2) (2019-11-22) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.7.3-rc.1...v1.7.3-rc.2) + + * Fix double date separator for room upgrade tiles + [\#3663](https://github.com/matrix-org/matrix-react-sdk/pull/3663) + * Show m.room.create event before the ELS on room upgrade + [\#3660](https://github.com/matrix-org/matrix-react-sdk/pull/3660) + * Make addEventListener conditional + [\#3659](https://github.com/matrix-org/matrix-react-sdk/pull/3659) + * Fix e2e icons + [\#3658](https://github.com/matrix-org/matrix-react-sdk/pull/3658) + +Changes in [1.7.3-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.7.3-rc.1) (2019-11-20) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.7.2...v1.7.3-rc.1) + + * Fix positioning, size, and colour of the composer e2e icon + [\#3641](https://github.com/matrix-org/matrix-react-sdk/pull/3641) + * upgrade nunito from 3.500 to 3.504 + [\#3639](https://github.com/matrix-org/matrix-react-sdk/pull/3639) + * Wire up the widget permission prompt to the cross-platform setting + [\#3630](https://github.com/matrix-org/matrix-react-sdk/pull/3630) + * Get theme automatically from system setting + [\#3637](https://github.com/matrix-org/matrix-react-sdk/pull/3637) + * Update code style for our 90 char life + [\#3636](https://github.com/matrix-org/matrix-react-sdk/pull/3636) + * use general warning icon instead of e2e one for room status + [\#3633](https://github.com/matrix-org/matrix-react-sdk/pull/3633) + * Add support for platform specific event indexing and search + [\#3550](https://github.com/matrix-org/matrix-react-sdk/pull/3550) + * Update from Weblate + [\#3635](https://github.com/matrix-org/matrix-react-sdk/pull/3635) + * Use a settings watcher to set the theme + [\#3634](https://github.com/matrix-org/matrix-react-sdk/pull/3634) + * Merge the `feature_user_info_panel` flag into `feature_dm_verification` + [\#3632](https://github.com/matrix-org/matrix-react-sdk/pull/3632) + * Fix some styling regressions in member panel + [\#3631](https://github.com/matrix-org/matrix-react-sdk/pull/3631) + * Add a bit more safety around breadcrumbs + [\#3629](https://github.com/matrix-org/matrix-react-sdk/pull/3629) + * Ensure widgets always have a sender associated with them + [\#3628](https://github.com/matrix-org/matrix-react-sdk/pull/3628) + * re-add missing case of codepath + [\#3627](https://github.com/matrix-org/matrix-react-sdk/pull/3627) + * Implement the bulk of the new widget permission prompt design + [\#3622](https://github.com/matrix-org/matrix-react-sdk/pull/3622) + * Relax identity server discovery error handling + [\#3588](https://github.com/matrix-org/matrix-react-sdk/pull/3588) + * Add cross-signing feature flag + [\#3626](https://github.com/matrix-org/matrix-react-sdk/pull/3626) + * Attempt number two at ripping out Bluebird from rageshake.js + [\#3624](https://github.com/matrix-org/matrix-react-sdk/pull/3624) + * Update from Weblate + [\#3625](https://github.com/matrix-org/matrix-react-sdk/pull/3625) + * Remove Bluebird: phase 2.1 + [\#3618](https://github.com/matrix-org/matrix-react-sdk/pull/3618) + * Add better error handling to Synapse user deactivation + [\#3619](https://github.com/matrix-org/matrix-react-sdk/pull/3619) + * New design for member panel + [\#3620](https://github.com/matrix-org/matrix-react-sdk/pull/3620) + * Show server details on login for unreachable homeserver + [\#3617](https://github.com/matrix-org/matrix-react-sdk/pull/3617) + * Add a function to get the "base" theme for a theme + [\#3615](https://github.com/matrix-org/matrix-react-sdk/pull/3615) + * Remove Bluebird: phase 2 + [\#3616](https://github.com/matrix-org/matrix-react-sdk/pull/3616) + * Remove Bluebird: phase 1 + [\#3612](https://github.com/matrix-org/matrix-react-sdk/pull/3612) + * Move notification count to in front of the room name in the page title + [\#3613](https://github.com/matrix-org/matrix-react-sdk/pull/3613) + * Add some logging/recovery for lost rooms + [\#3614](https://github.com/matrix-org/matrix-react-sdk/pull/3614) + * Add Mjolnir ban list support + [\#3585](https://github.com/matrix-org/matrix-react-sdk/pull/3585) + * Improve room switching performance with alias cache + [\#3610](https://github.com/matrix-org/matrix-react-sdk/pull/3610) + * Fix draw order when hovering composer format buttons + [\#3609](https://github.com/matrix-org/matrix-react-sdk/pull/3609) + * Use a ternary operator instead of relying on AND semantics in + EditHistoryDialog + [\#3606](https://github.com/matrix-org/matrix-react-sdk/pull/3606) + * Update from Weblate + [\#3608](https://github.com/matrix-org/matrix-react-sdk/pull/3608) + * Fix HTML fallback in replies + [\#3607](https://github.com/matrix-org/matrix-react-sdk/pull/3607) + * Fix rounded corners for the formatting toolbar + [\#3605](https://github.com/matrix-org/matrix-react-sdk/pull/3605) + * Check for a message type before assuming it is a room message + [\#3604](https://github.com/matrix-org/matrix-react-sdk/pull/3604) + * Remove lint comments about no-descending-specificity + [\#3603](https://github.com/matrix-org/matrix-react-sdk/pull/3603) + * Show verification requests in the timeline + [\#3601](https://github.com/matrix-org/matrix-react-sdk/pull/3601) + * Match identity server registration to the IS r0.3.0 spec + [\#3602](https://github.com/matrix-org/matrix-react-sdk/pull/3602) + * Restore thumbs after variation selector removal + [\#3600](https://github.com/matrix-org/matrix-react-sdk/pull/3600) + * Fix breadcrumbs so the bar is a toolbar and the buttons are buttons. + [\#3599](https://github.com/matrix-org/matrix-react-sdk/pull/3599) + * Now that part of spacing is padding, make it smaller when collapsed + [\#3597](https://github.com/matrix-org/matrix-react-sdk/pull/3597) + * Remove variation selectors from quick reactions + [\#3598](https://github.com/matrix-org/matrix-react-sdk/pull/3598) + * Fix linkify imports + [\#3595](https://github.com/matrix-org/matrix-react-sdk/pull/3595) + Changes in [1.7.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.7.2) (2019-11-06) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.7.1...v1.7.2) diff --git a/docs/ciderEditor.md b/docs/ciderEditor.md index e67c74a95c..00033b5b8c 100644 --- a/docs/ciderEditor.md +++ b/docs/ciderEditor.md @@ -2,8 +2,7 @@ The CIDER editor is a custom editor written for Riot. Most of the code can be found in the `/editor/` directory of the `matrix-react-sdk` project. -It is used to power the composer to edit messages, -and will soon be used as the main composer to send messages as well. +It is used to power the composer main composer (both to send and edit messages), and might be used for other usecases where autocomplete is desired (invite box, ...). ## High-level overview. diff --git a/package.json b/package.json index eb234e0573..8bac523b2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "1.7.2", + "version": "1.7.3", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -60,7 +60,6 @@ "dependencies": { "babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-runtime": "^6.26.0", - "bluebird": "^3.5.0", "blueimp-canvas-to-blob": "^3.5.0", "browser-encrypt-attachment": "^0.3.0", "browser-request": "^0.3.3", @@ -89,7 +88,7 @@ "linkifyjs": "^2.1.6", "lodash": "^4.17.14", "lolex": "4.2", - "matrix-js-sdk": "2.4.3", + "matrix-js-sdk": "2.4.4", "optimist": "^0.6.1", "pako": "^1.0.5", "png-chunks-extract": "^1.0.0", @@ -120,7 +119,6 @@ "babel-eslint": "^10.0.1", "babel-loader": "^7.1.5", "babel-plugin-add-module-exports": "^0.2.1", - "babel-plugin-transform-async-to-bluebird": "^1.1.1", "babel-plugin-transform-builtin-extend": "^1.1.2", "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-object-rest-spread": "^6.26.0", @@ -135,6 +133,7 @@ "eslint": "^5.12.0", "eslint-config-google": "^0.7.1", "eslint-plugin-babel": "^5.2.1", + "eslint-plugin-jest": "^23.0.4", "eslint-plugin-flowtype": "^2.30.0", "eslint-plugin-react": "^7.7.0", "eslint-plugin-react-hooks": "^2.0.1", diff --git a/res/css/_components.scss b/res/css/_components.scss index 40a2c576d0..c47222da59 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -25,6 +25,7 @@ @import "./structures/_TabbedView.scss"; @import "./structures/_TagPanel.scss"; @import "./structures/_TagPanelButtons.scss"; +@import "./structures/_ToastContainer.scss"; @import "./structures/_TopLeftMenuButton.scss"; @import "./structures/_UploadBar.scss"; @import "./structures/_ViewSource.scss"; @@ -48,6 +49,7 @@ @import "./views/context_menus/_StatusMessageContextMenu.scss"; @import "./views/context_menus/_TagTileContextMenu.scss"; @import "./views/context_menus/_TopLeftMenu.scss"; +@import "./views/context_menus/_WidgetContextMenu.scss"; @import "./views/dialogs/_AddressPickerDialog.scss"; @import "./views/dialogs/_Analytics.scss"; @import "./views/dialogs/_ChangelogDialog.scss"; @@ -90,6 +92,7 @@ @import "./views/elements/_ErrorBoundary.scss"; @import "./views/elements/_EventListSummary.scss"; @import "./views/elements/_Field.scss"; +@import "./views/elements/_FormButton.scss"; @import "./views/elements/_IconButton.scss"; @import "./views/elements/_ImageView.scss"; @import "./views/elements/_InlineSpinner.scss"; @@ -172,7 +175,7 @@ @import "./views/rooms/_WhoIsTypingTile.scss"; @import "./views/settings/_DevicesPanel.scss"; @import "./views/settings/_EmailAddresses.scss"; -@import "./views/settings/_IntegrationsManager.scss"; +@import "./views/settings/_IntegrationManager.scss"; @import "./views/settings/_KeyBackupPanel.scss"; @import "./views/settings/_Notifications.scss"; @import "./views/settings/_PhoneNumbers.scss"; diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss new file mode 100644 index 0000000000..4c5e746e66 --- /dev/null +++ b/res/css/structures/_ToastContainer.scss @@ -0,0 +1,98 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_ToastContainer { + position: absolute; + top: 0; + left: 70px; + z-index: 101; + padding: 4px; + display: grid; + grid-template-rows: 1fr 14px 6px; + + &.mx_ToastContainer_stacked::before { + content: ""; + margin: 0 4px; + grid-row: 2 / 4; + grid-column: 1; + background-color: white; + box-shadow: 0px 4px 12px $menu-box-shadow-color; + border-radius: 8px; + } + + .mx_Toast_toast { + grid-row: 1 / 3; + grid-column: 1; + color: $primary-fg-color; + background-color: $primary-bg-color; + box-shadow: 0px 4px 12px $menu-box-shadow-color; + border-radius: 8px; + overflow: hidden; + display: grid; + grid-template-columns: 20px 1fr; + column-gap: 10px; + row-gap: 4px; + padding: 8px; + padding-right: 16px; + + &.mx_Toast_hasIcon { + &::after { + content: ""; + width: 20px; + height: 20px; + grid-column: 1; + grid-row: 1; + mask-size: 100%; + mask-repeat: no-repeat; + } + + &.mx_Toast_icon_verification::after { + mask-image: url("$(res)/img/e2e/normal.svg"); + background-color: $primary-fg-color; + } + + h2, .mx_Toast_body { + grid-column: 2; + } + } + + h2 { + grid-column: 1 / 3; + grid-row: 1; + margin: 0; + font-size: 15px; + font-weight: 600; + } + + .mx_Toast_body { + grid-column: 1 / 3; + grid-row: 2; + } + + .mx_Toast_buttons { + display: flex; + } + + .mx_Toast_description { + max-width: 400px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin: 4px 0 11px 0; + font-size: 12px; + } + } +} diff --git a/res/css/views/context_menus/_WidgetContextMenu.scss b/res/css/views/context_menus/_WidgetContextMenu.scss new file mode 100644 index 0000000000..60b7b93f99 --- /dev/null +++ b/res/css/views/context_menus/_WidgetContextMenu.scss @@ -0,0 +1,36 @@ +/* +Copyright 2019 The Matrix.org Foundaction C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_WidgetContextMenu { + padding: 6px; + + .mx_WidgetContextMenu_option { + padding: 3px 6px 3px 6px; + cursor: pointer; + white-space: nowrap; + } + + .mx_WidgetContextMenu_separator { + margin-top: 0; + margin-bottom: 0; + border-bottom-style: none; + border-left-style: none; + border-right-style: none; + border-top-style: solid; + border-top-width: 1px; + border-color: $menu-border-color; + } +} diff --git a/res/css/views/dialogs/_TermsDialog.scss b/res/css/views/dialogs/_TermsDialog.scss index aad679a5b3..beb507e778 100644 --- a/res/css/views/dialogs/_TermsDialog.scss +++ b/res/css/views/dialogs/_TermsDialog.scss @@ -16,10 +16,10 @@ limitations under the License. /* * To avoid visual glitching of two modals stacking briefly, we customise the - * terms dialog sizing when it will appear for the integrations manager so that + * terms dialog sizing when it will appear for the integration manager so that * it gets the same basic size as the IM's own modal. */ -.mx_TermsDialog_forIntegrationsManager .mx_Dialog { +.mx_TermsDialog_forIntegrationManager .mx_Dialog { width: 60%; height: 70%; box-sizing: border-box; diff --git a/res/css/views/elements/_FormButton.scss b/res/css/views/elements/_FormButton.scss new file mode 100644 index 0000000000..1483fe2091 --- /dev/null +++ b/res/css/views/elements/_FormButton.scss @@ -0,0 +1,36 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_FormButton { + line-height: 16px; + padding: 5px 15px; + font-size: 12px; + height: min-content; + + &:not(:last-child) { + margin-right: 8px; + } + + &.mx_AccessibleButton_kind_primary { + color: $accent-color; + background-color: $accent-bg-color; + } + + &.mx_AccessibleButton_kind_danger { + color: $notice-primary-color; + background-color: $notice-primary-bg-color; + } +} diff --git a/res/css/views/messages/_MKeyVerificationRequest.scss b/res/css/views/messages/_MKeyVerificationRequest.scss index 87a75dee82..ee20751083 100644 --- a/res/css/views/messages/_MKeyVerificationRequest.scss +++ b/res/css/views/messages/_MKeyVerificationRequest.scss @@ -65,23 +65,6 @@ limitations under the License. .mx_KeyVerification_buttons { align-items: center; display: flex; - - .mx_AccessibleButton_kind_decline { - color: $notice-primary-color; - background-color: $notice-primary-bg-color; - } - - .mx_AccessibleButton_kind_accept { - color: $accent-color; - background-color: $accent-bg-color; - } - - [role=button] { - margin: 10px; - padding: 7px 15px; - border-radius: 5px; - height: min-content; - } } .mx_KeyVerification_state { diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 6f5e3abade..a3fe573ad0 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -153,40 +153,12 @@ $AppsDrawerBodyHeight: 273px; background-color: $accent-color; } -.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_reload { - mask-image: url('$(res)/img/feather-customised/widget/refresh.svg'); - mask-size: 100%; -} - .mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_popout { mask-image: url('$(res)/img/feather-customised/widget/external-link.svg'); } -.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_snapshot { - mask-image: url('$(res)/img/feather-customised/widget/camera.svg'); -} - -.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_edit { - mask-image: url('$(res)/img/feather-customised/widget/edit.svg'); -} - -.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_delete { - mask-image: url('$(res)/img/feather-customised/widget/bin.svg'); - background-color: $warning-color; -} - -.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_cancel { - mask-image: url('$(res)/img/feather-customised/widget/x-circle.svg'); -} - -/* delete ? */ -.mx_AppTileMenuBarWidget { - cursor: pointer; - width: 10px; - height: 10px; - padding: 1px; - transition-duration: 500ms; - border: 1px solid transparent; +.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_menu { + mask-image: url('$(res)/img/icon_context.svg'); } .mx_AppTileMenuBarWidgetDelete { diff --git a/res/css/views/rooms/_E2EIcon.scss b/res/css/views/rooms/_E2EIcon.scss index 1ee5008888..cb99aa63f1 100644 --- a/res/css/views/rooms/_E2EIcon.scss +++ b/res/css/views/rooms/_E2EIcon.scss @@ -15,8 +15,8 @@ limitations under the License. */ .mx_E2EIcon { - width: 25px; - height: 25px; + width: 16px; + height: 16px; margin: 0 9px; position: relative; display: block; @@ -30,16 +30,14 @@ limitations under the License. bottom: 0; left: 0; right: 0; - mask-repeat: no-repeat; - mask-size: contain; + background-repeat: no-repeat; + background-size: contain; } .mx_E2EIcon_verified::after { - mask-image: url('$(res)/img/e2e/verified.svg'); - background-color: $accent-color; + background-image: url('$(res)/img/e2e/verified.svg'); } .mx_E2EIcon_warning::after { - mask-image: url('$(res)/img/e2e/warning.svg'); - background-color: $warning-color; + background-image: url('$(res)/img/e2e/warning.svg'); } diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index 14562fe7ed..12e45a07c9 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -23,10 +23,6 @@ limitations under the License. padding-left: 84px; } -.mx_MessageComposer_wrapper.mx_MessageComposer_hasE2EIcon { - padding-left: 109px; -} - .mx_MessageComposer_replaced_wrapper { margin-left: auto; margin-right: auto; @@ -78,10 +74,8 @@ limitations under the License. .mx_MessageComposer_e2eIcon.mx_E2EIcon { position: absolute; left: 60px; - - &::after { - background-color: $composer-e2e-icon-color; - } + margin-right: 0; // Counteract the E2EIcon class + margin-left: 3px; // Counteract the E2EIcon class } .mx_MessageComposer_noperm_error { diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index 5da8ff76b9..f1e4456cc1 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -17,6 +17,10 @@ limitations under the License. .mx_RoomHeader { flex: 0 0 52px; border-bottom: 1px solid $primary-hairline-color; + + .mx_E2EIcon { + margin: 0 5px; + } } .mx_RoomHeader_wrapper { diff --git a/res/css/views/settings/_IntegrationsManager.scss b/res/css/views/settings/_IntegrationManager.scss similarity index 83% rename from res/css/views/settings/_IntegrationsManager.scss rename to res/css/views/settings/_IntegrationManager.scss index 8b51eb272e..81b01ab8de 100644 --- a/res/css/views/settings/_IntegrationsManager.scss +++ b/res/css/views/settings/_IntegrationManager.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_IntegrationsManager .mx_Dialog { +.mx_IntegrationManager .mx_Dialog { width: 60%; height: 70%; overflow: hidden; @@ -23,22 +23,22 @@ limitations under the License. max-height: initial; } -.mx_IntegrationsManager iframe { +.mx_IntegrationManager iframe { background-color: #fff; border: 0px; width: 100%; height: 100%; } -.mx_IntegrationsManager_loading h3 { +.mx_IntegrationManager_loading h3 { text-align: center; } -.mx_IntegrationsManager_error { +.mx_IntegrationManager_error { text-align: center; padding-top: 20px; } -.mx_IntegrationsManager_error h3 { +.mx_IntegrationManager_error h3 { color: $warning-color; } diff --git a/res/css/views/settings/_SetIntegrationManager.scss b/res/css/views/settings/_SetIntegrationManager.scss index 99537f9eb4..3e59ac73ac 100644 --- a/res/css/views/settings/_SetIntegrationManager.scss +++ b/res/css/views/settings/_SetIntegrationManager.scss @@ -14,10 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_SetIntegrationManager .mx_Field_input { - @mixin mx_Settings_fullWidthField; -} - .mx_SetIntegrationManager { margin-top: 10px; margin-bottom: 10px; @@ -32,6 +28,10 @@ limitations under the License. padding-left: 5px; } -.mx_SetIntegrationManager_tooltip { - @mixin mx_Settings_tooltip; +.mx_SetIntegrationManager .mx_ToggleSwitch { + display: inline-block; + float: right; + top: 9px; + + @mixin mx_Settings_fullWidthField; } diff --git a/res/img/e2e/verified.svg b/res/img/e2e/verified.svg index af6bb92297..464b443dcf 100644 --- a/res/img/e2e/verified.svg +++ b/res/img/e2e/verified.svg @@ -1,12 +1,4 @@ - - - + + + diff --git a/res/img/e2e/warning.svg b/res/img/e2e/warning.svg index 2501da6ab3..209ae0f71f 100644 --- a/res/img/e2e/warning.svg +++ b/res/img/e2e/warning.svg @@ -1,12 +1,5 @@ - - - + + + + diff --git a/res/img/feather-customised/widget/bin.svg b/res/img/feather-customised/widget/bin.svg deleted file mode 100644 index 7616d8931b..0000000000 --- a/res/img/feather-customised/widget/bin.svg +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/res/img/feather-customised/widget/camera.svg b/res/img/feather-customised/widget/camera.svg deleted file mode 100644 index 5502493068..0000000000 --- a/res/img/feather-customised/widget/camera.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/res/img/feather-customised/widget/edit.svg b/res/img/feather-customised/widget/edit.svg deleted file mode 100644 index 749e83f982..0000000000 --- a/res/img/feather-customised/widget/edit.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/res/img/feather-customised/widget/refresh.svg b/res/img/feather-customised/widget/refresh.svg deleted file mode 100644 index 0994bbdd52..0000000000 --- a/res/img/feather-customised/widget/refresh.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/res/img/feather-customised/widget/x-circle.svg b/res/img/feather-customised/widget/x-circle.svg deleted file mode 100644 index 951407b39c..0000000000 --- a/res/img/feather-customised/widget/x-circle.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index dcd7ce166e..0a3ef812b8 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -12,9 +12,9 @@ $monospace-font-family: Inconsolata, Twemoji, 'Apple Color Emoji', 'Segoe UI Emo // unified palette // try to use these colors when possible $accent-color: #03b381; -$accent-bg-color: rgba(115, 247, 91, 0.08); +$accent-bg-color: rgba(3, 179, 129, 0.16); $notice-primary-color: #ff4b55; -$notice-primary-bg-color: rgba(255, 75, 85, 0.08); +$notice-primary-bg-color: rgba(255, 75, 85, 0.16); $notice-secondary-color: #61708b; $header-panel-bg-color: #f3f8fd; diff --git a/src/CallHandler.js b/src/CallHandler.js index 427be14097..ecbf6c2c12 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -80,13 +80,26 @@ function play(audioId) { // which listens? const audio = document.getElementById(audioId); if (audio) { + const playAudio = async () => { + try { + // This still causes the chrome debugger to break on promise rejection if + // the promise is rejected, even though we're catching the exception. + await audio.play(); + } catch (e) { + // This is usually because the user hasn't interacted with the document, + // or chrome doesn't think so and is denying the request. Not sure what + // we can really do here... + // https://github.com/vector-im/riot-web/issues/7657 + console.log("Unable to play audio clip", e); + } + }; if (audioPromises[audioId]) { audioPromises[audioId] = audioPromises[audioId].then(()=>{ audio.load(); - return audio.play(); + return playAudio(); }); } else { - audioPromises[audioId] = audio.play(); + audioPromises[audioId] = playAudio(); } } } @@ -382,7 +395,7 @@ function _onAction(payload) { } async function _startCallApp(roomId, type) { - // check for a working integrations manager. Technically we could put + // check for a working integration manager. Technically we could put // the state event in anyway, but the resulting widget would then not // work for us. Better that the user knows before everyone else in the // room sees it. @@ -495,6 +508,17 @@ async function _startCallApp(roomId, type) { // with the dispatcher once if (!global.mxCallHandler) { dis.register(_onAction); + // add empty handlers for media actions, otherwise the media keys + // end up causing the audio elements with our ring/ringback etc + // audio clips in to play. + if (navigator.mediaSession) { + navigator.mediaSession.setActionHandler('play', function() {}); + navigator.mediaSession.setActionHandler('pause', function() {}); + navigator.mediaSession.setActionHandler('seekbackward', function() {}); + navigator.mediaSession.setActionHandler('seekforward', function() {}); + navigator.mediaSession.setActionHandler('previoustrack', function() {}); + navigator.mediaSession.setActionHandler('nexttrack', function() {}); + } } const callHandler = { diff --git a/src/ContentMessages.js b/src/ContentMessages.js index dab8de2465..6908a6a18e 100644 --- a/src/ContentMessages.js +++ b/src/ContentMessages.js @@ -17,7 +17,6 @@ limitations under the License. 'use strict'; -import Promise from 'bluebird'; import extend from './extend'; import dis from './dispatcher'; import MatrixClientPeg from './MatrixClientPeg'; diff --git a/src/Lifecycle.js b/src/Lifecycle.js index e36f7b6898..b81b563129 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -16,7 +16,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Promise from 'bluebird'; import Matrix from 'matrix-js-sdk'; import MatrixClientPeg from './MatrixClientPeg'; @@ -526,7 +525,7 @@ export function logout() { console.log("Failed to call logout API: token will not be invalidated"); onLoggedOut(); }, - ).done(); + ); } export function softLogout() { diff --git a/src/Modal.js b/src/Modal.js index cb19731f01..4fc9fdcb02 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -23,7 +23,6 @@ import Analytics from './Analytics'; import sdk from './index'; import dis from './dispatcher'; import { _t } from './languageHandler'; -import Promise from "bluebird"; import {defer} from "./utils/promise"; const DIALOG_CONTAINER_ID = "mx_Dialog_Container"; diff --git a/src/Notifier.js b/src/Notifier.js index cca0ea2b89..dd691d8ca7 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -146,7 +146,7 @@ const Notifier = { } document.body.appendChild(audioElement); } - audioElement.play(); + await audioElement.play(); } catch (ex) { console.warn("Caught error when trying to fetch room notification sound:", ex); } @@ -198,7 +198,7 @@ const Notifier = { if (enable) { // Attempt to get permission from user - plaf.requestNotificationPermission().done((result) => { + plaf.requestNotificationPermission().then((result) => { if (result !== 'granted') { // The permission request was dismissed or denied // TODO: Support alternative branding in messaging diff --git a/src/Resend.js b/src/Resend.js index 4eaee16d1b..51ec804c01 100644 --- a/src/Resend.js +++ b/src/Resend.js @@ -35,7 +35,7 @@ module.exports = { }, resend: function(event) { const room = MatrixClientPeg.get().getRoom(event.getRoomId()); - MatrixClientPeg.get().resendEvent(event, room).done(function(res) { + MatrixClientPeg.get().resendEvent(event, room).then(function(res) { dis.dispatch({ action: 'message_sent', event: event, diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index 2d5e4b3136..5bef4afd25 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -17,7 +17,6 @@ limitations under the License. import MatrixClientPeg from './MatrixClientPeg'; import PushProcessor from 'matrix-js-sdk/lib/pushprocessor'; -import Promise from 'bluebird'; export const ALL_MESSAGES_LOUD = 'all_messages_loud'; export const ALL_MESSAGES = 'all_messages'; diff --git a/src/Rooms.js b/src/Rooms.js index c8f90ec39a..239e348b58 100644 --- a/src/Rooms.js +++ b/src/Rooms.js @@ -15,7 +15,6 @@ limitations under the License. */ import MatrixClientPeg from './MatrixClientPeg'; -import Promise from 'bluebird'; /** * Given a room object, return the alias we should use for it, diff --git a/src/ScalarAuthClient.js b/src/ScalarAuthClient.js index 3623d47f8e..92f0ff6340 100644 --- a/src/ScalarAuthClient.js +++ b/src/ScalarAuthClient.js @@ -16,7 +16,6 @@ limitations under the License. */ import url from 'url'; -import Promise from 'bluebird'; import SettingsStore from "./settings/SettingsStore"; import { Service, startTermsFlow, TermsNotSignedError } from './Terms'; const request = require('browser-request'); diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index 910a6c4f13..c0ffc3022d 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -279,7 +279,7 @@ function inviteUser(event, roomId, userId) { } } - client.invite(roomId, userId).done(function() { + client.invite(roomId, userId).then(function() { sendResponse(event, { success: true, }); @@ -398,7 +398,7 @@ function setPlumbingState(event, roomId, status) { sendError(event, _t('You need to be logged in.')); return; } - client.sendStateEvent(roomId, "m.room.plumbing", { status: status }).done(() => { + client.sendStateEvent(roomId, "m.room.plumbing", { status: status }).then(() => { sendResponse(event, { success: true, }); @@ -414,7 +414,7 @@ function setBotOptions(event, roomId, userId) { sendError(event, _t('You need to be logged in.')); return; } - client.sendStateEvent(roomId, "m.room.bot.options", event.data.content, "_" + userId).done(() => { + client.sendStateEvent(roomId, "m.room.bot.options", event.data.content, "_" + userId).then(() => { sendResponse(event, { success: true, }); @@ -444,7 +444,7 @@ function setBotPower(event, roomId, userId, level) { }, ); - client.setPowerLevel(roomId, userId, level, powerEvent).done(() => { + client.setPowerLevel(roomId, userId, level, powerEvent).then(() => { sendResponse(event, { success: true, }); diff --git a/src/SlashCommands.js b/src/SlashCommands.js index 1a491da54f..31e7ca4f39 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -28,7 +28,6 @@ import { linkifyAndSanitizeHtml } from './HtmlUtils'; import QuestionDialog from "./components/views/dialogs/QuestionDialog"; import WidgetUtils from "./utils/WidgetUtils"; import {textToHtmlRainbow} from "./utils/colour"; -import Promise from "bluebird"; import { getAddressType } from './UserAddress'; import { abbreviateUrl } from './utils/UrlUtils'; import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/IdentityServerUtils'; diff --git a/src/Terms.js b/src/Terms.js index 685a39709c..14a7ccb65e 100644 --- a/src/Terms.js +++ b/src/Terms.js @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Promise from 'bluebird'; import classNames from 'classnames'; import MatrixClientPeg from './MatrixClientPeg'; diff --git a/src/ToWidgetPostMessageApi.js b/src/ToWidgetPostMessageApi.js index def4af56ae..00309d252c 100644 --- a/src/ToWidgetPostMessageApi.js +++ b/src/ToWidgetPostMessageApi.js @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Promise from "bluebird"; - // const OUTBOUND_API_NAME = 'toWidget'; // Initiate requests using the "toWidget" postMessage API and handle responses diff --git a/src/VectorConferenceHandler.js b/src/VectorConferenceHandler.js index 37b3a7ddad..e0e333a371 100644 --- a/src/VectorConferenceHandler.js +++ b/src/VectorConferenceHandler.js @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Promise from 'bluebird'; import {createNewMatrixCall, Room} from "matrix-js-sdk"; import CallHandler from './CallHandler'; import MatrixClientPeg from "./MatrixClientPeg"; diff --git a/src/autocomplete/Autocompleter.js b/src/autocomplete/Autocompleter.js index c385e13878..a26eb6033b 100644 --- a/src/autocomplete/Autocompleter.js +++ b/src/autocomplete/Autocompleter.js @@ -26,7 +26,6 @@ import RoomProvider from './RoomProvider'; import UserProvider from './UserProvider'; import EmojiProvider from './EmojiProvider'; import NotifProvider from './NotifProvider'; -import Promise from 'bluebird'; import {timeout} from "../utils/promise"; export type SelectionRange = { diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 776e7f0d6d..a0aa36803f 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -19,7 +19,6 @@ limitations under the License. import React from 'react'; import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; -import Promise from 'bluebird'; import MatrixClientPeg from '../../MatrixClientPeg'; import sdk from '../../index'; import dis from '../../dispatcher'; @@ -637,7 +636,7 @@ export default createReactClass({ title: _t('Error'), description: _t('Failed to upload image'), }); - }).done(); + }); }, _onJoinableChange: function(ev) { @@ -676,7 +675,7 @@ export default createReactClass({ this.setState({ avatarChanged: false, }); - }).done(); + }); }, _saveGroup: async function() { diff --git a/src/components/structures/InteractiveAuth.js b/src/components/structures/InteractiveAuth.js index 5e06d124c4..e1b02f653b 100644 --- a/src/components/structures/InteractiveAuth.js +++ b/src/components/structures/InteractiveAuth.js @@ -121,7 +121,7 @@ export default createReactClass({ this.setState({ errorText: msg, }); - }).done(); + }); this._intervalId = null; if (this.props.poll) { diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 889b0cdc8b..d071ba1d79 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -525,6 +525,7 @@ const LoggedInView = createReactClass({ const EmbeddedPage = sdk.getComponent('structures.EmbeddedPage'); const GroupView = sdk.getComponent('structures.GroupView'); const MyGroups = sdk.getComponent('structures.MyGroups'); + const ToastContainer = sdk.getComponent('structures.ToastContainer'); const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar'); const CookieBar = sdk.getComponent('globals.CookieBar'); const NewVersionBar = sdk.getComponent('globals.NewVersionBar'); @@ -628,6 +629,7 @@ const LoggedInView = createReactClass({ return (
{ topBar } +
{ + MatrixClientPeg.get().leave(payload.room_id).then(() => { modal.close(); if (this.state.currentRoomId === payload.room_id) { dis.dispatch({action: 'view_next_room'}); @@ -863,7 +858,7 @@ export default createReactClass({ waitFor = this.firstSyncPromise.promise; } - waitFor.done(() => { + waitFor.then(() => { let presentedId = roomInfo.room_alias || roomInfo.room_id; const room = MatrixClientPeg.get().getRoom(roomInfo.room_id); if (room) { @@ -980,7 +975,7 @@ export default createReactClass({ const [shouldCreate, createOpts] = await modal.finished; if (shouldCreate) { - createRoom({createOpts}).done(); + createRoom({createOpts}); } }, @@ -1270,7 +1265,6 @@ export default createReactClass({ this.firstSyncComplete = false; this.firstSyncPromise = defer(); const cli = MatrixClientPeg.get(); - const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog'); // Allow the JS SDK to reap timeline events. This reduces the amount of // memory consumed as the JS SDK stores multiple distinct copies of room @@ -1469,12 +1463,35 @@ export default createReactClass({ } }); - cli.on("crypto.verification.start", (verifier) => { - Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, { - verifier, - }); - }); + if (SettingsStore.isFeatureEnabled("feature_dm_verification")) { + cli.on("crypto.verification.request", request => { + let requestObserver; + if (request.event.getRoomId()) { + requestObserver = new KeyVerificationStateObserver( + request.event, MatrixClientPeg.get()); + } + if (!requestObserver || requestObserver.pending) { + dis.dispatch({ + action: "show_toast", + toast: { + key: request.event.getId(), + title: _t("Verification Request"), + icon: "verification", + props: {request, requestObserver}, + component: sdk.getComponent("toasts.VerificationRequestToast"), + }, + }); + } + }); + } else { + cli.on("crypto.verification.start", (verifier) => { + const IncomingSasDialog = sdk.getComponent("views.dialogs.IncomingSasDialog"); + Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, { + verifier, + }); + }); + } // Fire the tinter right on startup to ensure the default theme is applied // A later sync can/will correct the tint to be the right value for the user const colorScheme = SettingsStore.getValue("roomColor"); @@ -1745,7 +1762,7 @@ export default createReactClass({ return; } - cli.sendEvent(roomId, event.getType(), event.getContent()).done(() => { + cli.sendEvent(roomId, event.getType(), event.getContent()).then(() => { dis.dispatch({action: 'message_sent'}); }, (err) => { dis.dispatch({action: 'message_send_failed'}); diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index cf2a5b1738..912b865b9f 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -1,6 +1,7 @@ /* Copyright 2016 OpenMarket Ltd Copyright 2018 New Vector Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,7 +19,6 @@ limitations under the License. /* global Velocity */ import React from 'react'; -import createReactClass from 'create-react-class'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import classNames from 'classnames'; @@ -37,10 +37,8 @@ const isMembershipChange = (e) => e.getType() === 'm.room.member' || e.getType() /* (almost) stateless UI component which builds the event tiles in the room timeline. */ -module.exports = createReactClass({ - displayName: 'MessagePanel', - - propTypes: { +export default class MessagePanel extends React.Component { + static propTypes = { // true to give the component a 'display: none' style. hidden: PropTypes.bool, @@ -109,9 +107,10 @@ module.exports = createReactClass({ // whether to show reactions for an event showReactions: PropTypes.bool, - }, + }; - componentWillMount: function() { + constructor() { + super(); // the event after which we put a visible unread marker on the last // render cycle; null if readMarkerVisible was false or the RM was // suppressed (eg because it was at the end of the timeline) @@ -167,38 +166,42 @@ module.exports = createReactClass({ this._showHiddenEventsInTimeline = SettingsStore.getValue("showHiddenEventsInTimeline"); - this._isMounted = true; - }, - - componentWillUnmount: function() { this._isMounted = false; - }, + } + + componentDidMount() { + this._isMounted = true; + } + + componentWillUnmount() { + this._isMounted = false; + } /* get the DOM node representing the given event */ - getNodeForEventId: function(eventId) { + getNodeForEventId(eventId) { if (!this.eventNodes) { return undefined; } return this.eventNodes[eventId]; - }, + } /* return true if the content is fully scrolled down right now; else false. */ - isAtBottom: function() { + isAtBottom() { return this.refs.scrollPanel && this.refs.scrollPanel.isAtBottom(); - }, + } /* get the current scroll state. See ScrollPanel.getScrollState for * details. * * returns null if we are not mounted. */ - getScrollState: function() { + getScrollState() { if (!this.refs.scrollPanel) { return null; } return this.refs.scrollPanel.getScrollState(); - }, + } // returns one of: // @@ -206,7 +209,7 @@ module.exports = createReactClass({ // -1: read marker is above the window // 0: read marker is within the window // +1: read marker is below the window - getReadMarkerPosition: function() { + getReadMarkerPosition() { const readMarker = this.refs.readMarkerNode; const messageWrapper = this.refs.scrollPanel; @@ -226,45 +229,45 @@ module.exports = createReactClass({ } else { return 1; } - }, + } /* jump to the top of the content. */ - scrollToTop: function() { + scrollToTop() { if (this.refs.scrollPanel) { this.refs.scrollPanel.scrollToTop(); } - }, + } /* jump to the bottom of the content. */ - scrollToBottom: function() { + scrollToBottom() { if (this.refs.scrollPanel) { this.refs.scrollPanel.scrollToBottom(); } - }, + } /** * Page up/down. * * @param {number} mult: -1 to page up, +1 to page down */ - scrollRelative: function(mult) { + scrollRelative(mult) { if (this.refs.scrollPanel) { this.refs.scrollPanel.scrollRelative(mult); } - }, + } /** * Scroll up/down in response to a scroll key * * @param {KeyboardEvent} ev: the keyboard event to handle */ - handleScrollKey: function(ev) { + handleScrollKey(ev) { if (this.refs.scrollPanel) { this.refs.scrollPanel.handleScrollKey(ev); } - }, + } /* jump to the given event id. * @@ -276,33 +279,33 @@ module.exports = createReactClass({ * node (specifically, the bottom of it) will be positioned. If omitted, it * defaults to 0. */ - scrollToEvent: function(eventId, pixelOffset, offsetBase) { + scrollToEvent(eventId, pixelOffset, offsetBase) { if (this.refs.scrollPanel) { this.refs.scrollPanel.scrollToToken(eventId, pixelOffset, offsetBase); } - }, + } - scrollToEventIfNeeded: function(eventId) { + scrollToEventIfNeeded(eventId) { const node = this.eventNodes[eventId]; if (node) { node.scrollIntoView({block: "nearest", behavior: "instant"}); } - }, + } /* check the scroll state and send out pagination requests if necessary. */ - checkFillState: function() { + checkFillState() { if (this.refs.scrollPanel) { this.refs.scrollPanel.checkFillState(); } - }, + } - _isUnmounting: function() { + _isUnmounting() { return !this._isMounted; - }, + } // TODO: Implement granular (per-room) hide options - _shouldShowEvent: function(mxEv) { + _shouldShowEvent(mxEv) { if (mxEv.sender && MatrixClientPeg.get().isUserIgnored(mxEv.sender.userId)) { return false; // ignored = no show (only happens if the ignore happens after an event was received) } @@ -320,9 +323,9 @@ module.exports = createReactClass({ if (this.props.highlightedEventId === mxEv.getId()) return true; return !shouldHideEvent(mxEv); - }, + } - _getEventTiles: function() { + _getEventTiles() { const DateSeparator = sdk.getComponent('messages.DateSeparator'); const EventListSummary = sdk.getComponent('views.elements.EventListSummary'); const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary'); @@ -411,6 +414,12 @@ module.exports = createReactClass({ readMarkerInSummary = true; } + // If this m.room.create event should be shown (room upgrade) then show it before the summary + if (this._shouldShowEvent(mxEv)) { + // pass in the mxEv as prevEvent as well so no extra DateSeparator is rendered + ret.push(...this._getTilesForEvent(mxEv, mxEv, false)); + } + const summarisedEvents = []; // Don't add m.room.create here as we don't want it inside the summary for (;i + 1 < this.props.events.length; i++) { const collapsedMxEv = this.props.events[i + 1]; @@ -596,9 +605,9 @@ module.exports = createReactClass({ this.currentReadMarkerEventId = readMarkerVisible ? this.props.readMarkerEventId : null; return ret; - }, + } - _getTilesForEvent: function(prevEvent, mxEv, last) { + _getTilesForEvent(prevEvent, mxEv, last) { const EventTile = sdk.getComponent('rooms.EventTile'); const DateSeparator = sdk.getComponent('messages.DateSeparator'); const ret = []; @@ -691,20 +700,20 @@ module.exports = createReactClass({ ); return ret; - }, + } - _wantsDateSeparator: function(prevEvent, nextEventDate) { + _wantsDateSeparator(prevEvent, nextEventDate) { if (prevEvent == null) { // first event in the panel: depends if we could back-paginate from // here. return !this.props.suppressFirstDateSeparator; } return wantsDateSeparator(prevEvent.getDate(), nextEventDate); - }, + } // Get a list of read receipts that should be shown next to this event // Receipts are objects which have a 'userId', 'roomMember' and 'ts'. - _getReadReceiptsForEvent: function(event) { + _getReadReceiptsForEvent(event) { const myUserId = MatrixClientPeg.get().credentials.userId; // get list of read receipts, sorted most recent first @@ -728,12 +737,12 @@ module.exports = createReactClass({ }); }); return receipts; - }, + } // Get an object that maps from event ID to a list of read receipts that // 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. - _getReadReceiptsByShownEvent: function() { + _getReadReceiptsByShownEvent() { const receiptsByEvent = {}; const receiptsByUserId = {}; @@ -786,9 +795,9 @@ module.exports = createReactClass({ } return receiptsByEvent; - }, + } - _getReadMarkerTile: function(visible) { + _getReadMarkerTile(visible) { let hr; if (visible) { hr =
); - }, + } - _startAnimation: function(ghostNode) { + _startAnimation = (ghostNode) => { if (this._readMarkerGhostNode) { Velocity.Utilities.removeData(this._readMarkerGhostNode); } @@ -816,9 +825,9 @@ module.exports = createReactClass({ {duration: 400, easing: 'easeInSine', delay: 1000}); } - }, + }; - _getReadMarkerGhostTile: function() { + _getReadMarkerGhostTile() { const hr =
); - }, + } - _collectEventNode: function(eventId, node) { + _collectEventNode = (eventId, node) => { this.eventNodes[eventId] = node; - }, + } // once dynamic content in the events load, make the scrollPanel check the // scroll offsets. - _onHeightChanged: function() { + _onHeightChanged = () => { const scrollPanel = this.refs.scrollPanel; if (scrollPanel) { scrollPanel.checkScroll(); } - }, + }; - _onTypingShown: function() { + _onTypingShown = () => { const scrollPanel = this.refs.scrollPanel; // this will make the timeline grow, so checkScroll scrollPanel.checkScroll(); if (scrollPanel && scrollPanel.getScrollState().stuckAtBottom) { scrollPanel.preventShrinking(); } - }, + }; - _onTypingHidden: function() { + _onTypingHidden = () => { const scrollPanel = this.refs.scrollPanel; if (scrollPanel) { // as hiding the typing notifications doesn't @@ -868,9 +877,9 @@ module.exports = createReactClass({ // reveal added padding to balance the notifs disappearing. scrollPanel.checkScroll(); } - }, + }; - updateTimelineMinHeight: function() { + updateTimelineMinHeight() { const scrollPanel = this.refs.scrollPanel; if (scrollPanel) { @@ -885,16 +894,16 @@ module.exports = createReactClass({ scrollPanel.preventShrinking(); } } - }, + } - onTimelineReset: function() { + onTimelineReset() { const scrollPanel = this.refs.scrollPanel; if (scrollPanel) { scrollPanel.clearPreventShrinking(); } - }, + } - render: function() { + render() { const ScrollPanel = sdk.getComponent("structures.ScrollPanel"); const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile"); const Spinner = sdk.getComponent("elements.Spinner"); @@ -941,5 +950,5 @@ module.exports = createReactClass({ { bottomSpinner } ); - }, -}); + } +} diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js index 2de15a5444..63ae14ba09 100644 --- a/src/components/structures/MyGroups.js +++ b/src/components/structures/MyGroups.js @@ -47,7 +47,7 @@ export default createReactClass({ }, _fetch: function() { - this.context.matrixClient.getJoinedGroups().done((result) => { + this.context.matrixClient.getJoinedGroups().then((result) => { this.setState({groups: result.groups, error: null}); }, (err) => { if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') { diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index 84f402e484..efca8d12a8 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -27,7 +27,6 @@ const dis = require('../../dispatcher'); import { linkifyAndSanitizeHtml } from '../../HtmlUtils'; import PropTypes from 'prop-types'; -import Promise from 'bluebird'; import { _t } from '../../languageHandler'; import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils'; import Analytics from '../../Analytics'; @@ -89,7 +88,7 @@ module.exports = createReactClass({ this.setState({protocolsLoading: false}); return; } - MatrixClientPeg.get().getThirdpartyProtocols().done((response) => { + MatrixClientPeg.get().getThirdpartyProtocols().then((response) => { this.protocols = response; this.setState({protocolsLoading: false}); }, (err) => { @@ -135,7 +134,7 @@ module.exports = createReactClass({ publicRooms: [], loading: true, }); - this.getMoreRooms().done(); + this.getMoreRooms(); }, getMoreRooms: function() { @@ -246,7 +245,7 @@ module.exports = createReactClass({ if (!alias) return; step = _t('delete the alias.'); return MatrixClientPeg.get().deleteAlias(alias); - }).done(() => { + }).then(() => { modal.close(); this.refreshRoomList(); }, (err) => { @@ -348,7 +347,7 @@ module.exports = createReactClass({ }); return; } - MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).done((resp) => { + MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).then((resp) => { if (resp.length > 0 && resp[0].alias) { this.showRoomAlias(resp[0].alias, true); } else { diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index db7ae33c8a..5cc1e2b765 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -27,7 +27,6 @@ import React from 'react'; import createReactClass from 'create-react-class'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; -import Promise from 'bluebird'; import classNames from 'classnames'; import {Room} from "matrix-js-sdk"; import { _t } from '../../languageHandler'; @@ -1102,7 +1101,7 @@ module.exports = createReactClass({ } ContentMessages.sharedInstance().sendStickerContentToRoom(url, this.state.room.roomId, info, text, MatrixClientPeg.get()) - .done(undefined, (error) => { + .then(undefined, (error) => { if (error.name === "UnknownDeviceError") { // Let the staus bar handle this return; @@ -1135,7 +1134,7 @@ module.exports = createReactClass({ debuglog("sending search request"); const searchPromise = eventSearch(term, roomId); - this._handleSearchResult(searchPromise).done(); + this._handleSearchResult(searchPromise); }, _handleSearchResult: function(searchPromise) { @@ -1306,7 +1305,7 @@ module.exports = createReactClass({ }, onForgetClick: function() { - MatrixClientPeg.get().forget(this.state.room.roomId).done(function() { + MatrixClientPeg.get().forget(this.state.room.roomId).then(function() { dis.dispatch({ action: 'view_next_room' }); }, function(err) { const errCode = err.errcode || _t("unknown error code"); @@ -1323,7 +1322,7 @@ module.exports = createReactClass({ this.setState({ rejecting: true, }); - MatrixClientPeg.get().leave(this.state.roomId).done(function() { + MatrixClientPeg.get().leave(this.state.roomId).then(function() { dis.dispatch({ action: 'view_next_room' }); self.setState({ rejecting: false, diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index 1d5c520285..8a67e70467 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -17,7 +17,6 @@ limitations under the License. import React from "react"; import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; -import Promise from 'bluebird'; import { KeyCode } from '../../Keyboard'; import Timer from '../../utils/Timer'; import AutoHideScrollbar from "./AutoHideScrollbar"; diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index e8e23c2f76..80cbd43079 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -23,7 +23,6 @@ import React from 'react'; import createReactClass from 'create-react-class'; import ReactDOM from "react-dom"; import PropTypes from 'prop-types'; -import Promise from 'bluebird'; const Matrix = require("matrix-js-sdk"); const EventTimeline = Matrix.EventTimeline; @@ -462,7 +461,7 @@ const TimelinePanel = createReactClass({ // timeline window. // // see https://github.com/vector-im/vector-web/issues/1035 - this._timelineWindow.paginate(EventTimeline.FORWARDS, 1, false).done(() => { + this._timelineWindow.paginate(EventTimeline.FORWARDS, 1, false).then(() => { if (this.unmounted) { return; } const { events, liveEvents } = this._getEvents(); diff --git a/src/components/structures/ToastContainer.js b/src/components/structures/ToastContainer.js new file mode 100644 index 0000000000..a8dca35747 --- /dev/null +++ b/src/components/structures/ToastContainer.js @@ -0,0 +1,84 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import * as React from "react"; +import dis from "../../dispatcher"; +import { _t } from '../../languageHandler'; +import classNames from "classnames"; + +export default class ToastContainer extends React.Component { + constructor() { + super(); + this.state = {toasts: []}; + } + + componentDidMount() { + this._dispatcherRef = dis.register(this.onAction); + } + + componentWillUnmount() { + dis.unregister(this._dispatcherRef); + } + + onAction = (payload) => { + if (payload.action === "show_toast") { + this._addToast(payload.toast); + } + }; + + _addToast(toast) { + this.setState({toasts: this.state.toasts.concat(toast)}); + } + + dismissTopToast = () => { + const [, ...remaining] = this.state.toasts; + this.setState({toasts: remaining}); + }; + + render() { + const totalCount = this.state.toasts.length; + const isStacked = totalCount > 1; + let toast; + if (totalCount !== 0) { + const topToast = this.state.toasts[0]; + const {title, icon, key, component, props} = topToast; + const toastClasses = classNames("mx_Toast_toast", { + "mx_Toast_hasIcon": icon, + [`mx_Toast_icon_${icon}`]: icon, + }); + const countIndicator = isStacked ? _t(" (1/%(totalCount)s)", {totalCount}) : null; + + const toastProps = Object.assign({}, props, { + dismiss: this.dismissTopToast, + key, + }); + toast = (
+

{title}{countIndicator}

+
{React.createElement(component, toastProps)}
+
); + } + + const containerClasses = classNames("mx_ToastContainer", { + "mx_ToastContainer_stacked": isStacked, + }); + + return ( +
+ {toast} +
+ ); + } +} diff --git a/src/components/structures/auth/ForgotPassword.js b/src/components/structures/auth/ForgotPassword.js index 46a5fa7bd7..6f68293caa 100644 --- a/src/components/structures/auth/ForgotPassword.js +++ b/src/components/structures/auth/ForgotPassword.js @@ -105,7 +105,7 @@ module.exports = createReactClass({ phase: PHASE_SENDING_EMAIL, }); this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl); - this.reset.resetPassword(email, password).done(() => { + this.reset.resetPassword(email, password).then(() => { this.setState({ phase: PHASE_EMAIL_SENT, }); diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js index 0dd84d100d..b2e9d3e7cd 100644 --- a/src/components/structures/auth/Login.js +++ b/src/components/structures/auth/Login.js @@ -253,7 +253,7 @@ module.exports = createReactClass({ this.setState({ busy: false, }); - }).done(); + }); }, onUsernameChanged: function(username) { @@ -439,7 +439,7 @@ module.exports = createReactClass({ this.setState({ busy: false, }); - }).done(); + }); }, _isSupportedFlow: function(flow) { diff --git a/src/components/structures/auth/PostRegistration.js b/src/components/structures/auth/PostRegistration.js index 66075c80f7..760163585d 100644 --- a/src/components/structures/auth/PostRegistration.js +++ b/src/components/structures/auth/PostRegistration.js @@ -43,7 +43,7 @@ module.exports = createReactClass({ const cli = MatrixClientPeg.get(); this.setState({busy: true}); const self = this; - cli.getProfileInfo(cli.credentials.userId).done(function(result) { + cli.getProfileInfo(cli.credentials.userId).then(function(result) { self.setState({ avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(result.avatar_url), busy: false, diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 6321028457..3578d745f5 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -18,7 +18,6 @@ limitations under the License. */ import Matrix from 'matrix-js-sdk'; -import Promise from 'bluebird'; import React from 'react'; import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; @@ -371,7 +370,7 @@ module.exports = createReactClass({ if (pushers[i].kind === 'email') { const emailPusher = pushers[i]; emailPusher.data = { brand: this.props.brand }; - matrixClient.setPusher(emailPusher).done(() => { + matrixClient.setPusher(emailPusher).then(() => { console.log("Set email branding to " + this.props.brand); }, (error) => { console.error("Couldn't set email branding: " + error); diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index d19ce95b33..cc3f9f96c4 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -441,7 +441,7 @@ export const MsisdnAuthEntry = createReactClass({ this.props.fail(e); }).finally(() => { this.setState({requestingToken: false}); - }).done(); + }); }, /* diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js index fb056ee47f..97433e1f77 100644 --- a/src/components/views/context_menus/RoomTileContextMenu.js +++ b/src/components/views/context_menus/RoomTileContextMenu.js @@ -160,7 +160,7 @@ module.exports = createReactClass({ _onClickForget: function() { // FIXME: duplicated with RoomSettings (and dead code in RoomView) - MatrixClientPeg.get().forget(this.props.room.roomId).done(() => { + MatrixClientPeg.get().forget(this.props.room.roomId).then(() => { // Switch to another room view if we're currently viewing the // historical room if (RoomViewStore.getRoomId() === this.props.room.roomId) { @@ -190,7 +190,7 @@ module.exports = createReactClass({ this.setState({ roomNotifState: newState, }); - RoomNotifs.setRoomNotifsState(roomId, newState).done(() => { + RoomNotifs.setRoomNotifsState(roomId, newState).then(() => { // delay slightly so that the user can see their state change // before closing the menu return sleep(500).then(() => { diff --git a/src/components/views/context_menus/WidgetContextMenu.js b/src/components/views/context_menus/WidgetContextMenu.js new file mode 100644 index 0000000000..43e7e172cc --- /dev/null +++ b/src/components/views/context_menus/WidgetContextMenu.js @@ -0,0 +1,134 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import sdk from '../../../index'; +import {_t} from '../../../languageHandler'; + +export default class WidgetContextMenu extends React.Component { + static propTypes = { + onFinished: PropTypes.func, + + // Callback for when the revoke button is clicked. Required. + onRevokeClicked: PropTypes.func.isRequired, + + // Callback for when the snapshot button is clicked. Button not shown + // without a callback. + onSnapshotClicked: PropTypes.func, + + // Callback for when the reload button is clicked. Button not shown + // without a callback. + onReloadClicked: PropTypes.func, + + // Callback for when the edit button is clicked. Button not shown + // without a callback. + onEditClicked: PropTypes.func, + + // Callback for when the delete button is clicked. Button not shown + // without a callback. + onDeleteClicked: PropTypes.func, + }; + + proxyClick(fn) { + fn(); + if (this.props.onFinished) this.props.onFinished(); + } + + // XXX: It's annoying that our context menus require us to hit onFinished() to close :( + + onEditClicked = () => { + this.proxyClick(this.props.onEditClicked); + }; + + onReloadClicked = () => { + this.proxyClick(this.props.onReloadClicked); + }; + + onSnapshotClicked = () => { + this.proxyClick(this.props.onSnapshotClicked); + }; + + onDeleteClicked = () => { + this.proxyClick(this.props.onDeleteClicked); + }; + + onRevokeClicked = () => { + this.proxyClick(this.props.onRevokeClicked); + }; + + render() { + const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton"); + + const options = []; + + if (this.props.onEditClicked) { + options.push( + + {_t("Edit")} + , + ); + } + + if (this.props.onReloadClicked) { + options.push( + + {_t("Reload")} + , + ); + } + + if (this.props.onSnapshotClicked) { + options.push( + + {_t("Take picture")} + , + ); + } + + if (this.props.onDeleteClicked) { + options.push( + + {_t("Remove for everyone")} + , + ); + } + + // Push this last so it appears last. It's always present. + options.push( + + {_t("Remove for me")} + , + ); + + // Put separators between the options + if (options.length > 1) { + const length = options.length; + for (let i = 0; i < length - 1; i++) { + const sep =
; + + // Insert backwards so the insertions don't affect our math on where to place them. + // We also use our cached length to avoid worrying about options.length changing + options.splice(length - 1 - i, 0, sep); + } + } + + return
{options}
; + } +} diff --git a/src/components/views/dialogs/AddressPickerDialog.js b/src/components/views/dialogs/AddressPickerDialog.js index 24d8b96e0c..a40495893d 100644 --- a/src/components/views/dialogs/AddressPickerDialog.js +++ b/src/components/views/dialogs/AddressPickerDialog.js @@ -266,7 +266,7 @@ module.exports = createReactClass({ this.setState({ searchError: err.errcode ? err.message : _t('Something went wrong!'), }); - }).done(() => { + }).then(() => { this.setState({ busy: false, }); @@ -379,7 +379,7 @@ module.exports = createReactClass({ // Do a local search immediately this._doLocalSearch(query); } - }).done(() => { + }).then(() => { this.setState({ busy: false, }); diff --git a/src/components/views/dialogs/CreateGroupDialog.js b/src/components/views/dialogs/CreateGroupDialog.js index 11f4c21366..3430a12e71 100644 --- a/src/components/views/dialogs/CreateGroupDialog.js +++ b/src/components/views/dialogs/CreateGroupDialog.js @@ -93,7 +93,7 @@ export default createReactClass({ this.setState({createError: e}); }).finally(() => { this.setState({creating: false}); - }).done(); + }); }, _onCancel: function() { diff --git a/src/components/views/dialogs/IntegrationsDisabledDialog.js b/src/components/views/dialogs/IntegrationsDisabledDialog.js new file mode 100644 index 0000000000..3ab1123f8b --- /dev/null +++ b/src/components/views/dialogs/IntegrationsDisabledDialog.js @@ -0,0 +1,57 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import {_t} from "../../../languageHandler"; +import sdk from "../../../index"; +import dis from '../../../dispatcher'; + +export default class IntegrationsDisabledDialog extends React.Component { + static propTypes = { + onFinished: PropTypes.func.isRequired, + }; + + _onAcknowledgeClick = () => { + this.props.onFinished(); + }; + + _onOpenSettingsClick = () => { + this.props.onFinished(); + dis.dispatch({action: "view_user_settings"}); + }; + + render() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + + return ( + +
+

{_t("Enable 'Manage Integrations' in Settings to do this.")}

+
+ +
+ ); + } +} diff --git a/src/components/views/dialogs/IntegrationsImpossibleDialog.js b/src/components/views/dialogs/IntegrationsImpossibleDialog.js new file mode 100644 index 0000000000..9927f627f1 --- /dev/null +++ b/src/components/views/dialogs/IntegrationsImpossibleDialog.js @@ -0,0 +1,55 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import {_t} from "../../../languageHandler"; +import sdk from "../../../index"; + +export default class IntegrationsImpossibleDialog extends React.Component { + static propTypes = { + onFinished: PropTypes.func.isRequired, + }; + + _onAcknowledgeClick = () => { + this.props.onFinished(); + }; + + render() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + + return ( + +
+

+ {_t( + "Your Riot doesn't allow you to use an Integration Manager to do this. " + + "Please contact an admin.", + )} +

+
+ +
+ ); + } +} diff --git a/src/components/views/dialogs/KeyShareDialog.js b/src/components/views/dialogs/KeyShareDialog.js index a10c25a0fb..01e3479bb1 100644 --- a/src/components/views/dialogs/KeyShareDialog.js +++ b/src/components/views/dialogs/KeyShareDialog.js @@ -78,7 +78,7 @@ export default createReactClass({ true, ); } - }).done(); + }); }, componentWillUnmount: function() { diff --git a/src/components/views/dialogs/SetEmailDialog.js b/src/components/views/dialogs/SetEmailDialog.js index bedf713c4e..b527abffc9 100644 --- a/src/components/views/dialogs/SetEmailDialog.js +++ b/src/components/views/dialogs/SetEmailDialog.js @@ -62,7 +62,7 @@ export default createReactClass({ return; } this._addThreepid = new AddThreepid(); - this._addThreepid.addEmailAddress(emailAddress).done(() => { + this._addThreepid.addEmailAddress(emailAddress).then(() => { Modal.createTrackedDialog('Verification Pending', '', QuestionDialog, { title: _t("Verification Pending"), description: _t( @@ -96,7 +96,7 @@ export default createReactClass({ }, verifyEmailAddress: function() { - this._addThreepid.checkEmailLinkClicked().done(() => { + this._addThreepid.checkEmailLinkClicked().then(() => { this.props.onFinished(true); }, (err) => { this.setState({emailBusy: false}); diff --git a/src/components/views/dialogs/SetMxIdDialog.js b/src/components/views/dialogs/SetMxIdDialog.js index 3bc6f5597e..598d0ce354 100644 --- a/src/components/views/dialogs/SetMxIdDialog.js +++ b/src/components/views/dialogs/SetMxIdDialog.js @@ -15,7 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Promise from 'bluebird'; import React from 'react'; import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; diff --git a/src/components/views/dialogs/TabbedIntegrationManagerDialog.js b/src/components/views/dialogs/TabbedIntegrationManagerDialog.js index 5ef7aef9ab..e86a46fb36 100644 --- a/src/components/views/dialogs/TabbedIntegrationManagerDialog.js +++ b/src/components/views/dialogs/TabbedIntegrationManagerDialog.js @@ -82,10 +82,10 @@ export default class TabbedIntegrationManagerDialog extends React.Component { client.setTermsInteractionCallback((policyInfo, agreedUrls) => { // To avoid visual glitching of two modals stacking briefly, we customise the - // terms dialog sizing when it will appear for the integrations manager so that + // terms dialog sizing when it will appear for the integration manager so that // it gets the same basic size as the IM's own modal. return dialogTermsInteractionCallback( - policyInfo, agreedUrls, 'mx_TermsDialog_forIntegrationsManager', + policyInfo, agreedUrls, 'mx_TermsDialog_forIntegrationManager', ); }); @@ -139,7 +139,7 @@ export default class TabbedIntegrationManagerDialog extends React.Component { } _renderTab() { - const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); + const IntegrationManager = sdk.getComponent("views.settings.IntegrationManager"); let uiUrl = null; if (this.state.currentScalarClient) { uiUrl = this.state.currentScalarClient.getScalarInterfaceUrlForRoom( @@ -148,7 +148,7 @@ export default class TabbedIntegrationManagerDialog extends React.Component { this.props.integrationId, ); } - return {_t("Identity Server")}
({host})
; case Matrix.SERVICE_TYPES.IM: - return
{_t("Integrations Manager")}
({host})
; + return
{_t("Integration Manager")}
({host})
; } } diff --git a/src/components/views/elements/AppPermission.js b/src/components/views/elements/AppPermission.js index 422427d4c4..8dc58643bd 100644 --- a/src/components/views/elements/AppPermission.js +++ b/src/components/views/elements/AppPermission.js @@ -30,6 +30,7 @@ export default class AppPermission extends React.Component { creatorUserId: PropTypes.string.isRequired, roomId: PropTypes.string.isRequired, onPermissionGranted: PropTypes.func.isRequired, + isRoomEncrypted: PropTypes.bool, }; static defaultProps = { @@ -114,6 +115,8 @@ export default class AppPermission extends React.Component { : _t("Using this widget may share data with %(widgetDomain)s.", {widgetDomain: this.state.widgetDomain}, {helpIcon: () => warningTooltip}); + const encryptionWarning = this.props.isRoomEncrypted ? _t("Widgets do not use message encryption.") : null; + return (
@@ -128,7 +131,7 @@ export default class AppPermission extends React.Component { {warning}
- {_t("This widget may use cookies.")} + {_t("This widget may use cookies.")} {encryptionWarning}
diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index e9134c6175..4cfce0c5dd 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -35,6 +35,7 @@ import ActiveWidgetStore from '../../../stores/ActiveWidgetStore'; import classNames from 'classnames'; import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; +import {createMenu} from "../../structures/ContextualMenu"; const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:']; const ENABLE_REACT_PERF = false; @@ -52,7 +53,7 @@ export default class AppTile extends React.Component { this._onLoaded = this._onLoaded.bind(this); this._onEditClick = this._onEditClick.bind(this); this._onDeleteClick = this._onDeleteClick.bind(this); - this._onCancelClick = this._onCancelClick.bind(this); + this._onRevokeClicked = this._onRevokeClicked.bind(this); this._onSnapshotClick = this._onSnapshotClick.bind(this); this.onClickMenuBar = this.onClickMenuBar.bind(this); this._onMinimiseClick = this._onMinimiseClick.bind(this); @@ -207,7 +208,7 @@ export default class AppTile extends React.Component { if (!this._scalarClient) { this._scalarClient = defaultManager.getScalarClient(); } - this._scalarClient.getScalarToken().done((token) => { + this._scalarClient.getScalarToken().then((token) => { // Append scalar_token as a query param if not already present this._scalarClient.scalarToken = token; const u = url.parse(this._addWurlParams(this.props.url)); @@ -271,7 +272,7 @@ export default class AppTile extends React.Component { return WidgetUtils.canUserModifyWidgets(this.props.room.roomId); } - _onEditClick(e) { + _onEditClick() { console.log("Edit widget ID ", this.props.id); if (this.props.onEditClick) { this.props.onEditClick(); @@ -293,7 +294,7 @@ export default class AppTile extends React.Component { } } - _onSnapshotClick(e) { + _onSnapshotClick() { console.warn("Requesting widget snapshot"); ActiveWidgetStore.getWidgetMessaging(this.props.id).getScreenshot() .catch((err) => { @@ -360,13 +361,9 @@ export default class AppTile extends React.Component { } } - _onCancelClick() { - if (this.props.onDeleteClick) { - this.props.onDeleteClick(); - } else { - console.info("Revoke widget permissions - %s", this.props.id); - this._revokeWidgetPermission(); - } + _onRevokeClicked() { + console.info("Revoke widget permissions - %s", this.props.id); + this._revokeWidgetPermission(); } /** @@ -544,18 +541,59 @@ export default class AppTile extends React.Component { } } - _onPopoutWidgetClick(e) { + _onPopoutWidgetClick() { // Using Object.assign workaround as the following opens in a new window instead of a new tab. // window.open(this._getSafeUrl(), '_blank', 'noopener=yes'); Object.assign(document.createElement('a'), { target: '_blank', href: this._getSafeUrl(), rel: 'noopener'}).click(); } - _onReloadWidgetClick(e) { + _onReloadWidgetClick() { // Reload iframe in this way to avoid cross-origin restrictions this.refs.appFrame.src = this.refs.appFrame.src; } + _getMenuOptions(ev) { + // TODO: This block of code gets copy/pasted a lot. We should make that happen less. + const menuOptions = {}; + const buttonRect = ev.target.getBoundingClientRect(); + // The window X and Y offsets are to adjust position when zoomed in to page + const buttonLeft = buttonRect.left + window.pageXOffset; + const buttonTop = buttonRect.top + window.pageYOffset; + // Align the right edge of the menu to the left edge of the button + menuOptions.right = window.innerWidth - buttonLeft; + // Align the menu vertically on whichever side of the button has more + // space available. + if (buttonTop < window.innerHeight / 2) { + menuOptions.top = buttonTop; + } else { + menuOptions.bottom = window.innerHeight - buttonTop; + } + return menuOptions; + } + + _onContextMenuClick = (ev) => { + const WidgetContextMenu = sdk.getComponent('views.context_menus.WidgetContextMenu'); + const menuOptions = { + ...this._getMenuOptions(ev), + + // A revoke handler is always required + onRevokeClicked: this._onRevokeClicked, + }; + + const canUserModify = this._canUserModify(); + const showEditButton = Boolean(this._scalarClient && canUserModify); + const showDeleteButton = (this.props.showDelete === undefined || this.props.showDelete) && canUserModify; + const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show; + + if (showEditButton) menuOptions.onEditClicked = this._onEditClick; + if (showDeleteButton) menuOptions.onDeleteClicked = this._onDeleteClick; + if (showPictureSnapshotButton) menuOptions.onSnapshotClicked = this._onSnapshotClick; + if (this.props.showReload) menuOptions.onReloadClicked = this._onReloadWidgetClick; + + createMenu(WidgetContextMenu, menuOptions); + }; + render() { let appTileBody; @@ -565,7 +603,7 @@ export default class AppTile extends React.Component { } // Note that there is advice saying allow-scripts shouldn't be used with allow-same-origin - // because that would allow the iframe to prgramatically remove the sandbox attribute, but + // because that would allow the iframe to programmatically remove the sandbox attribute, but // this would only be for content hosted on the same origin as the riot client: anything // hosted on the same origin as the client will get the same access as if you clicked // a link to it. @@ -585,12 +623,14 @@ export default class AppTile extends React.Component {
); if (!this.state.hasPermissionToLoad) { + const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId); appTileBody = (
@@ -643,13 +683,6 @@ export default class AppTile extends React.Component { } } - // editing is done in scalar - const canUserModify = this._canUserModify(); - const showEditButton = Boolean(this._scalarClient && canUserModify); - const showDeleteButton = (this.props.showDelete === undefined || this.props.showDelete) && canUserModify; - const showCancelButton = (this.props.showCancel === undefined || this.props.showCancel) && !showDeleteButton; - // Picture snapshot - only show button when apps are maximised. - const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show; const showMinimiseButton = this.props.showMinimise && this.props.show; const showMaximiseButton = this.props.showMinimise && !this.props.show; @@ -688,41 +721,17 @@ export default class AppTile extends React.Component { { this.props.showTitle && this._getTileTitle() } - { /* Reload widget */ } - { this.props.showReload && } { /* Popout widget */ } { this.props.showPopout && } - { /* Snapshot widget */ } - { showPictureSnapshotButton && } - { /* Edit widget */ } - { showEditButton && } - { /* Delete widget */ } - { showDeleteButton && } - { /* Cancel widget */ } - { showCancelButton && }
} diff --git a/src/components/views/elements/EditableTextContainer.js b/src/components/views/elements/EditableTextContainer.js index 3bf37df951..5cba98470c 100644 --- a/src/components/views/elements/EditableTextContainer.js +++ b/src/components/views/elements/EditableTextContainer.js @@ -17,7 +17,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import sdk from '../../../index'; -import Promise from 'bluebird'; /** * A component which wraps an EditableText, with a spinner while updates take @@ -51,7 +50,7 @@ export default class EditableTextContainer extends React.Component { this.setState({busy: true}); - this.props.getInitialValue().done( + this.props.getInitialValue().then( (result) => { if (this._unmounted) { return; } this.setState({ @@ -83,7 +82,7 @@ export default class EditableTextContainer extends React.Component { errorString: null, }); - this.props.onSubmit(value).done( + this.props.onSubmit(value).then( () => { if (this._unmounted) { return; } this.setState({ diff --git a/src/components/views/elements/ErrorBoundary.js b/src/components/views/elements/ErrorBoundary.js index e53e1ec0fa..e36464c4ef 100644 --- a/src/components/views/elements/ErrorBoundary.js +++ b/src/components/views/elements/ErrorBoundary.js @@ -54,7 +54,7 @@ export default class ErrorBoundary extends React.PureComponent { if (!PlatformPeg.get()) return; MatrixClientPeg.get().stopClient(); - MatrixClientPeg.get().store.deleteAllData().done(() => { + MatrixClientPeg.get().store.deleteAllData().then(() => { PlatformPeg.get().reload(); }); }; diff --git a/src/components/views/elements/FormButton.js b/src/components/views/elements/FormButton.js new file mode 100644 index 0000000000..f6b4c986f5 --- /dev/null +++ b/src/components/views/elements/FormButton.js @@ -0,0 +1,28 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import AccessibleButton from "./AccessibleButton"; + +export default function FormButton(props) { + const {className, label, kind, ...restProps} = props; + const newClassName = (className || "") + " mx_FormButton"; + const allProps = Object.assign({}, restProps, + {className: newClassName, kind: kind || "primary", children: [label]}); + return React.createElement(AccessibleButton, allProps); +} + +FormButton.propTypes = AccessibleButton.propTypes; diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index 2772363bd0..b2f6d0abbb 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -84,7 +84,7 @@ export default class ImageView extends React.Component { title: _t('Error'), description: _t('You cannot delete this image. (%(code)s)', {code: code}), }); - }).done(); + }); }, }); }; diff --git a/src/components/views/elements/LanguageDropdown.js b/src/components/views/elements/LanguageDropdown.js index 365f9ded61..451c97d958 100644 --- a/src/components/views/elements/LanguageDropdown.js +++ b/src/components/views/elements/LanguageDropdown.js @@ -49,7 +49,7 @@ export default class LanguageDropdown extends React.Component { this.setState({langs}); }).catch(() => { this.setState({langs: ['en']}); - }).done(); + }); if (!this.props.value) { // If no value is given, we start with the first diff --git a/src/components/views/elements/Tooltip.js b/src/components/views/elements/Tooltip.js index bb5f9f0604..8ff3ce9bdb 100644 --- a/src/components/views/elements/Tooltip.js +++ b/src/components/views/elements/Tooltip.js @@ -100,7 +100,9 @@ module.exports = createReactClass({ const parent = ReactDOM.findDOMNode(this).parentNode; let style = {}; style = this._updatePosition(style); - style.display = "block"; + // Hide the entire container when not visible. This prevents flashing of the tooltip + // if it is not meant to be visible on first mount. + style.display = this.props.visible ? "block" : "none"; const tooltipClasses = classNames("mx_Tooltip", this.props.tooltipClassName, { "mx_Tooltip_visible": this.props.visible, diff --git a/src/components/views/groups/GroupUserSettings.js b/src/components/views/groups/GroupUserSettings.js index 7d80bdd209..3cd5731b99 100644 --- a/src/components/views/groups/GroupUserSettings.js +++ b/src/components/views/groups/GroupUserSettings.js @@ -36,7 +36,7 @@ export default createReactClass({ }, componentWillMount: function() { - this.context.matrixClient.getJoinedGroups().done((result) => { + this.context.matrixClient.getJoinedGroups().then((result) => { this.setState({groups: result.groups || [], error: null}); }, (err) => { console.error(err); diff --git a/src/components/views/messages/MAudioBody.js b/src/components/views/messages/MAudioBody.js index b4f26d0cbd..0246d28542 100644 --- a/src/components/views/messages/MAudioBody.js +++ b/src/components/views/messages/MAudioBody.js @@ -55,7 +55,7 @@ export default class MAudioBody extends React.Component { decryptFile(content.file).then(function(blob) { decryptedBlob = blob; return URL.createObjectURL(decryptedBlob); - }).done((url) => { + }).then((url) => { this.setState({ decryptedUrl: url, decryptedBlob: decryptedBlob, diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index 640baa1966..b12957a7df 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -24,7 +24,6 @@ import MFileBody from './MFileBody'; import Modal from '../../../Modal'; import sdk from '../../../index'; import { decryptFile } from '../../../utils/DecryptFile'; -import Promise from 'bluebird'; import { _t } from '../../../languageHandler'; import SettingsStore from "../../../settings/SettingsStore"; @@ -289,7 +288,7 @@ export default class MImageBody extends React.Component { this.setState({ error: err, }); - }).done(); + }); } // Remember that the user wanted to show this particular image diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.js index 21d82309ed..b2a1724fc6 100644 --- a/src/components/views/messages/MKeyVerificationRequest.js +++ b/src/components/views/messages/MKeyVerificationRequest.js @@ -111,10 +111,10 @@ export default class MKeyVerificationRequest extends React.Component { userLabelForEventRoom(fromUserId, mxEvent)}
); const isResolved = !(this.state.accepted || this.state.cancelled || this.state.done); if (isResolved) { - const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); + const FormButton = sdk.getComponent("elements.FormButton"); stateNode = (
- {_t("Decline")} - {_t("Accept")} + +
); } } else if (isOwn) { // request sent by us diff --git a/src/components/views/messages/MVideoBody.js b/src/components/views/messages/MVideoBody.js index d277b6eae9..44954344ff 100644 --- a/src/components/views/messages/MVideoBody.js +++ b/src/components/views/messages/MVideoBody.js @@ -20,7 +20,6 @@ import createReactClass from 'create-react-class'; import MFileBody from './MFileBody'; import MatrixClientPeg from '../../../MatrixClientPeg'; import { decryptFile } from '../../../utils/DecryptFile'; -import Promise from 'bluebird'; import { _t } from '../../../languageHandler'; import SettingsStore from "../../../settings/SettingsStore"; @@ -89,7 +88,7 @@ module.exports = createReactClass({ const content = this.props.mxEvent.getContent(); if (content.file !== undefined && this.state.decryptedUrl === null) { let thumbnailPromise = Promise.resolve(null); - if (content.info.thumbnail_file) { + if (content.info && content.info.thumbnail_file) { thumbnailPromise = decryptFile( content.info.thumbnail_file, ).then(function(blob) { @@ -115,7 +114,7 @@ module.exports = createReactClass({ this.setState({ error: err, }); - }).done(); + }); } }, diff --git a/src/components/views/messages/ReactionsRowButtonTooltip.js b/src/components/views/messages/ReactionsRowButtonTooltip.js index b70724d516..d7e1ef3488 100644 --- a/src/components/views/messages/ReactionsRowButtonTooltip.js +++ b/src/components/views/messages/ReactionsRowButtonTooltip.js @@ -43,7 +43,8 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent { if (room) { const senders = []; for (const reactionEvent of reactionEvents) { - const { name } = room.getMember(reactionEvent.getSender()); + const member = room.getMember(reactionEvent.getSender()); + const name = member ? member.name : reactionEvent.getSender(); senders.push(name); } const shortName = unicodeToShortcode(content); diff --git a/src/components/views/room_settings/ColorSettings.js b/src/components/views/room_settings/ColorSettings.js index aab6c04f53..952c49828b 100644 --- a/src/components/views/room_settings/ColorSettings.js +++ b/src/components/views/room_settings/ColorSettings.js @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Promise from 'bluebird'; import React from 'react'; import PropTypes from 'prop-types'; import createReactClass from 'create-react-class'; diff --git a/src/components/views/rooms/Autocomplete.js b/src/components/views/rooms/Autocomplete.js index d4b51081f4..76a3a19e00 100644 --- a/src/components/views/rooms/Autocomplete.js +++ b/src/components/views/rooms/Autocomplete.js @@ -21,7 +21,6 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import flatMap from 'lodash/flatMap'; import type {Completion} from '../../../autocomplete/Autocompleter'; -import Promise from 'bluebird'; import { Room } from 'matrix-js-sdk'; import SettingsStore from "../../../settings/SettingsStore"; diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index d93fe76b46..3826c410bf 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -53,7 +53,7 @@ module.exports = createReactClass({ ); }, (error)=>{ console.error("Failed to get URL preview: " + error); - }).done(); + }); }, componentDidMount: function() { diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index f2693d08b7..1a2c8e2212 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -248,7 +248,7 @@ module.exports = createReactClass({ return client.getStoredDevicesForUser(member.userId); }).finally(function() { self._cancelDeviceList = null; - }).done(function(devices) { + }).then(function(devices) { if (cancelled) { // we got cancelled - presumably a different user now return; @@ -581,7 +581,7 @@ module.exports = createReactClass({ }, ).finally(()=>{ this.setState({ updating: this.state.updating - 1 }); - }).done(); + }); }, onPowerChange: async function(powerLevel) { @@ -638,7 +638,7 @@ module.exports = createReactClass({ this.setState({ updating: this.state.updating + 1 }); createRoom({dmUserId: this.props.member.userId}).finally(() => { this.setState({ updating: this.state.updating - 1 }); - }).done(); + }); }, onLeaveClick: function() { diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 632ca53f82..128f9be964 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -25,7 +25,6 @@ import RoomViewStore from '../../../stores/RoomViewStore'; import Stickerpicker from './Stickerpicker'; import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks'; import ContentMessages from '../../../ContentMessages'; -import classNames from 'classnames'; import E2EIcon from './E2EIcon'; function ComposerAvatar(props) { @@ -353,13 +352,9 @@ export default class MessageComposer extends React.Component { ); } - const wrapperClasses = classNames({ - mx_MessageComposer_wrapper: true, - mx_MessageComposer_hasE2EIcon: !!this.props.e2eStatus, - }); return (
-
+
{ controls }
diff --git a/src/components/views/rooms/SlateMessageComposer.js b/src/components/views/rooms/SlateMessageComposer.js index 4bb2f29e61..eb41f6729b 100644 --- a/src/components/views/rooms/SlateMessageComposer.js +++ b/src/components/views/rooms/SlateMessageComposer.js @@ -460,13 +460,9 @@ export default class SlateMessageComposer extends React.Component { const showFormatBar = this.state.showFormatting && this.state.inputState.isRichTextEnabled; - const wrapperClasses = classNames({ - mx_MessageComposer_wrapper: true, - mx_MessageComposer_hasE2EIcon: !!this.props.e2eStatus, - }); return (
-
+
{ controls }
diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index 28e51ed12e..7eabf27528 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -74,10 +74,10 @@ export default class Stickerpicker extends React.Component { this.forceUpdate(); return this.scalarClient; }).catch((e) => { - this._imError(_td("Failed to connect to integrations server"), e); + this._imError(_td("Failed to connect to integration manager"), e); }); } else { - this._imError(_td("No integrations server is configured to manage stickers with")); + IntegrationManagers.sharedInstance().openNoManagerDialog(); } } @@ -287,12 +287,17 @@ export default class Stickerpicker extends React.Component { return stickersContent; } - /** + // Dev note: this isn't jsdoc because it's angry. + /* * Show the sticker picker overlay * If no stickerpacks have been added, show a link to the integration manager add sticker packs page. - * @param {Event} e Event that triggered the function */ _onShowStickersClick(e) { + if (!SettingsStore.getValue("integrationProvisioning")) { + // Intercept this case and spawn a warning. + return IntegrationManagers.sharedInstance().showDisabledDialog(); + } + // XXX: Simplify by using a context menu that is positioned relative to the sticker picker button const buttonRect = e.target.getBoundingClientRect(); @@ -346,7 +351,7 @@ export default class Stickerpicker extends React.Component { } /** - * Launch the integrations manager on the stickers integration page + * Launch the integration manager on the stickers integration page */ _launchManageIntegrations() { // TODO: Open the right integration manager for the widget diff --git a/src/components/views/settings/ChangeAvatar.js b/src/components/views/settings/ChangeAvatar.js index 32521006c7..904b17b15f 100644 --- a/src/components/views/settings/ChangeAvatar.js +++ b/src/components/views/settings/ChangeAvatar.js @@ -112,7 +112,7 @@ module.exports = createReactClass({ } }); - httpPromise.done(function() { + httpPromise.then(function() { self.setState({ phase: self.Phases.Display, avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(newUrl), diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index 91292b19f9..a317c46cec 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -25,7 +25,6 @@ const Modal = require("../../../Modal"); const sdk = require("../../../index"); import dis from "../../../dispatcher"; -import Promise from 'bluebird'; import AccessibleButton from '../elements/AccessibleButton'; import { _t } from '../../../languageHandler'; @@ -174,7 +173,7 @@ module.exports = createReactClass({ newPassword: "", newPasswordConfirm: "", }); - }).done(); + }); }, _optionallySetEmail: function() { diff --git a/src/components/views/settings/DevicesPanel.js b/src/components/views/settings/DevicesPanel.js index 30f507ea18..cb5db10be4 100644 --- a/src/components/views/settings/DevicesPanel.js +++ b/src/components/views/settings/DevicesPanel.js @@ -52,7 +52,7 @@ export default class DevicesPanel extends React.Component { } _loadDevices() { - MatrixClientPeg.get().getDevices().done( + MatrixClientPeg.get().getDevices().then( (resp) => { if (this._unmounted) { return; } this.setState({devices: resp.devices || []}); diff --git a/src/components/views/settings/IntegrationsManager.js b/src/components/views/settings/IntegrationManager.js similarity index 71% rename from src/components/views/settings/IntegrationsManager.js rename to src/components/views/settings/IntegrationManager.js index d463b043d5..1ab17ca8a0 100644 --- a/src/components/views/settings/IntegrationsManager.js +++ b/src/components/views/settings/IntegrationManager.js @@ -21,12 +21,9 @@ import sdk from '../../../index'; import { _t } from '../../../languageHandler'; import dis from '../../../dispatcher'; -export default class IntegrationsManager extends React.Component { +export default class IntegrationManager extends React.Component { static propTypes = { - // false to display an error saying that there is no integrations manager configured - configured: PropTypes.bool.isRequired, - - // false to display an error saying that we couldn't connect to the integrations manager + // false to display an error saying that we couldn't connect to the integration manager connected: PropTypes.bool.isRequired, // true to display a loading spinner @@ -40,7 +37,6 @@ export default class IntegrationsManager extends React.Component { }; static defaultProps = { - configured: true, connected: true, loading: false, }; @@ -70,20 +66,11 @@ export default class IntegrationsManager extends React.Component { }; render() { - if (!this.props.configured) { - return ( -
-

{_t("No integrations server configured")}

-

{_t("This Riot instance does not have an integrations server configured.")}

-
- ); - } - if (this.props.loading) { const Spinner = sdk.getComponent("elements.Spinner"); return ( -
-

{_t("Connecting to integrations server...")}

+
+

{_t("Connecting to integration manager...")}

); @@ -91,9 +78,9 @@ export default class IntegrationsManager extends React.Component { if (!this.props.connected) { return ( -
-

{_t("Cannot connect to integrations server")}

-

{_t("The integrations server is offline or it cannot reach your homeserver.")}

+
+

{_t("Cannot connect to integration manager")}

+

{_t("The integration manager is offline or it cannot reach your homeserver.")}

); } diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js index e3b4cfe122..6c71101eb8 100644 --- a/src/components/views/settings/Notifications.js +++ b/src/components/views/settings/Notifications.js @@ -16,7 +16,6 @@ limitations under the License. import React from 'react'; import createReactClass from 'create-react-class'; -import Promise from 'bluebird'; import sdk from '../../../index'; import { _t } from '../../../languageHandler'; import MatrixClientPeg from '../../../MatrixClientPeg'; @@ -97,7 +96,7 @@ module.exports = createReactClass({ phase: this.phases.LOADING, }); - MatrixClientPeg.get().setPushRuleEnabled('global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked).done(function() { + MatrixClientPeg.get().setPushRuleEnabled('global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked).then(function() { self._refreshFromServer(); }); }, @@ -170,7 +169,7 @@ module.exports = createReactClass({ emailPusher.kind = null; emailPusherPromise = MatrixClientPeg.get().setPusher(emailPusher); } - emailPusherPromise.done(() => { + emailPusherPromise.then(() => { this._refreshFromServer(); }, (error) => { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); @@ -274,7 +273,7 @@ module.exports = createReactClass({ } } - Promise.all(deferreds).done(function() { + Promise.all(deferreds).then(function() { self._refreshFromServer(); }, function(error) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); @@ -343,7 +342,7 @@ module.exports = createReactClass({ } } - Promise.all(deferreds).done(function(resps) { + Promise.all(deferreds).then(function(resps) { self._refreshFromServer(); }, function(error) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); @@ -398,7 +397,7 @@ module.exports = createReactClass({ }; // Then, add the new ones - Promise.all(removeDeferreds).done(function(resps) { + Promise.all(removeDeferreds).then(function(resps) { const deferreds = []; let pushRuleVectorStateKind = self.state.vectorContentRules.vectorState; @@ -434,7 +433,7 @@ module.exports = createReactClass({ } } - Promise.all(deferreds).done(function(resps) { + Promise.all(deferreds).then(function(resps) { self._refreshFromServer(); }, onError); }, onError); @@ -650,7 +649,7 @@ module.exports = createReactClass({ externalContentRules: self.state.externalContentRules, externalPushRules: self.state.externalPushRules, }); - }).done(); + }); MatrixClientPeg.get().getThreePids().then((r) => this.setState({threepids: r.threepids})); }, diff --git a/src/components/views/settings/SetIntegrationManager.js b/src/components/views/settings/SetIntegrationManager.js index b1268c8048..e205f02e6c 100644 --- a/src/components/views/settings/SetIntegrationManager.js +++ b/src/components/views/settings/SetIntegrationManager.js @@ -16,13 +16,9 @@ limitations under the License. import React from 'react'; import {_t} from "../../../languageHandler"; -import sdk from '../../../index'; -import Field from "../elements/Field"; import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; -import MatrixClientPeg from "../../../MatrixClientPeg"; -import {SERVICE_TYPES} from "matrix-js-sdk"; -import {IntegrationManagerInstance} from "../../../integrations/IntegrationManagerInstance"; -import Modal from "../../../Modal"; +import sdk from '../../../index'; +import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; export default class SetIntegrationManager extends React.Component { constructor() { @@ -32,135 +28,23 @@ export default class SetIntegrationManager extends React.Component { this.state = { currentManager, - url: "", // user-entered text - error: null, - busy: false, - checking: false, + provisioningEnabled: SettingsStore.getValue("integrationProvisioning"), }; } - _onUrlChanged = (ev) => { - const u = ev.target.value; - this.setState({url: u}); - }; + onProvisioningToggled = () => { + const current = this.state.provisioningEnabled; + SettingsStore.setValue("integrationProvisioning", null, SettingLevel.ACCOUNT, !current).catch(err => { + console.error("Error changing integration manager provisioning"); + console.error(err); - _getTooltip = () => { - if (this.state.checking) { - const InlineSpinner = sdk.getComponent('views.elements.InlineSpinner'); - return
- - { _t("Checking server") } -
; - } else if (this.state.error) { - return {this.state.error}; - } else { - return null; - } - }; - - _canChange = () => { - return !!this.state.url && !this.state.busy; - }; - - _continueTerms = async (manager) => { - try { - await IntegrationManagers.sharedInstance().overwriteManagerOnAccount(manager); - this.setState({ - busy: false, - error: null, - currentManager: IntegrationManagers.sharedInstance().getPrimaryManager(), - url: "", // clear input - }); - } catch (e) { - console.error(e); - this.setState({ - busy: false, - error: _t("Failed to update integration manager"), - }); - } - }; - - _setManager = async (ev) => { - // Don't reload the page when the user hits enter in the form. - ev.preventDefault(); - ev.stopPropagation(); - - this.setState({busy: true, checking: true, error: null}); - - let offline = false; - let manager: IntegrationManagerInstance; - try { - manager = await IntegrationManagers.sharedInstance().tryDiscoverManager(this.state.url); - offline = !manager; // no manager implies offline - } catch (e) { - console.error(e); - offline = true; // probably a connection error - } - if (offline) { - this.setState({ - busy: false, - checking: false, - error: _t("Integration manager offline or not accessible."), - }); - return; - } - - // Test the manager (causes terms of service prompt if agreement is needed) - // We also cancel the tooltip at this point so it doesn't collide with the dialog. - this.setState({checking: false}); - try { - const client = manager.getScalarClient(); - await client.connect(); - } catch (e) { - console.error(e); - this.setState({ - busy: false, - error: _t("Terms of service not accepted or the integration manager is invalid."), - }); - return; - } - - // Specifically request the terms of service to see if there are any. - // The above won't trigger a terms of service check if there are no terms to - // sign, so when there's no terms at all we need to ensure we tell the user. - let hasTerms = true; - try { - const terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IM, manager.trimmedApiUrl); - hasTerms = terms && terms['policies'] && Object.keys(terms['policies']).length > 0; - } catch (e) { - // Assume errors mean there are no terms. This could be a 404, 500, etc - console.error(e); - hasTerms = false; - } - if (!hasTerms) { - this.setState({busy: false}); - const QuestionDialog = sdk.getComponent("views.dialogs.QuestionDialog"); - Modal.createTrackedDialog('No Terms Warning', '', QuestionDialog, { - title: _t("Integration manager has no terms of service"), - description: ( -
- - {_t("The integration manager you have chosen does not have any terms of service.")} - - -  {_t("Only continue if you trust the owner of the server.")} - -
- ), - button: _t("Continue"), - onFinished: async (confirmed) => { - if (!confirmed) return; - this._continueTerms(manager); - }, - }); - return; - } - - this._continueTerms(manager); + this.setState({provisioningEnabled: current}); + }); + this.setState({provisioningEnabled: !current}); }; render() { - const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton'); + const ToggleSwitch = sdk.getComponent("views.elements.ToggleSwitch"); const currentManager = this.state.currentManager; let managerName; @@ -168,45 +52,32 @@ export default class SetIntegrationManager extends React.Component { if (currentManager) { managerName = `(${currentManager.name})`; bodyText = _t( - "You are currently using %(serverName)s to manage your bots, widgets, " + + "Use an Integration Manager (%(serverName)s) to manage bots, widgets, " + "and sticker packs.", {serverName: currentManager.name}, { b: sub => {sub} }, ); } else { - bodyText = _t( - "Add which integration manager you want to manage your bots, widgets, " + - "and sticker packs.", - ); + bodyText = _t("Use an Integration Manager to manage bots, widgets, and sticker packs."); } return ( -
+
- {_t("Integration Manager")} + {_t("Manage integrations")} {managerName} +
{bodyText} +
+
+ {_t( + "Integration Managers receive configuration data, and can modify widgets, " + + "send room invites, and set power levels on your behalf.", + )}
- - {_t("Change")} - +
); } } diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js index fbad327078..875f0bfc10 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js @@ -75,7 +75,7 @@ export default class HelpUserSettingsTab extends React.Component { // stopping in the middle of the logs. console.log("Clear cache & reload clicked"); MatrixClientPeg.get().stopClient(); - MatrixClientPeg.get().store.deleteAllData().done(() => { + MatrixClientPeg.get().store.deleteAllData().then(() => { PlatformPeg.get().reload(); }); }; diff --git a/src/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.js new file mode 100644 index 0000000000..89af91c41f --- /dev/null +++ b/src/components/views/toasts/VerificationRequestToast.js @@ -0,0 +1,123 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import sdk from "../../../index"; +import { _t } from '../../../languageHandler'; +import Modal from "../../../Modal"; +import MatrixClientPeg from '../../../MatrixClientPeg'; +import {verificationMethods} from 'matrix-js-sdk/lib/crypto'; +import KeyVerificationStateObserver, {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver"; +import dis from "../../../dispatcher"; + +export default class VerificationRequestToast extends React.PureComponent { + constructor(props) { + super(props); + const {event, timeout} = props.request; + // to_device requests don't have a timestamp, so consider them age=0 + const age = event.getTs() ? event.getLocalAge() : 0; + const remaining = Math.max(0, timeout - age); + const counter = Math.ceil(remaining / 1000); + this.state = {counter}; + if (this.props.requestObserver) { + this.props.requestObserver.setCallback(this._checkRequestIsPending); + } + } + + componentDidMount() { + if (this.props.requestObserver) { + this.props.requestObserver.attach(); + this._checkRequestIsPending(); + } + this._intervalHandle = setInterval(() => { + let {counter} = this.state; + counter -= 1; + if (counter <= 0) { + this.cancel(); + } else { + this.setState({counter}); + } + }, 1000); + } + + componentWillUnmount() { + clearInterval(this._intervalHandle); + if (this.props.requestObserver) { + this.props.requestObserver.detach(); + } + } + + _checkRequestIsPending = () => { + if (!this.props.requestObserver.pending) { + this.props.dismiss(); + } + } + + cancel = () => { + this.props.dismiss(); + try { + this.props.request.cancel(); + } catch (err) { + console.error("Error while cancelling verification request", err); + } + } + + accept = () => { + this.props.dismiss(); + const {event} = this.props.request; + // no room id for to_device requests + if (event.getRoomId()) { + dis.dispatch({ + action: 'view_room', + room_id: event.getRoomId(), + should_peek: false, + }); + } + + const verifier = this.props.request.beginKeyVerification(verificationMethods.SAS); + const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog'); + Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {verifier}); + }; + + render() { + const FormButton = sdk.getComponent("elements.FormButton"); + const {event} = this.props.request; + const userId = event.getSender(); + let nameLabel = event.getRoomId() ? userLabelForEventRoom(userId, event) : userId; + // for legacy to_device verification requests + if (nameLabel === userId) { + const client = MatrixClientPeg.get(); + const user = client.getUser(event.getSender()); + if (user && user.displayName) { + nameLabel = _t("%(name)s (%(userId)s)", {name: user.displayName, userId}); + } + } + return (
+
{nameLabel}
+
+ + +
+
); + } +} + +VerificationRequestToast.propTypes = { + dismiss: PropTypes.func.isRequired, + request: PropTypes.object.isRequired, + requestObserver: PropTypes.instanceOf(KeyVerificationStateObserver), +}; diff --git a/src/components/views/voip/CallView.js b/src/components/views/voip/CallView.js index a4d7927ac3..cf1f505197 100644 --- a/src/components/views/voip/CallView.js +++ b/src/components/views/voip/CallView.js @@ -90,6 +90,13 @@ module.exports = createReactClass({ } } else { call = CallHandler.getAnyActiveCall(); + // Ignore calls if we can't get the room associated with them. + // I think the underlying problem is that the js-sdk sends events + // for calls before it has made the rooms available in the store, + // although this isn't confirmed. + if (MatrixClientPeg.get().getRoom(call.roomId) === null) { + call = null; + } this.setState({ call: call }); } diff --git a/src/createRoom.js b/src/createRoom.js index 120043247d..0ee90beba8 100644 --- a/src/createRoom.js +++ b/src/createRoom.js @@ -21,7 +21,6 @@ import { _t } from './languageHandler'; import dis from "./dispatcher"; import * as Rooms from "./Rooms"; -import Promise from 'bluebird'; import {getAddressType} from "./UserAddress"; /** diff --git a/src/i18n/strings/bg.json b/src/i18n/strings/bg.json index 3697cc635c..0ec1d91b9b 100644 --- a/src/i18n/strings/bg.json +++ b/src/i18n/strings/bg.json @@ -2260,5 +2260,7 @@ "You cancelled": "Отказахте потвърждаването", "%(name)s cancelled": "%(name)s отказа", "%(name)s wants to verify": "%(name)s иска да извърши потвърждение", - "You sent a verification request": "Изпратихте заявка за потвърждение" + "You sent a verification request": "Изпратихте заявка за потвърждение", + "Custom (%(level)s)": "Собствен (%(level)s)", + "Try out new ways to ignore people (experimental)": "Опитайте нови начини да игнорирате хора (експериментално)" } diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index e4e01b0116..62e6147506 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -1269,7 +1269,7 @@ "Security & Privacy": "Bezpečnost & Soukromí", "Encryption": "Šifrování", "Once enabled, encryption cannot be disabled.": "Když se šifrování zapne, už nepůjde vypnout.", - "Encrypted": "Šifrování je zapnuté", + "Encrypted": "Šifrování", "General": "Obecné", "General failure": "Nějaká chyba", "This homeserver does not support login using email address.": "Tento homeserver neumožňuje přihlášní pomocí emailu.", diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7709a4a398..0a83237f45 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -342,7 +342,7 @@ "Multiple integration managers": "Multiple integration managers", "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", "Send verification requests in direct message, including a new verification UX in the member panel.": "Send verification requests in direct message, including a new verification UX in the member panel.", - "Enable cross-signing to verify per-user instead of per-device": "Enable cross-signing to verify per-user instead of per-device", + "Enable cross-signing to verify per-user instead of per-device (in development)": "Enable cross-signing to verify per-user instead of per-device (in development)", "Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)", "Use the new, faster, composer for writing messages": "Use the new, faster, composer for writing messages", "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", @@ -481,6 +481,7 @@ "Headphones": "Headphones", "Folder": "Folder", "Pin": "Pin", + "Decline (%(counter)s)": "Decline (%(counter)s)", "Accept to continue:": "Accept to continue:", "Failed to upload profile picture!": "Failed to upload profile picture!", "Upload new:": "Upload new:", @@ -507,11 +508,9 @@ "Failed to set display name": "Failed to set display name", "Disable Notifications": "Disable Notifications", "Enable Notifications": "Enable Notifications", - "No integrations server configured": "No integrations server configured", - "This Riot instance does not have an integrations server configured.": "This Riot instance does not have an integrations server configured.", - "Connecting to integrations server...": "Connecting to integrations server...", - "Cannot connect to integrations server": "Cannot connect to integrations server", - "The integrations server is offline or it cannot reach your homeserver.": "The integrations server is offline or it cannot reach your homeserver.", + "Connecting to integration manager...": "Connecting to integration manager...", + "Cannot connect to integration manager": "Cannot connect to integration manager", + "The integration manager is offline or it cannot reach your homeserver.": "The integration manager is offline or it cannot reach your homeserver.", "Delete Backup": "Delete Backup", "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.", "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", @@ -598,15 +597,10 @@ "Do not use an identity server": "Do not use an identity server", "Enter a new identity server": "Enter a new identity server", "Change": "Change", - "Failed to update integration manager": "Failed to update integration manager", - "Integration manager offline or not accessible.": "Integration manager offline or not accessible.", - "Terms of service not accepted or the integration manager is invalid.": "Terms of service not accepted or the integration manager is invalid.", - "Integration manager has no terms of service": "Integration manager has no terms of service", - "The integration manager you have chosen does not have any terms of service.": "The integration manager you have chosen does not have any terms of service.", - "You are currently using %(serverName)s to manage your bots, widgets, and sticker packs.": "You are currently using %(serverName)s to manage your bots, widgets, and sticker packs.", - "Add which integration manager you want to manage your bots, widgets, and sticker packs.": "Add which integration manager you want to manage your bots, widgets, and sticker packs.", - "Integration Manager": "Integration Manager", - "Enter a new integration manager": "Enter a new integration manager", + "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.", + "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Use an Integration Manager to manage bots, widgets, and sticker packs.", + "Manage integrations": "Manage integrations", + "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.", "Flair": "Flair", "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", "Success": "Success", @@ -1024,8 +1018,7 @@ "numbered-list": "numbered-list", "Show Text Formatting Toolbar": "Show Text Formatting Toolbar", "Hide Text Formatting Toolbar": "Hide Text Formatting Toolbar", - "Failed to connect to integrations server": "Failed to connect to integrations server", - "No integrations server is configured to manage stickers with": "No integrations server is configured to manage stickers with", + "Failed to connect to integration manager": "Failed to connect to integration manager", "You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled", "Add some now": "Add some now", "Stickerpack": "Stickerpack", @@ -1195,6 +1188,7 @@ "Widget ID": "Widget ID", "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Using this widget may share data with %(widgetDomain)s & your Integration Manager.", "Using this widget may share data with %(widgetDomain)s.": "Using this widget may share data with %(widgetDomain)s.", + "Widgets do not use message encryption.": "Widgets do not use message encryption.", "Widget added by": "Widget added by", "This widget may use cookies.": "This widget may use cookies.", "Delete Widget": "Delete Widget", @@ -1204,10 +1198,8 @@ "An error ocurred whilst trying to remove the widget from the room": "An error ocurred whilst trying to remove the widget from the room", "Minimize apps": "Minimize apps", "Maximize apps": "Maximize apps", - "Reload widget": "Reload widget", "Popout widget": "Popout widget", - "Picture": "Picture", - "Revoke widget access": "Revoke widget access", + "More options": "More options", "Create new room": "Create new room", "Unblacklist": "Unblacklist", "Blacklist": "Blacklist", @@ -1398,6 +1390,10 @@ "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.": "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.", "Waiting for partner to confirm...": "Waiting for partner to confirm...", "Incoming Verification Request": "Incoming Verification Request", + "Integrations are disabled": "Integrations are disabled", + "Enable 'Manage Integrations' in Settings to do this.": "Enable 'Manage Integrations' in Settings to do this.", + "Integrations not allowed": "Integrations not allowed", + "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.", "You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.", "Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.", "Start verification": "Start verification", @@ -1475,7 +1471,7 @@ "Missing session data": "Missing session data", "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.", "Your browser likely removed this data when running low on disk space.": "Your browser likely removed this data when running low on disk space.", - "Integrations Manager": "Integrations Manager", + "Integration Manager": "Integration Manager", "Find others by phone or email": "Find others by phone or email", "Be found by phone or email": "Be found by phone or email", "Use bots, bridges, widgets and sticker packs": "Use bots, bridges, widgets and sticker packs", @@ -1564,6 +1560,10 @@ "Hide": "Hide", "Home": "Home", "Sign in": "Sign in", + "Reload": "Reload", + "Take picture": "Take picture", + "Remove for everyone": "Remove for everyone", + "Remove for me": "Remove for me", "powered by Matrix": "powered by Matrix", "This homeserver would like to make sure you are not a robot.": "This homeserver would like to make sure you are not a robot.", "Custom Server Options": "Custom Server Options", @@ -1694,6 +1694,7 @@ "Review terms and conditions": "Review terms and conditions", "Old cryptography data detected": "Old cryptography data detected", "Data from an older version of Riot has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Data from an older version of Riot has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.", + "Verification Request": "Verification Request", "Logout": "Logout", "%(creator)s created and configured the room.": "%(creator)s created and configured the room.", "Your Communities": "Your Communities", @@ -1759,6 +1760,7 @@ "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.", "Failed to load timeline position": "Failed to load timeline position", + " (1/%(totalCount)s)": " (1/%(totalCount)s)", "Guest": "Guest", "Your profile": "Your profile", "Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others", diff --git a/src/i18n/strings/fi.json b/src/i18n/strings/fi.json index 80fbb9b138..81a8563e5b 100644 --- a/src/i18n/strings/fi.json +++ b/src/i18n/strings/fi.json @@ -2137,5 +2137,25 @@ "%(count)s unread messages including mentions.|one": "Yksi lukematon maininta.", "%(count)s unread messages.|one": "Yksi lukematon viesti.", "Unread messages.": "Lukemattomat viestit.", - "Message Actions": "Viestitoiminnot" + "Message Actions": "Viestitoiminnot", + "Custom (%(level)s)": "Mukautettu (%(level)s)", + "Match system dark mode setting": "Sovita järjestelmän tumman tilan asetukseen", + "None": "Ei mitään", + "Unsubscribe": "Lopeta tilaus", + "View rules": "Näytä säännöt", + "Subscribe": "Tilaa", + "Direct message": "Yksityisviesti", + "%(role)s in %(roomName)s": "%(role)s huoneessa %(roomName)s", + "Security": "Tietoturva", + "Any of the following data may be shared:": "Seuraavat tiedot saatetaan jakaa:", + "Your display name": "Näyttönimesi", + "Your avatar URL": "Kuvasi URL-osoite", + "Your user ID": "Käyttäjätunnuksesi", + "Your theme": "Teemasi", + "Riot URL": "Riotin URL-osoite", + "Room ID": "Huoneen tunnus", + "Widget ID": "Sovelman tunnus", + "Using this widget may share data with %(widgetDomain)s.": "Tämän sovelman käyttäminen voi jakaa tietoja verkkotunnukselle %(widgetDomain)s.", + "Widget added by": "Sovelman lisäsi", + "This widget may use cookies.": "Tämä sovelma saattaa käyttää evästeitä." } diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 64272bb839..097dd0824f 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2351,5 +2351,28 @@ "Using this widget may share data with %(widgetDomain)s.": "L’utilisation de ce widget pourrait partager des données avec %(widgetDomain)s.", "Widget added by": "Widget ajouté par", "This widget may use cookies.": "Ce widget pourrait utiliser des cookies.", - "Send verification requests in direct message, including a new verification UX in the member panel.": "Envoyer les demandes de vérification en message direct, en incluant une nouvelle expérience de vérification dans le tableau des membres." + "Send verification requests in direct message, including a new verification UX in the member panel.": "Envoyer les demandes de vérification en message direct, en incluant une nouvelle expérience de vérification dans le tableau des membres.", + "Enable local event indexing and E2EE search (requires restart)": "Activer l’indexation des événements locaux et la recherche des données chiffrées de bout en bout (nécessite un redémarrage)", + "Match system dark mode setting": "S’adapter aux paramètres de mode sombre du système", + "Connecting to integration manager...": "Connexion au gestionnaire d’intégrations…", + "Cannot connect to integration manager": "Impossible de se connecter au gestionnaire d’intégrations", + "The integration manager is offline or it cannot reach your homeserver.": "Le gestionnaire d’intégrations est hors ligne ou il ne peut pas joindre votre serveur d’accueil.", + "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Utilisez un gestionnaire d’intégrations (%(serverName)s) pour gérer les bots, les widgets et les packs de stickers.", + "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Utilisez un gestionnaire d’intégrations pour gérer les bots, les widgets et les packs de stickers.", + "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Les gestionnaires d’intégrations reçoivent les données de configuration et peuvent modifier les widgets, envoyer des invitations aux salons et définir les rangs à votre place.", + "Failed to connect to integration manager": "Échec de la connexion au gestionnaire d’intégrations", + "Widgets do not use message encryption.": "Les widgets n’utilisent pas le chiffrement des messages.", + "More options": "Plus d’options", + "Integrations are disabled": "Les intégrations sont désactivées", + "Enable 'Manage Integrations' in Settings to do this.": "Activez « Gérer les intégrations » dans les paramètres pour faire ça.", + "Integrations not allowed": "Les intégrations ne sont pas autorisées", + "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Votre Riot ne vous autorise pas à utiliser un gestionnaire d’intégrations pour faire ça. Contactez un administrateur.", + "Reload": "Recharger", + "Take picture": "Prendre une photo", + "Remove for everyone": "Supprimer pour tout le monde", + "Remove for me": "Supprimer pour moi", + "Decline (%(counter)s)": "Refuser (%(counter)s)", + "Manage integrations": "Gérer les intégrations", + "Verification Request": "Demande de vérification", + " (1/%(totalCount)s)": " (1/%(totalCount)s)" } diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 892f21dbb1..9e41b7381e 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2338,5 +2338,29 @@ "Using this widget may share data with %(widgetDomain)s.": "Ennek a kisalkalmazásnak a használata adatot oszthat meg %(widgetDomain)s domain-nel.", "Widget added by": "A kisalkalmazást hozzáadta", "This widget may use cookies.": "Ez a kisalkalmazás sütiket használhat.", - "Send verification requests in direct message, including a new verification UX in the member panel.": "Ellenőrzés küldése közvetlen üzenetben, beleértve az új ellenőrzési felhasználói élményt a résztvevői panelen." + "Send verification requests in direct message, including a new verification UX in the member panel.": "Ellenőrzés küldése közvetlen üzenetben, beleértve az új ellenőrzési felhasználói élményt a résztvevői panelen.", + "Enable local event indexing and E2EE search (requires restart)": "Helyi esemény indexálás és végponttól végpontig titkosított események keresésének engedélyezése (újraindítás szükséges)", + "Match system dark mode setting": "Rendszer sötét témájához alkalmazkodás", + "Widgets are not encrypted.": "A kisalkalmazások nem titkosítottak.", + "More options": "További beállítások", + "Reload": "Újratölt", + "Take picture": "Fénykép készítés", + "Remove for everyone": "Visszavonás mindenkitől", + "Remove for me": "Visszavonás magamtól", + "Connecting to integration manager...": "Kapcsolódás az integrációs menedzserhez...", + "Cannot connect to integration manager": "A kapcsolódás az integrációs menedzserhez sikertelen", + "The integration manager is offline or it cannot reach your homeserver.": "Az integrációs menedzser nem működik vagy nem éri el a matrix szerveredet.", + "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Használj Integrációs Menedzsert (%(serverName)s) a botok, kisalkalmazások és matrica csomagok kezeléséhez.", + "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Használj Integrációs Menedzsert a botok, kisalkalmazások és matrica csomagok kezeléséhez.", + "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integrációs Menedzser megkapja a konfigurációt, módosíthat kisalkalmazásokat, szobához meghívót küldhet és a hozzáférési szintet beállíthatja helyetted.", + "Failed to connect to integration manager": "Az integrációs menedzserhez nem sikerült csatlakozni", + "Widgets do not use message encryption.": "A kisalkalmazások nem használnak üzenet titkosítást.", + "Integrations are disabled": "Az integrációk le vannak tiltva", + "Enable 'Manage Integrations' in Settings to do this.": "Ehhez engedélyezd az „Integrációk Kezelésé”-t a Beállításokban.", + "Integrations not allowed": "Az integrációk nem engedélyezettek", + "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "A Riotod nem használhat ehhez Integrációs Menedzsert. Kérlek vedd fel a kapcsolatot az adminisztrátorral.", + "Decline (%(counter)s)": "Elutasítás (%(counter)s)", + "Manage integrations": "Integrációk kezelése", + "Verification Request": "Ellenőrzési kérés", + " (1/%(totalCount)s)": " (1/%(totalCount)s)" } diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index efab4595f6..21c03a7802 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2295,5 +2295,31 @@ "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Usando questo widget i dati possono essere condivisi con %(widgetDomain)s e il tuo Gestore di Integrazione.", "Using this widget may share data with %(widgetDomain)s.": "Usando questo widget i dati possono essere condivisi con %(widgetDomain)s.", "Widget added by": "Widget aggiunto da", - "This widget may use cookies.": "Questo widget può usare cookie." + "This widget may use cookies.": "Questo widget può usare cookie.", + "Send verification requests in direct message, including a new verification UX in the member panel.": "Invia le richieste di verifica via messaggio diretto, inclusa una nuova esperienza utente per la verifica nel pannello membri.", + "Enable local event indexing and E2EE search (requires restart)": "Attiva l'indicizzazione di eventi locali e la ricerca E2EE (richiede riavvio)", + "Match system dark mode setting": "Combacia la modalità scura di sistema", + "Connecting to integration manager...": "Connessione al gestore di integrazioni...", + "Cannot connect to integration manager": "Impossibile connettere al gestore di integrazioni", + "The integration manager is offline or it cannot reach your homeserver.": "Il gestore di integrazioni è offline o non riesce a raggiungere il tuo homeserver.", + "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Usa un gestore di integrazioni (%(serverName)s) per gestire bot, widget e pacchetti di adesivi.", + "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Usa un gestore di integrazioni per gestire bot, widget e pacchetti di adesivi.", + "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "I gestori di integrazione ricevono dati di configurazione e possono modificare widget, inviare inviti alla stanza, assegnare permessi a tuo nome.", + "Failed to connect to integration manager": "Connessione al gestore di integrazioni fallita", + "Widgets do not use message encryption.": "I widget non usano la cifratura dei messaggi.", + "More options": "Altre opzioni", + "Integrations are disabled": "Le integrazioni sono disattivate", + "Enable 'Manage Integrations' in Settings to do this.": "Attiva 'Gestisci integrazioni' nelle impostazioni per continuare.", + "Integrations not allowed": "Integrazioni non permesse", + "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Il tuo Riot non ti permette di usare il gestore di integrazioni per questa azione. Contatta un amministratore.", + "Reload": "Ricarica", + "Take picture": "Scatta foto", + "Remove for everyone": "Rimuovi per tutti", + "Remove for me": "Rimuovi per me", + "Trust": "Fidati", + "Decline (%(counter)s)": "Rifiuta (%(counter)s)", + "Manage integrations": "Gestisci integrazioni", + "Ignored/Blocked": "Ignorati/Bloccati", + "Verification Request": "Richiesta verifica", + " (1/%(totalCount)s)": " (1/%(totalCount)s)" } diff --git a/src/i18n/strings/lv.json b/src/i18n/strings/lv.json index 80e173dc3f..1c7d2b0311 100644 --- a/src/i18n/strings/lv.json +++ b/src/i18n/strings/lv.json @@ -1144,5 +1144,8 @@ "You do not have permission to start a conference call in this room": "Šajā istabā nav atļaujas sākt konferences zvanu", "Replying With Files": "Atbildot ar failiem", "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "Šobrīd nav iespējams atbildēt ar failu. Vai vēlaties augšupielādēt šo failu, neatbildot?", - "Your Riot is misconfigured": "Jūsu Riot ir nepareizi konfigurēts" + "Your Riot is misconfigured": "Jūsu Riot ir nepareizi konfigurēts", + "Add Email Address": "Pievienot e-pasta adresi", + "Add Phone Number": "Pievienot tālruņa numuru", + "Call failed due to misconfigured server": "Zvans neizdevās nekorekti nokonfigurēta servera dēļ" } diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index 825f3c6a48..347afc3583 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -1193,7 +1193,7 @@ "Encrypted, not sent": "Versleuteld, niet verstuurd", "Demote yourself?": "Uzelf degraderen?", "Demote": "Degraderen", - "Share Link to User": "Koppeling met gebruiker delen", + "Share Link to User": "Koppeling naar gebruiker delen", "deleted": "verwijderd", "underlined": "onderstreept", "inline-code": "code", diff --git a/src/i18n/strings/pt.json b/src/i18n/strings/pt.json index 5a56e807e4..7aa8851daa 100644 --- a/src/i18n/strings/pt.json +++ b/src/i18n/strings/pt.json @@ -350,7 +350,7 @@ "No devices with registered encryption keys": "Não há dispositivos com chaves de criptografia registradas", "No more results": "Não há mais resultados", "No results": "Sem resultados", - "OK": "Ok", + "OK": "OK", "Revoke Moderator": "Retirar status de moderador", "Search": "Pesquisar", "Search failed": "Busca falhou", @@ -847,6 +847,29 @@ "Add Phone Number": "Adicione número de telefone", "The platform you're on": "A plataforma em que se encontra", "The version of Riot.im": "A versão do RIOT.im", - "Whether or not you're logged in (we don't record your username)": "Tenha ou não, iniciado sessão (não iremos guardar o seu username)", - "Your language of choice": "O seu idioma de escolha" + "Whether or not you're logged in (we don't record your username)": "Tenha ou não, iniciado sessão (não iremos guardar o seu nome de utilizador)", + "Your language of choice": "O seu idioma que escolheu", + "Which officially provided instance you are using, if any": "Qual instância oficial está utilizando, se for o caso", + "Whether or not you're using the Richtext mode of the Rich Text Editor": "Se está a usar o modo de texto enriquecido do editor de texto enriquecido", + "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Se usa a funcionalidade 'breadcrumbs' (avatares em cima da lista de salas", + "Your homeserver's URL": "O URL do seu servidor de início", + "Your identity server's URL": "O URL do seu servidor de identidade", + "e.g. %(exampleValue)s": "ex. %(exampleValue)s", + "Every page you use in the app": "Todas as páginas que usa na aplicação", + "e.g. ": "ex. ", + "Your User Agent": "O seu Agente de Utilizador", + "Your device resolution": "A resolução do seu dispositivo", + "The information being sent to us to help make Riot.im better includes:": "As informações que estão sendo enviadas para ajudar a melhorar o Riot.im incluem:", + "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Quando esta página contém informação de que permitam a sua identificação, como uma sala, ID de utilizador ou de grupo, estes dados são removidos antes de serem enviados ao servidor.", + "Call Failed": "A chamada falhou", + "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "Há dispositivos desconhecidos nesta sala: se continuar sem os verificar, será possível que alguém espie a sua chamada.", + "Review Devices": "Rever dispositivos", + "Call Anyway": "Ligar na mesma", + "Answer Anyway": "Responder na mesma", + "Call": "Ligar", + "Answer": "Responder", + "Call failed due to misconfigured server": "Chamada falhada devido a um erro de configuração do servidor", + "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.": "Peça ao administrador do seu servidor inicial (%(homeserverDomain)s) de configurar um servidor TURN para que as chamadas funcionem fiavelmente.", + "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Alternativamente, pode tentar usar o servidor público em turn.matrix.org, mas não será tão fiável e partilhará o seu IP com esse servidor. Também pode gerir isso nas definições.", + "Try using turn.matrix.org": "Tente utilizar turn.matrix.org" } diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index 072215663d..415d2fdd4b 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -652,7 +652,7 @@ "Whether or not you're using the Richtext mode of the Rich Text Editor": "Se você está usando o editor de texto visual", "Your homeserver's URL": "A URL do seu Servidor de Base (homeserver)", "Your identity server's URL": "A URL do seu servidor de identidade", - "The information being sent to us to help make Riot.im better includes:": "As informações que estão sendo usadas para ajudar a melhorar o Riot.im incluem:", + "The information being sent to us to help make Riot.im better includes:": "As informações que estão sendo enviadas para ajudar a melhorar o Riot.im incluem:", "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Quando esta página tem informação de identificação, como uma sala, ID de usuária/o ou de grupo, estes dados são removidos antes de serem enviados ao servidor.", "Call Failed": "A chamada falhou", "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "Há dispositivos desconhecidos nesta sala: se você continuar sem verificá-los, será possível alguém espiar sua chamada.", diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 1dfdc34f1a..a5f8e5e04b 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2344,5 +2344,28 @@ "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "使用這個小工具可能會與 %(widgetDomain)s 以及您的整合管理員分享資料 。", "Using this widget may share data with %(widgetDomain)s.": "使用這個小工具可能會與 %(widgetDomain)s 分享資料 。", "Widget added by": "小工具新增由", - "This widget may use cookies.": "這個小工具可能會使用 cookies。" + "This widget may use cookies.": "這個小工具可能會使用 cookies。", + "Enable local event indexing and E2EE search (requires restart)": "啟用本機事件索引與端到端加密搜尋(需要重新啟動)", + "Match system dark mode setting": "與系統深色模式設定相符", + "Connecting to integration manager...": "正在連線到整合管理員……", + "Cannot connect to integration manager": "無法連線到整合管理員", + "The integration manager is offline or it cannot reach your homeserver.": "整合管理員已離線或無法存取您的家伺服器。", + "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "使用整合管理員 (%(serverName)s) 以管理機器人、小工具與貼紙包。", + "Use an Integration Manager to manage bots, widgets, and sticker packs.": "使用整合管理員以管理機器人、小工具與貼紙包。", + "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "整合管理員接收設定資料,並可以修改小工具、傳送聊天室邀請並設定權限等級。", + "Failed to connect to integration manager": "連線到整合管理員失敗", + "Widgets do not use message encryption.": "小工具不使用訊息加密。", + "More options": "更多選項", + "Integrations are disabled": "整合已停用", + "Enable 'Manage Integrations' in Settings to do this.": "在設定中啟用「管理整合」以執行此動作。", + "Integrations not allowed": "不允許整合", + "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "您的 Riot 不允許您使用整合管理員來執行此動作。請聯絡管理員。", + "Reload": "重新載入", + "Take picture": "拍照", + "Remove for everyone": "對所有人移除", + "Remove for me": "對我移除", + "Decline (%(counter)s)": "拒絕 (%(counter)s)", + "Manage integrations": "管理整合", + "Verification Request": "驗證請求", + " (1/%(totalCount)s)": " (1/%(totalCount)s)" } diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 6bad992017..cf7e2d8da2 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -35,7 +35,12 @@ export default class EventIndex { async init() { const indexManager = PlatformPeg.get().getEventIndexingManager(); + await indexManager.initEventIndex(); + console.log("EventIndex: Successfully initialized the event index"); + + this.crawlerCheckpoints = await indexManager.loadCheckpoints(); + console.log("EventIndex: Loaded checkpoints", this.crawlerCheckpoints); this.registerListeners(); } @@ -62,14 +67,6 @@ export default class EventIndex { onSync = async (state, prevState, data) => { const indexManager = PlatformPeg.get().getEventIndexingManager(); - if (prevState === null && state === "PREPARED") { - // Load our stored checkpoints, if any. - this.crawlerCheckpoints = await indexManager.loadCheckpoints(); - console.log("EventIndex: Loaded checkpoints", - this.crawlerCheckpoints); - return; - } - if (prevState === "PREPARED" && state === "SYNCING") { const addInitialCheckpoints = async () => { const client = MatrixClientPeg.get(); diff --git a/src/indexing/EventIndexPeg.js b/src/indexing/EventIndexPeg.js index c0bdd74ff4..75f0fa66ba 100644 --- a/src/indexing/EventIndexPeg.js +++ b/src/indexing/EventIndexPeg.js @@ -55,8 +55,6 @@ class EventIndexPeg { return false; } - console.log("EventIndex: Successfully initialized the event index"); - this.index = index; return true; diff --git a/src/integrations/IntegrationManagerInstance.js b/src/integrations/IntegrationManagerInstance.js index d36fa73d48..4958209351 100644 --- a/src/integrations/IntegrationManagerInstance.js +++ b/src/integrations/IntegrationManagerInstance.js @@ -20,6 +20,8 @@ import {dialogTermsInteractionCallback, TermsNotSignedError} from "../Terms"; import type {Room} from "matrix-js-sdk"; import Modal from '../Modal'; import url from 'url'; +import SettingsStore from "../settings/SettingsStore"; +import {IntegrationManagers} from "./IntegrationManagers"; export const KIND_ACCOUNT = "account"; export const KIND_CONFIG = "config"; @@ -57,19 +59,23 @@ export class IntegrationManagerInstance { } async open(room: Room = null, screen: string = null, integrationId: string = null): void { - const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); + if (!SettingsStore.getValue("integrationProvisioning")) { + return IntegrationManagers.sharedInstance().showDisabledDialog(); + } + + const IntegrationManager = sdk.getComponent("views.settings.IntegrationManager"); const dialog = Modal.createTrackedDialog( - 'Integration Manager', '', IntegrationsManager, - {loading: true}, 'mx_IntegrationsManager', + 'Integration Manager', '', IntegrationManager, + {loading: true}, 'mx_IntegrationManager', ); const client = this.getScalarClient(); client.setTermsInteractionCallback((policyInfo, agreedUrls) => { // To avoid visual glitching of two modals stacking briefly, we customise the - // terms dialog sizing when it will appear for the integrations manager so that + // terms dialog sizing when it will appear for the integration manager so that // it gets the same basic size as the IM's own modal. return dialogTermsInteractionCallback( - policyInfo, agreedUrls, 'mx_TermsDialog_forIntegrationsManager', + policyInfo, agreedUrls, 'mx_TermsDialog_forIntegrationManager', ); }); @@ -94,8 +100,8 @@ export class IntegrationManagerInstance { // Close the old dialog and open a new one dialog.close(); Modal.createTrackedDialog( - 'Integration Manager', '', IntegrationsManager, - newProps, 'mx_IntegrationsManager', + 'Integration Manager', '', IntegrationManager, + newProps, 'mx_IntegrationManager', ); } } diff --git a/src/integrations/IntegrationManagers.js b/src/integrations/IntegrationManagers.js index a0fbff56fb..6c4d2ae4d4 100644 --- a/src/integrations/IntegrationManagers.js +++ b/src/integrations/IntegrationManagers.js @@ -22,6 +22,7 @@ import type {MatrixClient, MatrixEvent, Room} from "matrix-js-sdk"; import WidgetUtils from "../utils/WidgetUtils"; import MatrixClientPeg from "../MatrixClientPeg"; import {AutoDiscovery} from "matrix-js-sdk"; +import SettingsStore from "../settings/SettingsStore"; const HS_MANAGERS_REFRESH_INTERVAL = 8 * 60 * 60 * 1000; // 8 hours const KIND_PREFERENCE = [ @@ -172,15 +173,19 @@ export class IntegrationManagers { } openNoManagerDialog(): void { - // TODO: Is it Integrations (plural) or Integration (singular). Singular is easier spoken. - const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); - Modal.createTrackedDialog( - "Integration Manager", "None", IntegrationsManager, - {configured: false}, 'mx_IntegrationsManager', - ); + const IntegrationsImpossibleDialog = sdk.getComponent("dialogs.IntegrationsImpossibleDialog"); + Modal.createTrackedDialog('Integrations impossible', '', IntegrationsImpossibleDialog); } openAll(room: Room = null, screen: string = null, integrationId: string = null): void { + if (!SettingsStore.getValue("integrationProvisioning")) { + return this.showDisabledDialog(); + } + + if (this._managers.length === 0) { + return this.openNoManagerDialog(); + } + const TabbedIntegrationManagerDialog = sdk.getComponent("views.dialogs.TabbedIntegrationManagerDialog"); Modal.createTrackedDialog( 'Tabbed Integration Manager', '', TabbedIntegrationManagerDialog, @@ -188,6 +193,11 @@ export class IntegrationManagers { ); } + showDisabledDialog(): void { + const IntegrationsDisabledDialog = sdk.getComponent("dialogs.IntegrationsDisabledDialog"); + Modal.createTrackedDialog('Integrations disabled', '', IntegrationsDisabledDialog); + } + async overwriteManagerOnAccount(manager: IntegrationManagerInstance) { // TODO: TravisR - We should be logging out of scalar clients. await WidgetUtils.removeIntegrationManagerWidgets(); diff --git a/src/languageHandler.js b/src/languageHandler.js index 179bb2d1d0..c56e5378df 100644 --- a/src/languageHandler.js +++ b/src/languageHandler.js @@ -19,7 +19,6 @@ limitations under the License. import request from 'browser-request'; import counterpart from 'counterpart'; -import Promise from 'bluebird'; import React from 'react'; import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; diff --git a/src/rageshake/rageshake.js b/src/rageshake/rageshake.js index 820550af88..47bab38079 100644 --- a/src/rageshake/rageshake.js +++ b/src/rageshake/rageshake.js @@ -16,8 +16,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Promise from 'bluebird'; - // This module contains all the code needed to log the console, persist it to // disk and submit bug reports. Rationale is as follows: // - Monkey-patching the console is preferable to having a log library because diff --git a/src/rageshake/submit-rageshake.js b/src/rageshake/submit-rageshake.js index e772912e48..457958eb82 100644 --- a/src/rageshake/submit-rageshake.js +++ b/src/rageshake/submit-rageshake.js @@ -17,7 +17,6 @@ limitations under the License. */ import pako from 'pako'; -import Promise from 'bluebird'; import MatrixClientPeg from '../MatrixClientPeg'; import PlatformPeg from '../PlatformPeg'; diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 89693f7c50..5a3283c5f0 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -143,7 +143,7 @@ export const SETTINGS = { }, "feature_cross_signing": { isFeature: true, - displayName: _td("Enable cross-signing to verify per-user instead of per-device"), + displayName: _td("Enable cross-signing to verify per-user instead of per-device (in development)"), supportedLevels: LEVELS_FEATURE, default: false, controller: new ReloadOnChangeController(), diff --git a/src/settings/handlers/DeviceSettingsHandler.js b/src/settings/handlers/DeviceSettingsHandler.js index 780815efd1..76c518b97b 100644 --- a/src/settings/handlers/DeviceSettingsHandler.js +++ b/src/settings/handlers/DeviceSettingsHandler.js @@ -15,7 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Promise from 'bluebird'; import SettingsHandler from "./SettingsHandler"; import MatrixClientPeg from "../../MatrixClientPeg"; import {SettingLevel} from "../SettingsStore"; diff --git a/src/settings/handlers/LocalEchoWrapper.js b/src/settings/handlers/LocalEchoWrapper.js index e6964f9bf7..4cbe4891be 100644 --- a/src/settings/handlers/LocalEchoWrapper.js +++ b/src/settings/handlers/LocalEchoWrapper.js @@ -15,7 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Promise from "bluebird"; import SettingsHandler from "./SettingsHandler"; /** diff --git a/src/settings/handlers/RoomDeviceSettingsHandler.js b/src/settings/handlers/RoomDeviceSettingsHandler.js index a0981ffbab..a9cf686c4c 100644 --- a/src/settings/handlers/RoomDeviceSettingsHandler.js +++ b/src/settings/handlers/RoomDeviceSettingsHandler.js @@ -15,7 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Promise from 'bluebird'; import SettingsHandler from "./SettingsHandler"; import {SettingLevel} from "../SettingsStore"; diff --git a/src/settings/handlers/SettingsHandler.js b/src/settings/handlers/SettingsHandler.js index d1566d6bfa..7d987fc136 100644 --- a/src/settings/handlers/SettingsHandler.js +++ b/src/settings/handlers/SettingsHandler.js @@ -15,8 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Promise from "bluebird"; - /** * Represents the base class for all level handlers. This class performs no logic * and should be overridden. diff --git a/src/stores/FlairStore.js b/src/stores/FlairStore.js index c8b4d75010..94b81c1ba5 100644 --- a/src/stores/FlairStore.js +++ b/src/stores/FlairStore.js @@ -15,7 +15,6 @@ limitations under the License. */ import EventEmitter from 'events'; -import Promise from 'bluebird'; const BULK_REQUEST_DEBOUNCE_MS = 200; diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js index 6a405124f4..a3caf876ef 100644 --- a/src/stores/RoomViewStore.js +++ b/src/stores/RoomViewStore.js @@ -234,7 +234,7 @@ class RoomViewStore extends Store { }); MatrixClientPeg.get().joinRoom( this._state.roomAlias || this._state.roomId, payload.opts, - ).done(() => { + ).then(() => { // We don't actually need to do anything here: we do *not* // clear the 'joining' flag because the Room object and/or // our 'joined' member event may not have come down the sync diff --git a/src/theme.js b/src/theme.js index fa7e3f783b..9208ff2045 100644 --- a/src/theme.js +++ b/src/theme.js @@ -41,14 +41,18 @@ export class ThemeWatcher { start() { this._themeWatchRef = SettingsStore.watchSetting("theme", null, this._onChange); this._systemThemeWatchRef = SettingsStore.watchSetting("use_system_theme", null, this._onChange); - this._preferDark.addEventListener('change', this._onChange); - this._preferLight.addEventListener('change', this._onChange); + if (this._preferDark.addEventListener) { + this._preferDark.addEventListener('change', this._onChange); + this._preferLight.addEventListener('change', this._onChange); + } this._dispatcherRef = dis.register(this._onAction); } stop() { - this._preferDark.removeEventListener('change', this._onChange); - this._preferLight.removeEventListener('change', this._onChange); + if (this._preferDark.addEventListener) { + this._preferDark.removeEventListener('change', this._onChange); + this._preferLight.removeEventListener('change', this._onChange); + } SettingsStore.unwatchSetting(this._systemThemeWatchRef); SettingsStore.unwatchSetting(this._themeWatchRef); dis.unregister(this._dispatcherRef); @@ -127,28 +131,14 @@ function getCustomTheme(themeName) { return customTheme; } -/** - * Gets the underlying theme name for the given theme. This is usually the theme or - * CSS resource that the theme relies upon to load. - * @param {string} theme The theme name to get the base of. - * @returns {string} The base theme (typically "light" or "dark"). - */ -export function getBaseTheme(theme) { - if (!theme) return "light"; - if (theme.startsWith("custom-")) { - const customTheme = getCustomTheme(theme.substr(7)); - return customTheme.is_dark ? "dark-custom" : "light-custom"; - } - - return theme; // it's probably a base theme -} - /** * Called whenever someone changes the theme + * Async function that returns once the theme has been set + * (ie. the CSS has been loaded) * * @param {string} theme new theme */ -export function setTheme(theme) { +export async function setTheme(theme) { if (!theme) { const themeWatcher = new ThemeWatcher(); theme = themeWatcher.getEffectiveTheme(); @@ -190,38 +180,41 @@ export function setTheme(theme) { styleElements[stylesheetName].disabled = false; - const switchTheme = function() { - // we re-enable our theme here just in case we raced with another - // theme set request as per https://github.com/vector-im/riot-web/issues/5601. - // We could alternatively lock or similar to stop the race, but - // this is probably good enough for now. - styleElements[stylesheetName].disabled = false; - Object.values(styleElements).forEach((a) => { - if (a == styleElements[stylesheetName]) return; - a.disabled = true; - }); - Tinter.setTheme(theme); - }; + return new Promise((resolve) => { + const switchTheme = function() { + // we re-enable our theme here just in case we raced with another + // theme set request as per https://github.com/vector-im/riot-web/issues/5601. + // We could alternatively lock or similar to stop the race, but + // this is probably good enough for now. + styleElements[stylesheetName].disabled = false; + Object.values(styleElements).forEach((a) => { + if (a == styleElements[stylesheetName]) return; + a.disabled = true; + }); + Tinter.setTheme(theme); + resolve(); + }; - // turns out that Firefox preloads the CSS for link elements with - // the disabled attribute, but Chrome doesn't. + // turns out that Firefox preloads the CSS for link elements with + // the disabled attribute, but Chrome doesn't. - let cssLoaded = false; + let cssLoaded = false; - styleElements[stylesheetName].onload = () => { - switchTheme(); - }; + styleElements[stylesheetName].onload = () => { + switchTheme(); + }; - for (let i = 0; i < document.styleSheets.length; i++) { - const ss = document.styleSheets[i]; - if (ss && ss.href === styleElements[stylesheetName].href) { - cssLoaded = true; - break; + for (let i = 0; i < document.styleSheets.length; i++) { + const ss = document.styleSheets[i]; + if (ss && ss.href === styleElements[stylesheetName].href) { + cssLoaded = true; + break; + } } - } - if (cssLoaded) { - styleElements[stylesheetName].onload = undefined; - switchTheme(); - } + if (cssLoaded) { + styleElements[stylesheetName].onload = undefined; + switchTheme(); + } + }); } diff --git a/src/utils/DecryptFile.js b/src/utils/DecryptFile.js index ea0e4c3fb0..f193bd7709 100644 --- a/src/utils/DecryptFile.js +++ b/src/utils/DecryptFile.js @@ -21,7 +21,6 @@ import encrypt from 'browser-encrypt-attachment'; import 'isomorphic-fetch'; // Grab the client so that we can turn mxc:// URLs into https:// URLS. import MatrixClientPeg from '../MatrixClientPeg'; -import Promise from 'bluebird'; // WARNING: We have to be very careful about what mime-types we allow into blobs, // as for performance reasons these are now rendered via URL.createObjectURL() diff --git a/src/utils/KeyVerificationStateObserver.js b/src/utils/KeyVerificationStateObserver.js index 7de50ec4bf..2f7c0367ad 100644 --- a/src/utils/KeyVerificationStateObserver.js +++ b/src/utils/KeyVerificationStateObserver.js @@ -30,6 +30,18 @@ export default class KeyVerificationStateObserver { this._updateVerificationState(); } + get concluded() { + return this.accepted || this.done || this.cancelled; + } + + get pending() { + return !this.concluded; + } + + setCallback(callback) { + this._updateCallback = callback; + } + attach() { this._requestEvent.on("Event.relationsCreated", this._onRelationsCreated); for (const phaseName of SUB_EVENT_TYPES_OF_INTEREST) { @@ -83,7 +95,7 @@ export default class KeyVerificationStateObserver { _onRelationsUpdated = (event) => { this._updateVerificationState(); - this._updateCallback(); + this._updateCallback && this._updateCallback(); }; _updateVerificationState() { diff --git a/src/utils/MultiInviter.js b/src/utils/MultiInviter.js index de5c2e7610..8b952a2b5b 100644 --- a/src/utils/MultiInviter.js +++ b/src/utils/MultiInviter.js @@ -15,11 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; import MatrixClientPeg from '../MatrixClientPeg'; import {getAddressType} from '../UserAddress'; import GroupStore from '../stores/GroupStore'; -import Promise from 'bluebird'; import {_t} from "../languageHandler"; import sdk from "../index"; import Modal from "../Modal"; diff --git a/src/utils/promise.js b/src/utils/promise.js index e6e6ccb5c8..d7e8d2eae1 100644 --- a/src/utils/promise.js +++ b/src/utils/promise.js @@ -14,9 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// This is only here to allow access to methods like done for the time being -import Promise from "bluebird"; - // @flow // Returns a promise which resolves with a given value after the given number of ms diff --git a/test/components/views/dialogs/InteractiveAuthDialog-test.js b/test/components/views/dialogs/InteractiveAuthDialog-test.js index 7612b43b48..5f90e0f21c 100644 --- a/test/components/views/dialogs/InteractiveAuthDialog-test.js +++ b/test/components/views/dialogs/InteractiveAuthDialog-test.js @@ -15,7 +15,6 @@ limitations under the License. */ import expect from 'expect'; -import Promise from 'bluebird'; import React from 'react'; import ReactDOM from 'react-dom'; import ReactTestUtils from 'react-dom/test-utils'; diff --git a/test/components/views/elements/MemberEventListSummary-test.js b/test/components/views/elements/MemberEventListSummary-test.js index 95f7e7999a..a31cbdebb5 100644 --- a/test/components/views/elements/MemberEventListSummary-test.js +++ b/test/components/views/elements/MemberEventListSummary-test.js @@ -91,7 +91,7 @@ describe('MemberEventListSummary', function() { testUtils.beforeEach(this); sandbox = testUtils.stubClient(); - languageHandler.setLanguage('en').done(done); + languageHandler.setLanguage('en').then(done); languageHandler.setMissingEntryGenerator(function(key) { return key.split('|', 2)[1]; }); diff --git a/test/components/views/rooms/MessageComposerInput-test.js b/test/components/views/rooms/MessageComposerInput-test.js index 04a5c83ed0..60380eecd2 100644 --- a/test/components/views/rooms/MessageComposerInput-test.js +++ b/test/components/views/rooms/MessageComposerInput-test.js @@ -3,7 +3,6 @@ import ReactTestUtils from 'react-dom/test-utils'; import ReactDOM from 'react-dom'; import expect from 'expect'; import sinon from 'sinon'; -import Promise from 'bluebird'; import * as testUtils from '../../../test-utils'; import sdk from 'matrix-react-sdk'; const MessageComposerInput = sdk.getComponent('views.rooms.MessageComposerInput'); diff --git a/test/components/views/rooms/RoomSettings-test.js b/test/components/views/rooms/RoomSettings-test.js index dd91e812bc..1c0bfd95dc 100644 --- a/test/components/views/rooms/RoomSettings-test.js +++ b/test/components/views/rooms/RoomSettings-test.js @@ -3,7 +3,6 @@ // import ReactDOM from 'react-dom'; // import expect from 'expect'; // import jest from 'jest-mock'; -// import Promise from 'bluebird'; // import * as testUtils from '../../../test-utils'; // import sdk from 'matrix-react-sdk'; // const WrappedRoomSettings = testUtils.wrapInMatrixClientContext(sdk.getComponent('views.rooms.RoomSettings')); diff --git a/test/i18n-test/languageHandler-test.js b/test/i18n-test/languageHandler-test.js index 0d96bc15ab..8f21638703 100644 --- a/test/i18n-test/languageHandler-test.js +++ b/test/i18n-test/languageHandler-test.js @@ -11,7 +11,7 @@ describe('languageHandler', function() { testUtils.beforeEach(this); sandbox = testUtils.stubClient(); - languageHandler.setLanguage('en').done(done); + languageHandler.setLanguage('en').then(done); }); afterEach(function() { diff --git a/test/stores/RoomViewStore-test.js b/test/stores/RoomViewStore-test.js index be598de8da..77dfb37b0a 100644 --- a/test/stores/RoomViewStore-test.js +++ b/test/stores/RoomViewStore-test.js @@ -1,13 +1,11 @@ import expect from 'expect'; -import dis from '../../src/dispatcher'; import RoomViewStore from '../../src/stores/RoomViewStore'; import peg from '../../src/MatrixClientPeg'; import * as testUtils from '../test-utils'; -import Promise from 'bluebird'; const dispatch = testUtils.getDispatchForStore(RoomViewStore); diff --git a/test/test-utils.js b/test/test-utils.js index ff800132b9..64704fc610 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -1,7 +1,6 @@ "use strict"; import sinon from 'sinon'; -import Promise from 'bluebird'; import React from 'react'; import PropTypes from 'prop-types'; import peg from '../src/MatrixClientPeg'; diff --git a/yarn.lock b/yarn.lock index 3e43c29ef6..1c29c0aedd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -244,6 +244,11 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/json-schema@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636" + integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A== + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -293,6 +298,28 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/experimental-utils@^2.5.0": + version "2.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.8.0.tgz#208b4164d175587e9b03ce6fea97d55f19c30ca9" + integrity sha512-jZ05E4SxCbbXseQGXOKf3ESKcsGxT8Ucpkp1jiVp55MGhOvZB2twmWKf894PAuVQTCgbPbJz9ZbRDqtUWzP8xA== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "2.8.0" + eslint-scope "^5.0.0" + +"@typescript-eslint/typescript-estree@2.8.0": + version "2.8.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.8.0.tgz#fcc3fe6532840085d29b75432c8a59895876aeca" + integrity sha512-ksvjBDTdbAQ04cR5JyFSDX113k66FxH1tAXmi+dj6hufsl/G0eMc/f1GgLjEVPkYClDbRKv+rnBFuE5EusomUw== + dependencies: + debug "^4.1.1" + eslint-visitor-keys "^1.1.0" + glob "^7.1.6" + is-glob "^4.0.1" + lodash.unescape "4.0.1" + semver "^6.3.0" + tsutils "^3.17.1" + "@webassemblyjs/ast@1.8.5": version "1.8.5" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" @@ -899,7 +926,7 @@ babel-helper-explode-assignable-expression@^6.24.1: babel-traverse "^6.24.1" babel-types "^6.24.1" -babel-helper-function-name@^6.24.1, babel-helper-function-name@^6.8.0: +babel-helper-function-name@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" integrity sha1-00dbjAPtmCQqJbSDUasYOZ01gKk= @@ -1042,16 +1069,6 @@ babel-plugin-syntax-trailing-function-commas@^6.22.0: resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" integrity sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM= -babel-plugin-transform-async-to-bluebird@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-bluebird/-/babel-plugin-transform-async-to-bluebird-1.1.1.tgz#46ea3e7c5af629782ac9f1ed1b7cd38f8425afd4" - integrity sha1-Ruo+fFr2KXgqyfHtG3zTj4Qlr9Q= - dependencies: - babel-helper-function-name "^6.8.0" - babel-plugin-syntax-async-functions "^6.8.0" - babel-template "^6.9.0" - babel-traverse "^6.10.4" - babel-plugin-transform-async-to-generator@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" @@ -1442,7 +1459,7 @@ babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtim core-js "^2.4.0" regenerator-runtime "^0.11.0" -babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.3.0, babel-template@^6.9.0: +babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.3.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI= @@ -1453,7 +1470,7 @@ babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.3.0, babel-tem babylon "^6.18.0" lodash "^4.17.4" -babel-traverse@^6.10.4, babel-traverse@^6.24.1, babel-traverse@^6.26.0: +babel-traverse@^6.24.1, babel-traverse@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= @@ -2891,6 +2908,13 @@ eslint-plugin-flowtype@^2.30.0: dependencies: lodash "^4.17.10" +eslint-plugin-jest@^23.0.4: + version "23.0.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.0.4.tgz#1ab81ffe3b16c5168efa72cbd4db14d335092aa0" + integrity sha512-OaP8hhT8chJNodUPvLJ6vl8gnalcsU/Ww1t9oR3HnGdEWjm/DdCCUXLOral+IPGAeWu/EwgVQCK/QtxALpH1Yw== + dependencies: + "@typescript-eslint/experimental-utils" "^2.5.0" + eslint-plugin-react-hooks@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.0.1.tgz#e898ec26a0a335af6f7b0ad1f0bedda7143ed756" @@ -2924,6 +2948,14 @@ eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-scope@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" + integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + eslint-utils@^1.3.1: version "1.4.2" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab" @@ -2931,7 +2963,7 @@ eslint-utils@^1.3.1: dependencies: eslint-visitor-keys "^1.0.0" -eslint-visitor-keys@^1.0.0: +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== @@ -3714,6 +3746,18 @@ glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-modules@2.0.0, global-modules@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -5038,6 +5082,11 @@ lodash.mergewith@^4.6.1: resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55" integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== +lodash.unescape@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" + integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw= + lodash@^4.1.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.2.1: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" @@ -5197,10 +5246,10 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz#6dff66c99d55ecf739ca53c492e626f1d12a33cc" integrity sha512-pWB896KPGSGkp1XtyzRBftpTzwSOL0Gfk0wLvxt4f2mgzjY19o0LxJ3U25vNWTzsh7da+KTbuXQoQ3lOJZ8WHw== -matrix-js-sdk@2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-2.4.3.tgz#23b78cc707a02eb0ce7eecb3aa50129e46dd5b6e" - integrity sha512-8qTqILd/NmTWF24tpaxmDIzkTk/bZhPD5N8h69PlvJ5Y6kMFctpRj+Tud5zZjl5/yhO07+g+JCyDzg+AagiM/A== +matrix-js-sdk@2.4.4: + version "2.4.4" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-2.4.4.tgz#d5e2d6fbe938c4275a1423a5f09330d33517ce3f" + integrity sha512-wSaRFvhWvwEzVaEkyBGo5ReumvaM5OrC1MJ6SVlyoLwH/WRPEXcUlu+rUNw5TFVEAH4TAVHXf/SVRBiR0j5nSQ== dependencies: another-json "^0.2.0" babel-runtime "^6.26.0" @@ -7149,7 +7198,7 @@ selection-is-backward@^1.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@6.3.0: +semver@6.3.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -8086,11 +8135,18 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.4.tgz#3b52b1f13924f460c3fbfd0df69b587dbcbc762e" integrity sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q== -tslib@^1.9.0: +tslib@^1.8.1, tslib@^1.9.0: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== +tsutils@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" + integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== + dependencies: + tslib "^1.8.1" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"