[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_SUCCESS',
|
||||
'TOGGLE_PIN_FAILURE',
|
||||
'SET_INPUT',
|
||||
'CLEAR_INPUT',
|
||||
'REPLY_INIT',
|
||||
'REPLY_CANCEL',
|
||||
'TOGGLE_REACTION_PICKER',
|
||||
'REPLY_BROADCAST'
|
||||
]);
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 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 <ReplyPreview key='reply-preview' message={replyMessage} close={closeReply} />;
|
||||
};
|
||||
|
||||
renderFilesActions = () => {
|
||||
if (!this.state.showFilesAction) {
|
||||
return null;
|
||||
|
@ -541,6 +584,7 @@ export default class MessageBox extends React.PureComponent {
|
|||
return (
|
||||
[
|
||||
this.renderMentions(),
|
||||
this.renderReplyPreview(),
|
||||
<View
|
||||
key='messagebox'
|
||||
style={[styles.textArea, this.props.editing && styles.editing]}
|
||||
|
|
|
@ -32,6 +32,7 @@ export default class Markdown extends React.Component {
|
|||
}
|
||||
let m = formatText(msg);
|
||||
m = emojify(m, { output: 'unicode' });
|
||||
m = m.replace(/^\[([^\]]*)\]\(([^)]*)\)/, '').trim();
|
||||
return (
|
||||
<MarkdownRenderer
|
||||
rules={{
|
||||
|
|
|
@ -5,6 +5,7 @@ const initialState = {
|
|||
failure: false,
|
||||
message: {},
|
||||
actionMessage: {},
|
||||
replyMessage: {},
|
||||
editing: false,
|
||||
showActions: false,
|
||||
showErrorActions: false,
|
||||
|
@ -76,6 +77,19 @@ export default function messages(state = initialState, action) {
|
|||
message: {},
|
||||
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:
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 {
|
||||
|
@ -13,7 +13,7 @@ import {
|
|||
toggleStarFailure,
|
||||
togglePinSuccess,
|
||||
togglePinFailure,
|
||||
setInput
|
||||
replyInit
|
||||
} from '../actions/messages';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import database from '../lib/realm';
|
||||
|
@ -99,9 +99,7 @@ const handleReplyBroadcast = function* handleReplyBroadcast({ message }) {
|
|||
yield goRoom({ rid: room.rid, name: username });
|
||||
}
|
||||
yield delay(500);
|
||||
const server = yield select(state => 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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue