Jelajahi Sumber

Prioritize roster nickname as message and chatbox display name

Set reference to roster contact on the message and chatbox object and
listen for changes to the nickname.

Currently, because chat boxes are fetched and initialized before the
roster, messages and chats are repainted with the correct display name
only later, causing a "flash" effect.

Ideally we would only initialize the chat boxes after the roster
contacts have been fetched, but this is currently not easily possible
because we need the control box to render before everything else.
JC Brand 6 tahun lalu
induk
melakukan
a75c118a2c

+ 1 - 0
CHANGES.md

@@ -4,6 +4,7 @@
 
 * Bugfix: Don't set `muc_domain` for roomspanel if `locked_muc_domain` is `true`.
 * Bugfix: Modal auto-closes when you open it for a second time.
+* Take roster nickname into consideration when rendering messages and chat headings.
 
 ## 4.2.0 (2019-04-04)
 

+ 53 - 38
dist/converse.js

@@ -48734,7 +48734,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].plugins
     initStatus: function initStatus(reconnecting) {
       const _converse = this.__super__._converse;
 
-      if (!reconnecting) {
+      if (!reconnecting && _converse.chatboxviews) {
         _converse.chatboxviews.closeAllChatBoxes();
       }
 
@@ -49097,20 +49097,26 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].plugins
     _converse.ChatBoxHeading = _converse.ViewWithAvatar.extend({
       initialize() {
         this.model.on('change:status', this.onStatusMessageChanged, this);
-        this.model.vcard.on('change', this.render, this);
+        this.debouncedRender = _.debounce(this.render, 50);
+        this.model.vcard.on('change', this.debouncedRender, this);
+        this.model.on('rosterContactAdded', () => {
+          this.model.contact.on('change:nickname', this.debouncedRender, this);
+          this.debouncedRender();
+        });
       },
 
       render() {
         this.el.innerHTML = templates_chatbox_head_html__WEBPACK_IMPORTED_MODULE_8___default()(_.extend(this.model.vcard.toJSON(), this.model.toJSON(), {
           '_converse': _converse,
-          'info_close': __('Close this chat box')
+          'info_close': __('Close this chat box'),
+          'display_name': this.model.getDisplayName()
         }));
         this.renderAvatar();
         return this;
       },
 
       onStatusMessageChanged(item) {
-        this.render();
+        this.debouncedRender();
         /**
          * When a contact's custom status message has changed.
          * @event _converse#contactStatusMessageChanged
@@ -52271,10 +52277,16 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].plugins
       },
 
       initialize() {
+        this.debouncedRender = _.debounce(this.render, 50);
+
         if (this.model.vcard) {
-          this.model.vcard.on('change', this.render, this);
+          this.model.vcard.on('change', this.debouncedRender, this);
         }
 
+        this.model.on('rosterContactAdded', () => {
+          this.model.contact.on('change:nickname', this.debouncedRender, this);
+          this.debouncedRender();
+        });
         this.model.on('change', this.onChanged, this);
         this.model.on('destroy', this.remove, this);
       },
@@ -52316,7 +52328,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_1__["default"].plugins
         }
 
         if (_.filter(['correcting', 'message', 'type', 'upload', 'received'], prop => Object.prototype.hasOwnProperty.call(this.model.changed, prop)).length) {
-          await this.render();
+          await this.debouncedRender();
         }
 
         if (edited) {
@@ -60201,7 +60213,6 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__["default"].plugins
       },
 
       onContactChange(contact) {
-        this.updateChatBox(contact);
         this.update();
 
         if (_.has(contact.changed, 'subscription')) {
@@ -60223,21 +60234,6 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_4__["default"].plugins
         this.updateFilter();
       },
 
-      updateChatBox(contact) {
-        if (!this.model.chatbox) {
-          return this;
-        }
-
-        const changes = {};
-
-        if (_.has(contact.changed, 'status')) {
-          changes.status = contact.get('status');
-        }
-
-        this.model.chatbox.save(changes);
-        return this;
-      },
-
       getGroup(name) {
         /* Returns the group as specified by name.
          * Creates the group if it doesn't exist.
@@ -61771,7 +61767,20 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
 
     _converse.router.route('converse/chat?jid=:jid', openChat);
 
-    _converse.Message = Backbone.Model.extend({
+    const ModelWithContact = Backbone.Model.extend({
+      async setRosterContact(jid) {
+        await _converse.api.waitUntil('rosterContactsFetched');
+
+        const contact = _converse.roster.get(jid);
+
+        if (contact) {
+          this.contact = contact;
+          this.trigger('rosterContactAdded');
+        }
+      }
+
+    });
+    _converse.Message = ModelWithContact.extend({
       defaults() {
         return {
           'msgid': _converse.connection.getUniqueId(),
@@ -61782,6 +61791,10 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
       initialize() {
         this.setVCard();
 
+        if (this.get('type') === 'chat') {
+          this.setRosterContact(Strophe.getBareJidFromJid(this.get('from')));
+        }
+
         if (this.get('file')) {
           this.on('change:put', this.uploadFile, this);
         }
@@ -61857,7 +61870,11 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
         if (this.get('type') === 'groupchat') {
           return this.get('nick');
         } else {
-          return this.vcard.get('fullname') || this.get('from');
+          if (this.contact) {
+            return this.contact.getDisplayName();
+          }
+
+          return this.vcard.get('nickname') || this.vcard.get('fullname') || this.get('from');
         }
       },
 
@@ -61971,7 +61988,7 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
      * @memberOf _converse
      */
 
-    _converse.ChatBox = Backbone.Model.extend({
+    _converse.ChatBox = ModelWithContact.extend({
       defaults() {
         return {
           'bookmarked': false,
@@ -62012,6 +62029,11 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
         }) || _converse.presences.create({
           'jid': jid
         });
+
+        if (this.get('type') === _converse.PRIVATE_CHAT_TYPE) {
+          this.setRosterContact(jid);
+        }
+
         this.messages = new _converse.Messages();
 
         const storage = _converse.config.get('storage');
@@ -62045,7 +62067,11 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
       },
 
       getDisplayName() {
-        return this.vcard.get('fullname') || this.get('jid');
+        if (this.contact) {
+          return this.contact.getDisplayName();
+        }
+
+        return this.vcard.get('nickname') || this.vcard.get('fullname') || this.get('jid');
       },
 
       getUpdatedMessageAttributes(message, stanza) {
@@ -68833,7 +68859,6 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
           'jid': bare_jid,
           'user_id': Strophe.getNodeFromJid(jid)
         }, attributes));
-        this.setChatBox();
         /**
          * When a contact's presence status has changed.
          * The presence status is either `online`, `offline`, `dnd`, `away` or `xa`.
@@ -68846,16 +68871,6 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
         this.presence.on('change:show', () => this.trigger('presenceChanged'));
       },
 
-      setChatBox() {
-        let chatbox = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
-        chatbox = chatbox || _converse.chatboxes.get(this.get('jid'));
-
-        if (chatbox) {
-          this.chatbox = chatbox;
-          this.chatbox.on('change:hidden', this.render, this);
-        }
-      },
-
       getDisplayName() {
         return this.get('nickname') || this.vcard.get('nickname') || this.vcard.get('fullname') || this.get('jid');
       },
@@ -93010,7 +93025,7 @@ __e(o.url) +
 '" target="_blank" rel="noopener" class="user">\n                ';
  } ;
 __p += '\n                        ' +
-__e( o.nickname || o.fullname || o.jid ) +
+__e( o.display_name ) +
 '\n                ';
  if (o.url) { ;
 __p += '\n                    </a>\n                ';

+ 17 - 15
spec/messages.js

@@ -99,7 +99,7 @@
             expect(textarea.value).toBe('');
             expect(view.model.messages.at(0).get('correcting')).toBe(false);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-            await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false);
+            await test_utils.waitUntil(() => (u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false), 500);
 
             // Test that messages from other users don't have the pencil icon
             _converse.chatboxes.onMessage(
@@ -154,7 +154,7 @@
             expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
             expect(view.model.messages.at(0).get('correcting')).toBe(true);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-            await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')));
+            await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
 
             spyOn(_converse.connection, 'send');
             textarea.value = 'But soft, what light through yonder window breaks?';
@@ -185,7 +185,7 @@
             expect(corrected_message.get('older_versions')[0]).toBe('But soft, what light through yonder airlock breaks?');
 
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-            await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false);
+            await test_utils.waitUntil(() => (u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false), 500);
 
             // Test that pressing the down arrow cancels message correction
             expect(textarea.value).toBe('');
@@ -196,7 +196,7 @@
             expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
             expect(view.model.messages.at(0).get('correcting')).toBe(true);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-            await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')) === true);
+            await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
             expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
             view.keyPressed({
                 target: textarea,
@@ -205,7 +205,7 @@
             expect(textarea.value).toBe('');
             expect(view.model.messages.at(0).get('correcting')).toBe(false);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-            await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false);
+            await test_utils.waitUntil(() => (u.hasClass('correcting', view.el.querySelector('.chat-msg')) === false), 500);
 
             textarea.value = 'It is the east, and Juliet is the one.';
             view.keyPressed({
@@ -233,7 +233,7 @@
             expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
             expect(view.model.messages.at(1).get('correcting')).toBeFalsy();
             expect(view.model.messages.at(2).get('correcting')).toBe(true);
-            await test_utils.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg:last', view.el).pop()));
+            await test_utils.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg:last', view.el).pop()), 500);
 
             textarea.selectionEnd = 0; // Happens by pressing up,
                                     // but for some reason not in tests, so we set it manually.
@@ -245,7 +245,7 @@
             expect(view.model.messages.at(0).get('correcting')).toBeFalsy();
             expect(view.model.messages.at(1).get('correcting')).toBe(true);
             expect(view.model.messages.at(2).get('correcting')).toBeFalsy();
-            await test_utils.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg', view.el)[1]));
+            await test_utils.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg', view.el)[1]), 500);
 
             textarea.value = 'It is the east, and Juliet is the sun.';
             view.keyPressed({
@@ -2335,7 +2335,7 @@
                 }).c('body').t('But soft, what light through yonder chimney breaks?').up()
                     .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
             await test_utils.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
-                'But soft, what light through yonder chimney breaks?');
+                'But soft, what light through yonder chimney breaks?', 500);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
 
@@ -2348,7 +2348,7 @@
                     .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree());
 
             await test_utils.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
-                'But soft, what light through yonder window breaks?');
+                'But soft, what light through yonder window breaks?', 500);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
             expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1);
             view.el.querySelector('.chat-msg__content .fa-edit').click();
@@ -2451,7 +2451,7 @@
             expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
             expect(view.model.messages.at(0).get('correcting')).toBe(true);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
-            await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')));
+            await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
             expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
             view.keyPressed({
                 target: textarea,
@@ -2460,7 +2460,7 @@
             expect(textarea.value).toBe('');
             expect(view.model.messages.at(0).get('correcting')).toBe(false);
             expect(view.el.querySelectorAll('.chat-msg').length).toBe(2);
-            await test_utils.waitUntil(() => !u.hasClass('correcting', view.el.querySelector('.chat-msg')));
+            await test_utils.waitUntil(() => !u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
             done();
         }));
 
@@ -2493,7 +2493,7 @@
                     <origin-id xmlns="urn:xmpp:sid:0" id="${msg_obj.get('origin_id')}"/>
                 </message>`);
             await view.model.onMessage(stanza);
-            await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-msg__receipt').length);
+            await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-msg__receipt').length, 500);
             expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(1);
             expect(view.model.messages.length).toBe(1);
 
@@ -2854,10 +2854,12 @@
                 expect(textarea.value).toBe('hello @z3r0 @gibson @mr.robot, how are you?');
                 expect(view.model.messages.at(0).get('correcting')).toBe(true);
                 expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-                await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')));
+                await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500);
 
                 textarea.value = 'hello @z3r0 @gibson @sw0rdf1sh, how are you?';
                 view.keyPressed(enter_event);
+                await test_utils.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent ===
+                    'hello z3r0 gibson sw0rdf1sh, how are you?', 500);
 
                 const correction = _converse.connection.send.calls.all()[1].args[0];
                 expect(correction.toLocaleString())
@@ -2866,9 +2868,9 @@
                             `xmlns="jabber:client">`+
                                 `<body>hello z3r0 gibson sw0rdf1sh, how are you?</body>`+
                                 `<active xmlns="http://jabber.org/protocol/chatstates"/>`+
-                                `<reference begin="18" end="27" type="mention" uri="xmpp:sw0rdf1sh@localhost" xmlns="urn:xmpp:reference:0"/>`+
-                                `<reference begin="11" end="17" type="mention" uri="xmpp:gibson@localhost" xmlns="urn:xmpp:reference:0"/>`+
                                 `<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@localhost" xmlns="urn:xmpp:reference:0"/>`+
+                                `<reference begin="11" end="17" type="mention" uri="xmpp:gibson@localhost" xmlns="urn:xmpp:reference:0"/>`+
+                                `<reference begin="18" end="27" type="mention" uri="xmpp:sw0rdf1sh@localhost" xmlns="urn:xmpp:reference:0"/>`+
                                 `<replace id="${msg.nodeTree.getAttribute("id")}" xmlns="urn:xmpp:message-correct:0"/>`+
                                 `<origin-id id="${msg.nodeTree.querySelector('origin-id').getAttribute("id")}" xmlns="urn:xmpp:sid:0"/>`+
                             `</message>`);

+ 2 - 2
spec/minchats.js

@@ -1,6 +1,6 @@
 (function (root, factory) {
-    define(["jquery", "jasmine", "mock", "test-utils"], factory);
-} (this, function ($, jasmine, mock, test_utils) {
+    define(["jasmine", "mock", "test-utils"], factory);
+} (this, function (jasmine, mock, test_utils) {
     const _ = converse.env._;
     const  $msg = converse.env.$msg;
     const u = converse.env.utils;

+ 1 - 1
src/converse-chatboxviews.js

@@ -47,7 +47,7 @@ converse.plugins.add('converse-chatboxviews', {
 
         initStatus: function (reconnecting) {
             const { _converse } = this.__super__;
-            if (!reconnecting) {
+            if (!reconnecting && _converse.chatboxviews) {
                 _converse.chatboxviews.closeAllChatBoxes();
             }
             return this.__super__.initStatus.apply(this, arguments);

+ 10 - 3
src/converse-chatview.js

@@ -161,7 +161,13 @@ converse.plugins.add('converse-chatview', {
         _converse.ChatBoxHeading = _converse.ViewWithAvatar.extend({
             initialize () {
                 this.model.on('change:status', this.onStatusMessageChanged, this);
-                this.model.vcard.on('change', this.render, this);
+
+                this.debouncedRender = _.debounce(this.render, 50);
+                this.model.vcard.on('change', this.debouncedRender, this);
+                this.model.on('rosterContactAdded', () => {
+                    this.model.contact.on('change:nickname', this.debouncedRender, this);
+                    this.debouncedRender();
+                });
             },
 
             render () {
@@ -170,7 +176,8 @@ converse.plugins.add('converse-chatview', {
                         this.model.vcard.toJSON(),
                         this.model.toJSON(),
                         { '_converse': _converse,
-                          'info_close': __('Close this chat box')
+                          'info_close': __('Close this chat box'),
+                          'display_name': this.model.getDisplayName()
                         }
                     )
                 );
@@ -179,7 +186,7 @@ converse.plugins.add('converse-chatview', {
             },
 
             onStatusMessageChanged (item) {
-                this.render();
+                this.debouncedRender();
                 /**
                  * When a contact's custom status message has changed.
                  * @event _converse#contactStatusMessageChanged

+ 7 - 2
src/converse-message-view.js

@@ -81,9 +81,14 @@ converse.plugins.add('converse-message-view', {
             },
 
             initialize () {
+                this.debouncedRender = _.debounce(this.render, 50);
                 if (this.model.vcard) {
-                    this.model.vcard.on('change', this.render, this);
+                    this.model.vcard.on('change', this.debouncedRender, this);
                 }
+                this.model.on('rosterContactAdded', () => {
+                    this.model.contact.on('change:nickname', this.debouncedRender, this);
+                    this.debouncedRender();
+                });
                 this.model.on('change', this.onChanged, this);
                 this.model.on('destroy', this.remove, this);
             },
@@ -119,7 +124,7 @@ converse.plugins.add('converse-message-view', {
                 }
                 if (_.filter(['correcting', 'message', 'type', 'upload', 'received'],
                              prop => Object.prototype.hasOwnProperty.call(this.model.changed, prop)).length) {
-                    await this.render();
+                    await this.debouncedRender();
                 }
                 if (edited) {
                     this.onMessageEdited();

+ 0 - 13
src/converse-rosterview.js

@@ -926,7 +926,6 @@ converse.plugins.add('converse-rosterview', {
             },
 
             onContactChange (contact) {
-                this.updateChatBox(contact)
                 this.update();
                 if (_.has(contact.changed, 'subscription')) {
                     if (contact.changed.subscription === 'from') {
@@ -944,18 +943,6 @@ converse.plugins.add('converse-rosterview', {
                 this.updateFilter();
             },
 
-            updateChatBox (contact) {
-                if (!this.model.chatbox) {
-                    return this;
-                }
-                const changes = {};
-                if (_.has(contact.changed, 'status')) {
-                    changes.status = contact.get('status');
-                }
-                this.model.chatbox.save(changes);
-                return this;
-            },
-
             getGroup (name) {
                 /* Returns the group as specified by name.
                  * Creates the group if it doesn't exist.

+ 29 - 4
src/headless/converse-chatboxes.js

@@ -56,7 +56,20 @@ converse.plugins.add('converse-chatboxes', {
         _converse.router.route('converse/chat?jid=:jid', openChat);
 
 
-        _converse.Message = Backbone.Model.extend({
+        const ModelWithContact = Backbone.Model.extend({
+
+            async setRosterContact (jid) {
+                await _converse.api.waitUntil('rosterContactsFetched');
+                const contact = _converse.roster.get(jid);
+                if (contact) {
+                    this.contact = contact;
+                    this.trigger('rosterContactAdded');
+                }
+            }
+        });
+
+
+        _converse.Message = ModelWithContact.extend({
 
             defaults () {
                 return {
@@ -67,6 +80,9 @@ converse.plugins.add('converse-chatboxes', {
 
             initialize () {
                 this.setVCard();
+                if (this.get('type') === 'chat') {
+                    this.setRosterContact(Strophe.getBareJidFromJid(this.get('from')));
+                }
                 if (this.get('file')) {
                     this.on('change:put', this.uploadFile, this);
                 }
@@ -120,7 +136,10 @@ converse.plugins.add('converse-chatboxes', {
                 if (this.get('type') === 'groupchat') {
                     return this.get('nick');
                 } else {
-                    return this.vcard.get('fullname') || this.get('from');
+                    if (this.contact) {
+                        return this.contact.getDisplayName();
+                    }
+                    return this.vcard.get('nickname') || this.vcard.get('fullname') || this.get('from');
                 }
             },
 
@@ -226,7 +245,7 @@ converse.plugins.add('converse-chatboxes', {
          * @namespace _converse.ChatBox
          * @memberOf _converse
          */
-        _converse.ChatBox = Backbone.Model.extend({
+        _converse.ChatBox = ModelWithContact.extend({
             defaults () {
                 return {
                     'bookmarked': false,
@@ -258,6 +277,9 @@ converse.plugins.add('converse-chatboxes', {
                 // probably shouldn't have here, so we should probably move
                 // ChatBox out of converse-chatboxes
                 this.presence = _converse.presences.findWhere({'jid': jid}) || _converse.presences.create({'jid': jid});
+                if (this.get('type') === _converse.PRIVATE_CHAT_TYPE) {
+                    this.setRosterContact(jid);
+                }
 
                 this.messages = new _converse.Messages();
                 const storage = _converse.config.get('storage');
@@ -293,7 +315,10 @@ converse.plugins.add('converse-chatboxes', {
             },
 
             getDisplayName () {
-                return this.vcard.get('fullname') || this.get('jid');
+                if (this.contact) {
+                    return this.contact.getDisplayName();
+                }
+                return this.vcard.get('nickname') || this.vcard.get('fullname') || this.get('jid');
             },
 
             getUpdatedMessageAttributes (message, stanza) {

+ 0 - 11
src/headless/converse-roster.js

@@ -246,9 +246,6 @@ converse.plugins.add('converse-roster', {
                     'jid': bare_jid,
                     'user_id': Strophe.getNodeFromJid(jid)
                 }, attributes));
-
-                this.setChatBox();
-
                 /**
                  * When a contact's presence status has changed.
                  * The presence status is either `online`, `offline`, `dnd`, `away` or `xa`.
@@ -260,14 +257,6 @@ converse.plugins.add('converse-roster', {
                 this.presence.on('change:show', () => this.trigger('presenceChanged'));
             },
 
-            setChatBox (chatbox=null) {
-                chatbox = chatbox || _converse.chatboxes.get(this.get('jid'));
-                if (chatbox) {
-                    this.chatbox = chatbox;
-                    this.chatbox.on('change:hidden', this.render, this);
-                }
-            },
-
             getDisplayName () {
                 return this.get('nickname') || this.vcard.get('nickname') || this.vcard.get('fullname') || this.get('jid');
             },

+ 34 - 15
src/headless/dist/converse-headless.js

@@ -40215,7 +40215,20 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
 
     _converse.router.route('converse/chat?jid=:jid', openChat);
 
-    _converse.Message = Backbone.Model.extend({
+    const ModelWithContact = Backbone.Model.extend({
+      async setRosterContact(jid) {
+        await _converse.api.waitUntil('rosterContactsFetched');
+
+        const contact = _converse.roster.get(jid);
+
+        if (contact) {
+          this.contact = contact;
+          this.trigger('rosterContactAdded');
+        }
+      }
+
+    });
+    _converse.Message = ModelWithContact.extend({
       defaults() {
         return {
           'msgid': _converse.connection.getUniqueId(),
@@ -40226,6 +40239,10 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
       initialize() {
         this.setVCard();
 
+        if (this.get('type') === 'chat') {
+          this.setRosterContact(Strophe.getBareJidFromJid(this.get('from')));
+        }
+
         if (this.get('file')) {
           this.on('change:put', this.uploadFile, this);
         }
@@ -40301,7 +40318,11 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
         if (this.get('type') === 'groupchat') {
           return this.get('nick');
         } else {
-          return this.vcard.get('fullname') || this.get('from');
+          if (this.contact) {
+            return this.contact.getDisplayName();
+          }
+
+          return this.vcard.get('nickname') || this.vcard.get('fullname') || this.get('from');
         }
       },
 
@@ -40415,7 +40436,7 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
      * @memberOf _converse
      */
 
-    _converse.ChatBox = Backbone.Model.extend({
+    _converse.ChatBox = ModelWithContact.extend({
       defaults() {
         return {
           'bookmarked': false,
@@ -40456,6 +40477,11 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
         }) || _converse.presences.create({
           'jid': jid
         });
+
+        if (this.get('type') === _converse.PRIVATE_CHAT_TYPE) {
+          this.setRosterContact(jid);
+        }
+
         this.messages = new _converse.Messages();
 
         const storage = _converse.config.get('storage');
@@ -40489,7 +40515,11 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
       },
 
       getDisplayName() {
-        return this.vcard.get('fullname') || this.get('jid');
+        if (this.contact) {
+          return this.contact.getDisplayName();
+        }
+
+        return this.vcard.get('nickname') || this.vcard.get('fullname') || this.get('jid');
       },
 
       getUpdatedMessageAttributes(message, stanza) {
@@ -47277,7 +47307,6 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
           'jid': bare_jid,
           'user_id': Strophe.getNodeFromJid(jid)
         }, attributes));
-        this.setChatBox();
         /**
          * When a contact's presence status has changed.
          * The presence status is either `online`, `offline`, `dnd`, `away` or `xa`.
@@ -47290,16 +47319,6 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
         this.presence.on('change:show', () => this.trigger('presenceChanged'));
       },
 
-      setChatBox() {
-        let chatbox = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
-        chatbox = chatbox || _converse.chatboxes.get(this.get('jid'));
-
-        if (chatbox) {
-          this.chatbox = chatbox;
-          this.chatbox.on('change:hidden', this.render, this);
-        }
-      },
-
       getDisplayName() {
         return this.get('nickname') || this.vcard.get('nickname') || this.vcard.get('fullname') || this.get('jid');
       },

+ 1 - 1
src/templates/chatbox_head.html

@@ -7,7 +7,7 @@
                 {[ if (o.url) { ]}
                     <a href="{{{o.url}}}" target="_blank" rel="noopener" class="user">
                 {[ } ]}
-                        {{{ o.nickname || o.fullname || o.jid }}}
+                        {{{ o.display_name }}}
                 {[ if (o.url) { ]}
                     </a>
                 {[ } ]}