Forráskód Böngészése

Asynchronously render spoiler button only if all clients support it

JC Brand 7 éve
szülő
commit
03b9447f1d

+ 1 - 1
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();
                 }));
 

+ 51 - 20
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';
-            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);
-
-            view.model.set('otr_status', 0);
-            spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
-            expect(spoiler_toggle).not.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', 2);
-            spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
-            expect(spoiler_toggle).toBe(null);
+            // 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);
 
-            view.model.set('otr_status', 3);
-            spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
-            expect(spoiler_toggle).not.toBe(null);
-            done();
+            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);
+
+                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', 3);
+
+                    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', 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();
+                });
+            });
         }));
     });
 

+ 125 - 100
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';
-            test_utils.openChatBoxFor(_converse, contact_jid);
-
-            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();
+            // 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 textarea = view.el.querySelector('.chat-textarea');
-            textarea.value = 'This is the spoiler';
-            view.keyPressed({
-                target: textarea,
-                preventDefault: _.noop,
-                keyCode: 13
+            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 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 
+                *
+                * <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",
@@ -155,61 +169,72 @@
             test_utils.openControlBox();
             test_utils.openContactsPanel(_converse);
             var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-            test_utils.openChatBoxFor(_converse, contact_jid);
 
-            var view = _converse.chatboxviews.get(contact_jid);
-
-            var spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler');
-            spoiler_toggle.click();
-
-            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';
+            // 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);
 
-            view.keyPressed({
-                target: textarea,
-                preventDefault: _.noop,
-                keyCode: 13
+            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();
+
+                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';
+
+                view.keyPressed({
+                    target: textarea,
+                    preventDefault: _.noop,
+                    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">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();
         }));
     });
 }));

+ 32 - 3
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();

+ 4 - 1
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,

+ 1 - 2
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);

+ 1 - 1
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;
             }

+ 1 - 2
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;
 });

+ 3 - 6
src/templates/spoiler_button.html

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

+ 0 - 8
src/templates/toolbar.html

@@ -4,14 +4,6 @@
     <span class="emoji-picker"></span>
 </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)  { ]}
 <li class="toggle-call"><a class="icon-phone" title="{{{o.label_start_call}}}"></a></li>
 {[ } ]}