Basic and very raw implementation of the Reply To feature.

This commit is contained in:
aforge
2021-08-14 16:30:18 +03:00
parent 47e7632399
commit b482f6649d
9 changed files with 457 additions and 15 deletions
+43 -5
View File
@@ -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);
+17
View File
@@ -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 = [];
+29
View File
@@ -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>
);
}
};