Improve performance on post textbox (#1138)
This commit is contained in:
parent
f6d829c445
commit
49899b2a00
10 changed files with 115 additions and 141 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export default class PaperClipIcon extends Component {
|
|||
if (this.root) {
|
||||
this.root.setNativeProps(nativeProps);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View style={[style.sendButton, style.disableButton]}>
|
||||
<PaperPlane
|
||||
height={13}
|
||||
width={15}
|
||||
color={theme.buttonColor}
|
||||
/>
|
||||
<View style={style.sendButtonContainer}>
|
||||
<View style={[style.sendButton, style.disableButton]}>
|
||||
<PaperPlane
|
||||
height={13}
|
||||
width={15}
|
||||
color={theme.buttonColor}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
@ -273,13 +288,15 @@ class PostTextbox extends PureComponent {
|
|||
return (
|
||||
<TouchableOpacity
|
||||
onPress={this.handleSendMessage}
|
||||
style={style.sendButton}
|
||||
style={style.sendButtonContainer}
|
||||
>
|
||||
<PaperPlane
|
||||
height={13}
|
||||
width={15}
|
||||
color={theme.buttonColor}
|
||||
/>
|
||||
<View style={style.sendButton}>
|
||||
<PaperPlane
|
||||
height={13}
|
||||
width={15}
|
||||
color={theme.buttonColor}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 {
|
|||
/>
|
||||
<Autocomplete
|
||||
ref={this.attachAutocomplete}
|
||||
onChangeText={this.changeDraft}
|
||||
rootId={this.props.rootId}
|
||||
onChangeText={this.handleTextChange}
|
||||
value={this.state.value}
|
||||
/>
|
||||
<View style={style.inputWrapper}>
|
||||
{attachmentButton}
|
||||
|
|
@ -402,6 +421,7 @@ class PostTextbox extends PureComponent {
|
|||
keyboardType={this.state.keyboardType}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
onEndEditing={this.handleEndEditing}
|
||||
/>
|
||||
{this.renderSendButton()}
|
||||
</View>
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -627,6 +627,7 @@ class Search extends PureComponent {
|
|||
ref={this.attachAutocomplete}
|
||||
onChangeText={this.handleTextChanged}
|
||||
isSearch={true}
|
||||
value={value}
|
||||
/>
|
||||
{previewComponent}
|
||||
</View>
|
||||
|
|
|
|||
Loading…
Reference in a new issue