editing once works, editing twice does not
This commit is contained in:
+1
-1
@@ -72,7 +72,7 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-intl": "^6.2.1",
|
"react-intl": "^6.2.1",
|
||||||
"tinode-sdk": "^0.20.2",
|
"tinode-sdk": "^0.21.0-beta1",
|
||||||
"webrtc-adapter": "^8.2.0"
|
"webrtc-adapter": "^8.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -97,6 +97,8 @@ export const MESSAGE_PREVIEW_LENGTH = 80;
|
|||||||
export const QUOTED_REPLY_LENGTH = 30;
|
export const QUOTED_REPLY_LENGTH = 30;
|
||||||
// Length of a preview of a forwarded message.
|
// Length of a preview of a forwarded message.
|
||||||
export const FORWARDED_PREVIEW_LENGTH = 84;
|
export const FORWARDED_PREVIEW_LENGTH = 84;
|
||||||
|
// Length of preview for a message being edited.
|
||||||
|
export const EDIT_PREVIEW_LENGTH = 60;
|
||||||
|
|
||||||
// Minimum duration of an audio recording in milliseconds (2 sec).
|
// Minimum duration of an audio recording in milliseconds (2 sec).
|
||||||
export const MIN_DURATION = 2_000;
|
export const MIN_DURATION = 2_000;
|
||||||
|
|||||||
@@ -1451,5 +1451,26 @@
|
|||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Message explaining that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"menu_item_edit": {
|
||||||
|
"translation": "Bearbeiten",
|
||||||
|
"defaultMessage": "Edit",
|
||||||
|
"description": "Edit message",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"editing_message": {
|
||||||
|
"translation": "Bearbeitung",
|
||||||
|
"defaultMessage": "Editing",
|
||||||
|
"description": "Title over message editing preview",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"message_edited_marker": {
|
||||||
|
"translation": ", bearbeitet",
|
||||||
|
"defaultMessage": ", edited",
|
||||||
|
"description": "Marker indicating that the message was edited",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1451,5 +1451,26 @@
|
|||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Message explaining that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"menu_item_edit": {
|
||||||
|
"translation": "Edit",
|
||||||
|
"defaultMessage": "Edit",
|
||||||
|
"description": "Edit message",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"editing_message": {
|
||||||
|
"translation": "Editing",
|
||||||
|
"defaultMessage": "Editing",
|
||||||
|
"description": "Title over message editing preview",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"message_edited_marker": {
|
||||||
|
"translation": ", edited",
|
||||||
|
"defaultMessage": ", edited",
|
||||||
|
"description": "Marker indicating that the message was edited",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1451,5 +1451,26 @@
|
|||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Message explaining that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"menu_item_edit": {
|
||||||
|
"translation": "Editar",
|
||||||
|
"defaultMessage": "Edit",
|
||||||
|
"description": "Edit message",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"editing_message": {
|
||||||
|
"translation": "Edición",
|
||||||
|
"defaultMessage": "Editing",
|
||||||
|
"description": "Title over message editing preview",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"message_edited_marker": {
|
||||||
|
"translation": ", editado",
|
||||||
|
"defaultMessage": ", edited",
|
||||||
|
"description": "Marker indicating that the message was edited",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,6 +243,10 @@
|
|||||||
"defaultMessage": "Unsupported",
|
"defaultMessage": "Unsupported",
|
||||||
"description": "Unsupported entity in drafty"
|
"description": "Unsupported entity in drafty"
|
||||||
},
|
},
|
||||||
|
"editing_message": {
|
||||||
|
"defaultMessage": "Editing",
|
||||||
|
"description": "Title over message editing preview"
|
||||||
|
},
|
||||||
"email_dative": {
|
"email_dative": {
|
||||||
"defaultMessage": "email",
|
"defaultMessage": "email",
|
||||||
"description": "Dative case of 'email', i.e. 'email' in 'by email'"
|
"description": "Dative case of 'email', i.e. 'email' in 'by email'"
|
||||||
@@ -480,6 +484,10 @@
|
|||||||
"defaultMessage": "Delete",
|
"defaultMessage": "Delete",
|
||||||
"description": "Delete entire topic"
|
"description": "Delete entire topic"
|
||||||
},
|
},
|
||||||
|
"menu_item_edit": {
|
||||||
|
"defaultMessage": "Edit",
|
||||||
|
"description": "Edit message"
|
||||||
|
},
|
||||||
"menu_item_edit_permissions": {
|
"menu_item_edit_permissions": {
|
||||||
"defaultMessage": "Edit permissions",
|
"defaultMessage": "Edit permissions",
|
||||||
"description": "Menu item [Edit permissions]"
|
"description": "Menu item [Edit permissions]"
|
||||||
@@ -524,6 +532,10 @@
|
|||||||
"defaultMessage": "Video call",
|
"defaultMessage": "Video call",
|
||||||
"description": "Start video call"
|
"description": "Start video call"
|
||||||
},
|
},
|
||||||
|
"message_edited_marker": {
|
||||||
|
"defaultMessage": ", edited",
|
||||||
|
"description": "Marker indicating that the message was edited"
|
||||||
|
},
|
||||||
"message_sending": {
|
"message_sending": {
|
||||||
"defaultMessage": "sending...",
|
"defaultMessage": "sending...",
|
||||||
"description": "Message being sent, in place of time stamp"
|
"description": "Message being sent, in place of time stamp"
|
||||||
|
|||||||
@@ -1451,5 +1451,26 @@
|
|||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Message explaining that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"menu_item_edit": {
|
||||||
|
"translation": "Modifier",
|
||||||
|
"defaultMessage": "Edit",
|
||||||
|
"description": "Edit message",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"editing_message": {
|
||||||
|
"translation": "Édition",
|
||||||
|
"defaultMessage": "Editing",
|
||||||
|
"description": "Title over message editing preview",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"message_edited_marker": {
|
||||||
|
"translation": ", édité",
|
||||||
|
"defaultMessage": ", edited",
|
||||||
|
"description": "Marker indicating that the message was edited",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1451,5 +1451,26 @@
|
|||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Message explaining that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"menu_item_edit": {
|
||||||
|
"translation": "편집",
|
||||||
|
"defaultMessage": "Edit",
|
||||||
|
"description": "Edit message",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"editing_message": {
|
||||||
|
"translation": "교정하다",
|
||||||
|
"defaultMessage": "Editing",
|
||||||
|
"description": "Title over message editing preview",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"message_edited_marker": {
|
||||||
|
"translation": " 수정됨",
|
||||||
|
"defaultMessage": ", edited",
|
||||||
|
"description": "Marker indicating that the message was edited",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1451,5 +1451,26 @@
|
|||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Message explaining that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"menu_item_edit": {
|
||||||
|
"translation": "Editează",
|
||||||
|
"defaultMessage": "Edit",
|
||||||
|
"description": "Edit message",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"editing_message": {
|
||||||
|
"translation": "Editare",
|
||||||
|
"defaultMessage": "Editing",
|
||||||
|
"description": "Title over message editing preview",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"message_edited_marker": {
|
||||||
|
"translation": ", editat",
|
||||||
|
"defaultMessage": ", edited",
|
||||||
|
"description": "Marker indicating that the message was edited",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1451,5 +1451,26 @@
|
|||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Message explaining that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"menu_item_edit": {
|
||||||
|
"translation": "Изменить",
|
||||||
|
"defaultMessage": "Edit",
|
||||||
|
"description": "Edit message",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"editing_message": {
|
||||||
|
"translation": "Редактирование",
|
||||||
|
"defaultMessage": "Editing",
|
||||||
|
"description": "Title over message editing preview",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"message_edited_marker": {
|
||||||
|
"translation": ", изменено",
|
||||||
|
"defaultMessage": ", edited",
|
||||||
|
"description": "Marker indicating that the message was edited",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1451,5 +1451,26 @@
|
|||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Message explaining that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"menu_item_edit": {
|
||||||
|
"translation": "編輯",
|
||||||
|
"defaultMessage": "Edit",
|
||||||
|
"description": "Edit message",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"editing_message": {
|
||||||
|
"translation": "編輯",
|
||||||
|
"defaultMessage": "Editing",
|
||||||
|
"description": "Title over message editing preview",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"message_edited_marker": {
|
||||||
|
"translation": ",編輯",
|
||||||
|
"defaultMessage": ", edited",
|
||||||
|
"description": "Marker indicating that the message was edited",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1451,5 +1451,26 @@
|
|||||||
"description": "Message explaining that the credential was successfully validated.",
|
"description": "Message explaining that the credential was successfully validated.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"menu_item_edit": {
|
||||||
|
"translation": "编辑",
|
||||||
|
"defaultMessage": "Edit",
|
||||||
|
"description": "Edit message",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"editing_message": {
|
||||||
|
"translation": "编辑",
|
||||||
|
"defaultMessage": "Editing",
|
||||||
|
"description": "Title over message editing preview",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"message_edited_marker": {
|
||||||
|
"translation": ",编辑",
|
||||||
|
"defaultMessage": ", edited",
|
||||||
|
"description": "Marker indicating that the message was edited",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+57
-13
@@ -19,7 +19,7 @@ import LogoView from './logo-view.jsx';
|
|||||||
import MetaMessage from '../widgets/meta-message.jsx';
|
import MetaMessage from '../widgets/meta-message.jsx';
|
||||||
import SendMessage from '../widgets/send-message.jsx';
|
import SendMessage from '../widgets/send-message.jsx';
|
||||||
|
|
||||||
import { DEFAULT_P2P_ACCESS_MODE, IMAGE_PREVIEW_DIM, KEYPRESS_DELAY,
|
import { DEFAULT_P2P_ACCESS_MODE, EDIT_PREVIEW_LENGTH, IMAGE_PREVIEW_DIM, KEYPRESS_DELAY,
|
||||||
MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE,
|
MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE,
|
||||||
READ_DELAY, QUOTED_REPLY_LENGTH } from '../config.js';
|
READ_DELAY, QUOTED_REPLY_LENGTH } from '../config.js';
|
||||||
import { CALL_STATE_OUTGOING_INITATED, CALL_STATE_IN_PROGRESS } from '../constants.js';
|
import { CALL_STATE_OUTGOING_INITATED, CALL_STATE_IN_PROGRESS } from '../constants.js';
|
||||||
@@ -63,6 +63,11 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'invalid content',
|
defaultMessage: 'invalid content',
|
||||||
description: 'Shown when the message is unreadable'
|
description: 'Shown when the message is unreadable'
|
||||||
},
|
},
|
||||||
|
editing_message: {
|
||||||
|
id: 'editing_message',
|
||||||
|
defaultMessage: 'Editing',
|
||||||
|
description: 'Title over message editing preview'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Checks if the access permissions are granted but not yet accepted.
|
// Checks if the access permissions are granted but not yet accepted.
|
||||||
@@ -125,6 +130,7 @@ class MessagesView extends React.Component {
|
|||||||
this.goToLatestMessage = this.goToLatestMessage.bind(this);
|
this.goToLatestMessage = this.goToLatestMessage.bind(this);
|
||||||
|
|
||||||
this.handlePickReply = this.handlePickReply.bind(this);
|
this.handlePickReply = this.handlePickReply.bind(this);
|
||||||
|
this.handleEditMessage = this.handleEditMessage.bind(this);
|
||||||
this.handleCancelReply = this.handleCancelReply.bind(this);
|
this.handleCancelReply = this.handleCancelReply.bind(this);
|
||||||
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
||||||
this.handleCallHangup = this.handleCallHangup.bind(this);
|
this.handleCallHangup = this.handleCallHangup.bind(this);
|
||||||
@@ -237,6 +243,7 @@ class MessagesView extends React.Component {
|
|||||||
peerMessagingDisabled: false,
|
peerMessagingDisabled: false,
|
||||||
channel: false,
|
channel: false,
|
||||||
reply: null,
|
reply: null,
|
||||||
|
contentToEdit: null,
|
||||||
showGoToLastButton: false
|
showGoToLastButton: false
|
||||||
};
|
};
|
||||||
} else if (nextProps.topic != prevState.topic) {
|
} else if (nextProps.topic != prevState.topic) {
|
||||||
@@ -753,14 +760,19 @@ class MessagesView extends React.Component {
|
|||||||
msg = this.props.forwardMessage.msg;
|
msg = this.props.forwardMessage.msg;
|
||||||
head = this.props.forwardMessage.head;
|
head = this.props.forwardMessage.head;
|
||||||
this.handleCancelReply();
|
this.handleCancelReply();
|
||||||
} else if (this.state.reply && this.state.reply.content) {
|
} else if (this.state.reply) {
|
||||||
// We are replying to a message in this topic.
|
if (this.state.reply.editing) {
|
||||||
head = {reply: '' + this.state.reply.seq};
|
// Editing an existing message.
|
||||||
|
head = {replace: ':' + this.state.reply.seq};
|
||||||
|
} else if (this.state.reply.content) {
|
||||||
|
// Replying to a message in this topic.
|
||||||
// Turn it into Drafty so we can make a quoted Drafty object later.
|
// Turn it into Drafty so we can make a quoted Drafty object later.
|
||||||
|
head = {reply: '' + this.state.reply.seq};
|
||||||
if (typeof msg == 'string') {
|
if (typeof msg == 'string') {
|
||||||
msg = Drafty.parse(msg);
|
msg = Drafty.parse(msg);
|
||||||
}
|
}
|
||||||
msg = Drafty.append(Drafty.sanitizeEntities(this.state.reply.content), msg);
|
msg = Drafty.append(Drafty.sanitizeEntities(this.state.reply.content), msg);
|
||||||
|
}
|
||||||
this.handleCancelReply();
|
this.handleCancelReply();
|
||||||
}
|
}
|
||||||
this.props.sendMessage(msg, uploadCompletionPromise, uploader, head);
|
this.props.sendMessage(msg, uploadCompletionPromise, uploader, head);
|
||||||
@@ -769,7 +781,7 @@ class MessagesView extends React.Component {
|
|||||||
// Retry sending a message.
|
// Retry sending a message.
|
||||||
retrySend(pub) {
|
retrySend(pub) {
|
||||||
this.props.sendMessage(pub.content, undefined, undefined, pub.head)
|
this.props.sendMessage(pub.content, undefined, undefined, pub.head)
|
||||||
.then(() => {
|
.then(_ => {
|
||||||
// All good. Remove the original message draft from the cache.
|
// All good. Remove the original message draft from the cache.
|
||||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||||
topic.delMessagesList([pub.seq], true);
|
topic.delMessagesList([pub.seq], true);
|
||||||
@@ -977,9 +989,8 @@ class MessagesView extends React.Component {
|
|||||||
// senderId: UID of the sender of the source message.
|
// senderId: UID of the sender of the source message.
|
||||||
// senderName: full name of the sender of the original message.
|
// senderName: full name of the sender of the original message.
|
||||||
handlePickReply(seq, content, senderId, senderName) {
|
handlePickReply(seq, content, senderId, senderName) {
|
||||||
this.setState({reply: null});
|
|
||||||
|
|
||||||
if (!seq || !content) {
|
if (!seq || !content) {
|
||||||
|
this.setState({reply: null});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1001,8 +1012,38 @@ class MessagesView extends React.Component {
|
|||||||
this.props.onCancelForwardMessage();
|
this.props.onCancelForwardMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCancelReply() {
|
// seq: seq ID of the message to edit.
|
||||||
|
// context: message content.
|
||||||
|
handleEditMessage(seq, content) {
|
||||||
|
if (!seq || !content) {
|
||||||
this.setState({reply: null});
|
this.setState({reply: null});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
content = typeof content == 'string' ? Drafty.init(content) : content;
|
||||||
|
const editable = Drafty.toMarkdown(content);
|
||||||
|
if (Drafty.isValid(content)) {
|
||||||
|
content = Drafty.replyContent(content, EDIT_PREVIEW_LENGTH);
|
||||||
|
} else {
|
||||||
|
// /!\ invalid content.
|
||||||
|
content = Drafty.append(Drafty.init('\u26A0 '),
|
||||||
|
Drafty.wrapInto(this.props.intl.formatMessage(messages.invalid_content), 'EM'));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
reply: {
|
||||||
|
content: Drafty.quote(this.props.intl.formatMessage(messages.message_editing), null, content),
|
||||||
|
seq: seq,
|
||||||
|
editing: true
|
||||||
|
},
|
||||||
|
contentToEdit: editable
|
||||||
|
});
|
||||||
|
this.props.onCancelForwardMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
handleCancelReply() {
|
||||||
|
this.setState({reply: null, contentToEdit: null});
|
||||||
this.props.onCancelForwardMessage();
|
this.props.onCancelForwardMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1011,7 +1052,7 @@ class MessagesView extends React.Component {
|
|||||||
if (ref && ref.current) {
|
if (ref && ref.current) {
|
||||||
ref.current.scrollIntoView({block: "center", behavior: "smooth"});
|
ref.current.scrollIntoView({block: "center", behavior: "smooth"});
|
||||||
ref.current.classList.add('flash');
|
ref.current.classList.add('flash');
|
||||||
setTimeout(() => { ref.current.classList.remove('flash') } , 1000);
|
setTimeout(_ => {ref.current.classList.remove('flash')} , 1000);
|
||||||
} else {
|
} else {
|
||||||
console.error("Unresolved message ref", replyToSeq);
|
console.error("Unresolved message ref", replyToSeq);
|
||||||
}
|
}
|
||||||
@@ -1157,6 +1198,8 @@ class MessagesView extends React.Component {
|
|||||||
tinode={this.props.tinode}
|
tinode={this.props.tinode}
|
||||||
content={msg.content}
|
content={msg.content}
|
||||||
mimeType={msg.head && msg.head.mime}
|
mimeType={msg.head && msg.head.mime}
|
||||||
|
replyToSeq={replyToSeq}
|
||||||
|
edited={msg.head && msg.head.replace && !msg.head.webrtc}
|
||||||
timestamp={msg.ts}
|
timestamp={msg.ts}
|
||||||
response={isReply}
|
response={isReply}
|
||||||
seq={msg.seq}
|
seq={msg.seq}
|
||||||
@@ -1168,17 +1211,17 @@ class MessagesView extends React.Component {
|
|||||||
sequence={sequence}
|
sequence={sequence}
|
||||||
received={deliveryStatus}
|
received={deliveryStatus}
|
||||||
uploader={msg._uploader}
|
uploader={msg._uploader}
|
||||||
|
userIsWriter={this.state.isWriter}
|
||||||
viewportWidth={this.props.viewportWidth} // Used by `formatter`.
|
viewportWidth={this.props.viewportWidth} // Used by `formatter`.
|
||||||
showContextMenu={this.handleShowMessageContextMenu}
|
showContextMenu={this.handleShowMessageContextMenu}
|
||||||
onImagePreview={this.handleImagePostview}
|
onImagePreview={this.handleImagePostview}
|
||||||
onFormResponse={this.handleFormResponse}
|
onFormResponse={this.handleFormResponse}
|
||||||
onError={this.props.onError}
|
|
||||||
onCancelUpload={this.handleCancelUpload}
|
onCancelUpload={this.handleCancelUpload}
|
||||||
pickReply={this.handlePickReply}
|
pickReply={this.handlePickReply}
|
||||||
replyToSeq={replyToSeq}
|
editMessage={this.handleEditMessage}
|
||||||
onQuoteClick={this.handleQuoteClick}
|
onQuoteClick={this.handleQuoteClick}
|
||||||
|
onError={this.props.onError}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
userIsWriter={this.state.isWriter}
|
|
||||||
key={msg.seq} />
|
key={msg.seq} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1287,13 +1330,14 @@ class MessagesView extends React.Component {
|
|||||||
topicName={this.state.topic}
|
topicName={this.state.topic}
|
||||||
noInput={!!this.props.forwardMessage}
|
noInput={!!this.props.forwardMessage}
|
||||||
disabled={!this.state.isWriter || this.state.deleted}
|
disabled={!this.state.isWriter || this.state.deleted}
|
||||||
|
reply={this.state.reply}
|
||||||
|
initMessage={this.state.contentToEdit}
|
||||||
onKeyPress={this.sendKeyPress}
|
onKeyPress={this.sendKeyPress}
|
||||||
onSendMessage={this.sendMessage}
|
onSendMessage={this.sendMessage}
|
||||||
onAttachFile={this.props.forwardMessage ? null : this.handleAttachFile}
|
onAttachFile={this.props.forwardMessage ? null : this.handleAttachFile}
|
||||||
onAttachImage={this.props.forwardMessage ? null : this.handleAttachImage}
|
onAttachImage={this.props.forwardMessage ? null : this.handleAttachImage}
|
||||||
onAttachAudio={this.props.forwardMessage ? null : this.sendAudioAttachment}
|
onAttachAudio={this.props.forwardMessage ? null : this.sendAudioAttachment}
|
||||||
onError={this.props.onError}
|
onError={this.props.onError}
|
||||||
reply={this.state.reply}
|
|
||||||
onQuoteClick={this.handleQuoteClick}
|
onQuoteClick={this.handleQuoteClick}
|
||||||
onCancelReply={this.handleCancelReply} />}
|
onCancelReply={this.handleCancelReply} />}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
this.props.received > Tinode.MESSAGE_STATUS_FAILED &&
|
this.props.received > Tinode.MESSAGE_STATUS_FAILED &&
|
||||||
this.props.received < Tinode.MESSAGE_STATUS_DEL_RANGE) {
|
this.props.received < Tinode.MESSAGE_STATUS_DEL_RANGE) {
|
||||||
menuItems.push('menu_item_reply');
|
menuItems.push('menu_item_reply');
|
||||||
|
if (!this.props.response) {
|
||||||
|
menuItems.push('menu_item_edit');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
menuItems.push('menu_item_forward');
|
menuItems.push('menu_item_forward');
|
||||||
|
|
||||||
@@ -90,7 +93,8 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
userName: this.props.userName,
|
userName: this.props.userName,
|
||||||
y: e.pageY,
|
y: e.pageY,
|
||||||
x: e.pageX,
|
x: e.pageX,
|
||||||
pickReply: this.props.pickReply
|
pickReply: this.props.pickReply,
|
||||||
|
editMessage: this.props.editMessage,
|
||||||
}, menuItems);
|
}, menuItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +179,7 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
</div>
|
</div>
|
||||||
{this.props.timestamp ?
|
{this.props.timestamp ?
|
||||||
<ReceivedMarker
|
<ReceivedMarker
|
||||||
|
edited={this.props.edited}
|
||||||
timestamp={this.props.timestamp}
|
timestamp={this.props.timestamp}
|
||||||
received={this.props.received} />
|
received={this.props.received} />
|
||||||
: null}
|
: null}
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Forward',
|
defaultMessage: 'Forward',
|
||||||
description: 'Forward message'
|
description: 'Forward message'
|
||||||
},
|
},
|
||||||
|
edit: {
|
||||||
|
id: 'menu_item_edit',
|
||||||
|
defaultMessage: 'Edit',
|
||||||
|
description: 'Edit message'
|
||||||
|
},
|
||||||
topic_delete: {
|
topic_delete: {
|
||||||
id: 'menu_item_delete_topic',
|
id: 'menu_item_delete_topic',
|
||||||
defaultMessage: 'Delete',
|
defaultMessage: 'Delete',
|
||||||
@@ -193,6 +198,13 @@ class ContextMenu extends React.Component {
|
|||||||
title: formatMessage(messages.forward),
|
title: formatMessage(messages.forward),
|
||||||
handler: () => {} /* the action is taken directly in tinode-web */
|
handler: () => {} /* the action is taken directly in tinode-web */
|
||||||
},
|
},
|
||||||
|
'menu_item_edit': {
|
||||||
|
id: 'menu_item_edit',
|
||||||
|
title: formatMessage(messages.edit),
|
||||||
|
handler: (params, errorHandler) => {
|
||||||
|
return this.editMessage(params, errorHandler);
|
||||||
|
}
|
||||||
|
},
|
||||||
'topic_unmute': {
|
'topic_unmute': {
|
||||||
id: 'topic_unmute',
|
id: 'topic_unmute',
|
||||||
title: formatMessage(messages.unmute),
|
title: formatMessage(messages.unmute),
|
||||||
@@ -437,6 +449,10 @@ class ContextMenu extends React.Component {
|
|||||||
params.pickReply(params.seq, params.content, params.userFrom, params.userName, errorHandler);
|
params.pickReply(params.seq, params.content, params.userFrom, params.userName, errorHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
editMessage(params, errorHandler) {
|
||||||
|
params.editMessage(params.seq, params.content, errorHandler);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const menu = [];
|
const menu = [];
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ const messages = defineMessages({
|
|||||||
id: 'message_sending_failed',
|
id: 'message_sending_failed',
|
||||||
defaultMessage: 'failed',
|
defaultMessage: 'failed',
|
||||||
description: 'Failed to send message, in place of time stamp'
|
description: 'Failed to send message, in place of time stamp'
|
||||||
|
},
|
||||||
|
message_edited_marker: {
|
||||||
|
id: 'message_edited_marker',
|
||||||
|
defaultMessage: ', edited',
|
||||||
|
description: 'Marker indicating that the message was edited'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,10 +38,11 @@ class ReceivedMarker extends React.PureComponent {
|
|||||||
|
|
||||||
const icon = deliveryMarker(this.props.received);
|
const icon = deliveryMarker(this.props.received);
|
||||||
const marker = icon ? <i className={'material-icons small ' + icon.color}>{icon.name}</i> : null;
|
const marker = icon ? <i className={'material-icons small ' + icon.color}>{icon.name}</i> : null;
|
||||||
|
const edited = this.props.edited ? formatMessage(messages.message_edited_marker) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="timestamp">
|
<span className="timestamp">
|
||||||
{timestamp}{'\u00a0'}{marker}
|
{timestamp}{edited}{'\u00a0'}{marker}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,14 @@ class SendMessage extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (prevProps.topicName != this.props.topicName) {
|
if (prevProps.topicName != this.props.topicName) {
|
||||||
this.setState({message: '', audioRec: false, quote: null});
|
this.setState({message: this.props.initMessage || '', audioRec: false, quote: null});
|
||||||
|
} else if (prevProps.initMessage != this.props.initMessage) {
|
||||||
|
const msg = this.props.initMessage || '';
|
||||||
|
this.setState({message: msg}, _ => {
|
||||||
|
// If there is text, scroll to bottom and set caret to the end.
|
||||||
|
this.messageEditArea.scrollTop = this.messageEditArea.scrollHeight;
|
||||||
|
this.messageEditArea.setSelectionRange(msg.length, msg.length);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (prevProps.reply != this.props.reply) {
|
if (prevProps.reply != this.props.reply) {
|
||||||
this.setState({quote: this.formatReply()});
|
this.setState({quote: this.formatReply()});
|
||||||
@@ -193,10 +200,13 @@ class SendMessage extends React.PureComponent {
|
|||||||
formatMessage(messages[this.props.messagePrompt]) :
|
formatMessage(messages[this.props.messagePrompt]) :
|
||||||
formatMessage(messages.type_new_message));
|
formatMessage(messages.type_new_message));
|
||||||
|
|
||||||
|
const sendIcon = (this.props.reply && this.props.reply.editing) ?
|
||||||
|
'check_circle' : 'send';
|
||||||
|
|
||||||
const quote = this.state.quote ?
|
const quote = this.state.quote ?
|
||||||
(<div id="reply-quote-preview">
|
(<div id="reply-quote-preview">
|
||||||
<div className="cancel">
|
<div className="cancel">
|
||||||
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onCancelReply();}}><i className="material-icons gray">close</i></a>
|
<a href="#" onClick={e => {e.preventDefault(); this.props.onCancelReply();}}><i className="material-icons gray">close</i></a>
|
||||||
</div>
|
</div>
|
||||||
{this.state.quote}
|
{this.state.quote}
|
||||||
</div>) : null;
|
</div>) : null;
|
||||||
@@ -209,10 +219,10 @@ class SendMessage extends React.PureComponent {
|
|||||||
<>
|
<>
|
||||||
{this.props.onAttachFile && !this.state.audioRec ?
|
{this.props.onAttachFile && !this.state.audioRec ?
|
||||||
<>
|
<>
|
||||||
<a href="#" onClick={(e) => {e.preventDefault(); this.attachImage.click();}} title="Add image">
|
<a href="#" onClick={e => {e.preventDefault(); this.attachImage.click();}} title="Add image">
|
||||||
<i className="material-icons secondary">photo</i>
|
<i className="material-icons secondary">photo</i>
|
||||||
</a>
|
</a>
|
||||||
<a href="#" onClick={(e) => {e.preventDefault(); this.attachFile.click();}} title="Attach file">
|
<a href="#" onClick={e => {e.preventDefault(); this.attachFile.click();}} title="Attach file">
|
||||||
<i className="material-icons secondary">attach_file</i>
|
<i className="material-icons secondary">attach_file</i>
|
||||||
</a>
|
</a>
|
||||||
</>
|
</>
|
||||||
@@ -225,13 +235,14 @@ class SendMessage extends React.PureComponent {
|
|||||||
onDeleted={_ => this.setState({audioRec: false})}
|
onDeleted={_ => this.setState({audioRec: false})}
|
||||||
onFinished={this.handleAttachAudio}/> :
|
onFinished={this.handleAttachAudio}/> :
|
||||||
<textarea id="sendMessage" placeholder={prompt}
|
<textarea id="sendMessage" placeholder={prompt}
|
||||||
value={this.state.message} onChange={this.handleMessageTyping}
|
value={this.state.message}
|
||||||
|
onChange={this.handleMessageTyping}
|
||||||
onKeyPress={this.handleKeyPress}
|
onKeyPress={this.handleKeyPress}
|
||||||
ref={(ref) => {this.messageEditArea = ref;}}
|
ref={(ref) => {this.messageEditArea = ref;}}
|
||||||
autoFocus />)}
|
autoFocus />)}
|
||||||
{this.state.message || !audioEnabled ?
|
{this.state.message || !audioEnabled ?
|
||||||
<a href="#" onClick={this.handleSend} title="Send">
|
<a href="#" onClick={this.handleSend} title="Send">
|
||||||
<i className="material-icons">send</i>
|
<i className="material-icons">{sendIcon}</i>
|
||||||
</a> :
|
</a> :
|
||||||
!this.state.audioRec ?
|
!this.state.audioRec ?
|
||||||
<a href="#" onClick={e => {e.preventDefault(); this.setState({audioRec: true})}} title="Voice">
|
<a href="#" onClick={e => {e.preventDefault(); this.setState({audioRec: true})}} title="Voice">
|
||||||
@@ -239,9 +250,9 @@ class SendMessage extends React.PureComponent {
|
|||||||
</a> :
|
</a> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
<input type="file" ref={(ref) => {this.attachFile = ref;}}
|
<input type="file" ref={ref => {this.attachFile = ref}}
|
||||||
onChange={this.handleAttachFile} style={{display: 'none'}} />
|
onChange={this.handleAttachFile} style={{display: 'none'}} />
|
||||||
<input type="file" ref={(ref) => {this.attachImage = ref;}} accept="image/*"
|
<input type="file" ref={ref => {this.attachImage = ref}} accept="image/*"
|
||||||
onChange={this.handleAttachImage} style={{display: 'none'}} />
|
onChange={this.handleAttachImage} style={{display: 'none'}} />
|
||||||
</>
|
</>
|
||||||
:
|
:
|
||||||
|
|||||||
+99
-14
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+64
-24
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user