Sfoglia il codice sorgente

updates #1069 Highlight currently open private chat in the sidebar

JC Brand 7 anni fa
parent
commit
efa5e30596

+ 149 - 134
css/converse.css

@@ -7821,15 +7821,24 @@ body.reset {
         color: #578EA9;
         font-size: 20px;
         margin-right: 0.5em; }
-#conversejs .set-xmpp-status .fa-circle, #conversejs .xmpp-status .fa-circle, #conversejs .roster-contacts .fa-circle {
+#conversejs .set-xmpp-status .fa-circle,
+#conversejs .xmpp-status .fa-circle,
+#conversejs .roster-contacts .fa-circle {
   color: #3AA569; }
-#conversejs .set-xmpp-status .fa-minus-circle, #conversejs .xmpp-status .fa-minus-circle, #conversejs .roster-contacts .fa-minus-circle {
+#conversejs .set-xmpp-status .fa-minus-circle,
+#conversejs .xmpp-status .fa-minus-circle,
+#conversejs .roster-contacts .fa-minus-circle {
   color: #E77051; }
-#conversejs .set-xmpp-status .fa-dot-circle-o, #conversejs .xmpp-status .fa-dot-circle-o, #conversejs .roster-contacts .fa-dot-circle-o {
+#conversejs .set-xmpp-status .fa-dot-circle-o,
+#conversejs .xmpp-status .fa-dot-circle-o,
+#conversejs .roster-contacts .fa-dot-circle-o {
   color: #E7A151; }
-#conversejs .set-xmpp-status .fa-circle-o, #conversejs .xmpp-status .fa-circle-o, #conversejs .roster-contacts .fa-circle-o {
-  color: #A8ABA1; }
-#conversejs .set-xmpp-status .fa-times-circle, #conversejs .xmpp-status .fa-times-circle, #conversejs .roster-contacts .fa-times-circle {
+#conversejs .set-xmpp-status .fa-circle-o,
+#conversejs .set-xmpp-status .fa-times-circle,
+#conversejs .xmpp-status .fa-circle-o,
+#conversejs .xmpp-status .fa-times-circle,
+#conversejs .roster-contacts .fa-circle-o,
+#conversejs .roster-contacts .fa-times-circle {
   color: #A8ABA1; }
 #conversejs .room-info {
   font-size: 12px;
@@ -8259,132 +8268,142 @@ body.reset {
     padding: 0 0 0.5rem 0; }
     #conversejs .list-container .rooms-toggle:hover {
       color: #585B51; }
-  #conversejs .list-container .items-list {
-    text-align: left; }
-    #conversejs .list-container .items-list .list-item {
-      border: none;
-      clear: both;
-      color: #666;
-      display: block;
-      height: 2em;
-      overflow: hidden;
-      padding-top: 0.5em;
-      text-shadow: 0 1px 0 #FAFAFA;
-      word-wrap: break-word; }
-    #conversejs .list-container .items-list .available-chatroom:hover,
-    #conversejs .list-container .items-list .open-headline:hover,
-    #conversejs .list-container .items-list .open-chatroom:hover {
-      background-color: #eff4f7; }
-      #conversejs .list-container .items-list .available-chatroom:hover .remove-bookmark,
-      #conversejs .list-container .items-list .available-chatroom:hover .add-bookmark,
-      #conversejs .list-container .items-list .available-chatroom:hover .close-room,
-      #conversejs .list-container .items-list .available-chatroom:hover .room-info,
-      #conversejs .list-container .items-list .open-headline:hover .remove-bookmark,
-      #conversejs .list-container .items-list .open-headline:hover .add-bookmark,
-      #conversejs .list-container .items-list .open-headline:hover .close-room,
-      #conversejs .list-container .items-list .open-headline:hover .room-info,
-      #conversejs .list-container .items-list .open-chatroom:hover .remove-bookmark,
-      #conversejs .list-container .items-list .open-chatroom:hover .add-bookmark,
-      #conversejs .list-container .items-list .open-chatroom:hover .close-room,
-      #conversejs .list-container .items-list .open-chatroom:hover .room-info {
-        display: block !important; }
-    #conversejs .list-container .items-list .available-chatroom.open,
-    #conversejs .list-container .items-list .open-headline.open,
-    #conversejs .list-container .items-list .open-chatroom.open {
+#conversejs .items-list {
+  text-align: left; }
+  #conversejs .items-list .list-item {
+    border: none;
+    clear: both;
+    color: #666;
+    display: block;
+    height: 2em;
+    overflow: hidden;
+    padding-top: 0.5em;
+    text-shadow: 0 1px 0 #FAFAFA;
+    word-wrap: break-word; }
+    #conversejs .items-list .list-item.open {
       background-color: #578EA9; }
-      #conversejs .list-container .items-list .available-chatroom.open a,
-      #conversejs .list-container .items-list .open-headline.open a,
-      #conversejs .list-container .items-list .open-chatroom.open a {
+      #conversejs .items-list .list-item.open:hover {
+        background-color: #578EA9 !important; }
+      #conversejs .items-list .list-item.open a {
         color: white !important; }
-    #conversejs .list-container .items-list .available-chatroom.unread-msgs .msgs-indicator,
-    #conversejs .list-container .items-list .open-headline.unread-msgs .msgs-indicator,
-    #conversejs .list-container .items-list .open-chatroom.unread-msgs .msgs-indicator {
-      border-radius: 10%;
-      opacity: 1; }
-    #conversejs .list-container .items-list .available-chatroom.unread-msgs .available-room,
-    #conversejs .list-container .items-list .available-chatroom.unread-msgs .open-room,
-    #conversejs .list-container .items-list .open-headline.unread-msgs .available-room,
-    #conversejs .list-container .items-list .open-headline.unread-msgs .open-room,
-    #conversejs .list-container .items-list .open-chatroom.unread-msgs .available-room,
-    #conversejs .list-container .items-list .open-chatroom.unread-msgs .open-room {
-      width: 100%;
-      font-weight: bold; }
-    #conversejs .list-container .items-list .available-chatroom a:hover,
-    #conversejs .list-container .items-list .open-headline a:hover,
-    #conversejs .list-container .items-list .open-chatroom a:hover {
-      color: #206485; }
-    #conversejs .list-container .items-list .available-chatroom a.remove-bookmark, #conversejs .list-container .items-list .available-chatroom a.add-bookmark, #conversejs .list-container .items-list .available-chatroom a.close-room, #conversejs .list-container .items-list .available-chatroom a.room-info,
-    #conversejs .list-container .items-list .open-headline a.remove-bookmark,
-    #conversejs .list-container .items-list .open-headline a.add-bookmark,
-    #conversejs .list-container .items-list .open-headline a.close-room,
-    #conversejs .list-container .items-list .open-headline a.room-info,
-    #conversejs .list-container .items-list .open-chatroom a.remove-bookmark,
-    #conversejs .list-container .items-list .open-chatroom a.add-bookmark,
-    #conversejs .list-container .items-list .open-chatroom a.close-room,
-    #conversejs .list-container .items-list .open-chatroom a.room-info {
-      display: none; }
-      #conversejs .list-container .items-list .available-chatroom a.remove-bookmark:before, #conversejs .list-container .items-list .available-chatroom a.add-bookmark:before, #conversejs .list-container .items-list .available-chatroom a.close-room:before, #conversejs .list-container .items-list .available-chatroom a.room-info:before,
-      #conversejs .list-container .items-list .open-headline a.remove-bookmark:before,
-      #conversejs .list-container .items-list .open-headline a.add-bookmark:before,
-      #conversejs .list-container .items-list .open-headline a.close-room:before,
-      #conversejs .list-container .items-list .open-headline a.room-info:before,
-      #conversejs .list-container .items-list .open-chatroom a.remove-bookmark:before,
-      #conversejs .list-container .items-list .open-chatroom a.add-bookmark:before,
-      #conversejs .list-container .items-list .open-chatroom a.close-room:before,
-      #conversejs .list-container .items-list .open-chatroom a.room-info:before {
-        font-size: 15px; }
-    #conversejs .list-container .items-list .available-chatroom a.open-room,
-    #conversejs .list-container .items-list .open-headline a.open-room,
-    #conversejs .list-container .items-list .open-chatroom a.open-room {
-      width: 68%;
-      float: left;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
-      padding-right: 0.5em; }
-    #conversejs .list-container .items-list .available-chatroom a.available-room,
-    #conversejs .list-container .items-list .open-headline a.available-room,
-    #conversejs .list-container .items-list .open-chatroom a.available-room {
-      width: 85%; }
-    #conversejs .list-container .items-list .available-chatroom .add-bookmark,
-    #conversejs .list-container .items-list .available-chatroom .remove-bookmark,
-    #conversejs .list-container .items-list .available-chatroom .close-room,
-    #conversejs .list-container .items-list .available-chatroom .room-info,
-    #conversejs .list-container .items-list .open-headline .add-bookmark,
-    #conversejs .list-container .items-list .open-headline .remove-bookmark,
-    #conversejs .list-container .items-list .open-headline .close-room,
-    #conversejs .list-container .items-list .open-headline .room-info,
-    #conversejs .list-container .items-list .open-chatroom .add-bookmark,
-    #conversejs .list-container .items-list .open-chatroom .remove-bookmark,
-    #conversejs .list-container .items-list .open-chatroom .close-room,
-    #conversejs .list-container .items-list .open-chatroom .room-info {
-      color: #A8ABA1; }
-      #conversejs .list-container .items-list .available-chatroom .add-bookmark.button-on,
-      #conversejs .list-container .items-list .available-chatroom .remove-bookmark.button-on,
-      #conversejs .list-container .items-list .available-chatroom .close-room.button-on,
-      #conversejs .list-container .items-list .available-chatroom .room-info.button-on,
-      #conversejs .list-container .items-list .open-headline .add-bookmark.button-on,
-      #conversejs .list-container .items-list .open-headline .remove-bookmark.button-on,
-      #conversejs .list-container .items-list .open-headline .close-room.button-on,
-      #conversejs .list-container .items-list .open-headline .room-info.button-on,
-      #conversejs .list-container .items-list .open-chatroom .add-bookmark.button-on,
-      #conversejs .list-container .items-list .open-chatroom .remove-bookmark.button-on,
-      #conversejs .list-container .items-list .open-chatroom .close-room.button-on,
-      #conversejs .list-container .items-list .open-chatroom .room-info.button-on {
-        color: #578EA9; }
-        #conversejs .list-container .items-list .available-chatroom .add-bookmark.button-on:hover,
-        #conversejs .list-container .items-list .available-chatroom .remove-bookmark.button-on:hover,
-        #conversejs .list-container .items-list .available-chatroom .close-room.button-on:hover,
-        #conversejs .list-container .items-list .available-chatroom .room-info.button-on:hover,
-        #conversejs .list-container .items-list .open-headline .add-bookmark.button-on:hover,
-        #conversejs .list-container .items-list .open-headline .remove-bookmark.button-on:hover,
-        #conversejs .list-container .items-list .open-headline .close-room.button-on:hover,
-        #conversejs .list-container .items-list .open-headline .room-info.button-on:hover,
-        #conversejs .list-container .items-list .open-chatroom .add-bookmark.button-on:hover,
-        #conversejs .list-container .items-list .open-chatroom .remove-bookmark.button-on:hover,
-        #conversejs .list-container .items-list .open-chatroom .close-room.button-on:hover,
-        #conversejs .list-container .items-list .open-chatroom .room-info.button-on:hover {
-          color: #206485; }
+      #conversejs .items-list .list-item.open .fa:hover {
+        color: white; }
+      #conversejs .items-list .list-item.open .fa-circle {
+        color: #89d6ab; }
+      #conversejs .items-list .list-item.open .fa-minus-circle {
+        color: #f0a794; }
+      #conversejs .items-list .list-item.open .fa-dot-circle-o {
+        color: #f0c594; }
+      #conversejs .items-list .list-item.open .fa-circle-o,
+      #conversejs .items-list .list-item.open .fa-times-circle {
+        color: #e6e7e4; }
+  #conversejs .items-list .available-chatroom:hover,
+  #conversejs .items-list .open-headline:hover,
+  #conversejs .items-list .open-chatroom:hover,
+  #conversejs .items-list .list-item:hover {
+    background-color: #eff4f7; }
+    #conversejs .items-list .available-chatroom:hover .fa,
+    #conversejs .items-list .open-headline:hover .fa,
+    #conversejs .items-list .open-chatroom:hover .fa,
+    #conversejs .items-list .list-item:hover .fa {
+      display: block !important; }
+  #conversejs .items-list .available-chatroom.unread-msgs .msgs-indicator,
+  #conversejs .items-list .open-headline.unread-msgs .msgs-indicator,
+  #conversejs .items-list .open-chatroom.unread-msgs .msgs-indicator,
+  #conversejs .items-list .list-item.unread-msgs .msgs-indicator {
+    border-radius: 10%;
+    opacity: 1; }
+  #conversejs .items-list .available-chatroom.unread-msgs .available-room,
+  #conversejs .items-list .available-chatroom.unread-msgs .open-room,
+  #conversejs .items-list .open-headline.unread-msgs .available-room,
+  #conversejs .items-list .open-headline.unread-msgs .open-room,
+  #conversejs .items-list .open-chatroom.unread-msgs .available-room,
+  #conversejs .items-list .open-chatroom.unread-msgs .open-room,
+  #conversejs .items-list .list-item.unread-msgs .available-room,
+  #conversejs .items-list .list-item.unread-msgs .open-room {
+    width: 100%;
+    font-weight: bold; }
+  #conversejs .items-list .available-chatroom a:hover,
+  #conversejs .items-list .open-headline a:hover,
+  #conversejs .items-list .open-chatroom a:hover,
+  #conversejs .items-list .list-item a:hover {
+    color: #206485; }
+  #conversejs .items-list .available-chatroom a.fa,
+  #conversejs .items-list .open-headline a.fa,
+  #conversejs .items-list .open-chatroom a.fa,
+  #conversejs .items-list .list-item a.fa {
+    display: none; }
+    #conversejs .items-list .available-chatroom a.fa:before,
+    #conversejs .items-list .open-headline a.fa:before,
+    #conversejs .items-list .open-chatroom a.fa:before,
+    #conversejs .items-list .list-item a.fa:before {
+      font-size: 15px; }
+  #conversejs .items-list .available-chatroom a.open-room,
+  #conversejs .items-list .open-headline a.open-room,
+  #conversejs .items-list .open-chatroom a.open-room,
+  #conversejs .items-list .list-item a.open-room {
+    width: 68%;
+    float: left;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    padding-right: 0.5em; }
+  #conversejs .items-list .available-chatroom a.available-room,
+  #conversejs .items-list .open-headline a.available-room,
+  #conversejs .items-list .open-chatroom a.available-room,
+  #conversejs .items-list .list-item a.available-room {
+    width: 85%; }
+  #conversejs .items-list .available-chatroom .add-bookmark,
+  #conversejs .items-list .available-chatroom .remove-bookmark,
+  #conversejs .items-list .available-chatroom .close-room,
+  #conversejs .items-list .available-chatroom .room-info,
+  #conversejs .items-list .open-headline .add-bookmark,
+  #conversejs .items-list .open-headline .remove-bookmark,
+  #conversejs .items-list .open-headline .close-room,
+  #conversejs .items-list .open-headline .room-info,
+  #conversejs .items-list .open-chatroom .add-bookmark,
+  #conversejs .items-list .open-chatroom .remove-bookmark,
+  #conversejs .items-list .open-chatroom .close-room,
+  #conversejs .items-list .open-chatroom .room-info,
+  #conversejs .items-list .list-item .add-bookmark,
+  #conversejs .items-list .list-item .remove-bookmark,
+  #conversejs .items-list .list-item .close-room,
+  #conversejs .items-list .list-item .room-info {
+    color: #A8ABA1; }
+    #conversejs .items-list .available-chatroom .add-bookmark.button-on,
+    #conversejs .items-list .available-chatroom .remove-bookmark.button-on,
+    #conversejs .items-list .available-chatroom .close-room.button-on,
+    #conversejs .items-list .available-chatroom .room-info.button-on,
+    #conversejs .items-list .open-headline .add-bookmark.button-on,
+    #conversejs .items-list .open-headline .remove-bookmark.button-on,
+    #conversejs .items-list .open-headline .close-room.button-on,
+    #conversejs .items-list .open-headline .room-info.button-on,
+    #conversejs .items-list .open-chatroom .add-bookmark.button-on,
+    #conversejs .items-list .open-chatroom .remove-bookmark.button-on,
+    #conversejs .items-list .open-chatroom .close-room.button-on,
+    #conversejs .items-list .open-chatroom .room-info.button-on,
+    #conversejs .items-list .list-item .add-bookmark.button-on,
+    #conversejs .items-list .list-item .remove-bookmark.button-on,
+    #conversejs .items-list .list-item .close-room.button-on,
+    #conversejs .items-list .list-item .room-info.button-on {
+      color: #578EA9; }
+      #conversejs .items-list .available-chatroom .add-bookmark.button-on:hover,
+      #conversejs .items-list .available-chatroom .remove-bookmark.button-on:hover,
+      #conversejs .items-list .available-chatroom .close-room.button-on:hover,
+      #conversejs .items-list .available-chatroom .room-info.button-on:hover,
+      #conversejs .items-list .open-headline .add-bookmark.button-on:hover,
+      #conversejs .items-list .open-headline .remove-bookmark.button-on:hover,
+      #conversejs .items-list .open-headline .close-room.button-on:hover,
+      #conversejs .items-list .open-headline .room-info.button-on:hover,
+      #conversejs .items-list .open-chatroom .add-bookmark.button-on:hover,
+      #conversejs .items-list .open-chatroom .remove-bookmark.button-on:hover,
+      #conversejs .items-list .open-chatroom .close-room.button-on:hover,
+      #conversejs .items-list .open-chatroom .room-info.button-on:hover,
+      #conversejs .items-list .list-item .add-bookmark.button-on:hover,
+      #conversejs .items-list .list-item .remove-bookmark.button-on:hover,
+      #conversejs .items-list .list-item .close-room.button-on:hover,
+      #conversejs .items-list .list-item .room-info.button-on:hover {
+        color: #206485; }
 
 #conversejs #converse-roster {
   text-align: left;
@@ -8452,10 +8471,6 @@ body.reset {
             width: 1.5em; }
         #conversejs #converse-roster .roster-contacts .roster-group li.requesting-xmpp-contact .req-contact-name {
           padding: 0 0.2em 0 0; }
-        #conversejs #converse-roster .roster-contacts .roster-group li a:hover {
-          color: #206485; }
-        #conversejs #converse-roster .roster-contacts .roster-group li a .fa:hover {
-          color: white; }
         #conversejs #converse-roster .roster-contacts .roster-group li .open-chat {
           margin: 0;
           padding: 0; }

+ 83 - 33
dist/converse.js

@@ -68732,7 +68732,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
 
           if (type === 'error') {
             const error = message.querySelector('error');
-            return _.propertyOf(error.querySelector('text'))('textContent') || __('Sorry, an error occured:') + ' ' + error.innerHTML;
+            return _.propertyOf(error.querySelector('text'))('textContent') || __('Sorry, an error occurred:') + ' ' + error.innerHTML;
           } else {
             return _.propertyOf(message.querySelector('body'))('textContent');
           }
@@ -71924,7 +71924,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
       });
       this.connection.addHandler(iq => {
         if (iq.querySelectorAll('error').length > 0) {
-          _converse.log('An error occured while trying to enable message carbons.', Strophe.LogLevel.ERROR);
+          _converse.log('An error occurred while trying to enable message carbons.', Strophe.LogLevel.WARN);
         } else {
           this.session.save({
             'carbons_enabled': true
@@ -81216,10 +81216,20 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
             'jid': bare_jid,
             'user_id': Strophe.getNodeFromJid(jid)
           }, attributes));
+          this.setChatBox();
           this.presence.on('change:show', () => _converse.emit('contactPresenceChanged', this));
           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.vcard.get('fullname') || this.get('jid');
         },
@@ -81464,7 +81474,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
            *    (String) name - The name of that user
            *    (Array of Strings) groups - Any roster groups the user might belong to
            *    (Function) callback - A function to call once the IQ is returned
-           *    (Function) errback - A function to call if an error occured
+           *    (Function) errback - A function to call if an error occurred
            */
           name = _.isEmpty(name) ? jid : name;
           const iq = $iq({
@@ -81873,6 +81883,22 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
       /********** Event Handlers *************/
 
 
+      function updateUnreadCounter(chatbox) {
+        const contact = _converse.roster.findWhere({
+          'jid': chatbox.get('jid')
+        });
+
+        if (!_.isUndefined(contact)) {
+          contact.save({
+            'num_unread': chatbox.get('num_unread')
+          });
+        }
+      }
+
+      _converse.api.listen.on('chatBoxesInitialized', () => {
+        _converse.chatboxes.on('change:num_unread', updateUnreadCounter);
+      });
+
       _converse.api.listen.on('beforeTearDown', _converse.unregisterPresenceHandler());
 
       _converse.api.listen.on('afterTearDown', () => {
@@ -82348,7 +82374,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
       });
       _converse.RosterContactView = Backbone.NativeView.extend({
         tagName: 'li',
-        className: 'd-flex hidden controlbox-padded',
+        className: 'list-item d-flex hidden controlbox-padded',
         events: {
           "click .accept-xmpp-request": "acceptRequest",
           "click .decline-xmpp-request": "declineRequest",
@@ -82358,6 +82384,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
 
         initialize() {
           this.model.on("change", this.render, this);
+          this.model.on("highlight", this.highlight, this);
           this.model.on("destroy", this.remove, this);
           this.model.on("open", this.openChat, this);
           this.model.on("remove", this.remove, this);
@@ -82373,11 +82400,10 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
             return this;
           }
 
-          const item = this.model,
-                ask = item.get('ask'),
-                show = item.presence.get('show'),
-                requesting = item.get('requesting'),
-                subscription = item.get('subscription');
+          const ask = this.model.get('ask'),
+                show = this.model.presence.get('show'),
+                requesting = this.model.get('requesting'),
+                subscription = this.model.get('subscription');
           const classes_to_remove = ['current-xmpp-contact', 'pending-xmpp-contact', 'requesting-xmpp-contact'].concat(_.keys(STATUSES));
 
           _.each(classes_to_remove, function (cls) {
@@ -82388,6 +82414,19 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
 
           this.el.classList.add(show);
           this.el.setAttribute('data-status', show);
+          this.highlight();
+
+          if (_converse.isSingleton()) {
+            const chatbox = _converse.chatboxes.get(this.model.get('jid'));
+
+            if (chatbox) {
+              if (chatbox.get('hidden')) {
+                this.el.classList.remove('open');
+              } else {
+                this.el.classList.add('open');
+              }
+            }
+          }
 
           if (ask === 'subscribe' || subscription === 'from') {
             /* ask === 'subscribe'
@@ -82401,17 +82440,17 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
              *
              *  So in both cases the user is a "pending" contact.
              */
-            const display_name = item.getDisplayName();
+            const display_name = this.model.getDisplayName();
             this.el.classList.add('pending-xmpp-contact');
-            this.el.innerHTML = tpl_pending_contact(_.extend(item.toJSON(), {
+            this.el.innerHTML = tpl_pending_contact(_.extend(this.model.toJSON(), {
               'display_name': display_name,
               'desc_remove': __('Click to remove %1$s as a contact', display_name),
               'allow_chat_pending_contacts': _converse.allow_chat_pending_contacts
             }));
           } else if (requesting === true) {
-            const display_name = item.getDisplayName();
+            const display_name = this.model.getDisplayName();
             this.el.classList.add('requesting-xmpp-contact');
-            this.el.innerHTML = tpl_requesting_contact(_.extend(item.toJSON(), {
+            this.el.innerHTML = tpl_requesting_contact(_.extend(this.model.toJSON(), {
               'display_name': display_name,
               'desc_accept': __("Click to accept the contact request from %1$s", display_name),
               'desc_decline': __("Click to decline the contact request from %1$s", display_name),
@@ -82421,12 +82460,28 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
             this.el.classList.add('current-xmpp-contact');
             this.el.classList.remove(_.without(['both', 'to'], subscription)[0]);
             this.el.classList.add(subscription);
-            this.renderRosterItem(item);
+            this.renderRosterItem(this.model);
           }
 
           return this;
         },
 
+        highlight() {
+          /* If appropriate, highlight the contact (by adding the 'open' class).
+           */
+          if (_converse.isSingleton()) {
+            const chatbox = _converse.chatboxes.get(this.model.get('jid'));
+
+            if (chatbox) {
+              if (chatbox.get('hidden')) {
+                this.el.classList.remove('open');
+              } else {
+                this.el.classList.add('open');
+              }
+            }
+          }
+        },
+
         renderRosterItem(item) {
           let status_icon = 'fa-times-circle';
           const show = item.presence.get('show') || 'offline';
@@ -82913,18 +82968,17 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
         },
 
         updateChatBox(contact) {
-          const chatbox = _converse.chatboxes.get(contact.get('jid')),
-                changes = {};
-
-          if (!chatbox) {
+          if (!this.model.chatbox) {
             return this;
           }
 
+          const changes = {};
+
           if (_.has(contact.changed, 'status')) {
             changes.status = contact.get('status');
           }
 
-          chatbox.save(changes);
+          this.model.chatbox.save(changes);
           return this;
         },
 
@@ -82982,20 +83036,16 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
       });
       /* -------- Event Handlers ----------- */
 
-      function updateUnreadCounter(chatbox) {
-        const contact = _.head(_converse.roster.where({
-          'jid': chatbox.get('jid')
-        }));
-
-        if (!_.isUndefined(contact)) {
-          contact.save({
-            'num_unread': chatbox.get('num_unread')
+      _converse.api.listen.on('chatBoxesInitialized', () => {
+        _converse.chatboxes.on('change:hidden', chatbox => {
+          const contact = _converse.roster.findWhere({
+            'jid': chatbox.get('jid')
           });
-        }
-      }
 
-      _converse.api.listen.on('chatBoxesInitialized', () => {
-        _converse.chatboxes.on('change:num_unread', updateUnreadCounter);
+          if (!_.isUndefined(contact)) {
+            contact.trigger('highlight', contact);
+          }
+        });
       });
 
       function initRoster() {
@@ -85266,7 +85316,7 @@ __p += ' fa-caret-right ';
  } ;
 __p += '">\n    </span> ' +
 __e(o.label_group) +
-'</a>\n<ul class="roster-group-contacts ';
+'</a>\n<ul class="items-list roster-group-contacts ';
  if (o.toggle_state === o._converse.CLOSED) { ;
 __p += ' collapsed ';
  } ;
@@ -86368,7 +86418,7 @@ var _ = {escape:__webpack_require__(/*! ./node_modules/lodash/escape.js */ "./no
 module.exports = function(o) {
 var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
 function print() { __p += __j.call(arguments, '') }
-__p += '<!-- src/templates/roster_item.html -->\n<a class="open-chat w-100 ';
+__p += '<!-- src/templates/roster_item.html -->\n<a class="cbox-list-item open-chat w-100 ';
  if (o.num_unread) { ;
 __p += ' unread-msgs ';
  } ;

+ 1 - 1
mockup/chatbox.html

@@ -9,7 +9,7 @@
 </head>
 
 <body class="reset">
-    <div id="conversejs" class="fullscreen">
+    <div id="conversejs" class="fullscreen converse-fullscreen">
         <div class="sidebar"></div>
         <div class="converse-chatboxes row no-gutters">
             <div id="controlbox" class="chatbox">

+ 13 - 13
mockup/user-panel.html

@@ -109,12 +109,12 @@
             <a href="#" class="group-toggle controlbox-padded " title="Click to hide these contacts">
                 <span class="fa fa-caret-down"></span> Contact Requests</a>
             <ul class="roster-group-contacts">
-                <li class=" controlbox-padded offline requesting-xmpp-contact d-flex">
+                <li class="list-item controlbox-padded offline requesting-xmpp-contact d-flex">
                     <span class="req-contact-name w-100">The Nurse</span>
                     <a class="accept-xmpp-request fa fa-check" title="Click here to accept this contact's request" href="#"></a>
                     <a class="decline-xmpp-request fa fa-times" title="Click here to decline this contact's request" href="#"></a>
                 </li>
-                <li class=" controlbox-padded offline requesting-xmpp-contact d-flex">
+                <li class="list-item controlbox-padded offline requesting-xmpp-contact d-flex">
                     <span class="req-contact-name w-100">Friar Laurence</span>
                     <a class="accept-xmpp-request fa fa-check" title="Click here to accept this contact's request" href="#"></a>
                     <a class="decline-xmpp-request fa fa-times" title="Click here to decline this contact's request" href="#"></a>
@@ -126,12 +126,12 @@
             <a href="#" data-group="Colleagues" class="group-toggle controlbox-padded " title="Click to hide these contacts">
                 <span class="fa fa-caret-down"></span> Colleagues</a>
             <ul>
-                <li class=" controlbox-padded away current-xmpp-contact d-flex">
+                <li class="list-item controlbox-padded away current-xmpp-contact d-flex">
                     <a class="open-chat w-100" title="Click to chat with this contact" href="chatbox.html">
                         <span class="fa fa-circle-o" title="this contact is away"></span> Balthasar</a>
                     <a class="remove-xmpp-contact fa fa-trash" title="Click to remove this contact" href="#"></a>
                 </li>
-                <li class=" controlbox-padded dnd current-xmpp-contact d-flex">
+                <li class="list-item controlbox-padded dnd current-xmpp-contact d-flex">
                     <a class="open-chat w-100" title="Click to chat with this contact" href="chatbox.html">
                         <span class="fa fa-minus-circle" title="This contact is busy"></span> Escalus, Prince of Verona</a>
                     <a class="remove-xmpp-contact fa fa-trash" title="Click to remove this contact" href="#"></a>
@@ -143,17 +143,17 @@
             <a href="#" data-group="Family" class="group-toggle controlbox-padded " title="Click to hide these contacts">
                 <span class="fa fa-caret-down"></span> Family</a>
             <ul>
-                <li class=" controlbox-padded online current-xmpp-contact d-flex">
+                <li class="list-item controlbox-padded online current-xmpp-contact d-flex">
                     <a class="open-chat w-100" title="Click to chat with this contact" href="chatbox.html">
                         <span class="fa fa-circle" title="This contact is online"></span> Benvolio Montague</a>
                     <a class="remove-xmpp-contact fa fa-trash" title="Click to remove this contact" href="#"></a>
                 </li>
-                <li class=" controlbox-padded offline current-xmpp-contact d-flex">
+                <li class="list-item controlbox-padded offline current-xmpp-contact d-flex">
                     <a class="open-chat w-100" title="Click to chat with this contact" href="chatbox.html">
                         <span class="fa fa-times-circle" title="This contact is offline"></span> Montague</a>
                     <a class="remove-xmpp-contact fa fa-trash" title="Click to remove this contact" href="#"></a>
                 </li>
-                <li class=" controlbox-padded offline current-xmpp-contact d-flex">
+                <li class="list-item controlbox-padded offline current-xmpp-contact d-flex">
                     <a class="open-chat w-100" title="Click to chat with this contact" href="chatbox.html">
                         <span class="fa fa-times-circle" title="This contact is offline"></span> Lady Montague</a>
                     <a class="remove-xmpp-contact fa fa-trash" title="Click to remove this contact" href="#"></a>
@@ -165,7 +165,7 @@
             <a href="#" data-group="Friends" class="group-toggle controlbox-padded " title="Click to hide these contacts">
                 <span class="fa fa-caret-down"></span> Friends</a>
             <ul>
-                <li class=" controlbox-padded online current-xmpp-contact d-flex">
+                <li class="list-item controlbox-padded online current-xmpp-contact d-flex">
                     <a class="open-chat w-100" title="Click to chat with this contact" href="chatbox.html">
                         <span class="fa fa-circle" title="This contact is online"></span> Mercutio</a>
                     <a class="remove-xmpp-contact fa fa-trash" title="Click to remove this contact" href="#"></a>
@@ -177,17 +177,17 @@
             <a href="#" class="group-toggle controlbox-padded " title="Click to hide these contacts">
                 <span class="fa fa-caret-down"></span> Ungrouped</a>
             <ul>
-                <li class=" controlbox-padded online current-xmpp-contact d-flex">
+                <li class="list-item controlbox-padded online current-xmpp-contact d-flex">
                     <a class="open-chat w-100" title="Click to chat with this contact" href="chatbox.html">
                         <span class="fa fa-circle" title="This contact is online"></span> Gregory</a>
                     <a class="remove-xmpp-contact fa fa-trash" title="Click to remove this contact" href="#"></a>
                 </li>
-                <li class=" controlbox-padded online current-xmpp-contact d-flex">
+                <li class="list-item controlbox-padded online current-xmpp-contact d-flex">
                     <a class="open-chat w-100" title="Click to chat with this contact" href="chatbox.html">
                         <span class="fa fa-circle" title="This contact is online"></span> Peter</a>
                     <a class="remove-xmpp-contact fa fa-trash" title="Click to remove this contact" href="#"></a>
                 </li>
-                <li class=" controlbox-padded online current-xmpp-contact d-flex">
+                <li class="list-item controlbox-padded online current-xmpp-contact d-flex">
                     <a class="open-chat w-100" title="Click to chat with this contact" href="chatbox.html">
                         <span class="fa fa-circle" title="This contact is online"></span> Sampson</a>
                     <a class="remove-xmpp-contact fa fa-trash" title="Click to remove this contact" href="#"></a>
@@ -199,11 +199,11 @@
             <a href="#" class="group-toggle controlbox-padded " title="Click to hide these contacts">
                 <span class="fa fa-caret-down"></span> Pending Contacts</a>
             <ul>
-                <li class=" controlbox-padded offline pending-xmpp-contact d-flex">
+                <li class="list-item controlbox-padded offline pending-xmpp-contact d-flex">
                     <span class="pending-contact-name w-100">An Apothecary</span>
                     <a class="remove-xmpp-contact fa fa-trash" title="Click to remove this contact" href="#"></a>
                 </li>
-                <li class=" controlbox-padded offline pending-xmpp-contact d-flex">
+                <li class="list-item controlbox-padded offline pending-xmpp-contact d-flex">
                     <span class="pending-contact-name w-100">Abram</span>
                     <a class="remove-xmpp-contact fa fa-trash" title="Click to remove this contact" href="#"></a>
                 </li>

+ 4 - 4
sass/_controlbox.scss

@@ -20,7 +20,9 @@
         }
     }
 
-    .set-xmpp-status, .xmpp-status, .roster-contacts {
+    .set-xmpp-status,
+    .xmpp-status,
+    .roster-contacts {
         .fa-circle {
             color: $green;
         }
@@ -30,9 +32,7 @@
         .fa-dot-circle-o {
             color: $orange,
         }
-        .fa-circle-o {
-            color: $subdued-color;
-        }
+        .fa-circle-o,
         .fa-times-circle {
             color: $subdued-color;
         }

+ 116 - 0
sass/_lists.scss

@@ -0,0 +1,116 @@
+#conversejs {
+    .list-container {
+        text-align: left;
+        padding: 0.3em 0;
+
+        .rooms-toggle {
+            font-family: $heading-font; 
+            display: block;
+            color: $text-color;
+            padding: 0 0 0.5rem 0;
+            &:hover {
+                color: $dark-gray-color;
+            }
+        }
+    }
+
+    .items-list {
+        text-align: left;
+
+        .list-item {
+            border: none;
+            clear: both;
+            color: $text-color;
+            display: block;
+            height: 2em;
+            overflow: hidden;
+            padding-top: 0.5em;
+            text-shadow: 0 1px 0 $text-shadow-color;
+            word-wrap: break-word;
+
+            &.open {
+                background-color: $controlbox-head-color;
+                &:hover {
+                    background-color: $controlbox-head-color !important;
+                }
+                a {
+                    color: white !important;
+                }
+                .fa:hover {
+                    color: white;
+                }
+                .fa-circle {
+                    color: lighten($green, 25%);
+                }
+                .fa-minus-circle {
+                    color: lighten($red, 15%);
+                }
+                .fa-dot-circle-o {
+                    color: lighten($orange, 15%);
+                }
+                .fa-circle-o,
+                .fa-times-circle {
+                    color: lighten($subdued-color, 25%);
+                }
+            }
+
+        }
+
+        .available-chatroom,
+        .open-headline,
+        .open-chatroom,
+        .list-item {
+            &:hover {
+                background-color: lighten($controlbox-head-color, 45%);
+                .fa {
+                    display: block !important;
+                }
+            }
+            &.unread-msgs {
+                .msgs-indicator {
+                    border-radius: 10%;
+                    opacity: 1;
+                }
+                .available-room,
+                .open-room {
+                    width: 100%;
+                    font-weight: bold;
+                }
+            }
+            a {
+                &:hover {
+                    color: $dark-link-color;
+                }
+                &.fa {
+                    display: none;
+                    &:before {
+                        font-size: 15px;
+                    }
+                }
+                &.open-room {
+                    width: 68%;
+                    float: left;
+                    overflow: hidden;
+                    text-overflow: ellipsis;
+                    white-space: nowrap;
+                    padding-right: 0.5em;
+                }
+                &.available-room {
+                    width: 85%;
+                }
+            }
+            .add-bookmark,
+            .remove-bookmark,
+            .close-room,
+            .room-info {
+                &.button-on {
+                    color: $link-color;
+                    &:hover {
+                        color: $dark-link-color;
+                    }
+                }
+                color: $subdued-color;
+            }
+        }
+    }
+}

+ 0 - 99
sass/_roomslist.scss

@@ -1,99 +0,0 @@
-#conversejs {
-    .list-container {
-        text-align: left;
-        padding: 0.3em 0;
-
-        .rooms-toggle {
-            font-family: $heading-font; 
-            display: block;
-            color: $text-color;
-            padding: 0 0 0.5rem 0;
-            &:hover {
-                color: $dark-gray-color;
-            }
-        }
-        .items-list {
-            text-align: left;
-
-            .list-item {
-                border: none;
-                clear: both;
-                color: $text-color;
-                display: block;
-                height: 2em;
-                overflow: hidden;
-                padding-top: 0.5em;
-                text-shadow: 0 1px 0 $text-shadow-color;
-                word-wrap: break-word;
-            }
-
-            .available-chatroom,
-            .open-headline,
-            .open-chatroom {
-                &:hover {
-                    background-color: lighten($controlbox-head-color, 45%);
-                    .remove-bookmark,
-                    .add-bookmark,
-                    .close-room,
-                    .room-info {
-                        display: block !important;
-                    }
-                }
-                &.open {
-                    background-color: $controlbox-head-color;
-                    a {
-                        color: white !important;
-                    }
-                }
-                &.unread-msgs {
-                    .msgs-indicator {
-                        border-radius: 10%;
-                        opacity: 1;
-                    }
-                    .available-room,
-                    .open-room {
-                        width: 100%;
-                        font-weight: bold;
-                    }
-                }
-                a {
-                    &:hover {
-                        color: $dark-link-color;
-                    }
-                    &.remove-bookmark,
-                    &.add-bookmark,
-                    &.close-room,
-                    &.room-info {
-                        display: none;
-                        &:before {
-                            font-size: 15px;
-                        }
-                    }
-                    &.open-room {
-                        width: 68%;
-                        float: left;
-                        overflow: hidden;
-                        text-overflow: ellipsis;
-                        white-space: nowrap;
-                        padding-right: 0.5em;
-                    }
-                    &.available-room {
-                        width: 85%;
-                    }
-                }
-                .add-bookmark,
-                .remove-bookmark,
-                .close-room,
-                .room-info {
-                    &.button-on {
-                        color: $link-color;
-                        &:hover {
-                            color: $dark-link-color;
-                        }
-                    }
-                    color: $subdued-color;
-                }
-            }
-        }
-    }
-}

+ 0 - 8
sass/_roster.scss

@@ -94,14 +94,6 @@
                         padding: 0 0.2em 0 0;
                     }
                 }
-                a {
-                    &:hover {
-                        color: $dark-link-color;
-                    }
-                    .fa:hover {
-                        color: white;
-                    }
-                }
 
                 .open-chat {
                     margin: 0;

+ 1 - 1
sass/converse.scss

@@ -43,7 +43,7 @@
 @import "profile";
 @import "chatbox";
 @import "controlbox";
-@import "roomslist";
+@import "lists";
 @import "roster";
 @import "chatrooms";
 @import "headline";

+ 1 - 1
src/converse-chatboxes.js

@@ -417,7 +417,7 @@
                     if (type === 'error') {
                         const error = message.querySelector('error');
                         return _.propertyOf(error.querySelector('text'))('textContent') ||
-                            __('Sorry, an error occured:') + ' ' + error.innerHTML;
+                            __('Sorry, an error occurred:') + ' ' + error.innerHTML;
                     } else {
                         return _.propertyOf(message.querySelector('body'))('textContent');
                     }

+ 2 - 2
src/converse-core.js

@@ -740,8 +740,8 @@
             this.connection.addHandler((iq) => {
                 if (iq.querySelectorAll('error').length > 0) {
                     _converse.log(
-                        'An error occured while trying to enable message carbons.',
-                        Strophe.LogLevel.ERROR);
+                        'An error occurred while trying to enable message carbons.',
+                        Strophe.LogLevel.WARN);
                 } else {
                     this.session.save({'carbons_enabled': true});
                     _converse.log('Message carbons have been enabled.');

+ 21 - 1
src/converse-roster.js

@@ -232,10 +232,20 @@
                         'user_id': Strophe.getNodeFromJid(jid)
                     }, attributes));
 
+                    this.setChatBox();
+
                     this.presence.on('change:show', () => _converse.emit('contactPresenceChanged', this));
                     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.vcard.get('fullname') || this.get('jid');
                 },
@@ -450,7 +460,7 @@
                      *    (String) name - The name of that user
                      *    (Array of Strings) groups - Any roster groups the user might belong to
                      *    (Function) callback - A function to call once the IQ is returned
-                     *    (Function) errback - A function to call if an error occured
+                     *    (Function) errback - A function to call if an error occurred
                      */
                     name = _.isEmpty(name)? jid: name;
                     const iq = $iq({type: 'set'})
@@ -798,6 +808,16 @@
 
             /********** Event Handlers *************/
 
+            function updateUnreadCounter (chatbox) {
+                const contact = _converse.roster.findWhere({'jid': chatbox.get('jid')});
+                if (!_.isUndefined(contact)) {
+                    contact.save({'num_unread': chatbox.get('num_unread')});
+                }
+            }
+            _converse.api.listen.on('chatBoxesInitialized', () => {
+                _converse.chatboxes.on('change:num_unread', updateUnreadCounter)
+            });
+
             _converse.api.listen.on('beforeTearDown', _converse.unregisterPresenceHandler());
 
             _converse.api.listen.on('afterTearDown', () => {

+ 49 - 24
src/converse-rosterview.js

@@ -358,7 +358,7 @@
 
             _converse.RosterContactView = Backbone.NativeView.extend({
                 tagName: 'li',
-                className: 'd-flex hidden controlbox-padded',
+                className: 'list-item d-flex hidden controlbox-padded',
 
                 events: {
                     "click .accept-xmpp-request": "acceptRequest",
@@ -369,10 +369,11 @@
 
                 initialize () {
                     this.model.on("change", this.render, this);
+                    this.model.on("highlight", this.highlight, this);
                     this.model.on("destroy", this.remove, this);
                     this.model.on("open", this.openChat, this);
                     this.model.on("remove", this.remove, this);
-                    
+
                     this.model.presence.on("change:show", this.render, this);
                     this.model.vcard.on('change:fullname', this.render, this);
                 },
@@ -383,11 +384,10 @@
                         u.hideElement(this.el);
                         return this;
                     }
-                    const item = this.model,
-                        ask = item.get('ask'),
-                        show = item.presence.get('show'),
-                        requesting  = item.get('requesting'),
-                        subscription = item.get('subscription');
+                    const ask = this.model.get('ask'),
+                        show = this.model.presence.get('show'),
+                        requesting  = this.model.get('requesting'),
+                        subscription = this.model.get('subscription');
 
                     const classes_to_remove = [
                         'current-xmpp-contact',
@@ -403,6 +403,18 @@
                         });
                     this.el.classList.add(show);
                     this.el.setAttribute('data-status', show);
+                    this.highlight();
+
+                    if (_converse.isSingleton()) {
+                        const chatbox = _converse.chatboxes.get(this.model.get('jid'));
+                        if (chatbox) {
+                            if (chatbox.get('hidden')) {
+                                this.el.classList.remove('open');
+                            } else {
+                                this.el.classList.add('open');
+                            }
+                        }
+                    }
 
                     if ((ask === 'subscribe') || (subscription === 'from')) {
                         /* ask === 'subscribe'
@@ -416,20 +428,20 @@
                          *
                          *  So in both cases the user is a "pending" contact.
                          */
-                        const display_name = item.getDisplayName();
+                        const display_name = this.model.getDisplayName();
                         this.el.classList.add('pending-xmpp-contact');
                         this.el.innerHTML = tpl_pending_contact(
-                            _.extend(item.toJSON(), {
+                            _.extend(this.model.toJSON(), {
                                 'display_name': display_name,
                                 'desc_remove': __('Click to remove %1$s as a contact', display_name),
                                 'allow_chat_pending_contacts': _converse.allow_chat_pending_contacts
                             })
                         );
                     } else if (requesting === true) {
-                        const display_name = item.getDisplayName();
+                        const display_name = this.model.getDisplayName();
                         this.el.classList.add('requesting-xmpp-contact');
                         this.el.innerHTML = tpl_requesting_contact(
-                            _.extend(item.toJSON(), {
+                            _.extend(this.model.toJSON(), {
                                 'display_name': display_name,
                                 'desc_accept': __("Click to accept the contact request from %1$s", display_name),
                                 'desc_decline': __("Click to decline the contact request from %1$s", display_name),
@@ -440,11 +452,26 @@
                         this.el.classList.add('current-xmpp-contact');
                         this.el.classList.remove(_.without(['both', 'to'], subscription)[0]);
                         this.el.classList.add(subscription);
-                        this.renderRosterItem(item);
+                        this.renderRosterItem(this.model);
                     }
                     return this;
                 },
 
+                highlight () {
+                    /* If appropriate, highlight the contact (by adding the 'open' class).
+                     */
+                    if (_converse.isSingleton()) {
+                        const chatbox = _converse.chatboxes.get(this.model.get('jid'));
+                        if (chatbox) {
+                            if (chatbox.get('hidden')) {
+                                this.el.classList.remove('open');
+                            } else {
+                                this.el.classList.add('open');
+                            }
+                        }
+                    }
+                },
+
                 renderRosterItem (item) {
                     let status_icon = 'fa-times-circle';
                     const show = item.presence.get('show') || 'offline';
@@ -880,15 +907,14 @@
                 },
 
                 updateChatBox (contact) {
-                    const chatbox = _converse.chatboxes.get(contact.get('jid')),
-                        changes = {};
-                    if (!chatbox) {
+                    if (!this.model.chatbox) {
                         return this;
                     }
+                    const changes = {};
                     if (_.has(contact.changed, 'status')) {
                         changes.status = contact.get('status');
                     }
-                    chatbox.save(changes);
+                    this.model.chatbox.save(changes);
                     return this;
                 },
 
@@ -937,15 +963,14 @@
 
 
             /* -------- Event Handlers ----------- */
-
-            function updateUnreadCounter (chatbox) {
-                const contact = _.head(_converse.roster.where({'jid': chatbox.get('jid')}));
-                if (!_.isUndefined(contact)) {
-                    contact.save({'num_unread': chatbox.get('num_unread')});
-                }
-            }
             _converse.api.listen.on('chatBoxesInitialized', () => {
-                _converse.chatboxes.on('change:num_unread', updateUnreadCounter)
+
+                _converse.chatboxes.on('change:hidden', (chatbox) => {
+                    const contact = _converse.roster.findWhere({'jid': chatbox.get('jid')});
+                    if (!_.isUndefined(contact)) {
+                        contact.trigger('highlight', contact);
+                    }
+                });
             });
 
             function initRoster () {

+ 1 - 1
src/templates/group_header.html

@@ -1,4 +1,4 @@
 <a href="#" class="group-toggle controlbox-padded" title="{{{o.desc_group_toggle}}}">
     <span class="fa {[ if (o.toggle_state === o._converse.OPENED) { ]} fa-caret-down {[ } else { ]} fa-caret-right {[ } ]}">
     </span> {{{o.label_group}}}</a>
-<ul class="roster-group-contacts {[ if (o.toggle_state === o._converse.CLOSED) { ]} collapsed {[ } ]}"></ul>
+<ul class="items-list roster-group-contacts {[ if (o.toggle_state === o._converse.CLOSED) { ]} collapsed {[ } ]}"></ul>

+ 1 - 1
src/templates/roster_item.html

@@ -1,4 +1,4 @@
-<a class="open-chat w-100 {[ if (o.num_unread) { ]} unread-msgs {[ } ]}"
+<a class="cbox-list-item open-chat w-100 {[ if (o.num_unread) { ]} unread-msgs {[ } ]}"
    title="{{{o.desc_chat}}}" href="#">
     <span class="fa {{{o.status_icon}}}" title="{{{o.desc_status}}}"></span>
     {[ if (o.num_unread) { ]}