Asynchronously render spoiler button only if all clients support it

This commit is contained in:
JC Brand 2018-02-08 17:06:53 +01:00
parent 680f30412a
commit 03b9447f1d
10 changed files with 209 additions and 134 deletions

View File

@ -419,7 +419,7 @@
expect(view).toBeDefined(); expect(view).toBeDefined();
var $toolbar = $(view.el).find('ul.chat-toolbar'); var $toolbar = $(view.el).find('ul.chat-toolbar');
expect($toolbar.length).toBe(1); expect($toolbar.length).toBe(1);
expect($toolbar.children('li').length).toBe(4); expect($toolbar.children('li').length).toBe(3);
done(); done();
})); }));

View File

@ -3,6 +3,8 @@
} (this, function ($, jasmine, mock, converse, test_utils) { } (this, function ($, jasmine, mock, converse, test_utils) {
var Strophe = converse.env.Strophe; var Strophe = converse.env.Strophe;
var b64_sha1 = converse.env.b64_sha1; var b64_sha1 = converse.env.b64_sha1;
var $pres = converse.env.$pres;
var _ = converse.env._;
describe("A chatbox with an active OTR session", function() { describe("A chatbox with an active OTR session", function() {
@ -13,28 +15,57 @@
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current');
var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
// XXX: We need to send a presence from the contact, so that we
// have a resource, that resource is then queried to see
// whether Strophe.NS.SPOILER is supported, in which case
// the spoiler button will appear.
var presence = $pres({
'from': contact_jid+'/phone',
'to': 'dummy@localhost'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
test_utils.openChatBoxFor(_converse, contact_jid); test_utils.openChatBoxFor(_converse, contact_jid);
var view = _converse.chatboxviews.get(contact_jid); test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]).then(function () {
var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); var spoiler_toggle;
expect(spoiler_toggle).not.toBe(null); var view = _converse.chatboxviews.get(contact_jid);
spyOn(view, 'addSpoilerButton').and.callThrough();
view.model.set('otr_status', 1);
view.model.set('otr_status', 0); test_utils.waitUntil(function () {
spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); return _.isNull(view.el.querySelector('.toggle-compose-spoiler'));
expect(spoiler_toggle).not.toBe(null); }).then(function () {
spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
expect(spoiler_toggle).toBe(null);
view.model.set('otr_status', 1); view.model.set('otr_status', 3);
spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
expect(spoiler_toggle).toBe(null);
view.model.set('otr_status', 2); return test_utils.waitUntil(function () {
spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); return !_.isNull(view.el.querySelector('.toggle-compose-spoiler'));
expect(spoiler_toggle).toBe(null); });
}).then(function () {
spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
expect(spoiler_toggle).not.toBe(null);
view.model.set('otr_status', 3); view.model.set('otr_status', 2);
spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); return test_utils.waitUntil(function () {
expect(spoiler_toggle).not.toBe(null); return _.isNull(view.el.querySelector('.toggle-compose-spoiler'));
done(); });
}).then(function () {
spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
expect(spoiler_toggle).toBe(null);
view.model.set('otr_status', 4);
return test_utils.waitUntil(function () {
return !_.isNull(view.el.querySelector('.toggle-compose-spoiler'));
});
}).then(function () {
spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
expect(spoiler_toggle).not.toBe(null);
done();
});
});
})); }));
}); });

View File

@ -9,7 +9,9 @@
} (this, function (jasmine, utils, mock, converse, test_utils) { } (this, function (jasmine, utils, mock, converse, test_utils) {
var _ = converse.env._; var _ = converse.env._;
var Strophe = converse.env.Strophe;
var $msg = converse.env.$msg; var $msg = converse.env.$msg;
var $pres = converse.env.$pres;
var u = converse.env.utils; var u = converse.env.utils;
return describe("A spoiler message", function () { return describe("A spoiler message", function () {
@ -93,57 +95,69 @@
test_utils.openControlBox(); test_utils.openControlBox();
test_utils.openContactsPanel(_converse); test_utils.openContactsPanel(_converse);
var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
// XXX: We need to send a presence from the contact, so that we
// have a resource, that resource is then queried to see
// whether Strophe.NS.SPOILER is supported, in which case
// the spoiler button will appear.
var presence = $pres({
'from': contact_jid+'/phone',
'to': 'dummy@localhost'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
test_utils.openChatBoxFor(_converse, contact_jid); test_utils.openChatBoxFor(_converse, contact_jid);
var view = _converse.chatboxviews.get(contact_jid); test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]).then(function () {
spyOn(view, 'onMessageSubmitted').and.callThrough(); var view = _converse.chatboxviews.get(contact_jid);
spyOn(_converse.connection, 'send'); spyOn(view, 'onMessageSubmitted').and.callThrough();
spyOn(_converse.connection, 'send');
var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
spoiler_toggle.click(); spoiler_toggle.click();
var textarea = view.el.querySelector('.chat-textarea'); var textarea = view.el.querySelector('.chat-textarea');
textarea.value = 'This is the spoiler'; textarea.value = 'This is the spoiler';
view.keyPressed({ view.keyPressed({
target: textarea, target: textarea,
preventDefault: _.noop, preventDefault: _.noop,
keyCode: 13 keyCode: 13
});
expect(view.onMessageSubmitted).toHaveBeenCalled();
/* Test the XML stanza
*
* <message from="dummy@localhost/resource"
* to="max.frankfurter@localhost"
* type="chat"
* id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
* xmlns="jabber:client">
* <body>This is the spoiler</body>
* <active xmlns="http://jabber.org/protocol/chatstates"/>
* <spoiler xmlns="urn:xmpp:spoiler:0"/>
* </message>"
*/
var stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
var spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]');
expect(_.isNull(spoiler_el)).toBeFalsy();
expect(spoiler_el.textContent).toBe('');
var body_el = stanza.querySelector('body');
expect(body_el.textContent).toBe('This is the spoiler');
/* Test the HTML spoiler message */
var spoiler_msg_el = view.el.querySelector('.chat-msg-content.spoiler');
expect(spoiler_msg_el.textContent).toBe('This is the spoiler');
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
spoiler_toggle = view.el.querySelector('.toggle-spoiler');
expect(spoiler_toggle.textContent).toBe('Show hidden message');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeFalsy();
expect(spoiler_toggle.textContent).toBe('Hide hidden message');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
done();
}); });
expect(view.onMessageSubmitted).toHaveBeenCalled();
/* Test the XML stanza
*
* <message from="dummy@localhost/resource"
* to="max.frankfurter@localhost"
* type="chat"
* id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
* xmlns="jabber:client">
* <body>This is the spoiler</body>
* <active xmlns="http://jabber.org/protocol/chatstates"/>
* <spoiler xmlns="urn:xmpp:spoiler:0"/>
* </message>"
*/
var stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
var spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]');
expect(_.isNull(spoiler_el)).toBeFalsy();
expect(spoiler_el.textContent).toBe('');
var body_el = stanza.querySelector('body');
expect(body_el.textContent).toBe('This is the spoiler');
/* Test the HTML spoiler message */
var spoiler_msg_el = view.el.querySelector('.chat-msg-content.spoiler');
expect(spoiler_msg_el.textContent).toBe('This is the spoiler');
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
spoiler_toggle = view.el.querySelector('.toggle-spoiler');
expect(spoiler_toggle.textContent).toBe('Show hidden message');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeFalsy();
expect(spoiler_toggle.textContent).toBe('Hide hidden message');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
done();
})); }));
it("can be sent with a hint", it("can be sent with a hint",
@ -155,61 +169,72 @@
test_utils.openControlBox(); test_utils.openControlBox();
test_utils.openContactsPanel(_converse); test_utils.openContactsPanel(_converse);
var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
// XXX: We need to send a presence from the contact, so that we
// have a resource, that resource is then queried to see
// whether Strophe.NS.SPOILER is supported, in which case
// the spoiler button will appear.
var presence = $pres({
'from': contact_jid+'/phone',
'to': 'dummy@localhost'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
test_utils.openChatBoxFor(_converse, contact_jid); test_utils.openChatBoxFor(_converse, contact_jid);
var view = _converse.chatboxviews.get(contact_jid); test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]).then(function () {
var view = _converse.chatboxviews.get(contact_jid);
var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
spoiler_toggle.click();
var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); spyOn(view, 'onMessageSubmitted').and.callThrough();
spoiler_toggle.click(); spyOn(_converse.connection, 'send');
spyOn(view, 'onMessageSubmitted').and.callThrough(); var textarea = view.el.querySelector('.chat-textarea');
spyOn(_converse.connection, 'send'); textarea.value = 'This is the spoiler';
var hint_input = view.el.querySelector('.spoiler-hint');
hint_input.value = 'This is the hint';
var textarea = view.el.querySelector('.chat-textarea'); view.keyPressed({
textarea.value = 'This is the spoiler'; target: textarea,
var hint_input = view.el.querySelector('.spoiler-hint'); preventDefault: _.noop,
hint_input.value = 'This is the hint'; keyCode: 13
});
expect(view.onMessageSubmitted).toHaveBeenCalled();
view.keyPressed({ /* Test the XML stanza
target: textarea, *
preventDefault: _.noop, * <message from="dummy@localhost/resource"
keyCode: 13 * to="max.frankfurter@localhost"
* type="chat"
* id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
* xmlns="jabber:client">
* <body>This is the spoiler</body>
* <active xmlns="http://jabber.org/protocol/chatstates"/>
* <spoiler xmlns="urn:xmpp:spoiler:0">This is the hint</spoiler>
* </message>"
*/
var stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
var spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]');
expect(_.isNull(spoiler_el)).toBeFalsy();
expect(spoiler_el.textContent).toBe('This is the hint');
var body_el = stanza.querySelector('body');
expect(body_el.textContent).toBe('This is the spoiler');
/* Test the HTML spoiler message */
var spoiler_msg_el = view.el.querySelector('.chat-msg-content.spoiler');
expect(spoiler_msg_el.textContent).toBe('This is the spoiler');
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
spoiler_toggle = view.el.querySelector('.toggle-spoiler');
expect(spoiler_toggle.textContent).toBe('Show hidden message');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeFalsy();
expect(spoiler_toggle.textContent).toBe('Hide hidden message');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
done();
}); });
expect(view.onMessageSubmitted).toHaveBeenCalled();
/* Test the XML stanza
*
* <message from="dummy@localhost/resource"
* to="max.frankfurter@localhost"
* type="chat"
* id="4547c38b-d98b-45a5-8f44-b4004dbc335e"
* xmlns="jabber:client">
* <body>This is the spoiler</body>
* <active xmlns="http://jabber.org/protocol/chatstates"/>
* <spoiler xmlns="urn:xmpp:spoiler:0">This is the hint</spoiler>
* </message>"
*/
var stanza = _converse.connection.send.calls.argsFor(0)[0].tree();
var spoiler_el = stanza.querySelector('spoiler[xmlns="urn:xmpp:spoiler:0"]');
expect(_.isNull(spoiler_el)).toBeFalsy();
expect(spoiler_el.textContent).toBe('This is the hint');
var body_el = stanza.querySelector('body');
expect(body_el.textContent).toBe('This is the spoiler');
/* Test the HTML spoiler message */
var spoiler_msg_el = view.el.querySelector('.chat-msg-content.spoiler');
expect(spoiler_msg_el.textContent).toBe('This is the spoiler');
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
spoiler_toggle = view.el.querySelector('.toggle-spoiler');
expect(spoiler_toggle.textContent).toBe('Show hidden message');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeFalsy();
expect(spoiler_toggle.textContent).toBe('Hide hidden message');
spoiler_toggle.click();
expect(_.includes(spoiler_msg_el.classList, 'collapsed')).toBeTruthy();
done();
})); }));
}); });
})); }));

View File

@ -22,6 +22,7 @@
"tpl!message", "tpl!message",
"tpl!new_day", "tpl!new_day",
"tpl!spinner", "tpl!spinner",
"tpl!spoiler_button",
"tpl!spoiler_message", "tpl!spoiler_message",
"tpl!toolbar" "tpl!toolbar"
], factory); ], factory);
@ -40,11 +41,12 @@
tpl_message, tpl_message,
tpl_new_day, tpl_new_day,
tpl_spinner, tpl_spinner,
tpl_spoiler_button,
tpl_spoiler_message, tpl_spoiler_message,
tpl_toolbar tpl_toolbar
) { ) {
"use strict"; "use strict";
const { $msg, Backbone, Strophe, _, b64_sha1, sizzle, moment } = converse.env; const { $msg, Backbone, Promise, Strophe, _, b64_sha1, f, sizzle, moment } = converse.env;
const u = converse.env.utils; const u = converse.env.utils;
const KEY = { const KEY = {
ENTER: 13, ENTER: 13,
@ -323,6 +325,7 @@
this.getToolbarOptions(options || {}) this.getToolbarOptions(options || {})
); );
this.el.querySelector('.chat-toolbar').innerHTML = toolbar(options); this.el.querySelector('.chat-toolbar').innerHTML = toolbar(options);
this.addSpoilerButton(options);
this.insertEmojiPicker(); this.insertEmojiPicker();
return this; return this;
}, },
@ -343,14 +346,40 @@
'label_spoiler_hint': __('Optional hint'), 'label_spoiler_hint': __('Optional hint'),
'message_value': _.get(this.el.querySelector('.chat-textarea'), 'value'), 'message_value': _.get(this.el.querySelector('.chat-textarea'), 'value'),
'show_send_button': _converse.show_send_button, 'show_send_button': _converse.show_send_button,
'show_spoiler_button': _converse.visible_toolbar_buttons.spoiler,
'show_textarea': true,
'show_toolbar': _converse.show_toolbar, 'show_toolbar': _converse.show_toolbar,
'unread_msgs': __('You have unread messages') 'unread_msgs': __('You have unread messages')
})); }));
this.renderToolbar(); this.renderToolbar();
}, },
addSpoilerButton (options) {
/* Asynchronously adds a button for writing spoiler
* messages, based on whether the contact's client supports
* it.
*/
if (!options.show_spoiler_button || this.model.get('type') === 'chatroom') {
return;
}
const contact_jid = this.model.get('jid');
const resources = this.model.get('resources');
if (_.isEmpty(resources)) {
return;
}
Promise.all(_.map(_.keys(resources), (resource) =>
_converse.api.disco.supports(Strophe.NS.SPOILER, `${contact_jid}/${resource}`)
)).then((results) => {
const supported = _.every(f.map(f.get('supported'))(results));
if (supported) {
const html = tpl_spoiler_button(this.model.toJSON());
if (_converse.visible_toolbar_buttons.emoji) {
this.el.querySelector('.toggle-smiley').insertAdjacentHTML('afterEnd', html);
} else {
this.el.querySelector('.chat-toolbar').insertAdjacentHTML('afterBegin', html);
}
}
});
},
insertHeading () { insertHeading () {
this.heading = new _converse.ChatBoxHeading({'model': this.model}); this.heading = new _converse.ChatBoxHeading({'model': this.model});
this.heading.render(); this.heading.render();

View File

@ -9,6 +9,7 @@
define(["sizzle", define(["sizzle",
"es6-promise", "es6-promise",
"lodash.noconflict", "lodash.noconflict",
"lodash.fp",
"polyfill", "polyfill",
"i18n", "i18n",
"utils", "utils",
@ -19,7 +20,7 @@
"backbone.nativeview", "backbone.nativeview",
"backbone.browserStorage" "backbone.browserStorage"
], factory); ], factory);
}(this, function (sizzle, Promise, _, polyfill, i18n, utils, moment, Strophe, pluggable, Backbone) { }(this, function (sizzle, Promise, _, f, polyfill, i18n, utils, moment, Strophe, pluggable, Backbone) {
/* Cannot use this due to Safari bug. /* Cannot use this due to Safari bug.
* See https://github.com/jcbrand/converse.js/issues/196 * See https://github.com/jcbrand/converse.js/issues/196
@ -972,6 +973,7 @@
const resources = _.isObject(this.get('resources')) ? this.get('resources') : {}; const resources = _.isObject(this.get('resources')) ? this.get('resources') : {};
resources[resource] = { resources[resource] = {
'name': resource,
'priority': priority, 'priority': priority,
'status': chat_status, 'status': chat_status,
'timestamp': timestamp 'timestamp': timestamp
@ -2019,6 +2021,7 @@
'Promise': Promise, 'Promise': Promise,
'Strophe': Strophe, 'Strophe': Strophe,
'_': _, '_': _,
'f': f,
'b64_sha1': b64_sha1, 'b64_sha1': b64_sha1,
'moment': moment, 'moment': moment,
'sizzle': sizzle, 'sizzle': sizzle,

View File

@ -146,8 +146,7 @@
} }
function registerHeadlineHandler () { function registerHeadlineHandler () {
_converse.connection.addHandler( _converse.connection.addHandler(onHeadlineMessage, null, 'message');
onHeadlineMessage, null, 'message');
} }
_converse.on('connected', registerHeadlineHandler); _converse.on('connected', registerHeadlineHandler);
_converse.on('reconnected', registerHeadlineHandler); _converse.on('reconnected', registerHeadlineHandler);

View File

@ -83,7 +83,7 @@
} }
const messages = []; const messages = [];
const message_handler = _converse.connection.addHandler(function (message) { const message_handler = _converse.connection.addHandler((message) => {
if (options.groupchat && message.getAttribute('from') !== options['with']) { // eslint-disable-line dot-notation if (options.groupchat && message.getAttribute('from') !== options['with']) { // eslint-disable-line dot-notation
return true; return true;
} }

View File

@ -1,5 +1,4 @@
define(['lodash', 'lodash.converter', 'converse-core'], function (_, lodashConverter, converse) { define(['lodash', 'lodash.converter'], function (_, lodashConverter) {
var fp = lodashConverter(_.runInContext()); var fp = lodashConverter(_.runInContext());
converse.env.fp = fp;
return fp; return fp;
}); });

View File

@ -1,9 +1,6 @@
{[ if (o.show_spoiler_button) { ]}
<!-- XXX: This markup is also in src/templates/toolbar.html -->
<li class="toggle-compose-spoiler"> <li class="toggle-compose-spoiler">
<a class=" <a class="
{[ if (o.sending_spoiler) { ]} icon-eye-blocked {[ } ]} {[ if (o.composing_spoiler) { ]} icon-eye-blocked {[ } ]}
{[ if (!o.sending_spoiler) { ]} icon-eye {[ } ]}" {[ if (!o.composing_spoiler) { ]} icon-eye {[ } ]}"
title="{{ o.title }}"></a> title="{{ o.label_toggle_spoiler }}"></a>
</li> </li>
{[ } ]}

View File

@ -4,14 +4,6 @@
<span class="emoji-picker"></span> <span class="emoji-picker"></span>
</li> </li>
{[ } ]} {[ } ]}
{[ if (o.show_spoiler_button) { ]}
<li class="toggle-compose-spoiler">
<a class="
{[ if (o.composing_spoiler) { ]} icon-eye-blocked {[ } ]}
{[ if (!o.composing_spoiler) { ]} icon-eye {[ } ]}"
title="{{ o.label_toggle_spoiler }}"></a>
</li>
{[ } ]}
{[ if (o.show_call_button) { ]} {[ if (o.show_call_button) { ]}
<li class="toggle-call"><a class="icon-phone" title="{{{o.label_start_call}}}"></a></li> <li class="toggle-call"><a class="icon-phone" title="{{{o.label_start_call}}}"></a></li>
{[ } ]} {[ } ]}