From b925627a35462bae36032d366e28723ef4de5d5d Mon Sep 17 00:00:00 2001 From: Guilherme Martini Date: Tue, 20 Feb 2018 15:20:43 -0300 Subject: [PATCH 1/6] Allow multiple mention tags Added multiple mention triggers and parsing mention text --- src/MentionsTextInput.js | 61 +++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 41d9d6b..ae2d175 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -2,12 +2,14 @@ import React, { Component } from 'react'; import { Text, View, + Alert, Animated, TextInput, FlatList, ViewPropTypes } from 'react-native'; import PropTypes from 'prop-types'; +import ParsedText from 'react-native-parsed-text'; export default class MentionsTextInput extends Component { constructor() { @@ -16,7 +18,8 @@ export default class MentionsTextInput extends Component { textInputHeight: "", isTrackingStarted: false, suggestionRowHeight: new Animated.Value(0), - + currentTrigger: "", + currentTriggerIndex: -1 } this.isTrackingStarted = false; this.previousChar = " "; @@ -29,20 +32,22 @@ export default class MentionsTextInput extends Component { } componentWillReceiveProps(nextProps) { - if (!nextProps.value) { + if (!nextProps.inputValue) { this.resetTextbox(); - } else if (this.isTrackingStarted && !nextProps.horizontal && nextProps.suggestionsData.length !== 0) { - const numOfRows = nextProps.MaxVisibleRowCount >= nextProps.suggestionsData.length ? nextProps.suggestionsData.length : nextProps.MaxVisibleRowCount; + } else if (this.isTrackingStarted && !nextProps.horizontal && nextProps.suggestionsData[this.state.currentTriggerIndex] && nextProps.suggestionsData[this.state.currentTriggerIndex].length !== 0) { + const numOfRows = nextProps.MaxVisibleRowCount >= nextProps.suggestionsData[this.state.currentTriggerIndex] ? nextProps.suggestionsData[this.state.currentTriggerIndex].length : nextProps.MaxVisibleRowCount; const height = numOfRows * nextProps.suggestionRowHeight; this.openSuggestionsPanel(height); } } - startTracking() { + startTracking(trigger) { this.isTrackingStarted = true; this.openSuggestionsPanel(); this.setState({ - isTrackingStarted: true + isTrackingStarted: true, + currentTrigger: trigger, + currentTriggerIndex: this.props.trigger.indexOf(trigger) }) } @@ -50,7 +55,9 @@ export default class MentionsTextInput extends Component { this.isTrackingStarted = false; this.closeSuggestionsPanel(); this.setState({ - isTrackingStarted: false + isTrackingStarted: false, + currentTrigger: "", + currentTriggerIndex: -1 }) } @@ -68,18 +75,24 @@ export default class MentionsTextInput extends Component { }).start(); } - updateSuggestions(lastKeyword) { - this.props.triggerCallback(lastKeyword); + updateSuggestions(lastKeyword, trigger) { + const triggerIndex = this.state.currentTriggerIndex + if (triggerIndex > -1) { + this.props.triggerCallback[triggerIndex](lastKeyword); + } + } identifyKeyword(val) { if (this.isTrackingStarted) { + const trigger = this.state.currentTrigger const boundary = this.props.triggerLocation === 'new-word-only' ? 'B' : ''; - const pattern = new RegExp(`\\${boundary}${this.props.trigger}[a-z0-9_-]+|\\${boundary}${this.props.trigger}`, `gi`); + const pattern = new RegExp(`\\${boundary}${trigger}[a-z0-9_-]+|\\${boundary}${trigger}`, `gi`); const keywordArray = val.match(pattern); if (keywordArray && !!keywordArray.length) { + this.state.currentTrigger = trigger const lastKeyword = keywordArray[keywordArray.length - 1]; - this.updateSuggestions(lastKeyword); + this.updateSuggestions(lastKeyword, trigger); } } } @@ -88,8 +101,8 @@ export default class MentionsTextInput extends Component { this.props.onChangeText(val); // pass changed text back const lastChar = val.substr(val.length - 1); const wordBoundry = (this.props.triggerLocation === 'new-word-only') ? this.previousChar.trim().length === 0 : true; - if (lastChar === this.props.trigger && wordBoundry) { - this.startTracking(); + if (this.props.trigger.indexOf(lastChar) > -1 && wordBoundry) { + this.startTracking(lastChar); } else if (lastChar === ' ' && this.state.isTrackingStarted || val === "") { this.stopTracking(); } @@ -112,9 +125,10 @@ export default class MentionsTextInput extends Component { horizontal={this.props.horizontal} ListEmptyComponent={this.props.loadingComponent} enableEmptySections={true} - data={this.props.suggestionsData} + data={this.props.suggestionsData[this.state.currentTriggerIndex]} keyExtractor={this.props.keyExtractor} - renderItem={(rowData) => { return this.props.renderSuggestionsRow(rowData, this.stopTracking.bind(this)) }} + inverted={true} + renderItem={(rowData) => { return this.props.renderSuggestionsRow[this.state.currentTriggerIndex](rowData, this.stopTracking.bind(this)) }} /> this._textInput = component} onChangeText={this.onChangeText.bind(this)} multiline={true} - value={this.props.value} style={[{ ...this.props.textInputStyle }, { height: Math.min(this.props.textInputMaxHeight, this.state.textInputHeight) }]} placeholder={this.props.placeholder ? this.props.placeholder : 'Write a comment...'} - /> + > + + {this.props.inputValue} + + + ) } @@ -145,12 +165,13 @@ MentionsTextInput.propTypes = { ]), textInputMinHeight: PropTypes.number, textInputMaxHeight: PropTypes.number, - trigger: PropTypes.string.isRequired, + trigger: PropTypes.array.isRequired, triggerLocation: PropTypes.oneOf(['new-word-only', 'anywhere']).isRequired, - value: PropTypes.string.isRequired, + inputValue: PropTypes.string.isRequired, onChangeText: PropTypes.func.isRequired, - triggerCallback: PropTypes.func.isRequired, + triggerCallback: PropTypes.array.isRequired, renderSuggestionsRow: PropTypes.oneOfType([ + PropTypes.array, PropTypes.func, PropTypes.element, ]).isRequired, From 9dca80664cce61a0f98b1fccaaf4815625ae6533 Mon Sep 17 00:00:00 2001 From: Guilherme Martini Date: Tue, 20 Feb 2018 16:09:54 -0300 Subject: [PATCH 2/6] Added parsed-text dependency --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 74c4ebe..4f288f1 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "type": "git", "url": "git+https://github.com/harshq/react-native-mentions.git" }, + "dependencies": { + "react-native-parsed-text": "^0.0.20" + }, "keywords": [ "react-component", "react-native", From a7003c9fdc54299d209b7acf3bc82fb5b0472b3a Mon Sep 17 00:00:00 2001 From: Guilherme Martini Date: Tue, 20 Feb 2018 16:13:42 -0300 Subject: [PATCH 3/6] Updated example --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0bbe3b9..d791c7b 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,13 @@ import MentionsTextInput from 'react-native-mentions'; loadingComponent={() => } textInputMinHeight={30} textInputMaxHeight={80} - trigger={'@'} + trigger={['@', '#']} triggerLocation={'new-word-only'} // 'new-word-only', 'anywhere' - value={this.state.value} + inputValue={this.state.value} onChangeText={(val) => { this.setState({ value: val }) }} - triggerCallback={this.callback.bind(this)} - renderSuggestionsRow={this.renderSuggestionsRow.bind(this)} - suggestionsData={this.state.data} // array of objects + triggerCallback={[this.callback1.bind(this), this.callback2.bind(this)]} + renderSuggestionsRow={[this.renderSuggestionsRow1.bind(this), this.renderSuggestionsRow2.bind(this)]} + suggestionsData={[this.state.data1, this.state.data2]} // array of objects keyExtractor={(item, index) => item.UserName} suggestionRowHeight={45} From b2daef79cb550b3abb8ba862b52dff27638cfa60 Mon Sep 17 00:00:00 2001 From: Guilherme Martini Date: Tue, 6 Mar 2018 08:23:29 -0300 Subject: [PATCH 4/6] Removed ParsedText from input --- src/MentionsTextInput.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index ae2d175..4993462 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -9,7 +9,7 @@ import { ViewPropTypes } from 'react-native'; import PropTypes from 'prop-types'; -import ParsedText from 'react-native-parsed-text'; +// import ParsedText from 'react-native-parsed-text'; export default class MentionsTextInput extends Component { constructor() { @@ -117,6 +117,11 @@ export default class MentionsTextInput extends Component { } render() { + // + // {this.props.inputValue} + // return ( @@ -144,11 +149,7 @@ export default class MentionsTextInput extends Component { style={[{ ...this.props.textInputStyle }, { height: Math.min(this.props.textInputMaxHeight, this.state.textInputHeight) }]} placeholder={this.props.placeholder ? this.props.placeholder : 'Write a comment...'} > - - {this.props.inputValue} - + {this.props.inputValue} From b81272936b778fc21d15e206d709e4b33bcc7292 Mon Sep 17 00:00:00 2001 From: Guilherme Martini Date: Thu, 29 Mar 2018 16:14:11 -0300 Subject: [PATCH 5/6] Accept receiving render function before and after input --- src/MentionsTextInput.js | 72 +++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 4993462..d56440a 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -8,9 +8,35 @@ import { FlatList, ViewPropTypes } from 'react-native'; +import Icon from 'react-native-vector-icons/FontAwesome'; import PropTypes from 'prop-types'; // import ParsedText from 'react-native-parsed-text'; +const styles = { + searchWrapper: { + overflow: 'hidden', + flexDirection: 'row', + backgroundColor: '#ffffff', + borderColor: '#f0f0f0', + borderWidth: 1, + borderRadius: 4, + justifyContent: 'space-between' + }, + searchIcon: { + padding: 10, + alignSelf: 'center', + }, + input: { + flex: 1, + paddingTop: 10, + paddingRight: 10, + paddingBottom: 10, + paddingLeft: 0, + backgroundColor: 'white', + color: '#424242' + } +} + export default class MentionsTextInput extends Component { constructor() { super(); @@ -31,6 +57,12 @@ export default class MentionsTextInput extends Component { }) } + componentDidMount() { + if (this.props.focus) { + this._textInput.focus() + } + } + componentWillReceiveProps(nextProps) { if (!nextProps.inputValue) { this.resetTextbox(); @@ -122,6 +154,12 @@ export default class MentionsTextInput extends Component { // childrenProps={{allowFontScaling: false}}> // {this.props.inputValue} // + const self = this; + if (this.props.focus) { + setTimeout(() => { + self._textInput.focus() + }, 500) + } return ( @@ -136,21 +174,25 @@ export default class MentionsTextInput extends Component { renderItem={(rowData) => { return this.props.renderSuggestionsRow[this.state.currentTriggerIndex](rowData, this.stopTracking.bind(this)) }} /> - { - this.setState({ - textInputHeight: this.props.textInputMinHeight >= event.nativeEvent.contentSize.height ? this.props.textInputMinHeight : event.nativeEvent.contentSize.height + 10, - }); - }} - ref={component => this._textInput = component} - onChangeText={this.onChangeText.bind(this)} - multiline={true} - style={[{ ...this.props.textInputStyle }, { height: Math.min(this.props.textInputMaxHeight, this.state.textInputHeight) }]} - placeholder={this.props.placeholder ? this.props.placeholder : 'Write a comment...'} - > - {this.props.inputValue} - + + {this.props.renderLeftSideIcon && this.props.renderLeftSideIcon()} + { + this.setState({ + textInputHeight: this.props.textInputMinHeight >= event.nativeEvent.contentSize.height ? this.props.textInputMinHeight : event.nativeEvent.contentSize.height + 10, + }); + }} + ref={component => this._textInput = component} + onChangeText={this.onChangeText.bind(this)} + multiline={true} + style={[{ ...this.props.textInputStyle }, { height: Math.min(this.props.textInputMaxHeight, this.state.textInputHeight) }]} + placeholder={this.props.placeholder ? this.props.placeholder : 'Write a comment...'} + > + {this.props.inputValue} + + {this.props.renderRightSideIcon && this.props.renderRightSideIcon()} + ) From 551d296546d747841a0adcb191865a8b5db3d51c Mon Sep 17 00:00:00 2001 From: Guilherme Martini Date: Fri, 6 Apr 2018 07:51:07 -0300 Subject: [PATCH 6/6] avoiding focus errors --- src/MentionsTextInput.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index d56440a..9cf331a 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -58,7 +58,7 @@ export default class MentionsTextInput extends Component { } componentDidMount() { - if (this.props.focus) { + if (this.props.focus && this._textInput) { this._textInput.focus() } } @@ -157,7 +157,9 @@ export default class MentionsTextInput extends Component { const self = this; if (this.props.focus) { setTimeout(() => { - self._textInput.focus() + if (self._textInput) { + self._textInput.focus() + } }, 500) } return (