RN-335 Reaction shortcut (#920)

* RN-335 Reaction shortcut

* Review feedback
This commit is contained in:
Chris Duarte 2017-09-20 08:08:17 -07:00 committed by Harrison Healey
parent ad80fd37c7
commit 8694f59e80
5 changed files with 77 additions and 19 deletions

View file

@ -0,0 +1,17 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {addReaction} from 'mattermost-redux/actions/posts';
import {getPostsInCurrentChannel, makeGetPostsForThread} from 'mattermost-redux/selectors/entities/posts';
const getPostsForThread = makeGetPostsForThread();
export function addReactionToLatestPost(emoji, rootId) {
return async (dispatch, getState) => {
const state = getState();
const posts = rootId ? getPostsForThread(state, {rootId}) : getPostsInCurrentChannel(state);
const lastPost = posts[0];
dispatch(addReaction(lastPost.id, emoji));
};
}

View file

@ -13,15 +13,19 @@ import {
import Emoji from 'app/components/emoji';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
const EMOJI_REGEX = /\B(:([^:\r\n\s]*))$/i;
const EMOJI_REGEX = /(^|\s|^\+|^-)(:([^:\s]*))$/i;
export default class EmojiSuggestion extends Component {
static propTypes = {
actions: PropTypes.shape({
addReactionToLatestPost: PropTypes.func.isRequired
}).isRequired,
cursorPosition: PropTypes.number,
emojis: PropTypes.array.isRequired,
postDraft: PropTypes.string,
theme: PropTypes.object.isRequired,
onChangeText: PropTypes.func.isRequired
onChangeText: PropTypes.func.isRequired,
rootId: PropTypes.string
};
static defaultProps = {
@ -47,7 +51,7 @@ export default class EmojiSuggestion extends Component {
return;
}
const matchTerm = match[2];
const matchTerm = match[3];
if (matchTerm !== this.state.matchTerm) {
this.setState({
matchTerm
@ -70,16 +74,22 @@ export default class EmojiSuggestion extends Component {
}
completeSuggestion = (emoji) => {
const {cursorPosition, onChangeText, postDraft} = this.props;
const {actions, cursorPosition, onChangeText, postDraft, rootId} = this.props;
const emojiPart = postDraft.substring(0, cursorPosition);
let completedDraft = emojiPart.replace(EMOJI_REGEX, `:${emoji}: `);
if (emojiPart.startsWith('+:')) {
actions.addReactionToLatestPost(emoji, rootId);
onChangeText('');
} else {
let completedDraft = emojiPart.replace(EMOJI_REGEX, `:${emoji}: `);
if (postDraft.length > cursorPosition) {
completedDraft += postDraft.substring(cursorPosition);
if (postDraft.length > cursorPosition) {
completedDraft += postDraft.substring(cursorPosition);
}
onChangeText(completedDraft);
}
onChangeText(completedDraft);
this.setState({
active: false,
emojiComplete: true

View file

@ -3,9 +3,11 @@
import {connect} from 'react-redux';
import {createSelector} from 'reselect';
import {bindActionCreators} from 'redux';
import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
import {addReactionToLatestPost} from 'app/actions/views/emoji';
import {getTheme} from 'app/selectors/preferences';
import {EmojiIndicesByAlias} from 'app/utils/emojis';
@ -47,4 +49,12 @@ function mapStateToProps(state, ownProps) {
};
}
export default connect(mapStateToProps)(EmojiSuggestion);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
addReactionToLatestPost
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(EmojiSuggestion);

View file

@ -6,6 +6,7 @@ import {connect} from 'react-redux';
import {createPost} from 'mattermost-redux/actions/posts';
import {userTyping} from 'mattermost-redux/actions/websocket';
import {addReactionToLatestPost} from 'app/actions/views/emoji';
import {handleClearFiles, handleRemoveLastFile, handleUploadFiles} from 'app/actions/views/file_upload';
import {getTheme} from 'app/selectors/preferences';
import {canUploadFilesOnMobile} from 'mattermost-redux/selectors/entities/general';
@ -29,6 +30,7 @@ function mapStateToProps(state, ownProps) {
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
addReactionToLatestPost,
createPost,
handleClearFiles,
handleRemoveLastFile,

View file

@ -27,10 +27,12 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
const INITIAL_HEIGHT = Platform.OS === 'ios' ? 34 : 36;
const MAX_CONTENT_HEIGHT = 100;
const MAX_MESSAGE_LENGTH = 4000;
const IS_REACTION_REGEX = /(^\+:([^:\s]*):)$/i;
class PostTextbox extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
addReactionToLatestPost: PropTypes.func.isRequired,
createPost: PropTypes.func.isRequired,
handleClearFiles: PropTypes.func.isRequired,
handleRemoveLastFile: PropTypes.func.isRequired,
@ -137,7 +139,16 @@ class PostTextbox extends PureComponent {
return;
}
const hasFailedImages = this.props.files.some((f) => f.failed);
const {files, value} = this.props;
const isReactionMatch = value.match(IS_REACTION_REGEX);
if (isReactionMatch) {
const emoji = isReactionMatch[2];
this.sendReaction(emoji);
return;
}
const hasFailedImages = files.some((f) => f.failed);
if (hasFailedImages) {
const {intl} = this.props;
@ -163,19 +174,21 @@ class PostTextbox extends PureComponent {
};
sendMessage = () => {
const files = this.props.files.filter((f) => !f.failed);
const {actions, currentUserId, channelId, files, rootId, value} = this.props;
const postFiles = files.filter((f) => !f.failed);
const post = {
user_id: this.props.currentUserId,
channel_id: this.props.channelId,
root_id: this.props.rootId,
parent_id: this.props.rootId,
message: this.props.value
user_id: currentUserId,
channel_id: channelId,
root_id: rootId,
parent_id: rootId,
message: value
};
this.props.actions.createPost(post, files);
actions.createPost(post, postFiles);
this.handleTextChange('');
if (this.props.files.length) {
this.props.actions.handleClearFiles(this.props.channelId, this.props.rootId);
if (postFiles.length) {
actions.handleClearFiles(channelId, rootId);
}
// Shrink the input textbox since the layout events lag slightly
@ -184,6 +197,12 @@ class PostTextbox extends PureComponent {
});
};
sendReaction = (emoji) => {
const {actions, rootId} = this.props;
actions.addReactionToLatestPost(emoji, rootId);
this.handleTextChange('');
}
handleTextChange = (text) => {
const {
onChangeText,