From e8affd22a099920eb87d817e3ffd432bd7a205fd Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Mon, 21 Nov 2022 19:26:21 +0100 Subject: [PATCH 1/8] Fix: Cursor moves to end after adding space in Room Name --- src/components/RoomNameInput/index.js | 127 ++++++++++++++++++ .../index.native.js} | 6 +- 2 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 src/components/RoomNameInput/index.js rename src/components/{RoomNameInput.js => RoomNameInput/index.native.js} (95%) diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js new file mode 100644 index 00000000000..d60d10956f3 --- /dev/null +++ b/src/components/RoomNameInput/index.js @@ -0,0 +1,127 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import CONST from '../../CONST'; +import withLocalize, {withLocalizePropTypes} from '../withLocalize'; +import TextInput from '../TextInput'; + +const propTypes = { + /** Callback to execute when the text input is modified correctly */ + onChangeText: PropTypes.func, + + /** Room name to show in input field. This should include the '#' already prefixed to the name */ + value: PropTypes.string, + + /** Whether we should show the input as disabled */ + disabled: PropTypes.bool, + + /** Error text to show */ + errorText: PropTypes.string, + + ...withLocalizePropTypes, + + /** A ref forwarded to the TextInput */ + forwardedRef: PropTypes.func, +}; + +const defaultProps = { + onChangeText: () => {}, + value: '', + disabled: false, + errorText: '', + forwardedRef: () => {}, +}; + +class RoomNameInput extends Component { + constructor(props) { + super(props); + + this.setModifiedRoomName = this.setModifiedRoomName.bind(this); + this.setSelection = this.setSelection.bind(this); + + this.state = { + selection: {start: 0, end: 0}, + }; + } + + /** + * Calls the onChangeText callback with a modified room name + * @param {Event} event + */ + setModifiedRoomName(event) { + const roomName = event.nativeEvent.text; + const modifiedRoomName = this.modifyRoomName(roomName); + this.props.onChangeText(modifiedRoomName); + + // Prevent cursor jump behaviour: + // Check if newRoomNameWithHash is the same as modifiedRoomName + // If it is then the room name is valid (does not contain unallowed characters); no action required + // If not then the room name contains unvalid characters and we must adjust the cursor position manually + // Read more: https://github.com/Expensify/App/issues/12741 + const oldRoomNameWithHash = this.props.value || ''; + const newRoomNameWithHash = `${CONST.POLICY.ROOM_PREFIX}${roomName}`; + if (modifiedRoomName !== newRoomNameWithHash) { + const offset = modifiedRoomName.length - oldRoomNameWithHash.length; + const selection = { + start: this.state.selection.start + offset, + end: this.state.selection.end + offset, + } + this.setSelection(selection); + } + } + + /** + * Set the selection + * @param {Object} selection + */ + setSelection(selection) { + this.setState({ + selection: selection, + }); + } + + /** + * Modifies the room name to follow our conventions: + * - Max length 80 characters + * - Cannot not include space or special characters, and we automatically apply an underscore for spaces + * - Must be lowercase + * @param {String} roomName + * @returns {String} + */ + modifyRoomName(roomName) { + const modifiedRoomNameWithoutHash = roomName + .replace(/ /g, '_') + .replace(/[^a-zA-Z\d_]/g, '') + .substr(0, CONST.REPORT.MAX_ROOM_NAME_LENGTH) + .toLowerCase(); + + return `${CONST.POLICY.ROOM_PREFIX}${modifiedRoomNameWithoutHash}`; + } + + render() { + return ( + this.setSelection(event.nativeEvent.selection)} + errorText={this.props.errorText} + autoCapitalize="none" + /> + ); + } +} + +RoomNameInput.propTypes = propTypes; +RoomNameInput.defaultProps = defaultProps; + +export default withLocalize( + React.forwardRef((props, ref) => ( + // eslint-disable-next-line react/jsx-props-no-spreading + + )), +); diff --git a/src/components/RoomNameInput.js b/src/components/RoomNameInput/index.native.js similarity index 95% rename from src/components/RoomNameInput.js rename to src/components/RoomNameInput/index.native.js index 95e016cd6a1..1bb6170c0eb 100644 --- a/src/components/RoomNameInput.js +++ b/src/components/RoomNameInput/index.native.js @@ -1,8 +1,8 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; -import CONST from '../CONST'; -import withLocalize, {withLocalizePropTypes} from './withLocalize'; -import TextInput from './TextInput'; +import CONST from '../../CONST'; +import withLocalize, {withLocalizePropTypes} from '../withLocalize'; +import TextInput from '../TextInput'; const propTypes = { /** Callback to execute when the text input is modified correctly */ From af7a844330e97e417bd837277d988ad9ae583284 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Mon, 21 Nov 2022 20:24:50 +0100 Subject: [PATCH 2/8] Fixed lint errors --- src/components/RoomNameInput/index.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js index d60d10956f3..a6fc0efb159 100644 --- a/src/components/RoomNameInput/index.js +++ b/src/components/RoomNameInput/index.js @@ -64,7 +64,7 @@ class RoomNameInput extends Component { const selection = { start: this.state.selection.start + offset, end: this.state.selection.end + offset, - } + }; this.setSelection(selection); } } @@ -74,9 +74,7 @@ class RoomNameInput extends Component { * @param {Object} selection */ setSelection(selection) { - this.setState({ - selection: selection, - }); + this.setState({selection}); } /** @@ -108,7 +106,7 @@ class RoomNameInput extends Component { onChange={this.setModifiedRoomName} value={this.props.value.substring(1)} // Since the room name always starts with a prefix, we omit the first character to avoid displaying it twice. selection={this.state.selection} - onSelectionChange={(event) => this.setSelection(event.nativeEvent.selection)} + onSelectionChange={event => this.setSelection(event.nativeEvent.selection)} errorText={this.props.errorText} autoCapitalize="none" /> From c5aca3ab1085eab62c9a095d0373b68801448540 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Tue, 22 Nov 2022 14:58:59 +0100 Subject: [PATCH 3/8] moved propTypes and defaultProps to roomNameInputPropTypes.js --- .../RoomNameInput/roomNameInputPropTypes.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/components/RoomNameInput/roomNameInputPropTypes.js diff --git a/src/components/RoomNameInput/roomNameInputPropTypes.js b/src/components/RoomNameInput/roomNameInputPropTypes.js new file mode 100644 index 00000000000..f99379cc53a --- /dev/null +++ b/src/components/RoomNameInput/roomNameInputPropTypes.js @@ -0,0 +1,30 @@ +import PropTypes from 'prop-types'; + +const propTypes = { + /** Callback to execute when the text input is modified correctly */ + onChangeText: PropTypes.func, + + /** Room name to show in input field. This should include the '#' already prefixed to the name */ + value: PropTypes.string, + + /** Whether we should show the input as disabled */ + disabled: PropTypes.bool, + + /** Error text to show */ + errorText: PropTypes.string, + + ...withLocalizePropTypes, + + /** A ref forwarded to the TextInput */ + forwardedRef: PropTypes.func, +}; + +const defaultProps = { + onChangeText: () => {}, + value: '', + disabled: false, + errorText: '', + forwardedRef: () => {}, +}; + +export {propTypes, defaultProps}; From e246296eca17118d7d6e5783afc4dde7732ca887 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Tue, 22 Nov 2022 15:01:04 +0100 Subject: [PATCH 4/8] moved propTypes and defaultProps to roomNameInputPropTypes.js (2) --- src/components/RoomNameInput/index.js | 32 ++------------------ src/components/RoomNameInput/index.native.js | 32 ++------------------ 2 files changed, 6 insertions(+), 58 deletions(-) diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js index a6fc0efb159..cc0d0ff7e0c 100644 --- a/src/components/RoomNameInput/index.js +++ b/src/components/RoomNameInput/index.js @@ -3,33 +3,7 @@ import PropTypes from 'prop-types'; import CONST from '../../CONST'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import TextInput from '../TextInput'; - -const propTypes = { - /** Callback to execute when the text input is modified correctly */ - onChangeText: PropTypes.func, - - /** Room name to show in input field. This should include the '#' already prefixed to the name */ - value: PropTypes.string, - - /** Whether we should show the input as disabled */ - disabled: PropTypes.bool, - - /** Error text to show */ - errorText: PropTypes.string, - - ...withLocalizePropTypes, - - /** A ref forwarded to the TextInput */ - forwardedRef: PropTypes.func, -}; - -const defaultProps = { - onChangeText: () => {}, - value: '', - disabled: false, - errorText: '', - forwardedRef: () => {}, -}; +import * as roomNameInputPropTypes from './roomNameInputPropTypes'; class RoomNameInput extends Component { constructor(props) { @@ -114,8 +88,8 @@ class RoomNameInput extends Component { } } -RoomNameInput.propTypes = propTypes; -RoomNameInput.defaultProps = defaultProps; +RoomNameInput.propTypes = roomNameInputPropTypes.propTypes; +RoomNameInput.defaultProps = roomNameInputPropTypes.defaultProps; export default withLocalize( React.forwardRef((props, ref) => ( diff --git a/src/components/RoomNameInput/index.native.js b/src/components/RoomNameInput/index.native.js index 1bb6170c0eb..16209349247 100644 --- a/src/components/RoomNameInput/index.native.js +++ b/src/components/RoomNameInput/index.native.js @@ -3,33 +3,7 @@ import PropTypes from 'prop-types'; import CONST from '../../CONST'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import TextInput from '../TextInput'; - -const propTypes = { - /** Callback to execute when the text input is modified correctly */ - onChangeText: PropTypes.func, - - /** Room name to show in input field. This should include the '#' already prefixed to the name */ - value: PropTypes.string, - - /** Whether we should show the input as disabled */ - disabled: PropTypes.bool, - - /** Error text to show */ - errorText: PropTypes.string, - - ...withLocalizePropTypes, - - /** A ref forwarded to the TextInput */ - forwardedRef: PropTypes.func, -}; - -const defaultProps = { - onChangeText: () => {}, - value: '', - disabled: false, - errorText: '', - forwardedRef: () => {}, -}; +import * as roomNameInputPropTypes from './roomNameInputPropTypes'; class RoomNameInput extends Component { constructor(props) { @@ -83,8 +57,8 @@ class RoomNameInput extends Component { } } -RoomNameInput.propTypes = propTypes; -RoomNameInput.defaultProps = defaultProps; +RoomNameInput.propTypes = roomNameInputPropTypes.propTypes; +RoomNameInput.defaultProps = roomNameInputPropTypes.defaultProps; export default withLocalize( React.forwardRef((props, ref) => ( From 494d6f17642104a749dbddd4c39a988bdf709e7a Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Tue, 22 Nov 2022 15:03:10 +0100 Subject: [PATCH 5/8] Set default selection to undefined --- src/components/RoomNameInput/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js index cc0d0ff7e0c..787824e2ec9 100644 --- a/src/components/RoomNameInput/index.js +++ b/src/components/RoomNameInput/index.js @@ -13,7 +13,7 @@ class RoomNameInput extends Component { this.setSelection = this.setSelection.bind(this); this.state = { - selection: {start: 0, end: 0}, + selection: undefined, }; } From 3ffbb01789ad2799e1737ce8bbf4747140b5abd9 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Tue, 22 Nov 2022 15:07:13 +0100 Subject: [PATCH 6/8] Fixed lint issue withLocalizePropTypes not defined --- src/components/RoomNameInput/index.js | 2 +- src/components/RoomNameInput/index.native.js | 2 +- src/components/RoomNameInput/roomNameInputPropTypes.js | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js index 787824e2ec9..27712fe1762 100644 --- a/src/components/RoomNameInput/index.js +++ b/src/components/RoomNameInput/index.js @@ -1,7 +1,7 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; import CONST from '../../CONST'; -import withLocalize, {withLocalizePropTypes} from '../withLocalize'; +import withLocalize from '../withLocalize'; import TextInput from '../TextInput'; import * as roomNameInputPropTypes from './roomNameInputPropTypes'; diff --git a/src/components/RoomNameInput/index.native.js b/src/components/RoomNameInput/index.native.js index 16209349247..23c18fb0e50 100644 --- a/src/components/RoomNameInput/index.native.js +++ b/src/components/RoomNameInput/index.native.js @@ -1,7 +1,7 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; import CONST from '../../CONST'; -import withLocalize, {withLocalizePropTypes} from '../withLocalize'; +import withLocalize from '../withLocalize'; import TextInput from '../TextInput'; import * as roomNameInputPropTypes from './roomNameInputPropTypes'; diff --git a/src/components/RoomNameInput/roomNameInputPropTypes.js b/src/components/RoomNameInput/roomNameInputPropTypes.js index f99379cc53a..db5ce4b3c0c 100644 --- a/src/components/RoomNameInput/roomNameInputPropTypes.js +++ b/src/components/RoomNameInput/roomNameInputPropTypes.js @@ -1,4 +1,5 @@ import PropTypes from 'prop-types'; +import {withLocalizePropTypes} from '../withLocalize'; const propTypes = { /** Callback to execute when the text input is modified correctly */ From f947319ecc4c01a771518dae0e436bc4b9c93d55 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Tue, 22 Nov 2022 15:13:39 +0100 Subject: [PATCH 7/8] Fix lint PropTypes is defined and never used --- src/components/RoomNameInput/index.js | 1 - src/components/RoomNameInput/index.native.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js index 27712fe1762..aa18efd9e5d 100644 --- a/src/components/RoomNameInput/index.js +++ b/src/components/RoomNameInput/index.js @@ -1,5 +1,4 @@ import React, {Component} from 'react'; -import PropTypes from 'prop-types'; import CONST from '../../CONST'; import withLocalize from '../withLocalize'; import TextInput from '../TextInput'; diff --git a/src/components/RoomNameInput/index.native.js b/src/components/RoomNameInput/index.native.js index 23c18fb0e50..e129fe965b5 100644 --- a/src/components/RoomNameInput/index.native.js +++ b/src/components/RoomNameInput/index.native.js @@ -1,5 +1,4 @@ import React, {Component} from 'react'; -import PropTypes from 'prop-types'; import CONST from '../../CONST'; import withLocalize from '../withLocalize'; import TextInput from '../TextInput'; From d36f0d89d2d0ee2ca691560d398738e213ff62f2 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Tue, 22 Nov 2022 16:06:47 +0100 Subject: [PATCH 8/8] Moved modifyRoomName function to RoomNameInputUtils --- src/components/RoomNameInput/index.js | 21 ++--------------- src/components/RoomNameInput/index.native.js | 21 ++--------------- src/libs/RoomNameInputUtils.js | 24 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 38 deletions(-) create mode 100644 src/libs/RoomNameInputUtils.js diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js index aa18efd9e5d..6c015ec361d 100644 --- a/src/components/RoomNameInput/index.js +++ b/src/components/RoomNameInput/index.js @@ -3,6 +3,7 @@ import CONST from '../../CONST'; import withLocalize from '../withLocalize'; import TextInput from '../TextInput'; import * as roomNameInputPropTypes from './roomNameInputPropTypes'; +import * as RoomNameInputUtils from '../../libs/RoomNameInputUtils'; class RoomNameInput extends Component { constructor(props) { @@ -22,7 +23,7 @@ class RoomNameInput extends Component { */ setModifiedRoomName(event) { const roomName = event.nativeEvent.text; - const modifiedRoomName = this.modifyRoomName(roomName); + const modifiedRoomName = RoomNameInputUtils.modifyRoomName(roomName); this.props.onChangeText(modifiedRoomName); // Prevent cursor jump behaviour: @@ -50,24 +51,6 @@ class RoomNameInput extends Component { this.setState({selection}); } - /** - * Modifies the room name to follow our conventions: - * - Max length 80 characters - * - Cannot not include space or special characters, and we automatically apply an underscore for spaces - * - Must be lowercase - * @param {String} roomName - * @returns {String} - */ - modifyRoomName(roomName) { - const modifiedRoomNameWithoutHash = roomName - .replace(/ /g, '_') - .replace(/[^a-zA-Z\d_]/g, '') - .substr(0, CONST.REPORT.MAX_ROOM_NAME_LENGTH) - .toLowerCase(); - - return `${CONST.POLICY.ROOM_PREFIX}${modifiedRoomNameWithoutHash}`; - } - render() { return (