Jelajahi Sumber

Mark followup messags so that they can be styled differently

JC Brand 7 tahun lalu
induk
melakukan
4bcf8e7bc3
3 mengubah file dengan 219 tambahan dan 12 penghapusan
  1. 178 10
      spec/messages.js
  2. 40 1
      src/converse-chatview.js
  3. 1 1
      src/templates/message.html

+ 178 - 10
spec/messages.js

@@ -37,10 +37,10 @@
                     var message = 'This is a received message';
                     var message = 'This is a received message';
                     var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
                     var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
                     var msg = $msg({
                     var msg = $msg({
-                            from: sender_jid,
-                            to: _converse.connection.jid,
-                            type: 'chat',
-                            id: (new Date()).getTime()
+                            'from': sender_jid,
+                            'to': _converse.connection.jid,
+                            'type': 'chat',
+                            'id': (new Date()).getTime()
                         }).c('body').t(message).up()
                         }).c('body').t(message).up()
                         .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
                         .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
 
 
@@ -575,6 +575,7 @@
                 expect($day[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toBe('Older message');
                 expect($day[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toBe('Older message');
 
 
                 var $el = $chat_content.find('.chat-msg:first').find('.chat-msg-text')
                 var $el = $chat_content.find('.chat-msg:first').find('.chat-msg-text')
+                expect($el.hasClass('chat-msg-followup')).toBe(false);
                 expect($el.text()).toEqual('Older message');
                 expect($el.text()).toEqual('Older message');
 
 
                 $time = $chat_content.find('time:eq(1)');
                 $time = $chat_content.find('time:eq(1)');
@@ -589,6 +590,7 @@
                 expect($el[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toEqual('another inbetween message');
                 expect($el[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toEqual('another inbetween message');
                 $el = $chat_content.find('.chat-msg:eq(2)');
                 $el = $chat_content.find('.chat-msg:eq(2)');
                 expect($el.find('.chat-msg-text').text()).toEqual('another inbetween message');
                 expect($el.find('.chat-msg-text').text()).toEqual('another inbetween message');
+                expect($el.hasClass('chat-msg-followup')).toBe(true);
 
 
                 $time = $chat_content.find('time:nth(2)');
                 $time = $chat_content.find('time:nth(2)');
                 expect($time.text()).toEqual("Tuesday Jan 2nd 2018");
                 expect($time.text()).toEqual("Tuesday Jan 2nd 2018");
@@ -599,14 +601,17 @@
 
 
                 $el = $chat_content.find('.chat-msg:eq(3)');
                 $el = $chat_content.find('.chat-msg:eq(3)');
                 expect($el.find('.chat-msg-text').text()).toEqual('An earlier message on the next day');
                 expect($el.find('.chat-msg-text').text()).toEqual('An earlier message on the next day');
+                expect($el.hasClass('chat-msg-followup')).toBe(false);
 
 
                 $el = $chat_content.find('.chat-msg:eq(4)');
                 $el = $chat_content.find('.chat-msg:eq(4)');
                 expect($el.find('.chat-msg-text').text()).toEqual('message');
                 expect($el.find('.chat-msg-text').text()).toEqual('message');
                 expect($el[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toEqual('newer message from the next day');
                 expect($el[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toEqual('newer message from the next day');
+                expect($el.hasClass('chat-msg-followup')).toBe(false);
 
 
                 $day = $chat_content.find('.date-separator:last');
                 $day = $chat_content.find('.date-separator:last');
                 expect($day.data('isodate')).toEqual(moment().startOf('day').format());
                 expect($day.data('isodate')).toEqual(moment().startOf('day').format());
                 expect($day[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toBe('latest message');
                 expect($day[0].nextElementSibling.querySelector('.chat-msg-text').textContent).toBe('latest message');
+                expect($el.hasClass('chat-msg-followup')).toBe(false);
                 done();
                 done();
             });
             });
         }));
         }));
@@ -701,12 +706,7 @@
                 function (done, _converse) {
                 function (done, _converse) {
 
 
             var contact, sent_stanza, IQ_id, stanza;
             var contact, sent_stanza, IQ_id, stanza;
-            test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp'])
-            .then(function () {
-                return test_utils.waitUntil(function () {
-                    return _converse.xmppstatus.get('fullname');
-                }, 300);
-            }).then(function () {
+            test_utils.waitUntilDiscoConfirmed(_converse, 'localhost', [], ['vcard-temp']).then(function () {
                 test_utils.createContacts(_converse, 'current');
                 test_utils.createContacts(_converse, 'current');
                 test_utils.openControlBox();
                 test_utils.openControlBox();
 
 
@@ -1138,6 +1138,174 @@
             done();
             done();
         }));
         }));
 
 
+        it("will be correctly identified and rendered as a followup message",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
+            test_utils.createContacts(_converse, 'current');
+            test_utils.openControlBox();
+
+            test_utils.waitUntil(() => $(_converse.rosterview.el).find('.roster-group').length, 300)
+            .then(function () {
+                const base_time = new Date();
+                const ONE_MINUTE_LATER = 60000;
+
+                jasmine.clock().install();
+                jasmine.clock().mockDate(base_time);
+
+                var message, msg;
+                spyOn(_converse, 'log');
+                spyOn(_converse.chatboxes, 'getChatBox').and.callThrough();
+                _converse.filter_by_resource = true;
+                var sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+
+                _converse.chatboxes.onMessage($msg({
+                        'from': sender_jid,
+                        'to': _converse.connection.jid,
+                        'type': 'chat',
+                        'id': (new Date()).getTime()
+                    }).c('body').t('A message').up()
+                    .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
+
+                jasmine.clock().tick(3*ONE_MINUTE_LATER);
+
+                _converse.chatboxes.onMessage($msg({
+                        'from': sender_jid,
+                        'to': _converse.connection.jid,
+                        'type': 'chat',
+                        'id': (new Date()).getTime()
+                    }).c('body').t("Another message 3 minutes later").up()
+                    .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
+
+                jasmine.clock().tick(11*ONE_MINUTE_LATER);
+
+                _converse.chatboxes.onMessage($msg({
+                        'from': sender_jid,
+                        'to': _converse.connection.jid,
+                        'type': 'chat',
+                        'id': (new Date()).getTime()
+                    }).c('body').t("Another message 14 minutes since we started").up()
+                    .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
+
+                jasmine.clock().tick(1000);
+
+                // Insert <composing> message, to also check that
+                // text messages are inserted correctly with
+                // temporary chat events in the chat contents.
+                _converse.chatboxes.onMessage($msg({
+                        'id': 'aeb219',
+                        'to': _converse.bare_jid,
+                        'xmlns': 'jabber:client',
+                        'from': sender_jid,
+                        'type': 'chat'})
+                    .c('composing', {'xmlns': Strophe.NS.CHATSTATES}).up()
+                    .tree());
+
+                jasmine.clock().tick(1*ONE_MINUTE_LATER);
+
+                _converse.chatboxes.onMessage($msg({
+                        'from': sender_jid,
+                        'to': _converse.connection.jid,
+                        'type': 'chat',
+                        'id': (new Date()).getTime()
+                    }).c('body').t("Another message 1 minute and 1 second since the previous one").up()
+                    .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
+
+                jasmine.clock().tick(1*ONE_MINUTE_LATER);
+
+                var view = _converse.chatboxviews.get(sender_jid);
+                test_utils.sendMessage(view, "Another message within 10 minutes, but from a different person");
+
+                var chat_content = view.el.querySelector('.chat-content');
+                expect(chat_content.querySelectorAll('.message').length).toBe(6);
+                expect(chat_content.querySelectorAll('.chat-msg').length).toBe(5);
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(2) .chat-msg-text').textContent).toBe("A message");
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(3) .chat-msg-text').textContent).toBe(
+                    "Another message 3 minutes later");
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(4) .chat-msg-text').textContent).toBe(
+                    "Another message 14 minutes since we started");
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(5) .chat-msg-text').textContent).toBe(
+                    "Another message 1 minute and 1 second since the previous one");
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(6) .chat-msg-text').textContent).toBe(
+                    "Another message within 10 minutes, but from a different person");
+
+                // Let's add a delayed, inbetween message
+                _converse.chatboxes.onMessage($msg({'id': 'aeb218', 'to': _converse.bare_jid})
+                    .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
+                        .c('delay', {'xmlns': 'urn:xmpp:delay',
+                                     'stamp': moment(base_time).add(5, 'minutes').format()
+                                    }).up()
+                        .c('message', {
+                            'xmlns': 'jabber:client',
+                            'to': _converse.bare_jid,
+                            'from': sender_jid,
+                            'type': 'chat'})
+                        .c('body').t("A delayed message, sent 5 minutes since we started")
+                        .tree());
+
+                expect(chat_content.querySelectorAll('.message').length).toBe(7);
+                expect(chat_content.querySelectorAll('.chat-msg').length).toBe(6);
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(2) .chat-msg-text').textContent).toBe("A message");
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(3) .chat-msg-text').textContent).toBe(
+                    "Another message 3 minutes later");
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(4) .chat-msg-text').textContent).toBe(
+                    "A delayed message, sent 5 minutes since we started");
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(5) .chat-msg-text').textContent).toBe(
+                    "Another message 14 minutes since we started");
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(6) .chat-msg-text').textContent).toBe(
+                    "Another message 1 minute and 1 second since the previous one");
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(false);
+
+                _converse.chatboxes.onMessage($msg({'id': 'aeb213', 'to': _converse.bare_jid})
+                    .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'})
+                        .c('delay', {'xmlns': 'urn:xmpp:delay', 'stamp':moment(base_time).add(4, 'minutes').format()}).up()
+                        .c('message', {
+                            'xmlns': 'jabber:client',
+                            'to': sender_jid,
+                            'from': _converse.bare_jid+"/some-other-resource",
+                            'type': 'chat'})
+                        .c('body').t("A carbon message 4 minutes later")
+                        .tree());
+                expect(chat_content.querySelectorAll('.message').length).toBe(8);
+                expect(chat_content.querySelectorAll('.chat-msg').length).toBe(7);
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(2) .chat-msg-text').textContent).toBe("A message");
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(3) .chat-msg-text').textContent).toBe(
+                    "Another message 3 minutes later");
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(4) .chat-msg-text').textContent).toBe(
+                    "A carbon message 4 minutes later");
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(5) .chat-msg-text').textContent).toBe(
+                    "A delayed message, sent 5 minutes since we started");
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(6) .chat-msg-text').textContent).toBe(
+                    "Another message 14 minutes since we started");
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(true);
+                expect(chat_content.querySelector('.message:nth-child(7) .chat-msg-text').textContent).toBe(
+                    "Another message 1 minute and 1 second since the previous one");
+                expect(u.hasClass('chat-msg-followup', chat_content.querySelector('.message:nth-child(8)'))).toBe(false);
+                expect(chat_content.querySelector('.message:nth-child(8) .chat-msg-text').textContent).toBe(
+                    "Another message within 10 minutes, but from a different person");
+
+                jasmine.clock().uninstall();
+                done();
+            });
+        }));
+
+
         describe("which contains a OOB URL", function () {
         describe("which contains a OOB URL", function () {
 
 
             it("will render audio from oob mp3 URLs",
             it("will render audio from oob mp3 URLs",

+ 40 - 1
src/converse-chatview.js

@@ -637,7 +637,7 @@
                 },
                 },
 
 
                 insertMessage (view) {
                 insertMessage (view) {
-                    /* Given a view representing a message, insert it inot the
+                    /* Given a view representing a message, insert it into the
                      * content area of the chat box.
                      * content area of the chat box.
                      *
                      *
                      * Parameters:
                      * Parameters:
@@ -663,6 +663,45 @@
                             return;
                             return;
                         }
                         }
                         previous_msg_el.insertAdjacentElement('afterend', view.el);
                         previous_msg_el.insertAdjacentElement('afterend', view.el);
+                        this.markFollowups(view.el);
+                    }
+                },
+
+                markFollowups (el) {
+                    /* Given a message element, determine wether it should be
+                     * marked as a followup message to the previous element.
+                     *
+                     * Also determine whether the element following it is a
+                     * followup message or not.
+                     *
+                     * Followup messages are subsequent ones written by the same
+                     * author with no other conversation elements inbetween and
+                     * posted within 10 minutes of one another.
+                     *
+                     * Parameters:
+                     *  (HTMLElement) el - The message element.
+                     */
+                    const from = el.getAttribute('data-from'),
+                          previous_el = el.previousElementSibling,
+                          date = moment(el.getAttribute('data-isodate'));
+
+                    if (previous_el.getAttribute('data-from') === from &&
+                        date.isBefore(moment(previous_el.getAttribute('data-isodate')).add(10, 'minutes'))) {
+
+                        u.addClass('chat-msg-followup', el);
+                    }
+                    const next_el = el.nextElementSibling;
+                    if (!next_el) {
+                        return;
+                    }
+                    if (next_el.getAttribute('data-from') !== from) {
+                        u.removeClass('chat-msg-followup', next_el);
+                    } else {
+                        if (moment(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes'))) {
+                            u.addClass('chat-msg-followup', next_el);
+                        } else {
+                            u.removeClass('chat-msg-followup', next_el);
+                        }
                     }
                     }
                 },
                 },
 
 

+ 1 - 1
src/templates/message.html

@@ -1,4 +1,4 @@
-<div class="message chat-msg {{{o.type}}} {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}">
+<div class="message chat-msg {{{o.type}}} {{{o.extra_classes}}}" data-isodate="{{{o.time}}}" data-msgid="{{{o.msgid}}}" data-from="{{{o.from}}}">
     {[ if (o.type !== 'headline') { ]}
     {[ if (o.type !== 'headline') { ]}
     <img alt="User Avatar" class="avatar" height="36px" width="36px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
     <img alt="User Avatar" class="avatar" height="36px" width="36px" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
     {[ } ]}
     {[ } ]}