Browse Source

Regenerated dist files

JC Brand 7 years ago
parent
commit
08ab2e5416
3 changed files with 322 additions and 159 deletions
  1. 71 51
      dist/converse-muc-embedded.js
  2. 194 74
      dist/converse-no-dependencies.js
  3. 57 34
      dist/converse.js

+ 71 - 51
dist/converse-muc-embedded.js

@@ -39941,6 +39941,7 @@ return Backbone.BrowserStorage;
       locales: ['af', 'ar', 'bg', 'ca', 'de', 'es', 'en', 'fr', 'he', 'hu', 'id', 'it', 'ja', 'nb', 'nl', 'pl', 'pt_BR', 'ru', 'tr', 'uk', 'zh_CN', 'zh_TW'],
       locales: ['af', 'ar', 'bg', 'ca', 'de', 'es', 'en', 'fr', 'he', 'hu', 'id', 'it', 'ja', 'nb', 'nl', 'pl', 'pt_BR', 'ru', 'tr', 'uk', 'zh_CN', 'zh_TW'],
       message_carbons: true,
       message_carbons: true,
       message_storage: 'session',
       message_storage: 'session',
+      nickname: undefined,
       password: undefined,
       password: undefined,
       prebind_url: null,
       prebind_url: null,
       priority: 0,
       priority: 0,
@@ -40411,9 +40412,9 @@ return Backbone.BrowserStorage;
       }
       }
 
 
       var carbons_iq = new Strophe.Builder('iq', {
       var carbons_iq = new Strophe.Builder('iq', {
-        from: this.connection.jid,
-        id: 'enablecarbons',
-        type: 'set'
+        'from': this.connection.jid,
+        'id': 'enablecarbons',
+        'type': 'set'
       }).c('enable', {
       }).c('enable', {
         xmlns: Strophe.NS.CARBONS
         xmlns: Strophe.NS.CARBONS
       });
       });
@@ -40576,8 +40577,6 @@ return Backbone.BrowserStorage;
         'status': ''
         'status': ''
       },
       },
       initialize: function initialize(attributes) {
       initialize: function initialize(attributes) {
-        var _this3 = this;
-
         var jid = attributes.jid,
         var jid = attributes.jid,
             bare_jid = Strophe.getBareJidFromJid(jid).toLowerCase(),
             bare_jid = Strophe.getBareJidFromJid(jid).toLowerCase(),
             resource = Strophe.getResourceFromJid(jid);
             resource = Strophe.getResourceFromJid(jid);
@@ -40590,9 +40589,6 @@ return Backbone.BrowserStorage;
           'resources': {},
           'resources': {},
           'user_id': Strophe.getNodeFromJid(jid)
           'user_id': Strophe.getNodeFromJid(jid)
         }, attributes));
         }, attributes));
-        this.on('destroy', function () {
-          _this3.removeFromRoster();
-        });
         this.on('change:chat_status', function (item) {
         this.on('change:chat_status', function (item) {
           _converse.emit('contactStatusChanged', item.attributes);
           _converse.emit('contactStatusChanged', item.attributes);
         });
         });
@@ -40604,8 +40600,6 @@ return Backbone.BrowserStorage;
          *    (String) message - An optional message to explain the
          *    (String) message - An optional message to explain the
          *      reason for the subscription request.
          *      reason for the subscription request.
          */
          */
-        this.save('ask', "subscribe"); // ask === 'subscribe' Means we have ask to subscribe to them.
-
         var pres = $pres({
         var pres = $pres({
           to: this.get('jid'),
           to: this.get('jid'),
           type: "subscribe"
           type: "subscribe"
@@ -40615,9 +40609,9 @@ return Backbone.BrowserStorage;
           pres.c("status").t(message).up();
           pres.c("status").t(message).up();
         }
         }
 
 
-        var nick = _converse.xmppstatus.get('fullname');
+        var nick = _converse.xmppstatus.get('nickname') || _converse.xmppstatus.get('fullname');
 
 
-        if (nick && nick !== "") {
+        if (nick) {
           pres.c('nick', {
           pres.c('nick', {
             'xmlns': Strophe.NS.NICK
             'xmlns': Strophe.NS.NICK
           }).t(nick).up();
           }).t(nick).up();
@@ -40625,6 +40619,8 @@ return Backbone.BrowserStorage;
 
 
         _converse.connection.send(pres);
         _converse.connection.send(pres);
 
 
+        this.save('ask', "subscribe"); // ask === 'subscribe' Means we have asked to subscribe to them.
+
         return this;
         return this;
       },
       },
       ackSubscribe: function ackSubscribe() {
       ackSubscribe: function ackSubscribe() {
@@ -40652,7 +40648,8 @@ return Backbone.BrowserStorage;
           'to': this.get('jid')
           'to': this.get('jid')
         }));
         }));
 
 
-        this.destroy(); // Will cause removeFromRoster to be called.
+        this.removeFromRoster();
+        this.destroy();
       },
       },
       unauthorize: function unauthorize(message) {
       unauthorize: function unauthorize(message) {
         /* Unauthorize this contact's presence subscription
         /* Unauthorize this contact's presence subscription
@@ -40669,8 +40666,8 @@ return Backbone.BrowserStorage;
          *   (String) message - Optional message to send to the person being authorized
          *   (String) message - Optional message to send to the person being authorized
          */
          */
         var pres = $pres({
         var pres = $pres({
-          to: this.get('jid'),
-          type: "subscribed"
+          'to': this.get('jid'),
+          'type': "subscribed"
         });
         });
 
 
         if (message && message !== "") {
         if (message && message !== "") {
@@ -40816,7 +40813,7 @@ return Backbone.BrowserStorage;
         }, Strophe.NS.ROSTERX, 'message', null);
         }, Strophe.NS.ROSTERX, 'message', null);
       },
       },
       fetchRosterContacts: function fetchRosterContacts() {
       fetchRosterContacts: function fetchRosterContacts() {
-        var _this4 = this;
+        var _this3 = this;
 
 
         /* Fetches the roster contacts, first by trying the
         /* Fetches the roster contacts, first by trying the
          * sessionStorage cache, and if that's empty, then by querying
          * sessionStorage cache, and if that's empty, then by querying
@@ -40826,7 +40823,7 @@ return Backbone.BrowserStorage;
          * fetched.
          * fetched.
          */
          */
         return new Promise(function (resolve, reject) {
         return new Promise(function (resolve, reject) {
-          _this4.fetch({
+          _this3.fetch({
             'add': true,
             'add': true,
             'silent': true,
             'silent': true,
             success: function success(collection) {
             success: function success(collection) {
@@ -40846,7 +40843,7 @@ return Backbone.BrowserStorage;
       subscribeToSuggestedItems: function subscribeToSuggestedItems(msg) {
       subscribeToSuggestedItems: function subscribeToSuggestedItems(msg) {
         _.each(msg.querySelectorAll('item'), function (item) {
         _.each(msg.querySelectorAll('item'), function (item) {
           if (item.getAttribute('action') === 'add') {
           if (item.getAttribute('action') === 'add') {
-            _converse.roster.addAndSubscribe(item.getAttribute('jid'), null, _converse.xmppstatus.get('fullname'));
+            _converse.roster.addAndSubscribe(item.getAttribute('jid'), _converse.xmppstatus.get('nickname') || _converse.xmppstatus.get('fullname'));
           }
           }
         });
         });
 
 
@@ -40872,7 +40869,7 @@ return Backbone.BrowserStorage;
           }
           }
         };
         };
 
 
-        this.addContact(jid, name, groups, attributes).then(handler, handler);
+        this.addContactToRoster(jid, name, groups, attributes).then(handler, handler);
       },
       },
       sendContactAddIQ: function sendContactAddIQ(jid, name, groups, callback, errback) {
       sendContactAddIQ: function sendContactAddIQ(jid, name, groups, callback, errback) {
         /*  Send an IQ stanza to the XMPP server to add a new roster contact.
         /*  Send an IQ stanza to the XMPP server to add a new roster contact.
@@ -40900,8 +40897,8 @@ return Backbone.BrowserStorage;
 
 
         _converse.connection.sendIQ(iq, callback, errback);
         _converse.connection.sendIQ(iq, callback, errback);
       },
       },
-      addContact: function addContact(jid, name, groups, attributes) {
-        var _this5 = this;
+      addContactToRoster: function addContactToRoster(jid, name, groups, attributes) {
+        var _this4 = this;
 
 
         /* Adds a RosterContact instance to _converse.roster and
         /* Adds a RosterContact instance to _converse.roster and
          * registers the contact on the XMPP server.
          * registers the contact on the XMPP server.
@@ -40918,8 +40915,8 @@ return Backbone.BrowserStorage;
           groups = groups || [];
           groups = groups || [];
           name = _.isEmpty(name) ? jid : name;
           name = _.isEmpty(name) ? jid : name;
 
 
-          _this5.sendContactAddIQ(jid, name, groups, function () {
-            var contact = _this5.create(_.assignIn({
+          _this4.sendContactAddIQ(jid, name, groups, function () {
+            var contact = _this4.create(_.assignIn({
               ask: undefined,
               ask: undefined,
               fullname: name,
               fullname: name,
               groups: groups,
               groups: groups,
@@ -40940,7 +40937,7 @@ return Backbone.BrowserStorage;
           });
           });
         });
         });
       },
       },
-      subscribeBack: function subscribeBack(bare_jid) {
+      subscribeBack: function subscribeBack(bare_jid, presence) {
         var contact = this.get(bare_jid);
         var contact = this.get(bare_jid);
 
 
         if (contact instanceof _converse.RosterContact) {
         if (contact instanceof _converse.RosterContact) {
@@ -40953,7 +40950,9 @@ return Backbone.BrowserStorage;
             }
             }
           };
           };
 
 
-          this.addContact(bare_jid, '', [], {
+          var nickname = _.get(sizzle("nick[xmlns=\"".concat(Strophe.NS.NICK, "\"]"), presence).pop(), 'textContent', null);
+
+          this.addContactToRoster(bare_jid, nickname, [], {
             'subscription': 'from'
             'subscription': 'from'
           }).then(handler, handler);
           }).then(handler, handler);
         }
         }
@@ -41012,7 +41011,7 @@ return Backbone.BrowserStorage;
         return true;
         return true;
       },
       },
       fetchFromServer: function fetchFromServer() {
       fetchFromServer: function fetchFromServer() {
-        var _this6 = this;
+        var _this5 = this;
 
 
         /* Fetch the roster from the XMPP server */
         /* Fetch the roster from the XMPP server */
         return new Promise(function (resolve, reject) {
         return new Promise(function (resolve, reject) {
@@ -41023,7 +41022,7 @@ return Backbone.BrowserStorage;
             xmlns: Strophe.NS.ROSTER
             xmlns: Strophe.NS.ROSTER
           });
           });
 
 
-          var callback = _.flow(_this6.onReceivedFromServer.bind(_this6), resolve);
+          var callback = _.flow(_this5.onReceivedFromServer.bind(_this5), resolve);
 
 
           var errback = function errback(iq) {
           var errback = function errback(iq) {
             var errmsg = "Error while trying to fetch roster from the server";
             var errmsg = "Error while trying to fetch roster from the server";
@@ -41067,17 +41066,17 @@ return Backbone.BrowserStorage;
           }
           }
 
 
           this.create({
           this.create({
-            ask: ask,
-            fullname: item.getAttribute("name") || jid,
-            groups: groups,
-            jid: jid,
-            subscription: subscription
+            'ask': ask,
+            'fullname': item.getAttribute("name") || jid,
+            'groups': groups,
+            'jid': jid,
+            'subscription': subscription
           }, {
           }, {
             sort: false
             sort: false
           });
           });
         } else {
         } else {
           if (subscription === "remove") {
           if (subscription === "remove") {
-            return contact.destroy(); // will trigger removeFromRoster
+            return contact.destroy();
           } // We only find out about requesting contacts via the
           } // We only find out about requesting contacts via the
           // presence handler, so if we receive a contact
           // presence handler, so if we receive a contact
           // here, we know they aren't requesting anymore.
           // here, we know they aren't requesting anymore.
@@ -41085,10 +41084,10 @@ return Backbone.BrowserStorage;
 
 
 
 
           contact.save({
           contact.save({
-            subscription: subscription,
-            ask: ask,
-            requesting: null,
-            groups: groups
+            'subscription': subscription,
+            'ask': ask,
+            'requesting': null,
+            'groups': groups
           });
           });
         }
         }
       },
       },
@@ -41098,13 +41097,14 @@ return Backbone.BrowserStorage;
          * Note: this method gets completely overridden by converse-vcard.js
          * Note: this method gets completely overridden by converse-vcard.js
          */
          */
         var bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from')),
         var bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from')),
-            nick_el = presence.querySelector("nick[xmlns=\"".concat(Strophe.NS.NICK, "\"]"));
+            nickname = _.get(sizzle("nick[xmlns=\"".concat(Strophe.NS.NICK, "\"]"), presence).pop(), 'textContent', null);
+
         var user_data = {
         var user_data = {
-          jid: bare_jid,
-          subscription: 'none',
-          ask: null,
-          requesting: true,
-          fullname: nick_el && nick_el.textContent || bare_jid
+          'jid': bare_jid,
+          'subscription': 'none',
+          'ask': null,
+          'requesting': true,
+          'fullname': nickname
         };
         };
         this.create(user_data);
         this.create(user_data);
 
 
@@ -41121,7 +41121,7 @@ return Backbone.BrowserStorage;
 
 
         if (_converse.auto_subscribe) {
         if (_converse.auto_subscribe) {
           if (!contact || contact.get('subscription') !== 'to') {
           if (!contact || contact.get('subscription') !== 'to') {
-            this.subscribeBack(bare_jid);
+            this.subscribeBack(bare_jid, presence);
           } else {
           } else {
             contact.authorize();
             contact.authorize();
           }
           }
@@ -41167,6 +41167,24 @@ return Backbone.BrowserStorage;
             }
             }
           }
           }
 
 
+          if (_converse.jid === jid && presence_type === 'unavailable') {
+            // XXX: We've received an "unavailable" presence from our
+            // own resource. Apparently this happens due to a
+            // Prosody bug, whereby we send an IQ stanza to remove
+            // a roster contact, and Prosody then sends
+            // "unavailable" globally, instead of directed to the
+            // particular user that's removed.
+            //
+            // Here is the bug report: https://prosody.im/issues/1121
+            //
+            // I'm not sure whether this might legitimately happen
+            // in other cases.
+            //
+            // As a workaround for now we simply send our presence again,
+            // otherwise we're treated as offline.
+            _converse.xmppstatus.sendPresence();
+          }
+
           return;
           return;
         } else if (sizzle("query[xmlns=\"".concat(Strophe.NS.MUC, "\"]"), presence).length) {
         } else if (sizzle("query[xmlns=\"".concat(Strophe.NS.MUC, "\"]"), presence).length) {
           return; // Ignore MUC
           return; // Ignore MUC
@@ -41207,7 +41225,7 @@ return Backbone.BrowserStorage;
     this.RosterGroups = Backbone.Collection.extend({
     this.RosterGroups = Backbone.Collection.extend({
       model: _converse.RosterGroup,
       model: _converse.RosterGroup,
       fetchRosterGroups: function fetchRosterGroups() {
       fetchRosterGroups: function fetchRosterGroups() {
-        var _this7 = this;
+        var _this6 = this;
 
 
         /* Fetches all the roster groups from sessionStorage.
         /* Fetches all the roster groups from sessionStorage.
          *
          *
@@ -41215,7 +41233,7 @@ return Backbone.BrowserStorage;
          * returned.
          * returned.
          */
          */
         return new Promise(function (resolve, reject) {
         return new Promise(function (resolve, reject) {
-          _this7.fetch({
+          _this6.fetch({
             silent: true,
             silent: true,
             // We need to first have all groups before
             // We need to first have all groups before
             // we can start positioning them, so we set
             // we can start positioning them, so we set
@@ -41242,23 +41260,24 @@ return Backbone.BrowserStorage;
         return {
         return {
           "status": _converse.default_state,
           "status": _converse.default_state,
           "jid": _converse.bare_jid,
           "jid": _converse.bare_jid,
+          "nickname": _converse.nickname,
           "vcard_updated": null
           "vcard_updated": null
         };
         };
       },
       },
       initialize: function initialize() {
       initialize: function initialize() {
-        var _this8 = this;
+        var _this7 = this;
 
 
         this.on('change:status', function (item) {
         this.on('change:status', function (item) {
-          var status = _this8.get('status');
+          var status = _this7.get('status');
 
 
-          _this8.sendPresence(status);
+          _this7.sendPresence(status);
 
 
           _converse.emit('statusChanged', status);
           _converse.emit('statusChanged', status);
         });
         });
         this.on('change:status_message', function () {
         this.on('change:status_message', function () {
-          var status_message = _this8.get('status_message');
+          var status_message = _this7.get('status_message');
 
 
-          _this8.sendPresence(_this8.get('status'), status_message);
+          _this7.sendPresence(_this7.get('status'), status_message);
 
 
           _converse.emit('statusMessageChanged', status_message);
           _converse.emit('statusMessageChanged', status_message);
         });
         });
@@ -51011,6 +51030,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
             'affiliation': null,
             'affiliation': null,
             'connection_status': converse.ROOMSTATUS.DISCONNECTED,
             'connection_status': converse.ROOMSTATUS.DISCONNECTED,
             'name': '',
             'name': '',
+            'nick': _converse.xmppstatus.get('nickname'),
             'description': '',
             'description': '',
             'features_fetched': false,
             'features_fetched': false,
             'roomconfig': {},
             'roomconfig': {},

+ 194 - 74
dist/converse-no-dependencies.js

@@ -7217,6 +7217,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
       locales: ['af', 'bg', 'ca', 'de', 'es', 'en', 'fr', 'he', 'hu', 'id', 'it', 'ja', 'nb', 'nl', 'pl', 'pt_BR', 'ru', 'tr', 'uk', 'zh_CN', 'zh_TW'],
       locales: ['af', 'bg', 'ca', 'de', 'es', 'en', 'fr', 'he', 'hu', 'id', 'it', 'ja', 'nb', 'nl', 'pl', 'pt_BR', 'ru', 'tr', 'uk', 'zh_CN', 'zh_TW'],
       message_carbons: true,
       message_carbons: true,
       message_storage: 'session',
       message_storage: 'session',
+      nickname: undefined,
       password: undefined,
       password: undefined,
       prebind_url: null,
       prebind_url: null,
       priority: 0,
       priority: 0,
@@ -7687,9 +7688,9 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
       }
       }
 
 
       var carbons_iq = new Strophe.Builder('iq', {
       var carbons_iq = new Strophe.Builder('iq', {
-        from: this.connection.jid,
-        id: 'enablecarbons',
-        type: 'set'
+        'from': this.connection.jid,
+        'id': 'enablecarbons',
+        'type': 'set'
       }).c('enable', {
       }).c('enable', {
         xmlns: Strophe.NS.CARBONS
         xmlns: Strophe.NS.CARBONS
       });
       });
@@ -7852,8 +7853,6 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
         'status': ''
         'status': ''
       },
       },
       initialize: function initialize(attributes) {
       initialize: function initialize(attributes) {
-        var _this3 = this;
-
         var jid = attributes.jid,
         var jid = attributes.jid,
             bare_jid = Strophe.getBareJidFromJid(jid).toLowerCase(),
             bare_jid = Strophe.getBareJidFromJid(jid).toLowerCase(),
             resource = Strophe.getResourceFromJid(jid);
             resource = Strophe.getResourceFromJid(jid);
@@ -7866,9 +7865,6 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
           'resources': {},
           'resources': {},
           'user_id': Strophe.getNodeFromJid(jid)
           'user_id': Strophe.getNodeFromJid(jid)
         }, attributes));
         }, attributes));
-        this.on('destroy', function () {
-          _this3.removeFromRoster();
-        });
         this.on('change:chat_status', function (item) {
         this.on('change:chat_status', function (item) {
           _converse.emit('contactStatusChanged', item.attributes);
           _converse.emit('contactStatusChanged', item.attributes);
         });
         });
@@ -7880,8 +7876,6 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
          *    (String) message - An optional message to explain the
          *    (String) message - An optional message to explain the
          *      reason for the subscription request.
          *      reason for the subscription request.
          */
          */
-        this.save('ask', "subscribe"); // ask === 'subscribe' Means we have ask to subscribe to them.
-
         var pres = $pres({
         var pres = $pres({
           to: this.get('jid'),
           to: this.get('jid'),
           type: "subscribe"
           type: "subscribe"
@@ -7891,9 +7885,9 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
           pres.c("status").t(message).up();
           pres.c("status").t(message).up();
         }
         }
 
 
-        var nick = _converse.xmppstatus.get('fullname');
+        var nick = _converse.xmppstatus.get('nickname') || _converse.xmppstatus.get('fullname');
 
 
-        if (nick && nick !== "") {
+        if (nick) {
           pres.c('nick', {
           pres.c('nick', {
             'xmlns': Strophe.NS.NICK
             'xmlns': Strophe.NS.NICK
           }).t(nick).up();
           }).t(nick).up();
@@ -7901,6 +7895,8 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
 
 
         _converse.connection.send(pres);
         _converse.connection.send(pres);
 
 
+        this.save('ask', "subscribe"); // ask === 'subscribe' Means we have asked to subscribe to them.
+
         return this;
         return this;
       },
       },
       ackSubscribe: function ackSubscribe() {
       ackSubscribe: function ackSubscribe() {
@@ -7928,7 +7924,8 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
           'to': this.get('jid')
           'to': this.get('jid')
         }));
         }));
 
 
-        this.destroy(); // Will cause removeFromRoster to be called.
+        this.removeFromRoster();
+        this.destroy();
       },
       },
       unauthorize: function unauthorize(message) {
       unauthorize: function unauthorize(message) {
         /* Unauthorize this contact's presence subscription
         /* Unauthorize this contact's presence subscription
@@ -7945,8 +7942,8 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
          *   (String) message - Optional message to send to the person being authorized
          *   (String) message - Optional message to send to the person being authorized
          */
          */
         var pres = $pres({
         var pres = $pres({
-          to: this.get('jid'),
-          type: "subscribed"
+          'to': this.get('jid'),
+          'type': "subscribed"
         });
         });
 
 
         if (message && message !== "") {
         if (message && message !== "") {
@@ -8092,7 +8089,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
         }, Strophe.NS.ROSTERX, 'message', null);
         }, Strophe.NS.ROSTERX, 'message', null);
       },
       },
       fetchRosterContacts: function fetchRosterContacts() {
       fetchRosterContacts: function fetchRosterContacts() {
-        var _this4 = this;
+        var _this3 = this;
 
 
         /* Fetches the roster contacts, first by trying the
         /* Fetches the roster contacts, first by trying the
          * sessionStorage cache, and if that's empty, then by querying
          * sessionStorage cache, and if that's empty, then by querying
@@ -8102,7 +8099,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
          * fetched.
          * fetched.
          */
          */
         return new Promise(function (resolve, reject) {
         return new Promise(function (resolve, reject) {
-          _this4.fetch({
+          _this3.fetch({
             'add': true,
             'add': true,
             'silent': true,
             'silent': true,
             success: function success(collection) {
             success: function success(collection) {
@@ -8122,7 +8119,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
       subscribeToSuggestedItems: function subscribeToSuggestedItems(msg) {
       subscribeToSuggestedItems: function subscribeToSuggestedItems(msg) {
         _.each(msg.querySelectorAll('item'), function (item) {
         _.each(msg.querySelectorAll('item'), function (item) {
           if (item.getAttribute('action') === 'add') {
           if (item.getAttribute('action') === 'add') {
-            _converse.roster.addAndSubscribe(item.getAttribute('jid'), null, _converse.xmppstatus.get('fullname'));
+            _converse.roster.addAndSubscribe(item.getAttribute('jid'), _converse.xmppstatus.get('nickname') || _converse.xmppstatus.get('fullname'));
           }
           }
         });
         });
 
 
@@ -8148,7 +8145,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
           }
           }
         };
         };
 
 
-        this.addContact(jid, name, groups, attributes).then(handler, handler);
+        this.addContactToRoster(jid, name, groups, attributes).then(handler, handler);
       },
       },
       sendContactAddIQ: function sendContactAddIQ(jid, name, groups, callback, errback) {
       sendContactAddIQ: function sendContactAddIQ(jid, name, groups, callback, errback) {
         /*  Send an IQ stanza to the XMPP server to add a new roster contact.
         /*  Send an IQ stanza to the XMPP server to add a new roster contact.
@@ -8176,8 +8173,8 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
 
 
         _converse.connection.sendIQ(iq, callback, errback);
         _converse.connection.sendIQ(iq, callback, errback);
       },
       },
-      addContact: function addContact(jid, name, groups, attributes) {
-        var _this5 = this;
+      addContactToRoster: function addContactToRoster(jid, name, groups, attributes) {
+        var _this4 = this;
 
 
         /* Adds a RosterContact instance to _converse.roster and
         /* Adds a RosterContact instance to _converse.roster and
          * registers the contact on the XMPP server.
          * registers the contact on the XMPP server.
@@ -8194,8 +8191,8 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
           groups = groups || [];
           groups = groups || [];
           name = _.isEmpty(name) ? jid : name;
           name = _.isEmpty(name) ? jid : name;
 
 
-          _this5.sendContactAddIQ(jid, name, groups, function () {
-            var contact = _this5.create(_.assignIn({
+          _this4.sendContactAddIQ(jid, name, groups, function () {
+            var contact = _this4.create(_.assignIn({
               ask: undefined,
               ask: undefined,
               fullname: name,
               fullname: name,
               groups: groups,
               groups: groups,
@@ -8216,7 +8213,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
           });
           });
         });
         });
       },
       },
-      subscribeBack: function subscribeBack(bare_jid) {
+      subscribeBack: function subscribeBack(bare_jid, presence) {
         var contact = this.get(bare_jid);
         var contact = this.get(bare_jid);
 
 
         if (contact instanceof _converse.RosterContact) {
         if (contact instanceof _converse.RosterContact) {
@@ -8229,7 +8226,9 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
             }
             }
           };
           };
 
 
-          this.addContact(bare_jid, '', [], {
+          var nickname = _.get(sizzle("nick[xmlns=\"".concat(Strophe.NS.NICK, "\"]"), presence).pop(), 'textContent', null);
+
+          this.addContactToRoster(bare_jid, nickname, [], {
             'subscription': 'from'
             'subscription': 'from'
           }).then(handler, handler);
           }).then(handler, handler);
         }
         }
@@ -8288,7 +8287,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
         return true;
         return true;
       },
       },
       fetchFromServer: function fetchFromServer() {
       fetchFromServer: function fetchFromServer() {
-        var _this6 = this;
+        var _this5 = this;
 
 
         /* Fetch the roster from the XMPP server */
         /* Fetch the roster from the XMPP server */
         return new Promise(function (resolve, reject) {
         return new Promise(function (resolve, reject) {
@@ -8299,7 +8298,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
             xmlns: Strophe.NS.ROSTER
             xmlns: Strophe.NS.ROSTER
           });
           });
 
 
-          var callback = _.flow(_this6.onReceivedFromServer.bind(_this6), resolve);
+          var callback = _.flow(_this5.onReceivedFromServer.bind(_this5), resolve);
 
 
           var errback = function errback(iq) {
           var errback = function errback(iq) {
             var errmsg = "Error while trying to fetch roster from the server";
             var errmsg = "Error while trying to fetch roster from the server";
@@ -8343,17 +8342,17 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
           }
           }
 
 
           this.create({
           this.create({
-            ask: ask,
-            fullname: item.getAttribute("name") || jid,
-            groups: groups,
-            jid: jid,
-            subscription: subscription
+            'ask': ask,
+            'fullname': item.getAttribute("name") || jid,
+            'groups': groups,
+            'jid': jid,
+            'subscription': subscription
           }, {
           }, {
             sort: false
             sort: false
           });
           });
         } else {
         } else {
           if (subscription === "remove") {
           if (subscription === "remove") {
-            return contact.destroy(); // will trigger removeFromRoster
+            return contact.destroy();
           } // We only find out about requesting contacts via the
           } // We only find out about requesting contacts via the
           // presence handler, so if we receive a contact
           // presence handler, so if we receive a contact
           // here, we know they aren't requesting anymore.
           // here, we know they aren't requesting anymore.
@@ -8361,10 +8360,10 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
 
 
 
 
           contact.save({
           contact.save({
-            subscription: subscription,
-            ask: ask,
-            requesting: null,
-            groups: groups
+            'subscription': subscription,
+            'ask': ask,
+            'requesting': null,
+            'groups': groups
           });
           });
         }
         }
       },
       },
@@ -8374,13 +8373,14 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
          * Note: this method gets completely overridden by converse-vcard.js
          * Note: this method gets completely overridden by converse-vcard.js
          */
          */
         var bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from')),
         var bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from')),
-            nick_el = presence.querySelector("nick[xmlns=\"".concat(Strophe.NS.NICK, "\"]"));
+            nickname = _.get(sizzle("nick[xmlns=\"".concat(Strophe.NS.NICK, "\"]"), presence).pop(), 'textContent', null);
+
         var user_data = {
         var user_data = {
-          jid: bare_jid,
-          subscription: 'none',
-          ask: null,
-          requesting: true,
-          fullname: nick_el && nick_el.textContent || bare_jid
+          'jid': bare_jid,
+          'subscription': 'none',
+          'ask': null,
+          'requesting': true,
+          'fullname': nickname
         };
         };
         this.create(user_data);
         this.create(user_data);
 
 
@@ -8397,7 +8397,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
 
 
         if (_converse.auto_subscribe) {
         if (_converse.auto_subscribe) {
           if (!contact || contact.get('subscription') !== 'to') {
           if (!contact || contact.get('subscription') !== 'to') {
-            this.subscribeBack(bare_jid);
+            this.subscribeBack(bare_jid, presence);
           } else {
           } else {
             contact.authorize();
             contact.authorize();
           }
           }
@@ -8443,6 +8443,24 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
             }
             }
           }
           }
 
 
+          if (_converse.jid === jid && presence_type === 'unavailable') {
+            // XXX: We've received an "unavailable" presence from our
+            // own resource. Apparently this happens due to a
+            // Prosody bug, whereby we send an IQ stanza to remove
+            // a roster contact, and Prosody then sends
+            // "unavailable" globally, instead of directed to the
+            // particular user that's removed.
+            //
+            // Here is the bug report: https://prosody.im/issues/1121
+            //
+            // I'm not sure whether this might legitimately happen
+            // in other cases.
+            //
+            // As a workaround for now we simply send our presence again,
+            // otherwise we're treated as offline.
+            _converse.xmppstatus.sendPresence();
+          }
+
           return;
           return;
         } else if (sizzle("query[xmlns=\"".concat(Strophe.NS.MUC, "\"]"), presence).length) {
         } else if (sizzle("query[xmlns=\"".concat(Strophe.NS.MUC, "\"]"), presence).length) {
           return; // Ignore MUC
           return; // Ignore MUC
@@ -8483,7 +8501,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
     this.RosterGroups = Backbone.Collection.extend({
     this.RosterGroups = Backbone.Collection.extend({
       model: _converse.RosterGroup,
       model: _converse.RosterGroup,
       fetchRosterGroups: function fetchRosterGroups() {
       fetchRosterGroups: function fetchRosterGroups() {
-        var _this7 = this;
+        var _this6 = this;
 
 
         /* Fetches all the roster groups from sessionStorage.
         /* Fetches all the roster groups from sessionStorage.
          *
          *
@@ -8491,7 +8509,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
          * returned.
          * returned.
          */
          */
         return new Promise(function (resolve, reject) {
         return new Promise(function (resolve, reject) {
-          _this7.fetch({
+          _this6.fetch({
             silent: true,
             silent: true,
             // We need to first have all groups before
             // We need to first have all groups before
             // we can start positioning them, so we set
             // we can start positioning them, so we set
@@ -8518,23 +8536,24 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
         return {
         return {
           "status": _converse.default_state,
           "status": _converse.default_state,
           "jid": _converse.bare_jid,
           "jid": _converse.bare_jid,
+          "nickname": _converse.nickname,
           "vcard_updated": null
           "vcard_updated": null
         };
         };
       },
       },
       initialize: function initialize() {
       initialize: function initialize() {
-        var _this8 = this;
+        var _this7 = this;
 
 
         this.on('change:status', function (item) {
         this.on('change:status', function (item) {
-          var status = _this8.get('status');
+          var status = _this7.get('status');
 
 
-          _this8.sendPresence(status);
+          _this7.sendPresence(status);
 
 
           _converse.emit('statusChanged', status);
           _converse.emit('statusChanged', status);
         });
         });
         this.on('change:status_message', function () {
         this.on('change:status_message', function () {
-          var status_message = _this8.get('status_message');
+          var status_message = _this7.get('status_message');
 
 
-          _this8.sendPresence(_this8.get('status'), status_message);
+          _this7.sendPresence(_this7.get('status'), status_message);
 
 
           _converse.emit('statusMessageChanged', status_message);
           _converse.emit('statusMessageChanged', status_message);
         });
         });
@@ -15336,7 +15355,11 @@ var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
 function print() { __p += __j.call(arguments, '') }
 function print() { __p += __j.call(arguments, '') }
 __p += '<!-- Add contact Modal -->\n<div class="modal fade" id="add-contact-modal" tabindex="-1" role="dialog" aria-labelledby="addContactModalLabel" aria-hidden="true">\n    <div class="modal-dialog" role="document">\n        <div class="modal-content">\n            <div class="modal-header">\n                <h5 class="modal-title" id="addContactModalLabel">' +
 __p += '<!-- Add contact Modal -->\n<div class="modal fade" id="add-contact-modal" tabindex="-1" role="dialog" aria-labelledby="addContactModalLabel" aria-hidden="true">\n    <div class="modal-dialog" role="document">\n        <div class="modal-content">\n            <div class="modal-header">\n                <h5 class="modal-title" id="addContactModalLabel">' +
 __e(o.heading_new_contact) +
 __e(o.heading_new_contact) +
-'</h5>\n                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>\n            </div>\n            <form class="converse-form add-xmpp-contact">\n                <div class="modal-body">\n                    <div class="form-group">\n                        <label for="jid">' +
+'</h5>\n                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>\n            </div>\n            <form class="converse-form add-xmpp-contact">\n                <div class="modal-body">\n                    <div class="form-group ';
+ if (o._converse.xhr_user_search_url) { ;
+__p += ' hidden ';
+ } ;
+__p += '">\n                        <label class="clearfix" for="jid">' +
 __e(o.label_xmpp_address) +
 __e(o.label_xmpp_address) +
 ':</label>\n                        <input type="text" name="jid" required="required" value="' +
 ':</label>\n                        <input type="text" name="jid" required="required" value="' +
 __e(o.jid) +
 __e(o.jid) +
@@ -15346,13 +15369,23 @@ __p += ' is-invalid ';
  } ;
  } ;
 __p += '"\n                               placeholder="' +
 __p += '"\n                               placeholder="' +
 __e(o.contact_placeholder) +
 __e(o.contact_placeholder) +
-'">\n                        ';
+'"/>\n                        ';
  if (o.error_message) { ;
  if (o.error_message) { ;
 __p += '\n                            <div class="invalid-feedback">' +
 __p += '\n                            <div class="invalid-feedback">' +
 __e(o.error_message) +
 __e(o.error_message) +
 '</div>\n                        ';
 '</div>\n                        ';
  } ;
  } ;
-__p += '\n                    </div>\n                </div>\n                <div class="modal-footer">\n                    <button type="submit" class="btn btn-primary">' +
+__p += '\n                    </div>\n                    <div class="form-group">\n                        <label class="clearfix" for="name">' +
+__e(o.label_nickname) +
+':</label>\n                        <input type="text" name="name" value="' +
+__e(o.nickname) +
+'"\n                               class="form-control ';
+ if (o.error_message) { ;
+__p += ' is-invalid ';
+ } ;
+__p += '"\n                               placeholder="' +
+__e(o.nickname_placeholder) +
+'"/>\n                    </div>\n                </div>\n                <div class="modal-footer">\n                    <button type="submit" class="btn btn-primary">' +
 __e(o.label_add) +
 __e(o.label_add) +
 '</button>\n                </div>\n            </form>\n        </div>\n    </div>\n</div>\n';
 '</button>\n                </div>\n            </form>\n        </div>\n    </div>\n</div>\n';
 return __p
 return __p
@@ -16629,8 +16662,8 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
 
 
 /*global define */
 /*global define */
 (function (root, factory) {
 (function (root, factory) {
-  define('converse-rosterview',["converse-core", "tpl!add_contact_modal", "tpl!group_header", "tpl!pending_contact", "tpl!requesting_contact", "tpl!roster", "tpl!roster_filter", "tpl!roster_item", "tpl!search_contact", "converse-chatboxes", "converse-modal"], factory);
-})(this, function (converse, tpl_add_contact_modal, tpl_group_header, tpl_pending_contact, tpl_requesting_contact, tpl_roster, tpl_roster_filter, tpl_roster_item, tpl_search_contact) {
+  define('converse-rosterview',["converse-core", "tpl!add_contact_modal", "tpl!group_header", "tpl!pending_contact", "tpl!requesting_contact", "tpl!roster", "tpl!roster_filter", "tpl!roster_item", "tpl!search_contact", "awesomplete", "converse-chatboxes", "converse-modal"], factory);
+})(this, function (converse, tpl_add_contact_modal, tpl_group_header, tpl_pending_contact, tpl_requesting_contact, tpl_roster, tpl_roster_filter, tpl_roster_item, tpl_search_contact, Awesomplete) {
   "use strict";
   "use strict";
 
 
   var _converse$env = converse.env,
   var _converse$env = converse.env,
@@ -16679,9 +16712,10 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
           __ = _converse.__;
           __ = _converse.__;
 
 
       _converse.api.settings.update({
       _converse.api.settings.update({
-        allow_chat_pending_contacts: true,
-        allow_contact_removal: true,
-        show_toolbar: true
+        'allow_chat_pending_contacts': true,
+        'allow_contact_removal': true,
+        'show_toolbar': true,
+        'xhr_user_search_url': null
       });
       });
 
 
       _converse.api.promises.add('rosterViewInitialized');
       _converse.api.promises.add('rosterViewInitialized');
@@ -16740,8 +16774,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
 
 
       _converse.AddContactModal = _converse.BootstrapModal.extend({
       _converse.AddContactModal = _converse.BootstrapModal.extend({
         events: {
         events: {
-          'submit form': 'addContactFromForm',
-          'submit form.search-xmpp-contact': 'searchContacts'
+          'submit form': 'addContactFromForm'
         },
         },
         initialize: function initialize() {
         initialize: function initialize() {
           _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
           _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
@@ -16749,18 +16782,81 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
           this.model.on('change', this.render, this);
           this.model.on('change', this.render, this);
         },
         },
         toHTML: function toHTML() {
         toHTML: function toHTML() {
+          var label_nickname = _converse.xhr_user_search_url ? __('Contact name') : __('Optional nickname');
           return tpl_add_contact_modal(_.extend(this.model.toJSON(), {
           return tpl_add_contact_modal(_.extend(this.model.toJSON(), {
+            '_converse': _converse,
             'heading_new_contact': __('Add a Contact'),
             'heading_new_contact': __('Add a Contact'),
             'label_xmpp_address': __('XMPP Address'),
             'label_xmpp_address': __('XMPP Address'),
-            'label_nickname': __('Optional nickname'),
+            'label_nickname': label_nickname,
             'contact_placeholder': __('name@example.org'),
             'contact_placeholder': __('name@example.org'),
             'label_add': __('Add')
             'label_add': __('Add')
           }));
           }));
         },
         },
+        afterRender: function afterRender() {
+          if (_converse.xhr_user_search_url && _.isString(_converse.xhr_user_search_url)) {
+            this.initXHRAutoComplete();
+          } else {
+            this.initJIDAutoComplete();
+          }
+        },
+        initJIDAutoComplete: function initJIDAutoComplete() {
+          var jid_input = this.el.querySelector('input[name="jid"]');
+
+          var list = _.uniq(_converse.roster.map(function (item) {
+            return Strophe.getDomainFromJid(item.get('jid'));
+          }));
+
+          new Awesomplete(jid_input, {
+            'list': list,
+            'data': function data(text, input) {
+              return input.slice(0, input.indexOf("@")) + "@" + text;
+            },
+            'filter': Awesomplete.FILTER_STARTSWITH
+          });
+          this.el.addEventListener('shown.bs.modal', function () {
+            jid_input.focus();
+          }, false);
+        },
+        initXHRAutoComplete: function initXHRAutoComplete() {
+          var name_input = this.el.querySelector('input[name="name"]');
+          var jid_input = this.el.querySelector('input[name="jid"]');
+          var awesomplete = new Awesomplete(name_input, {
+            'minChars': 1,
+            'list': []
+          });
+          var xhr = new window.XMLHttpRequest(); // `open` must be called after `onload` for mock/testing purposes.
+
+          xhr.onload = function () {
+            if (xhr.responseText) {
+              awesomplete.list = JSON.parse(xhr.responseText).map(function (i) {
+                //eslint-disable-line arrow-body-style
+                return {
+                  'label': i.fullname,
+                  'value': i.jid
+                };
+              });
+              awesomplete.evaluate();
+            }
+          };
+
+          name_input.addEventListener('input', _.debounce(function () {
+            xhr.open("GET", "".concat(_converse.xhr_user_search_url, "?q=").concat(name_input.value), true);
+            xhr.send();
+          }, 300));
+          this.el.addEventListener('awesomplete-selectcomplete', function (ev) {
+            jid_input.value = ev.text.value;
+            name_input.value = ev.text.label;
+          });
+          this.el.addEventListener('shown.bs.modal', function () {
+            name_input.focus();
+          }, false);
+        },
         addContactFromForm: function addContactFromForm(ev) {
         addContactFromForm: function addContactFromForm(ev) {
           ev.preventDefault();
           ev.preventDefault();
           var data = new FormData(ev.target),
           var data = new FormData(ev.target),
-              jid = data.get('jid');
+              jid = data.get('jid'),
+              name = data.get('name');
+          ev.target.reset();
 
 
           if (!jid || _.compact(jid.split('@')).length < 2) {
           if (!jid || _.compact(jid.split('@')).length < 2) {
             this.model.set({
             this.model.set({
@@ -16768,7 +16864,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
               'jid': jid
               'jid': jid
             });
             });
           } else {
           } else {
-            _converse.roster.addAndSubscribe(jid);
+            _converse.roster.addAndSubscribe(jid, name);
 
 
             this.model.clear();
             this.model.clear();
             this.modal.hide();
             this.modal.hide();
@@ -17284,8 +17380,10 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
           this.onRemove(contact);
           this.onRemove(contact);
         },
         },
         onRemove: function onRemove(contact) {
         onRemove: function onRemove(contact) {
+          this.remove(contact.get('jid'));
+
           if (this.model.contacts.length === 0) {
           if (this.model.contacts.length === 0) {
-            this.el.parentElement.removeChild(this.el);
+            this.remove();
           }
           }
         }
         }
       });
       });
@@ -17487,6 +17585,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
         },
         },
         addContactToGroup: function addContactToGroup(contact, name, options) {
         addContactToGroup: function addContactToGroup(contact, name, options) {
           this.getGroup(name).contacts.add(contact, options);
           this.getGroup(name).contacts.add(contact, options);
+          this.sortAndPositionAllItems();
         },
         },
         addExistingContact: function addExistingContact(contact, options) {
         addExistingContact: function addExistingContact(contact, options) {
           var groups;
           var groups;
@@ -17862,6 +17961,13 @@ return __p
   }
   }
 
 
   converse.plugins.add('converse-vcard', {
   converse.plugins.add('converse-vcard', {
+    enabled: function enabled(_converse) {
+      _converse.api.settings.update({
+        'use_vcards': true
+      });
+
+      return _converse.use_vcards;
+    },
     overrides: {
     overrides: {
       // Overrides mentioned here will be picked up by converse.js's
       // Overrides mentioned here will be picked up by converse.js's
       // plugin architecture they will replace existing methods on the
       // plugin architecture they will replace existing methods on the
@@ -17887,10 +17993,6 @@ return __p
        */
        */
       var _converse = this._converse;
       var _converse = this._converse;
 
 
-      _converse.api.settings.update({
-        'use_vcards': true
-      });
-
       _converse.createRequestingContactFromVCard = function (presence, vcard) {
       _converse.createRequestingContactFromVCard = function (presence, vcard) {
         var bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from'));
         var bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from'));
         var fullname = vcard.fullname;
         var fullname = vcard.fullname;
@@ -17920,9 +18022,7 @@ return __p
 
 
 
 
       _converse.on('addClientFeatures', function () {
       _converse.on('addClientFeatures', function () {
-        if (_converse.use_vcards) {
-          _converse.connection.disco.addFeature(Strophe.NS.VCARD);
-        }
+        _converse.connection.disco.addFeature(Strophe.NS.VCARD);
       });
       });
 
 
       _converse.on('chatBoxInitialized', function (chatbox) {
       _converse.on('chatBoxInitialized', function (chatbox) {
@@ -18028,6 +18128,13 @@ return __p
             'placeholder_status_message': __('Personal status message')
             'placeholder_status_message': __('Personal status message')
           }));
           }));
         },
         },
+        afterRender: function afterRender() {
+          var _this = this;
+
+          this.el.addEventListener('shown.bs.modal', function () {
+            _this.el.querySelector('input[name="status_message"]').focus();
+          }, false);
+        },
         clearStatusMessage: function clearStatusMessage(ev) {
         clearStatusMessage: function clearStatusMessage(ev) {
           if (ev && ev.preventDefault) {
           if (ev && ev.preventDefault) {
             ev.preventDefault();
             ev.preventDefault();
@@ -19712,6 +19819,7 @@ return __p
             'affiliation': null,
             'affiliation': null,
             'connection_status': converse.ROOMSTATUS.DISCONNECTED,
             'connection_status': converse.ROOMSTATUS.DISCONNECTED,
             'name': '',
             'name': '',
+            'nick': _converse.xmppstatus.get('nickname'),
             'description': '',
             'description': '',
             'features_fetched': false,
             'features_fetched': false,
             'roomconfig': {},
             'roomconfig': {},
@@ -21854,7 +21962,7 @@ var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
 function print() { __p += __j.call(arguments, '') }
 function print() { __p += __j.call(arguments, '') }
 __p += '<form class="room-invite">\n    ';
 __p += '<form class="room-invite">\n    ';
  if (o.error_message) { ;
  if (o.error_message) { ;
-__p += '\n        <span class="pure-form-message error">' +
+__p += '\n        <span class="error">' +
 __e(o.error_message) +
 __e(o.error_message) +
 '</span>\n    ';
 '</span>\n    ';
  } ;
  } ;
@@ -22245,7 +22353,7 @@ return __p
 
 
                     if (!this.roomspanel.model.get('nick')) {
                     if (!this.roomspanel.model.get('nick')) {
                         this.roomspanel.model.save({
                         this.roomspanel.model.save({
-                            nick: Strophe.getNodeFromJid(_converse.bare_jid)
+                            nick: _converse.xmppstatus.get('nickname') || Strophe.getNodeFromJid(_converse.bare_jid)
                         });
                         });
                     }
                     }
                     _converse.emit('roomsPanelRendered');
                     _converse.emit('roomsPanelRendered');
@@ -22370,6 +22478,12 @@ return __p
                     }));
                     }));
                 },
                 },
 
 
+                afterRender () {
+                    this.el.addEventListener('shown.bs.modal', () => {
+                        this.el.querySelector('input[name="server"]').focus();
+                    }, false);
+                },
+
                 openRoom (ev) {
                 openRoom (ev) {
                     ev.preventDefault();
                     ev.preventDefault();
                     const jid = ev.target.getAttribute('data-room-jid');
                     const jid = ev.target.getAttribute('data-room-jid');
@@ -22491,6 +22605,12 @@ return __p
                     }));
                     }));
                 },
                 },
 
 
+                afterRender () {
+                    this.el.addEventListener('shown.bs.modal', () => {
+                        this.el.querySelector('input[name="chatroom"]').focus();
+                    }, false);
+                },
+
                 parseRoomDataFromEvent (form) {
                 parseRoomDataFromEvent (form) {
                     const data = new FormData(form);
                     const data = new FormData(form);
                     const jid = data.get('chatroom');
                     const jid = data.get('chatroom');

+ 57 - 34
dist/converse.js

@@ -39941,6 +39941,7 @@ return Backbone.BrowserStorage;
       locales: ['af', 'ar', 'bg', 'ca', 'de', 'es', 'en', 'fr', 'he', 'hu', 'id', 'it', 'ja', 'nb', 'nl', 'pl', 'pt_BR', 'ru', 'tr', 'uk', 'zh_CN', 'zh_TW'],
       locales: ['af', 'ar', 'bg', 'ca', 'de', 'es', 'en', 'fr', 'he', 'hu', 'id', 'it', 'ja', 'nb', 'nl', 'pl', 'pt_BR', 'ru', 'tr', 'uk', 'zh_CN', 'zh_TW'],
       message_carbons: true,
       message_carbons: true,
       message_storage: 'session',
       message_storage: 'session',
+      nickname: undefined,
       password: undefined,
       password: undefined,
       prebind_url: null,
       prebind_url: null,
       priority: 0,
       priority: 0,
@@ -40576,8 +40577,6 @@ return Backbone.BrowserStorage;
         'status': ''
         'status': ''
       },
       },
       initialize: function initialize(attributes) {
       initialize: function initialize(attributes) {
-        var _this3 = this;
-
         var jid = attributes.jid,
         var jid = attributes.jid,
             bare_jid = Strophe.getBareJidFromJid(jid).toLowerCase(),
             bare_jid = Strophe.getBareJidFromJid(jid).toLowerCase(),
             resource = Strophe.getResourceFromJid(jid);
             resource = Strophe.getResourceFromJid(jid);
@@ -40590,9 +40589,6 @@ return Backbone.BrowserStorage;
           'resources': {},
           'resources': {},
           'user_id': Strophe.getNodeFromJid(jid)
           'user_id': Strophe.getNodeFromJid(jid)
         }, attributes));
         }, attributes));
-        this.on('destroy', function () {
-          _this3.removeFromRoster();
-        });
         this.on('change:chat_status', function (item) {
         this.on('change:chat_status', function (item) {
           _converse.emit('contactStatusChanged', item.attributes);
           _converse.emit('contactStatusChanged', item.attributes);
         });
         });
@@ -40613,9 +40609,9 @@ return Backbone.BrowserStorage;
           pres.c("status").t(message).up();
           pres.c("status").t(message).up();
         }
         }
 
 
-        var nick = _converse.xmppstatus.get('fullname');
+        var nick = _converse.xmppstatus.get('nickname') || _converse.xmppstatus.get('fullname');
 
 
-        if (nick && nick !== "") {
+        if (nick) {
           pres.c('nick', {
           pres.c('nick', {
             'xmlns': Strophe.NS.NICK
             'xmlns': Strophe.NS.NICK
           }).t(nick).up();
           }).t(nick).up();
@@ -40652,7 +40648,8 @@ return Backbone.BrowserStorage;
           'to': this.get('jid')
           'to': this.get('jid')
         }));
         }));
 
 
-        this.destroy(); // Will cause removeFromRoster to be called.
+        this.removeFromRoster();
+        this.destroy();
       },
       },
       unauthorize: function unauthorize(message) {
       unauthorize: function unauthorize(message) {
         /* Unauthorize this contact's presence subscription
         /* Unauthorize this contact's presence subscription
@@ -40816,7 +40813,7 @@ return Backbone.BrowserStorage;
         }, Strophe.NS.ROSTERX, 'message', null);
         }, Strophe.NS.ROSTERX, 'message', null);
       },
       },
       fetchRosterContacts: function fetchRosterContacts() {
       fetchRosterContacts: function fetchRosterContacts() {
-        var _this4 = this;
+        var _this3 = this;
 
 
         /* Fetches the roster contacts, first by trying the
         /* Fetches the roster contacts, first by trying the
          * sessionStorage cache, and if that's empty, then by querying
          * sessionStorage cache, and if that's empty, then by querying
@@ -40826,7 +40823,7 @@ return Backbone.BrowserStorage;
          * fetched.
          * fetched.
          */
          */
         return new Promise(function (resolve, reject) {
         return new Promise(function (resolve, reject) {
-          _this4.fetch({
+          _this3.fetch({
             'add': true,
             'add': true,
             'silent': true,
             'silent': true,
             success: function success(collection) {
             success: function success(collection) {
@@ -40846,7 +40843,7 @@ return Backbone.BrowserStorage;
       subscribeToSuggestedItems: function subscribeToSuggestedItems(msg) {
       subscribeToSuggestedItems: function subscribeToSuggestedItems(msg) {
         _.each(msg.querySelectorAll('item'), function (item) {
         _.each(msg.querySelectorAll('item'), function (item) {
           if (item.getAttribute('action') === 'add') {
           if (item.getAttribute('action') === 'add') {
-            _converse.roster.addAndSubscribe(item.getAttribute('jid'), null, _converse.xmppstatus.get('fullname'));
+            _converse.roster.addAndSubscribe(item.getAttribute('jid'), _converse.xmppstatus.get('nickname') || _converse.xmppstatus.get('fullname'));
           }
           }
         });
         });
 
 
@@ -40872,7 +40869,7 @@ return Backbone.BrowserStorage;
           }
           }
         };
         };
 
 
-        this.addContact(jid, name, groups, attributes).then(handler, handler);
+        this.addContactToRoster(jid, name, groups, attributes).then(handler, handler);
       },
       },
       sendContactAddIQ: function sendContactAddIQ(jid, name, groups, callback, errback) {
       sendContactAddIQ: function sendContactAddIQ(jid, name, groups, callback, errback) {
         /*  Send an IQ stanza to the XMPP server to add a new roster contact.
         /*  Send an IQ stanza to the XMPP server to add a new roster contact.
@@ -40900,8 +40897,8 @@ return Backbone.BrowserStorage;
 
 
         _converse.connection.sendIQ(iq, callback, errback);
         _converse.connection.sendIQ(iq, callback, errback);
       },
       },
-      addContact: function addContact(jid, name, groups, attributes) {
-        var _this5 = this;
+      addContactToRoster: function addContactToRoster(jid, name, groups, attributes) {
+        var _this4 = this;
 
 
         /* Adds a RosterContact instance to _converse.roster and
         /* Adds a RosterContact instance to _converse.roster and
          * registers the contact on the XMPP server.
          * registers the contact on the XMPP server.
@@ -40918,8 +40915,8 @@ return Backbone.BrowserStorage;
           groups = groups || [];
           groups = groups || [];
           name = _.isEmpty(name) ? jid : name;
           name = _.isEmpty(name) ? jid : name;
 
 
-          _this5.sendContactAddIQ(jid, name, groups, function () {
-            var contact = _this5.create(_.assignIn({
+          _this4.sendContactAddIQ(jid, name, groups, function () {
+            var contact = _this4.create(_.assignIn({
               ask: undefined,
               ask: undefined,
               fullname: name,
               fullname: name,
               groups: groups,
               groups: groups,
@@ -40940,7 +40937,7 @@ return Backbone.BrowserStorage;
           });
           });
         });
         });
       },
       },
-      subscribeBack: function subscribeBack(bare_jid) {
+      subscribeBack: function subscribeBack(bare_jid, presence) {
         var contact = this.get(bare_jid);
         var contact = this.get(bare_jid);
 
 
         if (contact instanceof _converse.RosterContact) {
         if (contact instanceof _converse.RosterContact) {
@@ -40953,7 +40950,9 @@ return Backbone.BrowserStorage;
             }
             }
           };
           };
 
 
-          this.addContact(bare_jid, '', [], {
+          var nickname = _.get(sizzle("nick[xmlns=\"".concat(Strophe.NS.NICK, "\"]"), presence).pop(), 'textContent', null);
+
+          this.addContactToRoster(bare_jid, nickname, [], {
             'subscription': 'from'
             'subscription': 'from'
           }).then(handler, handler);
           }).then(handler, handler);
         }
         }
@@ -41012,7 +41011,7 @@ return Backbone.BrowserStorage;
         return true;
         return true;
       },
       },
       fetchFromServer: function fetchFromServer() {
       fetchFromServer: function fetchFromServer() {
-        var _this6 = this;
+        var _this5 = this;
 
 
         /* Fetch the roster from the XMPP server */
         /* Fetch the roster from the XMPP server */
         return new Promise(function (resolve, reject) {
         return new Promise(function (resolve, reject) {
@@ -41023,7 +41022,7 @@ return Backbone.BrowserStorage;
             xmlns: Strophe.NS.ROSTER
             xmlns: Strophe.NS.ROSTER
           });
           });
 
 
-          var callback = _.flow(_this6.onReceivedFromServer.bind(_this6), resolve);
+          var callback = _.flow(_this5.onReceivedFromServer.bind(_this5), resolve);
 
 
           var errback = function errback(iq) {
           var errback = function errback(iq) {
             var errmsg = "Error while trying to fetch roster from the server";
             var errmsg = "Error while trying to fetch roster from the server";
@@ -41077,7 +41076,7 @@ return Backbone.BrowserStorage;
           });
           });
         } else {
         } else {
           if (subscription === "remove") {
           if (subscription === "remove") {
-            return contact.destroy(); // will trigger removeFromRoster
+            return contact.destroy();
           } // We only find out about requesting contacts via the
           } // We only find out about requesting contacts via the
           // presence handler, so if we receive a contact
           // presence handler, so if we receive a contact
           // here, we know they aren't requesting anymore.
           // here, we know they aren't requesting anymore.
@@ -41098,13 +41097,14 @@ return Backbone.BrowserStorage;
          * Note: this method gets completely overridden by converse-vcard.js
          * Note: this method gets completely overridden by converse-vcard.js
          */
          */
         var bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from')),
         var bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from')),
-            nick_el = presence.querySelector("nick[xmlns=\"".concat(Strophe.NS.NICK, "\"]"));
+            nickname = _.get(sizzle("nick[xmlns=\"".concat(Strophe.NS.NICK, "\"]"), presence).pop(), 'textContent', null);
+
         var user_data = {
         var user_data = {
           'jid': bare_jid,
           'jid': bare_jid,
           'subscription': 'none',
           'subscription': 'none',
           'ask': null,
           'ask': null,
           'requesting': true,
           'requesting': true,
-          'fullname': nick_el && nick_el.textContent || bare_jid
+          'fullname': nickname
         };
         };
         this.create(user_data);
         this.create(user_data);
 
 
@@ -41121,7 +41121,7 @@ return Backbone.BrowserStorage;
 
 
         if (_converse.auto_subscribe) {
         if (_converse.auto_subscribe) {
           if (!contact || contact.get('subscription') !== 'to') {
           if (!contact || contact.get('subscription') !== 'to') {
-            this.subscribeBack(bare_jid);
+            this.subscribeBack(bare_jid, presence);
           } else {
           } else {
             contact.authorize();
             contact.authorize();
           }
           }
@@ -41167,6 +41167,24 @@ return Backbone.BrowserStorage;
             }
             }
           }
           }
 
 
+          if (_converse.jid === jid && presence_type === 'unavailable') {
+            // XXX: We've received an "unavailable" presence from our
+            // own resource. Apparently this happens due to a
+            // Prosody bug, whereby we send an IQ stanza to remove
+            // a roster contact, and Prosody then sends
+            // "unavailable" globally, instead of directed to the
+            // particular user that's removed.
+            //
+            // Here is the bug report: https://prosody.im/issues/1121
+            //
+            // I'm not sure whether this might legitimately happen
+            // in other cases.
+            //
+            // As a workaround for now we simply send our presence again,
+            // otherwise we're treated as offline.
+            _converse.xmppstatus.sendPresence();
+          }
+
           return;
           return;
         } else if (sizzle("query[xmlns=\"".concat(Strophe.NS.MUC, "\"]"), presence).length) {
         } else if (sizzle("query[xmlns=\"".concat(Strophe.NS.MUC, "\"]"), presence).length) {
           return; // Ignore MUC
           return; // Ignore MUC
@@ -41207,7 +41225,7 @@ return Backbone.BrowserStorage;
     this.RosterGroups = Backbone.Collection.extend({
     this.RosterGroups = Backbone.Collection.extend({
       model: _converse.RosterGroup,
       model: _converse.RosterGroup,
       fetchRosterGroups: function fetchRosterGroups() {
       fetchRosterGroups: function fetchRosterGroups() {
-        var _this7 = this;
+        var _this6 = this;
 
 
         /* Fetches all the roster groups from sessionStorage.
         /* Fetches all the roster groups from sessionStorage.
          *
          *
@@ -41215,7 +41233,7 @@ return Backbone.BrowserStorage;
          * returned.
          * returned.
          */
          */
         return new Promise(function (resolve, reject) {
         return new Promise(function (resolve, reject) {
-          _this7.fetch({
+          _this6.fetch({
             silent: true,
             silent: true,
             // We need to first have all groups before
             // We need to first have all groups before
             // we can start positioning them, so we set
             // we can start positioning them, so we set
@@ -41242,23 +41260,24 @@ return Backbone.BrowserStorage;
         return {
         return {
           "status": _converse.default_state,
           "status": _converse.default_state,
           "jid": _converse.bare_jid,
           "jid": _converse.bare_jid,
+          "nickname": _converse.nickname,
           "vcard_updated": null
           "vcard_updated": null
         };
         };
       },
       },
       initialize: function initialize() {
       initialize: function initialize() {
-        var _this8 = this;
+        var _this7 = this;
 
 
         this.on('change:status', function (item) {
         this.on('change:status', function (item) {
-          var status = _this8.get('status');
+          var status = _this7.get('status');
 
 
-          _this8.sendPresence(status);
+          _this7.sendPresence(status);
 
 
           _converse.emit('statusChanged', status);
           _converse.emit('statusChanged', status);
         });
         });
         this.on('change:status_message', function () {
         this.on('change:status_message', function () {
-          var status_message = _this8.get('status_message');
+          var status_message = _this7.get('status_message');
 
 
-          _this8.sendPresence(_this8.get('status'), status_message);
+          _this7.sendPresence(_this7.get('status'), status_message);
 
 
           _converse.emit('statusMessageChanged', status_message);
           _converse.emit('statusMessageChanged', status_message);
         });
         });
@@ -50761,12 +50780,13 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
           this.model.contacts.remove(contact, {
           this.model.contacts.remove(contact, {
             'silent': true
             'silent': true
           });
           });
-          this.get(contact.get('jid')).remove();
           this.onRemove(contact);
           this.onRemove(contact);
         },
         },
         onRemove: function onRemove(contact) {
         onRemove: function onRemove(contact) {
+          this.remove(contact.get('jid'));
+
           if (this.model.contacts.length === 0) {
           if (this.model.contacts.length === 0) {
-            this.el.parentElement.removeChild(this.el);
+            this.remove();
           }
           }
         }
         }
       });
       });
@@ -51447,6 +51467,8 @@ return __p
       /* The initialize function gets called as soon as the plugin is
       /* The initialize function gets called as soon as the plugin is
        * loaded by converse.js's plugin machinery.
        * loaded by converse.js's plugin machinery.
        */
        */
+      var _converse = this._converse;
+
       _converse.createRequestingContactFromVCard = function (presence, vcard) {
       _converse.createRequestingContactFromVCard = function (presence, vcard) {
         var bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from'));
         var bare_jid = Strophe.getBareJidFromJid(presence.getAttribute('from'));
         var fullname = vcard.fullname;
         var fullname = vcard.fullname;
@@ -53547,6 +53569,7 @@ Strophe.addConnectionPlugin('disco',
             'affiliation': null,
             'affiliation': null,
             'connection_status': converse.ROOMSTATUS.DISCONNECTED,
             'connection_status': converse.ROOMSTATUS.DISCONNECTED,
             'name': '',
             'name': '',
+            'nick': _converse.xmppstatus.get('nickname'),
             'description': '',
             'description': '',
             'features_fetched': false,
             'features_fetched': false,
             'roomconfig': {},
             'roomconfig': {},
@@ -56126,7 +56149,7 @@ return __p
 
 
           if (!this.roomspanel.model.get('nick')) {
           if (!this.roomspanel.model.get('nick')) {
             this.roomspanel.model.save({
             this.roomspanel.model.save({
-              nick: Strophe.getNodeFromJid(_converse.bare_jid)
+              nick: _converse.xmppstatus.get('nickname') || Strophe.getNodeFromJid(_converse.bare_jid)
             });
             });
           }
           }