diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 42818244b3..430546d281 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -2,9 +2,7 @@ src/autocomplete/AutocompleteProvider.js src/autocomplete/Autocompleter.js -src/autocomplete/EmojiProvider.js src/autocomplete/UserProvider.js -src/CallHandler.js src/component-index.js src/components/structures/BottomLeftMenu.js src/components/structures/CompatibilityPage.js @@ -13,27 +11,22 @@ src/components/structures/HomePage.js src/components/structures/LeftPanel.js src/components/structures/LoggedInView.js src/components/structures/login/ForgotPassword.js -src/components/structures/login/Login.js -src/components/structures/login/Registration.js src/components/structures/LoginBox.js src/components/structures/MessagePanel.js src/components/structures/NotificationPanel.js src/components/structures/RoomDirectory.js src/components/structures/RoomStatusBar.js -src/components/structures/RoomSubList.js src/components/structures/RoomView.js src/components/structures/ScrollPanel.js 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/GroupAvatar.js src/components/views/avatars/MemberAvatar.js src/components/views/create_room/RoomAlias.js -src/components/views/dialogs/BugReportDialog.js src/components/views/dialogs/ChangelogDialog.js -src/components/views/dialogs/ChatCreateOrReuseDialog.js src/components/views/dialogs/DeactivateAccountDialog.js src/components/views/dialogs/SetPasswordDialog.js src/components/views/dialogs/UnknownDeviceDialog.js @@ -41,12 +34,12 @@ 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/EditableText.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/UserInfo.js src/components/views/elements/UserSelector.js src/components/views/globals/MatrixToolbar.js src/components/views/globals/NewVersionBar.js @@ -65,7 +58,6 @@ src/components/views/room_settings/UrlPreviewSettings.js src/components/views/rooms/Autocomplete.js src/components/views/rooms/AuxPanel.js src/components/views/rooms/EntityTile.js -src/components/views/rooms/EventTile.js src/components/views/rooms/LinkPreviewWidget.js src/components/views/rooms/MemberDeviceInfo.js src/components/views/rooms/MemberInfo.js @@ -73,12 +65,11 @@ src/components/views/rooms/MemberList.js src/components/views/rooms/MemberTile.js src/components/views/rooms/MessageComposer.js src/components/views/rooms/MessageComposerInput.js +src/components/views/rooms/PinnedEventTile.js src/components/views/rooms/RoomDropTarget.js src/components/views/rooms/RoomList.js src/components/views/rooms/RoomPreviewBar.js src/components/views/rooms/RoomSettings.js -src/components/views/rooms/RoomTile.js -src/components/views/rooms/RoomTooltip.js src/components/views/rooms/SearchableEntityList.js src/components/views/rooms/SearchBar.js src/components/views/rooms/SearchResultTile.js @@ -86,12 +77,12 @@ src/components/views/rooms/TopUnreadMessagesBar.js src/components/views/rooms/UserTile.js src/components/views/settings/AddPhoneNumber.js src/components/views/settings/ChangeAvatar.js -src/components/views/settings/ChangeDisplayName.js src/components/views/settings/ChangePassword.js src/components/views/settings/DevicesPanel.js src/components/views/settings/IntegrationsManager.js src/components/views/settings/Notifications.js src/ContentMessages.js +src/GroupAddressPicker.js src/HtmlUtils.js src/ImageUtils.js src/languageHandler.js @@ -135,6 +126,7 @@ test/components/structures/TimelinePanel-test.js test/components/views/dialogs/InteractiveAuthDialog-test.js test/components/views/login/RegistrationForm-test.js test/components/views/rooms/MessageComposerInput-test.js +test/components/views/rooms/RoomSettings-test.js test/mock-clock.js test/notifications/ContentRules-test.js test/notifications/PushRuleVectorState-test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b97251604..8483e2a0a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,117 @@ +Changes in [0.12.8](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.8) (2018-06-29) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.8-rc.2...v0.12.8) + + * Revert "affix copyButton so that it doesn't get scrolled horizontally" + [\#2013](https://github.com/matrix-org/matrix-react-sdk/pull/2013) + * don't fire share dialog when clicking timestamp of event + [\#2017](https://github.com/matrix-org/matrix-react-sdk/pull/2017) + * when the user switches room, close room settings + [\#2019](https://github.com/matrix-org/matrix-react-sdk/pull/2019) + +Changes in [0.12.8-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.8-rc.2) (2018-06-22) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.8-rc.1...v0.12.8-rc.2) + + * slash got consumed in the consolidation + [\#1998](https://github.com/matrix-org/matrix-react-sdk/pull/1998) + +Changes in [0.12.8-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.8-rc.1) (2018-06-21) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.7...v0.12.8-rc.1) + + * Update from Weblate. + [\#1997](https://github.com/matrix-org/matrix-react-sdk/pull/1997) + * refactor, consolidate and improve SlashCommands + [\#1988](https://github.com/matrix-org/matrix-react-sdk/pull/1988) + * Take replies out of labs! + [\#1996](https://github.com/matrix-org/matrix-react-sdk/pull/1996) + * re-merge reset PR + [\#1987](https://github.com/matrix-org/matrix-react-sdk/pull/1987) + * once command has a space, strict match instead of fuzzy match + [\#1985](https://github.com/matrix-org/matrix-react-sdk/pull/1985) + * Fix matrix.to URL RegExp + [\#1986](https://github.com/matrix-org/matrix-react-sdk/pull/1986) + * Fix blank sticker picker + [\#1984](https://github.com/matrix-org/matrix-react-sdk/pull/1984) + * fix e2ee file/media stuff + [\#1972](https://github.com/matrix-org/matrix-react-sdk/pull/1972) + * right click for room tile context menu + [\#1978](https://github.com/matrix-org/matrix-react-sdk/pull/1978) + * only show m.room.message in FilePanel + [\#1983](https://github.com/matrix-org/matrix-react-sdk/pull/1983) + * improve command provider + [\#1981](https://github.com/matrix-org/matrix-react-sdk/pull/1981) + * affix copyButton so that it doesn't get scrolled horizontally + [\#1980](https://github.com/matrix-org/matrix-react-sdk/pull/1980) + * split continuation if there is a gap in conversation + [\#1979](https://github.com/matrix-org/matrix-react-sdk/pull/1979) + * fix a bunch of instances of react console spam + [\#1973](https://github.com/matrix-org/matrix-react-sdk/pull/1973) + * Track decryption success/failure rate with piwik + [\#1949](https://github.com/matrix-org/matrix-react-sdk/pull/1949) + * route matrix.to/#/+... links internally (not just group ids) + [\#1975](https://github.com/matrix-org/matrix-react-sdk/pull/1975) + * implement `hitting enter after Ctrl-K should switch to the first result` + [\#1976](https://github.com/matrix-org/matrix-react-sdk/pull/1976) + * Remove tag panel feature flag + [\#1970](https://github.com/matrix-org/matrix-react-sdk/pull/1970) + * QuestionDialog pass hasCancelButton to DialogButtons + [\#1968](https://github.com/matrix-org/matrix-react-sdk/pull/1968) + * check type before msgtype in the case of `m.sticker` with msgtype + [\#1965](https://github.com/matrix-org/matrix-react-sdk/pull/1965) + * apply roomlist searchFilter to aliases if it begins with a `#` + [\#1957](https://github.com/matrix-org/matrix-react-sdk/pull/1957) + * Share Dialog + [\#1948](https://github.com/matrix-org/matrix-react-sdk/pull/1948) + * make RoomTooltip generic and add ContextMenu&Tooltip to GroupInviteTile + [\#1950](https://github.com/matrix-org/matrix-react-sdk/pull/1950) + * Fix widgets re-appearing after being deleted + [\#1958](https://github.com/matrix-org/matrix-react-sdk/pull/1958) + * Fix crash on unspecified thumbnail info, and handle gracefully + [\#1967](https://github.com/matrix-org/matrix-react-sdk/pull/1967) + * fix styling of clearButton when its not there + [\#1964](https://github.com/matrix-org/matrix-react-sdk/pull/1964) + * Implement slightly magical CSS soln. to thumbnail sizing + [\#1912](https://github.com/matrix-org/matrix-react-sdk/pull/1912) + * Select audio output for WebRTC + [\#1932](https://github.com/matrix-org/matrix-react-sdk/pull/1932) + * move css rule to be more generic; remove overriden rule + [\#1962](https://github.com/matrix-org/matrix-react-sdk/pull/1962) + * improve tag panel accessibility and remove a no-op dispatch + [\#1960](https://github.com/matrix-org/matrix-react-sdk/pull/1960) + * Revert "Fix exception when opening dev tools" + [\#1963](https://github.com/matrix-org/matrix-react-sdk/pull/1963) + * fix message appears unencrypted while encrypting and not_sent + [\#1959](https://github.com/matrix-org/matrix-react-sdk/pull/1959) + * Fix exception when opening dev tools + [\#1961](https://github.com/matrix-org/matrix-react-sdk/pull/1961) + * show redacted stickers like other redacted messages + [\#1956](https://github.com/matrix-org/matrix-react-sdk/pull/1956) + * add mx_filterFlipColor to mx_MemberInfo_cancel img + [\#1951](https://github.com/matrix-org/matrix-react-sdk/pull/1951) + * don't set the displayname on registration as Synapse now does it + [\#1953](https://github.com/matrix-org/matrix-react-sdk/pull/1953) + * allow CreateRoom to scale properly horizontally + [\#1955](https://github.com/matrix-org/matrix-react-sdk/pull/1955) + * Keep context menus that extend downwards vertically on screen + [\#1952](https://github.com/matrix-org/matrix-react-sdk/pull/1952) + * re-run checkIfAlone if a member change occurred in the active room + [\#1947](https://github.com/matrix-org/matrix-react-sdk/pull/1947) + * Persist pinned message open-ness between room switches + [\#1935](https://github.com/matrix-org/matrix-react-sdk/pull/1935) + * Pinned message cosmetic improvements + [\#1933](https://github.com/matrix-org/matrix-react-sdk/pull/1933) + * Update sinon to 5.0.7 + [\#1916](https://github.com/matrix-org/matrix-react-sdk/pull/1916) + * re-run checkIfAlone if a member change occurred in the active room + [\#1946](https://github.com/matrix-org/matrix-react-sdk/pull/1946) + * Replace "Login as guest" with "Try the app first" on login page + [\#1937](https://github.com/matrix-org/matrix-react-sdk/pull/1937) + * kill stream when using gUM for permission to device labels to turn off + camera + [\#1931](https://github.com/matrix-org/matrix-react-sdk/pull/1931) + Changes in [0.12.7](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.7) (2018-06-12) ===================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.7-rc.1...v0.12.7) diff --git a/package-lock.json b/package-lock.json index 97ed7b5dea..048fba30e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.12.4", + "version": "0.12.7", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -19,7 +19,7 @@ "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", "dev": true, "requires": { - "mime-types": "~2.1.11", + "mime-types": "2.1.17", "negotiator": "0.6.1" } }, @@ -35,7 +35,7 @@ "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", "dev": true, "requires": { - "acorn": "^3.0.4" + "acorn": "3.3.0" }, "dependencies": { "acorn": { @@ -57,10 +57,10 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" } }, "ajv-keywords": { @@ -75,9 +75,9 @@ "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" } }, "amdefine": { @@ -115,8 +115,8 @@ "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", "dev": true, "requires": { - "micromatch": "^2.1.5", - "normalize-path": "^2.0.0" + "micromatch": "2.3.11", + "normalize-path": "2.1.1" } }, "argparse": { @@ -125,7 +125,7 @@ "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", "dev": true, "requires": { - "sprintf-js": "~1.0.2" + "sprintf-js": "1.0.3" }, "dependencies": { "sprintf-js": { @@ -142,7 +142,7 @@ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, "requires": { - "arr-flatten": "^1.0.1" + "arr-flatten": "1.1.0" } }, "arr-flatten": { @@ -157,8 +157,8 @@ "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.7.0" + "define-properties": "1.1.2", + "es-abstract": "1.9.0" } }, "array-slice": { @@ -173,7 +173,7 @@ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, "requires": { - "array-uniq": "^1.0.1" + "array-uniq": "1.0.3" } }, "array-uniq": { @@ -257,21 +257,21 @@ "integrity": "sha1-UCq1SHTX24itALiHoGODzgPQAvE=", "dev": true, "requires": { - "babel-core": "^6.26.0", - "babel-polyfill": "^6.26.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "chokidar": "^1.6.1", - "commander": "^2.11.0", - "convert-source-map": "^1.5.0", - "fs-readdir-recursive": "^1.0.0", - "glob": "^7.1.2", - "lodash": "^4.17.4", - "output-file-sync": "^1.1.2", - "path-is-absolute": "^1.0.1", - "slash": "^1.0.0", - "source-map": "^0.5.6", - "v8flags": "^2.1.1" + "babel-core": "6.26.0", + "babel-polyfill": "6.26.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "chokidar": "1.7.0", + "commander": "2.11.0", + "convert-source-map": "1.5.0", + "fs-readdir-recursive": "1.0.0", + "glob": "7.1.2", + "lodash": "4.17.4", + "output-file-sync": "1.1.2", + "path-is-absolute": "1.0.1", + "slash": "1.0.0", + "source-map": "0.5.7", + "v8flags": "2.1.1" }, "dependencies": { "glob": { @@ -280,12 +280,12 @@ "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } } } @@ -296,9 +296,9 @@ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" } }, "babel-core": { @@ -307,25 +307,25 @@ "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", "dev": true, "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.0", - "debug": "^2.6.8", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.7", - "slash": "^1.0.0", - "source-map": "^0.5.6" + "babel-code-frame": "6.26.0", + "babel-generator": "6.26.0", + "babel-helpers": "6.24.1", + "babel-messages": "6.23.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "convert-source-map": "1.5.0", + "debug": "2.6.9", + "json5": "0.5.1", + "lodash": "4.17.4", + "minimatch": "3.0.4", + "path-is-absolute": "1.0.1", + "private": "0.1.8", + "slash": "1.0.0", + "source-map": "0.5.7" } }, "babel-eslint": { @@ -334,11 +334,11 @@ "integrity": "sha1-UpNBn+NnLWZZjTJ9qWlFZ7pqXy8=", "dev": true, "requires": { - "babel-traverse": "^6.0.20", - "babel-types": "^6.0.19", - "babylon": "^6.0.18", - "lodash.assign": "^4.0.0", - "lodash.pickby": "^4.0.0" + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash.assign": "4.2.0", + "lodash.pickby": "4.6.0" } }, "babel-generator": { @@ -347,14 +347,14 @@ "integrity": "sha1-rBriAHC3n248odMmlhMFN3TyDcU=", "dev": true, "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.6", - "trim-right": "^1.0.1" + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.4", + "source-map": "0.5.7", + "trim-right": "1.0.1" } }, "babel-helper-builder-binary-assignment-operator-visitor": { @@ -363,9 +363,9 @@ "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", "dev": true, "requires": { - "babel-helper-explode-assignable-expression": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-helper-explode-assignable-expression": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" } }, "babel-helper-builder-react-jsx": { @@ -374,9 +374,9 @@ "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "esutils": "^2.0.2" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "esutils": "2.0.2" } }, "babel-helper-call-delegate": { @@ -385,10 +385,10 @@ "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", "dev": true, "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-hoist-variables": "6.24.1", + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" } }, "babel-helper-define-map": { @@ -397,10 +397,10 @@ "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", "dev": true, "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "lodash": "4.17.4" } }, "babel-helper-explode-assignable-expression": { @@ -409,9 +409,9 @@ "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" } }, "babel-helper-function-name": { @@ -420,11 +420,11 @@ "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", "dev": true, "requires": { - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-get-function-arity": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" } }, "babel-helper-get-function-arity": { @@ -433,8 +433,8 @@ "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" } }, "babel-helper-hoist-variables": { @@ -443,8 +443,8 @@ "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" } }, "babel-helper-optimise-call-expression": { @@ -453,8 +453,8 @@ "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" } }, "babel-helper-regex": { @@ -463,9 +463,9 @@ "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "lodash": "4.17.4" } }, "babel-helper-remap-async-to-generator": { @@ -474,11 +474,11 @@ "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", "dev": true, "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" } }, "babel-helper-replace-supers": { @@ -487,12 +487,12 @@ "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", "dev": true, "requires": { - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-optimise-call-expression": "6.24.1", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" } }, "babel-helpers": { @@ -501,8 +501,8 @@ "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" } }, "babel-loader": { @@ -511,10 +511,10 @@ "integrity": "sha1-CzQRLVsHSKjc2/Uaz2+b1C1QuMo=", "dev": true, "requires": { - "find-cache-dir": "^0.1.1", - "loader-utils": "^0.2.16", - "mkdirp": "^0.5.1", - "object-assign": "^4.0.1" + "find-cache-dir": "0.1.1", + "loader-utils": "0.2.17", + "mkdirp": "0.5.1", + "object-assign": "4.1.1" } }, "babel-messages": { @@ -523,7 +523,7 @@ "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" } }, "babel-plugin-add-module-exports": { @@ -538,7 +538,7 @@ "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" } }, "babel-plugin-syntax-async-functions": { @@ -589,10 +589,10 @@ "integrity": "sha1-Ruo+fFr2KXgqyfHtG3zTj4Qlr9Q=", "dev": true, "requires": { - "babel-helper-function-name": "^6.8.0", - "babel-plugin-syntax-async-functions": "^6.8.0", - "babel-template": "^6.9.0", - "babel-traverse": "^6.10.4" + "babel-helper-function-name": "6.24.1", + "babel-plugin-syntax-async-functions": "6.13.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0" } }, "babel-plugin-transform-async-to-generator": { @@ -601,9 +601,9 @@ "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", "dev": true, "requires": { - "babel-helper-remap-async-to-generator": "^6.24.1", - "babel-plugin-syntax-async-functions": "^6.8.0", - "babel-runtime": "^6.22.0" + "babel-helper-remap-async-to-generator": "6.24.1", + "babel-plugin-syntax-async-functions": "6.13.0", + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-class-properties": { @@ -612,10 +612,10 @@ "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", "dev": true, "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-plugin-syntax-class-properties": "^6.8.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-helper-function-name": "6.24.1", + "babel-plugin-syntax-class-properties": "6.13.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" } }, "babel-plugin-transform-es2015-arrow-functions": { @@ -624,7 +624,7 @@ "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-block-scoped-functions": { @@ -633,7 +633,7 @@ "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-block-scoping": { @@ -642,11 +642,11 @@ "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "lodash": "4.17.4" } }, "babel-plugin-transform-es2015-classes": { @@ -655,15 +655,15 @@ "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", "dev": true, "requires": { - "babel-helper-define-map": "^6.24.1", - "babel-helper-function-name": "^6.24.1", - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-helper-replace-supers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-define-map": "6.26.0", + "babel-helper-function-name": "6.24.1", + "babel-helper-optimise-call-expression": "6.24.1", + "babel-helper-replace-supers": "6.24.1", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" } }, "babel-plugin-transform-es2015-computed-properties": { @@ -672,8 +672,8 @@ "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" } }, "babel-plugin-transform-es2015-destructuring": { @@ -682,7 +682,7 @@ "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-duplicate-keys": { @@ -691,8 +691,8 @@ "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" } }, "babel-plugin-transform-es2015-for-of": { @@ -701,7 +701,7 @@ "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-function-name": { @@ -710,9 +710,9 @@ "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", "dev": true, "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" } }, "babel-plugin-transform-es2015-literals": { @@ -721,7 +721,7 @@ "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-modules-amd": { @@ -730,9 +730,9 @@ "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", "dev": true, "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" } }, "babel-plugin-transform-es2015-modules-commonjs": { @@ -741,10 +741,10 @@ "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=", "dev": true, "requires": { - "babel-plugin-transform-strict-mode": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-types": "^6.26.0" + "babel-plugin-transform-strict-mode": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-types": "6.26.0" } }, "babel-plugin-transform-es2015-modules-systemjs": { @@ -753,9 +753,9 @@ "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", "dev": true, "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-helper-hoist-variables": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" } }, "babel-plugin-transform-es2015-modules-umd": { @@ -764,9 +764,9 @@ "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", "dev": true, "requires": { - "babel-plugin-transform-es2015-modules-amd": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-plugin-transform-es2015-modules-amd": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" } }, "babel-plugin-transform-es2015-object-super": { @@ -775,8 +775,8 @@ "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", "dev": true, "requires": { - "babel-helper-replace-supers": "^6.24.1", - "babel-runtime": "^6.22.0" + "babel-helper-replace-supers": "6.24.1", + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-parameters": { @@ -785,12 +785,12 @@ "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", "dev": true, "requires": { - "babel-helper-call-delegate": "^6.24.1", - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-call-delegate": "6.24.1", + "babel-helper-get-function-arity": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" } }, "babel-plugin-transform-es2015-shorthand-properties": { @@ -799,8 +799,8 @@ "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" } }, "babel-plugin-transform-es2015-spread": { @@ -809,7 +809,7 @@ "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-sticky-regex": { @@ -818,9 +818,9 @@ "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", "dev": true, "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-helper-regex": "6.26.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" } }, "babel-plugin-transform-es2015-template-literals": { @@ -829,7 +829,7 @@ "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-typeof-symbol": { @@ -838,7 +838,7 @@ "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-es2015-unicode-regex": { @@ -847,9 +847,9 @@ "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", "dev": true, "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "regexpu-core": "^2.0.0" + "babel-helper-regex": "6.26.0", + "babel-runtime": "6.26.0", + "regexpu-core": "2.0.0" } }, "babel-plugin-transform-exponentiation-operator": { @@ -858,9 +858,9 @@ "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", "dev": true, "requires": { - "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", - "babel-plugin-syntax-exponentiation-operator": "^6.8.0", - "babel-runtime": "^6.22.0" + "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", + "babel-plugin-syntax-exponentiation-operator": "6.13.0", + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-flow-strip-types": { @@ -869,8 +869,8 @@ "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", "dev": true, "requires": { - "babel-plugin-syntax-flow": "^6.18.0", - "babel-runtime": "^6.22.0" + "babel-plugin-syntax-flow": "6.18.0", + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-object-rest-spread": { @@ -879,8 +879,8 @@ "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", "dev": true, "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.8.0", - "babel-runtime": "^6.26.0" + "babel-plugin-syntax-object-rest-spread": "6.13.0", + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-react-display-name": { @@ -889,7 +889,7 @@ "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-react-jsx": { @@ -898,9 +898,9 @@ "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", "dev": true, "requires": { - "babel-helper-builder-react-jsx": "^6.24.1", - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" + "babel-helper-builder-react-jsx": "6.26.0", + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-react-jsx-self": { @@ -909,8 +909,8 @@ "integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=", "dev": true, "requires": { - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-react-jsx-source": { @@ -919,8 +919,8 @@ "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", "dev": true, "requires": { - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-regenerator": { @@ -929,7 +929,7 @@ "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", "dev": true, "requires": { - "regenerator-transform": "^0.10.0" + "regenerator-transform": "0.10.1" } }, "babel-plugin-transform-runtime": { @@ -938,7 +938,7 @@ "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" } }, "babel-plugin-transform-strict-mode": { @@ -947,8 +947,8 @@ "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" } }, "babel-polyfill": { @@ -957,9 +957,9 @@ "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" + "babel-runtime": "6.26.0", + "core-js": "2.5.1", + "regenerator-runtime": "0.10.5" }, "dependencies": { "regenerator-runtime": { @@ -976,30 +976,30 @@ "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", "dev": true, "requires": { - "babel-plugin-check-es2015-constants": "^6.22.0", - "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoping": "^6.24.1", - "babel-plugin-transform-es2015-classes": "^6.24.1", - "babel-plugin-transform-es2015-computed-properties": "^6.24.1", - "babel-plugin-transform-es2015-destructuring": "^6.22.0", - "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1", - "babel-plugin-transform-es2015-for-of": "^6.22.0", - "babel-plugin-transform-es2015-function-name": "^6.24.1", - "babel-plugin-transform-es2015-literals": "^6.22.0", - "babel-plugin-transform-es2015-modules-amd": "^6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", - "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", - "babel-plugin-transform-es2015-modules-umd": "^6.24.1", - "babel-plugin-transform-es2015-object-super": "^6.24.1", - "babel-plugin-transform-es2015-parameters": "^6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", - "babel-plugin-transform-es2015-spread": "^6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "^6.24.1", - "babel-plugin-transform-es2015-template-literals": "^6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0", - "babel-plugin-transform-es2015-unicode-regex": "^6.24.1", - "babel-plugin-transform-regenerator": "^6.24.1" + "babel-plugin-check-es2015-constants": "6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoping": "6.26.0", + "babel-plugin-transform-es2015-classes": "6.24.1", + "babel-plugin-transform-es2015-computed-properties": "6.24.1", + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", + "babel-plugin-transform-es2015-for-of": "6.23.0", + "babel-plugin-transform-es2015-function-name": "6.24.1", + "babel-plugin-transform-es2015-literals": "6.22.0", + "babel-plugin-transform-es2015-modules-amd": "6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", + "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", + "babel-plugin-transform-es2015-modules-umd": "6.24.1", + "babel-plugin-transform-es2015-object-super": "6.24.1", + "babel-plugin-transform-es2015-parameters": "6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", + "babel-plugin-transform-es2015-spread": "6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "6.24.1", + "babel-plugin-transform-es2015-template-literals": "6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "6.24.1", + "babel-plugin-transform-regenerator": "6.26.0" } }, "babel-preset-es2016": { @@ -1008,7 +1008,7 @@ "integrity": "sha1-+QC/k+LrwNJ235uKtZck6/2Vn4s=", "dev": true, "requires": { - "babel-plugin-transform-exponentiation-operator": "^6.24.1" + "babel-plugin-transform-exponentiation-operator": "6.24.1" } }, "babel-preset-es2017": { @@ -1017,8 +1017,8 @@ "integrity": "sha1-WXvq37n38gi8/YoS6bKym4svFNE=", "dev": true, "requires": { - "babel-plugin-syntax-trailing-function-commas": "^6.22.0", - "babel-plugin-transform-async-to-generator": "^6.24.1" + "babel-plugin-syntax-trailing-function-commas": "6.22.0", + "babel-plugin-transform-async-to-generator": "6.24.1" } }, "babel-preset-flow": { @@ -1027,7 +1027,7 @@ "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=", "dev": true, "requires": { - "babel-plugin-transform-flow-strip-types": "^6.22.0" + "babel-plugin-transform-flow-strip-types": "6.22.0" } }, "babel-preset-react": { @@ -1036,12 +1036,12 @@ "integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=", "dev": true, "requires": { - "babel-plugin-syntax-jsx": "^6.3.13", - "babel-plugin-transform-react-display-name": "^6.23.0", - "babel-plugin-transform-react-jsx": "^6.24.1", - "babel-plugin-transform-react-jsx-self": "^6.22.0", - "babel-plugin-transform-react-jsx-source": "^6.22.0", - "babel-preset-flow": "^6.23.0" + "babel-plugin-syntax-jsx": "6.18.0", + "babel-plugin-transform-react-display-name": "6.25.0", + "babel-plugin-transform-react-jsx": "6.24.1", + "babel-plugin-transform-react-jsx-self": "6.22.0", + "babel-plugin-transform-react-jsx-source": "6.22.0", + "babel-preset-flow": "6.23.0" } }, "babel-register": { @@ -1050,13 +1050,13 @@ "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", "dev": true, "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" + "babel-core": "6.26.0", + "babel-runtime": "6.26.0", + "core-js": "2.5.1", + "home-or-tmp": "2.0.0", + "lodash": "4.17.4", + "mkdirp": "0.5.1", + "source-map-support": "0.4.18" } }, "babel-runtime": { @@ -1064,8 +1064,8 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.5.1", + "regenerator-runtime": "0.11.0" } }, "babel-template": { @@ -1074,11 +1074,11 @@ "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash": "4.17.4" } }, "babel-traverse": { @@ -1087,15 +1087,15 @@ "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "dev": true, "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.9", + "globals": "9.18.0", + "invariant": "2.2.2", + "lodash": "4.17.4" } }, "babel-types": { @@ -1104,10 +1104,10 @@ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.4", + "to-fast-properties": "1.0.3" } }, "babylon": { @@ -1151,7 +1151,7 @@ "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", "optional": true, "requires": { - "tweetnacl": "^0.14.3" + "tweetnacl": "0.14.5" } }, "better-assert": { @@ -1198,15 +1198,15 @@ "dev": true, "requires": { "bytes": "3.0.0", - "content-type": "~1.0.4", + "content-type": "1.0.4", "debug": "2.6.9", - "depd": "~1.1.1", - "http-errors": "~1.6.2", + "depd": "1.1.1", + "http-errors": "1.6.2", "iconv-lite": "0.4.19", - "on-finished": "~2.3.0", + "on-finished": "2.3.0", "qs": "6.5.1", "raw-body": "2.3.2", - "type-is": "~1.6.15" + "type-is": "1.6.15" } }, "brace-expansion": { @@ -1214,7 +1214,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", "requires": { - "balanced-match": "^1.0.0", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, @@ -1224,9 +1224,9 @@ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" } }, "browser-encrypt-attachment": { @@ -1251,7 +1251,7 @@ "integrity": "sha1-BnFJtmjfMcS1hTPgLQHoBthgjiw=", "dev": true, "requires": { - "inherits": "^2.0.1" + "inherits": "2.0.3" } }, "browserify-zlib": { @@ -1260,7 +1260,7 @@ "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", "dev": true, "requires": { - "pako": "~0.2.0" + "pako": "0.2.9" }, "dependencies": { "pako": { @@ -1277,9 +1277,9 @@ "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" + "base64-js": "1.2.1", + "ieee754": "1.1.8", + "isarray": "1.0.0" } }, "builtin-status-codes": { @@ -1300,7 +1300,7 @@ "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", "dev": true, "requires": { - "callsites": "^0.2.0" + "callsites": "0.2.0" } }, "callsite": { @@ -1332,8 +1332,8 @@ "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", "dev": true, "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" + "align-text": "0.1.4", + "lazy-cache": "1.0.4" } }, "chalk": { @@ -1342,11 +1342,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" } }, "chokidar": { @@ -1355,15 +1355,15 @@ "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", "dev": true, "requires": { - "anymatch": "^1.3.0", - "async-each": "^1.0.0", - "fsevents": "^1.0.0", - "glob-parent": "^2.0.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^2.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0" + "anymatch": "1.3.2", + "async-each": "1.0.1", + "fsevents": "1.1.2", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0" } }, "circular-json": { @@ -1383,7 +1383,7 @@ "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", "dev": true, "requires": { - "restore-cursor": "^1.0.1" + "restore-cursor": "1.0.1" } }, "cli-width": { @@ -1398,8 +1398,8 @@ "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", "dev": true, "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", + "center-align": "0.1.3", + "right-align": "0.1.3", "wordwrap": "0.0.2" }, "dependencies": { @@ -1440,7 +1440,7 @@ "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", "dev": true, "requires": { - "lodash": "^4.5.0" + "lodash": "4.17.4" } }, "combined-stream": { @@ -1448,7 +1448,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { - "delayed-stream": "~1.0.0" + "delayed-stream": "1.0.0" } }, "commander": { @@ -1468,10 +1468,10 @@ "resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.28.1.tgz", "integrity": "sha1-Buq41SM4uDn6Gi11rwCF7tGxvq4=", "requires": { - "entities": "~ 1.1.1", - "mdurl": "~ 1.0.1", - "minimist": "~ 1.2.0", - "string.prototype.repeat": "^0.2.0" + "entities": "1.1.1", + "mdurl": "1.0.1", + "minimist": "1.2.0", + "string.prototype.repeat": "0.2.0" } }, "component-bind": { @@ -1503,9 +1503,9 @@ "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", "dev": true, "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "typedarray": "0.0.6" } }, "connect": { @@ -1516,7 +1516,7 @@ "requires": { "debug": "2.6.9", "finalhandler": "1.0.6", - "parseurl": "~1.3.2", + "parseurl": "1.3.2", "utils-merge": "1.0.1" } }, @@ -1526,7 +1526,7 @@ "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", "dev": true, "requires": { - "date-now": "^0.1.4" + "date-now": "0.1.4" } }, "constants-browserify": { @@ -1567,11 +1567,11 @@ "resolved": "https://registry.npmjs.org/counterpart/-/counterpart-0.18.3.tgz", "integrity": "sha512-tli4qPAFeYB34LvvCc/1xYRLCWjf4WsUt6sXfpggDfGDKoI8rhnabz0SljDoBpAK8z1u8GBCg0YDkbvWb16uUQ==", "requires": { - "date-names": "^0.1.9", - "except": "^0.1.3", - "extend": "^3.0.0", - "pluralizers": "^0.1.6", - "sprintf-js": "^1.0.3" + "date-names": "0.1.10", + "except": "0.1.3", + "extend": "3.0.1", + "pluralizers": "0.1.6", + "sprintf-js": "1.1.1" } }, "create-react-class": { @@ -1579,9 +1579,9 @@ "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.2.tgz", "integrity": "sha1-zx7RXxKq1/FO9fLf4F5sQvke8Co=", "requires": { - "fbjs": "^0.8.9", - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1" } }, "crypto-browserify": { @@ -1608,7 +1608,7 @@ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "^0.10.9" + "es5-ext": "0.10.35" } }, "dashdash": { @@ -1616,7 +1616,7 @@ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "date-names": { @@ -1657,8 +1657,8 @@ "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", "dev": true, "requires": { - "foreach": "^2.0.5", - "object-keys": "^1.0.8" + "foreach": "2.0.5", + "object-keys": "1.0.11" } }, "del": { @@ -1667,13 +1667,13 @@ "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", "dev": true, "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.0", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.2" } }, "delayed-stream": { @@ -1693,7 +1693,7 @@ "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", "dev": true, "requires": { - "repeating": "^2.0.0" + "repeating": "2.0.1" } }, "di": { @@ -1714,8 +1714,8 @@ "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", "dev": true, "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" + "esutils": "2.0.2", + "isarray": "1.0.0" } }, "dom-serialize": { @@ -1724,10 +1724,10 @@ "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", "dev": true, "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" + "custom-event": "1.0.1", + "ent": "2.2.0", + "extend": "3.0.1", + "void-elements": "2.0.1" } }, "dom-serializer": { @@ -1735,8 +1735,8 @@ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", "requires": { - "domelementtype": "~1.1.1", - "entities": "~1.1.1" + "domelementtype": "1.1.3", + "entities": "1.1.1" }, "dependencies": { "domelementtype": { @@ -1762,7 +1762,7 @@ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", "requires": { - "domelementtype": "1" + "domelementtype": "1.3.0" } }, "domutils": { @@ -1770,8 +1770,8 @@ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.6.2.tgz", "integrity": "sha1-GVjMC0yUJuntNn+xyOhUiRsPo/8=", "requires": { - "dom-serializer": "0", - "domelementtype": "1" + "dom-serializer": "0.1.0", + "domelementtype": "1.3.0" } }, "draft-js": { @@ -1779,9 +1779,9 @@ "resolved": "https://registry.npmjs.org/draft-js/-/draft-js-0.11.0-alpha.tgz", "integrity": "sha1-MtshCPkn6bhEbaH3nkR1wrf4aK4=", "requires": { - "fbjs": "^0.8.12", - "immutable": "~3.7.4", - "object-assign": "^4.1.0" + "fbjs": "0.8.16", + "immutable": "3.7.6", + "object-assign": "4.1.1" } }, "draft-js-export-html": { @@ -1789,7 +1789,7 @@ "resolved": "https://registry.npmjs.org/draft-js-export-html/-/draft-js-export-html-0.6.0.tgz", "integrity": "sha1-zIDwVExD0Kf+28U8DLCRToCQ92k=", "requires": { - "draft-js-utils": ">=0.2.0" + "draft-js-utils": "1.2.0" } }, "draft-js-export-markdown": { @@ -1797,7 +1797,7 @@ "resolved": "https://registry.npmjs.org/draft-js-export-markdown/-/draft-js-export-markdown-0.3.0.tgz", "integrity": "sha1-hjkOA86vHTR/xhaGerf1Net2v0I=", "requires": { - "draft-js-utils": ">=0.2.0" + "draft-js-utils": "1.2.0" } }, "draft-js-utils": { @@ -1811,7 +1811,7 @@ "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "0.1.1" } }, "ee-first": { @@ -1842,7 +1842,7 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "requires": { - "iconv-lite": "~0.4.13" + "iconv-lite": "0.4.19" } }, "engine.io": { @@ -1939,9 +1939,9 @@ "integrity": "sha1-TW5omzcl+GCQknzMhs2fFjW4ni4=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.2.0", - "tapable": "^0.1.8" + "graceful-fs": "4.1.11", + "memory-fs": "0.2.0", + "tapable": "0.1.10" }, "dependencies": { "memory-fs": { @@ -1969,7 +1969,7 @@ "integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=", "dev": true, "requires": { - "prr": "~0.0.0" + "prr": "0.0.0" } }, "es-abstract": { @@ -1978,11 +1978,11 @@ "integrity": "sha512-kk3IJoKo7A3pWJc0OV8yZ/VEX2oSUytfekrJiqoxBlKJMFAJVJVpGdHClCCTdv+Fn2zHfpDHHIelMFhZVfef3Q==", "dev": true, "requires": { - "es-to-primitive": "^1.1.1", - "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" + "es-to-primitive": "1.1.1", + "function-bind": "1.1.1", + "has": "1.0.1", + "is-callable": "1.1.3", + "is-regex": "1.0.4" } }, "es-to-primitive": { @@ -1991,9 +1991,9 @@ "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", "dev": true, "requires": { - "is-callable": "^1.1.1", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.1" + "is-callable": "1.1.3", + "is-date-object": "1.0.1", + "is-symbol": "1.0.1" } }, "es5-ext": { @@ -2002,8 +2002,8 @@ "integrity": "sha1-GO6FjOajxFx9eekcFfzKnsVoSU8=", "dev": true, "requires": { - "es6-iterator": "~2.0.1", - "es6-symbol": "~3.1.1" + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" } }, "es6-iterator": { @@ -2012,9 +2012,9 @@ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "d": "1.0.0", + "es5-ext": "0.10.35", + "es6-symbol": "3.1.1" } }, "es6-map": { @@ -2023,12 +2023,12 @@ "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" + "d": "1.0.0", + "es5-ext": "0.10.35", + "es6-iterator": "2.0.3", + "es6-set": "0.1.5", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" } }, "es6-set": { @@ -2037,11 +2037,11 @@ "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", + "d": "1.0.0", + "es5-ext": "0.10.35", + "es6-iterator": "2.0.3", "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" + "event-emitter": "0.3.5" } }, "es6-symbol": { @@ -2050,8 +2050,8 @@ "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "d": "1.0.0", + "es5-ext": "0.10.35" } }, "es6-weak-map": { @@ -2060,10 +2060,10 @@ "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.14", - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" + "d": "1.0.0", + "es5-ext": "0.10.35", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" } }, "escape-html": { @@ -2084,10 +2084,10 @@ "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", "dev": true, "requires": { - "es6-map": "^0.1.3", - "es6-weak-map": "^2.0.1", - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "es6-map": "0.1.5", + "es6-weak-map": "2.0.2", + "esrecurse": "4.2.0", + "estraverse": "4.2.0" } }, "eslint": { @@ -2096,41 +2096,41 @@ "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", "dev": true, "requires": { - "babel-code-frame": "^6.16.0", - "chalk": "^1.1.3", - "concat-stream": "^1.5.2", - "debug": "^2.1.1", - "doctrine": "^2.0.0", - "escope": "^3.6.0", - "espree": "^3.4.0", - "esquery": "^1.0.0", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "glob": "^7.0.3", - "globals": "^9.14.0", - "ignore": "^3.2.0", - "imurmurhash": "^0.1.4", - "inquirer": "^0.12.0", - "is-my-json-valid": "^2.10.0", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.5.1", - "json-stable-stringify": "^1.0.0", - "levn": "^0.3.0", - "lodash": "^4.0.0", - "mkdirp": "^0.5.0", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.1", - "pluralize": "^1.2.1", - "progress": "^1.1.8", - "require-uncached": "^1.0.2", - "shelljs": "^0.7.5", - "strip-bom": "^3.0.0", - "strip-json-comments": "~2.0.1", - "table": "^3.7.8", - "text-table": "~0.2.0", - "user-home": "^2.0.0" + "babel-code-frame": "6.26.0", + "chalk": "1.1.3", + "concat-stream": "1.6.0", + "debug": "2.6.9", + "doctrine": "2.0.0", + "escope": "3.6.0", + "espree": "3.5.1", + "esquery": "1.0.0", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "glob": "7.1.2", + "globals": "9.18.0", + "ignore": "3.3.5", + "imurmurhash": "0.1.4", + "inquirer": "0.12.0", + "is-my-json-valid": "2.16.1", + "is-resolvable": "1.0.0", + "js-yaml": "3.10.0", + "json-stable-stringify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.4", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "1.2.1", + "progress": "1.1.8", + "require-uncached": "1.0.3", + "shelljs": "0.7.8", + "strip-bom": "3.0.0", + "strip-json-comments": "2.0.1", + "table": "3.8.3", + "text-table": "0.2.0", + "user-home": "2.0.0" }, "dependencies": { "glob": { @@ -2139,12 +2139,12 @@ "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "user-home": { @@ -2153,7 +2153,7 @@ "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", "dev": true, "requires": { - "os-homedir": "^1.0.0" + "os-homedir": "1.0.2" } } } @@ -2176,7 +2176,7 @@ "integrity": "sha512-RiQv+7Z9QDJuzt+NO8sYgkLGT+h+WeCrxP7y8lI7wpU41x3x/2o3PGtHk9ck8QnA9/mlbNcy/hG0eKvmd7npaA==", "dev": true, "requires": { - "lodash": "^4.15.0" + "lodash": "4.17.4" } }, "eslint-plugin-react": { @@ -2185,10 +2185,10 @@ "integrity": "sha512-KC7Snr4YsWZD5flu6A5c0AcIZidzW3Exbqp7OT67OaD2AppJtlBr/GuPrW/vaQM/yfZotEvKAdrxrO+v8vwYJA==", "dev": true, "requires": { - "doctrine": "^2.0.2", - "has": "^1.0.1", - "jsx-ast-utils": "^2.0.1", - "prop-types": "^15.6.0" + "doctrine": "2.1.0", + "has": "1.0.1", + "jsx-ast-utils": "2.0.1", + "prop-types": "15.6.0" }, "dependencies": { "doctrine": { @@ -2197,7 +2197,7 @@ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "esutils": "^2.0.2" + "esutils": "2.0.2" } } } @@ -2208,8 +2208,8 @@ "integrity": "sha1-DJiLirRttTEAoZVK5LqZXd0n2H4=", "dev": true, "requires": { - "acorn": "^5.1.1", - "acorn-jsx": "^3.0.0" + "acorn": "5.1.2", + "acorn-jsx": "3.0.1" } }, "esprima": { @@ -2224,7 +2224,7 @@ "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", "dev": true, "requires": { - "estraverse": "^4.0.0" + "estraverse": "4.2.0" } }, "esrecurse": { @@ -2233,8 +2233,8 @@ "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", "dev": true, "requires": { - "estraverse": "^4.1.0", - "object-assign": "^4.0.1" + "estraverse": "4.2.0", + "object-assign": "4.1.1" } }, "estraverse": { @@ -2261,8 +2261,8 @@ "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "d": "1.0.0", + "es5-ext": "0.10.35" } }, "eventemitter3": { @@ -2297,9 +2297,9 @@ "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", "dev": true, "requires": { - "array-slice": "^0.2.3", - "array-unique": "^0.2.1", - "braces": "^0.1.2" + "array-slice": "0.2.3", + "array-unique": "0.2.1", + "braces": "0.1.5" }, "dependencies": { "braces": { @@ -2308,7 +2308,7 @@ "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", "dev": true, "requires": { - "expand-range": "^0.1.0" + "expand-range": "0.1.1" } }, "expand-range": { @@ -2317,8 +2317,8 @@ "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", "dev": true, "requires": { - "is-number": "^0.1.1", - "repeat-string": "^0.2.2" + "is-number": "0.1.1", + "repeat-string": "0.2.2" } }, "is-number": { @@ -2341,7 +2341,7 @@ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, "requires": { - "is-posix-bracket": "^0.1.0" + "is-posix-bracket": "0.1.1" } }, "expand-range": { @@ -2350,7 +2350,7 @@ "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { - "fill-range": "^2.1.0" + "fill-range": "2.2.3" } }, "expect": { @@ -2359,13 +2359,13 @@ "integrity": "sha1-1Fj+TFYAQDa64yMkFqP2Nh8E+WU=", "dev": true, "requires": { - "define-properties": "~1.1.2", - "has": "^1.0.1", - "is-equal": "^1.5.1", - "is-regex": "^1.0.3", - "object-inspect": "^1.1.0", - "object-keys": "^1.0.9", - "tmatch": "^2.0.1" + "define-properties": "1.1.2", + "has": "1.0.1", + "is-equal": "1.5.5", + "is-regex": "1.0.4", + "object-inspect": "1.3.0", + "object-keys": "1.0.11", + "tmatch": "2.0.1" } }, "extend": { @@ -2379,7 +2379,7 @@ "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "extsprintf": { @@ -2408,7 +2408,7 @@ "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-2.1.1.tgz", "integrity": "sha1-Uj4U/a9SSIBbsC9i78M75wP1GGU=", "requires": { - "fbjs": "^0.8.4" + "fbjs": "0.8.16" } }, "fbjs": { @@ -2416,13 +2416,13 @@ "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", "requires": { - "core-js": "^1.0.0", - "isomorphic-fetch": "^2.1.1", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.9" + "core-js": "1.2.7", + "isomorphic-fetch": "2.2.1", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "promise": "7.3.1", + "setimmediate": "1.0.5", + "ua-parser-js": "0.7.17" }, "dependencies": { "core-js": { @@ -2438,8 +2438,8 @@ "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" } }, "file-entry-cache": { @@ -2448,8 +2448,8 @@ "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", "dev": true, "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "flat-cache": "1.3.0", + "object-assign": "4.1.1" } }, "file-saver": { @@ -2474,11 +2474,11 @@ "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", "dev": true, "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^1.1.3", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" } }, "finalhandler": { @@ -2488,12 +2488,12 @@ "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.1", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.3.1", - "unpipe": "~1.0.0" + "encodeurl": "1.0.1", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.3.1", + "unpipe": "1.0.0" } }, "find-cache-dir": { @@ -2502,9 +2502,9 @@ "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", "dev": true, "requires": { - "commondir": "^1.0.1", - "mkdirp": "^0.5.1", - "pkg-dir": "^1.0.0" + "commondir": "1.0.1", + "mkdirp": "0.5.1", + "pkg-dir": "1.0.0" } }, "find-up": { @@ -2513,8 +2513,8 @@ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" } }, "flat-cache": { @@ -2523,10 +2523,10 @@ "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", "dev": true, "requires": { - "circular-json": "^0.3.1", - "del": "^2.0.2", - "graceful-fs": "^4.1.2", - "write": "^0.2.1" + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" } }, "flow-parser": { @@ -2540,9 +2540,9 @@ "resolved": "https://registry.npmjs.org/flux/-/flux-2.1.1.tgz", "integrity": "sha1-LGrGUtQzdIiWhInGWG86/yajjqQ=", "requires": { - "fbemitter": "^2.0.0", + "fbemitter": "2.1.1", "fbjs": "0.1.0-alpha.7", - "immutable": "^3.7.4" + "immutable": "3.7.6" }, "dependencies": { "core-js": { @@ -2555,9 +2555,9 @@ "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.1.0-alpha.7.tgz", "integrity": "sha1-rUMIuPIy+zxzYDNJ6nJdHpw5Mjw=", "requires": { - "core-js": "^1.0.0", - "promise": "^7.0.3", - "whatwg-fetch": "^0.9.0" + "core-js": "1.2.7", + "promise": "7.3.1", + "whatwg-fetch": "0.9.0" } }, "whatwg-fetch": { @@ -2572,7 +2572,7 @@ "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-2.4.3.tgz", "integrity": "sha512-sT5Ip9nyAIxWq8Apt1Fdv6yTci5GotaOtO5Ro1/+F3PizttNBcCYz8j/Qze54PPFK73KUbOqh++HUCiyNPqvhA==", "requires": { - "tabbable": "^1.0.3" + "tabbable": "1.1.2" } }, "focus-trap-react": { @@ -2580,7 +2580,7 @@ "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-3.1.2.tgz", "integrity": "sha512-MoQmONoy9gRPyrC5DGezkcOMGgx7MtIOAQDHe098UtL2sA2vmucJwEmQisb+8LRXNYFHxuw5zJ1oLFeKu4Mteg==", "requires": { - "focus-trap": "^2.0.1" + "focus-trap": "2.4.3" } }, "for-in": { @@ -2595,7 +2595,7 @@ "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "dev": true, "requires": { - "for-in": "^1.0.1" + "for-in": "1.0.2" } }, "foreach": { @@ -2620,9 +2620,9 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "requires": { - "asynckit": "^0.4.0", + "asynckit": "0.4.0", "combined-stream": "1.0.6", - "mime-types": "^2.1.12" + "mime-types": "2.1.17" } }, "fs-access": { @@ -2631,7 +2631,7 @@ "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", "dev": true, "requires": { - "null-check": "^1.0.0" + "null-check": "1.0.0" } }, "fs-readdir-recursive": { @@ -2653,8 +2653,8 @@ "dev": true, "optional": true, "requires": { - "nan": "^2.3.0", - "node-pre-gyp": "^0.6.36" + "nan": "2.7.0", + "node-pre-gyp": "0.6.36" }, "dependencies": { "abbrev": { @@ -2690,8 +2690,8 @@ "dev": true, "optional": true, "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "delegates": "1.0.0", + "readable-stream": "2.2.9" } }, "asn1": { @@ -2735,7 +2735,7 @@ "dev": true, "optional": true, "requires": { - "tweetnacl": "^0.14.3" + "tweetnacl": "0.14.5" } }, "block-stream": { @@ -2743,16 +2743,15 @@ "bundled": true, "dev": true, "requires": { - "inherits": "~2.0.0" + "inherits": "2.0.3" } }, "boom": { "version": "2.10.1", "bundled": true, "dev": true, - "optional": true, "requires": { - "hoek": "2.x.x" + "hoek": "2.16.3" } }, "brace-expansion": { @@ -2760,7 +2759,7 @@ "bundled": true, "dev": true, "requires": { - "balanced-match": "^0.4.1", + "balanced-match": "0.4.2", "concat-map": "0.0.1" } }, @@ -2791,7 +2790,7 @@ "bundled": true, "dev": true, "requires": { - "delayed-stream": "~1.0.0" + "delayed-stream": "1.0.0" } }, "concat-map": { @@ -2815,7 +2814,7 @@ "dev": true, "optional": true, "requires": { - "boom": "2.x.x" + "boom": "2.10.1" } }, "dashdash": { @@ -2824,7 +2823,7 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" }, "dependencies": { "assert-plus": { @@ -2867,7 +2866,7 @@ "dev": true, "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "0.1.1" } }, "extend": { @@ -2908,10 +2907,10 @@ "bundled": true, "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" } }, "fstream-ignore": { @@ -2920,9 +2919,9 @@ "dev": true, "optional": true, "requires": { - "fstream": "^1.0.0", - "inherits": "2", - "minimatch": "^3.0.0" + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" } }, "gauge": { @@ -2931,14 +2930,14 @@ "dev": true, "optional": true, "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "aproba": "1.1.1", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" } }, "getpass": { @@ -2947,7 +2946,7 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" }, "dependencies": { "assert-plus": { @@ -2963,12 +2962,12 @@ "bundled": true, "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "graceful-fs": { @@ -3013,8 +3012,7 @@ "hoek": { "version": "2.16.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "http-signature": { "version": "1.1.1", @@ -3022,9 +3020,9 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "^0.2.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "assert-plus": "0.2.0", + "jsprim": "1.4.0", + "sshpk": "1.13.0" } }, "inflight": { @@ -3032,8 +3030,8 @@ "bundled": true, "dev": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -3078,7 +3076,7 @@ "dev": true, "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "0.1.1" } }, "jsbn": { @@ -3099,7 +3097,7 @@ "dev": true, "optional": true, "requires": { - "jsonify": "~0.0.0" + "jsonify": "0.0.0" } }, "json-stringify-safe": { @@ -3180,15 +3178,15 @@ "dev": true, "optional": true, "requires": { - "mkdirp": "^0.5.1", - "nopt": "^4.0.1", - "npmlog": "^4.0.2", - "rc": "^1.1.7", - "request": "^2.81.0", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^2.2.1", - "tar-pack": "^3.4.0" + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.0", + "rc": "1.2.1", + "request": "2.81.0", + "rimraf": "2.6.1", + "semver": "5.3.0", + "tar": "2.2.1", + "tar-pack": "3.4.0" } }, "nopt": { @@ -3207,10 +3205,10 @@ "dev": true, "optional": true, "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" } }, "number-is-nan": { @@ -3235,7 +3233,7 @@ "bundled": true, "dev": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "os-homedir": { @@ -3313,13 +3311,13 @@ "bundled": true, "dev": true, "requires": { - "buffer-shims": "~1.0.0", - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~1.0.0", - "util-deprecate": "~1.0.1" + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "1.0.1", + "util-deprecate": "1.0.2" } }, "request": { @@ -3328,28 +3326,28 @@ "dev": true, "optional": true, "requires": { - "aws-sign2": "~0.6.0", - "aws4": "^1.2.1", - "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.0", - "forever-agent": "~0.6.1", - "form-data": "~2.1.1", - "har-validator": "~4.2.1", - "hawk": "~3.1.3", - "http-signature": "~1.1.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.7", - "oauth-sign": "~0.8.1", - "performance-now": "^0.2.0", - "qs": "~6.4.0", - "safe-buffer": "^5.0.1", - "stringstream": "~0.0.4", - "tough-cookie": "~2.3.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.0.0" + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.15", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.0.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.0.1" } }, "rimraf": { @@ -3357,7 +3355,7 @@ "bundled": true, "dev": true, "requires": { - "glob": "^7.0.5" + "glob": "7.1.2" } }, "safe-buffer": { @@ -3398,15 +3396,15 @@ "dev": true, "optional": true, "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jodid25519": "^1.0.0", - "jsbn": "~0.1.0", - "tweetnacl": "~0.14.0" + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jodid25519": "1.0.2", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" }, "dependencies": { "assert-plus": { @@ -3460,9 +3458,9 @@ "bundled": true, "dev": true, "requires": { - "block-stream": "*", - "fstream": "^1.0.2", - "inherits": "2" + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" } }, "tar-pack": { @@ -3471,14 +3469,14 @@ "dev": true, "optional": true, "requires": { - "debug": "^2.2.0", - "fstream": "^1.0.10", - "fstream-ignore": "^1.0.5", - "once": "^1.3.3", - "readable-stream": "^2.1.4", - "rimraf": "^2.5.1", - "tar": "^2.2.1", - "uid-number": "^0.0.6" + "debug": "2.6.8", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.2.9", + "rimraf": "2.6.1", + "tar": "2.2.1", + "uid-number": "0.0.6" } }, "tough-cookie": { @@ -3537,7 +3535,7 @@ "dev": true, "optional": true, "requires": { - "string-width": "^1.0.2" + "string-width": "1.0.2" } }, "wrappy": { @@ -3559,8 +3557,7 @@ "integrity": "sha1-luQg/efvARrEnCWKYhMU/ldlNvk=" }, "gemini-scrollbar": { - "version": "github:matrix-org/gemini-scrollbar#b302279810d05319ac5ff1bd34910bff32325c7b", - "from": "gemini-scrollbar@github:matrix-org/gemini-scrollbar#b302279810d05319ac5ff1bd34910bff32325c7b" + "version": "github:matrix-org/gemini-scrollbar#b302279810d05319ac5ff1bd34910bff32325c7b" }, "generate-function": { "version": "2.0.0", @@ -3574,7 +3571,7 @@ "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", "dev": true, "requires": { - "is-property": "^1.0.0" + "is-property": "1.0.2" } }, "getpass": { @@ -3582,7 +3579,7 @@ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "gfm.css": { @@ -3595,11 +3592,11 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "glob-base": { @@ -3608,8 +3605,8 @@ "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", "dev": true, "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" + "glob-parent": "2.0.0", + "is-glob": "2.0.1" } }, "glob-parent": { @@ -3618,7 +3615,7 @@ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { - "is-glob": "^2.0.0" + "is-glob": "2.0.1" } }, "globals": { @@ -3633,12 +3630,12 @@ "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", "dev": true, "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" }, "dependencies": { "glob": { @@ -3647,12 +3644,12 @@ "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } } } @@ -3673,8 +3670,8 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "requires": { - "ajv": "^5.1.0", - "har-schema": "^2.0.0" + "ajv": "5.5.2", + "har-schema": "2.0.0" } }, "has": { @@ -3683,7 +3680,7 @@ "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", "dev": true, "requires": { - "function-bind": "^1.0.2" + "function-bind": "1.1.1" } }, "has-ansi": { @@ -3692,7 +3689,7 @@ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "has-binary": { @@ -3746,8 +3743,8 @@ "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", "dev": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" } }, "htmlparser2": { @@ -3755,12 +3752,12 @@ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", "requires": { - "domelementtype": "^1.3.0", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^2.0.2" + "domelementtype": "1.3.0", + "domhandler": "2.4.1", + "domutils": "1.6.2", + "entities": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.3" } }, "http-errors": { @@ -3772,7 +3769,7 @@ "depd": "1.1.1", "inherits": "2.0.3", "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" + "statuses": "1.3.1" } }, "http-proxy": { @@ -3781,8 +3778,8 @@ "integrity": "sha1-Bt/ykpUr9k2+hHH6nfcwZtTzd0I=", "dev": true, "requires": { - "eventemitter3": "1.x.x", - "requires-port": "1.x.x" + "eventemitter3": "1.2.0", + "requires-port": "1.0.0" } }, "http-signature": { @@ -3790,9 +3787,9 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.2" } }, "https-browserify": { @@ -3839,8 +3836,8 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -3854,19 +3851,19 @@ "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", "dev": true, "requires": { - "ansi-escapes": "^1.1.0", - "ansi-regex": "^2.0.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", - "cli-width": "^2.0.0", - "figures": "^1.3.5", - "lodash": "^4.3.0", - "readline2": "^1.0.1", - "run-async": "^0.1.0", - "rx-lite": "^3.1.2", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.0", - "through": "^2.3.6" + "ansi-escapes": "1.4.0", + "ansi-regex": "2.1.1", + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "cli-width": "2.2.0", + "figures": "1.7.0", + "lodash": "4.17.4", + "readline2": "1.0.1", + "run-async": "0.1.0", + "rx-lite": "3.1.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "through": "2.3.8" } }, "interpret": { @@ -3880,7 +3877,7 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", "requires": { - "loose-envify": "^1.0.0" + "loose-envify": "1.3.1" } }, "is-arrow-function": { @@ -3889,7 +3886,7 @@ "integrity": "sha1-Kb4sLY2UUIUri7r7Y1unuNjofsI=", "dev": true, "requires": { - "is-callable": "^1.0.4" + "is-callable": "1.1.3" } }, "is-binary-path": { @@ -3898,7 +3895,7 @@ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "binary-extensions": "^1.0.0" + "binary-extensions": "1.10.0" } }, "is-boolean-object": { @@ -3937,17 +3934,17 @@ "integrity": "sha1-XoXxlX4FKIMkf+s4aWWju6Ffuz0=", "dev": true, "requires": { - "has": "^1.0.1", - "is-arrow-function": "^2.0.3", - "is-boolean-object": "^1.0.0", - "is-callable": "^1.1.3", - "is-date-object": "^1.0.1", - "is-generator-function": "^1.0.6", - "is-number-object": "^1.0.3", - "is-regex": "^1.0.3", - "is-string": "^1.0.4", - "is-symbol": "^1.0.1", - "object.entries": "^1.0.4" + "has": "1.0.1", + "is-arrow-function": "2.0.3", + "is-boolean-object": "1.0.0", + "is-callable": "1.1.3", + "is-date-object": "1.0.1", + "is-generator-function": "1.0.6", + "is-number-object": "1.0.3", + "is-regex": "1.0.4", + "is-string": "1.0.4", + "is-symbol": "1.0.1", + "object.entries": "1.0.4" } }, "is-equal-shallow": { @@ -3956,7 +3953,7 @@ "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", "dev": true, "requires": { - "is-primitive": "^2.0.0" + "is-primitive": "2.0.0" } }, "is-extendable": { @@ -3977,7 +3974,7 @@ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "is-fullwidth-code-point": { @@ -3986,7 +3983,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "is-generator-function": { @@ -4001,7 +3998,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "is-my-json-valid": { @@ -4010,10 +4007,10 @@ "integrity": "sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ==", "dev": true, "requires": { - "generate-function": "^2.0.0", - "generate-object-property": "^1.1.0", - "jsonpointer": "^4.0.0", - "xtend": "^4.0.0" + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" } }, "is-number": { @@ -4022,7 +4019,7 @@ "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "is-number-object": { @@ -4043,7 +4040,7 @@ "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", "dev": true, "requires": { - "is-path-inside": "^1.0.0" + "is-path-inside": "1.0.0" } }, "is-path-inside": { @@ -4052,7 +4049,7 @@ "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", "dev": true, "requires": { - "path-is-inside": "^1.0.1" + "path-is-inside": "1.0.2" } }, "is-posix-bracket": { @@ -4079,7 +4076,7 @@ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { - "has": "^1.0.1" + "has": "1.0.1" } }, "is-resolvable": { @@ -4088,7 +4085,7 @@ "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", "dev": true, "requires": { - "tryit": "^1.0.1" + "tryit": "1.0.3" } }, "is-stream": { @@ -4144,8 +4141,8 @@ "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", "requires": { - "node-fetch": "^1.0.1", - "whatwg-fetch": ">=0.10.0" + "node-fetch": "1.7.3", + "whatwg-fetch": "1.1.1" } }, "isstream": { @@ -4169,8 +4166,8 @@ "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "1.0.9", + "esprima": "4.0.0" } }, "jsbn": { @@ -4207,7 +4204,7 @@ "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", "dev": true, "requires": { - "jsonify": "~0.0.0" + "jsonify": "0.0.0" } }, "json-stringify-safe": { @@ -4256,7 +4253,7 @@ "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", "dev": true, "requires": { - "array-includes": "^3.0.3" + "array-includes": "3.0.3" } }, "just-extend": { @@ -4271,33 +4268,33 @@ "integrity": "sha512-k5pBjHDhmkdaUccnC7gE3mBzZjcxyxYsYVaqiL2G5AqlfLyBO5nw2VdNK+O16cveEPd/gIOWULH7gkiYYwVNHg==", "dev": true, "requires": { - "bluebird": "^3.3.0", - "body-parser": "^1.16.1", - "chokidar": "^1.4.1", - "colors": "^1.1.0", - "combine-lists": "^1.0.0", - "connect": "^3.6.0", - "core-js": "^2.2.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.0", - "expand-braces": "^0.1.1", - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "http-proxy": "^1.13.0", - "isbinaryfile": "^3.0.0", - "lodash": "^3.8.0", - "log4js": "^0.6.31", - "mime": "^1.3.4", - "minimatch": "^3.0.2", - "optimist": "^0.6.1", - "qjobs": "^1.1.4", - "range-parser": "^1.2.0", - "rimraf": "^2.6.0", - "safe-buffer": "^5.0.1", + "bluebird": "3.5.1", + "body-parser": "1.18.2", + "chokidar": "1.7.0", + "colors": "1.1.2", + "combine-lists": "1.0.1", + "connect": "3.6.5", + "core-js": "2.5.1", + "di": "0.0.1", + "dom-serialize": "2.2.1", + "expand-braces": "0.1.2", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "http-proxy": "1.16.2", + "isbinaryfile": "3.0.2", + "lodash": "3.10.1", + "log4js": "0.6.38", + "mime": "1.4.1", + "minimatch": "3.0.4", + "optimist": "0.6.1", + "qjobs": "1.1.5", + "range-parser": "1.2.0", + "rimraf": "2.6.2", + "safe-buffer": "5.1.1", "socket.io": "1.7.3", - "source-map": "^0.5.3", + "source-map": "0.5.7", "tmp": "0.0.31", - "useragent": "^2.1.12" + "useragent": "2.2.1" }, "dependencies": { "glob": { @@ -4306,12 +4303,12 @@ "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "lodash": { @@ -4328,8 +4325,8 @@ "integrity": "sha1-TG1wDRY6nTTGGO/YeRi+SeekqMk=", "dev": true, "requires": { - "fs-access": "^1.0.0", - "which": "^1.2.1" + "fs-access": "1.0.1", + "which": "1.3.0" } }, "karma-cli": { @@ -4338,7 +4335,7 @@ "integrity": "sha1-ys6oQ3Hs4Zh2JlyPoQLru5/uSow=", "dev": true, "requires": { - "resolve": "^1.1.6" + "resolve": "1.4.0" } }, "karma-junit-reporter": { @@ -4347,7 +4344,7 @@ "integrity": "sha1-SSojZyj+TJKqz0GfzQEQpDJ+nX8=", "dev": true, "requires": { - "path-is-absolute": "^1.0.0", + "path-is-absolute": "1.0.1", "xmlbuilder": "3.1.0" } }, @@ -4369,7 +4366,7 @@ "integrity": "sha1-kTIsd/jxPUb+0GKwQuEAnUxFBdg=", "dev": true, "requires": { - "graceful-fs": "^4.1.2" + "graceful-fs": "4.1.11" } }, "karma-spec-reporter": { @@ -4378,7 +4375,7 @@ "integrity": "sha1-SDDccUihVcfXoYbmMjOaDYD63sM=", "dev": true, "requires": { - "colors": "^1.1.2" + "colors": "1.1.2" } }, "karma-summary-reporter": { @@ -4387,7 +4384,7 @@ "integrity": "sha1-nHQKJLYL+RNes59acylsTM0Q2Zs=", "dev": true, "requires": { - "chalk": "^1.1.3" + "chalk": "1.1.3" } }, "karma-webpack": { @@ -4396,11 +4393,11 @@ "integrity": "sha1-OdX9Lt7qPMPvW0BZibN9Ww5qO04=", "dev": true, "requires": { - "async": "~0.9.0", - "loader-utils": "^0.2.5", - "lodash": "^3.8.0", - "source-map": "^0.1.41", - "webpack-dev-middleware": "^1.0.11" + "async": "0.9.2", + "loader-utils": "0.2.17", + "lodash": "3.10.1", + "source-map": "0.1.43", + "webpack-dev-middleware": "1.12.0" }, "dependencies": { "lodash": { @@ -4415,7 +4412,7 @@ "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", "dev": true, "requires": { - "amdefine": ">=0.0.4" + "amdefine": "1.0.1" } } } @@ -4426,7 +4423,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } }, "lazy-cache": { @@ -4441,18 +4438,50 @@ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "1.1.2", + "type-check": "0.3.2" } }, "linkifyjs": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-2.1.5.tgz", - "integrity": "sha512-8FqxPXQDLjI2nNHlM7eGewxE6DHvMbtiW0AiXzm0s4RkTwVZYRDTeVXkiRxLHTd4CuRBQY/JPtvtqJWdS7gHyA==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-2.1.6.tgz", + "integrity": "sha512-nA94bEM9rmt7Iu4OEIYSKpW+Dy6fhlBTjk2Bg9bFuxHQYcy+lWq2EleHb0rp/ev8oBO82vLHZctM5YlSR5DTzw==", "requires": { - "jquery": ">=1.9.0", - "react": ">=0.14.0", - "react-dom": ">=0.14.0" + "jquery": "3.3.1", + "react": "16.4.1", + "react-dom": "16.4.1" + }, + "dependencies": { + "jquery": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", + "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==", + "optional": true + }, + "react": { + "version": "16.4.1", + "resolved": "https://registry.npmjs.org/react/-/react-16.4.1.tgz", + "integrity": "sha512-3GEs0giKp6E0Oh/Y9ZC60CmYgUPnp7voH9fbjWsvXtYFb4EWtgQub0ADSq0sJR0BbHc4FThLLtzlcFaFXIorwg==", + "optional": true, + "requires": { + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "prop-types": "15.6.0" + } + }, + "react-dom": { + "version": "16.4.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.4.1.tgz", + "integrity": "sha512-1Gin+wghF/7gl4Cqcvr1DxFX2Osz7ugxSwl6gBqCMpdrxHjIFUS7GYxrFftZ9Ln44FHw0JxCFD9YtZsrbR5/4A==", + "optional": true, + "requires": { + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "prop-types": "15.6.0" + } + } } }, "loader-utils": { @@ -4461,10 +4490,10 @@ "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0", - "object-assign": "^4.0.1" + "big.js": "3.2.0", + "emojis-list": "2.1.0", + "json5": "0.5.1", + "object-assign": "4.1.1" } }, "lodash": { @@ -4501,8 +4530,8 @@ "integrity": "sha1-LElBFmldb7JUgJQ9P8hy5mKlIv0=", "dev": true, "requires": { - "readable-stream": "~1.0.2", - "semver": "~4.3.3" + "readable-stream": "1.0.34", + "semver": "4.3.6" }, "dependencies": { "isarray": { @@ -4517,10 +4546,10 @@ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "string_decoder": { @@ -4532,9 +4561,9 @@ } }, "lolex": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.2.tgz", - "integrity": "sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.0.tgz", + "integrity": "sha512-uJkH2e0BVfU5KOJUevbTOtpDduooSarH5PopO+LfM/vZf8Z9sJzODqKev804JYM2i++ktJfUmC1le4LwFQ1VMg==" }, "longest": { "version": "1.0.1", @@ -4547,7 +4576,7 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", "requires": { - "js-tokens": "^3.0.0" + "js-tokens": "3.0.2" } }, "lru-cache": { @@ -4557,16 +4586,16 @@ "dev": true }, "matrix-js-sdk": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-0.10.2.tgz", - "integrity": "sha512-7o9a+4wWmxoW4cfdGVLGjZgTFLpWf/I0UqyicIzdV73qotYIO/q6k1bLf1+G0hgwZ/umwke4CB7GemxvvvxMcA==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-0.10.4.tgz", + "integrity": "sha512-jmO08eml0mr+us2Xs9F9UD2U6gX/MVD20QEqrEt3p+cuZ043OEWCg6Ko8mR65P/JteqjXMz+TXOMmfcxLwCLFA==", "requires": { - "another-json": "^0.2.0", - "babel-runtime": "^6.26.0", - "bluebird": "^3.5.0", - "browser-request": "^0.3.3", - "content-type": "^1.0.2", - "request": "^2.53.0" + "another-json": "0.2.0", + "babel-runtime": "6.26.0", + "bluebird": "3.5.1", + "browser-request": "0.3.3", + "content-type": "1.0.4", + "request": "2.87.0" } }, "matrix-mock-request": { @@ -4575,8 +4604,8 @@ "integrity": "sha1-2aWrqNPYJG6I/3YyWYuZwUE/QjI=", "dev": true, "requires": { - "bluebird": "^3.5.0", - "expect": "^1.20.2" + "bluebird": "3.5.1", + "expect": "1.20.2" } }, "matrix-react-test-utils": { @@ -4585,8 +4614,8 @@ "integrity": "sha1-tUiETQ6+M46hucjxZHTDDRfDvfQ=", "dev": true, "requires": { - "react": "^15.6.1", - "react-dom": "^15.6.1" + "react": "15.6.2", + "react-dom": "15.6.2" } }, "mdurl": { @@ -4611,8 +4640,8 @@ "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", "dev": true, "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" + "errno": "0.1.4", + "readable-stream": "2.3.3" } }, "micromatch": { @@ -4621,19 +4650,19 @@ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" } }, "mime": { @@ -4652,7 +4681,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", "requires": { - "mime-db": "~1.30.0" + "mime-db": "1.30.0" } }, "minimatch": { @@ -4660,7 +4689,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.8" } }, "minimist": { @@ -4725,12 +4754,12 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "growl": { @@ -4751,7 +4780,7 @@ "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", "dev": true, "requires": { - "has-flag": "^2.0.0" + "has-flag": "2.0.0" } } } @@ -4793,11 +4822,11 @@ "integrity": "sha512-v1J/FLUB9PfGqZLGDBhQqODkbLotP0WtLo9R4EJY2PPu5f5Xg4o0rA8FDlmrjFSv9vBBKcfnOSpfYYuu5RTHqg==", "dev": true, "requires": { - "@sinonjs/formatio": "^2.0.0", - "just-extend": "^1.1.27", - "lolex": "^2.3.2", - "path-to-regexp": "^1.7.0", - "text-encoding": "^0.6.4" + "@sinonjs/formatio": "2.0.0", + "just-extend": "1.1.27", + "lolex": "2.6.0", + "path-to-regexp": "1.7.0", + "text-encoding": "0.6.4" }, "dependencies": { "lolex": { @@ -4813,8 +4842,8 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" + "encoding": "0.1.12", + "is-stream": "1.1.0" } }, "node-libs-browser": { @@ -4823,28 +4852,28 @@ "integrity": "sha1-PicsCBnjCJNeJmdECNevDhSRuDs=", "dev": true, "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.1.4", - "buffer": "^4.9.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", + "assert": "1.4.1", + "browserify-zlib": "0.1.4", + "buffer": "4.9.1", + "console-browserify": "1.1.0", + "constants-browserify": "1.0.0", "crypto-browserify": "3.3.0", - "domain-browser": "^1.1.1", - "events": "^1.0.0", + "domain-browser": "1.1.7", + "events": "1.1.1", "https-browserify": "0.0.1", - "os-browserify": "^0.2.0", + "os-browserify": "0.2.1", "path-browserify": "0.0.0", - "process": "^0.11.0", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.0.5", - "stream-browserify": "^2.0.1", - "stream-http": "^2.3.1", - "string_decoder": "^0.10.25", - "timers-browserify": "^2.0.2", + "process": "0.11.10", + "punycode": "1.4.1", + "querystring-es3": "0.2.1", + "readable-stream": "2.3.3", + "stream-browserify": "2.0.1", + "stream-http": "2.7.2", + "string_decoder": "0.10.31", + "timers-browserify": "2.0.4", "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.10.3", + "url": "0.11.0", + "util": "0.10.3", "vm-browserify": "0.0.4" }, "dependencies": { @@ -4862,7 +4891,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "remove-trailing-separator": "1.1.0" } }, "null-check": { @@ -4911,10 +4940,10 @@ "integrity": "sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.6.1", - "function-bind": "^1.1.0", - "has": "^1.0.1" + "define-properties": "1.1.2", + "es-abstract": "1.9.0", + "function-bind": "1.1.1", + "has": "1.0.1" } }, "object.omit": { @@ -4923,8 +4952,8 @@ "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "dev": true, "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" + "for-own": "0.1.5", + "is-extendable": "0.1.1" } }, "on-finished": { @@ -4941,7 +4970,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "onetime": { @@ -4955,8 +4984,8 @@ "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" + "minimist": "0.0.10", + "wordwrap": "0.0.3" }, "dependencies": { "minimist": { @@ -4972,12 +5001,12 @@ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "dev": true, "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" }, "dependencies": { "wordwrap": { @@ -5018,9 +5047,9 @@ "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=", "dev": true, "requires": { - "graceful-fs": "^4.1.4", - "mkdirp": "^0.5.1", - "object-assign": "^4.1.0" + "graceful-fs": "4.1.11", + "mkdirp": "0.5.1", + "object-assign": "4.1.1" } }, "pako": { @@ -5040,10 +5069,10 @@ "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", "dev": true, "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" } }, "parsejson": { @@ -5052,7 +5081,7 @@ "integrity": "sha1-q343WfIJ7OmUN5c/fQ8fZK4OZKs=", "dev": true, "requires": { - "better-assert": "~1.0.0" + "better-assert": "1.0.2" } }, "parseqs": { @@ -5061,7 +5090,7 @@ "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", "dev": true, "requires": { - "better-assert": "~1.0.0" + "better-assert": "1.0.2" } }, "parseuri": { @@ -5070,7 +5099,7 @@ "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", "dev": true, "requires": { - "better-assert": "~1.0.0" + "better-assert": "1.0.2" } }, "parseurl": { @@ -5091,7 +5120,7 @@ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { - "pinkie-promise": "^2.0.0" + "pinkie-promise": "2.0.1" } }, "path-is-absolute": { @@ -5157,7 +5186,7 @@ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "pinkie": "^2.0.0" + "pinkie": "2.0.4" } }, "pkg-dir": { @@ -5166,7 +5195,7 @@ "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", "dev": true, "requires": { - "find-up": "^1.0.0" + "find-up": "1.1.2" } }, "pluralize": { @@ -5220,7 +5249,7 @@ "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "requires": { - "asap": "~2.0.3" + "asap": "2.0.6" } }, "prop-types": { @@ -5228,9 +5257,9 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz", "integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=", "requires": { - "fbjs": "^0.8.16", - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1" } }, "prr": { @@ -5250,6 +5279,19 @@ "integrity": "sha1-ZZ3p8s+NzCehSBJ28gU3cnI4LnM=", "dev": true }, + "qr.js": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz", + "integrity": "sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8=" + }, + "qrcode-react": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/qrcode-react/-/qrcode-react-0.1.16.tgz", + "integrity": "sha512-FK+QCfFqCQMSxUE1byzglERJQkwKqXYvYMCS+/Ad2zACJOfoHkHHtRqsQQPji7lfb1y1qCXLvL+3eP1hAfg8Ng==", + "requires": { + "qr.js": "0.0.0" + } + }, "qs": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", @@ -5271,7 +5313,7 @@ "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz", "integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==", "requires": { - "performance-now": "^2.1.0" + "performance-now": "2.1.0" } }, "raf-schd": { @@ -5285,8 +5327,8 @@ "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=", "dev": true, "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "is-number": "3.0.0", + "kind-of": "4.0.0" }, "dependencies": { "is-number": { @@ -5295,7 +5337,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -5304,7 +5346,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -5315,7 +5357,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -5343,11 +5385,11 @@ "resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz", "integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=", "requires": { - "create-react-class": "^15.6.0", - "fbjs": "^0.8.9", - "loose-envify": "^1.1.0", - "object-assign": "^4.1.0", - "prop-types": "^15.5.10" + "create-react-class": "15.6.2", + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "prop-types": "15.6.0" } }, "react-addons-css-transition-group": { @@ -5366,16 +5408,16 @@ "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-4.0.1.tgz", "integrity": "sha512-d73RMu4QOFCyjUELLWFyY/EuclnfqulI9pECx+2gIuJvV0ycf1uR88o+1x0RSB9ILD70inHMzCBKNkWVbbt+vA==", "requires": { - "babel-runtime": "^6.26.0", - "invariant": "^2.2.2", - "memoize-one": "^3.0.1", - "prop-types": "^15.6.0", - "raf-schd": "^2.1.0", - "react-motion": "^0.5.2", - "react-redux": "^5.0.6", - "redux": "^3.7.2", - "redux-thunk": "^2.2.0", - "reselect": "^3.0.1" + "babel-runtime": "6.26.0", + "invariant": "2.2.2", + "memoize-one": "3.1.1", + "prop-types": "15.6.0", + "raf-schd": "2.1.1", + "react-motion": "0.5.2", + "react-redux": "5.0.7", + "redux": "3.7.2", + "redux-thunk": "2.2.0", + "reselect": "3.0.1" } }, "react-dom": { @@ -5383,15 +5425,14 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz", "integrity": "sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=", "requires": { - "fbjs": "^0.8.9", - "loose-envify": "^1.1.0", - "object-assign": "^4.1.0", - "prop-types": "^15.5.10" + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "prop-types": "15.6.0" } }, "react-gemini-scrollbar": { "version": "github:matrix-org/react-gemini-scrollbar#5e97aef7e034efc8db1431f4b0efe3b26e249ae9", - "from": "react-gemini-scrollbar@github:matrix-org/react-gemini-scrollbar#5e97aef7e034efc8db1431f4b0efe3b26e249ae9", "requires": { "gemini-scrollbar": "github:matrix-org/gemini-scrollbar#b302279810d05319ac5ff1bd34910bff32325c7b" } @@ -5401,9 +5442,9 @@ "resolved": "https://registry.npmjs.org/react-motion/-/react-motion-0.5.2.tgz", "integrity": "sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==", "requires": { - "performance-now": "^0.2.0", - "prop-types": "^15.5.8", - "raf": "^3.1.0" + "performance-now": "0.2.0", + "prop-types": "15.6.0", + "raf": "3.4.0" }, "dependencies": { "performance-now": { @@ -5418,12 +5459,12 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz", "integrity": "sha512-5VI8EV5hdgNgyjfmWzBbdrqUkrVRKlyTKk1sGH3jzM2M2Mhj/seQgPXaz6gVAj2lz/nz688AdTqMO18Lr24Zhg==", "requires": { - "hoist-non-react-statics": "^2.5.0", - "invariant": "^2.0.0", - "lodash": "^4.17.5", - "lodash-es": "^4.17.5", - "loose-envify": "^1.1.0", - "prop-types": "^15.6.0" + "hoist-non-react-statics": "2.5.0", + "invariant": "2.2.2", + "lodash": "4.17.10", + "lodash-es": "4.17.10", + "loose-envify": "1.3.1", + "prop-types": "15.6.0" }, "dependencies": { "lodash": { @@ -5438,13 +5479,13 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" } }, "readdirp": { @@ -5453,10 +5494,10 @@ "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "minimatch": "^3.0.2", - "readable-stream": "^2.0.2", - "set-immediate-shim": "^1.0.1" + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "readable-stream": "2.3.3", + "set-immediate-shim": "1.0.1" } }, "readline2": { @@ -5465,8 +5506,8 @@ "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", "mute-stream": "0.0.5" } }, @@ -5476,7 +5517,7 @@ "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "dev": true, "requires": { - "resolve": "^1.1.6" + "resolve": "1.4.0" } }, "redux": { @@ -5484,10 +5525,10 @@ "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", "requires": { - "lodash": "^4.2.1", - "lodash-es": "^4.2.1", - "loose-envify": "^1.1.0", - "symbol-observable": "^1.0.3" + "lodash": "4.17.4", + "lodash-es": "4.17.10", + "loose-envify": "1.3.1", + "symbol-observable": "1.2.0" } }, "redux-thunk": { @@ -5512,9 +5553,9 @@ "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", "dev": true, "requires": { - "babel-runtime": "^6.18.0", - "babel-types": "^6.19.0", - "private": "^0.1.6" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "private": "0.1.8" } }, "regex-cache": { @@ -5523,7 +5564,7 @@ "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", "dev": true, "requires": { - "is-equal-shallow": "^0.1.3" + "is-equal-shallow": "0.1.3" } }, "regexp-quote": { @@ -5537,9 +5578,9 @@ "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", "dev": true, "requires": { - "regenerate": "^1.2.1", - "regjsgen": "^0.2.0", - "regjsparser": "^0.1.4" + "regenerate": "1.3.3", + "regjsgen": "0.2.0", + "regjsparser": "0.1.5" } }, "regjsgen": { @@ -5554,7 +5595,7 @@ "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "dev": true, "requires": { - "jsesc": "~0.5.0" + "jsesc": "0.5.0" }, "dependencies": { "jsesc": { @@ -5589,7 +5630,7 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "^1.0.0" + "is-finite": "1.0.2" } }, "request": { @@ -5597,26 +5638,26 @@ "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", - "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", - "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "tough-cookie": "~2.3.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" + "aws-sign2": "0.7.0", + "aws4": "1.7.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.3.0" } }, "require-json": { @@ -5631,8 +5672,8 @@ "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", "dev": true, "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" + "caller-path": "0.1.0", + "resolve-from": "1.0.1" } }, "requires-port": { @@ -5652,7 +5693,7 @@ "integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==", "dev": true, "requires": { - "path-parse": "^1.0.5" + "path-parse": "1.0.5" } }, "resolve-from": { @@ -5667,8 +5708,8 @@ "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", "dev": true, "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" + "exit-hook": "1.1.1", + "onetime": "1.1.0" } }, "right-align": { @@ -5677,7 +5718,7 @@ "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", "dev": true, "requires": { - "align-text": "^0.1.1" + "align-text": "0.1.4" } }, "rimraf": { @@ -5686,7 +5727,7 @@ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "requires": { - "glob": "^7.0.5" + "glob": "7.1.2" }, "dependencies": { "glob": { @@ -5695,12 +5736,12 @@ "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } } } @@ -5717,7 +5758,7 @@ "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", "dev": true, "requires": { - "once": "^1.3.0" + "once": "1.4.0" } }, "rx-lite": { @@ -5731,6 +5772,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "samsam": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", @@ -5742,9 +5788,9 @@ "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.14.1.tgz", "integrity": "sha1-cw/6Ikm98YMz7/5FsoYXPJxa0Lg=", "requires": { - "htmlparser2": "^3.9.0", + "htmlparser2": "3.9.2", "regexp-quote": "0.0.0", - "xtend": "^4.0.0" + "xtend": "4.0.1" } }, "semver": { @@ -5782,9 +5828,9 @@ "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", "dev": true, "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" + "glob": "7.1.2", + "interpret": "1.0.4", + "rechoir": "0.6.2" }, "dependencies": { "glob": { @@ -5793,12 +5839,12 @@ "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } } } @@ -5809,13 +5855,13 @@ "integrity": "sha512-GvNLrwpvLZ8jIMZBUhHGUZDq5wlUdceJWyHvZDmqBxnjazpxY1L0FNbGBX6VpcOEoQ8Q4XMWFzm2myJMvx+VjA==", "dev": true, "requires": { - "@sinonjs/formatio": "^2.0.0", - "diff": "^3.1.0", - "lodash.get": "^4.4.2", - "lolex": "^2.2.0", - "nise": "^1.2.0", - "supports-color": "^5.1.0", - "type-detect": "^4.0.5" + "@sinonjs/formatio": "2.0.0", + "diff": "3.5.0", + "lodash.get": "4.4.2", + "lolex": "2.6.0", + "nise": "1.3.3", + "supports-color": "5.4.0", + "type-detect": "4.0.8" }, "dependencies": { "has-flag": { @@ -5836,7 +5882,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -6013,9 +6059,9 @@ "integrity": "sha512-MYbFX9DYxmTQFfy2v8FC1XZwpwHKYxg3SK8Wb7VPBKuhDjz8gi9re2819MsG4p49HDyiOSUKlmZ+nQBArW5CGw==", "dev": true, "requires": { - "async": "^2.5.0", - "loader-utils": "~0.2.2", - "source-map": "~0.6.1" + "async": "2.6.0", + "loader-utils": "0.2.17", + "source-map": "0.6.1" }, "dependencies": { "async": { @@ -6024,7 +6070,7 @@ "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", "dev": true, "requires": { - "lodash": "^4.14.0" + "lodash": "4.17.4" } }, "source-map": { @@ -6041,7 +6087,7 @@ "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "dev": true, "requires": { - "source-map": "^0.5.6" + "source-map": "0.5.7" } }, "sprintf-js": { @@ -6050,18 +6096,19 @@ "integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw=" }, "sshpk": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", - "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "tweetnacl": "~0.14.0" + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.5" } }, "statuses": { @@ -6076,8 +6123,8 @@ "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", "dev": true, "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" + "inherits": "2.0.3", + "readable-stream": "2.3.3" } }, "stream-http": { @@ -6086,11 +6133,11 @@ "integrity": "sha1-QKBQ7I3DtTsz2ZCUFcAsC/Gr+60=", "dev": true, "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.2.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" + "builtin-status-codes": "3.0.0", + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "to-arraybuffer": "1.0.1", + "xtend": "4.0.1" } }, "string-width": { @@ -6099,9 +6146,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "string.prototype.repeat": { @@ -6114,7 +6161,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "strip-ansi": { @@ -6123,7 +6170,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-bom": { @@ -6160,12 +6207,12 @@ "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", "dev": true, "requires": { - "ajv": "^4.7.0", - "ajv-keywords": "^1.0.0", - "chalk": "^1.1.1", - "lodash": "^4.0.0", + "ajv": "4.11.8", + "ajv-keywords": "1.5.1", + "chalk": "1.1.3", + "lodash": "4.17.4", "slice-ansi": "0.0.4", - "string-width": "^2.0.0" + "string-width": "2.1.1" }, "dependencies": { "ajv": { @@ -6174,8 +6221,8 @@ "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", "dev": true, "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" + "co": "4.6.0", + "json-stable-stringify": "1.0.1" } }, "ansi-regex": { @@ -6196,8 +6243,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" } }, "strip-ansi": { @@ -6206,7 +6253,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -6252,7 +6299,7 @@ "integrity": "sha512-uZYhyU3EX8O7HQP+J9fTVYwsq90Vr68xPEFo7yrVImIxYvHgukBEgOB/SgGoorWVTzGM/3Z+wUNnboA4M8jWrg==", "dev": true, "requires": { - "setimmediate": "^1.0.4" + "setimmediate": "1.0.5" } }, "tmatch": { @@ -6267,7 +6314,7 @@ "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", "dev": true, "requires": { - "os-tmpdir": "~1.0.1" + "os-tmpdir": "1.0.2" } }, "to-array": { @@ -6293,7 +6340,7 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "requires": { - "punycode": "^1.4.1" + "punycode": "1.4.1" } }, "trim-right": { @@ -6319,7 +6366,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "^5.0.1" + "safe-buffer": "5.1.1" } }, "tweetnacl": { @@ -6334,7 +6381,7 @@ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "1.1.2" } }, "type-detect": { @@ -6350,7 +6397,7 @@ "dev": true, "requires": { "media-typer": "0.3.0", - "mime-types": "~2.1.15" + "mime-types": "2.1.17" } }, "typedarray": { @@ -6370,10 +6417,10 @@ "integrity": "sha1-RhLAx7qu4rp8SH3kkErhIgefLKg=", "dev": true, "requires": { - "async": "~0.2.6", - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" + "async": "0.2.10", + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" }, "dependencies": { "async": { @@ -6430,8 +6477,8 @@ "integrity": "sha1-z1k+9PLRdYdei7ZY6pLhik/QbY4=", "dev": true, "requires": { - "lru-cache": "2.2.x", - "tmp": "0.0.x" + "lru-cache": "2.2.4", + "tmp": "0.0.31" } }, "util": { @@ -6463,9 +6510,9 @@ "dev": true }, "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.0.tgz", + "integrity": "sha512-ijO9N2xY/YaOqQ5yz5c4sy2ZjWmA6AR6zASb/gdpeKZ8+948CxwfMW9RrKVk5may6ev8c0/Xguu32e2Llelpqw==" }, "v8flags": { "version": "2.1.1", @@ -6473,14 +6520,13 @@ "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", "dev": true, "requires": { - "user-home": "^1.1.1" + "user-home": "1.1.1" } }, "velocity-vector": { "version": "github:vector-im/velocity#059e3b2348f1110888d033974d3109fd5a3af00f", - "from": "velocity-vector@github:vector-im/velocity#059e3b2348f1110888d033974d3109fd5a3af00f", "requires": { - "jquery": ">= 1.4.3" + "jquery": "3.2.1" } }, "verror": { @@ -6488,9 +6534,9 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { - "assert-plus": "^1.0.0", + "assert-plus": "1.0.0", "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "extsprintf": "1.3.0" } }, "vm-browserify": { @@ -6514,7 +6560,7 @@ "integrity": "sha1-MbTbZnjyrgHDnqn7hyWpAx5Vins=", "dev": true, "requires": { - "foreachasync": "^3.0.0" + "foreachasync": "3.0.0" } }, "watchpack": { @@ -6523,9 +6569,9 @@ "integrity": "sha1-Yuqkq15bo1/fwBgnVibjwPXj+ws=", "dev": true, "requires": { - "async": "^0.9.0", - "chokidar": "^1.0.0", - "graceful-fs": "^4.1.2" + "async": "0.9.2", + "chokidar": "1.7.0", + "graceful-fs": "4.1.11" } }, "webpack": { @@ -6534,21 +6580,21 @@ "integrity": "sha1-T/MfU9sDM55VFkqdRo7gMklo/pg=", "dev": true, "requires": { - "acorn": "^3.0.0", - "async": "^1.3.0", - "clone": "^1.0.2", - "enhanced-resolve": "~0.9.0", - "interpret": "^0.6.4", - "loader-utils": "^0.2.11", - "memory-fs": "~0.3.0", - "mkdirp": "~0.5.0", - "node-libs-browser": "^0.7.0", - "optimist": "~0.6.0", - "supports-color": "^3.1.0", - "tapable": "~0.1.8", - "uglify-js": "~2.7.3", - "watchpack": "^0.2.1", - "webpack-core": "~0.6.9" + "acorn": "3.3.0", + "async": "1.5.2", + "clone": "1.0.2", + "enhanced-resolve": "0.9.1", + "interpret": "0.6.6", + "loader-utils": "0.2.17", + "memory-fs": "0.3.0", + "mkdirp": "0.5.1", + "node-libs-browser": "0.7.0", + "optimist": "0.6.1", + "supports-color": "3.2.3", + "tapable": "0.1.10", + "uglify-js": "2.7.5", + "watchpack": "0.2.9", + "webpack-core": "0.6.9" }, "dependencies": { "acorn": { @@ -6575,8 +6621,8 @@ "integrity": "sha1-e8xrYp46Q+hx1+Kaymrop/FcuyA=", "dev": true, "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" + "errno": "0.1.4", + "readable-stream": "2.3.3" } }, "supports-color": { @@ -6585,7 +6631,7 @@ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "1.0.0" } } } @@ -6596,8 +6642,8 @@ "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", "dev": true, "requires": { - "source-list-map": "~0.1.7", - "source-map": "~0.4.1" + "source-list-map": "0.1.8", + "source-map": "0.4.4" }, "dependencies": { "source-map": { @@ -6606,7 +6652,7 @@ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { - "amdefine": ">=0.0.4" + "amdefine": "1.0.1" } } } @@ -6617,11 +6663,11 @@ "integrity": "sha1-007++y7dp+HTtdvgcolRMhllFwk=", "dev": true, "requires": { - "memory-fs": "~0.4.1", - "mime": "^1.3.4", - "path-is-absolute": "^1.0.0", - "range-parser": "^1.0.3", - "time-stamp": "^2.0.0" + "memory-fs": "0.4.1", + "mime": "1.4.1", + "path-is-absolute": "1.0.1", + "range-parser": "1.2.0", + "time-stamp": "2.0.0" } }, "whatwg-fetch": { @@ -6635,7 +6681,7 @@ "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } }, "window-size": { @@ -6660,7 +6706,7 @@ "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", "dev": true, "requires": { - "mkdirp": "^0.5.1" + "mkdirp": "0.5.1" } }, "ws": { @@ -6669,8 +6715,8 @@ "integrity": "sha1-iiRPoFJAHgjJiGz0SoUYnh/UBn8=", "dev": true, "requires": { - "options": ">=0.0.5", - "ultron": "1.0.x" + "options": "0.0.6", + "ultron": "1.0.2" } }, "wtf-8": { @@ -6685,7 +6731,7 @@ "integrity": "sha1-LIaIjy1OrehQ+jjKf3Ij9yCVFuE=", "dev": true, "requires": { - "lodash": "^3.5.0" + "lodash": "3.10.1" }, "dependencies": { "lodash": { @@ -6713,9 +6759,9 @@ "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "dev": true, "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", "window-size": "0.1.0" } }, diff --git a/package.json b/package.json index 1fed55748c..47a8f2c47a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.12.7", + "version": "0.12.8", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -73,13 +73,14 @@ "glob": "^5.0.14", "highlight.js": "^9.0.0", "isomorphic-fetch": "^2.2.1", - "linkifyjs": "^2.1.3", + "linkifyjs": "^2.1.6", "lodash": "^4.13.1", "lolex": "2.3.2", - "matrix-js-sdk": "0.10.4", + "matrix-js-sdk": "0.10.5", "optimist": "^0.6.1", "pako": "^1.0.5", "prop-types": "^15.5.8", + "qrcode-react": "^0.1.16", "querystring": "^0.2.0", "react": "^15.6.0", "react-addons-css-transition-group": "15.3.2", diff --git a/res/css/_components.scss b/res/css/_components.scss index 2734939ae3..173939e143 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -42,6 +42,7 @@ @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/directory/_NetworkDropdown.scss"; @import "./views/elements/_AccessibleButton.scss"; diff --git a/res/css/structures/_ContextualMenu.scss b/res/css/structures/_ContextualMenu.scss index a0191b92cf..7474c3d107 100644 --- a/res/css/structures/_ContextualMenu.scss +++ b/res/css/structures/_ContextualMenu.scss @@ -16,7 +16,7 @@ limitations under the License. .mx_ContextualMenu_wrapper { position: fixed; - z-index: 2000; + z-index: 5000; } .mx_ContextualMenu_background { @@ -26,7 +26,7 @@ limitations under the License. width: 100%; height: 100%; opacity: 1.0; - z-index: 2000; + z-index: 5000; } .mx_ContextualMenu { @@ -37,7 +37,7 @@ limitations under the License. position: absolute; padding: 6px; font-size: 14px; - z-index: 2001; + z-index: 5001; } .mx_ContextualMenu.mx_ContextualMenu_right { diff --git a/res/css/structures/_RoomStatusBar.scss b/res/css/structures/_RoomStatusBar.scss index ca7431eac2..2a9cc9f6c7 100644 --- a/res/css/structures/_RoomStatusBar.scss +++ b/res/css/structures/_RoomStatusBar.scss @@ -113,6 +113,8 @@ limitations under the License. } .mx_RoomStatusBar_connectionLostBar { + display: flex; + margin-top: 19px; min-height: 58px; } @@ -132,6 +134,7 @@ limitations under the License. color: $primary-fg-color; font-size: 13px; opacity: 0.5; + padding-bottom: 20px; } .mx_RoomStatusBar_resend_link { diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index a2863460ad..6798f75a14 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -91,6 +91,10 @@ limitations under the License. background-color: $accent-color; } +.mx_RoomSubList_label .mx_RoomSubList_badge:hover { + filter: brightness($focus-brightness); +} + /* .collapsed .mx_RoomSubList_badge { display: none; diff --git a/res/css/structures/_TagPanel.scss b/res/css/structures/_TagPanel.scss index ab1d4feac3..415aafd924 100644 --- a/res/css/structures/_TagPanel.scss +++ b/res/css/structures/_TagPanel.scss @@ -17,7 +17,6 @@ limitations under the License. .mx_TagPanel { flex: 0 0 60px; background-color: $tertiary-accent-color; - cursor: pointer; display: flex; flex-direction: column; @@ -25,7 +24,11 @@ limitations under the License. justify-content: space-between; } -.mx_TagPanel .mx_TagPanel_clearButton { +.mx_TagPanel_items_selected { + cursor: pointer; +} + +.mx_TagPanel .mx_TagPanel_clearButton_container { /* Constant height within flex mx_TagPanel */ height: 70px; width: 60px; diff --git a/res/css/views/dialogs/_CreateRoomDialog.scss b/res/css/views/dialogs/_CreateRoomDialog.scss index 888f147d21..05d5bfcebf 100644 --- a/res/css/views/dialogs/_CreateRoomDialog.scss +++ b/res/css/views/dialogs/_CreateRoomDialog.scss @@ -23,6 +23,10 @@ limitations under the License. padding-bottom: 12px; } +.mx_CreateRoomDialog_input_container { + padding-right: 20px; +} + .mx_CreateRoomDialog_input { font-size: 15px; border-radius: 3px; @@ -30,4 +34,5 @@ limitations under the License. padding: 9px; color: $primary-fg-color; background-color: $primary-bg-color; + width: 100%; } diff --git a/res/css/views/dialogs/_ShareDialog.scss b/res/css/views/dialogs/_ShareDialog.scss new file mode 100644 index 0000000000..116bef8dfd --- /dev/null +++ b/res/css/views/dialogs/_ShareDialog.scss @@ -0,0 +1,89 @@ +/* +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_ShareDialog { + // this is to center the content + padding-right: 58px; +} + +.mx_ShareDialog hr { + margin-top: 25px; + margin-bottom: 25px; + border-color: $light-fg-color; +} + +.mx_ShareDialog_content { + margin: 10px 0; +} + +.mx_ShareDialog_matrixto { + display: flex; + justify-content: space-between; + border-radius: 5px; + border: solid 1px $light-fg-color; + margin-bottom: 10px; + margin-top: 30px; + padding: 10px; +} + +.mx_ShareDialog_matrixto a { + text-decoration: none; +} + +.mx_ShareDialog_matrixto_link { + flex-shrink: 1; + overflow: hidden; + text-overflow: ellipsis; +} + +.mx_ShareDialog_matrixto_copy { + flex-shrink: 0; + cursor: pointer; + margin-left: 20px; + display: inherit; +} +.mx_ShareDialog_matrixto_copy > div { + background-image: url($copy-button-url); + margin-left: 5px; + width: 20px; + height: 20px; +} + +.mx_ShareDialog_split { + display: flex; + flex-wrap: wrap; +} + +.mx_ShareDialog_qrcode_container { + float: left; + background-color: #ffffff; + padding: 5px; // makes qr code more readable in dark theme + border-radius: 5px; + height: 256px; + width: 256px; + margin-right: 64px; +} + +.mx_ShareDialog_social_container { + display: inline-block; + width: 299px; +} + +.mx_ShareDialog_social_icon { + display: inline-grid; + margin-right: 10px; + margin-bottom: 10px; +} diff --git a/res/css/views/elements/_RichText.scss b/res/css/views/elements/_RichText.scss index 474a123455..e21fc184ba 100644 --- a/res/css/views/elements/_RichText.scss +++ b/res/css/views/elements/_RichText.scss @@ -4,6 +4,7 @@ .mx_UserPill, .mx_RoomPill, +.mx_GroupPill, .mx_AtRoomPill { border-radius: 16px; display: inline-block; @@ -13,7 +14,8 @@ } .mx_EventTile_body .mx_UserPill, -.mx_EventTile_body .mx_RoomPill { +.mx_EventTile_body .mx_RoomPill, +.mx_EventTile_body .mx_GroupPill { cursor: pointer; } @@ -35,14 +37,25 @@ /* More specific to override `.markdown-body a` color */ .mx_EventTile_content .markdown-body a.mx_RoomPill, -.mx_RoomPill { +.mx_EventTile_content .markdown-body a.mx_GroupPill, +.mx_RoomPill, +.mx_GroupPill { color: $accent-fg-color; background-color: $rte-room-pill-color; padding-right: 5px; } +/* More specific to override `.markdown-body a` color */ +.mx_EventTile_content .markdown-body a.mx_GroupPill, +.mx_GroupPill { + color: $accent-fg-color; + background-color: $rte-group-pill-color; + padding-right: 5px; +} + .mx_UserPill .mx_BaseAvatar, .mx_RoomPill .mx_BaseAvatar, +.mx_GroupPill .mx_BaseAvatar, .mx_AtRoomPill .mx_BaseAvatar { position: relative; left: -3px; diff --git a/res/css/views/messages/_MImageBody.scss b/res/css/views/messages/_MImageBody.scss index 1c809f0743..4c763c5991 100644 --- a/res/css/views/messages/_MImageBody.scss +++ b/res/css/views/messages/_MImageBody.scss @@ -20,5 +20,29 @@ limitations under the License. } .mx_MImageBody_thumbnail { - max-width: 100%; -} \ No newline at end of file + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; +} + +.mx_MImageBody_thumbnail_container { + // Prevent the padding-bottom (added inline in MImageBody.js) from + // affecting elements below the container. + overflow: hidden; + + // Make sure the _thumbnail is positioned relative to the _container + position: relative; +} + +.mx_MImageBody_thumbnail_spinner { + position: absolute; + left: 50%; + top: 50%; +} + +// Inner img and TintableSvg should be centered around 0, 0 +.mx_MImageBody_thumbnail_spinner > * { + transform: translate(-50%, -50%); +} diff --git a/res/css/views/messages/_MStickerBody.scss b/res/css/views/messages/_MStickerBody.scss index 3e6bbe5aa4..e4977bcc34 100644 --- a/res/css/views/messages/_MStickerBody.scss +++ b/res/css/views/messages/_MStickerBody.scss @@ -14,33 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_MStickerBody { - display: block; - margin-right: 34px; - min-height: 110px; - padding: 20px 0; +.mx_MStickerBody_wrapper { + padding: 20px 0px; } -.mx_MStickerBody_image_container { - display: inline-block; - position: relative; -} - -.mx_MStickerBody_image { - max-width: 100%; - opacity: 0; -} - -.mx_MStickerBody_image_visible { - opacity: 1; -} - -.mx_MStickerBody_placeholder { - position: absolute; - opacity: 1; -} - -.mx_MStickerBody_placeholder_invisible { - transition: 500ms; - opacity: 0; +.mx_MStickerBody_tooltip { + position: absolute; + top: 50%; } diff --git a/res/css/views/messages/_MTextBody.scss b/res/css/views/messages/_MTextBody.scss index fcf397fd2d..93a89ad1b7 100644 --- a/res/css/views/messages/_MTextBody.scss +++ b/res/css/views/messages/_MTextBody.scss @@ -17,8 +17,3 @@ limitations under the License. .mx_MTextBody { white-space: pre-wrap; } - -.mx_MTextBody pre{ - overflow-y: auto; - max-height: 30vh; -} diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index ce2bf9c8a4..d95ed2ded0 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -391,6 +391,7 @@ limitations under the License. .mx_EventTile_content .markdown-body pre { overflow-x: overlay; overflow-y: visible; + max-height: 30vh; } .mx_EventTile_content .markdown-body code { @@ -399,6 +400,12 @@ limitations under the License. color: #333; } +.mx_EventTile_pre_container { + // For correct positioning of _copyButton (See TextualBody) + position: relative; +} + +// Inserted adjacent to
 blocks, (See TextualBody)
 .mx_EventTile_copyButton {
     position: absolute;
     display: inline-block;
@@ -412,7 +419,6 @@ limitations under the License.
 }
 
 .mx_EventTile_body pre {
-    position: relative;
     border: 1px solid transparent;
 }
 
@@ -421,7 +427,7 @@ limitations under the License.
     border: 1px solid #e5e5e5; // deliberate constant as we're behind an invert filter
 }
 
