Merge branch 'develop' into travis/well-known
This commit is contained in:
commit
6f0f930e0a
140 changed files with 4922 additions and 683 deletions
2
.babelrc
2
.babelrc
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"presets": ["react", "es2015", "es2016"],
|
"presets": ["react", "es2015", "es2016"],
|
||||||
"plugins": ["transform-class-properties", "transform-object-rest-spread", "transform-async-to-bluebird", "transform-runtime", "add-module-exports"]
|
"plugins": ["transform-class-properties", "transform-object-rest-spread", "transform-async-to-bluebird", "transform-runtime", "add-module-exports", "syntax-dynamic-import"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,8 @@ src/component-index.js
|
||||||
src/components/structures/BottomLeftMenu.js
|
src/components/structures/BottomLeftMenu.js
|
||||||
src/components/structures/CompatibilityPage.js
|
src/components/structures/CompatibilityPage.js
|
||||||
src/components/structures/CreateRoom.js
|
src/components/structures/CreateRoom.js
|
||||||
src/components/structures/HomePage.js
|
|
||||||
src/components/structures/LeftPanel.js
|
|
||||||
src/components/structures/LoggedInView.js
|
src/components/structures/LoggedInView.js
|
||||||
src/components/structures/login/ForgotPassword.js
|
src/components/structures/login/ForgotPassword.js
|
||||||
src/components/structures/LoginBox.js
|
|
||||||
src/components/structures/MessagePanel.js
|
src/components/structures/MessagePanel.js
|
||||||
src/components/structures/NotificationPanel.js
|
src/components/structures/NotificationPanel.js
|
||||||
src/components/structures/RoomDirectory.js
|
src/components/structures/RoomDirectory.js
|
||||||
|
@ -22,22 +19,17 @@ src/components/structures/SearchBox.js
|
||||||
src/components/structures/TimelinePanel.js
|
src/components/structures/TimelinePanel.js
|
||||||
src/components/structures/UploadBar.js
|
src/components/structures/UploadBar.js
|
||||||
src/components/structures/UserSettings.js
|
src/components/structures/UserSettings.js
|
||||||
src/components/structures/ViewSource.js
|
|
||||||
src/components/views/avatars/BaseAvatar.js
|
src/components/views/avatars/BaseAvatar.js
|
||||||
src/components/views/avatars/MemberAvatar.js
|
src/components/views/avatars/MemberAvatar.js
|
||||||
src/components/views/create_room/RoomAlias.js
|
src/components/views/create_room/RoomAlias.js
|
||||||
src/components/views/dialogs/ChangelogDialog.js
|
|
||||||
src/components/views/dialogs/DeactivateAccountDialog.js
|
src/components/views/dialogs/DeactivateAccountDialog.js
|
||||||
src/components/views/dialogs/SetPasswordDialog.js
|
src/components/views/dialogs/SetPasswordDialog.js
|
||||||
src/components/views/dialogs/UnknownDeviceDialog.js
|
src/components/views/dialogs/UnknownDeviceDialog.js
|
||||||
src/components/views/directory/NetworkDropdown.js
|
src/components/views/directory/NetworkDropdown.js
|
||||||
src/components/views/elements/AddressSelector.js
|
src/components/views/elements/AddressSelector.js
|
||||||
src/components/views/elements/DeviceVerifyButtons.js
|
|
||||||
src/components/views/elements/DirectorySearchBox.js
|
src/components/views/elements/DirectorySearchBox.js
|
||||||
src/components/views/elements/ImageView.js
|
src/components/views/elements/ImageView.js
|
||||||
src/components/views/elements/InlineSpinner.js
|
|
||||||
src/components/views/elements/MemberEventListSummary.js
|
src/components/views/elements/MemberEventListSummary.js
|
||||||
src/components/views/elements/Spinner.js
|
|
||||||
src/components/views/elements/TintableSvg.js
|
src/components/views/elements/TintableSvg.js
|
||||||
src/components/views/elements/UserSelector.js
|
src/components/views/elements/UserSelector.js
|
||||||
src/components/views/globals/MatrixToolbar.js
|
src/components/views/globals/MatrixToolbar.js
|
||||||
|
@ -90,7 +82,6 @@ src/MatrixClientPeg.js
|
||||||
src/Modal.js
|
src/Modal.js
|
||||||
src/notifications/ContentRules.js
|
src/notifications/ContentRules.js
|
||||||
src/notifications/PushRuleVectorState.js
|
src/notifications/PushRuleVectorState.js
|
||||||
src/notifications/StandardActions.js
|
|
||||||
src/notifications/VectorPushRulesDefinitions.js
|
src/notifications/VectorPushRulesDefinitions.js
|
||||||
src/Notifier.js
|
src/Notifier.js
|
||||||
src/PlatformPeg.js
|
src/PlatformPeg.js
|
||||||
|
@ -111,7 +102,6 @@ src/utils/MultiInviter.js
|
||||||
src/utils/Receipt.js
|
src/utils/Receipt.js
|
||||||
src/VectorConferenceHandler.js
|
src/VectorConferenceHandler.js
|
||||||
src/Velociraptor.js
|
src/Velociraptor.js
|
||||||
src/VelocityBounce.js
|
|
||||||
src/WhoIsTyping.js
|
src/WhoIsTyping.js
|
||||||
src/wrappers/withMatrixClient.js
|
src/wrappers/withMatrixClient.js
|
||||||
test/components/structures/login/Registration-test.js
|
test/components/structures/login/Registration-test.js
|
||||||
|
|
|
@ -27,12 +27,15 @@ npm run build
|
||||||
npm run test
|
npm run test
|
||||||
popd
|
popd
|
||||||
|
|
||||||
# run end to end tests
|
if [ "$TRAVIS_BRANCH" = "develop" ]
|
||||||
git clone https://github.com/matrix-org/matrix-react-end-to-end-tests.git --branch master
|
then
|
||||||
pushd matrix-react-end-to-end-tests
|
# run end to end tests
|
||||||
ln -s $REACT_SDK_DIR/$RIOT_WEB_DIR riot/riot-web
|
git clone https://github.com/matrix-org/matrix-react-end-to-end-tests.git --branch master
|
||||||
# PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh
|
pushd matrix-react-end-to-end-tests
|
||||||
# CHROME_PATH=$(which google-chrome-stable) ./run.sh
|
ln -s $REACT_SDK_DIR/$RIOT_WEB_DIR riot/riot-web
|
||||||
./install.sh
|
# PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh
|
||||||
./run.sh --travis
|
# CHROME_PATH=$(which google-chrome-stable) ./run.sh
|
||||||
popd
|
./install.sh
|
||||||
|
./run.sh --travis
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
|
126
CHANGELOG.md
126
CHANGELOG.md
|
@ -1,3 +1,129 @@
|
||||||
|
Changes in [0.14.5](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.5) (2018-11-19)
|
||||||
|
=====================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.5-rc.2...v0.14.5)
|
||||||
|
|
||||||
|
* No changes since rc.1
|
||||||
|
|
||||||
|
Changes in [0.14.5-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.5-rc.2) (2018-11-15)
|
||||||
|
===============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.5-rc.1...v0.14.5-rc.2)
|
||||||
|
|
||||||
|
* Update to js-sdk v0.14.0-rc.1 which uses the new Olm API
|
||||||
|
(v0.14.0-rc.1 was broken because of this).
|
||||||
|
|
||||||
|
Changes in [0.14.5-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.5-rc.1) (2018-11-15)
|
||||||
|
===============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.4...v0.14.5-rc.1)
|
||||||
|
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#2278](https://github.com/matrix-org/matrix-react-sdk/pull/2278)
|
||||||
|
* Support room IDs and event permalinks in the join command
|
||||||
|
[\#2272](https://github.com/matrix-org/matrix-react-sdk/pull/2272)
|
||||||
|
* Align encrypted event buttons in Safari
|
||||||
|
[\#2274](https://github.com/matrix-org/matrix-react-sdk/pull/2274)
|
||||||
|
* Align buttons in encrypted event dialog
|
||||||
|
[\#2273](https://github.com/matrix-org/matrix-react-sdk/pull/2273)
|
||||||
|
* Add visible guest warning to encourage login
|
||||||
|
[\#2268](https://github.com/matrix-org/matrix-react-sdk/pull/2268)
|
||||||
|
* Regenerate the room list when m.fully_read is issued
|
||||||
|
[\#2266](https://github.com/matrix-org/matrix-react-sdk/pull/2266)
|
||||||
|
* Remove the request-only stuff we don't need anymore
|
||||||
|
[\#2263](https://github.com/matrix-org/matrix-react-sdk/pull/2263)
|
||||||
|
* Improve performance of room list and fix timestamp ordering when pinning
|
||||||
|
rooms
|
||||||
|
[\#2265](https://github.com/matrix-org/matrix-react-sdk/pull/2265)
|
||||||
|
* Add options to pin unread/mentioned rooms to the top of the room list
|
||||||
|
[\#1936](https://github.com/matrix-org/matrix-react-sdk/pull/1936)
|
||||||
|
* only run e2e tests on PRs targeted on develop
|
||||||
|
[\#2261](https://github.com/matrix-org/matrix-react-sdk/pull/2261)
|
||||||
|
* Fix and test matrix.to alias permalinks
|
||||||
|
[\#2254](https://github.com/matrix-org/matrix-react-sdk/pull/2254)
|
||||||
|
* click-through svg on tag tile context menu to make it less weird
|
||||||
|
[\#2257](https://github.com/matrix-org/matrix-react-sdk/pull/2257)
|
||||||
|
* Hide Matthew's Time Machine
|
||||||
|
[\#2256](https://github.com/matrix-org/matrix-react-sdk/pull/2256)
|
||||||
|
* Update babel-eslint to 8.1.1
|
||||||
|
[\#2255](https://github.com/matrix-org/matrix-react-sdk/pull/2255)
|
||||||
|
* Support routing matrix.to links to joinable rooms
|
||||||
|
[\#2250](https://github.com/matrix-org/matrix-react-sdk/pull/2250)
|
||||||
|
* Fix autoreplacement of ascii emoji
|
||||||
|
[\#2253](https://github.com/matrix-org/matrix-react-sdk/pull/2253)
|
||||||
|
* Repair DevTools button padding by centralizing styles
|
||||||
|
[\#2252](https://github.com/matrix-org/matrix-react-sdk/pull/2252)
|
||||||
|
* Redirect widgets to another location before deleting them
|
||||||
|
[\#2232](https://github.com/matrix-org/matrix-react-sdk/pull/2232)
|
||||||
|
* disable e2e tests for PRs targeted at experimental (redesign)
|
||||||
|
[\#2251](https://github.com/matrix-org/matrix-react-sdk/pull/2251)
|
||||||
|
* Fix emoji replacement in composer
|
||||||
|
[\#2247](https://github.com/matrix-org/matrix-react-sdk/pull/2247)
|
||||||
|
* Add a devtools button to roomsettings
|
||||||
|
[\#2249](https://github.com/matrix-org/matrix-react-sdk/pull/2249)
|
||||||
|
* Add warning when administrator leaves community (#5724)
|
||||||
|
[\#2242](https://github.com/matrix-org/matrix-react-sdk/pull/2242)
|
||||||
|
|
||||||
|
Changes in [0.14.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.4) (2018-11-13)
|
||||||
|
=====================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.3...v0.14.4)
|
||||||
|
|
||||||
|
* Include change that was supposed to be included in orevious version
|
||||||
|
|
||||||
|
Changes in [0.14.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.3) (2018-11-13)
|
||||||
|
=====================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.2...v0.14.3)
|
||||||
|
|
||||||
|
* Add banner with login/register links for users who aren't logged in
|
||||||
|
|
||||||
|
Changes in [0.14.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.2) (2018-10-29)
|
||||||
|
=====================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.2-rc.1...v0.14.2)
|
||||||
|
|
||||||
|
* Fix autoreplacement of ascii emoji
|
||||||
|
[\#2258](https://github.com/matrix-org/matrix-react-sdk/pull/2258)
|
||||||
|
|
||||||
|
Changes in [0.14.2-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.2-rc.1) (2018-10-24)
|
||||||
|
===============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.1...v0.14.2-rc.1)
|
||||||
|
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#2244](https://github.com/matrix-org/matrix-react-sdk/pull/2244)
|
||||||
|
* Show the group member list again
|
||||||
|
[\#2223](https://github.com/matrix-org/matrix-react-sdk/pull/2223)
|
||||||
|
* lint: make colorScheme camel case
|
||||||
|
[\#2237](https://github.com/matrix-org/matrix-react-sdk/pull/2237)
|
||||||
|
* Change leave room button text, OK -> Leave
|
||||||
|
[\#2236](https://github.com/matrix-org/matrix-react-sdk/pull/2236)
|
||||||
|
* Move all dialog buttons to the right and fix their order
|
||||||
|
[\#2231](https://github.com/matrix-org/matrix-react-sdk/pull/2231)
|
||||||
|
* Add a bit of text to explain the purpose of the RoomPreviewSpinner
|
||||||
|
[\#2225](https://github.com/matrix-org/matrix-react-sdk/pull/2225)
|
||||||
|
* Move the login box from the left sidebar to where the composer is
|
||||||
|
[\#2219](https://github.com/matrix-org/matrix-react-sdk/pull/2219)
|
||||||
|
* Fix an error where React doesn't like value=null on a select
|
||||||
|
[\#2230](https://github.com/matrix-org/matrix-react-sdk/pull/2230)
|
||||||
|
* add missing sticker translation
|
||||||
|
[\#2216](https://github.com/matrix-org/matrix-react-sdk/pull/2216)
|
||||||
|
* Support m.login.terms during registration
|
||||||
|
[\#2221](https://github.com/matrix-org/matrix-react-sdk/pull/2221)
|
||||||
|
* Don't show the invite nag bar when peeking
|
||||||
|
[\#2220](https://github.com/matrix-org/matrix-react-sdk/pull/2220)
|
||||||
|
* Apply the user's tint once the MatrixClientPeg is moderately ready
|
||||||
|
[\#2214](https://github.com/matrix-org/matrix-react-sdk/pull/2214)
|
||||||
|
* Make rageshake use less memory
|
||||||
|
[\#2217](https://github.com/matrix-org/matrix-react-sdk/pull/2217)
|
||||||
|
* Fix autocomplete
|
||||||
|
[\#2212](https://github.com/matrix-org/matrix-react-sdk/pull/2212)
|
||||||
|
* Explain feature states in a lot more detail
|
||||||
|
[\#2211](https://github.com/matrix-org/matrix-react-sdk/pull/2211)
|
||||||
|
* Fix various lint errors
|
||||||
|
[\#2213](https://github.com/matrix-org/matrix-react-sdk/pull/2213)
|
||||||
|
|
||||||
|
Changes in [0.14.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.1) (2018-10-19)
|
||||||
|
=====================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.0...v0.14.1)
|
||||||
|
|
||||||
|
* Apply the user's tint once the MatrixClientPeg is moderately ready
|
||||||
|
[\#2214](https://github.com/matrix-org/matrix-react-sdk/pull/2214)
|
||||||
|
|
||||||
Changes in [0.14.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.0) (2018-10-16)
|
Changes in [0.14.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.0) (2018-10-16)
|
||||||
=====================================================================================================
|
=====================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.0-rc.1...v0.14.0)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.0-rc.1...v0.14.0)
|
||||||
|
|
|
@ -4,7 +4,7 @@ set -e
|
||||||
|
|
||||||
export NVM_DIR="$HOME/.nvm"
|
export NVM_DIR="$HOME/.nvm"
|
||||||
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
||||||
nvm use 6
|
nvm use 10
|
||||||
|
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
|
|
11
package.json
11
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "0.14.0",
|
"version": "0.14.5",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -53,6 +53,7 @@
|
||||||
"test-multi": "karma start"
|
"test-multi": "karma start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
"bluebird": "^3.5.0",
|
"bluebird": "^3.5.0",
|
||||||
"blueimp-canvas-to-blob": "^3.5.0",
|
"blueimp-canvas-to-blob": "^3.5.0",
|
||||||
|
@ -75,7 +76,7 @@
|
||||||
"linkifyjs": "^2.1.6",
|
"linkifyjs": "^2.1.6",
|
||||||
"lodash": "^4.13.1",
|
"lodash": "^4.13.1",
|
||||||
"lolex": "2.3.2",
|
"lolex": "2.3.2",
|
||||||
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
|
"matrix-js-sdk": "0.14.0",
|
||||||
"optimist": "^0.6.1",
|
"optimist": "^0.6.1",
|
||||||
"pako": "^1.0.5",
|
"pako": "^1.0.5",
|
||||||
"prop-types": "^15.5.8",
|
"prop-types": "^15.5.8",
|
||||||
|
@ -100,7 +101,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-cli": "^6.26.0",
|
"babel-cli": "^6.26.0",
|
||||||
"babel-core": "^6.26.3",
|
"babel-core": "^6.26.3",
|
||||||
"babel-eslint": "^6.1.2",
|
"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-async-to-bluebird": "^1.1.1",
|
||||||
|
@ -114,9 +115,9 @@
|
||||||
"babel-preset-react": "^6.24.1",
|
"babel-preset-react": "^6.24.1",
|
||||||
"chokidar": "^1.6.1",
|
"chokidar": "^1.6.1",
|
||||||
"concurrently": "^4.0.1",
|
"concurrently": "^4.0.1",
|
||||||
"eslint": "^3.13.1",
|
"eslint": "^5.8.0",
|
||||||
"eslint-config-google": "^0.7.1",
|
"eslint-config-google": "^0.7.1",
|
||||||
"eslint-plugin-babel": "^4.1.2",
|
"eslint-plugin-babel": "^5.2.1",
|
||||||
"eslint-plugin-flowtype": "^2.30.0",
|
"eslint-plugin-flowtype": "^2.30.0",
|
||||||
"eslint-plugin-react": "^7.7.0",
|
"eslint-plugin-react": "^7.7.0",
|
||||||
"estree-walker": "^0.5.0",
|
"estree-walker": "^0.5.0",
|
||||||
|
|
|
@ -170,8 +170,7 @@ textarea {
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 58px;
|
padding: 0 58px 36px;
|
||||||
padding-bottom: 36px;
|
|
||||||
width: 60%;
|
width: 60%;
|
||||||
max-width: 704px;
|
max-width: 704px;
|
||||||
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2);
|
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2);
|
||||||
|
@ -216,12 +215,16 @@ textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Dialog_content {
|
.mx_Dialog_content {
|
||||||
margin: 24px 58px 68px 0;
|
margin: 24px 0 68px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_Dialog_buttons {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_Dialog button, .mx_Dialog input[type="submit"] {
|
.mx_Dialog button, .mx_Dialog input[type="submit"] {
|
||||||
@mixin mx_DialogButton;
|
@mixin mx_DialogButton;
|
||||||
margin-left: 0px;
|
margin-left: 0px;
|
||||||
|
@ -259,6 +262,11 @@ textarea {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_linkButton {
|
||||||
|
cursor: pointer;
|
||||||
|
color: $accent-color;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_Dialog_title {
|
.mx_Dialog_title {
|
||||||
min-height: 16px;
|
min-height: 16px;
|
||||||
padding-top: 40px;
|
padding-top: 40px;
|
||||||
|
|
|
@ -33,18 +33,21 @@
|
||||||
@import "./views/dialogs/_ChatInviteDialog.scss";
|
@import "./views/dialogs/_ChatInviteDialog.scss";
|
||||||
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
|
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
|
||||||
@import "./views/dialogs/_CreateGroupDialog.scss";
|
@import "./views/dialogs/_CreateGroupDialog.scss";
|
||||||
|
@import "./views/dialogs/_CreateKeyBackupDialog.scss";
|
||||||
@import "./views/dialogs/_CreateRoomDialog.scss";
|
@import "./views/dialogs/_CreateRoomDialog.scss";
|
||||||
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
||||||
@import "./views/dialogs/_DevtoolsDialog.scss";
|
@import "./views/dialogs/_DevtoolsDialog.scss";
|
||||||
@import "./views/dialogs/_EncryptedEventDialog.scss";
|
@import "./views/dialogs/_EncryptedEventDialog.scss";
|
||||||
@import "./views/dialogs/_GroupAddressPicker.scss";
|
@import "./views/dialogs/_GroupAddressPicker.scss";
|
||||||
@import "./views/dialogs/_QuestionDialog.scss";
|
@import "./views/dialogs/_RestoreKeyBackupDialog.scss";
|
||||||
@import "./views/dialogs/_RoomUpgradeDialog.scss";
|
@import "./views/dialogs/_RoomUpgradeDialog.scss";
|
||||||
@import "./views/dialogs/_SetEmailDialog.scss";
|
@import "./views/dialogs/_SetEmailDialog.scss";
|
||||||
@import "./views/dialogs/_SetMxIdDialog.scss";
|
@import "./views/dialogs/_SetMxIdDialog.scss";
|
||||||
@import "./views/dialogs/_SetPasswordDialog.scss";
|
@import "./views/dialogs/_SetPasswordDialog.scss";
|
||||||
@import "./views/dialogs/_ShareDialog.scss";
|
@import "./views/dialogs/_ShareDialog.scss";
|
||||||
@import "./views/dialogs/_UnknownDeviceDialog.scss";
|
@import "./views/dialogs/_UnknownDeviceDialog.scss";
|
||||||
|
@import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss";
|
||||||
|
@import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss";
|
||||||
@import "./views/directory/_NetworkDropdown.scss";
|
@import "./views/directory/_NetworkDropdown.scss";
|
||||||
@import "./views/elements/_AccessibleButton.scss";
|
@import "./views/elements/_AccessibleButton.scss";
|
||||||
@import "./views/elements/_AddressSelector.scss";
|
@import "./views/elements/_AddressSelector.scss";
|
||||||
|
@ -108,6 +111,7 @@
|
||||||
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
||||||
@import "./views/settings/_DevicesPanel.scss";
|
@import "./views/settings/_DevicesPanel.scss";
|
||||||
@import "./views/settings/_IntegrationsManager.scss";
|
@import "./views/settings/_IntegrationsManager.scss";
|
||||||
|
@import "./views/settings/_KeyBackupPanel.scss";
|
||||||
@import "./views/settings/_Notifications.scss";
|
@import "./views/settings/_Notifications.scss";
|
||||||
@import "./views/voip/_CallView.scss";
|
@import "./views/voip/_CallView.scss";
|
||||||
@import "./views/voip/_IncomingCallbox.scss";
|
@import "./views/voip/_IncomingCallbox.scss";
|
||||||
|
|
|
@ -33,3 +33,16 @@ limitations under the License.
|
||||||
.mx_HomePage_body {
|
.mx_HomePage_body {
|
||||||
// margin-left: 63px;
|
// margin-left: 63px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_HomePage_guest_warning {
|
||||||
|
display: flex;
|
||||||
|
background-color: $secondary-accent-color;
|
||||||
|
border: 1px solid $accent-color;
|
||||||
|
margin: 20px;
|
||||||
|
padding: 20px 40px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_HomePage_guest_warning img {
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
||||||
height: unset !important;
|
height: unset !important;
|
||||||
padding-top: 13px !important;
|
padding-top: 13px !important;
|
||||||
padding-bottom: 14px !important;
|
padding-bottom: 14px !important;
|
||||||
|
order: 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_LoginBox_loginButton_wrapper {
|
.mx_LoginBox_loginButton_wrapper {
|
||||||
|
|
|
@ -142,6 +142,17 @@ limitations under the License.
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_Login_sso_link {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Login_sso_link:link {
|
||||||
|
color: $primary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_Login_loader {
|
.mx_Login_loader {
|
||||||
display: inline;
|
display: inline;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -25,6 +25,10 @@ limitations under the License.
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_TagTileContextMenu_item object {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.mx_TagTileContextMenu_item_icon {
|
.mx_TagTileContextMenu_item_icon {
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
|
|
|
@ -14,14 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_ChatInviteDialog {
|
|
||||||
/* XXX: padding-left is on mx_Dialog but padding-right has subsequently
|
|
||||||
* been added on other dialogs. Surely all our dialogs should have consistent
|
|
||||||
* right hand padding?
|
|
||||||
*/
|
|
||||||
padding-right: 58px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Using a textarea for this element, to circumvent autofill */
|
/* Using a textarea for this element, to circumvent autofill */
|
||||||
.mx_ChatInviteDialog_input,
|
.mx_ChatInviteDialog_input,
|
||||||
.mx_ChatInviteDialog_input:focus
|
.mx_ChatInviteDialog_input:focus
|
||||||
|
|
25
res/css/views/dialogs/_CreateKeyBackupDialog.scss
Normal file
25
res/css/views/dialogs/_CreateKeyBackupDialog.scss
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
|
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_CreateKeyBackupDialog {
|
||||||
|
padding-right: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateKeyBackupDialog_recoveryKey {
|
||||||
|
padding: 20px;
|
||||||
|
color: $info-plinth-fg-color;
|
||||||
|
background-color: $info-plinth-bg-color;
|
||||||
|
}
|
|
@ -25,3 +25,7 @@ limitations under the License.
|
||||||
background-color: $primary-bg-color;
|
background-color: $primary-bg-color;
|
||||||
color: $accent-color;
|
color: $accent-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_EncryptedEventDialog button {
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 New Vector Ltd.
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
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.
|
||||||
|
@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
.mx_QuestionDialog {
|
|
||||||
padding-right: 58px;
|
.mx_RestoreKeyBackupDialog_keyStatus {
|
||||||
|
height: 30px;
|
||||||
}
|
}
|
|
@ -14,11 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_ShareDialog {
|
|
||||||
// this is to center the content
|
|
||||||
padding-right: 58px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_ShareDialog hr {
|
.mx_ShareDialog hr {
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
|
|
|
@ -20,9 +20,6 @@ limitations under the License.
|
||||||
// is a pain in the ass. plus might as well make the dialog big given how
|
// is a pain in the ass. plus might as well make the dialog big given how
|
||||||
// important it is.
|
// important it is.
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
// position the gemini scrollbar nicely
|
|
||||||
padding-right: 58px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_UnknownDeviceDialog {
|
.mx_UnknownDeviceDialog {
|
||||||
|
|
39
res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss
Normal file
39
res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
|
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_CreateKeyBackupDialog_primaryContainer {
|
||||||
|
/*FIXME: plinth colour in new theme(s). background-color: $accent-color;*/
|
||||||
|
padding: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateKeyBackupDialog_passPhraseInput {
|
||||||
|
width: 300px;
|
||||||
|
border: 1px solid $accent-color;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateKeyBackupDialog_passPhraseMatch {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateKeyBackupDialog_recoveryKeyButtons {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateKeyBackupDialog_recoveryKey {
|
||||||
|
width: 300px;
|
||||||
|
}
|
29
res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss
Normal file
29
res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
|
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_RestoreKeyBackupDialog_primaryContainer {
|
||||||
|
/*FIXME: plinth colour in new theme(s). background-color: $accent-color;*/
|
||||||
|
padding: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RestoreKeyBackupDialog_passPhraseInput,
|
||||||
|
.mx_RestoreKeyBackupDialog_recoveryKeyInput {
|
||||||
|
width: 300px;
|
||||||
|
border: 1px solid $accent-color;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2107 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
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.
|
||||||
|
|
|
@ -35,8 +35,24 @@ limitations under the License.
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_InteractiveAuthEntryComponents_termsSubmit {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
// XXX: This should be a common button class
|
// XXX: This should be a common button class
|
||||||
.mx_InteractiveAuthEntryComponents_msisdnSubmit:disabled {
|
.mx_InteractiveAuthEntryComponents_msisdnSubmit:disabled {
|
||||||
background-color: $light-fg-color;
|
background-color: $light-fg-color;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_InteractiveAuthEntryComponents_termsSubmit:disabled {
|
||||||
|
background-color: $accent-color-50pct;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_InteractiveAuthEntryComponents_termsPolicy {
|
||||||
|
display: block;
|
||||||
|
}
|
|
@ -46,3 +46,14 @@ limitations under the License.
|
||||||
.mx_MImageBody_thumbnail_spinner > * {
|
.mx_MImageBody_thumbnail_spinner > * {
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MImageBody_gifLabel {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
top: 0px;
|
||||||
|
left: 14px;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: $imagebody-giflabel;
|
||||||
|
border: 2px solid $imagebody-giflabel-border;
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MemberDeviceInfo.mx_DeviceVerifyButtons {
|
.mx_MemberDeviceInfo.mx_DeviceVerifyButtons {
|
||||||
padding: 6px 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
|
@ -111,6 +111,3 @@ limitations under the License.
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MemberList_outerWrapper {
|
|
||||||
height: 0px;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2107 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
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.
|
||||||
|
|
|
@ -56,3 +56,7 @@ limitations under the License.
|
||||||
.mx_RoomPreviewBar_warningIcon {
|
.mx_RoomPreviewBar_warningIcon {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RoomPreviewBar_spinnerIntro {
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
|
@ -28,6 +28,13 @@ limitations under the License.
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RoomSettings_devtoolsButton {
|
||||||
|
@mixin mx_DialogButton;
|
||||||
|
position: relative;
|
||||||
|
padding: 4px 1.5em;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_RoomSettings_upgradeButton,
|
.mx_RoomSettings_upgradeButton,
|
||||||
.mx_RoomSettings_leaveButton:hover,
|
.mx_RoomSettings_leaveButton:hover,
|
||||||
.mx_RoomSettings_unbanButton:hover {
|
.mx_RoomSettings_unbanButton:hover {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
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.
|
||||||
|
@ -14,25 +14,19 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
.mx_KeyBackupPanel_sigValid, .mx_KeyBackupPanel_sigInvalid,
|
||||||
|
.mx_KeyBackupPanel_deviceVerified, .mx_KeyBackupPanel_deviceNotVerified {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
import React from 'react';
|
.mx_KeyBackupPanel_sigValid, .mx_KeyBackupPanel_deviceVerified {
|
||||||
import PropTypes from 'prop-types';
|
color: $e2e-verified-color;
|
||||||
import { _t } from '../../../languageHandler';
|
}
|
||||||
|
|
||||||
module.exports = React.createClass({
|
.mx_KeyBackupPanel_sigInvalid, .mx_KeyBackupPanel_deviceNotVerified {
|
||||||
displayName: 'CasLogin',
|
color: $e2e-warning-color;
|
||||||
|
}
|
||||||
|
|
||||||
propTypes: {
|
.mx_KeyBackupPanel_deviceName {
|
||||||
onSubmit: PropTypes.func, // fn()
|
font-style: italic;
|
||||||
},
|
}
|
||||||
|
|
||||||
render: function() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<button onClick={this.props.onSubmit}>{ _t("Sign in with CAS") }</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
|
@ -12,6 +12,7 @@ $light-fg-color: #747474;
|
||||||
// button UI (white-on-green in light skin)
|
// button UI (white-on-green in light skin)
|
||||||
$accent-fg-color: $primary-bg-color;
|
$accent-fg-color: $primary-bg-color;
|
||||||
$accent-color: #76CFA6;
|
$accent-color: #76CFA6;
|
||||||
|
$accent-color-50pct: #76CFA67F;
|
||||||
|
|
||||||
$selection-fg-color: $primary-fg-color;
|
$selection-fg-color: $primary-fg-color;
|
||||||
|
|
||||||
|
@ -143,6 +144,9 @@ $lightbox-bg-color: #454545;
|
||||||
$lightbox-fg-color: #ffffff;
|
$lightbox-fg-color: #ffffff;
|
||||||
$lightbox-border-color: #ffffff;
|
$lightbox-border-color: #ffffff;
|
||||||
|
|
||||||
|
$imagebody-giflabel: rgba(1, 1, 1, 0.7);
|
||||||
|
$imagebody-giflabel-border: rgba(1, 1, 1, 0.2);
|
||||||
|
|
||||||
// unused?
|
// unused?
|
||||||
$progressbar-color: #000;
|
$progressbar-color: #000;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ $focus-bg-color: #dddddd;
|
||||||
// button UI (white-on-green in light skin)
|
// button UI (white-on-green in light skin)
|
||||||
$accent-fg-color: #ffffff;
|
$accent-fg-color: #ffffff;
|
||||||
$accent-color: #76CFA6;
|
$accent-color: #76CFA6;
|
||||||
|
$accent-color-50pct: #76CFA67F;
|
||||||
|
|
||||||
$selection-fg-color: $primary-bg-color;
|
$selection-fg-color: $primary-bg-color;
|
||||||
|
|
||||||
|
@ -148,6 +149,9 @@ $lightbox-bg-color: #454545;
|
||||||
$lightbox-fg-color: #ffffff;
|
$lightbox-fg-color: #ffffff;
|
||||||
$lightbox-border-color: #ffffff;
|
$lightbox-border-color: #ffffff;
|
||||||
|
|
||||||
|
$imagebody-giflabel: rgba(0, 0, 0, 0.7);
|
||||||
|
$imagebody-giflabel-border: rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
// unused?
|
// unused?
|
||||||
$progressbar-color: #000;
|
$progressbar-color: #000;
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,6 @@ import _clamp from 'lodash/clamp';
|
||||||
type MessageFormat = 'rich' | 'markdown';
|
type MessageFormat = 'rich' | 'markdown';
|
||||||
|
|
||||||
class HistoryItem {
|
class HistoryItem {
|
||||||
|
|
||||||
// We store history items in their native format to ensure history is accurate
|
// We store history items in their native format to ensure history is accurate
|
||||||
// and then convert them if our RTE has subsequently changed format.
|
// and then convert them if our RTE has subsequently changed format.
|
||||||
value: Value;
|
value: Value;
|
||||||
|
|
|
@ -78,7 +78,6 @@ class MemberEntity extends Entity {
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserEntity extends Entity {
|
class UserEntity extends Entity {
|
||||||
|
|
||||||
constructor(model, showInviteButton, inviteFn) {
|
constructor(model, showInviteButton, inviteFn) {
|
||||||
super(model);
|
super(model);
|
||||||
this.showInviteButton = Boolean(showInviteButton);
|
this.showInviteButton = Boolean(showInviteButton);
|
||||||
|
|
|
@ -64,7 +64,7 @@ export function containsEmoji(str) {
|
||||||
* because we want to include emoji shortnames in title text
|
* because we want to include emoji shortnames in title text
|
||||||
*/
|
*/
|
||||||
function unicodeToImage(str) {
|
function unicodeToImage(str) {
|
||||||
let replaceWith, unicode, alt, short, fname;
|
let replaceWith; let unicode; let alt; let short; let fname;
|
||||||
const mappedUnicode = emojione.mapUnicodeToShort();
|
const mappedUnicode = emojione.mapUnicodeToShort();
|
||||||
|
|
||||||
str = str.replace(emojione.regUnicode, function(unicodeChar) {
|
str = str.replace(emojione.regUnicode, function(unicodeChar) {
|
||||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Matrix from "matrix-js-sdk";
|
import Matrix from "matrix-js-sdk";
|
||||||
import { _t } from "./languageHandler";
|
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
|
@ -225,19 +224,18 @@ export default class Login {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectToCas() {
|
getSsoLoginUrl(loginType) {
|
||||||
const client = this._createTemporaryClient();
|
const client = this._createTemporaryClient();
|
||||||
const parsedUrl = url.parse(window.location.href, true);
|
const parsedUrl = url.parse(window.location.href, true);
|
||||||
|
|
||||||
// XXX: at this point, the fragment will always be #/login, which is no
|
// XXX: at this point, the fragment will always be #/login, which is no
|
||||||
// use to anyone. Ideally, we would get the intended fragment from
|
// use to anyone. Ideally, we would get the intended fragment from
|
||||||
// MatrixChat.screenAfterLogin so that you could follow #/room links etc
|
// MatrixChat.screenAfterLogin so that you could follow #/room links etc
|
||||||
// through a CAS login.
|
// through an SSO login.
|
||||||
parsedUrl.hash = "";
|
parsedUrl.hash = "";
|
||||||
|
|
||||||
parsedUrl.query["homeserver"] = client.getHomeserverUrl();
|
parsedUrl.query["homeserver"] = client.getHomeserverUrl();
|
||||||
parsedUrl.query["identityServer"] = client.getIdentityServerUrl();
|
parsedUrl.query["identityServer"] = client.getIdentityServerUrl();
|
||||||
const casUrl = client.getCasLoginUrl(url.format(parsedUrl));
|
return client.getSsoLoginUrl(url.format(parsedUrl), loginType);
|
||||||
window.location.href = casUrl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,12 @@ import Matrix from 'matrix-js-sdk';
|
||||||
import utils from 'matrix-js-sdk/lib/utils';
|
import utils from 'matrix-js-sdk/lib/utils';
|
||||||
import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline';
|
import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline';
|
||||||
import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set';
|
import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set';
|
||||||
|
import sdk from './index';
|
||||||
import createMatrixClient from './utils/createMatrixClient';
|
import createMatrixClient from './utils/createMatrixClient';
|
||||||
import SettingsStore from './settings/SettingsStore';
|
import SettingsStore from './settings/SettingsStore';
|
||||||
import MatrixActionCreators from './actions/MatrixActionCreators';
|
import MatrixActionCreators from './actions/MatrixActionCreators';
|
||||||
import {phasedRollOutExpiredForUser} from "./PhasedRollOut";
|
import {phasedRollOutExpiredForUser} from "./PhasedRollOut";
|
||||||
|
import Modal from './Modal';
|
||||||
|
|
||||||
interface MatrixClientCreds {
|
interface MatrixClientCreds {
|
||||||
homeserverUrl: string,
|
homeserverUrl: string,
|
||||||
|
@ -116,6 +118,14 @@ class MatrixClientPeg {
|
||||||
await this.matrixClient.initCrypto();
|
await this.matrixClient.initCrypto();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (e.name === 'InvalidCryptoStoreError') {
|
||||||
|
// The js-sdk found a crypto DB too new for it to use
|
||||||
|
const CryptoStoreTooNewDialog =
|
||||||
|
sdk.getComponent("views.dialogs.CryptoStoreTooNewDialog");
|
||||||
|
Modal.createDialog(CryptoStoreTooNewDialog, {
|
||||||
|
host: window.location.host,
|
||||||
|
});
|
||||||
|
}
|
||||||
// this can happen for a number of reasons, the most likely being
|
// this can happen for a number of reasons, the most likely being
|
||||||
// that the olm library was missing. It's not fatal.
|
// that the olm library was missing. It's not fatal.
|
||||||
console.warn("Unable to initialise e2e: " + e);
|
console.warn("Unable to initialise e2e: " + e);
|
||||||
|
|
48
src/Modal.js
48
src/Modal.js
|
@ -23,6 +23,7 @@ import PropTypes from 'prop-types';
|
||||||
import Analytics from './Analytics';
|
import Analytics from './Analytics';
|
||||||
import sdk from './index';
|
import sdk from './index';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher';
|
||||||
|
import { _t } from './languageHandler';
|
||||||
|
|
||||||
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
||||||
|
|
||||||
|
@ -32,15 +33,15 @@ const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
||||||
*/
|
*/
|
||||||
const AsyncWrapper = React.createClass({
|
const AsyncWrapper = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
/** A function which takes a 'callback' argument which it will call
|
/** A promise which resolves with the real component
|
||||||
* with the real component once it loads.
|
|
||||||
*/
|
*/
|
||||||
loader: PropTypes.func.isRequired,
|
prom: PropTypes.object.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
component: null,
|
component: null,
|
||||||
|
error: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -49,14 +50,18 @@ const AsyncWrapper = React.createClass({
|
||||||
// XXX: temporary logging to try to diagnose
|
// XXX: temporary logging to try to diagnose
|
||||||
// https://github.com/vector-im/riot-web/issues/3148
|
// https://github.com/vector-im/riot-web/issues/3148
|
||||||
console.log('Starting load of AsyncWrapper for modal');
|
console.log('Starting load of AsyncWrapper for modal');
|
||||||
this.props.loader((e) => {
|
this.props.prom.then((result) => {
|
||||||
// XXX: temporary logging to try to diagnose
|
|
||||||
// https://github.com/vector-im/riot-web/issues/3148
|
|
||||||
console.log('AsyncWrapper load completed with '+e.displayName);
|
|
||||||
if (this._unmounted) {
|
if (this._unmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState({component: e});
|
// Take the 'default' member if it's there, then we support
|
||||||
|
// passing in just an import()ed module, since ES6 async import
|
||||||
|
// always returns a module *namespace*.
|
||||||
|
const component = result.default ? result.default : result;
|
||||||
|
this.setState({component});
|
||||||
|
}).catch((e) => {
|
||||||
|
console.warn('AsyncWrapper promise failed', e);
|
||||||
|
this.setState({error: e});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -64,11 +69,27 @@ const AsyncWrapper = React.createClass({
|
||||||
this._unmounted = true;
|
this._unmounted = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onWrapperCancelClick: function() {
|
||||||
|
this.props.onFinished(false);
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const {loader, ...otherProps} = this.props;
|
const {loader, ...otherProps} = this.props;
|
||||||
if (this.state.component) {
|
if (this.state.component) {
|
||||||
const Component = this.state.component;
|
const Component = this.state.component;
|
||||||
return <Component {...otherProps} />;
|
return <Component {...otherProps} />;
|
||||||
|
} else if (this.state.error) {
|
||||||
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
return <BaseDialog onFinished={this.props.onFinished}
|
||||||
|
title={_t("Error")}
|
||||||
|
>
|
||||||
|
{_t("Unable to load! Check your network connectivity and try again.")}
|
||||||
|
<DialogButtons primaryButton={_t("Dismiss")}
|
||||||
|
onPrimaryButtonClick={this._onWrapperCancelClick}
|
||||||
|
hasCancel={false}
|
||||||
|
/>
|
||||||
|
</BaseDialog>;
|
||||||
} else {
|
} else {
|
||||||
// show a spinner until the component is loaded.
|
// show a spinner until the component is loaded.
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
@ -115,7 +136,7 @@ class ModalManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
createDialog(Element, ...rest) {
|
createDialog(Element, ...rest) {
|
||||||
return this.createDialogAsync((cb) => {cb(Element);}, ...rest);
|
return this.createDialogAsync(new Promise(resolve => resolve(Element)), ...rest);
|
||||||
}
|
}
|
||||||
|
|
||||||
createTrackedDialogAsync(analyticsAction, analyticsInfo, ...rest) {
|
createTrackedDialogAsync(analyticsAction, analyticsInfo, ...rest) {
|
||||||
|
@ -133,9 +154,8 @@ class ModalManager {
|
||||||
* require(['<module>'], cb);
|
* require(['<module>'], cb);
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @param {Function} loader a function which takes a 'callback' argument,
|
* @param {Promise} prom a promise which resolves with a React component
|
||||||
* which it should call with a React component which will be displayed as
|
* which will be displayed as the modal view.
|
||||||
* the modal view.
|
|
||||||
*
|
*
|
||||||
* @param {Object} props properties to pass to the displayed
|
* @param {Object} props properties to pass to the displayed
|
||||||
* component. (We will also pass an 'onFinished' property.)
|
* component. (We will also pass an 'onFinished' property.)
|
||||||
|
@ -147,7 +167,7 @@ class ModalManager {
|
||||||
* Also, when closed, all modals will be removed
|
* Also, when closed, all modals will be removed
|
||||||
* from the stack.
|
* from the stack.
|
||||||
*/
|
*/
|
||||||
createDialogAsync(loader, props, className, isPriorityModal) {
|
createDialogAsync(prom, props, className, isPriorityModal) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const modal = {};
|
const modal = {};
|
||||||
|
|
||||||
|
@ -178,7 +198,7 @@ class ModalManager {
|
||||||
// FIXME: If a dialog uses getDefaultProps it clobbers the onFinished
|
// FIXME: If a dialog uses getDefaultProps it clobbers the onFinished
|
||||||
// property set here so you can't close the dialog from a button click!
|
// property set here so you can't close the dialog from a button click!
|
||||||
modal.elem = (
|
modal.elem = (
|
||||||
<AsyncWrapper key={modalCount} loader={loader} {...props}
|
<AsyncWrapper key={modalCount} prom={prom} {...props}
|
||||||
onFinished={closeDialog} />
|
onFinished={closeDialog} />
|
||||||
);
|
);
|
||||||
modal.onFinished = props ? props.onFinished : null;
|
modal.onFinished = props ? props.onFinished : null;
|
||||||
|
|
|
@ -25,7 +25,6 @@ import { _t } from './languageHandler';
|
||||||
* API on the homeserver in question with the new password.
|
* API on the homeserver in question with the new password.
|
||||||
*/
|
*/
|
||||||
class PasswordReset {
|
class PasswordReset {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure the endpoints for password resetting.
|
* Configure the endpoints for password resetting.
|
||||||
* @param {string} homeserverUrl The URL to the HS which has the account to reset.
|
* @param {string} homeserverUrl The URL to the HS which has the account to reset.
|
||||||
|
|
|
@ -23,7 +23,6 @@ const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
|
||||||
const PRESENCE_STATES = ["online", "offline", "unavailable"];
|
const PRESENCE_STATES = ["online", "offline", "unavailable"];
|
||||||
|
|
||||||
class Presence {
|
class Presence {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start listening the user activity to evaluate his presence state.
|
* Start listening the user activity to evaluate his presence state.
|
||||||
* Any state change will be sent to the Home Server.
|
* Any state change will be sent to the Home Server.
|
||||||
|
|
|
@ -45,7 +45,7 @@ export async function startAnyRegistrationFlow(options) {
|
||||||
// caution though.
|
// caution though.
|
||||||
const hasIlagFlow = flows.some((flow) => {
|
const hasIlagFlow = flows.some((flow) => {
|
||||||
return flow.stages.every((stage) => {
|
return flow.stages.every((stage) => {
|
||||||
return ['m.login.dummy', 'm.login.recaptcha'].includes(stage);
|
return ['m.login.dummy', 'm.login.recaptcha', 'm.login.terms'].includes(stage);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,6 @@ export function getDisplayAliasForRoom(room) {
|
||||||
* return the other one. Otherwise, return null.
|
* return the other one. Otherwise, return null.
|
||||||
*/
|
*/
|
||||||
export function getOnlyOtherMember(room, myUserId) {
|
export function getOnlyOtherMember(room, myUserId) {
|
||||||
|
|
||||||
if (room.currentState.getJoinedMemberCount() === 2) {
|
if (room.currentState.getJoinedMemberCount() === 2) {
|
||||||
return room.getJoinedMembers().filter(function(m) {
|
return room.getJoinedMembers().filter(function(m) {
|
||||||
return m.userId !== myUserId;
|
return m.userId !== myUserId;
|
||||||
|
@ -103,7 +102,7 @@ export function guessAndSetDMRoom(room, isDirect) {
|
||||||
let newTarget;
|
let newTarget;
|
||||||
if (isDirect) {
|
if (isDirect) {
|
||||||
const guessedUserId = guessDMRoomTargetId(
|
const guessedUserId = guessDMRoomTargetId(
|
||||||
room, MatrixClientPeg.get().getUserId()
|
room, MatrixClientPeg.get().getUserId(),
|
||||||
);
|
);
|
||||||
newTarget = guessedUserId;
|
newTarget = guessedUserId;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -22,7 +22,6 @@ const SdkConfig = require('./SdkConfig');
|
||||||
const MatrixClientPeg = require('./MatrixClientPeg');
|
const MatrixClientPeg = require('./MatrixClientPeg');
|
||||||
|
|
||||||
class ScalarAuthClient {
|
class ScalarAuthClient {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.scalarToken = null;
|
this.scalarToken = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ const DEFAULTS = {
|
||||||
};
|
};
|
||||||
|
|
||||||
class SdkConfig {
|
class SdkConfig {
|
||||||
|
|
||||||
static get() {
|
static get() {
|
||||||
return global.mxReactSdkConfig || {};
|
return global.mxReactSdkConfig || {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ import sdk from './index';
|
||||||
import {_t, _td} from './languageHandler';
|
import {_t, _td} from './languageHandler';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import SettingsStore, {SettingLevel} from './settings/SettingsStore';
|
import SettingsStore, {SettingLevel} from './settings/SettingsStore';
|
||||||
|
import {MATRIXTO_URL_PATTERN} from "./linkify-matrix";
|
||||||
|
import * as querystring from "querystring";
|
||||||
|
|
||||||
|
|
||||||
class Command {
|
class Command {
|
||||||
|
@ -153,11 +155,24 @@ export const CommandMap = {
|
||||||
description: _td('Joins room with given alias'),
|
description: _td('Joins room with given alias'),
|
||||||
runFn: function(roomId, args) {
|
runFn: function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
const matches = args.match(/^(\S+)$/);
|
// Note: we support 2 versions of this command. The first is
|
||||||
if (matches) {
|
// the public-facing one for most users and the other is a
|
||||||
let roomAlias = matches[1];
|
// power-user edition where someone may join via permalink or
|
||||||
if (roomAlias[0] !== '#') return reject(this.getUsage());
|
// room ID with optional servers. Practically, this results
|
||||||
|
// in the following variations:
|
||||||
|
// /join #example:example.org
|
||||||
|
// /join !example:example.org
|
||||||
|
// /join !example:example.org altserver.com elsewhere.ca
|
||||||
|
// /join https://matrix.to/#/!example:example.org?via=altserver.com
|
||||||
|
// The command also supports event permalinks transparently:
|
||||||
|
// /join https://matrix.to/#/!example:example.org/$something:example.org
|
||||||
|
// /join https://matrix.to/#/!example:example.org/$something:example.org?via=altserver.com
|
||||||
|
const params = args.split(' ');
|
||||||
|
if (params.length < 1) return reject(this.getUsage());
|
||||||
|
|
||||||
|
const matrixToMatches = params[0].match(MATRIXTO_URL_PATTERN);
|
||||||
|
if (params[0][0] === '#') {
|
||||||
|
let roomAlias = params[0];
|
||||||
if (!roomAlias.includes(':')) {
|
if (!roomAlias.includes(':')) {
|
||||||
roomAlias += ':' + MatrixClientPeg.get().getDomain();
|
roomAlias += ':' + MatrixClientPeg.get().getDomain();
|
||||||
}
|
}
|
||||||
|
@ -167,7 +182,65 @@ export const CommandMap = {
|
||||||
room_alias: roomAlias,
|
room_alias: roomAlias,
|
||||||
auto_join: true,
|
auto_join: true,
|
||||||
});
|
});
|
||||||
|
return success();
|
||||||
|
} else if (params[0][0] === '!') {
|
||||||
|
const roomId = params[0];
|
||||||
|
const viaServers = params.splice(0);
|
||||||
|
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
room_id: roomId,
|
||||||
|
opts: {
|
||||||
|
// These are passed down to the js-sdk's /join call
|
||||||
|
server_name: viaServers,
|
||||||
|
},
|
||||||
|
auto_join: true,
|
||||||
|
});
|
||||||
|
return success();
|
||||||
|
} else if (matrixToMatches) {
|
||||||
|
let entity = matrixToMatches[1];
|
||||||
|
let eventId = null;
|
||||||
|
let viaServers = [];
|
||||||
|
|
||||||
|
if (entity[0] !== '!' && entity[0] !== '#') return reject(this.getUsage());
|
||||||
|
|
||||||
|
if (entity.indexOf('?') !== -1) {
|
||||||
|
const parts = entity.split('?');
|
||||||
|
entity = parts[0];
|
||||||
|
|
||||||
|
const parsed = querystring.parse(parts[1]);
|
||||||
|
viaServers = parsed["via"];
|
||||||
|
if (typeof viaServers === 'string') viaServers = [viaServers];
|
||||||
|
}
|
||||||
|
|
||||||
|
// We quietly support event ID permalinks too
|
||||||
|
if (entity.indexOf('/$') !== -1) {
|
||||||
|
const parts = entity.split("/$");
|
||||||
|
entity = parts[0];
|
||||||
|
eventId = `$${parts[1]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dispatch = {
|
||||||
|
action: 'view_room',
|
||||||
|
auto_join: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (entity[0] === '!') dispatch["room_id"] = entity;
|
||||||
|
else dispatch["room_alias"] = entity;
|
||||||
|
|
||||||
|
if (eventId) {
|
||||||
|
dispatch["event_id"] = eventId;
|
||||||
|
dispatch["highlighted"] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viaServers) {
|
||||||
|
dispatch["opts"] = {
|
||||||
|
// These are passed down to the js-sdk's /join call
|
||||||
|
server_name: viaServers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dis.dispatch(dispatch);
|
||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -492,6 +565,7 @@ export const CommandMap = {
|
||||||
const aliases = {
|
const aliases = {
|
||||||
j: "join",
|
j: "join",
|
||||||
newballsplease: "discardsession",
|
newballsplease: "discardsession",
|
||||||
|
goto: "join", // because it handles event permalinks magically
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ const CURRENTLY_ACTIVE_THRESHOLD_MS = 2000;
|
||||||
* with the app (but at a much lower frequency than mouse move events)
|
* with the app (but at a much lower frequency than mouse move events)
|
||||||
*/
|
*/
|
||||||
class UserActivity {
|
class UserActivity {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start listening to user activity
|
* Start listening to user activity
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3,8 +3,8 @@ const Velocity = require('velocity-vector');
|
||||||
// courtesy of https://github.com/julianshapiro/velocity/issues/283
|
// courtesy of https://github.com/julianshapiro/velocity/issues/283
|
||||||
// We only use easeOutBounce (easeInBounce is just sort of nonsensical)
|
// We only use easeOutBounce (easeInBounce is just sort of nonsensical)
|
||||||
function bounce( p ) {
|
function bounce( p ) {
|
||||||
let pow2,
|
let pow2;
|
||||||
bounce = 4;
|
let bounce = 4;
|
||||||
|
|
||||||
while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {
|
while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {
|
||||||
// just sets pow2
|
// just sets pow2
|
||||||
|
|
|
@ -62,6 +62,35 @@ function createAccountDataAction(matrixClient, accountDataEvent) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef RoomAccountDataAction
|
||||||
|
* @type {Object}
|
||||||
|
* @property {string} action 'MatrixActions.Room.accountData'.
|
||||||
|
* @property {MatrixEvent} event the MatrixEvent that triggered the dispatch.
|
||||||
|
* @property {string} event_type the type of the MatrixEvent, e.g. "m.direct".
|
||||||
|
* @property {Object} event_content the content of the MatrixEvent.
|
||||||
|
* @property {Room} room the room where the account data was changed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a MatrixActions.Room.accountData action that represents a MatrixClient `Room.accountData`
|
||||||
|
* matrix event.
|
||||||
|
*
|
||||||
|
* @param {MatrixClient} matrixClient the matrix client.
|
||||||
|
* @param {MatrixEvent} accountDataEvent the account data event.
|
||||||
|
* @param {Room} room the room where account data was changed
|
||||||
|
* @returns {RoomAccountDataAction} an action of type MatrixActions.Room.accountData.
|
||||||
|
*/
|
||||||
|
function createRoomAccountDataAction(matrixClient, accountDataEvent, room) {
|
||||||
|
return {
|
||||||
|
action: 'MatrixActions.Room.accountData',
|
||||||
|
event: accountDataEvent,
|
||||||
|
event_type: accountDataEvent.getType(),
|
||||||
|
event_content: accountDataEvent.getContent(),
|
||||||
|
room: room,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef RoomAction
|
* @typedef RoomAction
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
|
@ -201,6 +230,7 @@ export default {
|
||||||
start(matrixClient) {
|
start(matrixClient) {
|
||||||
this._addMatrixClientListener(matrixClient, 'sync', createSyncAction);
|
this._addMatrixClientListener(matrixClient, 'sync', createSyncAction);
|
||||||
this._addMatrixClientListener(matrixClient, 'accountData', createAccountDataAction);
|
this._addMatrixClientListener(matrixClient, 'accountData', createAccountDataAction);
|
||||||
|
this._addMatrixClientListener(matrixClient, 'Room.accountData', createRoomAccountDataAction);
|
||||||
this._addMatrixClientListener(matrixClient, 'Room', createRoomAction);
|
this._addMatrixClientListener(matrixClient, 'Room', createRoomAction);
|
||||||
this._addMatrixClientListener(matrixClient, 'Room.tags', createRoomTagsAction);
|
this._addMatrixClientListener(matrixClient, 'Room.tags', createRoomTagsAction);
|
||||||
this._addMatrixClientListener(matrixClient, 'Room.timeline', createRoomTimelineAction);
|
this._addMatrixClientListener(matrixClient, 'Room.timeline', createRoomTimelineAction);
|
||||||
|
|
|
@ -85,7 +85,7 @@ export default class Autocompleter {
|
||||||
provider
|
provider
|
||||||
.getCompletions(query, selection, force)
|
.getCompletions(query, selection, force)
|
||||||
.timeout(PROVIDER_COMPLETION_TIMEOUT)
|
.timeout(PROVIDER_COMPLETION_TIMEOUT)
|
||||||
.reflect()
|
.reflect(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ export default class CommunityProvider extends AutocompleteProvider {
|
||||||
if (command) {
|
if (command) {
|
||||||
const joinedGroups = cli.getGroups().filter(({myMembership}) => myMembership === 'join');
|
const joinedGroups = cli.getGroups().filter(({myMembership}) => myMembership === 'join');
|
||||||
|
|
||||||
const groups = (await Promise.all(joinedGroups.map(async ({groupId}) => {
|
const groups = (await Promise.all(joinedGroups.map(async({groupId}) => {
|
||||||
try {
|
try {
|
||||||
return FlairStore.getGroupProfileCached(cli, groupId);
|
return FlairStore.getGroupProfileCached(cli, groupId);
|
||||||
} catch (e) { // if FlairStore failed, fall back to just groupId
|
} catch (e) { // if FlairStore failed, fall back to just groupId
|
||||||
|
|
|
@ -26,7 +26,6 @@ import { Block } from 'slate';
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class PlainWithPillsSerializer {
|
class PlainWithPillsSerializer {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @param {String} options.pillFormat - either 'md', 'plain', 'id'
|
* @param {String} options.pillFormat - either 'md', 'plain', 'id'
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -33,12 +33,12 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return({
|
return ({
|
||||||
directoryHover : false,
|
directoryHover: false,
|
||||||
roomsHover : false,
|
roomsHover: false,
|
||||||
homeHover: false,
|
homeHover: false,
|
||||||
peopleHover : false,
|
peopleHover: false,
|
||||||
settingsHover : false,
|
settingsHover: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ module.exports = React.createClass({
|
||||||
// Get the label/tooltip to show
|
// Get the label/tooltip to show
|
||||||
getLabel: function(label, show) {
|
getLabel: function(label, show) {
|
||||||
if (show) {
|
if (show) {
|
||||||
var RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
||||||
return <RoomTooltip className="mx_BottomLeftMenu_tooltip" label={label} />;
|
return <RoomTooltip className="mx_BottomLeftMenu_tooltip" label={label} />;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,18 +16,18 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
const React = require('react');
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'CompatibilityPage',
|
displayName: 'CompatibilityPage',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onAccept: React.PropTypes.func
|
onAccept: React.PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
onAccept: function() {} // NOP
|
onAccept: function() {}, // NOP
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_CompatibilityPage">
|
<div className="mx_CompatibilityPage">
|
||||||
<div className="mx_CompatibilityPage_box">
|
<div className="mx_CompatibilityPage_box">
|
||||||
|
@ -69,5 +68,5 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -746,14 +746,38 @@ export default React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_leaveGroupWarnings: function() {
|
||||||
|
const warnings = [];
|
||||||
|
|
||||||
|
if (this.state.isUserPrivileged) {
|
||||||
|
warnings.push((
|
||||||
|
<span className="warning">
|
||||||
|
{ " " /* Whitespace, otherwise the sentences get smashed together */ }
|
||||||
|
{ _t("You are an administrator of this community. You will not be " +
|
||||||
|
"able to rejoin without an invite from another administrator.") }
|
||||||
|
</span>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return warnings;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
_onLeaveClick: function() {
|
_onLeaveClick: function() {
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
const warnings = this._leaveGroupWarnings();
|
||||||
|
|
||||||
Modal.createTrackedDialog('Leave Group', '', QuestionDialog, {
|
Modal.createTrackedDialog('Leave Group', '', QuestionDialog, {
|
||||||
title: _t("Leave Community"),
|
title: _t("Leave Community"),
|
||||||
description: _t("Leave %(groupName)s?", {groupName: this.props.groupId}),
|
description: (
|
||||||
|
<span>
|
||||||
|
{ _t("Leave %(groupName)s?", {groupName: this.props.groupId}) }
|
||||||
|
{ warnings }
|
||||||
|
</span>
|
||||||
|
),
|
||||||
button: _t("Leave"),
|
button: _t("Leave"),
|
||||||
danger: true,
|
danger: this.state.isUserPrivileged,
|
||||||
onFinished: async (confirmed) => {
|
onFinished: async(confirmed) => {
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
this.setState({membershipBusy: true});
|
this.setState({membershipBusy: true});
|
||||||
|
|
|
@ -23,6 +23,8 @@ import request from 'browser-request';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import sanitizeHtml from 'sanitize-html';
|
import sanitizeHtml from 'sanitize-html';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
|
import dis from '../../dispatcher';
|
||||||
|
|
||||||
class HomePage extends React.Component {
|
class HomePage extends React.Component {
|
||||||
static displayName = 'HomePage';
|
static displayName = 'HomePage';
|
||||||
|
@ -37,6 +39,10 @@ class HomePage extends React.Component {
|
||||||
homePageUrl: PropTypes.string,
|
homePageUrl: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||||
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
iframeSrc: '',
|
iframeSrc: '',
|
||||||
page: '',
|
page: '',
|
||||||
|
@ -52,15 +58,14 @@ class HomePage extends React.Component {
|
||||||
|
|
||||||
if (this.props.teamToken && this.props.teamServerUrl) {
|
if (this.props.teamToken && this.props.teamServerUrl) {
|
||||||
this.setState({
|
this.setState({
|
||||||
iframeSrc: `${this.props.teamServerUrl}/static/${this.props.teamToken}/home.html`
|
iframeSrc: `${this.props.teamServerUrl}/static/${this.props.teamToken}/home.html`,
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// we use request() to inline the homepage into the react component
|
// we use request() to inline the homepage into the react component
|
||||||
// so that it can inherit CSS and theming easily rather than mess around
|
// so that it can inherit CSS and theming easily rather than mess around
|
||||||
// with iframes and trying to synchronise document.stylesheets.
|
// with iframes and trying to synchronise document.stylesheets.
|
||||||
|
|
||||||
let src = this.props.homePageUrl || 'home.html';
|
const src = this.props.homePageUrl || 'home.html';
|
||||||
|
|
||||||
request(
|
request(
|
||||||
{ method: "GET", url: src },
|
{ method: "GET", url: src },
|
||||||
|
@ -77,7 +82,7 @@ class HomePage extends React.Component {
|
||||||
|
|
||||||
body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1));
|
body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1));
|
||||||
this.setState({ page: body });
|
this.setState({ page: body });
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,18 +91,55 @@ class HomePage extends React.Component {
|
||||||
this._unmounted = true;
|
this._unmounted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onLoginClick() {
|
||||||
|
dis.dispatch({ action: 'start_login' });
|
||||||
|
}
|
||||||
|
|
||||||
|
onRegisterClick() {
|
||||||
|
dis.dispatch({ action: 'start_registration' });
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.iframeSrc) {
|
let guestWarning = "";
|
||||||
return (
|
if (this.context.matrixClient.isGuest()) {
|
||||||
<div className="mx_HomePage">
|
guestWarning = (
|
||||||
<iframe src={ this.state.iframeSrc } />
|
<div className="mx_HomePage_guest_warning">
|
||||||
|
<img src="img/warning.svg" width="24" height="23" />
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
{ _t("You are currently using Riot anonymously as a guest.") }
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{ _t(
|
||||||
|
'If you would like to create a Matrix account you can <a>register</a> now.',
|
||||||
|
{},
|
||||||
|
{ 'a': (sub) => <a href="#" onClick={this.onRegisterClick}>{ sub }</a> },
|
||||||
|
) }
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{ _t(
|
||||||
|
'If you already have a Matrix account you can <a>log in</a> instead.',
|
||||||
|
{},
|
||||||
|
{ 'a': (sub) => <a href="#" onClick={this.onLoginClick}>{ sub }</a> },
|
||||||
|
) }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
|
if (this.state.iframeSrc) {
|
||||||
|
return (
|
||||||
|
<div className="mx_HomePage">
|
||||||
|
{ guestWarning }
|
||||||
|
<iframe src={ this.state.iframeSrc } />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
||||||
return (
|
return (
|
||||||
<GeminiScrollbarWrapper autoshow={true} className="mx_HomePage">
|
<GeminiScrollbarWrapper autoshow={true} className="mx_HomePage">
|
||||||
|
{ guestWarning }
|
||||||
<div className="mx_HomePage_body" dangerouslySetInnerHTML={{ __html: this.state.page }}>
|
<div className="mx_HomePage_body" dangerouslySetInnerHTML={{ __html: this.state.page }}>
|
||||||
</div>
|
</div>
|
||||||
</GeminiScrollbarWrapper>
|
</GeminiScrollbarWrapper>
|
||||||
|
|
|
@ -68,6 +68,11 @@ export default React.createClass({
|
||||||
// If true, poll to see if the auth flow has been completed
|
// If true, poll to see if the auth flow has been completed
|
||||||
// out-of-band
|
// out-of-band
|
||||||
poll: PropTypes.bool,
|
poll: PropTypes.bool,
|
||||||
|
|
||||||
|
// If true, components will be told that the 'Continue' button
|
||||||
|
// is managed by some other party and should not be managed by
|
||||||
|
// the component itself.
|
||||||
|
continueIsManaged: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -128,6 +133,12 @@ export default React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
tryContinue: function() {
|
||||||
|
if (this.refs.stageComponent && this.refs.stageComponent.tryContinue) {
|
||||||
|
this.refs.stageComponent.tryContinue();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
_authStateUpdated: function(stageType, stageState) {
|
_authStateUpdated: function(stageType, stageState) {
|
||||||
const oldStage = this.state.authStage;
|
const oldStage = this.state.authStage;
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -192,6 +203,7 @@ export default React.createClass({
|
||||||
fail={this._onAuthStageFailed}
|
fail={this._onAuthStageFailed}
|
||||||
setEmailSid={this._setEmailSid}
|
setEmailSid={this._setEmailSid}
|
||||||
makeRegistrationUrl={this.props.makeRegistrationUrl}
|
makeRegistrationUrl={this.props.makeRegistrationUrl}
|
||||||
|
showContinue={!this.props.continueIsManaged}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,7 +28,7 @@ import VectorConferenceHandler from '../../VectorConferenceHandler';
|
||||||
import SettingsStore from '../../settings/SettingsStore';
|
import SettingsStore from '../../settings/SettingsStore';
|
||||||
|
|
||||||
|
|
||||||
var LeftPanel = React.createClass({
|
const LeftPanel = React.createClass({
|
||||||
displayName: 'LeftPanel',
|
displayName: 'LeftPanel',
|
||||||
|
|
||||||
// NB. If you add props, don't forget to update
|
// NB. If you add props, don't forget to update
|
||||||
|
@ -181,14 +181,8 @@ var LeftPanel = React.createClass({
|
||||||
const BottomLeftMenu = sdk.getComponent('structures.BottomLeftMenu');
|
const BottomLeftMenu = sdk.getComponent('structures.BottomLeftMenu');
|
||||||
const CallPreview = sdk.getComponent('voip.CallPreview');
|
const CallPreview = sdk.getComponent('voip.CallPreview');
|
||||||
|
|
||||||
let topBox;
|
|
||||||
if (this.context.matrixClient.isGuest()) {
|
|
||||||
const LoginBox = sdk.getComponent('structures.LoginBox');
|
|
||||||
topBox = <LoginBox collapsed={ this.props.collapsed }/>;
|
|
||||||
} else {
|
|
||||||
const SearchBox = sdk.getComponent('structures.SearchBox');
|
const SearchBox = sdk.getComponent('structures.SearchBox');
|
||||||
topBox = <SearchBox collapsed={ this.props.collapsed } onSearch={ this.onSearch } />;
|
const topBox = <SearchBox collapsed={ this.props.collapsed } onSearch={ this.onSearch } />;
|
||||||
}
|
|
||||||
|
|
||||||
const classes = classNames(
|
const classes = classNames(
|
||||||
"mx_LeftPanel",
|
"mx_LeftPanel",
|
||||||
|
@ -220,11 +214,11 @@ var LeftPanel = React.createClass({
|
||||||
collapsed={this.props.collapsed}
|
collapsed={this.props.collapsed}
|
||||||
searchFilter={this.state.searchFilter}
|
searchFilter={this.state.searchFilter}
|
||||||
ConferenceHandler={VectorConferenceHandler} />
|
ConferenceHandler={VectorConferenceHandler} />
|
||||||
<BottomLeftMenu collapsed={this.props.collapsed}/>
|
<BottomLeftMenu collapsed={this.props.collapsed} />
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = LeftPanel;
|
module.exports = LeftPanel;
|
||||||
|
|
|
@ -64,6 +64,9 @@ const LoggedInView = React.createClass({
|
||||||
|
|
||||||
teamToken: PropTypes.string,
|
teamToken: PropTypes.string,
|
||||||
|
|
||||||
|
// Used by the RoomView to handle joining rooms
|
||||||
|
viaServers: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
|
||||||
// and lots and lots of other stuff.
|
// and lots and lots of other stuff.
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -389,6 +392,7 @@ const LoggedInView = React.createClass({
|
||||||
onRegistered={this.props.onRegistered}
|
onRegistered={this.props.onRegistered}
|
||||||
thirdPartyInvite={this.props.thirdPartyInvite}
|
thirdPartyInvite={this.props.thirdPartyInvite}
|
||||||
oobData={this.props.roomOobData}
|
oobData={this.props.roomOobData}
|
||||||
|
viaServers={this.props.viaServers}
|
||||||
eventPixelOffset={this.props.initialEventPixelOffset}
|
eventPixelOffset={this.props.initialEventPixelOffset}
|
||||||
key={this.props.currentRoomId || 'roomview'}
|
key={this.props.currentRoomId || 'roomview'}
|
||||||
disabled={this.props.middleDisabled}
|
disabled={this.props.middleDisabled}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
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.
|
||||||
|
@ -16,31 +17,15 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
const React = require('react');
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
var sdk = require('../../index')
|
const dis = require('../../dispatcher');
|
||||||
var dis = require('../../dispatcher');
|
const AccessibleButton = require('../../components/views/elements/AccessibleButton');
|
||||||
var rate_limited_func = require('../../ratelimitedfunc');
|
|
||||||
var AccessibleButton = require('../../components/views/elements/AccessibleButton');
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'LoginBox',
|
displayName: 'LoginBox',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
collapsed: React.PropTypes.bool,
|
|
||||||
},
|
|
||||||
|
|
||||||
onToggleCollapse: function(show) {
|
|
||||||
if (show) {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'show_left_panel',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'hide_left_panel',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onLoginClick: function() {
|
onLoginClick: function() {
|
||||||
|
@ -52,25 +37,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var TintableSvg = sdk.getComponent('elements.TintableSvg');
|
const loginButton = (
|
||||||
|
|
||||||
var toggleCollapse;
|
|
||||||
if (this.props.collapsed) {
|
|
||||||
toggleCollapse =
|
|
||||||
<AccessibleButton className="mx_SearchBox_maximise" onClick={ this.onToggleCollapse.bind(this, true) }>
|
|
||||||
<TintableSvg src="img/maximise.svg" width="10" height="16" alt="Expand panel"/>
|
|
||||||
</AccessibleButton>
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
toggleCollapse =
|
|
||||||
<AccessibleButton className="mx_SearchBox_minimise" onClick={ this.onToggleCollapse.bind(this, false) }>
|
|
||||||
<TintableSvg src="img/minimise.svg" width="10" height="16" alt="Collapse panel"/>
|
|
||||||
</AccessibleButton>
|
|
||||||
}
|
|
||||||
|
|
||||||
var loginButton;
|
|
||||||
if (!this.props.collapsed) {
|
|
||||||
loginButton = (
|
|
||||||
<div className="mx_LoginBox_loginButton_wrapper">
|
<div className="mx_LoginBox_loginButton_wrapper">
|
||||||
<AccessibleButton className="mx_LoginBox_loginButton" element="button" onClick={this.onLoginClick}>
|
<AccessibleButton className="mx_LoginBox_loginButton" element="button" onClick={this.onLoginClick}>
|
||||||
{ _t("Login") }
|
{ _t("Login") }
|
||||||
|
@ -80,14 +47,11 @@ module.exports = React.createClass({
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_SearchBox mx_LoginBox">
|
<div className="mx_LoginBox">
|
||||||
{ loginButton }
|
{ loginButton }
|
||||||
{ toggleCollapse }
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -840,6 +840,7 @@ export default React.createClass({
|
||||||
page_type: PageTypes.RoomView,
|
page_type: PageTypes.RoomView,
|
||||||
thirdPartyInvite: roomInfo.third_party_invite,
|
thirdPartyInvite: roomInfo.third_party_invite,
|
||||||
roomOobData: roomInfo.oob_data,
|
roomOobData: roomInfo.oob_data,
|
||||||
|
viaServers: roomInfo.via_servers,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (roomInfo.room_alias) {
|
if (roomInfo.room_alias) {
|
||||||
|
@ -1034,6 +1035,7 @@ export default React.createClass({
|
||||||
{ warnings }
|
{ warnings }
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
|
button: _t("Leave"),
|
||||||
onFinished: (shouldLeave) => {
|
onFinished: (shouldLeave) => {
|
||||||
if (shouldLeave) {
|
if (shouldLeave) {
|
||||||
const d = MatrixClientPeg.get().leave(roomId);
|
const d = MatrixClientPeg.get().leave(roomId);
|
||||||
|
@ -1373,6 +1375,7 @@ export default React.createClass({
|
||||||
cli.on("crypto.roomKeyRequestCancellation", (req) => {
|
cli.on("crypto.roomKeyRequestCancellation", (req) => {
|
||||||
krh.handleKeyRequestCancellation(req);
|
krh.handleKeyRequestCancellation(req);
|
||||||
});
|
});
|
||||||
|
|
||||||
cli.on("Room", (room) => {
|
cli.on("Room", (room) => {
|
||||||
if (MatrixClientPeg.get().isCryptoEnabled()) {
|
if (MatrixClientPeg.get().isCryptoEnabled()) {
|
||||||
const blacklistEnabled = SettingsStore.getValueAt(
|
const blacklistEnabled = SettingsStore.getValueAt(
|
||||||
|
@ -1406,8 +1409,8 @@ export default React.createClass({
|
||||||
|
|
||||||
// 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 color_scheme = SettingsStore.getValue("roomColor");
|
const colorScheme = SettingsStore.getValue("roomColor");
|
||||||
Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
|
Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1488,9 +1491,21 @@ export default React.createClass({
|
||||||
inviterName: params.inviter_name,
|
inviterName: params.inviter_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// on our URLs there might be a ?via=matrix.org or similar to help
|
||||||
|
// joins to the room succeed. We'll pass these through as an array
|
||||||
|
// to other levels. If there's just one ?via= then params.via is a
|
||||||
|
// single string. If someone does something like ?via=one.com&via=two.com
|
||||||
|
// then params.via is an array of strings.
|
||||||
|
let via = [];
|
||||||
|
if (params.via) {
|
||||||
|
if (typeof(params.via) === 'string') via = [params.via];
|
||||||
|
else via = params.via;
|
||||||
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
action: 'view_room',
|
action: 'view_room',
|
||||||
event_id: eventId,
|
event_id: eventId,
|
||||||
|
via_servers: via,
|
||||||
// If an event ID is given in the URL hash, notify RoomViewStore to mark
|
// If an event ID is given in the URL hash, notify RoomViewStore to mark
|
||||||
// it as highlighted, which will propagate to RoomView and highlight the
|
// it as highlighted, which will propagate to RoomView and highlight the
|
||||||
// associated EventTile.
|
// associated EventTile.
|
||||||
|
|
|
@ -16,18 +16,18 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
const React = require('react');
|
||||||
|
|
||||||
var MatrixClientPeg = require('../../MatrixClientPeg');
|
const MatrixClientPeg = require('../../MatrixClientPeg');
|
||||||
var ContentRepo = require("matrix-js-sdk").ContentRepo;
|
const ContentRepo = require("matrix-js-sdk").ContentRepo;
|
||||||
var Modal = require('../../Modal');
|
const Modal = require('../../Modal');
|
||||||
var sdk = require('../../index');
|
const sdk = require('../../index');
|
||||||
var dis = require('../../dispatcher');
|
const dis = require('../../dispatcher');
|
||||||
|
|
||||||
var linkify = require('linkifyjs');
|
const linkify = require('linkifyjs');
|
||||||
var linkifyString = require('linkifyjs/string');
|
const linkifyString = require('linkifyjs/string');
|
||||||
var linkifyMatrix = require('../../linkify-matrix');
|
const linkifyMatrix = require('../../linkify-matrix');
|
||||||
var sanitizeHtml = require('sanitize-html');
|
const sanitizeHtml = require('sanitize-html');
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
|
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
@ -46,7 +46,7 @@ module.exports = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
config: {},
|
config: {},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -58,7 +58,7 @@ module.exports = React.createClass({
|
||||||
includeAll: false,
|
includeAll: false,
|
||||||
roomServer: null,
|
roomServer: null,
|
||||||
filterString: null,
|
filterString: null,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -134,13 +134,12 @@ module.exports = React.createClass({
|
||||||
opts.include_all_networks = true;
|
opts.include_all_networks = true;
|
||||||
}
|
}
|
||||||
if (this.nextBatch) opts.since = this.nextBatch;
|
if (this.nextBatch) opts.since = this.nextBatch;
|
||||||
if (my_filter_string) opts.filter = { generic_search_term: my_filter_string } ;
|
if (my_filter_string) opts.filter = { generic_search_term: my_filter_string };
|
||||||
return MatrixClientPeg.get().publicRooms(opts).then((data) => {
|
return MatrixClientPeg.get().publicRooms(opts).then((data) => {
|
||||||
if (
|
if (
|
||||||
my_filter_string != this.state.filterString ||
|
my_filter_string != this.state.filterString ||
|
||||||
my_server != this.state.roomServer ||
|
my_server != this.state.roomServer ||
|
||||||
my_next_batch != this.nextBatch)
|
my_next_batch != this.nextBatch) {
|
||||||
{
|
|
||||||
// if the filter or server has changed since this request was sent,
|
// if the filter or server has changed since this request was sent,
|
||||||
// throw away the result (don't even clear the busy flag
|
// throw away the result (don't even clear the busy flag
|
||||||
// since we must still have a request in flight)
|
// since we must still have a request in flight)
|
||||||
|
@ -163,8 +162,7 @@ module.exports = React.createClass({
|
||||||
if (
|
if (
|
||||||
my_filter_string != this.state.filterString ||
|
my_filter_string != this.state.filterString ||
|
||||||
my_server != this.state.roomServer ||
|
my_server != this.state.roomServer ||
|
||||||
my_next_batch != this.nextBatch)
|
my_next_batch != this.nextBatch) {
|
||||||
{
|
|
||||||
// as above: we don't care about errors for old
|
// as above: we don't care about errors for old
|
||||||
// requests either
|
// requests either
|
||||||
return;
|
return;
|
||||||
|
@ -177,10 +175,10 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
|
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createTrackedDialog('Failed to get public room list', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to get public room list', '', ErrorDialog, {
|
||||||
title: _t('Failed to get public room list'),
|
title: _t('Failed to get public room list'),
|
||||||
description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded'))
|
description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded')),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -193,13 +191,13 @@ module.exports = React.createClass({
|
||||||
* this needs SPEC-417.
|
* this needs SPEC-417.
|
||||||
*/
|
*/
|
||||||
removeFromDirectory: function(room) {
|
removeFromDirectory: function(room) {
|
||||||
var alias = get_display_alias_for_room(room);
|
const alias = get_display_alias_for_room(room);
|
||||||
var name = room.name || alias || _t('Unnamed room');
|
const name = room.name || alias || _t('Unnamed room');
|
||||||
|
|
||||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
|
||||||
var desc;
|
let desc;
|
||||||
if (alias) {
|
if (alias) {
|
||||||
desc = _t('Delete the room alias %(alias)s and remove %(name)s from the directory?', {alias: alias, name: name});
|
desc = _t('Delete the room alias %(alias)s and remove %(name)s from the directory?', {alias: alias, name: name});
|
||||||
} else {
|
} else {
|
||||||
|
@ -212,9 +210,9 @@ module.exports = React.createClass({
|
||||||
onFinished: (should_delete) => {
|
onFinished: (should_delete) => {
|
||||||
if (!should_delete) return;
|
if (!should_delete) return;
|
||||||
|
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
var modal = Modal.createDialog(Loader);
|
const modal = Modal.createDialog(Loader);
|
||||||
var step = _t('remove %(name)s from the directory.', {name: name});
|
let step = _t('remove %(name)s from the directory.', {name: name});
|
||||||
|
|
||||||
MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => {
|
MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => {
|
||||||
if (!alias) return;
|
if (!alias) return;
|
||||||
|
@ -229,10 +227,10 @@ module.exports = React.createClass({
|
||||||
console.error("Failed to " + step + ": " + err);
|
console.error("Failed to " + step + ": " + err);
|
||||||
Modal.createTrackedDialog('Remove from Directory Error', '', ErrorDialog, {
|
Modal.createTrackedDialog('Remove from Directory Error', '', ErrorDialog, {
|
||||||
title: _t('Error'),
|
title: _t('Error'),
|
||||||
description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded'))
|
description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded')),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -347,7 +345,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
showRoom: function(room, room_alias) {
|
showRoom: function(room, room_alias) {
|
||||||
var payload = {action: 'view_room'};
|
const payload = {action: 'view_room'};
|
||||||
if (room) {
|
if (room) {
|
||||||
// Don't let the user view a room they won't be able to either
|
// Don't let the user view a room they won't be able to either
|
||||||
// peek or join: fail earlier so they don't have to click back
|
// peek or join: fail earlier so they don't have to click back
|
||||||
|
@ -383,16 +381,16 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
getRows: function() {
|
getRows: function() {
|
||||||
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
|
|
||||||
if (!this.state.publicRooms) return [];
|
if (!this.state.publicRooms) return [];
|
||||||
|
|
||||||
var rooms = this.state.publicRooms;
|
const rooms = this.state.publicRooms;
|
||||||
var rows = [];
|
const rows = [];
|
||||||
var self = this;
|
const self = this;
|
||||||
var guestRead, guestJoin, perms;
|
let guestRead; let guestJoin; let perms;
|
||||||
for (var i = 0; i < rooms.length; i++) {
|
for (let i = 0; i < rooms.length; i++) {
|
||||||
var name = rooms[i].name || get_display_alias_for_room(rooms[i]) || _t('Unnamed room');
|
const name = rooms[i].name || get_display_alias_for_room(rooms[i]) || _t('Unnamed room');
|
||||||
guestRead = null;
|
guestRead = null;
|
||||||
guestJoin = null;
|
guestJoin = null;
|
||||||
|
|
||||||
|
@ -412,7 +410,7 @@ module.exports = React.createClass({
|
||||||
perms = <div className="mx_RoomDirectory_perms">{guestRead}{guestJoin}</div>;
|
perms = <div className="mx_RoomDirectory_perms">{guestRead}{guestJoin}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
var topic = rooms[i].topic || '';
|
let topic = rooms[i].topic || '';
|
||||||
topic = linkifyString(sanitizeHtml(topic));
|
topic = linkifyString(sanitizeHtml(topic));
|
||||||
|
|
||||||
rows.push(
|
rows.push(
|
||||||
|
@ -432,14 +430,14 @@ module.exports = React.createClass({
|
||||||
<div className="mx_RoomDirectory_name">{ name }</div>
|
<div className="mx_RoomDirectory_name">{ name }</div>
|
||||||
{ perms }
|
{ perms }
|
||||||
<div className="mx_RoomDirectory_topic"
|
<div className="mx_RoomDirectory_topic"
|
||||||
onClick={ function(e) { e.stopPropagation() } }
|
onClick={ function(e) { e.stopPropagation(); } }
|
||||||
dangerouslySetInnerHTML={{ __html: topic }}/>
|
dangerouslySetInnerHTML={{ __html: topic }} />
|
||||||
<div className="mx_RoomDirectory_alias">{ get_display_alias_for_room(rooms[i]) }</div>
|
<div className="mx_RoomDirectory_alias">{ get_display_alias_for_room(rooms[i]) }</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="mx_RoomDirectory_roomMemberCount">
|
<td className="mx_RoomDirectory_roomMemberCount">
|
||||||
{ rooms[i].num_joined_members }
|
{ rooms[i].num_joined_members }
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return rows;
|
return rows;
|
||||||
|
@ -524,7 +522,7 @@ module.exports = React.createClass({
|
||||||
onFillRequest={ this.onFillRequest }
|
onFillRequest={ this.onFillRequest }
|
||||||
stickyBottom={false}
|
stickyBottom={false}
|
||||||
startAtBottom={false}
|
startAtBottom={false}
|
||||||
onResize={function(){}}
|
onResize={function() {}}
|
||||||
>
|
>
|
||||||
{ scrollpanel_content }
|
{ scrollpanel_content }
|
||||||
</ScrollPanel>;
|
</ScrollPanel>;
|
||||||
|
@ -577,7 +575,7 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
|
// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
|
||||||
|
|
|
@ -37,7 +37,7 @@ function getUnsentMessages(room) {
|
||||||
return room.getPendingEvents().filter(function(ev) {
|
return room.getPendingEvents().filter(function(ev) {
|
||||||
return ev.status === Matrix.EventStatus.NOT_SENT;
|
return ev.status === Matrix.EventStatus.NOT_SENT;
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomStatusBar',
|
displayName: 'RoomStatusBar',
|
||||||
|
@ -303,7 +303,7 @@ module.exports = React.createClass({
|
||||||
const errorIsMauError = Boolean(
|
const errorIsMauError = Boolean(
|
||||||
this.state.syncStateData &&
|
this.state.syncStateData &&
|
||||||
this.state.syncStateData.error &&
|
this.state.syncStateData.error &&
|
||||||
this.state.syncStateData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED'
|
this.state.syncStateData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED',
|
||||||
);
|
);
|
||||||
return this.state.syncState === "ERROR" && !errorIsMauError;
|
return this.state.syncState === "ERROR" && !errorIsMauError;
|
||||||
},
|
},
|
||||||
|
|
|
@ -88,6 +88,9 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// is the RightPanel collapsed?
|
// is the RightPanel collapsed?
|
||||||
collapsedRhs: PropTypes.bool,
|
collapsedRhs: PropTypes.bool,
|
||||||
|
|
||||||
|
// Servers the RoomView can use to try and assist joins
|
||||||
|
viaServers: PropTypes.arrayOf(PropTypes.string),
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -678,8 +681,8 @@ module.exports = React.createClass({
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
|
|
||||||
console.log("Tinter.tint from updateTint");
|
console.log("Tinter.tint from updateTint");
|
||||||
const color_scheme = SettingsStore.getValue("roomColor", room.roomId);
|
const colorScheme = SettingsStore.getValue("roomColor", room.roomId);
|
||||||
Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
|
Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color);
|
||||||
},
|
},
|
||||||
|
|
||||||
onAccountData: function(event) {
|
onAccountData: function(event) {
|
||||||
|
@ -694,10 +697,10 @@ module.exports = React.createClass({
|
||||||
if (room.roomId == this.state.roomId) {
|
if (room.roomId == this.state.roomId) {
|
||||||
const type = event.getType();
|
const type = event.getType();
|
||||||
if (type === "org.matrix.room.color_scheme") {
|
if (type === "org.matrix.room.color_scheme") {
|
||||||
const color_scheme = event.getContent();
|
const colorScheme = event.getContent();
|
||||||
// XXX: we should validate the event
|
// XXX: we should validate the event
|
||||||
console.log("Tinter.tint from onRoomAccountData");
|
console.log("Tinter.tint from onRoomAccountData");
|
||||||
Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
|
Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color);
|
||||||
} else if (type === "org.matrix.room.preview_urls" || type === "im.vector.web.settings") {
|
} else if (type === "org.matrix.room.preview_urls" || type === "im.vector.web.settings") {
|
||||||
// non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls`
|
// non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls`
|
||||||
this._updatePreviewUrlVisibility(room);
|
this._updatePreviewUrlVisibility(room);
|
||||||
|
@ -833,7 +836,7 @@ module.exports = React.createClass({
|
||||||
action: 'do_after_sync_prepared',
|
action: 'do_after_sync_prepared',
|
||||||
deferred_action: {
|
deferred_action: {
|
||||||
action: 'join_room',
|
action: 'join_room',
|
||||||
opts: { inviteSignUrl: signUrl },
|
opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -875,7 +878,7 @@ module.exports = React.createClass({
|
||||||
this.props.thirdPartyInvite.inviteSignUrl : undefined;
|
this.props.thirdPartyInvite.inviteSignUrl : undefined;
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'join_room',
|
action: 'join_room',
|
||||||
opts: { inviteSignUrl: signUrl },
|
opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers },
|
||||||
});
|
});
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
|
@ -1514,6 +1517,7 @@ module.exports = React.createClass({
|
||||||
canPreview={false} error={this.state.roomLoadError}
|
canPreview={false} error={this.state.roomLoadError}
|
||||||
roomAlias={roomAlias}
|
roomAlias={roomAlias}
|
||||||
spinner={this.state.joining}
|
spinner={this.state.joining}
|
||||||
|
spinnerState="joining"
|
||||||
inviterName={inviterName}
|
inviterName={inviterName}
|
||||||
invitedEmail={invitedEmail}
|
invitedEmail={invitedEmail}
|
||||||
room={this.state.room}
|
room={this.state.room}
|
||||||
|
@ -1558,6 +1562,7 @@ module.exports = React.createClass({
|
||||||
inviterName={inviterName}
|
inviterName={inviterName}
|
||||||
canPreview={false}
|
canPreview={false}
|
||||||
spinner={this.state.joining}
|
spinner={this.state.joining}
|
||||||
|
spinnerState="joining"
|
||||||
room={this.state.room}
|
room={this.state.room}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1645,6 +1650,7 @@ module.exports = React.createClass({
|
||||||
onForgetClick={this.onForgetClick}
|
onForgetClick={this.onForgetClick}
|
||||||
onRejectClick={this.onRejectThreepidInviteButtonClicked}
|
onRejectClick={this.onRejectThreepidInviteButtonClicked}
|
||||||
spinner={this.state.joining}
|
spinner={this.state.joining}
|
||||||
|
spinnerState="joining"
|
||||||
inviterName={inviterName}
|
inviterName={inviterName}
|
||||||
invitedEmail={invitedEmail}
|
invitedEmail={invitedEmail}
|
||||||
canPreview={this.state.canPeek}
|
canPreview={this.state.canPeek}
|
||||||
|
@ -1667,10 +1673,10 @@ module.exports = React.createClass({
|
||||||
</AuxPanel>
|
</AuxPanel>
|
||||||
);
|
);
|
||||||
|
|
||||||
let messageComposer, searchInfo;
|
let messageComposer; let searchInfo;
|
||||||
const canSpeak = (
|
const canSpeak = (
|
||||||
// joined and not showing search results
|
// joined and not showing search results
|
||||||
myMembership == 'join' && !this.state.searchResults
|
myMembership === 'join' && !this.state.searchResults
|
||||||
);
|
);
|
||||||
if (canSpeak) {
|
if (canSpeak) {
|
||||||
messageComposer =
|
messageComposer =
|
||||||
|
@ -1684,6 +1690,11 @@ module.exports = React.createClass({
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
const LoginBox = sdk.getComponent('structures.LoginBox');
|
||||||
|
messageComposer = <LoginBox />;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Why aren't we storing the term/scope/count in this format
|
// TODO: Why aren't we storing the term/scope/count in this format
|
||||||
// in this.state if this is what RoomHeader desires?
|
// in this.state if this is what RoomHeader desires?
|
||||||
if (this.state.searchResults) {
|
if (this.state.searchResults) {
|
||||||
|
@ -1695,7 +1706,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inCall) {
|
if (inCall) {
|
||||||
let zoomButton, voiceMuteButton, videoMuteButton;
|
let zoomButton; let voiceMuteButton; let videoMuteButton;
|
||||||
|
|
||||||
if (call.type === "video") {
|
if (call.type === "video") {
|
||||||
zoomButton = (
|
zoomButton = (
|
||||||
|
|
|
@ -72,7 +72,7 @@ module.exports = React.createClass({
|
||||||
function() {
|
function() {
|
||||||
this.props.onSearch(this.refs.search.value);
|
this.props.onSearch(this.refs.search.value);
|
||||||
},
|
},
|
||||||
100
|
100,
|
||||||
),
|
),
|
||||||
|
|
||||||
onToggleCollapse: function(show) {
|
onToggleCollapse: function(show) {
|
||||||
|
@ -80,8 +80,7 @@ module.exports = React.createClass({
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'show_left_panel',
|
action: 'show_left_panel',
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'hide_left_panel',
|
action: 'hide_left_panel',
|
||||||
});
|
});
|
||||||
|
@ -103,25 +102,24 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var TintableSvg = sdk.getComponent('elements.TintableSvg');
|
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||||
|
|
||||||
var collapseTabIndex = this.refs.search && this.refs.search.value !== "" ? "-1" : "0";
|
const collapseTabIndex = this.refs.search && this.refs.search.value !== "" ? "-1" : "0";
|
||||||
|
|
||||||
var toggleCollapse;
|
let toggleCollapse;
|
||||||
if (this.props.collapsed) {
|
if (this.props.collapsed) {
|
||||||
toggleCollapse =
|
toggleCollapse =
|
||||||
<AccessibleButton className="mx_SearchBox_maximise" tabIndex={collapseTabIndex} onClick={ this.onToggleCollapse.bind(this, true) }>
|
<AccessibleButton className="mx_SearchBox_maximise" tabIndex={collapseTabIndex} onClick={ this.onToggleCollapse.bind(this, true) }>
|
||||||
<TintableSvg src="img/maximise.svg" width="10" height="16" alt={ _t("Expand panel") }/>
|
<TintableSvg src="img/maximise.svg" width="10" height="16" alt={ _t("Expand panel") } />
|
||||||
</AccessibleButton>
|
</AccessibleButton>;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
toggleCollapse =
|
toggleCollapse =
|
||||||
<AccessibleButton className="mx_SearchBox_minimise" tabIndex={collapseTabIndex} onClick={ this.onToggleCollapse.bind(this, false) }>
|
<AccessibleButton className="mx_SearchBox_minimise" tabIndex={collapseTabIndex} onClick={ this.onToggleCollapse.bind(this, false) }>
|
||||||
<TintableSvg src="img/minimise.svg" width="10" height="16" alt={ _t("Collapse panel") }/>
|
<TintableSvg src="img/minimise.svg" width="10" height="16" alt={ _t("Collapse panel") } />
|
||||||
</AccessibleButton>
|
</AccessibleButton>;
|
||||||
}
|
}
|
||||||
|
|
||||||
var searchControls;
|
let searchControls;
|
||||||
if (!this.props.collapsed) {
|
if (!this.props.collapsed) {
|
||||||
searchControls = [
|
searchControls = [
|
||||||
this.state.searchTerm.length > 0 ?
|
this.state.searchTerm.length > 0 ?
|
||||||
|
@ -148,16 +146,16 @@ module.exports = React.createClass({
|
||||||
onChange={ this.onChange }
|
onChange={ this.onChange }
|
||||||
onKeyDown={ this._onKeyDown }
|
onKeyDown={ this._onKeyDown }
|
||||||
placeholder={ _t('Filter room names') }
|
placeholder={ _t('Filter room names') }
|
||||||
/>
|
/>,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
var self = this;
|
const self = this;
|
||||||
return (
|
return (
|
||||||
<div className="mx_SearchBox">
|
<div className="mx_SearchBox">
|
||||||
{ searchControls }
|
{ searchControls }
|
||||||
{ toggleCollapse }
|
{ toggleCollapse }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -82,6 +82,9 @@ const SIMPLE_SETTINGS = [
|
||||||
{ id: "TagPanel.disableTagPanel" },
|
{ id: "TagPanel.disableTagPanel" },
|
||||||
{ id: "enableWidgetScreenshots" },
|
{ id: "enableWidgetScreenshots" },
|
||||||
{ id: "RoomSubList.showEmpty" },
|
{ id: "RoomSubList.showEmpty" },
|
||||||
|
{ id: "pinMentionedRooms" },
|
||||||
|
{ id: "pinUnreadRooms" },
|
||||||
|
{ id: "showDeveloperTools" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// These settings must be defined in SettingsStore
|
// These settings must be defined in SettingsStore
|
||||||
|
@ -586,23 +589,21 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_onExportE2eKeysClicked: function() {
|
_onExportE2eKeysClicked: function() {
|
||||||
Modal.createTrackedDialogAsync('Export E2E Keys', '', (cb) => {
|
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
||||||
require.ensure(['../../async-components/views/dialogs/ExportE2eKeysDialog'], () => {
|
import('../../async-components/views/dialogs/ExportE2eKeysDialog'),
|
||||||
cb(require('../../async-components/views/dialogs/ExportE2eKeysDialog'));
|
{
|
||||||
}, "e2e-export");
|
|
||||||
}, {
|
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onImportE2eKeysClicked: function() {
|
_onImportE2eKeysClicked: function() {
|
||||||
Modal.createTrackedDialogAsync('Import E2E Keys', '', (cb) => {
|
Modal.createTrackedDialogAsync('Import E2E Keys', '',
|
||||||
require.ensure(['../../async-components/views/dialogs/ImportE2eKeysDialog'], () => {
|
import('../../async-components/views/dialogs/ImportE2eKeysDialog'),
|
||||||
cb(require('../../async-components/views/dialogs/ImportE2eKeysDialog'));
|
{
|
||||||
}, "e2e-export");
|
|
||||||
}, {
|
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderGroupSettings: function() {
|
_renderGroupSettings: function() {
|
||||||
|
@ -736,6 +737,16 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let keyBackupSection;
|
||||||
|
if (SettingsStore.isFeatureEnabled("feature_keybackup")) {
|
||||||
|
const KeyBackupPanel = sdk.getComponent('views.settings.KeyBackupPanel');
|
||||||
|
keyBackupSection = <div className="mx_UserSettings_section">
|
||||||
|
<h3>{ _t("Key Backup") }</h3>
|
||||||
|
<KeyBackupPanel />
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3>{ _t("Cryptography") }</h3>
|
<h3>{ _t("Cryptography") }</h3>
|
||||||
|
@ -751,6 +762,7 @@ module.exports = React.createClass({
|
||||||
<div className="mx_UserSettings_section">
|
<div className="mx_UserSettings_section">
|
||||||
{ CRYPTO_SETTINGS.map( this._renderDeviceSetting ) }
|
{ CRYPTO_SETTINGS.map( this._renderDeviceSetting ) }
|
||||||
</div>
|
</div>
|
||||||
|
{keyBackupSection}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -844,7 +856,7 @@ module.exports = React.createClass({
|
||||||
SettingsStore.getLabsFeatures().forEach((featureId) => {
|
SettingsStore.getLabsFeatures().forEach((featureId) => {
|
||||||
// TODO: this ought to be a separate component so that we don't need
|
// TODO: this ought to be a separate component so that we don't need
|
||||||
// to rebind the onChange each time we render
|
// to rebind the onChange each time we render
|
||||||
const onChange = async (e) => {
|
const onChange = async(e) => {
|
||||||
const checked = e.target.checked;
|
const checked = e.target.checked;
|
||||||
if (featureId === "feature_lazyloading") {
|
if (featureId === "feature_lazyloading") {
|
||||||
const confirmed = await this._onLazyLoadChanging(checked);
|
const confirmed = await this._onLazyLoadChanging(checked);
|
||||||
|
@ -1297,7 +1309,7 @@ module.exports = React.createClass({
|
||||||
// If the olmVersion is not defined then either crypto is disabled, or
|
// If the olmVersion is not defined then either crypto is disabled, or
|
||||||
// we are using a version old version of olm. We assume the former.
|
// we are using a version old version of olm. We assume the former.
|
||||||
let olmVersionString = "<not-enabled>";
|
let olmVersionString = "<not-enabled>";
|
||||||
if (olmVersion !== undefined) {
|
if (olmVersion) {
|
||||||
olmVersionString = `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}`;
|
olmVersionString = `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,5 +53,5 @@ module.exports = React.createClass({
|
||||||
</SyntaxHighlight>
|
</SyntaxHighlight>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -121,13 +121,12 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_onExportE2eKeysClicked: function() {
|
_onExportE2eKeysClicked: function() {
|
||||||
Modal.createTrackedDialogAsync('Export E2E Keys', 'Forgot Password', (cb) => {
|
Modal.createTrackedDialogAsync('Export E2E Keys', 'Forgot Password',
|
||||||
require.ensure(['../../../async-components/views/dialogs/ExportE2eKeysDialog'], () => {
|
import('../../../async-components/views/dialogs/ExportE2eKeysDialog'),
|
||||||
cb(require('../../../async-components/views/dialogs/ExportE2eKeysDialog'));
|
{
|
||||||
}, "e2e-export");
|
|
||||||
}, {
|
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
onInputChanged: function(stateKey, ev) {
|
onInputChanged: function(stateKey, ev) {
|
||||||
|
|
|
@ -90,7 +90,10 @@ module.exports = React.createClass({
|
||||||
// letting you do that login type
|
// letting you do that login type
|
||||||
this._stepRendererMap = {
|
this._stepRendererMap = {
|
||||||
'm.login.password': this._renderPasswordStep,
|
'm.login.password': this._renderPasswordStep,
|
||||||
'm.login.cas': this._renderCasStep,
|
|
||||||
|
// CAS and SSO are the same thing, modulo the url we link to
|
||||||
|
'm.login.cas': () => this._renderSsoStep(this._loginLogic.getSsoLoginUrl("cas")),
|
||||||
|
'm.login.sso': () => this._renderSsoStep(this._loginLogic.getSsoLoginUrl("sso")),
|
||||||
};
|
};
|
||||||
|
|
||||||
this._initLoginLogic();
|
this._initLoginLogic();
|
||||||
|
@ -201,10 +204,6 @@ module.exports = React.createClass({
|
||||||
}).done();
|
}).done();
|
||||||
},
|
},
|
||||||
|
|
||||||
onCasLogin: function() {
|
|
||||||
this._loginLogic.redirectToCas();
|
|
||||||
},
|
|
||||||
|
|
||||||
_onLoginAsGuestClick: function() {
|
_onLoginAsGuestClick: function() {
|
||||||
const self = this;
|
const self = this;
|
||||||
self.setState({
|
self.setState({
|
||||||
|
@ -541,10 +540,9 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderCasStep: function() {
|
_renderSsoStep: function(url) {
|
||||||
const CasLogin = sdk.getComponent('login.CasLogin');
|
|
||||||
return (
|
return (
|
||||||
<CasLogin onSubmit={this.onCasLogin} />
|
<a href={url} className="mx_Login_sso_link">{ _t('Sign in with single sign-on') }</a>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ module.exports = React.createClass({
|
||||||
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||||
|
|
||||||
let {member, fallbackUserId, onClick, viewUserOnClick, ...otherProps} = this.props;
|
let {member, fallbackUserId, onClick, viewUserOnClick, ...otherProps} = this.props;
|
||||||
let userId = member ? member.userId : fallbackUserId;
|
const userId = member ? member.userId : fallbackUserId;
|
||||||
|
|
||||||
if (viewUserOnClick) {
|
if (viewUserOnClick) {
|
||||||
onClick = () => {
|
onClick = () => {
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default class GroupInviteTileContextMenu extends React.Component {
|
||||||
Modal.createTrackedDialog('Reject community invite', '', QuestionDialog, {
|
Modal.createTrackedDialog('Reject community invite', '', QuestionDialog, {
|
||||||
title: _t('Reject invitation'),
|
title: _t('Reject invitation'),
|
||||||
description: _t('Are you sure you want to reject the invitation?'),
|
description: _t('Are you sure you want to reject the invitation?'),
|
||||||
onFinished: async (shouldLeave) => {
|
onFinished: async(shouldLeave) => {
|
||||||
if (!shouldLeave) return;
|
if (!shouldLeave) return;
|
||||||
|
|
||||||
// FIXME: controller shouldn't be loading a view :(
|
// FIXME: controller shouldn't be loading a view :(
|
||||||
|
|
|
@ -31,13 +31,13 @@ export default class ChangelogDialog extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const version = this.props.newVersion.split('-');
|
const version = this.props.newVersion.split('-');
|
||||||
const version2 = this.props.version.split('-');
|
const version2 = this.props.version.split('-');
|
||||||
if(version == null || version2 == null) return;
|
if (version == null || version2 == null) return;
|
||||||
// parse versions of form: [vectorversion]-react-[react-sdk-version]-js-[js-sdk-version]
|
// parse versions of form: [vectorversion]-react-[react-sdk-version]-js-[js-sdk-version]
|
||||||
for(let i=0; i<REPOS.length; i++) {
|
for (let i=0; i<REPOS.length; i++) {
|
||||||
const oldVersion = version2[2*i];
|
const oldVersion = version2[2*i];
|
||||||
const newVersion = version[2*i];
|
const newVersion = version[2*i];
|
||||||
request(`https://api.github.com/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`, (a, b, body) => {
|
request(`https://api.github.com/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`, (a, b, body) => {
|
||||||
if(body == null) return;
|
if (body == null) return;
|
||||||
this.setState({[REPOS[i]]: JSON.parse(body).commits});
|
this.setState({[REPOS[i]]: JSON.parse(body).commits});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ export default class ChangelogDialog extends React.Component {
|
||||||
{this.state[repo].map(this._elementsForCommit)}
|
{this.state[repo].map(this._elementsForCommit)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
|
@ -83,7 +83,7 @@ export default class ChangelogDialog extends React.Component {
|
||||||
button={_t("Update")}
|
button={_t("Update")}
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,6 @@ import Unread from '../../../Unread';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export default class ChatCreateOrReuseDialog extends React.Component {
|
export default class ChatCreateOrReuseDialog extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.onFinished = this.onFinished.bind(this);
|
this.onFinished = this.onFinished.bind(this);
|
||||||
|
|
|
@ -57,7 +57,7 @@ export default React.createClass({
|
||||||
let error = null;
|
let error = null;
|
||||||
if (!this.state.groupId) {
|
if (!this.state.groupId) {
|
||||||
error = _t("Community IDs cannot be empty.");
|
error = _t("Community IDs cannot be empty.");
|
||||||
} else if (!/^[a-z0-9=_\-\.\/]*$/.test(this.state.groupId)) {
|
} else if (!/^[a-z0-9=_\-./]*$/.test(this.state.groupId)) {
|
||||||
error = _t("Community IDs may only contain characters a-z, 0-9, or '=_-./'");
|
error = _t("Community IDs may only contain characters a-z, 0-9, or '=_-./'");
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
71
src/components/views/dialogs/CryptoStoreTooNewDialog.js
Normal file
71
src/components/views/dialogs/CryptoStoreTooNewDialog.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
|
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 sdk from '../../../index';
|
||||||
|
import dis from '../../../dispatcher';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import Modal from '../../../Modal';
|
||||||
|
|
||||||
|
export default (props) => {
|
||||||
|
const _onLogoutClicked = () => {
|
||||||
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
Modal.createTrackedDialog('Logout e2e db too new', '', QuestionDialog, {
|
||||||
|
title: _t("Sign out"),
|
||||||
|
description: _t(
|
||||||
|
"To avoid losing your chat history, you must export your room keys " +
|
||||||
|
"before logging out. You will need to go back to the newer version of " +
|
||||||
|
"Riot to do this",
|
||||||
|
),
|
||||||
|
button: _t("Sign out"),
|
||||||
|
focus: false,
|
||||||
|
onFinished: (doLogout) => {
|
||||||
|
if (doLogout) {
|
||||||
|
dis.dispatch({action: 'logout'});
|
||||||
|
props.onFinished();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const description =
|
||||||
|
_t("You've previously used a newer version of Riot on %(host)s. " +
|
||||||
|
"To use this version again with end to end encryption, you will " +
|
||||||
|
"need to sign out and back in again. ",
|
||||||
|
{host: props.host},
|
||||||
|
);
|
||||||
|
|
||||||
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
return (<BaseDialog className="mx_CryptoStoreTooNewDialog"
|
||||||
|
contentId='mx_Dialog_content'
|
||||||
|
title={_t("Incompatible Database")}
|
||||||
|
hasCancel={false}
|
||||||
|
onFinished={props.onFinished}
|
||||||
|
>
|
||||||
|
<div className="mx_Dialog_content" id='mx_Dialog_content'>
|
||||||
|
{ description }
|
||||||
|
</div>
|
||||||
|
<DialogButtons primaryButton={_t('Continue With Encryption Disabled')}
|
||||||
|
hasCancel={false}
|
||||||
|
onPrimaryButtonClick={props.onFinished}
|
||||||
|
>
|
||||||
|
<button onClick={_onLogoutClicked} >
|
||||||
|
{ _t('Sign out') }
|
||||||
|
</button>
|
||||||
|
</DialogButtons>
|
||||||
|
</BaseDialog>);
|
||||||
|
};
|
|
@ -101,6 +101,9 @@ export default React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onSubmit: function(ev) {
|
onSubmit: function(ev) {
|
||||||
|
if (this.refs.uiAuth) {
|
||||||
|
this.refs.uiAuth.tryContinue();
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
doingUIAuth: true,
|
doingUIAuth: true,
|
||||||
});
|
});
|
||||||
|
@ -217,6 +220,8 @@ export default React.createClass({
|
||||||
onAuthFinished={this._onUIAuthFinished}
|
onAuthFinished={this._onUIAuthFinished}
|
||||||
inputs={{}}
|
inputs={{}}
|
||||||
poll={true}
|
poll={true}
|
||||||
|
ref="uiAuth"
|
||||||
|
continueIsManaged={true}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
const inputClasses = classnames({
|
const inputClasses = classnames({
|
||||||
|
|
460
src/components/views/dialogs/keybackup/CreateKeyBackupDialog.js
Normal file
460
src/components/views/dialogs/keybackup/CreateKeyBackupDialog.js
Normal file
|
@ -0,0 +1,460 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
|
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 sdk from '../../../../index';
|
||||||
|
import MatrixClientPeg from '../../../../MatrixClientPeg';
|
||||||
|
|
||||||
|
import FileSaver from 'file-saver';
|
||||||
|
|
||||||
|
import { _t, _td } from '../../../../languageHandler';
|
||||||
|
|
||||||
|
const PHASE_PASSPHRASE = 0;
|
||||||
|
const PHASE_PASSPHRASE_CONFIRM = 1;
|
||||||
|
const PHASE_SHOWKEY = 2;
|
||||||
|
const PHASE_KEEPITSAFE = 3;
|
||||||
|
const PHASE_BACKINGUP = 4;
|
||||||
|
const PHASE_DONE = 5;
|
||||||
|
const PHASE_OPTOUT_CONFIRM = 6;
|
||||||
|
|
||||||
|
// XXX: copied from ShareDialog: factor out into utils
|
||||||
|
function selectText(target) {
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNodeContents(target);
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walks the user through the process of creating an e2e key backup
|
||||||
|
* on the server.
|
||||||
|
*/
|
||||||
|
export default React.createClass({
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
phase: PHASE_PASSPHRASE,
|
||||||
|
passPhrase: '',
|
||||||
|
passPhraseConfirm: '',
|
||||||
|
copied: false,
|
||||||
|
downloaded: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this._recoveryKeyNode = null;
|
||||||
|
this._keyBackupInfo = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
_collectRecoveryKeyNode: function(n) {
|
||||||
|
this._recoveryKeyNode = n;
|
||||||
|
},
|
||||||
|
|
||||||
|
_onCopyClick: function() {
|
||||||
|
selectText(this._recoveryKeyNode);
|
||||||
|
const successful = document.execCommand('copy');
|
||||||
|
if (successful) {
|
||||||
|
this.setState({
|
||||||
|
copied: true,
|
||||||
|
phase: PHASE_KEEPITSAFE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onDownloadClick: function() {
|
||||||
|
const blob = new Blob([this._keyBackupInfo.recovery_key], {
|
||||||
|
type: 'text/plain;charset=us-ascii',
|
||||||
|
});
|
||||||
|
FileSaver.saveAs(blob, 'recovery-key.txt');
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
downloaded: true,
|
||||||
|
phase: PHASE_KEEPITSAFE,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_createBackup: function() {
|
||||||
|
this.setState({
|
||||||
|
phase: PHASE_BACKINGUP,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
this._createBackupPromise = MatrixClientPeg.get().createKeyBackupVersion(
|
||||||
|
this._keyBackupInfo,
|
||||||
|
).then((info) => {
|
||||||
|
return MatrixClientPeg.get().backupAllGroupSessions(info.version);
|
||||||
|
}).then(() => {
|
||||||
|
this.setState({
|
||||||
|
phase: PHASE_DONE,
|
||||||
|
});
|
||||||
|
}).catch(e => {
|
||||||
|
console.log("Error creating key backup", e);
|
||||||
|
this.setState({
|
||||||
|
error: e,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onCancel: function() {
|
||||||
|
this.props.onFinished(false);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onDone: function() {
|
||||||
|
this.props.onFinished(true);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onOptOutClick: function() {
|
||||||
|
this.setState({phase: PHASE_OPTOUT_CONFIRM});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onSetUpClick: function() {
|
||||||
|
this.setState({phase: PHASE_PASSPHRASE});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onSkipPassPhraseClick: async function() {
|
||||||
|
this._keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion();
|
||||||
|
this.setState({
|
||||||
|
copied: false,
|
||||||
|
phase: PHASE_SHOWKEY,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onPassPhraseNextClick: function() {
|
||||||
|
this.setState({phase: PHASE_PASSPHRASE_CONFIRM});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onPassPhraseKeyPress: function(e) {
|
||||||
|
if (e.key === 'Enter' && this._passPhraseIsValid()) {
|
||||||
|
this._onPassPhraseNextClick();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onPassPhraseConfirmNextClick: async function() {
|
||||||
|
this._keyBackupInfo = await MatrixClientPeg.get().prepareKeyBackupVersion(this.state.passPhrase);
|
||||||
|
this.setState({
|
||||||
|
copied: false,
|
||||||
|
phase: PHASE_SHOWKEY,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onPassPhraseConfirmKeyPress: function(e) {
|
||||||
|
if (e.key === 'Enter' && this.state.passPhrase === this.state.passPhraseConfirm) {
|
||||||
|
this._onPassPhraseConfirmNextClick();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onSetAgainClick: function() {
|
||||||
|
this.setState({
|
||||||
|
passPhrase: '',
|
||||||
|
passPhraseConfirm: '',
|
||||||
|
phase: PHASE_PASSPHRASE,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onKeepItSafeGotItClick: function() {
|
||||||
|
this.setState({
|
||||||
|
phase: PHASE_SHOWKEY,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onPassPhraseChange: function(e) {
|
||||||
|
this.setState({
|
||||||
|
passPhrase: e.target.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onPassPhraseConfirmChange: function(e) {
|
||||||
|
this.setState({
|
||||||
|
passPhraseConfirm: e.target.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_passPhraseIsValid: function() {
|
||||||
|
return this.state.passPhrase !== '';
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderPhasePassPhrase: function() {
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
|
return <div>
|
||||||
|
<p>{_t("Secure your encrypted message history with a Recovery Passphrase.")}</p>
|
||||||
|
<p>{_t("You'll need it if you log out or lose access to this device.")}</p>
|
||||||
|
|
||||||
|
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
||||||
|
<input type="password"
|
||||||
|
onChange={this._onPassPhraseChange}
|
||||||
|
onKeyPress={this._onPassPhraseKeyPress}
|
||||||
|
value={this.state.passPhrase}
|
||||||
|
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
||||||
|
placeholder={_t("Enter a passphrase...")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogButtons primaryButton={_t('Next')}
|
||||||
|
onPrimaryButtonClick={this._onPassPhraseNextClick}
|
||||||
|
hasCancel={false}
|
||||||
|
disabled={!this._passPhraseIsValid()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p>{_t(
|
||||||
|
"If you don't want encrypted message history to be availble on other devices, "+
|
||||||
|
"<button>opt out</button>.",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
button: sub => <AccessibleButton
|
||||||
|
element="span"
|
||||||
|
className="mx_linkButton"
|
||||||
|
onClick={this._onOptOutClick}
|
||||||
|
>
|
||||||
|
{sub}
|
||||||
|
</AccessibleButton>,
|
||||||
|
},
|
||||||
|
)}</p>
|
||||||
|
<p>{_t(
|
||||||
|
"Or, if you don't want to create a Recovery Passphrase, skip this step and "+
|
||||||
|
"<button>download a recovery key</button>.",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
button: sub => <AccessibleButton
|
||||||
|
element="span"
|
||||||
|
className="mx_linkButton"
|
||||||
|
onClick={this._onSkipPassPhraseClick}
|
||||||
|
>
|
||||||
|
{sub}
|
||||||
|
</AccessibleButton>,
|
||||||
|
},
|
||||||
|
)}</p>
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderPhasePassPhraseConfirm: function() {
|
||||||
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
|
|
||||||
|
let passPhraseMatch = null;
|
||||||
|
if (this.state.passPhraseConfirm.length > 0) {
|
||||||
|
let matchText;
|
||||||
|
if (this.state.passPhraseConfirm === this.state.passPhrase) {
|
||||||
|
matchText = _t("That matches!");
|
||||||
|
} else {
|
||||||
|
matchText = _t("That doesn't match.");
|
||||||
|
}
|
||||||
|
passPhraseMatch = <div className="mx_CreateKeyBackupDialog_passPhraseMatch">
|
||||||
|
<div>{matchText}</div>
|
||||||
|
<div>
|
||||||
|
<AccessibleButton element="span" className="mx_linkButton" onClick={this._onSetAgainClick}>
|
||||||
|
{_t("Go back to set it again.")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
return <div>
|
||||||
|
<p>{_t(
|
||||||
|
"Type in your Recovery Passphrase to confirm you remember it. " +
|
||||||
|
"If it helps, add it to your password manager or store it " +
|
||||||
|
"somewhere safe.",
|
||||||
|
)}</p>
|
||||||
|
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
||||||
|
{passPhraseMatch}
|
||||||
|
<div>
|
||||||
|
<input type="password"
|
||||||
|
onChange={this._onPassPhraseConfirmChange}
|
||||||
|
onKeyPress={this._onPassPhraseConfirmKeyPress}
|
||||||
|
value={this.state.passPhraseConfirm}
|
||||||
|
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
||||||
|
placeholder={_t("Repeat your passphrase...")}
|
||||||
|
autoFocus={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DialogButtons primaryButton={_t('Next')}
|
||||||
|
onPrimaryButtonClick={this._onPassPhraseConfirmNextClick}
|
||||||
|
hasCancel={false}
|
||||||
|
disabled={this.state.passPhrase !== this.state.passPhraseConfirm}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderPhaseShowKey: function() {
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
return <div>
|
||||||
|
<p>{_t("Make a copy of this Recovery Key and keep it safe.")}</p>
|
||||||
|
<p>{_t("As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.")}</p>
|
||||||
|
<p className="mx_CreateKeyBackupDialog_primaryContainer">
|
||||||
|
<div>{_t("Your Recovery Key")}</div>
|
||||||
|
<div className="mx_CreateKeyBackupDialog_recoveryKeyButtons">
|
||||||
|
<button onClick={this._onCopyClick}>
|
||||||
|
{_t("Copy to clipboard")}
|
||||||
|
</button>
|
||||||
|
{
|
||||||
|
// FIXME REDESIGN: buttons should be adjacent but insufficient room in current design
|
||||||
|
}
|
||||||
|
<br /><br />
|
||||||
|
<button onClick={this._onDownloadClick}>
|
||||||
|
{_t("Download")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="mx_CreateKeyBackupDialog_recoveryKey">
|
||||||
|
<code ref={this._collectRecoveryKeyNode}>{this._keyBackupInfo.recovery_key}</code>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
<DialogButtons primaryButton={_t("I've made a copy")}
|
||||||
|
onPrimaryButtonClick={this._createBackup}
|
||||||
|
hasCancel={false}
|
||||||
|
disabled={!this.state.copied}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderPhaseKeepItSafe: function() {
|
||||||
|
let introText;
|
||||||
|
if (this.state.copied) {
|
||||||
|
introText = _t(
|
||||||
|
"Your Recovery Key has been <b>copied to your clipboard</b>, paste it to:",
|
||||||
|
{}, {b: s => <b>{s}</b>},
|
||||||
|
);
|
||||||
|
} else if (this.state.downloaded) {
|
||||||
|
introText = _t(
|
||||||
|
"Your Recovery Key is in your <b>Downloads</b> folder.",
|
||||||
|
{}, {b: s => <b>{s}</b>},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
return <div>
|
||||||
|
{introText}
|
||||||
|
<ul>
|
||||||
|
<li>{_t("<b>Print it</b> and store it somewhere safe", {}, {b: s => <b>{s}</b>})}</li>
|
||||||
|
<li>{_t("<b>Save it</b> on a USB key or backup drive", {}, {b: s => <b>{s}</b>})}</li>
|
||||||
|
<li>{_t("<b>Copy it</b> to your personal cloud storage", {}, {b: s => <b>{s}</b>})}</li>
|
||||||
|
</ul>
|
||||||
|
<DialogButtons primaryButton={_t("Got it")}
|
||||||
|
onPrimaryButtonClick={this._onKeepItSafeGotItClick}
|
||||||
|
hasCancel={false}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderBusyPhase: function(text) {
|
||||||
|
const Spinner = sdk.getComponent('views.elements.Spinner');
|
||||||
|
return <div>
|
||||||
|
<p>{_t(text)}</p>
|
||||||
|
<Spinner />
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderPhaseDone: function() {
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
return <div>
|
||||||
|
<p>{_t("Backup created")}</p>
|
||||||
|
<p>{_t("Your encryption keys are now being backed up to your Homeserver.")}</p>
|
||||||
|
<DialogButtons primaryButton={_t('Close')}
|
||||||
|
onPrimaryButtonClick={this._onDone}
|
||||||
|
hasCancel={false}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderPhaseOptOutConfirm: function() {
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
return <div>
|
||||||
|
{_t(
|
||||||
|
"Without setting up Secure Message Recovery, you won't be able to restore your " +
|
||||||
|
"encrypted message history if you log out or use another device.",
|
||||||
|
)}
|
||||||
|
<DialogButtons primaryButton={_t('Set up Secure Message Recovery')}
|
||||||
|
onPrimaryButtonClick={this._onSetUpClick}
|
||||||
|
hasCancel={false}
|
||||||
|
>
|
||||||
|
<button onClick={this._onCancel}>I understand, continue without</button>
|
||||||
|
</DialogButtons>
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
|
_titleForPhase: function(phase) {
|
||||||
|
switch (phase) {
|
||||||
|
case PHASE_PASSPHRASE:
|
||||||
|
return _t('Create a Recovery Passphrase');
|
||||||
|
case PHASE_PASSPHRASE_CONFIRM:
|
||||||
|
return _t('Confirm Recovery Passphrase');
|
||||||
|
case PHASE_OPTOUT_CONFIRM:
|
||||||
|
return _t('Warning!');
|
||||||
|
case PHASE_SHOWKEY:
|
||||||
|
return _t('Recovery Key');
|
||||||
|
case PHASE_KEEPITSAFE:
|
||||||
|
return _t('Keep it safe');
|
||||||
|
case PHASE_BACKINGUP:
|
||||||
|
return _t('Backing up...');
|
||||||
|
default:
|
||||||
|
return _t("Create Key Backup");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
|
||||||
|
let content;
|
||||||
|
if (this.state.error) {
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
content = <div>
|
||||||
|
<p>{_t("Unable to create key backup")}</p>
|
||||||
|
<div className="mx_Dialog_buttons">
|
||||||
|
<DialogButtons primaryButton={_t('Retry')}
|
||||||
|
onPrimaryButtonClick={this._createBackup}
|
||||||
|
hasCancel={true}
|
||||||
|
onCancel={this._onCancel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
} else {
|
||||||
|
switch (this.state.phase) {
|
||||||
|
case PHASE_PASSPHRASE:
|
||||||
|
content = this._renderPhasePassPhrase();
|
||||||
|
break;
|
||||||
|
case PHASE_PASSPHRASE_CONFIRM:
|
||||||
|
content = this._renderPhasePassPhraseConfirm();
|
||||||
|
break;
|
||||||
|
case PHASE_SHOWKEY:
|
||||||
|
content = this._renderPhaseShowKey();
|
||||||
|
break;
|
||||||
|
case PHASE_KEEPITSAFE:
|
||||||
|
content = this._renderPhaseKeepItSafe();
|
||||||
|
break;
|
||||||
|
case PHASE_BACKINGUP:
|
||||||
|
content = this._renderBusyPhase(_td("Backing up..."));
|
||||||
|
break;
|
||||||
|
case PHASE_DONE:
|
||||||
|
content = this._renderPhaseDone();
|
||||||
|
break;
|
||||||
|
case PHASE_OPTOUT_CONFIRM:
|
||||||
|
content = this._renderPhaseOptOutConfirm();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseDialog className='mx_CreateKeyBackupDialog'
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
|
title={this._titleForPhase(this.state.phase)}
|
||||||
|
hasCancel={[PHASE_DONE].includes(this.state.phase)}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
303
src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js
Normal file
303
src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
|
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 sdk from '../../../../index';
|
||||||
|
import MatrixClientPeg from '../../../../MatrixClientPeg';
|
||||||
|
import Modal from '../../../../Modal';
|
||||||
|
|
||||||
|
import { _t } from '../../../../languageHandler';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog for restoring e2e keys from a backup and the user's recovery key
|
||||||
|
*/
|
||||||
|
export default React.createClass({
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
backupInfo: null,
|
||||||
|
loading: false,
|
||||||
|
loadError: null,
|
||||||
|
restoreError: null,
|
||||||
|
recoveryKey: "",
|
||||||
|
recoverInfo: null,
|
||||||
|
recoveryKeyValid: false,
|
||||||
|
forceRecoveryKey: false,
|
||||||
|
passPhrase: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this._loadBackupStatus();
|
||||||
|
},
|
||||||
|
|
||||||
|
_onCancel: function() {
|
||||||
|
this.props.onFinished(false);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onDone: function() {
|
||||||
|
this.props.onFinished(true);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onUseRecoveryKeyClick: function() {
|
||||||
|
this.setState({
|
||||||
|
forceRecoveryKey: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onResetRecoveryClick: function() {
|
||||||
|
this.props.onFinished(false);
|
||||||
|
const CreateKeyBackupDialog = sdk.getComponent("dialogs.keybackup.CreateKeyBackupDialog");
|
||||||
|
Modal.createTrackedDialog('Create Key Backup', '', CreateKeyBackupDialog, {});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onRecoveryKeyChange: function(e) {
|
||||||
|
this.setState({
|
||||||
|
recoveryKey: e.target.value,
|
||||||
|
recoveryKeyValid: MatrixClientPeg.get().isValidRecoveryKey(e.target.value),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onPassPhraseNext: async function() {
|
||||||
|
this.setState({
|
||||||
|
loading: true,
|
||||||
|
restoreError: null,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithPassword(
|
||||||
|
this.state.passPhrase, undefined, undefined, this.state.backupInfo.version,
|
||||||
|
);
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
recoverInfo,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error restoring backup", e);
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
restoreError: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onRecoveryKeyNext: async function() {
|
||||||
|
this.setState({
|
||||||
|
loading: true,
|
||||||
|
restoreError: null,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithRecoveryKey(
|
||||||
|
this.state.recoveryKey, undefined, undefined, this.state.backupInfo.version,
|
||||||
|
);
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
recoverInfo,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error restoring backup", e);
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
restoreError: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onPassPhraseChange: function(e) {
|
||||||
|
this.setState({
|
||||||
|
passPhrase: e.target.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onPassPhraseKeyPress: function(e) {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
this._onPassPhraseNext();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onRecoveryKeyKeyPress: function(e) {
|
||||||
|
if (e.key === "Enter" && this.state.recoveryKeyValid) {
|
||||||
|
this._onRecoveryKeyNext();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_loadBackupStatus: async function() {
|
||||||
|
this.setState({
|
||||||
|
loading: true,
|
||||||
|
loadError: null,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
||||||
|
this.setState({
|
||||||
|
loadError: null,
|
||||||
|
loading: false,
|
||||||
|
backupInfo,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error loading backup status", e);
|
||||||
|
this.setState({
|
||||||
|
loadError: e,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
|
||||||
|
const backupHasPassphrase = (
|
||||||
|
this.state.backupInfo &&
|
||||||
|
this.state.backupInfo.auth_data &&
|
||||||
|
this.state.backupInfo.auth_data.private_key_salt &&
|
||||||
|
this.state.backupInfo.auth_data.private_key_iterations
|
||||||
|
);
|
||||||
|
|
||||||
|
let content;
|
||||||
|
let title;
|
||||||
|
if (this.state.loading) {
|
||||||
|
title = _t("Loading...");
|
||||||
|
content = <Spinner />;
|
||||||
|
} else if (this.state.loadError) {
|
||||||
|
title = _t("Error");
|
||||||
|
content = _t("Unable to load backup status");
|
||||||
|
} else if (this.state.restoreError) {
|
||||||
|
title = _t("Error");
|
||||||
|
content = _t("Unable to restore backup");
|
||||||
|
} else if (this.state.backupInfo === null) {
|
||||||
|
title = _t("Error");
|
||||||
|
content = _t("No backup found!");
|
||||||
|
} else if (this.state.recoverInfo) {
|
||||||
|
title = _t("Backup Restored");
|
||||||
|
let failedToDecrypt;
|
||||||
|
if (this.state.recoverInfo.total > this.state.recoverInfo.imported) {
|
||||||
|
failedToDecrypt = <p>{_t(
|
||||||
|
"Failed to decrypt %(failedCount)s sessions!",
|
||||||
|
{failedCount: this.state.recoverInfo.total - this.state.recoverInfo.imported},
|
||||||
|
)}</p>;
|
||||||
|
}
|
||||||
|
content = <div>
|
||||||
|
<p>{_t("Restored %(sessionCount)s session keys", {sessionCount: this.state.recoverInfo.imported})}</p>
|
||||||
|
{failedToDecrypt}
|
||||||
|
</div>;
|
||||||
|
} else if (backupHasPassphrase && !this.state.forceRecoveryKey) {
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
|
title = _t("Enter Recovery Passphrase");
|
||||||
|
content = <div>
|
||||||
|
{_t(
|
||||||
|
"Access your secure message history and set up secure " +
|
||||||
|
"messaging by entering your recovery passphrase.",
|
||||||
|
)}<br />
|
||||||
|
|
||||||
|
<div className="mx_RestoreKeyBackupDialog_primaryContainer">
|
||||||
|
<input type="password"
|
||||||
|
className="mx_RestoreKeyBackupDialog_passPhraseInput"
|
||||||
|
onChange={this._onPassPhraseChange}
|
||||||
|
onKeyPress={this._onPassPhraseKeyPress}
|
||||||
|
value={this.state.passPhrase}
|
||||||
|
autoFocus={true}
|
||||||
|
/>
|
||||||
|
<DialogButtons primaryButton={_t('Next')}
|
||||||
|
onPrimaryButtonClick={this._onPassPhraseNext}
|
||||||
|
hasCancel={true}
|
||||||
|
onCancel={this._onCancel}
|
||||||
|
focus={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{_t(
|
||||||
|
"If you've forgotten your recovery passphrase you can "+
|
||||||
|
"<button1>use your recovery key</button1> or " +
|
||||||
|
"<button2>set up new recovery options</button2>"
|
||||||
|
, {}, {
|
||||||
|
button1: s => <AccessibleButton className="mx_linkButton"
|
||||||
|
element="span"
|
||||||
|
onClick={this._onUseRecoveryKeyClick}
|
||||||
|
>
|
||||||
|
{s}
|
||||||
|
</AccessibleButton>,
|
||||||
|
button2: s => <AccessibleButton className="mx_linkButton"
|
||||||
|
element="span"
|
||||||
|
onClick={this._onResetRecoveryClick}
|
||||||
|
>
|
||||||
|
{s}
|
||||||
|
</AccessibleButton>,
|
||||||
|
})}
|
||||||
|
</div>;
|
||||||
|
} else {
|
||||||
|
title = _t("Enter Recovery Key");
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
|
|
||||||
|
let keyStatus;
|
||||||
|
if (this.state.recoveryKey.length === 0) {
|
||||||
|
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus"></div>;
|
||||||
|
} else if (this.state.recoveryKeyValid) {
|
||||||
|
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus">
|
||||||
|
{"\uD83D\uDC4D "}{_t("This looks like a valid recovery key!")}
|
||||||
|
</div>;
|
||||||
|
} else {
|
||||||
|
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus">
|
||||||
|
{"\uD83D\uDC4E "}{_t("Not a valid recovery key")}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
content = <div>
|
||||||
|
{_t(
|
||||||
|
"Access your secure message history and set up secure " +
|
||||||
|
"messaging by entering your recovery key.",
|
||||||
|
)}<br />
|
||||||
|
|
||||||
|
<div className="mx_RestoreKeyBackupDialog_primaryContainer">
|
||||||
|
<input className="mx_RestoreKeyBackupDialog_recoveryKeyInput"
|
||||||
|
onChange={this._onRecoveryKeyChange}
|
||||||
|
onKeyPress={this._onRecoveryKeyKeyPress}
|
||||||
|
value={this.state.recoveryKey}
|
||||||
|
autoFocus={true}
|
||||||
|
/>
|
||||||
|
{keyStatus}
|
||||||
|
<DialogButtons primaryButton={_t('Next')}
|
||||||
|
onPrimaryButtonClick={this._onRecoveryKeyNext}
|
||||||
|
hasCancel={true}
|
||||||
|
onCancel={this._onCancel}
|
||||||
|
focus={false}
|
||||||
|
primaryDisabled={!this.state.recoveryKeyValid}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{_t(
|
||||||
|
"If you've forgotten your recovery passphrase you can "+
|
||||||
|
"<button>set up new recovery options</button>"
|
||||||
|
, {}, {
|
||||||
|
button: s => <AccessibleButton className="mx_linkButton"
|
||||||
|
element="span"
|
||||||
|
onClick={this._onResetRecoveryClick}
|
||||||
|
>
|
||||||
|
{s}
|
||||||
|
</AccessibleButton>,
|
||||||
|
})}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseDialog className='mx_RestoreKeyBackupDialog'
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -153,8 +153,8 @@ export default class NetworkDropdown extends React.Component {
|
||||||
|
|
||||||
const sortedInstances = this.props.protocols[proto].instances;
|
const sortedInstances = this.props.protocols[proto].instances;
|
||||||
sortedInstances.sort(function(x, y) {
|
sortedInstances.sort(function(x, y) {
|
||||||
const a = x.desc
|
const a = x.desc;
|
||||||
const b = y.desc
|
const b = y.desc;
|
||||||
if (a < b) {
|
if (a < b) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (a > b) {
|
} else if (a > b) {
|
||||||
|
@ -208,7 +208,7 @@ export default class NetworkDropdown extends React.Component {
|
||||||
return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}>
|
return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}>
|
||||||
{icon}
|
{icon}
|
||||||
<span className="mx_NetworkDropdown_menu_network">{name}</span>
|
<span className="mx_NetworkDropdown_menu_network">{name}</span>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -223,11 +223,11 @@ export default class NetworkDropdown extends React.Component {
|
||||||
current_value = <input type="text" className="mx_NetworkDropdown_networkoption"
|
current_value = <input type="text" className="mx_NetworkDropdown_networkoption"
|
||||||
ref={this.collectInputTextBox} onKeyUp={this.onInputKeyUp}
|
ref={this.collectInputTextBox} onKeyUp={this.onInputKeyUp}
|
||||||
placeholder="matrix.org" // 'matrix.org' as an example of an HS name
|
placeholder="matrix.org" // 'matrix.org' as an example of an HS name
|
||||||
/>
|
/>;
|
||||||
} else {
|
} else {
|
||||||
const instance = instanceForInstanceId(this.props.protocols, this.state.selectedInstanceId);
|
const instance = instanceForInstanceId(this.props.protocols, this.state.selectedInstanceId);
|
||||||
current_value = this._makeMenuOption(
|
current_value = this._makeMenuOption(
|
||||||
this.state.selectedServer, instance, this.state.includeAll, false
|
this.state.selectedServer, instance, this.state.includeAll, false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -318,6 +318,19 @@ export default class AppTile extends React.Component {
|
||||||
}
|
}
|
||||||
this.setState({deleting: true});
|
this.setState({deleting: true});
|
||||||
|
|
||||||
|
// HACK: This is a really dirty way to ensure that Jitsi cleans up
|
||||||
|
// its hold on the webcam. Without this, the widget holds a media
|
||||||
|
// stream open, even after death. See https://github.com/vector-im/riot-web/issues/7351
|
||||||
|
if (this.refs.appFrame) {
|
||||||
|
// In practice we could just do `+= ''` to trick the browser
|
||||||
|
// into thinking the URL changed, however I can foresee this
|
||||||
|
// being optimized out by a browser. Instead, we'll just point
|
||||||
|
// the iframe at a page that is reasonably safe to use in the
|
||||||
|
// event the iframe doesn't wink away.
|
||||||
|
// This is relative to where the Riot instance is located.
|
||||||
|
this.refs.appFrame.src = 'about:blank';
|
||||||
|
}
|
||||||
|
|
||||||
WidgetUtils.setRoomWidget(
|
WidgetUtils.setRoomWidget(
|
||||||
this.props.room.roomId,
|
this.props.room.roomId,
|
||||||
this.props.id,
|
this.props.id,
|
||||||
|
|
|
@ -78,7 +78,7 @@ export default React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
let blacklistButton = null, verifyButton = null;
|
let blacklistButton = null; let verifyButton = null;
|
||||||
|
|
||||||
if (this.state.device.isBlocked()) {
|
if (this.state.device.isBlocked()) {
|
||||||
blacklistButton = (
|
blacklistButton = (
|
||||||
|
|
|
@ -43,7 +43,11 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
focus: PropTypes.bool,
|
focus: PropTypes.bool,
|
||||||
|
|
||||||
|
// disables the primary and cancel buttons
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
|
|
||||||
|
// disables only the primary button
|
||||||
|
primaryDisabled: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -70,15 +74,15 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
|
{ cancelButton }
|
||||||
|
{ this.props.children }
|
||||||
<button className={primaryButtonClassName}
|
<button className={primaryButtonClassName}
|
||||||
onClick={this.props.onPrimaryButtonClick}
|
onClick={this.props.onPrimaryButtonClick}
|
||||||
autoFocus={this.props.focus}
|
autoFocus={this.props.focus}
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled || this.props.primaryDisabled}
|
||||||
>
|
>
|
||||||
{ this.props.primaryButton }
|
{ this.props.primaryButton }
|
||||||
</button>
|
</button>
|
||||||
{ this.props.children }
|
|
||||||
{ cancelButton }
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -122,7 +122,6 @@ export default class EditableTextContainer extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EditableTextContainer.propTypes = {
|
EditableTextContainer.propTypes = {
|
||||||
|
|
|
@ -16,13 +16,13 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
const React = require('react');
|
||||||
|
|
||||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
|
|
||||||
import {formatDate} from '../../../DateUtils';
|
import {formatDate} from '../../../DateUtils';
|
||||||
var filesize = require('filesize');
|
const filesize = require('filesize');
|
||||||
var AccessibleButton = require('../../../components/views/elements/AccessibleButton');
|
const AccessibleButton = require('../../../components/views/elements/AccessibleButton');
|
||||||
const Modal = require('../../../Modal');
|
const Modal = require('../../../Modal');
|
||||||
const sdk = require('../../../index');
|
const sdk = require('../../../index');
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
@ -69,24 +69,24 @@ module.exports = React.createClass({
|
||||||
Modal.createTrackedDialog('Confirm Redact Dialog', 'Image View', ConfirmRedactDialog, {
|
Modal.createTrackedDialog('Confirm Redact Dialog', 'Image View', ConfirmRedactDialog, {
|
||||||
onFinished: (proceed) => {
|
onFinished: (proceed) => {
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
var self = this;
|
const self = this;
|
||||||
MatrixClientPeg.get().redactEvent(
|
MatrixClientPeg.get().redactEvent(
|
||||||
this.props.mxEvent.getRoomId(), this.props.mxEvent.getId()
|
this.props.mxEvent.getRoomId(), this.props.mxEvent.getId(),
|
||||||
).catch(function(e) {
|
).catch(function(e) {
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
// display error message stating you couldn't delete this.
|
// display error message stating you couldn't delete this.
|
||||||
var code = e.errcode || e.statusCode;
|
const code = e.errcode || e.statusCode;
|
||||||
Modal.createTrackedDialog('You cannot delete this image.', '', ErrorDialog, {
|
Modal.createTrackedDialog('You cannot delete this image.', '', ErrorDialog, {
|
||||||
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();
|
}).done();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getName: function () {
|
getName: function() {
|
||||||
var name = this.props.name;
|
let name = this.props.name;
|
||||||
if (name && this.props.link) {
|
if (name && this.props.link) {
|
||||||
name = <a href={ this.props.link } target="_blank" rel="noopener">{ name }</a>;
|
name = <a href={ this.props.link } target="_blank" rel="noopener">{ name }</a>;
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,6 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// In theory max-width: 80%, max-height: 80% on the CSS should work
|
// In theory max-width: 80%, max-height: 80% on the CSS should work
|
||||||
// but in practice, it doesn't, so do it manually:
|
// but in practice, it doesn't, so do it manually:
|
||||||
|
@ -123,7 +122,7 @@ module.exports = React.createClass({
|
||||||
height: displayHeight
|
height: displayHeight
|
||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
var style, res;
|
let style; let res;
|
||||||
|
|
||||||
if (this.props.width && this.props.height) {
|
if (this.props.width && this.props.height) {
|
||||||
style = {
|
style = {
|
||||||
|
@ -133,23 +132,22 @@ module.exports = React.createClass({
|
||||||
res = style.width + "x" + style.height + "px";
|
res = style.width + "x" + style.height + "px";
|
||||||
}
|
}
|
||||||
|
|
||||||
var size;
|
let size;
|
||||||
if (this.props.fileSize) {
|
if (this.props.fileSize) {
|
||||||
size = filesize(this.props.fileSize);
|
size = filesize(this.props.fileSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
var size_res;
|
let size_res;
|
||||||
if (size && res) {
|
if (size && res) {
|
||||||
size_res = size + ", " + res;
|
size_res = size + ", " + res;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
size_res = size || res;
|
size_res = size || res;
|
||||||
}
|
}
|
||||||
|
|
||||||
var showEventMeta = !!this.props.mxEvent;
|
const showEventMeta = !!this.props.mxEvent;
|
||||||
|
|
||||||
var eventMeta;
|
let eventMeta;
|
||||||
if(showEventMeta) {
|
if (showEventMeta) {
|
||||||
// Figure out the sender, defaulting to mxid
|
// Figure out the sender, defaulting to mxid
|
||||||
let sender = this.props.mxEvent.getSender();
|
let sender = this.props.mxEvent.getSender();
|
||||||
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
@ -163,8 +161,8 @@ module.exports = React.createClass({
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
var eventRedact;
|
let eventRedact;
|
||||||
if(showEventMeta) {
|
if (showEventMeta) {
|
||||||
eventRedact = (<div className="mx_ImageView_button" onClick={this.onRedactClick}>
|
eventRedact = (<div className="mx_ImageView_button" onClick={this.onRedactClick}>
|
||||||
{ _t('Remove') }
|
{ _t('Remove') }
|
||||||
</div>);
|
</div>);
|
||||||
|
@ -175,10 +173,10 @@ module.exports = React.createClass({
|
||||||
<div className="mx_ImageView_lhs">
|
<div className="mx_ImageView_lhs">
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_ImageView_content">
|
<div className="mx_ImageView_content">
|
||||||
<img src={this.props.src} style={style}/>
|
<img src={this.props.src} style={style} />
|
||||||
<div className="mx_ImageView_labelWrapper">
|
<div className="mx_ImageView_labelWrapper">
|
||||||
<div className="mx_ImageView_label">
|
<div className="mx_ImageView_label">
|
||||||
<AccessibleButton className="mx_ImageView_cancel" onClick={ this.props.onFinished }><img src="img/cancel-white.svg" width="18" height="18" alt={ _t('Close') }/></AccessibleButton>
|
<AccessibleButton className="mx_ImageView_cancel" onClick={ this.props.onFinished }><img src="img/cancel-white.svg" width="18" height="18" alt={ _t('Close') } /></AccessibleButton>
|
||||||
<div className="mx_ImageView_shim">
|
<div className="mx_ImageView_shim">
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_ImageView_name">
|
<div className="mx_ImageView_name">
|
||||||
|
@ -187,7 +185,7 @@ module.exports = React.createClass({
|
||||||
{ eventMeta }
|
{ eventMeta }
|
||||||
<a className="mx_ImageView_link" href={ this.props.src } download={ this.props.name } target="_blank" rel="noopener">
|
<a className="mx_ImageView_link" href={ this.props.src } download={ this.props.name } target="_blank" rel="noopener">
|
||||||
<div className="mx_ImageView_download">
|
<div className="mx_ImageView_download">
|
||||||
{ _t('Download this file') }<br/>
|
{ _t('Download this file') }<br />
|
||||||
<span className="mx_ImageView_size">{ size_res }</span>
|
<span className="mx_ImageView_size">{ size_res }</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -201,5 +199,5 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,14 +20,14 @@ module.exports = React.createClass({
|
||||||
displayName: 'InlineSpinner',
|
displayName: 'InlineSpinner',
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var w = this.props.w || 16;
|
const w = this.props.w || 16;
|
||||||
var h = this.props.h || 16;
|
const h = this.props.h || 16;
|
||||||
var imgClass = this.props.imgClassName || "";
|
const imgClass = this.props.imgClassName || "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_InlineSpinner">
|
<div className="mx_InlineSpinner">
|
||||||
<img src="img/spinner.gif" width={w} height={h} className={imgClass}/>
|
<img src="img/spinner.gif" width={w} height={h} className={imgClass} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -54,7 +54,6 @@ function getOrCreateContainer(containerId) {
|
||||||
* bounding rect as the parent of PE.
|
* bounding rect as the parent of PE.
|
||||||
*/
|
*/
|
||||||
export default class PersistedElement extends React.Component {
|
export default class PersistedElement extends React.Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
// Unique identifier for this PersistedElement instance
|
// Unique identifier for this PersistedElement instance
|
||||||
// Any PersistedElements with the same persistKey will use
|
// Any PersistedElements with the same persistKey will use
|
||||||
|
|
|
@ -29,7 +29,7 @@ const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN);
|
||||||
|
|
||||||
// For URLs of matrix.to links in the timeline which have been reformatted by
|
// For URLs of matrix.to links in the timeline which have been reformatted by
|
||||||
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
|
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
|
||||||
const REGEX_LOCAL_MATRIXTO = /^#\/(?:user|room|group)\/(([#!@+])[^\/]*)$/;
|
const REGEX_LOCAL_MATRIXTO = /^#\/(?:user|room|group)\/(([#!@+])[^/]*)$/;
|
||||||
|
|
||||||
const Pill = React.createClass({
|
const Pill = React.createClass({
|
||||||
statics: {
|
statics: {
|
||||||
|
|
|
@ -16,19 +16,19 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
const React = require('react');
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'Spinner',
|
displayName: 'Spinner',
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var w = this.props.w || 32;
|
const w = this.props.w || 32;
|
||||||
var h = this.props.h || 32;
|
const h = this.props.h || 32;
|
||||||
var imgClass = this.props.imgClassName || "";
|
const imgClass = this.props.imgClassName || "";
|
||||||
return (
|
return (
|
||||||
<div className="mx_Spinner">
|
<div className="mx_Spinner">
|
||||||
<img src="img/spinner.gif" width={w} height={h} className={imgClass}/>
|
<img src="img/spinner.gif" width={w} height={h} className={imgClass} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,7 +20,6 @@ import TintableSvg from './TintableSvg';
|
||||||
import AccessibleButton from './AccessibleButton';
|
import AccessibleButton from './AccessibleButton';
|
||||||
|
|
||||||
export default class TintableSvgButton extends React.Component {
|
export default class TintableSvgButton extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ module.exports = React.createClass({
|
||||||
<div className="mx_MatrixToolbar_content">
|
<div className="mx_MatrixToolbar_content">
|
||||||
{ _t('You are not receiving desktop notifications') } <a className="mx_MatrixToolbar_link" onClick={ this.onClick }> { _t('Enable them now') }</a>
|
{ _t('You are not receiving desktop notifications') } <a className="mx_MatrixToolbar_link" onClick={ this.onClick }> { _t('Enable them now') }</a>
|
||||||
</div>
|
</div>
|
||||||
<AccessibleButton className="mx_MatrixToolbar_close" onClick={ this.hideToolbar } ><img src="img/cancel.svg" width="18" height="18" alt={_t('Close')}/></AccessibleButton>
|
<AccessibleButton className="mx_MatrixToolbar_close" onClick={ this.hideToolbar } ><img src="img/cancel.svg" width="18" height="18" alt={_t('Close')} /></AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -45,10 +45,10 @@ export default React.createClass({
|
||||||
description: <div className="mx_MatrixToolbar_changelog">{releaseNotes}</div>,
|
description: <div className="mx_MatrixToolbar_changelog">{releaseNotes}</div>,
|
||||||
button: _t("Update"),
|
button: _t("Update"),
|
||||||
onFinished: (update) => {
|
onFinished: (update) => {
|
||||||
if(update && PlatformPeg.get()) {
|
if (update && PlatformPeg.get()) {
|
||||||
PlatformPeg.get().installUpdate();
|
PlatformPeg.get().installUpdate();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -58,10 +58,10 @@ export default React.createClass({
|
||||||
version: this.props.version,
|
version: this.props.version,
|
||||||
newVersion: this.props.newVersion,
|
newVersion: this.props.newVersion,
|
||||||
onFinished: (update) => {
|
onFinished: (update) => {
|
||||||
if(update && PlatformPeg.get()) {
|
if (update && PlatformPeg.get()) {
|
||||||
PlatformPeg.get().installUpdate();
|
PlatformPeg.get().installUpdate();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -103,5 +103,5 @@ export default React.createClass({
|
||||||
{action_button}
|
{action_button}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,14 +32,14 @@ export default React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
detail: '',
|
detail: '',
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getStatusText: function() {
|
getStatusText: function() {
|
||||||
// we can't import the enum from riot-web as we don't want matrix-react-sdk
|
// we can't import the enum from riot-web as we don't want matrix-react-sdk
|
||||||
// to depend on riot-web. so we grab it as a normal object via API instead.
|
// to depend on riot-web. so we grab it as a normal object via API instead.
|
||||||
const updateCheckStatusEnum = PlatformPeg.get().getUpdateCheckStatusEnum();
|
const updateCheckStatusEnum = PlatformPeg.get().getUpdateCheckStatusEnum();
|
||||||
switch(this.props.status) {
|
switch (this.props.status) {
|
||||||
case updateCheckStatusEnum.ERROR:
|
case updateCheckStatusEnum.ERROR:
|
||||||
return _t('Error encountered (%(errorDetail)s).', { errorDetail: this.props.detail });
|
return _t('Error encountered (%(errorDetail)s).', { errorDetail: this.props.detail });
|
||||||
case updateCheckStatusEnum.CHECKING:
|
case updateCheckStatusEnum.CHECKING:
|
||||||
|
@ -59,7 +59,7 @@ export default React.createClass({
|
||||||
const message = this.getStatusText();
|
const message = this.getStatusText();
|
||||||
const warning = _t('Warning');
|
const warning = _t('Warning');
|
||||||
|
|
||||||
if (!'getUpdateCheckStatusEnum' in PlatformPeg.get()) {
|
if (!('getUpdateCheckStatusEnum' in PlatformPeg.get())) {
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,9 +83,9 @@ export default React.createClass({
|
||||||
{message}
|
{message}
|
||||||
</div>
|
</div>
|
||||||
<AccessibleButton className="mx_MatrixToolbar_close" onClick={this.hideToolbar}>
|
<AccessibleButton className="mx_MatrixToolbar_close" onClick={this.hideToolbar}>
|
||||||
<img src="img/cancel.svg" width="18" height="18" alt={_t('Close')}/>
|
<img src="img/cancel.svg" width="18" height="18" alt={_t('Close')} />
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -165,7 +165,7 @@ export default React.createClass({
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberList">
|
<div className="mx_MemberList">
|
||||||
{ inputBox }
|
{ inputBox }
|
||||||
<GeminiScrollbarWrapper autoshow={true} className="mx_MemberList_outerWrapper">
|
<GeminiScrollbarWrapper autoshow={true}>
|
||||||
{ joined }
|
{ joined }
|
||||||
{ invited }
|
{ invited }
|
||||||
</GeminiScrollbarWrapper>
|
</GeminiScrollbarWrapper>
|
||||||
|
|
|
@ -222,6 +222,7 @@ export const TermsAuthEntry = React.createClass({
|
||||||
stageParams: PropTypes.object.isRequired,
|
stageParams: PropTypes.object.isRequired,
|
||||||
errorText: PropTypes.string,
|
errorText: PropTypes.string,
|
||||||
busy: PropTypes.bool,
|
busy: PropTypes.bool,
|
||||||
|
showContinue: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -275,19 +276,30 @@ export const TermsAuthEntry = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_trySubmit: function(policyId) {
|
tryContinue: function() {
|
||||||
|
this._trySubmit();
|
||||||
|
},
|
||||||
|
|
||||||
|
_togglePolicy: function(policyId) {
|
||||||
const newToggles = {};
|
const newToggles = {};
|
||||||
let allChecked = true;
|
|
||||||
for (const policy of this.state.policies) {
|
for (const policy of this.state.policies) {
|
||||||
let checked = this.state.toggledPolicies[policy.id];
|
let checked = this.state.toggledPolicies[policy.id];
|
||||||
if (policy.id === policyId) checked = !checked;
|
if (policy.id === policyId) checked = !checked;
|
||||||
|
|
||||||
newToggles[policy.id] = checked;
|
newToggles[policy.id] = checked;
|
||||||
|
}
|
||||||
|
this.setState({"toggledPolicies": newToggles});
|
||||||
|
},
|
||||||
|
|
||||||
|
_trySubmit: function() {
|
||||||
|
let allChecked = true;
|
||||||
|
for (const policy of this.state.policies) {
|
||||||
|
let checked = this.state.toggledPolicies[policy.id];
|
||||||
allChecked = allChecked && checked;
|
allChecked = allChecked && checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({"toggledPolicies": newToggles});
|
|
||||||
if (allChecked) this.props.submitAuthDict({type: TermsAuthEntry.LOGIN_TYPE});
|
if (allChecked) this.props.submitAuthDict({type: TermsAuthEntry.LOGIN_TYPE});
|
||||||
|
else this.setState({errorText: _t("Please review and accept all of the homeserver's policies")});
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
@ -296,34 +308,42 @@ export const TermsAuthEntry = React.createClass({
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let checkboxes = [];
|
const checkboxes = [];
|
||||||
let allChecked = true;
|
let allChecked = true;
|
||||||
for (const policy of this.state.policies) {
|
for (const policy of this.state.policies) {
|
||||||
const checked = this.state.toggledPolicies[policy.id];
|
const checked = this.state.toggledPolicies[policy.id];
|
||||||
allChecked = allChecked && checked;
|
allChecked = allChecked && checked;
|
||||||
|
|
||||||
checkboxes.push(
|
checkboxes.push(
|
||||||
<label key={"policy_checkbox_" + policy.id}>
|
<label key={"policy_checkbox_" + policy.id} className="mx_InteractiveAuthEntryComponents_termsPolicy">
|
||||||
<input type="checkbox" onClick={() => this._trySubmit(policy.id)} checked={checked} />
|
<input type="checkbox" onClick={() => this._togglePolicy(policy.id)} checked={checked} />
|
||||||
<a href={policy.url} target="_blank" rel="noopener">{ policy.name }</a>
|
<a href={policy.url} target="_blank" rel="noopener">{ policy.name }</a>
|
||||||
</label>
|
</label>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let errorSection;
|
let errorSection;
|
||||||
if (this.props.errorText) {
|
if (this.props.errorText || this.state.errorText) {
|
||||||
errorSection = (
|
errorSection = (
|
||||||
<div className="error" role="alert">
|
<div className="error" role="alert">
|
||||||
{ this.props.errorText }
|
{ this.props.errorText || this.state.errorText }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let submitButton;
|
||||||
|
if (this.props.showContinue !== false) {
|
||||||
|
// XXX: button classes
|
||||||
|
submitButton = <button className="mx_InteractiveAuthEntryComponents_termsSubmit mx_UserSettings_button"
|
||||||
|
onClick={this._trySubmit} disabled={!allChecked}>{_t("Accept")}</button>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{_t("Please review and accept the policies of this homeserver:")}</p>
|
<p>{_t("Please review and accept the policies of this homeserver:")}</p>
|
||||||
{ checkboxes }
|
{ checkboxes }
|
||||||
{ errorSection }
|
{ errorSection }
|
||||||
|
{ submitButton }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -278,6 +278,7 @@ export default class MImageBody extends React.Component {
|
||||||
|
|
||||||
let img = null;
|
let img = null;
|
||||||
let placeholder = null;
|
let placeholder = null;
|
||||||
|
let gifLabel = null;
|
||||||
|
|
||||||
// e2e image hasn't been decrypted yet
|
// e2e image hasn't been decrypted yet
|
||||||
if (content.file !== undefined && this.state.decryptedUrl === null) {
|
if (content.file !== undefined && this.state.decryptedUrl === null) {
|
||||||
|
@ -302,11 +303,14 @@ export default class MImageBody extends React.Component {
|
||||||
onMouseLeave={this.onImageLeave} />;
|
onMouseLeave={this.onImageLeave} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._isGif() && !SettingsStore.getValue("autoplayGifsAndVideos") && !this.state.hover) {
|
||||||
|
gifLabel = <p className="mx_MImageBody_gifLabel">GIF</p>;
|
||||||
|
}
|
||||||
|
|
||||||
const thumbnail = (
|
const thumbnail = (
|
||||||
<div className="mx_MImageBody_thumbnail_container" style={{ maxHeight: maxHeight + "px" }} >
|
<div className="mx_MImageBody_thumbnail_container" style={{ maxHeight: maxHeight + "px" }} >
|
||||||
{ /* Calculate aspect ratio, using %padding will size _container correctly */ }
|
{ /* Calculate aspect ratio, using %padding will size _container correctly */ }
|
||||||
<div style={{ paddingBottom: (100 * infoHeight / infoWidth) + '%' }} />
|
<div style={{ paddingBottom: (100 * infoHeight / infoWidth) + '%' }} />
|
||||||
|
|
||||||
{ showPlaceholder &&
|
{ showPlaceholder &&
|
||||||
<div className="mx_MImageBody_thumbnail" style={{
|
<div className="mx_MImageBody_thumbnail" style={{
|
||||||
// Constrain width here so that spinner appears central to the loaded thumbnail
|
// Constrain width here so that spinner appears central to the loaded thumbnail
|
||||||
|
@ -320,6 +324,7 @@ export default class MImageBody extends React.Component {
|
||||||
|
|
||||||
<div style={{display: !showPlaceholder ? undefined : 'none'}}>
|
<div style={{display: !showPlaceholder ? undefined : 'none'}}>
|
||||||
{ img }
|
{ img }
|
||||||
|
{ gifLabel }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ this.state.hover && this.getTooltip() }
|
{ this.state.hover && this.getTooltip() }
|
||||||
|
|
|
@ -103,7 +103,7 @@ module.exports = React.createClass({
|
||||||
oldCanonicalAlias = this.props.canonicalAliasEvent.getContent().alias;
|
oldCanonicalAlias = this.props.canonicalAliasEvent.getContent().alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
let newCanonicalAlias = this.state.canonicalAlias;
|
const newCanonicalAlias = this.state.canonicalAlias;
|
||||||
|
|
||||||
if (this.props.canSetCanonicalAlias && oldCanonicalAlias !== newCanonicalAlias) {
|
if (this.props.canSetCanonicalAlias && oldCanonicalAlias !== newCanonicalAlias) {
|
||||||
console.log("AliasSettings: Updating canonical alias");
|
console.log("AliasSettings: Updating canonical alias");
|
||||||
|
@ -167,7 +167,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
if (!this.props.canonicalAlias) {
|
if (!this.props.canonicalAlias) {
|
||||||
this.setState({
|
this.setState({
|
||||||
canonicalAlias: alias
|
canonicalAlias: alias,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -220,8 +220,9 @@ module.exports = React.createClass({
|
||||||
let canonical_alias_section;
|
let canonical_alias_section;
|
||||||
if (this.props.canSetCanonicalAlias) {
|
if (this.props.canSetCanonicalAlias) {
|
||||||
let found = false;
|
let found = false;
|
||||||
|
const canonicalValue = this.state.canonicalAlias || "";
|
||||||
canonical_alias_section = (
|
canonical_alias_section = (
|
||||||
<select onChange={this.onCanonicalAliasChange} value={this.state.canonicalAlias}>
|
<select onChange={this.onCanonicalAliasChange} value={canonicalValue}>
|
||||||
<option value="" key="unset">{ _t('not specified') }</option>
|
<option value="" key="unset">{ _t('not specified') }</option>
|
||||||
{
|
{
|
||||||
Object.keys(self.state.domainToAliases).map((domain, i) => {
|
Object.keys(self.state.domainToAliases).map((domain, i) => {
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { _t } from '../../../languageHandler';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
|
|
||||||
const GROUP_ID_REGEX = /\+\S+\:\S+/;
|
const GROUP_ID_REGEX = /\+\S+:\S+/;
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RelatedGroupSettings',
|
displayName: 'RelatedGroupSettings',
|
||||||
|
|
|
@ -33,7 +33,6 @@ import Autocompleter from '../../../autocomplete/Autocompleter';
|
||||||
const COMPOSER_SELECTED = 0;
|
const COMPOSER_SELECTED = 0;
|
||||||
|
|
||||||
export default class Autocomplete extends React.Component {
|
export default class Autocomplete extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
|
|
@ -416,11 +416,10 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
onCryptoClicked: function(e) {
|
onCryptoClicked: function(e) {
|
||||||
const event = this.props.mxEvent;
|
const event = this.props.mxEvent;
|
||||||
|
|
||||||
Modal.createTrackedDialogAsync('Encrypted Event Dialog', '', (cb) => {
|
Modal.createTrackedDialogAsync('Encrypted Event Dialog', '',
|
||||||
require(['../../../async-components/views/dialogs/EncryptedEventDialog'], cb);
|
import('../../../async-components/views/dialogs/EncryptedEventDialog'),
|
||||||
}, {
|
{event},
|
||||||
event: event,
|
);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onRequestKeysClick: function() {
|
onRequestKeysClick: function() {
|
||||||
|
|
|
@ -107,7 +107,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing?
|
// FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing?
|
||||||
let image = p["og:image"];
|
let image = p["og:image"];
|
||||||
let imageMaxWidth = 100, imageMaxHeight = 100;
|
const imageMaxWidth = 100; const imageMaxHeight = 100;
|
||||||
if (image && image.startsWith("mxc://")) {
|
if (image && image.startsWith("mxc://")) {
|
||||||
image = MatrixClientPeg.get().mxcUrlToHttp(image, imageMaxWidth, imageMaxHeight);
|
image = MatrixClientPeg.get().mxcUrlToHttp(image, imageMaxWidth, imageMaxHeight);
|
||||||
}
|
}
|
||||||
|
|
|
@ -712,7 +712,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
|
|
||||||
if (!member || !member.membership || member.membership === 'leave') {
|
if (!member || !member.membership || member.membership === 'leave') {
|
||||||
const roomId = member && member.roomId ? member.roomId : RoomViewStore.getRoomId();
|
const roomId = member && member.roomId ? member.roomId : RoomViewStore.getRoomId();
|
||||||
const onInviteUserButton = async () => {
|
const onInviteUserButton = async() => {
|
||||||
try {
|
try {
|
||||||
await cli.invite(roomId, member.userId);
|
await cli.invite(roomId, member.userId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -447,7 +447,7 @@ module.exports = React.createClass({
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberList">
|
<div className="mx_MemberList">
|
||||||
{ inputBox }
|
{ inputBox }
|
||||||
<GeminiScrollbarWrapper autoshow={true} className="mx_MemberList_joined mx_MemberList_outerWrapper">
|
<GeminiScrollbarWrapper autoshow={true} className="mx_MemberList_joined">
|
||||||
<TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAtJoined}
|
<TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAtJoined}
|
||||||
createOverflowElement={this._createOverflowTileJoined}
|
createOverflowElement={this._createOverflowTileJoined}
|
||||||
getChildren={this._getChildrenJoined}
|
getChildren={this._getChildrenJoined}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue