diff --git a/src/i18n/en.json b/src/i18n/en.json
index b4e584b9..b5f55528 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -146,6 +146,7 @@
"search_placeholder": "List like email:alice@example.com, tel:17025550003...",
"messaging_disabled_prompt": "Messaging disabled",
"new_message_prompt": "New message",
+ "image_caption_prompt": "Image caption",
"file_attachment_too_large": "The file size {size} exceeds the {limit} limit.",
"cannot_initiate_file_upload": "Cannot initiate file upload.",
"tags_not_found": "No tags defined. Add some.",
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 37088586..c22e813b 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -150,5 +150,6 @@
"topic_delete_warning": "Вы действительно хотите удалить этот чат?",
"topic_block_warning": "Вы действительно хотите заблокировать этот чат?",
"search_no_results": "Ничего не найдено",
- "deleted_content": "удалено"
+ "deleted_content": "удалено",
+ "image_caption_prompt": "Подпись к фото"
}
diff --git a/src/i18n/zh.json b/src/i18n/zh.json
index 6e8412dd..edfcfe17 100644
--- a/src/i18n/zh.json
+++ b/src/i18n/zh.json
@@ -150,5 +150,6 @@
"topic_delete_warning": ":!![MISSING]!!:Are you sure you want to delete this conversation?",
"topic_block_warning": ":!![MISSING]!!:Are you sure you want to block this conversation?",
"search_no_results": ":!![MISSING]!!:Search returned no results",
- "deleted_content": "内容已删除"
+ "deleted_content": "内容已删除",
+ "image_caption_prompt": "图片标题"
}
diff --git a/src/messages.json b/src/messages.json
index 46658966..19c36312 100644
--- a/src/messages.json
+++ b/src/messages.json
@@ -147,6 +147,7 @@
"search_placeholder": "List like email:alice@example.com, tel:17025550003...",
"messaging_disabled_prompt": "Messaging disabled",
"new_message_prompt": "New message",
+ "image_caption_prompt": "Image caption",
"file_attachment_too_large": "The file size {size} exceeds the {limit} limit.",
"cannot_initiate_file_upload": "Cannot initiate file upload.",
"tags_not_found": "No tags defined. Add some.",
@@ -305,7 +306,8 @@
"topic_delete_warning": "Вы действительно хотите удалить этот чат?",
"topic_block_warning": "Вы действительно хотите заблокировать этот чат?",
"search_no_results": "Ничего не найдено",
- "deleted_content": "удалено"
+ "deleted_content": "удалено",
+ "image_caption_prompt": "Подпись к фото"
},
"zh": {
"archived_contacts": "已归档联系人 ({count})",
@@ -450,6 +452,7 @@
"action_delete_messages": "删除所有帖子",
"action_block_contact": "屏蔽联系人",
"action_report_chat": "检举垃圾邮件",
- "deleted_content": "内容已删除"
+ "deleted_content": "内容已删除",
+ "image_caption_prompt": "图片标题"
}
}
diff --git a/src/views/messages-view.jsx b/src/views/messages-view.jsx
index 3f06002b..dc77ee68 100644
--- a/src/views/messages-view.jsx
+++ b/src/views/messages-view.jsx
@@ -14,9 +14,11 @@ import LoadSpinner from '../widgets/load-spinner.jsx';
import LogoView from './logo-view.jsx';
import SendMessage from '../widgets/send-message.jsx';
-import { DEFAULT_P2P_ACCESS_MODE, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE } from '../config.js';
-import { SUPPORTED_IMAGE_FORMATS, filePasted, fileToBase64, imageFileToBase64, imageFileScaledToBase64, makeImageUrl } from '../lib/blob-helpers.js';
-import { shortDateFormat } from '../lib/strformat.js';
+import { DEFAULT_P2P_ACCESS_MODE, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE,
+ MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE } from '../config.js';
+import { SUPPORTED_IMAGE_FORMATS, filePasted, fileToBase64, imageFileToBase64,
+ imageFileScaledToBase64, makeImageUrl } from '../lib/blob-helpers.js';
+import { bytesToHumanSize, shortDateFormat } from '../lib/strformat.js';
const messages = defineMessages({
online_now: {
@@ -61,6 +63,9 @@ class MessagesView extends React.Component {
// this.propsChange = this.propsChange.bind(this);
this.leave = this.leave.bind(this);
+ this.sendImageAttachment = this.sendImageAttachment.bind(this);
+ this.sendFileAttachment = this.sendFileAttachment.bind(this);
+ this.sendKeyPress = this.sendKeyPress.bind(this);
this.handleScrollReference = this.handleScrollReference.bind(this);
this.handleScrollEvent = this.handleScrollEvent.bind(this);
this.handleDescChange = this.handleDescChange.bind(this);
@@ -68,7 +73,8 @@ class MessagesView extends React.Component {
this.handleNewMessage = this.handleNewMessage.bind(this);
this.handleAllMessagesReceived = this.handleAllMessagesReceived.bind(this);
this.handleInfoReceipt = this.handleInfoReceipt.bind(this);
- this.handleImagePreview = this.handleImagePreview.bind(this);
+ this.handlePreImagePreview = this.handlePreImagePreview.bind(this);
+ this.handlePostImagePreview = this.handlePostImagePreview.bind(this);
this.handleCloseImagePreview = this.handleCloseImagePreview.bind(this);
this.handleFormResponse = this.handleFormResponse.bind(this);
this.handleContextClick = this.handleContextClick.bind(this);
@@ -77,7 +83,6 @@ class MessagesView extends React.Component {
this.handleEnablePeer = this.handleEnablePeer.bind(this);
this.handleAttachFile = this.handleAttachFile.bind(this);
this.handleAttachImage = this.handleAttachImage.bind(this);
- this.handleNewImagePreview = this.handleNewImagePreview.bind(this);
}
componentDidMount() {
@@ -172,7 +177,8 @@ class MessagesView extends React.Component {
topic: null,
title: '',
avatar: null,
- imagePreview: null,
+ imagePrePreview: null,
+ imagePostPreview: null,
typingIndicator: false,
scrollPosition: 0,
fetchingMessages: false,
@@ -182,7 +188,8 @@ class MessagesView extends React.Component {
const topic = nextProps.tinode.getTopic(nextProps.topic);
nextState = {
topic: nextProps.topic,
- imagePreview: null,
+ imagePrePreview: null,
+ imagePostPreview: null,
typingIndicator: false,
scrollPosition: 0,
fetchingMessages: false
@@ -435,12 +442,12 @@ class MessagesView extends React.Component {
}
}
- handleImagePreview(content) {
- this.setState({ imagePreview: content });
+ handlePostImagePreview(content) {
+ this.setState({ imagePostPreview: content });
}
handleCloseImagePreview() {
- this.setState({ imagePreview: null });
+ this.setState({ imagePostPreview: null, imagePrePreview: null });
}
handleFormResponse(action, text, data) {
@@ -495,7 +502,14 @@ class MessagesView extends React.Component {
this.props.onChangePermissions(this.state.topic, DEFAULT_P2P_ACCESS_MODE, this.state.topic);
}
- handleAttachFile(mime, bits, fname, size, uploadCompletionPromise, uploader) {
+ sendKeyPress() {
+ const topic = this.props.tinode.getTopic(this.state.topic);
+ if (topic.isSubscribed()) {
+ topic.noteKeyPress();
+ }
+ }
+
+ sendFileAttachment(mime, bits, fname, size, uploadCompletionPromise, uploader) {
if (uploadCompletionPromise) {
// Format data and initiate upload.
const msg = Drafty.attachFile(null, mime, null, fname, size, uploadCompletionPromise);
@@ -506,9 +520,42 @@ class MessagesView extends React.Component {
}
}
- handleNewImagePreview(content) {
- this.setState({ imagePreview: content });
- // this.props.sendMessage(Drafty.insertImage(null, 0, mime, bits, width, height, fname));
+ handleAttachFile(file) {
+ if (file.size > MAX_EXTERN_ATTACHMENT_SIZE) {
+ // Too large.
+ this.props.onError(formatMessage(messages.file_attachment_too_large,
+ {size: bytesToHumanSize(file.size), limit: bytesToHumanSize(MAX_EXTERN_ATTACHMENT_SIZE)}), 'err');
+ } else if (file.size > MAX_INBAND_ATTACHMENT_SIZE) {
+ // Too large to send inband - uploading out of band and sending as a link.
+ const uploader = this.props.tinode.getLargeFileHelper();
+ if (!uploader) {
+ this.props.onError(formatMessage(messages.cannot_initiate_upload));
+ return;
+ }
+ // Pass data and the uploader to the TinodeWeb.
+ this.sendFileAttachment(file.type, null, file.name, file.size, uploadCompletionPromise, uploader);
+ } else {
+ // Small enough to send inband.
+ fileToBase64(file,
+ (mime, bits, fname) => {
+ this.sendFileAttachment(mime, bits, fname);
+ },
+ this.props.onError
+ );
+ }
+ }
+
+ sendImageAttachment(caption, mime, bits, width, height, fname) {
+ let msg = Drafty.insertImage(null, 0, mime, bits, width, height, fname);
+ if (caption) {
+ msg = Drafty.appendLineBreak(msg);
+ msg = Drafty.append(msg, Drafty.init(caption));
+ }
+ this.props.sendMessage(msg);
+ }
+
+ handlePreImagePreview(content) {
+ this.setState({ imagePrePreview: content });
}
handleAttachImage(file) {
@@ -518,8 +565,9 @@ class MessagesView extends React.Component {
imageFileScaledToBase64(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, false,
// Success
(bits, mime, width, height, fname) => {
- this.handleNewImagePreview({
+ this.handlePreImagePreview({
url: URL.createObjectURL(file),
+ bits: bits,
filename: fname,
width: width,
height: height,
@@ -536,8 +584,9 @@ class MessagesView extends React.Component {
imageFileToBase64(file,
// Success
(bits, mime, width, height, fname) => {
- this.handleNewImagePreview({
+ this.handlePreImagePreview({
url: URL.createObjectURL(file),
+ bits: bits,
filename: fname,
width: width,
height: height,
@@ -567,13 +616,20 @@ class MessagesView extends React.Component {
);
} else {
let component2;
- if (this.state.imagePreview) {
+ if (this.state.imagePrePreview) {
+ // Preview image before sending.
component2 = (
+ onSendMessage={this.sendImageAttachment} />
+ );
+ } else if (this.state.imagePostPreview) {
+ // Expand received image.
+ component2 = (
+
);
} else {
const topic = this.props.tinode.getTopic(this.state.topic);
@@ -627,7 +683,7 @@ class MessagesView extends React.Component {
sequence={sequence} received={deliveryStatus} uploader={msg._uploader}
viewportWidth={this.props.viewportWidth}
showContextMenu={this.handleShowContextMenuMessage}
- onImagePreview={this.handleImagePreview}
+ onImagePreview={this.handlePostImagePreview}
onFormResponse={this.handleFormResponse}
onError={this.props.onError}
key={msg.seq} />
@@ -723,6 +779,7 @@ class MessagesView extends React.Component {
topic={this.props.topic}
disabled={!this.state.isWriter}
onSendMessage={this.props.sendMessage}
+ onKeyPress={this.sendKeyPress}
onAttachFile={this.handleAttachFile}
onAttachImage={this.handleAttachImage}
onError={this.props.onError} />}
diff --git a/src/widgets/image-preview.jsx b/src/widgets/image-preview.jsx
index 3f8a4834..e69a509f 100644
--- a/src/widgets/image-preview.jsx
+++ b/src/widgets/image-preview.jsx
@@ -14,6 +14,8 @@ export default class ImagePreview extends React.PureComponent {
width: 0,
height: 0
};
+
+ this.handleSendImage = this.handleSendImage.bind(this);
}
assignWidth(node) {
@@ -26,6 +28,12 @@ export default class ImagePreview extends React.PureComponent {
}
}
+ handleSendImage(caption) {
+ this.props.onClose();
+ this.props.onSendMessage(caption, this.props.content.type, this.props.content.bits,
+ this.props.content.width, this.props.content.height, this.props.content.filename);
+ }
+
render() {
if (!this.props.content) {
return null;
@@ -47,15 +55,16 @@ export default class ImagePreview extends React.PureComponent {
const width = this.props.content.width || '-';
const height = this.props.content.height || '-';
return (
-
+
this.assignWidth(node)}>
@@ -65,7 +74,9 @@ export default class ImagePreview extends React.PureComponent {
: