Merge branch 'develop' into matthew/fix_logging
1
.babelrc
|
@ -13,7 +13,6 @@
|
||||||
],
|
],
|
||||||
"transform-class-properties",
|
"transform-class-properties",
|
||||||
"transform-object-rest-spread",
|
"transform-object-rest-spread",
|
||||||
"transform-async-to-bluebird",
|
|
||||||
"transform-runtime",
|
"transform-runtime",
|
||||||
"add-module-exports",
|
"add-module-exports",
|
||||||
"syntax-dynamic-import"
|
"syntax-dynamic-import"
|
||||||
|
|
113
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)
|
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)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.7.1...v1.7.2)
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
The CIDER editor is a custom editor written for Riot.
|
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.
|
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,
|
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, ...).
|
||||||
and will soon be used as the main composer to send messages as well.
|
|
||||||
|
|
||||||
## High-level overview.
|
## High-level overview.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "1.7.2",
|
"version": "1.7.3",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -60,7 +60,6 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
"bluebird": "^3.5.0",
|
|
||||||
"blueimp-canvas-to-blob": "^3.5.0",
|
"blueimp-canvas-to-blob": "^3.5.0",
|
||||||
"browser-encrypt-attachment": "^0.3.0",
|
"browser-encrypt-attachment": "^0.3.0",
|
||||||
"browser-request": "^0.3.3",
|
"browser-request": "^0.3.3",
|
||||||
|
@ -89,7 +88,7 @@
|
||||||
"linkifyjs": "^2.1.6",
|
"linkifyjs": "^2.1.6",
|
||||||
"lodash": "^4.17.14",
|
"lodash": "^4.17.14",
|
||||||
"lolex": "4.2",
|
"lolex": "4.2",
|
||||||
"matrix-js-sdk": "2.4.3",
|
"matrix-js-sdk": "2.4.4",
|
||||||
"optimist": "^0.6.1",
|
"optimist": "^0.6.1",
|
||||||
"pako": "^1.0.5",
|
"pako": "^1.0.5",
|
||||||
"png-chunks-extract": "^1.0.0",
|
"png-chunks-extract": "^1.0.0",
|
||||||
|
@ -120,7 +119,6 @@
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.1",
|
||||||
"babel-loader": "^7.1.5",
|
"babel-loader": "^7.1.5",
|
||||||
"babel-plugin-add-module-exports": "^0.2.1",
|
"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-builtin-extend": "^1.1.2",
|
||||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||||
|
@ -135,6 +133,7 @@
|
||||||
"eslint": "^5.12.0",
|
"eslint": "^5.12.0",
|
||||||
"eslint-config-google": "^0.7.1",
|
"eslint-config-google": "^0.7.1",
|
||||||
"eslint-plugin-babel": "^5.2.1",
|
"eslint-plugin-babel": "^5.2.1",
|
||||||
|
"eslint-plugin-jest": "^23.0.4",
|
||||||
"eslint-plugin-flowtype": "^2.30.0",
|
"eslint-plugin-flowtype": "^2.30.0",
|
||||||
"eslint-plugin-react": "^7.7.0",
|
"eslint-plugin-react": "^7.7.0",
|
||||||
"eslint-plugin-react-hooks": "^2.0.1",
|
"eslint-plugin-react-hooks": "^2.0.1",
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
@import "./structures/_TabbedView.scss";
|
@import "./structures/_TabbedView.scss";
|
||||||
@import "./structures/_TagPanel.scss";
|
@import "./structures/_TagPanel.scss";
|
||||||
@import "./structures/_TagPanelButtons.scss";
|
@import "./structures/_TagPanelButtons.scss";
|
||||||
|
@import "./structures/_ToastContainer.scss";
|
||||||
@import "./structures/_TopLeftMenuButton.scss";
|
@import "./structures/_TopLeftMenuButton.scss";
|
||||||
@import "./structures/_UploadBar.scss";
|
@import "./structures/_UploadBar.scss";
|
||||||
@import "./structures/_ViewSource.scss";
|
@import "./structures/_ViewSource.scss";
|
||||||
|
@ -48,6 +49,7 @@
|
||||||
@import "./views/context_menus/_StatusMessageContextMenu.scss";
|
@import "./views/context_menus/_StatusMessageContextMenu.scss";
|
||||||
@import "./views/context_menus/_TagTileContextMenu.scss";
|
@import "./views/context_menus/_TagTileContextMenu.scss";
|
||||||
@import "./views/context_menus/_TopLeftMenu.scss";
|
@import "./views/context_menus/_TopLeftMenu.scss";
|
||||||
|
@import "./views/context_menus/_WidgetContextMenu.scss";
|
||||||
@import "./views/dialogs/_AddressPickerDialog.scss";
|
@import "./views/dialogs/_AddressPickerDialog.scss";
|
||||||
@import "./views/dialogs/_Analytics.scss";
|
@import "./views/dialogs/_Analytics.scss";
|
||||||
@import "./views/dialogs/_ChangelogDialog.scss";
|
@import "./views/dialogs/_ChangelogDialog.scss";
|
||||||
|
@ -90,6 +92,7 @@
|
||||||
@import "./views/elements/_ErrorBoundary.scss";
|
@import "./views/elements/_ErrorBoundary.scss";
|
||||||
@import "./views/elements/_EventListSummary.scss";
|
@import "./views/elements/_EventListSummary.scss";
|
||||||
@import "./views/elements/_Field.scss";
|
@import "./views/elements/_Field.scss";
|
||||||
|
@import "./views/elements/_FormButton.scss";
|
||||||
@import "./views/elements/_IconButton.scss";
|
@import "./views/elements/_IconButton.scss";
|
||||||
@import "./views/elements/_ImageView.scss";
|
@import "./views/elements/_ImageView.scss";
|
||||||
@import "./views/elements/_InlineSpinner.scss";
|
@import "./views/elements/_InlineSpinner.scss";
|
||||||
|
@ -172,7 +175,7 @@
|
||||||
@import "./views/rooms/_WhoIsTypingTile.scss";
|
@import "./views/rooms/_WhoIsTypingTile.scss";
|
||||||
@import "./views/settings/_DevicesPanel.scss";
|
@import "./views/settings/_DevicesPanel.scss";
|
||||||
@import "./views/settings/_EmailAddresses.scss";
|
@import "./views/settings/_EmailAddresses.scss";
|
||||||
@import "./views/settings/_IntegrationsManager.scss";
|
@import "./views/settings/_IntegrationManager.scss";
|
||||||
@import "./views/settings/_KeyBackupPanel.scss";
|
@import "./views/settings/_KeyBackupPanel.scss";
|
||||||
@import "./views/settings/_Notifications.scss";
|
@import "./views/settings/_Notifications.scss";
|
||||||
@import "./views/settings/_PhoneNumbers.scss";
|
@import "./views/settings/_PhoneNumbers.scss";
|
||||||
|
|
98
res/css/structures/_ToastContainer.scss
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
res/css/views/context_menus/_WidgetContextMenu.scss
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,10 +16,10 @@ limitations under the License.
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* To avoid visual glitching of two modals stacking briefly, we customise the
|
* 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.
|
* it gets the same basic size as the IM's own modal.
|
||||||
*/
|
*/
|
||||||
.mx_TermsDialog_forIntegrationsManager .mx_Dialog {
|
.mx_TermsDialog_forIntegrationManager .mx_Dialog {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
height: 70%;
|
height: 70%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
36
res/css/views/elements/_FormButton.scss
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,23 +65,6 @@ limitations under the License.
|
||||||
.mx_KeyVerification_buttons {
|
.mx_KeyVerification_buttons {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
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 {
|
.mx_KeyVerification_state {
|
||||||
|
|
|
@ -153,40 +153,12 @@ $AppsDrawerBodyHeight: 273px;
|
||||||
background-color: $accent-color;
|
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 {
|
.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_popout {
|
||||||
mask-image: url('$(res)/img/feather-customised/widget/external-link.svg');
|
mask-image: url('$(res)/img/feather-customised/widget/external-link.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_snapshot {
|
.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_menu {
|
||||||
mask-image: url('$(res)/img/feather-customised/widget/camera.svg');
|
mask-image: url('$(res)/img/icon_context.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_AppTileMenuBarWidgetDelete {
|
.mx_AppTileMenuBarWidgetDelete {
|
||||||
|
|
|
@ -15,8 +15,8 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_E2EIcon {
|
.mx_E2EIcon {
|
||||||
width: 25px;
|
width: 16px;
|
||||||
height: 25px;
|
height: 16px;
|
||||||
margin: 0 9px;
|
margin: 0 9px;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -30,16 +30,14 @@ limitations under the License.
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
mask-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
mask-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_E2EIcon_verified::after {
|
.mx_E2EIcon_verified::after {
|
||||||
mask-image: url('$(res)/img/e2e/verified.svg');
|
background-image: url('$(res)/img/e2e/verified.svg');
|
||||||
background-color: $accent-color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_E2EIcon_warning::after {
|
.mx_E2EIcon_warning::after {
|
||||||
mask-image: url('$(res)/img/e2e/warning.svg');
|
background-image: url('$(res)/img/e2e/warning.svg');
|
||||||
background-color: $warning-color;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,6 @@ limitations under the License.
|
||||||
padding-left: 84px;
|
padding-left: 84px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageComposer_wrapper.mx_MessageComposer_hasE2EIcon {
|
|
||||||
padding-left: 109px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MessageComposer_replaced_wrapper {
|
.mx_MessageComposer_replaced_wrapper {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
@ -78,10 +74,8 @@ limitations under the License.
|
||||||
.mx_MessageComposer_e2eIcon.mx_E2EIcon {
|
.mx_MessageComposer_e2eIcon.mx_E2EIcon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 60px;
|
left: 60px;
|
||||||
|
margin-right: 0; // Counteract the E2EIcon class
|
||||||
&::after {
|
margin-left: 3px; // Counteract the E2EIcon class
|
||||||
background-color: $composer-e2e-icon-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageComposer_noperm_error {
|
.mx_MessageComposer_noperm_error {
|
||||||
|
|
|
@ -17,6 +17,10 @@ limitations under the License.
|
||||||
.mx_RoomHeader {
|
.mx_RoomHeader {
|
||||||
flex: 0 0 52px;
|
flex: 0 0 52px;
|
||||||
border-bottom: 1px solid $primary-hairline-color;
|
border-bottom: 1px solid $primary-hairline-color;
|
||||||
|
|
||||||
|
.mx_E2EIcon {
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomHeader_wrapper {
|
.mx_RoomHeader_wrapper {
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_IntegrationsManager .mx_Dialog {
|
.mx_IntegrationManager .mx_Dialog {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
height: 70%;
|
height: 70%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -23,22 +23,22 @@ limitations under the License.
|
||||||
max-height: initial;
|
max-height: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_IntegrationsManager iframe {
|
.mx_IntegrationManager iframe {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_IntegrationsManager_loading h3 {
|
.mx_IntegrationManager_loading h3 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_IntegrationsManager_error {
|
.mx_IntegrationManager_error {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_IntegrationsManager_error h3 {
|
.mx_IntegrationManager_error h3 {
|
||||||
color: $warning-color;
|
color: $warning-color;
|
||||||
}
|
}
|
|
@ -14,10 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_SetIntegrationManager .mx_Field_input {
|
|
||||||
@mixin mx_Settings_fullWidthField;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SetIntegrationManager {
|
.mx_SetIntegrationManager {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
@ -32,6 +28,10 @@ limitations under the License.
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SetIntegrationManager_tooltip {
|
.mx_SetIntegrationManager .mx_ToggleSwitch {
|
||||||
@mixin mx_Settings_tooltip;
|
display: inline-block;
|
||||||
|
float: right;
|
||||||
|
top: 9px;
|
||||||
|
|
||||||
|
@mixin mx_Settings_fullWidthField;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,4 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 21C12 21 21 17.2 21 11.5V4.85L12 2L3 4.85V11.5C3 17.2 12 21 12 21Z" fill="#03B381" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.2268 7.80652C17.6053 8.17175 17.6053 8.7639 17.2268 9.12913L11.4013 14.7502C11.0228 15.1154 10.4091 15.1154 10.0306 14.7502L10.0145 14.7342C10.0084 14.7286 10.0023 14.7229 9.99635 14.7171L7.32348 12.1381C6.92604 11.7546 6.92604 11.1328 7.32348 10.7493C7.72091 10.3658 8.36528 10.3658 8.76272 10.7493L10.7838 12.6995L15.8561 7.80652C16.2346 7.44129 16.8483 7.44129 17.2268 7.80652Z" fill="white"/>
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none">
|
|
||||||
<path
|
|
||||||
style="stroke:none;fill:#03b381;fill-opacity:1"
|
|
||||||
d="M 12 2 L 3 4.8496094 L 3 11.5 C 3 17.2 12 21 12 21 C 12 21 21 17.2 21 11.5 L 21 4.8496094 L 12 2 z M 16.541016 7.5332031 C 16.789066 7.5332031 17.037312 7.6240256 17.226562 7.8066406 C 17.605062 8.1718706 17.605063 8.7636762 17.226562 9.1289062 L 11.400391 14.75 C 11.021891 15.1152 10.40975 15.1152 10.03125 14.75 L 10.013672 14.734375 C 10.007572 14.728775 10.002044 14.722597 9.9960938 14.716797 L 7.3242188 12.138672 C 6.9267788 11.755172 6.9267788 11.1335 7.3242188 10.75 C 7.7216487 10.3665 8.3662319 10.3665 8.7636719 10.75 L 10.783203 12.699219 L 15.855469 7.8066406 C 16.044719 7.6240256 16.292966 7.5332031 16.541016 7.5332031 z "
|
|
||||||
id="path2" />
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 902 B After Width: | Height: | Size: 753 B |
|
@ -1,12 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 21C12 21 21 17.2 21 11.5V4.85L12 2L3 4.85V11.5C3 17.2 12 21 12 21Z" fill="#FF4B55" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<rect x="10.8" y="5.5" width="2.5" height="8" rx="1.25" fill="white"/>
|
||||||
width="24"
|
<rect x="10.8" y="15" width="2.5" height="2.5" rx="1.25" fill="white"/>
|
||||||
height="24"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24">
|
|
||||||
<path
|
|
||||||
style="fill-opacity:1;fill:#ff4b55;stroke:none"
|
|
||||||
d="M 12 2 L 3 4.8496094 L 3 11.5 C 3 17.2 12 21 12 21 C 12 21 21 17.2 21 11.5 L 21 4.8496094 L 12 2 z M 12.050781 5.5 C 12.743281 5.5 13.300781 6.0575 13.300781 6.75 L 13.300781 12.25 C 13.300781 12.9425 12.743281 13.5 12.050781 13.5 C 11.358281 13.5 10.800781 12.9425 10.800781 12.25 L 10.800781 6.75 C 10.800781 6.0575 11.358281 5.5 12.050781 5.5 z M 12.050781 15 C 12.743281 15 13.300781 15.5575 13.300781 16.25 C 13.300781 16.9425 12.743281 17.5 12.050781 17.5 C 11.358281 17.5 10.800781 16.9425 10.800781 16.25 C 10.800781 15.5575 11.358281 15 12.050781 15 z "
|
|
||||||
id="path2" />
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 824 B After Width: | Height: | Size: 446 B |
|
@ -1,65 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="11.014242"
|
|
||||||
height="12"
|
|
||||||
viewBox="0 0 11.014242 12"
|
|
||||||
version="1.1"
|
|
||||||
id="svg6"
|
|
||||||
sodipodi:docname="bin.svg"
|
|
||||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
|
||||||
<metadata
|
|
||||||
id="metadata12">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title></dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<defs
|
|
||||||
id="defs10" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="750"
|
|
||||||
inkscape:window-height="480"
|
|
||||||
id="namedview8"
|
|
||||||
showgrid="false"
|
|
||||||
fit-margin-top="0.5"
|
|
||||||
fit-margin-left="0.5"
|
|
||||||
fit-margin-bottom="0.5"
|
|
||||||
fit-margin-right="0.5"
|
|
||||||
inkscape:zoom="19.666667"
|
|
||||||
inkscape:cx="5.5071212"
|
|
||||||
inkscape:cy="6"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="27"
|
|
||||||
inkscape:window-maximized="0"
|
|
||||||
inkscape:current-layer="svg6" />
|
|
||||||
<g
|
|
||||||
id="g4"
|
|
||||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
|
|
||||||
transform="translate(1.0071212)">
|
|
||||||
<path
|
|
||||||
d="M 0,3 H 9 M 8,3 v 7 A 1,1 0 0 1 7,11 H 2 A 1,1 0 0 1 1,10 V 3 M 2.5,3 V 2 a 1,1 0 0 1 1,-1 h 2 a 1,1 0 0 1 1,1 v 1 m -3,2.5 v 3 m 2,-3 v 3"
|
|
||||||
id="path2"
|
|
||||||
style="stroke:#000000;stroke-opacity:1"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2 KiB |
|
@ -1,6 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="11" viewBox="0 0 13 11">
|
|
||||||
<g fill="none" fill-rule="evenodd" stroke="#212121" stroke-linecap="round" stroke-linejoin="round" transform="translate(1 1)">
|
|
||||||
<path d="M11 8a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V2.5a1 1 0 0 1 1-1h2L4 0h3l1 1.5h2a1 1 0 0 1 1 1V8z"/>
|
|
||||||
<circle cx="5.5" cy="5" r="2"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 378 B |
|
@ -1,6 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="11" viewBox="0 0 12 11">
|
|
||||||
<g fill="none" fill-rule="evenodd" stroke="#212121" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M10 6.33V9a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h2.67"/>
|
|
||||||
<path d="M9 0l2 2-5 5H4V5z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 324 B |
|
@ -1,6 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="11" viewBox="0 0 13 11">
|
|
||||||
<g fill="none" fill-rule="evenodd" stroke="#212121" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M12 1.5v3H9M1 9.5v-3h3"/>
|
|
||||||
<path d="M2.255 4A4.5 4.5 0 0 1 9.68 2.32L12 4.5m-11 2l2.32 2.18A4.5 4.5 0 0 0 10.745 7"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 346 B |
|
@ -1,6 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
|
|
||||||
<g fill="none" fill-rule="evenodd" stroke="#212121" stroke-linecap="round" stroke-linejoin="round" transform="translate(1 1)">
|
|
||||||
<circle cx="5" cy="5" r="5"/>
|
|
||||||
<path d="M6.5 3.5l-3 3M3.5 3.5l3 3"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 315 B |
|
@ -12,9 +12,9 @@ $monospace-font-family: Inconsolata, Twemoji, 'Apple Color Emoji', 'Segoe UI Emo
|
||||||
// unified palette
|
// unified palette
|
||||||
// try to use these colors when possible
|
// try to use these colors when possible
|
||||||
$accent-color: #03b381;
|
$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-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;
|
$notice-secondary-color: #61708b;
|
||||||
$header-panel-bg-color: #f3f8fd;
|
$header-panel-bg-color: #f3f8fd;
|
||||||
|
|
||||||
|
|
|
@ -80,13 +80,26 @@ function play(audioId) {
|
||||||
// which listens?
|
// which listens?
|
||||||
const audio = document.getElementById(audioId);
|
const audio = document.getElementById(audioId);
|
||||||
if (audio) {
|
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]) {
|
if (audioPromises[audioId]) {
|
||||||
audioPromises[audioId] = audioPromises[audioId].then(()=>{
|
audioPromises[audioId] = audioPromises[audioId].then(()=>{
|
||||||
audio.load();
|
audio.load();
|
||||||
return audio.play();
|
return playAudio();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
audioPromises[audioId] = audio.play();
|
audioPromises[audioId] = playAudio();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -382,7 +395,7 @@ function _onAction(payload) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _startCallApp(roomId, type) {
|
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
|
// 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
|
// work for us. Better that the user knows before everyone else in the
|
||||||
// room sees it.
|
// room sees it.
|
||||||
|
@ -495,6 +508,17 @@ async function _startCallApp(roomId, type) {
|
||||||
// with the dispatcher once
|
// with the dispatcher once
|
||||||
if (!global.mxCallHandler) {
|
if (!global.mxCallHandler) {
|
||||||
dis.register(_onAction);
|
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 = {
|
const callHandler = {
|
||||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import extend from './extend';
|
import extend from './extend';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher';
|
||||||
import MatrixClientPeg from './MatrixClientPeg';
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
|
|
|
@ -16,7 +16,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import Matrix from 'matrix-js-sdk';
|
import Matrix from 'matrix-js-sdk';
|
||||||
|
|
||||||
import MatrixClientPeg from './MatrixClientPeg';
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
|
@ -526,7 +525,7 @@ export function logout() {
|
||||||
console.log("Failed to call logout API: token will not be invalidated");
|
console.log("Failed to call logout API: token will not be invalidated");
|
||||||
onLoggedOut();
|
onLoggedOut();
|
||||||
},
|
},
|
||||||
).done();
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function softLogout() {
|
export function softLogout() {
|
||||||
|
|
|
@ -23,7 +23,6 @@ import Analytics from './Analytics';
|
||||||
import sdk from './index';
|
import sdk from './index';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import Promise from "bluebird";
|
|
||||||
import {defer} from "./utils/promise";
|
import {defer} from "./utils/promise";
|
||||||
|
|
||||||
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
||||||
|
|
|
@ -146,7 +146,7 @@ const Notifier = {
|
||||||
}
|
}
|
||||||
document.body.appendChild(audioElement);
|
document.body.appendChild(audioElement);
|
||||||
}
|
}
|
||||||
audioElement.play();
|
await audioElement.play();
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.warn("Caught error when trying to fetch room notification sound:", ex);
|
console.warn("Caught error when trying to fetch room notification sound:", ex);
|
||||||
}
|
}
|
||||||
|
@ -198,7 +198,7 @@ const Notifier = {
|
||||||
|
|
||||||
if (enable) {
|
if (enable) {
|
||||||
// Attempt to get permission from user
|
// Attempt to get permission from user
|
||||||
plaf.requestNotificationPermission().done((result) => {
|
plaf.requestNotificationPermission().then((result) => {
|
||||||
if (result !== 'granted') {
|
if (result !== 'granted') {
|
||||||
// The permission request was dismissed or denied
|
// The permission request was dismissed or denied
|
||||||
// TODO: Support alternative branding in messaging
|
// TODO: Support alternative branding in messaging
|
||||||
|
|
|
@ -35,7 +35,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
resend: function(event) {
|
resend: function(event) {
|
||||||
const room = MatrixClientPeg.get().getRoom(event.getRoomId());
|
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({
|
dis.dispatch({
|
||||||
action: 'message_sent',
|
action: 'message_sent',
|
||||||
event: event,
|
event: event,
|
||||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
|
|
||||||
import MatrixClientPeg from './MatrixClientPeg';
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
import PushProcessor from 'matrix-js-sdk/lib/pushprocessor';
|
import PushProcessor from 'matrix-js-sdk/lib/pushprocessor';
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
export const ALL_MESSAGES_LOUD = 'all_messages_loud';
|
export const ALL_MESSAGES_LOUD = 'all_messages_loud';
|
||||||
export const ALL_MESSAGES = 'all_messages';
|
export const ALL_MESSAGES = 'all_messages';
|
||||||
|
|
|
@ -15,7 +15,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import MatrixClientPeg from './MatrixClientPeg';
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a room object, return the alias we should use for it,
|
* Given a room object, return the alias we should use for it,
|
||||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
import Promise from 'bluebird';
|
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import { Service, startTermsFlow, TermsNotSignedError } from './Terms';
|
import { Service, startTermsFlow, TermsNotSignedError } from './Terms';
|
||||||
const request = require('browser-request');
|
const request = require('browser-request');
|
||||||
|
|
|
@ -279,7 +279,7 @@ function inviteUser(event, roomId, userId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client.invite(roomId, userId).done(function() {
|
client.invite(roomId, userId).then(function() {
|
||||||
sendResponse(event, {
|
sendResponse(event, {
|
||||||
success: true,
|
success: true,
|
||||||
});
|
});
|
||||||
|
@ -398,7 +398,7 @@ function setPlumbingState(event, roomId, status) {
|
||||||
sendError(event, _t('You need to be logged in.'));
|
sendError(event, _t('You need to be logged in.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.sendStateEvent(roomId, "m.room.plumbing", { status: status }).done(() => {
|
client.sendStateEvent(roomId, "m.room.plumbing", { status: status }).then(() => {
|
||||||
sendResponse(event, {
|
sendResponse(event, {
|
||||||
success: true,
|
success: true,
|
||||||
});
|
});
|
||||||
|
@ -414,7 +414,7 @@ function setBotOptions(event, roomId, userId) {
|
||||||
sendError(event, _t('You need to be logged in.'));
|
sendError(event, _t('You need to be logged in.'));
|
||||||
return;
|
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, {
|
sendResponse(event, {
|
||||||
success: true,
|
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, {
|
sendResponse(event, {
|
||||||
success: true,
|
success: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,7 +28,6 @@ import { linkifyAndSanitizeHtml } from './HtmlUtils';
|
||||||
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
|
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
|
||||||
import WidgetUtils from "./utils/WidgetUtils";
|
import WidgetUtils from "./utils/WidgetUtils";
|
||||||
import {textToHtmlRainbow} from "./utils/colour";
|
import {textToHtmlRainbow} from "./utils/colour";
|
||||||
import Promise from "bluebird";
|
|
||||||
import { getAddressType } from './UserAddress';
|
import { getAddressType } from './UserAddress';
|
||||||
import { abbreviateUrl } from './utils/UrlUtils';
|
import { abbreviateUrl } from './utils/UrlUtils';
|
||||||
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/IdentityServerUtils';
|
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/IdentityServerUtils';
|
||||||
|
|
|
@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import MatrixClientPeg from './MatrixClientPeg';
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
|
|
|
@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from "bluebird";
|
|
||||||
|
|
||||||
// const OUTBOUND_API_NAME = 'toWidget';
|
// const OUTBOUND_API_NAME = 'toWidget';
|
||||||
|
|
||||||
// Initiate requests using the "toWidget" postMessage API and handle responses
|
// Initiate requests using the "toWidget" postMessage API and handle responses
|
||||||
|
|
|
@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import {createNewMatrixCall, Room} from "matrix-js-sdk";
|
import {createNewMatrixCall, Room} from "matrix-js-sdk";
|
||||||
import CallHandler from './CallHandler';
|
import CallHandler from './CallHandler';
|
||||||
import MatrixClientPeg from "./MatrixClientPeg";
|
import MatrixClientPeg from "./MatrixClientPeg";
|
||||||
|
|
|
@ -26,7 +26,6 @@ import RoomProvider from './RoomProvider';
|
||||||
import UserProvider from './UserProvider';
|
import UserProvider from './UserProvider';
|
||||||
import EmojiProvider from './EmojiProvider';
|
import EmojiProvider from './EmojiProvider';
|
||||||
import NotifProvider from './NotifProvider';
|
import NotifProvider from './NotifProvider';
|
||||||
import Promise from 'bluebird';
|
|
||||||
import {timeout} from "../utils/promise";
|
import {timeout} from "../utils/promise";
|
||||||
|
|
||||||
export type SelectionRange = {
|
export type SelectionRange = {
|
||||||
|
|
|
@ -19,7 +19,6 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Promise from 'bluebird';
|
|
||||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
|
@ -637,7 +636,7 @@ export default createReactClass({
|
||||||
title: _t('Error'),
|
title: _t('Error'),
|
||||||
description: _t('Failed to upload image'),
|
description: _t('Failed to upload image'),
|
||||||
});
|
});
|
||||||
}).done();
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_onJoinableChange: function(ev) {
|
_onJoinableChange: function(ev) {
|
||||||
|
@ -676,7 +675,7 @@ export default createReactClass({
|
||||||
this.setState({
|
this.setState({
|
||||||
avatarChanged: false,
|
avatarChanged: false,
|
||||||
});
|
});
|
||||||
}).done();
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_saveGroup: async function() {
|
_saveGroup: async function() {
|
||||||
|
|
|
@ -121,7 +121,7 @@ export default createReactClass({
|
||||||
this.setState({
|
this.setState({
|
||||||
errorText: msg,
|
errorText: msg,
|
||||||
});
|
});
|
||||||
}).done();
|
});
|
||||||
|
|
||||||
this._intervalId = null;
|
this._intervalId = null;
|
||||||
if (this.props.poll) {
|
if (this.props.poll) {
|
||||||
|
|
|
@ -525,6 +525,7 @@ const LoggedInView = createReactClass({
|
||||||
const EmbeddedPage = sdk.getComponent('structures.EmbeddedPage');
|
const EmbeddedPage = sdk.getComponent('structures.EmbeddedPage');
|
||||||
const GroupView = sdk.getComponent('structures.GroupView');
|
const GroupView = sdk.getComponent('structures.GroupView');
|
||||||
const MyGroups = sdk.getComponent('structures.MyGroups');
|
const MyGroups = sdk.getComponent('structures.MyGroups');
|
||||||
|
const ToastContainer = sdk.getComponent('structures.ToastContainer');
|
||||||
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
||||||
const CookieBar = sdk.getComponent('globals.CookieBar');
|
const CookieBar = sdk.getComponent('globals.CookieBar');
|
||||||
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
|
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
|
||||||
|
@ -628,6 +629,7 @@ const LoggedInView = createReactClass({
|
||||||
return (
|
return (
|
||||||
<div onPaste={this._onPaste} onKeyDown={this._onReactKeyDown} className='mx_MatrixChat_wrapper' aria-hidden={this.props.hideToSRUsers} onMouseDown={this._onMouseDown} onMouseUp={this._onMouseUp}>
|
<div onPaste={this._onPaste} onKeyDown={this._onReactKeyDown} className='mx_MatrixChat_wrapper' aria-hidden={this.props.hideToSRUsers} onMouseDown={this._onMouseDown} onMouseUp={this._onMouseUp}>
|
||||||
{ topBar }
|
{ topBar }
|
||||||
|
<ToastContainer />
|
||||||
<DragDropContext onDragEnd={this._onDragEnd}>
|
<DragDropContext onDragEnd={this._onDragEnd}>
|
||||||
<div ref={this._setResizeContainerRef} className={bodyClasses}>
|
<div ref={this._setResizeContainerRef} className={bodyClasses}>
|
||||||
<LeftPanel
|
<LeftPanel
|
||||||
|
|
|
@ -17,8 +17,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
@ -62,10 +60,7 @@ import { countRoomsWithNotif } from '../../RoomNotifs';
|
||||||
import { ThemeWatcher } from "../../theme";
|
import { ThemeWatcher } from "../../theme";
|
||||||
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
||||||
import { defer } from "../../utils/promise";
|
import { defer } from "../../utils/promise";
|
||||||
|
import KeyVerificationStateObserver from '../../utils/KeyVerificationStateObserver';
|
||||||
// Disable warnings for now: we use deprecated bluebird functions
|
|
||||||
// and need to migrate, but they spam the console with warnings.
|
|
||||||
Promise.config({warnings: false});
|
|
||||||
|
|
||||||
/** constants for MatrixChat.state.view */
|
/** constants for MatrixChat.state.view */
|
||||||
const VIEWS = {
|
const VIEWS = {
|
||||||
|
@ -545,7 +540,7 @@ export default createReactClass({
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
||||||
|
|
||||||
MatrixClientPeg.get().leave(payload.room_id).done(() => {
|
MatrixClientPeg.get().leave(payload.room_id).then(() => {
|
||||||
modal.close();
|
modal.close();
|
||||||
if (this.state.currentRoomId === payload.room_id) {
|
if (this.state.currentRoomId === payload.room_id) {
|
||||||
dis.dispatch({action: 'view_next_room'});
|
dis.dispatch({action: 'view_next_room'});
|
||||||
|
@ -863,7 +858,7 @@ export default createReactClass({
|
||||||
waitFor = this.firstSyncPromise.promise;
|
waitFor = this.firstSyncPromise.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
waitFor.done(() => {
|
waitFor.then(() => {
|
||||||
let presentedId = roomInfo.room_alias || roomInfo.room_id;
|
let presentedId = roomInfo.room_alias || roomInfo.room_id;
|
||||||
const room = MatrixClientPeg.get().getRoom(roomInfo.room_id);
|
const room = MatrixClientPeg.get().getRoom(roomInfo.room_id);
|
||||||
if (room) {
|
if (room) {
|
||||||
|
@ -980,7 +975,7 @@ export default createReactClass({
|
||||||
|
|
||||||
const [shouldCreate, createOpts] = await modal.finished;
|
const [shouldCreate, createOpts] = await modal.finished;
|
||||||
if (shouldCreate) {
|
if (shouldCreate) {
|
||||||
createRoom({createOpts}).done();
|
createRoom({createOpts});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1270,7 +1265,6 @@ export default createReactClass({
|
||||||
this.firstSyncComplete = false;
|
this.firstSyncComplete = false;
|
||||||
this.firstSyncPromise = defer();
|
this.firstSyncPromise = defer();
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog');
|
|
||||||
|
|
||||||
// Allow the JS SDK to reap timeline events. This reduces the amount of
|
// 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
|
// 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) => {
|
if (SettingsStore.isFeatureEnabled("feature_dm_verification")) {
|
||||||
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
|
cli.on("crypto.verification.request", request => {
|
||||||
verifier,
|
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
|
// 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
|
// A later sync can/will correct the tint to be the right value for the user
|
||||||
const colorScheme = SettingsStore.getValue("roomColor");
|
const colorScheme = SettingsStore.getValue("roomColor");
|
||||||
|
@ -1745,7 +1762,7 @@ export default createReactClass({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.sendEvent(roomId, event.getType(), event.getContent()).done(() => {
|
cli.sendEvent(roomId, event.getType(), event.getContent()).then(() => {
|
||||||
dis.dispatch({action: 'message_sent'});
|
dis.dispatch({action: 'message_sent'});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
dis.dispatch({action: 'message_send_failed'});
|
dis.dispatch({action: 'message_send_failed'});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
Copyright 2018 New Vector 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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -18,7 +19,6 @@ limitations under the License.
|
||||||
/* global Velocity */
|
/* global Velocity */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
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.
|
/* (almost) stateless UI component which builds the event tiles in the room timeline.
|
||||||
*/
|
*/
|
||||||
module.exports = createReactClass({
|
export default class MessagePanel extends React.Component {
|
||||||
displayName: 'MessagePanel',
|
static propTypes = {
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
// true to give the component a 'display: none' style.
|
// true to give the component a 'display: none' style.
|
||||||
hidden: PropTypes.bool,
|
hidden: PropTypes.bool,
|
||||||
|
|
||||||
|
@ -109,9 +107,10 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
// whether to show reactions for an event
|
// whether to show reactions for an event
|
||||||
showReactions: PropTypes.bool,
|
showReactions: PropTypes.bool,
|
||||||
},
|
};
|
||||||
|
|
||||||
componentWillMount: function() {
|
constructor() {
|
||||||
|
super();
|
||||||
// the event after which we put a visible unread marker on the last
|
// the event after which we put a visible unread marker on the last
|
||||||
// render cycle; null if readMarkerVisible was false or the RM was
|
// render cycle; null if readMarkerVisible was false or the RM was
|
||||||
// suppressed (eg because it was at the end of the timeline)
|
// suppressed (eg because it was at the end of the timeline)
|
||||||
|
@ -167,38 +166,42 @@ module.exports = createReactClass({
|
||||||
this._showHiddenEventsInTimeline =
|
this._showHiddenEventsInTimeline =
|
||||||
SettingsStore.getValue("showHiddenEventsInTimeline");
|
SettingsStore.getValue("showHiddenEventsInTimeline");
|
||||||
|
|
||||||
this._isMounted = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
|
||||||
this._isMounted = false;
|
this._isMounted = false;
|
||||||
},
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this._isMounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this._isMounted = false;
|
||||||
|
}
|
||||||
|
|
||||||
/* get the DOM node representing the given event */
|
/* get the DOM node representing the given event */
|
||||||
getNodeForEventId: function(eventId) {
|
getNodeForEventId(eventId) {
|
||||||
if (!this.eventNodes) {
|
if (!this.eventNodes) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.eventNodes[eventId];
|
return this.eventNodes[eventId];
|
||||||
},
|
}
|
||||||
|
|
||||||
/* return true if the content is fully scrolled down right now; else false.
|
/* return true if the content is fully scrolled down right now; else false.
|
||||||
*/
|
*/
|
||||||
isAtBottom: function() {
|
isAtBottom() {
|
||||||
return this.refs.scrollPanel
|
return this.refs.scrollPanel
|
||||||
&& this.refs.scrollPanel.isAtBottom();
|
&& this.refs.scrollPanel.isAtBottom();
|
||||||
},
|
}
|
||||||
|
|
||||||
/* get the current scroll state. See ScrollPanel.getScrollState for
|
/* get the current scroll state. See ScrollPanel.getScrollState for
|
||||||
* details.
|
* details.
|
||||||
*
|
*
|
||||||
* returns null if we are not mounted.
|
* returns null if we are not mounted.
|
||||||
*/
|
*/
|
||||||
getScrollState: function() {
|
getScrollState() {
|
||||||
if (!this.refs.scrollPanel) { return null; }
|
if (!this.refs.scrollPanel) { return null; }
|
||||||
return this.refs.scrollPanel.getScrollState();
|
return this.refs.scrollPanel.getScrollState();
|
||||||
},
|
}
|
||||||
|
|
||||||
// returns one of:
|
// returns one of:
|
||||||
//
|
//
|
||||||
|
@ -206,7 +209,7 @@ module.exports = createReactClass({
|
||||||
// -1: read marker is above the window
|
// -1: read marker is above the window
|
||||||
// 0: read marker is within the window
|
// 0: read marker is within the window
|
||||||
// +1: read marker is below the window
|
// +1: read marker is below the window
|
||||||
getReadMarkerPosition: function() {
|
getReadMarkerPosition() {
|
||||||
const readMarker = this.refs.readMarkerNode;
|
const readMarker = this.refs.readMarkerNode;
|
||||||
const messageWrapper = this.refs.scrollPanel;
|
const messageWrapper = this.refs.scrollPanel;
|
||||||
|
|
||||||
|
@ -226,45 +229,45 @@ module.exports = createReactClass({
|
||||||
} else {
|
} else {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/* jump to the top of the content.
|
/* jump to the top of the content.
|
||||||
*/
|
*/
|
||||||
scrollToTop: function() {
|
scrollToTop() {
|
||||||
if (this.refs.scrollPanel) {
|
if (this.refs.scrollPanel) {
|
||||||
this.refs.scrollPanel.scrollToTop();
|
this.refs.scrollPanel.scrollToTop();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/* jump to the bottom of the content.
|
/* jump to the bottom of the content.
|
||||||
*/
|
*/
|
||||||
scrollToBottom: function() {
|
scrollToBottom() {
|
||||||
if (this.refs.scrollPanel) {
|
if (this.refs.scrollPanel) {
|
||||||
this.refs.scrollPanel.scrollToBottom();
|
this.refs.scrollPanel.scrollToBottom();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page up/down.
|
* Page up/down.
|
||||||
*
|
*
|
||||||
* @param {number} mult: -1 to page up, +1 to page down
|
* @param {number} mult: -1 to page up, +1 to page down
|
||||||
*/
|
*/
|
||||||
scrollRelative: function(mult) {
|
scrollRelative(mult) {
|
||||||
if (this.refs.scrollPanel) {
|
if (this.refs.scrollPanel) {
|
||||||
this.refs.scrollPanel.scrollRelative(mult);
|
this.refs.scrollPanel.scrollRelative(mult);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scroll up/down in response to a scroll key
|
* Scroll up/down in response to a scroll key
|
||||||
*
|
*
|
||||||
* @param {KeyboardEvent} ev: the keyboard event to handle
|
* @param {KeyboardEvent} ev: the keyboard event to handle
|
||||||
*/
|
*/
|
||||||
handleScrollKey: function(ev) {
|
handleScrollKey(ev) {
|
||||||
if (this.refs.scrollPanel) {
|
if (this.refs.scrollPanel) {
|
||||||
this.refs.scrollPanel.handleScrollKey(ev);
|
this.refs.scrollPanel.handleScrollKey(ev);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/* jump to the given event id.
|
/* 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
|
* node (specifically, the bottom of it) will be positioned. If omitted, it
|
||||||
* defaults to 0.
|
* defaults to 0.
|
||||||
*/
|
*/
|
||||||
scrollToEvent: function(eventId, pixelOffset, offsetBase) {
|
scrollToEvent(eventId, pixelOffset, offsetBase) {
|
||||||
if (this.refs.scrollPanel) {
|
if (this.refs.scrollPanel) {
|
||||||
this.refs.scrollPanel.scrollToToken(eventId, pixelOffset, offsetBase);
|
this.refs.scrollPanel.scrollToToken(eventId, pixelOffset, offsetBase);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
scrollToEventIfNeeded: function(eventId) {
|
scrollToEventIfNeeded(eventId) {
|
||||||
const node = this.eventNodes[eventId];
|
const node = this.eventNodes[eventId];
|
||||||
if (node) {
|
if (node) {
|
||||||
node.scrollIntoView({block: "nearest", behavior: "instant"});
|
node.scrollIntoView({block: "nearest", behavior: "instant"});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/* check the scroll state and send out pagination requests if necessary.
|
/* check the scroll state and send out pagination requests if necessary.
|
||||||
*/
|
*/
|
||||||
checkFillState: function() {
|
checkFillState() {
|
||||||
if (this.refs.scrollPanel) {
|
if (this.refs.scrollPanel) {
|
||||||
this.refs.scrollPanel.checkFillState();
|
this.refs.scrollPanel.checkFillState();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
_isUnmounting: function() {
|
_isUnmounting() {
|
||||||
return !this._isMounted;
|
return !this._isMounted;
|
||||||
},
|
}
|
||||||
|
|
||||||
// TODO: Implement granular (per-room) hide options
|
// TODO: Implement granular (per-room) hide options
|
||||||
_shouldShowEvent: function(mxEv) {
|
_shouldShowEvent(mxEv) {
|
||||||
if (mxEv.sender && MatrixClientPeg.get().isUserIgnored(mxEv.sender.userId)) {
|
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)
|
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;
|
if (this.props.highlightedEventId === mxEv.getId()) return true;
|
||||||
|
|
||||||
return !shouldHideEvent(mxEv);
|
return !shouldHideEvent(mxEv);
|
||||||
},
|
}
|
||||||
|
|
||||||
_getEventTiles: function() {
|
_getEventTiles() {
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
|
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
|
||||||
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
|
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
|
||||||
|
@ -411,6 +414,12 @@ module.exports = createReactClass({
|
||||||
readMarkerInSummary = true;
|
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
|
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++) {
|
for (;i + 1 < this.props.events.length; i++) {
|
||||||
const collapsedMxEv = this.props.events[i + 1];
|
const collapsedMxEv = this.props.events[i + 1];
|
||||||
|
@ -596,9 +605,9 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
this.currentReadMarkerEventId = readMarkerVisible ? this.props.readMarkerEventId : null;
|
this.currentReadMarkerEventId = readMarkerVisible ? this.props.readMarkerEventId : null;
|
||||||
return ret;
|
return ret;
|
||||||
},
|
}
|
||||||
|
|
||||||
_getTilesForEvent: function(prevEvent, mxEv, last) {
|
_getTilesForEvent(prevEvent, mxEv, last) {
|
||||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
const ret = [];
|
const ret = [];
|
||||||
|
@ -691,20 +700,20 @@ module.exports = createReactClass({
|
||||||
);
|
);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
},
|
}
|
||||||
|
|
||||||
_wantsDateSeparator: function(prevEvent, nextEventDate) {
|
_wantsDateSeparator(prevEvent, nextEventDate) {
|
||||||
if (prevEvent == null) {
|
if (prevEvent == null) {
|
||||||
// first event in the panel: depends if we could back-paginate from
|
// first event in the panel: depends if we could back-paginate from
|
||||||
// here.
|
// here.
|
||||||
return !this.props.suppressFirstDateSeparator;
|
return !this.props.suppressFirstDateSeparator;
|
||||||
}
|
}
|
||||||
return wantsDateSeparator(prevEvent.getDate(), nextEventDate);
|
return wantsDateSeparator(prevEvent.getDate(), nextEventDate);
|
||||||
},
|
}
|
||||||
|
|
||||||
// Get a list of read receipts that should be shown next to this event
|
// Get a list of read receipts that should be shown next to this event
|
||||||
// Receipts are objects which have a 'userId', 'roomMember' and 'ts'.
|
// Receipts are objects which have a 'userId', 'roomMember' and 'ts'.
|
||||||
_getReadReceiptsForEvent: function(event) {
|
_getReadReceiptsForEvent(event) {
|
||||||
const myUserId = MatrixClientPeg.get().credentials.userId;
|
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||||
|
|
||||||
// get list of read receipts, sorted most recent first
|
// get list of read receipts, sorted most recent first
|
||||||
|
@ -728,12 +737,12 @@ module.exports = createReactClass({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return receipts;
|
return receipts;
|
||||||
},
|
}
|
||||||
|
|
||||||
// Get an object that maps from event ID to a list of read receipts that
|
// 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,
|
// 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.
|
// they are folded into the receipts of the last shown event.
|
||||||
_getReadReceiptsByShownEvent: function() {
|
_getReadReceiptsByShownEvent() {
|
||||||
const receiptsByEvent = {};
|
const receiptsByEvent = {};
|
||||||
const receiptsByUserId = {};
|
const receiptsByUserId = {};
|
||||||
|
|
||||||
|
@ -786,9 +795,9 @@ module.exports = createReactClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return receiptsByEvent;
|
return receiptsByEvent;
|
||||||
},
|
}
|
||||||
|
|
||||||
_getReadMarkerTile: function(visible) {
|
_getReadMarkerTile(visible) {
|
||||||
let hr;
|
let hr;
|
||||||
if (visible) {
|
if (visible) {
|
||||||
hr = <hr className="mx_RoomView_myReadMarker"
|
hr = <hr className="mx_RoomView_myReadMarker"
|
||||||
|
@ -802,9 +811,9 @@ module.exports = createReactClass({
|
||||||
{ hr }
|
{ hr }
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
_startAnimation: function(ghostNode) {
|
_startAnimation = (ghostNode) => {
|
||||||
if (this._readMarkerGhostNode) {
|
if (this._readMarkerGhostNode) {
|
||||||
Velocity.Utilities.removeData(this._readMarkerGhostNode);
|
Velocity.Utilities.removeData(this._readMarkerGhostNode);
|
||||||
}
|
}
|
||||||
|
@ -816,9 +825,9 @@ module.exports = createReactClass({
|
||||||
{duration: 400, easing: 'easeInSine',
|
{duration: 400, easing: 'easeInSine',
|
||||||
delay: 1000});
|
delay: 1000});
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
_getReadMarkerGhostTile: function() {
|
_getReadMarkerGhostTile() {
|
||||||
const hr = <hr className="mx_RoomView_myReadMarker"
|
const hr = <hr className="mx_RoomView_myReadMarker"
|
||||||
style={{opacity: 1, width: '99%'}}
|
style={{opacity: 1, width: '99%'}}
|
||||||
ref={this._startAnimation}
|
ref={this._startAnimation}
|
||||||
|
@ -833,31 +842,31 @@ module.exports = createReactClass({
|
||||||
{ hr }
|
{ hr }
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
_collectEventNode: function(eventId, node) {
|
_collectEventNode = (eventId, node) => {
|
||||||
this.eventNodes[eventId] = node;
|
this.eventNodes[eventId] = node;
|
||||||
},
|
}
|
||||||
|
|
||||||
// once dynamic content in the events load, make the scrollPanel check the
|
// once dynamic content in the events load, make the scrollPanel check the
|
||||||
// scroll offsets.
|
// scroll offsets.
|
||||||
_onHeightChanged: function() {
|
_onHeightChanged = () => {
|
||||||
const scrollPanel = this.refs.scrollPanel;
|
const scrollPanel = this.refs.scrollPanel;
|
||||||
if (scrollPanel) {
|
if (scrollPanel) {
|
||||||
scrollPanel.checkScroll();
|
scrollPanel.checkScroll();
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
_onTypingShown: function() {
|
_onTypingShown = () => {
|
||||||
const scrollPanel = this.refs.scrollPanel;
|
const scrollPanel = this.refs.scrollPanel;
|
||||||
// this will make the timeline grow, so checkScroll
|
// this will make the timeline grow, so checkScroll
|
||||||
scrollPanel.checkScroll();
|
scrollPanel.checkScroll();
|
||||||
if (scrollPanel && scrollPanel.getScrollState().stuckAtBottom) {
|
if (scrollPanel && scrollPanel.getScrollState().stuckAtBottom) {
|
||||||
scrollPanel.preventShrinking();
|
scrollPanel.preventShrinking();
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
_onTypingHidden: function() {
|
_onTypingHidden = () => {
|
||||||
const scrollPanel = this.refs.scrollPanel;
|
const scrollPanel = this.refs.scrollPanel;
|
||||||
if (scrollPanel) {
|
if (scrollPanel) {
|
||||||
// as hiding the typing notifications doesn't
|
// as hiding the typing notifications doesn't
|
||||||
|
@ -868,9 +877,9 @@ module.exports = createReactClass({
|
||||||
// reveal added padding to balance the notifs disappearing.
|
// reveal added padding to balance the notifs disappearing.
|
||||||
scrollPanel.checkScroll();
|
scrollPanel.checkScroll();
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
updateTimelineMinHeight: function() {
|
updateTimelineMinHeight() {
|
||||||
const scrollPanel = this.refs.scrollPanel;
|
const scrollPanel = this.refs.scrollPanel;
|
||||||
|
|
||||||
if (scrollPanel) {
|
if (scrollPanel) {
|
||||||
|
@ -885,16 +894,16 @@ module.exports = createReactClass({
|
||||||
scrollPanel.preventShrinking();
|
scrollPanel.preventShrinking();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
onTimelineReset: function() {
|
onTimelineReset() {
|
||||||
const scrollPanel = this.refs.scrollPanel;
|
const scrollPanel = this.refs.scrollPanel;
|
||||||
if (scrollPanel) {
|
if (scrollPanel) {
|
||||||
scrollPanel.clearPreventShrinking();
|
scrollPanel.clearPreventShrinking();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
render: function() {
|
render() {
|
||||||
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
||||||
const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile");
|
const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile");
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
@ -941,5 +950,5 @@ module.exports = createReactClass({
|
||||||
{ bottomSpinner }
|
{ bottomSpinner }
|
||||||
</ScrollPanel>
|
</ScrollPanel>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ export default createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_fetch: function() {
|
_fetch: function() {
|
||||||
this.context.matrixClient.getJoinedGroups().done((result) => {
|
this.context.matrixClient.getJoinedGroups().then((result) => {
|
||||||
this.setState({groups: result.groups, error: null});
|
this.setState({groups: result.groups, error: null});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') {
|
if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') {
|
||||||
|
|
|
@ -27,7 +27,6 @@ const dis = require('../../dispatcher');
|
||||||
|
|
||||||
import { linkifyAndSanitizeHtml } from '../../HtmlUtils';
|
import { linkifyAndSanitizeHtml } from '../../HtmlUtils';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Promise from 'bluebird';
|
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
||||||
import Analytics from '../../Analytics';
|
import Analytics from '../../Analytics';
|
||||||
|
@ -89,7 +88,7 @@ module.exports = createReactClass({
|
||||||
this.setState({protocolsLoading: false});
|
this.setState({protocolsLoading: false});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
|
MatrixClientPeg.get().getThirdpartyProtocols().then((response) => {
|
||||||
this.protocols = response;
|
this.protocols = response;
|
||||||
this.setState({protocolsLoading: false});
|
this.setState({protocolsLoading: false});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
|
@ -135,7 +134,7 @@ module.exports = createReactClass({
|
||||||
publicRooms: [],
|
publicRooms: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
});
|
});
|
||||||
this.getMoreRooms().done();
|
this.getMoreRooms();
|
||||||
},
|
},
|
||||||
|
|
||||||
getMoreRooms: function() {
|
getMoreRooms: function() {
|
||||||
|
@ -246,7 +245,7 @@ module.exports = createReactClass({
|
||||||
if (!alias) return;
|
if (!alias) return;
|
||||||
step = _t('delete the alias.');
|
step = _t('delete the alias.');
|
||||||
return MatrixClientPeg.get().deleteAlias(alias);
|
return MatrixClientPeg.get().deleteAlias(alias);
|
||||||
}).done(() => {
|
}).then(() => {
|
||||||
modal.close();
|
modal.close();
|
||||||
this.refreshRoomList();
|
this.refreshRoomList();
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
|
@ -348,7 +347,7 @@ module.exports = createReactClass({
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).done((resp) => {
|
MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).then((resp) => {
|
||||||
if (resp.length > 0 && resp[0].alias) {
|
if (resp.length > 0 && resp[0].alias) {
|
||||||
this.showRoomAlias(resp[0].alias, true);
|
this.showRoomAlias(resp[0].alias, true);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -27,7 +27,6 @@ import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Promise from 'bluebird';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {Room} from "matrix-js-sdk";
|
import {Room} from "matrix-js-sdk";
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
@ -1102,7 +1101,7 @@ module.exports = createReactClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentMessages.sharedInstance().sendStickerContentToRoom(url, this.state.room.roomId, info, text, MatrixClientPeg.get())
|
ContentMessages.sharedInstance().sendStickerContentToRoom(url, this.state.room.roomId, info, text, MatrixClientPeg.get())
|
||||||
.done(undefined, (error) => {
|
.then(undefined, (error) => {
|
||||||
if (error.name === "UnknownDeviceError") {
|
if (error.name === "UnknownDeviceError") {
|
||||||
// Let the staus bar handle this
|
// Let the staus bar handle this
|
||||||
return;
|
return;
|
||||||
|
@ -1135,7 +1134,7 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
debuglog("sending search request");
|
debuglog("sending search request");
|
||||||
const searchPromise = eventSearch(term, roomId);
|
const searchPromise = eventSearch(term, roomId);
|
||||||
this._handleSearchResult(searchPromise).done();
|
this._handleSearchResult(searchPromise);
|
||||||
},
|
},
|
||||||
|
|
||||||
_handleSearchResult: function(searchPromise) {
|
_handleSearchResult: function(searchPromise) {
|
||||||
|
@ -1306,7 +1305,7 @@ module.exports = createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onForgetClick: function() {
|
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' });
|
dis.dispatch({ action: 'view_next_room' });
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
const errCode = err.errcode || _t("unknown error code");
|
const errCode = err.errcode || _t("unknown error code");
|
||||||
|
@ -1323,7 +1322,7 @@ module.exports = createReactClass({
|
||||||
this.setState({
|
this.setState({
|
||||||
rejecting: true,
|
rejecting: true,
|
||||||
});
|
});
|
||||||
MatrixClientPeg.get().leave(this.state.roomId).done(function() {
|
MatrixClientPeg.get().leave(this.state.roomId).then(function() {
|
||||||
dis.dispatch({ action: 'view_next_room' });
|
dis.dispatch({ action: 'view_next_room' });
|
||||||
self.setState({
|
self.setState({
|
||||||
rejecting: false,
|
rejecting: false,
|
||||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Promise from 'bluebird';
|
|
||||||
import { KeyCode } from '../../Keyboard';
|
import { KeyCode } from '../../Keyboard';
|
||||||
import Timer from '../../utils/Timer';
|
import Timer from '../../utils/Timer';
|
||||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
|
|
|
@ -23,7 +23,6 @@ import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
const Matrix = require("matrix-js-sdk");
|
const Matrix = require("matrix-js-sdk");
|
||||||
const EventTimeline = Matrix.EventTimeline;
|
const EventTimeline = Matrix.EventTimeline;
|
||||||
|
@ -462,7 +461,7 @@ const TimelinePanel = createReactClass({
|
||||||
// timeline window.
|
// timeline window.
|
||||||
//
|
//
|
||||||
// see https://github.com/vector-im/vector-web/issues/1035
|
// 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; }
|
if (this.unmounted) { return; }
|
||||||
|
|
||||||
const { events, liveEvents } = this._getEvents();
|
const { events, liveEvents } = this._getEvents();
|
||||||
|
|
84
src/components/structures/ToastContainer.js
Normal file
|
@ -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 = (<div className={toastClasses}>
|
||||||
|
<h2>{title}{countIndicator}</h2>
|
||||||
|
<div className="mx_Toast_body">{React.createElement(component, toastProps)}</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerClasses = classNames("mx_ToastContainer", {
|
||||||
|
"mx_ToastContainer_stacked": isStacked,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={containerClasses} role="alert">
|
||||||
|
{toast}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -105,7 +105,7 @@ module.exports = createReactClass({
|
||||||
phase: PHASE_SENDING_EMAIL,
|
phase: PHASE_SENDING_EMAIL,
|
||||||
});
|
});
|
||||||
this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl);
|
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({
|
this.setState({
|
||||||
phase: PHASE_EMAIL_SENT,
|
phase: PHASE_EMAIL_SENT,
|
||||||
});
|
});
|
||||||
|
|
|
@ -253,7 +253,7 @@ module.exports = createReactClass({
|
||||||
this.setState({
|
this.setState({
|
||||||
busy: false,
|
busy: false,
|
||||||
});
|
});
|
||||||
}).done();
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onUsernameChanged: function(username) {
|
onUsernameChanged: function(username) {
|
||||||
|
@ -439,7 +439,7 @@ module.exports = createReactClass({
|
||||||
this.setState({
|
this.setState({
|
||||||
busy: false,
|
busy: false,
|
||||||
});
|
});
|
||||||
}).done();
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_isSupportedFlow: function(flow) {
|
_isSupportedFlow: function(flow) {
|
||||||
|
|
|
@ -43,7 +43,7 @@ module.exports = createReactClass({
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
this.setState({busy: true});
|
this.setState({busy: true});
|
||||||
const self = this;
|
const self = this;
|
||||||
cli.getProfileInfo(cli.credentials.userId).done(function(result) {
|
cli.getProfileInfo(cli.credentials.userId).then(function(result) {
|
||||||
self.setState({
|
self.setState({
|
||||||
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(result.avatar_url),
|
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(result.avatar_url),
|
||||||
busy: false,
|
busy: false,
|
||||||
|
|
|
@ -18,7 +18,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Matrix from 'matrix-js-sdk';
|
import Matrix from 'matrix-js-sdk';
|
||||||
import Promise from 'bluebird';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
@ -371,7 +370,7 @@ module.exports = createReactClass({
|
||||||
if (pushers[i].kind === 'email') {
|
if (pushers[i].kind === 'email') {
|
||||||
const emailPusher = pushers[i];
|
const emailPusher = pushers[i];
|
||||||
emailPusher.data = { brand: this.props.brand };
|
emailPusher.data = { brand: this.props.brand };
|
||||||
matrixClient.setPusher(emailPusher).done(() => {
|
matrixClient.setPusher(emailPusher).then(() => {
|
||||||
console.log("Set email branding to " + this.props.brand);
|
console.log("Set email branding to " + this.props.brand);
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
console.error("Couldn't set email branding: " + error);
|
console.error("Couldn't set email branding: " + error);
|
||||||
|
|
|
@ -441,7 +441,7 @@ export const MsisdnAuthEntry = createReactClass({
|
||||||
this.props.fail(e);
|
this.props.fail(e);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.setState({requestingToken: false});
|
this.setState({requestingToken: false});
|
||||||
}).done();
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -160,7 +160,7 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
_onClickForget: function() {
|
_onClickForget: function() {
|
||||||
// FIXME: duplicated with RoomSettings (and dead code in RoomView)
|
// 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
|
// Switch to another room view if we're currently viewing the
|
||||||
// historical room
|
// historical room
|
||||||
if (RoomViewStore.getRoomId() === this.props.room.roomId) {
|
if (RoomViewStore.getRoomId() === this.props.room.roomId) {
|
||||||
|
@ -190,7 +190,7 @@ module.exports = createReactClass({
|
||||||
this.setState({
|
this.setState({
|
||||||
roomNotifState: newState,
|
roomNotifState: newState,
|
||||||
});
|
});
|
||||||
RoomNotifs.setRoomNotifsState(roomId, newState).done(() => {
|
RoomNotifs.setRoomNotifsState(roomId, newState).then(() => {
|
||||||
// delay slightly so that the user can see their state change
|
// delay slightly so that the user can see their state change
|
||||||
// before closing the menu
|
// before closing the menu
|
||||||
return sleep(500).then(() => {
|
return sleep(500).then(() => {
|
||||||
|
|
134
src/components/views/context_menus/WidgetContextMenu.js
Normal file
|
@ -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(
|
||||||
|
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onEditClicked} key='edit'>
|
||||||
|
{_t("Edit")}
|
||||||
|
</AccessibleButton>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.onReloadClicked) {
|
||||||
|
options.push(
|
||||||
|
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onReloadClicked}
|
||||||
|
key='reload'>
|
||||||
|
{_t("Reload")}
|
||||||
|
</AccessibleButton>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.onSnapshotClicked) {
|
||||||
|
options.push(
|
||||||
|
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onSnapshotClicked}
|
||||||
|
key='snap'>
|
||||||
|
{_t("Take picture")}
|
||||||
|
</AccessibleButton>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.onDeleteClicked) {
|
||||||
|
options.push(
|
||||||
|
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onDeleteClicked}
|
||||||
|
key='delete'>
|
||||||
|
{_t("Remove for everyone")}
|
||||||
|
</AccessibleButton>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push this last so it appears last. It's always present.
|
||||||
|
options.push(
|
||||||
|
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onRevokeClicked} key='revoke'>
|
||||||
|
{_t("Remove for me")}
|
||||||
|
</AccessibleButton>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Put separators between the options
|
||||||
|
if (options.length > 1) {
|
||||||
|
const length = options.length;
|
||||||
|
for (let i = 0; i < length - 1; i++) {
|
||||||
|
const sep = <hr key={i} className="mx_WidgetContextMenu_separator" />;
|
||||||
|
|
||||||
|
// 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 <div className="mx_WidgetContextMenu">{options}</div>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -266,7 +266,7 @@ module.exports = createReactClass({
|
||||||
this.setState({
|
this.setState({
|
||||||
searchError: err.errcode ? err.message : _t('Something went wrong!'),
|
searchError: err.errcode ? err.message : _t('Something went wrong!'),
|
||||||
});
|
});
|
||||||
}).done(() => {
|
}).then(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
busy: false,
|
busy: false,
|
||||||
});
|
});
|
||||||
|
@ -379,7 +379,7 @@ module.exports = createReactClass({
|
||||||
// Do a local search immediately
|
// Do a local search immediately
|
||||||
this._doLocalSearch(query);
|
this._doLocalSearch(query);
|
||||||
}
|
}
|
||||||
}).done(() => {
|
}).then(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
busy: false,
|
busy: false,
|
||||||
});
|
});
|
||||||
|
|
|
@ -93,7 +93,7 @@ export default createReactClass({
|
||||||
this.setState({createError: e});
|
this.setState({createError: e});
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.setState({creating: false});
|
this.setState({creating: false});
|
||||||
}).done();
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_onCancel: function() {
|
_onCancel: function() {
|
||||||
|
|
57
src/components/views/dialogs/IntegrationsDisabledDialog.js
Normal file
|
@ -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 (
|
||||||
|
<BaseDialog className='mx_IntegrationsDisabledDialog' hasCancel={true}
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
|
title={_t("Integrations are disabled")}>
|
||||||
|
<div className='mx_IntegrationsDisabledDialog_content'>
|
||||||
|
<p>{_t("Enable 'Manage Integrations' in Settings to do this.")}</p>
|
||||||
|
</div>
|
||||||
|
<DialogButtons
|
||||||
|
primaryButton={_t("Settings")}
|
||||||
|
onPrimaryButtonClick={this._onOpenSettingsClick}
|
||||||
|
cancelButton={_t("OK")}
|
||||||
|
onCancel={this._onAcknowledgeClick}
|
||||||
|
/>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
55
src/components/views/dialogs/IntegrationsImpossibleDialog.js
Normal file
|
@ -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 (
|
||||||
|
<BaseDialog className='mx_IntegrationsImpossibleDialog' hasCancel={false}
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
|
title={_t("Integrations not allowed")}>
|
||||||
|
<div className='mx_IntegrationsImpossibleDialog_content'>
|
||||||
|
<p>
|
||||||
|
{_t(
|
||||||
|
"Your Riot doesn't allow you to use an Integration Manager to do this. " +
|
||||||
|
"Please contact an admin.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<DialogButtons
|
||||||
|
primaryButton={_t("OK")}
|
||||||
|
onPrimaryButtonClick={this._onAcknowledgeClick}
|
||||||
|
hasCancel={false}
|
||||||
|
/>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -78,7 +78,7 @@ export default createReactClass({
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}).done();
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
|
|
|
@ -62,7 +62,7 @@ export default createReactClass({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._addThreepid = new AddThreepid();
|
this._addThreepid = new AddThreepid();
|
||||||
this._addThreepid.addEmailAddress(emailAddress).done(() => {
|
this._addThreepid.addEmailAddress(emailAddress).then(() => {
|
||||||
Modal.createTrackedDialog('Verification Pending', '', QuestionDialog, {
|
Modal.createTrackedDialog('Verification Pending', '', QuestionDialog, {
|
||||||
title: _t("Verification Pending"),
|
title: _t("Verification Pending"),
|
||||||
description: _t(
|
description: _t(
|
||||||
|
@ -96,7 +96,7 @@ export default createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
verifyEmailAddress: function() {
|
verifyEmailAddress: function() {
|
||||||
this._addThreepid.checkEmailLinkClicked().done(() => {
|
this._addThreepid.checkEmailLinkClicked().then(() => {
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
this.setState({emailBusy: false});
|
this.setState({emailBusy: false});
|
||||||
|
|
|
@ -15,7 +15,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
|
@ -82,10 +82,10 @@ export default class TabbedIntegrationManagerDialog extends React.Component {
|
||||||
|
|
||||||
client.setTermsInteractionCallback((policyInfo, agreedUrls) => {
|
client.setTermsInteractionCallback((policyInfo, agreedUrls) => {
|
||||||
// To avoid visual glitching of two modals stacking briefly, we customise the
|
// 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.
|
// it gets the same basic size as the IM's own modal.
|
||||||
return dialogTermsInteractionCallback(
|
return dialogTermsInteractionCallback(
|
||||||
policyInfo, agreedUrls, 'mx_TermsDialog_forIntegrationsManager',
|
policyInfo, agreedUrls, 'mx_TermsDialog_forIntegrationManager',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ export default class TabbedIntegrationManagerDialog extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderTab() {
|
_renderTab() {
|
||||||
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
|
const IntegrationManager = sdk.getComponent("views.settings.IntegrationManager");
|
||||||
let uiUrl = null;
|
let uiUrl = null;
|
||||||
if (this.state.currentScalarClient) {
|
if (this.state.currentScalarClient) {
|
||||||
uiUrl = this.state.currentScalarClient.getScalarInterfaceUrlForRoom(
|
uiUrl = this.state.currentScalarClient.getScalarInterfaceUrlForRoom(
|
||||||
|
@ -148,7 +148,7 @@ export default class TabbedIntegrationManagerDialog extends React.Component {
|
||||||
this.props.integrationId,
|
this.props.integrationId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <IntegrationsManager
|
return <IntegrationManager
|
||||||
configured={true}
|
configured={true}
|
||||||
loading={this.state.currentLoading}
|
loading={this.state.currentLoading}
|
||||||
connected={this.state.currentConnected}
|
connected={this.state.currentConnected}
|
||||||
|
|
|
@ -86,7 +86,7 @@ export default class TermsDialog extends React.PureComponent {
|
||||||
case Matrix.SERVICE_TYPES.IS:
|
case Matrix.SERVICE_TYPES.IS:
|
||||||
return <div>{_t("Identity Server")}<br />({host})</div>;
|
return <div>{_t("Identity Server")}<br />({host})</div>;
|
||||||
case Matrix.SERVICE_TYPES.IM:
|
case Matrix.SERVICE_TYPES.IM:
|
||||||
return <div>{_t("Integrations Manager")}<br />({host})</div>;
|
return <div>{_t("Integration Manager")}<br />({host})</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ export default class AppPermission extends React.Component {
|
||||||
creatorUserId: PropTypes.string.isRequired,
|
creatorUserId: PropTypes.string.isRequired,
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
onPermissionGranted: PropTypes.func.isRequired,
|
onPermissionGranted: PropTypes.func.isRequired,
|
||||||
|
isRoomEncrypted: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -114,6 +115,8 @@ export default class AppPermission extends React.Component {
|
||||||
: _t("Using this widget may share data <helpIcon /> with %(widgetDomain)s.",
|
: _t("Using this widget may share data <helpIcon /> with %(widgetDomain)s.",
|
||||||
{widgetDomain: this.state.widgetDomain}, {helpIcon: () => warningTooltip});
|
{widgetDomain: this.state.widgetDomain}, {helpIcon: () => warningTooltip});
|
||||||
|
|
||||||
|
const encryptionWarning = this.props.isRoomEncrypted ? _t("Widgets do not use message encryption.") : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mx_AppPermissionWarning'>
|
<div className='mx_AppPermissionWarning'>
|
||||||
<div className='mx_AppPermissionWarning_row mx_AppPermissionWarning_bolder mx_AppPermissionWarning_smallText'>
|
<div className='mx_AppPermissionWarning_row mx_AppPermissionWarning_bolder mx_AppPermissionWarning_smallText'>
|
||||||
|
@ -128,7 +131,7 @@ export default class AppPermission extends React.Component {
|
||||||
{warning}
|
{warning}
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_AppPermissionWarning_row mx_AppPermissionWarning_smallText'>
|
<div className='mx_AppPermissionWarning_row mx_AppPermissionWarning_smallText'>
|
||||||
{_t("This widget may use cookies.")}
|
{_t("This widget may use cookies.")} {encryptionWarning}
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_AppPermissionWarning_row'>
|
<div className='mx_AppPermissionWarning_row'>
|
||||||
<AccessibleButton kind='primary_sm' onClick={this.props.onPermissionGranted}>
|
<AccessibleButton kind='primary_sm' onClick={this.props.onPermissionGranted}>
|
||||||
|
|
|
@ -35,6 +35,7 @@ import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||||
|
import {createMenu} from "../../structures/ContextualMenu";
|
||||||
|
|
||||||
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
|
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
|
||||||
const ENABLE_REACT_PERF = false;
|
const ENABLE_REACT_PERF = false;
|
||||||
|
@ -52,7 +53,7 @@ export default class AppTile extends React.Component {
|
||||||
this._onLoaded = this._onLoaded.bind(this);
|
this._onLoaded = this._onLoaded.bind(this);
|
||||||
this._onEditClick = this._onEditClick.bind(this);
|
this._onEditClick = this._onEditClick.bind(this);
|
||||||
this._onDeleteClick = this._onDeleteClick.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._onSnapshotClick = this._onSnapshotClick.bind(this);
|
||||||
this.onClickMenuBar = this.onClickMenuBar.bind(this);
|
this.onClickMenuBar = this.onClickMenuBar.bind(this);
|
||||||
this._onMinimiseClick = this._onMinimiseClick.bind(this);
|
this._onMinimiseClick = this._onMinimiseClick.bind(this);
|
||||||
|
@ -207,7 +208,7 @@ export default class AppTile extends React.Component {
|
||||||
if (!this._scalarClient) {
|
if (!this._scalarClient) {
|
||||||
this._scalarClient = defaultManager.getScalarClient();
|
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
|
// Append scalar_token as a query param if not already present
|
||||||
this._scalarClient.scalarToken = token;
|
this._scalarClient.scalarToken = token;
|
||||||
const u = url.parse(this._addWurlParams(this.props.url));
|
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);
|
return WidgetUtils.canUserModifyWidgets(this.props.room.roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onEditClick(e) {
|
_onEditClick() {
|
||||||
console.log("Edit widget ID ", this.props.id);
|
console.log("Edit widget ID ", this.props.id);
|
||||||
if (this.props.onEditClick) {
|
if (this.props.onEditClick) {
|
||||||
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");
|
console.warn("Requesting widget snapshot");
|
||||||
ActiveWidgetStore.getWidgetMessaging(this.props.id).getScreenshot()
|
ActiveWidgetStore.getWidgetMessaging(this.props.id).getScreenshot()
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
@ -360,13 +361,9 @@ export default class AppTile extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCancelClick() {
|
_onRevokeClicked() {
|
||||||
if (this.props.onDeleteClick) {
|
console.info("Revoke widget permissions - %s", this.props.id);
|
||||||
this.props.onDeleteClick();
|
this._revokeWidgetPermission();
|
||||||
} else {
|
|
||||||
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.
|
// Using Object.assign workaround as the following opens in a new window instead of a new tab.
|
||||||
// window.open(this._getSafeUrl(), '_blank', 'noopener=yes');
|
// window.open(this._getSafeUrl(), '_blank', 'noopener=yes');
|
||||||
Object.assign(document.createElement('a'),
|
Object.assign(document.createElement('a'),
|
||||||
{ target: '_blank', href: this._getSafeUrl(), rel: 'noopener'}).click();
|
{ target: '_blank', href: this._getSafeUrl(), rel: 'noopener'}).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onReloadWidgetClick(e) {
|
_onReloadWidgetClick() {
|
||||||
// Reload iframe in this way to avoid cross-origin restrictions
|
// Reload iframe in this way to avoid cross-origin restrictions
|
||||||
this.refs.appFrame.src = this.refs.appFrame.src;
|
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() {
|
render() {
|
||||||
let appTileBody;
|
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
|
// 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
|
// 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
|
// hosted on the same origin as the client will get the same access as if you clicked
|
||||||
// a link to it.
|
// a link to it.
|
||||||
|
@ -585,12 +623,14 @@ export default class AppTile extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
if (!this.state.hasPermissionToLoad) {
|
if (!this.state.hasPermissionToLoad) {
|
||||||
|
const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
||||||
appTileBody = (
|
appTileBody = (
|
||||||
<div className={appTileBodyClass}>
|
<div className={appTileBodyClass}>
|
||||||
<AppPermission
|
<AppPermission
|
||||||
roomId={this.props.room.roomId}
|
roomId={this.props.room.roomId}
|
||||||
creatorUserId={this.props.creatorUserId}
|
creatorUserId={this.props.creatorUserId}
|
||||||
url={this.state.widgetUrl}
|
url={this.state.widgetUrl}
|
||||||
|
isRoomEncrypted={isEncrypted}
|
||||||
onPermissionGranted={this._grantWidgetPermission}
|
onPermissionGranted={this._grantWidgetPermission}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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 showMinimiseButton = this.props.showMinimise && this.props.show;
|
||||||
const showMaximiseButton = 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() }
|
{ this.props.showTitle && this._getTileTitle() }
|
||||||
</span>
|
</span>
|
||||||
<span className="mx_AppTileMenuBarWidgets">
|
<span className="mx_AppTileMenuBarWidgets">
|
||||||
{ /* Reload widget */ }
|
|
||||||
{ this.props.showReload && <AccessibleButton
|
|
||||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_reload"
|
|
||||||
title={_t('Reload widget')}
|
|
||||||
onClick={this._onReloadWidgetClick}
|
|
||||||
/> }
|
|
||||||
{ /* Popout widget */ }
|
{ /* Popout widget */ }
|
||||||
{ this.props.showPopout && <AccessibleButton
|
{ this.props.showPopout && <AccessibleButton
|
||||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_popout"
|
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_popout"
|
||||||
title={_t('Popout widget')}
|
title={_t('Popout widget')}
|
||||||
onClick={this._onPopoutWidgetClick}
|
onClick={this._onPopoutWidgetClick}
|
||||||
/> }
|
/> }
|
||||||
{ /* Snapshot widget */ }
|
{ /* Context menu */ }
|
||||||
{ showPictureSnapshotButton && <AccessibleButton
|
{ <AccessibleButton
|
||||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_snapshot"
|
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_menu"
|
||||||
title={_t('Picture')}
|
title={_t('More options')}
|
||||||
onClick={this._onSnapshotClick}
|
onClick={this._onContextMenuClick}
|
||||||
/> }
|
|
||||||
{ /* Edit widget */ }
|
|
||||||
{ showEditButton && <AccessibleButton
|
|
||||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_edit"
|
|
||||||
title={_t('Edit')}
|
|
||||||
onClick={this._onEditClick}
|
|
||||||
/> }
|
|
||||||
{ /* Delete widget */ }
|
|
||||||
{ showDeleteButton && <AccessibleButton
|
|
||||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_delete"
|
|
||||||
title={_t('Delete widget')}
|
|
||||||
onClick={this._onDeleteClick}
|
|
||||||
/> }
|
|
||||||
{ /* Cancel widget */ }
|
|
||||||
{ showCancelButton && <AccessibleButton
|
|
||||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_cancel"
|
|
||||||
title={_t('Revoke widget access')}
|
|
||||||
onClick={this._onCancelClick}
|
|
||||||
/> }
|
/> }
|
||||||
</span>
|
</span>
|
||||||
</div> }
|
</div> }
|
||||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A component which wraps an EditableText, with a spinner while updates take
|
* 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.setState({busy: true});
|
||||||
|
|
||||||
this.props.getInitialValue().done(
|
this.props.getInitialValue().then(
|
||||||
(result) => {
|
(result) => {
|
||||||
if (this._unmounted) { return; }
|
if (this._unmounted) { return; }
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -83,7 +82,7 @@ export default class EditableTextContainer extends React.Component {
|
||||||
errorString: null,
|
errorString: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.props.onSubmit(value).done(
|
this.props.onSubmit(value).then(
|
||||||
() => {
|
() => {
|
||||||
if (this._unmounted) { return; }
|
if (this._unmounted) { return; }
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
|
@ -54,7 +54,7 @@ export default class ErrorBoundary extends React.PureComponent {
|
||||||
if (!PlatformPeg.get()) return;
|
if (!PlatformPeg.get()) return;
|
||||||
|
|
||||||
MatrixClientPeg.get().stopClient();
|
MatrixClientPeg.get().stopClient();
|
||||||
MatrixClientPeg.get().store.deleteAllData().done(() => {
|
MatrixClientPeg.get().store.deleteAllData().then(() => {
|
||||||
PlatformPeg.get().reload();
|
PlatformPeg.get().reload();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
28
src/components/views/elements/FormButton.js
Normal file
|
@ -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;
|
|
@ -84,7 +84,7 @@ export default class ImageView extends React.Component {
|
||||||
title: _t('Error'),
|
title: _t('Error'),
|
||||||
description: _t('You cannot delete this image. (%(code)s)', {code: code}),
|
description: _t('You cannot delete this image. (%(code)s)', {code: code}),
|
||||||
});
|
});
|
||||||
}).done();
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default class LanguageDropdown extends React.Component {
|
||||||
this.setState({langs});
|
this.setState({langs});
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.setState({langs: ['en']});
|
this.setState({langs: ['en']});
|
||||||
}).done();
|
});
|
||||||
|
|
||||||
if (!this.props.value) {
|
if (!this.props.value) {
|
||||||
// If no value is given, we start with the first
|
// If no value is given, we start with the first
|
||||||
|
|
|
@ -100,7 +100,9 @@ module.exports = createReactClass({
|
||||||
const parent = ReactDOM.findDOMNode(this).parentNode;
|
const parent = ReactDOM.findDOMNode(this).parentNode;
|
||||||
let style = {};
|
let style = {};
|
||||||
style = this._updatePosition(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, {
|
const tooltipClasses = classNames("mx_Tooltip", this.props.tooltipClassName, {
|
||||||
"mx_Tooltip_visible": this.props.visible,
|
"mx_Tooltip_visible": this.props.visible,
|
||||||
|
|
|
@ -36,7 +36,7 @@ export default createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this.context.matrixClient.getJoinedGroups().done((result) => {
|
this.context.matrixClient.getJoinedGroups().then((result) => {
|
||||||
this.setState({groups: result.groups || [], error: null});
|
this.setState({groups: result.groups || [], error: null});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
|
@ -55,7 +55,7 @@ export default class MAudioBody extends React.Component {
|
||||||
decryptFile(content.file).then(function(blob) {
|
decryptFile(content.file).then(function(blob) {
|
||||||
decryptedBlob = blob;
|
decryptedBlob = blob;
|
||||||
return URL.createObjectURL(decryptedBlob);
|
return URL.createObjectURL(decryptedBlob);
|
||||||
}).done((url) => {
|
}).then((url) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
decryptedUrl: url,
|
decryptedUrl: url,
|
||||||
decryptedBlob: decryptedBlob,
|
decryptedBlob: decryptedBlob,
|
||||||
|
|
|
@ -24,7 +24,6 @@ import MFileBody from './MFileBody';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { decryptFile } from '../../../utils/DecryptFile';
|
import { decryptFile } from '../../../utils/DecryptFile';
|
||||||
import Promise from 'bluebird';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
|
||||||
|
@ -289,7 +288,7 @@ export default class MImageBody extends React.Component {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: err,
|
error: err,
|
||||||
});
|
});
|
||||||
}).done();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remember that the user wanted to show this particular image
|
// Remember that the user wanted to show this particular image
|
||||||
|
|
|
@ -111,10 +111,10 @@ export default class MKeyVerificationRequest extends React.Component {
|
||||||
userLabelForEventRoom(fromUserId, mxEvent)}</div>);
|
userLabelForEventRoom(fromUserId, mxEvent)}</div>);
|
||||||
const isResolved = !(this.state.accepted || this.state.cancelled || this.state.done);
|
const isResolved = !(this.state.accepted || this.state.cancelled || this.state.done);
|
||||||
if (isResolved) {
|
if (isResolved) {
|
||||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
const FormButton = sdk.getComponent("elements.FormButton");
|
||||||
stateNode = (<div className="mx_KeyVerification_buttons">
|
stateNode = (<div className="mx_KeyVerification_buttons">
|
||||||
<AccessibleButton kind="decline" onClick={this._onRejectClicked}>{_t("Decline")}</AccessibleButton>
|
<FormButton kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} />
|
||||||
<AccessibleButton kind="accept" onClick={this._onAcceptClicked}>{_t("Accept")}</AccessibleButton>
|
<FormButton onClick={this._onAcceptClicked} label={_t("Accept")} />
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
} else if (isOwn) { // request sent by us
|
} else if (isOwn) { // request sent by us
|
||||||
|
|
|
@ -20,7 +20,6 @@ import createReactClass from 'create-react-class';
|
||||||
import MFileBody from './MFileBody';
|
import MFileBody from './MFileBody';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import { decryptFile } from '../../../utils/DecryptFile';
|
import { decryptFile } from '../../../utils/DecryptFile';
|
||||||
import Promise from 'bluebird';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
|
||||||
|
@ -89,7 +88,7 @@ module.exports = createReactClass({
|
||||||
const content = this.props.mxEvent.getContent();
|
const content = this.props.mxEvent.getContent();
|
||||||
if (content.file !== undefined && this.state.decryptedUrl === null) {
|
if (content.file !== undefined && this.state.decryptedUrl === null) {
|
||||||
let thumbnailPromise = Promise.resolve(null);
|
let thumbnailPromise = Promise.resolve(null);
|
||||||
if (content.info.thumbnail_file) {
|
if (content.info && content.info.thumbnail_file) {
|
||||||
thumbnailPromise = decryptFile(
|
thumbnailPromise = decryptFile(
|
||||||
content.info.thumbnail_file,
|
content.info.thumbnail_file,
|
||||||
).then(function(blob) {
|
).then(function(blob) {
|
||||||
|
@ -115,7 +114,7 @@ module.exports = createReactClass({
|
||||||
this.setState({
|
this.setState({
|
||||||
error: err,
|
error: err,
|
||||||
});
|
});
|
||||||
}).done();
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,8 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent {
|
||||||
if (room) {
|
if (room) {
|
||||||
const senders = [];
|
const senders = [];
|
||||||
for (const reactionEvent of reactionEvents) {
|
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);
|
senders.push(name);
|
||||||
}
|
}
|
||||||
const shortName = unicodeToShortcode(content);
|
const shortName = unicodeToShortcode(content);
|
||||||
|
|
|
@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
|
|
|
@ -21,7 +21,6 @@ import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import flatMap from 'lodash/flatMap';
|
import flatMap from 'lodash/flatMap';
|
||||||
import type {Completion} from '../../../autocomplete/Autocompleter';
|
import type {Completion} from '../../../autocomplete/Autocompleter';
|
||||||
import Promise from 'bluebird';
|
|
||||||
import { Room } from 'matrix-js-sdk';
|
import { Room } from 'matrix-js-sdk';
|
||||||
|
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
|
|
@ -53,7 +53,7 @@ module.exports = createReactClass({
|
||||||
);
|
);
|
||||||
}, (error)=>{
|
}, (error)=>{
|
||||||
console.error("Failed to get URL preview: " + error);
|
console.error("Failed to get URL preview: " + error);
|
||||||
}).done();
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
|
|
|
@ -248,7 +248,7 @@ module.exports = createReactClass({
|
||||||
return client.getStoredDevicesForUser(member.userId);
|
return client.getStoredDevicesForUser(member.userId);
|
||||||
}).finally(function() {
|
}).finally(function() {
|
||||||
self._cancelDeviceList = null;
|
self._cancelDeviceList = null;
|
||||||
}).done(function(devices) {
|
}).then(function(devices) {
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
// we got cancelled - presumably a different user now
|
// we got cancelled - presumably a different user now
|
||||||
return;
|
return;
|
||||||
|
@ -581,7 +581,7 @@ module.exports = createReactClass({
|
||||||
},
|
},
|
||||||
).finally(()=>{
|
).finally(()=>{
|
||||||
this.setState({ updating: this.state.updating - 1 });
|
this.setState({ updating: this.state.updating - 1 });
|
||||||
}).done();
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onPowerChange: async function(powerLevel) {
|
onPowerChange: async function(powerLevel) {
|
||||||
|
@ -638,7 +638,7 @@ module.exports = createReactClass({
|
||||||
this.setState({ updating: this.state.updating + 1 });
|
this.setState({ updating: this.state.updating + 1 });
|
||||||
createRoom({dmUserId: this.props.member.userId}).finally(() => {
|
createRoom({dmUserId: this.props.member.userId}).finally(() => {
|
||||||
this.setState({ updating: this.state.updating - 1 });
|
this.setState({ updating: this.state.updating - 1 });
|
||||||
}).done();
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onLeaveClick: function() {
|
onLeaveClick: function() {
|
||||||
|
|
|
@ -25,7 +25,6 @@ import RoomViewStore from '../../../stores/RoomViewStore';
|
||||||
import Stickerpicker from './Stickerpicker';
|
import Stickerpicker from './Stickerpicker';
|
||||||
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
|
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
|
||||||
import ContentMessages from '../../../ContentMessages';
|
import ContentMessages from '../../../ContentMessages';
|
||||||
import classNames from 'classnames';
|
|
||||||
import E2EIcon from './E2EIcon';
|
import E2EIcon from './E2EIcon';
|
||||||
|
|
||||||
function ComposerAvatar(props) {
|
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 (
|
return (
|
||||||
<div className="mx_MessageComposer">
|
<div className="mx_MessageComposer">
|
||||||
<div className={wrapperClasses}>
|
<div className="mx_MessageComposer_wrapper">
|
||||||
<div className="mx_MessageComposer_row">
|
<div className="mx_MessageComposer_row">
|
||||||
{ controls }
|
{ controls }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -460,13 +460,9 @@ export default class SlateMessageComposer extends React.Component {
|
||||||
|
|
||||||
const showFormatBar = this.state.showFormatting && this.state.inputState.isRichTextEnabled;
|
const showFormatBar = this.state.showFormatting && this.state.inputState.isRichTextEnabled;
|
||||||
|
|
||||||
const wrapperClasses = classNames({
|
|
||||||
mx_MessageComposer_wrapper: true,
|
|
||||||
mx_MessageComposer_hasE2EIcon: !!this.props.e2eStatus,
|
|
||||||
});
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_MessageComposer">
|
<div className="mx_MessageComposer">
|
||||||
<div className={wrapperClasses}>
|
<div className="mx_MessageComposer_wrapper">
|
||||||
<div className="mx_MessageComposer_row">
|
<div className="mx_MessageComposer_row">
|
||||||
{ controls }
|
{ controls }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -74,10 +74,10 @@ export default class Stickerpicker extends React.Component {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
return this.scalarClient;
|
return this.scalarClient;
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
this._imError(_td("Failed to connect to integrations server"), e);
|
this._imError(_td("Failed to connect to integration manager"), e);
|
||||||
});
|
});
|
||||||
} else {
|
} 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;
|
return stickersContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Dev note: this isn't jsdoc because it's angry.
|
||||||
|
/*
|
||||||
* Show the sticker picker overlay
|
* Show the sticker picker overlay
|
||||||
* If no stickerpacks have been added, show a link to the integration manager add sticker packs page.
|
* 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) {
|
_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
|
// XXX: Simplify by using a context menu that is positioned relative to the sticker picker button
|
||||||
|
|
||||||
const buttonRect = e.target.getBoundingClientRect();
|
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() {
|
_launchManageIntegrations() {
|
||||||
// TODO: Open the right integration manager for the widget
|
// TODO: Open the right integration manager for the widget
|
||||||
|
|
|
@ -112,7 +112,7 @@ module.exports = createReactClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
httpPromise.done(function() {
|
httpPromise.then(function() {
|
||||||
self.setState({
|
self.setState({
|
||||||
phase: self.Phases.Display,
|
phase: self.Phases.Display,
|
||||||
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(newUrl),
|
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(newUrl),
|
||||||
|
|
|
@ -25,7 +25,6 @@ const Modal = require("../../../Modal");
|
||||||
const sdk = require("../../../index");
|
const sdk = require("../../../index");
|
||||||
|
|
||||||
import dis from "../../../dispatcher";
|
import dis from "../../../dispatcher";
|
||||||
import Promise from 'bluebird';
|
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
@ -174,7 +173,7 @@ module.exports = createReactClass({
|
||||||
newPassword: "",
|
newPassword: "",
|
||||||
newPasswordConfirm: "",
|
newPasswordConfirm: "",
|
||||||
});
|
});
|
||||||
}).done();
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_optionallySetEmail: function() {
|
_optionallySetEmail: function() {
|
||||||
|
|
|
@ -52,7 +52,7 @@ export default class DevicesPanel extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadDevices() {
|
_loadDevices() {
|
||||||
MatrixClientPeg.get().getDevices().done(
|
MatrixClientPeg.get().getDevices().then(
|
||||||
(resp) => {
|
(resp) => {
|
||||||
if (this._unmounted) { return; }
|
if (this._unmounted) { return; }
|
||||||
this.setState({devices: resp.devices || []});
|
this.setState({devices: resp.devices || []});
|
||||||
|
|
|
@ -21,12 +21,9 @@ import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
|
|
||||||
export default class IntegrationsManager extends React.Component {
|
export default class IntegrationManager extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
// false to display an error saying that there is no integrations manager configured
|
// false to display an error saying that we couldn't connect to the integration manager
|
||||||
configured: PropTypes.bool.isRequired,
|
|
||||||
|
|
||||||
// false to display an error saying that we couldn't connect to the integrations manager
|
|
||||||
connected: PropTypes.bool.isRequired,
|
connected: PropTypes.bool.isRequired,
|
||||||
|
|
||||||
// true to display a loading spinner
|
// true to display a loading spinner
|
||||||
|
@ -40,7 +37,6 @@ export default class IntegrationsManager extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
configured: true,
|
|
||||||
connected: true,
|
connected: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
};
|
};
|
||||||
|
@ -70,20 +66,11 @@ export default class IntegrationsManager extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.props.configured) {
|
|
||||||
return (
|
|
||||||
<div className='mx_IntegrationsManager_error'>
|
|
||||||
<h3>{_t("No integrations server configured")}</h3>
|
|
||||||
<p>{_t("This Riot instance does not have an integrations server configured.")}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.loading) {
|
if (this.props.loading) {
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
return (
|
return (
|
||||||
<div className='mx_IntegrationsManager_loading'>
|
<div className='mx_IntegrationManager_loading'>
|
||||||
<h3>{_t("Connecting to integrations server...")}</h3>
|
<h3>{_t("Connecting to integration manager...")}</h3>
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -91,9 +78,9 @@ export default class IntegrationsManager extends React.Component {
|
||||||
|
|
||||||
if (!this.props.connected) {
|
if (!this.props.connected) {
|
||||||
return (
|
return (
|
||||||
<div className='mx_IntegrationsManager_error'>
|
<div className='mx_IntegrationManager_error'>
|
||||||
<h3>{_t("Cannot connect to integrations server")}</h3>
|
<h3>{_t("Cannot connect to integration manager")}</h3>
|
||||||
<p>{_t("The integrations server is offline or it cannot reach your homeserver.")}</p>
|
<p>{_t("The integration manager is offline or it cannot reach your homeserver.")}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import Promise from 'bluebird';
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
@ -97,7 +96,7 @@ module.exports = createReactClass({
|
||||||
phase: this.phases.LOADING,
|
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();
|
self._refreshFromServer();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -170,7 +169,7 @@ module.exports = createReactClass({
|
||||||
emailPusher.kind = null;
|
emailPusher.kind = null;
|
||||||
emailPusherPromise = MatrixClientPeg.get().setPusher(emailPusher);
|
emailPusherPromise = MatrixClientPeg.get().setPusher(emailPusher);
|
||||||
}
|
}
|
||||||
emailPusherPromise.done(() => {
|
emailPusherPromise.then(() => {
|
||||||
this._refreshFromServer();
|
this._refreshFromServer();
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
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();
|
self._refreshFromServer();
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
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();
|
self._refreshFromServer();
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
@ -398,7 +397,7 @@ module.exports = createReactClass({
|
||||||
};
|
};
|
||||||
|
|
||||||
// Then, add the new ones
|
// Then, add the new ones
|
||||||
Promise.all(removeDeferreds).done(function(resps) {
|
Promise.all(removeDeferreds).then(function(resps) {
|
||||||
const deferreds = [];
|
const deferreds = [];
|
||||||
|
|
||||||
let pushRuleVectorStateKind = self.state.vectorContentRules.vectorState;
|
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();
|
self._refreshFromServer();
|
||||||
}, onError);
|
}, onError);
|
||||||
}, onError);
|
}, onError);
|
||||||
|
@ -650,7 +649,7 @@ module.exports = createReactClass({
|
||||||
externalContentRules: self.state.externalContentRules,
|
externalContentRules: self.state.externalContentRules,
|
||||||
externalPushRules: self.state.externalPushRules,
|
externalPushRules: self.state.externalPushRules,
|
||||||
});
|
});
|
||||||
}).done();
|
});
|
||||||
|
|
||||||
MatrixClientPeg.get().getThreePids().then((r) => this.setState({threepids: r.threepids}));
|
MatrixClientPeg.get().getThreePids().then((r) => this.setState({threepids: r.threepids}));
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,13 +16,9 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {_t} from "../../../languageHandler";
|
import {_t} from "../../../languageHandler";
|
||||||
import sdk from '../../../index';
|
|
||||||
import Field from "../elements/Field";
|
|
||||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
import sdk from '../../../index';
|
||||||
import {SERVICE_TYPES} from "matrix-js-sdk";
|
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||||
import {IntegrationManagerInstance} from "../../../integrations/IntegrationManagerInstance";
|
|
||||||
import Modal from "../../../Modal";
|
|
||||||
|
|
||||||
export default class SetIntegrationManager extends React.Component {
|
export default class SetIntegrationManager extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -32,135 +28,23 @@ export default class SetIntegrationManager extends React.Component {
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
currentManager,
|
currentManager,
|
||||||
url: "", // user-entered text
|
provisioningEnabled: SettingsStore.getValue("integrationProvisioning"),
|
||||||
error: null,
|
|
||||||
busy: false,
|
|
||||||
checking: false,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUrlChanged = (ev) => {
|
onProvisioningToggled = () => {
|
||||||
const u = ev.target.value;
|
const current = this.state.provisioningEnabled;
|
||||||
this.setState({url: u});
|
SettingsStore.setValue("integrationProvisioning", null, SettingLevel.ACCOUNT, !current).catch(err => {
|
||||||
};
|
console.error("Error changing integration manager provisioning");
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
_getTooltip = () => {
|
this.setState({provisioningEnabled: current});
|
||||||
if (this.state.checking) {
|
});
|
||||||
const InlineSpinner = sdk.getComponent('views.elements.InlineSpinner');
|
this.setState({provisioningEnabled: !current});
|
||||||
return <div>
|
|
||||||
<InlineSpinner />
|
|
||||||
{ _t("Checking server") }
|
|
||||||
</div>;
|
|
||||||
} else if (this.state.error) {
|
|
||||||
return <span className="warning">{this.state.error}</span>;
|
|
||||||
} 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: (
|
|
||||||
<div>
|
|
||||||
<span className="warning">
|
|
||||||
{_t("The integration manager you have chosen does not have any terms of service.")}
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
{_t("Only continue if you trust the owner of the server.")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
button: _t("Continue"),
|
|
||||||
onFinished: async (confirmed) => {
|
|
||||||
if (!confirmed) return;
|
|
||||||
this._continueTerms(manager);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._continueTerms(manager);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
|
const ToggleSwitch = sdk.getComponent("views.elements.ToggleSwitch");
|
||||||
|
|
||||||
const currentManager = this.state.currentManager;
|
const currentManager = this.state.currentManager;
|
||||||
let managerName;
|
let managerName;
|
||||||
|
@ -168,45 +52,32 @@ export default class SetIntegrationManager extends React.Component {
|
||||||
if (currentManager) {
|
if (currentManager) {
|
||||||
managerName = `(${currentManager.name})`;
|
managerName = `(${currentManager.name})`;
|
||||||
bodyText = _t(
|
bodyText = _t(
|
||||||
"You are currently using <b>%(serverName)s</b> to manage your bots, widgets, " +
|
"Use an Integration Manager <b>(%(serverName)s)</b> to manage bots, widgets, " +
|
||||||
"and sticker packs.",
|
"and sticker packs.",
|
||||||
{serverName: currentManager.name},
|
{serverName: currentManager.name},
|
||||||
{ b: sub => <b>{sub}</b> },
|
{ b: sub => <b>{sub}</b> },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
bodyText = _t(
|
bodyText = _t("Use an Integration Manager to manage bots, widgets, and sticker packs.");
|
||||||
"Add which integration manager you want to manage your bots, widgets, " +
|
|
||||||
"and sticker packs.",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="mx_SettingsTab_section mx_SetIntegrationManager" onSubmit={this._setManager}>
|
<div className='mx_SetIntegrationManager'>
|
||||||
<div className="mx_SettingsTab_heading">
|
<div className="mx_SettingsTab_heading">
|
||||||
<span>{_t("Integration Manager")}</span>
|
<span>{_t("Manage integrations")}</span>
|
||||||
<span className="mx_SettingsTab_subheading">{managerName}</span>
|
<span className="mx_SettingsTab_subheading">{managerName}</span>
|
||||||
|
<ToggleSwitch checked={this.state.provisioningEnabled} onChange={this.onProvisioningToggled} />
|
||||||
</div>
|
</div>
|
||||||
<span className="mx_SettingsTab_subsectionText">
|
<span className="mx_SettingsTab_subsectionText">
|
||||||
{bodyText}
|
{bodyText}
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
{_t(
|
||||||
|
"Integration Managers receive configuration data, and can modify widgets, " +
|
||||||
|
"send room invites, and set power levels on your behalf.",
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<Field
|
</div>
|
||||||
label={_t("Enter a new integration manager")}
|
|
||||||
id="mx_SetIntegrationManager_newUrl"
|
|
||||||
type="text" value={this.state.url}
|
|
||||||
autoComplete="off"
|
|
||||||
onChange={this._onUrlChanged}
|
|
||||||
tooltipContent={this._getTooltip()}
|
|
||||||
tooltipClassName="mx_SetIntegrationManager_tooltip"
|
|
||||||
disabled={this.state.busy}
|
|
||||||
flagInvalid={!!this.state.error}
|
|
||||||
/>
|
|
||||||
<AccessibleButton
|
|
||||||
kind="primary_sm"
|
|
||||||
type="submit"
|
|
||||||
disabled={!this._canChange()}
|
|
||||||
onClick={this._setManager}
|
|
||||||
>{_t("Change")}</AccessibleButton>
|
|
||||||
</form>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ export default class HelpUserSettingsTab extends React.Component {
|
||||||
// stopping in the middle of the logs.
|
// stopping in the middle of the logs.
|
||||||
console.log("Clear cache & reload clicked");
|
console.log("Clear cache & reload clicked");
|
||||||
MatrixClientPeg.get().stopClient();
|
MatrixClientPeg.get().stopClient();
|
||||||
MatrixClientPeg.get().store.deleteAllData().done(() => {
|
MatrixClientPeg.get().store.deleteAllData().then(() => {
|
||||||
PlatformPeg.get().reload();
|
PlatformPeg.get().reload();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
123
src/components/views/toasts/VerificationRequestToast.js
Normal file
|
@ -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 (<div>
|
||||||
|
<div className="mx_Toast_description">{nameLabel}</div>
|
||||||
|
<div className="mx_Toast_buttons" aria-live="off">
|
||||||
|
<FormButton label={_t("Decline (%(counter)s)", {counter: this.state.counter})} kind="danger" onClick={this.cancel} />
|
||||||
|
<FormButton label={_t("Accept")} onClick={this.accept} />
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VerificationRequestToast.propTypes = {
|
||||||
|
dismiss: PropTypes.func.isRequired,
|
||||||
|
request: PropTypes.object.isRequired,
|
||||||
|
requestObserver: PropTypes.instanceOf(KeyVerificationStateObserver),
|
||||||
|
};
|
|
@ -90,6 +90,13 @@ module.exports = createReactClass({
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
call = CallHandler.getAnyActiveCall();
|
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 });
|
this.setState({ call: call });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ import { _t } from './languageHandler';
|
||||||
import dis from "./dispatcher";
|
import dis from "./dispatcher";
|
||||||
import * as Rooms from "./Rooms";
|
import * as Rooms from "./Rooms";
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import {getAddressType} from "./UserAddress";
|
import {getAddressType} from "./UserAddress";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2260,5 +2260,7 @@
|
||||||
"You cancelled": "Отказахте потвърждаването",
|
"You cancelled": "Отказахте потвърждаването",
|
||||||
"%(name)s cancelled": "%(name)s отказа",
|
"%(name)s cancelled": "%(name)s отказа",
|
||||||
"%(name)s wants to verify": "%(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)": "Опитайте нови начини да игнорирате хора (експериментално)"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1269,7 +1269,7 @@
|
||||||
"Security & Privacy": "Bezpečnost & Soukromí",
|
"Security & Privacy": "Bezpečnost & Soukromí",
|
||||||
"Encryption": "Šifrování",
|
"Encryption": "Šifrování",
|
||||||
"Once enabled, encryption cannot be disabled.": "Když se šifrování zapne, už nepůjde vypnout.",
|
"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": "Obecné",
|
||||||
"General failure": "Nějaká chyba",
|
"General failure": "Nějaká chyba",
|
||||||
"This homeserver does not support login using email address.": "Tento homeserver neumožňuje přihlášní pomocí emailu.",
|
"This homeserver does not support login using email address.": "Tento homeserver neumožňuje přihlášní pomocí emailu.",
|
||||||
|
|
|
@ -342,7 +342,7 @@
|
||||||
"Multiple integration managers": "Multiple integration managers",
|
"Multiple integration managers": "Multiple integration managers",
|
||||||
"Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)",
|
"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.",
|
"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)",
|
"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",
|
"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",
|
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
||||||
|
@ -481,6 +481,7 @@
|
||||||
"Headphones": "Headphones",
|
"Headphones": "Headphones",
|
||||||
"Folder": "Folder",
|
"Folder": "Folder",
|
||||||
"Pin": "Pin",
|
"Pin": "Pin",
|
||||||
|
"Decline (%(counter)s)": "Decline (%(counter)s)",
|
||||||
"Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:",
|
"Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:",
|
||||||
"Failed to upload profile picture!": "Failed to upload profile picture!",
|
"Failed to upload profile picture!": "Failed to upload profile picture!",
|
||||||
"Upload new:": "Upload new:",
|
"Upload new:": "Upload new:",
|
||||||
|
@ -507,11 +508,9 @@
|
||||||
"Failed to set display name": "Failed to set display name",
|
"Failed to set display name": "Failed to set display name",
|
||||||
"Disable Notifications": "Disable Notifications",
|
"Disable Notifications": "Disable Notifications",
|
||||||
"Enable Notifications": "Enable Notifications",
|
"Enable Notifications": "Enable Notifications",
|
||||||
"No integrations server configured": "No integrations server configured",
|
"Connecting to integration manager...": "Connecting to integration manager...",
|
||||||
"This Riot instance does not have an integrations server configured.": "This Riot instance does not have an integrations server configured.",
|
"Cannot connect to integration manager": "Cannot connect to integration manager",
|
||||||
"Connecting to integrations server...": "Connecting to integrations server...",
|
"The integration manager is offline or it cannot reach your homeserver.": "The integration manager is offline or it cannot reach your homeserver.",
|
||||||
"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.",
|
|
||||||
"Delete Backup": "Delete Backup",
|
"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.",
|
"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.",
|
"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",
|
"Do not use an identity server": "Do not use an identity server",
|
||||||
"Enter a new identity server": "Enter a new identity server",
|
"Enter a new identity server": "Enter a new identity server",
|
||||||
"Change": "Change",
|
"Change": "Change",
|
||||||
"Failed to update integration manager": "Failed to update integration manager",
|
"Use an Integration Manager <b>(%(serverName)s)</b> to manage bots, widgets, and sticker packs.": "Use an Integration Manager <b>(%(serverName)s)</b> to manage bots, widgets, and sticker packs.",
|
||||||
"Integration manager offline or not accessible.": "Integration manager offline or not accessible.",
|
"Use an Integration Manager to manage bots, widgets, and sticker packs.": "Use an Integration Manager to manage bots, widgets, and sticker packs.",
|
||||||
"Terms of service not accepted or the integration manager is invalid.": "Terms of service not accepted or the integration manager is invalid.",
|
"Manage integrations": "Manage integrations",
|
||||||
"Integration manager has no terms of service": "Integration manager has no terms of service",
|
"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.",
|
||||||
"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 <b>%(serverName)s</b> to manage your bots, widgets, and sticker packs.": "You are currently using <b>%(serverName)s</b> 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",
|
|
||||||
"Flair": "Flair",
|
"Flair": "Flair",
|
||||||
"Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?",
|
"Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?",
|
||||||
"Success": "Success",
|
"Success": "Success",
|
||||||
|
@ -1024,8 +1018,7 @@
|
||||||
"numbered-list": "numbered-list",
|
"numbered-list": "numbered-list",
|
||||||
"Show Text Formatting Toolbar": "Show Text Formatting Toolbar",
|
"Show Text Formatting Toolbar": "Show Text Formatting Toolbar",
|
||||||
"Hide Text Formatting Toolbar": "Hide Text Formatting Toolbar",
|
"Hide Text Formatting Toolbar": "Hide Text Formatting Toolbar",
|
||||||
"Failed to connect to integrations server": "Failed to connect to integrations server",
|
"Failed to connect to integration manager": "Failed to connect to integration manager",
|
||||||
"No integrations server is configured to manage stickers with": "No integrations server is configured to manage stickers with",
|
|
||||||
"You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled",
|
"You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled",
|
||||||
"Add some now": "Add some now",
|
"Add some now": "Add some now",
|
||||||
"Stickerpack": "Stickerpack",
|
"Stickerpack": "Stickerpack",
|
||||||
|
@ -1195,6 +1188,7 @@
|
||||||
"Widget ID": "Widget ID",
|
"Widget ID": "Widget ID",
|
||||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.": "Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.",
|
"Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.": "Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.",
|
||||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s.": "Using this widget may share data <helpIcon /> with %(widgetDomain)s.",
|
"Using this widget may share data <helpIcon /> with %(widgetDomain)s.": "Using this widget may share data <helpIcon /> with %(widgetDomain)s.",
|
||||||
|
"Widgets do not use message encryption.": "Widgets do not use message encryption.",
|
||||||
"Widget added by": "Widget added by",
|
"Widget added by": "Widget added by",
|
||||||
"This widget may use cookies.": "This widget may use cookies.",
|
"This widget may use cookies.": "This widget may use cookies.",
|
||||||
"Delete Widget": "Delete Widget",
|
"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",
|
"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",
|
"Minimize apps": "Minimize apps",
|
||||||
"Maximize apps": "Maximize apps",
|
"Maximize apps": "Maximize apps",
|
||||||
"Reload widget": "Reload widget",
|
|
||||||
"Popout widget": "Popout widget",
|
"Popout widget": "Popout widget",
|
||||||
"Picture": "Picture",
|
"More options": "More options",
|
||||||
"Revoke widget access": "Revoke widget access",
|
|
||||||
"Create new room": "Create new room",
|
"Create new room": "Create new room",
|
||||||
"Unblacklist": "Unblacklist",
|
"Unblacklist": "Unblacklist",
|
||||||
"Blacklist": "Blacklist",
|
"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.",
|
"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...",
|
"Waiting for partner to confirm...": "Waiting for partner to confirm...",
|
||||||
"Incoming Verification Request": "Incoming Verification Request",
|
"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.",
|
"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.",
|
"Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.",
|
||||||
"Start verification": "Start verification",
|
"Start verification": "Start verification",
|
||||||
|
@ -1475,7 +1471,7 @@
|
||||||
"Missing session data": "Missing session data",
|
"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.",
|
"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.",
|
"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",
|
"Find others by phone or email": "Find others by phone or email",
|
||||||
"Be found by phone or email": "Be found 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",
|
"Use bots, bridges, widgets and sticker packs": "Use bots, bridges, widgets and sticker packs",
|
||||||
|
@ -1564,6 +1560,10 @@
|
||||||
"Hide": "Hide",
|
"Hide": "Hide",
|
||||||
"Home": "Home",
|
"Home": "Home",
|
||||||
"Sign in": "Sign in",
|
"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",
|
"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.",
|
"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",
|
"Custom Server Options": "Custom Server Options",
|
||||||
|
@ -1694,6 +1694,7 @@
|
||||||
"Review terms and conditions": "Review terms and conditions",
|
"Review terms and conditions": "Review terms and conditions",
|
||||||
"Old cryptography data detected": "Old cryptography data detected",
|
"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.",
|
"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",
|
"Logout": "Logout",
|
||||||
"%(creator)s created and configured the room.": "%(creator)s created and configured the room.",
|
"%(creator)s created and configured the room.": "%(creator)s created and configured the room.",
|
||||||
"Your Communities": "Your Communities",
|
"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 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.",
|
"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",
|
"Failed to load timeline position": "Failed to load timeline position",
|
||||||
|
" (1/%(totalCount)s)": " (1/%(totalCount)s)",
|
||||||
"Guest": "Guest",
|
"Guest": "Guest",
|
||||||
"Your profile": "Your profile",
|
"Your profile": "Your profile",
|
||||||
"Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others",
|
"Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others",
|
||||||
|
|