diff --git a/src/lib/strformat.js b/src/lib/strformat.js
index eee1a37b..b0b30c54 100644
--- a/src/lib/strformat.js
+++ b/src/lib/strformat.js
@@ -39,7 +39,7 @@ export function bytesToHumanSize(bytes) {
// Get 32 bit integer hash value for a string. Ideally it should produce the same value
// as Java's String#hash().
-export function stringToColorHash(value) {
+function stringToColorHash(value) {
let hash = 0;
value = '' + value;
for (let i = 0; i < value.length; i++) {
diff --git a/src/views/messages-view.jsx b/src/views/messages-view.jsx
index 20d03aa8..7182e9f7 100644
--- a/src/views/messages-view.jsx
+++ b/src/views/messages-view.jsx
@@ -674,7 +674,7 @@ class MessagesView extends React.Component {
sendMessage(msg, uploadCompletionPromise, uploader) {
let head;
if (this.state.reply && this.state.reply.content) {
- head = {replyToSeq: this.state.reply.seq};
+ head = {reply: '' + this.state.reply.seq};
// Turn it into Drafty so we can make a quoted Drafty object later.
if (typeof msg == 'string') {
msg = Drafty.parse(msg);
@@ -879,79 +879,87 @@ class MessagesView extends React.Component {
}, this);
}
- this.convertIntoThumbnails(ents, 0, (success) => {
- if (success) {
+
+ // Turn all images into thumbnails.
+ const promises = [];
+ ents.map((ex) => {
+ let p = new Promise((resolve, reject) => {
+ const scale = (origBlob) => {
+ imageScaled(origBlob, IMAGE_THUMBNAIL_DIM, IMAGE_THUMBNAIL_DIM, -1, false,
+ // Success
+ (mime, blob, width, height, fname) => {
+ ex.data.mime = mime;
+ ex.data.size = blob.size;
+ ex.data.width = width;
+ ex.data.height = height;
+ ex.data.name = fname;
+ ex.data.ref = undefined;
+
+ blobToBase64(blob, (blobMime, tinyBits64) => {
+ ex.data.val = tinyBits64;
+ resolve(true);
+ });
+ },
+ // Failure
+ (err) => {
+ reject(`Could not scale image: ${err}`);
+ });
+ }
+ if (ex.data.val) {
+ const b = base64ToBlob(ex.data.val, ex.data.mime);
+ if (b) {
+ scale(b);
+ }
+ } else {
+ const from = this.props.tinode.authorizeURL(sanitizeImageUrl(ex.data.ref));
+ fetch(from)
+ .then(e => {
+ if (e.ok) {
+ return e.blob();
+ } else {
+ reject(`Image fetch unsuccessful: ${e.status} - ${e.statusText}`);
+ }
+ })
+ .then((b) => scale(b))
+ .catch((err) => reject(`Error fetching image data: ${err}`));
+ return;
+ }
+ });
+
+ promises.push(p);
+ });
+
+ Promise.all(promises)
+ .then((_) => {
+ // All done. Create a reply quote.
const msg = Drafty.createQuote(header, cont, letterTileColorId(thisFrom));
this.setState({reply: {content: msg, seq: m.seq}});
- }
- });
+ })
+ .catch((err) => {
+ this.props.onError(err, 'err');
+ });
return;
}
}
}
- // Turn images in the provided Drafty.ent entries into thumbnails.
- convertIntoThumbnails(ents, idx, done) {
- if (idx >= ents.length) {
- done(true);
- return;
- }
-
- const scale = (origBlob) => {
- imageScaled(origBlob, IMAGE_THUMBNAIL_DIM, IMAGE_THUMBNAIL_DIM, -1, false,
- // Success
- (mime, blob, width, height, fname) => {
- const ex = ents[idx];
-
- ex.data.mime = mime;
- ex.data.size = blob.size;
- ex.data.width = width;
- ex.data.height = height;
- ex.data.name = fname;
- ex.data.ref = undefined;
-
- blobToBase64(blob, (blobMime, tinyBits64) => {
- ex.data.val = tinyBits64;
- this.convertIntoThumbnails(ents, idx + 1, done);
- });
- },
- // Failure
- (err) => {
- done(false);
- this.props.onError(err, 'err');
- });
- }
- const ex = ents[idx];
- if (ex.data.val) {
- const b = base64ToBlob(ex.data.val, ex.data.mime);
- if (b) {
- scale.call(this, b);
- }
- } else {
- const from = this.props.tinode.authorizeURL(sanitizeImageUrl(ex.data.ref));
- fetch(from)
- .then(e => e.blob())
- .then((b) => {
- scale.call(this, b);
- });
- return;
- }
- }
-
handleCancelReply() {
this.setState({reply: null})
}
handleQuoteClick(replyToSeq) {
const ref = this.getOrCreateMessageRef(replyToSeq);
+ if (!ref) {
+ return;
+ }
const element = ref.current;
if (element) {
element.scrollIntoView({block: "center", behavior: "smooth"});
element.style.backgroundColor = 'rgb(0, 0, 0, 0.4)';
setTimeout(() => { element.style.backgroundColor = ''; } , 1000);
} else {
- console.error("Unresolved ref: seqId", replyToSeq);
+ console.error("Unresolved message ref: seqId", replyToSeq);
}
}
@@ -975,7 +983,7 @@ class MessagesView extends React.Component {
);
});
@@ -1225,17 +1236,17 @@ function quotePreviewFmt(fmt, ent) {
return [new_fmt, ent];
case 'BN':
new_fmt.tp = null;
- return [new_fmt, { tp: 'IC', data: { orig: 'BN', iconName: 'dashboard', iconTitle: 'drafty_form'} }];
+ return [new_fmt, { tp: 'IC', data: { orig: 'BN', iconName: 'button'}}];
case 'FM':
new_fmt.tp = null;
new_fmt.len = 0;
- return [new_fmt, {tp: 'IC', data: { orig: 'FM', iconName: 'dashboard', iconTitle: 'drafty_form'} }];
+ return [new_fmt, {tp: 'IC', data: { orig: 'FM', iconName: 'form'}}];
case 'RW':
return [null, null];
case 'EX':
// Make it an icon.
new_fmt.tp = null;
- return [new_fmt, {tp: 'IC', data: { orig: 'EX', iconName: 'attachment', iconTitle: 'drafty_attachment'} }];
+ return [new_fmt, {tp: 'IC', data: { orig: 'EX', iconName: 'attachment'}}];
case 'QQ':
// Quote/citation.
return [null,null];
@@ -1327,14 +1338,31 @@ function draftyFormatter(style, data, values, key) {
break;
case 'IC':
// Icon.
- if (data.orig == 'BN') {
+ if (data.iconName == 'button') {
attr.className = 'flat-button faux';
} else {
+ let iconName;
+ let iconTitle;
+ switch (data.iconName) {
+ case 'form':
+ iconName = 'dashboard';
+ iconTitle = 'drafty_form';
+ break;
+ case 'attachment':
+ iconName = 'attachment';
+ iconTitle = 'drafty_attachment';
+ break;
+ default:
+ break;
+ }
el = React.Fragment;
- //values = []
- const iconKey = data.orig.toLowerCase();
- values = [{data.iconName},
- formatMessage(messages[data.iconTitle])].concat(values || []);
+ if (iconName && iconTitle) {
+ const iconKey = data.orig.toLowerCase();
+ values = [{iconName},
+ formatMessage(messages[iconTitle])].concat(values || []);
+ } else {
+ values = [];
+ }
}
break;
default:
diff --git a/src/widgets/chat-message.jsx b/src/widgets/chat-message.jsx
index d980a9ed..4e638877 100644
--- a/src/widgets/chat-message.jsx
+++ b/src/widgets/chat-message.jsx
@@ -62,7 +62,8 @@ class BaseChatMessage extends React.PureComponent {
e.preventDefault();
e.stopPropagation();
const menuItems = this.props.received == Tinode.MESSAGE_STATUS_FAILED ? ['menu_item_send_retry'] : [];
- if (this.props.received >= Tinode.MESSAGE_STATUS_FAILED &&
+ if (this.props.userIsWriter &&
+ this.props.received > Tinode.MESSAGE_STATUS_FAILED &&
this.props.received < Tinode.MESSAGE_STATUS_DEL_RANGE) {
menuItems.push('menu_item_reply');
}
@@ -82,7 +83,9 @@ class BaseChatMessage extends React.PureComponent {
e.preventDefault();
e.stopPropagation();
const replyToSeq = this.props.replyToSeq;
- this.props.onQuoteClick(replyToSeq);
+ if (replyToSeq) {
+ this.props.onQuoteClick(replyToSeq);
+ }
}
render() {
diff --git a/src/widgets/doc-preview.jsx b/src/widgets/doc-preview.jsx
index dd3c3116..67f69a80 100644
--- a/src/widgets/doc-preview.jsx
+++ b/src/widgets/doc-preview.jsx
@@ -46,7 +46,7 @@ export default class DocPreview extends React.PureComponent {