diff --git a/CHANGES.md b/CHANGES.md index 680f714b7..31214a1f8 100755 --- a/CHANGES.md +++ b/CHANGES.md @@ -47,6 +47,7 @@ - Consolidate error and validation reporting on the registration form. - Don't close the emojis panel after inserting an emoji. - Focus the message textarea when the emojis panel is opened or closed. +- MUC chatroom occupants are now sorted alphabetically and according to their roles. ### Technical changes - Converse.js now includes a [Virtual DOM](https://github.com/snabbdom/snabbdom) diff --git a/css/converse.css b/css/converse.css index f0a6b2fb9..89ce7b9eb 100644 --- a/css/converse.css +++ b/css/converse.css @@ -2605,7 +2605,8 @@ padding: .5em; } #converse-embedded-chat .chatroom .box-flyout .chatroom-body .occupants ul, #conversejs .chatroom .box-flyout .chatroom-body .occupants ul { - padding: 0.3em 0; + padding: 0.5em 0 0 0; + margin-bottom: 0.5em; overflow-x: hidden; overflow-y: auto; list-style: none; } diff --git a/css/inverse.css b/css/inverse.css index 84ebec0f0..c7133ec50 100644 --- a/css/inverse.css +++ b/css/inverse.css @@ -2767,7 +2767,8 @@ body { padding: .5em; } #converse-embedded-chat .chatroom .box-flyout .chatroom-body .occupants ul, #conversejs .chatroom .box-flyout .chatroom-body .occupants ul { - padding: 0.3em 0; + padding: 0.5em 0 0 0; + margin-bottom: 0.5em; overflow-x: hidden; overflow-y: auto; list-style: none; } diff --git a/sass/_chatrooms.scss b/sass/_chatrooms.scss index 5252e1c50..2c47a3c4d 100644 --- a/sass/_chatrooms.scss +++ b/sass/_chatrooms.scss @@ -131,7 +131,8 @@ } } ul { - padding: 0.3em 0; + padding: 0.5em 0 0 0; + margin-bottom: 0.5em; overflow-x: hidden; overflow-y: auto; list-style: none; diff --git a/spec/chatroom.js b/spec/chatroom.js index 3bda751b1..93d919396 100644 --- a/spec/chatroom.js +++ b/spec/chatroom.js @@ -838,12 +838,13 @@ test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function() { var name; var view = _converse.chatboxviews.get('lounge@localhost'), - $occupants = view.$('.occupant-list'); - var presence, role; + occupants = view.el.querySelector('.occupant-list'); + var presence, role, jid, model; for (var i=0; i nick2? 1 : 0); + } else { + return MUC_ROLE_WEIGHTS[role1] < MUC_ROLE_WEIGHTS[role2] ? -1 : 1; + } + }, }); _converse.ChatRoomOccupantsView = Backbone.Overview.extend({ @@ -2144,6 +2148,11 @@ initialize () { this.model.on("add", this.onOccupantAdded, this); + this.model.on("change:role", (occupant) => { + this.model.sort(); + this.positionOccupant(occupant); + }); + this.chatroomview = this.model.chatroomview; this.chatroomview.model.on('change:open', this.renderInviteWidget, this); this.chatroomview.model.on('change:affiliation', this.renderInviteWidget, this); @@ -2160,6 +2169,17 @@ this.chatroomview.model.on('change:temporary', this.onFeatureChanged, this); this.chatroomview.model.on('change:unmoderated', this.onFeatureChanged, this); this.chatroomview.model.on('change:unsecured', this.onFeatureChanged, this); + + const id = b64_sha1(`converse.occupants${_converse.bare_jid}${this.chatroomview.model.get('jid')}`); + this.model.browserStorage = new Backbone.BrowserStorage.session(id); + this.render(); + this.model.fetch({ + 'add': true, + 'silent': true, + 'success': () => { + this.model.each(this.onOccupantAdded.bind(this)); + } + }); }, render () { @@ -2269,6 +2289,36 @@ `height: calc(100% - ${el.offsetHeight}px - 5em);`; }, + positionOccupant (occupant) { + /* Positions an occupant correctly in the list of + * occupants. + * + * IMPORTANT: there's an important implicit assumption being + * made here. And that is that initially this method gets called + * for each occupant in the right positional order. + * + * In other words, it gets called for the 0th, then the + * 1st, then the 2nd, 3rd and so on. + * + * That's why we call it in the "success" handler after + * fetching the occupants, so that we know we have ALL of + * them and that they're sorted. + */ + const view = this.get(occupant.get('id')); + view.render(); + const list = this.el.querySelector('.occupant-list'); + const index = this.model.indexOf(view.model); + if (index === 0) { + list.insertAdjacentElement('afterbegin', view.el); + } else if (index === (this.model.length-1)) { + list.insertAdjacentElement('beforeend', view.el); + } else { + const neighbour = list.querySelector('li:nth-child('+index+')'); + neighbour.insertAdjacentElement('afterend', view.el); + } + return view; + }, + onOccupantAdded (item) { let view = this.get(item.get('id')); if (!view) { @@ -2277,11 +2327,10 @@ new _converse.ChatRoomOccupantView({model: item}) ); } else { - delete view.model; // Remove ref to old model to help garbage collection view.model = item; view.initialize(); } - this.$('.occupant-list').append(view.render().$el); + this.positionOccupant(item); }, parsePresence (pres) {