-.mx_EventTile_body pre:hover .mx_EventTile_copyButton
+.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_copyButton
 {
     visibility: visible;
 }
diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss
index 0a708a8edc..f1de103d3b 100644
--- a/res/css/views/rooms/_MessageComposer.scss
+++ b/res/css/views/rooms/_MessageComposer.scss
@@ -70,6 +70,7 @@ limitations under the License.
     flex: 1;
     display: flex;
     flex-direction: column;
+    cursor: text;
 }
 
 .mx_MessageComposer_input {
diff --git a/res/img/e2e-encrypting.svg b/res/img/e2e-encrypting.svg
new file mode 100644
index 0000000000..469611cc8d
--- /dev/null
+++ b/res/img/e2e-encrypting.svg
@@ -0,0 +1,12 @@
+
+
+
+48BF5D32-306C-4B20-88EB-24B1F743CAC9
+Created with sketchtool.
+
+
+    
+        
+    
+
+
diff --git a/res/img/e2e-not_sent.svg b/res/img/e2e-not_sent.svg
new file mode 100644
index 0000000000..fca79ae547
--- /dev/null
+++ b/res/img/e2e-not_sent.svg
@@ -0,0 +1,12 @@
+
+
+
+48BF5D32-306C-4B20-88EB-24B1F743CAC9
+Created with sketchtool.
+
+
+    
+        
+    
+
+
diff --git a/res/img/icons-share.svg b/res/img/icons-share.svg
new file mode 100644
index 0000000000..b27616d5d5
--- /dev/null
+++ b/res/img/icons-share.svg
@@ -0,0 +1,6 @@
+
+
+
+	
+
+
diff --git a/res/img/matrix-m.svg b/res/img/matrix-m.svg
new file mode 100644
index 0000000000..ccb1df0fc5
--- /dev/null
+++ b/res/img/matrix-m.svg
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/res/img/social/email-1.png b/res/img/social/email-1.png
new file mode 100644
index 0000000000..193cb659da
Binary files /dev/null and b/res/img/social/email-1.png differ
diff --git a/res/img/social/facebook.png b/res/img/social/facebook.png
new file mode 100644
index 0000000000..457ef761a1
Binary files /dev/null and b/res/img/social/facebook.png differ
diff --git a/res/img/social/linkedin.png b/res/img/social/linkedin.png
new file mode 100644
index 0000000000..4c92adb56b
Binary files /dev/null and b/res/img/social/linkedin.png differ
diff --git a/res/img/social/reddit.png b/res/img/social/reddit.png
new file mode 100644
index 0000000000..1310168470
Binary files /dev/null and b/res/img/social/reddit.png differ
diff --git a/res/img/social/twitter-2.png b/res/img/social/twitter-2.png
new file mode 100644
index 0000000000..9f6e7c602b
Binary files /dev/null and b/res/img/social/twitter-2.png differ
diff --git a/res/themes/light/css/_base.scss b/res/themes/light/css/_base.scss
index 5d5f5d7c90..55c761e8d9 100644
--- a/res/themes/light/css/_base.scss
+++ b/res/themes/light/css/_base.scss
@@ -97,6 +97,7 @@ $voip-accept-color: #80f480;
 $rte-bg-color: #e9e9e9;
 $rte-code-bg-color: rgba(0, 0, 0, 0.04);
 $rte-room-pill-color: #aaa;
+$rte-group-pill-color: #aaa;
 
 // ********************
 
diff --git a/src/Analytics.js b/src/Analytics.js
index 8ffce7077f..d85d635b28 100644
--- a/src/Analytics.js
+++ b/src/Analytics.js
@@ -39,9 +39,17 @@ function getRedactedHash(hash) {
     return hash.replace(hashRegex, "#/$1");
 }
 
-// Return the current origin and hash separated with a `/`. This does not include query parameters.
+// Return the current origin, path and hash separated with a `/`. This does
+// not include query parameters.
 function getRedactedUrl() {
-    const { origin, pathname, hash } = window.location;
+    const { origin, hash } = window.location;
+    let { pathname } = window.location;
+
+    // Redact paths which could contain unexpected PII
+    if (origin.startsWith('file://')) {
+        pathname = "//";
+    }
+
     return origin + pathname + getRedactedHash(hash);
 }
 
@@ -191,9 +199,9 @@ class Analytics {
         this._paq.push(['trackPageView']);
     }
 
-    trackEvent(category, action, name) {
+    trackEvent(category, action, name, value) {
         if (this.disabled) return;
-        this._paq.push(['trackEvent', category, action, name]);
+        this._paq.push(['trackEvent', category, action, name, value]);
     }
 
     logout() {
diff --git a/src/CallHandler.js b/src/CallHandler.js
index fd56d7f1b1..7403483e36 100644
--- a/src/CallHandler.js
+++ b/src/CallHandler.js
@@ -1,6 +1,6 @@
 /*
 Copyright 2015, 2016 OpenMarket Ltd
-Copyright 2017 New Vector Ltd
+Copyright 2017, 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.
@@ -60,6 +60,8 @@ import { _t } from './languageHandler';
 import Matrix from 'matrix-js-sdk';
 import dis from './dispatcher';
 import { showUnknownDeviceDialogForCalls } from './cryptodevices';
+import SettingsStore from "./settings/SettingsStore";
+import WidgetUtils from './utils/WidgetUtils';
 
 global.mxCalls = {
     //room_id: MatrixCall
@@ -123,7 +125,7 @@ function _setCallListeners(call) {
                 description: _t(
                     "There are unknown devices in this room: "+
                     "if you proceed without verifying them, it will be "+
-                    "possible for someone to eavesdrop on your call."
+                    "possible for someone to eavesdrop on your call.",
                 ),
                 button: _t('Review Devices'),
                 onFinished: function(confirmed) {
@@ -246,66 +248,58 @@ function _onAction(payload) {
 
     switch (payload.action) {
         case 'place_call':
-            if (module.exports.getAnyActiveCall()) {
-                const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
-                Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, {
-                    title: _t('Existing Call'),
-                    description: _t('You are already in a call.'),
-                });
-                return; // don't allow >1 call to be placed.
-            }
+            {
+                if (module.exports.getAnyActiveCall()) {
+                    const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+                    Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, {
+                        title: _t('Existing Call'),
+                        description: _t('You are already in a call.'),
+                    });
+                    return; // don't allow >1 call to be placed.
+                }
 
-            // if the runtime env doesn't do VoIP, whine.
-            if (!MatrixClientPeg.get().supportsVoip()) {
-                const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
-                Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, {
-                    title: _t('VoIP is unsupported'),
-                    description: _t('You cannot place VoIP calls in this browser.'),
-                });
-                return;
-            }
+                // if the runtime env doesn't do VoIP, whine.
+                if (!MatrixClientPeg.get().supportsVoip()) {
+                    const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+                    Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, {
+                        title: _t('VoIP is unsupported'),
+                        description: _t('You cannot place VoIP calls in this browser.'),
+                    });
+                    return;
+                }
 
-            var room = MatrixClientPeg.get().getRoom(payload.room_id);
-            if (!room) {
-                console.error("Room %s does not exist.", payload.room_id);
-                return;
-            }
+                const room = MatrixClientPeg.get().getRoom(payload.room_id);
+                if (!room) {
+                    console.error("Room %s does not exist.", payload.room_id);
+                    return;
+                }
 
-            var members = room.getJoinedMembers();
-            if (members.length <= 1) {
-                const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
-                Modal.createTrackedDialog('Call Handler', 'Cannot place call with self', ErrorDialog, {
-                    description: _t('You cannot place a call with yourself.'),
-                });
-                return;
-            } else if (members.length === 2) {
-                console.log("Place %s call in %s", payload.type, payload.room_id);
-                const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), payload.room_id);
-                placeCall(call);
-            } else { // > 2
-                dis.dispatch({
-                    action: "place_conference_call",
-                    room_id: payload.room_id,
-                    type: payload.type,
-                    remote_element: payload.remote_element,
-                    local_element: payload.local_element,
-                });
+                const members = room.getJoinedMembers();
+                if (members.length <= 1) {
+                    const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+                    Modal.createTrackedDialog('Call Handler', 'Cannot place call with self', ErrorDialog, {
+                        description: _t('You cannot place a call with yourself.'),
+                    });
+                    return;
+                } else if (members.length === 2) {
+                    console.log("Place %s call in %s", payload.type, payload.room_id);
+                    const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), payload.room_id);
+                    placeCall(call);
+                } else { // > 2
+                    dis.dispatch({
+                        action: "place_conference_call",
+                        room_id: payload.room_id,
+                        type: payload.type,
+                        remote_element: payload.remote_element,
+                        local_element: payload.local_element,
+                    });
+                }
             }
             break;
         case 'place_conference_call':
             console.log("Place conference call in %s", payload.room_id);
-            if (!ConferenceHandler) {
-                const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
-                Modal.createTrackedDialog('Call Handler', 'Conference call unsupported client', ErrorDialog, {
-                    description: _t('Conference calls are not supported in this client'),
-                });
-            } else if (!MatrixClientPeg.get().supportsVoip()) {
-                const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
-                Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, {
-                    title: _t('VoIP is unsupported'),
-                    description: _t('You cannot place VoIP calls in this browser.'),
-                });
-            } else if (MatrixClientPeg.get().isRoomEncrypted(payload.room_id)) {
+
+            if (MatrixClientPeg.get().isRoomEncrypted(payload.room_id)) {
                 // Conference calls are implemented by sending the media to central
                 // server which combines the audio from all the participants together
                 // into a single stream. This is incompatible with end-to-end encryption
@@ -316,47 +310,75 @@ function _onAction(payload) {
                 Modal.createTrackedDialog('Call Handler', 'Conference calls unsupported e2e', ErrorDialog, {
                     description: _t('Conference calls are not supported in encrypted rooms'),
                 });
+                return;
+            }
+
+            if (SettingsStore.isFeatureEnabled('feature_jitsi')) {
+                _startCallApp(payload.room_id, payload.type);
             } else {
-                const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
-                Modal.createTrackedDialog('Call Handler', 'Conference calling in development', QuestionDialog, {
-                    title: _t('Warning!'),
-                    description: _t('Conference calling is in development and may not be reliable.'),
-                    onFinished: (confirm)=>{
-                        if (confirm) {
-                            ConferenceHandler.createNewMatrixCall(
-                                MatrixClientPeg.get(), payload.room_id,
-                            ).done(function(call) {
-                                placeCall(call);
-                            }, function(err) {
-                                const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
-                                console.error("Conference call failed: " + err);
-                                Modal.createTrackedDialog('Call Handler', 'Failed to set up conference call', ErrorDialog, {
-                                    title: _t('Failed to set up conference call'),
-                                    description: _t('Conference call failed.') + ' ' + ((err && err.message) ? err.message : ''),
+                if (!ConferenceHandler) {
+                    const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+                    Modal.createTrackedDialog('Call Handler', 'Conference call unsupported client', ErrorDialog, {
+                        description: _t('Conference calls are not supported in this client'),
+                    });
+                } else if (!MatrixClientPeg.get().supportsVoip()) {
+                    const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+                    Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, {
+                        title: _t('VoIP is unsupported'),
+                        description: _t('You cannot place VoIP calls in this browser.'),
+                    });
+                } else {
+                    const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
+                    Modal.createTrackedDialog('Call Handler', 'Conference calling in development', QuestionDialog, {
+                        title: _t('Warning!'),
+                        description: _t('Conference calling is in development and may not be reliable.'),
+                        onFinished: (confirm)=>{
+                            if (confirm) {
+                                ConferenceHandler.createNewMatrixCall(
+                                    MatrixClientPeg.get(), payload.room_id,
+                                ).done(function(call) {
+                                    placeCall(call);
+                                }, function(err) {
+                                    const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+                                    console.error("Conference call failed: " + err);
+                                    Modal.createTrackedDialog(
+                                        'Call Handler',
+                                        'Failed to set up conference call',
+                                        ErrorDialog,
+                                        {
+                                            title: _t('Failed to set up conference call'),
+                                            description: (
+                                                _t('Conference call failed.') +
+                                                ' ' + ((err && err.message) ? err.message : '')
+                                            ),
+                                        },
+                                    );
                                 });
-                            });
-                        }
-                    },
-                });
+                            }
+                        },
+                    });
+                }
             }
             break;
         case 'incoming_call':
-            if (module.exports.getAnyActiveCall()) {
-                // ignore multiple incoming calls. in future, we may want a line-1/line-2 setup.
-                // we avoid rejecting with "busy" in case the user wants to answer it on a different device.
-                // in future we could signal a "local busy" as a warning to the caller.
-                // see https://github.com/vector-im/vector-web/issues/1964
-                return;
-            }
+            {
+                if (module.exports.getAnyActiveCall()) {
+                    // ignore multiple incoming calls. in future, we may want a line-1/line-2 setup.
+                    // we avoid rejecting with "busy" in case the user wants to answer it on a different device.
+                    // in future we could signal a "local busy" as a warning to the caller.
+                    // see https://github.com/vector-im/vector-web/issues/1964
+                    return;
+                }
 
-            // if the runtime env doesn't do VoIP, stop here.
-            if (!MatrixClientPeg.get().supportsVoip()) {
-                return;
-            }
+                // if the runtime env doesn't do VoIP, stop here.
+                if (!MatrixClientPeg.get().supportsVoip()) {
+                    return;
+                }
 
-            var call = payload.call;
-            _setCallListeners(call);
-            _setCallState(call, call.roomId, "ringing");
+                const call = payload.call;
+                _setCallListeners(call);
+                _setCallState(call, call.roomId, "ringing");
+            }
             break;
         case 'hangup':
             if (!calls[payload.room_id]) {
@@ -378,6 +400,71 @@ function _onAction(payload) {
             break;
     }
 }
+
+function _startCallApp(roomId, type) {
+    dis.dispatch({
+        action: 'appsDrawer',
+        show: true,
+    });
+
+    const room = MatrixClientPeg.get().getRoom(roomId);
+    if (!room) {
+        console.error("Attempted to start conference call widget in unknown room: " + roomId);
+        return;
+    }
+
+    const currentJitsiWidgets = WidgetUtils.getRoomWidgets(room).filter((ev) => {
+        return ev.getContent().type === 'jitsi';
+    });
+    if (currentJitsiWidgets.length > 0) {
+        console.warn(
+            "Refusing to start conference call widget in " + roomId +
+            " a conference call widget is already present",
+        );
+        const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+
+        Modal.createTrackedDialog('Already have Jitsi Widget', '', ErrorDialog, {
+            title: _t('Call in Progress'),
+            description: _t('A call is already in progress!'),
+        });
+        return;
+    }
+
+    // This inherits its poor naming from the field of the same name that goes into
+    // the event. It's just a random string to make the Jitsi URLs unique.
+    const widgetSessionId = Math.random().toString(36).substring(2);
+    const confId = room.roomId.replace(/[^A-Za-z0-9]/g, '') + widgetSessionId;
+    // NB. we can't just encodeURICompoent all of these because the $ signs need to be there
+    // (but currently the only thing that needs encoding is the confId)
+    const queryString = [
+        'confId='+encodeURIComponent(confId),
+        'isAudioConf='+(type === 'voice' ? 'true' : 'false'),
+        'displayName=$matrix_display_name',
+        'avatarUrl=$matrix_avatar_url',
+        'email=$matrix_user_id',
+    ].join('&');
+    const widgetUrl = (
+        'https://scalar.vector.im/api/widgets' +
+        '/jitsi.html?' +
+        queryString
+    );
+
+    const widgetData = { widgetSessionId };
+
+    const widgetId = (
+        'jitsi_' +
+        MatrixClientPeg.get().credentials.userId +
+        '_' +
+        Date.now()
+    );
+
+    WidgetUtils.setRoomWidget(roomId, widgetId, 'jitsi', widgetUrl, 'Jitsi', widgetData).then(() => {
+        console.log('Jitsi widget added');
+    }).catch((e) => {
+        console.error(e);
+    });
+}
+
 // FIXME: Nasty way of making sure we only register
 // with the dispatcher once
 if (!global.mxCallHandler) {
diff --git a/src/CallMediaHandler.js b/src/CallMediaHandler.js
index cdc5c61921..2330f86b99 100644
--- a/src/CallMediaHandler.js
+++ b/src/CallMediaHandler.js
@@ -22,34 +22,44 @@ export default {
         // Only needed for Electron atm, though should work in modern browsers
         // once permission has been granted to the webapp
         return navigator.mediaDevices.enumerateDevices().then(function(devices) {
-            const audioIn = [];
-            const videoIn = [];
+            const audiooutput = [];
+            const audioinput = [];
+            const videoinput = [];
 
             if (devices.some((device) => !device.label)) return false;
 
             devices.forEach((device) => {
                 switch (device.kind) {
-                    case 'audioinput': audioIn.push(device); break;
-                    case 'videoinput': videoIn.push(device); break;
+                    case 'audiooutput': audiooutput.push(device); break;
+                    case 'audioinput': audioinput.push(device); break;
+                    case 'videoinput': videoinput.push(device); break;
                 }
             });
 
             // console.log("Loaded WebRTC Devices", mediaDevices);
             return {
-                audioinput: audioIn,
-                videoinput: videoIn,
+                audiooutput,
+                audioinput,
+                videoinput,
             };
         }, (error) => { console.log('Unable to refresh WebRTC Devices: ', error); });
     },
 
     loadDevices: function() {
+        const audioOutDeviceId = SettingsStore.getValue("webrtc_audiooutput");
         const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
         const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
 
+        Matrix.setMatrixCallAudioOutput(audioOutDeviceId);
         Matrix.setMatrixCallAudioInput(audioDeviceId);
         Matrix.setMatrixCallVideoInput(videoDeviceId);
     },
 
+    setAudioOutput: function(deviceId) {
+        SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
+        Matrix.setMatrixCallAudioOutput(deviceId);
+    },
+
     setAudioInput: function(deviceId) {
         SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId);
         Matrix.setMatrixCallAudioInput(deviceId);
diff --git a/src/ContentMessages.js b/src/ContentMessages.js
index 7fe625f8b9..fd21977108 100644
--- a/src/ContentMessages.js
+++ b/src/ContentMessages.js
@@ -243,6 +243,7 @@ function uploadFile(matrixClient, roomId, file, progressHandler) {
             const blob = new Blob([encryptResult.data]);
             return matrixClient.uploadContent(blob, {
                 progressHandler: progressHandler,
+                includeFilename: false,
             }).then(function(url) {
                 // If the attachment is encrypted then bundle the URL along
                 // with the information needed to decrypt the attachment and
diff --git a/src/DecryptionFailureTracker.js b/src/DecryptionFailureTracker.js
new file mode 100644
index 0000000000..e7809d2f6c
--- /dev/null
+++ b/src/DecryptionFailureTracker.js
@@ -0,0 +1,172 @@
+/*
+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.
+*/
+
+class DecryptionFailure {
+    constructor(failedEventId) {
+        this.failedEventId = failedEventId;
+        this.ts = Date.now();
+    }
+}
+
+export default class DecryptionFailureTracker {
+    // Array of items of type DecryptionFailure. Every `CHECK_INTERVAL_MS`, this list
+    // is checked for failures that happened > `GRACE_PERIOD_MS` ago. Those that did
+    // are added to `failuresToTrack`.
+    failures = [];
+
+    // Every TRACK_INTERVAL_MS (so as to spread the number of hits done on Analytics),
+    // one DecryptionFailure of this FIFO is removed and tracked.
+    failuresToTrack = [];
+
+    // Event IDs of failures that were tracked previously
+    trackedEventHashMap = {
+        // [eventId]: true
+    };
+
+    // Set to an interval ID when `start` is called
+    checkInterval = null;
+    trackInterval = null;
+
+    // Spread the load on `Analytics` by tracking at a low frequency, `TRACK_INTERVAL_MS`.
+    static TRACK_INTERVAL_MS = 60000;
+
+    // Call `checkFailures` every `CHECK_INTERVAL_MS`.
+    static CHECK_INTERVAL_MS = 5000;
+
+    // Give events a chance to be decrypted by waiting `GRACE_PERIOD_MS` before moving
+    // the failure to `failuresToTrack`.
+    static GRACE_PERIOD_MS = 60000;
+
+    constructor(fn) {
+        if (!fn || typeof fn !== 'function') {
+            throw new Error('DecryptionFailureTracker requires tracking function');
+        }
+
+        this.trackDecryptionFailure = fn;
+    }
+
+    // loadTrackedEventHashMap() {
+    //     this.trackedEventHashMap = JSON.parse(localStorage.getItem('mx-decryption-failure-event-id-hashes')) || {};
+    // }
+
+    // saveTrackedEventHashMap() {
+    //     localStorage.setItem('mx-decryption-failure-event-id-hashes', JSON.stringify(this.trackedEventHashMap));
+    // }
+
+    eventDecrypted(e) {
+        if (e.isDecryptionFailure()) {
+            this.addDecryptionFailureForEvent(e);
+        } else {
+            // Could be an event in the failures, remove it
+            this.removeDecryptionFailuresForEvent(e);
+        }
+    }
+
+    addDecryptionFailureForEvent(e) {
+        this.failures.push(new DecryptionFailure(e.getId()));
+    }
+
+    removeDecryptionFailuresForEvent(e) {
+        this.failures = this.failures.filter((f) => f.failedEventId !== e.getId());
+    }
+
+    /**
+     * Start checking for and tracking failures.
+     */
+    start() {
+        this.checkInterval = setInterval(
+            () => this.checkFailures(Date.now()),
+            DecryptionFailureTracker.CHECK_INTERVAL_MS,
+        );
+
+        this.trackInterval = setInterval(
+            () => this.trackFailure(),
+            DecryptionFailureTracker.TRACK_INTERVAL_MS,
+        );
+    }
+
+    /**
+     * Clear state and stop checking for and tracking failures.
+     */
+    stop() {
+        clearInterval(this.checkInterval);
+        clearInterval(this.trackInterval);
+
+        this.failures = [];
+        this.failuresToTrack = [];
+    }
+
+    /**
+     * Mark failures that occured before nowTs - GRACE_PERIOD_MS as failures that should be
+     * tracked. Only mark one failure per event ID.
+     * @param {number} nowTs the timestamp that represents the time now.
+     */
+    checkFailures(nowTs) {
+        const failuresGivenGrace = [];
+        const failuresNotReady = [];
+        while (this.failures.length > 0) {
+            const f = this.failures.shift();
+            if (nowTs > f.ts + DecryptionFailureTracker.GRACE_PERIOD_MS) {
+                failuresGivenGrace.push(f);
+            } else {
+                failuresNotReady.push(f);
+            }
+        }
+        this.failures = failuresNotReady;
+
+        // Only track one failure per event
+        const dedupedFailuresMap = failuresGivenGrace.reduce(
+            (map, failure) => {
+                if (!this.trackedEventHashMap[failure.failedEventId]) {
+                    return map.set(failure.failedEventId, failure);
+                } else {
+                    return map;
+                }
+            },
+            // Use a map to preseve key ordering
+            new Map(),
+        );
+
+        const trackedEventIds = [...dedupedFailuresMap.keys()];
+
+        this.trackedEventHashMap = trackedEventIds.reduce(
+            (result, eventId) => ({...result, [eventId]: true}),
+            this.trackedEventHashMap,
+        );
+
+        // Commented out for now for expediency, we need to consider unbound nature of storing
+        // this in localStorage
+        // this.saveTrackedEventHashMap();
+
+        const dedupedFailures = dedupedFailuresMap.values();
+
+        this.failuresToTrack = [...this.failuresToTrack, ...dedupedFailures];
+    }
+
+    /**
+     * If there are failures that should be tracked, call the given trackDecryptionFailure
+     * function with the number of failures that should be tracked.
+     */
+    trackFailure() {
+        if (this.failuresToTrack.length > 0) {
+            // Remove all failures, and expose the number of failures for now.
+            //
+            // TODO: Track a histogram of error types to cardinailty to allow for
+            // aggregation by error type.
+            this.trackDecryptionFailure(this.failuresToTrack.splice(0).length);
+        }
+    }
+}
diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js
index 7ca404be31..57be007209 100644
--- a/src/HtmlUtils.js
+++ b/src/HtmlUtils.js
@@ -216,10 +216,17 @@ const sanitizeHtmlParams = {
                     m = attribs.href.match(linkifyMatrix.MATRIXTO_URL_PATTERN);
                     if (m) {
                         const entity = m[1];
-                        if (entity[0] === '@') {
-                            attribs.href = '#/user/' + entity;
-                        } else if (entity[0] === '#' || entity[0] === '!') {
-                            attribs.href = '#/room/' + entity;
+                        switch (entity[0]) {
+                            case '@':
+                                attribs.href = '#/user/' + entity;
+                                break;
+                            case '+':
+                                attribs.href = '#/group/' + entity;
+                                break;
+                            case '#':
+                            case '!':
+                                attribs.href = '#/room/' + entity;
+                                break;
                         }
                         delete attribs.target;
                     }
diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js
index 9457e6ccfb..3325044b84 100644
--- a/src/ScalarMessaging.js
+++ b/src/ScalarMessaging.js
@@ -1,6 +1,7 @@
 /*
 Copyright 2016 OpenMarket Ltd
 Copyright 2017 Vector Creations 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.
@@ -231,11 +232,12 @@ Example:
 }
 */
 
-const SdkConfig = require('./SdkConfig');
-const MatrixClientPeg = require("./MatrixClientPeg");
-const MatrixEvent = require("matrix-js-sdk").MatrixEvent;
-const dis = require("./dispatcher");
-const Widgets = require('./utils/widgets');
+import SdkConfig from './SdkConfig';
+import MatrixClientPeg from './MatrixClientPeg';
+import { MatrixEvent } from 'matrix-js-sdk';
+import dis from './dispatcher';
+import WidgetUtils from './utils/WidgetUtils';
+import RoomViewStore from './stores/RoomViewStore';
 import { _t } from './languageHandler';
 
 function sendResponse(event, res) {
@@ -286,51 +288,6 @@ function inviteUser(event, roomId, userId) {
     });
 }
 
-/**
- * Returns a promise that resolves when a widget with the given
- * ID has been added as a user widget (ie. the accountData event
- * arrives) or rejects after a timeout
- *
- * @param {string} widgetId The ID of the widget to wait for
- * @param {boolean} add True to wait for the widget to be added,
- *     false to wait for it to be deleted.
- * @returns {Promise} that resolves when the widget is available
- */
-function waitForUserWidget(widgetId, add) {
-    return new Promise((resolve, reject) => {
-        const currentAccountDataEvent = MatrixClientPeg.get().getAccountData('m.widgets');
-
-        // Tests an account data event, returning true if it's in the state
-        // we're waiting for it to be in
-        function eventInIntendedState(ev) {
-            if (!ev || !currentAccountDataEvent.getContent()) return false;
-            if (add) {
-                return ev.getContent()[widgetId] !== undefined;
-            } else {
-                return ev.getContent()[widgetId] === undefined;
-            }
-        }
-
-        if (eventInIntendedState(currentAccountDataEvent)) {
-            resolve();
-            return;
-        }
-
-        function onAccountData(ev) {
-            if (eventInIntendedState(currentAccountDataEvent)) {
-                MatrixClientPeg.get().removeListener('accountData', onAccountData);
-                clearTimeout(timerId);
-                resolve();
-            }
-        }
-        const timerId = setTimeout(() => {
-            MatrixClientPeg.get().removeListener('accountData', onAccountData);
-            reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear"));
-        }, 10000);
-        MatrixClientPeg.get().on('accountData', onAccountData);
-    });
-}
-
 function setWidget(event, roomId) {
     const widgetId = event.data.widget_id;
     const widgetType = event.data.type;
@@ -339,12 +296,6 @@ function setWidget(event, roomId) {
     const widgetData = event.data.data; // optional
     const userWidget = event.data.userWidget;
 
-    const client = MatrixClientPeg.get();
-    if (!client) {
-        sendError(event, _t('You need to be logged in.'));
-        return;
-    }
-
     // both adding/removing widgets need these checks
     if (!widgetId || widgetUrl === undefined) {
         sendError(event, _t("Unable to create widget."), new Error("Missing required widget fields."));
@@ -371,42 +322,8 @@ function setWidget(event, roomId) {
         }
     }
 
-    let content = {
-        type: widgetType,
-        url: widgetUrl,
-        name: widgetName,
-        data: widgetData,
-    };
-
     if (userWidget) {
-        const client = MatrixClientPeg.get();
-        const userWidgets = Widgets.getUserWidgets();
-
-        // Delete existing widget with ID
-        try {
-            delete userWidgets[widgetId];
-        } catch (e) {
-            console.error(`$widgetId is non-configurable`);
-        }
-
-        // Add new widget / update
-        if (widgetUrl !== null) {
-            userWidgets[widgetId] = {
-                content: content,
-                sender: client.getUserId(),
-                state_key: widgetId,
-                type: 'm.widget',
-                id: widgetId,
-            };
-        }
-
-        // This starts listening for when the echo comes back from the server
-        // since the widget won't appear added until this happens. If we don't
-        // wait for this, the action will complete but if the user is fast enough,
-        // the widget still won't actually be there.
-        client.setAccountData('m.widgets', userWidgets).then(() => {
-            return waitForUserWidget(widgetId, widgetUrl !== null);
-        }).then(() => {
+        WidgetUtils.setUserWidget(widgetId, widgetType, widgetUrl, widgetName, widgetData).then(() => {
             sendResponse(event, {
                 success: true,
             });
@@ -419,15 +336,7 @@ function setWidget(event, roomId) {
         if (!roomId) {
             sendError(event, _t('Missing roomId.'), null);
         }
-
-        if (widgetUrl === null) { // widget is being deleted
-            content = {};
-        }
-        // TODO - Room widgets need to be moved to 'm.widget' state events
-        // https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing
-        client.sendStateEvent(roomId, "im.vector.modular.widgets", content, widgetId).done(() => {
-            // XXX: We should probably wait for the echo of the state event to come back from the server,
-            // as we do with user widgets.
+        WidgetUtils.setRoomWidget(roomId, widgetId, widgetType, widgetUrl, widgetName, widgetData).then(() => {
             sendResponse(event, {
                 success: true,
             });
@@ -451,21 +360,13 @@ function getWidgets(event, roomId) {
             sendError(event, _t('This room is not recognised.'));
             return;
         }
-        // TODO - Room widgets need to be moved to 'm.widget' state events
-        // https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing
-        const stateEvents = room.currentState.getStateEvents("im.vector.modular.widgets");
-        // Only return widgets which have required fields
-        if (room) {
-            stateEvents.forEach((ev) => {
-                if (ev.getContent().type && ev.getContent().url) {
-                    widgetStateEvents.push(ev.event); // return the raw event
-                }
-            });
-        }
+        // XXX: This gets the raw event object (I think because we can't
+        // send the MatrixEvent over postMessage?)
+        widgetStateEvents = WidgetUtils.getRoomWidgets(room).map((ev) => ev.event);
     }
 
     // Add user widgets (not linked to a specific room)
-    const userWidgets = Widgets.getUserWidgetsArray();
+    const userWidgets = WidgetUtils.getUserWidgetsArray();
     widgetStateEvents = widgetStateEvents.concat(userWidgets);
 
     sendResponse(event, widgetStateEvents);
@@ -637,19 +538,6 @@ function returnStateEvent(event, roomId, eventType, stateKey) {
     sendResponse(event, stateEvent.getContent());
 }
 
-let currentRoomId = null;
-let currentRoomAlias = null;
-
-// Listen for when a room is viewed
-dis.register(onAction);
-function onAction(payload) {
-    if (payload.action !== "view_room") {
-        return;
-    }
-    currentRoomId = payload.room_id;
-    currentRoomAlias = payload.room_alias;
-}
-
 const onMessage = function(event) {
     if (!event.origin) { // stupid chrome
         event.origin = event.originalEvent.origin;
@@ -700,80 +588,63 @@ const onMessage = function(event) {
             return;
         }
     }
-    let promise = Promise.resolve(currentRoomId);
-    if (!currentRoomId) {
-        if (!currentRoomAlias) {
-            sendError(event, _t('Must be viewing a room'));
-            return;
-        }
-        // no room ID but there is an alias, look it up.
-        console.log("Looking up alias " + currentRoomAlias);
-        promise = MatrixClientPeg.get().getRoomIdForAlias(currentRoomAlias).then((res) => {
-            return res.room_id;
-        });
+
+    if (roomId !== RoomViewStore.getRoomId()) {
+        sendError(event, _t('Room %(roomId)s not visible', {roomId: roomId}));
+        return;
     }
 
-    promise.then((viewingRoomId) => {
-        if (roomId !== viewingRoomId) {
-            sendError(event, _t('Room %(roomId)s not visible', {roomId: roomId}));
-            return;
-        }
+    // Get and set room-based widgets
+    if (event.data.action === "get_widgets") {
+        getWidgets(event, roomId);
+        return;
+    } else if (event.data.action === "set_widget") {
+        setWidget(event, roomId);
+        return;
+    }
 
-        // Get and set room-based widgets
-        if (event.data.action === "get_widgets") {
-            getWidgets(event, roomId);
-            return;
-        } else if (event.data.action === "set_widget") {
-            setWidget(event, roomId);
-            return;
-        }
+    // These APIs don't require userId
+    if (event.data.action === "join_rules_state") {
+        getJoinRules(event, roomId);
+        return;
+    } else if (event.data.action === "set_plumbing_state") {
+        setPlumbingState(event, roomId, event.data.status);
+        return;
+    } else if (event.data.action === "get_membership_count") {
+        getMembershipCount(event, roomId);
+        return;
+    } else if (event.data.action === "get_room_enc_state") {
+        getRoomEncState(event, roomId);
+        return;
+    } else if (event.data.action === "can_send_event") {
+        canSendEvent(event, roomId);
+        return;
+    }
 
-        // These APIs don't require userId
-        if (event.data.action === "join_rules_state") {
-            getJoinRules(event, roomId);
-            return;
-        } else if (event.data.action === "set_plumbing_state") {
-            setPlumbingState(event, roomId, event.data.status);
-            return;
-        } else if (event.data.action === "get_membership_count") {
-            getMembershipCount(event, roomId);
-            return;
-        } else if (event.data.action === "get_room_enc_state") {
-            getRoomEncState(event, roomId);
-            return;
-        } else if (event.data.action === "can_send_event") {
-            canSendEvent(event, roomId);
-            return;
-        }
-
-        if (!userId) {
-            sendError(event, _t('Missing user_id in request'));
-            return;
-        }
-        switch (event.data.action) {
-            case "membership_state":
-                getMembershipState(event, roomId, userId);
-                break;
-            case "invite":
-                inviteUser(event, roomId, userId);
-                break;
-            case "bot_options":
-                botOptions(event, roomId, userId);
-                break;
-            case "set_bot_options":
-                setBotOptions(event, roomId, userId);
-                break;
-            case "set_bot_power":
-                setBotPower(event, roomId, userId, event.data.level);
-                break;
-            default:
-                console.warn("Unhandled postMessage event with action '" + event.data.action +"'");
-                break;
-        }
-    }, (err) => {
-        console.error(err);
-        sendError(event, _t('Failed to lookup current room') + '.');
-    });
+    if (!userId) {
+        sendError(event, _t('Missing user_id in request'));
+        return;
+    }
+    switch (event.data.action) {
+        case "membership_state":
+            getMembershipState(event, roomId, userId);
+            break;
+        case "invite":
+            inviteUser(event, roomId, userId);
+            break;
+        case "bot_options":
+            botOptions(event, roomId, userId);
+            break;
+        case "set_bot_options":
+            setBotOptions(event, roomId, userId);
+            break;
+        case "set_bot_power":
+            setBotPower(event, roomId, userId, event.data.level);
+            break;
+        default:
+            console.warn("Unhandled postMessage event with action '" + event.data.action +"'");
+            break;
+    }
 };
 
 let listenerCount = 0;
diff --git a/src/SlashCommands.js b/src/SlashCommands.js
index d45e45e84c..211a68e7b0 100644
--- a/src/SlashCommands.js
+++ b/src/SlashCommands.js
@@ -14,28 +14,31 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import MatrixClientPeg from "./MatrixClientPeg";
-import dis from "./dispatcher";
-import Tinter from "./Tinter";
+
+import React from 'react';
+import MatrixClientPeg from './MatrixClientPeg';
+import dis from './dispatcher';
+import Tinter from './Tinter';
 import sdk from './index';
-import { _t } from './languageHandler';
+import {_t, _td} from './languageHandler';
 import Modal from './Modal';
-import SettingsStore, {SettingLevel} from "./settings/SettingsStore";
+import SettingsStore, {SettingLevel} from './settings/SettingsStore';
 
 
 class Command {
-    constructor(name, paramArgs, runFn) {
-        this.name = name;
-        this.paramArgs = paramArgs;
+    constructor({name, args='', description, runFn}) {
+        this.command = '/' + name;
+        this.args = args;
+        this.description = description;
         this.runFn = runFn;
     }
 
     getCommand() {
-        return "/" + this.name;
+        return this.command;
     }
 
     getCommandWithArgs() {
-        return this.getCommand() + " " + this.paramArgs;
+        return this.getCommand() + " " + this.args;
     }
 
     run(roomId, args) {
@@ -47,16 +50,12 @@ class Command {
     }
 }
 
-function reject(msg) {
-    return {
-        error: msg,
-    };
+function reject(error) {
+    return {error};
 }
 
 function success(promise) {
-    return {
-        promise: promise,
-    };
+    return {promise};
 }
 
 /* Disable the "unexpected this" error for these commands - all of the run
@@ -65,352 +64,408 @@ function success(promise) {
 
 /* eslint-disable babel/no-invalid-this */
 
-const commands = {
-    ddg: new Command("ddg", "", function(roomId, args) {
-        const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
-        // TODO Don't explain this away, actually show a search UI here.
-        Modal.createTrackedDialog('Slash Commands', '/ddg is not a command', ErrorDialog, {
-            title: _t('/ddg is not a command'),
-            description: _t('To use it, just wait for autocomplete results to load and tab through them.'),
-        });
-        return success();
+export const CommandMap = {
+    ddg: new Command({
+        name: 'ddg',
+        args: '',
+        description: _td('Searches DuckDuckGo for results'),
+        runFn: function(roomId, args) {
+            const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
+            // TODO Don't explain this away, actually show a search UI here.
+            Modal.createTrackedDialog('Slash Commands', '/ddg is not a command', ErrorDialog, {
+                title: _t('/ddg is not a command'),
+                description: _t('To use it, just wait for autocomplete results to load and tab through them.'),
+            });
+            return success();
+        },
     }),
 
-    // Change your nickname
-    nick: new Command("nick", "", function(roomId, args) {
-        if (args) {
-            return success(
-                MatrixClientPeg.get().setDisplayName(args),
-            );
-        }
-        return reject(this.getUsage());
-    }),
-
-    // Changes the colorscheme of your current room
-    tint: new Command("tint", " []", function(roomId, args) {
-        if (args) {
-            const matches = args.match(/^(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}))( +(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})))?$/);
-            if (matches) {
-                Tinter.tint(matches[1], matches[4]);
-                const colorScheme = {};
-                colorScheme.primary_color = matches[1];
-                if (matches[4]) {
-                    colorScheme.secondary_color = matches[4];
-                } else {
-                    colorScheme.secondary_color = colorScheme.primary_color;
-                }
-                return success(
-                    SettingsStore.setValue("roomColor", roomId, SettingLevel.ROOM_ACCOUNT, colorScheme),
-                );
+    nick: new Command({
+        name: 'nick',
+        args: '',
+        description: _td('Changes your display nickname'),
+        runFn: function(roomId, args) {
+            if (args) {
+                return success(MatrixClientPeg.get().setDisplayName(args));
             }
-        }
-        return reject(this.getUsage());
+            return reject(this.getUsage());
+        },
     }),
 
-    // Change the room topic
-    topic: new Command("topic", "", function(roomId, args) {
-        if (args) {
-            return success(
-                MatrixClientPeg.get().setRoomTopic(roomId, args),
-            );
-        }
-        return reject(this.getUsage());
-    }),
-
-    // Invite a user
-    invite: new Command("invite", "", function(roomId, args) {
-        if (args) {
-            const matches = args.match(/^(\S+)$/);
-            if (matches) {
-                return success(
-                    MatrixClientPeg.get().invite(roomId, matches[1]),
-                );
-            }
-        }
-        return reject(this.getUsage());
-    }),
-
-    // Join a room
-    join: new Command("join", "#alias:domain", function(roomId, args) {
-        if (args) {
-            const matches = args.match(/^(\S+)$/);
-            if (matches) {
-                let roomAlias = matches[1];
-                if (roomAlias[0] !== '#') {
-                    return reject(this.getUsage());
-                }
-                if (!roomAlias.match(/:/)) {
-                    roomAlias += ':' + MatrixClientPeg.get().getDomain();
-                }
-
-                dis.dispatch({
-                    action: 'view_room',
-                    room_alias: roomAlias,
-                    auto_join: true,
-                });
-
-                return success();
-            }
-        }
-        return reject(this.getUsage());
-    }),
-
-    part: new Command("part", "[#alias:domain]", function(roomId, args) {
-        let targetRoomId;
-        if (args) {
-            const matches = args.match(/^(\S+)$/);
-            if (matches) {
-                let roomAlias = matches[1];
-                if (roomAlias[0] !== '#') {
-                    return reject(this.getUsage());
-                }
-                if (!roomAlias.match(/:/)) {
-                    roomAlias += ':' + MatrixClientPeg.get().getDomain();
-                }
-
-                // Try to find a room with this alias
-                const rooms = MatrixClientPeg.get().getRooms();
-                for (let i = 0; i < rooms.length; i++) {
-                    const aliasEvents = rooms[i].currentState.getStateEvents(
-                        "m.room.aliases",
-                    );
-                    for (let j = 0; j < aliasEvents.length; j++) {
-                        const aliases = aliasEvents[j].getContent().aliases || [];
-                        for (let k = 0; k < aliases.length; k++) {
-                            if (aliases[k] === roomAlias) {
-                                targetRoomId = rooms[i].roomId;
-                                break;
-                            }
-                        }
-                        if (targetRoomId) { break; }
+    tint: new Command({
+        name: 'tint',
+        args: ' []',
+        description: _td('Changes colour scheme of current room'),
+        runFn: function(roomId, args) {
+            if (args) {
+                const matches = args.match(/^(#([\da-fA-F]{3}|[\da-fA-F]{6}))( +(#([\da-fA-F]{3}|[\da-fA-F]{6})))?$/);
+                if (matches) {
+                    Tinter.tint(matches[1], matches[4]);
+                    const colorScheme = {};
+                    colorScheme.primary_color = matches[1];
+                    if (matches[4]) {
+                        colorScheme.secondary_color = matches[4];
+                    } else {
+                        colorScheme.secondary_color = colorScheme.primary_color;
                     }
-                    if (targetRoomId) { break; }
-                }
-                if (!targetRoomId) {
-                    return reject(_t("Unrecognised room alias:") + ' ' + roomAlias);
+                    return success(
+                        SettingsStore.setValue('roomColor', roomId, SettingLevel.ROOM_ACCOUNT, colorScheme),
+                    );
                 }
             }
-        }
-        if (!targetRoomId) targetRoomId = roomId;
-        return success(
-            MatrixClientPeg.get().leave(targetRoomId).then(
-                function() {
-                    dis.dispatch({action: 'view_next_room'});
-                },
-            ),
-        );
+            return reject(this.getUsage());
+        },
     }),
 
-    // Kick a user from the room with an optional reason
-    kick: new Command("kick", " []", function(roomId, args) {
-        if (args) {
-            const matches = args.match(/^(\S+?)( +(.*))?$/);
-            if (matches) {
-                return success(
-                    MatrixClientPeg.get().kick(roomId, matches[1], matches[3]),
-                );
+    topic: new Command({
+        name: 'topic',
+        args: '',
+        description: _td('Sets the room topic'),
+        runFn: function(roomId, args) {
+            if (args) {
+                return success(MatrixClientPeg.get().setRoomTopic(roomId, args));
             }
-        }
-        return reject(this.getUsage());
+            return reject(this.getUsage());
+        },
+    }),
+
+    invite: new Command({
+        name: 'invite',
+        args: '',
+        description: _td('Invites user with given id to current room'),
+        runFn: function(roomId, args) {
+            if (args) {
+                const matches = args.match(/^(\S+)$/);
+                if (matches) {
+                    return success(MatrixClientPeg.get().invite(roomId, matches[1]));
+                }
+            }
+            return reject(this.getUsage());
+        },
+    }),
+
+    join: new Command({
+        name: 'join',
+        args: '',
+        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());
+
+                    if (!roomAlias.includes(':')) {
+                        roomAlias += ':' + MatrixClientPeg.get().getDomain();
+                    }
+
+                    dis.dispatch({
+                        action: 'view_room',
+                        room_alias: roomAlias,
+                        auto_join: true,
+                    });
+
+                    return success();
+                }
+            }
+            return reject(this.getUsage());
+        },
+    }),
+
+    part: new Command({
+        name: 'part',
+        args: '[]',
+        description: _td('Leave room'),
+        runFn: function(roomId, args) {
+            const cli = MatrixClientPeg.get();
+
+            let targetRoomId;
+            if (args) {
+                const matches = args.match(/^(\S+)$/);
+                if (matches) {
+                    let roomAlias = matches[1];
+                    if (roomAlias[0] !== '#') return reject(this.getUsage());
+
+                    if (!roomAlias.includes(':')) {
+                        roomAlias += ':' + cli.getDomain();
+                    }
+
+                    // Try to find a room with this alias
+                    const rooms = cli.getRooms();
+                    for (let i = 0; i < rooms.length; i++) {
+                        const aliasEvents = rooms[i].currentState.getStateEvents('m.room.aliases');
+                        for (let j = 0; j < aliasEvents.length; j++) {
+                            const aliases = aliasEvents[j].getContent().aliases || [];
+                            for (let k = 0; k < aliases.length; k++) {
+                                if (aliases[k] === roomAlias) {
+                                    targetRoomId = rooms[i].roomId;
+                                    break;
+                                }
+                            }
+                            if (targetRoomId) break;
+                        }
+                        if (targetRoomId) break;
+                    }
+                    if (!targetRoomId) return reject(_t('Unrecognised room alias:') + ' ' + roomAlias);
+                }
+            }
+
+            if (!targetRoomId) targetRoomId = roomId;
+            return success(
+                cli.leave(targetRoomId).then(function() {
+                    dis.dispatch({action: 'view_next_room'});
+                }),
+            );
+        },
+    }),
+
+    kick: new Command({
+        name: 'kick',
+        args: ' [reason]',
+        description: _td('Kicks user with given id'),
+        runFn: function(roomId, args) {
+            if (args) {
+                const matches = args.match(/^(\S+?)( +(.*))?$/);
+                if (matches) {
+                    return success(MatrixClientPeg.get().kick(roomId, matches[1], matches[3]));
+                }
+            }
+            return reject(this.getUsage());
+        },
     }),
 
     // Ban a user from the room with an optional reason
-    ban: new Command("ban", " []", function(roomId, args) {
-        if (args) {
-            const matches = args.match(/^(\S+?)( +(.*))?$/);
-            if (matches) {
-                return success(
-                    MatrixClientPeg.get().ban(roomId, matches[1], matches[3]),
-                );
+    ban: new Command({
+        name: 'ban',
+        args: ' [reason]',
+        description: _td('Bans user with given id'),
+        runFn: function(roomId, args) {
+            if (args) {
+                const matches = args.match(/^(\S+?)( +(.*))?$/);
+                if (matches) {
+                    return success(MatrixClientPeg.get().ban(roomId, matches[1], matches[3]));
+                }
             }
-        }
-        return reject(this.getUsage());
+            return reject(this.getUsage());
+        },
     }),
 
-    // Unban a user from the room
-    unban: new Command("unban", "", function(roomId, args) {
-        if (args) {
-            const matches = args.match(/^(\S+)$/);
-            if (matches) {
-                // Reset the user membership to "leave" to unban him
-                return success(
-                    MatrixClientPeg.get().unban(roomId, matches[1]),
-                );
+    // Unban a user from ythe room
+    unban: new Command({
+        name: 'unban',
+        args: '',
+        description: _td('Unbans user with given id'),
+        runFn: function(roomId, args) {
+            if (args) {
+                const matches = args.match(/^(\S+)$/);
+                if (matches) {
+                    // Reset the user membership to "leave" to unban him
+                    return success(MatrixClientPeg.get().unban(roomId, matches[1]));
+                }
             }
-        }
-        return reject(this.getUsage());
+            return reject(this.getUsage());
+        },
     }),
 
-    ignore: new Command("ignore", "", function(roomId, args) {
-        if (args) {
-            const matches = args.match(/^(\S+)$/);
-            if (matches) {
-                const userId = matches[1];
-                const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
-                ignoredUsers.push(userId); // de-duped internally in the js-sdk
-                return success(
-                    MatrixClientPeg.get().setIgnoredUsers(ignoredUsers).then(() => {
-                        const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
-                        Modal.createTrackedDialog('Slash Commands', 'User ignored', QuestionDialog, {
-                            title: _t("Ignored user"),
-                            description: (
-                                
-

{ _t("You are now ignoring %(userId)s", {userId: userId}) }

-
- ), - hasCancelButton: false, - }); - }), - ); + ignore: new Command({ + name: 'ignore', + args: '', + description: _td('Ignores a user, hiding their messages from you'), + runFn: function(roomId, args) { + if (args) { + const cli = MatrixClientPeg.get(); + + const matches = args.match(/^(\S+)$/); + if (matches) { + const userId = matches[1]; + const ignoredUsers = cli.getIgnoredUsers(); + ignoredUsers.push(userId); // de-duped internally in the js-sdk + return success( + cli.setIgnoredUsers(ignoredUsers).then(() => { + const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog'); + Modal.createTrackedDialog('Slash Commands', 'User ignored', QuestionDialog, { + title: _t('Ignored user'), + description:
+

{ _t('You are now ignoring %(userId)s', {userId}) }

+
, + hasCancelButton: false, + }); + }), + ); + } } - } - return reject(this.getUsage()); + return reject(this.getUsage()); + }, }), - unignore: new Command("unignore", "", function(roomId, args) { - if (args) { - const matches = args.match(/^(\S+)$/); - if (matches) { - const userId = matches[1]; - const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers(); - const index = ignoredUsers.indexOf(userId); - if (index !== -1) ignoredUsers.splice(index, 1); - return success( - MatrixClientPeg.get().setIgnoredUsers(ignoredUsers).then(() => { - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createTrackedDialog('Slash Commands', 'User unignored', QuestionDialog, { - title: _t("Unignored user"), - description: ( -
-

{ _t("You are no longer ignoring %(userId)s", {userId: userId}) }

-
- ), - hasCancelButton: false, - }); - }), - ); + unignore: new Command({ + name: 'unignore', + args: '', + description: _td('Stops ignoring a user, showing their messages going forward'), + runFn: function(roomId, args) { + if (args) { + const cli = MatrixClientPeg.get(); + + const matches = args.match(/^(\S+)$/); + if (matches) { + const userId = matches[1]; + const ignoredUsers = cli.getIgnoredUsers(); + const index = ignoredUsers.indexOf(userId); + if (index !== -1) ignoredUsers.splice(index, 1); + return success( + cli.setIgnoredUsers(ignoredUsers).then(() => { + const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog'); + Modal.createTrackedDialog('Slash Commands', 'User unignored', QuestionDialog, { + title: _t('Unignored user'), + description:
+

{ _t('You are no longer ignoring %(userId)s', {userId}) }

+
, + hasCancelButton: false, + }); + }), + ); + } } - } - return reject(this.getUsage()); + return reject(this.getUsage()); + }, }), // Define the power level of a user - op: new Command("op", " []", function(roomId, args) { - if (args) { - const matches = args.match(/^(\S+?)( +(-?\d+))?$/); - let powerLevel = 50; // default power level for op - if (matches) { - const userId = matches[1]; - if (matches.length === 4 && undefined !== matches[3]) { - powerLevel = parseInt(matches[3]); - } - if (!isNaN(powerLevel)) { - const room = MatrixClientPeg.get().getRoom(roomId); - if (!room) { - return reject("Bad room ID: " + roomId); + op: new Command({ + name: 'op', + args: ' []', + description: _td('Define the power level of a user'), + runFn: function(roomId, args) { + if (args) { + const matches = args.match(/^(\S+?)( +(-?\d+))?$/); + let powerLevel = 50; // default power level for op + if (matches) { + const userId = matches[1]; + if (matches.length === 4 && undefined !== matches[3]) { + powerLevel = parseInt(matches[3]); + } + if (!isNaN(powerLevel)) { + const cli = MatrixClientPeg.get(); + const room = cli.getRoom(roomId); + if (!room) return reject('Bad room ID: ' + roomId); + + const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', ''); + return success(cli.setPowerLevel(roomId, userId, powerLevel, powerLevelEvent)); } - const powerLevelEvent = room.currentState.getStateEvents( - "m.room.power_levels", "", - ); - return success( - MatrixClientPeg.get().setPowerLevel( - roomId, userId, powerLevel, powerLevelEvent, - ), - ); } } - } - return reject(this.getUsage()); + return reject(this.getUsage()); + }, }), // Reset the power level of a user - deop: new Command("deop", "", function(roomId, args) { - if (args) { - const matches = args.match(/^(\S+)$/); - if (matches) { - const room = MatrixClientPeg.get().getRoom(roomId); - if (!room) { - return reject("Bad room ID: " + roomId); - } + deop: new Command({ + name: 'deop', + args: '', + description: _td('Deops user with given id'), + runFn: function(roomId, args) { + if (args) { + const matches = args.match(/^(\S+)$/); + if (matches) { + const cli = MatrixClientPeg.get(); + const room = cli.getRoom(roomId); + if (!room) return reject('Bad room ID: ' + roomId); - const powerLevelEvent = room.currentState.getStateEvents( - "m.room.power_levels", "", - ); - return success( - MatrixClientPeg.get().setPowerLevel( - roomId, args, undefined, powerLevelEvent, - ), - ); + const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', ''); + return success(cli.setPowerLevel(roomId, args, undefined, powerLevelEvent)); + } } - } - return reject(this.getUsage()); + return reject(this.getUsage()); + }, }), - // Open developer tools - devtools: new Command("devtools", "", function(roomId) { - const DevtoolsDialog = sdk.getComponent("dialogs.DevtoolsDialog"); - Modal.createDialog(DevtoolsDialog, { roomId }); - return success(); + devtools: new Command({ + name: 'devtools', + description: _td('Opens the Developer Tools dialog'), + runFn: function(roomId) { + const DevtoolsDialog = sdk.getComponent('dialogs.DevtoolsDialog'); + Modal.createDialog(DevtoolsDialog, {roomId}); + return success(); + }, }), // Verify a user, device, and pubkey tuple - verify: new Command("verify", " ", function(roomId, args) { - if (args) { - const matches = args.match(/^(\S+) +(\S+) +(\S+)$/); - if (matches) { - const userId = matches[1]; - const deviceId = matches[2]; - const fingerprint = matches[3]; + verify: new Command({ + name: 'verify', + args: ' ', + description: _td('Verifies a user, device, and pubkey tuple'), + runFn: function(roomId, args) { + if (args) { + const matches = args.match(/^(\S+) +(\S+) +(\S+)$/); + if (matches) { + const cli = MatrixClientPeg.get(); - return success( - // Promise.resolve to handle transition from static result to promise; can be removed - // in future - Promise.resolve(MatrixClientPeg.get().getStoredDevice(userId, deviceId)).then((device) => { - if (!device) { - throw new Error(_t(`Unknown (user, device) pair:`) + ` (${userId}, ${deviceId})`); - } + const userId = matches[1]; + const deviceId = matches[2]; + const fingerprint = matches[3]; - if (device.isVerified()) { - if (device.getFingerprint() === fingerprint) { - throw new Error(_t(`Device already verified!`)); - } else { - throw new Error(_t(`WARNING: Device already verified, but keys do NOT MATCH!`)); + return success( + // Promise.resolve to handle transition from static result to promise; can be removed + // in future + Promise.resolve(cli.getStoredDevice(userId, deviceId)).then((device) => { + if (!device) { + throw new Error(_t('Unknown (user, device) pair:') + ` (${userId}, ${deviceId})`); } - } - if (device.getFingerprint() !== fingerprint) { - const fprint = device.getFingerprint(); - throw new Error( - _t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device' + - ' %(deviceId)s is "%(fprint)s" which does not match the provided key' + - ' "%(fingerprint)s". This could mean your communications are being intercepted!', - {deviceId: deviceId, fprint: fprint, userId: userId, fingerprint: fingerprint})); - } + if (device.isVerified()) { + if (device.getFingerprint() === fingerprint) { + throw new Error(_t('Device already verified!')); + } else { + throw new Error(_t('WARNING: Device already verified, but keys do NOT MATCH!')); + } + } - return MatrixClientPeg.get().setDeviceVerified(userId, deviceId, true); - }).then(() => { - // Tell the user we verified everything - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createTrackedDialog('Slash Commands', 'Verified key', QuestionDialog, { - title: _t("Verified key"), - description: ( -
-

- { - _t("The signing key you provided matches the signing key you received " + - "from %(userId)s's device %(deviceId)s. Device marked as verified.", - {userId: userId, deviceId: deviceId}) - } -

-
- ), - hasCancelButton: false, - }); - }), - ); + if (device.getFingerprint() !== fingerprint) { + const fprint = device.getFingerprint(); + throw new Error( + _t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device' + + ' %(deviceId)s is "%(fprint)s" which does not match the provided key ' + + '"%(fingerprint)s". This could mean your communications are being intercepted!', + { + fprint, + userId, + deviceId, + fingerprint, + })); + } + + return cli.setDeviceVerified(userId, deviceId, true); + }).then(() => { + // Tell the user we verified everything + const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog'); + Modal.createTrackedDialog('Slash Commands', 'Verified key', QuestionDialog, { + title: _t('Verified key'), + description:
+

+ { + _t('The signing key you provided matches the signing key you received ' + + 'from %(userId)s\'s device %(deviceId)s. Device marked as verified.', + {userId, deviceId}) + } +

+
, + hasCancelButton: false, + }); + }), + ); + } } - } - return reject(this.getUsage()); + return reject(this.getUsage()); + }, + }), + + // Command definitions for autocompletion ONLY: + + // /me is special because its not handled by SlashCommands.js and is instead done inside the Composer classes + me: new Command({ + name: 'me', + args: '', + description: _td('Displays action'), }), }; /* eslint-enable babel/no-invalid-this */ @@ -421,50 +476,39 @@ const aliases = { j: "join", }; -module.exports = { - /** - * Process the given text for /commands and perform them. - * @param {string} roomId The room in which the command was performed. - * @param {string} input The raw text input by the user. - * @return {Object|null} An object with the property 'error' if there was an error - * processing the command, or 'promise' if a request was sent out. - * Returns null if the input didn't match a command. - */ - processInput: function(roomId, input) { - // trim any trailing whitespace, as it can confuse the parser for - // IRC-style commands - input = input.replace(/\s+$/, ""); - if (input[0] === "/" && input[1] !== "/") { - const bits = input.match(/^(\S+?)( +((.|\n)*))?$/); - let cmd; - let args; - if (bits) { - cmd = bits[1].substring(1).toLowerCase(); - args = bits[3]; - } else { - cmd = input; - } - if (cmd === "me") return null; - if (aliases[cmd]) { - cmd = aliases[cmd]; - } - if (commands[cmd]) { - return commands[cmd].run(roomId, args); - } else { - return reject(_t("Unrecognised command:") + ' ' + input); - } - } - return null; // not a command - }, +/** + * Process the given text for /commands and perform them. + * @param {string} roomId The room in which the command was performed. + * @param {string} input The raw text input by the user. + * @return {Object|null} An object with the property 'error' if there was an error + * processing the command, or 'promise' if a request was sent out. + * Returns null if the input didn't match a command. + */ +export function processCommandInput(roomId, input) { + // trim any trailing whitespace, as it can confuse the parser for + // IRC-style commands + input = input.replace(/\s+$/, ''); + if (input[0] !== '/' || input[1] === '/') return null; // not a command - getCommandList: function() { - // Return all the commands plus /me and /markdown which aren't handled like normal commands - const cmds = Object.keys(commands).sort().map(function(cmdKey) { - return commands[cmdKey]; - }); - cmds.push(new Command("me", "", function() {})); - cmds.push(new Command("markdown", "", function() {})); + const bits = input.match(/^(\S+?)( +((.|\n)*))?$/); + let cmd; + let args; + if (bits) { + cmd = bits[1].substring(1).toLowerCase(); + args = bits[3]; + } else { + cmd = input; + } - return cmds; - }, -}; + if (aliases[cmd]) { + cmd = aliases[cmd]; + } + if (CommandMap[cmd]) { + // if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me` + if (!CommandMap[cmd].runFn) return null; + + return CommandMap[cmd].run(roomId, args); + } else { + return reject(_t('Unrecognised command:') + ' ' + input); + } +} diff --git a/src/WidgetUtils.js b/src/WidgetUtils.js deleted file mode 100644 index 10cd473904..0000000000 --- a/src/WidgetUtils.js +++ /dev/null @@ -1,93 +0,0 @@ -/* -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. -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 MatrixClientPeg from './MatrixClientPeg'; -import SdkConfig from "./SdkConfig"; -import * as url from "url"; - -export default class WidgetUtils { - /* Returns true if user is able to send state events to modify widgets in this room - * (Does not apply to non-room-based / user widgets) - * @param roomId -- The ID of the room to check - * @return Boolean -- true if the user can modify widgets in this room - * @throws Error -- specifies the error reason - */ - static canUserModifyWidgets(roomId) { - if (!roomId) { - console.warn('No room ID specified'); - return false; - } - - const client = MatrixClientPeg.get(); - if (!client) { - console.warn('User must be be logged in'); - return false; - } - - const room = client.getRoom(roomId); - if (!room) { - console.warn(`Room ID ${roomId} is not recognised`); - return false; - } - - const me = client.credentials.userId; - if (!me) { - console.warn('Failed to get user ID'); - return false; - } - - const member = room.getMember(me); - if (!member || member.membership !== "join") { - console.warn(`User ${me} is not in room ${roomId}`); - return false; - } - - return room.currentState.maySendStateEvent('im.vector.modular.widgets', me); - } - - /** - * Returns true if specified url is a scalar URL, typically https://scalar.vector.im/api - * @param {[type]} testUrlString URL to check - * @return {Boolean} True if specified URL is a scalar URL - */ - static isScalarUrl(testUrlString) { - if (!testUrlString) { - console.error('Scalar URL check failed. No URL specified'); - return false; - } - - const testUrl = url.parse(testUrlString); - - let scalarUrls = SdkConfig.get().integrations_widgets_urls; - if (!scalarUrls || scalarUrls.length === 0) { - scalarUrls = [SdkConfig.get().integrations_rest_url]; - } - - for (let i = 0; i < scalarUrls.length; i++) { - const scalarUrl = url.parse(scalarUrls[i]); - if (testUrl && scalarUrl) { - if ( - testUrl.protocol === scalarUrl.protocol && - testUrl.host === scalarUrl.host && - testUrl.pathname.startsWith(scalarUrl.pathname) - ) { - return true; - } - } - } - return false; - } -} diff --git a/src/autocomplete/AutocompleteProvider.js b/src/autocomplete/AutocompleteProvider.js index c93ae4fb2a..3fdb2998e7 100644 --- a/src/autocomplete/AutocompleteProvider.js +++ b/src/autocomplete/AutocompleteProvider.js @@ -1,7 +1,7 @@ /* Copyright 2016 Aviral Dasgupta Copyright 2017 Vector Creations Ltd -Copyright 2017 New Vector Ltd +Copyright 2017, 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. @@ -36,7 +36,7 @@ export default class AutocompleteProvider { /** * Of the matched commands in the query, returns the first that contains or is contained by the selection, or null. */ - getCurrentCommand(query: string, selection: {start: number, end: number}, force: boolean = false): ?string { + getCurrentCommand(query: string, selection: SelectionRange, force: boolean = false): ?string { let commandRegex = this.commandRegex; if (force && this.shouldForceComplete()) { @@ -51,14 +51,14 @@ export default class AutocompleteProvider { let match; while ((match = commandRegex.exec(query)) != null) { - let matchStart = match.index, - matchEnd = matchStart + match[0].length; - if (selection.start <= matchEnd && selection.end >= matchStart) { + const start = match.index; + const end = start + match[0].length; + if (selection.start <= end && selection.end >= start) { return { command: match, range: { - start: matchStart, - end: matchEnd, + start, + end, }, }; } diff --git a/src/autocomplete/Autocompleter.js b/src/autocomplete/Autocompleter.js index 3d30363d9f..f5fec4c502 100644 --- a/src/autocomplete/Autocompleter.js +++ b/src/autocomplete/Autocompleter.js @@ -1,6 +1,6 @@ /* Copyright 2016 Aviral Dasgupta -Copyright 2017 New Vector Ltd +Copyright 2017, 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. @@ -18,7 +18,9 @@ limitations under the License. // @flow import type {Component} from 'react'; +import {Room} from 'matrix-js-sdk'; import CommandProvider from './CommandProvider'; +import CommunityProvider from './CommunityProvider'; import DuckDuckGoProvider from './DuckDuckGoProvider'; import RoomProvider from './RoomProvider'; import UserProvider from './UserProvider'; @@ -47,6 +49,7 @@ const PROVIDERS = [ EmojiProvider, NotifProvider, CommandProvider, + CommunityProvider, DuckDuckGoProvider, ]; @@ -54,7 +57,7 @@ const PROVIDERS = [ const PROVIDER_COMPLETION_TIMEOUT = 3000; export default class Autocompleter { - constructor(room) { + constructor(room: Room) { this.room = room; this.providers = PROVIDERS.map((p) => { return new p(room); diff --git a/src/autocomplete/CommandProvider.js b/src/autocomplete/CommandProvider.js index e33fa7861f..5582b57e14 100644 --- a/src/autocomplete/CommandProvider.js +++ b/src/autocomplete/CommandProvider.js @@ -2,6 +2,7 @@ Copyright 2016 Aviral Dasgupta Copyright 2017 Vector Creations Ltd Copyright 2017 New Vector Ltd +Copyright 2018 Michael Telatynski <7t3chguy@gmail.com> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,103 +18,16 @@ limitations under the License. */ import React from 'react'; -import { _t, _td } from '../languageHandler'; +import {_t} from '../languageHandler'; import AutocompleteProvider from './AutocompleteProvider'; import FuzzyMatcher from './FuzzyMatcher'; import {TextualCompletion} from './Components'; +import type {Completion, SelectionRange} from "./Autocompleter"; +import {CommandMap} from '../SlashCommands'; -// TODO merge this with the factory mechanics of SlashCommands? -// Warning: Since the description string will be translated in _t(result.description), all these strings below must be in i18n/strings/en_EN.json file -const COMMANDS = [ - { - command: '/me', - args: '', - description: _td('Displays action'), - }, - { - command: '/ban', - args: ' [reason]', - description: _td('Bans user with given id'), - }, - { - command: '/unban', - args: '', - description: _td('Unbans user with given id'), - }, - { - command: '/op', - args: ' []', - description: _td('Define the power level of a user'), - }, - { - command: '/deop', - args: '', - description: _td('Deops user with given id'), - }, - { - command: '/invite', - args: '', - description: _td('Invites user with given id to current room'), - }, - { - command: '/join', - args: '', - description: _td('Joins room with given alias'), - }, - { - command: '/part', - args: '[]', - description: _td('Leave room'), - }, - { - command: '/topic', - args: '', - description: _td('Sets the room topic'), - }, - { - command: '/kick', - args: ' [reason]', - description: _td('Kicks user with given id'), - }, - { - command: '/nick', - args: '', - description: _td('Changes your display nickname'), - }, - { - command: '/ddg', - args: '', - description: _td('Searches DuckDuckGo for results'), - }, - { - command: '/tint', - args: ' []', - description: _td('Changes colour scheme of current room'), - }, - { - command: '/verify', - args: ' ', - description: _td('Verifies a user, device, and pubkey tuple'), - }, - { - command: '/ignore', - args: '', - description: _td('Ignores a user, hiding their messages from you'), - }, - { - command: '/unignore', - args: '', - description: _td('Stops ignoring a user, showing their messages going forward'), - }, - { - command: '/devtools', - args: '', - description: _td('Opens the Developer Tools dialog'), - }, - // Omitting `/markdown` as it only seems to apply to OldComposer -]; +const COMMANDS = Object.values(CommandMap); -const COMMAND_RE = /(^\/\w*)/g; +const COMMAND_RE = /(^\/\w*)(?: .*)?/g; export default class CommandProvider extends AutocompleteProvider { constructor() { @@ -123,23 +37,36 @@ export default class CommandProvider extends AutocompleteProvider { }); } - async getCompletions(query: string, selection: {start: number, end: number}) { - let completions = []; + async getCompletions(query: string, selection: SelectionRange, force?: boolean): Array { const {command, range} = this.getCurrentCommand(query, selection); - if (command) { - completions = this.matcher.match(command[0]).map((result) => { - return { - completion: result.command + ' ', - component: (), - range, - }; - }); + if (!command) return []; + + let matches = []; + if (command[0] !== command[1]) { + // The input looks like a command with arguments, perform exact match + const name = command[1].substr(1); // strip leading `/` + if (CommandMap[name]) { + matches = [CommandMap[name]]; + } + } else { + if (query === '/') { + // If they have just entered `/` show everything + matches = COMMANDS; + } else { + // otherwise fuzzy match against all of the fields + matches = this.matcher.match(command[1]); + } } - return completions; + + return matches.map((result) => ({ + // If the command is the same as the one they entered, we don't want to discard their arguments + completion: result.command === command[1] ? command[0] : (result.command + ' '), + component: , + range, + })); } getName() { diff --git a/src/autocomplete/CommunityProvider.js b/src/autocomplete/CommunityProvider.js new file mode 100644 index 0000000000..6bcf1a02fd --- /dev/null +++ b/src/autocomplete/CommunityProvider.js @@ -0,0 +1,111 @@ +/* +Copyright 2018 New Vector Ltd +Copyright 2018 Michael Telatynski <7t3chguy@gmail.com> + +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 { _t } from '../languageHandler'; +import AutocompleteProvider from './AutocompleteProvider'; +import MatrixClientPeg from '../MatrixClientPeg'; +import FuzzyMatcher from './FuzzyMatcher'; +import {PillCompletion} from './Components'; +import sdk from '../index'; +import _sortBy from 'lodash/sortBy'; +import {makeGroupPermalink} from "../matrix-to"; +import type {Completion, SelectionRange} from "./Autocompleter"; +import FlairStore from "../stores/FlairStore"; + +const COMMUNITY_REGEX = /\B\+\S*/g; + +function score(query, space) { + const index = space.indexOf(query); + if (index === -1) { + return Infinity; + } else { + return index; + } +} + +export default class CommunityProvider extends AutocompleteProvider { + constructor() { + super(COMMUNITY_REGEX); + this.matcher = new FuzzyMatcher([], { + keys: ['groupId', 'name', 'shortDescription'], + }); + } + + async getCompletions(query: string, selection: SelectionRange, force?: boolean = false): Array { + const BaseAvatar = sdk.getComponent('views.avatars.BaseAvatar'); + + // Disable autocompletions when composing commands because of various issues + // (see https://github.com/vector-im/riot-web/issues/4762) + if (/^(\/join|\/leave)/.test(query)) { + return []; + } + + const cli = MatrixClientPeg.get(); + let completions = []; + const {command, range} = this.getCurrentCommand(query, selection, force); + if (command) { + const joinedGroups = cli.getGroups().filter(({myMembership}) => myMembership === 'join'); + + 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 + return Promise.resolve({ + name: '', + groupId, + avatarUrl: '', + shortDescription: '', + }); + } + }))); + + this.matcher.setObjects(groups); + + const matchedString = command[0]; + completions = this.matcher.match(matchedString); + completions = _sortBy(completions, [ + (c) => score(matchedString, c.groupId), + (c) => c.groupId.length, + ]).map(({avatarUrl, groupId, name}) => ({ + completion: groupId, + suffix: ' ', + href: makeGroupPermalink(groupId), + component: ( + + } title={name} description={groupId} /> + ), + range, + })) + .slice(0, 4); + } + return completions; + } + + getName() { + return '💬 ' + _t('Communities'); + } + + renderCompletions(completions: [React.Component]): ?React.Component { + return
+ { completions } +
; + } +} diff --git a/src/autocomplete/DuckDuckGoProvider.js b/src/autocomplete/DuckDuckGoProvider.js index 68d4915f56..e25ef16428 100644 --- a/src/autocomplete/DuckDuckGoProvider.js +++ b/src/autocomplete/DuckDuckGoProvider.js @@ -1,7 +1,7 @@ /* Copyright 2016 Aviral Dasgupta Copyright 2017 Vector Creations Ltd -Copyright 2017 New Vector Ltd +Copyright 2017, 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. @@ -22,6 +22,7 @@ import AutocompleteProvider from './AutocompleteProvider'; import 'whatwg-fetch'; import {TextualCompletion} from './Components'; +import type {SelectionRange} from "./Autocompleter"; const DDG_REGEX = /\/ddg\s+(.+)$/g; const REFERRER = 'vector'; @@ -36,7 +37,7 @@ export default class DuckDuckGoProvider extends AutocompleteProvider { + `&format=json&no_redirect=1&no_html=1&t=${encodeURIComponent(REFERRER)}`; } - async getCompletions(query: string, selection: {start: number, end: number}) { + async getCompletions(query: string, selection: SelectionRange, force?: boolean = false) { const {command, range} = this.getCurrentCommand(query, selection); if (!query || !command) { return []; diff --git a/src/autocomplete/EmojiProvider.js b/src/autocomplete/EmojiProvider.js index f4e576ea0f..81f6144fd3 100644 --- a/src/autocomplete/EmojiProvider.js +++ b/src/autocomplete/EmojiProvider.js @@ -1,7 +1,7 @@ /* Copyright 2016 Aviral Dasgupta Copyright 2017 Vector Creations Ltd -Copyright 2017 New Vector Ltd +Copyright 2017, 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. @@ -19,11 +19,11 @@ limitations under the License. import React from 'react'; import { _t } from '../languageHandler'; import AutocompleteProvider from './AutocompleteProvider'; -import {emojioneList, shortnameToImage, shortnameToUnicode, asciiRegexp, unicodeRegexp} from 'emojione'; +import {shortnameToUnicode, asciiRegexp, unicodeRegexp} from 'emojione'; import FuzzyMatcher from './FuzzyMatcher'; import sdk from '../index'; import {PillCompletion} from './Components'; -import type {SelectionRange, Completion} from './Autocompleter'; +import type {Completion, SelectionRange} from './Autocompleter'; import _uniq from 'lodash/uniq'; import _sortBy from 'lodash/sortBy'; import SettingsStore from "../settings/SettingsStore"; @@ -95,7 +95,7 @@ export default class EmojiProvider extends AutocompleteProvider { }); } - async getCompletions(query: string, selection: SelectionRange) { + async getCompletions(query: string, selection: SelectionRange, force?: boolean): Array { if (SettingsStore.getValue("MessageComposerInput.dontSuggestEmoji")) { return []; // don't give any suggestions if the user doesn't want them } diff --git a/src/autocomplete/NotifProvider.js b/src/autocomplete/NotifProvider.js index b7ac645525..842fb4fb18 100644 --- a/src/autocomplete/NotifProvider.js +++ b/src/autocomplete/NotifProvider.js @@ -20,6 +20,7 @@ import { _t } from '../languageHandler'; import MatrixClientPeg from '../MatrixClientPeg'; import {PillCompletion} from './Components'; import sdk from '../index'; +import type {Completion, SelectionRange} from "./Autocompleter"; const AT_ROOM_REGEX = /@\S*/g; @@ -29,7 +30,7 @@ export default class NotifProvider extends AutocompleteProvider { this.room = room; } - async getCompletions(query: string, selection: {start: number, end: number}, force = false) { + async getCompletions(query: string, selection: SelectionRange, force?:boolean = false): Array { const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar'); const client = MatrixClientPeg.get(); diff --git a/src/autocomplete/QueryMatcher.js b/src/autocomplete/QueryMatcher.js index 762b285685..9d4d4d0598 100644 --- a/src/autocomplete/QueryMatcher.js +++ b/src/autocomplete/QueryMatcher.js @@ -1,6 +1,7 @@ //@flow /* Copyright 2017 Aviral Dasgupta +Copyright 2018 Michael Telatynski <7t3chguy@gmail.com> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -27,6 +28,10 @@ class KeyMap { priorityMap = new Map(); } +function stripDiacritics(str: string): string { + return str.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); +} + export default class QueryMatcher { /** * @param {object[]} objects the objects to perform a match on @@ -46,10 +51,11 @@ export default class QueryMatcher { objects.forEach((object, i) => { const keyValues = _at(object, keys); for (const keyValue of keyValues) { - if (!map.hasOwnProperty(keyValue)) { - map[keyValue] = []; + const key = stripDiacritics(keyValue).toLowerCase(); + if (!map.hasOwnProperty(key)) { + map[key] = []; } - map[keyValue].push(object); + map[key].push(object); } keyMap.priorityMap.set(object, i); }); @@ -82,7 +88,7 @@ export default class QueryMatcher { } match(query: String): Array { - query = query.toLowerCase(); + query = stripDiacritics(query).toLowerCase(); if (this.options.shouldMatchWordsOnly) { query = query.replace(/[^\w]/g, ''); } @@ -91,7 +97,7 @@ export default class QueryMatcher { } const results = []; this.keyMap.keys.forEach((key) => { - let resultKey = key.toLowerCase(); + let resultKey = key; if (this.options.shouldMatchWordsOnly) { resultKey = resultKey.replace(/[^\w]/g, ''); } diff --git a/src/autocomplete/RoomProvider.js b/src/autocomplete/RoomProvider.js index 31599703c2..4e41a4221a 100644 --- a/src/autocomplete/RoomProvider.js +++ b/src/autocomplete/RoomProvider.js @@ -1,7 +1,8 @@ /* Copyright 2016 Aviral Dasgupta Copyright 2017 Vector Creations Ltd -Copyright 2017 New Vector Ltd +Copyright 2017, 2018 New Vector Ltd +Copyright 2018 Michael Telatynski <7t3chguy@gmail.com> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -26,8 +27,9 @@ import {getDisplayAliasForRoom} from '../Rooms'; import sdk from '../index'; import _sortBy from 'lodash/sortBy'; import {makeRoomPermalink} from "../matrix-to"; +import type {Completion, SelectionRange} from "./Autocompleter"; -const ROOM_REGEX = /(?=#)(\S*)/g; +const ROOM_REGEX = /\B#\S*/g; function score(query, space) { const index = space.indexOf(query); @@ -46,7 +48,7 @@ export default class RoomProvider extends AutocompleteProvider { }); } - async getCompletions(query: string, selection: {start: number, end: number}, force = false) { + async getCompletions(query: string, selection: SelectionRange, force?: boolean = false): Array { const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar'); // Disable autocompletions when composing commands because of various issues diff --git a/src/autocomplete/UserProvider.js b/src/autocomplete/UserProvider.js index ce8f1020a1..2806b1dec8 100644 --- a/src/autocomplete/UserProvider.js +++ b/src/autocomplete/UserProvider.js @@ -2,7 +2,8 @@ /* Copyright 2016 Aviral Dasgupta Copyright 2017 Vector Creations Ltd -Copyright 2017 New Vector Ltd +Copyright 2017, 2018 New Vector Ltd +Copyright 2018 Michael Telatynski <7t3chguy@gmail.com> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,20 +24,20 @@ import AutocompleteProvider from './AutocompleteProvider'; import {PillCompletion} from './Components'; import sdk from '../index'; import FuzzyMatcher from './FuzzyMatcher'; -import _pull from 'lodash/pull'; import _sortBy from 'lodash/sortBy'; import MatrixClientPeg from '../MatrixClientPeg'; -import type {Room, RoomMember} from 'matrix-js-sdk'; +import type {MatrixEvent, Room, RoomMember, RoomState} from 'matrix-js-sdk'; import {makeUserPermalink} from "../matrix-to"; +import type {Completion, SelectionRange} from "./Autocompleter"; -const USER_REGEX = /@\S*/g; +const USER_REGEX = /\B@\S*/g; export default class UserProvider extends AutocompleteProvider { users: Array = null; room: Room = null; - constructor(room) { + constructor(room: Room) { super(USER_REGEX, { keys: ['name'], }); @@ -44,7 +45,7 @@ export default class UserProvider extends AutocompleteProvider { this.matcher = new FuzzyMatcher([], { keys: ['name', 'userId'], shouldMatchPrefix: true, - shouldMatchWordsOnly: false + shouldMatchWordsOnly: false, }); this._onRoomTimelineBound = this._onRoomTimeline.bind(this); @@ -61,7 +62,7 @@ export default class UserProvider extends AutocompleteProvider { } } - _onRoomTimeline(ev, room, toStartOfTimeline, removed, data) { + _onRoomTimeline(ev: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean, data: Object) { if (!room) return; if (removed) return; if (room.roomId !== this.room.roomId) return; @@ -77,7 +78,7 @@ export default class UserProvider extends AutocompleteProvider { this.onUserSpoke(ev.sender); } - _onRoomStateMember(ev, state, member) { + _onRoomStateMember(ev: MatrixEvent, state: RoomState, member: RoomMember) { // ignore members in other rooms if (member.roomId !== this.room.roomId) { return; @@ -87,7 +88,7 @@ export default class UserProvider extends AutocompleteProvider { this.users = null; } - async getCompletions(query: string, selection: {start: number, end: number}, force = false) { + async getCompletions(query: string, selection: SelectionRange, force?: boolean = false): Array { const MemberAvatar = sdk.getComponent('views.avatars.MemberAvatar'); // Disable autocompletions when composing commands because of various issues @@ -128,7 +129,7 @@ export default class UserProvider extends AutocompleteProvider { return completions; } - getName() { + getName(): string { return '👥 ' + _t('Users'); } @@ -141,13 +142,9 @@ export default class UserProvider extends AutocompleteProvider { } const currentUserId = MatrixClientPeg.get().credentials.userId; - this.users = this.room.getJoinedMembers().filter((member) => { - if (member.userId !== currentUserId) return true; - }); + this.users = this.room.getJoinedMembers().filter(({userId}) => userId !== currentUserId); - this.users = _sortBy(this.users, (member) => - 1E20 - lastSpoken[member.userId] || 1E20, - ); + this.users = _sortBy(this.users, (member) => 1E20 - lastSpoken[member.userId] || 1E20); this.matcher.setObjects(this.users); } diff --git a/src/components/structures/ContextualMenu.js b/src/components/structures/ContextualMenu.js index daac294d12..adc8dfd11c 100644 --- a/src/components/structures/ContextualMenu.js +++ b/src/components/structures/ContextualMenu.js @@ -1,5 +1,6 @@ /* 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. @@ -15,12 +16,10 @@ limitations under the License. */ -'use strict'; - -const classNames = require('classnames'); -const React = require('react'); -const ReactDOM = require('react-dom'); +import React from 'react'; +import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; // Shamelessly ripped off Modal.js. There's probably a better way // of doing reusable widgets like dialog boxes & menus where we go and @@ -61,6 +60,54 @@ export default class ContextualMenu extends React.Component { // If true, insert an invisible screen-sized element behind the // menu that when clicked will close it. hasBackground: PropTypes.bool, + + // The component to render as the context menu + elementClass: PropTypes.element.isRequired, + // on resize callback + windowResize: PropTypes.func, + // method to close menu + closeMenu: PropTypes.func, + }; + + constructor() { + super(); + this.state = { + contextMenuRect: null, + }; + + this.onContextMenu = this.onContextMenu.bind(this); + this.collectContextMenuRect = this.collectContextMenuRect.bind(this); + } + + collectContextMenuRect(element) { + // We don't need to clean up when unmounting, so ignore + if (!element) return; + + this.setState({ + contextMenuRect: element.getBoundingClientRect(), + }); + } + + onContextMenu(e) { + if (this.props.closeMenu) { + this.props.closeMenu(); + + e.preventDefault(); + const x = e.clientX; + const y = e.clientY; + + // XXX: This isn't pretty but the only way to allow opening a different context menu on right click whilst + // a context menu and its click-guard are up without completely rewriting how the context menus work. + setImmediate(() => { + const clickEvent = document.createEvent('MouseEvents'); + clickEvent.initMouseEvent( + 'contextmenu', true, true, window, 0, + 0, 0, x, y, false, false, + false, false, 0, null, + ); + document.elementFromPoint(x, y).dispatchEvent(clickEvent); + }); + } } render() { @@ -83,6 +130,9 @@ export default class ContextualMenu extends React.Component { chevronFace = 'right'; } + const contextMenuRect = this.state.contextMenuRect || null; + const padding = 10; + const chevronOffset = {}; if (props.chevronFace) { chevronFace = props.chevronFace; @@ -90,7 +140,19 @@ export default class ContextualMenu extends React.Component { if (chevronFace === 'top' || chevronFace === 'bottom') { chevronOffset.left = props.chevronOffset; } else { - chevronOffset.top = props.chevronOffset; + const target = position.top; + + // By default, no adjustment is made + let adjusted = target; + + // If we know the dimensions of the context menu, adjust its position + // such that it does not leave the (padded) window. + if (contextMenuRect) { + adjusted = Math.min(position.top, document.body.clientHeight - contextMenuRect.height - padding); + } + + position.top = adjusted; + chevronOffset.top = Math.max(props.chevronOffset, props.chevronOffset + target - adjusted); } // To override the default chevron colour, if it's been set @@ -112,7 +174,7 @@ export default class ContextualMenu extends React.Component { `; } - const chevron =
; + const chevron =
; const className = 'mx_ContextualMenu_wrapper'; const menuClasses = classNames({ @@ -154,17 +216,17 @@ export default class ContextualMenu extends React.Component { // FIXME: If a menu uses getDefaultProps it clobbers the onFinished // property set here so you can't close the menu from a button click! return
-
+
{ chevron }
- { props.hasBackground &&
} + { props.hasBackground &&
}
; } } -export function createMenu(ElementClass, props) { +export function createMenu(ElementClass, props, hasBackground=true) { const closeMenu = function(...args) { ReactDOM.unmountComponentAtNode(getOrCreateContainer()); @@ -175,8 +237,8 @@ export function createMenu(ElementClass, props) { // We only reference closeMenu once per call to createMenu const menu =
{ _t('Only people who have been invited') } @@ -1064,7 +1071,7 @@ export default React.createClass({
{ _t('Everyone') } @@ -1127,10 +1134,6 @@ export default React.createClass({ let avatarNode; let nameNode; let shortDescNode; - const bodyNodes = [ - this._getMembershipSection(), - this._getGroupSection(), - ]; const rightButtons = []; if (this.state.editing && this.state.isUserPrivileged) { let avatarImage; @@ -1207,6 +1210,7 @@ export default React.createClass({ shortDescNode = { summary.profile.short_description }; } } + if (this.state.editing) { rightButtons.push( , ); } + rightButtons.push( + + + , + ); if (this.props.collapsedRhs) { rightButtons.push(
- { bodyNodes } + { this._getMembershipSection() } + { this._getGroupSection() }
); diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js index 5acceb1009..ebe5d7f507 100644 --- a/src/components/structures/LeftPanel.js +++ b/src/components/structures/LeftPanel.js @@ -82,17 +82,26 @@ var LeftPanel = React.createClass({ _onKeyDown: function(ev) { if (!this.focusedElement) return; - let handled = false; + let handled = true; switch (ev.keyCode) { + case KeyCode.TAB: + this._onMoveFocus(ev.shiftKey); + break; case KeyCode.UP: this._onMoveFocus(true); - handled = true; break; case KeyCode.DOWN: this._onMoveFocus(false); - handled = true; break; + case KeyCode.ENTER: + this._onMoveFocus(false); + if (this.focusedElement) { + this.focusedElement.click(); + } + break; + default: + handled = false; } if (handled) { @@ -102,37 +111,33 @@ var LeftPanel = React.createClass({ }, _onMoveFocus: function(up) { - var element = this.focusedElement; + let element = this.focusedElement; // unclear why this isn't needed // var descending = (up == this.focusDirection) ? this.focusDescending : !this.focusDescending; // this.focusDirection = up; - var descending = false; // are we currently descending or ascending through the DOM tree? - var classes; + let descending = false; // are we currently descending or ascending through the DOM tree? + let classes; do { - var child = up ? element.lastElementChild : element.firstElementChild; - var sibling = up ? element.previousElementSibling : element.nextElementSibling; + const child = up ? element.lastElementChild : element.firstElementChild; + const sibling = up ? element.previousElementSibling : element.nextElementSibling; if (descending) { if (child) { element = child; - } - else if (sibling) { + } else if (sibling) { element = sibling; - } - else { + } else { descending = false; element = element.parentElement; } - } - else { + } else { if (sibling) { element = sibling; descending = true; - } - else { + } else { element = element.parentElement; } } @@ -144,8 +149,7 @@ var LeftPanel = React.createClass({ descending = true; } } - - } while(element && !( + } while (element && !( classes.contains("mx_RoomTile") || classes.contains("mx_SearchBox_search") || classes.contains("mx_RoomSubList_ellipsis"))); diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index bd8f66163f..293b0130d0 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -23,6 +23,7 @@ import PropTypes from 'prop-types'; import Matrix from "matrix-js-sdk"; import Analytics from "../../Analytics"; +import DecryptionFailureTracker from "../../DecryptionFailureTracker"; import MatrixClientPeg from "../../MatrixClientPeg"; import PlatformPeg from "../../PlatformPeg"; import SdkConfig from "../../SdkConfig"; @@ -1143,11 +1144,6 @@ export default React.createClass({ } else if (this._is_registered) { this._is_registered = false; - // Set the display name = user ID localpart - MatrixClientPeg.get().setDisplayName( - MatrixClientPeg.get().getUserIdLocalpart(), - ); - if (this.props.config.welcomeUserId && getCurrentLanguage().startsWith("en")) { createRoom({ dmUserId: this.props.config.welcomeUserId, @@ -1308,6 +1304,21 @@ export default React.createClass({ } }); + const dft = new DecryptionFailureTracker((total) => { + // TODO: Pass reason for failure as third argument to trackEvent + Analytics.trackEvent('E2E', 'Decryption failure', null, total); + }); + + // Shelved for later date when we have time to think about persisting history of + // tracked events across sessions. + // dft.loadTrackedEventHashMap(); + + dft.start(); + + // When logging out, stop tracking failures and destroy state + cli.on("Session.logged_out", () => dft.stop()); + cli.on("Event.decrypted", (e) => dft.eventDecrypted(e)); + const krh = new KeyRequestHandler(cli); cli.on("crypto.roomKeyRequest", (req) => { krh.handleKeyRequest(req); diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 50bdb37734..f5fa2ceabf 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -1,5 +1,6 @@ /* Copyright 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. @@ -25,6 +26,9 @@ import sdk from '../../index'; import MatrixClientPeg from '../../MatrixClientPeg'; +const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes +const continuedTypes = ['m.sticker', 'm.room.message']; + /* (almost) stateless UI component which builds the event tiles in the room timeline. */ module.exports = React.createClass({ @@ -189,7 +193,7 @@ module.exports = React.createClass({ /** * Page up/down. * - * mult: -1 to page up, +1 to page down + * @param {number} mult: -1 to page up, +1 to page down */ scrollRelative: function(mult) { if (this.refs.scrollPanel) { @@ -199,6 +203,8 @@ module.exports = React.createClass({ /** * Scroll up/down in response to a scroll key + * + * @param {KeyboardEvent} ev: the keyboard event to handle */ handleScrollKey: function(ev) { if (this.refs.scrollPanel) { @@ -257,6 +263,7 @@ module.exports = React.createClass({ this.eventNodes = {}; + let visible = false; let i; // first figure out which is the last event in the list which we're @@ -297,7 +304,7 @@ module.exports = React.createClass({ // if the readmarker has moved, cancel any active ghost. if (this.currentReadMarkerEventId && this.props.readMarkerEventId && this.props.readMarkerVisible && - this.currentReadMarkerEventId != this.props.readMarkerEventId) { + this.currentReadMarkerEventId !== this.props.readMarkerEventId) { this.currentGhostEventId = null; } @@ -404,8 +411,8 @@ module.exports = React.createClass({ let isVisibleReadMarker = false; - if (eventId == this.props.readMarkerEventId) { - var visible = this.props.readMarkerVisible; + if (eventId === this.props.readMarkerEventId) { + visible = this.props.readMarkerVisible; // if the read marker comes at the end of the timeline (except // for local echoes, which are excluded from RMs, because they @@ -423,11 +430,11 @@ module.exports = React.createClass({ // XXX: there should be no need for a ghost tile - we should just use a // a dispatch (user_activity_end) to start the RM animation. - if (eventId == this.currentGhostEventId) { + if (eventId === this.currentGhostEventId) { // if we're showing an animation, continue to show it. ret.push(this._getReadMarkerGhostTile()); } else if (!isVisibleReadMarker && - eventId == this.currentReadMarkerEventId) { + eventId === this.currentReadMarkerEventId) { // there is currently a read-up-to marker at this point, but no // more. Show an animation of it disappearing. ret.push(this._getReadMarkerGhostTile()); @@ -449,16 +456,17 @@ module.exports = React.createClass({ // Some events should appear as continuations from previous events of // different types. - const continuedTypes = ['m.sticker', 'm.room.message']; + const eventTypeContinues = prevEvent !== null && continuedTypes.includes(mxEv.getType()) && continuedTypes.includes(prevEvent.getType()); - if (prevEvent !== null - && prevEvent.sender && mxEv.sender - && mxEv.sender.userId === prevEvent.sender.userId - && (mxEv.getType() == prevEvent.getType() || eventTypeContinues)) { + // if there is a previous event and it has the same sender as this event + // and the types are the same/is in continuedTypes and the time between them is <= CONTINUATION_MAX_INTERVAL + if (prevEvent !== null && prevEvent.sender && mxEv.sender && mxEv.sender.userId === prevEvent.sender.userId && + (mxEv.getType() === prevEvent.getType() || eventTypeContinues) && + (mxEv.getTs() - prevEvent.getTs() <= CONTINUATION_MAX_INTERVAL)) { continuation = true; } @@ -493,7 +501,7 @@ module.exports = React.createClass({ } const eventId = mxEv.getId(); - const highlight = (eventId == this.props.highlightedEventId); + const highlight = (eventId === this.props.highlightedEventId); // we can't use local echoes as scroll tokens, because their event IDs change. // Local echos have a send "status". @@ -632,7 +640,8 @@ module.exports = React.createClass({ render: function() { const ScrollPanel = sdk.getComponent("structures.ScrollPanel"); const Spinner = sdk.getComponent("elements.Spinner"); - let topSpinner, bottomSpinner; + let topSpinner; + let bottomSpinner; if (this.props.backPaginating) { topSpinner =
  • ; } diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js index 7a93cfb886..edb50fcedb 100644 --- a/src/components/structures/MyGroups.js +++ b/src/components/structures/MyGroups.js @@ -70,7 +70,7 @@ export default withMatrixClient(React.createClass({ if (this.state.groups) { const groupNodes = []; this.state.groups.forEach((g) => { - groupNodes.push(); + groupNodes.push(); }); contentHeader = groupNodes.length > 0 ?

    { _t('Your Communities') }

    :
    ; content = groupNodes.length > 0 ? @@ -124,7 +124,7 @@ export default withMatrixClient(React.createClass({ ) }
    -
    + {/*
    @@ -140,7 +140,7 @@ export default withMatrixClient(React.createClass({ { 'i': (sub) => { sub } }) }
    -
    +
    */}
    { contentHeader } diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index 8034923158..9aa77e695a 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -25,6 +25,7 @@ import MatrixClientPeg from '../../MatrixClientPeg'; import MemberAvatar from '../views/avatars/MemberAvatar'; import Resend from '../../Resend'; import * as cryptodevices from '../../cryptodevices'; +import dis from '../../dispatcher'; const STATUS_BAR_HIDDEN = 0; const STATUS_BAR_EXPANDED = 1; @@ -157,10 +158,12 @@ module.exports = React.createClass({ _onResendAllClick: function() { Resend.resendUnsentEvents(this.props.room); + dis.dispatch({action: 'focus_composer'}); }, _onCancelAllClick: function() { Resend.cancelUnsentEvents(this.props.room); + dis.dispatch({action: 'focus_composer'}); }, _onShowDevicesClick: function() { @@ -305,7 +308,26 @@ module.exports = React.createClass({ }, ); } else { - if ( + let consentError = null; + for (const m of unsentMessages) { + if (m.error && m.error.errcode === 'M_CONSENT_NOT_GIVEN') { + consentError = m.error; + break; + } + } + if (consentError) { + title = _t( + "You can't send any messages until you review and agree to " + + "our terms and conditions.", + {}, + { + 'consentLink': (sub) => + + { sub } + , + }, + ); + } else if ( unsentMessages.length === 1 && unsentMessages[0].error && unsentMessages[0].error.data && @@ -329,11 +351,13 @@ module.exports = React.createClass({ return
    {_t("Warning")} -
    - { title } -
    -
    - { content } +
    +
    + { title } +
    +
    + { content } +
    ; }, @@ -350,11 +374,13 @@ module.exports = React.createClass({ return (
    /!\ -
    - { _t('Connectivity to the server has been lost.') } -
    -
    - { _t('Sent messages will be stored until your connection has returned.') } +
    +
    + { _t('Connectivity to the server has been lost.') } +
    +
    + { _t('Sent messages will be stored until your connection has returned.') } +
    ); diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index fb82ee067b..0c78043a42 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -1,6 +1,7 @@ /* -Copyright 2017 Vector Creations Ltd Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 Vector Creations 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. @@ -15,30 +16,25 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; - -var React = require('react'); -var ReactDOM = require('react-dom'); -var classNames = require('classnames'); -var sdk = require('../../index'); +import React from 'react'; +import classNames from 'classnames'; +import sdk from '../../index'; import { Droppable } from 'react-beautiful-dnd'; import { _t } from '../../languageHandler'; -var dis = require('../../dispatcher'); -var Unread = require('../../Unread'); -var MatrixClientPeg = require('../../MatrixClientPeg'); -var RoomNotifs = require('../../RoomNotifs'); -var FormattingUtils = require('../../utils/FormattingUtils'); -var AccessibleButton = require('../../components/views/elements/AccessibleButton'); -import Modal from '../../Modal'; +import dis from '../../dispatcher'; +import Unread from '../../Unread'; +import * as RoomNotifs from '../../RoomNotifs'; +import * as FormattingUtils from '../../utils/FormattingUtils'; import { KeyCode } from '../../Keyboard'; +import { Group } from 'matrix-js-sdk'; // turn this on for drop & drag console debugging galore -var debug = false; +const debug = false; const TRUNCATE_AT = 10; -var RoomSubList = React.createClass({ +const RoomSubList = React.createClass({ displayName: 'RoomSubList', debug: debug, @@ -77,8 +73,10 @@ var RoomSubList = React.createClass({ getDefaultProps: function() { return { - onHeaderClick: function() {}, // NOP - onShowMoreRooms: function() {}, // NOP + onHeaderClick: function() { + }, // NOP + onShowMoreRooms: function() { + }, // NOP extraTiles: [], isInvite: false, }; @@ -105,15 +103,17 @@ var RoomSubList = React.createClass({ applySearchFilter: function(list, filter) { if (filter === "") return list; - return list.filter((room) => { - return room.name && room.name.toLowerCase().indexOf(filter.toLowerCase()) >= 0 - }); + const lcFilter = filter.toLowerCase(); + // case insensitive if room name includes filter, + // or if starts with `#` and one of room's aliases starts with filter + return list.filter((room) => (room.name && room.name.toLowerCase().includes(lcFilter)) || + (filter[0] === '#' && room.getAliases().some((alias) => alias.toLowerCase().startsWith(lcFilter)))); }, // The header is collapsable if it is hidden or not stuck // The dataset elements are added in the RoomList _initAndPositionStickyHeaders method isCollapsableOnClick: function() { - var stuck = this.refs.header.dataset.stuck; + const stuck = this.refs.header.dataset.stuck; if (this.state.hidden || stuck === undefined || stuck === "none") { return true; } else { @@ -139,12 +139,12 @@ var RoomSubList = React.createClass({ onClick: function(ev) { if (this.isCollapsableOnClick()) { // The header isCollapsable, so the click is to be interpreted as collapse and truncation logic - var isHidden = !this.state.hidden; - this.setState({ hidden : isHidden }); + const isHidden = !this.state.hidden; + this.setState({hidden: isHidden}); if (isHidden) { // as good a way as any to reset the truncate state - this.setState({ truncateAt : TRUNCATE_AT }); + this.setState({truncateAt: TRUNCATE_AT}); } this.props.onShowMoreRooms(); @@ -159,7 +159,7 @@ var RoomSubList = React.createClass({ dis.dispatch({ action: 'view_room', room_id: roomId, - clear_search: (ev && (ev.keyCode == KeyCode.ENTER || ev.keyCode == KeyCode.SPACE)), + clear_search: (ev && (ev.keyCode === KeyCode.ENTER || ev.keyCode === KeyCode.SPACE)), }); }, @@ -169,17 +169,17 @@ var RoomSubList = React.createClass({ }, _shouldShowMentionBadge: function(roomNotifState) { - return roomNotifState != RoomNotifs.MUTE; + return roomNotifState !== RoomNotifs.MUTE; }, /** * Total up all the notification counts from the rooms * - * @param {Number} If supplied will only total notifications for rooms outside the truncation number + * @param {Number} truncateAt If supplied will only total notifications for rooms outside the truncation number * @returns {Array} The array takes the form [total, highlight] where highlight is a bool */ roomNotificationCount: function(truncateAt) { - var self = this; + const self = this; if (this.props.isInvite) { return [0, true]; @@ -187,9 +187,9 @@ var RoomSubList = React.createClass({ return this.props.list.reduce(function(result, room, index) { if (truncateAt === undefined || index >= truncateAt) { - var roomNotifState = RoomNotifs.getRoomNotifsState(room.roomId); - var highlight = room.getUnreadNotificationCount('highlight') > 0; - var notificationCount = room.getUnreadNotificationCount(); + const roomNotifState = RoomNotifs.getRoomNotifsState(room.roomId); + const highlight = room.getUnreadNotificationCount('highlight') > 0; + const notificationCount = room.getUnreadNotificationCount(); const notifBadges = notificationCount > 0 && self._shouldShowNotifBadge(roomNotifState); const mentionBadges = highlight && self._shouldShowMentionBadge(roomNotifState); @@ -238,38 +238,83 @@ var RoomSubList = React.createClass({ }); }, + _onNotifBadgeClick: function(e) { + // prevent the roomsublist collapsing + e.preventDefault(); + e.stopPropagation(); + // find first room which has notifications and switch to it + for (const room of this.state.sortedList) { + const roomNotifState = RoomNotifs.getRoomNotifsState(room.roomId); + const highlight = room.getUnreadNotificationCount('highlight') > 0; + const notificationCount = room.getUnreadNotificationCount(); + + const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge(roomNotifState); + const mentionBadges = highlight && this._shouldShowMentionBadge(roomNotifState); + + if (notifBadges || mentionBadges) { + dis.dispatch({ + action: 'view_room', + room_id: room.roomId, + }); + return; + } + } + }, + + _onInviteBadgeClick: function(e) { + // prevent the roomsublist collapsing + e.preventDefault(); + e.stopPropagation(); + // switch to first room in sortedList as that'll be the top of the list for the user + if (this.state.sortedList && this.state.sortedList.length > 0) { + dis.dispatch({ + action: 'view_room', + room_id: this.state.sortedList[0].roomId, + }); + } else if (this.props.extraTiles && this.props.extraTiles.length > 0) { + // Group Invites are different in that they are all extra tiles and not rooms + // XXX: this is a horrible special case because Group Invite sublist is a hack + if (this.props.extraTiles[0].props && this.props.extraTiles[0].props.group instanceof Group) { + dis.dispatch({ + action: 'view_group', + group_id: this.props.extraTiles[0].props.group.groupId, + }); + } + } + }, + _getHeaderJsx: function() { - var TintableSvg = sdk.getComponent("elements.TintableSvg"); + const subListNotifications = this.roomNotificationCount(); + const subListNotifCount = subListNotifications[0]; + const subListNotifHighlight = subListNotifications[1]; - var subListNotifications = this.roomNotificationCount(); - var subListNotifCount = subListNotifications[0]; - var subListNotifHighlight = subListNotifications[1]; + const totalTiles = this.props.list.length + (this.props.extraTiles || []).length; + const roomCount = totalTiles > 0 ? totalTiles : ''; - var totalTiles = this.props.list.length + (this.props.extraTiles || []).length; - var roomCount = totalTiles > 0 ? totalTiles : ''; - - var chevronClasses = classNames({ + const chevronClasses = classNames({ 'mx_RoomSubList_chevron': true, 'mx_RoomSubList_chevronRight': this.state.hidden, 'mx_RoomSubList_chevronDown': !this.state.hidden, }); - var badgeClasses = classNames({ + const badgeClasses = classNames({ 'mx_RoomSubList_badge': true, 'mx_RoomSubList_badgeHighlight': subListNotifHighlight, }); - var badge; + let badge; if (subListNotifCount > 0) { - badge =
    { FormattingUtils.formatCount(subListNotifCount) }
    ; + badge =
    + { FormattingUtils.formatCount(subListNotifCount) } +
    ; } else if (this.props.isInvite) { // no notifications but highlight anyway because this is an invite badge - badge =
    !
    ; + badge =
    !
    ; } // When collapsed, allow a long hover on the header to show user // the full tag name and room count - var title; + let title; if (this.props.collapsed) { title = this.props.label; if (roomCount !== '') { @@ -277,63 +322,66 @@ var RoomSubList = React.createClass({ } } - var incomingCall; + let incomingCall; if (this.props.incomingCall) { - var self = this; + const self = this; // Check if the incoming call is for this section - var incomingCallRoom = this.props.list.filter(function(room) { + const incomingCallRoom = this.props.list.filter(function(room) { return self.props.incomingCall.roomId === room.roomId; }); if (incomingCallRoom.length === 1) { - var IncomingCallBox = sdk.getComponent("voip.IncomingCallBox"); - incomingCall = ; + const IncomingCallBox = sdk.getComponent("voip.IncomingCallBox"); + incomingCall = + ; } } - var tabindex = this.props.searchFilter === "" ? "0" : "-1"; + const tabindex = this.props.searchFilter === "" ? "0" : "-1"; + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); return ( -
    - - { this.props.collapsed ? '' : this.props.label } -
    { roomCount }
    -
    - { badge } - { incomingCall } +
    + + {this.props.collapsed ? '' : this.props.label} +
    {roomCount}
    +
    + {badge} + {incomingCall}
    ); }, _createOverflowTile: function(overflowCount, totalCount) { - var content =
    ; + let content =
    ; - var overflowNotifications = this.roomNotificationCount(TRUNCATE_AT); - var overflowNotifCount = overflowNotifications[0]; - var overflowNotifHighlight = overflowNotifications[1]; + const overflowNotifications = this.roomNotificationCount(TRUNCATE_AT); + const overflowNotifCount = overflowNotifications[0]; + const overflowNotifHighlight = overflowNotifications[1]; if (overflowNotifCount && !this.props.collapsed) { content = FormattingUtils.formatCount(overflowNotifCount); } - var badgeClasses = classNames({ + const badgeClasses = classNames({ 'mx_RoomSubList_moreBadge': true, 'mx_RoomSubList_moreBadgeNotify': overflowNotifCount && !this.props.collapsed, 'mx_RoomSubList_moreBadgeHighlight': overflowNotifHighlight && !this.props.collapsed, }); + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); return ( -
    -
    { _t("more") }
    -
    { content }
    +
    +
    {_t("more")}
    +
    {content}
    ); }, _showFullMemberList: function() { this.setState({ - truncateAt: -1 + truncateAt: -1, }); this.props.onShowMoreRooms(); @@ -341,37 +389,39 @@ var RoomSubList = React.createClass({ }, render: function() { - var connectDropTarget = this.props.connectDropTarget; - var TruncatedList = sdk.getComponent('elements.TruncatedList'); - - var label = this.props.collapsed ? null : this.props.label; + const TruncatedList = sdk.getComponent('elements.TruncatedList'); let content; - if (this.state.sortedList.length === 0 && !this.props.searchFilter && this.props.extraTiles.length === 0) { - content = this.props.emptyContent; + if (this.state.sortedList.length === 0 && this.props.extraTiles.length === 0) { + // if no search filter is applied and there is a placeholder defined then show it, otherwise show nothing + if (!this.props.searchFilter && this.props.emptyContent) { + content = this.props.emptyContent; + } else { + // don't show an empty sublist + return null; + } } else { content = this.makeRoomTiles(); content.push(...this.props.extraTiles); } if (this.state.sortedList.length > 0 || this.props.extraTiles.length > 0 || this.props.editable) { - var subList; - var classes = "mx_RoomSubList"; + let subList; + const classes = "mx_RoomSubList"; if (!this.state.hidden) { - subList = - { content } - ; - } - else { - subList = - ; + subList = + {content} + ; + } else { + subList = + ; } const subListContent =
    - { this._getHeaderJsx() } - { subList } + {this._getHeaderJsx()} + {subList}
    ; return this.props.editable ? @@ -379,23 +429,26 @@ var RoomSubList = React.createClass({ droppableId={"room-sub-list-droppable_" + this.props.tagName} type="draggable-RoomTile" > - { (provided, snapshot) => ( + {(provided, snapshot) => (
    - { subListContent } + {subListContent}
    - ) } + )} : subListContent; - } - else { - var Loader = sdk.getComponent("elements.Spinner"); + } else { + const Loader = sdk.getComponent("elements.Spinner"); + if (this.props.showSpinner) { + content = ; + } + return (
    - { this.props.alwaysShowHeader ? this._getHeaderJsx() : undefined } - { (this.props.showSpinner && !this.state.hidden) ? : undefined } + {this.props.alwaysShowHeader ? this._getHeaderJsx() : undefined} + { this.state.hidden ? undefined : content }
    ); } - } + }, }); module.exports = RoomSubList; diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 97e575ff4e..98d700e0a0 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1,6 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations 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. @@ -45,6 +46,7 @@ import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard'; import RoomViewStore from '../../stores/RoomViewStore'; import RoomScrollStateStore from '../../stores/RoomScrollStateStore'; import SettingsStore, {SettingLevel} from "../../settings/SettingsStore"; +import WidgetUtils from '../../utils/WidgetUtils'; const DEBUG = false; let debuglog = function() {}; @@ -317,14 +319,7 @@ module.exports = React.createClass({ return false; } - const appsStateEvents = room.currentState.getStateEvents('im.vector.modular.widgets'); - // any valid widget = show apps - for (let i = 0; i < appsStateEvents.length; i++) { - if (appsStateEvents[i].getContent().type && appsStateEvents[i].getContent().url) { - return true; - } - } - return false; + return WidgetUtils.getRoomWidgets(room).length > 0; }, componentDidMount: function() { @@ -618,9 +613,11 @@ module.exports = React.createClass({ } }, - _updatePreviewUrlVisibility: function(room) { + _updatePreviewUrlVisibility: function({roomId}) { + // URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit + const key = MatrixClientPeg.get().isRoomEncrypted(roomId) ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled'; this.setState({ - showUrlPreview: SettingsStore.getValue("urlPreviewsEnabled", room.roomId), + showUrlPreview: SettingsStore.getValue(key, roomId), }); }, @@ -645,19 +642,23 @@ module.exports = React.createClass({ }, onAccountData: function(event) { - if (event.getType() === "org.matrix.preview_urls" && this.state.room) { + const type = event.getType(); + if ((type === "org.matrix.preview_urls" || type === "im.vector.web.settings") && this.state.room) { + // non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls` this._updatePreviewUrlVisibility(this.state.room); } }, onRoomAccountData: function(event, room) { if (room.roomId == this.state.roomId) { - if (event.getType() === "org.matrix.room.color_scheme") { + const type = event.getType(); + if (type === "org.matrix.room.color_scheme") { const color_scheme = event.getContent(); // XXX: we should validate the event console.log("Tinter.tint from onRoomAccountData"); Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color); - } else if (event.getType() === "org.matrix.room.preview_urls") { + } 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` this._updatePreviewUrlVisibility(room); } } @@ -913,6 +914,8 @@ module.exports = React.createClass({ }, uploadFile: async function(file) { + dis.dispatch({action: 'focus_composer'}); + if (MatrixClientPeg.get().isGuest()) { dis.dispatch({action: 'view_set_mxid'}); return; diff --git a/src/components/structures/TagPanel.js b/src/components/structures/TagPanel.js index 0b6dc9fc75..652211595b 100644 --- a/src/components/structures/TagPanel.js +++ b/src/components/structures/TagPanel.js @@ -1,5 +1,5 @@ /* -Copyright 2017 New Vector Ltd. +Copyright 2017, 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. @@ -26,6 +26,7 @@ import dis from '../../dispatcher'; import { _t } from '../../languageHandler'; import { Droppable } from 'react-beautiful-dnd'; +import classNames from 'classnames'; const TagPanel = React.createClass({ displayName: 'TagPanel', @@ -84,7 +85,10 @@ const TagPanel = React.createClass({ }, onMouseDown(e) { - dis.dispatch({action: 'deselect_tags'}); + // only dispatch if its not a no-op + if (this.state.selectedTags.length > 0) { + dis.dispatch({action: 'deselect_tags'}); + } }, onCreateGroupClick(ev) { @@ -113,17 +117,26 @@ const TagPanel = React.createClass({ />; }); - const clearButton = this.state.selectedTags.length > 0 ? - : -
    ; + const itemsSelected = this.state.selectedTags.length > 0; - return
    - + let clearButton; + if (itemsSelected) { + clearButton = + + ; + } + + const classes = classNames('mx_TagPanel', { + mx_TagPanel_items_selected: itemsSelected, + }); + + return
    +
    { clearButton } - +
    { device.label }); }, + _setAudioOutput: function(deviceId) { + this.setState({activeAudioOutput: deviceId}); + CallMediaHandler.setAudioOutput(deviceId); + }, + _setAudioInput: function(deviceId) { this.setState({activeAudioInput: deviceId}); CallMediaHandler.setAudioInput(deviceId); @@ -1016,6 +1021,7 @@ module.exports = React.createClass({ const Dropdown = sdk.getComponent('elements.Dropdown'); + let speakerDropdown =

    { _t('No Audio Outputs detected') }

    ; let microphoneDropdown =

    { _t('No Microphones detected') }

    ; let webcamDropdown =

    { _t('No Webcams detected') }

    ; @@ -1024,6 +1030,26 @@ module.exports = React.createClass({ label: _t('Default Device'), }; + const audioOutputs = this.state.mediaDevices.audiooutput.slice(0); + if (audioOutputs.length > 0) { + let defaultOutput = ''; + if (!audioOutputs.some((input) => input.deviceId === 'default')) { + audioOutputs.unshift(defaultOption); + } else { + defaultOutput = 'default'; + } + + speakerDropdown =
    +

    { _t('Audio Output') }

    + + { this._mapWebRtcDevicesToSpans(audioOutputs) } + +
    ; + } + const audioInputs = this.state.mediaDevices.audioinput.slice(0); if (audioInputs.length > 0) { let defaultInput = ''; @@ -1065,8 +1091,9 @@ module.exports = React.createClass({ } return
    - { microphoneDropdown } - { webcamDropdown } + { speakerDropdown } + { microphoneDropdown } + { webcamDropdown }
    ; }, @@ -1080,6 +1107,14 @@ module.exports = React.createClass({
    ; }, + onSelfShareClick: function() { + const cli = MatrixClientPeg.get(); + const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); + Modal.createTrackedDialog('share self dialog', '', ShareDialog, { + target: cli.getUser(this._me), + }); + }, + _showSpoiler: function(event) { const target = event.target; target.innerHTML = target.getAttribute('data-spoiler'); @@ -1301,10 +1336,13 @@ module.exports = React.createClass({
    - { _t("Logged in as:") } { this._me } + { _t("Logged in as:") + ' ' } + + { this._me } +
    - { _t('Access Token:') } + { _t('Access Token:') + ' ' } diff --git a/src/components/structures/login/ForgotPassword.js b/src/components/structures/login/ForgotPassword.js index ca50b9db6e..7e0cd5da8e 100644 --- a/src/components/structures/login/ForgotPassword.js +++ b/src/components/structures/login/ForgotPassword.js @@ -1,6 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017 New Vector Ltd +Copyright 2017, 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. @@ -15,8 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; - import React from 'react'; import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; @@ -45,6 +43,8 @@ module.exports = React.createClass({ enteredHomeserverUrl: this.props.customHsUrl || this.props.defaultHsUrl, enteredIdentityServerUrl: this.props.customIsUrl || this.props.defaultIsUrl, progress: null, + password: null, + password2: null, }; }, @@ -103,7 +103,7 @@ module.exports = React.createClass({
    , button: _t('Continue'), extraButtons: [ - , @@ -169,7 +169,8 @@ module.exports = React.createClass({ } else if (this.state.progress === "sent_email") { resetPasswordJsx = (
    - { _t("An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.", { emailAddress: this.state.email }) } + { _t("An email has been sent to %(emailAddress)s. Once you've followed the link it contains, " + + "click below.", { emailAddress: this.state.email }) }
    @@ -179,14 +180,15 @@ module.exports = React.createClass({ resetPasswordJsx = (

    { _t('Your password has been reset') }.

    -

    { _t('You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device') }.

    +

    { _t('You have been logged out of all devices and will no longer receive push notifications. ' + + 'To re-enable notifications, sign in again on each device') }.

    ); } else { let serverConfigSection; - if (!SdkConfig.get().disable_custom_urls) { + if (!SdkConfig.get()['disable_custom_urls']) { serverConfigSection = (
    @@ -233,6 +237,7 @@ module.exports = React.createClass({ { _t('Create an account') } +
    diff --git a/src/components/structures/login/LanguageSelector.js b/src/components/structures/login/LanguageSelector.js new file mode 100644 index 0000000000..965d8334d9 --- /dev/null +++ b/src/components/structures/login/LanguageSelector.js @@ -0,0 +1,38 @@ +/* +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 SdkConfig from "../../../SdkConfig"; +import {getCurrentLanguage} from "../../../languageHandler"; +import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; +import PlatformPeg from "../../../PlatformPeg"; +import sdk from '../../../index'; +import React from 'react'; + +function onChange(newLang) { + if (getCurrentLanguage() !== newLang) { + SettingsStore.setValue("language", null, SettingLevel.DEVICE, newLang); + PlatformPeg.get().reload(); + } +} + +export default function LanguageSelector() { + if (SdkConfig.get()['disable_login_language_selector']) return
    ; + + const LanguageDropdown = sdk.getComponent('views.elements.LanguageDropdown'); + return
    + +
    ; +} diff --git a/src/components/structures/login/Login.js b/src/components/structures/login/Login.js index 1332f52f97..43264e7003 100644 --- a/src/components/structures/login/Login.js +++ b/src/components/structures/login/Login.js @@ -1,6 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations 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. @@ -20,15 +21,13 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; -import * as languageHandler from '../../../languageHandler'; import sdk from '../../../index'; import Login from '../../../Login'; -import PlatformPeg from '../../../PlatformPeg'; import SdkConfig from '../../../SdkConfig'; -import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; +import SettingsStore from "../../../settings/SettingsStore"; // For validating phone numbers without country codes -const PHONE_NUMBER_REGEX = /^[0-9\(\)\-\s]*$/; +const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/; /** * A wire component which glues together login UI components and Login logic @@ -94,6 +93,13 @@ module.exports = React.createClass({ this._unmounted = true; }, + onPasswordLoginError: function(errorText) { + this.setState({ + errorText, + loginIncorrect: Boolean(errorText), + }); + }, + onPasswordLogin: function(username, phoneCountry, phoneNumber, password) { this.setState({ busy: true, @@ -113,10 +119,10 @@ module.exports = React.createClass({ // Some error strings only apply for logging in const usingEmail = username.indexOf("@") > 0; - if (error.httpStatus == 400 && usingEmail) { + if (error.httpStatus === 400 && usingEmail) { errorText = _t('This Home Server does not support login using email address.'); } else if (error.httpStatus === 401 || error.httpStatus === 403) { - if (SdkConfig.get().disable_custom_urls) { + if (SdkConfig.get()['disable_custom_urls']) { errorText = (
    { _t('Incorrect username and/or password.') }
    @@ -143,7 +149,7 @@ module.exports = React.createClass({ // but the login API gives a 403 https://matrix.org/jira/browse/SYN-744 // mentions this (although the bug is for UI auth which is not this) // We treat both as an incorrect password - loginIncorrect: error.httpStatus === 401 || error.httpStatus == 403, + loginIncorrect: error.httpStatus === 401 || error.httpStatus === 403, }); }).finally(() => { if (this._unmounted) { @@ -231,7 +237,7 @@ module.exports = React.createClass({ hsUrl = hsUrl || this.state.enteredHomeserverUrl; isUrl = isUrl || this.state.enteredIdentityServerUrl; - const fallbackHsUrl = hsUrl == this.props.defaultHsUrl ? this.props.fallbackHsUrl : null; + const fallbackHsUrl = hsUrl === this.props.defaultHsUrl ? this.props.fallbackHsUrl : null; const loginLogic = new Login(hsUrl, isUrl, fallbackHsUrl, { defaultDeviceDisplayName: this.props.defaultDeviceDisplayName, @@ -310,19 +316,27 @@ module.exports = React.createClass({ !this.state.enteredHomeserverUrl.startsWith("http")) ) { errorText = - { - _t("Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " + - "Either use HTTPS or enable unsafe scripts.", - {}, - { 'a': (sub) => { return { sub }; } }, + { _t("Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " + + "Either use HTTPS or enable unsafe scripts.", {}, + { + 'a': (sub) => { + return + { sub } + ; + }, + }, ) } ; } else { errorText = - { - _t("Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.", - {}, - { 'a': (sub) => { return { sub }; } }, + { _t("Can't connect to homeserver - please check your connectivity, ensure your " + + "homeserver's SSL certificate is trusted, and that a browser extension " + + "is not blocking requests.", {}, + { + 'a': (sub) => { + return { sub }; + }, + }, ) } ; } @@ -350,6 +364,7 @@ module.exports = React.createClass({ return ( - -
    ; - }, - render: function() { const Loader = sdk.getComponent("elements.Spinner"); const LoginPage = sdk.getComponent("login.LoginPage"); @@ -403,21 +401,10 @@ module.exports = React.createClass({ ; } - let returnToAppJsx; - /* - // with the advent of ILAG I don't think we need this any more - if (this.props.onCancelClick) { - returnToAppJsx = - - { _t('Return to app') } - ; - } - */ - let serverConfig; let header; - if (!SdkConfig.get().disable_custom_urls) { + if (!SdkConfig.get()['disable_custom_urls']) { serverConfig =
    @@ -460,8 +449,7 @@ module.exports = React.createClass({ { _t('Create an account') } { loginAsGuestJsx } - { returnToAppJsx } - { !SdkConfig.get().disable_login_language_selector ? this._renderLanguageSetting() : '' } +
    diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index 62a3ee4f68..462063406f 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -1,6 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations 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. @@ -22,7 +23,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import sdk from '../../../index'; -import ServerConfig from '../../views/login/ServerConfig'; import MatrixClientPeg from '../../../MatrixClientPeg'; import RegistrationForm from '../../views/login/RegistrationForm'; import RtsClient from '../../../RtsClient'; @@ -62,6 +62,12 @@ module.exports = React.createClass({ onLoginClick: PropTypes.func.isRequired, onCancelClick: PropTypes.func, onServerConfigChange: PropTypes.func.isRequired, + + rtsClient: PropTypes.shape({ + getTeamsConfig: PropTypes.func.isRequired, + trackReferral: PropTypes.func.isRequired, + getTeam: PropTypes.func.isRequired, + }), }, getInitialState: function() { @@ -133,7 +139,7 @@ module.exports = React.createClass({ newState.isUrl = config.isUrl; } this.props.onServerConfigChange(config); - this.setState(newState, function() { + this.setState(newState, () => { this._replaceClient(); }); }, @@ -159,11 +165,11 @@ module.exports = React.createClass({ let msg = response.message || response.toString(); // can we give a better error message? if (response.required_stages && response.required_stages.indexOf('m.login.msisdn') > -1) { - let msisdn_available = false; + let msisdnAvailable = false; for (const flow of response.available_flows) { - msisdn_available |= flow.stages.indexOf('m.login.msisdn') > -1; + msisdnAvailable |= flow.stages.indexOf('m.login.msisdn') > -1; } - if (!msisdn_available) { + if (!msisdnAvailable) { msg = _t('This server does not support authentication with a phone number.'); } } @@ -242,7 +248,7 @@ module.exports = React.createClass({ return matrixClient.getPushers().then((resp)=>{ const pushers = resp.pushers; for (let i = 0; i < pushers.length; ++i) { - if (pushers[i].kind == 'email') { + if (pushers[i].kind === 'email') { const emailPusher = pushers[i]; emailPusher.data = { brand: this.props.brand }; matrixClient.setPusher(emailPusher).done(() => { @@ -267,7 +273,7 @@ module.exports = React.createClass({ errMsg = _t('Passwords don\'t match.'); break; case "RegistrationForm.ERR_PASSWORD_LENGTH": - errMsg = _t('Password too short (min %(MIN_PASSWORD_LENGTH)s).', {MIN_PASSWORD_LENGTH: MIN_PASSWORD_LENGTH}); + errMsg = _t('Password too short (min %(MIN_PASSWORD_LENGTH)s).', {MIN_PASSWORD_LENGTH}); break; case "RegistrationForm.ERR_EMAIL_INVALID": errMsg = _t('This doesn\'t look like a valid email address.'); @@ -353,7 +359,7 @@ module.exports = React.createClass({ registerBody = ; } else { let serverConfigSection; - if (!SdkConfig.get().disable_custom_urls) { + if (!SdkConfig.get()['disable_custom_urls']) { serverConfigSection = ( - { _t('Return to app') } - - ); - } - */ - let header; let errorText; // FIXME: remove hardcoded Status team tweaks at some point @@ -418,6 +412,8 @@ module.exports = React.createClass({ ); } + const LanguageSelector = sdk.getComponent('structures.login.LanguageSelector'); + return (
    @@ -431,7 +427,7 @@ module.exports = React.createClass({ { registerBody } { signIn } { errorText } - { returnToAppJsx } +
    diff --git a/src/components/views/context_menus/GroupInviteTileContextMenu.js b/src/components/views/context_menus/GroupInviteTileContextMenu.js new file mode 100644 index 0000000000..e30acca16d --- /dev/null +++ b/src/components/views/context_menus/GroupInviteTileContextMenu.js @@ -0,0 +1,87 @@ +/* +Copyright 2018 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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import sdk from '../../../index'; +import { _t } from '../../../languageHandler'; +import Modal from '../../../Modal'; +import {Group} from 'matrix-js-sdk'; +import GroupStore from "../../../stores/GroupStore"; + +export default class GroupInviteTileContextMenu extends React.Component { + static propTypes = { + group: PropTypes.instanceOf(Group).isRequired, + /* callback called when the menu is dismissed */ + onFinished: PropTypes.func, + }; + + constructor(props, context) { + super(props, context); + + this._onClickReject = this._onClickReject.bind(this); + } + + componentWillMount() { + this._unmounted = false; + } + + componentWillUnmount() { + this._unmounted = true; + } + + _onClickReject() { + const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog'); + Modal.createTrackedDialog('Reject community invite', '', QuestionDialog, { + title: _t('Reject invitation'), + description: _t('Are you sure you want to reject the invitation?'), + onFinished: async (shouldLeave) => { + if (!shouldLeave) return; + + // FIXME: controller shouldn't be loading a view :( + const Loader = sdk.getComponent("elements.Spinner"); + const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner'); + + try { + await GroupStore.leaveGroup(this.props.group.groupId); + } catch (e) { + console.error("Error rejecting community invite: ", e); + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createTrackedDialog('Error rejecting invite', '', ErrorDialog, { + title: _t("Error"), + description: _t("Unable to reject invite"), + }); + } finally { + modal.close(); + } + }, + }); + + // Close the context menu + if (this.props.onFinished) { + this.props.onFinished(); + } + } + + render() { + return
    +
    + + { _t('Reject') } +
    +
    ; + } +} diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 99ec493ced..4802244c74 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -15,10 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; - import React from 'react'; import PropTypes from 'prop-types'; +import {EventStatus} from 'matrix-js-sdk'; import MatrixClientPeg from '../../../MatrixClientPeg'; import dis from '../../../dispatcher'; @@ -184,6 +183,15 @@ module.exports = React.createClass({ this.closeMenu(); }, + onPermalinkClick: function(e: Event) { + e.preventDefault(); + const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); + Modal.createTrackedDialog('share room message dialog', '', ShareDialog, { + target: this.props.mxEvent, + }); + this.closeMenu(); + }, + onReplyClick: function() { dis.dispatch({ action: 'reply_to_event', @@ -211,7 +219,10 @@ module.exports = React.createClass({ let replyButton; let collapseReplyThread; - if (eventStatus === 'not_sent') { + // status is SENT before remote-echo, null after + const isSent = !eventStatus || eventStatus === EventStatus.SENT; + + if (eventStatus === EventStatus.NOT_SENT) { resendButton = (
    { _t('Resend') } @@ -219,7 +230,7 @@ module.exports = React.createClass({ ); } - if (!eventStatus && this.state.canRedact) { + if (isSent && this.state.canRedact) { redactButton = (
    { _t('Remove') } @@ -227,7 +238,7 @@ module.exports = React.createClass({ ); } - if (eventStatus === "queued" || eventStatus === "not_sent") { + if (eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT) { cancelButton = (
    { _t('Cancel Sending') } @@ -235,7 +246,7 @@ module.exports = React.createClass({ ); } - if (!eventStatus && this.props.mxEvent.getType() === 'm.room.message') { + if (isSent && this.props.mxEvent.getType() === 'm.room.message') { const content = this.props.mxEvent.getContent(); if (content.msgtype && content.msgtype !== 'm.bad.encrypted' && content.hasOwnProperty('body')) { forwardButton = ( @@ -244,13 +255,11 @@ module.exports = React.createClass({
    ); - if (SettingsStore.isFeatureEnabled("feature_rich_quoting")) { - replyButton = ( -
    - { _t('Reply') } -
    - ); - } + replyButton = ( +
    + { _t('Reply') } +
    + ); if (this.state.canPin) { pinButton = ( @@ -290,7 +299,7 @@ module.exports = React.createClass({ const permalinkButton = (
    { _t('Permalink') } + target="_blank" rel="noopener" onClick={this.onPermalinkClick}>{ _t('Share Message') }
    ); diff --git a/src/components/views/dialogs/AddressPickerDialog.js b/src/components/views/dialogs/AddressPickerDialog.js index 0d0b7456b5..abc52f7b1d 100644 --- a/src/components/views/dialogs/AddressPickerDialog.js +++ b/src/components/views/dialogs/AddressPickerDialog.js @@ -1,6 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017 New Vector Ltd +Copyright 2017, 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. @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import { _t } from '../../../languageHandler'; +import { _t, _td } from '../../../languageHandler'; import sdk from '../../../index'; import MatrixClientPeg from '../../../MatrixClientPeg'; import Promise from 'bluebird'; @@ -27,6 +27,13 @@ import GroupStore from '../../../stores/GroupStore'; const TRUNCATE_QUERY_LIST = 40; const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200; +const addressTypeName = { + 'mx-user-id': _td("Matrix ID"), + 'mx-room-id': _td("Matrix Room ID"), + 'email': _td("email address"), +}; + + module.exports = React.createClass({ displayName: "AddressPickerDialog", @@ -66,7 +73,7 @@ module.exports = React.createClass({ // List of UserAddressType objects representing // the list of addresses we're going to invite - userList: [], + selectedList: [], // Whether a search is ongoing busy: false, @@ -76,10 +83,9 @@ module.exports = React.createClass({ serverSupportsUserDirectory: true, // The query being searched for query: "", - // List of UserAddressType objects representing - // the set of auto-completion results for the current search - // query. - queryList: [], + // List of UserAddressType objects representing the set of + // auto-completion results for the current search query. + suggestedList: [], }; }, @@ -91,14 +97,14 @@ module.exports = React.createClass({ }, onButtonClick: function() { - let userList = this.state.userList.slice(); + let selectedList = this.state.selectedList.slice(); // Check the text input field to see if user has an unconverted address - // If there is and it's valid add it to the local userList + // If there is and it's valid add it to the local selectedList if (this.refs.textinput.value !== '') { - userList = this._addInputToList(); - if (userList === null) return; + selectedList = this._addInputToList(); + if (selectedList === null) return; } - this.props.onFinished(true, userList); + this.props.onFinished(true, selectedList); }, onCancel: function() { @@ -118,18 +124,18 @@ module.exports = React.createClass({ e.stopPropagation(); e.preventDefault(); if (this.addressSelector) this.addressSelector.moveSelectionDown(); - } else if (this.state.queryList.length > 0 && (e.keyCode === 188 || e.keyCode === 13 || e.keyCode === 9)) { // comma or enter or tab + } else if (this.state.suggestedList.length > 0 && (e.keyCode === 188 || e.keyCode === 13 || e.keyCode === 9)) { // comma or enter or tab e.stopPropagation(); e.preventDefault(); if (this.addressSelector) this.addressSelector.chooseSelection(); - } else if (this.refs.textinput.value.length === 0 && this.state.userList.length && e.keyCode === 8) { // backspace + } else if (this.refs.textinput.value.length === 0 && this.state.selectedList.length && e.keyCode === 8) { // backspace e.stopPropagation(); e.preventDefault(); - this.onDismissed(this.state.userList.length - 1)(); + this.onDismissed(this.state.selectedList.length - 1)(); } else if (e.keyCode === 13) { // enter e.stopPropagation(); e.preventDefault(); - if (this.refs.textinput.value == '') { + if (this.refs.textinput.value === '') { // if there's nothing in the input box, submit the form this.onButtonClick(); } else { @@ -148,7 +154,7 @@ module.exports = React.createClass({ clearTimeout(this.queryChangedDebouncer); } // Only do search if there is something to search - if (query.length > 0 && query != '@' && query.length >= 2) { + if (query.length > 0 && query !== '@' && query.length >= 2) { this.queryChangedDebouncer = setTimeout(() => { if (this.props.pickerType === 'user') { if (this.props.groupId) { @@ -170,7 +176,7 @@ module.exports = React.createClass({ }, QUERY_USER_DIRECTORY_DEBOUNCE_MS); } else { this.setState({ - queryList: [], + suggestedList: [], query: "", searchError: null, }); @@ -179,11 +185,11 @@ module.exports = React.createClass({ onDismissed: function(index) { return () => { - const userList = this.state.userList.slice(); - userList.splice(index, 1); + const selectedList = this.state.selectedList.slice(); + selectedList.splice(index, 1); this.setState({ - userList: userList, - queryList: [], + selectedList, + suggestedList: [], query: "", }); if (this._cancelThreepidLookup) this._cancelThreepidLookup(); @@ -197,11 +203,11 @@ module.exports = React.createClass({ }, onSelected: function(index) { - const userList = this.state.userList.slice(); - userList.push(this.state.queryList[index]); + const selectedList = this.state.selectedList.slice(); + selectedList.push(this.state.suggestedList[index]); this.setState({ - userList: userList, - queryList: [], + selectedList, + suggestedList: [], query: "", }); if (this._cancelThreepidLookup) this._cancelThreepidLookup(); @@ -379,10 +385,10 @@ module.exports = React.createClass({ }, _processResults: function(results, query) { - const queryList = []; + const suggestedList = []; results.forEach((result) => { if (result.room_id) { - queryList.push({ + suggestedList.push({ addressType: 'mx-room-id', address: result.room_id, displayName: result.name, @@ -399,7 +405,7 @@ module.exports = React.createClass({ // Return objects, structure of which is defined // by UserAddressType - queryList.push({ + suggestedList.push({ addressType: 'mx-user-id', address: result.user_id, displayName: result.display_name, @@ -413,18 +419,18 @@ module.exports = React.createClass({ // a perfectly valid address if there are close matches. const addrType = getAddressType(query); if (this.props.validAddressTypes.includes(addrType)) { - queryList.unshift({ + suggestedList.unshift({ addressType: addrType, address: query, isKnown: false, }); if (this._cancelThreepidLookup) this._cancelThreepidLookup(); - if (addrType == 'email') { + if (addrType === 'email') { this._lookupThreepid(addrType, query).done(); } } this.setState({ - queryList, + suggestedList, error: false, }, () => { if (this.addressSelector) this.addressSelector.moveSelectionTop(); @@ -442,14 +448,14 @@ module.exports = React.createClass({ if (!this.props.validAddressTypes.includes(addrType)) { this.setState({ error: true }); return null; - } else if (addrType == 'mx-user-id') { + } else if (addrType === 'mx-user-id') { const user = MatrixClientPeg.get().getUser(addrObj.address); if (user) { addrObj.displayName = user.displayName; addrObj.avatarMxc = user.avatarUrl; addrObj.isKnown = true; } - } else if (addrType == 'mx-room-id') { + } else if (addrType === 'mx-room-id') { const room = MatrixClientPeg.get().getRoom(addrObj.address); if (room) { addrObj.displayName = room.name; @@ -458,15 +464,15 @@ module.exports = React.createClass({ } } - const userList = this.state.userList.slice(); - userList.push(addrObj); + const selectedList = this.state.selectedList.slice(); + selectedList.push(addrObj); this.setState({ - userList: userList, - queryList: [], + selectedList, + suggestedList: [], query: "", }); if (this._cancelThreepidLookup) this._cancelThreepidLookup(); - return userList; + return selectedList; }, _lookupThreepid: function(medium, address) { @@ -492,7 +498,7 @@ module.exports = React.createClass({ if (res === null) return null; if (cancelled) return null; this.setState({ - queryList: [{ + suggestedList: [{ // a UserAddressType addressType: medium, address: address, @@ -510,15 +516,27 @@ module.exports = React.createClass({ const AddressSelector = sdk.getComponent("elements.AddressSelector"); this.scrollElement = null; + // map addressType => set of addresses to avoid O(n*m) operation + const selectedAddresses = {}; + this.state.selectedList.forEach(({address, addressType}) => { + if (!selectedAddresses[addressType]) selectedAddresses[addressType] = new Set(); + selectedAddresses[addressType].add(address); + }); + + // Filter out any addresses in the above already selected addresses (matching both type and address) + const filteredSuggestedList = this.state.suggestedList.filter(({address, addressType}) => { + return !(selectedAddresses[addressType] && selectedAddresses[addressType].has(address)); + }); + const query = []; // create the invite list - if (this.state.userList.length > 0) { + if (this.state.selectedList.length > 0) { const AddressTile = sdk.getComponent("elements.AddressTile"); - for (let i = 0; i < this.state.userList.length; i++) { + for (let i = 0; i < this.state.selectedList.length; i++) { query.push( , @@ -528,7 +546,7 @@ module.exports = React.createClass({ // Add the query at the end query.push( -