Browse Source

converse-message-view: Add hooks before/after message text transformations

JC Brand 6 years ago
parent
commit
21a9ea7365

+ 6 - 4
spec/messages.js

@@ -565,6 +565,7 @@
             expect(msg_obj.get('sender')).toEqual('them');
             expect(msg_obj.get('sender')).toEqual('them');
             expect(msg_obj.get('is_delayed')).toEqual(false);
             expect(msg_obj.get('is_delayed')).toEqual(false);
             // Now check that the message appears inside the chatbox in the DOM
             // Now check that the message appears inside the chatbox in the DOM
+            await new Promise(resolve => view.once('messageInserted', resolve));
             const chat_content = view.el.querySelector('.chat-content');
             const chat_content = view.el.querySelector('.chat-content');
             expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(msgtext);
             expect(chat_content.querySelector('.chat-msg .chat-msg__text').textContent).toEqual(msgtext);
             expect(chat_content.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
             expect(chat_content.querySelector('.chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy();
@@ -1603,6 +1604,7 @@
                     expect(view).toBeDefined();
                     expect(view).toBeDefined();
                     expect(chatbox.get('fullname') === sender_jid);
                     expect(chatbox.get('fullname') === sender_jid);
 
 
+                    await new Promise(resolve => view.once('messageInserted', resolve));
                     await u.waitUntil(() => view.el.querySelector('.chat-msg__author').textContent.trim() === 'Mercutio');
                     await u.waitUntil(() => view.el.querySelector('.chat-msg__author').textContent.trim() === 'Mercutio');
                     let author_el = view.el.querySelector('.chat-msg__author');
                     let author_el = view.el.querySelector('.chat-msg__author');
                     expect( _.includes(author_el.textContent.trim(), 'Mercutio')).toBeTruthy();
                     expect( _.includes(author_el.textContent.trim(), 'Mercutio')).toBeTruthy();
@@ -3010,9 +3012,9 @@
                             `xmlns="jabber:client">`+
                             `xmlns="jabber:client">`+
                                 `<body>hello z3r0 gibson mr.robot, how are you?</body>`+
                                 `<body>hello z3r0 gibson mr.robot, how are you?</body>`+
                                 `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
                                 `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
-                                `<reference begin="18" end="26" type="mention" uri="xmpp:mr.robot@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
-                                `<reference begin="11" end="17" type="mention" uri="xmpp:gibson@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
                                 `<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
                                 `<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
+                                `<reference begin="11" end="17" type="mention" uri="xmpp:gibson@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
+                                `<reference begin="18" end="26" type="mention" uri="xmpp:mr.robot@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
                                 `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
                                 `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
                             `</message>`);
                             `</message>`);
 
 
@@ -3087,9 +3089,9 @@
                             `xmlns="jabber:client">`+
                             `xmlns="jabber:client">`+
                                 `<body>hello z3r0 gibson mr.robot, how are you?</body>`+
                                 `<body>hello z3r0 gibson mr.robot, how are you?</body>`+
                                 `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
                                 `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
-                                `<reference begin="18" end="26" type="mention" uri="xmpp:mr.robot@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
-                                `<reference begin="11" end="17" type="mention" uri="xmpp:gibson@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
                                 `<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
                                 `<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
+                                `<reference begin="11" end="17" type="mention" uri="xmpp:gibson@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
+                                `<reference begin="18" end="26" type="mention" uri="xmpp:mr.robot@montague.lit" xmlns="urn:xmpp:reference:0"/>`+
                                 `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
                                 `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
                             `</message>`);
                             `</message>`);
                 done();
                 done();

+ 2 - 2
src/converse-chatboxviews.js

@@ -32,8 +32,8 @@ const AvatarMixin = {
                 'width': avatar_el.getAttribute('width'),
                 'width': avatar_el.getAttribute('width'),
                 'height': avatar_el.getAttribute('height'),
                 'height': avatar_el.getAttribute('height'),
             }
             }
-            const image_type = this.model.vcard.get('image_type'),
-                  image = this.model.vcard.get('image');
+            const image_type = this.model.vcard.get('image_type');
+            const image = this.model.vcard.get('image');
             data['image'] = "data:" + image_type + ";base64," + image;
             data['image'] = "data:" + image_type + ";base64," + image;
             avatar_el.outerHTML = tpl_avatar(data);
             avatar_el.outerHTML = tpl_avatar(data);
         }
         }

+ 0 - 1
src/converse-chatview.js

@@ -307,7 +307,6 @@ converse.plugins.add('converse-chatview', {
 
 
         /**
         /**
          * The View of an open/ongoing chat conversation.
          * The View of an open/ongoing chat conversation.
-         *
          * @class
          * @class
          * @namespace _converse.ChatBoxView
          * @namespace _converse.ChatBoxView
          * @memberOf _converse
          * @memberOf _converse

+ 28 - 5
src/converse-message-view.js

@@ -80,6 +80,11 @@ converse.plugins.add('converse-message-view', {
         });
         });
 
 
 
 
+        /**
+         * @class
+         * @namespace _converse.MessageView
+         * @memberOf _converse
+         */
         _converse.MessageView = _converse.ViewWithAvatar.extend({
         _converse.MessageView = _converse.ViewWithAvatar.extend({
             events: {
             events: {
                 'click .chat-msg__edit-modal': 'showMessageVersionsModal',
                 'click .chat-msg__edit-modal': 'showMessageVersionsModal',
@@ -204,14 +209,33 @@ converse.plugins.add('converse-message-view', {
                 return u.renderImageURL(_converse, url);
                 return u.renderImageURL(_converse, url);
             },
             },
 
 
-            transformBodyText (text) {
+            async transformBodyText (text) {
+                /**
+                 * Synchronous event which provides a hook for transforming a chat message's body text
+                 * before the default transformations have been applied.
+                 * @event _converse#beforeMessageBodyTransformed
+                 * @param { _converse.MessageView } view - The view representing the message
+                 * @param { string } text - The message text
+                 * @example _converse.api.listen.on('beforeMessageBodyTransformed', (view, text) => { ... });
+                 */
+                await _converse.api.trigger('beforeMessageBodyTransformed', this, text, {'Synchronous': true});
                 text = this.isMeCommand() ? text.substring(4) : text;
                 text = this.isMeCommand() ? text.substring(4) : text;
                 text = xss.filterXSS(text, {'whiteList': {}, 'onTag': onTagFoundDuringXSSFilter});
                 text = xss.filterXSS(text, {'whiteList': {}, 'onTag': onTagFoundDuringXSSFilter});
                 text = u.geoUriToHttp(text, _converse.geouri_replacement);
                 text = u.geoUriToHttp(text, _converse.geouri_replacement);
                 text = u.addMentionsMarkup(text, this.model.get('references'), this.model.collection.chatbox);
                 text = u.addMentionsMarkup(text, this.model.get('references'), this.model.collection.chatbox);
                 text = u.addHyperlinks(text);
                 text = u.addHyperlinks(text);
                 text = u.renderNewLines(text);
                 text = u.renderNewLines(text);
-                return u.addEmoji(_converse, text);
+                text = u.addEmoji(_converse, text);
+                /**
+                 * Synchronous event which provides a hook for transforming a chat message's body text
+                 * after the default transformations have been applied.
+                 * @event _converse#afterMessageBodyTransformed
+                 * @param { _converse.MessageView } view - The view representing the message
+                 * @param { string } text - The message text
+                 * @example _converse.api.listen.on('afterMessageBodyTransformed', (view, text) => { ... });
+                 */
+                await _converse.api.trigger('afterMessageBodyTransformed', this, text, {'Synchronous': true});
+                return text;
             },
             },
 
 
             async renderChatMessage () {
             async renderChatMessage () {
@@ -244,13 +268,12 @@ converse.plugins.add('converse-message-view', {
                 const text = this.getMessageText();
                 const text = this.getMessageText();
                 const msg_content = msg.querySelector('.chat-msg__text');
                 const msg_content = msg.querySelector('.chat-msg__text');
                 if (text && text !== url) {
                 if (text && text !== url) {
-                    msg_content.innerHTML = this.transformBodyText(text);
+                    msg_content.innerHTML = await this.transformBodyText(text);
+                    await u.renderImageURLs(_converse, msg_content);
                 }
                 }
-                const promise = u.renderImageURLs(_converse, msg_content);
                 if (this.model.get('type') !== 'headline') {
                 if (this.model.get('type') !== 'headline') {
                     this.renderAvatar(msg);
                     this.renderAvatar(msg);
                 }
                 }
-                await promise;
                 this.replaceElement(msg);
                 this.replaceElement(msg);
                 if (this.model.collection) {
                 if (this.model.collection) {
                     // If the model gets destroyed in the meantime, it no
                     // If the model gets destroyed in the meantime, it no

+ 5 - 3
src/headless/converse-core.js

@@ -1463,16 +1463,18 @@ _converse.api = {
      * {@link _converse.api.listen.on} or {@link _converse.api.listen.once}
      * {@link _converse.api.listen.on} or {@link _converse.api.listen.once}
      * (see [_converse.api.listen](http://localhost:8000/docs/html/api/-_converse.api.listen.html)).
      * (see [_converse.api.listen](http://localhost:8000/docs/html/api/-_converse.api.listen.html)).
      *
      *
+     * Some events also double as promises and can be waited on via {@link _converse.api.waitUntil}.
+     *
      * @method _converse.api.trigger
      * @method _converse.api.trigger
      * @param {string} name - The event name
      * @param {string} name - The event name
      * @param {...any} [argument] - Argument to be passed to the event handler
      * @param {...any} [argument] - Argument to be passed to the event handler
      * @param {object} [options]
      * @param {object} [options]
      * @param {boolean} [options.synchronous] - Whether the event is synchronous or not.
      * @param {boolean} [options.synchronous] - Whether the event is synchronous or not.
-     *    When a synchronous event is fired, Converse will wait for all
-     *    promises returned by the event's handlers to finish before continuing.
+     *  When a synchronous event is fired, a promise will be returned
+     *  by {@link _converse.api.trigger} which resolves once all the
+     *  event handlers' promises have been resolved.
      */
      */
     async trigger (name) {
     async trigger (name) {
-        /* Event emitter and promise resolver */
         const args = Array.from(arguments);
         const args = Array.from(arguments);
         const options = args.pop();
         const options = args.pop();
         if (options && options.synchronous) {
         if (options && options.synchronous) {

+ 9 - 13
src/utils/html.js

@@ -161,33 +161,29 @@ u.renderImageURL = function (_converse, url) {
 };
 };
 
 
 
 
+/**
+ * Returns a Promise which resolves once all images have been loaded.
+ * @returns {Promise}
+ */
 u.renderImageURLs = function (_converse, el) {
 u.renderImageURLs = function (_converse, el) {
-    /* Returns a Promise which resolves once all images have been loaded.
-     */
     if (!_converse.show_images_inline) {
     if (!_converse.show_images_inline) {
         return Promise.resolve();
         return Promise.resolve();
     }
     }
-    const { __ } = _converse;
     const list = el.textContent.match(URL_REGEX) || [];
     const list = el.textContent.match(URL_REGEX) || [];
     return Promise.all(
     return Promise.all(
-        _.map(list, url =>
+        list.map(url =>
             new Promise((resolve, reject) => {
             new Promise((resolve, reject) => {
                 if (u.isImageURL(url)) {
                 if (u.isImageURL(url)) {
                     return isImage(url).then(img => {
                     return isImage(url).then(img => {
                         const i = new Image();
                         const i = new Image();
                         i.src = img.src;
                         i.src = img.src;
                         i.addEventListener('load', resolve);
                         i.addEventListener('load', resolve);
-                        // We also resolve for non-images, otherwise the
-                        // Promise.all resolves prematurely.
+                        // We also resolve (instead of reject) for non-images,
+                        // otherwise the Promise.all resolves prematurely.
                         i.addEventListener('error', resolve);
                         i.addEventListener('error', resolve);
-
                         const { __ } = _converse;
                         const { __ } = _converse;
-                        _.each(sizzle(`a[href="${url}"]`, el), (a) => {
-                            a.outerHTML= tpl_image({
-                                'url': url,
-                                'label_download': __('Download')
-                            })
-                        });
+                        sizzle(`a[href="${url}"]`, el)
+                            .forEach(a => (a.outerHTML = tpl_image({url, 'label_download': __('Download')})));
                     }).catch(resolve)
                     }).catch(resolve)
                 } else {
                 } else {
                     return resolve();
                     return resolve();