diff --git a/.babelrc b/.babelrc index 6ba0e0dae0..fc5bd1788f 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,4 @@ { "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"] } diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 8d0821207a..0b4266c0b5 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -7,11 +7,8 @@ src/component-index.js src/components/structures/BottomLeftMenu.js src/components/structures/CompatibilityPage.js src/components/structures/CreateRoom.js -src/components/structures/HomePage.js -src/components/structures/LeftPanel.js src/components/structures/LoggedInView.js src/components/structures/login/ForgotPassword.js -src/components/structures/LoginBox.js src/components/structures/MessagePanel.js src/components/structures/NotificationPanel.js src/components/structures/RoomDirectory.js @@ -22,22 +19,17 @@ src/components/structures/SearchBox.js src/components/structures/TimelinePanel.js src/components/structures/UploadBar.js src/components/structures/UserSettings.js -src/components/structures/ViewSource.js src/components/views/avatars/BaseAvatar.js src/components/views/avatars/MemberAvatar.js src/components/views/create_room/RoomAlias.js -src/components/views/dialogs/ChangelogDialog.js src/components/views/dialogs/DeactivateAccountDialog.js src/components/views/dialogs/SetPasswordDialog.js src/components/views/dialogs/UnknownDeviceDialog.js src/components/views/directory/NetworkDropdown.js src/components/views/elements/AddressSelector.js -src/components/views/elements/DeviceVerifyButtons.js src/components/views/elements/DirectorySearchBox.js src/components/views/elements/ImageView.js -src/components/views/elements/InlineSpinner.js src/components/views/elements/MemberEventListSummary.js -src/components/views/elements/Spinner.js src/components/views/elements/TintableSvg.js src/components/views/elements/UserSelector.js src/components/views/globals/MatrixToolbar.js @@ -90,7 +82,6 @@ src/MatrixClientPeg.js src/Modal.js src/notifications/ContentRules.js src/notifications/PushRuleVectorState.js -src/notifications/StandardActions.js src/notifications/VectorPushRulesDefinitions.js src/Notifier.js src/PlatformPeg.js @@ -111,7 +102,6 @@ src/utils/MultiInviter.js src/utils/Receipt.js src/VectorConferenceHandler.js src/Velociraptor.js -src/VelocityBounce.js src/WhoIsTyping.js src/wrappers/withMatrixClient.js test/components/structures/login/Registration-test.js diff --git a/.travis-test-riot.sh b/.travis-test-riot.sh index 7f2660a906..88b3719b3a 100755 --- a/.travis-test-riot.sh +++ b/.travis-test-riot.sh @@ -27,12 +27,15 @@ npm run build npm run test popd -# run end to end tests -git clone https://github.com/matrix-org/matrix-react-end-to-end-tests.git --branch master -pushd matrix-react-end-to-end-tests -ln -s $REACT_SDK_DIR/$RIOT_WEB_DIR riot/riot-web -# PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh -# CHROME_PATH=$(which google-chrome-stable) ./run.sh -./install.sh -./run.sh --travis -popd +if [ "$TRAVIS_BRANCH" = "develop" ] +then + # run end to end tests + git clone https://github.com/matrix-org/matrix-react-end-to-end-tests.git --branch master + pushd matrix-react-end-to-end-tests + ln -s $REACT_SDK_DIR/$RIOT_WEB_DIR riot/riot-web + # PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh + # CHROME_PATH=$(which google-chrome-stable) ./run.sh + ./install.sh + ./run.sh --travis + popd +fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 40cb8c5283..eea47dcb8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,135 @@ +Changes in [0.14.6](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.6) (2018-11-22) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.5...v0.14.6) + + * Warning when crypto DB is too new to use. + +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) ===================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.0-rc.1...v0.14.0) diff --git a/jenkins.sh b/jenkins.sh index 8cf5ee4a1f..f4bb8da449 100755 --- a/jenkins.sh +++ b/jenkins.sh @@ -4,7 +4,7 @@ set -e export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" -nvm use 6 +nvm use 10 set -x diff --git a/package.json b/package.json index 5b28053256..b5cdfdf401 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.14.0", + "version": "0.14.6", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -53,6 +53,7 @@ "test-multi": "karma start" }, "dependencies": { + "babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-runtime": "^6.26.0", "bluebird": "^3.5.0", "blueimp-canvas-to-blob": "^3.5.0", @@ -75,7 +76,7 @@ "linkifyjs": "^2.1.6", "lodash": "^4.13.1", "lolex": "2.3.2", - "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "0.14.1", "optimist": "^0.6.1", "pako": "^1.0.5", "prop-types": "^15.5.8", @@ -100,7 +101,7 @@ "devDependencies": { "babel-cli": "^6.26.0", "babel-core": "^6.26.3", - "babel-eslint": "^6.1.2", + "babel-eslint": "^10.0.1", "babel-loader": "^7.1.5", "babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-transform-async-to-bluebird": "^1.1.1", @@ -114,9 +115,9 @@ "babel-preset-react": "^6.24.1", "chokidar": "^1.6.1", "concurrently": "^4.0.1", - "eslint": "^3.13.1", + "eslint": "^5.8.0", "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-react": "^7.7.0", "estree-walker": "^0.5.0", diff --git a/res/css/_common.scss b/res/css/_common.scss index 38f576a532..11e04f5dc0 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -170,8 +170,7 @@ textarea { font-weight: 300; font-size: 15px; position: relative; - padding-left: 58px; - padding-bottom: 36px; + padding: 0 58px 36px; width: 60%; max-width: 704px; box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2); @@ -216,12 +215,16 @@ textarea { } .mx_Dialog_content { - margin: 24px 58px 68px 0; + margin: 24px 0 68px; font-size: 14px; color: $primary-fg-color; word-wrap: break-word; } +.mx_Dialog_buttons { + text-align: right; +} + .mx_Dialog button, .mx_Dialog input[type="submit"] { @mixin mx_DialogButton; margin-left: 0px; @@ -259,6 +262,11 @@ textarea { opacity: 0.7; } +.mx_linkButton { + cursor: pointer; + color: $accent-color; +} + .mx_Dialog_title { min-height: 16px; padding-top: 40px; diff --git a/res/css/_components.scss b/res/css/_components.scss index 0e40b40a29..083071ef6c 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -33,18 +33,21 @@ @import "./views/dialogs/_ChatInviteDialog.scss"; @import "./views/dialogs/_ConfirmUserActionDialog.scss"; @import "./views/dialogs/_CreateGroupDialog.scss"; +@import "./views/dialogs/_CreateKeyBackupDialog.scss"; @import "./views/dialogs/_CreateRoomDialog.scss"; @import "./views/dialogs/_DeactivateAccountDialog.scss"; @import "./views/dialogs/_DevtoolsDialog.scss"; @import "./views/dialogs/_EncryptedEventDialog.scss"; @import "./views/dialogs/_GroupAddressPicker.scss"; -@import "./views/dialogs/_QuestionDialog.scss"; +@import "./views/dialogs/_RestoreKeyBackupDialog.scss"; @import "./views/dialogs/_RoomUpgradeDialog.scss"; @import "./views/dialogs/_SetEmailDialog.scss"; @import "./views/dialogs/_SetMxIdDialog.scss"; @import "./views/dialogs/_SetPasswordDialog.scss"; @import "./views/dialogs/_ShareDialog.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/elements/_AccessibleButton.scss"; @import "./views/elements/_AddressSelector.scss"; @@ -108,6 +111,7 @@ @import "./views/rooms/_TopUnreadMessagesBar.scss"; @import "./views/settings/_DevicesPanel.scss"; @import "./views/settings/_IntegrationsManager.scss"; +@import "./views/settings/_KeyBackupPanel.scss"; @import "./views/settings/_Notifications.scss"; @import "./views/voip/_CallView.scss"; @import "./views/voip/_IncomingCallbox.scss"; diff --git a/res/css/structures/_HomePage.scss b/res/css/structures/_HomePage.scss index cdac1bcc8a..dc3158b39d 100644 --- a/res/css/structures/_HomePage.scss +++ b/res/css/structures/_HomePage.scss @@ -33,3 +33,16 @@ limitations under the License. .mx_HomePage_body { // 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; +} diff --git a/res/css/structures/_LoginBox.scss b/res/css/structures/_LoginBox.scss index 7f6199c451..0a3e21a980 100644 --- a/res/css/structures/_LoginBox.scss +++ b/res/css/structures/_LoginBox.scss @@ -19,6 +19,7 @@ limitations under the License. height: unset !important; padding-top: 13px !important; padding-bottom: 14px !important; + order: 4; } .mx_LoginBox_loginButton_wrapper { diff --git a/res/css/structures/login/_Login.scss b/res/css/structures/login/_Login.scss index 84b8306a74..1264d2a30f 100644 --- a/res/css/structures/login/_Login.scss +++ b/res/css/structures/login/_Login.scss @@ -142,6 +142,17 @@ limitations under the License. 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 { display: inline; position: relative; diff --git a/res/css/views/context_menus/_TagTileContextMenu.scss b/res/css/views/context_menus/_TagTileContextMenu.scss index 759b92bd68..799151b198 100644 --- a/res/css/views/context_menus/_TagTileContextMenu.scss +++ b/res/css/views/context_menus/_TagTileContextMenu.scss @@ -25,6 +25,10 @@ limitations under the License. line-height: 16px; } +.mx_TagTileContextMenu_item object { + pointer-events: none; +} + .mx_TagTileContextMenu_item_icon { padding-right: 8px; diff --git a/res/css/views/dialogs/_ChatInviteDialog.scss b/res/css/views/dialogs/_ChatInviteDialog.scss index 6fc211743d..dcc0f5921a 100644 --- a/res/css/views/dialogs/_ChatInviteDialog.scss +++ b/res/css/views/dialogs/_ChatInviteDialog.scss @@ -14,14 +14,6 @@ See the License for the specific language governing permissions and 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 */ .mx_ChatInviteDialog_input, .mx_ChatInviteDialog_input:focus diff --git a/res/css/views/dialogs/_CreateKeyBackupDialog.scss b/res/css/views/dialogs/_CreateKeyBackupDialog.scss new file mode 100644 index 0000000000..a422cf858c --- /dev/null +++ b/res/css/views/dialogs/_CreateKeyBackupDialog.scss @@ -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; +} diff --git a/res/css/views/dialogs/_EncryptedEventDialog.scss b/res/css/views/dialogs/_EncryptedEventDialog.scss index b4dd353370..58ed8b2527 100644 --- a/res/css/views/dialogs/_EncryptedEventDialog.scss +++ b/res/css/views/dialogs/_EncryptedEventDialog.scss @@ -24,4 +24,8 @@ limitations under the License. @mixin mx_DialogButton; background-color: $primary-bg-color; color: $accent-color; -} \ No newline at end of file +} + +.mx_EncryptedEventDialog button { + margin-top: 0px; +} diff --git a/res/css/views/dialogs/_QuestionDialog.scss b/res/css/views/dialogs/_RestoreKeyBackupDialog.scss similarity index 85% rename from res/css/views/dialogs/_QuestionDialog.scss rename to res/css/views/dialogs/_RestoreKeyBackupDialog.scss index 3d47f17592..69e00c416a 100644 --- a/res/css/views/dialogs/_QuestionDialog.scss +++ b/res/css/views/dialogs/_RestoreKeyBackupDialog.scss @@ -1,5 +1,5 @@ /* -Copyright 2017 New Vector Ltd. +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. @@ -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 limitations under the License. */ -.mx_QuestionDialog { - padding-right: 58px; + +.mx_RestoreKeyBackupDialog_keyStatus { + height: 30px; } diff --git a/res/css/views/dialogs/_ShareDialog.scss b/res/css/views/dialogs/_ShareDialog.scss index 116bef8dfd..9a2f67dea3 100644 --- a/res/css/views/dialogs/_ShareDialog.scss +++ b/res/css/views/dialogs/_ShareDialog.scss @@ -14,11 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_ShareDialog { - // this is to center the content - padding-right: 58px; -} - .mx_ShareDialog hr { margin-top: 25px; margin-bottom: 25px; diff --git a/res/css/views/dialogs/_UnknownDeviceDialog.scss b/res/css/views/dialogs/_UnknownDeviceDialog.scss index 3457e50b92..e3801e3550 100644 --- a/res/css/views/dialogs/_UnknownDeviceDialog.scss +++ b/res/css/views/dialogs/_UnknownDeviceDialog.scss @@ -20,9 +20,6 @@ limitations under the License. // is a pain in the ass. plus might as well make the dialog big given how // important it is. height: 100%; - - // position the gemini scrollbar nicely - padding-right: 58px; } .mx_UnknownDeviceDialog { @@ -51,4 +48,4 @@ limitations under the License. .mx_UnknownDeviceDialog .mx_UnknownDeviceDialog_deviceList li { height: 40px; border-bottom: 1px solid $primary-hairline-color; -} \ No newline at end of file +} diff --git a/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss b/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss new file mode 100644 index 0000000000..507c89ace7 --- /dev/null +++ b/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss @@ -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; +} diff --git a/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss b/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss new file mode 100644 index 0000000000..612c921038 --- /dev/null +++ b/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss @@ -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; +} + diff --git a/res/css/views/elements/_RoleButton.scss b/res/css/views/elements/_RoleButton.scss index 094e0b9b1b..41cd11d7c1 100644 --- a/res/css/views/elements/_RoleButton.scss +++ b/res/css/views/elements/_RoleButton.scss @@ -1,5 +1,5 @@ /* -Copyright 2107 Vector Creations Ltd +Copyright 2017 Vector Creations Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/res/css/views/login/_InteractiveAuthEntryComponents.scss b/res/css/views/login/_InteractiveAuthEntryComponents.scss index 183b5cd251..e2ea7d86fb 100644 --- a/res/css/views/login/_InteractiveAuthEntryComponents.scss +++ b/res/css/views/login/_InteractiveAuthEntryComponents.scss @@ -35,8 +35,24 @@ limitations under the License. margin-bottom: 5px; } +.mx_InteractiveAuthEntryComponents_termsSubmit { + margin-top: 20px; + margin-bottom: 5px; + display: block; + width: 100%; +} + // XXX: This should be a common button class .mx_InteractiveAuthEntryComponents_msisdnSubmit:disabled { background-color: $light-fg-color; cursor: default; } + +.mx_InteractiveAuthEntryComponents_termsSubmit:disabled { + background-color: $accent-color-50pct; + cursor: default; +} + +.mx_InteractiveAuthEntryComponents_termsPolicy { + display: block; +} \ No newline at end of file diff --git a/res/css/views/messages/_MImageBody.scss b/res/css/views/messages/_MImageBody.scss index 4c763c5991..a5a1e66a3b 100644 --- a/res/css/views/messages/_MImageBody.scss +++ b/res/css/views/messages/_MImageBody.scss @@ -46,3 +46,14 @@ limitations under the License. .mx_MImageBody_thumbnail_spinner > * { 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; +} diff --git a/res/css/views/rooms/_MemberDeviceInfo.scss b/res/css/views/rooms/_MemberDeviceInfo.scss index 5888820e0d..d4e4bb1adf 100644 --- a/res/css/views/rooms/_MemberDeviceInfo.scss +++ b/res/css/views/rooms/_MemberDeviceInfo.scss @@ -19,7 +19,6 @@ limitations under the License. } .mx_MemberDeviceInfo.mx_DeviceVerifyButtons { - padding: 6px 0; display: flex; flex-wrap: wrap; justify-content: space-between; diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss index 83fc70aefb..cfac8797b9 100644 --- a/res/css/views/rooms/_MemberList.scss +++ b/res/css/views/rooms/_MemberList.scss @@ -111,6 +111,3 @@ limitations under the License. width: 100%; } -.mx_MemberList_outerWrapper { - height: 0px; -} diff --git a/res/css/views/rooms/_RoomList.scss b/res/css/views/rooms/_RoomList.scss index 581016d5ba..a346a06a20 100644 --- a/res/css/views/rooms/_RoomList.scss +++ b/res/css/views/rooms/_RoomList.scss @@ -1,6 +1,6 @@ /* 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"); you may not use this file except in compliance with the License. diff --git a/res/css/views/rooms/_RoomPreviewBar.scss b/res/css/views/rooms/_RoomPreviewBar.scss index 331eb582ea..8196740499 100644 --- a/res/css/views/rooms/_RoomPreviewBar.scss +++ b/res/css/views/rooms/_RoomPreviewBar.scss @@ -56,3 +56,7 @@ limitations under the License. .mx_RoomPreviewBar_warningIcon { padding: 12px; } + +.mx_RoomPreviewBar_spinnerIntro { + margin-top: 50px; +} \ No newline at end of file diff --git a/res/css/views/rooms/_RoomSettings.scss b/res/css/views/rooms/_RoomSettings.scss index f04042ea77..b3858f3ba7 100644 --- a/res/css/views/rooms/_RoomSettings.scss +++ b/res/css/views/rooms/_RoomSettings.scss @@ -28,6 +28,13 @@ limitations under the License. margin-right: 8px; } +.mx_RoomSettings_devtoolsButton { + @mixin mx_DialogButton; + position: relative; + padding: 4px 1.5em; + margin-top: 8px; +} + .mx_RoomSettings_upgradeButton, .mx_RoomSettings_leaveButton:hover, .mx_RoomSettings_unbanButton:hover { diff --git a/src/components/views/login/CasLogin.js b/res/css/views/settings/_KeyBackupPanel.scss similarity index 52% rename from src/components/views/login/CasLogin.js rename to res/css/views/settings/_KeyBackupPanel.scss index 9219c79733..1bcc0ab10d 100644 --- a/src/components/views/login/CasLogin.js +++ b/res/css/views/settings/_KeyBackupPanel.scss @@ -1,5 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd +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. @@ -14,25 +14,19 @@ See the License for the specific language governing permissions and 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'; -import PropTypes from 'prop-types'; -import { _t } from '../../../languageHandler'; +.mx_KeyBackupPanel_sigValid, .mx_KeyBackupPanel_deviceVerified { + color: $e2e-verified-color; +} -module.exports = React.createClass({ - displayName: 'CasLogin', +.mx_KeyBackupPanel_sigInvalid, .mx_KeyBackupPanel_deviceNotVerified { + color: $e2e-warning-color; +} - propTypes: { - onSubmit: PropTypes.func, // fn() - }, - - render: function() { - return ( -
- -
- ); - }, - -}); +.mx_KeyBackupPanel_deviceName { + font-style: italic; +} diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 8ab338790e..b773d7c720 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -12,6 +12,7 @@ $light-fg-color: #747474; // button UI (white-on-green in light skin) $accent-fg-color: $primary-bg-color; $accent-color: #76CFA6; +$accent-color-50pct: #76CFA67F; $selection-fg-color: $primary-fg-color; @@ -143,6 +144,9 @@ $lightbox-bg-color: #454545; $lightbox-fg-color: #ffffff; $lightbox-border-color: #ffffff; +$imagebody-giflabel: rgba(1, 1, 1, 0.7); +$imagebody-giflabel-border: rgba(1, 1, 1, 0.2); + // unused? $progressbar-color: #000; diff --git a/res/themes/light/css/_base.scss b/res/themes/light/css/_base.scss index c7fd38259c..49347492ff 100644 --- a/res/themes/light/css/_base.scss +++ b/res/themes/light/css/_base.scss @@ -18,6 +18,7 @@ $focus-bg-color: #dddddd; // button UI (white-on-green in light skin) $accent-fg-color: #ffffff; $accent-color: #76CFA6; +$accent-color-50pct: #76CFA67F; $selection-fg-color: $primary-bg-color; @@ -148,6 +149,9 @@ $lightbox-bg-color: #454545; $lightbox-fg-color: #ffffff; $lightbox-border-color: #ffffff; +$imagebody-giflabel: rgba(0, 0, 0, 0.7); +$imagebody-giflabel-border: rgba(0, 0, 0, 0.2); + // unused? $progressbar-color: #000; diff --git a/src/ComposerHistoryManager.js b/src/ComposerHistoryManager.js index 0164e6c4cd..ecf773f2e7 100644 --- a/src/ComposerHistoryManager.js +++ b/src/ComposerHistoryManager.js @@ -22,7 +22,6 @@ import _clamp from 'lodash/clamp'; type MessageFormat = 'rich' | 'markdown'; class HistoryItem { - // We store history items in their native format to ensure history is accurate // and then convert them if our RTE has subsequently changed format. value: Value; diff --git a/src/Entities.js b/src/Entities.js index 21abd9c473..8be1da0db8 100644 --- a/src/Entities.js +++ b/src/Entities.js @@ -78,7 +78,6 @@ class MemberEntity extends Entity { } class UserEntity extends Entity { - constructor(model, showInviteButton, inviteFn) { super(model); this.showInviteButton = Boolean(showInviteButton); diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index b6a2bd0acb..e72c0bfe4b 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -64,7 +64,7 @@ export function containsEmoji(str) { * because we want to include emoji shortnames in title text */ function unicodeToImage(str) { - let replaceWith, unicode, alt, short, fname; + let replaceWith; let unicode; let alt; let short; let fname; const mappedUnicode = emojione.mapUnicodeToShort(); str = str.replace(emojione.regUnicode, function(unicodeChar) { diff --git a/src/Login.js b/src/Login.js index 61a14959d8..ec55a1e8c7 100644 --- a/src/Login.js +++ b/src/Login.js @@ -16,7 +16,6 @@ limitations under the License. */ import Matrix from "matrix-js-sdk"; -import { _t } from "./languageHandler"; import Promise from 'bluebird'; import url from 'url'; @@ -225,19 +224,18 @@ export default class Login { }); } - redirectToCas() { + getSsoLoginUrl(loginType) { const client = this._createTemporaryClient(); const parsedUrl = url.parse(window.location.href, true); // XXX: at this point, the fragment will always be #/login, which is no // use to anyone. Ideally, we would get the intended fragment from // MatrixChat.screenAfterLogin so that you could follow #/room links etc - // through a CAS login. + // through an SSO login. parsedUrl.hash = ""; parsedUrl.query["homeserver"] = client.getHomeserverUrl(); parsedUrl.query["identityServer"] = client.getIdentityServerUrl(); - const casUrl = client.getCasLoginUrl(url.format(parsedUrl)); - window.location.href = casUrl; + return client.getSsoLoginUrl(url.format(parsedUrl), loginType); } } diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index 04b3b47e43..9a77901d2e 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -23,10 +23,12 @@ import Matrix from 'matrix-js-sdk'; import utils from 'matrix-js-sdk/lib/utils'; import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline'; import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set'; +import sdk from './index'; import createMatrixClient from './utils/createMatrixClient'; import SettingsStore from './settings/SettingsStore'; import MatrixActionCreators from './actions/MatrixActionCreators'; import {phasedRollOutExpiredForUser} from "./PhasedRollOut"; +import Modal from './Modal'; interface MatrixClientCreds { homeserverUrl: string, @@ -116,6 +118,14 @@ class MatrixClientPeg { await this.matrixClient.initCrypto(); } } 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 // that the olm library was missing. It's not fatal. console.warn("Unable to initialise e2e: " + e); diff --git a/src/Modal.js b/src/Modal.js index 06a96824a7..960e0e5c30 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -23,6 +23,7 @@ import PropTypes from 'prop-types'; import Analytics from './Analytics'; import sdk from './index'; import dis from './dispatcher'; +import { _t } from './languageHandler'; const DIALOG_CONTAINER_ID = "mx_Dialog_Container"; @@ -32,15 +33,15 @@ const DIALOG_CONTAINER_ID = "mx_Dialog_Container"; */ const AsyncWrapper = React.createClass({ propTypes: { - /** A function which takes a 'callback' argument which it will call - * with the real component once it loads. + /** A promise which resolves with the real component */ - loader: PropTypes.func.isRequired, + prom: PropTypes.object.isRequired, }, getInitialState: function() { return { component: null, + error: null, }; }, @@ -49,14 +50,18 @@ const AsyncWrapper = React.createClass({ // XXX: temporary logging to try to diagnose // https://github.com/vector-im/riot-web/issues/3148 console.log('Starting load of AsyncWrapper for modal'); - this.props.loader((e) => { - // XXX: temporary logging to try to diagnose - // https://github.com/vector-im/riot-web/issues/3148 - console.log('AsyncWrapper load completed with '+e.displayName); + this.props.prom.then((result) => { if (this._unmounted) { 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; }, + _onWrapperCancelClick: function() { + this.props.onFinished(false); + }, + render: function() { const {loader, ...otherProps} = this.props; if (this.state.component) { const Component = this.state.component; return ; + } else if (this.state.error) { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + return + {_t("Unable to load! Check your network connectivity and try again.")} + + ; } else { // show a spinner until the component is loaded. const Spinner = sdk.getComponent("elements.Spinner"); @@ -115,7 +136,7 @@ class ModalManager { } createDialog(Element, ...rest) { - return this.createDialogAsync((cb) => {cb(Element);}, ...rest); + return this.createDialogAsync(new Promise(resolve => resolve(Element)), ...rest); } createTrackedDialogAsync(analyticsAction, analyticsInfo, ...rest) { @@ -133,9 +154,8 @@ class ModalManager { * require([''], cb); * } * - * @param {Function} loader a function which takes a 'callback' argument, - * which it should call with a React component which will be displayed as - * the modal view. + * @param {Promise} prom a promise which resolves with a React component + * which will be displayed as the modal view. * * @param {Object} props properties to pass to the displayed * component. (We will also pass an 'onFinished' property.) @@ -147,7 +167,7 @@ class ModalManager { * Also, when closed, all modals will be removed * from the stack. */ - createDialogAsync(loader, props, className, isPriorityModal) { + createDialogAsync(prom, props, className, isPriorityModal) { const self = this; const modal = {}; @@ -178,7 +198,7 @@ class ModalManager { // FIXME: If a dialog uses getDefaultProps it clobbers the onFinished // property set here so you can't close the dialog from a button click! modal.elem = ( - ); modal.onFinished = props ? props.onFinished : null; diff --git a/src/PasswordReset.js b/src/PasswordReset.js index 71fc4f6b31..df51e4d846 100644 --- a/src/PasswordReset.js +++ b/src/PasswordReset.js @@ -25,7 +25,6 @@ import { _t } from './languageHandler'; * API on the homeserver in question with the new password. */ class PasswordReset { - /** * Configure the endpoints for password resetting. * @param {string} homeserverUrl The URL to the HS which has the account to reset. diff --git a/src/Presence.js b/src/Presence.js index 9367fe35cd..b1e85e4bc7 100644 --- a/src/Presence.js +++ b/src/Presence.js @@ -23,7 +23,6 @@ const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins const PRESENCE_STATES = ["online", "offline", "unavailable"]; class Presence { - /** * Start listening the user activity to evaluate his presence state. * Any state change will be sent to the Home Server. diff --git a/src/Registration.js b/src/Registration.js index 070178fecb..f86c9cc618 100644 --- a/src/Registration.js +++ b/src/Registration.js @@ -45,7 +45,7 @@ export async function startAnyRegistrationFlow(options) { // caution though. const hasIlagFlow = flows.some((flow) => { 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); }); }); diff --git a/src/Rooms.js b/src/Rooms.js index e24b8316b3..6f73ea0659 100644 --- a/src/Rooms.js +++ b/src/Rooms.js @@ -32,7 +32,6 @@ export function getDisplayAliasForRoom(room) { * return the other one. Otherwise, return null. */ export function getOnlyOtherMember(room, myUserId) { - if (room.currentState.getJoinedMemberCount() === 2) { return room.getJoinedMembers().filter(function(m) { return m.userId !== myUserId; @@ -103,7 +102,7 @@ export function guessAndSetDMRoom(room, isDirect) { let newTarget; if (isDirect) { const guessedUserId = guessDMRoomTargetId( - room, MatrixClientPeg.get().getUserId() + room, MatrixClientPeg.get().getUserId(), ); newTarget = guessedUserId; } else { diff --git a/src/ScalarAuthClient.js b/src/ScalarAuthClient.js index 2038430576..0639e7ceae 100644 --- a/src/ScalarAuthClient.js +++ b/src/ScalarAuthClient.js @@ -22,7 +22,6 @@ const SdkConfig = require('./SdkConfig'); const MatrixClientPeg = require('./MatrixClientPeg'); class ScalarAuthClient { - constructor() { this.scalarToken = null; } diff --git a/src/SdkConfig.js b/src/SdkConfig.js index 8df725a913..65982bd6f2 100644 --- a/src/SdkConfig.js +++ b/src/SdkConfig.js @@ -24,7 +24,6 @@ const DEFAULTS = { }; class SdkConfig { - static get() { return global.mxReactSdkConfig || {}; } diff --git a/src/SlashCommands.js b/src/SlashCommands.js index 3a8e77293b..8a34ba7ab1 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -24,6 +24,8 @@ import sdk from './index'; import {_t, _td} from './languageHandler'; import Modal from './Modal'; import SettingsStore, {SettingLevel} from './settings/SettingsStore'; +import {MATRIXTO_URL_PATTERN} from "./linkify-matrix"; +import * as querystring from "querystring"; class Command { @@ -153,11 +155,24 @@ export const CommandMap = { description: _td('Joins room with given alias'), runFn: function(roomId, args) { if (args) { - const matches = args.match(/^(\S+)$/); - if (matches) { - let roomAlias = matches[1]; - if (roomAlias[0] !== '#') return reject(this.getUsage()); + // Note: we support 2 versions of this command. The first is + // the public-facing one for most users and the other is a + // power-user edition where someone may join via permalink or + // 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(':')) { roomAlias += ':' + MatrixClientPeg.get().getDomain(); } @@ -167,7 +182,65 @@ export const CommandMap = { room_alias: roomAlias, 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(); } } @@ -492,6 +565,7 @@ export const CommandMap = { const aliases = { j: "join", newballsplease: "discardsession", + goto: "join", // because it handles event permalinks magically }; diff --git a/src/UserActivity.js b/src/UserActivity.js index b6fae38ed5..c628ab4186 100644 --- a/src/UserActivity.js +++ b/src/UserActivity.js @@ -25,7 +25,6 @@ const CURRENTLY_ACTIVE_THRESHOLD_MS = 2000; * with the app (but at a much lower frequency than mouse move events) */ class UserActivity { - /** * Start listening to user activity */ diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index 5d2af3715f..b40d0529a2 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -87,7 +87,7 @@ export default { device_display_name: address, lang: navigator.language, data: data, - append: true, // We always append for email pushers since we don't want to stop other accounts notifying to the same email address + append: true, // We always append for email pushers since we don't want to stop other accounts notifying to the same email address }); }, }; diff --git a/src/VelocityBounce.js b/src/VelocityBounce.js index 2141b05325..b9513249b8 100644 --- a/src/VelocityBounce.js +++ b/src/VelocityBounce.js @@ -3,8 +3,8 @@ const Velocity = require('velocity-vector'); // courtesy of https://github.com/julianshapiro/velocity/issues/283 // We only use easeOutBounce (easeInBounce is just sort of nonsensical) function bounce( p ) { - let pow2, - bounce = 4; + let pow2; + let bounce = 4; while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) { // just sets pow2 diff --git a/src/actions/MatrixActionCreators.js b/src/actions/MatrixActionCreators.js index 31bcac3e52..c1d42ffd0d 100644 --- a/src/actions/MatrixActionCreators.js +++ b/src/actions/MatrixActionCreators.js @@ -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 * @type {Object} @@ -201,6 +230,7 @@ export default { start(matrixClient) { this._addMatrixClientListener(matrixClient, 'sync', createSyncAction); this._addMatrixClientListener(matrixClient, 'accountData', createAccountDataAction); + this._addMatrixClientListener(matrixClient, 'Room.accountData', createRoomAccountDataAction); this._addMatrixClientListener(matrixClient, 'Room', createRoomAction); this._addMatrixClientListener(matrixClient, 'Room.tags', createRoomTagsAction); this._addMatrixClientListener(matrixClient, 'Room.timeline', createRoomTimelineAction); diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js new file mode 100644 index 0000000000..2f43d18072 --- /dev/null +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -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
+

{_t("Secure your encrypted message history with a Recovery Passphrase.")}

+

{_t("You'll need it if you log out or lose access to this device.")}

+ +
+ +
+ + + +

{_t( + "If you don't want encrypted message history to be availble on other devices, "+ + ".", + {}, + { + button: sub => + {sub} + , + }, + )}

+

{_t( + "Or, if you don't want to create a Recovery Passphrase, skip this step and "+ + ".", + {}, + { + button: sub => + {sub} + , + }, + )}

+
; + }, + + _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 =
+
{matchText}
+
+ + {_t("Go back to set it again.")} + +
+
; + } + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + return
+

{_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.", + )}

+
+ {passPhraseMatch} +
+ +
+
+ +
; + }, + + _renderPhaseShowKey: function() { + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + return
+

{_t("Make a copy of this Recovery Key and keep it safe.")}

+

{_t("As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.")}

+

+

{_t("Your Recovery Key")}
+
+ + { + // FIXME REDESIGN: buttons should be adjacent but insufficient room in current design + } +

+ +
+
+ {this._keyBackupInfo.recovery_key} +
+

+
+ +
; + }, + + _renderPhaseKeepItSafe: function() { + let introText; + if (this.state.copied) { + introText = _t( + "Your Recovery Key has been copied to your clipboard, paste it to:", + {}, {b: s => {s}}, + ); + } else if (this.state.downloaded) { + introText = _t( + "Your Recovery Key is in your Downloads folder.", + {}, {b: s => {s}}, + ); + } + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + return
+ {introText} +
    +
  • {_t("Print it and store it somewhere safe", {}, {b: s => {s}})}
  • +
  • {_t("Save it on a USB key or backup drive", {}, {b: s => {s}})}
  • +
  • {_t("Copy it to your personal cloud storage", {}, {b: s => {s}})}
  • +
+ +
; + }, + + _renderBusyPhase: function(text) { + const Spinner = sdk.getComponent('views.elements.Spinner'); + return
+

{_t(text)}

+ +
; + }, + + _renderPhaseDone: function() { + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + return
+

{_t("Backup created")}

+

{_t("Your encryption keys are now being backed up to your Homeserver.")}

+ +
; + }, + + _renderPhaseOptOutConfirm: function() { + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + return
+ {_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.", + )} + + + +
; + }, + + _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 =
+

{_t("Unable to create key backup")}

+
+ +
+
; + } 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 ( + +
+ {content} +
+
+ ); + }, +}); diff --git a/src/autocomplete/Autocompleter.js b/src/autocomplete/Autocompleter.js index 7f91676cc3..e7b89fe576 100644 --- a/src/autocomplete/Autocompleter.js +++ b/src/autocomplete/Autocompleter.js @@ -85,7 +85,7 @@ export default class Autocompleter { provider .getCompletions(query, selection, force) .timeout(PROVIDER_COMPLETION_TIMEOUT) - .reflect() + .reflect(), ), ); diff --git a/src/autocomplete/CommunityProvider.js b/src/autocomplete/CommunityProvider.js index d164fab46a..b85c09b320 100644 --- a/src/autocomplete/CommunityProvider.js +++ b/src/autocomplete/CommunityProvider.js @@ -61,7 +61,7 @@ export default class CommunityProvider extends AutocompleteProvider { if (command) { 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 { return FlairStore.getGroupProfileCached(cli, groupId); } catch (e) { // if FlairStore failed, fall back to just groupId diff --git a/src/autocomplete/PlainWithPillsSerializer.js b/src/autocomplete/PlainWithPillsSerializer.js index 59cf1bde3b..09bb3772ac 100644 --- a/src/autocomplete/PlainWithPillsSerializer.js +++ b/src/autocomplete/PlainWithPillsSerializer.js @@ -26,7 +26,6 @@ import { Block } from 'slate'; */ class PlainWithPillsSerializer { - /* * @param {String} options.pillFormat - either 'md', 'plain', 'id' */ diff --git a/src/components/structures/BottomLeftMenu.js b/src/components/structures/BottomLeftMenu.js index d289ca5da1..ed8b8a00b7 100644 --- a/src/components/structures/BottomLeftMenu.js +++ b/src/components/structures/BottomLeftMenu.js @@ -33,12 +33,12 @@ module.exports = React.createClass({ }, getInitialState: function() { - return({ - directoryHover : false, - roomsHover : false, + return ({ + directoryHover: false, + roomsHover: false, homeHover: false, - peopleHover : false, - settingsHover : false, + peopleHover: false, + settingsHover: false, }); }, @@ -145,7 +145,7 @@ module.exports = React.createClass({ // Get the label/tooltip to show getLabel: function(label, show) { if (show) { - var RoomTooltip = sdk.getComponent("rooms.RoomTooltip"); + const RoomTooltip = sdk.getComponent("rooms.RoomTooltip"); return ; } }, diff --git a/src/components/structures/CompatibilityPage.js b/src/components/structures/CompatibilityPage.js index 4cbaab3dfa..3c5005c053 100644 --- a/src/components/structures/CompatibilityPage.js +++ b/src/components/structures/CompatibilityPage.js @@ -16,18 +16,18 @@ limitations under the License. 'use strict'; -var React = require('react'); +const React = require('react'); import { _t } from '../../languageHandler'; module.exports = React.createClass({ displayName: 'CompatibilityPage', propTypes: { - onAccept: React.PropTypes.func + onAccept: React.PropTypes.func, }, getDefaultProps: function() { return { - onAccept: function() {} // NOP + onAccept: function() {}, // NOP }; }, @@ -36,7 +36,6 @@ module.exports = React.createClass({ }, render: function() { - return (
@@ -69,5 +68,5 @@ module.exports = React.createClass({
); - } + }, }); diff --git a/src/components/structures/CreateRoom.js b/src/components/structures/CreateRoom.js index 2bb9adb544..a8aac71479 100644 --- a/src/components/structures/CreateRoom.js +++ b/src/components/structures/CreateRoom.js @@ -36,10 +36,10 @@ module.exports = React.createClass({ }, phases: { - CONFIG: "CONFIG", // We're waiting for user to configure and hit create. - CREATING: "CREATING", // We're sending the request. - CREATED: "CREATED", // We successfully created the room. - ERROR: "ERROR", // There was an error while trying to create room. + CONFIG: "CONFIG", // We're waiting for user to configure and hit create. + CREATING: "CREATING", // We're sending the request. + CREATED: "CREATED", // We successfully created the room. + ERROR: "ERROR", // There was an error while trying to create room. }, getDefaultProps: function() { diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index d104019a01..2c287c1b60 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -746,14 +746,38 @@ export default React.createClass({ }); }, + _leaveGroupWarnings: function() { + const warnings = []; + + if (this.state.isUserPrivileged) { + warnings.push(( + + { " " /* 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.") } + + )); + } + + return warnings; + }, + + _onLeaveClick: function() { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + const warnings = this._leaveGroupWarnings(); + Modal.createTrackedDialog('Leave Group', '', QuestionDialog, { title: _t("Leave Community"), - description: _t("Leave %(groupName)s?", {groupName: this.props.groupId}), + description: ( + + { _t("Leave %(groupName)s?", {groupName: this.props.groupId}) } + { warnings } + + ), button: _t("Leave"), - danger: true, - onFinished: async (confirmed) => { + danger: this.state.isUserPrivileged, + onFinished: async(confirmed) => { if (!confirmed) return; this.setState({membershipBusy: true}); diff --git a/src/components/structures/HomePage.js b/src/components/structures/HomePage.js index 457796f5dc..01aabf6115 100644 --- a/src/components/structures/HomePage.js +++ b/src/components/structures/HomePage.js @@ -23,6 +23,8 @@ import request from 'browser-request'; import { _t } from '../../languageHandler'; import sanitizeHtml from 'sanitize-html'; import sdk from '../../index'; +import { MatrixClient } from 'matrix-js-sdk'; +import dis from '../../dispatcher'; class HomePage extends React.Component { static displayName = 'HomePage'; @@ -37,6 +39,10 @@ class HomePage extends React.Component { homePageUrl: PropTypes.string, }; + static contextTypes = { + matrixClient: PropTypes.instanceOf(MatrixClient), + }; + state = { iframeSrc: '', page: '', @@ -52,15 +58,14 @@ class HomePage extends React.Component { if (this.props.teamToken && this.props.teamServerUrl) { 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 // so that it can inherit CSS and theming easily rather than mess around // with iframes and trying to synchronise document.stylesheets. - let src = this.props.homePageUrl || 'home.html'; + const src = this.props.homePageUrl || 'home.html'; request( { 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)); this.setState({ page: body }); - } + }, ); } } @@ -86,18 +91,55 @@ class HomePage extends React.Component { this._unmounted = true; } + onLoginClick() { + dis.dispatch({ action: 'start_login' }); + } + + onRegisterClick() { + dis.dispatch({ action: 'start_registration' }); + } + render() { - if (this.state.iframeSrc) { - return ( -
-