Procházet zdrojové kódy

Create new build files

JC Brand před 7 roky
rodič
revize
19a2fb17ad
2 změnil soubory, kde provedl 418 přidání a 130 odebrání
  1. 207 65
      dist/converse-no-dependencies.js
  2. 211 65
      dist/converse.js

+ 207 - 65
dist/converse-no-dependencies.js

@@ -14677,14 +14677,35 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
   };
   };
 
 
   u.parseMemberListIQ = function parseMemberListIQ(iq) {
   u.parseMemberListIQ = function parseMemberListIQ(iq) {
-    /* Given an IQ stanza with a member list, create an array of member
-        * objects.
-        */
+    /* Given an IQ stanza with a member list, create an array of member objects.
+    */
     return _.map(sizzle("query[xmlns=\"".concat(Strophe.NS.MUC_ADMIN, "\"] item"), iq), function (item) {
     return _.map(sizzle("query[xmlns=\"".concat(Strophe.NS.MUC_ADMIN, "\"] item"), iq), function (item) {
-      return {
-        'jid': item.getAttribute('jid'),
+      var data = {
         'affiliation': item.getAttribute('affiliation')
         'affiliation': item.getAttribute('affiliation')
       };
       };
+      var jid = item.getAttribute('jid');
+
+      if (u.isValidJID(jid)) {
+        data['jid'] = jid;
+      } else {
+        // XXX: Prosody sends nick for the jid attribute value
+        // Perhaps for anonymous room?
+        data['nick'] = jid;
+      }
+
+      var nick = item.getAttribute('nick');
+
+      if (nick) {
+        data['nick'] = nick;
+      }
+
+      var role = item.getAttribute('role');
+
+      if (role) {
+        data['role'] = nick;
+      }
+
+      return data;
     });
     });
   };
   };
 
 
@@ -14714,7 +14735,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
     'moderator': 1,
     'moderator': 1,
     'participant': 2,
     'participant': 2,
     'visitor': 3,
     'visitor': 3,
-    'none': 4
+    'none': 2
   };
   };
   var _converse$env = converse.env,
   var _converse$env = converse.env,
       Strophe = _converse$env.Strophe,
       Strophe = _converse$env.Strophe,
@@ -14874,6 +14895,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
 
 
           this.occupants = new _converse.ChatRoomOccupants();
           this.occupants = new _converse.ChatRoomOccupants();
           this.occupants.browserStorage = new Backbone.BrowserStorage.session(b64_sha1("converse.occupants-".concat(_converse.bare_jid).concat(this.get('jid'))));
           this.occupants.browserStorage = new Backbone.BrowserStorage.session(b64_sha1("converse.occupants-".concat(_converse.bare_jid).concat(this.get('jid'))));
+          this.occupants.chatroom = this;
           this.registerHandlers();
           this.registerHandlers();
           this.on('change:chat_state', this.sendChatState, this);
           this.on('change:chat_state', this.sendChatState, this);
         },
         },
@@ -15495,26 +15517,6 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
 
 
           return this;
           return this;
         },
         },
-        findOccupant: function findOccupant(data) {
-          /* Try to find an existing occupant based on the passed in
-           * data object.
-           *
-           * If we have a JID, we use that as lookup variable,
-           * otherwise we use the nick. We don't always have both,
-           * but should have at least one or the other.
-           */
-          var jid = Strophe.getBareJidFromJid(data.jid);
-
-          if (jid !== null) {
-            return this.occupants.where({
-              'jid': jid
-            }).pop();
-          } else {
-            return this.occupants.where({
-              'nick': data.nick
-            }).pop();
-          }
-        },
         updateOccupantsOnPresence: function updateOccupantsOnPresence(pres) {
         updateOccupantsOnPresence: function updateOccupantsOnPresence(pres) {
           /* Given a presence stanza, update the occupant model
           /* Given a presence stanza, update the occupant model
            * based on its contents.
            * based on its contents.
@@ -15528,7 +15530,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
             return true;
             return true;
           }
           }
 
 
-          var occupant = this.findOccupant(data);
+          var occupant = this.occupants.findOccupant(data);
 
 
           if (data.type === 'unavailable') {
           if (data.type === 'unavailable') {
             if (occupant) {
             if (occupant) {
@@ -15568,7 +15570,8 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
             'from': from,
             'from': from,
             'nick': Strophe.getResourceFromJid(from),
             'nick': Strophe.getResourceFromJid(from),
             'type': pres.getAttribute("type"),
             'type': pres.getAttribute("type"),
-            'states': []
+            'states': [],
+            'show': 'online'
           };
           };
 
 
           _.each(pres.childNodes, function (child) {
           _.each(pres.childNodes, function (child) {
@@ -15793,6 +15796,9 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
         }
         }
       });
       });
       _converse.ChatRoomOccupant = Backbone.Model.extend({
       _converse.ChatRoomOccupant = Backbone.Model.extend({
+        defaults: {
+          'show': 'offline'
+        },
         initialize: function initialize(attributes) {
         initialize: function initialize(attributes) {
           this.set(_.extend({
           this.set(_.extend({
             'id': _converse.connection.getUniqueId()
             'id': _converse.connection.getUniqueId()
@@ -15828,6 +15834,43 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
           } else {
           } else {
             return MUC_ROLE_WEIGHTS[role1] < MUC_ROLE_WEIGHTS[role2] ? -1 : 1;
             return MUC_ROLE_WEIGHTS[role1] < MUC_ROLE_WEIGHTS[role2] ? -1 : 1;
           }
           }
+        },
+        fetchMembers: function fetchMembers() {
+          var _this10 = this;
+
+          this.chatroom.getJidsWithAffiliations(['member', 'owner', 'admin']).then(function (jids) {
+            _.each(jids, function (attrs) {
+              var occupant = _this10.findOccupant({
+                'jid': attrs.jid
+              });
+
+              if (occupant) {
+                occupant.save(attrs);
+              } else {
+                _this10.create(attrs);
+              }
+            });
+          }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
+        },
+        findOccupant: function findOccupant(data) {
+          /* Try to find an existing occupant based on the passed in
+           * data object.
+           *
+           * If we have a JID, we use that as lookup variable,
+           * otherwise we use the nick. We don't always have both,
+           * but should have at least one or the other.
+           */
+          var jid = Strophe.getBareJidFromJid(data.jid);
+
+          if (jid !== null) {
+            return this.where({
+              'jid': jid
+            }).pop();
+          } else {
+            return this.where({
+              'nick': data.nick
+            }).pop();
+          }
         }
         }
       });
       });
       _converse.RoomsPanelModel = Backbone.Model.extend({
       _converse.RoomsPanelModel = Backbone.Model.extend({
@@ -23220,6 +23263,24 @@ __e(o.label_search) +
 return __p
 return __p
 };});
 };});
 
 
+
+define('tpl!alert_modal', ['lodash'], function(_) {return function(o) {
+var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
+function print() { __p += __j.call(arguments, '') }
+__p += '<div class="modal" tabindex="-1" role="dialog">\n  <div class="modal-dialog" role="document">\n    <div class="modal-content">\n      <div class="modal-header ' +
+__e(o.type) +
+'">\n        <h5 class="modal-title">' +
+__e(o.title) +
+'</h5>\n        <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n          <span aria-hidden="true">&times;</span>\n        </button>\n      </div>\n      <div class="modal-body">';
+ _.each(o.messages, function (message) { ;
+__p += '\n          <p>' +
+__e(message) +
+'</p>\n      ';
+ }) ;
+__p += '\n    </div>\n    </div>\n  </div>\n</div>\n';
+return __p
+};});
+
 // Converse.js
 // Converse.js
 // http://conversejs.org
 // http://conversejs.org
 //
 //
@@ -23230,15 +23291,15 @@ return __p
     if (typeof define === 'function' && define.amd) {
     if (typeof define === 'function' && define.amd) {
         define('converse-modal',[
         define('converse-modal',[
             "converse-core",
             "converse-core",
+            "tpl!alert_modal",
             "bootstrap",
             "bootstrap",
-            "underscore",
-            "backbone",
             "backbone.vdomview"
             "backbone.vdomview"
         ], factory);
         ], factory);
    }
    }
-}(this, function (converse, bootstrap, _, Backbone) {
+}(this, function (converse, tpl_alert_modal, bootstrap) {
     "use strict";
     "use strict";
 
 
+    const { Strophe, Backbone, _ } = converse.env;
 
 
     converse.plugins.add('converse-modal', {
     converse.plugins.add('converse-modal', {
 
 
@@ -23266,12 +23327,64 @@ return __p
                 },
                 },
 
 
                 show (ev) {
                 show (ev) {
-                    ev.preventDefault();
-                    this.trigger_el = ev.target;
-                    this.trigger_el.classList.add('selected');
+                    if (ev) {
+                        ev.preventDefault();
+                        this.trigger_el = ev.target;
+                        this.trigger_el.classList.add('selected');
+                    }
                     this.modal.show();
                     this.modal.show();
                 }
                 }
             });
             });
+
+            _converse.Alert = _converse.BootstrapModal.extend({
+
+                initialize () {
+                    _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
+                    this.model.on('change', this.render, this);
+                },
+
+                toHTML () {
+                    return tpl_alert_modal(this.model.toJSON());
+                }
+            });
+
+
+            /************************ BEGIN API ************************/
+            // We extend the default converse.js API to add methods specific to MUC chat rooms.
+            let alert 
+
+            _.extend(_converse.api, {
+                'alert': {
+                    'show' (type, title, messages) {
+                        if (_.isString(messages)) {
+                            messages = [messages];
+                        }
+                        if (type === Strophe.LogLevel.ERROR) {
+                            type = 'alert-danger';
+                        } else if (type === Strophe.LogLevel.INFO) {
+                            type = 'alert-info';
+                        } else if (type === Strophe.LogLevel.WARN) {
+                            type = 'alert-warning';
+                        }
+
+                        if (_.isUndefined(alert)) {
+                            const model = new Backbone.Model({
+                                'title': title,
+                                'messages': messages,
+                                'type': type
+                            })
+                            alert = new _converse.Alert({'model': model});
+                        } else {
+                            alert.model.set({
+                                'title': title,
+                                'messages': messages,
+                                'type': type
+                            });
+                        }
+                        alert.show();
+                    }
+                }
+            });
         }
         }
     });
     });
 }));
 }));
@@ -24771,22 +24884,14 @@ return __p
           var _this2 = this;
           var _this2 = this;
 
 
           _converse.api.vcard.set(data).then(function () {
           _converse.api.vcard.set(data).then(function () {
-            _converse.api.vcard.update(_this2.model.vcard, true);
-
-            var html = tpl_alert({
-              'message': __('Profile data succesfully saved'),
-              'type': 'alert-primary'
-            });
-            body.insertAdjacentHTML('afterBegin', html);
+            return _converse.api.vcard.update(_this2.model.vcard, true);
           }).catch(function (err) {
           }).catch(function (err) {
             _converse.log(err, Strophe.LogLevel.FATAL);
             _converse.log(err, Strophe.LogLevel.FATAL);
 
 
-            var html = tpl_alert({
-              'message': __('An error happened while trying to save your profile data'),
-              'type': 'alert-danger'
-            });
-            body.insertAdjacentHTML('afterBegin', html);
+            _converse.api.alert.show(Strophe.LogLevel.ERROR, __('Error'), [__("Sorry, an error happened while trying to save your profile data."), __("You can check your browser's developer console for any error output.")]);
           });
           });
+
+          this.modal.hide();
         },
         },
         onFormSubmitted: function onFormSubmitted(ev) {
         onFormSubmitted: function onFormSubmitted(ev) {
           var _this3 = this;
           var _this3 = this;
@@ -27524,7 +27629,7 @@ __p += '\n';
  if (o.nonanonymous) { ;
  if (o.nonanonymous) { ;
 __p += '\n<li class="feature" title="' +
 __p += '\n<li class="feature" title="' +
 __e( o.tt_nonanonymous ) +
 __e( o.tt_nonanonymous ) +
-'"><span class="fa fa-idcard-dark"></span>' +
+'"><span class="fa fa-id-card"></span>' +
 __e( o.label_nonanonymous ) +
 __e( o.label_nonanonymous ) +
 '</li>\n';
 '</li>\n';
  } ;
  } ;
@@ -27703,9 +27808,7 @@ return __p
 define('tpl!occupant', ['lodash'], function(_) {return function(o) {
 define('tpl!occupant', ['lodash'], function(_) {return function(o) {
 var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
 var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
 function print() { __p += __j.call(arguments, '') }
 function print() { __p += __j.call(arguments, '') }
-__p += '<li class="' +
-__e( o.role ) +
-' occupant" id="' +
+__p += '<li class="occupant" id="' +
 __e( o.id ) +
 __e( o.id ) +
 '"\n    ';
 '"\n    ';
  if (o.role === "moderator") { ;
  if (o.role === "moderator") { ;
@@ -27718,11 +27821,11 @@ __e( o.hint_occupant ) +
 '"\n    ';
 '"\n    ';
  } ;
  } ;
 __p += '\n    ';
 __p += '\n    ';
- if (o.role === "occupant") { ;
+ if (o.role === "participant") { ;
 __p += '\n       title="' +
 __p += '\n       title="' +
 __e( o.jid ) +
 __e( o.jid ) +
 ' ' +
 ' ' +
-__e( o.desc_occupant ) +
+__e( o.desc_participant ) +
 ' ' +
 ' ' +
 __e( o.hint_occupant ) +
 __e( o.hint_occupant ) +
 '"\n    ';
 '"\n    ';
@@ -27738,20 +27841,50 @@ __e( o.hint_occupant ) +
 '"\n    ';
 '"\n    ';
  } ;
  } ;
 __p += '\n    ';
 __p += '\n    ';
- if (!_.includes(["visitor", "occupant", "moderator"], o.role)) { ;
+ if (!_.includes(["visitor", "participant", "moderator"], o.role)) { ;
 __p += '\n       title="' +
 __p += '\n       title="' +
 __e( o.jid ) +
 __e( o.jid ) +
 ' ' +
 ' ' +
 __e( o.hint_occupant ) +
 __e( o.hint_occupant ) +
-'"\n       ';
+'"\n    ';
  } ;
  } ;
-__p += '><div class="occupant-status occupant-' +
+__p += '>\n    <div class="row no-gutters">\n        <div class="col-auto">\n            <div class="occupant-status occupant-' +
 __e(o.show) +
 __e(o.show) +
 ' circle" title="' +
 ' circle" title="' +
 __e(o.hint_show) +
 __e(o.hint_show) +
-'"></div>' +
-__e(o.nick) +
-'</li>\n';
+'"></div>\n        </div>\n        <div class="col">\n            <span class="occupant-nick">' +
+__e(o.nick || o.jid) +
+'</span>\n            ';
+ if (o.affiliation === "owner") { ;
+__p += '\n                <span class="badge badge-danger">' +
+__e(o.label_owner) +
+'</span>\n            ';
+ } ;
+__p += '\n            ';
+ if (o.affiliation === "admin") { ;
+__p += '\n                <span class="badge badge-info">' +
+__e(o.label_admin) +
+'</span>\n            ';
+ } ;
+__p += '\n            ';
+ if (o.affiliation === "member") { ;
+__p += '\n                <span class="badge badge-info">' +
+__e(o.label_member) +
+'</span>\n            ';
+ } ;
+__p += '\n\n            ';
+ if (o.role === "moderator") { ;
+__p += '\n                <span class="badge badge-info">' +
+__e(o.label_moderator) +
+'</span>\n            ';
+ } ;
+__p += '\n            ';
+ if (o.role === "visitor") { ;
+__p += '\n                <span class="badge badge-secondary">' +
+__e(o.label_visitor) +
+'</span>\n            ';
+ } ;
+__p += '\n        </div>\n    </div>\n</li>\n';
 return __p
 return __p
 };});
 };});
 
 
@@ -28423,8 +28556,7 @@ return __p
                                 // collection anymore).
                                 // collection anymore).
                                 return;
                                 return;
                             }
                             }
