From 8d8445429c97da0e7306cb318549a7576ab1fbf0 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 15 Mar 2019 14:13:15 -0600 Subject: [PATCH 01/64] Show options for .m.rule.tombstone push rules Part of vector-im/riot-web#8447 --- src/components/views/settings/Notifications.js | 2 ++ src/i18n/strings/en_EN.json | 1 + src/notifications/VectorPushRulesDefinitions.js | 11 +++++++++++ 3 files changed, 14 insertions(+) diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js index b8f8279bb0..4520f5c9a1 100644 --- a/src/components/views/settings/Notifications.js +++ b/src/components/views/settings/Notifications.js @@ -507,6 +507,7 @@ module.exports = React.createClass({ //'.m.rule.member_event': 'vector', '.m.rule.call': 'vector', '.m.rule.suppress_notices': 'vector', + '.m.rule.tombstone': 'vector', // Others go to others }; @@ -562,6 +563,7 @@ module.exports = React.createClass({ //'im.vector.rule.member_event', '.m.rule.call', '.m.rule.suppress_notices', + '.m.rule.tombstone', ]; for (const i in vectorRuleIds) { const vectorRuleId = vectorRuleIds[i]; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index e23be021e8..7b26ed5cf2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -325,6 +325,7 @@ "When I'm invited to a room": "When I'm invited to a room", "Call invitation": "Call invitation", "Messages sent by bot": "Messages sent by bot", + "When rooms are upgraded": "When rooms are upgraded", "Active call (%(roomName)s)": "Active call (%(roomName)s)", "unknown caller": "unknown caller", "Incoming voice call from %(name)s": "Incoming voice call from %(name)s", diff --git a/src/notifications/VectorPushRulesDefinitions.js b/src/notifications/VectorPushRulesDefinitions.js index 402a69e7a6..b15fb4ccd7 100644 --- a/src/notifications/VectorPushRulesDefinitions.js +++ b/src/notifications/VectorPushRulesDefinitions.js @@ -183,4 +183,15 @@ module.exports = { off: StandardActions.ACTION_DONT_NOTIFY, }, }), + + // Room upgrades (tombstones) + ".m.rule.tombstone": new VectorPushRuleDefinition({ + kind: "override", + description: _td("When rooms are upgraded"), // passed through _t() translation in src/components/views/settings/Notifications.js + vectorStateToActions: { // The actions for each vector state, or null to disable the rule. + on: StandardActions.ACTION_NOTIFY, + loud: StandardActions.ACTION_HIGHLIGHT, + off: StandardActions.ACTION_DISABLED, + }, + }), }; From e5059fdf0f82b120c50ad2c9447726b7f9885ce2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 15 Mar 2019 14:14:00 -0600 Subject: [PATCH 02/64] Don't show Matrix-namespaced push rules which the server doesn't declare So that users can't change push rules they don't have. Similar to the behaviour in https://github.com/matrix-org/matrix-js-sdk/pull/860 --- src/components/views/settings/Notifications.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js index 4520f5c9a1..23f7cd484a 100644 --- a/src/components/views/settings/Notifications.js +++ b/src/components/views/settings/Notifications.js @@ -704,6 +704,10 @@ module.exports = React.createClass({ const rows = []; for (const i in this.state.vectorPushRules) { const rule = this.state.vectorPushRules[i]; + if (rule.rule === undefined && rule.vectorRuleId.startsWith(".m.")) { + console.warn(`Skipping render of rule ${rule.vectorRuleId} due to no underlying rule`); + continue; + } //console.log("rendering: " + rule.description + ", " + rule.vectorRuleId + ", " + rule.vectorState); rows.push(this.renderNotifRulesTableRow(rule.description, rule.vectorRuleId, rule.vectorState)); } From 12f92c49a672bbff1ad24c3e2cd9fda0c1bd6424 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Sun, 21 Apr 2019 23:18:06 +0200 Subject: [PATCH 03/64] Mark a few CSS classes as not selectable Should be enough to make copy-pasting not a nightmare. For vector-im/riot-web#7460 Also remove an instance where the vendor prefixes were used, but a build step adds those automatically Signed-off-by: Luca Weiss --- res/css/views/avatars/_BaseAvatar.scss | 1 + res/css/views/rooms/_EventTile.scss | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/res/css/views/avatars/_BaseAvatar.scss b/res/css/views/avatars/_BaseAvatar.scss index 91b2d6c426..a085034758 100644 --- a/res/css/views/avatars/_BaseAvatar.scss +++ b/res/css/views/avatars/_BaseAvatar.scss @@ -26,6 +26,7 @@ limitations under the License. // https://bugzilla.mozilla.org/show_bug.cgi?id=1535053 // https://bugzilla.mozilla.org/show_bug.cgi?id=255139 display: inline-block; + user-select: none; } .mx_BaseAvatar_initial { diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 42eab24051..6363750f4c 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -31,6 +31,7 @@ limitations under the License. top: 14px; left: 8px; cursor: pointer; + user-select: none; } .mx_EventTile.mx_EventTile_info .mx_EventTile_avatar { @@ -62,6 +63,7 @@ limitations under the License. vertical-align: top; height: 16px; overflow: hidden; + user-select: none; img { vertical-align: -2px; @@ -80,6 +82,7 @@ limitations under the License. width: 46px; /* 8 + 30 (avatar) + 8 */ text-align: center; position: absolute; + user-select: none; } .mx_EventTile_line, .mx_EventTile_reply { @@ -226,9 +229,6 @@ limitations under the License. width: 19px; height: 19px; background-image: url($edit-button-url); - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; user-select: none; } @@ -243,6 +243,7 @@ limitations under the License. width: 14px; height: 14px; top: 29px; + user-select: none; } .mx_EventTile_continuation .mx_EventTile_readAvatars, From 2ddaa06e67f860382d878df1c98ec392cd6e5b91 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 24 Apr 2019 11:01:32 +0100 Subject: [PATCH 04/64] Rebuild strings --- src/i18n/strings/en_EN.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 2b50fd9ad3..94815ce6dc 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -727,20 +727,20 @@ "block-quote": "block-quote", "bulleted-list": "bulleted-list", "numbered-list": "numbered-list", - "Hangup": "Hangup", "Voice call": "Voice call", "Video call": "Video call", - "Upload file": "Upload file", + "Hangup": "Hangup", "Show Text Formatting Toolbar": "Show Text Formatting Toolbar", + "Upload file": "Upload file", "Send an encrypted reply…": "Send an encrypted reply…", "Send a reply (unencrypted)…": "Send a reply (unencrypted)…", "Send an encrypted message…": "Send an encrypted message…", "Send a message (unencrypted)…": "Send a message (unencrypted)…", + "Markdown is disabled": "Markdown is disabled", + "Hide Text Formatting Toolbar": "Hide Text Formatting Toolbar", "The conversation continues here.": "The conversation continues here.", "This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.", "You do not have permission to post to this room": "You do not have permission to post to this room", - "Markdown is disabled": "Markdown is disabled", - "Hide Text Formatting Toolbar": "Hide Text Formatting Toolbar", "Server error": "Server error", "Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.", "Command error": "Command error", From 4784d5e9f2b1626fedc4262f054249aed910c766 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 24 Apr 2019 11:05:27 +0100 Subject: [PATCH 05/64] Also say "Connect ..." on remaining key backup buttons This updates the remaining buttons shown when a backup exists but is not trusted so that they all now say "Connect this device to Key Backup" instead of "Use Key Backup". This is a follow up to https://github.com/matrix-org/matrix-react-sdk/pull/2917 and was agreed with Riot iOS team https://github.com/vector-im/riot-ios/pull/2375#issuecomment-485788118. Fixes https://github.com/vector-im/riot-web/issues/9542 --- src/components/views/dialogs/LogoutDialog.js | 2 +- src/components/views/rooms/RoomRecoveryReminder.js | 2 +- src/i18n/strings/en_EN.json | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/views/dialogs/LogoutDialog.js b/src/components/views/dialogs/LogoutDialog.js index 617c455dfb..5241c14096 100644 --- a/src/components/views/dialogs/LogoutDialog.js +++ b/src/components/views/dialogs/LogoutDialog.js @@ -130,7 +130,7 @@ export default class LogoutDialog extends React.Component { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); let setupButtonCaption; if (this.state.backupInfo) { - setupButtonCaption = _t("Use Key Backup"); + setupButtonCaption = _t("Connect this device to Key Backup"); } else { // if there's an error fetching the backup info, we'll just assume there's // no backup for the purpose of the button caption diff --git a/src/components/views/rooms/RoomRecoveryReminder.js b/src/components/views/rooms/RoomRecoveryReminder.js index 12fc78be4e..6b7366bc4f 100644 --- a/src/components/views/rooms/RoomRecoveryReminder.js +++ b/src/components/views/rooms/RoomRecoveryReminder.js @@ -119,7 +119,7 @@ export default class RoomRecoveryReminder extends React.PureComponent { let setupCaption; if (this.state.backupInfo) { - setupCaption = _t("Use Key Backup"); + setupCaption = _t("Connect this device to Key Backup"); } else { setupCaption = _t("Start using Key Backup"); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 94815ce6dc..a7fae2803f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -822,7 +822,6 @@ "%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.", "Try again later, or ask a room admin to check if you have access.": "Try again later, or ask a room admin to check if you have access.", "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.", - "Use Key Backup": "Use Key Backup", "Never lose encrypted messages": "Never lose encrypted messages", "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", "Securely back up your keys to avoid losing them. Learn more.": "Securely back up your keys to avoid losing them. Learn more.", From 37ecf2a623113bdc6dc3b95c0867fd2b125668bb Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 16 Apr 2019 15:40:04 +0100 Subject: [PATCH 06/64] Remove unused ref from Field component The `fieldInput` ref is no longer used now that we have controlled components everywhere. --- src/components/views/elements/Field.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js index 12e20ad789..1b9d55c221 100644 --- a/src/components/views/elements/Field.js +++ b/src/components/views/elements/Field.js @@ -74,7 +74,6 @@ export default class Field extends React.PureComponent { // Set some defaults for the element inputProps.type = inputProps.type || "text"; - inputProps.ref = "fieldInput"; inputProps.placeholder = inputProps.placeholder || inputProps.label; inputProps.onChange = this.onChange; From 338d83ab55c85ee7ba12d797c30b1d260ffed665 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 16 Apr 2019 16:52:31 +0100 Subject: [PATCH 07/64] Add validation feedback helper This adds a general validation feedback mechanism for checking input values. An initial example is wired up for the username input on registration. --- res/css/_components.scss | 1 + res/css/views/elements/_Validation.scss | 32 ++++++ src/components/views/auth/RegistrationForm.js | 56 ++++++---- src/components/views/elements/Field.js | 8 +- src/components/views/elements/Validation.js | 102 ++++++++++++++++++ src/i18n/strings/en_EN.json | 2 + 6 files changed, 176 insertions(+), 25 deletions(-) create mode 100644 res/css/views/elements/_Validation.scss create mode 100644 src/components/views/elements/Validation.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 1f896d270d..8bea138acb 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -100,6 +100,7 @@ @import "./views/elements/_ToggleSwitch.scss"; @import "./views/elements/_ToolTipButton.scss"; @import "./views/elements/_Tooltip.scss"; +@import "./views/elements/_Validation.scss"; @import "./views/globals/_MatrixToolbar.scss"; @import "./views/groups/_GroupPublicityToggle.scss"; @import "./views/groups/_GroupRoomList.scss"; diff --git a/res/css/views/elements/_Validation.scss b/res/css/views/elements/_Validation.scss new file mode 100644 index 0000000000..08ae793663 --- /dev/null +++ b/res/css/views/elements/_Validation.scss @@ -0,0 +1,32 @@ +/* +Copyright 2019 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_Validation_details { + padding-left: 15px; +} + +.mx_Validation_detail { + font-weight: normal; + + // TODO: Check / cross images + &.mx_Validation_valid { + color: $input-valid-border-color; + } + + &.mx_Validation_invalid { + color: $input-invalid-border-color; + } +} diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 1784ab61c3..83ea4dfae6 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -25,6 +25,7 @@ import Modal from '../../../Modal'; import { _t } from '../../../languageHandler'; import SdkConfig from '../../../SdkConfig'; import { SAFE_LOCALPART_REGEX } from '../../../Registration'; +import withValidation from '../elements/Validation'; const FIELD_EMAIL = 'field_email'; const FIELD_PHONE_NUMBER = 'field_phone_number'; @@ -86,6 +87,7 @@ module.exports = React.createClass({ // is the one from the first invalid field. // It's not super ideal that this just calls // onValidationChange once for each invalid field. + // TODO: Change this to trigger new-style validation for an invalid fields. this.validateField(FIELD_PHONE_NUMBER, ev.type); this.validateField(FIELD_EMAIL, ev.type); this.validateField(FIELD_PASSWORD_CONFIRM, ev.type); @@ -152,6 +154,8 @@ module.exports = React.createClass({ const pwd2 = this.state.passwordConfirm.trim(); const allowEmpty = eventType === "blur"; + // TODO: Remove rules here as they are converted to new-style validation + switch (fieldID) { case FIELD_EMAIL: { const email = this.state.email; @@ -173,12 +177,6 @@ module.exports = React.createClass({ const username = this.state.username; if (allowEmpty && username === '') { this.markFieldValid(fieldID, true); - } else if (!SAFE_LOCALPART_REGEX.test(username)) { - this.markFieldValid( - fieldID, - false, - "RegistrationForm.ERR_USERNAME_INVALID", - ); } else if (username == '') { this.markFieldValid( fieldID, @@ -232,11 +230,14 @@ module.exports = React.createClass({ this.setState({ fieldErrors, }); + // TODO: Remove outer validation handling once all fields converted to new-style + // validation in the form. this.props.onValidationChange(fieldErrors); }, _classForField: function(fieldID, ...baseClasses) { let cls = baseClasses.join(' '); + // TODO: Remove this from fields as they are converted to new-style validation. if (this.state.fieldErrors[fieldID]) { if (cls) cls += ' '; cls += 'error'; @@ -291,10 +292,6 @@ module.exports = React.createClass({ }); }, - onUsernameBlur(ev) { - this.validateField(FIELD_USERNAME, ev.type); - }, - onUsernameChange(ev) { this.setState({ username: ev.target.value, @@ -325,6 +322,33 @@ module.exports = React.createClass({ }); }, + renderUsername() { + const Field = sdk.getComponent('elements.Field'); + + const onValidate = withValidation({ + description: _t("Use letters, numbers, dashes and underscores only"), + rules: [ + { + key: "safeLocalpart", + regex: SAFE_LOCALPART_REGEX, + invalid: _t("Some characters not allowed"), + }, + ], + }); + + return ; + }, + render: function() { const Field = sdk.getComponent('elements.Field'); @@ -412,17 +436,7 @@ module.exports = React.createClass({
- + {this.renderUsername()}
; @@ -108,7 +108,7 @@ export default class Field extends React.PureComponent { {prefixContainer} {fieldInput} - {feedback} + {tooltip}
; } } diff --git a/src/components/views/elements/Validation.js b/src/components/views/elements/Validation.js new file mode 100644 index 0000000000..21538609c1 --- /dev/null +++ b/src/components/views/elements/Validation.js @@ -0,0 +1,102 @@ +/* +Copyright 2019 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 classNames from 'classnames'; + +/** + * Creates a validation function from a set of rules describing what to validate. + * + * @param {String} description + * Summary of the kind of value that will meet the validation rules. Shown at + * the top of the validation feedback. + * @param {Object} rules + * An array of rules describing how to check to input value. Each rule in an object + * and may have the following properties: + * - `key`: A unique ID for the rule. Required. + * - `regex`: A regex used to determine the rule's current validity. Required. + * - `valid`: Text to show when the rule is valid. Only shown if set. + * - `invalid`: Text to show when the rule is invalid. Only shown if set. + * @returns {Function} + * A validation function that takes in the current input value and returns + * the overall validity and a feedback UI that can be rendered for more detail. + */ +export default function withValidation({ description, rules }) { + return function onValidate(value) { + // TODO: Hide on blur + // TODO: Re-run only after ~200ms of inactivity + if (!value) { + return { + valid: null, + feedback: null, + }; + } + + const results = []; + let valid = true; + if (rules && rules.length) { + for (const rule of rules) { + if (!rule.key || !rule.regex) { + continue; + } + const ruleValid = rule.regex.test(value); + valid = valid && ruleValid; + if (ruleValid && rule.valid) { + // If the rule's result is valid and has text to show for + // the valid state, show it. + results.push({ + key: rule.key, + valid: true, + text: rule.valid, + }); + } else if (!ruleValid && rule.invalid) { + // If the rule's result is invalid and has text to show for + // the invalid state, show it. + results.push({ + key: rule.key, + valid: false, + text: rule.invalid, + }); + } + } + } + + let details; + if (results && results.length) { + details =
    + {results.map(result => { + const classes = classNames({ + "mx_Validation_detail": true, + "mx_Validation_valid": result.valid, + "mx_Validation_invalid": !result.valid, + }); + return
  • + {result.text} +
  • ; + })} +
; + } + + const feedback =
+
{description}
+ {details} +
; + + return { + valid, + feedback, + }; + }; +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a7fae2803f..51967c39f1 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1322,6 +1322,8 @@ "Change": "Change", "Sign in with": "Sign in with", "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?", + "Use letters, numbers, dashes and underscores only": "Use letters, numbers, dashes and underscores only", + "Some characters not allowed": "Some characters not allowed", "Create your Matrix account": "Create your Matrix account", "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", "Email (optional)": "Email (optional)", From 87f13cfe55b45802b45fd58879b2a4afff83d8c6 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 16 Apr 2019 18:12:13 +0100 Subject: [PATCH 08/64] Add focus handling to validation Update the Field component and validation handling to show / hide validation feedback on focus / blur events. --- src/components/views/elements/Field.js | 49 ++++++++++++++++++--- src/components/views/elements/Validation.js | 11 ++++- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js index 6182a80b70..6d58d29a3d 100644 --- a/src/components/views/elements/Field.js +++ b/src/components/views/elements/Field.js @@ -53,20 +53,53 @@ export default class Field extends React.PureComponent { }; } - onChange = (ev) => { - if (this.props.onValidate) { - const result = this.props.onValidate(ev.target.value); - this.setState({ - valid: result.valid, - feedback: result.feedback, - }); + onFocus = (ev) => { + this.validate({ + value: ev.target.value, + focused: true, + }); + // Parent component may have supplied its own `onFocus` as well + if (this.props.onFocus) { + this.props.onFocus(ev); } + }; + + onChange = (ev) => { + this.validate({ + value: ev.target.value, + focused: true, + }); // Parent component may have supplied its own `onChange` as well if (this.props.onChange) { this.props.onChange(ev); } }; + onBlur = (ev) => { + this.validate({ + value: ev.target.value, + focused: false, + }); + // Parent component may have supplied its own `onBlur` as well + if (this.props.onBlur) { + this.props.onBlur(ev); + } + }; + + validate({ value, focused }) { + if (!this.props.onValidate) { + return; + } + const { valid, feedback } = this.props.onValidate({ + value, + focused, + }); + this.setState({ + valid, + feedback, + }); + } + render() { const { element, prefix, onValidate, children, ...inputProps } = this.props; @@ -76,7 +109,9 @@ export default class Field extends React.PureComponent { inputProps.type = inputProps.type || "text"; inputProps.placeholder = inputProps.placeholder || inputProps.label; + inputProps.onFocus = this.onFocus; inputProps.onChange = this.onChange; + inputProps.onBlur = this.onBlur; const fieldInput = React.createElement(inputElement, inputProps, children); diff --git a/src/components/views/elements/Validation.js b/src/components/views/elements/Validation.js index 21538609c1..44be046a73 100644 --- a/src/components/views/elements/Validation.js +++ b/src/components/views/elements/Validation.js @@ -34,8 +34,7 @@ import classNames from 'classnames'; * the overall validity and a feedback UI that can be rendered for more detail. */ export default function withValidation({ description, rules }) { - return function onValidate(value) { - // TODO: Hide on blur + return function onValidate({ value, focused }) { // TODO: Re-run only after ~200ms of inactivity if (!value) { return { @@ -73,6 +72,14 @@ export default function withValidation({ description, rules }) { } } + // Hide feedback when not focused + if (!focused) { + return { + valid, + feedback: null, + }; + } + let details; if (results && results.length) { details =
    From 37e09b5569b2ce38bc41128201417d8baf2ece81 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 16 Apr 2019 18:49:03 +0100 Subject: [PATCH 09/64] Add check and x icons for validation feedback Adds icons from the Feather set with the same color as text. Tweaks validation item spacing to match the design. --- res/css/views/elements/_Validation.scss | 33 +++++++++++++++++++-- res/img/feather-customised/check.svg | 3 ++ res/img/feather-customised/x.svg | 4 +++ src/components/views/elements/Validation.js | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 res/img/feather-customised/check.svg create mode 100644 res/img/feather-customised/x.svg diff --git a/res/css/views/elements/_Validation.scss b/res/css/views/elements/_Validation.scss index 08ae793663..f101f6cd26 100644 --- a/res/css/views/elements/_Validation.scss +++ b/res/css/views/elements/_Validation.scss @@ -14,19 +14,48 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_Validation_description { + margin-bottom: 1em; +} + .mx_Validation_details { - padding-left: 15px; + padding-left: 20px; + margin: 0; } .mx_Validation_detail { + position: relative; font-weight: normal; + list-style: none; + margin-bottom: 0.5em; + + &::before { + content: ""; + position: absolute; + width: 14px; + height: 14px; + top: 0; + left: -18px; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + } - // TODO: Check / cross images &.mx_Validation_valid { color: $input-valid-border-color; + + &::before { + mask-image: url('$(res)/img/feather-customised/check.svg'); + background-color: $input-valid-border-color; + } } &.mx_Validation_invalid { color: $input-invalid-border-color; + + &::before { + mask-image: url('$(res)/img/feather-customised/x.svg'); + background-color: $input-invalid-border-color; + } } } diff --git a/res/img/feather-customised/check.svg b/res/img/feather-customised/check.svg new file mode 100644 index 0000000000..5c600f8649 --- /dev/null +++ b/res/img/feather-customised/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/feather-customised/x.svg b/res/img/feather-customised/x.svg new file mode 100644 index 0000000000..5468caa8aa --- /dev/null +++ b/res/img/feather-customised/x.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/views/elements/Validation.js b/src/components/views/elements/Validation.js index 44be046a73..4770c968e2 100644 --- a/src/components/views/elements/Validation.js +++ b/src/components/views/elements/Validation.js @@ -97,7 +97,7 @@ export default function withValidation({ description, rules }) { } const feedback =
    -
    {description}
    +
    {description}
    {details}
    ; From 62a01e7a3712e56ca8c021b813b2e372725b0889 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 17 Apr 2019 11:39:11 +0100 Subject: [PATCH 10/64] Track per-field validity with new-style validation This updates the registration form to include the new-style validation state when deciding whether the entire form is valid overall. In addition, this tweaks the validation helper to take functions instead of strings for translated text. This allows the validation helper to be create once per component instead of once every render, which improves performance. --- src/components/views/auth/RegistrationForm.js | 79 ++++++++++++------- src/components/views/elements/Validation.js | 21 +++-- 2 files changed, 64 insertions(+), 36 deletions(-) diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 83ea4dfae6..6e139d7051 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -68,7 +68,9 @@ module.exports = React.createClass({ getInitialState: function() { return { // Field error codes by field ID + // TODO: Remove `fieldErrors` once converted to new-style validation fieldErrors: {}, + fieldValid: {}, // The ISO2 country code selected in the phone number entry phoneCountry: this.props.defaultPhoneCountry, username: "", @@ -140,12 +142,19 @@ module.exports = React.createClass({ * @returns {boolean} true if all fields were valid last time they were validated. */ allFieldsValid: function() { - const keys = Object.keys(this.state.fieldErrors); + // TODO: Remove `fieldErrors` here when all fields converted + let keys = Object.keys(this.state.fieldErrors); for (let i = 0; i < keys.length; ++i) { if (this.state.fieldErrors[keys[i]]) { return false; } } + keys = Object.keys(this.state.fieldValid); + for (let i = 0; i < keys.length; ++i) { + if (!this.state.fieldValid[keys[i]]) { + return false; + } + } return true; }, @@ -161,57 +170,57 @@ module.exports = React.createClass({ const email = this.state.email; const emailValid = email === '' || Email.looksValid(email); if (this._authStepIsRequired('m.login.email.identity') && (!emailValid || email === '')) { - this.markFieldValid(fieldID, false, "RegistrationForm.ERR_MISSING_EMAIL"); - } else this.markFieldValid(fieldID, emailValid, "RegistrationForm.ERR_EMAIL_INVALID"); + this.markFieldError(fieldID, false, "RegistrationForm.ERR_MISSING_EMAIL"); + } else this.markFieldError(fieldID, emailValid, "RegistrationForm.ERR_EMAIL_INVALID"); break; } case FIELD_PHONE_NUMBER: { const phoneNumber = this.state.phoneNumber; const phoneNumberValid = phoneNumber === '' || phoneNumberLooksValid(phoneNumber); if (this._authStepIsRequired('m.login.msisdn') && (!phoneNumberValid || phoneNumber === '')) { - this.markFieldValid(fieldID, false, "RegistrationForm.ERR_MISSING_PHONE_NUMBER"); - } else this.markFieldValid(fieldID, phoneNumberValid, "RegistrationForm.ERR_PHONE_NUMBER_INVALID"); + this.markFieldError(fieldID, false, "RegistrationForm.ERR_MISSING_PHONE_NUMBER"); + } else this.markFieldError(fieldID, phoneNumberValid, "RegistrationForm.ERR_PHONE_NUMBER_INVALID"); break; } case FIELD_USERNAME: { const username = this.state.username; if (allowEmpty && username === '') { - this.markFieldValid(fieldID, true); + this.markFieldError(fieldID, true); } else if (username == '') { - this.markFieldValid( + this.markFieldError( fieldID, false, "RegistrationForm.ERR_USERNAME_BLANK", ); } else { - this.markFieldValid(fieldID, true); + this.markFieldError(fieldID, true); } break; } case FIELD_PASSWORD: if (allowEmpty && pwd1 === "") { - this.markFieldValid(fieldID, true); + this.markFieldError(fieldID, true); } else if (pwd1 == '') { - this.markFieldValid( + this.markFieldError( fieldID, false, "RegistrationForm.ERR_PASSWORD_MISSING", ); } else if (pwd1.length < this.props.minPasswordLength) { - this.markFieldValid( + this.markFieldError( fieldID, false, "RegistrationForm.ERR_PASSWORD_LENGTH", ); } else { - this.markFieldValid(fieldID, true); + this.markFieldError(fieldID, true); } break; case FIELD_PASSWORD_CONFIRM: if (allowEmpty && pwd2 === "") { - this.markFieldValid(fieldID, true); + this.markFieldError(fieldID, true); } else { - this.markFieldValid( + this.markFieldError( fieldID, pwd1 == pwd2, "RegistrationForm.ERR_PASSWORD_MISMATCH", ); @@ -220,7 +229,8 @@ module.exports = React.createClass({ } }, - markFieldValid: function(fieldID, valid, errorCode) { + markFieldError: function(fieldID, valid, errorCode) { + // TODO: Remove this function once all fields converted to new-style validation. const { fieldErrors } = this.state; if (valid) { fieldErrors[fieldID] = null; @@ -235,6 +245,14 @@ module.exports = React.createClass({ this.props.onValidationChange(fieldErrors); }, + markFieldValid: function(fieldID, valid) { + const { fieldValid } = this.state; + fieldValid[fieldID] = valid; + this.setState({ + fieldValid, + }); + }, + _classForField: function(fieldID, ...baseClasses) { let cls = baseClasses.join(' '); // TODO: Remove this from fields as they are converted to new-style validation. @@ -298,6 +316,23 @@ module.exports = React.createClass({ }); }, + onUsernameValidate(fieldState) { + const result = this.validateUsernameRules(fieldState); + this.markFieldValid(FIELD_USERNAME, result.valid); + return result; + }, + + validateUsernameRules: withValidation({ + description: () => _t("Use letters, numbers, dashes and underscores only"), + rules: [ + { + key: "safeLocalpart", + regex: SAFE_LOCALPART_REGEX, + invalid: () => _t("Some characters not allowed"), + }, + ], + }), + /** * A step is required if all flows include that step. * @@ -324,18 +359,6 @@ module.exports = React.createClass({ renderUsername() { const Field = sdk.getComponent('elements.Field'); - - const onValidate = withValidation({ - description: _t("Use letters, numbers, dashes and underscores only"), - rules: [ - { - key: "safeLocalpart", - regex: SAFE_LOCALPART_REGEX, - invalid: _t("Some characters not allowed"), - }, - ], - }); - return ; }, diff --git a/src/components/views/elements/Validation.js b/src/components/views/elements/Validation.js index 4770c968e2..8fc584edce 100644 --- a/src/components/views/elements/Validation.js +++ b/src/components/views/elements/Validation.js @@ -19,16 +19,16 @@ import classNames from 'classnames'; /** * Creates a validation function from a set of rules describing what to validate. * - * @param {String} description - * Summary of the kind of value that will meet the validation rules. Shown at - * the top of the validation feedback. + * @param {Function} description + * Function that returns a string summary of the kind of value that will + * meet the validation rules. Shown at the top of the validation feedback. * @param {Object} rules * An array of rules describing how to check to input value. Each rule in an object * and may have the following properties: * - `key`: A unique ID for the rule. Required. * - `regex`: A regex used to determine the rule's current validity. Required. - * - `valid`: Text to show when the rule is valid. Only shown if set. - * - `invalid`: Text to show when the rule is invalid. Only shown if set. + * - `valid`: Function returning text to show when the rule is valid. Only shown if set. + * - `invalid`: Function returning text to show when the rule is invalid. Only shown if set. * @returns {Function} * A validation function that takes in the current input value and returns * the overall validity and a feedback UI that can be rendered for more detail. @@ -58,7 +58,7 @@ export default function withValidation({ description, rules }) { results.push({ key: rule.key, valid: true, - text: rule.valid, + text: rule.valid(), }); } else if (!ruleValid && rule.invalid) { // If the rule's result is invalid and has text to show for @@ -66,7 +66,7 @@ export default function withValidation({ description, rules }) { results.push({ key: rule.key, valid: false, - text: rule.invalid, + text: rule.invalid(), }); } } @@ -96,8 +96,13 @@ export default function withValidation({ description, rules }) {
; } + let summary; + if (description) { + summary =
{description()}
; + } + const feedback =
-
{description}
+ {summary} {details}
; From 5d95c318756d3cf2e72ca7748f48c828eede57f2 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 17 Apr 2019 14:23:59 +0100 Subject: [PATCH 11/64] Focus the first invalid field This adjusts the submission step to focus the first invalid field and redisplay validation. This also rearranges the older style field error handling on registration which is slated for removal once we convert all fields to the new style. --- src/components/views/auth/RegistrationForm.js | 76 ++++++++++++++----- src/components/views/elements/Field.js | 5 ++ 2 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 6e139d7051..c680d058c5 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -89,34 +89,37 @@ module.exports = React.createClass({ // is the one from the first invalid field. // It's not super ideal that this just calls // onValidationChange once for each invalid field. - // TODO: Change this to trigger new-style validation for an invalid fields. + // TODO: Remove these calls once converted to new-style validation. this.validateField(FIELD_PHONE_NUMBER, ev.type); this.validateField(FIELD_EMAIL, ev.type); this.validateField(FIELD_PASSWORD_CONFIRM, ev.type); this.validateField(FIELD_PASSWORD, ev.type); this.validateField(FIELD_USERNAME, ev.type); + const allFieldsValid = this.verifyFieldsBeforeSubmit(); + if (!allFieldsValid) { + return; + } + const self = this; - if (this.allFieldsValid()) { - if (this.state.email == '') { - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createTrackedDialog('If you don\'t specify an email address...', '', QuestionDialog, { - title: _t("Warning!"), - description: -
- { _t("If you don't specify an email address, you won't be able to reset your password. " + - "Are you sure?") } -
, - button: _t("Continue"), - onFinished: function(confirmed) { - if (confirmed) { - self._doSubmit(ev); - } - }, - }); - } else { - self._doSubmit(ev); - } + if (this.state.email == '') { + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + Modal.createTrackedDialog('If you don\'t specify an email address...', '', QuestionDialog, { + title: _t("Warning!"), + description: +
+ { _t("If you don't specify an email address, you won't be able to reset your password. " + + "Are you sure?") } +
, + button: _t("Continue"), + onFinished: function(confirmed) { + if (confirmed) { + self._doSubmit(ev); + } + }, + }); + } else { + self._doSubmit(ev); } }, @@ -138,6 +141,27 @@ module.exports = React.createClass({ } }, + verifyFieldsBeforeSubmit() { + if (this.allFieldsValid()) { + return true; + } + + const invalidField = this.findFirstInvalidField([ + FIELD_USERNAME, + FIELD_PASSWORD, + FIELD_PASSWORD_CONFIRM, + FIELD_EMAIL, + FIELD_PHONE_NUMBER, + ]); + + if (!invalidField) { + return true; + } + + invalidField.focus(); + return false; + }, + /** * @returns {boolean} true if all fields were valid last time they were validated. */ @@ -158,6 +182,15 @@ module.exports = React.createClass({ return true; }, + findFirstInvalidField(fieldIDs) { + for (const fieldID of fieldIDs) { + if (!this.state.fieldValid[fieldID] && this[fieldID]) { + return this[fieldID]; + } + } + return null; + }, + validateField: function(fieldID, eventType) { const pwd1 = this.state.password.trim(); const pwd2 = this.state.passwordConfirm.trim(); @@ -362,6 +395,7 @@ module.exports = React.createClass({ return this[FIELD_USERNAME] = field} type="text" autoFocus={true} label={_t("Username")} diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js index 6d58d29a3d..93d1f1e71d 100644 --- a/src/components/views/elements/Field.js +++ b/src/components/views/elements/Field.js @@ -86,6 +86,10 @@ export default class Field extends React.PureComponent { } }; + focus() { + this.input.focus(); + } + validate({ value, focused }) { if (!this.props.onValidate) { return; @@ -107,6 +111,7 @@ export default class Field extends React.PureComponent { // Set some defaults for the element inputProps.type = inputProps.type || "text"; + inputProps.ref = input => this.input = input; inputProps.placeholder = inputProps.placeholder || inputProps.label; inputProps.onFocus = this.onFocus; From 778697abf13e6d1aa548bfabaa7b5498de16ca4d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 18 Apr 2019 17:37:07 +0100 Subject: [PATCH 12/64] Use input element's value directly Since we're keeping the input as a ref anyway, let's use that rather than requiring the value to be passed to `validate`. This allows others to call `validate` as well. --- src/components/views/elements/Field.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js index 93d1f1e71d..dfe3a51697 100644 --- a/src/components/views/elements/Field.js +++ b/src/components/views/elements/Field.js @@ -55,7 +55,6 @@ export default class Field extends React.PureComponent { onFocus = (ev) => { this.validate({ - value: ev.target.value, focused: true, }); // Parent component may have supplied its own `onFocus` as well @@ -66,7 +65,6 @@ export default class Field extends React.PureComponent { onChange = (ev) => { this.validate({ - value: ev.target.value, focused: true, }); // Parent component may have supplied its own `onChange` as well @@ -77,7 +75,6 @@ export default class Field extends React.PureComponent { onBlur = (ev) => { this.validate({ - value: ev.target.value, focused: false, }); // Parent component may have supplied its own `onBlur` as well @@ -90,10 +87,11 @@ export default class Field extends React.PureComponent { this.input.focus(); } - validate({ value, focused }) { + validate({ focused }) { if (!this.props.onValidate) { return; } + const { value } = this.input; const { valid, feedback } = this.props.onValidate({ value, focused, From a7c37733b8f741f220a32384732f2e30348f8d29 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 18 Apr 2019 21:22:37 +0100 Subject: [PATCH 13/64] Rebalance margins in validation tooltip --- res/css/views/elements/_Validation.scss | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/res/css/views/elements/_Validation.scss b/res/css/views/elements/_Validation.scss index f101f6cd26..4c059f9747 100644 --- a/res/css/views/elements/_Validation.scss +++ b/res/css/views/elements/_Validation.scss @@ -14,21 +14,25 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_Validation_description { - margin-bottom: 1em; -} - .mx_Validation_details { padding-left: 20px; margin: 0; } +.mx_Validation_description + .mx_Validation_details { + margin: 1em 0 0; +} + .mx_Validation_detail { position: relative; font-weight: normal; list-style: none; margin-bottom: 0.5em; + &:last-child { + margin-bottom: 0; + } + &::before { content: ""; position: absolute; From 1cbb4be6f7480690b57c2196edd3713584f12225 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 18 Apr 2019 21:33:37 +0100 Subject: [PATCH 14/64] Add support for validating more strictly at submit time When submitting a form, we want to validate more strictly to check for empty values that might be required. A separate mode is used since we want to ignore this issue when visiting a field one by one to enter data. As an example, we convert the pre-existing logic for the username requirement using this new support. --- .../structures/auth/Registration.js | 6 --- src/components/views/auth/RegistrationForm.js | 51 ++++++++++--------- src/components/views/elements/Field.js | 5 +- src/components/views/elements/Validation.js | 10 ++-- src/i18n/strings/en_EN.json | 2 +- 5 files changed, 36 insertions(+), 38 deletions(-) diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 0d36e592f8..b19be60fbe 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -344,12 +344,6 @@ module.exports = React.createClass({ case "RegistrationForm.ERR_MISSING_PHONE_NUMBER": errMsg = _t('A phone number is required to register on this homeserver.'); break; - case "RegistrationForm.ERR_USERNAME_INVALID": - errMsg = _t("A username can only contain lower case letters, numbers and '=_-./'"); - break; - case "RegistrationForm.ERR_USERNAME_BLANK": - errMsg = _t('You need to enter a username.'); - break; default: console.error("Unknown error code: %s", errCode); errMsg = _t('An unknown error occurred.'); diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index c680d058c5..1f30af3710 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -94,7 +94,6 @@ module.exports = React.createClass({ this.validateField(FIELD_EMAIL, ev.type); this.validateField(FIELD_PASSWORD_CONFIRM, ev.type); this.validateField(FIELD_PASSWORD, ev.type); - this.validateField(FIELD_USERNAME, ev.type); const allFieldsValid = this.verifyFieldsBeforeSubmit(); if (!allFieldsValid) { @@ -142,23 +141,38 @@ module.exports = React.createClass({ }, verifyFieldsBeforeSubmit() { - if (this.allFieldsValid()) { - return true; - } - - const invalidField = this.findFirstInvalidField([ + const fieldIDsInDisplayOrder = [ FIELD_USERNAME, FIELD_PASSWORD, FIELD_PASSWORD_CONFIRM, FIELD_EMAIL, FIELD_PHONE_NUMBER, - ]); + ]; + + // Run all fields with stricter validation that no longer allows empty + // values for required fields. + for (const fieldID of fieldIDsInDisplayOrder) { + const field = this[fieldID]; + if (!field) { + continue; + } + field.validate({ allowEmpty: false }); + } + + if (this.allFieldsValid()) { + return true; + } + + const invalidField = this.findFirstInvalidField(fieldIDsInDisplayOrder); if (!invalidField) { return true; } + // Focus the first invalid field and show feedback in the stricter mode + // that no longer allows empty values for required fields. invalidField.focus(); + invalidField.validate({ allowEmpty: false, focused: true }); return false; }, @@ -215,21 +229,6 @@ module.exports = React.createClass({ } else this.markFieldError(fieldID, phoneNumberValid, "RegistrationForm.ERR_PHONE_NUMBER_INVALID"); break; } - case FIELD_USERNAME: { - const username = this.state.username; - if (allowEmpty && username === '') { - this.markFieldError(fieldID, true); - } else if (username == '') { - this.markFieldError( - fieldID, - false, - "RegistrationForm.ERR_USERNAME_BLANK", - ); - } else { - this.markFieldError(fieldID, true); - } - break; - } case FIELD_PASSWORD: if (allowEmpty && pwd1 === "") { this.markFieldError(fieldID, true); @@ -358,9 +357,14 @@ module.exports = React.createClass({ validateUsernameRules: withValidation({ description: () => _t("Use letters, numbers, dashes and underscores only"), rules: [ + { + key: "required", + test: ({ value, allowEmpty }) => allowEmpty || !!value, + invalid: () => _t("Enter username"), + }, { key: "safeLocalpart", - regex: SAFE_LOCALPART_REGEX, + test: ({ value }) => !value || SAFE_LOCALPART_REGEX.test(value), invalid: () => _t("Some characters not allowed"), }, ], @@ -393,7 +397,6 @@ module.exports = React.createClass({ renderUsername() { const Field = sdk.getComponent('elements.Field'); return this[FIELD_USERNAME] = field} type="text" diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js index dfe3a51697..6eba832523 100644 --- a/src/components/views/elements/Field.js +++ b/src/components/views/elements/Field.js @@ -87,14 +87,15 @@ export default class Field extends React.PureComponent { this.input.focus(); } - validate({ focused }) { + validate({ focused, allowEmpty = true }) { if (!this.props.onValidate) { return; } - const { value } = this.input; + const value = this.input ? this.input.value : null; const { valid, feedback } = this.props.onValidate({ value, focused, + allowEmpty, }); this.setState({ valid, diff --git a/src/components/views/elements/Validation.js b/src/components/views/elements/Validation.js index 8fc584edce..8142dfcd65 100644 --- a/src/components/views/elements/Validation.js +++ b/src/components/views/elements/Validation.js @@ -26,7 +26,7 @@ import classNames from 'classnames'; * An array of rules describing how to check to input value. Each rule in an object * and may have the following properties: * - `key`: A unique ID for the rule. Required. - * - `regex`: A regex used to determine the rule's current validity. Required. + * - `test`: A function used to determine the rule's current validity. Required. * - `valid`: Function returning text to show when the rule is valid. Only shown if set. * - `invalid`: Function returning text to show when the rule is invalid. Only shown if set. * @returns {Function} @@ -34,9 +34,9 @@ import classNames from 'classnames'; * the overall validity and a feedback UI that can be rendered for more detail. */ export default function withValidation({ description, rules }) { - return function onValidate({ value, focused }) { + return function onValidate({ value, focused, allowEmpty = true }) { // TODO: Re-run only after ~200ms of inactivity - if (!value) { + if (!value && allowEmpty) { return { valid: null, feedback: null, @@ -47,10 +47,10 @@ export default function withValidation({ description, rules }) { let valid = true; if (rules && rules.length) { for (const rule of rules) { - if (!rule.key || !rule.regex) { + if (!rule.key || !rule.test) { continue; } - const ruleValid = rule.regex.test(value); + const ruleValid = rule.test({ value, allowEmpty }); valid = valid && ruleValid; if (ruleValid && rule.valid) { // If the rule's result is valid and has text to show for diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 51967c39f1..bafb516af7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1324,6 +1324,7 @@ "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?", "Use letters, numbers, dashes and underscores only": "Use letters, numbers, dashes and underscores only", "Some characters not allowed": "Some characters not allowed", + "Enter username": "Enter username", "Create your Matrix account": "Create your Matrix account", "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", "Email (optional)": "Email (optional)", @@ -1524,7 +1525,6 @@ "This doesn't look like a valid phone number.": "This doesn't look like a valid phone number.", "An email address is required to register on this homeserver.": "An email address is required to register on this homeserver.", "A phone number is required to register on this homeserver.": "A phone number is required to register on this homeserver.", - "You need to enter a username.": "You need to enter a username.", "An unknown error occurred.": "An unknown error occurred.", "Create your account": "Create your account", "Commands": "Commands", From 9064875312cfea5b23073946e89cbdf7e30e07fd Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 18 Apr 2019 22:53:46 +0100 Subject: [PATCH 15/64] Migrate email on registration to new validation --- .../structures/auth/Registration.js | 6 -- src/components/views/auth/RegistrationForm.js | 85 ++++++++++--------- src/components/views/elements/Validation.js | 16 ++-- src/i18n/strings/en_EN.json | 12 +-- 4 files changed, 65 insertions(+), 54 deletions(-) diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index b19be60fbe..22dd8c0ea9 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -332,15 +332,9 @@ module.exports = React.createClass({ case "RegistrationForm.ERR_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.'); - break; case "RegistrationForm.ERR_PHONE_NUMBER_INVALID": errMsg = _t('This doesn\'t look like a valid phone number.'); break; - case "RegistrationForm.ERR_MISSING_EMAIL": - errMsg = _t('An email address is required to register on this homeserver.'); - break; case "RegistrationForm.ERR_MISSING_PHONE_NUMBER": errMsg = _t('A phone number is required to register on this homeserver.'); break; diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 1f30af3710..5f2a9a8dcc 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -91,7 +91,6 @@ module.exports = React.createClass({ // onValidationChange once for each invalid field. // TODO: Remove these calls once converted to new-style validation. this.validateField(FIELD_PHONE_NUMBER, ev.type); - this.validateField(FIELD_EMAIL, ev.type); this.validateField(FIELD_PASSWORD_CONFIRM, ev.type); this.validateField(FIELD_PASSWORD, ev.type); @@ -213,14 +212,6 @@ module.exports = React.createClass({ // TODO: Remove rules here as they are converted to new-style validation switch (fieldID) { - case FIELD_EMAIL: { - const email = this.state.email; - const emailValid = email === '' || Email.looksValid(email); - if (this._authStepIsRequired('m.login.email.identity') && (!emailValid || email === '')) { - this.markFieldError(fieldID, false, "RegistrationForm.ERR_MISSING_EMAIL"); - } else this.markFieldError(fieldID, emailValid, "RegistrationForm.ERR_EMAIL_INVALID"); - break; - } case FIELD_PHONE_NUMBER: { const phoneNumber = this.state.phoneNumber; const phoneNumberValid = phoneNumber === '' || phoneNumberLooksValid(phoneNumber); @@ -295,16 +286,36 @@ module.exports = React.createClass({ return cls; }, - onEmailBlur(ev) { - this.validateField(FIELD_EMAIL, ev.type); - }, - onEmailChange(ev) { this.setState({ email: ev.target.value, }); }, + onEmailValidate(fieldState) { + const result = this.validateEmailRules(fieldState); + this.markFieldValid(FIELD_EMAIL, result.valid); + return result; + }, + + validateEmailRules: withValidation({ + description: () => _t("Use an email address to recover your account"), + rules: [ + { + key: "required", + test: function({ value, allowEmpty }) { + return allowEmpty || !this._authStepIsRequired('m.login.email.identity') || !!value; + }, + invalid: () => _t("Enter email address (required on this homeserver)"), + }, + { + key: "email", + test: ({ value }) => !value || Email.looksValid(value), + invalid: () => _t("Doesn't look like a valid email address"), + }, + ], + }), + onPasswordBlur(ev) { this.validateField(FIELD_PASSWORD, ev.type); }, @@ -394,6 +405,26 @@ module.exports = React.createClass({ }); }, + renderEmail() { + if (!this._authStepIsUsed('m.login.email.identity')) { + return null; + } + const Field = sdk.getComponent('elements.Field'); + const emailPlaceholder = this._authStepIsRequired('m.login.email.identity') ? + _t("Email") : + _t("Email (optional)"); + return this[FIELD_EMAIL] = field} + type="text" + label={emailPlaceholder} + defaultValue={this.props.defaultEmail} + value={this.state.email} + onChange={this.onEmailChange} + onValidate={this.onEmailValidate} + />; + }, + renderUsername() { const Field = sdk.getComponent('elements.Field'); return ; } - let emailSection; - if (this._authStepIsUsed('m.login.email.identity')) { - const emailPlaceholder = this._authStepIsRequired('m.login.email.identity') ? - _t("Email") : - _t("Email (optional)"); - - emailSection = ( - - ); - } - const threePidLogin = !SdkConfig.get().disable_3pid_login; const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown'); let phoneSection; @@ -521,13 +532,11 @@ module.exports = React.createClass({ />
- { emailSection } + {this.renderEmail()} { phoneSection }
- {_t( - "Use an email address to recover your account. Other users " + - "can invite you to rooms using your contact details.", - )} + {_t("Use an email address to recover your account.") + " "} + {_t("Other users can invite you to rooms using your contact details.")} { registerButton } diff --git a/src/components/views/elements/Validation.js b/src/components/views/elements/Validation.js index 8142dfcd65..458d000e0e 100644 --- a/src/components/views/elements/Validation.js +++ b/src/components/views/elements/Validation.js @@ -50,7 +50,10 @@ export default function withValidation({ description, rules }) { if (!rule.key || !rule.test) { continue; } - const ruleValid = rule.test({ value, allowEmpty }); + // We're setting `this` to whichever component hold the validation + // function. That allows rules to access the state of the component. + // eslint-disable-next-line babel/no-invalid-this + const ruleValid = rule.test.call(this, { value, allowEmpty }); valid = valid && ruleValid; if (ruleValid && rule.valid) { // If the rule's result is valid and has text to show for @@ -101,10 +104,13 @@ export default function withValidation({ description, rules }) { summary =
{description()}
; } - const feedback =
- {summary} - {details} -
; + let feedback; + if (summary || details) { + feedback =
+ {summary} + {details} +
; + } return { valid, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index bafb516af7..d219b3a19a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1322,15 +1322,19 @@ "Change": "Change", "Sign in with": "Sign in with", "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?", + "Use an email address to recover your account": "Use an email address to recover your account", + "Enter email address (required on this homeserver)": "Enter email address (required on this homeserver)", + "Doesn't look like a valid email address": "Doesn't look like a valid email address", "Use letters, numbers, dashes and underscores only": "Use letters, numbers, dashes and underscores only", - "Some characters not allowed": "Some characters not allowed", "Enter username": "Enter username", + "Some characters not allowed": "Some characters not allowed", + "Email (optional)": "Email (optional)", "Create your Matrix account": "Create your Matrix account", "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", - "Email (optional)": "Email (optional)", "Phone (optional)": "Phone (optional)", "Confirm": "Confirm", - "Use an email address to recover your account. Other users can invite you to rooms using your contact details.": "Use an email address to recover your account. Other users can invite you to rooms using your contact details.", + "Use an email address to recover your account.": "Use an email address to recover your account.", + "Other users can invite you to rooms using your contact details.": "Other users can invite you to rooms using your contact details.", "Other servers": "Other servers", "Enter custom server URLs What does this mean?": "Enter custom server URLs What does this mean?", "Homeserver URL": "Homeserver URL", @@ -1521,9 +1525,7 @@ "Missing password.": "Missing password.", "Passwords don't match.": "Passwords don't match.", "Password too short (min %(MIN_PASSWORD_LENGTH)s).": "Password too short (min %(MIN_PASSWORD_LENGTH)s).", - "This doesn't look like a valid email address.": "This doesn't look like a valid email address.", "This doesn't look like a valid phone number.": "This doesn't look like a valid phone number.", - "An email address is required to register on this homeserver.": "An email address is required to register on this homeserver.", "A phone number is required to register on this homeserver.": "A phone number is required to register on this homeserver.", "An unknown error occurred.": "An unknown error occurred.", "Create your account": "Create your account", From aaf745ae2a6578d9ca21327dc65925527b4bacf7 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 18 Apr 2019 23:05:55 +0100 Subject: [PATCH 16/64] Migrate phone number on registration to new validation --- .../structures/auth/Registration.js | 6 -- src/components/views/auth/RegistrationForm.js | 95 +++++++++++-------- src/i18n/strings/en_EN.json | 7 +- 3 files changed, 58 insertions(+), 50 deletions(-) diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 22dd8c0ea9..90e7d99bfe 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -332,12 +332,6 @@ module.exports = React.createClass({ case "RegistrationForm.ERR_PASSWORD_LENGTH": errMsg = _t('Password too short (min %(MIN_PASSWORD_LENGTH)s).', {MIN_PASSWORD_LENGTH}); break; - case "RegistrationForm.ERR_PHONE_NUMBER_INVALID": - errMsg = _t('This doesn\'t look like a valid phone number.'); - break; - case "RegistrationForm.ERR_MISSING_PHONE_NUMBER": - errMsg = _t('A phone number is required to register on this homeserver.'); - break; default: console.error("Unknown error code: %s", errCode); errMsg = _t('An unknown error occurred.'); diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 5f2a9a8dcc..a3816829e7 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -90,7 +90,6 @@ module.exports = React.createClass({ // It's not super ideal that this just calls // onValidationChange once for each invalid field. // TODO: Remove these calls once converted to new-style validation. - this.validateField(FIELD_PHONE_NUMBER, ev.type); this.validateField(FIELD_PASSWORD_CONFIRM, ev.type); this.validateField(FIELD_PASSWORD, ev.type); @@ -212,14 +211,6 @@ module.exports = React.createClass({ // TODO: Remove rules here as they are converted to new-style validation switch (fieldID) { - case FIELD_PHONE_NUMBER: { - const phoneNumber = this.state.phoneNumber; - const phoneNumberValid = phoneNumber === '' || phoneNumberLooksValid(phoneNumber); - if (this._authStepIsRequired('m.login.msisdn') && (!phoneNumberValid || phoneNumber === '')) { - this.markFieldError(fieldID, false, "RegistrationForm.ERR_MISSING_PHONE_NUMBER"); - } else this.markFieldError(fieldID, phoneNumberValid, "RegistrationForm.ERR_PHONE_NUMBER_INVALID"); - break; - } case FIELD_PASSWORD: if (allowEmpty && pwd1 === "") { this.markFieldError(fieldID, true); @@ -343,16 +334,36 @@ module.exports = React.createClass({ }); }, - onPhoneNumberBlur(ev) { - this.validateField(FIELD_PHONE_NUMBER, ev.type); - }, - onPhoneNumberChange(ev) { this.setState({ phoneNumber: ev.target.value, }); }, + onPhoneNumberValidate(fieldState) { + const result = this.validatePhoneNumberRules(fieldState); + this.markFieldValid(FIELD_PHONE_NUMBER, result.valid); + return result; + }, + + validatePhoneNumberRules: withValidation({ + description: () => _t("Other users can invite you to rooms using your contact details"), + rules: [ + { + key: "required", + test: function({ value, allowEmpty }) { + return allowEmpty || !this._authStepIsRequired('m.login.msisdn') || !!value; + }, + invalid: () => _t("Enter phone number (required on this homeserver)"), + }, + { + key: "email", + test: ({ value }) => !value || phoneNumberLooksValid(value), + invalid: () => _t("Doesn't look like a valid phone number"), + }, + ], + }), + onUsernameChange(ev) { this.setState({ username: ev.target.value, @@ -425,6 +436,35 @@ module.exports = React.createClass({ />; }, + renderPhoneNumber() { + const threePidLogin = !SdkConfig.get().disable_3pid_login; + if (!threePidLogin || !this._authStepIsUsed('m.login.msisdn')) { + return null; + } + const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown'); + const Field = sdk.getComponent('elements.Field'); + const phoneLabel = this._authStepIsRequired('m.login.msisdn') ? + _t("Phone") : + _t("Phone (optional)"); + const phoneCountry = ; + return this[FIELD_PHONE_NUMBER] = field} + type="text" + label={phoneLabel} + defaultValue={this.props.defaultPhoneNumber} + value={this.state.phoneNumber} + prefix={phoneCountry} + onChange={this.onPhoneNumberChange} + onValidate={this.onPhoneNumberValidate} + />; + }, + renderUsername() { const Field = sdk.getComponent('elements.Field'); return ; } - const threePidLogin = !SdkConfig.get().disable_3pid_login; - const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown'); - let phoneSection; - if (threePidLogin && this._authStepIsUsed('m.login.msisdn')) { - const phoneLabel = this._authStepIsRequired('m.login.msisdn') ? - _t("Phone") : - _t("Phone (optional)"); - const phoneCountry = ; - - phoneSection = ; - } - const registerButton = ( ); @@ -533,7 +546,7 @@ module.exports = React.createClass({
{this.renderEmail()} - { phoneSection } + {this.renderPhoneNumber()}
{_t("Use an email address to recover your account.") + " "} {_t("Other users can invite you to rooms using your contact details.")} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d219b3a19a..910fc8f74f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1325,13 +1325,16 @@ "Use an email address to recover your account": "Use an email address to recover your account", "Enter email address (required on this homeserver)": "Enter email address (required on this homeserver)", "Doesn't look like a valid email address": "Doesn't look like a valid email address", + "Other users can invite you to rooms using your contact details": "Other users can invite you to rooms using your contact details", + "Enter phone number (required on this homeserver)": "Enter phone number (required on this homeserver)", + "Doesn't look like a valid phone number": "Doesn't look like a valid phone number", "Use letters, numbers, dashes and underscores only": "Use letters, numbers, dashes and underscores only", "Enter username": "Enter username", "Some characters not allowed": "Some characters not allowed", "Email (optional)": "Email (optional)", + "Phone (optional)": "Phone (optional)", "Create your Matrix account": "Create your Matrix account", "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", - "Phone (optional)": "Phone (optional)", "Confirm": "Confirm", "Use an email address to recover your account.": "Use an email address to recover your account.", "Other users can invite you to rooms using your contact details.": "Other users can invite you to rooms using your contact details.", @@ -1525,8 +1528,6 @@ "Missing password.": "Missing password.", "Passwords don't match.": "Passwords don't match.", "Password too short (min %(MIN_PASSWORD_LENGTH)s).": "Password too short (min %(MIN_PASSWORD_LENGTH)s).", - "This doesn't look like a valid phone number.": "This doesn't look like a valid phone number.", - "A phone number is required to register on this homeserver.": "A phone number is required to register on this homeserver.", "An unknown error occurred.": "An unknown error occurred.", "Create your account": "Create your account", "Commands": "Commands", From 008ca3543bdfe6afefd4d37331c1b193718ce5ab Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 18 Apr 2019 23:29:05 +0100 Subject: [PATCH 17/64] Migrate passwords on registration to new validation In addition to migrating password fields, this also removes the remaining support for old-style validation in registration now that all checks have been converted. --- .../structures/auth/Registration.js | 35 ---- src/components/views/auth/RegistrationForm.js | 191 +++++++----------- src/components/views/elements/Validation.js | 7 +- src/i18n/strings/en_EN.json | 8 +- 4 files changed, 85 insertions(+), 156 deletions(-) diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 90e7d99bfe..87fea3ec4b 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -308,40 +308,6 @@ module.exports = React.createClass({ }); }, - onFormValidationChange: function(fieldErrors) { - // `fieldErrors` is an object mapping field IDs to error codes when there is an - // error or `null` for no error, so the values array will be something like: - // `[ null, "RegistrationForm.ERR_PASSWORD_MISSING", null]` - // Find the first non-null error code and show that. - const errCode = Object.values(fieldErrors).find(value => !!value); - if (!errCode) { - this.setState({ - errorText: null, - }); - return; - } - - let errMsg; - switch (errCode) { - case "RegistrationForm.ERR_PASSWORD_MISSING": - errMsg = _t('Missing password.'); - break; - case "RegistrationForm.ERR_PASSWORD_MISMATCH": - 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}); - break; - default: - console.error("Unknown error code: %s", errCode); - errMsg = _t('An unknown error occurred.'); - break; - } - this.setState({ - errorText: errMsg, - }); - }, - onLoginClick: function(ev) { ev.preventDefault(); ev.stopPropagation(); @@ -517,7 +483,6 @@ module.exports = React.createClass({ defaultPhoneNumber={this.state.formVals.phoneNumber} defaultPassword={this.state.formVals.password} minPasswordLength={MIN_PASSWORD_LENGTH} - onValidationChange={this.onFormValidationChange} onRegisterClick={this.onFormSubmit} onEditServerDetailsClick={onEditServerDetailsClick} flows={this.state.flows} diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index a3816829e7..a7d7efee0c 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -47,7 +47,6 @@ module.exports = React.createClass({ defaultUsername: PropTypes.string, defaultPassword: PropTypes.string, minPasswordLength: PropTypes.number, - onValidationChange: PropTypes.func, onRegisterClick: PropTypes.func.isRequired, // onRegisterClick(Object) => ?Promise onEditServerDetailsClick: PropTypes.func, flows: PropTypes.arrayOf(PropTypes.object).isRequired, @@ -68,8 +67,6 @@ module.exports = React.createClass({ getInitialState: function() { return { // Field error codes by field ID - // TODO: Remove `fieldErrors` once converted to new-style validation - fieldErrors: {}, fieldValid: {}, // The ISO2 country code selected in the phone number entry phoneCountry: this.props.defaultPhoneCountry, @@ -84,15 +81,6 @@ module.exports = React.createClass({ onSubmit: function(ev) { ev.preventDefault(); - // validate everything, in reverse order so - // the error that ends up being displayed - // is the one from the first invalid field. - // It's not super ideal that this just calls - // onValidationChange once for each invalid field. - // TODO: Remove these calls once converted to new-style validation. - this.validateField(FIELD_PASSWORD_CONFIRM, ev.type); - this.validateField(FIELD_PASSWORD, ev.type); - const allFieldsValid = this.verifyFieldsBeforeSubmit(); if (!allFieldsValid) { return; @@ -178,14 +166,7 @@ module.exports = React.createClass({ * @returns {boolean} true if all fields were valid last time they were validated. */ allFieldsValid: function() { - // TODO: Remove `fieldErrors` here when all fields converted - let keys = Object.keys(this.state.fieldErrors); - for (let i = 0; i < keys.length; ++i) { - if (this.state.fieldErrors[keys[i]]) { - return false; - } - } - keys = Object.keys(this.state.fieldValid); + const keys = Object.keys(this.state.fieldValid); for (let i = 0; i < keys.length; ++i) { if (!this.state.fieldValid[keys[i]]) { return false; @@ -203,62 +184,6 @@ module.exports = React.createClass({ return null; }, - validateField: function(fieldID, eventType) { - const pwd1 = this.state.password.trim(); - const pwd2 = this.state.passwordConfirm.trim(); - const allowEmpty = eventType === "blur"; - - // TODO: Remove rules here as they are converted to new-style validation - - switch (fieldID) { - case FIELD_PASSWORD: - if (allowEmpty && pwd1 === "") { - this.markFieldError(fieldID, true); - } else if (pwd1 == '') { - this.markFieldError( - fieldID, - false, - "RegistrationForm.ERR_PASSWORD_MISSING", - ); - } else if (pwd1.length < this.props.minPasswordLength) { - this.markFieldError( - fieldID, - false, - "RegistrationForm.ERR_PASSWORD_LENGTH", - ); - } else { - this.markFieldError(fieldID, true); - } - break; - case FIELD_PASSWORD_CONFIRM: - if (allowEmpty && pwd2 === "") { - this.markFieldError(fieldID, true); - } else { - this.markFieldError( - fieldID, pwd1 == pwd2, - "RegistrationForm.ERR_PASSWORD_MISMATCH", - ); - } - break; - } - }, - - markFieldError: function(fieldID, valid, errorCode) { - // TODO: Remove this function once all fields converted to new-style validation. - const { fieldErrors } = this.state; - if (valid) { - fieldErrors[fieldID] = null; - } else { - fieldErrors[fieldID] = errorCode; - } - this.setState({ - fieldErrors, - }); - // TODO: Remove outer validation handling once all fields converted to new-style - // validation in the form. - this.props.onValidationChange(fieldErrors); - }, - markFieldValid: function(fieldID, valid) { const { fieldValid } = this.state; fieldValid[fieldID] = valid; @@ -267,16 +192,6 @@ module.exports = React.createClass({ }); }, - _classForField: function(fieldID, ...baseClasses) { - let cls = baseClasses.join(' '); - // TODO: Remove this from fields as they are converted to new-style validation. - if (this.state.fieldErrors[fieldID]) { - if (cls) cls += ' '; - cls += 'error'; - } - return cls; - }, - onEmailChange(ev) { this.setState({ email: ev.target.value, @@ -307,26 +222,66 @@ module.exports = React.createClass({ ], }), - onPasswordBlur(ev) { - this.validateField(FIELD_PASSWORD, ev.type); - }, - onPasswordChange(ev) { this.setState({ password: ev.target.value, }); }, - onPasswordConfirmBlur(ev) { - this.validateField(FIELD_PASSWORD_CONFIRM, ev.type); + onPasswordValidate(fieldState) { + const result = this.validatePasswordRules(fieldState); + this.markFieldValid(FIELD_PASSWORD, result.valid); + return result; }, + validatePasswordRules: withValidation({ + rules: [ + { + key: "required", + test: ({ value, allowEmpty }) => allowEmpty || !!value, + invalid: () => _t("Enter password"), + }, + { + key: "minLength", + test: function({ value }) { + return !value || value.length >= this.props.minPasswordLength; + }, + invalid: function() { + return _t("Too short (min %(length)s)", { length: this.props.minPasswordLength }); + }, + }, + ], + }), + onPasswordConfirmChange(ev) { this.setState({ passwordConfirm: ev.target.value, }); }, + onPasswordConfirmValidate(fieldState) { + const result = this.validatePasswordConfirmRules(fieldState); + this.markFieldValid(FIELD_PASSWORD_CONFIRM, result.valid); + return result; + }, + + validatePasswordConfirmRules: withValidation({ + rules: [ + { + key: "required", + test: ({ value, allowEmpty }) => allowEmpty || !!value, + invalid: () => _t("Confirm password"), + }, + { + key: "match", + test: function({ value }) { + return !value || value === this.state.password; + }, + invalid: () => _t("Passwords don't match"), + }, + ], + }), + onPhoneCountryChange(newVal) { this.setState({ phoneCountry: newVal.iso2, @@ -436,6 +391,34 @@ module.exports = React.createClass({ />; }, + renderPassword() { + const Field = sdk.getComponent('elements.Field'); + return this[FIELD_PASSWORD] = field} + type="password" + label={_t("Password")} + defaultValue={this.props.defaultPassword} + value={this.state.password} + onChange={this.onPasswordChange} + onValidate={this.onPasswordValidate} + />; + }, + + renderPasswordConfirm() { + const Field = sdk.getComponent('elements.Field'); + return this[FIELD_PASSWORD_CONFIRM] = field} + type="password" + label={_t("Confirm")} + defaultValue={this.props.defaultPassword} + value={this.state.passwordConfirm} + onChange={this.onPasswordConfirmChange} + onValidate={this.onPasswordConfirmValidate} + />; + }, + renderPhoneNumber() { const threePidLogin = !SdkConfig.get().disable_3pid_login; if (!threePidLogin || !this._authStepIsUsed('m.login.msisdn')) { @@ -481,8 +464,6 @@ module.exports = React.createClass({ }, render: function() { - const Field = sdk.getComponent('elements.Field'); - let yourMatrixAccountText = _t('Create your Matrix account'); if (this.props.hsName) { yourMatrixAccountText = _t('Create your Matrix account on %(serverName)s', { @@ -523,26 +504,8 @@ module.exports = React.createClass({ {this.renderUsername()}
- - + {this.renderPassword()} + {this.renderPasswordConfirm()}
{this.renderEmail()} diff --git a/src/components/views/elements/Validation.js b/src/components/views/elements/Validation.js index 458d000e0e..b567eb4058 100644 --- a/src/components/views/elements/Validation.js +++ b/src/components/views/elements/Validation.js @@ -52,7 +52,7 @@ export default function withValidation({ description, rules }) { } // We're setting `this` to whichever component hold the validation // function. That allows rules to access the state of the component. - // eslint-disable-next-line babel/no-invalid-this + /* eslint-disable babel/no-invalid-this */ const ruleValid = rule.test.call(this, { value, allowEmpty }); valid = valid && ruleValid; if (ruleValid && rule.valid) { @@ -61,7 +61,7 @@ export default function withValidation({ description, rules }) { results.push({ key: rule.key, valid: true, - text: rule.valid(), + text: rule.valid.call(this), }); } else if (!ruleValid && rule.invalid) { // If the rule's result is invalid and has text to show for @@ -69,9 +69,10 @@ export default function withValidation({ description, rules }) { results.push({ key: rule.key, valid: false, - text: rule.invalid(), + text: rule.invalid.call(this), }); } + /* eslint-enable babel/no-invalid-this */ } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 910fc8f74f..5fd97a4305 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1325,6 +1325,9 @@ "Use an email address to recover your account": "Use an email address to recover your account", "Enter email address (required on this homeserver)": "Enter email address (required on this homeserver)", "Doesn't look like a valid email address": "Doesn't look like a valid email address", + "Enter password": "Enter password", + "Too short (min %(length)s)": "Too short (min %(length)s)", + "Passwords don't match": "Passwords don't match", "Other users can invite you to rooms using your contact details": "Other users can invite you to rooms using your contact details", "Enter phone number (required on this homeserver)": "Enter phone number (required on this homeserver)", "Doesn't look like a valid phone number": "Doesn't look like a valid phone number", @@ -1332,10 +1335,10 @@ "Enter username": "Enter username", "Some characters not allowed": "Some characters not allowed", "Email (optional)": "Email (optional)", + "Confirm": "Confirm", "Phone (optional)": "Phone (optional)", "Create your Matrix account": "Create your Matrix account", "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", - "Confirm": "Confirm", "Use an email address to recover your account.": "Use an email address to recover your account.", "Other users can invite you to rooms using your contact details.": "Other users can invite you to rooms using your contact details.", "Other servers": "Other servers", @@ -1525,9 +1528,6 @@ "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", "Unable to query for supported registration methods.": "Unable to query for supported registration methods.", "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", - "Missing password.": "Missing password.", - "Passwords don't match.": "Passwords don't match.", - "Password too short (min %(MIN_PASSWORD_LENGTH)s).": "Password too short (min %(MIN_PASSWORD_LENGTH)s).", "An unknown error occurred.": "An unknown error occurred.", "Create your account": "Create your account", "Commands": "Commands", From 4f41161a474f35f244e27af24f16314608683035 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 23 Apr 2019 14:55:57 +0100 Subject: [PATCH 18/64] Check password complexity during registration This adds a password complexity rule during registration to require strong passwords. This is based on the `zxcvbn` module that we already use for key backup passphrases. In addition, this also tweaks validation more generally to allow rules to be async functions. --- src/components/views/auth/RegistrationForm.js | 64 +++++++++++++++---- src/components/views/elements/Field.js | 4 +- src/components/views/elements/Validation.js | 27 +++++--- src/i18n/strings/en_EN.json | 4 +- src/utils/PasswordScorer.js | 4 +- 5 files changed, 77 insertions(+), 26 deletions(-) diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index a7d7efee0c..2ba01dc0c6 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -33,6 +33,8 @@ const FIELD_USERNAME = 'field_username'; const FIELD_PASSWORD = 'field_password'; const FIELD_PASSWORD_CONFIRM = 'field_password_confirm'; +const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc. + /** * A pure UI component which displays a registration form. */ @@ -75,13 +77,14 @@ module.exports = React.createClass({ phoneNumber: "", password: "", passwordConfirm: "", + passwordComplexity: null, }; }, - onSubmit: function(ev) { + onSubmit: async function(ev) { ev.preventDefault(); - const allFieldsValid = this.verifyFieldsBeforeSubmit(); + const allFieldsValid = await this.verifyFieldsBeforeSubmit(); if (!allFieldsValid) { return; } @@ -126,7 +129,7 @@ module.exports = React.createClass({ } }, - verifyFieldsBeforeSubmit() { + async verifyFieldsBeforeSubmit() { const fieldIDsInDisplayOrder = [ FIELD_USERNAME, FIELD_PASSWORD, @@ -145,6 +148,10 @@ module.exports = React.createClass({ field.validate({ allowEmpty: false }); } + // Validation and state updates are async, so we need to wait for them to complete + // first. Queue a `setState` callback and wait for it to resolve. + await new Promise(resolve => this.setState({}, resolve)); + if (this.allFieldsValid()) { return true; } @@ -198,8 +205,8 @@ module.exports = React.createClass({ }); }, - onEmailValidate(fieldState) { - const result = this.validateEmailRules(fieldState); + async onEmailValidate(fieldState) { + const result = await this.validateEmailRules(fieldState); this.markFieldValid(FIELD_EMAIL, result.valid); return result; }, @@ -228,13 +235,21 @@ module.exports = React.createClass({ }); }, - onPasswordValidate(fieldState) { - const result = this.validatePasswordRules(fieldState); + async onPasswordValidate(fieldState) { + const result = await this.validatePasswordRules(fieldState); this.markFieldValid(FIELD_PASSWORD, result.valid); return result; }, validatePasswordRules: withValidation({ + description: function() { + const complexity = this.state.passwordComplexity; + const score = complexity ? complexity.score : 0; + return ; + }, rules: [ { key: "required", @@ -250,6 +265,29 @@ module.exports = React.createClass({ return _t("Too short (min %(length)s)", { length: this.props.minPasswordLength }); }, }, + { + key: "complexity", + test: async function({ value }) { + if (!value) { + return false; + } + const { scorePassword } = await import('../../../utils/PasswordScorer'); + const complexity = scorePassword(value); + this.setState({ + passwordComplexity: complexity, + }); + return complexity.score >= PASSWORD_MIN_SCORE; + }, + valid: () => _t("Nice, strong password!"), + invalid: function() { + const complexity = this.state.passwordComplexity; + if (!complexity) { + return null; + } + const { feedback } = complexity; + return feedback.warning || feedback.suggestions[0] || _t("Keep going..."); + }, + }, ], }), @@ -259,8 +297,8 @@ module.exports = React.createClass({ }); }, - onPasswordConfirmValidate(fieldState) { - const result = this.validatePasswordConfirmRules(fieldState); + async onPasswordConfirmValidate(fieldState) { + const result = await this.validatePasswordConfirmRules(fieldState); this.markFieldValid(FIELD_PASSWORD_CONFIRM, result.valid); return result; }, @@ -295,8 +333,8 @@ module.exports = React.createClass({ }); }, - onPhoneNumberValidate(fieldState) { - const result = this.validatePhoneNumberRules(fieldState); + async onPhoneNumberValidate(fieldState) { + const result = await this.validatePhoneNumberRules(fieldState); this.markFieldValid(FIELD_PHONE_NUMBER, result.valid); return result; }, @@ -325,8 +363,8 @@ module.exports = React.createClass({ }); }, - onUsernameValidate(fieldState) { - const result = this.validateUsernameRules(fieldState); + async onUsernameValidate(fieldState) { + const result = await this.validateUsernameRules(fieldState); this.markFieldValid(FIELD_USERNAME, result.valid); return result; }, diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js index 6eba832523..8f0746dff2 100644 --- a/src/components/views/elements/Field.js +++ b/src/components/views/elements/Field.js @@ -87,12 +87,12 @@ export default class Field extends React.PureComponent { this.input.focus(); } - validate({ focused, allowEmpty = true }) { + async validate({ focused, allowEmpty = true }) { if (!this.props.onValidate) { return; } const value = this.input ? this.input.value : null; - const { valid, feedback } = this.props.onValidate({ + const { valid, feedback } = await this.props.onValidate({ value, focused, allowEmpty, diff --git a/src/components/views/elements/Validation.js b/src/components/views/elements/Validation.js index b567eb4058..c37970e534 100644 --- a/src/components/views/elements/Validation.js +++ b/src/components/views/elements/Validation.js @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +/* eslint-disable babel/no-invalid-this */ + import classNames from 'classnames'; /** @@ -34,7 +36,7 @@ import classNames from 'classnames'; * the overall validity and a feedback UI that can be rendered for more detail. */ export default function withValidation({ description, rules }) { - return function onValidate({ value, focused, allowEmpty = true }) { + return async function onValidate({ value, focused, allowEmpty = true }) { // TODO: Re-run only after ~200ms of inactivity if (!value && allowEmpty) { return { @@ -50,29 +52,35 @@ export default function withValidation({ description, rules }) { if (!rule.key || !rule.test) { continue; } - // We're setting `this` to whichever component hold the validation + // We're setting `this` to whichever component holds the validation // function. That allows rules to access the state of the component. - /* eslint-disable babel/no-invalid-this */ - const ruleValid = rule.test.call(this, { value, allowEmpty }); + const ruleValid = await rule.test.call(this, { value, allowEmpty }); valid = valid && ruleValid; if (ruleValid && rule.valid) { // If the rule's result is valid and has text to show for // the valid state, show it. + const text = rule.valid.call(this); + if (!text) { + continue; + } results.push({ key: rule.key, valid: true, - text: rule.valid.call(this), + text, }); } else if (!ruleValid && rule.invalid) { // If the rule's result is invalid and has text to show for // the invalid state, show it. + const text = rule.invalid.call(this); + if (!text) { + continue; + } results.push({ key: rule.key, valid: false, - text: rule.invalid.call(this), + text, }); } - /* eslint-enable babel/no-invalid-this */ } } @@ -102,7 +110,10 @@ export default function withValidation({ description, rules }) { let summary; if (description) { - summary =
{description()}
; + // We're setting `this` to whichever component holds the validation + // function. That allows rules to access the state of the component. + const content = description.call(this); + summary =
{content}
; } let feedback; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5fd97a4305..262e143ae6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1327,6 +1327,8 @@ "Doesn't look like a valid email address": "Doesn't look like a valid email address", "Enter password": "Enter password", "Too short (min %(length)s)": "Too short (min %(length)s)", + "Nice, strong password!": "Nice, strong password!", + "Keep going...": "Keep going...", "Passwords don't match": "Passwords don't match", "Other users can invite you to rooms using your contact details": "Other users can invite you to rooms using your contact details", "Enter phone number (required on this homeserver)": "Enter phone number (required on this homeserver)", @@ -1528,7 +1530,6 @@ "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", "Unable to query for supported registration methods.": "Unable to query for supported registration methods.", "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", - "An unknown error occurred.": "An unknown error occurred.", "Create your account": "Create your account", "Commands": "Commands", "Results from DuckDuckGo": "Results from DuckDuckGo", @@ -1567,7 +1568,6 @@ "File to import": "File to import", "Import": "Import", "Great! This passphrase looks strong enough.": "Great! This passphrase looks strong enough.", - "Keep going...": "Keep going...", "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.": "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.", "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", "Enter a passphrase...": "Enter a passphrase...", diff --git a/src/utils/PasswordScorer.js b/src/utils/PasswordScorer.js index 647436c131..3c366a73f8 100644 --- a/src/utils/PasswordScorer.js +++ b/src/utils/PasswordScorer.js @@ -67,7 +67,9 @@ export function scorePassword(password) { if (password.length === 0) return null; const userInputs = ZXCVBN_USER_INPUTS.slice(); - userInputs.push(MatrixClientPeg.get().getUserIdLocalpart()); + if (MatrixClientPeg.get()) { + userInputs.push(MatrixClientPeg.get().getUserIdLocalpart()); + } let zxcvbnResult = zxcvbn(password, userInputs); // Work around https://github.com/dropbox/zxcvbn/issues/216 From a20d23daf317786728e71793411abdd8ce649e8a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 23 Apr 2019 15:05:03 +0100 Subject: [PATCH 19/64] Remove older password length check Now that we have a fancier password complexity check, remove the older minimum length to avoid the feeling of two password style guides fighting each other. --- src/components/structures/auth/Registration.js | 3 --- src/components/views/auth/RegistrationForm.js | 11 ----------- src/i18n/strings/en_EN.json | 1 - 3 files changed, 15 deletions(-) diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 87fea3ec4b..df87c1b9ca 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -28,8 +28,6 @@ import SdkConfig from '../../../SdkConfig'; import { messageForResourceLimitError } from '../../../utils/ErrorUtils'; import * as ServerType from '../../views/auth/ServerTypeSelector'; -const MIN_PASSWORD_LENGTH = 6; - // Phases // Show controls to configure server details const PHASE_SERVER_DETAILS = 0; @@ -482,7 +480,6 @@ module.exports = React.createClass({ defaultPhoneCountry={this.state.formVals.phoneCountry} defaultPhoneNumber={this.state.formVals.phoneNumber} defaultPassword={this.state.formVals.password} - minPasswordLength={MIN_PASSWORD_LENGTH} onRegisterClick={this.onFormSubmit} onEditServerDetailsClick={onEditServerDetailsClick} flows={this.state.flows} diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 2ba01dc0c6..21c2743301 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -48,7 +48,6 @@ module.exports = React.createClass({ defaultPhoneNumber: PropTypes.string, defaultUsername: PropTypes.string, defaultPassword: PropTypes.string, - minPasswordLength: PropTypes.number, onRegisterClick: PropTypes.func.isRequired, // onRegisterClick(Object) => ?Promise onEditServerDetailsClick: PropTypes.func, flows: PropTypes.arrayOf(PropTypes.object).isRequired, @@ -61,7 +60,6 @@ module.exports = React.createClass({ getDefaultProps: function() { return { - minPasswordLength: 6, onValidationChange: console.error, }; }, @@ -256,15 +254,6 @@ module.exports = React.createClass({ test: ({ value, allowEmpty }) => allowEmpty || !!value, invalid: () => _t("Enter password"), }, - { - key: "minLength", - test: function({ value }) { - return !value || value.length >= this.props.minPasswordLength; - }, - invalid: function() { - return _t("Too short (min %(length)s)", { length: this.props.minPasswordLength }); - }, - }, { key: "complexity", test: async function({ value }) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 262e143ae6..d1ff8b2695 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1326,7 +1326,6 @@ "Enter email address (required on this homeserver)": "Enter email address (required on this homeserver)", "Doesn't look like a valid email address": "Doesn't look like a valid email address", "Enter password": "Enter password", - "Too short (min %(length)s)": "Too short (min %(length)s)", "Nice, strong password!": "Nice, strong password!", "Keep going...": "Keep going...", "Passwords don't match": "Passwords don't match", From 67d7091dcdbdb51457aebaaca6b3f9b909f6c54b Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 23 Apr 2019 16:35:11 +0100 Subject: [PATCH 20/64] Password score progress should be full width in tooltip --- res/css/views/auth/_AuthBody.scss | 4 ++++ res/css/views/elements/_Field.scss | 1 + src/components/views/auth/RegistrationForm.js | 1 + 3 files changed, 6 insertions(+) diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index fa034095b6..ce597f1416 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -130,3 +130,7 @@ limitations under the License. .mx_AuthBody_spinner { margin: 1em 0; } + +.mx_AuthBody_passwordScore { + width: 100%; +} diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index 20b1efd28b..147bb3b471 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -168,6 +168,7 @@ limitations under the License. .mx_Field_tooltip { margin-top: -12px; margin-left: 4px; + width: 200px; } .mx_Field_tooltip.mx_Field_valid { diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 21c2743301..0045b4dd59 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -244,6 +244,7 @@ module.exports = React.createClass({ const complexity = this.state.passwordComplexity; const score = complexity ? complexity.score : 0; return ; From aec14e64facc3e172e60af1cba18b075bf52a6af Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 23 Apr 2019 17:59:19 +0100 Subject: [PATCH 21/64] Throttle validation in response to user input This avoids the case of the password complexity progress jumping wildly for every character you type. --- src/components/views/elements/Field.js | 14 +++++++++++--- src/components/views/elements/Validation.js | 1 - 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js index 8f0746dff2..91447a8846 100644 --- a/src/components/views/elements/Field.js +++ b/src/components/views/elements/Field.js @@ -18,6 +18,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import sdk from '../../../index'; +import { throttle } from 'lodash'; + +// Invoke validation from user input (when typing, etc.) at most once every N ms. +const VALIDATION_THROTTLE_MS = 200; export default class Field extends React.PureComponent { static propTypes = { @@ -64,9 +68,7 @@ export default class Field extends React.PureComponent { }; onChange = (ev) => { - this.validate({ - focused: true, - }); + this.validateOnChange(); // Parent component may have supplied its own `onChange` as well if (this.props.onChange) { this.props.onChange(ev); @@ -103,6 +105,12 @@ export default class Field extends React.PureComponent { }); } + validateOnChange = throttle(() => { + this.validate({ + focused: true, + }); + }, VALIDATION_THROTTLE_MS); + render() { const { element, prefix, onValidate, children, ...inputProps } = this.props; diff --git a/src/components/views/elements/Validation.js b/src/components/views/elements/Validation.js index c37970e534..31363b87c8 100644 --- a/src/components/views/elements/Validation.js +++ b/src/components/views/elements/Validation.js @@ -37,7 +37,6 @@ import classNames from 'classnames'; */ export default function withValidation({ description, rules }) { return async function onValidate({ value, focused, allowEmpty = true }) { - // TODO: Re-run only after ~200ms of inactivity if (!value && allowEmpty) { return { valid: null, From 0b42ded007691f5b15dcb40c9644d5f3cdacd487 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 24 Apr 2019 09:42:52 +0100 Subject: [PATCH 22/64] Style complexity progress bars more heavily This disables the native progress appearance and uses the green color from our themes. --- res/css/views/auth/_AuthBody.scss | 20 ++++++++++++++++++++ res/css/views/elements/_Validation.scss | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index ce597f1416..16ac876869 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -133,4 +133,24 @@ limitations under the License. .mx_AuthBody_passwordScore { width: 100%; + appearance: none; + height: 4px; + border: 0; + border-radius: 2px; + position: absolute; + top: -12px; + + &::-moz-progress-bar { + border-radius: 2px; + background-color: $accent-color; + } + + &::-webkit-progress-bar, + &::-webkit-progress-value { + border-radius: 2px; + } + + &::-webkit-progress-value { + background-color: $accent-color; + } } diff --git a/res/css/views/elements/_Validation.scss b/res/css/views/elements/_Validation.scss index 4c059f9747..1f9bd880e6 100644 --- a/res/css/views/elements/_Validation.scss +++ b/res/css/views/elements/_Validation.scss @@ -14,6 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_Validation { + position: relative; +} + .mx_Validation_details { padding-left: 20px; margin: 0; From 26f732723ebbfc749dc0cc08ecde243a730df58e Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 25 Apr 2019 11:10:35 +0100 Subject: [PATCH 23/64] Animate tooltips when hiding as well as showing This uses the same animation style as on show, but twice as fast. --- res/css/views/elements/_Tooltip.scss | 9 ++++++++- src/components/views/elements/Field.js | 22 ++++++++++++++++++---- src/components/views/elements/Tooltip.js | 15 ++++++++++++++- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/res/css/views/elements/_Tooltip.scss b/res/css/views/elements/_Tooltip.scss index 2f35bd338e..43ddf6dde5 100644 --- a/res/css/views/elements/_Tooltip.scss +++ b/res/css/views/elements/_Tooltip.scss @@ -50,7 +50,6 @@ limitations under the License. .mx_Tooltip { display: none; - animation: mx_fadein 0.2s; position: fixed; border: 1px solid $menu-border-color; border-radius: 4px; @@ -66,4 +65,12 @@ limitations under the License. max-width: 200px; word-break: break-word; margin-right: 50px; + + &.mx_Tooltip_visible { + animation: mx_fadein 0.2s forwards; + } + + &.mx_Tooltip_invisible { + animation: mx_fadeout 0.1s forwards; + } } diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js index 91447a8846..93bea70fc8 100644 --- a/src/components/views/elements/Field.js +++ b/src/components/views/elements/Field.js @@ -99,10 +99,23 @@ export default class Field extends React.PureComponent { focused, allowEmpty, }); - this.setState({ - valid, - feedback, - }); + + if (feedback) { + this.setState({ + valid, + feedback, + feedbackVisible: true, + }); + } else { + // When we receive null `feedback`, we want to hide the tooltip. + // We leave the previous `feedback` content in state without updating it, + // so that we can hide the tooltip containing the most recent feedback + // via CSS animation. + this.setState({ + valid, + feedbackVisible: false, + }); + } } validateOnChange = throttle(() => { @@ -147,6 +160,7 @@ export default class Field extends React.PureComponent { if (this.state.feedback) { tooltip = ; } diff --git a/src/components/views/elements/Tooltip.js b/src/components/views/elements/Tooltip.js index 473aeb3bdc..1cc82978ed 100644 --- a/src/components/views/elements/Tooltip.js +++ b/src/components/views/elements/Tooltip.js @@ -31,10 +31,20 @@ module.exports = React.createClass({ className: React.PropTypes.string, // Class applied to the tooltip itself tooltipClassName: React.PropTypes.string, + // Whether the tooltip is visible or hidden. + // The hidden state allows animating the tooltip away via CSS. + // Defaults to visible if unset. + visible: React.PropTypes.bool, // the react element to put into the tooltip label: React.PropTypes.node, }, + getDefaultProps() { + return { + visible: true, + }; + }, + // Create a wrapper for the tooltip outside the parent and attach it to the body element componentDidMount: function() { this.tooltipContainer = document.createElement("div"); @@ -85,7 +95,10 @@ module.exports = React.createClass({ style = this._updatePosition(style); style.display = "block"; - const tooltipClasses = classNames("mx_Tooltip", this.props.tooltipClassName); + const tooltipClasses = classNames("mx_Tooltip", this.props.tooltipClassName, { + "mx_Tooltip_visible": this.props.visible, + "mx_Tooltip_invisible": !this.props.visible, + }); const tooltip = (
From af178292290337c8b7cdedac3a24d02b1670a646 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 25 Apr 2019 11:27:03 +0100 Subject: [PATCH 24/64] Blur active field before submit validation --- src/components/views/auth/RegistrationForm.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 0045b4dd59..6e55581af0 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -128,6 +128,13 @@ module.exports = React.createClass({ }, async verifyFieldsBeforeSubmit() { + // Blur the active element if any, so we first run its blur validation, + // which is less strict than the pass we're about to do below for all fields. + const activeElement = document.activeElement; + if (activeElement) { + activeElement.blur(); + } + const fieldIDsInDisplayOrder = [ FIELD_USERNAME, FIELD_PASSWORD, From 443a15eeb95be355a32a77c42af0fe2f523147aa Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Wed, 24 Apr 2019 18:09:23 -0400 Subject: [PATCH 25/64] actually clear bit 63 instead of bit 55 --- src/utils/MegolmExportEncryption.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/MegolmExportEncryption.js b/src/utils/MegolmExportEncryption.js index 01c521da0c..2f2fc4cca7 100644 --- a/src/utils/MegolmExportEncryption.js +++ b/src/utils/MegolmExportEncryption.js @@ -147,7 +147,7 @@ export async function encryptMegolmKeyFile(data, password, options) { // clear bit 63 of the IV to stop us hitting the 64-bit counter boundary // (which would mean we wouldn't be able to decrypt on Android). The loss // of a single bit of iv is a price we have to pay. - iv[9] &= 0x7f; + iv[8] &= 0x7f; const [aesKey, hmacKey] = await deriveKeys(salt, kdfRounds, password); const encodedData = new TextEncoder().encode(data); From 147c287acdc5054ad6a8a6d18ba8cee8c80194e5 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 26 Apr 2019 10:45:13 +0100 Subject: [PATCH 26/64] Fix typo in Settings docs --- docs/settings.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/settings.md b/docs/settings.md index c88888663b..1ba8981f84 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -229,7 +229,7 @@ Controllers are notified of changes by the `SettingsStore`, and are given the op ### Features -Features automatically get considered as `disabled` if they are not listed in the `SdkConfig` or `enable_labs` is +Features automatically get considered as `disabled` if they are not listed in the `SdkConfig` or `enableLabs` is false/not set. Features are always checked against the configuration before going through the level order as they have the option of being forced-on or forced-off for the application. This is done by the `features` section and looks something like this: @@ -260,4 +260,4 @@ In practice, handlers which rely on remote changes (account data, room events, e generalized `WatchManager` - a class specifically designed to deduplicate the logic of managing watchers. The handlers which are localized to the local client (device) generally just trigger the `WatchManager` when they manipulate the setting themselves as there's nothing to really 'watch'. - \ No newline at end of file + From 38d5b2c21c6cce51619280e71a0c6bdb195e9025 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Apr 2019 15:54:35 +0100 Subject: [PATCH 27/64] Trigger riot-web build Can't really test this without it being on develop --- .buildkite/pipeline.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index ac05da02e3..fa8a19d7f6 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -79,3 +79,13 @@ steps: - docker#v3.0.1: image: "node:10" propagate-environment: true + + - wait + + - label: "🐴 Trigger riot-web" + trigger: "riot-web" + branches: "develop" + build: + branch: "develop" + message: "[react-sdk] ${BUILDKITE_MESSAGE}" + async: true From 74abaa4abc6456b001f303bbdc3d1193fc050591 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Apr 2019 16:02:12 +0100 Subject: [PATCH 28/64] wrong number of spaces --- .buildkite/pipeline.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index fa8a19d7f6..946e417676 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -80,12 +80,12 @@ steps: image: "node:10" propagate-environment: true - - wait - - - label: "🐴 Trigger riot-web" - trigger: "riot-web" - branches: "develop" - build: - branch: "develop" - message: "[react-sdk] ${BUILDKITE_MESSAGE}" - async: true + - wait + + - label: "🐴 Trigger riot-web" + trigger: "riot-web" + branches: "develop" + build: + branch: "develop" + message: "[react-sdk] ${BUILDKITE_MESSAGE}" + async: true From 530c92e03d847098867898a52c017fc6ef1d1842 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 26 Apr 2019 11:01:29 +0100 Subject: [PATCH 29/64] Rename event edit button to options button This naming is clearer as it doesn't really edit at all (it shows a context menu). This should also be less confusing with actual editing when it arrives. --- res/css/views/rooms/_EventTile.scss | 8 ++++---- src/components/views/rooms/EventTile.js | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 6363750f4c..bbca1c3498 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -219,7 +219,7 @@ limitations under the License. width: auto; } -.mx_EventTile_editButton { +.mx_EventTile_optionsButton { position: absolute; display: inline-block; visibility: hidden; @@ -232,8 +232,8 @@ limitations under the License. user-select: none; } -.mx_EventTile:hover .mx_EventTile_editButton, -.mx_EventTile.menu .mx_EventTile_editButton { +.mx_EventTile:hover .mx_EventTile_optionsButton, +.mx_EventTile.menu .mx_EventTile_optionsButton { visibility: visible; } @@ -551,7 +551,7 @@ limitations under the License. top: 3px; } - .mx_EventTile_editButton { + .mx_EventTile_optionsButton { top: 3px; } diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index d7c9f1e443..933f134be7 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -309,7 +309,7 @@ module.exports = withMatrixClient(React.createClass({ return actions.tweaks.highlight; }, - onEditClicked: function(e) { + onOptionsClicked: function(e) { const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu'); const buttonRect = e.target.getBoundingClientRect(); @@ -602,8 +602,8 @@ module.exports = withMatrixClient(React.createClass({ } } - const editButton = ( - + const optionsButton = ( + ); const timestamp = this.props.mxEvent.getTs() ? @@ -755,7 +755,7 @@ module.exports = withMatrixClient(React.createClass({ showUrlPreview={this.props.showUrlPreview} onHeightChanged={this.props.onHeightChanged} /> { keyRequestInfo } - { editButton } + { optionsButton }
{ // The avatar goes after the event tile as it's absolutly positioned to be over the From ed8bbc70820d0b067af0f5b9b528f9442a6f83cb Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 26 Apr 2019 12:14:30 +0100 Subject: [PATCH 30/64] Extract message options button to action bar This adds a new action bar component to hold multiple per-message actions. This existing options button has moved to this new component, and is currently the only action. --- res/css/_components.scss | 1 + res/css/views/messages/_MessageActionBar.scss | 37 ++++++++ res/css/views/rooms/_EventTile.scss | 25 +----- .../views/messages/MessageActionBar.js | 87 +++++++++++++++++++ src/components/views/rooms/EventTile.js | 57 ++++-------- src/i18n/strings/en_EN.json | 2 +- 6 files changed, 148 insertions(+), 61 deletions(-) create mode 100644 res/css/views/messages/_MessageActionBar.scss create mode 100644 src/components/views/messages/MessageActionBar.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 8bea138acb..bb09b873a3 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -113,6 +113,7 @@ @import "./views/messages/_MNoticeBody.scss"; @import "./views/messages/_MStickerBody.scss"; @import "./views/messages/_MTextBody.scss"; +@import "./views/messages/_MessageActionBar.scss"; @import "./views/messages/_MessageTimestamp.scss"; @import "./views/messages/_RoomAvatarEvent.scss"; @import "./views/messages/_SenderProfile.scss"; diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss new file mode 100644 index 0000000000..917f82dc66 --- /dev/null +++ b/res/css/views/messages/_MessageActionBar.scss @@ -0,0 +1,37 @@ +/* +Copyright 2019 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_MessageActionBar { + position: absolute; + visibility: hidden; + cursor: pointer; + top: 6px; + right: 6px; + user-select: none; +} + +.mx_MessageActionBar_optionsButton { + display: inline-block; + width: 19px; + height: 19px; + background-image: url($edit-button-url); +} + +.mx_MatrixChat_useCompactLayout { + .mx_MessageActionBar { + top: 3px; + } +} diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index bbca1c3498..f4c12bb734 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -121,7 +121,7 @@ limitations under the License. } .mx_EventTile:hover .mx_EventTile_line, -.mx_EventTile.menu .mx_EventTile_line +.mx_EventTile.mx_EventTile_actionBarFocused .mx_EventTile_line { background-color: $event-selected-color; } @@ -206,7 +206,7 @@ limitations under the License. // Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies) .mx_EventTile_last > div > a > .mx_MessageTimestamp, .mx_EventTile:hover > div > a > .mx_MessageTimestamp, -.mx_EventTile.menu > div > a > .mx_MessageTimestamp { +.mx_EventTile.mx_EventTile_actionBarFocused > div > a > .mx_MessageTimestamp { visibility: visible; } @@ -219,21 +219,8 @@ limitations under the License. width: auto; } -.mx_EventTile_optionsButton { - position: absolute; - display: inline-block; - visibility: hidden; - cursor: pointer; - top: 6px; - right: 6px; - width: 19px; - height: 19px; - background-image: url($edit-button-url); - user-select: none; -} - -.mx_EventTile:hover .mx_EventTile_optionsButton, -.mx_EventTile.menu .mx_EventTile_optionsButton { +.mx_EventTile:hover .mx_MessageActionBar, +.mx_EventTile.mx_EventTile_actionBarFocused .mx_MessageActionBar { visibility: visible; } @@ -551,10 +538,6 @@ limitations under the License. top: 3px; } - .mx_EventTile_optionsButton { - top: 3px; - } - .mx_EventTile_readAvatars { top: 27px; } diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js new file mode 100644 index 0000000000..c4c271e1a5 --- /dev/null +++ b/src/components/views/messages/MessageActionBar.js @@ -0,0 +1,87 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { _t } from '../../../languageHandler'; +import sdk from '../../../index'; +import Modal from '../../../Modal'; +import { createMenu } from '../../structures/ContextualMenu'; + +export default class MessageActionBar extends React.PureComponent { + static propTypes = { + mxEvent: PropTypes.object.isRequired, + permalinkCreator: PropTypes.object, + tile: PropTypes.element, + replyThread: PropTypes.element, + onFocusChange: PropTypes.func, + }; + + onFocusChange = (focused) => { + if (!this.props.onFocusChange) { + return; + } + this.props.onFocusChange(focused); + } + + onCryptoClicked = () => { + const event = this.props.mxEvent; + Modal.createTrackedDialogAsync('Encrypted Event Dialog', '', + import('../../../async-components/views/dialogs/EncryptedEventDialog'), + {event}, + ); + } + + onOptionsClicked = (ev) => { + const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu'); + const buttonRect = ev.target.getBoundingClientRect(); + + // The window X and Y offsets are to adjust position when zoomed in to page + const x = buttonRect.right + window.pageXOffset; + const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19; + + const {tile, replyThread} = this.props; + + let e2eInfoCallback = null; + if (this.props.mxEvent.isEncrypted()) { + e2eInfoCallback = () => this.onCryptoClicked(); + } + + createMenu(MessageContextMenu, { + chevronOffset: 10, + mxEvent: this.props.mxEvent, + left: x, + top: y, + permalinkCreator: this.props.permalinkCreator, + eventTileOps: tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined, + collapseReplyThread: replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined, + e2eInfoCallback: e2eInfoCallback, + onFinished: () => { + this.onFocusChange(false); + }, + }); + + this.onFocusChange(true); + } + + render() { + // TODO: Move the bar to a separate element once there are several buttons + return ; + } +} diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 933f134be7..dd0a7aa47b 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -17,7 +17,6 @@ limitations under the License. 'use strict'; - import ReplyThread from "../elements/ReplyThread"; const React = require('react'); @@ -30,7 +29,6 @@ const sdk = require('../../../index'); const TextForEvent = require('../../../TextForEvent'); import withMatrixClient from '../../../wrappers/withMatrixClient'; -const ContextualMenu = require('../../structures/ContextualMenu'); import dis from '../../../dispatcher'; import SettingsStore from "../../../settings/SettingsStore"; import {EventStatus} from 'matrix-js-sdk'; @@ -172,8 +170,8 @@ module.exports = withMatrixClient(React.createClass({ getInitialState: function() { return { - // Whether the context menu is being displayed. - menu: false, + // Whether the action bar is focused. + actionBarFocused: false, // Whether all read receipts are being displayed. If not, only display // a truncation of them. allReadAvatars: false, @@ -309,36 +307,6 @@ module.exports = withMatrixClient(React.createClass({ return actions.tweaks.highlight; }, - onOptionsClicked: function(e) { - const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu'); - const buttonRect = e.target.getBoundingClientRect(); - - // The window X and Y offsets are to adjust position when zoomed in to page - const x = buttonRect.right + window.pageXOffset; - const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19; - const self = this; - - const {tile, replyThread} = this.refs; - - let e2eInfoCallback = null; - if (this.props.mxEvent.isEncrypted()) e2eInfoCallback = () => this.onCryptoClicked(); - - ContextualMenu.createMenu(MessageContextMenu, { - chevronOffset: 10, - mxEvent: this.props.mxEvent, - left: x, - top: y, - permalinkCreator: this.props.permalinkCreator, - eventTileOps: tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined, - collapseReplyThread: replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined, - e2eInfoCallback: e2eInfoCallback, - onFinished: function() { - self.setState({menu: false}); - }, - }); - this.setState({menu: true}); - }, - toggleAllReadAvatars: function() { this.setState({ allReadAvatars: !this.state.allReadAvatars, @@ -490,6 +458,12 @@ module.exports = withMatrixClient(React.createClass({ return null; }, + onActionBarFocusChange(focused) { + this.setState({ + actionBarFocused: focused, + }); + }, + render: function() { const MessageTimestamp = sdk.getComponent('messages.MessageTimestamp'); const SenderProfile = sdk.getComponent('messages.SenderProfile'); @@ -536,7 +510,7 @@ module.exports = withMatrixClient(React.createClass({ mx_EventTile_continuation: this.props.tileShape ? '' : this.props.continuation, mx_EventTile_last: this.props.last, mx_EventTile_contextual: this.props.contextual, - menu: this.state.menu, + mx_EventTile_actionBarFocused: this.state.actionBarFocused, mx_EventTile_verified: this.state.verified === true, mx_EventTile_unverified: this.state.verified === false, mx_EventTile_bad: isEncryptionFailure, @@ -602,9 +576,14 @@ module.exports = withMatrixClient(React.createClass({ } } - const optionsButton = ( - - ); + const MessageActionBar = sdk.getComponent('messages.MessageActionBar'); + const actionBar = ; const timestamp = this.props.mxEvent.getTs() ? : null; @@ -755,7 +734,7 @@ module.exports = withMatrixClient(React.createClass({ showUrlPreview={this.props.showUrlPreview} onHeightChanged={this.props.onHeightChanged} /> { keyRequestInfo } - { optionsButton } + { actionBar }
{ // The avatar goes after the event tile as it's absolutly positioned to be over the diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d1ff8b2695..d7d2b108a2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -671,7 +671,6 @@ "%(senderName)s sent an image": "%(senderName)s sent an image", "%(senderName)s sent a video": "%(senderName)s sent a video", "%(senderName)s uploaded a file": "%(senderName)s uploaded a file", - "Options": "Options", "Your key share request has been sent - please check your other devices for key share requests.": "Your key share request has been sent - please check your other devices for key share requests.", "Key share requests are sent to your other devices automatically. If you rejected or dismissed the key share request on your other devices, click here to request the keys for this session again.": "Key share requests are sent to your other devices automatically. If you rejected or dismissed the key share request on your other devices, click here to request the keys for this session again.", "If your other devices do not have the key for this message you will not be able to decrypt them.": "If your other devices do not have the key for this message you will not be able to decrypt them.", @@ -889,6 +888,7 @@ "Today": "Today", "Yesterday": "Yesterday", "Error decrypting audio": "Error decrypting audio", + "Options": "Options", "Attachment": "Attachment", "Error decrypting attachment": "Error decrypting attachment", "Decrypt %(text)s": "Decrypt %(text)s", From 8ef9fe951dca64db7f83eb3dd60032a2f77b4ace Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 29 Apr 2019 13:04:16 +0100 Subject: [PATCH 31/64] Update styling of message action bar for multiple buttons This applies the new design for multiple buttons in the message action bar, paving the way for more things to appear here. In addition, this changes the existing options button to use the three vertical dots icon. Some theme colors are also tweaked to align with what they were meant to be from the unified palette. --- res/css/views/messages/_MessageActionBar.scss | 56 +++++++++++++++---- res/img/icon_context_message.svg | 15 ----- res/themes/dark/css/_dark.scss | 3 + res/themes/light/css/_light.scss | 8 ++- .../views/messages/MessageActionBar.js | 11 ++-- 5 files changed, 58 insertions(+), 35 deletions(-) delete mode 100644 res/img/icon_context_message.svg diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index 917f82dc66..7bd5fa5cf9 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -18,20 +18,52 @@ limitations under the License. position: absolute; visibility: hidden; cursor: pointer; - top: 6px; - right: 6px; + display: flex; + height: 24px; + border-radius: 4px; + background: $primary-bg-color; + top: -13px; + right: 8px; user-select: none; -} -.mx_MessageActionBar_optionsButton { - display: inline-block; - width: 19px; - height: 19px; - background-image: url($edit-button-url); -} + > * { + display: inline-block; + position: relative; + width: 27px; + border: 1px solid $message-action-bar-border-color; + margin-left: -1px; -.mx_MatrixChat_useCompactLayout { - .mx_MessageActionBar { - top: 3px; + &:hover { + border-color: $message-action-bar-hover-border-color; + z-index: 1; + } + + &:first-child { + border-radius: 3px 0 0 3px; + } + + &:last-child { + border-radius: 0 3px 3px 0; + } + + &:only-child { + border-radius: 3px; + } + + &::after { + content: ''; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + mask-repeat: no-repeat; + mask-position: center; + background-color: $primary-fg-color; + } } } + +.mx_MessageActionBar_optionsButton::after { + mask-image: url('$(res)/img/icon_context.svg'); +} diff --git a/res/img/icon_context_message.svg b/res/img/icon_context_message.svg deleted file mode 100644 index f2ceccfa78..0000000000 --- a/res/img/icon_context_message.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - ED5D3E59-2561-4AC1-9B43-82FBC51767FC - Created with sketchtool. - - - - - - - - - - diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index c433a028a3..0d3b40b64f 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -146,6 +146,9 @@ $room-warning-bg-color: $header-panel-bg-color; $dark-panel-bg-color: $header-panel-bg-color; $panel-gradient: rgba(34, 38, 46, 0), rgba(34, 38, 46, 1); +$message-action-bar-border-color: $input-darker-bg-color; +$message-action-bar-hover-border-color: $text-secondary-color; + // ***** Mixins! ***** @define-mixin mx_DialogButton { diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index ca2e4cf58d..bdd5f10cc9 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -73,7 +73,7 @@ $primary-hairline-color: #e5e5e5; // used for the border of input text fields $input-border-color: #e7e7e7; -$input-darker-bg-color: rgba(193, 201, 214, 0.29); +$input-darker-bg-color: #e3e8f0; $input-darker-fg-color: #9fa9ba; $input-lighter-bg-color: #f2f5f8; $input-lighter-fg-color: $input-darker-fg-color; @@ -153,7 +153,7 @@ $roomheader-button-color: #91A1C0; $groupheader-button-color: #91A1C0; $rightpanel-button-color: #91A1C0; $composer-button-color: #91A1C0; -$roomtopic-color: #9fa9ba; +$roomtopic-color: #9e9e9e; $eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #c9ced6; @@ -203,7 +203,6 @@ $event-redacted-border-color: #cccccc; // event timestamp $event-timestamp-color: #acacac; -$edit-button-url: "$(res)/img/icon_context_message.svg"; $copy-button-url: "$(res)/img/icon_copy_message.svg"; // e2e @@ -255,6 +254,9 @@ $authpage-secondary-color: #61708b; $dark-panel-bg-color: $secondary-accent-color; $panel-gradient: rgba(242, 245, 248, 0), rgba(242, 245, 248, 1); +$message-action-bar-border-color: $input-darker-bg-color; +$message-action-bar-hover-border-color: $roomtopic-color; + // ***** Mixins! ***** @define-mixin mx_DialogButton { diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index c4c271e1a5..3543c41ace 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -78,10 +78,11 @@ export default class MessageActionBar extends React.PureComponent { } render() { - // TODO: Move the bar to a separate element once there are several buttons - return ; + return
+ +
; } } From 739c8c0314e7a04a130e0b93fd1cea39ce205e39 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 29 Apr 2019 15:16:16 +0100 Subject: [PATCH 32/64] Promote reply button up to message action bar This moves the reply action out of the existing options menu and up to the message action bar for easier access. --- res/css/views/messages/_MessageActionBar.scss | 8 +++- res/img/reply.svg | 6 +++ .../views/context_menus/MessageContextMenu.js | 16 -------- .../views/messages/MessageActionBar.js | 37 ++++++++++++++++++- 4 files changed, 47 insertions(+), 20 deletions(-) create mode 100644 res/img/reply.svg diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index 7bd5fa5cf9..fc73d16d8f 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -55,8 +55,8 @@ limitations under the License. position: absolute; top: 0; left: 0; - bottom: 0; - right: 0; + height: 100%; + width: 100%; mask-repeat: no-repeat; mask-position: center; background-color: $primary-fg-color; @@ -64,6 +64,10 @@ limitations under the License. } } +.mx_MessageActionBar_replyButton::after { + mask-image: url('$(res)/img/reply.svg'); +} + .mx_MessageActionBar_optionsButton::after { mask-image: url('$(res)/img/icon_context.svg'); } diff --git a/res/img/reply.svg b/res/img/reply.svg new file mode 100644 index 0000000000..8cbbad3550 --- /dev/null +++ b/res/img/reply.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 504729f629..1191b6d66e 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -201,14 +201,6 @@ module.exports = React.createClass({ this.closeMenu(); }, - onReplyClick: function() { - dis.dispatch({ - action: 'reply_to_event', - event: this.props.mxEvent, - }); - this.closeMenu(); - }, - onCollapseReplyThreadClick: function() { this.props.collapseReplyThread(); this.closeMenu(); @@ -226,7 +218,6 @@ module.exports = React.createClass({ let unhidePreviewButton; let externalURLButton; let quoteButton; - let replyButton; let collapseReplyThread; // status is SENT before remote-echo, null after @@ -265,12 +256,6 @@ module.exports = React.createClass({ ); - replyButton = ( -
- { _t('Reply') } -
- ); - if (this.state.canPin) { pinButton = (
@@ -368,7 +353,6 @@ module.exports = React.createClass({ { unhidePreviewButton } { permalinkButton } { quoteButton } - { replyButton } { externalURLButton } { collapseReplyThread } { e2eInfo } diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 3543c41ace..fa020612c4 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -16,8 +16,11 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; +import {EventStatus} from 'matrix-js-sdk'; + import { _t } from '../../../languageHandler'; import sdk from '../../../index'; +import dis from '../../../dispatcher'; import Modal from '../../../Modal'; import { createMenu } from '../../structures/ContextualMenu'; @@ -45,7 +48,14 @@ export default class MessageActionBar extends React.PureComponent { ); } - onOptionsClicked = (ev) => { + onReplyClick = (ev) => { + dis.dispatch({ + action: 'reply_to_event', + event: this.props.mxEvent, + }); + } + + onOptionsClick = (ev) => { const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu'); const buttonRect = ev.target.getBoundingClientRect(); @@ -78,10 +88,33 @@ export default class MessageActionBar extends React.PureComponent { } render() { + const { mxEvent } = this.props; + const { status: eventStatus } = mxEvent; + + // status is SENT before remote-echo, null after + const isSent = !eventStatus || eventStatus === EventStatus.SENT; + + let replyButton; + + if (isSent && mxEvent.getType() === 'm.room.message') { + const content = mxEvent.getContent(); + if ( + content.msgtype && + content.msgtype !== 'm.bad.encrypted' && + content.hasOwnProperty('body') + ) { + replyButton = ; + } + } + return
+ {replyButton}
; } From 338dc602f04b745adc9f020e38da5362c38d236a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 29 Apr 2019 16:34:57 +0100 Subject: [PATCH 33/64] Explicitly mention the room name in all preview bar cases Adjusts all cases of the room preview bar to mention the room name explicitly when possible. --- src/components/views/rooms/RoomPreviewBar.js | 23 +++++++++++++++----- src/i18n/strings/en_EN.json | 8 +++---- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js index 55c39d3947..9a64ba5cf6 100644 --- a/src/components/views/rooms/RoomPreviewBar.js +++ b/src/components/views/rooms/RoomPreviewBar.js @@ -279,7 +279,8 @@ module.exports = React.createClass({ break; } case MessageCase.OtherThreePIDError: { - title = _t("Something went wrong with your invite to this room"); + title = _t("Something went wrong with your invite to %(roomName)s", + {roomName: this._roomName()}); const joinRule = this._joinRule(); const errCodeMessage = _t("%(errcode)s was returned while trying to valide your invite. You could try to pass this information on to a room admin.", {errcode: this.state.threePidFetchError.errcode}, @@ -305,14 +306,19 @@ module.exports = React.createClass({ break; } case MessageCase.InvitedEmailMismatch: { - title = _t("The room invite wasn't sent to your account"); + title = _t("This invite to %(roomName)s wasn't sent to your account", + {roomName: this._roomName()}); const joinRule = this._joinRule(); if (joinRule === "public") { subTitle = _t("You can still join it because this is a public room."); primaryActionLabel = _t("Join the discussion"); primaryActionHandler = this.props.onJoinClick; } else { - subTitle = _t("Sign in with a different account, ask for another invite, or add the e-mail address %(email)s to this account.", {email: this.props.invitedEmail}); + subTitle = _t( + "Sign in with a different account, ask for another invite, or " + + "add the e-mail address %(email)s to this account.", + {email: this.props.invitedEmail}, + ); if (joinRule !== "invite") { primaryActionLabel = _t("Try to join anyway"); primaryActionHandler = this.props.onJoinClick; @@ -340,7 +346,8 @@ module.exports = React.createClass({ inviterElement = ({this.props.inviterName}); } - title = _t("Do you want to join this room?"); + title = _t("Do you want to join %(roomName)s?", + {roomName: this._roomName()}); subTitle = [ avatar, _t(" invited you", {}, {userName: () => inviterElement}), @@ -354,7 +361,8 @@ module.exports = React.createClass({ } case MessageCase.ViewingRoom: { if (this.props.canPreview) { - title = _t("You're previewing this room. Want to join it?"); + title = _t("You're previewing %(roomName)s. Want to join it?", + {roomName: this._roomName()}); } else { title = _t("%(roomName)s can't be previewed. Do you want to join it?", {roomName: this._roomName(true)}); @@ -372,7 +380,10 @@ module.exports = React.createClass({ title = _t("%(roomName)s is not accessible at this time.", {roomName: this._roomName(true)}); subTitle = [ _t("Try again later, or ask a room admin to check if you have access."), - _t("%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.", + _t( + "%(errcode)s was returned while trying to access the room. " + + "If you think you're seeing this message in error, please " + + "submit a bug report.", { errcode: this.props.error.errcode }, { issueLink: label => { label } }, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d1ff8b2695..9197b19ff0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -804,18 +804,18 @@ "Forget this room": "Forget this room", "Re-join": "Re-join", "You were banned from %(roomName)s by %(memberName)s": "You were banned from %(roomName)s by %(memberName)s", - "Something went wrong with your invite to this room": "Something went wrong with your invite to this room", + "Something went wrong with your invite to %(roomName)s": "Something went wrong with your invite to %(roomName)s", "%(errcode)s was returned while trying to valide your invite. You could try to pass this information on to a room admin.": "%(errcode)s was returned while trying to valide your invite. You could try to pass this information on to a room admin.", "You can only join it with a working invite.": "You can only join it with a working invite.", "You can still join it because this is a public room.": "You can still join it because this is a public room.", "Join the discussion": "Join the discussion", "Try to join anyway": "Try to join anyway", - "The room invite wasn't sent to your account": "The room invite wasn't sent to your account", + "This invite to %(roomName)s wasn't sent to your account": "This invite to %(roomName)s wasn't sent to your account", "Sign in with a different account, ask for another invite, or add the e-mail address %(email)s to this account.": "Sign in with a different account, ask for another invite, or add the e-mail address %(email)s to this account.", - "Do you want to join this room?": "Do you want to join this room?", + "Do you want to join %(roomName)s?": "Do you want to join %(roomName)s?", " invited you": " invited you", "Reject": "Reject", - "You're previewing this room. Want to join it?": "You're previewing this room. Want to join it?", + "You're previewing %(roomName)s. Want to join it?": "You're previewing %(roomName)s. Want to join it?", "%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s can't be previewed. Do you want to join it?", "%(roomName)s does not exist.": "%(roomName)s does not exist.", "This room doesn't exist. Are you sure you're at the right place?": "This room doesn't exist. Are you sure you're at the right place?", From ad6be3cc1bda85f8788e65318741475c09711eea Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 29 Apr 2019 17:48:48 +0100 Subject: [PATCH 34/64] Change invite preview text for DMs Use more specifc text for when previewing an invite to a direct message room. --- src/components/views/rooms/RoomPreviewBar.js | 34 ++++++++++++++++---- src/i18n/strings/en_EN.json | 1 + 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js index 9a64ba5cf6..02a93ebcc0 100644 --- a/src/components/views/rooms/RoomPreviewBar.js +++ b/src/components/views/rooms/RoomPreviewBar.js @@ -118,8 +118,7 @@ module.exports = React.createClass({ return MessageCase.NotLoggedIn; } - const myMember = this.props.room && - this.props.room.getMember(MatrixClientPeg.get().getUserId()); + const myMember = this._getMyMember(); if (myMember) { if (myMember.isKicked()) { @@ -158,9 +157,7 @@ module.exports = React.createClass({ }, _getKickOrBanInfo() { - const myMember = this.props.room ? - this.props.room.getMember(MatrixClientPeg.get().getUserId()) : - null; + const myMember = this._getMyMember(); if (!myMember) { return {}; } @@ -194,6 +191,13 @@ module.exports = React.createClass({ } }, + _getMyMember() { + return ( + this.props.room && + this.props.room.getMember(MatrixClientPeg.get().getUserId()) + ); + }, + _getInviteMember: function() { const {room} = this.props; if (!room) { @@ -208,6 +212,16 @@ module.exports = React.createClass({ return room.currentState.getMember(inviterUserId); }, + _isDMInvite() { + const myMember = this._getMyMember(); + if (!myMember) { + return false; + } + const memberEvent = myMember.events.member; + const memberContent = memberEvent.getContent(); + return memberContent.membership === "invite" && memberContent.is_direct; + }, + onLoginClick: function() { dis.dispatch({ action: 'start_login' }); }, @@ -346,8 +360,14 @@ module.exports = React.createClass({ inviterElement = ({this.props.inviterName}); } - title = _t("Do you want to join %(roomName)s?", - {roomName: this._roomName()}); + const isDM = this._isDMInvite(); + if (isDM) { + title = _t("Do you want to chat with %(user)s?", + { user: inviteMember.name }); + } else { + title = _t("Do you want to join %(roomName)s?", + { roomName: this._roomName() }); + } subTitle = [ avatar, _t(" invited you", {}, {userName: () => inviterElement}), diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9197b19ff0..dd15020d79 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -812,6 +812,7 @@ "Try to join anyway": "Try to join anyway", "This invite to %(roomName)s wasn't sent to your account": "This invite to %(roomName)s wasn't sent to your account", "Sign in with a different account, ask for another invite, or add the e-mail address %(email)s to this account.": "Sign in with a different account, ask for another invite, or add the e-mail address %(email)s to this account.", + "Do you want to chat with %(user)s?": "Do you want to chat with %(user)s?", "Do you want to join %(roomName)s?": "Do you want to join %(roomName)s?", " invited you": " invited you", "Reject": "Reject", From e71896420eae0d7e4874d996acc3b88f26843782 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 30 Apr 2019 09:22:41 +0100 Subject: [PATCH 35/64] Show only a static inviter name with full MXID This removes the clickable inviter behaviour, as it was too confusing to reveal the user info sidebar and also hide the invite. Keeping both on screen would be okay, but seems a bit too complex to resolve right before RC. In addition, this adds the full inviter MXID to ensure it's clear who invited you. --- src/components/views/rooms/RoomPreviewBar.js | 23 +++++--------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js index 02a93ebcc0..8426007e2f 100644 --- a/src/components/views/rooms/RoomPreviewBar.js +++ b/src/components/views/rooms/RoomPreviewBar.js @@ -24,7 +24,6 @@ import MatrixClientPeg from '../../../MatrixClientPeg'; import dis from '../../../dispatcher'; import classNames from 'classnames'; import { _t } from '../../../languageHandler'; -import {getUserNameColorClass} from '../../../utils/FormattingUtils'; const MessageCase = Object.freeze({ NotLoggedIn: "NotLoggedIn", @@ -105,12 +104,6 @@ module.exports = React.createClass({ } }, - _onInviterClick(evt) { - evt.preventDefault(); - const member = this._getInviteMember(); - dis.dispatch({action: 'view_user_info', userId: member.userId}); - }, - _getMessageCase() { const isGuest = MatrixClientPeg.get().isGuest(); @@ -346,16 +339,12 @@ module.exports = React.createClass({ let inviterElement; if (inviteMember) { const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar"); - avatar = (); - const inviterClasses = [ - "mx_RoomPreviewBar_inviter", - getUserNameColorClass(inviteMember.userId), - ].join(" "); - inviterElement = ( - - {inviteMember.name} - - ); + avatar = (); + inviterElement = + + {inviteMember.rawDisplayName} + ({inviteMember.userId}) + ; } else { inviterElement = ({this.props.inviterName}); } From b7642b38a7f8e3c2547549c84e23184c63b91610 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 30 Apr 2019 09:30:36 +0100 Subject: [PATCH 36/64] Show the room avatar for invites This changes to the room avatar instead of the inviter's avatar. --- src/components/views/rooms/RoomPreviewBar.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js index 8426007e2f..5d79463112 100644 --- a/src/components/views/rooms/RoomPreviewBar.js +++ b/src/components/views/rooms/RoomPreviewBar.js @@ -334,12 +334,12 @@ module.exports = React.createClass({ break; } case MessageCase.Invite: { + const RoomAvatar = sdk.getComponent("views.avatars.RoomAvatar"); + const avatar = ; + const inviteMember = this._getInviteMember(); - let avatar; let inviterElement; if (inviteMember) { - const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar"); - avatar = (); inviterElement = {inviteMember.rawDisplayName} From 5b92693cb3de28936424d6ee5e39df5ee4c8ca2d Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 30 Apr 2019 11:53:10 +0100 Subject: [PATCH 37/64] js-sdk rc.1 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d844566f3a..c22429d26d 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "linkifyjs": "^2.1.6", "lodash": "^4.13.1", "lolex": "2.3.2", - "matrix-js-sdk": "1.0.4", + "matrix-js-sdk": "^1.1.0-rc.1", "optimist": "^0.6.1", "pako": "^1.0.5", "png-chunks-extract": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index a2942fd1d3..37df3b83be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4950,10 +4950,10 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.0.tgz#490b70e062ee24636536e3d9481e333733d00f2c" integrity sha512-3Zs9P/0zzwTob2pdgT0CHZuMbnSUSp8MB1bddfm+HDmnFWHGT4jvEZRf+2RuPoa+cjdn/z25SEt5gFTqdhvJAg== -matrix-js-sdk@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-1.0.4.tgz#dbfa8399f750a23b020c1ec8f037a2f5c36d4672" - integrity sha512-FPx7U1a0SmLbDXhXlR4XHlC+FVKTnK2/+ZBtyOWGLi3nxw4x8hCSSzJ82gzStya1qvhHvbf/y7eblYFVE1l7SQ== +matrix-js-sdk@^1.1.0-rc.1: + version "1.1.0-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-1.1.0-rc.1.tgz#11a9e9215e766274d99f44eff0a8c4d33d34f870" + integrity sha512-I87gvaMKKmFGDU8q4YUiEP0RVr0ni+v64TYaqllKV2zMGgl2serz+O9pvgfyGgzwFUQ77nPG7NGH4ku+S5I2LA== dependencies: another-json "^0.2.0" babel-runtime "^6.26.0" From 0f59ceb93c2332c4801b871f3a6143a1d79d53e5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 30 Apr 2019 12:00:53 +0100 Subject: [PATCH 38/64] Prepare changelog for v1.1.0-rc.1 --- CHANGELOG.md | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4536064675..9141e56bab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,185 @@ +Changes in [1.1.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.1.0-rc.1) (2019-04-30) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.0.7...v1.1.0-rc.1) + + * Add important info to new preview bar + [\#2936](https://github.com/matrix-org/matrix-react-sdk/pull/2936) + * Add a message action bar + [\#2935](https://github.com/matrix-org/matrix-react-sdk/pull/2935) + * Trigger riot-web build + [\#2934](https://github.com/matrix-org/matrix-react-sdk/pull/2934) + * Input validation tooltips for registration + [\#2933](https://github.com/matrix-org/matrix-react-sdk/pull/2933) + * Also say "Connect ..." on remaining key backup buttons + [\#2931](https://github.com/matrix-org/matrix-react-sdk/pull/2931) + * Mark a few CSS classes as not selectable + [\#2929](https://github.com/matrix-org/matrix-react-sdk/pull/2929) + * Cleanup message composer render() method + [\#2883](https://github.com/matrix-org/matrix-react-sdk/pull/2883) + * Redesigned room preview bar + [\#2925](https://github.com/matrix-org/matrix-react-sdk/pull/2925) + * Prevent user pills containing only emoji from embiggening + [\#2907](https://github.com/matrix-org/matrix-react-sdk/pull/2907) + * Make alt-enter insert new line on macOS + [\#2923](https://github.com/matrix-org/matrix-react-sdk/pull/2923) + * Test `defaultServerName` before showing it on forgot password + [\#2924](https://github.com/matrix-org/matrix-react-sdk/pull/2924) + * Add a function to append/overwrite objects in the config on the fly + [\#2922](https://github.com/matrix-org/matrix-react-sdk/pull/2922) + * use SdkConfig brand name instead of static "Riot" + [\#2921](https://github.com/matrix-org/matrix-react-sdk/pull/2921) + * Use dedicated permalink creators in search results with multiple rooms + [\#2898](https://github.com/matrix-org/matrix-react-sdk/pull/2898) + * Clarify that use backup means restore + [\#2917](https://github.com/matrix-org/matrix-react-sdk/pull/2917) + * Fix key backup status when missing device + [\#2919](https://github.com/matrix-org/matrix-react-sdk/pull/2919) + * Ensure `` tags appear bold for all browsers + [\#2918](https://github.com/matrix-org/matrix-react-sdk/pull/2918) + * Add a link in room settings to get at the tombstoned room if it exists + [\#2908](https://github.com/matrix-org/matrix-react-sdk/pull/2908) + * Add a generic error page element for startup errors + [\#2915](https://github.com/matrix-org/matrix-react-sdk/pull/2915) + * Add strings for js-sdk autodiscovery errors + [\#2916](https://github.com/matrix-org/matrix-react-sdk/pull/2916) + * Focus the composer view on file upload + [\#2914](https://github.com/matrix-org/matrix-react-sdk/pull/2914) + * use medium agent for e2e tests + [\#2911](https://github.com/matrix-org/matrix-react-sdk/pull/2911) + * adjust prop in HeaderButton + [\#2912](https://github.com/matrix-org/matrix-react-sdk/pull/2912) + * Remove breadcrumb scroll tolerances and use sensible defaults + [\#2913](https://github.com/matrix-org/matrix-react-sdk/pull/2913) + * Fix having to click the member list button twice to show it after having + changed room. + [\#2906](https://github.com/matrix-org/matrix-react-sdk/pull/2906) + * Add period to the end of upgrade notice + [\#2909](https://github.com/matrix-org/matrix-react-sdk/pull/2909) + * Remove duplicate space in credits + [\#2889](https://github.com/matrix-org/matrix-react-sdk/pull/2889) + * Handle M_UNSUPPORTED_ROOM_VERSION in invites and room creation + [\#2905](https://github.com/matrix-org/matrix-react-sdk/pull/2905) + * Re-enable E2E tests + [\#2867](https://github.com/matrix-org/matrix-react-sdk/pull/2867) + * Remove BottomLeftMenu and supporting bits + [\#2903](https://github.com/matrix-org/matrix-react-sdk/pull/2903) + * Fix for retina thumbnails being massive + [\#2439](https://github.com/matrix-org/matrix-react-sdk/pull/2439) + * Send breadcrumb updates only when they change + [\#2894](https://github.com/matrix-org/matrix-react-sdk/pull/2894) + * Add some tolerances to breadcrumb scrolling + [\#2892](https://github.com/matrix-org/matrix-react-sdk/pull/2892) + * Fix validation to avoid `undefined` class on fields + [\#2902](https://github.com/matrix-org/matrix-react-sdk/pull/2902) + * Always return a client from onRegistered + [\#2895](https://github.com/matrix-org/matrix-react-sdk/pull/2895) + * Fix room upgrade warnings popping up in upgraded rooms + [\#2897](https://github.com/matrix-org/matrix-react-sdk/pull/2897) + * Fix style lint errors & enable on CI + [\#2901](https://github.com/matrix-org/matrix-react-sdk/pull/2901) + * Add stylelint + [\#2900](https://github.com/matrix-org/matrix-react-sdk/pull/2900) + * Key backup: Handle case where your onw sig is invalid + [\#2899](https://github.com/matrix-org/matrix-react-sdk/pull/2899) + * Simplify settings dialog CSS + [\#2891](https://github.com/matrix-org/matrix-react-sdk/pull/2891) + * Fix upload cancel in e2e rooms + [\#2893](https://github.com/matrix-org/matrix-react-sdk/pull/2893) + * Set E2E room status to warning when crypto is disabled + [\#2890](https://github.com/matrix-org/matrix-react-sdk/pull/2890) + * Move SettingsDialog width override to fixedWidth + [\#2888](https://github.com/matrix-org/matrix-react-sdk/pull/2888) + * Prevent the permalink creator from causing cascading failure + [\#2882](https://github.com/matrix-org/matrix-react-sdk/pull/2882) + * Don't include all networks by default in the room directory + [\#2881](https://github.com/matrix-org/matrix-react-sdk/pull/2881) + * Fix fixed width dialogs + [\#2886](https://github.com/matrix-org/matrix-react-sdk/pull/2886) + * Fix settings dialog layout + [\#2885](https://github.com/matrix-org/matrix-react-sdk/pull/2885) + * Update from Weblate + [\#2884](https://github.com/matrix-org/matrix-react-sdk/pull/2884) + * Design tweaks to dialogs + [\#2868](https://github.com/matrix-org/matrix-react-sdk/pull/2868) + * Remove 'try the app' link from login + [\#2880](https://github.com/matrix-org/matrix-react-sdk/pull/2880) + * Track store failures after startup + [\#2870](https://github.com/matrix-org/matrix-react-sdk/pull/2870) + * Translate vertical scrolling to horizontal movement in breadcrumbs + [\#2877](https://github.com/matrix-org/matrix-react-sdk/pull/2877) + * Add telemetry for breadcrumbs and have the setting apply without refresh + [\#2873](https://github.com/matrix-org/matrix-react-sdk/pull/2873) + * Fix a few bugs introduced in file upload rework + [\#2879](https://github.com/matrix-org/matrix-react-sdk/pull/2879) + * Sync breadcrumb rooms through account data + [\#2875](https://github.com/matrix-org/matrix-react-sdk/pull/2875) + * Scroll breadcrumbs to the left when they change + [\#2878](https://github.com/matrix-org/matrix-react-sdk/pull/2878) + * Add an indicator to show a room is a direct chat in breadcrumbs + [\#2874](https://github.com/matrix-org/matrix-react-sdk/pull/2874) + * Use the most recent version of the room in breadcrumbs + [\#2872](https://github.com/matrix-org/matrix-react-sdk/pull/2872) + * Autohide the scrollbar on breadcrumbs + [\#2876](https://github.com/matrix-org/matrix-react-sdk/pull/2876) + * Ensure the page URL is redacted before tracking analytics events + [\#2871](https://github.com/matrix-org/matrix-react-sdk/pull/2871) + * fix NPE for rooms with redacted tombstones + [\#2869](https://github.com/matrix-org/matrix-react-sdk/pull/2869) + * Don't re-init the stickerpicker unless something actually changes + [\#2862](https://github.com/matrix-org/matrix-react-sdk/pull/2862) + * Add option to rotate images + [\#2855](https://github.com/matrix-org/matrix-react-sdk/pull/2855) + * Add badges to breadcrumb rooms + [\#2861](https://github.com/matrix-org/matrix-react-sdk/pull/2861) + * Include the current power level in the selector + [\#2866](https://github.com/matrix-org/matrix-react-sdk/pull/2866) + * Apply 50% opacity to left breadcrumbs + [\#2860](https://github.com/matrix-org/matrix-react-sdk/pull/2860) + * Small scroll fixes + [\#2865](https://github.com/matrix-org/matrix-react-sdk/pull/2865) + * Put the stickerpicker below dialogs + [\#2863](https://github.com/matrix-org/matrix-react-sdk/pull/2863) + * Logging tweaks + [\#2864](https://github.com/matrix-org/matrix-react-sdk/pull/2864) + * Implement redesigned upload confirmation screens + [\#2858](https://github.com/matrix-org/matrix-react-sdk/pull/2858) + * Use Field component in bug report dialog + [\#2859](https://github.com/matrix-org/matrix-react-sdk/pull/2859) + * Notify user when crypto data is missing + [\#2841](https://github.com/matrix-org/matrix-react-sdk/pull/2841) + * Update from Weblate + [\#2857](https://github.com/matrix-org/matrix-react-sdk/pull/2857) + * Download PDFs as blobs to avoid empty grey screens + [\#2847](https://github.com/matrix-org/matrix-react-sdk/pull/2847) + * Set title attribute on images in lightbox + [\#2848](https://github.com/matrix-org/matrix-react-sdk/pull/2848) + * Add MemberInfo for 3pid invites and support revoking those invites + [\#2843](https://github.com/matrix-org/matrix-react-sdk/pull/2843) + * round scrollTop upwards to prevent never detecting bottom + [\#2846](https://github.com/matrix-org/matrix-react-sdk/pull/2846) + * Notifier is how singleton is known outside of this module + [\#2845](https://github.com/matrix-org/matrix-react-sdk/pull/2845) + * Delay `Notifier` check until we have push rules + [\#2844](https://github.com/matrix-org/matrix-react-sdk/pull/2844) + * BACAT Scrolling + [\#2842](https://github.com/matrix-org/matrix-react-sdk/pull/2842) + * Handle storage fallback cases in consistency check + [\#2840](https://github.com/matrix-org/matrix-react-sdk/pull/2840) + * Handle all the segments of a v3 event ID + [\#2827](https://github.com/matrix-org/matrix-react-sdk/pull/2827) + * Add custom tooltips and scrolling to breadcrumbs + [\#2839](https://github.com/matrix-org/matrix-react-sdk/pull/2839) + * Check if the message panel is at the end of the timeline on init + [\#2829](https://github.com/matrix-org/matrix-react-sdk/pull/2829) + * Persist breadcrumb state between sessions + [\#2837](https://github.com/matrix-org/matrix-react-sdk/pull/2837) + * Always append the current room to the breadcrumbs + [\#2838](https://github.com/matrix-org/matrix-react-sdk/pull/2838) + * Alert the user to unread notifications in prior versions of rooms + [\#2831](https://github.com/matrix-org/matrix-react-sdk/pull/2831) + * Filter out upgraded rooms from autocomplete results + [\#2830](https://github.com/matrix-org/matrix-react-sdk/pull/2830) + Changes in [1.0.7](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.0.7) (2019-04-08) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.0.6...v1.0.7) From 57322f82be55a9295207efde597769b4f2ce787d Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 30 Apr 2019 12:02:19 +0100 Subject: [PATCH 39/64] v1.1.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c22429d26d..3fb9514cd8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "1.0.7", + "version": "1.1.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 561ecc5ebecd04954fd5aeda7f305d66ddd83f4d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 30 Apr 2019 13:36:37 +0100 Subject: [PATCH 40/64] Rebuild strings --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 162e20dcf7..0ee2f2082a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -889,6 +889,7 @@ "Today": "Today", "Yesterday": "Yesterday", "Error decrypting audio": "Error decrypting audio", + "Reply": "Reply", "Options": "Options", "Attachment": "Attachment", "Error decrypting attachment": "Error decrypting attachment", @@ -1260,7 +1261,6 @@ "Resend": "Resend", "Cancel Sending": "Cancel Sending", "Forward Message": "Forward Message", - "Reply": "Reply", "Pin Message": "Pin Message", "View Source": "View Source", "View Decrypted Source": "View Decrypted Source", From 4b50d2a2bfa6b4d6fbc8c40fae5d370c1eb0c817 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 30 Apr 2019 17:41:11 +0100 Subject: [PATCH 41/64] Update action bar colors for dark theme In addition, this also adjusts the event hover colors to match the palette. Fixes https://github.com/vector-im/riot-web/issues/9591 --- res/css/views/messages/_MessageActionBar.scss | 4 ++-- res/themes/dark/css/_dark.scss | 8 +++++--- res/themes/light/css/_light.scss | 10 ++++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index fc73d16d8f..f01ef4f04f 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -21,7 +21,7 @@ limitations under the License. display: flex; height: 24px; border-radius: 4px; - background: $primary-bg-color; + background: $message-action-bar-bg-color; top: -13px; right: 8px; user-select: none; @@ -59,7 +59,7 @@ limitations under the License. width: 100%; mask-repeat: no-repeat; mask-position: center; - background-color: $primary-fg-color; + background-color: $message-action-bar-fg-color; } } } diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 0d3b40b64f..7c0f8ef9ab 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -40,7 +40,7 @@ $tagpanel-bg-color: $base-color; $selected-color: $room-highlight-color; // selected for hoverover & selected event tiles -$event-selected-color: #111316; +$event-selected-color: $header-panel-bg-color; // used for the hairline dividers in RoomView $primary-hairline-color: $header-panel-border-color; @@ -146,8 +146,10 @@ $room-warning-bg-color: $header-panel-bg-color; $dark-panel-bg-color: $header-panel-bg-color; $panel-gradient: rgba(34, 38, 46, 0), rgba(34, 38, 46, 1); -$message-action-bar-border-color: $input-darker-bg-color; -$message-action-bar-hover-border-color: $text-secondary-color; +$message-action-bar-bg-color: $header-panel-bg-color; +$message-action-bar-fg-color: $header-panel-text-primary-color; +$message-action-bar-border-color: #616b7f; +$message-action-bar-hover-border-color: $header-panel-text-primary-color; // ***** Mixins! ***** diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index bdd5f10cc9..7451a23991 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -11,7 +11,7 @@ $font-family: 'Nunito', Arial, Helvetica, Sans-Serif; $accent-color: #03b381; $notice-primary-color: #ff4b55; $notice-secondary-color: #61708b; -$header-panel-bg-color: #f2f5f8; +$header-panel-bg-color: #f3f8fd; // typical text (dark-on-white in light skin) $primary-fg-color: #2e2f32; @@ -66,7 +66,7 @@ $droptarget-bg-color: rgba(255,255,255,0.5); $selected-color: $secondary-accent-color; // selected for hoverover & selected event tiles -$event-selected-color: #f7f7f7; +$event-selected-color: $header-panel-bg-color; // used for the hairline dividers in RoomView $primary-hairline-color: #e5e5e5; @@ -254,8 +254,10 @@ $authpage-secondary-color: #61708b; $dark-panel-bg-color: $secondary-accent-color; $panel-gradient: rgba(242, 245, 248, 0), rgba(242, 245, 248, 1); -$message-action-bar-border-color: $input-darker-bg-color; -$message-action-bar-hover-border-color: $roomtopic-color; +$message-action-bar-bg-color: $primary-bg-color; +$message-action-bar-fg-color: $primary-fg-color; +$message-action-bar-border-color: #e9edf1; +$message-action-bar-hover-border-color: #b8c1d2; // ***** Mixins! ***** From 7f62cdf1249be58c3c8fb2284ccef30588b3ede6 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 26 Apr 2019 10:52:36 +0100 Subject: [PATCH 42/64] Add reactions feature flag --- src/i18n/strings/en_EN.json | 1 + src/settings/Settings.js | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0ee2f2082a..e18dc2761d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -297,6 +297,7 @@ "Show recent room avatars above the room list": "Show recent room avatars above the room list", "Group & filter rooms by custom tags (refresh to apply changes)": "Group & filter rooms by custom tags (refresh to apply changes)", "Render simple counters in room header": "Render simple counters in room header", + "React to messages with emoji": "React to messages with emoji", "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", "Use compact timeline layout": "Use compact timeline layout", "Show a placeholder for removed messages": "Show a placeholder for removed messages", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 35baa718b9..4be1b67227 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -118,6 +118,12 @@ export const SETTINGS = { supportedLevels: LEVELS_FEATURE, default: false, }, + "feature_reactions": { + isFeature: true, + displayName: _td("React to messages with emoji"), + supportedLevels: LEVELS_FEATURE, + default: false, + }, "MessageComposerInput.suggestEmoji": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Enable Emoji suggestions while typing'), From 00ca930d2e9abe6b51189292a60444d6bf9f13f1 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 29 Apr 2019 16:11:23 +0100 Subject: [PATCH 43/64] Extract actionable content check to helper --- .../views/messages/MessageActionBar.js | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index fa020612c4..3fe3f74a9d 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -87,15 +87,13 @@ export default class MessageActionBar extends React.PureComponent { this.onFocusChange(true); } - render() { + isContentActionable() { const { mxEvent } = this.props; const { status: eventStatus } = mxEvent; // status is SENT before remote-echo, null after const isSent = !eventStatus || eventStatus === EventStatus.SENT; - let replyButton; - if (isSent && mxEvent.getType() === 'm.room.message') { const content = mxEvent.getContent(); if ( @@ -103,13 +101,23 @@ export default class MessageActionBar extends React.PureComponent { content.msgtype !== 'm.bad.encrypted' && content.hasOwnProperty('body') ) { - replyButton = ; + return true; } } + return false; + } + + render() { + let replyButton; + + if (this.isContentActionable()) { + replyButton = ; + } + return
{replyButton} Date: Tue, 30 Apr 2019 18:09:10 +0100 Subject: [PATCH 44/64] Add primary reactions to action bar This adds the primary reactions to the action bar. They act as toggles where you can only select one from each group at a time. Note that currently we aren't actually sending the reaction at all. That's left for a separate task. Fixes https://github.com/vector-im/riot-web/issues/9576 --- res/css/views/messages/_MessageActionBar.scss | 35 ++++-- .../views/messages/MessageActionBar.js | 117 +++++++++++++++++- src/i18n/strings/en_EN.json | 2 + 3 files changed, 140 insertions(+), 14 deletions(-) diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index f01ef4f04f..877e1916fc 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -20,6 +20,7 @@ limitations under the License. cursor: pointer; display: flex; height: 24px; + line-height: 24px; border-radius: 4px; background: $message-action-bar-bg-color; top: -13px; @@ -49,21 +50,21 @@ limitations under the License. &:only-child { border-radius: 3px; } - - &::after { - content: ''; - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 100%; - mask-repeat: no-repeat; - mask-position: center; - background-color: $message-action-bar-fg-color; - } } } +.mx_MessageActionBar_maskButton::after { + content: ''; + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + mask-repeat: no-repeat; + mask-position: center; + background-color: $message-action-bar-fg-color; +} + .mx_MessageActionBar_replyButton::after { mask-image: url('$(res)/img/reply.svg'); } @@ -71,3 +72,13 @@ limitations under the License. .mx_MessageActionBar_optionsButton::after { mask-image: url('$(res)/img/icon_context.svg'); } + +.mx_MessageActionBar_reactionDimension { + width: 42px; + display: flex; + justify-content: space-evenly; +} + +.mx_MessageActionBar_reactionDisabled { + opacity: 0.4; +} diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 3fe3f74a9d..276a142ccb 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -23,6 +23,8 @@ import sdk from '../../../index'; import dis from '../../../dispatcher'; import Modal from '../../../Modal'; import { createMenu } from '../../structures/ContextualMenu'; +import SettingsStore from '../../../settings/SettingsStore'; +import classNames from 'classnames'; export default class MessageActionBar extends React.PureComponent { static propTypes = { @@ -33,6 +35,15 @@ export default class MessageActionBar extends React.PureComponent { onFocusChange: PropTypes.func, }; + constructor(props) { + super(props); + + this.state = { + agreeDimension: null, + likeDimension: null, + }; + } + onFocusChange = (focused) => { if (!this.props.onFocusChange) { return; @@ -48,6 +59,31 @@ export default class MessageActionBar extends React.PureComponent { ); } + onAgreeClick = (ev) => { + this.toggleDimensionValue("agreeDimension", "agree"); + } + + onDisagreeClick = (ev) => { + this.toggleDimensionValue("agreeDimension", "disagree"); + } + + onLikeClick = (ev) => { + this.toggleDimensionValue("likeDimension", "like"); + } + + onDislikeClick = (ev) => { + this.toggleDimensionValue("likeDimension", "dislike"); + } + + toggleDimensionValue(dimension, value) { + const state = this.state[dimension]; + const newState = state !== value ? value : null; + this.setState({ + [dimension]: newState, + }); + // TODO: Send the reaction event + } + onReplyClick = (ev) => { dis.dispatch({ action: 'reply_to_event', @@ -108,19 +144,96 @@ export default class MessageActionBar extends React.PureComponent { return false; } + isReactionsEnabled() { + return SettingsStore.isFeatureEnabled("feature_reactions"); + } + + renderAgreeDimension() { + if (!this.isReactionsEnabled()) { + return null; + } + + const state = this.state.agreeDimension; + const options = [ + { + key: "agree", + content: "👍", + onClick: this.onAgreeClick, + }, + { + key: "disagree", + content: "👎", + onClick: this.onDisagreeClick, + }, + ]; + + return + {this.renderReactionDimensionItems(state, options)} + ; + } + + renderLikeDimension() { + if (!this.isReactionsEnabled()) { + return null; + } + + const state = this.state.likeDimension; + const options = [ + { + key: "like", + content: "🙂", + onClick: this.onLikeClick, + }, + { + key: "dislike", + content: "😔", + onClick: this.onDislikeClick, + }, + ]; + + return + {this.renderReactionDimensionItems(state, options)} + ; + } + + renderReactionDimensionItems(state, options) { + return options.map(option => { + const disabled = state && state !== option.key; + const classes = classNames({ + mx_MessageActionBar_reactionDisabled: disabled, + }); + return + {option.content} + ; + }); + } + render() { + let agreeDimensionReactionButtons; + let likeDimensionReactionButtons; let replyButton; if (this.isContentActionable()) { - replyButton = ; } return
+ {agreeDimensionReactionButtons} + {likeDimensionReactionButtons} {replyButton} - diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index e18dc2761d..5cfb61d60b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -890,6 +890,8 @@ "Today": "Today", "Yesterday": "Yesterday", "Error decrypting audio": "Error decrypting audio", + "Agree or Disagree": "Agree or Disagree", + "Like or Dislike": "Like or Dislike", "Reply": "Reply", "Options": "Options", "Attachment": "Attachment", From f4b783e802e46a13eebeb7f8b461f43075a959ae Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 1 May 2019 14:30:25 +0100 Subject: [PATCH 45/64] Fix lint errors in TimelinePanel --- .eslintignore.errorfiles | 1 - src/components/structures/TimelinePanel.js | 44 +++++++++++++--------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 2224ef67bc..ffe0ade5cc 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -10,7 +10,6 @@ src/components/structures/RoomStatusBar.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/views/avatars/BaseAvatar.js src/components/views/avatars/MemberAvatar.js diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index aba7964a15..aa278f2349 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -44,11 +44,10 @@ const READ_RECEIPT_INTERVAL_MS = 500; const DEBUG = false; +let debuglog = function() {}; if (DEBUG) { // using bind means that we get to keep useful line numbers in the console - var debuglog = console.log.bind(console); -} else { - var debuglog = function() {}; + debuglog = console.log.bind(console); } /* @@ -56,7 +55,7 @@ if (DEBUG) { * * Also responsible for handling and sending read receipts. */ -var TimelinePanel = React.createClass({ +const TimelinePanel = React.createClass({ displayName: 'TimelinePanel', propTypes: { @@ -445,6 +444,7 @@ var TimelinePanel = React.createClass({ const updatedState = {events: events}; + let callRMUpdated; if (this.props.manageReadMarkers) { // when a new event arrives when the user is not watching the // window, but the window is in its auto-scroll mode, make sure the @@ -456,7 +456,7 @@ var TimelinePanel = React.createClass({ // const myUserId = MatrixClientPeg.get().credentials.userId; const sender = ev.sender ? ev.sender.userId : null; - var callRMUpdated = false; + callRMUpdated = false; if (sender != myUserId && !UserActivity.sharedInstance().userActiveRecently()) { updatedState.readMarkerVisible = true; } else if (lastEv && this.getReadMarkerPosition() === 0) { @@ -566,7 +566,7 @@ var TimelinePanel = React.createClass({ UserActivity.sharedInstance().timeWhileActiveRecently(this._readMarkerActivityTimer); try { await this._readMarkerActivityTimer.finished(); - } catch(e) { continue; /* aborted */ } + } catch (e) { continue; /* aborted */ } // outside of try/catch to not swallow errors this.updateReadMarker(); } @@ -578,7 +578,7 @@ var TimelinePanel = React.createClass({ UserActivity.sharedInstance().timeWhileActiveNow(this._readReceiptActivityTimer); try { await this._readReceiptActivityTimer.finished(); - } catch(e) { continue; /* aborted */ } + } catch (e) { continue; /* aborted */ } // outside of try/catch to not swallow errors this.sendReadReceipt(); } @@ -732,7 +732,8 @@ var TimelinePanel = React.createClass({ const events = this._timelineWindow.getEvents(); // first find where the current RM is - for (var i = 0; i < events.length; i++) { + let i; + for (i = 0; i < events.length; i++) { if (events[i].getId() == this.state.readMarkerEventId) { break; } @@ -744,7 +745,7 @@ var TimelinePanel = React.createClass({ // now think about advancing it const myUserId = MatrixClientPeg.get().credentials.userId; for (i++; i < events.length; i++) { - var ev = events[i]; + const ev = events[i]; if (!ev.sender || ev.sender.userId != myUserId) { break; } @@ -752,7 +753,7 @@ var TimelinePanel = React.createClass({ // i is now the first unread message which we didn't send ourselves. i--; - var ev = events[i]; + const ev = events[i]; this._setReadMarker(ev.getId(), ev.getTs()); }, @@ -882,7 +883,7 @@ var TimelinePanel = React.createClass({ return ret; }, - /** + /* * called by the parent component when PageUp/Down/etc is pressed. * * We pass it down to the scroll panel. @@ -975,11 +976,10 @@ var TimelinePanel = React.createClass({ }; const onError = (error) => { - this.setState({timelineLoading: false}); + this.setState({ timelineLoading: false }); console.error( `Error loading timeline panel at ${eventId}: ${error}`, ); - const msg = error.message ? error.message : JSON.stringify(error); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); let onFinished; @@ -997,9 +997,18 @@ var TimelinePanel = React.createClass({ }); }; } - const message = (error.errcode == 'M_FORBIDDEN') - ? _t("Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.") - : _t("Tried to load a specific point in this room's timeline, but was unable to find it."); + let message; + if (error.errcode == 'M_FORBIDDEN') { + message = _t( + "Tried to load a specific point in this room's timeline, but you " + + "do not have permission to view the message in question.", + ); + } else { + message = _t( + "Tried to load a specific point in this room's timeline, but was " + + "unable to find it.", + ); + } Modal.createTrackedDialog('Failed to load timeline position', '', ErrorDialog, { title: _t("Failed to load timeline position"), description: message, @@ -1104,12 +1113,13 @@ var TimelinePanel = React.createClass({ }, /** - * get the id of the event corresponding to our user's latest read-receipt. + * Get the id of the event corresponding to our user's latest read-receipt. * * @param {Boolean} ignoreSynthesized If true, return only receipts that * have been sent by the server, not * implicit ones generated by the JS * SDK. + * @return {String} the event ID */ _getCurrentReadReceipt: function(ignoreSynthesized) { const client = MatrixClientPeg.get(); From 5a204edf907b768f1274fc738d2dd48b025c4170 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 1 May 2019 13:37:57 -0600 Subject: [PATCH 46/64] Remove timeline explosion rageshake prompt Concludes https://github.com/vector-im/riot-web/issues/8593 We are no longer seeing this error being triggered, and are considering it fixed. As a result, the dialog can be removed to reduce the amount of dead code in the project. --- src/components/structures/MatrixChat.js | 12 -- .../views/dialogs/TimelineExplosionDialog.js | 130 ------------------ src/i18n/strings/en_EN.json | 4 - 3 files changed, 146 deletions(-) delete mode 100644 src/components/views/dialogs/TimelineExplosionDialog.js diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 33f1bac090..bf2e7beb16 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -50,7 +50,6 @@ import SettingsStore, {SettingLevel} from "../../settings/SettingsStore"; import { startAnyRegistrationFlow } from "../../Registration.js"; import { messageForSyncError } from '../../utils/ErrorUtils'; import ResizeNotifier from "../../utils/ResizeNotifier"; -import TimelineExplosionDialog from "../views/dialogs/TimelineExplosionDialog"; const AutoDiscovery = Matrix.AutoDiscovery; @@ -1307,17 +1306,6 @@ export default React.createClass({ return self._loggedInView.child.canResetTimelineInRoom(roomId); }); - cli.on('sync.unexpectedError', function(err) { - if (err.message && err.message.includes("live timeline ") && err.message.includes(" is no longer live ")) { - console.error("Caught timeline explosion - trying to ask user for more information"); - if (Modal.hasDialogs()) { - console.warn("User has another dialog open - skipping prompt"); - return; - } - Modal.createTrackedDialog('Timeline exploded', '', TimelineExplosionDialog, {}); - } - }); - cli.on('sync', function(state, prevState, data) { // LifecycleStore and others cannot directly subscribe to matrix client for // events because flux only allows store state changes during flux dispatches. diff --git a/src/components/views/dialogs/TimelineExplosionDialog.js b/src/components/views/dialogs/TimelineExplosionDialog.js deleted file mode 100644 index 6e810d0421..0000000000 --- a/src/components/views/dialogs/TimelineExplosionDialog.js +++ /dev/null @@ -1,130 +0,0 @@ -/* -Copyright 2019 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import sdk from '../../../index'; -import SdkConfig from '../../../SdkConfig'; -import { _t } from '../../../languageHandler'; - -// Dev note: this should be a temporary dialog while we work out what is -// actually going on. See https://github.com/vector-im/riot-web/issues/8593 -// for more details. This dialog is almost entirely a copy/paste job of -// BugReportDialog. -export default class TimelineExplosionDialog extends React.Component { - static propTypes = { - onFinished: React.PropTypes.func.isRequired, - }; - - constructor(props, context) { - super(props, context); - this.state = { - busy: false, - progress: null, - }; - } - - _onCancel() { - console.log("Reloading without sending logs for timeline explosion"); - window.location.reload(); - } - - _onSubmit = () => { - const userText = "Caught timeline explosion\n\nhttps://github.com/vector-im/riot-web/issues/8593"; - - this.setState({busy: true, progress: null}); - this._sendProgressCallback(_t("Preparing to send logs")); - - require(['../../../rageshake/submit-rageshake'], (s) => { - s(SdkConfig.get().bug_report_endpoint_url, { - userText, - sendLogs: true, - progressCallback: this._sendProgressCallback, - }).then(() => { - console.log("Logs sent for timeline explosion - reloading Riot"); - window.location.reload(); - }, (err) => { - console.error("Error sending logs for timeline explosion - reloading anyways.", err); - window.location.reload(); - }); - }); - }; - - _sendProgressCallback = (progress) => { - this.setState({progress: progress}); - }; - - render() { - const Loader = sdk.getComponent("elements.Spinner"); - const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - - let progress = null; - if (this.state.busy) { - progress = ( -
- {this.state.progress} ... - -
- ); - } - - return ( - -
-

- {_t( - "Riot has run into a problem which makes it difficult to show you " + - "your messages right now. Nothing has been lost and reloading the app " + - "should fix this for you. In order to assist us in troubleshooting the " + - "problem, we'd like to take a look at your debug logs. You do not need " + - "to send your logs unless you want to, but we would really appreciate " + - "it if you did. We'd also like to apologize for having to show this " + - "message to you - we hope your debug logs are the key to solving the " + - "issue once and for all. If you'd like more information on the bug you've " + - "accidentally run into, please visit the issue.", - {}, - { - 'a': (sub) => { - return {sub}; - }, - }, - )} -

-

- {_t( - "Debug logs contain application usage data including your " + - "username, the IDs or aliases of the rooms or groups you " + - "have visited and the usernames of other users. They do " + - "not contain messages.", - )} -

- {progress} -
- -
- ); - } -} - diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5cfb61d60b..deca0737eb 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1210,10 +1210,6 @@ "Missing session data": "Missing session data", "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.", "Your browser likely removed this data when running low on disk space.": "Your browser likely removed this data when running low on disk space.", - "Error showing you your room": "Error showing you your room", - "Riot has run into a problem which makes it difficult to show you your messages right now. Nothing has been lost and reloading the app should fix this for you. In order to assist us in troubleshooting the problem, we'd like to take a look at your debug logs. You do not need to send your logs unless you want to, but we would really appreciate it if you did. We'd also like to apologize for having to show this message to you - we hope your debug logs are the key to solving the issue once and for all. If you'd like more information on the bug you've accidentally run into, please visit the issue.": "Riot has run into a problem which makes it difficult to show you your messages right now. Nothing has been lost and reloading the app should fix this for you. In order to assist us in troubleshooting the problem, we'd like to take a look at your debug logs. You do not need to send your logs unless you want to, but we would really appreciate it if you did. We'd also like to apologize for having to show this message to you - we hope your debug logs are the key to solving the issue once and for all. If you'd like more information on the bug you've accidentally run into, please visit the issue.", - "Send debug logs and reload Riot": "Send debug logs and reload Riot", - "Reload Riot without sending logs": "Reload Riot without sending logs", "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.", "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.", "Room contains unknown devices": "Room contains unknown devices", From 44e9ca6c522aeb9de8db8f3018e5fe2fe3890334 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 1 May 2019 13:58:32 +0100 Subject: [PATCH 47/64] Extract `isContentActionable` to a separate helper This moves the check about whether an event is actionable (for the purpose of replies, edits, reactions, etc.) to shared utils module. --- .../views/context_menus/MessageContextMenu.js | 26 +++++------ .../views/messages/MessageActionBar.js | 27 ++--------- src/utils/EventUtils.js | 45 +++++++++++++++++++ 3 files changed, 60 insertions(+), 38 deletions(-) create mode 100644 src/utils/EventUtils.js diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 1191b6d66e..2e4611f7d0 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -27,6 +27,7 @@ import Modal from '../../../Modal'; import Resend from '../../../Resend'; import SettingsStore from '../../../settings/SettingsStore'; import { isUrlPermitted } from '../../../HtmlUtils'; +import { isContentActionable } from '../../../utils/EventUtils'; module.exports = React.createClass({ displayName: 'MessageContextMenu', @@ -247,22 +248,19 @@ module.exports = React.createClass({ ); } - if (isSent && mxEvent.getType() === 'm.room.message') { - const content = mxEvent.getContent(); - if (content.msgtype && content.msgtype !== 'm.bad.encrypted' && content.hasOwnProperty('body')) { - forwardButton = ( -
- { _t('Forward Message') } + if (isContentActionable(mxEvent)) { + forwardButton = ( +
+ { _t('Forward Message') } +
+ ); + + if (this.state.canPin) { + pinButton = ( +
+ { this._isPinned() ? _t('Unpin Message') : _t('Pin Message') }
); - - if (this.state.canPin) { - pinButton = ( -
- { this._isPinned() ? _t('Unpin Message') : _t('Pin Message') } -
- ); - } } } diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 276a142ccb..76bae137f5 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -16,7 +16,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {EventStatus} from 'matrix-js-sdk'; +import classNames from 'classnames'; import { _t } from '../../../languageHandler'; import sdk from '../../../index'; @@ -24,7 +24,7 @@ import dis from '../../../dispatcher'; import Modal from '../../../Modal'; import { createMenu } from '../../structures/ContextualMenu'; import SettingsStore from '../../../settings/SettingsStore'; -import classNames from 'classnames'; +import { isContentActionable } from '../../../utils/EventUtils'; export default class MessageActionBar extends React.PureComponent { static propTypes = { @@ -123,27 +123,6 @@ export default class MessageActionBar extends React.PureComponent { this.onFocusChange(true); } - isContentActionable() { - const { mxEvent } = this.props; - const { status: eventStatus } = mxEvent; - - // status is SENT before remote-echo, null after - const isSent = !eventStatus || eventStatus === EventStatus.SENT; - - if (isSent && mxEvent.getType() === 'm.room.message') { - const content = mxEvent.getContent(); - if ( - content.msgtype && - content.msgtype !== 'm.bad.encrypted' && - content.hasOwnProperty('body') - ) { - return true; - } - } - - return false; - } - isReactionsEnabled() { return SettingsStore.isFeatureEnabled("feature_reactions"); } @@ -220,7 +199,7 @@ export default class MessageActionBar extends React.PureComponent { let likeDimensionReactionButtons; let replyButton; - if (this.isContentActionable()) { + if (isContentActionable(this.props.mxEvent)) { agreeDimensionReactionButtons = this.renderAgreeDimension(); likeDimensionReactionButtons = this.renderLikeDimension(); replyButton = Date: Wed, 1 May 2019 18:05:11 +0100 Subject: [PATCH 48/64] Display existing reactions below the message This displays the existing reactions a message has from all users below the message. Since we don't currently have an API to actually get these events yet, adds a temporary hook that looks for a specific message to inject some sample data. This helps build out the UI for now and can be removed once it exists. Fixes https://github.com/vector-im/riot-web/issues/9573 --- res/css/_components.scss | 2 + res/css/views/messages/_ReactionsRow.scss | 19 ++++++ .../views/messages/_ReactionsRowButton.scss | 30 +++++++++ res/themes/dark/css/_dark.scss | 4 ++ res/themes/light/css/_light.scss | 4 ++ src/components/views/messages/ReactionsRow.js | 65 +++++++++++++++++++ .../views/messages/ReactionsRowButton.js | 33 ++++++++++ src/components/views/rooms/EventTile.js | 9 +++ 8 files changed, 166 insertions(+) create mode 100644 res/css/views/messages/_ReactionsRow.scss create mode 100644 res/css/views/messages/_ReactionsRowButton.scss create mode 100644 src/components/views/messages/ReactionsRow.js create mode 100644 src/components/views/messages/ReactionsRowButton.js diff --git a/res/css/_components.scss b/res/css/_components.scss index bb09b873a3..36648f4982 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -115,6 +115,8 @@ @import "./views/messages/_MTextBody.scss"; @import "./views/messages/_MessageActionBar.scss"; @import "./views/messages/_MessageTimestamp.scss"; +@import "./views/messages/_ReactionsRow.scss"; +@import "./views/messages/_ReactionsRowButton.scss"; @import "./views/messages/_RoomAvatarEvent.scss"; @import "./views/messages/_SenderProfile.scss"; @import "./views/messages/_TextualEvent.scss"; diff --git a/res/css/views/messages/_ReactionsRow.scss b/res/css/views/messages/_ReactionsRow.scss new file mode 100644 index 0000000000..fb66ffbb8c --- /dev/null +++ b/res/css/views/messages/_ReactionsRow.scss @@ -0,0 +1,19 @@ +/* +Copyright 2019 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_ReactionsRow { + margin: 6px 0; +} diff --git a/res/css/views/messages/_ReactionsRowButton.scss b/res/css/views/messages/_ReactionsRowButton.scss new file mode 100644 index 0000000000..9cbf839f21 --- /dev/null +++ b/res/css/views/messages/_ReactionsRowButton.scss @@ -0,0 +1,30 @@ +/* +Copyright 2019 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_ReactionsRowButton { + display: inline-block; + height: 20px; + line-height: 21px; + margin-right: 6px; + padding: 0 6px; + border: 1px solid $reaction-row-button-border-color; + border-radius: 10px; + background-color: $reaction-row-button-bg-color; + + &:hover { + border-color: $reaction-row-button-hover-border-color; + } +} diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 7c0f8ef9ab..30066c7af4 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -151,6 +151,10 @@ $message-action-bar-fg-color: $header-panel-text-primary-color; $message-action-bar-border-color: #616b7f; $message-action-bar-hover-border-color: $header-panel-text-primary-color; +$reaction-row-button-bg-color: $header-panel-bg-color; +$reaction-row-button-border-color: #616b7f; +$reaction-row-button-hover-border-color: $header-panel-text-primary-color; + // ***** Mixins! ***** @define-mixin mx_DialogButton { diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 7451a23991..223d0fc80c 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -259,6 +259,10 @@ $message-action-bar-fg-color: $primary-fg-color; $message-action-bar-border-color: #e9edf1; $message-action-bar-hover-border-color: #b8c1d2; +$reaction-row-button-bg-color: $header-panel-bg-color; +$reaction-row-button-border-color: #e9edf1; +$reaction-row-button-hover-border-color: #bebebe; + // ***** Mixins! ***** @define-mixin mx_DialogButton { diff --git a/src/components/views/messages/ReactionsRow.js b/src/components/views/messages/ReactionsRow.js new file mode 100644 index 0000000000..a4299b9853 --- /dev/null +++ b/src/components/views/messages/ReactionsRow.js @@ -0,0 +1,65 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; + +import sdk from '../../../index'; +import { isContentActionable } from '../../../utils/EventUtils'; + +// TODO: Actually load reactions from the timeline +// Since we don't yet load reactions, let's inject some dummy data for testing the UI +// only. The UI assumes these are already sorted into the order we want to present, +// presumably highest vote first. +const SAMPLE_REACTIONS = { + "👍": 4, + "👎": 2, + "🙂": 1, +}; + +export default class ReactionsRow extends React.PureComponent { + static propTypes = { + // The event we're displaying reactions for + mxEvent: PropTypes.object.isRequired, + } + + render() { + const { mxEvent } = this.props; + + if (!isContentActionable(mxEvent)) { + return null; + } + + const content = mxEvent.getContent(); + // TODO: Remove this once we load real reactions + if (!content.body || content.body !== "reactions test") { + return null; + } + + const ReactionsRowButton = sdk.getComponent('messages.ReactionsRowButton'); + const items = Object.entries(SAMPLE_REACTIONS).map(([content, count]) => { + return ; + }); + + return
+ {items} +
; + } +} diff --git a/src/components/views/messages/ReactionsRowButton.js b/src/components/views/messages/ReactionsRowButton.js new file mode 100644 index 0000000000..4afcf93fff --- /dev/null +++ b/src/components/views/messages/ReactionsRowButton.js @@ -0,0 +1,33 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; + +export default class ReactionsRowButton extends React.PureComponent { + static propTypes = { + content: PropTypes.string.isRequired, + count: PropTypes.number.isRequired, + } + + render() { + const { content, count } = this.props; + + return + {content} {count} + ; + } +} diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index dd0a7aa47b..6bec3f4fff 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -622,6 +622,14 @@ module.exports = withMatrixClient(React.createClass({
: null; + let reactions; + if (SettingsStore.isFeatureEnabled("feature_reactions")) { + const ReactionsRow = sdk.getComponent('messages.ReactionsRow'); + reactions = ; + } + switch (this.props.tileShape) { case 'notif': { const EmojiText = sdk.getComponent('elements.EmojiText'); @@ -734,6 +742,7 @@ module.exports = withMatrixClient(React.createClass({ showUrlPreview={this.props.showUrlPreview} onHeightChanged={this.props.onHeightChanged} /> { keyRequestInfo } + { reactions } { actionBar }
{ From 87f737b8a38454c674d786674af7669cc808e0c9 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 2 May 2019 11:48:32 +0100 Subject: [PATCH 49/64] Increment an existing reaction This allows you to increment an existing reaction below a message by clicking on it. At the moment, this is not linked to the action bar, so they each are using local state. We'll likely want to add some mechanism so that we can local echo to both of these UI areas at the same time, but that can be done separately. Fixes https://github.com/vector-im/riot-web/issues/9486 --- .../views/messages/_ReactionsRowButton.scss | 6 ++++ res/themes/dark/css/_dark.scss | 2 ++ res/themes/light/css/_light.scss | 2 ++ .../views/messages/ReactionsRowButton.js | 36 +++++++++++++++++-- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/res/css/views/messages/_ReactionsRowButton.scss b/res/css/views/messages/_ReactionsRowButton.scss index 9cbf839f21..49e3930979 100644 --- a/res/css/views/messages/_ReactionsRowButton.scss +++ b/res/css/views/messages/_ReactionsRowButton.scss @@ -23,8 +23,14 @@ limitations under the License. border: 1px solid $reaction-row-button-border-color; border-radius: 10px; background-color: $reaction-row-button-bg-color; + cursor: pointer; &:hover { border-color: $reaction-row-button-hover-border-color; } + + &.mx_ReactionsRowButton_selected { + background-color: $reaction-row-button-selected-bg-color; + border-color: $reaction-row-button-selected-border-color; + } } diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 30066c7af4..592b1a1887 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -154,6 +154,8 @@ $message-action-bar-hover-border-color: $header-panel-text-primary-color; $reaction-row-button-bg-color: $header-panel-bg-color; $reaction-row-button-border-color: #616b7f; $reaction-row-button-hover-border-color: $header-panel-text-primary-color; +$reaction-row-button-selected-bg-color: #1f6954; +$reaction-row-button-selected-border-color: $accent-color; // ***** Mixins! ***** diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 223d0fc80c..adadd39333 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -262,6 +262,8 @@ $message-action-bar-hover-border-color: #b8c1d2; $reaction-row-button-bg-color: $header-panel-bg-color; $reaction-row-button-border-color: #e9edf1; $reaction-row-button-hover-border-color: #bebebe; +$reaction-row-button-selected-bg-color: #e9fff9; +$reaction-row-button-selected-border-color: $accent-color; // ***** Mixins! ***** diff --git a/src/components/views/messages/ReactionsRowButton.js b/src/components/views/messages/ReactionsRowButton.js index 4afcf93fff..985479a237 100644 --- a/src/components/views/messages/ReactionsRowButton.js +++ b/src/components/views/messages/ReactionsRowButton.js @@ -16,6 +16,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; export default class ReactionsRowButton extends React.PureComponent { static propTypes = { @@ -23,11 +24,42 @@ export default class ReactionsRowButton extends React.PureComponent { count: PropTypes.number.isRequired, } + constructor(props) { + super(props); + + // TODO: This should be derived from actual reactions you may have sent + // once we have some API to read them. + this.state = { + selected: false, + }; + } + + onClick = (ev) => { + const state = this.state.selected; + this.setState({ + selected: !state, + }); + // TODO: Send the reaction event + }; + render() { const { content, count } = this.props; + const { selected } = this.state; - return - {content} {count} + const classes = classNames({ + mx_ReactionsRowButton: true, + mx_ReactionsRowButton_selected: selected, + }); + + let adjustedCount = count; + if (selected) { + adjustedCount++; + } + + return + {content} {adjustedCount} ; } } From 33bb9e2af65e805180aa17dd6000a3f4fe238c85 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 30 Apr 2019 17:41:11 +0100 Subject: [PATCH 50/64] Update action bar colors for dark theme In addition, this also adjusts the event hover colors to match the palette. Fixes https://github.com/vector-im/riot-web/issues/9591 --- res/css/views/messages/_MessageActionBar.scss | 4 ++-- res/themes/dark/css/_dark.scss | 8 +++++--- res/themes/light/css/_light.scss | 10 ++++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index fc73d16d8f..f01ef4f04f 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -21,7 +21,7 @@ limitations under the License. display: flex; height: 24px; border-radius: 4px; - background: $primary-bg-color; + background: $message-action-bar-bg-color; top: -13px; right: 8px; user-select: none; @@ -59,7 +59,7 @@ limitations under the License. width: 100%; mask-repeat: no-repeat; mask-position: center; - background-color: $primary-fg-color; + background-color: $message-action-bar-fg-color; } } } diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 0d3b40b64f..7c0f8ef9ab 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -40,7 +40,7 @@ $tagpanel-bg-color: $base-color; $selected-color: $room-highlight-color; // selected for hoverover & selected event tiles -$event-selected-color: #111316; +$event-selected-color: $header-panel-bg-color; // used for the hairline dividers in RoomView $primary-hairline-color: $header-panel-border-color; @@ -146,8 +146,10 @@ $room-warning-bg-color: $header-panel-bg-color; $dark-panel-bg-color: $header-panel-bg-color; $panel-gradient: rgba(34, 38, 46, 0), rgba(34, 38, 46, 1); -$message-action-bar-border-color: $input-darker-bg-color; -$message-action-bar-hover-border-color: $text-secondary-color; +$message-action-bar-bg-color: $header-panel-bg-color; +$message-action-bar-fg-color: $header-panel-text-primary-color; +$message-action-bar-border-color: #616b7f; +$message-action-bar-hover-border-color: $header-panel-text-primary-color; // ***** Mixins! ***** diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index bdd5f10cc9..7451a23991 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -11,7 +11,7 @@ $font-family: 'Nunito', Arial, Helvetica, Sans-Serif; $accent-color: #03b381; $notice-primary-color: #ff4b55; $notice-secondary-color: #61708b; -$header-panel-bg-color: #f2f5f8; +$header-panel-bg-color: #f3f8fd; // typical text (dark-on-white in light skin) $primary-fg-color: #2e2f32; @@ -66,7 +66,7 @@ $droptarget-bg-color: rgba(255,255,255,0.5); $selected-color: $secondary-accent-color; // selected for hoverover & selected event tiles -$event-selected-color: #f7f7f7; +$event-selected-color: $header-panel-bg-color; // used for the hairline dividers in RoomView $primary-hairline-color: #e5e5e5; @@ -254,8 +254,10 @@ $authpage-secondary-color: #61708b; $dark-panel-bg-color: $secondary-accent-color; $panel-gradient: rgba(242, 245, 248, 0), rgba(242, 245, 248, 1); -$message-action-bar-border-color: $input-darker-bg-color; -$message-action-bar-hover-border-color: $roomtopic-color; +$message-action-bar-bg-color: $primary-bg-color; +$message-action-bar-fg-color: $primary-fg-color; +$message-action-bar-border-color: #e9edf1; +$message-action-bar-hover-border-color: #b8c1d2; // ***** Mixins! ***** From 1620ccac53cb4ccabbcd101cefb54052819ede62 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 May 2019 23:55:40 -0600 Subject: [PATCH 51/64] Always default to the registration form Fixes https://github.com/vector-im/riot-web/issues/8886 --- .../structures/auth/Registration.js | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index df87c1b9ca..78346170be 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -69,26 +69,6 @@ module.exports = React.createClass({ getInitialState: function() { const serverType = ServerType.getTypeFromHsUrl(this.props.customHsUrl); - const customURLsAllowed = !SdkConfig.get()['disable_custom_urls']; - let initialPhase = this.getDefaultPhaseForServerType(serverType); - if ( - // if we have these two, skip to the good bit - // (they could come in from the URL params in a - // registration email link) - (this.props.clientSecret && this.props.sessionId) || - // if custom URLs aren't allowed, skip to form - !customURLsAllowed || - // if other logic says to, skip to form - this.props.skipServerDetails - ) { - // TODO: It would seem we've now added enough conditions here that the initial - // phase will _always_ be the form. It's tempting to remove the complexity and - // just do that, but we keep tweaking and changing auth, so let's wait until - // things settle a bit. - // Filed https://github.com/vector-im/riot-web/issues/8886 to track this. - initialPhase = PHASE_REGISTRATION; - } - return { busy: false, errorText: null, @@ -111,7 +91,7 @@ module.exports = React.createClass({ hsUrl: this.props.customHsUrl, isUrl: this.props.customIsUrl, // Phase of the overall registration dialog. - phase: initialPhase, + phase: PHASE_REGISTRATION, flows: null, }; }, From 4e579930945249cd1741e3371bdfcc587fa15096 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 3 May 2019 10:22:10 +0100 Subject: [PATCH 52/64] yarn upgrade --- yarn.lock | 871 +++++++++++++++++++++++++----------------------------- 1 file changed, 409 insertions(+), 462 deletions(-) diff --git a/yarn.lock b/yarn.lock index a2942fd1d3..e471fcd974 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,17 +10,17 @@ "@babel/highlight" "^7.0.0" "@babel/core@>=7.1.0": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.3.tgz#198d6d3af4567be3989550d97e068de94503074f" - integrity sha512-oDpASqKFlbspQfzAE7yaeTmdljSH2ADIvBlb0RwbStltTuWa0+7CCI1fYVINNv9saHPa1W7oaKeuNuKj+RQCvA== + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.4.tgz#84055750b05fcd50f9915a826b44fa347a825250" + integrity sha512-lQgGX3FPRgbz2SKmhMtYgJvVzGZrmjaF4apZ2bLwofAKiSjxU0drPh4S/VasyYXwaTs+A1gvQ45BN8SQJzHsQQ== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.4.0" - "@babel/helpers" "^7.4.3" - "@babel/parser" "^7.4.3" - "@babel/template" "^7.4.0" - "@babel/traverse" "^7.4.3" - "@babel/types" "^7.4.0" + "@babel/generator" "^7.4.4" + "@babel/helpers" "^7.4.4" + "@babel/parser" "^7.4.4" + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.4.4" + "@babel/types" "^7.4.4" convert-source-map "^1.1.0" debug "^4.1.0" json5 "^2.1.0" @@ -29,23 +29,12 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.3.4": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.4.tgz#9aa48c1989257877a9d971296e5b73bfe72e446e" - integrity sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg== +"@babel/generator@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041" + integrity sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ== dependencies: - "@babel/types" "^7.3.4" - jsesc "^2.5.1" - lodash "^4.17.11" - source-map "^0.5.0" - trim-right "^1.0.1" - -"@babel/generator@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.0.tgz#c230e79589ae7a729fd4631b9ded4dc220418196" - integrity sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ== - dependencies: - "@babel/types" "^7.4.0" + "@babel/types" "^7.4.4" jsesc "^2.5.1" lodash "^4.17.11" source-map "^0.5.0" @@ -67,28 +56,21 @@ dependencies: "@babel/types" "^7.0.0" -"@babel/helper-split-export-declaration@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz#3aae285c0311c2ab095d997b8c9a94cad547d813" - integrity sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag== +"@babel/helper-split-export-declaration@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" + integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.4.4" -"@babel/helper-split-export-declaration@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz#571bfd52701f492920d63b7f735030e9a3e10b55" - integrity sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw== +"@babel/helpers@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.4.tgz#868b0ef59c1dd4e78744562d5ce1b59c89f2f2a5" + integrity sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A== dependencies: - "@babel/types" "^7.4.0" - -"@babel/helpers@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.3.tgz#7b1d354363494b31cb9a2417ae86af32b7853a3b" - integrity sha512-BMh7X0oZqb36CfyhvtbSmcWc3GXocfxv3yNsAEuM0l+fAqSO22rQrUpijr3oE/10jCTrB6/0b9kzmG4VetCj8Q== - dependencies: - "@babel/template" "^7.4.0" - "@babel/traverse" "^7.4.3" - "@babel/types" "^7.4.0" + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.4.4" + "@babel/types" "^7.4.4" "@babel/highlight@^7.0.0": version "7.0.0" @@ -99,96 +81,57 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.2.2", "@babel/parser@^7.3.4": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.4.tgz#a43357e4bbf4b92a437fb9e465c192848287f27c" - integrity sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ== - -"@babel/parser@^7.4.0", "@babel/parser@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.3.tgz#eb3ac80f64aa101c907d4ce5406360fe75b7895b" - integrity sha512-gxpEUhTS1sGA63EGQGuA+WESPR/6tz6ng7tSHFCmaTJK/cGK8y37cBTspX+U2xCAue2IQVvF6Z0oigmjwD8YGQ== +"@babel/parser@^7.0.0", "@babel/parser@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.4.tgz#5977129431b8fe33471730d255ce8654ae1250b6" + integrity sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w== "@babel/runtime@^7.1.2": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83" - integrity sha512-IvfvnMdSaLBateu0jfsYIpZTxAc2cKEXEMiezGGN75QcBcecDUKd3PgLAncT0oOgxKy8dd8hrJKj9MfzgfZd6g== + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.4.tgz#dc2e34982eb236803aa27a07fea6857af1b9171d" + integrity sha512-w0+uT71b6Yi7i5SE0co4NioIpSYS6lLiXvCzWzGSKvpK5vdQtCbICHMj+gbAKAOtxiV6HsVh/MBdaF9EQ6faSg== dependencies: - regenerator-runtime "^0.12.0" + regenerator-runtime "^0.13.2" -"@babel/template@^7.1.0": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907" - integrity sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g== +"@babel/template@^7.1.0", "@babel/template@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" + integrity sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.2.2" - "@babel/types" "^7.2.2" + "@babel/parser" "^7.4.4" + "@babel/types" "^7.4.4" -"@babel/template@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.0.tgz#12474e9c077bae585c5d835a95c0b0b790c25c8b" - integrity sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.4.tgz#0776f038f6d78361860b6823887d4f3937133fe8" + integrity sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.4.0" - "@babel/types" "^7.4.0" - -"@babel/traverse@^7.0.0": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.3.4.tgz#1330aab72234f8dea091b08c4f8b9d05c7119e06" - integrity sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.3.4" + "@babel/generator" "^7.4.4" "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.0.0" - "@babel/parser" "^7.3.4" - "@babel/types" "^7.3.4" + "@babel/helper-split-export-declaration" "^7.4.4" + "@babel/parser" "^7.4.4" + "@babel/types" "^7.4.4" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.11" -"@babel/traverse@^7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.3.tgz#1a01f078fc575d589ff30c0f71bf3c3d9ccbad84" - integrity sha512-HmA01qrtaCwwJWpSKpA948cBvU5BrmviAief/b3AVw936DtcdsTexlbyzNuDnthwhOQ37xshn7hvQaEQk7ISYQ== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.4.0" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.4.0" - "@babel/parser" "^7.4.3" - "@babel/types" "^7.4.0" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.11" - -"@babel/types@^7.0.0", "@babel/types@^7.2.2", "@babel/types@^7.3.4": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.4.tgz#bf482eaeaffb367a28abbf9357a94963235d90ed" - integrity sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ== +"@babel/types@^7.0.0", "@babel/types@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0" + integrity sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ== dependencies: esutils "^2.0.2" lodash "^4.17.11" to-fast-properties "^2.0.0" -"@babel/types@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.0.tgz#670724f77d24cce6cc7d8cf64599d511d164894c" - integrity sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA== - dependencies: - esutils "^2.0.2" - lodash "^4.17.11" - to-fast-properties "^2.0.0" - -"@jest/console@^24.3.0": - version "24.3.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.3.0.tgz#7bd920d250988ba0bf1352c4493a48e1cb97671e" - integrity sha512-NaCty/OOei6rSDcbPdMiCbYCI0KGFGPgGO6B09lwWt5QTxnkuhKYET9El5u5z1GAcSxkQmSMtM63e24YabCWqA== +"@jest/console@^24.7.1": + version "24.7.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.7.1.tgz#32a9e42535a97aedfe037e725bd67e954b459545" + integrity sha512-iNhtIy2M8bXlAOULWVTUxmnelTLFneTNEkHCgPmgd+zNwy9zVddJ6oS5rZ9iwoscNdT5mMwUd0C51v/fSlzItg== dependencies: "@jest/source-map" "^24.3.0" - "@types/node" "*" chalk "^2.0.1" slash "^2.0.0" @@ -201,21 +144,21 @@ graceful-fs "^4.1.15" source-map "^0.6.0" -"@jest/test-result@^24.3.0": - version "24.3.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.3.0.tgz#4c0b1c9716212111920f7cf8c4329c69bc81924a" - integrity sha512-j7UZ49T8C4CVipEY99nLttnczVTtLyVzFfN20OiBVn7awOs0U3endXSTq7ouPrLR5y4YjI5GDcbcvDUjgeamzg== +"@jest/test-result@^24.7.1": + version "24.7.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.7.1.tgz#19eacdb29a114300aed24db651e5d975f08b6bbe" + integrity sha512-3U7wITxstdEc2HMfBX7Yx3JZgiNBubwDqQMh+BXmZXHa3G13YWF3p6cK+5g0hGkN3iufg/vGPl3hLxQXD74Npg== dependencies: - "@jest/console" "^24.3.0" - "@jest/types" "^24.3.0" - "@types/istanbul-lib-coverage" "^1.1.0" + "@jest/console" "^24.7.1" + "@jest/types" "^24.7.0" + "@types/istanbul-lib-coverage" "^2.0.0" -"@jest/types@^24.3.0": - version "24.3.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.3.0.tgz#3f6e117e47248a9a6b5f1357ec645bd364f7ad23" - integrity sha512-VoO1F5tU2n/93QN/zaZ7Q8SeV/Rj+9JJOgbvKbBwy4lenvmdj1iDaQEPXGTKrO6OSvDeb2drTFipZJYxgo6kIQ== +"@jest/types@^24.7.0": + version "24.7.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.7.0.tgz#c4ec8d1828cdf23234d9b4ee31f5482a3f04f48b" + integrity sha512-ipJUa2rFWiKoBqMKP63Myb6h9+iT3FHRTF2M8OR6irxWzItisa8i4dcSg14IbvmXUnBlHBlUQPYUHWyX3UPpYA== dependencies: - "@types/istanbul-lib-coverage" "^1.1.0" + "@types/istanbul-lib-coverage" "^2.0.0" "@types/yargs" "^12.0.9" "@mrmlnc/readdir-enhanced@^2.2.1": @@ -254,9 +197,9 @@ "@sinonjs/samsam" "^3.1.0" "@sinonjs/samsam@^3.1.0": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.3.0.tgz#9557ea89cd39dbc94ffbd093c8085281cac87416" - integrity sha512-beHeJM/RRAaLLsMJhsCvHK31rIqZuobfPLa/80yGH5hnD8PV1hyh9xJBJNFfNmO7yWqm+zomijHsXpI6iTQJfQ== + version "3.3.1" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.3.1.tgz#e88c53fbd9d91ad9f0f2b0140c16c7c107fe0d07" + integrity sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw== dependencies: "@sinonjs/commons" "^1.0.2" array-from "^2.1.1" @@ -281,10 +224,10 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/istanbul-lib-coverage@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.0.tgz#2cc2ca41051498382b43157c8227fea60363f94a" - integrity sha512-ohkhb9LehJy+PA40rDtGAji61NCgdtKLAlFoYp4cnuuQEswwdK3vz9SOIkkyc3wrk8dzjphQApNs56yyXLStaQ== +"@types/istanbul-lib-coverage@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" + integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg== "@types/minimatch@*": version "3.0.3" @@ -292,9 +235,9 @@ integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== "@types/node@*": - version "11.11.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.0.tgz#070e9ce7c90e727aca0e0c14e470f9a93ffe9390" - integrity sha512-D5Rt+HXgEywr3RQJcGlZUCTCx1qVbCZpVk3/tOOA6spLNZdGm8BU+zRgdRYDoF1pO3RuXLxADzMrF903JlQXqg== + version "11.13.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.9.tgz#f80697caca7f7fb2526527a5c5a2743487f05ccc" + integrity sha512-NJ4yuEVw5podZbINp3tEqUIImMSAEHaCXRiWCf3KC32l6hIKf0iPJEh2uZdT0fELfRYk310yLmMXqy2leZQUbg== "@types/stack-utils@^1.0.1": version "1.0.1" @@ -324,9 +267,9 @@ "@types/vfile-message" "*" "@types/yargs@^12.0.9": - version "12.0.9" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0" - integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA== + version "12.0.12" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916" + integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw== "@webassemblyjs/ast@1.8.5": version "1.8.5" @@ -490,12 +433,12 @@ abbrev@1: integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== accepts@~1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" - integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I= + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== dependencies: - mime-types "~2.1.18" - negotiator "0.6.1" + mime-types "~2.1.24" + negotiator "0.6.2" acorn-dynamic-import@^4.0.0: version "4.0.0" @@ -731,9 +674,9 @@ astral-regex@^1.0.0: integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== async-each@^1.0.0, async-each@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" - integrity sha1-GdOGodntxufByF04iu28xW0zYC0= + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== async-limiter@~1.0.0: version "1.0.0" @@ -1569,9 +1512,9 @@ big.js@^5.2.2: integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== binary-extensions@^1.0.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.0.tgz#9523e001306a32444b907423f1de2164222f6ab1" - integrity sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw== + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== blob@0.0.5: version "0.0.5" @@ -1579,9 +1522,9 @@ blob@0.0.5: integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig== bluebird@^3.3.0, bluebird@^3.5.0, bluebird@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" - integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== + version "3.5.4" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.4.tgz#d6cc661595de30d5b3af5fcedd3c0b3ef6ec5714" + integrity sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw== blueimp-canvas-to-blob@^3.5.0: version "3.14.0" @@ -1594,20 +1537,20 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== body-parser@^1.16.1: - version "1.18.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" - integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ= + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== dependencies: - bytes "3.0.0" + bytes "3.1.0" content-type "~1.0.4" debug "2.6.9" depd "~1.1.2" - http-errors "~1.6.3" - iconv-lite "0.4.23" + http-errors "1.7.2" + iconv-lite "0.4.24" on-finished "~2.3.0" - qs "6.5.2" - raw-body "2.3.3" - type-is "~1.6.16" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" brace-expansion@^1.1.7: version "1.1.11" @@ -1722,13 +1665,13 @@ browserify-zlib@^0.2.0: pako "~1.0.5" browserslist@^4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.5.4.tgz#166c4ecef3b51737a42436ea8002aeea466ea2c7" - integrity sha512-rAjx494LMjqKnMPhFkuLmLp8JWEX0o8ADTGeAbOqaF+XCvYLreZrG5uVjnPBlAQ8REZK4pzXGvp0bWgrFtKaag== + version "4.5.6" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.5.6.tgz#ea42e8581ca2513fa7f371d4dd66da763938163d" + integrity sha512-o/hPOtbU9oX507lIqon+UvPYqpx3mHc8cV3QemSBTXwkG8gSQSK6UKvXcE/DcleU3+A59XTUHyCvZ5qGy8xVAg== dependencies: - caniuse-lite "^1.0.30000955" - electron-to-chromium "^1.3.122" - node-releases "^1.1.13" + caniuse-lite "^1.0.30000963" + electron-to-chromium "^1.3.127" + node-releases "^1.1.17" bs58@^4.0.1: version "4.0.1" @@ -1779,10 +1722,10 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== cacache@^11.0.2: version "11.3.2" @@ -1849,9 +1792,9 @@ callsites@^2.0.0: integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= callsites@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" - integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw== + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase-keys@^4.0.0: version "4.2.0" @@ -1868,14 +1811,14 @@ camelcase@^4.1.0: integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= camelcase@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.2.0.tgz#e7522abda5ed94cc0489e1b8466610e88404cf45" - integrity sha512-IXFsBS2pC+X0j0N/GE7Dm7j3bsEBp+oTpb7F50dwEVX7rf3IgwO9XatnegTsDtniKCUtEJH4fSU6Asw7uoVLfQ== + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caniuse-lite@^1.0.30000955, caniuse-lite@^1.0.30000957: - version "1.0.30000957" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000957.tgz#fb1026bf184d7d62c685205358c3b24b9e29f7b3" - integrity sha512-8wxNrjAzyiHcLXN/iunskqQnJquQQ6VX8JHfW5kLgAPRSiSuKZiNfmIkP5j7jgyXqAQBSoXyJxfnbCFS0ThSiQ== +caniuse-lite@^1.0.30000957, caniuse-lite@^1.0.30000963: + version "1.0.30000966" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000966.tgz#f3c6fefacfbfbfb981df6dfa68f2aae7bff41b64" + integrity sha512-qqLQ/uYrpZmFhPY96VuBkMEo8NhVFBZ9y/Bh+KnvGzGJ5I8hvpIaWlF2pw5gqe4PLAL+ZjsPgMOvoXSpX21Keg== caseless@~0.12.0: version "0.12.0" @@ -1949,9 +1892,9 @@ chokidar@^1.6.1: fsevents "^1.0.0" chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.2.tgz#9c23ea40b01638439e0513864d362aeacc5ad058" - integrity sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg== + version "2.1.5" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.5.tgz#0ae8434d962281a5f56c72869e79cb6d9d86ad4d" + integrity sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A== dependencies: anymatch "^2.0.0" async-each "^1.0.1" @@ -1963,7 +1906,7 @@ chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.1.2: normalize-path "^3.0.0" path-is-absolute "^1.0.0" readdirp "^2.2.1" - upath "^1.1.0" + upath "^1.1.1" optionalDependencies: fsevents "^1.2.7" @@ -2079,9 +2022,9 @@ commander@2.15.1: integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== commander@^2.11.0, commander@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" - integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== + version "2.20.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" + integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== commondir@^1.0.1: version "1.0.1" @@ -2103,11 +2046,16 @@ component-bind@1.0.0: resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E= -component-emitter@1.2.1, component-emitter@^1.2.1: +component-emitter@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + component-inherit@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" @@ -2357,7 +2305,7 @@ date-now@^0.1.4: resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= -debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -2378,7 +2326,7 @@ debug@^3.1.0, debug@^3.2.6: dependencies: ms "^2.1.1" -debug@^4.0.0, debug@^4.0.1, debug@^4.1.0: +debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -2605,10 +2553,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.3.122: - version "1.3.124" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.124.tgz#861fc0148748a11b3e5ccebdf8b795ff513fa11f" - integrity sha512-glecGr/kFdfeXUHOHAWvGcXrxNU+1wSO/t5B23tT1dtlvYB26GY8aHzZSWD7HqhqC800Lr+w/hQul6C5AF542w== +electron-to-chromium@^1.3.127: + version "1.3.130" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.130.tgz#27f84e823bd80a5090e2baeca4fefbaf476cf7af" + integrity sha512-UY2DI+gsnqGtQJqO8wXN0DnpJY+29FwJafACj0h18ZShn5besKnrRq6+lXWUbKzdxw92QQcnTqRLgNByOKXcUg== elliptic@^6.0.0: version "6.4.1" @@ -2781,17 +2729,17 @@ eslint-plugin-flowtype@^2.30.0: lodash "^4.17.10" eslint-plugin-react@^7.7.0: - version "7.12.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz#b1ecf26479d61aee650da612e425c53a99f48c8c" - integrity sha512-1puHJkXJY+oS1t467MjbqjvX53uQ05HXwjqDgdbGBqf5j9eeydI54G3KwiJmWciQ0HTBacIKw2jgwSBSH3yfgQ== + version "7.13.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.13.0.tgz#bc13fd7101de67996ea51b33873cd9dc2b7e5758" + integrity sha512-uA5LrHylu8lW/eAH3bEQe9YdzpPaFd9yAJTwTi/i/BKTD7j6aQMKVAdGM/ML72zD6womuSK7EiGtMKuK06lWjQ== dependencies: array-includes "^3.0.3" doctrine "^2.1.0" has "^1.0.3" - jsx-ast-utils "^2.0.1" + jsx-ast-utils "^2.1.0" object.fromentries "^2.0.0" - prop-types "^15.6.2" - resolve "^1.9.0" + prop-types "^15.7.2" + resolve "^1.10.1" eslint-rule-composer@^0.3.0: version "0.3.0" @@ -2806,10 +2754,10 @@ eslint-scope@3.7.1: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@^4.0.0, eslint-scope@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.2.tgz#5f10cd6cabb1965bf479fa65745673439e21cb0e" - integrity sha512-5q1+B/ogmHl8+paxtOKx38Z8LtWkVGuNt3+GQNErqwLl6ViNp/gdJGMCjZNxZ8j/VYjDNZ2Fo+eQc1TAVPIzbg== +eslint-scope@^4.0.0, eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" @@ -2825,9 +2773,9 @@ eslint-visitor-keys@^1.0.0: integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== eslint@^5.12.0: - version "5.15.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.15.1.tgz#8266b089fd5391e0009a047050795b1d73664524" - integrity sha512-NTcm6vQ+PTgN3UBsALw5BMhgO6i5EpIjQF/Xb5tIh3sk9QhrFafujUOczGz4J24JBlzWclSB9Vmx8d+9Z6bFCg== + version "5.16.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea" + integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.9.1" @@ -2835,7 +2783,7 @@ eslint@^5.12.0: cross-spawn "^6.0.5" debug "^4.0.1" doctrine "^3.0.0" - eslint-scope "^4.0.2" + eslint-scope "^4.0.3" eslint-utils "^1.3.1" eslint-visitor-keys "^1.0.0" espree "^5.0.1" @@ -2849,7 +2797,7 @@ eslint@^5.12.0: import-fresh "^3.0.0" imurmurhash "^0.1.4" inquirer "^6.2.2" - js-yaml "^3.12.0" + js-yaml "^3.13.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.3.0" lodash "^4.17.11" @@ -2915,9 +2863,9 @@ esutils@^2.0.2: integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= eventemitter3@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" - integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA== + version "3.1.2" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" + integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== events@^3.0.0: version "3.0.0" @@ -3007,15 +2955,15 @@ expect@^1.20.2: tmatch "^2.0.1" expect@^24.1.0: - version "24.3.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-24.3.1.tgz#7c42507da231a91a8099d065bc8dc9322dc85fc0" - integrity sha512-xnmobSlaqhg4FKqjb5REk4AobQzFMJoctDdREKfSGqrtzRfCWYbfqt3WmikAvQz/J8mCNQhORgYdEjPMJbMQPQ== + version "24.7.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-24.7.1.tgz#d91defbab4e627470a152feaf35b3c31aa1c7c14" + integrity sha512-mGfvMTPduksV3xoI0xur56pQsg2vJjNf5+a+bXOjqCkiCBbmCayrBbHS/75y9K430cfqyocPr2ZjiNiRx4SRKw== dependencies: - "@jest/types" "^24.3.0" + "@jest/types" "^24.7.0" ansi-styles "^3.2.0" jest-get-type "^24.3.0" - jest-matcher-utils "^24.3.1" - jest-message-util "^24.3.0" + jest-matcher-utils "^24.7.0" + jest-message-util "^24.7.1" jest-regex-util "^24.3.0" extend-shallow@^2.0.1: @@ -3227,12 +3175,12 @@ find-cache-dir@^1.0.0: pkg-dir "^2.0.0" find-cache-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.0.0.tgz#4c1faed59f45184530fb9d7fa123a4d04a98472d" - integrity sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA== + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== dependencies: commondir "^1.0.1" - make-dir "^1.0.0" + make-dir "^2.0.0" pkg-dir "^3.0.0" find-up@^2.0.0, find-up@^2.1.0: @@ -3406,12 +3354,12 @@ fs.realpath@^1.0.0: integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= fsevents@^1.0.0, fsevents@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4" - integrity sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw== + version "1.2.9" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f" + integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw== dependencies: - nan "^2.9.2" - node-pre-gyp "^0.10.0" + nan "^2.12.1" + node-pre-gyp "^0.12.0" function-bind@^1.1.1: version "1.1.1" @@ -3592,9 +3540,9 @@ global-prefix@^3.0.0: which "^1.3.1" globals@^11.1.0, globals@^11.7.0: - version "11.11.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.11.0.tgz#dcf93757fa2de5486fbeed7118538adf789e9c2e" - integrity sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw== + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^9.18.0: version "9.18.0" @@ -3806,15 +3754,16 @@ htmlparser2@^3.10.0: inherits "^2.0.1" readable-stream "^3.1.1" -http-errors@1.6.3, http-errors@~1.6.3: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== dependencies: depd "~1.1.2" inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" http-proxy@^1.13.0: version "1.17.0" @@ -3839,14 +3788,7 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -iconv-lite@0.4.23: - version "0.4.23" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" - integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: +iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -3854,9 +3796,9 @@ iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: safer-buffer ">= 2.1.2 < 3" ieee754@^1.1.4: - version "1.1.12" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" - integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA== + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== iferr@^0.1.5: version "0.1.5" @@ -3876,9 +3818,9 @@ ignore@^4.0.3, ignore@^4.0.6: integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== ignore@^5.0.4: - version "5.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.0.6.tgz#562dacc7ec27d672dde433aa683c543b24c17694" - integrity sha512-/+hp3kUf/Csa32ktIaj0OlRqQxrgs30n62M90UBpNd9k+ENEch5S+hmbW3DtcJGz3sYFTh4F3A6fQ0q7KWsp4w== + version "5.1.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.1.tgz#2fc6b8f518aff48fef65a7f348ed85632448e4a5" + integrity sha512-DWjnQIFLenVrwyRCKZT+7a7/U4Cqgar4WG8V++K3hw+lrW1hc/SIwdiGmtxKCVACmHULTuGeBbHJmbwW7/sAvA== immutable@^3.7.4: version "3.8.2" @@ -3958,9 +3900,9 @@ ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== inquirer@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.2.tgz#46941176f65c9eb20804627149b743a218f25406" - integrity sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA== + version "6.3.1" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.3.1.tgz#7a413b5e7950811013a3db491c61d1f3b776e8e7" + integrity sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA== dependencies: ansi-escapes "^3.2.0" chalk "^2.4.2" @@ -3973,7 +3915,7 @@ inquirer@^6.2.2: run-async "^2.2.0" rxjs "^6.4.0" string-width "^2.1.0" - strip-ansi "^5.0.0" + strip-ansi "^5.1.0" through "^2.3.6" interpret@^1.1.0: @@ -4206,9 +4148,9 @@ is-glob@^3.1.0: is-extglob "^2.1.0" is-glob@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" - integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A= + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== dependencies: is-extglob "^2.1.1" @@ -4406,39 +4348,39 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -jest-diff@^24.3.1: - version "24.3.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.3.1.tgz#87952e5ea1548567da91df398fa7bf7977d3f96a" - integrity sha512-YRVzDguyzShP3Pb9wP/ykBkV7Z+O4wltrMZ2P4LBtNxrHNpxwI2DECrpD9XevxWubRy5jcE8sSkxyX3bS7W+rA== +jest-diff@^24.7.0: + version "24.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.7.0.tgz#5d862899be46249754806f66e5729c07fcb3580f" + integrity sha512-ULQZ5B1lWpH70O4xsANC4tf4Ko6RrpwhE3PtG6ERjMg1TiYTC2Wp4IntJVGro6a8HG9luYHhhmF4grF0Pltckg== dependencies: chalk "^2.0.1" diff-sequences "^24.3.0" jest-get-type "^24.3.0" - pretty-format "^24.3.1" + pretty-format "^24.7.0" jest-get-type@^24.3.0: version "24.3.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.3.0.tgz#582cfd1a4f91b5cdad1d43d2932f816d543c65da" integrity sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow== -jest-matcher-utils@^24.3.1: - version "24.3.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.3.1.tgz#025e1cd9c54a5fde68e74b12428775d06d123aa8" - integrity sha512-P5VIsUTJeI0FYvWVMwEHjxK1L83vEkDiKMV0XFPIrT2jzWaWPB2+dPCHkP2ID9z4eUKElaHqynZnJiOdNVHfXQ== +jest-matcher-utils@^24.7.0: + version "24.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.7.0.tgz#bbee1ff37bc8b2e4afcaabc91617c1526af4bcd4" + integrity sha512-158ieSgk3LNXeUhbVJYRXyTPSCqNgVXOp/GT7O94mYd3pk/8+odKTyR1JLtNOQSPzNi8NFYVONtvSWA/e1RDXg== dependencies: chalk "^2.0.1" - jest-diff "^24.3.1" + jest-diff "^24.7.0" jest-get-type "^24.3.0" - pretty-format "^24.3.1" + pretty-format "^24.7.0" -jest-message-util@^24.3.0: - version "24.3.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.3.0.tgz#e8f64b63ebc75b1a9c67ee35553752596e70d4a9" - integrity sha512-lXM0YgKYGqN5/eH1NGw4Ix+Pk2I9Y77beyRas7xM24n+XTTK3TbT0VkT3L/qiyS7WkW0YwyxoXnnAaGw4hsEDA== +jest-message-util@^24.7.1: + version "24.7.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.7.1.tgz#f1dc3a6c195647096a99d0f1dadbc447ae547018" + integrity sha512-dk0gqVtyqezCHbcbk60CdIf+8UHgD+lmRHifeH3JRcnAqh4nEyPytSc9/L1+cQyxC+ceaeP696N4ATe7L+omcg== dependencies: "@babel/code-frame" "^7.0.0" - "@jest/test-result" "^24.3.0" - "@jest/types" "^24.3.0" + "@jest/test-result" "^24.7.1" + "@jest/types" "^24.7.0" "@types/stack-utils" "^1.0.1" chalk "^2.0.1" micromatch "^3.1.10" @@ -4456,9 +4398,9 @@ jest-regex-util@^24.3.0: integrity sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg== jquery@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" - integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg== + version "3.4.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" + integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" @@ -4470,14 +4412,6 @@ js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= -js-yaml@^3.12.0: - version "3.12.2" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.2.tgz#ef1d067c5a9d9cb65bd72f285b5d8105c77f14fc" - integrity sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - js-yaml@^3.13.0: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" @@ -4567,10 +4501,10 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jsx-ast-utils@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f" - integrity sha1-6AGxs5mF4g//yHtA43SAgOLcrH8= +jsx-ast-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.1.0.tgz#0ee4e2c971fb9601c67b5641b71be80faecf0b36" + integrity sha512-yDGDG2DS4JcqhA6blsuYbtsT09xL8AoLuUR2Gb5exrw7UEM19sBcOTq+YBBhrNbl0PUC4R4LnFu+dHg2HKeVvA== dependencies: array-includes "^3.0.3" @@ -4646,9 +4580,9 @@ karma-webpack@^4.0.0-beta.0: webpack-dev-middleware "^3.2.0" karma@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/karma/-/karma-4.0.1.tgz#2581d6caa0d4cd28b65131561b47bad6d5478773" - integrity sha512-ind+4s03BqIXas7ZmraV3/kc5+mnqwCd+VDX1FndS6jxbt03kQKX2vXrWxNLuCjVYmhMwOZosAEKMM0a2q7w7A== + version "4.1.0" + resolved "https://registry.yarnpkg.com/karma/-/karma-4.1.0.tgz#d07387c9743a575b40faf73e8a3eb5421c2193e1" + integrity sha512-xckiDqyNi512U4dXGOOSyLKPwek6X/vUizSy2f3geYevbLj+UIdvNwbn7IwfUIL2g1GXEPWt/87qFD1fBbl/Uw== dependencies: bluebird "^3.3.0" body-parser "^1.16.1" @@ -4829,15 +4763,15 @@ log-symbols@^2.0.0, log-symbols@^2.2.0: chalk "^2.0.1" log4js@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-4.0.2.tgz#0c73e623ca4448669653eb0e9f629beacc7fbbe3" - integrity sha512-KE7HjiieVDPPdveA3bJZSuu0n8chMkFl8mIoisBFxwEJ9FmXe4YzNuiqSwYUiR1K8q8/5/8Yd6AClENY1RA9ww== + version "4.1.0" + resolved "https://registry.yarnpkg.com/log4js/-/log4js-4.1.0.tgz#57983c6a443546a8c8607e9cb045d2a117c27644" + integrity sha512-eDa+zZPeVEeK6QGJAePyXM6pg4P3n3TO5rX9iZMVY48JshsTyLJZLIL5HipI1kQ2qLsSyOpUqNND/C5H4WhhiA== dependencies: date-format "^2.0.0" - debug "^3.1.0" + debug "^4.1.1" flatted "^2.0.0" rfdc "^1.1.2" - streamroller "^1.0.1" + streamroller "^1.0.4" loglevel@1.6.1: version "1.6.1" @@ -4896,6 +4830,14 @@ make-dir@^1.0.0: dependencies: pify "^3.0.0" +make-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + mamacro@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" @@ -5010,12 +4952,12 @@ media-typer@0.3.0: integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= mem@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-4.1.0.tgz#aeb9be2d21f47e78af29e4ac5978e8afa2ca5b8a" - integrity sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg== + version "4.3.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" + integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== dependencies: map-age-cleaner "^0.1.1" - mimic-fn "^1.0.0" + mimic-fn "^2.0.0" p-is-promise "^2.0.0" memoize-one@^3.0.1: @@ -5102,28 +5044,33 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@~1.38.0: - version "1.38.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" - integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg== +mime-db@1.40.0: + version "1.40.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" + integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== -mime-types@^2.1.12, mime-types@~2.1.18, mime-types@~2.1.19: - version "2.1.22" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.22.tgz#fe6b355a190926ab7698c9a0556a11199b2199bd" - integrity sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog== +mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24: + version "2.1.24" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" + integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== dependencies: - mime-db "~1.38.0" + mime-db "1.40.0" mime@^2.3.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.0.tgz#e051fd881358585f3279df333fe694da0bcffdd6" - integrity sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w== + version "2.4.2" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.2.tgz#ce5229a5e99ffc313abac806b482c10e7ba6ac78" + integrity sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg== mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== +mimic-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -5259,10 +5206,10 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -nan@^2.9.2: - version "2.12.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" - integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== +nan@^2.12.1: + version "2.13.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" + integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== nanomatch@^1.2.9: version "1.2.13" @@ -5287,18 +5234,18 @@ natural-compare@^1.4.0: integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= needle@^2.2.1: - version "2.2.4" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" - integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA== + version "2.3.1" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.1.tgz#d272f2f4034afb9c4c9ab1379aabc17fc85c9388" + integrity sha512-CaLXV3W8Vnbps8ZANqDGz7j4x7Yj1LW4TWF/TQuDfj7Cfx4nAPTvw98qgTevtto1oHDrh3pQkaODbqupXlsWTg== dependencies: - debug "^2.1.2" + debug "^4.1.0" iconv-lite "^0.4.4" sax "^1.2.4" -negotiator@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" - integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== neo-async@^2.5.0: version "2.6.0" @@ -5358,10 +5305,10 @@ node-libs-browser@^2.0.0: util "^0.11.0" vm-browserify "0.0.4" -node-pre-gyp@^0.10.0: - version "0.10.3" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" - integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A== +node-pre-gyp@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" + integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A== dependencies: detect-libc "^1.0.2" mkdirp "^0.5.1" @@ -5374,10 +5321,10 @@ node-pre-gyp@^0.10.0: semver "^5.3.0" tar "^4" -node-releases@^1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.13.tgz#8c03296b5ae60c08e2ff4f8f22ae45bd2f210083" - integrity sha512-fKZGviSXR6YvVPyc011NHuJDSD8gFQvLPmc2d2V3BS4gr52ycyQ1Xzs7a8B+Ax3Ni/W+5h1h4SqmzeoA8WZRmA== +node-releases@^1.1.17: + version "1.1.17" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.17.tgz#71ea4631f0a97d5cd4f65f7d04ecf9072eac711a" + integrity sha512-/SCjetyta1m7YXLgtACZGDYJdCSIBAWorDWkGCGZlydP2Ll7J48l7j/JxNYZ+xsgSPbWfdulVS/aY+GdjUsQ7Q== dependencies: semver "^5.3.0" @@ -5496,9 +5443,9 @@ object-inspect@^1.1.0: integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ== object-keys@^1.0.12, object-keys@^1.0.9: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.0.tgz#11bd22348dd2e096a045ab06f6c85bcc340fa032" - integrity sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg== + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== object-visit@^1.0.0: version "1.0.1" @@ -5635,9 +5582,9 @@ p-finally@^1.0.0: integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= p-is-promise@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.0.0.tgz#7554e3d572109a87e1f3f53f6a7d85d1b194f4c5" - integrity sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg== + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" + integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== p-limit@^1.1.0: version "1.3.0" @@ -5673,9 +5620,9 @@ p-try@^1.0.0: integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= p-try@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" - integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ== + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== pako@^1.0.5, pako@~1.0.5: version "1.0.10" @@ -5692,9 +5639,9 @@ parallel-transform@^1.1.0: readable-stream "^2.1.5" parent-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.0.tgz#df250bdc5391f4a085fb589dad761f5ad6b865b5" - integrity sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA== + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" @@ -5760,9 +5707,9 @@ parseuri@0.0.5: better-assert "~1.0.0" parseurl@~1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" - integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M= + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== pascalcase@^0.1.1: version "0.1.1" @@ -5989,12 +5936,12 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= -pretty-format@^24.3.1: - version "24.3.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.3.1.tgz#ae4a98e93d73d86913a8a7dd1a7c3c900f8fda59" - integrity sha512-NZGH1NWS6o4i9pvRWLsxIK00JB9pqOUzVrO7yWT6vjI2thdxwvxefBJO6O5T24UAhI8P5dMceZ7x5wphgVI7Mg== +pretty-format@^24.7.0: + version "24.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.7.0.tgz#d23106bc2edcd776079c2daa5da02bcb12ed0c10" + integrity sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA== dependencies: - "@jest/types" "^24.3.0" + "@jest/types" "^24.7.0" ansi-regex "^4.0.0" ansi-styles "^3.2.0" react-is "^16.8.4" @@ -6031,7 +5978,7 @@ promise@^7.0.3, promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2: +prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -6124,16 +6071,16 @@ qrcode-react@^0.1.16: dependencies: qr.js "0.0.0" -qs@6.5.2, qs@~6.5.2: +qs@6.7.0, qs@^6.5.2, qs@^6.6.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + +qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -qs@^6.5.2, qs@^6.6.0: - version "6.6.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.6.0.tgz#a99c0f69a8d26bf7ef012f871cdabb0aee4424c2" - integrity sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA== - querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -6190,14 +6137,14 @@ range-parser@^1.0.3, range-parser@^1.2.0: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= -raw-body@2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" - integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw== +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== dependencies: - bytes "3.0.0" - http-errors "1.6.3" - iconv-lite "0.4.23" + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" unpipe "1.0.0" rc@^1.2.7: @@ -6247,14 +6194,14 @@ react-dom@^15.6.0, react-dom@^15.6.1: prop-types "^15.5.10" react-dom@^16.4.2: - version "16.8.4" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.4.tgz#1061a8e01a2b3b0c8160037441c3bf00a0e3bc48" - integrity sha512-Ob2wK7XG2tUDt7ps7LtLzGYYB6DXMCLj0G5fO6WeEICtT4/HdpOi7W/xLzZnR6RCG1tYza60nMdqtxzA8FaPJQ== + version "16.8.6" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.6.tgz#71d6303f631e8b0097f56165ef608f051ff6e10f" + integrity sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.13.4" + scheduler "^0.13.6" "react-gemini-scrollbar@github:matrix-org/react-gemini-scrollbar#5e97aef": version "2.1.5" @@ -6268,9 +6215,9 @@ react-immutable-proptypes@^2.1.0: integrity sha1-Aj1vObsVyXwHHp5g0A0TbqxfoLQ= react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: - version "16.8.4" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.4.tgz#90f336a68c3a29a096a3d648ab80e87ec61482a2" - integrity sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA== + version "16.8.6" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" + integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== react-lifecycles-compat@^3.0.0: version "3.0.4" @@ -6311,14 +6258,14 @@ react@^15.6.0, react@^15.6.1: prop-types "^15.5.10" react@^16.4.2: - version "16.8.4" - resolved "https://registry.yarnpkg.com/react/-/react-16.8.4.tgz#fdf7bd9ae53f03a9c4cd1a371432c206be1c4768" - integrity sha512-0GQ6gFXfUH7aZcjGVymlPOASTuSjlQL4ZtVC5YKH+3JL6bBLCVO21DknzmaPlI90LN253ojj02nsapy+j7wIjg== + version "16.8.6" + resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" + integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.13.4" + scheduler "^0.13.6" read-pkg-up@^3.0.0: version "3.0.0" @@ -6360,9 +6307,9 @@ read-pkg@^4.0.1: util-deprecate "~1.0.1" readable-stream@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d" - integrity sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw== + version "3.3.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.3.0.tgz#cb8011aad002eb717bf040291feba8569c986fb9" + integrity sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" @@ -6415,10 +6362,10 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regenerator-runtime@^0.12.0: - version "0.12.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" - integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== +regenerator-runtime@^0.13.2: + version "0.13.2" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447" + integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA== regenerator-transform@^0.10.0: version "0.10.1" @@ -6633,10 +6580,10 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.9.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" - integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.3.2: + version "1.10.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.1.tgz#664842ac960795bbe758221cdccda61fb64b5f18" + integrity sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA== dependencies: path-parse "^1.0.6" @@ -6688,9 +6635,9 @@ run-queue@^1.0.0, run-queue@^1.0.3: aproba "^1.1.1" rxjs@^6.3.3, rxjs@^6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504" - integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw== + version "6.5.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.1.tgz#f7a005a9386361921b8524f38f54cbf80e5d08f4" + integrity sha512-y0j31WJc83wPu31vS1VlAFW5JGrnGC+j+TtGAa1fRQphy48+fDYiDmX8tjGloToEsMkxnouOg/1IzXGKkJnZMg== dependencies: tslib "^1.9.0" @@ -6717,9 +6664,9 @@ samsam@1.3.0: integrity sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg== sanitize-html@^1.18.4: - version "1.20.0" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.20.0.tgz#9a602beb1c9faf960fb31f9890f61911cc4d9156" - integrity sha512-BpxXkBoAG+uKCHjoXFmox6kCSYpnulABoGcZ/R3QyY9ndXbIM5S94eOr1IqnzTG8TnbmXaxWoDDzKC5eJv7fEQ== + version "1.20.1" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.20.1.tgz#f6effdf55dd398807171215a62bfc21811bacf85" + integrity sha512-txnH8TQjaQvg2Q0HY06G6CDJLVYCpbnxrdO0WN8gjCKaU5J0KbyGYhZxx5QJg3WLZ1lB7XU9kDkfrCXUozqptA== dependencies: chalk "^2.4.1" htmlparser2 "^3.10.0" @@ -6737,10 +6684,10 @@ sax@^1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -scheduler@^0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.4.tgz#8fef05e7a3580c76c0364d2df5e550e4c9140298" - integrity sha512-cvSOlRPxOHs5dAhP9yiS/6IDmVAVxmk33f0CtTJRkmUWcb1Us+t7b1wqdzoC0REw2muC9V5f1L/w5R5uKGaepA== +scheduler@^0.13.6: + version "0.13.6" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" + integrity sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -6759,20 +6706,15 @@ selection-is-backward@^1.0.0: resolved "https://registry.yarnpkg.com/selection-is-backward/-/selection-is-backward-1.0.0.tgz#97a54633188a511aba6419fc5c1fa91b467e6be1" integrity sha1-l6VGMxiKURq6ZBn8XB+pG0Z+a+E= -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.5.1: - version "5.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" - integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== - -semver@^5.4.1: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.7.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== serialize-javascript@^1.4.0: - version "1.6.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879" - integrity sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw== + version "1.7.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65" + integrity sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA== set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" @@ -6804,10 +6746,10 @@ setimmediate@^1.0.4, setimmediate@^1.0.5: resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.11" @@ -6858,9 +6800,9 @@ slash@^2.0.0: integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== slate-base64-serializer@^0.2.69: - version "0.2.97" - resolved "https://registry.yarnpkg.com/slate-base64-serializer/-/slate-base64-serializer-0.2.97.tgz#232220da1320cdc918369834940f1c997b1e80f9" - integrity sha512-f8TnU8rPz8qzKsGaJJwEzPoR5JQ55kB03nO/aIeXRVg4PPY/Rp9VaEcBP6vmeimH51gf3vpe8S5RUITbW9pXHw== + version "0.2.102" + resolved "https://registry.yarnpkg.com/slate-base64-serializer/-/slate-base64-serializer-0.2.102.tgz#05cdb9149172944b55c8d0a0d14b4499a1c3b5a2" + integrity sha512-44BI/jEbSMDNjEpOd92dVtpKyoFEaQtS3YgDoD30gHsvJvuzdRd/EQjp01BAUhDLLgDINi9qIHZS3AkpqKOtow== dependencies: isomorphic-base64 "^1.0.2" @@ -6902,9 +6844,9 @@ slate-html-serializer@^0.6.1: resolved "https://codeload.github.com/matrix-org/slate-md-serializer/tar.gz/f7c4ad394f5af34d4c623de7909ce95ab78072d3" slate-plain-serializer@^0.6.8: - version "0.6.36" - resolved "https://registry.yarnpkg.com/slate-plain-serializer/-/slate-plain-serializer-0.6.36.tgz#19ac5c66f972f068654b226c9a628bdc29475764" - integrity sha512-StVNj0wyM9KNtjRPYsFxNVrkeKtILwRy0QHr33Ln/SH05mAF6Of7MkmeC7GrqrN99J01ntz0pA2VLewq8ErhDQ== + version "0.6.39" + resolved "https://registry.yarnpkg.com/slate-plain-serializer/-/slate-plain-serializer-0.6.39.tgz#5fb8d4dc530a2e7e0689548d48964ce242c4516a" + integrity sha512-EGl+Y+9Fw9IULtPg8sttydaeiAoaibJolMXNfqI79+5GWTQwJFIbg24keKvsTw+3f2RieaPu8fcrKyujKtZ7ZQ== slate-prop-types@^0.4.67: version "0.4.67" @@ -7061,9 +7003,9 @@ source-map-support@^0.4.15: source-map "^0.5.6" source-map-support@~0.5.10: - version "0.5.10" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c" - integrity sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ== + version "0.5.12" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" + integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -7110,9 +7052,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e" - integrity sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g== + version "3.0.4" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz#75ecd1a88de8c184ef015eafb51b5b48bfd11bb1" + integrity sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA== specificity@^0.4.1: version "0.4.1" @@ -7184,7 +7126,7 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.4.0 < 2": +"statuses@>= 1.5.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= @@ -7226,10 +7168,10 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= -streamroller@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-1.0.3.tgz#cb51e7e382f799a9381a5d7490ce3053b325fba3" - integrity sha512-P7z9NwP51EltdZ81otaGAN3ob+/F88USJE546joNq7bqRNTe6jc74fTBDyynxP4qpIfKlt/CesEYicuMzI0yJg== +streamroller@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-1.0.4.tgz#d485c7624796d5e2eb34190c79afcbf006afb5e6" + integrity sha512-Wc2Gm5ygjSX8ZpW9J7Y9FwiSzTlKSvcl0FTTMd3rn7RoxDXpBW+xD9TY5sWL2n0UR61COB0LG1BQvN6nTUQbLQ== dependencies: async "^2.6.1" date-format "^2.0.0" @@ -7306,10 +7248,10 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0, strip-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.1.0.tgz#55aaa54e33b4c0649a7338a43437b1887d153ec4" - integrity sha512-TjxrkPONqO2Z8QDCpeE2j6n0M6EwxzyDgzEeGp+FbdvaJAt//ClYi6W5my+3ROlC/hZX2KACUwDfK49Ka5eDvg== +strip-ansi@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== dependencies: ansi-regex "^4.1.0" @@ -7338,17 +7280,17 @@ style-search@^0.1.0: resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI= -stylelint-config-recommended@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-2.1.0.tgz#f526d5c771c6811186d9eaedbed02195fee30858" - integrity sha512-ajMbivOD7JxdsnlS5945KYhvt7L/HwN6YeYF2BH6kE4UCLJR0YvXMf+2j7nQpJyYLZx9uZzU5G1ZOSBiWAc6yA== +stylelint-config-recommended@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-2.2.0.tgz#46ab139db4a0e7151fd5f94af155512886c96d3f" + integrity sha512-bZ+d4RiNEfmoR74KZtCKmsABdBJr4iXRiCso+6LtMJPw5rd/KnxUWTxht7TbafrTJK1YRjNgnN0iVZaJfc3xJA== stylelint-config-standard@^18.2.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-18.2.0.tgz#6283149aba7f64f18731aef8f0abfb35cf619e06" - integrity sha512-07x0TaSIzvXlbOioUU4ORkCIM07kyIuojkbSVCyFWNVgXMXYHfhnQSCkqu+oHWJf3YADAnPGWzdJ53NxkoJ7RA== + version "18.3.0" + resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-18.3.0.tgz#a2a1b788d2cf876c013feaff8ae276117a1befa7" + integrity sha512-Tdc/TFeddjjy64LvjPau9SsfVRexmTFqUhnMBrzz07J4p2dVQtmpncRF/o8yZn8ugA3Ut43E6o1GtjX80TFytw== dependencies: - stylelint-config-recommended "^2.1.0" + stylelint-config-recommended "^2.2.0" stylelint@^9.10.1: version "9.10.1" @@ -7469,9 +7411,9 @@ table@^5.0.0, table@^5.2.3: string-width "^3.0.0" tapable@^1.0.0, tapable@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.1.tgz#4d297923c5a72a42360de2ab52dadfaaec00018e" - integrity sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA== + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== tar@^4: version "4.4.8" @@ -7596,6 +7538,11 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" @@ -7668,13 +7615,13 @@ type-detect@4.0.8, type-detect@^4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-is@~1.6.16: - version "1.6.16" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" - integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q== +type-is@~1.6.17: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== dependencies: media-typer "0.3.0" - mime-types "~2.1.18" + mime-types "~2.1.24" type-of@^2.0.1: version "2.0.1" @@ -7808,7 +7755,7 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -upath@^1.1.0: +upath@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q== @@ -7965,9 +7912,9 @@ watchpack@^1.5.0: neo-async "^2.5.0" webpack-cli@^3.1.1: - version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.2.3.tgz#13653549adfd8ccd920ad7be1ef868bacc22e346" - integrity sha512-Ik3SjV6uJtWIAN5jp5ZuBMWEAaP5E4V78XJ2nI+paFPh8v4HPSwo/myN0r29Xc/6ZKnd2IdrAlpSgNOu2CDQ6Q== + version "3.3.1" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.1.tgz#98b0499c7138ba9ece8898bd99c4f007db59909d" + integrity sha512-c2inFU7SM0IttEgF7fK6AaUsbBnORRzminvbyRKS+NlbQHVZdCtzKBlavRL5359bFsywXGRAItA5di/IruC8mg== dependencies: chalk "^2.4.1" cross-spawn "^6.0.5" @@ -7979,12 +7926,12 @@ webpack-cli@^3.1.1: loader-utils "^1.1.0" supports-color "^5.5.0" v8-compile-cache "^2.0.2" - yargs "^12.0.4" + yargs "^12.0.5" webpack-dev-middleware@^3.2.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.6.1.tgz#91f2531218a633a99189f7de36045a331a4b9cd4" - integrity sha512-XQmemun8QJexMEvNFbD2BIg4eSKrmSIMrTfnl2nql2Sc6OGAYFyb8rwuYrCjl/IiEYYuyTEiimMscu7EXji/Dw== + version "3.6.2" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.6.2.tgz#f37a27ad7c09cd7dc67cd97655413abaa1f55942" + integrity sha512-A47I5SX60IkHrMmZUlB0ZKSWi29TZTcPz7cha1Z75yYOsgWh/1AcPmQEbC8ZIbU3A1ytSv1PMU0PyPz2Lmz2jg== dependencies: memory-fs "^0.4.1" mime "^2.3.1" @@ -8008,9 +7955,9 @@ webpack-sources@^1.1.0, webpack-sources@^1.3.0: source-map "~0.6.1" webpack@^4.20.2: - version "4.29.6" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.29.6.tgz#66bf0ec8beee4d469f8b598d3988ff9d8d90e955" - integrity sha512-MwBwpiE1BQpMDkbnUUaW6K8RFZjljJHArC6tWQJoFm0oQtfoSebtg4Y7/QHnJ/SddtjYLHaKGX64CFjG5rehJw== + version "4.30.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.30.0.tgz#aca76ef75630a22c49fcc235b39b4c57591d33a9" + integrity sha512-4hgvO2YbAFUhyTdlR4FNyt2+YaYBYHavyzjCMbZzgglo02rlKi/pcsEzwCuCpsn1ryzIl1cq/u8ArIKu8JBYMg== dependencies: "@webassemblyjs/ast" "1.8.5" "@webassemblyjs/helper-module-context" "1.8.5" @@ -8169,7 +8116,7 @@ yargs-parser@^11.1.1: camelcase "^5.0.0" decamelize "^1.2.0" -yargs@^12.0.1, yargs@^12.0.4: +yargs@^12.0.1, yargs@^12.0.5: version "12.0.5" resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== From 4b9be2aec480a3552c1edbdd05f98b46ed2f23d8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 3 May 2019 10:39:13 +0100 Subject: [PATCH 53/64] Remove the karma junit reporter We may have used it in our jenkins tests at some point but we don't have those anymore. It weas pulling in ancient dependencies because we were using version 2.0.0 which is fact much older than the current version (1.2.0). We have little use for junit output anymore so just remove it. --- karma.conf.js | 6 +----- package.json | 1 - yarn.lock | 20 -------------------- 3 files changed, 1 insertion(+), 26 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index b687be78fa..e2728cdc09 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -94,7 +94,7 @@ module.exports = function (config) { // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['logcapture', 'spec', 'junit', 'summary'], + reporters: ['logcapture', 'spec', 'summary'], specReporter: { suppressErrorSummary: false, // do print error summary @@ -156,10 +156,6 @@ module.exports = function (config) { // how many browser should be started simultaneous concurrency: Infinity, - junitReporter: { - outputDir: 'karma-reports', - }, - webpack: { module: { rules: [ diff --git a/package.json b/package.json index d844566f3a..a58cd30129 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,6 @@ "karma": "^4.0.1", "karma-chrome-launcher": "^2.2.0", "karma-cli": "^1.0.1", - "karma-junit-reporter": "^2.0.0", "karma-logcapture-reporter": "0.0.1", "karma-mocha": "^1.3.0", "karma-sourcemap-loader": "^0.3.7", diff --git a/yarn.lock b/yarn.lock index e471fcd974..af069feffc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4528,14 +4528,6 @@ karma-cli@^1.0.1: dependencies: resolve "^1.1.6" -karma-junit-reporter@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/karma-junit-reporter/-/karma-junit-reporter-2.0.0.tgz#f84629e0e1ef28dd2977c96f346c33d5bf93e159" - integrity sha1-+EYp4OHvKN0pd8lvNGwz1b+T4Vk= - dependencies: - path-is-absolute "^1.0.0" - xmlbuilder "3.1.0" - karma-logcapture-reporter@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/karma-logcapture-reporter/-/karma-logcapture-reporter-0.0.1.tgz#bf1b0b1c915e0de295a15fe2f0179d4281bacddc" @@ -4745,11 +4737,6 @@ lodash.mergewith@^4.6.1: resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" integrity sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ== -lodash@^3.5.0: - version "3.10.1" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" - integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y= - lodash@^4.1.1, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.2.1: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" @@ -8069,13 +8056,6 @@ x-is-string@^0.1.0: resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI= -xmlbuilder@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-3.1.0.tgz#2c86888f2d4eade850fa38ca7f7223f7209516e1" - integrity sha1-LIaIjy1OrehQ+jjKf3Ij9yCVFuE= - dependencies: - lodash "^3.5.0" - xmlhttprequest-ssl@~1.5.4: version "1.5.5" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" From 75e2001860074bafaaa76312c7a15271e2da0a0a Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 3 May 2019 10:49:08 +0100 Subject: [PATCH 54/64] don't need to ignore this anymore either --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index 7a3b1061b0..33e8bfc7ac 100644 --- a/.gitignore +++ b/.gitignore @@ -9,9 +9,6 @@ package-lock.json /git-revision.txt /matrix-react-sdk-*.tgz -# test reports created by karma -/karma-reports - /.idea /src/component-index.js From df4e6a391303fbf9dd36f1ac8a6ad34cd0f7ae95 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 3 May 2019 17:52:27 +0100 Subject: [PATCH 55/64] Check for `room` in all `Room.timeline*` handlers All `Room.timeline*` handlers must currently test for `room` first if they expect it to exist. It is emitted not only for rooms, but also for timeline sets without rooms, such as for notifications. Almost all such handlers were correctly testing as needed, but it was missing from `RoomBreadcrumbs`. While that's quite confusing, we can start by testing for `room` when we expect to have one. Fixes https://github.com/vector-im/riot-web/issues/9630 --- src/components/views/rooms/RoomBreadcrumbs.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/RoomBreadcrumbs.js b/src/components/views/rooms/RoomBreadcrumbs.js index fff1fa7f3c..3e874c172e 100644 --- a/src/components/views/rooms/RoomBreadcrumbs.js +++ b/src/components/views/rooms/RoomBreadcrumbs.js @@ -117,6 +117,7 @@ export default class RoomBreadcrumbs extends React.Component { }; onRoomTimeline = (event, room) => { + if (!room) return; // Can be null for the notification timeline, etc. if (this.state.rooms.map(r => r.room.roomId).includes(room.roomId)) { this._calculateRoomBadges(room); } From 35ad68751b34bc5e2b5f2c179a4c459dbb0401df Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 3 May 2019 11:13:36 -0600 Subject: [PATCH 56/64] Remove unused skipServerDetails prop from registration --- src/components/structures/MatrixChat.js | 12 ------------ src/components/structures/auth/Registration.js | 1 - 2 files changed, 13 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index bf2e7beb16..277985ba1d 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -249,17 +249,6 @@ export default React.createClass({ return this.state.defaultIsUrl || "https://vector.im"; }, - /** - * Whether to skip the server details phase of registration and start at the - * actual form. - * @return {boolean} - * If there was a configured default HS or default server name, skip the - * the server details. - */ - skipServerDetailsForRegistration() { - return !!this.state.defaultHsUrl; - }, - componentWillMount: function() { SdkConfig.put(this.props.config); @@ -1973,7 +1962,6 @@ export default React.createClass({ defaultServerDiscoveryError={this.state.defaultServerDiscoveryError} defaultHsUrl={this.getDefaultHsUrl()} defaultIsUrl={this.getDefaultIsUrl()} - skipServerDetails={this.skipServerDetailsForRegistration()} brand={this.props.config.brand} customHsUrl={this.getCurrentHsUrl()} customIsUrl={this.getCurrentIsUrl()} diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 78346170be..708118bb22 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -58,7 +58,6 @@ module.exports = React.createClass({ customIsUrl: PropTypes.string, defaultHsUrl: PropTypes.string, defaultIsUrl: PropTypes.string, - skipServerDetails: PropTypes.bool, brand: PropTypes.string, email: PropTypes.string, // registration shouldn't know or care how login is done. From c37ecb7a915db981a733de8b4bbd157d9e16b27a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 3 May 2019 17:52:27 +0100 Subject: [PATCH 57/64] Check for `room` in all `Room.timeline*` handlers All `Room.timeline*` handlers must currently test for `room` first if they expect it to exist. It is emitted not only for rooms, but also for timeline sets without rooms, such as for notifications. Almost all such handlers were correctly testing as needed, but it was missing from `RoomBreadcrumbs`. While that's quite confusing, we can start by testing for `room` when we expect to have one. Fixes https://github.com/vector-im/riot-web/issues/9630 --- src/components/views/rooms/RoomBreadcrumbs.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/RoomBreadcrumbs.js b/src/components/views/rooms/RoomBreadcrumbs.js index fff1fa7f3c..3e874c172e 100644 --- a/src/components/views/rooms/RoomBreadcrumbs.js +++ b/src/components/views/rooms/RoomBreadcrumbs.js @@ -117,6 +117,7 @@ export default class RoomBreadcrumbs extends React.Component { }; onRoomTimeline = (event, room) => { + if (!room) return; // Can be null for the notification timeline, etc. if (this.state.rooms.map(r => r.room.roomId).includes(room.roomId)) { this._calculateRoomBadges(room); } From 94a7afa35b334532bcff3948f3e4760311477e16 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 7 May 2019 12:06:50 +0100 Subject: [PATCH 58/64] Restore access to message quote option on first click This repairs access to the "Quote" option of the message context menu by passing down a getter so that we always access the most recent tile and reply thread instances. This ensures the context menu uses the newest information about the current event when determining menu options to show. Fixes https://github.com/vector-im/riot-web/issues/9639 --- src/components/views/messages/MessageActionBar.js | 8 +++++--- src/components/views/rooms/EventTile.js | 12 ++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index fa020612c4..24a746befd 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -28,8 +28,8 @@ export default class MessageActionBar extends React.PureComponent { static propTypes = { mxEvent: PropTypes.object.isRequired, permalinkCreator: PropTypes.object, - tile: PropTypes.element, - replyThread: PropTypes.element, + getTile: PropTypes.func, + getReplyThread: PropTypes.func, onFocusChange: PropTypes.func, }; @@ -63,7 +63,9 @@ export default class MessageActionBar extends React.PureComponent { const x = buttonRect.right + window.pageXOffset; const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19; - const {tile, replyThread} = this.props; + const { getTile, getReplyThread } = this.props; + const tile = getTile && getTile(); + const replyThread = getReplyThread && getReplyThread(); let e2eInfoCallback = null; if (this.props.mxEvent.isEncrypted()) { diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index dd0a7aa47b..b1e929dc16 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -464,6 +464,14 @@ module.exports = withMatrixClient(React.createClass({ }); }, + getTile() { + return this.refs.tile; + }, + + getReplyThread() { + return this.refs.replyThread; + }, + render: function() { const MessageTimestamp = sdk.getComponent('messages.MessageTimestamp'); const SenderProfile = sdk.getComponent('messages.SenderProfile'); @@ -580,8 +588,8 @@ module.exports = withMatrixClient(React.createClass({ const actionBar = ; From 74803c8ae1d730e3ee767f66ba60766d37e793f4 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 7 May 2019 12:35:42 +0100 Subject: [PATCH 59/64] Relax password requirements to score of 3 out of 4 This makes it a bit easier to meet the requirements while still requiring a fairly strong value. The progress bar displays a score of 3 as reaching 100% for simplicity. Fixes https://github.com/vector-im/riot-web/issues/9642 --- src/components/views/auth/RegistrationForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 6e55581af0..7c083ea270 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -33,7 +33,7 @@ const FIELD_USERNAME = 'field_username'; const FIELD_PASSWORD = 'field_password'; const FIELD_PASSWORD_CONFIRM = 'field_password_confirm'; -const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc. +const PASSWORD_MIN_SCORE = 3; // safely unguessable: moderate protection from offline slow-hash scenario. /** * A pure UI component which displays a registration form. From 0316deb6c58fbcaddfe2b0f9a3a3db963fbe2112 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 7 May 2019 12:06:50 +0100 Subject: [PATCH 60/64] Restore access to message quote option on first click This repairs access to the "Quote" option of the message context menu by passing down a getter so that we always access the most recent tile and reply thread instances. This ensures the context menu uses the newest information about the current event when determining menu options to show. Fixes https://github.com/vector-im/riot-web/issues/9639 --- src/components/views/messages/MessageActionBar.js | 8 +++++--- src/components/views/rooms/EventTile.js | 12 ++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 76bae137f5..74eb4165e9 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -30,8 +30,8 @@ export default class MessageActionBar extends React.PureComponent { static propTypes = { mxEvent: PropTypes.object.isRequired, permalinkCreator: PropTypes.object, - tile: PropTypes.element, - replyThread: PropTypes.element, + getTile: PropTypes.func, + getReplyThread: PropTypes.func, onFocusChange: PropTypes.func, }; @@ -99,7 +99,9 @@ export default class MessageActionBar extends React.PureComponent { const x = buttonRect.right + window.pageXOffset; const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19; - const {tile, replyThread} = this.props; + const { getTile, getReplyThread } = this.props; + const tile = getTile && getTile(); + const replyThread = getReplyThread && getReplyThread(); let e2eInfoCallback = null; if (this.props.mxEvent.isEncrypted()) { diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 6bec3f4fff..59025bf431 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -464,6 +464,14 @@ module.exports = withMatrixClient(React.createClass({ }); }, + getTile() { + return this.refs.tile; + }, + + getReplyThread() { + return this.refs.replyThread; + }, + render: function() { const MessageTimestamp = sdk.getComponent('messages.MessageTimestamp'); const SenderProfile = sdk.getComponent('messages.SenderProfile'); @@ -580,8 +588,8 @@ module.exports = withMatrixClient(React.createClass({ const actionBar = ; From 2b2bfbeaafde55211badddb81119b430a92b1b99 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 7 May 2019 12:35:42 +0100 Subject: [PATCH 61/64] Relax password requirements to score of 3 out of 4 This makes it a bit easier to meet the requirements while still requiring a fairly strong value. The progress bar displays a score of 3 as reaching 100% for simplicity. Fixes https://github.com/vector-im/riot-web/issues/9642 --- src/components/views/auth/RegistrationForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 6e55581af0..7c083ea270 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -33,7 +33,7 @@ const FIELD_USERNAME = 'field_username'; const FIELD_PASSWORD = 'field_password'; const FIELD_PASSWORD_CONFIRM = 'field_password_confirm'; -const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc. +const PASSWORD_MIN_SCORE = 3; // safely unguessable: moderate protection from offline slow-hash scenario. /** * A pure UI component which displays a registration form. From aec1e98086d9b16a206dfd8c7055cb91fbeb3790 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 7 May 2019 15:30:21 +0100 Subject: [PATCH 62/64] Released js-sdk --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3fb9514cd8..376e88e10e 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "linkifyjs": "^2.1.6", "lodash": "^4.13.1", "lolex": "2.3.2", - "matrix-js-sdk": "^1.1.0-rc.1", + "matrix-js-sdk": "1.1.0", "optimist": "^0.6.1", "pako": "^1.0.5", "png-chunks-extract": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index 37df3b83be..b47d8c011e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4950,10 +4950,10 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.0.tgz#490b70e062ee24636536e3d9481e333733d00f2c" integrity sha512-3Zs9P/0zzwTob2pdgT0CHZuMbnSUSp8MB1bddfm+HDmnFWHGT4jvEZRf+2RuPoa+cjdn/z25SEt5gFTqdhvJAg== -matrix-js-sdk@^1.1.0-rc.1: - version "1.1.0-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-1.1.0-rc.1.tgz#11a9e9215e766274d99f44eff0a8c4d33d34f870" - integrity sha512-I87gvaMKKmFGDU8q4YUiEP0RVr0ni+v64TYaqllKV2zMGgl2serz+O9pvgfyGgzwFUQ77nPG7NGH4ku+S5I2LA== +matrix-js-sdk@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-1.1.0.tgz#f0db49de2aa0b0d50c1fe49da538beb4926ce59c" + integrity sha512-ECoMN6DkwPdKiMa/jSoMkSDngFCo6x7oH84rLd1NtD7lBPl3Ejj6ARa0iIELE7u0OUO6J0FzdWh7Hd0ZnVTmww== dependencies: another-json "^0.2.0" babel-runtime "^6.26.0" From e798177c7aeec4849870494cb7e562485e532495 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 7 May 2019 15:36:09 +0100 Subject: [PATCH 63/64] Prepare changelog for v1.1.0 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9141e56bab..4d9a01e668 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +Changes in [1.1.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.1.0) (2019-05-07) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.1.0-rc.1...v1.1.0) + + * Relax password requirements to score of 3 out of 4 + [\#2949](https://github.com/matrix-org/matrix-react-sdk/pull/2949) + * Restore access to message quote option on first click + [\#2948](https://github.com/matrix-org/matrix-react-sdk/pull/2948) + * Check for `room` in all `Room.timeline*` handlers + [\#2946](https://github.com/matrix-org/matrix-react-sdk/pull/2946) + Changes in [1.1.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.1.0-rc.1) (2019-04-30) ============================================================================================================= [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.0.7...v1.1.0-rc.1) From 3b03e23ace490cfe96fdcde3c0f6a1c5d85b894c Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 7 May 2019 15:36:10 +0100 Subject: [PATCH 64/64] v1.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 376e88e10e..5735fed77c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "1.1.0-rc.1", + "version": "1.1.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": {