diff --git a/css/base.css b/css/base.css
index 1df47a00..e0efe80f 100644
--- a/css/base.css
+++ b/css/base.css
@@ -2528,3 +2528,16 @@ ul.tabbar li.active > a {
.mention {
font-weight: bolder;
}
+
+/* Message forwarding. */
+
+.forward-menu {
+ position: absolute;
+ z-index: 4;
+ max-height: 80%;
+ border-radius: 0.2rem;
+ padding-top: 0.35rem;
+ padding-bottom: 0.35rem;
+ box-shadow: 0 1px 6px rgba(0, 0, 0, .45);
+ background-color: white;
+}
diff --git a/src/views/messages-view.jsx b/src/views/messages-view.jsx
index ca4f66f2..d2b7d22d 100644
--- a/src/views/messages-view.jsx
+++ b/src/views/messages-view.jsx
@@ -214,6 +214,17 @@ class MessagesView extends React.Component {
};
} else if (nextProps.topic != prevState.topic) {
const topic = nextProps.tinode.getTopic(nextProps.topic);
+
+ let reply = null;
+ if (nextProps.forwardedMessage) {
+ // We are forwarding a message. Show preview.
+ const preview = nextProps.forwardedMessage.preview;
+ reply = {
+ content: preview,
+ seq: null
+ };
+ }
+
nextState = {
topic: nextProps.topic,
docPreview: null,
@@ -222,7 +233,7 @@ class MessagesView extends React.Component {
typingIndicator: false,
scrollPosition: 0,
fetchingMessages: false,
- reply: null
+ reply: reply
};
if (topic) {
@@ -665,14 +676,20 @@ class MessagesView extends React.Component {
// sendMessage sends the message with an optional subscription to topic first.
sendMessage(msg, uploadCompletionPromise, uploader) {
let head;
- if (this.state.reply && this.state.reply.content) {
+ if (this.props.forwardedMessage) {
+ // We are forwarding a message.
+ msg = this.props.forwardedMessage.msg;
+ head = this.props.forwardedMessage.head;
+ this.handleCancelReply();
+ } else if (this.state.reply && this.state.reply.content) {
+ // We are replying to a message in this topic.
head = {reply: '' + this.state.reply.seq};
// Turn it into Drafty so we can make a quoted Drafty object later.
if (typeof msg == 'string') {
msg = Drafty.parse(msg);
}
msg = Drafty.append(this.state.reply.content, msg);
- this.setState({reply: null});
+ this.handleCancelReply();
}
this.props.sendMessage(msg, uploadCompletionPromise, uploader, head);
}
@@ -825,7 +842,7 @@ class MessagesView extends React.Component {
uploader.cancel();
}
- handlePickReply(seq, content) {
+ handlePickReply(seq, content, senderId, senderName) {
this.setState({reply: null});
if (!seq || !content) {
@@ -843,23 +860,6 @@ class MessagesView extends React.Component {
Drafty.wrapInto(this.props.intl.formatMessage(messages.invalid_content), 'EM'));
}
- // Get the author.
- const topic = this.props.tinode.getTopic(this.state.topic);
- const msg = topic.findMessage(seq);
- let senderName, senderId;
- if (msg) {
- senderId = msg.from || 'chan';
- if (senderId != this.props.myUserId) {
- const topic = this.props.tinode.getTopic(this.state.topic);
- const user = topic.userDesc(senderId);
- if (user && user.public) {
- senderName = user.public.fn;
- }
- } else {
- senderName = this.props.myUserName;
- }
- }
-
this.setState({
reply: {
content: Drafty.quote(senderName, senderId, content),
@@ -869,7 +869,8 @@ class MessagesView extends React.Component {
}
handleCancelReply() {
- this.setState({reply: null})
+ this.setState({reply: null});
+ this.props.onCancelForwardMessage();
}
handleQuoteClick(replyToSeq) {
@@ -964,18 +965,14 @@ class MessagesView extends React.Component {
const isReply = !(thisFrom == this.props.myUserId);
const deliveryStatus = topic.msgStatus(msg, true);
- let userName, userAvatar, userFrom;
- if (groupTopic) {
- const user = topic.userDesc(thisFrom);
- if (user && user.public) {
- userName = user.public.fn;
- userAvatar = makeImageUrl(user.public.photo);
- }
- userFrom = thisFrom;
- chatBoxClass='chat-box group';
- } else {
- chatBoxClass='chat-box';
+ let userFrom = thisFrom, userName, userAvatar;
+ const user = topic.userDesc(thisFrom);
+ if (user && user.public) {
+ userName = user.public.fn;
+ userAvatar = makeImageUrl(user.public.photo);
}
+ //userFrom = thisFrom;
+ chatBoxClass = groupTopic ? 'chat-box group' : 'chat-box';
// Ref for this chat message.
const ref = this.getOrCreateMessageRef(msg.seq);
@@ -993,6 +990,7 @@ class MessagesView extends React.Component {
timestamp={msg.ts}
response={isReply}
seq={msg.seq}
+ isGroup={groupTopic}
userFrom={userFrom}
userName={userName}
userAvatar={userAvatar}
@@ -1106,11 +1104,12 @@ class MessagesView extends React.Component {
:
+ :
+ null
+ }
Tinode.MESSAGE_STATUS_FAILED &&
this.props.received < Tinode.MESSAGE_STATUS_DEL_RANGE) {
menuItems.push('menu_item_reply');
+ menuItems.push('menu_item_forward');
}
this.props.showContextMenu({ seq: this.props.seq, content: this.props.content,
+ userFrom: this.props.userFrom, userName: this.props.userName,
y: e.pageY, x: e.pageX, pickReply: this.props.pickReply }, menuItems);
}
@@ -103,7 +105,7 @@ class BaseChatMessage extends React.PureComponent {
(this.props.sequence + ' ' + (this.props.response ? 'left' : 'right'));
const bubbleClass = (this.props.sequence == 'single' || this.props.sequence == 'last') ? 'bubble tip' : 'bubble';
const avatar = this.props.deleted ? null : (this.props.userAvatar || true);
- const fullDisplay = (this.props.userFrom && this.props.response && !this.props.deleted &&
+ const fullDisplay = (this.props.isGroup && this.props.response && !this.props.deleted &&
(this.props.sequence == 'single' || this.props.sequence == 'last'));
let content = this.props.content;
@@ -143,7 +145,7 @@ class BaseChatMessage extends React.PureComponent {
return (
- {this.props.userFrom && this.props.response ?
+ {this.props.isGroup && this.props.response ?
{fullDisplay ?
{
+ return this.forwardMessage(params, errorHandler);
+ }
+ },
'topic_unmute': {
id: 'topic_unmute',
title: formatMessage(messages.unmute),
@@ -428,7 +440,13 @@ class ContextMenu extends React.Component {
}
replyToMessage(params, errorHandler) {
- params.pickReply(params.seq, params.content, errorHandler);
+ params.pickReply(params.seq, params.content, params.userFrom, params.userName, errorHandler);
+ }
+
+ forwardMessage(params, errorHandler) {
+ //params.pickReply({ seq: params.seq, content: params.content });
+ //this.props.
+ console.log('forwarding msg ', params);
}
render() {
diff --git a/src/widgets/forward-menu.jsx b/src/widgets/forward-menu.jsx
new file mode 100644
index 00000000..f03eabf9
--- /dev/null
+++ b/src/widgets/forward-menu.jsx
@@ -0,0 +1,97 @@
+// Forward Menu: message forwarding popup/dropdown menu.
+import React from 'react';
+
+import Tinode from 'tinode-sdk';
+
+import ContactList from './contact-list.jsx';
+import SearchContacts from './search-contacts.jsx';
+
+import HashNavigation from '../lib/navigation.js';
+import { REM_SIZE } from '../config.js';
+
+export default class ForwardMenu extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.selfRef = React.createRef();
+ this.handlePageClick = this.handlePageClick.bind(this);
+ this.handleEscapeKey = this.handleEscapeKey.bind(this);
+
+ this.handleSearchContacts = this.handleSearchContacts.bind(this);
+ this.handleContactSelected = this.handleContactSelected.bind(this);
+ }
+
+ componentDidMount() {
+ document.addEventListener('mousedown', this.handlePageClick, false);
+ document.addEventListener('keyup', this.handleEscapeKey, false);
+
+ this.props.onInitFind();
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('mousedown', this.handlePageClick, false);
+ document.removeEventListener('keyup', this.handleEscapeKey, false);
+ }
+
+ handlePageClick(e) {
+ if (this.selfRef.current.contains(e.target)) {
+ return;
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ // Not forwarding the message.
+ this.props.hide(false);
+ }
+
+ handleEscapeKey(e) {
+ if (e.keyCode === 27) {
+ // Not forwarding the message.
+ this.props.hide(false);
+ }
+ }
+
+ handleSearchContacts(query) {
+ this.props.onSearchContacts(query);
+ }
+
+ handleContactSelected(uid) {
+ this.props.onCreateTopic(uid);
+ this.props.hide(true);
+ }
+
+ render() {
+ // Ensure that menu is inside the app-container.
+ const hSize = 20 * REM_SIZE;
+ const vSize = REM_SIZE * (0.7 + this.props.contacts.length * 3);
+ const left = (this.props.bounds.right - this.props.clickAt.x < hSize) ?
+ (this.props.clickAt.x - this.props.bounds.left - hSize) :
+ (this.props.clickAt.x - this.props.bounds.left);
+ const top = (this.props.bounds.bottom - this.props.clickAt.y < vSize) ?
+ (this.props.clickAt.y - this.props.bounds.top - vSize) :
+ (this.props.clickAt.y - this.props.bounds.top);
+
+ const style = {
+ left: left + 'px',
+ top: top + 'px',
+ maxWidth: hSize + 'px'
+ };
+
+ return (
+
+ );
+ }
+}