-                            this.join();
-                            this.fetchMessages();
+                            this.populateAndJoin();
                             _converse.emit('chatRoomOpened', this);
                             _converse.emit('chatRoomOpened', this);
                         }
                         }
                         this.model.getRoomFeatures().then(handler, handler);
                         this.model.getRoomFeatures().then(handler, handler);
@@ -28805,6 +28937,12 @@ return __p
                     }
                     }
                 },
                 },
 
 
+                populateAndJoin () {
+                    this.model.occupants.fetchMembers();
+                    this.join();
+                    this.fetchMessages();
+                },
+
                 join (nick, password) {
                 join (nick, password) {
                     /* Join the chat room.
                     /* Join the chat room.
                      *
                      *
@@ -29422,7 +29560,7 @@ return __p
                 },
                 },
 
 
                 toHTML () {
                 toHTML () {
-                    const show = this.model.get('show') || 'online';
+                    const show = this.model.get('show');
                     return tpl_occupant(
                     return tpl_occupant(
                         _.extend(
                         _.extend(
                             { 'jid': '',
                             { 'jid': '',
@@ -29430,8 +29568,13 @@ return __p
                               'hint_show': _converse.PRETTY_CHAT_STATUS[show],
                               'hint_show': _converse.PRETTY_CHAT_STATUS[show],
                               'hint_occupant': __('Click to mention %1$s in your message.', this.model.get('nick')),
                               'hint_occupant': __('Click to mention %1$s in your message.', this.model.get('nick')),
                               'desc_moderator': __('This user is a moderator.'),
                               'desc_moderator': __('This user is a moderator.'),
-                              'desc_occupant': __('This user can send messages in this room.'),
-                              'desc_visitor': __('This user can NOT send messages in this room.')
+                              'desc_participant': __('This user can send messages in this room.'),
+                              'desc_visitor': __('This user can NOT send messages in this room.'),
+                              'label_moderator': __('Moderator'),
+                              'label_visitor': __('Visitor'),
+                              'label_owner': __('Owner'),
+                              'label_member': __('Member'),
+                              'label_admin': __('Admin')
                             }, this.model.toJSON())
                             }, this.model.toJSON())
                     );
                     );
                 },
                 },
@@ -29710,8 +29853,7 @@ return __p
                     if (view.model.get('type') === converse.CHATROOMS_TYPE) {
                     if (view.model.get('type') === converse.CHATROOMS_TYPE) {
                         view.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
                         view.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
                         view.model.registerHandlers();
                         view.model.registerHandlers();
-                        view.join();
-                        view.fetchMessages();
+                        view.populateAndJoin();
                     }
                     }
                 });
                 });
             }
             }

+ 211 - 65
dist/converse.js

@@ -47414,14 +47414,35 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
   };
   };
 
 
   u.parseMemberListIQ = function parseMemberListIQ(iq) {
   u.parseMemberListIQ = function parseMemberListIQ(iq) {
-    /* Given an IQ stanza with a member list, create an array of member
-        * objects.
-        */
+    /* Given an IQ stanza with a member list, create an array of member objects.
+    */
     return _.map(sizzle("query[xmlns=\"".concat(Strophe.NS.MUC_ADMIN, "\"] item"), iq), function (item) {
     return _.map(sizzle("query[xmlns=\"".concat(Strophe.NS.MUC_ADMIN, "\"] item"), iq), function (item) {
-      return {
-        'jid': item.getAttribute('jid'),
+      var data = {
         'affiliation': item.getAttribute('affiliation')
         'affiliation': item.getAttribute('affiliation')
       };
       };
+      var jid = item.getAttribute('jid');
+
+      if (u.isValidJID(jid)) {
+        data['jid'] = jid;
+      } else {
+        // XXX: Prosody sends nick for the jid attribute value
+        // Perhaps for anonymous room?
+        data['nick'] = jid;
+      }
+
+      var nick = item.getAttribute('nick');
+
+      if (nick) {
+        data['nick'] = nick;
+      }
+
+      var role = item.getAttribute('role');
+
+      if (role) {
+        data['role'] = nick;
+      }
+
+      return data;
     });
     });
   };
   };
 
 
@@ -47451,7 +47472,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
     'moderator': 1,
     'moderator': 1,
     'participant': 2,
     'participant': 2,
     'visitor': 3,
     'visitor': 3,
-    'none': 4
+    'none': 2
   };
   };
   var _converse$env = converse.env,
   var _converse$env = converse.env,
       Strophe = _converse$env.Strophe,
       Strophe = _converse$env.Strophe,
@@ -47611,6 +47632,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
 
 
           this.occupants = new _converse.ChatRoomOccupants();
           this.occupants = new _converse.ChatRoomOccupants();
           this.occupants.browserStorage = new Backbone.BrowserStorage.session(b64_sha1("converse.occupants-".concat(_converse.bare_jid).concat(this.get('jid'))));
           this.occupants.browserStorage = new Backbone.BrowserStorage.session(b64_sha1("converse.occupants-".concat(_converse.bare_jid).concat(this.get('jid'))));
+          this.occupants.chatroom = this;
           this.registerHandlers();
           this.registerHandlers();
           this.on('change:chat_state', this.sendChatState, this);
           this.on('change:chat_state', this.sendChatState, this);
         },
         },
@@ -48232,26 +48254,6 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
 
 
           return this;
           return this;
         },
         },
-        findOccupant: function findOccupant(data) {
-          /* Try to find an existing occupant based on the passed in
-           * data object.
-           *
-           * If we have a JID, we use that as lookup variable,
-           * otherwise we use the nick. We don't always have both,
-           * but should have at least one or the other.
-           */
-          var jid = Strophe.getBareJidFromJid(data.jid);
-
-          if (jid !== null) {
-            return this.occupants.where({
-              'jid': jid
-            }).pop();
-          } else {
-            return this.occupants.where({
-              'nick': data.nick
-            }).pop();
-          }
-        },
         updateOccupantsOnPresence: function updateOccupantsOnPresence(pres) {
         updateOccupantsOnPresence: function updateOccupantsOnPresence(pres) {
           /* Given a presence stanza, update the occupant model
           /* Given a presence stanza, update the occupant model
            * based on its contents.
            * based on its contents.
@@ -48265,7 +48267,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
             return true;
             return true;
           }
           }
 
 
-          var occupant = this.findOccupant(data);
+          var occupant = this.occupants.findOccupant(data);
 
 
           if (data.type === 'unavailable') {
           if (data.type === 'unavailable') {
             if (occupant) {
             if (occupant) {
@@ -48305,7 +48307,8 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
             'from': from,
             'from': from,
             'nick': Strophe.getResourceFromJid(from),
             'nick': Strophe.getResourceFromJid(from),
             'type': pres.getAttribute("type"),
             'type': pres.getAttribute("type"),
-            'states': []
+            'states': [],
+            'show': 'online'
           };
           };
 
 
           _.each(pres.childNodes, function (child) {
           _.each(pres.childNodes, function (child) {
@@ -48530,6 +48533,9 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
         }
         }
       });
       });
       _converse.ChatRoomOccupant = Backbone.Model.extend({
       _converse.ChatRoomOccupant = Backbone.Model.extend({
+        defaults: {
+          'show': 'offline'
+        },
         initialize: function initialize(attributes) {
         initialize: function initialize(attributes) {
           this.set(_.extend({
           this.set(_.extend({
             'id': _converse.connection.getUniqueId()
             'id': _converse.connection.getUniqueId()
@@ -48565,6 +48571,43 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat
           } else {
           } else {
             return MUC_ROLE_WEIGHTS[role1] < MUC_ROLE_WEIGHTS[role2] ? -1 : 1;
             return MUC_ROLE_WEIGHTS[role1] < MUC_ROLE_WEIGHTS[role2] ? -1 : 1;
           }
           }
+        },
+        fetchMembers: function fetchMembers() {
+          var _this10 = this;
+
+          this.chatroom.getJidsWithAffiliations(['member', 'owner', 'admin']).then(function (jids) {
+            _.each(jids, function (attrs) {
+              var occupant = _this10.findOccupant({
+                'jid': attrs.jid
+              });
+
+              if (occupant) {
+                occupant.save(attrs);
+              } else {
+                _this10.create(attrs);
+              }
+            });
+          }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
+        },
+        findOccupant: function findOccupant(data) {
+          /* Try to find an existing occupant based on the passed in
+           * data object.
+           *
+           * If we have a JID, we use that as lookup variable,
+           * otherwise we use the nick. We don't always have both,
+           * but should have at least one or the other.
+           */
+          var jid = Strophe.getBareJidFromJid(data.jid);
+
+          if (jid !== null) {
+            return this.where({
+              'jid': jid
+            }).pop();
+          } else {
+            return this.where({
+              'nick': data.nick
+            }).pop();
+          }
         }
         }
       });
       });
       _converse.RoomsPanelModel = Backbone.Model.extend({
       _converse.RoomsPanelModel = Backbone.Model.extend({
@@ -56441,6 +56484,24 @@ define("awesomplete", (function (global) {
     };
     };
 }(this)));
 }(this)));
 
 
+
+define('tpl!alert_modal', ['lodash'], function(_) {return function(o) {
+var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
+function print() { __p += __j.call(arguments, '') }
+__p += '<div class="modal" tabindex="-1" role="dialog">\n  <div class="modal-dialog" role="document">\n    <div class="modal-content">\n      <div class="modal-header ' +
+__e(o.type) +
+'">\n        <h5 class="modal-title">' +
+__e(o.title) +
+'</h5>\n        <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n          <span aria-hidden="true">&times;</span>\n        </button>\n      </div>\n      <div class="modal-body">';
+ _.each(o.messages, function (message) { ;
+__p += '\n          <p>' +
+__e(message) +
+'</p>\n      ';
+ }) ;
+__p += '\n    </div>\n    </div>\n  </div>\n</div>\n';
+return __p
+};});
+
 // Converse.js
 // Converse.js
 // http://conversejs.org
 // http://conversejs.org
 //
 //
@@ -56448,11 +56509,15 @@ define("awesomplete", (function (global) {
 // Licensed under the Mozilla Public License (MPLv2)
 // Licensed under the Mozilla Public License (MPLv2)
 (function (root, factory) {
 (function (root, factory) {
   if (typeof define === 'function' && define.amd) {
   if (typeof define === 'function' && define.amd) {
-    define('converse-modal',["converse-core", "bootstrap", "underscore", "backbone", "backbone.vdomview"], factory);
+    define('converse-modal',["converse-core", "tpl!alert_modal", "bootstrap", "backbone.vdomview"], factory);
   }
   }
-})(this, function (converse, bootstrap, _, Backbone) {
+})(this, function (converse, tpl_alert_modal, bootstrap) {
   "use strict";
   "use strict";
 
 
+  var _converse$env = converse.env,
+      Strophe = _converse$env.Strophe,
+      Backbone = _converse$env.Backbone,
+      _ = _converse$env._;
   converse.plugins.add('converse-modal', {
   converse.plugins.add('converse-modal', {
     initialize: function initialize() {
     initialize: function initialize() {
       var _converse = this._converse;
       var _converse = this._converse;
@@ -56477,12 +56542,66 @@ define("awesomplete", (function (global) {
           container_el.insertAdjacentElement('beforeEnd', this.el);
           container_el.insertAdjacentElement('beforeEnd', this.el);
         },
         },
         show: function show(ev) {
         show: function show(ev) {
-          ev.preventDefault();
-          this.trigger_el = ev.target;
-          this.trigger_el.classList.add('selected');
+          if (ev) {
+            ev.preventDefault();
+            this.trigger_el = ev.target;
+            this.trigger_el.classList.add('selected');
+          }
+
           this.modal.show();
           this.modal.show();
         }
         }
       });
       });
+      _converse.Alert = _converse.BootstrapModal.extend({
+        initialize: function initialize() {
+          _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
+
+          this.model.on('change', this.render, this);
+        },
+        toHTML: function toHTML() {
+          return tpl_alert_modal(this.model.toJSON());
+        }
+      });
+      /************************ BEGIN API ************************/
+      // We extend the default converse.js API to add methods specific to MUC chat rooms.
+
+      var alert;
+
+      _.extend(_converse.api, {
+        'alert': {
+          'show': function show(type, title, messages) {
+            if (_.isString(messages)) {
+              messages = [messages];
+            }
+
+            if (type === Strophe.LogLevel.ERROR) {
+              type = 'alert-danger';
+            } else if (type === Strophe.LogLevel.INFO) {
+              type = 'alert-info';
+            } else if (type === Strophe.LogLevel.WARN) {
+              type = 'alert-warning';
+            }
+
+            if (_.isUndefined(alert)) {
+              var model = new Backbone.Model({
+                'title': title,
+                'messages': messages,
+                'type': type
+              });
+              alert = new _converse.Alert({
+                'model': model
+              });
+            } else {
+              alert.model.set({
+                'title': title,
+                'messages': messages,
+                'type': type
+              });
+            }
+
+            alert.show();
+          }
+        }
+      });
     }
     }
   });
   });
 });
 });
@@ -60416,22 +60535,14 @@ return __p
           var _this2 = this;
           var _this2 = this;
 
 
           _converse.api.vcard.set(data).then(function () {
           _converse.api.vcard.set(data).then(function () {
-            _converse.api.vcard.update(_this2.model.vcard, true);
-
-            var html = tpl_alert({
-              'message': __('Profile data succesfully saved'),
-              'type': 'alert-primary'
-            });
-            body.insertAdjacentHTML('afterBegin', html);
+            return _converse.api.vcard.update(_this2.model.vcard, true);
           }).catch(function (err) {
           }).catch(function (err) {
             _converse.log(err, Strophe.LogLevel.FATAL);
             _converse.log(err, Strophe.LogLevel.FATAL);
 
 
-            var html = tpl_alert({
-              'message': __('An error happened while trying to save your profile data'),
-              'type': 'alert-danger'
-            });
-            body.insertAdjacentHTML('afterBegin', html);
+            _converse.api.alert.show(Strophe.LogLevel.ERROR, __('Error'), [__("Sorry, an error happened while trying to save your profile data."), __("You can check your browser's developer console for any error output.")]);
           });
           });
+
+          this.modal.hide();
         },
         },
         onFormSubmitted: function onFormSubmitted(ev) {
         onFormSubmitted: function onFormSubmitted(ev) {
           var _this3 = this;
           var _this3 = this;
@@ -63250,7 +63361,7 @@ __p += '\n';
  if (o.nonanonymous) { ;
  if (o.nonanonymous) { ;
 __p += '\n<li class="feature" title="' +
 __p += '\n<li class="feature" title="' +
 __e( o.tt_nonanonymous ) +
 __e( o.tt_nonanonymous ) +
-'"><span class="fa fa-idcard-dark"></span>' +
+'"><span class="fa fa-id-card"></span>' +
 __e( o.label_nonanonymous ) +
 __e( o.label_nonanonymous ) +
 '</li>\n';
 '</li>\n';
  } ;
  } ;
@@ -63429,9 +63540,7 @@ return __p
 define('tpl!occupant', ['lodash'], function(_) {return function(o) {
 define('tpl!occupant', ['lodash'], function(_) {return function(o) {
 var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
 var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
 function print() { __p += __j.call(arguments, '') }
 function print() { __p += __j.call(arguments, '') }
-__p += '<li class="' +
-__e( o.role ) +
-' occupant" id="' +
+__p += '<li class="occupant" id="' +
 __e( o.id ) +
 __e( o.id ) +
 '"\n    ';
 '"\n    ';
  if (o.role === "moderator") { ;
  if (o.role === "moderator") { ;
@@ -63444,11 +63553,11 @@ __e( o.hint_occupant ) +
 '"\n    ';
 '"\n    ';
  } ;
  } ;
 __p += '\n    ';
 __p += '\n    ';
- if (o.role === "occupant") { ;
+ if (o.role === "participant") { ;
 __p += '\n       title="' +
 __p += '\n       title="' +
 __e( o.jid ) +
 __e( o.jid ) +
 ' ' +
 ' ' +
-__e( o.desc_occupant ) +
+__e( o.desc_participant ) +
 ' ' +
 ' ' +
 __e( o.hint_occupant ) +
 __e( o.hint_occupant ) +
 '"\n    ';
 '"\n    ';
@@ -63464,20 +63573,50 @@ __e( o.hint_occupant ) +
 '"\n    ';
 '"\n    ';
  } ;
  } ;
 __p += '\n    ';
 __p += '\n    ';
- if (!_.includes(["visitor", "occupant", "moderator"], o.role)) { ;
+ if (!_.includes(["visitor", "participant", "moderator"], o.role)) { ;
 __p += '\n       title="' +
 __p += '\n       title="' +
 __e( o.jid ) +
 __e( o.jid ) +
 ' ' +
 ' ' +
 __e( o.hint_occupant ) +
 __e( o.hint_occupant ) +
-'"\n       ';
+'"\n    ';
  } ;
  } ;
-__p += '><div class="occupant-status occupant-' +
+__p += '>\n    <div class="row no-gutters">\n        <div class="col-auto">\n            <div class="occupant-status occupant-' +
 __e(o.show) +
 __e(o.show) +
 ' circle" title="' +
 ' circle" title="' +
 __e(o.hint_show) +
 __e(o.hint_show) +
-'"></div>' +
-__e(o.nick) +
-'</li>\n';
+'"></div>\n        </div>\n        <div class="col">\n            <span class="occupant-nick">' +
+__e(o.nick || o.jid) +
+'</span>\n            ';
+ if (o.affiliation === "owner") { ;
+__p += '\n                <span class="badge badge-danger">' +
+__e(o.label_owner) +
+'</span>\n            ';
+ } ;
+__p += '\n            ';
+ if (o.affiliation === "admin") { ;
+__p += '\n                <span class="badge badge-info">' +
+__e(o.label_admin) +
+'</span>\n            ';
+ } ;
+__p += '\n            ';
+ if (o.affiliation === "member") { ;
+__p += '\n                <span class="badge badge-info">' +
+__e(o.label_member) +
+'</span>\n            ';
+ } ;
+__p += '\n\n            ';
+ if (o.role === "moderator") { ;
+__p += '\n                <span class="badge badge-info">' +
+__e(o.label_moderator) +
+'</span>\n            ';
+ } ;
+__p += '\n            ';
+ if (o.role === "visitor") { ;
+__p += '\n                <span class="badge badge-secondary">' +
+__e(o.label_visitor) +
+'</span>\n            ';
+ } ;
+__p += '\n        </div>\n    </div>\n</li>\n';
 return __p
 return __p
 };});
 };});
 
 
@@ -64090,9 +64229,7 @@ return __p
                 return;
                 return;
               }
               }
 
 
-              _this3.join();
-
-              _this3.fetchMessages();
+              _this3.populateAndJoin();
 
 
               _converse.emit('chatRoomOpened', _this3);
               _converse.emit('chatRoomOpened', _this3);
             };
             };
@@ -64503,6 +64640,11 @@ return __p
             this.showStatusMessages(pres);
             this.showStatusMessages(pres);
           }
           }
         },
         },
+        populateAndJoin: function populateAndJoin() {
+          this.model.occupants.fetchMembers();
+          this.join();
+          this.fetchMessages();
+        },
         join: function join(nick, password) {
         join: function join(nick, password) {
           /* Join the chat room.
           /* Join the chat room.
            *
            *
@@ -65120,15 +65262,20 @@ return __p
           this.model.on('change', this.render, this);
           this.model.on('change', this.render, this);
         },
         },
         toHTML: function toHTML() {
         toHTML: function toHTML() {
-          var show = this.model.get('show') || 'online';
+          var show = this.model.get('show');
           return tpl_occupant(_.extend({
           return tpl_occupant(_.extend({
             'jid': '',
             'jid': '',
             'show': show,
             'show': show,
             'hint_show': _converse.PRETTY_CHAT_STATUS[show],
             'hint_show': _converse.PRETTY_CHAT_STATUS[show],
             'hint_occupant': __('Click to mention %1$s in your message.', this.model.get('nick')),
             'hint_occupant': __('Click to mention %1$s in your message.', this.model.get('nick')),
             'desc_moderator': __('This user is a moderator.'),
             'desc_moderator': __('This user is a moderator.'),
-            'desc_occupant': __('This user can send messages in this room.'),
-            'desc_visitor': __('This user can NOT send messages in this room.')
+            'desc_participant': __('This user can send messages in this room.'),
+            'desc_visitor': __('This user can NOT send messages in this room.'),
+            'label_moderator': __('Moderator'),
+            'label_visitor': __('Visitor'),
+            'label_owner': __('Owner'),
+            'label_member': __('Member'),
+            'label_admin': __('Admin')
           }, this.model.toJSON()));
           }, this.model.toJSON()));
         },
         },
         destroy: function destroy() {
         destroy: function destroy() {
@@ -65403,8 +65550,7 @@ return __p
           if (view.model.get('type') === converse.CHATROOMS_TYPE) {
           if (view.model.get('type') === converse.CHATROOMS_TYPE) {
             view.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
             view.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
             view.model.registerHandlers();
             view.model.registerHandlers();
-            view.join();
-            view.fetchMessages();
+            view.populateAndJoin();
           }
           }
         });
         });
       }
       }