Basic and very raw implementation of the Reply To feature.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// Single message, sent or received.
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { Drafty } from 'tinode-sdk';
|
||||
|
||||
import Attachment from './attachment.jsx';
|
||||
@@ -9,7 +9,7 @@ import ReceivedMarker from './received-marker.jsx'
|
||||
import UploadingImage from './uploading-image.jsx'
|
||||
import { sanitizeImageUrl, sanitizeUrl } from '../lib/utils.js';
|
||||
|
||||
export default class ChatMessage extends React.Component {
|
||||
class ChatMessage extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -25,6 +25,8 @@ export default class ChatMessage extends React.Component {
|
||||
this.handleFormButtonClick = this.handleFormButtonClick.bind(this);
|
||||
this.handleContextClick = this.handleContextClick.bind(this);
|
||||
this.handleCancelUpload = this.handleCancelUpload.bind(this);
|
||||
|
||||
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
||||
}
|
||||
|
||||
handleImagePreview(e) {
|
||||
@@ -60,8 +62,12 @@ export default class ChatMessage extends React.Component {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const menuItems = this.props.received == Tinode.MESSAGE_STATUS_FAILED ? ['menu_item_send_retry'] : [];
|
||||
if (this.props.received >= Tinode.MESSAGE_STATUS_FAILED &&
|
||||
this.props.received < Tinode.MESSAGE_STATUS_DEL_RANGE) {
|
||||
menuItems.push('menu_item_reply');
|
||||
}
|
||||
this.props.showContextMenu({ seq: this.props.seq, content: this.props.content,
|
||||
y: e.pageY, x: e.pageX }, menuItems);
|
||||
y: e.pageY, x: e.pageX, pickReply: this.props.pickReply }, menuItems);
|
||||
}
|
||||
|
||||
handleProgress(ratio) {
|
||||
@@ -72,6 +78,13 @@ export default class ChatMessage extends React.Component {
|
||||
this.props.onCancelUpload(this.props.seq, this.props.uploader);
|
||||
}
|
||||
|
||||
handleQuoteClick(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const replyToSeq = this.props.replyToSeq;
|
||||
this.props.onQuoteClick(replyToSeq);
|
||||
}
|
||||
|
||||
render() {
|
||||
const sideClass = this.props.deleted ? 'center' :
|
||||
(this.props.sequence + ' ' + (this.props.response ? 'left' : 'right'));
|
||||
@@ -80,6 +93,7 @@ export default class ChatMessage extends React.Component {
|
||||
const fullDisplay = (this.props.userFrom && this.props.response && !this.props.deleted &&
|
||||
(this.props.sequence == 'single' || this.props.sequence == 'last'));
|
||||
|
||||
const msgId = 'msg-' + this.props.seq;
|
||||
let content = this.props.content;
|
||||
const attachments = [];
|
||||
if (this.props.mimeType == Drafty.getContentType() && Drafty.isValid(content)) {
|
||||
@@ -99,7 +113,17 @@ export default class ChatMessage extends React.Component {
|
||||
onError={this.props.onError}
|
||||
key={i} />);
|
||||
}, this);
|
||||
content = React.createElement(React.Fragment, null, Drafty.format(content, draftyFormatter, this));
|
||||
|
||||
let qte = [];
|
||||
if (content.qte) {
|
||||
qte = Drafty.format(content.qte, this.props.onFormatQuote, this);
|
||||
// Append a line break.
|
||||
const lf = Drafty.appendLineBreak(null);
|
||||
qte = qte.concat(Drafty.format(lf, draftyFormatter, this, qte.length));
|
||||
}
|
||||
let tree = Drafty.format(content, draftyFormatter, this, qte.length);
|
||||
tree = qte.concat(tree);
|
||||
content = React.createElement(React.Fragment, null, tree);
|
||||
} else if (this.props.deleted) {
|
||||
// Message represents a range of deleted messages.
|
||||
content = <><i className="material-icons gray">block</i> <i className="gray">
|
||||
@@ -115,7 +139,7 @@ export default class ChatMessage extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={sideClass}>
|
||||
<li id={msgId} className={sideClass}>
|
||||
{this.props.userFrom && this.props.response ?
|
||||
<div className="avatar-box">
|
||||
{fullDisplay ?
|
||||
@@ -234,6 +258,18 @@ function draftyFormatter(style, data, values, key) {
|
||||
case 'RW':
|
||||
// Form element formatting is dependent on element content.
|
||||
break;
|
||||
case 'QQ':
|
||||
// Quote/citation.
|
||||
attr.className = 'reply-quote'
|
||||
attr.onClick = this.handleQuoteClick;
|
||||
break;
|
||||
case 'QH':
|
||||
// Quote header.
|
||||
attr.className = 'reply-quote-header';
|
||||
break;
|
||||
case 'QB':
|
||||
// Quote body.
|
||||
break;
|
||||
default:
|
||||
if (el == '_UNKN') {
|
||||
// Unknown element.
|
||||
@@ -247,3 +283,5 @@ function draftyFormatter(style, data, values, key) {
|
||||
return values;
|
||||
}
|
||||
};
|
||||
|
||||
export default injectIntl(ChatMessage);
|
||||
|
||||
@@ -45,6 +45,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Unmute',
|
||||
description: 'Turn notifications on'
|
||||
},
|
||||
reply: {
|
||||
id: 'menu_item_reply',
|
||||
defaultMessage: 'Reply',
|
||||
description: 'Reply to message'
|
||||
},
|
||||
topic_delete: {
|
||||
id: 'menu_item_delete_topic',
|
||||
defaultMessage: 'Delete',
|
||||
@@ -171,6 +176,13 @@ class ContextMenu extends React.Component {
|
||||
return this.retryMessage(params, errorHandler);
|
||||
}
|
||||
},
|
||||
'menu_item_reply': {
|
||||
id: 'menu_item_reply',
|
||||
title: formatMessage(messages.reply),
|
||||
handler: (params, errorHandler) => {
|
||||
return this.replyToMessage(params, errorHandler);
|
||||
}
|
||||
},
|
||||
'topic_unmute': {
|
||||
id: 'topic_unmute',
|
||||
title: formatMessage(messages.unmute),
|
||||
@@ -411,6 +423,11 @@ class ContextMenu extends React.Component {
|
||||
return result;
|
||||
}
|
||||
|
||||
replyToMessage(params, errorHandler) {
|
||||
console.log("Reply to message", params);
|
||||
params.pickReply({ seq: params.seq, content: params.content })
|
||||
}
|
||||
|
||||
render() {
|
||||
let count = 0;
|
||||
let menu = [];
|
||||
|
||||
@@ -50,6 +50,8 @@ class SendMessage extends React.PureComponent {
|
||||
this.handleSend = this.handleSend.bind(this);
|
||||
this.handleKeyPress = this.handleKeyPress.bind(this);
|
||||
this.handleMessageTyping = this.handleMessageTyping.bind(this);
|
||||
|
||||
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -141,6 +143,17 @@ class SendMessage extends React.PureComponent {
|
||||
this.setState(newState);
|
||||
}
|
||||
|
||||
handleQuoteClick(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (this.props.replyTo) {
|
||||
//quote = Drafty.format(this.props.replyTo.content, quoteFormatter, this);
|
||||
//quote = Drafty.format(this.props.replyTo.content, this.props.onFormatQuote, this);
|
||||
const replyToSeq = this.props.replyTo.seq;
|
||||
this.props.onQuoteClick(replyToSeq);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.props.intl;
|
||||
const prompt = this.props.disabled ?
|
||||
@@ -148,7 +161,22 @@ class SendMessage extends React.PureComponent {
|
||||
(this.props.messagePrompt ?
|
||||
formatMessage(messages[this.props.messagePrompt]) :
|
||||
formatMessage(messages.type_new_message));
|
||||
|
||||
let quote = null;
|
||||
if (this.props.replyTo) {
|
||||
quote = Drafty.format(this.props.replyTo.content, this.props.onFormatQuote, this);
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="send-message-wrapper">
|
||||
{quote ?
|
||||
<div className="reply-quote-preview">
|
||||
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onCancelReply();}}><i className="material-icons gray">close</i></a>
|
||||
{quote}
|
||||
</div>
|
||||
:
|
||||
null}
|
||||
|
||||
<div id="send-message-panel">
|
||||
{!this.props.disabled ?
|
||||
<>
|
||||
@@ -182,6 +210,7 @@ class SendMessage extends React.PureComponent {
|
||||
<div id="writing-disabled">{prompt}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user