Commit e181aaf9 authored by JC Brand's avatar JC Brand

Make the message view's `render` method async

So that we first render dynamic content (e.g. images) before inserting
it into the chat.

Also, add the `show_images_inline` setting (which is the cause of this
whole change).

Updated tests to handle this new change and start using async/await
instead of promise callbacks.
parent 2426f9b7
This diff is collapsed.
...@@ -2252,9 +2252,7 @@ ...@@ -2252,9 +2252,7 @@
} }
}, },
"backbone.browserStorage": { "backbone.browserStorage": {
"version": "0.0.3", "version": "github:jcbrand/Backbone.browserStorage#7079bf7bf9a43474da1d48e31e3cda6c4a716382",
"resolved": "https://registry.npmjs.org/backbone.browserStorage/-/backbone.browserStorage-0.0.3.tgz",
"integrity": "sha1-ikIi8I2bHQslLR14/1CUuNCKc2s=",
"dev": true, "dev": true,
"requires": { "requires": {
"backbone": "1.3.3", "backbone": "1.3.3",
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -95,21 +95,18 @@ ...@@ -95,21 +95,18 @@
{ whitelisted_plugins: ['converse-roomslist'], { whitelisted_plugins: ['converse-roomslist'],
allow_bookmarks: false // Makes testing easier, otherwise we allow_bookmarks: false // Makes testing easier, otherwise we
// have to mock stanza traffic. // have to mock stanza traffic.
}, function (done, _converse) { }, async function (done, _converse) {
let view;
const IQ_stanzas = _converse.connection.IQ_stanzas; const IQ_stanzas = _converse.connection.IQ_stanzas;
const room_jid = 'coven@chat.shakespeare.lit'; const room_jid = 'coven@chat.shakespeare.lit';
test_utils.openControlBox(); test_utils.openControlBox();
_converse.api.rooms.open(room_jid, {'nick': 'some1'}) await _converse.api.rooms.open(room_jid, {'nick': 'some1'});
.then(() => { const last_stanza = await test_utils.waitUntil(() => _.get(_.filter(
return test_utils.waitUntil(() => _.get(_.filter(
IQ_stanzas, IQ_stanzas,
iq => iq.nodeTree.querySelector( iq => iq.nodeTree.querySelector(
`iq[to="${room_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]` `iq[to="${room_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
)).pop(), 'nodeTree')); )).pop(), 'nodeTree'));
}).then(last_stanza => { const view = _converse.chatboxviews.get(room_jid);
view = _converse.chatboxviews.get(room_jid);
const IQ_id = last_stanza.getAttribute('id'); const IQ_id = last_stanza.getAttribute('id');
const features_stanza = $iq({ const features_stanza = $iq({
'from': 'coven@chat.shakespeare.lit', 'from': 'coven@chat.shakespeare.lit',
...@@ -139,9 +136,8 @@ ...@@ -139,9 +136,8 @@
.c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'}) .c('field', {'type':'text-single', 'var':'muc#roominfo_occupants', 'label':'Number of occupants'})
.c('value').t(0); .c('value').t(0);
_converse.connection._dataRecv(test_utils.createRequest(features_stanza)); _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
return test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING) await test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING)
}).then(function () { let presence = $pres({
var presence = $pres({
to: _converse.connection.jid, to: _converse.connection.jid,
from: 'coven@chat.shakespeare.lit/some1', from: 'coven@chat.shakespeare.lit/some1',
id: 'DC352437-C019-40EC-B590-AF29E879AF97' id: 'DC352437-C019-40EC-B590-AF29E879AF97'
...@@ -160,9 +156,7 @@ ...@@ -160,9 +156,7 @@
info_el.click(); info_el.click();
const modal = view.model.room_details_modal; const modal = view.model.room_details_modal;
return test_utils.waitUntil(() => u.isVisible(modal.el), 2000); await test_utils.waitUntil(() => u.isVisible(modal.el), 2000);
}).then(() => {
const modal = view.model.room_details_modal;
let els = modal.el.querySelectorAll('p.room-info'); let els = modal.el.querySelectorAll('p.room-info');
expect(els[0].textContent).toBe("Name: A Dark Cave") expect(els[0].textContent).toBe("Name: A Dark Cave")
expect(els[1].textContent).toBe("Groupchat address (JID): coven@chat.shakespeare.lit") expect(els[1].textContent).toBe("Groupchat address (JID): coven@chat.shakespeare.lit")
...@@ -177,7 +171,7 @@ ...@@ -177,7 +171,7 @@
'Not anonymous - All other groupchat participants can see your XMPP username'+ 'Not anonymous - All other groupchat participants can see your XMPP username'+
'Not moderated - Participants entering this groupchat can write right away' 'Not moderated - Participants entering this groupchat can write right away'
); );
const presence = $pres({ presence = $pres({
to: 'dummy@localhost/_converse.js-29092160', to: 'dummy@localhost/_converse.js-29092160',
from: 'coven@chat.shakespeare.lit/newguy' from: 'coven@chat.shakespeare.lit/newguy'
}) })
...@@ -201,7 +195,6 @@ ...@@ -201,7 +195,6 @@
expect(els[4].textContent).toBe("Topic author: someone") expect(els[4].textContent).toBe("Topic author: someone")
expect(els[5].textContent).toBe("Online users: 2") expect(els[5].textContent).toBe("Online users: 2")
done(); done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
})); }));
it("can be closed", mock.initConverseWithPromises( it("can be closed", mock.initConverseWithPromises(
......
This diff is collapsed.
...@@ -297,7 +297,7 @@ ...@@ -297,7 +297,7 @@
'message': _converse.chatboxes.getMessageBody(stanza), 'message': _converse.chatboxes.getMessageBody(stanza),
'references': this.getReferencesFromStanza(stanza), 'references': this.getReferencesFromStanza(stanza),
'older_versions': older_versions, 'older_versions': older_versions,
'edited': true 'edited': moment().format()
}); });
return true; return true;
} }
...@@ -395,7 +395,7 @@ ...@@ -395,7 +395,7 @@
older_versions.push(message.get('message')); older_versions.push(message.get('message'));
message.save({ message.save({
'correcting': false, 'correcting': false,
'edited': true, 'edited': moment().format(),
'message': attrs.message, 'message': attrs.message,
'older_versions': older_versions, 'older_versions': older_versions,
'references': attrs.references 'references': attrs.references
......
...@@ -17,8 +17,10 @@ ...@@ -17,8 +17,10 @@
const { Backbone, _ } = converse.env; const { Backbone, _ } = converse.env;
const AvatarMixin = { const AvatarMixin = {
renderAvatar () {
const canvas_el = this.el.querySelector('canvas'); renderAvatar (el) {
el = el || this.el;
const canvas_el = el.querySelector('canvas');
if (_.isNull(canvas_el)) { if (_.isNull(canvas_el)) {
return; return;
} }
...@@ -27,6 +29,7 @@ ...@@ -27,6 +29,7 @@
img_src = "data:" + image_type + ";base64," + image, img_src = "data:" + image_type + ";base64," + image,
img = new Image(); img = new Image();
return new Promise((resolve, reject) => {
img.onload = () => { img.onload = () => {
const ctx = canvas_el.getContext('2d'), const ctx = canvas_el.getContext('2d'),
ratio = img.width / img.height; ratio = img.width / img.height;
...@@ -38,8 +41,10 @@ ...@@ -38,8 +41,10 @@
} else { } else {
ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height*ratio); ctx.drawImage(img, 0, 0, canvas_el.width, canvas_el.height*ratio);
} }
resolve();
}; };
img.src = img_src; img.src = img_src;
});
}, },
}; };
......
...@@ -312,6 +312,7 @@ ...@@ -312,6 +312,7 @@
this.model.messages.on('add', this.onMessageAdded, this); this.model.messages.on('add', this.onMessageAdded, this);
this.model.messages.on('rendered', this.scrollDown, this); this.model.messages.on('rendered', this.scrollDown, this);
this.model.messages.on('edited', (view) => this.markFollowups(view.el));
this.model.on('show', this.show, this); this.model.on('show', this.show, this);
this.model.on('destroy', this.remove, this); this.model.on('destroy', this.remove, this);
...@@ -673,7 +674,8 @@ ...@@ -673,7 +674,8 @@
if (view.model.get('type') === 'error') { if (view.model.get('type') === 'error') {
const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`); const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`);
if (previous_msg_el) { if (previous_msg_el) {
return previous_msg_el.insertAdjacentElement('afterend', view.el); previous_msg_el.insertAdjacentElement('afterend', view.el);
return this.trigger('messageInserted', view.el);
} }
} }
const current_msg_date = moment(view.model.get('time')) || moment, const current_msg_date = moment(view.model.get('time')) || moment,
...@@ -692,6 +694,7 @@ ...@@ -692,6 +694,7 @@
previous_msg_el.insertAdjacentElement('afterend', view.el); previous_msg_el.insertAdjacentElement('afterend', view.el);
this.markFollowups(view.el); this.markFollowups(view.el);
} }
return this.trigger('messageInserted', view.el);
}, },
markFollowups (el) { markFollowups (el) {
...@@ -731,7 +734,7 @@ ...@@ -731,7 +734,7 @@
} }
}, },
showMessage (message) { async showMessage (message) {
/* Inserts a chat message into the content area of the chat box. /* Inserts a chat message into the content area of the chat box.
* *
* Will also insert a new day indicator if the message is on a * Will also insert a new day indicator if the message is on a
...@@ -741,6 +744,8 @@ ...@@ -741,6 +744,8 @@
* (Backbone.Model) message: The message object * (Backbone.Model) message: The message object
*/ */
const view = new _converse.MessageView({'model': message}); const view = new _converse.MessageView({'model': message});
await view.render();
this.clearChatStateNotification(message); this.clearChatStateNotification(message);
this.insertMessage(view); this.insertMessage(view);
this.insertDayIndicator(view.el); this.insertDayIndicator(view.el);
......
...@@ -41,8 +41,11 @@ ...@@ -41,8 +41,11 @@
{ __ } = _converse; { __ } = _converse;
_converse.MessageVersionsModal = _converse.BootstrapModal.extend({ _converse.api.settings.update({
'show_images_inline': true
});
_converse.MessageVersionsModal = _converse.BootstrapModal.extend({
toHTML () { toHTML () {
return tpl_message_versions_modal(_.extend( return tpl_message_versions_modal(_.extend(
this.model.toJSON(), { this.model.toJSON(), {
...@@ -61,17 +64,11 @@ ...@@ -61,17 +64,11 @@
if (this.model.vcard) { if (this.model.vcard) {
this.model.vcard.on('change', this.render, this); this.model.vcard.on('change', this.render, this);
} }
this.model.on('change:correcting', this.onMessageCorrection, this); this.model.on('change', this.onChanged, this);
this.model.on('change:message', this.render, this);
this.model.on('change:progress', this.renderFileUploadProgresBar, this);
this.model.on('change:type', this.render, this);
this.model.on('change:upload', this.render, this);
this.model.on('destroy', this.remove, this); this.model.on('destroy', this.remove, this);
this.render();
}, },
render () { async render () {
const is_followup = u.hasClass('chat-msg--followup', this.el);
let msg; let msg;
if (this.model.isOnlyChatStateNotification()) { if (this.model.isOnlyChatStateNotification()) {
this.renderChatStateNotification() this.renderChatStateNotification()
...@@ -80,20 +77,32 @@ ...@@ -80,20 +77,32 @@
} else if (this.model.get('type') === 'error') { } else if (this.model.get('type') === 'error') {
this.renderErrorMessage(); this.renderErrorMessage();
} else { } else {
this.renderChatMessage(); await this.renderChatMessage();
}
if (is_followup) {
u.addClass('chat-msg--followup', this.el);
} }
return this.el; return this.el;
}, },
onMessageCorrection () { async onChanged (item) {
this.render(); // Jot down whether it was edited because the `changed`
if (!this.model.get('correcting') && this.model.changed.message) { // attr gets removed when this.render() gets called further
// down.
const edited = item.changed.edited;
if (this.model.changed.progress) {
return this.renderFileUploadProgresBar();
}
if (_.filter(['correcting', 'message', 'type', 'upload'],
prop => Object.prototype.hasOwnProperty.call(this.model.changed, prop)).length) {
await this.render();
}
if (edited) {
this.onMessageEdited();
}
},
onMessageEdited () {
this.el.addEventListener('animationend', () => u.removeClass('onload', this.el)); this.el.addEventListener('animationend', () => u.removeClass('onload', this.el));
this.model.collection.trigger('edited', this);
u.addClass('onload', this.el); u.addClass('onload', this.el);
}
}, },
replaceElement (msg) { replaceElement (msg) {
...@@ -104,7 +113,7 @@ ...@@ -104,7 +113,7 @@
return this.el; return this.el;
}, },
renderChatMessage () { async renderChatMessage () {
const is_me_message = this.isMeCommand(), const is_me_message = this.isMeCommand(),
moment_time = moment(this.model.get('time')), moment_time = moment(this.model.get('time')),
role = this.model.vcard ? this.model.vcard.get('role') : null, role = this.model.vcard ? this.model.vcard.get('role') : null,
...@@ -148,14 +157,14 @@ ...@@ -148,14 +157,14 @@
_.partial(u.addEmoji, _converse, _) _.partial(u.addEmoji, _converse, _)
)(text); )(text);
} }
u.renderImageURLs(_converse, msg_content).then(() => { if (_converse.show_images_inline) {
this.model.collection.trigger('rendered'); await u.renderImageURLs(_converse, msg_content);
}); }
this.replaceElement(msg);
if (this.model.get('type') !== 'headline') { if (this.model.get('type') !== 'headline') {
this.renderAvatar(); await this.renderAvatar(msg);
} }
this.replaceElement(msg);
this.model.collection.trigger('rendered', this);
}, },
renderErrorMessage () { renderErrorMessage () {
......
...@@ -478,6 +478,7 @@ ...@@ -478,6 +478,7 @@
initialize () { initialize () {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments); _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('change', this.render, this); this.model.on('change', this.render, this);
this.model.occupants.on('add', this.render, this);
this.model.occupants.on('change', this.render, this); this.model.occupants.on('change', this.render, this);
}, },
......
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
'warn': _.get(console, 'log') ? console.log.bind(console) : _.noop 'warn': _.get(console, 'log') ? console.log.bind(console) : _.noop
}, console); }, console);
var isImage = function (url) { const isImage = function (url) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
var img = new Image(); var img = new Image();
var timer = window.setTimeout(function () { var timer = window.setTimeout(function () {
......
...@@ -297,13 +297,15 @@ ...@@ -297,13 +297,15 @@
.c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree(); .c('active', {'xmlns': Strophe.NS.CHATSTATES}).tree();
} }
utils.sendMessage = function (chatboxview, message) { utils.sendMessage = function (view, message) {
chatboxview.el.querySelector('.chat-textarea').value = message; const promise = new Promise((resolve, reject) => view.on('messageInserted', resolve));
chatboxview.keyPressed({ view.el.querySelector('.chat-textarea').value = message;
target: chatboxview.el.querySelector('textarea.chat-textarea'), view.keyPressed({
target: view.el.querySelector('textarea.chat-textarea'),
preventDefault: _.noop, preventDefault: _.noop,
keyCode: 13 keyCode: 13
}); });
return promise;
}; };
return utils; return utils;
})); }));
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment