diff --git a/spec/chatbox.js b/spec/chatbox.js index bf83e560c..b41561220 100644 --- a/spec/chatbox.js +++ b/spec/chatbox.js @@ -419,7 +419,7 @@ expect(view).toBeDefined(); var $toolbar = $(view.el).find('ul.chat-toolbar'); expect($toolbar.length).toBe(1); - expect($toolbar.children('li').length).toBe(4); + expect($toolbar.children('li').length).toBe(3); done(); })); diff --git a/spec/otr.js b/spec/otr.js index 57f403307..f868319d0 100644 --- a/spec/otr.js +++ b/spec/otr.js @@ -3,6 +3,8 @@ } (this, function ($, jasmine, mock, converse, test_utils) { var Strophe = converse.env.Strophe; var b64_sha1 = converse.env.b64_sha1; + var $pres = converse.env.$pres; + var _ = converse.env._; describe("A chatbox with an active OTR session", function() { @@ -13,28 +15,57 @@ test_utils.createContacts(_converse, 'current'); 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); - var view = _converse.chatboxviews.get(contact_jid); - var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); - expect(spoiler_toggle).not.toBe(null); + test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]).then(function () { + var spoiler_toggle; + var view = _converse.chatboxviews.get(contact_jid); + spyOn(view, 'addSpoilerButton').and.callThrough(); + view.model.set('otr_status', 1); - view.model.set('otr_status', 0); - spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); - expect(spoiler_toggle).not.toBe(null); + 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).toBe(null); - view.model.set('otr_status', 1); - spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); - expect(spoiler_toggle).toBe(null); + view.model.set('otr_status', 3); - view.model.set('otr_status', 2); - spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); - expect(spoiler_toggle).toBe(null); + 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); - view.model.set('otr_status', 3); - spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); - expect(spoiler_toggle).not.toBe(null); - done(); + view.model.set('otr_status', 2); + 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).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(); + }); + }); })); }); diff --git a/spec/spoilers.js b/spec/spoilers.js index cdd1e25ce..6a8992cd7 100644 --- a/spec/spoilers.js +++ b/spec/spoilers.js @@ -9,7 +9,9 @@ } (this, function (jasmine, utils, mock, converse, test_utils) { var _ = converse.env._; + var Strophe = converse.env.Strophe; var $msg = converse.env.$msg; + var $pres = converse.env.$pres; var u = converse.env.utils; return describe("A spoiler message", function () { @@ -93,57 +95,69 @@ test_utils.openControlBox(); test_utils.openContactsPanel(_converse); 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); - var view = _converse.chatboxviews.get(contact_jid); - spyOn(view, 'onMessageSubmitted').and.callThrough(); - spyOn(_converse.connection, 'send'); + test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]).then(function () { + var view = _converse.chatboxviews.get(contact_jid); + spyOn(view, 'onMessageSubmitted').and.callThrough(); + spyOn(_converse.connection, 'send'); - var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); - spoiler_toggle.click(); + var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); + spoiler_toggle.click(); - var textarea = view.el.querySelector('.chat-textarea'); - textarea.value = 'This is the spoiler'; - view.keyPressed({ - target: textarea, - preventDefault: _.noop, - keyCode: 13 + var textarea = view.el.querySelector('.chat-textarea'); + textarea.value = 'This is the spoiler'; + view.keyPressed({ + target: textarea, + preventDefault: _.noop, + keyCode: 13 + }); + expect(view.onMessageSubmitted).toHaveBeenCalled(); + + /* Test the XML stanza + * + * + * This is the spoiler + * + * + * " + */ + 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 - * - * - * This is the spoiler - * - * - * " - */ - 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", @@ -155,61 +169,72 @@ test_utils.openControlBox(); test_utils.openContactsPanel(_converse); 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); - 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'); - spoiler_toggle.click(); + spyOn(view, 'onMessageSubmitted').and.callThrough(); + spyOn(_converse.connection, 'send'); - spyOn(view, 'onMessageSubmitted').and.callThrough(); - spyOn(_converse.connection, 'send'); + var textarea = view.el.querySelector('.chat-textarea'); + 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'); - textarea.value = 'This is the spoiler'; - var hint_input = view.el.querySelector('.spoiler-hint'); - hint_input.value = 'This is the hint'; + view.keyPressed({ + target: textarea, + preventDefault: _.noop, + keyCode: 13 + }); + expect(view.onMessageSubmitted).toHaveBeenCalled(); - view.keyPressed({ - target: textarea, - preventDefault: _.noop, - keyCode: 13 + /* Test the XML stanza + * + * + * This is the spoiler + * + * This is the hint + * " + */ + 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 - * - * - * This is the spoiler - * - * This is the hint - * " - */ - 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(); })); }); })); diff --git a/src/converse-chatview.js b/src/converse-chatview.js index 8a3486eff..3b91988d6 100644 --- a/src/converse-chatview.js +++ b/src/converse-chatview.js @@ -22,6 +22,7 @@ "tpl!message", "tpl!new_day", "tpl!spinner", + "tpl!spoiler_button", "tpl!spoiler_message", "tpl!toolbar" ], factory); @@ -40,11 +41,12 @@ tpl_message, tpl_new_day, tpl_spinner, + tpl_spoiler_button, tpl_spoiler_message, tpl_toolbar ) { "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 KEY = { ENTER: 13, @@ -323,6 +325,7 @@ this.getToolbarOptions(options || {}) ); this.el.querySelector('.chat-toolbar').innerHTML = toolbar(options); + this.addSpoilerButton(options); this.insertEmojiPicker(); return this; }, @@ -343,14 +346,40 @@ 'label_spoiler_hint': __('Optional hint'), 'message_value': _.get(this.el.querySelector('.chat-textarea'), 'value'), 'show_send_button': _converse.show_send_button, - 'show_spoiler_button': _converse.visible_toolbar_buttons.spoiler, - 'show_textarea': true, 'show_toolbar': _converse.show_toolbar, 'unread_msgs': __('You have unread messages') })); 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 () { this.heading = new _converse.ChatBoxHeading({'model': this.model}); this.heading.render(); diff --git a/src/converse-core.js b/src/converse-core.js index a6278e491..aabe1abcf 100644 --- a/src/converse-core.js +++ b/src/converse-core.js @@ -9,6 +9,7 @@ define(["sizzle", "es6-promise", "lodash.noconflict", + "lodash.fp", "polyfill", "i18n", "utils", @@ -19,7 +20,7 @@ "backbone.nativeview", "backbone.browserStorage" ], 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. * See https://github.com/jcbrand/converse.js/issues/196 @@ -972,6 +973,7 @@ const resources = _.isObject(this.get('resources')) ? this.get('resources') : {}; resources[resource] = { + 'name': resource, 'priority': priority, 'status': chat_status, 'timestamp': timestamp @@ -2019,6 +2021,7 @@ 'Promise': Promise, 'Strophe': Strophe, '_': _, + 'f': f, 'b64_sha1': b64_sha1, 'moment': moment, 'sizzle': sizzle, diff --git a/src/converse-headline.js b/src/converse-headline.js index 78169ef95..63ef06b05 100644 --- a/src/converse-headline.js +++ b/src/converse-headline.js @@ -146,8 +146,7 @@ } function registerHeadlineHandler () { - _converse.connection.addHandler( - onHeadlineMessage, null, 'message'); + _converse.connection.addHandler(onHeadlineMessage, null, 'message'); } _converse.on('connected', registerHeadlineHandler); _converse.on('reconnected', registerHeadlineHandler); diff --git a/src/converse-mam.js b/src/converse-mam.js index 40df3367f..a72a9f791 100644 --- a/src/converse-mam.js +++ b/src/converse-mam.js @@ -83,7 +83,7 @@ } 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 return true; } diff --git a/src/lodash.fp.js b/src/lodash.fp.js index b91c7d36b..b3cf7e922 100644 --- a/src/lodash.fp.js +++ b/src/lodash.fp.js @@ -1,5 +1,4 @@ -define(['lodash', 'lodash.converter', 'converse-core'], function (_, lodashConverter, converse) { +define(['lodash', 'lodash.converter'], function (_, lodashConverter) { var fp = lodashConverter(_.runInContext()); - converse.env.fp = fp; return fp; }); diff --git a/src/templates/spoiler_button.html b/src/templates/spoiler_button.html index 591f82d7c..11f8fcf36 100644 --- a/src/templates/spoiler_button.html +++ b/src/templates/spoiler_button.html @@ -1,9 +1,6 @@ -{[ if (o.show_spoiler_button) { ]} -
  • + {[ if (o.composing_spoiler) { ]} icon-eye-blocked {[ } ]} + {[ if (!o.composing_spoiler) { ]} icon-eye {[ } ]}" + title="{{ o.label_toggle_spoiler }}">
  • -{[ } ]} diff --git a/src/templates/toolbar.html b/src/templates/toolbar.html index 13a10decd..0b6a7095e 100644 --- a/src/templates/toolbar.html +++ b/src/templates/toolbar.html @@ -4,14 +4,6 @@ {[ } ]} -{[ if (o.show_spoiler_button) { ]} -
  • - -
  • -{[ } ]} {[ if (o.show_call_button) { ]}
  • {[ } ]}