merge conflicts resolved
This commit is contained in:
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
src/i18n/src/
|
src/i18n/srci/
|
||||||
|
webapp.code-workspace.code-workspace
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ More translations are welcome. See [instructions](https://github.com/tinode/chat
|
|||||||
|
|
||||||
## Not done yet
|
## Not done yet
|
||||||
|
|
||||||
|
* Voice or video messages, video or audio calling.
|
||||||
* End-to-End encryption.
|
* End-to-End encryption.
|
||||||
* Emoji support is weak.
|
* Emoji support is weak.
|
||||||
* Mentions, hashtags.
|
* Mentions, hashtags.
|
||||||
|
|||||||
+15
-1
@@ -236,6 +236,7 @@ textarea.inplace-edit {
|
|||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.2);
|
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.2);
|
||||||
|
box-shadow: inset 0 0 6px rgba(0,0,0,0.2);
|
||||||
background-color: rgba(255,255,255,0.1);
|
background-color: rgba(255,255,255,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2512,7 +2513,11 @@ ul.tabbar li.active > a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-box .bubble .reply-quote {
|
.chat-box .bubble .reply-quote {
|
||||||
margin: -0.25rem -0.75rem 0.35rem -0.75rem;
|
margin: 0.15rem -0.75rem 0.3rem -0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-box .bubble .reply-quote:first-child {
|
||||||
|
margin-top: -0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-box .left .reply-quote {
|
.chat-box .left .reply-quote {
|
||||||
@@ -2528,6 +2533,10 @@ ul.tabbar li.active > a {
|
|||||||
margin-bottom: 0.1rem;
|
margin-bottom: 0.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reply-quote img.inline-image::before {
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
#reply-quote-preview {
|
#reply-quote-preview {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -2560,6 +2569,11 @@ ul.tabbar li.active > a {
|
|||||||
margin-bottom: 0.35rem;
|
margin-bottom: 0.35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.forwarding-quote {
|
||||||
|
font-size: 200%;
|
||||||
|
line-height: 200%;
|
||||||
|
}
|
||||||
|
|
||||||
.mention {
|
.mention {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Generated
+459
-970
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "tinode-webapp",
|
"name": "tinode-webapp",
|
||||||
"description": "Tinode messenger for the web",
|
"description": "Tinode messenger for the web",
|
||||||
"version": "0.18.0-beta2",
|
"version": "0.18.0-rc3",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/tinode/example-react-js.git"
|
"url": "https://github.com/tinode/example-react-js.git"
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-intl": "^5.20.2",
|
"react-intl": "^5.20.2",
|
||||||
"tinode-sdk": "^0.18.0-beta1"
|
"tinode-sdk": "^0.18.0-rc3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.14.5",
|
"@babel/core": "^7.14.5",
|
||||||
|
|||||||
@@ -1353,5 +1353,12 @@
|
|||||||
"description": "Contact search prompt when forwarding a message.",
|
"description": "Contact search prompt when forwarding a message.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_new_password": {
|
||||||
|
"translation": "Neues Kennwort",
|
||||||
|
"defaultMessage": "New password",
|
||||||
|
"description": "Label for entering a new password",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-56
@@ -34,13 +34,6 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"action_more": {
|
|
||||||
"translation": "More",
|
|
||||||
"defaultMessage": "More",
|
|
||||||
"description": "Action for showing more content",
|
|
||||||
"missing": false,
|
|
||||||
"obsolete": true
|
|
||||||
},
|
|
||||||
"action_report_chat": {
|
"action_report_chat": {
|
||||||
"translation": "Report Conversation",
|
"translation": "Report Conversation",
|
||||||
"defaultMessage": "Report Conversation",
|
"defaultMessage": "Report Conversation",
|
||||||
@@ -461,13 +454,6 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"label_default_access": {
|
|
||||||
"translation": "Default access mode:",
|
|
||||||
"defaultMessage": "Default access mode:",
|
|
||||||
"description": "Section title",
|
|
||||||
"missing": false,
|
|
||||||
"obsolete": true
|
|
||||||
},
|
|
||||||
"label_default_access_mode": {
|
"label_default_access_mode": {
|
||||||
"translation": "Default access mode:",
|
"translation": "Default access mode:",
|
||||||
"defaultMessage": "Default access mode:",
|
"defaultMessage": "Default access mode:",
|
||||||
@@ -1033,13 +1019,6 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"sidepanel_title_acc_general": {
|
|
||||||
"translation": "General",
|
|
||||||
"defaultMessage": "General",
|
|
||||||
"description": "Sidepanel title for TopicCommonView.",
|
|
||||||
"missing": false,
|
|
||||||
"obsolete": true
|
|
||||||
},
|
|
||||||
"sidepanel_title_acc_notifications": {
|
"sidepanel_title_acc_notifications": {
|
||||||
"translation": "Notifications",
|
"translation": "Notifications",
|
||||||
"defaultMessage": "Notifications",
|
"defaultMessage": "Notifications",
|
||||||
@@ -1047,13 +1026,6 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"sidepanel_title_acc_security": {
|
|
||||||
"translation": "Security",
|
|
||||||
"defaultMessage": "Security",
|
|
||||||
"description": "Sidepanel title for AccSecurityView.",
|
|
||||||
"missing": false,
|
|
||||||
"obsolete": true
|
|
||||||
},
|
|
||||||
"sidepanel_title_acc_support": {
|
"sidepanel_title_acc_support": {
|
||||||
"translation": "Support",
|
"translation": "Support",
|
||||||
"defaultMessage": "Support",
|
"defaultMessage": "Support",
|
||||||
@@ -1089,13 +1061,6 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"sidepanel_title_crop": {
|
|
||||||
"translation": "Drag to Adjust",
|
|
||||||
"defaultMessage": "Drag to Adjust",
|
|
||||||
"description": "Sidepanel title for AvatarCropView.",
|
|
||||||
"missing": false,
|
|
||||||
"obsolete": true
|
|
||||||
},
|
|
||||||
"sidepanel_title_login": {
|
"sidepanel_title_login": {
|
||||||
"translation": "Sign In",
|
"translation": "Sign In",
|
||||||
"defaultMessage": "Sign In",
|
"defaultMessage": "Sign In",
|
||||||
@@ -1187,13 +1152,6 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"title_info": {
|
|
||||||
"translation": "Info",
|
|
||||||
"defaultMessage": "Info",
|
|
||||||
"description": "Title for InfoView",
|
|
||||||
"missing": false,
|
|
||||||
"obsolete": true
|
|
||||||
},
|
|
||||||
"title_manage_tags": {
|
"title_manage_tags": {
|
||||||
"translation": "Manage",
|
"translation": "Manage",
|
||||||
"defaultMessage": "Manage",
|
"defaultMessage": "Manage",
|
||||||
@@ -1299,20 +1257,6 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
"panel_title_acc_general": {
|
|
||||||
"translation": "General",
|
|
||||||
"defaultMessage": "General",
|
|
||||||
"description": "Title for TopicCommonView.",
|
|
||||||
"missing": false,
|
|
||||||
"obsolete": true
|
|
||||||
},
|
|
||||||
"panel_title_acc_security": {
|
|
||||||
"translation": "Security",
|
|
||||||
"defaultMessage": "Security",
|
|
||||||
"description": "Title for TopicSecirityView and AccSecurityView.",
|
|
||||||
"missing": false,
|
|
||||||
"obsolete": true
|
|
||||||
},
|
|
||||||
"panel_title_crop": {
|
"panel_title_crop": {
|
||||||
"translation": "Drag to Adjust",
|
"translation": "Drag to Adjust",
|
||||||
"defaultMessage": "Drag to Adjust",
|
"defaultMessage": "Drag to Adjust",
|
||||||
@@ -1409,5 +1353,12 @@
|
|||||||
"description": "Contact search prompt when forwarding a message.",
|
"description": "Contact search prompt when forwarding a message.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_new_password": {
|
||||||
|
"translation": "New password",
|
||||||
|
"defaultMessage": "New password",
|
||||||
|
"description": "Label for entering a new password",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1353,5 +1353,12 @@
|
|||||||
"description": "Contact search prompt when forwarding a message.",
|
"description": "Contact search prompt when forwarding a message.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_new_password": {
|
||||||
|
"translation": "Nueva contraseña",
|
||||||
|
"defaultMessage": "New password",
|
||||||
|
"description": "Label for entering a new password",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -302,6 +302,10 @@
|
|||||||
"defaultMessage": "Muted:",
|
"defaultMessage": "Muted:",
|
||||||
"description": "Label for Muting/unmuting the topic"
|
"description": "Label for Muting/unmuting the topic"
|
||||||
},
|
},
|
||||||
|
"label_new_password": {
|
||||||
|
"defaultMessage": "New password",
|
||||||
|
"description": "Label for entering a new password"
|
||||||
|
},
|
||||||
"label_other_user": {
|
"label_other_user": {
|
||||||
"defaultMessage": "Other",
|
"defaultMessage": "Other",
|
||||||
"description": "Label for the other user when the user is unnamed"
|
"description": "Label for the other user when the user is unnamed"
|
||||||
|
|||||||
@@ -1353,5 +1353,12 @@
|
|||||||
"description": "Contact search prompt when forwarding a message.",
|
"description": "Contact search prompt when forwarding a message.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_new_password": {
|
||||||
|
"translation": "새 비밀번호",
|
||||||
|
"defaultMessage": "New password",
|
||||||
|
"description": "Label for entering a new password",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1353,5 +1353,12 @@
|
|||||||
"description": "Contact search prompt when forwarding a message.",
|
"description": "Contact search prompt when forwarding a message.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_new_password": {
|
||||||
|
"translation": "Parolă Nouă",
|
||||||
|
"defaultMessage": "New password",
|
||||||
|
"description": "Label for entering a new password",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1353,5 +1353,12 @@
|
|||||||
"description": "Contact search prompt when forwarding a message.",
|
"description": "Contact search prompt when forwarding a message.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_new_password": {
|
||||||
|
"translation": "Новый пароль",
|
||||||
|
"defaultMessage": "New password",
|
||||||
|
"description": "Label for entering a new password",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1353,5 +1353,12 @@
|
|||||||
"description": "Contact search prompt when forwarding a message.",
|
"description": "Contact search prompt when forwarding a message.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_new_password": {
|
||||||
|
"translation": "新密碼",
|
||||||
|
"defaultMessage": "New password",
|
||||||
|
"description": "Label for entering a new password",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1353,5 +1353,12 @@
|
|||||||
"description": "Contact search prompt when forwarding a message.",
|
"description": "Contact search prompt when forwarding a message.",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_new_password": {
|
||||||
|
"translation": "新密码",
|
||||||
|
"defaultMessage": "New password",
|
||||||
|
"description": "Label for entering a new password",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
// File and image helper functions.
|
// File and image helper functions.
|
||||||
import { MAX_INBAND_ATTACHMENT_SIZE, MAX_IMAGE_DIM } from '../config.js';
|
|
||||||
import { bytesToHumanSize } from './strformat.js'
|
|
||||||
|
|
||||||
// Supported image MIME types and corresponding file extensions.
|
// Supported image MIME types and corresponding file extensions.
|
||||||
export const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];
|
export const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];
|
||||||
@@ -9,11 +7,13 @@ export const MIME_EXTENSIONS = ['jpg', 'gif', 'png',
|
|||||||
// Get an URL from a theCard photo: either create a data: URL or return reference URL.
|
// Get an URL from a theCard photo: either create a data: URL or return reference URL.
|
||||||
export function makeImageUrl(photo) {
|
export function makeImageUrl(photo) {
|
||||||
if (photo && typeof photo == 'object') {
|
if (photo && typeof photo == 'object') {
|
||||||
|
if (photo.ref) {
|
||||||
|
return photo.ref
|
||||||
|
}
|
||||||
if (photo.data && photo.type) {
|
if (photo.data && photo.type) {
|
||||||
const mime = photo.type.startsWith('image/') ? photo.type : ('image/' + photo.type);
|
const mime = photo.type.startsWith('image/') ? photo.type : ('image/' + photo.type);
|
||||||
return 'data:' + mime + ';base64,' + photo.data;
|
return 'data:' + mime + ';base64,' + photo.data;
|
||||||
}
|
}
|
||||||
return photo.ref;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-26
@@ -124,25 +124,31 @@ function quotedImage(data) {
|
|||||||
// onImagePreview: this.handleImagePreview
|
// onImagePreview: this.handleImagePreview
|
||||||
// onFormButtonClick: this.handleFormButtonClick
|
// onFormButtonClick: this.handleFormButtonClick
|
||||||
// onQuoteClick: this.handleQuoteClick (optional)
|
// onQuoteClick: this.handleQuoteClick (optional)
|
||||||
export function fullFormatter(style, data, values, key) {
|
export function fullFormatter(style, data, values, key, stack) {
|
||||||
|
if (stack.includes('QQ')) {
|
||||||
|
return quoteFormatter.call(this, style, data, values, key);
|
||||||
|
}
|
||||||
|
|
||||||
if (!style) {
|
if (!style) {
|
||||||
// Unformatted.
|
// Unformatted.
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style == 'HD') {
|
|
||||||
// Hidden.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let el = Drafty.tagName(style);
|
let el = Drafty.tagName(style);
|
||||||
const attr = Drafty.attrValue(style, data) || {};
|
const attr = Drafty.attrValue(style, data) || {};
|
||||||
attr.key = key;
|
attr.key = key;
|
||||||
switch (style) {
|
switch (style) {
|
||||||
|
case 'BR':
|
||||||
|
values = null;
|
||||||
|
break;
|
||||||
case 'HL':
|
case 'HL':
|
||||||
// Highlighted text. Assign class name.
|
// Highlighted text. Assign class name.
|
||||||
attr.className = 'highlight';
|
attr.className = 'highlight';
|
||||||
break;
|
break;
|
||||||
|
case 'HD':
|
||||||
|
el = null;
|
||||||
|
values = null;
|
||||||
|
break;
|
||||||
case 'IM':
|
case 'IM':
|
||||||
// Additional processing for images
|
// Additional processing for images
|
||||||
el = handleImageData.call(this, el, data, attr);
|
el = handleImageData.call(this, el, data, attr);
|
||||||
@@ -192,7 +198,7 @@ export function fullFormatter(style, data, values, key) {
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
return React.createElement(el, attr, values);
|
return React.createElement(el, attr, values);
|
||||||
};
|
}
|
||||||
|
|
||||||
// Converts Drafty object into a one-line preview. 'this' is set by the caller.
|
// Converts Drafty object into a one-line preview. 'this' is set by the caller.
|
||||||
// 'this' must contain:
|
// 'this' must contain:
|
||||||
@@ -204,11 +210,6 @@ export function previewFormatter(style, data, values, key) {
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style == 'HD') {
|
|
||||||
// Hidden.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let el = Drafty.tagName(style);
|
let el = Drafty.tagName(style);
|
||||||
const attr = { key: key };
|
const attr = { key: key };
|
||||||
switch (style) {
|
switch (style) {
|
||||||
@@ -256,6 +257,11 @@ export function previewFormatter(style, data, values, key) {
|
|||||||
el = React.Fragment;
|
el = React.Fragment;
|
||||||
values = [<i key="ex" className="material-icons">attachment</i>, this.formatMessage(messages.drafty_attachment)];
|
values = [<i key="ex" className="material-icons">attachment</i>, this.formatMessage(messages.drafty_attachment)];
|
||||||
break;
|
break;
|
||||||
|
case 'QQ':
|
||||||
|
case 'HD':
|
||||||
|
el = null;
|
||||||
|
values = null;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
if (el == '_UNKN') {
|
if (el == '_UNKN') {
|
||||||
el = React.Fragment;
|
el = React.Fragment;
|
||||||
@@ -292,12 +298,15 @@ function inlineImageAttr(attr, data) {
|
|||||||
// messages: formatjs messages defined with defineMessages.
|
// messages: formatjs messages defined with defineMessages.
|
||||||
// authorizeURL: this.props.tinode.authorizeURL
|
// authorizeURL: this.props.tinode.authorizeURL
|
||||||
// onQuoteClick: this.handleQuoteClick (optional)
|
// onQuoteClick: this.handleQuoteClick (optional)
|
||||||
export function quoteFormatter(style, data, values, key) {
|
function quoteFormatter(style, data, values, key) {
|
||||||
if (['BR', 'EX', 'IM', 'MN', 'QQ'].includes(style)) {
|
if (['BR', 'EX', 'IM', 'MN'].includes(style)) {
|
||||||
let el = Drafty.tagName(style);
|
let el = Drafty.tagName(style);
|
||||||
let attr = Drafty.attrValue(style, data) || {};
|
let attr = Drafty.attrValue(style, data) || {};
|
||||||
attr.key = key;
|
attr.key = key;
|
||||||
switch(style) {
|
switch(style) {
|
||||||
|
case 'BR':
|
||||||
|
values = null;
|
||||||
|
break;
|
||||||
case 'IM':
|
case 'IM':
|
||||||
attr = inlineImageAttr.call(this, attr, data);
|
attr = inlineImageAttr.call(this, attr, data);
|
||||||
values = [React.createElement('img', attr, null), ' ', attr.alt];
|
values = [React.createElement('img', attr, null), ' ', attr.alt];
|
||||||
@@ -312,10 +321,6 @@ export function quoteFormatter(style, data, values, key) {
|
|||||||
attr.className += ' ' + idToColorClass(data.val, false, true);
|
attr.className += ' ' + idToColorClass(data.val, false, true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'QQ':
|
|
||||||
attr.className = 'reply-quote';
|
|
||||||
attr.onClick = this.onQuoteClick;
|
|
||||||
break;
|
|
||||||
case 'EX':
|
case 'EX':
|
||||||
let fname;
|
let fname;
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -344,15 +349,21 @@ function quoteImage(data) {
|
|||||||
// Get the blob from the image data.
|
// Get the blob from the image data.
|
||||||
if (data.val) {
|
if (data.val) {
|
||||||
const blob = base64ToBlob(data.val, data.mime);
|
const blob = base64ToBlob(data.val, data.mime);
|
||||||
promise = blob ? Promise.resolve(blob) : Promise.reject(new Error("Invalid image"));
|
if (!blob) {
|
||||||
} else {
|
throw new Error("Invalid image");
|
||||||
promise = fetch(this.authorizeURL(sanitizeImageUrl(data.ref))).then(evt => {
|
}
|
||||||
|
promise = Promise.resolve(blob);
|
||||||
|
} else if (data.ref) {
|
||||||
|
promise = fetch(this.authorizeURL(sanitizeImageUrl(data.ref)))
|
||||||
|
.then(evt => {
|
||||||
if (evt.ok) {
|
if (evt.ok) {
|
||||||
return evt.blob();
|
return evt.blob();
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Image fetch unsuccessful: ${evt.status} ${evt.statusText}`);
|
throw new Error(`Image fetch unsuccessful: ${evt.status} ${evt.statusText}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error("Missing image data");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale the blob.
|
// Scale the blob.
|
||||||
@@ -384,12 +395,29 @@ function quoteImage(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a preview of a reply.
|
// Create a preview of a reply.
|
||||||
export function replyFormatter(style, data, values, key) {
|
export function replyFormatter(style, data, values, key, stack) {
|
||||||
if (style != 'IM') {
|
if (style == 'IM') {
|
||||||
return quoteFormatter.call(this, style, data, values, key);
|
|
||||||
}
|
|
||||||
const attr = inlineImageAttr.call(this, {key: key}, data);
|
const attr = inlineImageAttr.call(this, {key: key}, data);
|
||||||
attr.whenDone = cancelablePromise(quoteImage.call(this, data));
|
let loadedPromise;
|
||||||
|
try {
|
||||||
|
loadedPromise = cancelablePromise(quoteImage.call(this, data));
|
||||||
|
} catch (error) {
|
||||||
|
loadedPromise = cancelablePromise(error);
|
||||||
|
}
|
||||||
|
attr.whenDone = loadedPromise;
|
||||||
values = [React.createElement(LazyImage, attr, null), ' ', attr.alt];
|
values = [React.createElement(LazyImage, attr, null), ' ', attr.alt];
|
||||||
return React.createElement(React.Fragment, {key: key}, values);
|
return React.createElement(React.Fragment, {key: key}, values);
|
||||||
|
} else if (style == 'QQ') {
|
||||||
|
if (stack.includes('QQ')) {
|
||||||
|
// Quote inside quote when forwarding a message.
|
||||||
|
return React.createElement('span', {key: key},
|
||||||
|
[<i key="qq" className="material-icons">format_quote</i>, ' ']);
|
||||||
|
}
|
||||||
|
|
||||||
|
const attr = Drafty.attrValue('QQ', data) || {};
|
||||||
|
attr.key = key;
|
||||||
|
attr.className = 'reply-quote'
|
||||||
|
return React.createElement(Drafty.tagName('QQ'), attr, values);
|
||||||
|
}
|
||||||
|
return quoteFormatter.call(this, style, data, values, key);
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -183,10 +183,14 @@ export function deliveryMarker(received) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wraps a promise to make it cancelable.
|
// Wraps a promise to make it cancelable.
|
||||||
|
// The parameter can be either a promise or an error. If it's an error, the wrapped promise is
|
||||||
|
// created in a rejected state.
|
||||||
export function cancelablePromise(promise) {
|
export function cancelablePromise(promise) {
|
||||||
let hasCanceled = false;
|
let hasCanceled = false;
|
||||||
|
|
||||||
const wrappedPromise = new Promise((resolve, reject) => {
|
const wrappedPromise = promise instanceof Error ?
|
||||||
|
Promise.reject(promise) :
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
promise.then(
|
promise.then(
|
||||||
result => hasCanceled ? reject({isCanceled: true}) : resolve(result),
|
result => hasCanceled ? reject({isCanceled: true}) : resolve(result),
|
||||||
error => hasCanceled ? reject({isCanceled: true}) : reject(error)
|
error => hasCanceled ? reject({isCanceled: true}) : reject(error)
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
// This is a generated file. Don't edit.
|
// This is a generated file. Don't edit.
|
||||||
|
|
||||||
export const PACKAGE_VERSION = "0.18.0-beta2";
|
export const PACKAGE_VERSION = "0.18.0-rc3";
|
||||||
|
|||||||
@@ -223,7 +223,6 @@ class MessagesView extends React.Component {
|
|||||||
const preview = nextProps.forwardMessage.preview;
|
const preview = nextProps.forwardMessage.preview;
|
||||||
reply = {
|
reply = {
|
||||||
content: preview,
|
content: preview,
|
||||||
forwarded: nextProps.forwardMessage.head.forwarded,
|
|
||||||
seq: null
|
seq: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -713,7 +712,7 @@ class MessagesView extends React.Component {
|
|||||||
if (typeof msg == 'string') {
|
if (typeof msg == 'string') {
|
||||||
msg = Drafty.parse(msg);
|
msg = Drafty.parse(msg);
|
||||||
}
|
}
|
||||||
msg = Drafty.append(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);
|
||||||
@@ -867,18 +866,21 @@ class MessagesView extends React.Component {
|
|||||||
uploader.cancel();
|
uploader.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePickReply(seq, content, forwarded, senderId, senderName) {
|
// seq: seq ID of the source message
|
||||||
|
// context: message content.
|
||||||
|
// forwarded: true if the source message is also a forwarded message.
|
||||||
|
// senderId: UID of the sender of the source message.
|
||||||
|
// senderName: full name of the sender of the original message.
|
||||||
|
handlePickReply(seq, content, senderId, senderName) {
|
||||||
this.setState({reply: null});
|
this.setState({reply: null});
|
||||||
|
|
||||||
if (!seq || !content) {
|
if (!seq || !content) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
content = forwarded ?
|
content = typeof content == 'string' ? Drafty.init(content) : content;
|
||||||
Drafty.forwardedContent(content) :
|
|
||||||
typeof content == 'string' ? Drafty.init(content) : content;
|
|
||||||
if (Drafty.isValid(content)) {
|
if (Drafty.isValid(content)) {
|
||||||
content = Drafty.preview(content, QUOTED_REPLY_LENGTH, undefined, !forwarded);
|
content = Drafty.replyContent(content, QUOTED_REPLY_LENGTH);
|
||||||
} else {
|
} else {
|
||||||
// /!\ invalid content.
|
// /!\ invalid content.
|
||||||
content = Drafty.append(Drafty.init('\u26A0 '),
|
content = Drafty.append(Drafty.init('\u26A0 '),
|
||||||
@@ -1010,7 +1012,6 @@ class MessagesView extends React.Component {
|
|||||||
<ChatMessage
|
<ChatMessage
|
||||||
tinode={this.props.tinode}
|
tinode={this.props.tinode}
|
||||||
content={msg.content}
|
content={msg.content}
|
||||||
forwarded={msg.head ? msg.head.forwarded : null}
|
|
||||||
deleted={msg.hi}
|
deleted={msg.hi}
|
||||||
mimeType={msg.head ? msg.head.mime : null}
|
mimeType={msg.head ? msg.head.mime : null}
|
||||||
timestamp={msg.ts}
|
timestamp={msg.ts}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class NewTopicView extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
tabSelected: 'p2p',
|
tabSelected: 'find',
|
||||||
searchQuery: null
|
searchQuery: null
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ class NewTopicView extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleContactSelected(uid) {
|
handleContactSelected(uid) {
|
||||||
if (this.state.tabSelected == 'p2p') {
|
if (this.state.tabSelected == 'find') {
|
||||||
HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));
|
HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));
|
||||||
this.props.onCreateTopic(uid);
|
this.props.onCreateTopic(uid);
|
||||||
}
|
}
|
||||||
@@ -87,8 +87,8 @@ class NewTopicView extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="flex-column">
|
<div className="flex-column">
|
||||||
<ul className="tabbar">
|
<ul className="tabbar">
|
||||||
<li className={this.state.tabSelected === "p2p" ? "active" : null}>
|
<li className={this.state.tabSelected === "find" ? "active" : null}>
|
||||||
<a href="#" data-id="p2p" onClick={this.handleTabClick}>
|
<a href="#" data-id="find" onClick={this.handleTabClick}>
|
||||||
<FormattedMessage id="tabtitle_find_user" defaultMessage="find"
|
<FormattedMessage id="tabtitle_find_user" defaultMessage="find"
|
||||||
description="Tab title Find" />
|
description="Tab title Find" />
|
||||||
</a>
|
</a>
|
||||||
@@ -116,7 +116,6 @@ class NewTopicView extends React.Component {
|
|||||||
onError={this.props.onError} /> :
|
onError={this.props.onError} /> :
|
||||||
<div className="flex-column">
|
<div className="flex-column">
|
||||||
<SearchContacts
|
<SearchContacts
|
||||||
type="p2p"
|
|
||||||
placeholder={search_placeholder}
|
placeholder={search_placeholder}
|
||||||
onSearchContacts={this.handleSearchContacts} />
|
onSearchContacts={this.handleSearchContacts} />
|
||||||
<ContactList
|
<ContactList
|
||||||
|
|||||||
@@ -53,15 +53,21 @@ export default class PasswordResetView extends React.PureComponent {
|
|||||||
return (
|
return (
|
||||||
<form id="password-reset-form" onSubmit={this.handleSubmit}>
|
<form id="password-reset-form" onSubmit={this.handleSubmit}>
|
||||||
{(this.state.token && this.state.scheme) ?
|
{(this.state.token && this.state.scheme) ?
|
||||||
|
<><label htmlFor="new-password">
|
||||||
|
<FormattedMessage id="label_new_password"
|
||||||
|
defaultMessage="New password"
|
||||||
|
description="Label for entering a new password" />
|
||||||
|
</label>
|
||||||
<FormattedMessage id="new_password_placeholder" defaultMessage="Enter new password"
|
<FormattedMessage id="new_password_placeholder" defaultMessage="Enter new password"
|
||||||
description="Placeholder for entering new password">{
|
description="Placeholder for entering new password">{
|
||||||
(placeholder) => <VisiblePassword
|
(placeholder) => <VisiblePassword
|
||||||
|
id="new-password"
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
value={this.state.password}
|
value={this.state.password}
|
||||||
required={true} autoFocus={true}
|
required={true} autoFocus={true}
|
||||||
onChange={this.handlePasswordChange} />
|
onChange={this.handlePasswordChange} />
|
||||||
}</FormattedMessage>
|
}</FormattedMessage></>
|
||||||
: this.state.sent ?
|
: this.state.sent ?
|
||||||
<>
|
<>
|
||||||
<br/>
|
<br/>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import MessagesView from './messages-view.jsx';
|
|||||||
import SidepanelView from './sidepanel-view.jsx';
|
import SidepanelView from './sidepanel-view.jsx';
|
||||||
|
|
||||||
import { API_KEY, APP_NAME, DEFAULT_P2P_ACCESS_MODE, FORWARDED_PREVIEW_LENGTH, LOGGING_ENABLED,
|
import { API_KEY, APP_NAME, DEFAULT_P2P_ACCESS_MODE, FORWARDED_PREVIEW_LENGTH, LOGGING_ENABLED,
|
||||||
MEDIA_BREAKPOINT, QUOTED_REPLY_LENGTH, RECEIVED_DELAY } from '../config.js';
|
MEDIA_BREAKPOINT, RECEIVED_DELAY } from '../config.js';
|
||||||
import { PACKAGE_VERSION } from '../version.js';
|
import { PACKAGE_VERSION } from '../version.js';
|
||||||
import { base64ReEncode, makeImageUrl } from '../lib/blob-helpers.js';
|
import { base64ReEncode, makeImageUrl } from '../lib/blob-helpers.js';
|
||||||
import { detectServerAddress, isLocalHost, isSecureConnection } from '../lib/host-name.js';
|
import { detectServerAddress, isLocalHost, isSecureConnection } from '../lib/host-name.js';
|
||||||
@@ -288,9 +288,12 @@ class TinodeWeb extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save possible topic name and navigate to blank state.
|
// Maybe navigate to home screen.
|
||||||
|
if (!['cred', 'reset', 'register'].includes(parsedNav.path[0])) {
|
||||||
|
// Save possible topic name.
|
||||||
this.setState({requestedTopic: parsedNav.path[1]});
|
this.setState({requestedTopic: parsedNav.path[1]});
|
||||||
HashNavigation.navigateTo('');
|
HashNavigation.navigateTo('');
|
||||||
|
}
|
||||||
|
|
||||||
this.readTimer = null;
|
this.readTimer = null;
|
||||||
this.readTimerCallback = null;
|
this.readTimerCallback = null;
|
||||||
@@ -1199,6 +1202,8 @@ class TinodeWeb extends React.Component {
|
|||||||
delete parsed.params.code;
|
delete parsed.params.code;
|
||||||
delete parsed.params.method;
|
delete parsed.params.method;
|
||||||
delete parsed.params.tab;
|
delete parsed.params.tab;
|
||||||
|
delete parsed.params.scheme;
|
||||||
|
delete parsed.params.token;
|
||||||
}
|
}
|
||||||
HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsed.path, parsed.params));
|
HashNavigation.navigateTo(HashNavigation.composeUrlHash(parsed.path, parsed.params));
|
||||||
this.setState({errorText: '', errorLevel: null});
|
this.setState({errorText: '', errorLevel: null});
|
||||||
@@ -1230,9 +1235,11 @@ class TinodeWeb extends React.Component {
|
|||||||
params.desc = {defacs: {auth: DEFAULT_P2P_ACCESS_MODE}};
|
params.desc = {defacs: {auth: DEFAULT_P2P_ACCESS_MODE}};
|
||||||
} else {
|
} else {
|
||||||
topicName = topicName || this.tinode.newGroupTopicName(isChannel);
|
topicName = topicName || this.tinode.newGroupTopicName(isChannel);
|
||||||
|
if (newTopicParams) {
|
||||||
params.desc = {public: newTopicParams.public, private: {comment: newTopicParams.private}};
|
params.desc = {public: newTopicParams.public, private: {comment: newTopicParams.private}};
|
||||||
params.tags = newTopicParams.tags;
|
params.tags = newTopicParams.tags;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
params._topicName = topicName;
|
params._topicName = topicName;
|
||||||
this.setState({newTopicParams: params}, () => {this.handleTopicSelected(topicName)});
|
this.setState({newTopicParams: params}, () => {this.handleTopicSelected(topicName)});
|
||||||
}
|
}
|
||||||
@@ -1443,22 +1450,22 @@ class TinodeWeb extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
handleShowForwardDialog(params) {
|
handleShowForwardDialog(params) {
|
||||||
if (this.state.sidePanelSelected == 'newtpk') {
|
if (this.state.sidePanelSelected == 'newtpk') {
|
||||||
// If the Find panel is active, close it.
|
// If the Find panel is active, close it.
|
||||||
this.handleSidepanelCancel();
|
this.handleSidepanelCancel();
|
||||||
}
|
}
|
||||||
const header = '➦ ' + params.userName;
|
const header = '➦ ' + params.userName;
|
||||||
const content = params.forwarded ?
|
const content = typeof params.content == 'string' ?
|
||||||
Tinode.Drafty.forwardedContent(params.content) :
|
Tinode.Drafty.init(params.content) : Tinode.Drafty.forwardedContent(params.content);
|
||||||
typeof params.content == 'string' ?
|
|
||||||
Tinode.Drafty.init(params.content) : params.content;
|
|
||||||
const preview = Tinode.Drafty.preview(content, FORWARDED_PREVIEW_LENGTH,
|
const preview = Tinode.Drafty.preview(content, FORWARDED_PREVIEW_LENGTH,
|
||||||
undefined, params.forwarded != null);
|
undefined, params.forwarded != null);
|
||||||
const msg = Tinode.Drafty.append(
|
const msg = Tinode.Drafty.append(
|
||||||
Tinode.Drafty.appendLineBreak(
|
Tinode.Drafty.appendLineBreak(
|
||||||
Tinode.Drafty.mention(header, params.userFrom)),
|
Tinode.Drafty.mention(header, params.userFrom)),
|
||||||
content);
|
content);
|
||||||
|
|
||||||
const msgPreview = Tinode.Drafty.quote(header, params.userFrom, preview);
|
const msgPreview = Tinode.Drafty.quote(header, params.userFrom, preview);
|
||||||
const head = {
|
const head = {
|
||||||
forwarded: params.topicName + ':' + params.seq
|
forwarded: params.topicName + ':' + params.seq
|
||||||
@@ -1611,6 +1618,9 @@ class TinodeWeb extends React.Component {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return this.tinode.updateAccountBasic(null, null, newPassword, {token: token});
|
return this.tinode.updateAccountBasic(null, null, newPassword, {token: token});
|
||||||
})
|
})
|
||||||
|
.then(() => {
|
||||||
|
HashNavigation.navigateTo('');
|
||||||
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
// Socket error
|
// Socket error
|
||||||
this.handleError(err.message, 'err');
|
this.handleError(err.message, 'err');
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default class AvatarUpload extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
// Randomize id value in case more than one AvatarUpload is shown
|
// Randomize id value in case more than one AvatarUpload is shown
|
||||||
// at the same time.
|
// at the same time.
|
||||||
const randId = 'file-input-avatar-' + (Math.random() + '').substr(2);
|
const randId = 'file-input-avatar-' + ('' + Math.random()).substring(0, 4);
|
||||||
const className = 'avatar-upload' + (this.props.readOnly ? ' read-only' : '');
|
const className = 'avatar-upload' + (this.props.readOnly ? ' read-only' : '');
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import Attachment from './attachment.jsx';
|
|||||||
import LetterTile from './letter-tile.jsx';
|
import LetterTile from './letter-tile.jsx';
|
||||||
import ReceivedMarker from './received-marker.jsx'
|
import ReceivedMarker from './received-marker.jsx'
|
||||||
|
|
||||||
import { fullFormatter, quoteFormatter } from '../lib/formatters.js';
|
import { fullFormatter } from '../lib/formatters.js';
|
||||||
import { sanitizeImageUrl, sanitizeUrl } from '../lib/utils.js';
|
import { sanitizeUrl } from '../lib/utils.js';
|
||||||
|
|
||||||
class BaseChatMessage extends React.PureComponent {
|
class BaseChatMessage extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -29,7 +29,6 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
||||||
|
|
||||||
this.formatterContext = {
|
this.formatterContext = {
|
||||||
getFormatter: (tp) => { return tp == 'QQ' ? quoteFormatter : null; },
|
|
||||||
formatMessage: props.intl.formatMessage.bind(props.intl),
|
formatMessage: props.intl.formatMessage.bind(props.intl),
|
||||||
viewportWidth: props.viewportWidth,
|
viewportWidth: props.viewportWidth,
|
||||||
authorizeURL: props.tinode.authorizeURL.bind(props.tinode),
|
authorizeURL: props.tinode.authorizeURL.bind(props.tinode),
|
||||||
@@ -87,7 +86,6 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
content: this.props.content,
|
content: this.props.content,
|
||||||
userFrom: this.props.userFrom,
|
userFrom: this.props.userFrom,
|
||||||
userName: this.props.userName,
|
userName: this.props.userName,
|
||||||
forwarded: this.props.forwarded,
|
|
||||||
y: e.pageY,
|
y: e.pageY,
|
||||||
x: e.pageX,
|
x: e.pageX,
|
||||||
pickReply: this.props.pickReply
|
pickReply: this.props.pickReply
|
||||||
|
|||||||
@@ -86,9 +86,10 @@ class ContactList extends React.Component {
|
|||||||
deliveryStatus = msg._status || c.msgStatus(msg, true);
|
deliveryStatus = msg._status || c.msgStatus(msg, true);
|
||||||
preview = typeof msg.content == 'string' ?
|
preview = typeof msg.content == 'string' ?
|
||||||
msg.content.substr(0, MESSAGE_PREVIEW_LENGTH) :
|
msg.content.substr(0, MESSAGE_PREVIEW_LENGTH) :
|
||||||
Drafty.preview(msg.content, MESSAGE_PREVIEW_LENGTH, undefined, forwarded != null);
|
Drafty.preview(msg.content, MESSAGE_PREVIEW_LENGTH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contactNodes.push(
|
contactNodes.push(
|
||||||
<Contact
|
<Contact
|
||||||
tinode={this.props.tinode}
|
tinode={this.props.tinode}
|
||||||
|
|||||||
+12
-9
@@ -69,17 +69,20 @@ class Contact extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const subtitle = this.props.preview ?
|
// The this.props.preview contains alreay shortened Drafty or string.
|
||||||
(typeof this.props.preview == 'string' ? this.props.preview :
|
let preview;
|
||||||
Drafty.isValid(this.props.preview) ?
|
if (typeof this.props.preview == 'string') {
|
||||||
React.createElement(React.Fragment, null, Drafty.format(this.props.preview, previewFormatter,
|
preview = this.props.preview;
|
||||||
{formatMessage: this.props.intl.formatMessage})) :
|
} else if (Drafty.isValid(this.props.preview)) {
|
||||||
|
preview = React.createElement(React.Fragment, null, Drafty.format(this.props.preview, previewFormatter,
|
||||||
|
{formatMessage: this.props.intl.formatMessage}));
|
||||||
|
} else if (this.props.preview) {
|
||||||
|
preview =
|
||||||
<><i className="material-icons gray">warning_amber</i> <i className="gray">
|
<><i className="material-icons gray">warning_amber</i> <i className="gray">
|
||||||
<FormattedMessage id="invalid_content"
|
<FormattedMessage id="invalid_content"
|
||||||
defaultMessage="invalid content" description="Shown when the message is unreadable" /></i>
|
defaultMessage="invalid content" description="Shown when the message is unreadable" /></i>
|
||||||
</>
|
</>;
|
||||||
) :
|
}
|
||||||
this.props.comment;
|
|
||||||
|
|
||||||
const icon = deliveryMarker(this.props.received);
|
const icon = deliveryMarker(this.props.received);
|
||||||
const marker = icon ? <i className={'material-icons small space-right' +
|
const marker = icon ? <i className={'material-icons small space-right' +
|
||||||
@@ -105,7 +108,7 @@ class Contact extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
{this.props.showMode ?
|
{this.props.showMode ?
|
||||||
<span><ContactBadges badges={badges} /></span> :
|
<span><ContactBadges badges={badges} /></span> :
|
||||||
this.props.small ? null : <div className="contact-comment">{marker}<span>{subtitle || '\u00A0'}</span></div>}
|
this.props.small ? null : <div className="contact-comment">{marker}<span>{preview || this.props.comment || '\u00A0'}</span></div>}
|
||||||
</div>
|
</div>
|
||||||
{this.props.showContextMenu ?
|
{this.props.showContextMenu ?
|
||||||
<span className="menuTrigger">
|
<span className="menuTrigger">
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ class ContextMenu extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
replyToMessage(params, errorHandler) {
|
replyToMessage(params, errorHandler) {
|
||||||
params.pickReply(params.seq, params.content, params.forwarded, params.userFrom, params.userName, errorHandler);
|
params.pickReply(params.seq, params.content, params.userFrom, params.userName, errorHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default class ImagePreview extends React.PureComponent {
|
|||||||
<div id="image-preview">
|
<div id="image-preview">
|
||||||
<div id="image-preview-caption-panel">
|
<div id="image-preview-caption-panel">
|
||||||
{!this.props.onSendMessage ?
|
{!this.props.onSendMessage ?
|
||||||
<a href={this.props.content.url} download={this.props.content.name}>
|
<a href={this.props.content.url} download={true}>
|
||||||
<i className="material-icons">file_download</i> <FormattedMessage
|
<i className="material-icons">file_download</i> <FormattedMessage
|
||||||
id="download_action" defaultMessage="download" description="Call to action [download]" />
|
id="download_action" defaultMessage="download" description="Call to action [download]" />
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export default class LazyImage extends React.PureComponent {
|
|||||||
if (prevProps.whenDone != this.props.whenDone) {
|
if (prevProps.whenDone != this.props.whenDone) {
|
||||||
this.setState({src: 'img/placeholder.png', style: {...this.state.style, padding: '4px'}});
|
this.setState({src: 'img/placeholder.png', style: {...this.state.style, padding: '4px'}});
|
||||||
this.props.whenDone
|
this.props.whenDone
|
||||||
|
.promise
|
||||||
.then(data => this.setState({src: data.src, style: {...this.state.style, padding: 0}}))
|
.then(data => this.setState({src: data.src, style: {...this.state.style, padding: 0}}))
|
||||||
.catch(() => this.setState({src: 'img/broken_image.png'}));
|
.catch(() => this.setState({src: 'img/broken_image.png'}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import Tinode from 'tinode-sdk';
|
import Tinode from 'tinode-sdk';
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Drafty } from 'tinode-sdk';
|
|||||||
|
|
||||||
import { KEYPRESS_DELAY } from '../config.js';
|
import { KEYPRESS_DELAY } from '../config.js';
|
||||||
import { filePasted } from '../lib/blob-helpers.js';
|
import { filePasted } from '../lib/blob-helpers.js';
|
||||||
import { quoteFormatter, replyFormatter } from '../lib/formatters.js';
|
import { replyFormatter } from '../lib/formatters.js';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
messaging_disabled: {
|
messaging_disabled: {
|
||||||
@@ -40,6 +40,7 @@ class SendMessage extends React.PureComponent {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
quote: null,
|
||||||
message: '',
|
message: '',
|
||||||
// Make initial keypress time as if it happened 5001 milliseconds in the past.
|
// Make initial keypress time as if it happened 5001 milliseconds in the past.
|
||||||
keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1
|
keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import AvatarUpload from './avatar-upload.jsx';
|
|||||||
import InPlaceEdit from './in-place-edit.jsx';
|
import InPlaceEdit from './in-place-edit.jsx';
|
||||||
import TagManager from './tag-manager.jsx';
|
import TagManager from './tag-manager.jsx';
|
||||||
|
|
||||||
import { AVATAR_SIZE, MAX_AVATAR_BYTES, MAX_EXTERN_ATTACHMENT_SIZE,
|
import { AVATAR_SIZE, MAX_AVATAR_BYTES, MAX_EXTERN_ATTACHMENT_SIZE, MAX_TITLE_LENGTH,
|
||||||
MAX_INBAND_ATTACHMENT_SIZE, MAX_TITLE_LENGTH, MAX_TOPIC_DESCRIPTION_LENGTH } from '../config.js';
|
MAX_TOPIC_DESCRIPTION_LENGTH } from '../config.js';
|
||||||
import { imageScaled, blobToBase64, makeImageUrl } from '../lib/blob-helpers.js';
|
import { imageScaled, blobToBase64, makeImageUrl } from '../lib/blob-helpers.js';
|
||||||
import { arrayEqual, theCard } from '../lib/utils.js';
|
import { arrayEqual, theCard } from '../lib/utils.js';
|
||||||
|
|
||||||
|
|||||||
+123
-71
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
+665
-429
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
// This is a generated file. Don't edit.
|
// This is a generated file. Don't edit.
|
||||||
|
|
||||||
const PACKAGE_VERSION = "0.18.0-beta2";
|
const PACKAGE_VERSION = "0.18.0-rc3";
|
||||||
|
|||||||
Reference in New Issue
Block a user