diff --git a/app/components/autocomplete/at_mention/at_mention.js b/app/components/autocomplete/at_mention/at_mention.js index 85f606880..fc4284aca 100644 --- a/app/components/autocomplete/at_mention/at_mention.js +++ b/app/components/autocomplete/at_mention/at_mention.js @@ -27,16 +27,16 @@ export default class AtMention extends PureComponent { matchTerm: PropTypes.string, onChangeText: PropTypes.func.isRequired, outChannel: PropTypes.array, - postDraft: PropTypes.string, requestStatus: PropTypes.string.isRequired, teamMembers: PropTypes.array, - theme: PropTypes.object.isRequired + theme: PropTypes.object.isRequired, + value: PropTypes.string }; static defaultProps = { defaultChannel: {}, - postDraft: '', - isSearch: false + isSearch: false, + value: '' }; constructor(props) { @@ -144,8 +144,8 @@ export default class AtMention extends PureComponent { }; completeMention = (mention) => { - const {cursorPosition, isSearch, onChangeText, postDraft} = this.props; - const mentionPart = postDraft.substring(0, cursorPosition); + const {cursorPosition, isSearch, onChangeText, value} = this.props; + const mentionPart = value.substring(0, cursorPosition); let completedDraft; if (isSearch) { @@ -154,8 +154,8 @@ export default class AtMention extends PureComponent { completedDraft = mentionPart.replace(AT_MENTION_REGEX, `@${mention} `); } - if (postDraft.length > cursorPosition) { - completedDraft += postDraft.substring(cursorPosition); + if (value.length > cursorPosition) { + completedDraft += value.substring(cursorPosition); } onChangeText(completedDraft, true); diff --git a/app/components/autocomplete/at_mention/index.js b/app/components/autocomplete/at_mention/index.js index f6b5c1435..767797b61 100644 --- a/app/components/autocomplete/at_mention/index.js +++ b/app/components/autocomplete/at_mention/index.js @@ -19,25 +19,10 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import AtMention from './at_mention'; function mapStateToProps(state, ownProps) { - const {cursorPosition, isSearch, rootId} = ownProps; + const {cursorPosition, isSearch} = ownProps; const currentChannelId = getCurrentChannelId(state); - let postDraft = ''; - if (isSearch) { - postDraft = state.views.search; - } else if (ownProps.rootId) { - const threadDraft = state.views.thread.drafts[rootId]; - if (threadDraft) { - postDraft = threadDraft.draft; - } - } else if (currentChannelId) { - const channelDraft = state.views.channel.drafts[currentChannelId]; - if (channelDraft) { - postDraft = channelDraft.draft; - } - } - - const value = postDraft.substring(0, cursorPosition); + const value = ownProps.value.substring(0, cursorPosition); const matchTerm = getMatchTermForAtMention(value, isSearch); let teamMembers; @@ -54,14 +39,12 @@ function mapStateToProps(state, ownProps) { currentChannelId, currentTeamId: getCurrentTeamId(state), defaultChannel: getDefaultChannel(state), - postDraft, matchTerm, teamMembers, inChannel, outChannel, requestStatus: state.requests.users.autocompleteUsers.status, - theme: getTheme(state), - ...ownProps + theme: getTheme(state) }; } diff --git a/app/components/autocomplete/channel_mention/channel_mention.js b/app/components/autocomplete/channel_mention/channel_mention.js index af898187c..ad6eb0605 100644 --- a/app/components/autocomplete/channel_mention/channel_mention.js +++ b/app/components/autocomplete/channel_mention/channel_mention.js @@ -24,16 +24,16 @@ export default class ChannelMention extends PureComponent { myChannels: PropTypes.array, otherChannels: PropTypes.array, onChangeText: PropTypes.func.isRequired, - postDraft: PropTypes.string, privateChannels: PropTypes.array, publicChannels: PropTypes.array, requestStatus: PropTypes.string.isRequired, - theme: PropTypes.object.isRequired + theme: PropTypes.object.isRequired, + value: PropTypes.string }; static defaultProps = { - postDraft: '', - isSearch: false + isSearch: false, + value: '' }; constructor(props) { @@ -116,8 +116,8 @@ export default class ChannelMention extends PureComponent { } completeMention = (mention) => { - const {cursorPosition, isSearch, onChangeText, postDraft} = this.props; - const mentionPart = postDraft.substring(0, cursorPosition); + const {cursorPosition, isSearch, onChangeText, value} = this.props; + const mentionPart = value.substring(0, cursorPosition); let completedDraft; if (isSearch) { @@ -127,8 +127,8 @@ export default class ChannelMention extends PureComponent { completedDraft = mentionPart.replace(CHANNEL_MENTION_REGEX, `~${mention} `); } - if (postDraft.length > cursorPosition) { - completedDraft += postDraft.substring(cursorPosition); + if (value.length > cursorPosition) { + completedDraft += value.substring(cursorPosition); } onChangeText(completedDraft, true); diff --git a/app/components/autocomplete/channel_mention/index.js b/app/components/autocomplete/channel_mention/index.js index 54c3740b8..273aaaead 100644 --- a/app/components/autocomplete/channel_mention/index.js +++ b/app/components/autocomplete/channel_mention/index.js @@ -5,7 +5,6 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; import {searchChannels} from 'mattermost-redux/actions/channels'; -import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels'; import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; import { @@ -20,25 +19,9 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import ChannelMention from './channel_mention'; function mapStateToProps(state, ownProps) { - const {cursorPosition, isSearch, rootId} = ownProps; - const currentChannelId = getCurrentChannelId(state); + const {cursorPosition, isSearch} = ownProps; - let postDraft = ''; - if (isSearch) { - postDraft = state.views.search; - } else if (rootId) { - const threadDraft = state.views.thread.drafts[rootId]; - if (threadDraft) { - postDraft = threadDraft.draft; - } - } else if (currentChannelId) { - const channelDraft = state.views.channel.drafts[currentChannelId]; - if (channelDraft) { - postDraft = channelDraft.draft; - } - } - - const value = postDraft.substring(0, cursorPosition); + const value = ownProps.value.substring(0, cursorPosition); const matchTerm = getMatchTermForChannelMention(value, isSearch); let myChannels; @@ -61,7 +44,6 @@ function mapStateToProps(state, ownProps) { privateChannels, currentTeamId: getCurrentTeamId(state), matchTerm, - postDraft, requestStatus: state.requests.channels.getChannels.status, theme: getTheme(state) }; diff --git a/app/components/autocomplete/emoji_suggestion/emoji_suggestion.js b/app/components/autocomplete/emoji_suggestion/emoji_suggestion.js index 33896b903..d4bf71806 100644 --- a/app/components/autocomplete/emoji_suggestion/emoji_suggestion.js +++ b/app/components/autocomplete/emoji_suggestion/emoji_suggestion.js @@ -22,25 +22,25 @@ export default class EmojiSuggestion extends Component { }).isRequired, cursorPosition: PropTypes.number, emojis: PropTypes.array.isRequired, - postDraft: PropTypes.string, theme: PropTypes.object.isRequired, onChangeText: PropTypes.func.isRequired, - rootId: PropTypes.string + rootId: PropTypes.string, + value: PropTypes.string }; static defaultProps = { defaultChannel: {}, - postDraft: '' + value: '' }; state = { active: false, dataSource: [] - } + }; componentWillReceiveProps(nextProps) { const regex = EMOJI_REGEX; - const match = nextProps.postDraft.substring(0, nextProps.cursorPosition).match(regex); + const match = nextProps.value.substring(0, nextProps.cursorPosition).match(regex); if (!match || this.state.emojiComplete) { this.setState({ @@ -74,8 +74,8 @@ export default class EmojiSuggestion extends Component { } completeSuggestion = (emoji) => { - const {actions, cursorPosition, onChangeText, postDraft, rootId} = this.props; - const emojiPart = postDraft.substring(0, cursorPosition); + const {actions, cursorPosition, onChangeText, value, rootId} = this.props; + const emojiPart = value.substring(0, cursorPosition); if (emojiPart.startsWith('+:')) { actions.addReactionToLatestPost(emoji, rootId); @@ -83,8 +83,8 @@ export default class EmojiSuggestion extends Component { } else { let completedDraft = emojiPart.replace(EMOJI_REGEX, `:${emoji}: `); - if (postDraft.length > cursorPosition) { - completedDraft += postDraft.substring(cursorPosition); + if (value.length > cursorPosition) { + completedDraft += value.substring(cursorPosition); } onChangeText(completedDraft); diff --git a/app/components/autocomplete/emoji_suggestion/index.js b/app/components/autocomplete/emoji_suggestion/index.js index 81ea1d342..e4fd2e6bf 100644 --- a/app/components/autocomplete/emoji_suggestion/index.js +++ b/app/components/autocomplete/emoji_suggestion/index.js @@ -25,26 +25,11 @@ const getEmojisByName = createSelector( } ); -function mapStateToProps(state, ownProps) { - const {currentChannelId} = state.entities.channels; +function mapStateToProps(state) { const emojis = getEmojisByName(state); - let postDraft; - if (ownProps.rootId) { - const threadDraft = state.views.thread.drafts[ownProps.rootId]; - if (threadDraft) { - postDraft = threadDraft.draft; - } - } else if (currentChannelId) { - const channelDraft = state.views.channel.drafts[currentChannelId]; - if (channelDraft) { - postDraft = channelDraft.draft; - } - } - return { emojis, - postDraft, theme: getTheme(state) }; } diff --git a/app/components/autocomplete/index.js b/app/components/autocomplete/index.js index 96001bdfb..1b2877272 100644 --- a/app/components/autocomplete/index.js +++ b/app/components/autocomplete/index.js @@ -17,7 +17,8 @@ export default class Autocomplete extends PureComponent { static propTypes = { onChangeText: PropTypes.func.isRequired, rootId: PropTypes.string, - isSearch: PropTypes.bool + isSearch: PropTypes.bool, + value: PropTypes.string }; static defaultProps = { diff --git a/app/components/post_textbox/components/paper_clip_icon.js b/app/components/post_textbox/components/paper_clip_icon.js index 4d494051a..aa988e383 100644 --- a/app/components/post_textbox/components/paper_clip_icon.js +++ b/app/components/post_textbox/components/paper_clip_icon.js @@ -16,7 +16,7 @@ export default class PaperClipIcon extends Component { if (this.root) { this.root.setNativeProps(nativeProps); } - } + }; render() { return ( diff --git a/app/components/post_textbox/post_textbox.js b/app/components/post_textbox/post_textbox.js index 0b7c2af99..09ef27b52 100644 --- a/app/components/post_textbox/post_textbox.js +++ b/app/components/post_textbox/post_textbox.js @@ -3,16 +3,7 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; -import { - Alert, - BackHandler, - Keyboard, - Platform, - Text, - TextInput, - TouchableOpacity, - View -} from 'react-native'; +import {Alert, BackHandler, Keyboard, Platform, Text, TextInput, TouchableOpacity, View} from 'react-native'; import {injectIntl, intlShape} from 'react-intl'; import {RequestStatus} from 'mattermost-redux/constants'; @@ -62,11 +53,16 @@ class PostTextbox extends PureComponent { value: '' }; - state = { - contentHeight: INITIAL_HEIGHT, - inputWidth: null, - keyboardType: 'default' - }; + constructor(props) { + super(props); + + this.state = { + contentHeight: INITIAL_HEIGHT, + inputWidth: null, + keyboardType: 'default', + value: props.value + }; + } componentDidMount() { if (Platform.OS === 'android') { @@ -76,24 +72,8 @@ class PostTextbox extends PureComponent { } componentWillReceiveProps(nextProps) { - const {intl} = this.props; - const {value} = nextProps; - const valueLength = value.trim().length; - - if (valueLength > MAX_MESSAGE_LENGTH) { - Alert.alert( - intl.formatMessage({ - id: 'mobile.message_length.title', - defaultMessage: 'Message Length' - }), - intl.formatMessage({ - id: 'mobile.message_length.message', - defaultMessage: 'Your current message is too long. Current character count: {max}/{count}' - }, { - max: MAX_MESSAGE_LENGTH, - count: valueLength - }) - ); + if (nextProps.channelId !== this.props.channelId || nextProps.rootId !== this.props.rootId) { + this.setState({value: nextProps.value}); } } @@ -113,7 +93,8 @@ class PostTextbox extends PureComponent { }; canSend = () => { - const {files, uploadFileRequestStatus, value} = this.props; + const {files, uploadFileRequestStatus} = this.props; + const {value} = this.state; const valueLength = value.trim().length; if (files.length) { @@ -137,6 +118,27 @@ class PostTextbox extends PureComponent { } }; + checkMessageLength = (value) => { + const {intl} = this.props; + const valueLength = value.trim().length; + + if (valueLength > MAX_MESSAGE_LENGTH) { + Alert.alert( + intl.formatMessage({ + id: 'mobile.message_length.title', + defaultMessage: 'Message Length' + }), + intl.formatMessage({ + id: 'mobile.message_length.message', + defaultMessage: 'Your current message is too long. Current character count: {max}/{count}' + }, { + max: MAX_MESSAGE_LENGTH, + count: valueLength + }) + ); + } + }; + handleAndroidKeyboard = () => { this.blur(); }; @@ -169,6 +171,12 @@ class PostTextbox extends PureComponent { }); }; + handleEndEditing = (e) => { + if (e && e.nativeEvent) { + this.changeDraft(e.nativeEvent.text || ''); + } + }; + handleFocus = () => { if (this.refs.input && Platform.OS === 'android') { this.refs.input.setNativeProps({ @@ -199,7 +207,8 @@ class PostTextbox extends PureComponent { return; } - const {files, value} = this.props; + const {files} = this.props; + const {value} = this.state; const isReactionMatch = value.match(IS_REACTION_REGEX); if (isReactionMatch) { @@ -233,15 +242,19 @@ class PostTextbox extends PureComponent { } }; - handleTextChange = (text) => { + handleTextChange = (value) => { const { actions, channelId, rootId } = this.props; - this.changeDraft(text); - actions.userTyping(channelId, rootId); + this.checkMessageLength(value); + this.setState({value}); + + if (value) { + actions.userTyping(channelId, rootId); + } }; handleUploadFiles = (images) => { @@ -253,12 +266,14 @@ class PostTextbox extends PureComponent { const style = getStyleSheet(theme); return ( - - + + + + ); }; @@ -273,13 +288,15 @@ class PostTextbox extends PureComponent { return ( - + + + ); } @@ -288,7 +305,8 @@ class PostTextbox extends PureComponent { }; sendMessage = () => { - const {actions, currentUserId, channelId, files, rootId, value} = this.props; + const {actions, currentUserId, channelId, files, rootId} = this.props; + const {value} = this.state; const postFiles = files.filter((f) => !f.failed); const post = { @@ -301,6 +319,7 @@ class PostTextbox extends PureComponent { actions.createPost(post, postFiles); this.handleTextChange(''); + this.changeDraft(''); if (postFiles.length) { actions.handleClearFiles(channelId, rootId); } @@ -325,6 +344,7 @@ class PostTextbox extends PureComponent { const {actions, rootId} = this.props; actions.addReactionToLatestPost(emoji, rootId); this.handleTextChange(''); + this.changeDraft(''); }; render() { @@ -332,14 +352,13 @@ class PostTextbox extends PureComponent { canUploadFiles, channelIsLoading, intl, - theme, - value + theme } = this.props; const style = getStyleSheet(theme); const textInputHeight = Math.min(this.state.contentHeight, MAX_CONTENT_HEIGHT); - const textValue = channelIsLoading ? '' : value; + const textValue = channelIsLoading ? '' : this.state.value; let placeholder; if (this.props.rootId) { @@ -380,8 +399,8 @@ class PostTextbox extends PureComponent { /> {attachmentButton} @@ -402,6 +421,7 @@ class PostTextbox extends PureComponent { keyboardType={this.state.keyboardType} onFocus={this.handleFocus} onBlur={this.handleBlur} + onEndEditing={this.handleEndEditing} /> {this.renderSendButton()} @@ -438,8 +458,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { flex: 1, flexDirection: 'row', backgroundColor: '#fff', - alignItems: 'flex-end', - marginRight: 10 + alignItems: 'flex-end' }, inputContainerWithoutFileUpload: { marginLeft: 10 @@ -452,6 +471,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { borderTopWidth: 1, borderTopColor: changeOpacity(theme.centerChannelColor, 0.20) }, + sendButtonContainer: { + paddingRight: 10 + }, sendButton: { backgroundColor: theme.buttonBg, borderRadius: 18, diff --git a/app/screens/search/search.js b/app/screens/search/search.js index a55b7bf33..271f6cd60 100644 --- a/app/screens/search/search.js +++ b/app/screens/search/search.js @@ -627,6 +627,7 @@ class Search extends PureComponent { ref={this.attachAutocomplete} onChangeText={this.handleTextChanged} isSearch={true} + value={value} /> {previewComponent}