From 157a3388a509c02742c084f150a2a4c78fe0145e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Sep 2019 17:39:46 +0200 Subject: [PATCH 01/36] change name to Field, no_federate to switch also construct room create options in dialog, instead of MatrixChat, as we'll have more to come --- src/components/structures/MatrixChat.js | 5 +- .../views/dialogs/CreateRoomDialog.js | 88 ++++++++++++++----- 2 files changed, 66 insertions(+), 27 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 306ef03fb1..774ef21aff 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -962,11 +962,8 @@ export default createReactClass({ const CreateRoomDialog = sdk.getComponent('dialogs.CreateRoomDialog'); const modal = Modal.createTrackedDialog('Create Room', '', CreateRoomDialog); - const [shouldCreate, name, noFederate] = await modal.finished; + const [shouldCreate, createOpts] = await modal.finished; if (shouldCreate) { - const createOpts = {}; - if (name) createOpts.name = name; - if (noFederate) createOpts.creation_content = {'m.federate': false}; createRoom({createOpts}).done(); } }, diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index e1da9f841d..3ae328839e 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -19,6 +19,7 @@ import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import sdk from '../../../index'; import SdkConfig from '../../../SdkConfig'; +import withValidation from '../elements/Validation'; import { _t } from '../../../languageHandler'; export default createReactClass({ @@ -27,47 +28,88 @@ export default createReactClass({ onFinished: PropTypes.func.isRequired, }, - componentWillMount: function() { + getInitialState() { const config = SdkConfig.get(); - // Dialog shows inverse of m.federate (noFederate) strict false check to skip undefined check (default = true) - this.defaultNoFederate = config.default_federate === false; + return { + name: "", + noFederate: config.default_federate === false, + nameIsValid: false, + }; }, - onOk: function() { - this.props.onFinished(true, this.refs.textinput.value, this.refs.checkbox.checked); + _roomCreateOptions() { + const createOpts = {}; + createOpts.name = this.state.name; + if (this.state.noFederate) { + createOpts.creation_content = {'m.federate': false}; + } + return createOpts; + }, + + componentDidMount() { + this._detailsRef.addEventListener("toggle", this.onDetailsToggled); + }, + + componentWillUnmount() { + this._detailsRef.removeEventListener("toggle", this.onDetailsToggled); + }, + + onOk: async function() { + this.props.onFinished(true, this._roomCreateOptions()); }, onCancel: function() { this.props.onFinished(false); }, + onNameChange(ev) { + this.setState({name: ev.target.value}); + }, + + + onDetailsToggled(ev) { + this.setState({detailsOpen: ev.target.open}); + }, + + onNoFederateChange(noFederate) { + this.setState({noFederate}); + }, + + collectDetailsRef(ref) { + this._detailsRef = ref; + }, + + async onNameValidate(fieldState) { + const result = await this._validateRoomName(fieldState); + this.setState({nameIsValid: result.valid}); + return result; + }, + + _validateRoomName: withValidation({ + rules: [ + { + key: "required", + test: async ({ value }) => !!value, + invalid: () => _t("Please enter a name for the room"), + }, + ], + }), + render: function() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + const Field = sdk.getComponent('views.elements.Field'); + const LabelledToggleSwitch = sdk.getComponent('views.elements.LabelledToggleSwitch'); return (
-
- -
-
- -
-
- -
- { _t('Advanced options') } -
- - -
+ this._nameFieldRef = ref} label={ _t('Name') } onChange={this.onNameChange} onValidate={this.onNameValidate} value={this.state.name} className="mx_CreateRoomDialog_name" /> +
+ { this.state.detailsOpen ? _t('Hide advanced') : _t('Show advanced') } +
From 4a7ae3ca8e5570dbc00f68640a300420d9fb72b2 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Sep 2019 17:41:03 +0200 Subject: [PATCH 02/36] add optional topic field --- src/components/views/dialogs/CreateRoomDialog.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index 3ae328839e..e31d7304ea 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -32,6 +32,7 @@ export default createReactClass({ const config = SdkConfig.get(); return { name: "", + topic: "", noFederate: config.default_federate === false, nameIsValid: false, }; @@ -40,6 +41,9 @@ export default createReactClass({ _roomCreateOptions() { const createOpts = {}; createOpts.name = this.state.name; + if (this.state.topic) { + createOpts.topic = this.state.topic; + } if (this.state.noFederate) { createOpts.creation_content = {'m.federate': false}; } @@ -66,6 +70,10 @@ export default createReactClass({ this.setState({name: ev.target.value}); }, + onTopicChange(ev) { + this.setState({topic: ev.target.value}); + }, + onDetailsToggled(ev) { this.setState({detailsOpen: ev.target.open}); @@ -107,6 +115,7 @@ export default createReactClass({
this._nameFieldRef = ref} label={ _t('Name') } onChange={this.onNameChange} onValidate={this.onNameValidate} value={this.state.name} className="mx_CreateRoomDialog_name" /> +
{ this.state.detailsOpen ? _t('Hide advanced') : _t('Show advanced') } From 761233c473efef1259e6c8c5c969d4d609877024 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Sep 2019 17:43:14 +0200 Subject: [PATCH 03/36] add public switch --- .../views/dialogs/CreateRoomDialog.js | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index e31d7304ea..10415680a7 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -31,6 +31,7 @@ export default createReactClass({ getInitialState() { const config = SdkConfig.get(); return { + isPublic: false, name: "", topic: "", noFederate: config.default_federate === false, @@ -41,6 +42,11 @@ export default createReactClass({ _roomCreateOptions() { const createOpts = {}; createOpts.name = this.state.name; + if (this.state.isPublic) { + createOpts.visibility = "public"; + createOpts.preset = "public_chat"; + // to prevent createRoom from enabling guest access + createOpts['initial_state'] = []; if (this.state.topic) { createOpts.topic = this.state.topic; } @@ -74,6 +80,10 @@ export default createReactClass({ this.setState({topic: ev.target.value}); }, + onPublicChange(isPublic) { + this.setState({isPublic}); + }, + onDetailsToggled(ev) { this.setState({detailsOpen: ev.target.open}); @@ -108,14 +118,26 @@ export default createReactClass({ const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const Field = sdk.getComponent('views.elements.Field'); const LabelledToggleSwitch = sdk.getComponent('views.elements.LabelledToggleSwitch'); + let privateLabel; + let publicLabel; + if (this.state.isPublic) { + publicLabel = (

{_t("Set a room alias to easily share your room with other people.")}

); + } else { + privateLabel = (

{_t("This room is private, and can only be joined by invitation.")}

); + } + + const title = this.state.isPublic ? _t('Create a public room') : _t('Create a private room'); return (
this._nameFieldRef = ref} label={ _t('Name') } onChange={this.onNameChange} onValidate={this.onNameValidate} value={this.state.name} className="mx_CreateRoomDialog_name" /> + + { privateLabel } + { publicLabel }
{ this.state.detailsOpen ? _t('Hide advanced') : _t('Show advanced') } From c5f9ef87baa1362293c66e069d7b0662c66e7ad5 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Sep 2019 17:44:07 +0200 Subject: [PATCH 04/36] fixup: detailsOpen state var --- src/components/views/dialogs/CreateRoomDialog.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index 10415680a7..b462230c24 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -34,6 +34,7 @@ export default createReactClass({ isPublic: false, name: "", topic: "", + detailsOpen: false, noFederate: config.default_federate === false, nameIsValid: false, }; From 8a1c1bbec4a1cf192b29f88da195b1a69a46b62e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Sep 2019 17:45:14 +0200 Subject: [PATCH 05/36] implement RoomAliasField component adding a postfix to Field to show the domain name --- res/css/_components.scss | 1 + res/css/views/elements/_Field.scss | 4 + res/css/views/elements/_RoomAliasField.scss | 56 ++++++++ src/components/views/elements/Field.js | 15 ++- .../views/elements/RoomAliasField.js | 125 ++++++++++++++++++ 5 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 res/css/views/elements/_RoomAliasField.scss create mode 100644 src/components/views/elements/RoomAliasField.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 213d0d714c..40a797dc15 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -99,6 +99,7 @@ @import "./views/elements/_ResizeHandle.scss"; @import "./views/elements/_RichText.scss"; @import "./views/elements/_RoleButton.scss"; +@import "./views/elements/_RoomAliasField.scss"; @import "./views/elements/_Spinner.scss"; @import "./views/elements/_SyntaxHighlight.scss"; @import "./views/elements/_TextWithTooltip.scss"; diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index 0e8252e89d..da896f947d 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -31,6 +31,10 @@ limitations under the License. border-right: 1px solid $input-border-color; } +.mx_Field_postfix { + border-left: 1px solid $input-border-color; +} + .mx_Field input, .mx_Field select, .mx_Field textarea { diff --git a/res/css/views/elements/_RoomAliasField.scss b/res/css/views/elements/_RoomAliasField.scss new file mode 100644 index 0000000000..0fe53b2766 --- /dev/null +++ b/res/css/views/elements/_RoomAliasField.scss @@ -0,0 +1,56 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_RoomAliasField { + // if parent is a flex container, this allows the + // width to be as wide as needed, and not 100% + flex: 0 1 auto; + display: flex; + align-items: stretch; + min-width: 0; + max-width: 100%; + + input { + width: 150px; + padding-left: 0; + padding-right: 0; + } + + input::placeholder { + color: $greyed-fg-color; + font-weight: normal; + } + + .mx_Field_prefix, .mx_Field_postfix { + color: $greyed-fg-color; + border-left: none; + border-right: none; + font-weight: 600; + padding: 9px 10px; + flex: 0 0 auto; + } + + .mx_Field_postfix { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + // this allows the domain name to show + // as long as it doesn't make the input shrink + // if it's too big, it shows an ellipsis + // 180: 28 for prefix, 152 for input + max-width: calc(100% - 180px); + } +} diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js index 08a578b963..0a737d963a 100644 --- a/src/components/views/elements/Field.js +++ b/src/components/views/elements/Field.js @@ -41,6 +41,8 @@ export default class Field extends React.PureComponent { value: PropTypes.string.isRequired, // Optional component to include inside the field before the input. prefix: PropTypes.node, + // Optional component to include inside the field after the input. + postfix: PropTypes.node, // The callback called whenever the contents of the field // changes. Returns an object with `valid` boolean field // and a `feedback` react component field to provide feedback @@ -54,6 +56,8 @@ export default class Field extends React.PureComponent { // If specified alongside tooltipContent, the class name to apply to the // tooltip itself. tooltipClassName: PropTypes.string, + // If specified, an additional class name to apply to the field container + className: PropTypes.string, // All other props pass through to the . }; @@ -143,8 +147,8 @@ export default class Field extends React.PureComponent { render() { const { - element, prefix, onValidate, children, tooltipContent, flagInvalid, - tooltipClassName, ...inputProps} = this.props; + element, prefix, postfix, className, onValidate, children, + tooltipContent, flagInvalid, tooltipClassName, ...inputProps} = this.props; const inputElement = element || "input"; @@ -163,9 +167,13 @@ export default class Field extends React.PureComponent { if (prefix) { prefixContainer = {prefix}; } + let postfixContainer = null; + if (postfix) { + postfixContainer = {postfix}; + } const hasValidationFlag = flagInvalid !== null && flagInvalid !== undefined; - const fieldClasses = classNames("mx_Field", `mx_Field_${inputElement}`, { + const fieldClasses = classNames("mx_Field", `mx_Field_${inputElement}`, className, { // If we have a prefix element, leave the label always at the top left and // don't animate it, as it looks a bit clunky and would add complexity to do // properly. @@ -192,6 +200,7 @@ export default class Field extends React.PureComponent { {prefixContainer} {fieldInput} + {postfixContainer} {fieldTooltip}
; } diff --git a/src/components/views/elements/RoomAliasField.js b/src/components/views/elements/RoomAliasField.js new file mode 100644 index 0000000000..03f4000e59 --- /dev/null +++ b/src/components/views/elements/RoomAliasField.js @@ -0,0 +1,125 @@ +/* +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 { _t } from '../../../languageHandler'; +import React from 'react'; +import PropTypes from 'prop-types'; +import sdk from '../../../index'; +import withValidation from './Validation'; +import MatrixClientPeg from '../../../MatrixClientPeg'; + +export default class RoomAliasField extends React.PureComponent { + static propTypes = { + id: PropTypes.string.isRequired, + domain: PropTypes.string.isRequired, + onChange: PropTypes.func, + }; + + constructor(props) { + super(props); + this.state = {isValid: true}; + } + + _asFullAlias(localpart) { + return `#${localpart}:${this.props.domain}`; + } + + render() { + const Field = sdk.getComponent('views.elements.Field'); + const poundSign = (#); + const aliasPostfix = ":" + this.props.domain; + const domain = ({aliasPostfix}); + const maxlength = 255 - this.props.domain.length - 2; // 2 for # and : + return ( + this._fieldRef = ref} + onValidate={this._onValidate} + placeholder={_t("e.g. my-room")} + onChange={this._onChange} + maxLength={maxlength} /> + ); + } + + _onChange = (ev) => { + if (this.props.onChange) { + this.props.onChange(this._asFullAlias(ev.target.value)); + } + } + + _onValidate = async (fieldState) => { + const result = await this._validationRules(fieldState); + this.setState({isValid: result.valid}); + return result; + }; + + _validationRules = withValidation({ + rules: [ + { + key: "safeLocalpart", + test: async ({ value }) => { + if (!value) { + return true; + } + const fullAlias = this._asFullAlias(value); + // XXX: FIXME https://github.com/matrix-org/matrix-doc/issues/668 + return !value.includes("#") && !value.includes(":") && !value.includes(",") && + encodeURI(fullAlias) === fullAlias; + }, + invalid: () => _t("Some characters not allowed"), + }, { + key: "required", + test: async ({ value, allowEmpty }) => allowEmpty || !!value, + invalid: () => _t("Please provide a room alias"), + }, { + key: "taken", + test: async ({value}) => { + if (!value) { + return true; + } + const client = MatrixClientPeg.get(); + try { + await client.getRoomIdForAlias(this._asFullAlias(value)); + // we got a room id, so the alias is taken + return false; + } catch (err) { + // any server error code will do, + // either it M_NOT_FOUND or the alias is invalid somehow, + // in which case we don't want to show the invalid message + return !!err.errcode; + } + }, + valid: () => _t("This alias is available to use"), + invalid: () => _t("This alias is already in use"), + }, + ], + }); + + get isValid() { + return this.state.isValid; + } + + validate(options) { + return this._fieldRef.validate(options); + } + + focus() { + this._fieldRef.focus(); + } +} From 6ae4b3e966c95b0edc2b8f0d89ab8f2887b7b28a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Sep 2019 17:46:14 +0200 Subject: [PATCH 06/36] add room alias field to dialog --- .../views/dialogs/CreateRoomDialog.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index b462230c24..501e6b6f80 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -21,6 +21,7 @@ import sdk from '../../../index'; import SdkConfig from '../../../SdkConfig'; import withValidation from '../elements/Validation'; import { _t } from '../../../languageHandler'; +import MatrixClientPeg from '../../../MatrixClientPeg'; export default createReactClass({ displayName: 'CreateRoomDialog', @@ -34,6 +35,7 @@ export default createReactClass({ isPublic: false, name: "", topic: "", + alias: "", detailsOpen: false, noFederate: config.default_federate === false, nameIsValid: false, @@ -48,6 +50,10 @@ export default createReactClass({ createOpts.preset = "public_chat"; // to prevent createRoom from enabling guest access createOpts['initial_state'] = []; + const {alias} = this.state; + const localPart = alias.substr(1, alias.indexOf(":") - 1); + createOpts['room_alias_name'] = localPart; + } if (this.state.topic) { createOpts.topic = this.state.topic; } @@ -85,6 +91,9 @@ export default createReactClass({ this.setState({isPublic}); }, + onAliasChange(alias) { + this.setState({alias}); + }, onDetailsToggled(ev) { this.setState({detailsOpen: ev.target.open}); @@ -119,10 +128,19 @@ export default createReactClass({ const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const Field = sdk.getComponent('views.elements.Field'); const LabelledToggleSwitch = sdk.getComponent('views.elements.LabelledToggleSwitch'); + const RoomAliasField = sdk.getComponent('views.elements.RoomAliasField'); + let privateLabel; let publicLabel; + let aliasField; if (this.state.isPublic) { publicLabel = (

{_t("Set a room alias to easily share your room with other people.")}

); + const domain = MatrixClientPeg.get().getDomain(); + aliasField = ( +
+ this._aliasFieldRef = ref} onChange={this.onAliasChange} domain={domain} /> +
+ ); } else { privateLabel = (

{_t("This room is private, and can only be joined by invitation.")}

); } @@ -139,6 +157,7 @@ export default createReactClass({ { privateLabel } { publicLabel } + { aliasField }
{ this.state.detailsOpen ? _t('Hide advanced') : _t('Show advanced') } From 3e0278d41a3893b1540b8d49e5b3a01e52930c85 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Sep 2019 17:46:47 +0200 Subject: [PATCH 07/36] add validation when clicking Ok in dialog --- .../views/dialogs/CreateRoomDialog.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index 501e6b6f80..e3070cfef3 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -72,7 +72,31 @@ export default createReactClass({ }, onOk: async function() { + const activeElement = document.activeElement; + if (activeElement) { + activeElement.blur(); + } + await this._nameFieldRef.validate({allowEmpty: false}); + if (this._aliasFieldRef) { + await this._aliasFieldRef.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.state.nameIsValid && (!this._aliasFieldRef || this._aliasFieldRef.isValid)) { this.props.onFinished(true, this._roomCreateOptions()); + } else { + let field; + if (!this.state.nameIsValid) { + field = this._nameFieldRef; + } else if (this._aliasFieldRef && !this._aliasFieldRef.isValid) { + field = this._aliasFieldRef; + } + if (field) { + field.focus(); + field.validate({ allowEmpty: false, focused: true }); + } + } }, onCancel: function() { From 6a3723c69e8ccb15fe01445c5c130e1b2627c0b2 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Sep 2019 17:47:02 +0200 Subject: [PATCH 08/36] dialog styling --- res/css/views/dialogs/_CreateRoomDialog.scss | 56 +++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/res/css/views/dialogs/_CreateRoomDialog.scss b/res/css/views/dialogs/_CreateRoomDialog.scss index 05d5bfcebf..d3c41e648f 100644 --- a/res/css/views/dialogs/_CreateRoomDialog.scss +++ b/res/css/views/dialogs/_CreateRoomDialog.scss @@ -14,8 +14,24 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_CreateRoomDialog_details_summary { - outline: none; +.mx_CreateRoomDialog_details { + .mx_CreateRoomDialog_details_summary { + outline: none; + list-style: none; + font-weight: 600; + cursor: pointer; + color: $accent-color; + } + + > div { + display: flex; + align-items: start; + margin: 5px 0; + + input[type=checkbox] { + margin-right: 10px; + } + } } .mx_CreateRoomDialog_label { @@ -36,3 +52,39 @@ limitations under the License. background-color: $primary-bg-color; width: 100%; } + +// needed to make the alias field only grow as wide as needed +// as opposed to full width +.mx_CreateRoomDialog_aliasContainer { + display: flex; + // put margin on container so it can collapse with siblings + margin: 10px 0; + + .mx_RoomAliasField { + margin: 0; + } +} + +.mx_CreateRoomDialog { + + &.mx_Dialog_fixedWidth { + width: 450px; + } + + .mx_SettingsFlag { + display: flex; + } + + .mx_SettingsFlag_label { + flex: 1 1 0; + min-width: 0; + font-weight: 600; + } + + .mx_ToggleSwitch { + display: ; + flex: 0 0 auto; + margin-left: 30px; + } +} + From 53b28b9de877a76dd985471efb9ee5e2fb37dbbd Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 20 Sep 2019 17:47:10 +0200 Subject: [PATCH 09/36] i18n --- src/i18n/strings/en_EN.json | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7ca31a0f94..49302fece1 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1189,6 +1189,12 @@ "Custom level": "Custom level", "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.", "In reply to ": "In reply to ", + "Room alias": "Room alias", + "e.g. my-room": "e.g. my-room", + "Some characters not allowed": "Some characters not allowed", + "Please provide a room alias": "Please provide a room alias", + "This alias is available to use": "This alias is available to use", + "This alias is already in use": "This alias is already in use", "Room directory": "Room directory", "And %(count)s more...|other": "And %(count)s more...", "ex. @bob:example.com": "ex. @bob:example.com", @@ -1235,11 +1241,18 @@ "Community ID": "Community ID", "example": "example", "Create": "Create", + "Please enter a name for the room": "Please enter a name for the room", + "Set a room alias to easily share your room with other people.": "Set a room alias to easily share your room with other people.", + "This room is private, and can only be joined by invitation.": "This room is private, and can only be joined by invitation.", + "Create a public room": "Create a public room", + "Create a private room": "Create a private room", + "Name": "Name", + "Topic (optional)": "Topic (optional)", + "Make this room public": "Make this room public", + "Hide advanced": "Hide advanced", + "Show advanced": "Show advanced", + "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)": "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)", "Create Room": "Create Room", - "Room name (optional)": "Room name (optional)", - "Advanced options": "Advanced options", - "Block users on other matrix homeservers from joining this room": "Block users on other matrix homeservers from joining this room", - "This setting cannot be changed later!": "This setting cannot be changed later!", "Sign out": "Sign out", "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of Riot to do this": "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of Riot to do this", "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ": "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ", @@ -1496,7 +1509,6 @@ "Doesn't look like a valid phone number": "Doesn't look like a valid phone number", "Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only", "Enter username": "Enter username", - "Some characters not allowed": "Some characters not allowed", "Email (optional)": "Email (optional)", "Confirm": "Confirm", "Phone (optional)": "Phone (optional)", @@ -1722,7 +1734,6 @@ "NOT verified": "NOT verified", "Blacklisted": "Blacklisted", "verified": "verified", - "Name": "Name", "Verification": "Verification", "Ed25519 fingerprint": "Ed25519 fingerprint", "User ID": "User ID", From 3c7a0f4c490ee2ad6d320f38860cfda1577bb96c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Sep 2019 11:18:28 +0200 Subject: [PATCH 10/36] remove invalid css --- res/css/views/dialogs/_CreateRoomDialog.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/res/css/views/dialogs/_CreateRoomDialog.scss b/res/css/views/dialogs/_CreateRoomDialog.scss index d3c41e648f..db59715a77 100644 --- a/res/css/views/dialogs/_CreateRoomDialog.scss +++ b/res/css/views/dialogs/_CreateRoomDialog.scss @@ -82,7 +82,6 @@ limitations under the License. } .mx_ToggleSwitch { - display: ; flex: 0 0 auto; margin-left: 30px; } From e98b753c217eb727f844ae080d1c67103b13859f Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 23 Sep 2019 12:21:25 +0100 Subject: [PATCH 11/36] Use alternate MSISDN submit URL when returned by HS This changes MSISDN token submission to send to an arbitrary URL (instead of the current IS) when the HS provides such a URL. Fixes https://github.com/vector-im/riot-web/issues/10923 --- src/AddThreepid.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/AddThreepid.js b/src/AddThreepid.js index 3b66b1d7ee..84fa07ff4b 100644 --- a/src/AddThreepid.js +++ b/src/AddThreepid.js @@ -35,6 +35,8 @@ import IdentityAuthClient from './IdentityAuthClient'; export default class AddThreepid { constructor() { this.clientSecret = MatrixClientPeg.get().generateClientSecret(); + this.sessionId = null; + this.submitUrl = null; } /** @@ -101,6 +103,7 @@ export default class AddThreepid { phoneCountry, phoneNumber, this.clientSecret, 1, ).then((res) => { this.sessionId = res.sid; + this.submitUrl = res.submit_url; return res; }, function(err) { if (err.errcode === 'M_THREEPID_IN_USE') { @@ -198,12 +201,23 @@ export default class AddThreepid { async haveMsisdnToken(msisdnToken) { const authClient = new IdentityAuthClient(); const identityAccessToken = await authClient.getAccessToken(); - const result = await MatrixClientPeg.get().submitMsisdnToken( - this.sessionId, - this.clientSecret, - msisdnToken, - identityAccessToken, - ); + + let result; + if (this.submitUrl) { + result = await MatrixClientPeg.get().submitMsisdnTokenOtherUrl( + this.submitUrl, + this.sessionId, + this.clientSecret, + msisdnToken, + ); + } else { + result = await MatrixClientPeg.get().submitMsisdnToken( + this.sessionId, + this.clientSecret, + msisdnToken, + identityAccessToken, + ); + } if (result.errcode) { throw result; } From 0ab9efc594f7918851fa55d579e9ecf4c825892c Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 23 Sep 2019 12:28:41 +0100 Subject: [PATCH 12/36] Delay IS access tokens --- src/AddThreepid.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/AddThreepid.js b/src/AddThreepid.js index 84fa07ff4b..8f19815210 100644 --- a/src/AddThreepid.js +++ b/src/AddThreepid.js @@ -200,7 +200,6 @@ export default class AddThreepid { */ async haveMsisdnToken(msisdnToken) { const authClient = new IdentityAuthClient(); - const identityAccessToken = await authClient.getAccessToken(); let result; if (this.submitUrl) { @@ -215,7 +214,7 @@ export default class AddThreepid { this.sessionId, this.clientSecret, msisdnToken, - identityAccessToken, + await authClient.getAccessToken(), ); } if (result.errcode) { @@ -225,13 +224,11 @@ export default class AddThreepid { const identityServerDomain = MatrixClientPeg.get().idBaseUrl.split("://")[1]; if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) { if (this.bind) { - const authClient = new IdentityAuthClient(); - const identityAccessToken = await authClient.getAccessToken(); await MatrixClientPeg.get().bindThreePid({ sid: this.sessionId, client_secret: this.clientSecret, id_server: identityServerDomain, - id_access_token: identityAccessToken, + id_access_token: await authClient.getAccessToken(), }); } else { await MatrixClientPeg.get().addThreePidOnly({ From 0ab7962ebd4389d20dff2a2940680ff81a547393 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Sep 2019 14:06:59 +0200 Subject: [PATCH 13/36] add z-index to format bar so it appears above status area and reply wrapper --- res/css/views/rooms/_MessageComposerFormatBar.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/res/css/views/rooms/_MessageComposerFormatBar.scss b/res/css/views/rooms/_MessageComposerFormatBar.scss index 6e8fc58470..43a2fb257e 100644 --- a/res/css/views/rooms/_MessageComposerFormatBar.scss +++ b/res/css/views/rooms/_MessageComposerFormatBar.scss @@ -23,6 +23,9 @@ limitations under the License. border-radius: 4px; background-color: $message-action-bar-bg-color; user-select: none; + // equal to z-index of mx_ReplyPreview and mx_RoomView_statusArea (1000) + // but as it appears after them in the DOM, will appear on top. + z-index: 1000; &.mx_MessageComposerFormatBar_shown { display: block; From 228905bec201a2662ceded2dc5f0832cf79bfe49 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Sep 2019 14:39:19 +0200 Subject: [PATCH 14/36] insert command completion as command part (instead of plain) this prevents the command being sent as plain text this adds a `type` property to completions to decide which parts should be inserted into the composer, hence deciding how they will be rendered. --- src/autocomplete/CommandProvider.js | 1 + src/autocomplete/CommunityProvider.js | 1 + src/autocomplete/NotifProvider.js | 1 + src/autocomplete/RoomProvider.js | 1 + src/autocomplete/UserProvider.js | 1 + src/editor/autocomplete.js | 24 +++++++++++++----------- src/editor/parts.js | 9 +++++++-- 7 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/autocomplete/CommandProvider.js b/src/autocomplete/CommandProvider.js index 609a8fa9a1..b13680ece2 100644 --- a/src/autocomplete/CommandProvider.js +++ b/src/autocomplete/CommandProvider.js @@ -64,6 +64,7 @@ export default class CommandProvider extends AutocompleteProvider { return matches.map((result) => ({ // If the command is the same as the one they entered, we don't want to discard their arguments completion: result.command === command[1] ? command[0] : (result.command + ' '), + type: "command", component: ({ completion: groupId, suffix: ' ', + type: "community", href: makeGroupPermalink(groupId), component: ( } title="@room" description={_t("Notify the whole room")} /> diff --git a/src/autocomplete/RoomProvider.js b/src/autocomplete/RoomProvider.js index f118a06b9e..b94edf590c 100644 --- a/src/autocomplete/RoomProvider.js +++ b/src/autocomplete/RoomProvider.js @@ -89,6 +89,7 @@ export default class RoomProvider extends AutocompleteProvider { return { completion: displayAlias, completionId: displayAlias, + type: "room", suffix: ' ', href: makeRoomPermalink(displayAlias), component: ( diff --git a/src/autocomplete/UserProvider.js b/src/autocomplete/UserProvider.js index d4a5ec5e74..62ae5d4970 100644 --- a/src/autocomplete/UserProvider.js +++ b/src/autocomplete/UserProvider.js @@ -114,6 +114,7 @@ export default class UserProvider extends AutocompleteProvider { // relies on the length of the entity === length of the text in the decoration. completion: user.rawDisplayName, completionId: user.userId, + type: "user", suffix: (selection.beginning && range.start === 0) ? ': ' : ' ', href: makeUserPermalink(user.userId), component: ( diff --git a/src/editor/autocomplete.js b/src/editor/autocomplete.js index beb155f297..3ee69dc477 100644 --- a/src/editor/autocomplete.js +++ b/src/editor/autocomplete.js @@ -100,19 +100,21 @@ export default class AutocompleteWrapperModel { _partForCompletion(completion) { const {completionId} = completion; const text = completion.completion; - const firstChr = completionId && completionId[0]; - switch (firstChr) { - case "@": { - if (completionId === "@room") { - return [this._partCreator.atRoomPill(completionId)]; - } else { - return this._partCreator.createMentionParts(this._partIndex, text, completionId); - } - } - case "#": + switch (completion.type) { + case "room": return [this._partCreator.roomPill(completionId)]; - // used for emoji and command completion replacement + case "at-room": + return [this._partCreator.atRoomPill(completionId)]; + case "user": + // not using suffix here, because we also need to calculate + // the suffix when clicking a display name to insert a mention, + // which happens in createMentionParts + return this._partCreator.createMentionParts(this._partIndex, text, completionId); + case "command": + // command needs special handling for auto complete, but also renders as plain texts + return [this._partCreator.command(text)]; default: + // used for emoji and other plain text completion replacement return [this._partCreator.plain(text)]; } } diff --git a/src/editor/parts.js b/src/editor/parts.js index 883eff8679..f0b713beb6 100644 --- a/src/editor/parts.js +++ b/src/editor/parts.js @@ -456,15 +456,20 @@ export class CommandPartCreator extends PartCreator { createPartForInput(text, partIndex) { // at beginning and starts with /? create if (partIndex === 0 && text[0] === "/") { - return new CommandPart("", this._autoCompleteCreator); + // text will be inserted by model, so pass empty string + return this.command(""); } else { return super.createPartForInput(text, partIndex); } } + command(text) { + return new CommandPart(text, this._autoCompleteCreator); + } + deserializePart(part) { if (part.type === "command") { - return new CommandPart(part.text, this._autoCompleteCreator); + return this.command(part.text); } else { return super.deserializePart(part); } From 40f7fa8f9433ea25a1b7a1ffe701a320f2f0069b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Sep 2019 14:40:41 +0200 Subject: [PATCH 15/36] add suffixes from provider to room and @room completions this prevents the @room not working when not typing an extra space --- src/editor/autocomplete.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/autocomplete.js b/src/editor/autocomplete.js index 3ee69dc477..1ead3aef07 100644 --- a/src/editor/autocomplete.js +++ b/src/editor/autocomplete.js @@ -102,9 +102,9 @@ export default class AutocompleteWrapperModel { const text = completion.completion; switch (completion.type) { case "room": - return [this._partCreator.roomPill(completionId)]; + return [this._partCreator.roomPill(completionId), this._partCreator.plain(completion.suffix)]; case "at-room": - return [this._partCreator.atRoomPill(completionId)]; + return [this._partCreator.atRoomPill(completionId), this._partCreator.plain(completion.suffix)]; case "user": // not using suffix here, because we also need to calculate // the suffix when clicking a display name to insert a mention, From 0a663398edd2c2ce56b31272a81fed1ddf1729e7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Sep 2019 14:59:53 +0200 Subject: [PATCH 16/36] Use underscore for when doing html > md to be consistent with the format bar, which also uses underscores for italics --- src/editor/deserialize.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/deserialize.js b/src/editor/deserialize.js index 925d0d1ab3..1b99d70946 100644 --- a/src/editor/deserialize.js +++ b/src/editor/deserialize.js @@ -90,7 +90,7 @@ function parseElement(n, partCreator, state) { case "BR": return partCreator.newline(); case "EM": - return partCreator.plain(`*${n.textContent}*`); + return partCreator.plain(`_${n.textContent}_`); case "STRONG": return partCreator.plain(`**${n.textContent}**`); case "PRE": From 24454212700a8aced8781177a9e416f8f89522df Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Sep 2019 15:06:22 +0200 Subject: [PATCH 17/36] add extra newline before P tags (when not first node) --- src/editor/deserialize.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/editor/deserialize.js b/src/editor/deserialize.js index 1b99d70946..947ce30ce7 100644 --- a/src/editor/deserialize.js +++ b/src/editor/deserialize.js @@ -76,7 +76,7 @@ function parseHeader(el, partCreator) { return partCreator.plain("#".repeat(depth) + " "); } -function parseElement(n, partCreator, state) { +function parseElement(n, partCreator, lastNode, state) { switch (n.nodeName) { case "H1": case "H2": @@ -107,6 +107,12 @@ function parseElement(n, partCreator, state) { return partCreator.plain(`${indent}- `); } } + case "P": { + if (lastNode) { + return partCreator.newline(); + } + break; + } case "OL": case "UL": state.listDepth = (state.listDepth || 0) + 1; @@ -183,7 +189,7 @@ function parseHtmlMessage(html, partCreator, isQuotedMessage) { if (n.nodeType === Node.TEXT_NODE) { newParts.push(...parseAtRoomMentions(n.nodeValue, partCreator)); } else if (n.nodeType === Node.ELEMENT_NODE) { - const parseResult = parseElement(n, partCreator, state); + const parseResult = parseElement(n, partCreator, lastNode, state); if (parseResult) { if (Array.isArray(parseResult)) { newParts.push(...parseResult); From 04720db2a33b6bb01f3593ccebb1ff780f0c2875 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Sep 2019 15:33:30 +0200 Subject: [PATCH 18/36] don't append extra newline after blockquote anymore now that P prepends newline when not first element --- src/editor/deserialize.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/editor/deserialize.js b/src/editor/deserialize.js index 947ce30ce7..04edd4541c 100644 --- a/src/editor/deserialize.js +++ b/src/editor/deserialize.js @@ -206,10 +206,6 @@ function parseHtmlMessage(html, partCreator, isQuotedMessage) { parts.push(...newParts); - // extra newline after quote, only if there something behind it... - if (lastNode && lastNode.nodeName === "BLOCKQUOTE") { - parts.push(partCreator.newline()); - } const decend = checkDecendInto(n); // when not decending (like for PRE), onNodeLeave won't be called to set lastNode // so do that here. From 7f4e07080708bd0091f84cc71e52103336d32a58 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Sep 2019 15:34:01 +0200 Subject: [PATCH 19/36] fix deserialize unit tests --- test/editor/deserialize-test.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/test/editor/deserialize-test.js b/test/editor/deserialize-test.js index 8a79b4101f..bf670b0fd8 100644 --- a/test/editor/deserialize-test.js +++ b/test/editor/deserialize-test.js @@ -94,7 +94,7 @@ describe('editor/deserialize', function() { const html = "bold and emphasized text"; const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); expect(parts.length).toBe(1); - expect(parts[0]).toStrictEqual({type: "plain", text: "**bold** and *emphasized* text"}); + expect(parts[0]).toStrictEqual({type: "plain", text: "**bold** and _emphasized_ text"}); }); it('hyperlink', function() { const html = 'click this!'; @@ -105,10 +105,11 @@ describe('editor/deserialize', function() { it('multiple lines with paragraphs', function() { const html = '

hello

world

'; const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); - expect(parts.length).toBe(3); + expect(parts.length).toBe(4); expect(parts[0]).toStrictEqual({type: "plain", text: "hello"}); expect(parts[1]).toStrictEqual({type: "newline", text: "\n"}); - expect(parts[2]).toStrictEqual({type: "plain", text: "world"}); + expect(parts[2]).toStrictEqual({type: "newline", text: "\n"}); + expect(parts[3]).toStrictEqual({type: "plain", text: "world"}); }); it('multiple lines with line breaks', function() { const html = 'hello
world'; @@ -121,18 +122,19 @@ describe('editor/deserialize', function() { it('multiple lines mixing paragraphs and line breaks', function() { const html = '

hello
warm

world

'; const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); - expect(parts.length).toBe(5); + expect(parts.length).toBe(6); expect(parts[0]).toStrictEqual({type: "plain", text: "hello"}); expect(parts[1]).toStrictEqual({type: "newline", text: "\n"}); expect(parts[2]).toStrictEqual({type: "plain", text: "warm"}); expect(parts[3]).toStrictEqual({type: "newline", text: "\n"}); - expect(parts[4]).toStrictEqual({type: "plain", text: "world"}); + expect(parts[4]).toStrictEqual({type: "newline", text: "\n"}); + expect(parts[5]).toStrictEqual({type: "plain", text: "world"}); }); it('quote', function() { const html = '

wise
words

indeed

'; const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); expect(parts.length).toBe(6); - expect(parts[0]).toStrictEqual({type: "plain", text: "> *wise*"}); + expect(parts[0]).toStrictEqual({type: "plain", text: "> _wise_"}); expect(parts[1]).toStrictEqual({type: "newline", text: "\n"}); expect(parts[2]).toStrictEqual({type: "plain", text: "> **words**"}); expect(parts[3]).toStrictEqual({type: "newline", text: "\n"}); @@ -159,7 +161,7 @@ describe('editor/deserialize', function() { const html = "formatted message for @room"; const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); expect(parts.length).toBe(2); - expect(parts[0]).toStrictEqual({type: "plain", text: "*formatted* message for "}); + expect(parts[0]).toStrictEqual({type: "plain", text: "_formatted_ message for "}); expect(parts[1]).toStrictEqual({type: "at-room-pill", text: "@room"}); }); it('inline code', function() { @@ -220,7 +222,7 @@ describe('editor/deserialize', function() { const html = "says DON'T SHOUT!"; const parts = normalize(parseEvent(htmlMessage(html, "m.emote"), createPartCreator())); expect(parts.length).toBe(1); - expect(parts[0]).toStrictEqual({type: "plain", text: "/me says *DON'T SHOUT*!"}); + expect(parts[0]).toStrictEqual({type: "plain", text: "/me says _DON'T SHOUT_!"}); }); }); }); From 151af65e4f49c45085c0b554f8c897a73e868157 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 23 Sep 2019 17:56:16 +0200 Subject: [PATCH 20/36] hide details summary arrow in create room dialog on webkit too --- res/css/views/dialogs/_CreateRoomDialog.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/css/views/dialogs/_CreateRoomDialog.scss b/res/css/views/dialogs/_CreateRoomDialog.scss index db59715a77..d3a8f6ff42 100644 --- a/res/css/views/dialogs/_CreateRoomDialog.scss +++ b/res/css/views/dialogs/_CreateRoomDialog.scss @@ -21,6 +21,11 @@ limitations under the License. font-weight: 600; cursor: pointer; color: $accent-color; + + // list-style doesn't do it for webkit + &::-webkit-details-marker { + display: none; + } } > div { From 54355c0e28161c7c52d0146f2fe4c2e95254be77 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 23 Sep 2019 22:01:49 +0100 Subject: [PATCH 21/36] put the room name in the title tag should fix https://github.com/vector-im/riot-web/issues/4454 --- src/components/structures/MatrixChat.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 774ef21aff..7c791456b1 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -271,6 +271,10 @@ export default createReactClass({ this.focusComposer = false; + // object field used for tracking the status info appended to the title tag. + // we don't do it as react state as i'm scared about triggering needless react refreshes. + this.subTitleStatus = ''; + // this can technically be done anywhere but doing this here keeps all // the routing url path logic together. if (this.onAliasClick) { @@ -1297,6 +1301,7 @@ export default createReactClass({ collapsedRhs: false, currentRoomId: null, }); + this.subTitleStatus = ''; this._setPageSubtitle(); }, @@ -1312,6 +1317,7 @@ export default createReactClass({ collapsedRhs: false, currentRoomId: null, }); + this.subTitleStatus = ''; this._setPageSubtitle(); }, @@ -1706,6 +1712,7 @@ export default createReactClass({ if (this.props.onNewScreen) { this.props.onNewScreen(screen); } + this._setPageSubtitle(); }, onAliasClick: function(event, alias) { @@ -1821,7 +1828,13 @@ export default createReactClass({ }, _setPageSubtitle: function(subtitle='') { - document.title = `${SdkConfig.get().brand || 'Riot'} ${subtitle}`; + if (this.state.currentRoomId) { + const room = MatrixClientPeg.get().getRoom(this.state.currentRoomId); + if (room) { + subtitle = `| ${ room.name }`; + } + } + document.title = `${SdkConfig.get().brand || 'Riot'} ${subtitle} ${this.subTitleStatus}`; }, updateStatusIndicator: function(state, prevState) { @@ -1832,15 +1845,15 @@ export default createReactClass({ PlatformPeg.get().setNotificationCount(notifCount); } - let subtitle = ''; + this.subTitleStatus = ''; if (state === "ERROR") { - subtitle += `[${_t("Offline")}] `; + this.subTitleStatus += `[${_t("Offline")}] `; } if (notifCount > 0) { - subtitle += `[${notifCount}]`; + this.subTitleStatus += `[${notifCount}]`; } - this._setPageSubtitle(subtitle); + this._setPageSubtitle(); }, onCloseAllSettings() { From b85dc7b5a887d00971e40b7bf03dda09689d5f85 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 24 Sep 2019 01:34:51 +0100 Subject: [PATCH 22/36] add debug for vector-im/riot-web#10940 --- src/components/structures/MatrixChat.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 7c791456b1..874149f2c6 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1831,8 +1831,9 @@ export default createReactClass({ if (this.state.currentRoomId) { const room = MatrixClientPeg.get().getRoom(this.state.currentRoomId); if (room) { - subtitle = `| ${ room.name }`; + subtitle = `| ${ room.name } ${subtitle}`; } + console.log(`updating subtitle as ${subtitle}`); } document.title = `${SdkConfig.get().brand || 'Riot'} ${subtitle} ${this.subTitleStatus}`; }, From 3ce47a01817fc57a075c962c3aed614b8614e641 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 24 Sep 2019 01:51:21 +0100 Subject: [PATCH 23/36] notify new screen after setting state --- src/components/structures/MatrixChat.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 874149f2c6..b7f1ead15c 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -874,9 +874,10 @@ export default createReactClass({ if (roomInfo.event_id && roomInfo.highlighted) { presentedId += "/" + roomInfo.event_id; } - this.notifyNewScreen('room/' + presentedId); newState.ready = true; - this.setState(newState); + this.setState(newState, ()=>{ + this.notifyNewScreen('room/' + presentedId); + }); }); }, From 26a8398a0ec4d1fee29019e9309702c45b4b4696 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 24 Sep 2019 12:53:33 +0200 Subject: [PATCH 24/36] make sure client exists while logging out --- src/components/structures/MatrixChat.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index b7f1ead15c..1dafc1f982 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1830,7 +1830,8 @@ export default createReactClass({ _setPageSubtitle: function(subtitle='') { if (this.state.currentRoomId) { - const room = MatrixClientPeg.get().getRoom(this.state.currentRoomId); + const client = MatrixClientPeg.get(); + const room = client && client.getRoom(this.state.currentRoomId); if (room) { subtitle = `| ${ room.name } ${subtitle}`; } From 4d151722fff14f43792fbbe8d7f7ef5a5d5398b8 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 24 Sep 2019 15:32:08 +0200 Subject: [PATCH 25/36] delay DOM modification after compositionend --- src/components/views/rooms/BasicMessageComposer.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js index 110df355fe..14c850b9a3 100644 --- a/src/components/views/rooms/BasicMessageComposer.js +++ b/src/components/views/rooms/BasicMessageComposer.js @@ -170,8 +170,13 @@ export default class BasicMessageEditor extends React.Component { _onCompositionEnd = (event) => { this._isIMEComposing = false; // some browsers (chromium) don't fire an input event after ending a composition - // so trigger a model update after the composition is done by calling the input handler - this._onInput({inputType: "insertCompositionText"}); + // so trigger a model update after the composition is done by calling the input handler. + // do this async though, as modifying the DOM from the compositionend event might confuse the composition. + setTimeout(() => { + this._onInput({inputType: "insertCompositionText"}); + }, 0); + } + } _onPaste = (event) => { From 9f47fad3057c1a3b591e59bc86c814b3482833cc Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 24 Sep 2019 15:32:30 +0200 Subject: [PATCH 26/36] ignore keydown events while doing IME composition --- src/components/views/rooms/BasicMessageComposer.js | 2 ++ src/components/views/rooms/EditMessageComposer.js | 3 +++ src/components/views/rooms/SendMessageComposer.js | 3 +++ 3 files changed, 8 insertions(+) diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js index 14c850b9a3..40142d4436 100644 --- a/src/components/views/rooms/BasicMessageComposer.js +++ b/src/components/views/rooms/BasicMessageComposer.js @@ -177,6 +177,8 @@ export default class BasicMessageEditor extends React.Component { }, 0); } + shouldIgnoreKeyDownEvents() { + return this._isIMEComposing; } _onPaste = (event) => { diff --git a/src/components/views/rooms/EditMessageComposer.js b/src/components/views/rooms/EditMessageComposer.js index a1d6fa618f..c744711f61 100644 --- a/src/components/views/rooms/EditMessageComposer.js +++ b/src/components/views/rooms/EditMessageComposer.js @@ -127,6 +127,9 @@ export default class EditMessageComposer extends React.Component { } _onKeyDown = (event) => { + if (this._editorRef.shouldIgnoreKeyDownEvents()) { + return; + } if (event.metaKey || event.altKey || event.shiftKey) { return; } diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index f6e5830329..d98a1a55cb 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -104,6 +104,9 @@ export default class SendMessageComposer extends React.Component { }; _onKeyDown = (event) => { + if (this._editorRef.shouldIgnoreKeyDownEvents()) { + return; + } const hasModifier = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; if (event.key === "Enter" && !hasModifier) { this._sendMessage(); From 0c51e41ea4de099deb9fdf0d7526f9994d3ba362 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 24 Sep 2019 14:47:08 +0100 Subject: [PATCH 27/36] Remove id_server param for password reset For HSes that no longer need it, remove the id_server param from password reset. Part of https://github.com/vector-im/riot-web/issues/10941 --- src/PasswordReset.js | 27 ++++++++++++------- .../structures/auth/ForgotPassword.js | 9 ++++--- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/PasswordReset.js b/src/PasswordReset.js index 0dd5802962..31339eb4e5 100644 --- a/src/PasswordReset.js +++ b/src/PasswordReset.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -72,15 +73,21 @@ class PasswordReset { * with a "message" property which contains a human-readable message detailing why * the reset failed, e.g. "There is no mapped matrix user ID for the given email address". */ - checkEmailLinkClicked() { - return this.client.setPassword({ - type: "m.login.email.identity", - threepid_creds: { - sid: this.sessionId, - client_secret: this.clientSecret, - id_server: this.identityServerDomain, - }, - }, this.password).catch(function(err) { + async checkEmailLinkClicked() { + const creds = { + sid: this.sessionId, + client_secret: this.clientSecret, + }; + if (await this.doesServerRequireIdServerParam()) { + creds.id_server = this.identityServerDomain; + } + + try { + await this.client.setPassword({ + type: "m.login.email.identity", + threepid_creds: creds, + }, this.password); + } catch (err) { if (err.httpStatus === 401) { err.message = _t('Failed to verify email address: make sure you clicked the link in the email'); } else if (err.httpStatus === 404) { @@ -90,7 +97,7 @@ class PasswordReset { err.message += ` (Status ${err.httpStatus})`; } throw err; - }); + } } } diff --git a/src/components/structures/auth/ForgotPassword.js b/src/components/structures/auth/ForgotPassword.js index de1b964ff4..46a5fa7bd7 100644 --- a/src/components/structures/auth/ForgotPassword.js +++ b/src/components/structures/auth/ForgotPassword.js @@ -117,17 +117,18 @@ module.exports = createReactClass({ }); }, - onVerify: function(ev) { + onVerify: async function(ev) { ev.preventDefault(); if (!this.reset) { console.error("onVerify called before submitPasswordReset!"); return; } - this.reset.checkEmailLinkClicked().done((res) => { + try { + await this.reset.checkEmailLinkClicked(); this.setState({ phase: PHASE_DONE }); - }, (err) => { + } catch (err) { this.showErrorDialog(err.message); - }); + } }, onSubmitForm: async function(ev) { From df33d0f74c7eed58fc83f2484bda0593fadfc3b3 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 24 Sep 2019 14:57:39 +0100 Subject: [PATCH 28/36] Remove debug for https://github.com/vector-im/riot-web/issues/10940 --- src/components/structures/MatrixChat.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 1dafc1f982..a185664038 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1835,7 +1835,6 @@ export default createReactClass({ if (room) { subtitle = `| ${ room.name } ${subtitle}`; } - console.log(`updating subtitle as ${subtitle}`); } document.title = `${SdkConfig.get().brand || 'Riot'} ${subtitle} ${this.subTitleStatus}`; }, From ec9e7f5855093a0bdb71c2e093c1a14c68eab97a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 24 Sep 2019 15:02:30 +0100 Subject: [PATCH 29/36] Remove id_server for MSISDN registration For HSes that no longer need it, remove the id_server param when verifying MSISDNs at registration. Fixes https://github.com/vector-im/riot-web/issues/10941 --- .../auth/InteractiveAuthEntryComponents.js | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index d2eb21df23..af41d07720 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -453,45 +453,45 @@ export const MsisdnAuthEntry = createReactClass({ }); }, - _onFormSubmit: function(e) { + _onFormSubmit: async function(e) { e.preventDefault(); if (this.state.token == '') return; - this.setState({ - errorText: null, - }); + this.setState({ + errorText: null, + }); - this.props.matrixClient.submitMsisdnToken( - this._sid, this.props.clientSecret, this.state.token, - ).then((result) => { + try { + const result = await this.props.matrixClient.submitMsisdnToken( + this._sid, this.props.clientSecret, this.state.token, + ); if (result.success) { - const idServerParsedUrl = url.parse( - this.props.matrixClient.getIdentityServerUrl(), - ); + const creds = { + sid: this._sid, + client_secret: this.props.clientSecret, + }; + if (await this.props.matrixClient.doesServerRequireIdServerParam()) { + const idServerParsedUrl = url.parse( + this.props.matrixClient.getIdentityServerUrl(), + ); + creds.id_server = idServerParsedUrl.host; + } this.props.submitAuthDict({ type: MsisdnAuthEntry.LOGIN_TYPE, // TODO: Remove `threepid_creds` once servers support proper UIA // See https://github.com/vector-im/riot-web/issues/10312 - threepid_creds: { - sid: this._sid, - client_secret: this.props.clientSecret, - id_server: idServerParsedUrl.host, - }, - threepidCreds: { - sid: this._sid, - client_secret: this.props.clientSecret, - id_server: idServerParsedUrl.host, - }, + threepid_creds: creds, + threepidCreds: creds, }); } else { this.setState({ errorText: _t("Token incorrect"), }); } - }).catch((e) => { + } catch (e) { this.props.fail(e); console.log("Failed to submit msisdn token"); - }).done(); + } }, render: function() { From 6ab816f79e5903aa3e2d81775a7c2d231ab11fdf Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 24 Sep 2019 12:29:20 -0600 Subject: [PATCH 30/36] Hide browser a11y outline on context menus Fixes https://github.com/vector-im/riot-web/issues/10926 Regressed by https://github.com/matrix-org/matrix-react-sdk/pull/3454/files Class introduced in https://github.com/matrix-org/matrix-react-sdk/pull/2994 --- src/components/structures/ContextualMenu.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/structures/ContextualMenu.js b/src/components/structures/ContextualMenu.js index 27a202785b..0f4d0b38d4 100644 --- a/src/components/structures/ContextualMenu.js +++ b/src/components/structures/ContextualMenu.js @@ -167,6 +167,7 @@ export default class ContextualMenu extends React.Component { const menuClasses = classNames({ 'mx_ContextualMenu': true, + 'mx_HiddenFocusable': true, // hide browser outline 'mx_ContextualMenu_left': !hasChevron && position.left, 'mx_ContextualMenu_right': !hasChevron && position.right, 'mx_ContextualMenu_top': !hasChevron && position.top, From 9e33be9b2a6b62f70a63756b9ad8c4d1314b9c7e Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 24 Sep 2019 15:42:35 +0100 Subject: [PATCH 31/36] Send MSISDN validation token to submit_url during registration Similar to previous changes for 3PID add, this changes registration with MSISDN to also send tokens to `submit_url` when supplied. Fixes https://github.com/vector-im/riot-web/issues/10939 --- .../views/auth/InteractiveAuthEntryComponents.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index af41d07720..145ef32643 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -420,6 +420,7 @@ export const MsisdnAuthEntry = createReactClass({ }, componentWillMount: function() { + this._submitUrl = null; this._sid = null; this._msisdn = null; this._tokenBox = null; @@ -442,6 +443,7 @@ export const MsisdnAuthEntry = createReactClass({ this.props.clientSecret, 1, // TODO: Multiple send attempts? ).then((result) => { + this._submitUrl = result.submit_url; this._sid = result.sid; this._msisdn = result.msisdn; }); @@ -462,9 +464,16 @@ export const MsisdnAuthEntry = createReactClass({ }); try { - const result = await this.props.matrixClient.submitMsisdnToken( - this._sid, this.props.clientSecret, this.state.token, - ); + let result; + if (this._submitUrl) { + result = await this.props.matrixClient.submitMsisdnTokenOtherUrl( + this._submitUrl, this._sid, this.props.clientSecret, this.state.token, + ); + } else { + result = await this.props.matrixClient.submitMsisdnToken( + this._sid, this.props.clientSecret, this.state.token, + ); + } if (result.success) { const creds = { sid: this._sid, From 7bda1c58ebf6a4306abbe24dffbef5028a954c82 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 24 Sep 2019 15:36:46 +0200 Subject: [PATCH 32/36] better naming --- src/components/views/rooms/BasicMessageComposer.js | 6 +++--- src/components/views/rooms/EditMessageComposer.js | 2 +- src/components/views/rooms/SendMessageComposer.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js index 40142d4436..ac528f07eb 100644 --- a/src/components/views/rooms/BasicMessageComposer.js +++ b/src/components/views/rooms/BasicMessageComposer.js @@ -173,12 +173,12 @@ export default class BasicMessageEditor extends React.Component { // so trigger a model update after the composition is done by calling the input handler. // do this async though, as modifying the DOM from the compositionend event might confuse the composition. setTimeout(() => { - this._onInput({inputType: "insertCompositionText"}); + this._onInput({inputType: "insertCompositionText"}, true); }, 0); } - shouldIgnoreKeyDownEvents() { - return this._isIMEComposing; + isComposing(event) { + return !!(this._isIMEComposing || event.isComposing); } _onPaste = (event) => { diff --git a/src/components/views/rooms/EditMessageComposer.js b/src/components/views/rooms/EditMessageComposer.js index c744711f61..1a7abb45fb 100644 --- a/src/components/views/rooms/EditMessageComposer.js +++ b/src/components/views/rooms/EditMessageComposer.js @@ -127,7 +127,7 @@ export default class EditMessageComposer extends React.Component { } _onKeyDown = (event) => { - if (this._editorRef.shouldIgnoreKeyDownEvents()) { + if (this._editorRef.isComposing(event)) { return; } if (event.metaKey || event.altKey || event.shiftKey) { diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index d98a1a55cb..6fc53492d3 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -104,7 +104,7 @@ export default class SendMessageComposer extends React.Component { }; _onKeyDown = (event) => { - if (this._editorRef.shouldIgnoreKeyDownEvents()) { + if (this._editorRef.isComposing(event)) { return; } const hasModifier = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; From ffe34ee8a1ea2f643b6160603057f89de9cdcb4c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 25 Sep 2019 10:33:52 +0200 Subject: [PATCH 33/36] try to see if this fixes safari back on of the 2 changes (updating dom async from compositionend, or ignoring keydown while composing) here has, while fixing chrome, broken safari. Don't do the async dom updating for safari if that was it. --- .../views/rooms/BasicMessageComposer.js | 24 +++++++++++++++---- .../views/rooms/EditMessageComposer.js | 1 + .../views/rooms/SendMessageComposer.js | 1 + 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js index ac528f07eb..2e0bc5f395 100644 --- a/src/components/views/rooms/BasicMessageComposer.js +++ b/src/components/views/rooms/BasicMessageComposer.js @@ -169,15 +169,31 @@ export default class BasicMessageEditor extends React.Component { _onCompositionEnd = (event) => { this._isIMEComposing = false; - // some browsers (chromium) don't fire an input event after ending a composition + // some browsers (Chrome) don't fire an input event after ending a composition, // so trigger a model update after the composition is done by calling the input handler. - // do this async though, as modifying the DOM from the compositionend event might confuse the composition. - setTimeout(() => { + + // however, modifying the DOM (caused by the editor model update) from the compositionend handler seems + // to confuse the IME in Chrome, likely causing https://github.com/vector-im/riot-web/issues/10913 , + // so we do it async + + // however, doing this async seems to break things in Safari for some reason, so browser sniff. + + const ua = navigator.userAgent.toLowerCase(); + const isSafari = ua.includes('safari/') && !ua.includes('chrome/'); + + if (isSafari) { this._onInput({inputType: "insertCompositionText"}, true); - }, 0); + } else { + setTimeout(() => { + this._onInput({inputType: "insertCompositionText"}, true); + }, 0); + } } isComposing(event) { + // checking the event.isComposing flag just in case any browser out there + // emits events related to the composition after compositionend + // has been fired return !!(this._isIMEComposing || event.isComposing); } diff --git a/src/components/views/rooms/EditMessageComposer.js b/src/components/views/rooms/EditMessageComposer.js index 1a7abb45fb..3430e793ac 100644 --- a/src/components/views/rooms/EditMessageComposer.js +++ b/src/components/views/rooms/EditMessageComposer.js @@ -127,6 +127,7 @@ export default class EditMessageComposer extends React.Component { } _onKeyDown = (event) => { + // ignore any keypress while doing IME compositions if (this._editorRef.isComposing(event)) { return; } diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 6fc53492d3..c8f8ab1293 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -104,6 +104,7 @@ export default class SendMessageComposer extends React.Component { }; _onKeyDown = (event) => { + // ignore any keypress while doing IME compositions if (this._editorRef.isComposing(event)) { return; } From c8af1a62561d0771b6e7d353840c40429b005782 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 25 Sep 2019 14:59:27 +0200 Subject: [PATCH 34/36] fixup: remove flag --- src/components/views/rooms/BasicMessageComposer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js index 2e0bc5f395..0601b9b84f 100644 --- a/src/components/views/rooms/BasicMessageComposer.js +++ b/src/components/views/rooms/BasicMessageComposer.js @@ -182,10 +182,10 @@ export default class BasicMessageEditor extends React.Component { const isSafari = ua.includes('safari/') && !ua.includes('chrome/'); if (isSafari) { - this._onInput({inputType: "insertCompositionText"}, true); + this._onInput({inputType: "insertCompositionText"}); } else { setTimeout(() => { - this._onInput({inputType: "insertCompositionText"}, true); + this._onInput({inputType: "insertCompositionText"}); }, 0); } } From 1c11b8e2746358bfeaaeea7f4ccd889ae9638cc1 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 25 Sep 2019 15:08:48 +0200 Subject: [PATCH 35/36] Move focus to first field in create room dialog when showing --- src/components/views/dialogs/CreateRoomDialog.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index e3070cfef3..8e267da5d1 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -65,6 +65,8 @@ export default createReactClass({ componentDidMount() { this._detailsRef.addEventListener("toggle", this.onDetailsToggled); + // move focus to first field when showing dialog + this._nameFieldRef.focus(); }, componentWillUnmount() { From 30af9a9056c1be479d0048c8af61b874ee9febb6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 25 Sep 2019 16:11:37 +0200 Subject: [PATCH 36/36] need to check isComposing on native event --- src/components/views/rooms/BasicMessageComposer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js index 0601b9b84f..c229f64d1a 100644 --- a/src/components/views/rooms/BasicMessageComposer.js +++ b/src/components/views/rooms/BasicMessageComposer.js @@ -194,7 +194,7 @@ export default class BasicMessageEditor extends React.Component { // checking the event.isComposing flag just in case any browser out there // emits events related to the composition after compositionend // has been fired - return !!(this._isIMEComposing || event.isComposing); + return !!(this._isIMEComposing || (event.nativeEvent && event.nativeEvent.isComposing)); } _onPaste = (event) => {