From 8322e7e57632841c8d3be787081e819acb6935b4 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 20 Jul 2018 16:54:46 -0300 Subject: [PATCH] [NEW] Reply preview (#374) * Updated to React Native 0.56 * Reply Preview --- app/actions/actionsTypes.js | 4 +- app/actions/messages.js | 11 ++-- app/containers/MessageActions.js | 29 +++------ app/containers/MessageBox/ReplyPreview.js | 78 +++++++++++++++++++++++ app/containers/MessageBox/index.js | 66 +++++++++++++++---- app/containers/message/Markdown.js | 1 + app/reducers/messages.js | 14 ++++ app/sagas/messages.js | 8 +-- 8 files changed, 169 insertions(+), 42 deletions(-) create mode 100644 app/containers/MessageBox/ReplyPreview.js diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index 56fb4f21..961b88ee 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -65,8 +65,8 @@ export const MESSAGES = createRequestTypes('MESSAGES', [ 'TOGGLE_PIN_REQUEST', 'TOGGLE_PIN_SUCCESS', 'TOGGLE_PIN_FAILURE', - 'SET_INPUT', - 'CLEAR_INPUT', + 'REPLY_INIT', + 'REPLY_CANCEL', 'TOGGLE_REACTION_PICKER', 'REPLY_BROADCAST' ]); diff --git a/app/actions/messages.js b/app/actions/messages.js index b7b3ee69..1b8451c7 100644 --- a/app/actions/messages.js +++ b/app/actions/messages.js @@ -137,16 +137,17 @@ export function togglePinFailure(err) { }; } -export function setInput(message) { +export function replyInit(message, mention) { return { - type: types.MESSAGES.SET_INPUT, - message + type: types.MESSAGES.REPLY_INIT, + message, + mention }; } -export function clearInput() { +export function replyCancel() { return { - type: types.MESSAGES.CLEAR_INPUT + type: types.MESSAGES.REPLY_CANCEL }; } diff --git a/app/containers/MessageActions.js b/app/containers/MessageActions.js index ccb25a79..7eec6433 100644 --- a/app/containers/MessageActions.js +++ b/app/containers/MessageActions.js @@ -10,9 +10,9 @@ import { editInit, toggleStarRequest, togglePinRequest, - setInput, actionsHide, - toggleReactionPicker + toggleReactionPicker, + replyInit } from '../actions/messages'; import { showToast } from '../utils/info'; import RocketChat from '../lib/rocketchat'; @@ -34,8 +34,8 @@ import I18n from '../i18n'; editInit: message => dispatch(editInit(message)), toggleStarRequest: message => dispatch(toggleStarRequest(message)), togglePinRequest: message => dispatch(togglePinRequest(message)), - setInput: message => dispatch(setInput(message)), - toggleReactionPicker: message => dispatch(toggleReactionPicker(message)) + toggleReactionPicker: message => dispatch(toggleReactionPicker(message)), + replyInit: (message, mention) => dispatch(replyInit(message, mention)) }) ) export default class MessageActions extends React.Component { @@ -43,13 +43,13 @@ export default class MessageActions extends React.Component { actionsHide: PropTypes.func.isRequired, room: PropTypes.object.isRequired, actionMessage: PropTypes.object, - user: PropTypes.object.isRequired, + // user: PropTypes.object.isRequired, deleteRequest: PropTypes.func.isRequired, editInit: PropTypes.func.isRequired, toggleStarRequest: PropTypes.func.isRequired, togglePinRequest: PropTypes.func.isRequired, - setInput: PropTypes.func.isRequired, toggleReactionPicker: PropTypes.func.isRequired, + replyInit: PropTypes.func.isRequired, Message_AllowDeleting: PropTypes.bool, Message_AllowDeleting_BlockDeleteInMinutes: PropTypes.number, Message_AllowEditing: PropTypes.bool, @@ -248,21 +248,12 @@ export default class MessageActions extends React.Component { this.props.togglePinRequest(this.props.actionMessage); } - async handleReply() { - const permalink = await this.getPermalink(this.props.actionMessage); - let msg = `[ ](${ permalink }) `; - - // if original message wasn't sent by current user and neither from a direct room - if (this.props.user.username !== this.props.actionMessage.u.username && this.props.room.t !== 'd') { - msg += `@${ this.props.actionMessage.u.username } `; - } - this.props.setInput({ msg }); + handleReply() { + this.props.replyInit(this.props.actionMessage, true); } - async handleQuote() { - const permalink = await this.getPermalink(this.props.actionMessage); - const msg = `[ ](${ permalink }) `; - this.props.setInput({ msg }); + handleQuote() { + this.props.replyInit(this.props.actionMessage, false); } handleReaction() { diff --git a/app/containers/MessageBox/ReplyPreview.js b/app/containers/MessageBox/ReplyPreview.js new file mode 100644 index 00000000..d71816c4 --- /dev/null +++ b/app/containers/MessageBox/ReplyPreview.js @@ -0,0 +1,78 @@ +import React, { Component } from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import PropTypes from 'prop-types'; +import moment from 'moment'; +import { connect } from 'react-redux'; +import Icon from 'react-native-vector-icons/MaterialIcons'; + +import Markdown from '../message/Markdown'; + +const styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: 'row' + }, + messageContainer: { + flex: 1, + marginHorizontal: 15, + backgroundColor: '#F3F4F5', + paddingHorizontal: 15, + paddingVertical: 10, + borderRadius: 2 + }, + header: { + flexDirection: 'row', + alignItems: 'center' + }, + username: { + color: '#1D74F5', + fontSize: 16, + fontWeight: '500' + }, + time: { + color: '#9EA2A8', + fontSize: 12, + lineHeight: 16, + marginLeft: 5 + }, + content: { + color: '#0C0D0F', + fontSize: 16, + lineHeight: 20 + }, + close: { + marginRight: 15 + } +}); + +@connect(state => ({ + Message_TimeFormat: state.settings.Message_TimeFormat +})) +export default class ReplyPreview extends Component { + static propTypes = { + message: PropTypes.object.isRequired, + Message_TimeFormat: PropTypes.string.isRequired, + close: PropTypes.func.isRequired + } + + close = () => { + this.props.close(); + } + + render() { + const { message, Message_TimeFormat } = this.props; + const time = moment(message.ts).format(Message_TimeFormat); + return ( + + + + {message.u.username} + {time} + + + + + + ); + } +} diff --git a/app/containers/MessageBox/index.js b/app/containers/MessageBox/index.js index c16cc3f8..63eb3bb1 100644 --- a/app/containers/MessageBox/index.js +++ b/app/containers/MessageBox/index.js @@ -9,7 +9,7 @@ import ImagePicker from 'react-native-image-crop-picker'; import { userTyping } from '../../actions/room'; import RocketChat from '../../lib/rocketchat'; -import { editRequest, editCancel, clearInput } from '../../actions/messages'; +import { editRequest, editCancel, replyCancel } from '../../actions/messages'; import styles from './styles'; import MyIcon from '../icons'; import database from '../../lib/realm'; @@ -22,6 +22,7 @@ import UploadModal from './UploadModal'; import './EmojiKeyboard'; import log from '../../utils/log'; import I18n from '../../i18n'; +import ReplyPreview from './ReplyPreview'; const MENTIONS_TRACKING_TYPE_USERS = '@'; const MENTIONS_TRACKING_TYPE_EMOJIS = ':'; @@ -39,27 +40,34 @@ const imagePickerConfig = { }; @connect(state => ({ - room: state.room, + roomType: state.room.t, message: state.messages.message, + replyMessage: state.messages.replyMessage, + replying: state.messages.replyMessage && !!state.messages.replyMessage.msg, editing: state.messages.editing, - baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' + baseUrl: state.settings.Site_Url || state.server ? state.server.server : '', + username: state.login.user && state.login.user.username }), dispatch => ({ editCancel: () => dispatch(editCancel()), editRequest: message => dispatch(editRequest(message)), typing: status => dispatch(userTyping(status)), - clearInput: () => dispatch(clearInput()) + closeReply: () => dispatch(replyCancel()) })) export default class MessageBox extends React.PureComponent { static propTypes = { - onSubmit: PropTypes.func.isRequired, rid: PropTypes.string.isRequired, - editCancel: PropTypes.func.isRequired, - editRequest: PropTypes.func.isRequired, baseUrl: PropTypes.string.isRequired, message: PropTypes.object, + replyMessage: PropTypes.object, + replying: PropTypes.bool, editing: PropTypes.bool, + username: PropTypes.string, + roomType: PropTypes.string, + editCancel: PropTypes.func.isRequired, + editRequest: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, typing: PropTypes.func, - clearInput: PropTypes.func + closeReply: PropTypes.func } constructor(props) { @@ -84,6 +92,8 @@ export default class MessageBox extends React.PureComponent { if (this.props.message !== nextProps.message && nextProps.message.msg) { this.setState({ text: nextProps.message.msg }); this.component.focus(); + } else if (this.props.replyMessage !== nextProps.replyMessage && nextProps.replyMessage.msg) { + this.component.focus(); } else if (!nextProps.message) { this.setState({ text: '' }); } @@ -180,6 +190,14 @@ export default class MessageBox extends React.PureComponent { return icons; } + getPermalink = async(message) => { + try { + return await RocketChat.getPermalink(message); + } catch (error) { + return null; + } + } + toggleFilesActions = () => { this.setState(prevState => ({ showFilesAction: !prevState.showFilesAction })); } @@ -259,7 +277,7 @@ export default class MessageBox extends React.PureComponent { this.setState({ showEmojiKeyboard: false }); } - submit(message) { + async submit(message) { this.setState({ text: '' }); this.closeEmoji(); this.stopTrackingMention(); @@ -268,15 +286,32 @@ export default class MessageBox extends React.PureComponent { return; } // if is editing a message - const { editing } = this.props; + const { + editing, replying + } = this.props; + if (editing) { const { _id, rid } = this.props.message; this.props.editRequest({ _id, msg: message, rid }); + } else if (replying) { + const { + username, replyMessage, roomType, closeReply + } = this.props; + const permalink = await this.getPermalink(replyMessage); + let msg = `[ ](${ permalink }) `; + + // if original message wasn't sent by current user and neither from a direct room + if (username !== replyMessage.u.username && roomType !== 'd' && replyMessage.mention) { + msg += `@${ replyMessage.u.username } `; + } + + msg = `${ msg } ${ message }`; + this.props.onSubmit(msg); + closeReply(); } else { // if is submiting a new message this.props.onSubmit(message); } - this.props.clearInput(); } _getFixedMentions(keyword) { @@ -520,6 +555,14 @@ export default class MessageBox extends React.PureComponent { ); }; + renderReplyPreview = () => { + const { replyMessage, replying, closeReply } = this.props; + if (!replying) { + return null; + } + return ; + }; + renderFilesActions = () => { if (!this.state.showFilesAction) { return null; @@ -541,6 +584,7 @@ export default class MessageBox extends React.PureComponent { return ( [ this.renderMentions(), + this.renderReplyPreview(), state.server.server); - const msg = `[ ](${ server }/direct/${ username }?msg=${ message._id }) `; - yield put(setInput({ msg })); + yield put(replyInit(message, false)); } catch (e) { log('handleReplyBroadcast', e); }