[NEW] Reply preview (#374)
* Updated to React Native 0.56 * Reply Preview
This commit is contained in:
parent
077c29503e
commit
8322e7e576
|
@ -65,8 +65,8 @@ export const MESSAGES = createRequestTypes('MESSAGES', [
|
||||||
'TOGGLE_PIN_REQUEST',
|
'TOGGLE_PIN_REQUEST',
|
||||||
'TOGGLE_PIN_SUCCESS',
|
'TOGGLE_PIN_SUCCESS',
|
||||||
'TOGGLE_PIN_FAILURE',
|
'TOGGLE_PIN_FAILURE',
|
||||||
'SET_INPUT',
|
'REPLY_INIT',
|
||||||
'CLEAR_INPUT',
|
'REPLY_CANCEL',
|
||||||
'TOGGLE_REACTION_PICKER',
|
'TOGGLE_REACTION_PICKER',
|
||||||
'REPLY_BROADCAST'
|
'REPLY_BROADCAST'
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -137,16 +137,17 @@ export function togglePinFailure(err) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setInput(message) {
|
export function replyInit(message, mention) {
|
||||||
return {
|
return {
|
||||||
type: types.MESSAGES.SET_INPUT,
|
type: types.MESSAGES.REPLY_INIT,
|
||||||
message
|
message,
|
||||||
|
mention
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearInput() {
|
export function replyCancel() {
|
||||||
return {
|
return {
|
||||||
type: types.MESSAGES.CLEAR_INPUT
|
type: types.MESSAGES.REPLY_CANCEL
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,9 @@ import {
|
||||||
editInit,
|
editInit,
|
||||||
toggleStarRequest,
|
toggleStarRequest,
|
||||||
togglePinRequest,
|
togglePinRequest,
|
||||||
setInput,
|
|
||||||
actionsHide,
|
actionsHide,
|
||||||
toggleReactionPicker
|
toggleReactionPicker,
|
||||||
|
replyInit
|
||||||
} from '../actions/messages';
|
} from '../actions/messages';
|
||||||
import { showToast } from '../utils/info';
|
import { showToast } from '../utils/info';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
@ -34,8 +34,8 @@ import I18n from '../i18n';
|
||||||
editInit: message => dispatch(editInit(message)),
|
editInit: message => dispatch(editInit(message)),
|
||||||
toggleStarRequest: message => dispatch(toggleStarRequest(message)),
|
toggleStarRequest: message => dispatch(toggleStarRequest(message)),
|
||||||
togglePinRequest: message => dispatch(togglePinRequest(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 {
|
export default class MessageActions extends React.Component {
|
||||||
|
@ -43,13 +43,13 @@ export default class MessageActions extends React.Component {
|
||||||
actionsHide: PropTypes.func.isRequired,
|
actionsHide: PropTypes.func.isRequired,
|
||||||
room: PropTypes.object.isRequired,
|
room: PropTypes.object.isRequired,
|
||||||
actionMessage: PropTypes.object,
|
actionMessage: PropTypes.object,
|
||||||
user: PropTypes.object.isRequired,
|
// user: PropTypes.object.isRequired,
|
||||||
deleteRequest: PropTypes.func.isRequired,
|
deleteRequest: PropTypes.func.isRequired,
|
||||||
editInit: PropTypes.func.isRequired,
|
editInit: PropTypes.func.isRequired,
|
||||||
toggleStarRequest: PropTypes.func.isRequired,
|
toggleStarRequest: PropTypes.func.isRequired,
|
||||||
togglePinRequest: PropTypes.func.isRequired,
|
togglePinRequest: PropTypes.func.isRequired,
|
||||||
setInput: PropTypes.func.isRequired,
|
|
||||||
toggleReactionPicker: PropTypes.func.isRequired,
|
toggleReactionPicker: PropTypes.func.isRequired,
|
||||||
|
replyInit: PropTypes.func.isRequired,
|
||||||
Message_AllowDeleting: PropTypes.bool,
|
Message_AllowDeleting: PropTypes.bool,
|
||||||
Message_AllowDeleting_BlockDeleteInMinutes: PropTypes.number,
|
Message_AllowDeleting_BlockDeleteInMinutes: PropTypes.number,
|
||||||
Message_AllowEditing: PropTypes.bool,
|
Message_AllowEditing: PropTypes.bool,
|
||||||
|
@ -248,21 +248,12 @@ export default class MessageActions extends React.Component {
|
||||||
this.props.togglePinRequest(this.props.actionMessage);
|
this.props.togglePinRequest(this.props.actionMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleReply() {
|
handleReply() {
|
||||||
const permalink = await this.getPermalink(this.props.actionMessage);
|
this.props.replyInit(this.props.actionMessage, true);
|
||||||
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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleQuote() {
|
handleQuote() {
|
||||||
const permalink = await this.getPermalink(this.props.actionMessage);
|
this.props.replyInit(this.props.actionMessage, false);
|
||||||
const msg = `[ ](${ permalink }) `;
|
|
||||||
this.props.setInput({ msg });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReaction() {
|
handleReaction() {
|
||||||
|
|
|
@ -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 (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.messageContainer}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.username}>{message.u.username}</Text>
|
||||||
|
<Text style={styles.time}>{time}</Text>
|
||||||
|
</View>
|
||||||
|
<Markdown msg={message.msg} />
|
||||||
|
</View>
|
||||||
|
<Icon name='close' size={20} style={styles.close} onPress={this.close} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import ImagePicker from 'react-native-image-crop-picker';
|
||||||
|
|
||||||
import { userTyping } from '../../actions/room';
|
import { userTyping } from '../../actions/room';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import { editRequest, editCancel, clearInput } from '../../actions/messages';
|
import { editRequest, editCancel, replyCancel } from '../../actions/messages';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import MyIcon from '../icons';
|
import MyIcon from '../icons';
|
||||||
import database from '../../lib/realm';
|
import database from '../../lib/realm';
|
||||||
|
@ -22,6 +22,7 @@ import UploadModal from './UploadModal';
|
||||||
import './EmojiKeyboard';
|
import './EmojiKeyboard';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
import ReplyPreview from './ReplyPreview';
|
||||||
|
|
||||||
const MENTIONS_TRACKING_TYPE_USERS = '@';
|
const MENTIONS_TRACKING_TYPE_USERS = '@';
|
||||||
const MENTIONS_TRACKING_TYPE_EMOJIS = ':';
|
const MENTIONS_TRACKING_TYPE_EMOJIS = ':';
|
||||||
|
@ -39,27 +40,34 @@ const imagePickerConfig = {
|
||||||
};
|
};
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
room: state.room,
|
roomType: state.room.t,
|
||||||
message: state.messages.message,
|
message: state.messages.message,
|
||||||
|
replyMessage: state.messages.replyMessage,
|
||||||
|
replying: state.messages.replyMessage && !!state.messages.replyMessage.msg,
|
||||||
editing: state.messages.editing,
|
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 => ({
|
}), dispatch => ({
|
||||||
editCancel: () => dispatch(editCancel()),
|
editCancel: () => dispatch(editCancel()),
|
||||||
editRequest: message => dispatch(editRequest(message)),
|
editRequest: message => dispatch(editRequest(message)),
|
||||||
typing: status => dispatch(userTyping(status)),
|
typing: status => dispatch(userTyping(status)),
|
||||||
clearInput: () => dispatch(clearInput())
|
closeReply: () => dispatch(replyCancel())
|
||||||
}))
|
}))
|
||||||
export default class MessageBox extends React.PureComponent {
|
export default class MessageBox extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onSubmit: PropTypes.func.isRequired,
|
|
||||||
rid: PropTypes.string.isRequired,
|
rid: PropTypes.string.isRequired,
|
||||||
editCancel: PropTypes.func.isRequired,
|
|
||||||
editRequest: PropTypes.func.isRequired,
|
|
||||||
baseUrl: PropTypes.string.isRequired,
|
baseUrl: PropTypes.string.isRequired,
|
||||||
message: PropTypes.object,
|
message: PropTypes.object,
|
||||||
|
replyMessage: PropTypes.object,
|
||||||
|
replying: PropTypes.bool,
|
||||||
editing: 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,
|
typing: PropTypes.func,
|
||||||
clearInput: PropTypes.func
|
closeReply: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -84,6 +92,8 @@ export default class MessageBox extends React.PureComponent {
|
||||||
if (this.props.message !== nextProps.message && nextProps.message.msg) {
|
if (this.props.message !== nextProps.message && nextProps.message.msg) {
|
||||||
this.setState({ text: nextProps.message.msg });
|
this.setState({ text: nextProps.message.msg });
|
||||||
this.component.focus();
|
this.component.focus();
|
||||||
|
} else if (this.props.replyMessage !== nextProps.replyMessage && nextProps.replyMessage.msg) {
|
||||||
|
this.component.focus();
|
||||||
} else if (!nextProps.message) {
|
} else if (!nextProps.message) {
|
||||||
this.setState({ text: '' });
|
this.setState({ text: '' });
|
||||||
}
|
}
|
||||||
|
@ -180,6 +190,14 @@ export default class MessageBox extends React.PureComponent {
|
||||||
return icons;
|
return icons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPermalink = async(message) => {
|
||||||
|
try {
|
||||||
|
return await RocketChat.getPermalink(message);
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
toggleFilesActions = () => {
|
toggleFilesActions = () => {
|
||||||
this.setState(prevState => ({ showFilesAction: !prevState.showFilesAction }));
|
this.setState(prevState => ({ showFilesAction: !prevState.showFilesAction }));
|
||||||
}
|
}
|
||||||
|
@ -259,7 +277,7 @@ export default class MessageBox extends React.PureComponent {
|
||||||
this.setState({ showEmojiKeyboard: false });
|
this.setState({ showEmojiKeyboard: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
submit(message) {
|
async submit(message) {
|
||||||
this.setState({ text: '' });
|
this.setState({ text: '' });
|
||||||
this.closeEmoji();
|
this.closeEmoji();
|
||||||
this.stopTrackingMention();
|
this.stopTrackingMention();
|
||||||
|
@ -268,15 +286,32 @@ export default class MessageBox extends React.PureComponent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// if is editing a message
|
// if is editing a message
|
||||||
const { editing } = this.props;
|
const {
|
||||||
|
editing, replying
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
if (editing) {
|
if (editing) {
|
||||||
const { _id, rid } = this.props.message;
|
const { _id, rid } = this.props.message;
|
||||||
this.props.editRequest({ _id, msg: message, rid });
|
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 {
|
} else {
|
||||||
// if is submiting a new message
|
// if is submiting a new message
|
||||||
this.props.onSubmit(message);
|
this.props.onSubmit(message);
|
||||||
}
|
}
|
||||||
this.props.clearInput();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_getFixedMentions(keyword) {
|
_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 <ReplyPreview key='reply-preview' message={replyMessage} close={closeReply} />;
|
||||||
|
};
|
||||||
|
|
||||||
renderFilesActions = () => {
|
renderFilesActions = () => {
|
||||||
if (!this.state.showFilesAction) {
|
if (!this.state.showFilesAction) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -541,6 +584,7 @@ export default class MessageBox extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
[
|
[
|
||||||
this.renderMentions(),
|
this.renderMentions(),
|
||||||
|
this.renderReplyPreview(),
|
||||||
<View
|
<View
|
||||||
key='messagebox'
|
key='messagebox'
|
||||||
style={[styles.textArea, this.props.editing && styles.editing]}
|
style={[styles.textArea, this.props.editing && styles.editing]}
|
||||||
|
|
|
@ -32,6 +32,7 @@ export default class Markdown extends React.Component {
|
||||||
}
|
}
|
||||||
let m = formatText(msg);
|
let m = formatText(msg);
|
||||||
m = emojify(m, { output: 'unicode' });
|
m = emojify(m, { output: 'unicode' });
|
||||||
|
m = m.replace(/^\[([^\]]*)\]\(([^)]*)\)/, '').trim();
|
||||||
return (
|
return (
|
||||||
<MarkdownRenderer
|
<MarkdownRenderer
|
||||||
rules={{
|
rules={{
|
||||||
|
|
|
@ -5,6 +5,7 @@ const initialState = {
|
||||||
failure: false,
|
failure: false,
|
||||||
message: {},
|
message: {},
|
||||||
actionMessage: {},
|
actionMessage: {},
|
||||||
|
replyMessage: {},
|
||||||
editing: false,
|
editing: false,
|
||||||
showActions: false,
|
showActions: false,
|
||||||
showErrorActions: false,
|
showErrorActions: false,
|
||||||
|
@ -76,6 +77,19 @@ export default function messages(state = initialState, action) {
|
||||||
message: {},
|
message: {},
|
||||||
editing: false
|
editing: false
|
||||||
};
|
};
|
||||||
|
case types.MESSAGES.REPLY_INIT:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
replyMessage: {
|
||||||
|
...action.message,
|
||||||
|
mention: action.mention
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case types.MESSAGES.REPLY_CANCEL:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
replyMessage: {}
|
||||||
|
};
|
||||||
case types.MESSAGES.SET_INPUT:
|
case types.MESSAGES.SET_INPUT:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { delay } from 'redux-saga';
|
import { delay } from 'redux-saga';
|
||||||
import { takeLatest, put, call, select } from 'redux-saga/effects';
|
import { takeLatest, put, call } from 'redux-saga/effects';
|
||||||
|
|
||||||
import { MESSAGES } from '../actions/actionsTypes';
|
import { MESSAGES } from '../actions/actionsTypes';
|
||||||
import {
|
import {
|
||||||
|
@ -13,7 +13,7 @@ import {
|
||||||
toggleStarFailure,
|
toggleStarFailure,
|
||||||
togglePinSuccess,
|
togglePinSuccess,
|
||||||
togglePinFailure,
|
togglePinFailure,
|
||||||
setInput
|
replyInit
|
||||||
} from '../actions/messages';
|
} from '../actions/messages';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import database from '../lib/realm';
|
import database from '../lib/realm';
|
||||||
|
@ -99,9 +99,7 @@ const handleReplyBroadcast = function* handleReplyBroadcast({ message }) {
|
||||||
yield goRoom({ rid: room.rid, name: username });
|
yield goRoom({ rid: room.rid, name: username });
|
||||||
}
|
}
|
||||||
yield delay(500);
|
yield delay(500);
|
||||||
const server = yield select(state => state.server.server);
|
yield put(replyInit(message, false));
|
||||||
const msg = `[ ](${ server }/direct/${ username }?msg=${ message._id }) `;
|
|
||||||
yield put(setInput({ msg }));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('handleReplyBroadcast', e);
|
log('handleReplyBroadcast', e);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue