Commit 859bc061 authored by JC Brand's avatar JC Brand

Fixes #1094

- Show users who are registered on the different members lists
- Show badges indicating user's roles and affiliations
parent 28ec1509
......@@ -8309,9 +8309,8 @@ body.reset {
display: block;
font-size: 12px;
overflow: hidden;
padding: 0.2em 0.5em 0.2em 0;
text-overflow: ellipsis;
white-space: nowrap; }
padding: 0.25em 0.25em 0.25em 0;
text-overflow: ellipsis; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li .fa,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li .fa {
margin-right: 0.5em; }
......@@ -8321,10 +8320,16 @@ body.reset {
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.occupant {
cursor: pointer; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant div.row.no-gutters,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.occupant div.row.no-gutters {
flex-wrap: nowrap; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .badge,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .badge {
margin-top: 0.125rem; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status {
display: inline-block;
margin-right: 0.5em;
margin: 0.5em 0.5em 0.5em 0;
width: 0.5em;
height: 0.5em; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status.occupant-online, #conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status.occupant-chat,
......@@ -8340,12 +8345,9 @@ body.reset {
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status.occupant-xa,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status.occupant-xa {
background-color: orange; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.moderator,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.moderator {
color: #D24E2B; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.visitor,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.visitor {
color: #A8ABA1; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status.occupant-offline,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status.occupant-offline {
background-color: darkgrey; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .chatroom-form-container,
#conversejs .chatroom .box-flyout .chatroom-body .chatroom-form-container {
background-color: white;
......
......@@ -8449,9 +8449,8 @@ body {
display: block;
font-size: 14px;
overflow: hidden;
padding: 0.2em 0.5em 0.2em 0;
text-overflow: ellipsis;
white-space: nowrap; }
padding: 0.25em 0.25em 0.25em 0;
text-overflow: ellipsis; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li .fa,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li .fa {
margin-right: 0.5em; }
......@@ -8461,10 +8460,16 @@ body {
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.occupant {
cursor: pointer; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant div.row.no-gutters,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.occupant div.row.no-gutters {
flex-wrap: nowrap; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .badge,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .badge {
margin-top: 0.125rem; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status {
display: inline-block;
margin-right: 0.5em;
margin: 0.5em 0.5em 0.5em 0;
width: 0.5em;
height: 0.5em; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status.occupant-online, #conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status.occupant-chat,
......@@ -8480,12 +8485,9 @@ body {
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status.occupant-xa,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status.occupant-xa {
background-color: orange; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.moderator,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.moderator {
color: #D24E2B; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.visitor,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.visitor {
color: #A8ABA1; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status.occupant-offline,
#conversejs .chatroom .box-flyout .chatroom-body .occupants ul li.occupant .occupant-status.occupant-offline {
background-color: darkgrey; }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .chatroom-form-container,
#conversejs .chatroom .box-flyout .chatroom-body .chatroom-form-container {
background-color: white;
......
......@@ -233,15 +233,21 @@
<ul class="occupant-list">
<li class="moderator occupant" title="Click to mention leia in your message.">
<div class="occupant-status occupant-online circle" title="Online"></div>Juliet Capulet</li>
<div class="occupant-status occupant-online circle" title="Online"></div>Juliet Capulet
<span class="badge badge-info">Owner</span>
</li>
<li class="moderator occupant" title="Click to mention leia in your message.">
<div class="occupant-status occupant-online circle" title="Online"></div>Romeo Montague</li>
<li class="participant occupant" title="Click to mention leia in your message.">
<div class="occupant-status occupant-online circle" title="Online"></div>Mercutio</li>
<div class="occupant-status occupant-online circle" title="Online"></div>Romeo Montague
<span class="badge badge-info">Moderator</span>
</li>
<li class="participant occupant" title="Click to mention leia in your message.">
<div class="occupant-status occupant-away circle" title="Away"></div>Lady Montague</li>
<li class="participant occupant" title="Click to mention leia in your message.">
<div class="occupant-status occupant-online circle" title="Online"></div>Lord Montague</li>
<div class="occupant-status occupant-online circle" title="Online"></div>Mercutio
<span class="badge badge-secondary">Visitor</span>
</li>
<li class="participant occupant" title="Click to mention leia in your message.">
<div class="occupant-status occupant-offline circle" title="Offline"></div>Lord Montague</li>
<li class="participant occupant" title="Click to mention leia in your message.">
<div class="occupant-status occupant-online circle" title="Online"></div>Friar Laurence</li>
<li class="participant occupant" title="Click to mention leia in your message.">
......
......@@ -172,9 +172,8 @@
display: block;
font-size: $font-size-small;
overflow: hidden;
padding: 0.2em 0.5em 0.2em 0;
padding: 0.25em 0.25em 0.25em 0;
text-overflow: ellipsis;
white-space: nowrap;
.fa {
margin-right: 0.5em;
}
......@@ -183,11 +182,21 @@
}
&.occupant {
cursor: pointer;
div.row.no-gutters {
flex-wrap: nowrap;
}
.badge {
margin-top: 0.125rem;
}
.occupant-status {
display: inline-block;
margin-right: 0.5em;
margin: 0.5em 0.5em 0.5em 0;
width: 0.5em;
height: 0.5em;
&.occupant-online,
&.occupant-chat {
background-color: #1A9707;
......@@ -201,14 +210,11 @@
&.occupant-xa {
background-color: orange;
}
&.occupant-offline {
background-color: darkgrey;
}
}
}
&.moderator {
color: $moderator-color;
}
&.visitor {
color: $visitor-color;
}
}
}
}
......
......@@ -1142,8 +1142,7 @@
expect(occupants.querySelectorAll('li').length).toBe(2+i);
model = view.occupantsview.model.where({'nick': name})[0];
var index = view.occupantsview.model.indexOf(model);
expect(occupants.querySelectorAll('li')[index].textContent).toBe(mock.chatroom_names[i]);
expect($(occupants.querySelectorAll('li')[index]).hasClass('moderator')).toBe(role === "moderator");
expect(occupants.querySelectorAll('li .occupant-nick')[index].textContent.trim()).toBe(mock.chatroom_names[i]);
}
// Test users leaving the room
......@@ -1166,7 +1165,7 @@
expect(occupants.querySelectorAll('li').length).toBe(i+1);
}
done();
});
}).catch(_.partial(console.error, _));
}));
it("escapes occupant nicknames when rendering them, to avoid JS-injection attacks",
......@@ -1193,9 +1192,9 @@
_converse.connection._dataRecv(test_utils.createRequest(presence));
var view = _converse.chatboxviews.get('lounge@localhost');
var occupants = view.el.querySelector('.occupant-list').querySelectorAll('li');
var occupants = view.el.querySelector('.occupant-list').querySelectorAll('li .occupant-nick');
expect(occupants.length).toBe(2);
expect($(occupants).first().text()).toBe("&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;");
expect($(occupants).first().text().trim()).toBe("&lt;img src=&quot;x&quot; onerror=&quot;alert(123)&quot;/&gt;");
done();
});
}));
......@@ -1208,6 +1207,13 @@
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
var view = _converse.chatboxviews.get('lounge@localhost');
var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
var occupants = view.el.querySelector('.occupant-list').querySelectorAll('li');
expect(occupants.length).toBe(1);
expect($(occupants).first().find('.occupant-nick').text().trim()).toBe("dummy");
expect($(occupants).first().find('.badge').length).toBe(1);
expect($(occupants).first().find('.badge').first().text()).toBe('Member');
var presence = $pres({
to:'dummy@localhost/pda',
from:'lounge@localhost/moderatorman'
......@@ -1220,11 +1226,14 @@
.c('status').attrs({code:'110'}).nodeTree;
_converse.connection._dataRecv(test_utils.createRequest(presence));
var occupants = view.el.querySelector('.occupant-list').querySelectorAll('li');
occupants = view.el.querySelector('.occupant-list').querySelectorAll('li');
expect(occupants.length).toBe(2);
expect($(occupants).first().text()).toBe("moderatorman");
expect($(occupants).last().text()).toBe("dummy");
expect($(occupants).first().attr('class').indexOf('moderator')).not.toBe(-1);
expect($(occupants).first().find('.occupant-nick').text().trim()).toBe("moderatorman");
expect($(occupants).last().find('.occupant-nick').text().trim()).toBe("dummy");
expect($(occupants).first().find('.badge').length).toBe(2);
expect($(occupants).first().find('.badge').first().text()).toBe('Admin');
expect($(occupants).first().find('.badge').last().text()).toBe('Moderator');
expect($(occupants).first().attr('title')).toBe(
contact_jid + ' This user is a moderator. Click to mention moderatorman in your message.'
);
......@@ -1242,13 +1251,14 @@
_converse.connection._dataRecv(test_utils.createRequest(presence));
occupants = view.el.querySelector('.occupant-list').querySelectorAll('li');
expect($(occupants).last().text()).toBe("visitorwoman");
expect($(occupants).last().attr('class').indexOf('visitor')).not.toBe(-1);
expect($(occupants).last().find('.occupant-nick').text().trim()).toBe("visitorwoman");
expect($(occupants).last().find('.badge').length).toBe(1);
expect($(occupants).last().find('.badge').last().text()).toBe('Visitor');
expect($(occupants).last().attr('title')).toBe(
contact_jid + ' This user can NOT send messages in this room. Click to mention visitorwoman in your message.'
);
done();
});
}).catch(_.partial(console.error, _));
}));
it("will use the user's reserved nickname, if it exists",
......@@ -1656,7 +1666,7 @@
var $occupants = $(view.el.querySelector('.occupant-list'));
expect($occupants.children().length).toBe(1);
expect($occupants.children().first(0).text()).toBe("oldnick");
expect($occupants.children().first(0).find('.occupant-nick').text().trim()).toBe("oldnick");
expect($chat_content.find('div.chat-info').length).toBe(1);
expect($chat_content.find('div.chat-info:first').html()).toBe("oldnick has entered the room");
......
......@@ -537,8 +537,7 @@
// collection anymore).
return;
}
this.join();
this.fetchMessages();
this.populateAndJoin();
_converse.emit('chatRoomOpened', this);
}
this.model.getRoomFeatures().then(handler, handler);
......@@ -919,6 +918,12 @@
}
},
populateAndJoin () {
this.model.occupants.fetchMembers();
this.join();
this.fetchMessages();
},
join (nick, password) {
/* Join the chat room.
*
......@@ -1536,7 +1541,7 @@
},
toHTML () {
const show = this.model.get('show') || 'online';
const show = this.model.get('show');
return tpl_occupant(
_.extend(
{ 'jid': '',
......@@ -1544,8 +1549,13 @@
'hint_show': _converse.PRETTY_CHAT_STATUS[show],
'hint_occupant': __('Click to mention %1$s in your message.', this.model.get('nick')),
'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())
);
},
......@@ -1824,8 +1834,7 @@
if (view.model.get('type') === converse.CHATROOMS_TYPE) {
view.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
view.model.registerHandlers();
view.join();
view.fetchMessages();
view.populateAndJoin();
}
});
}
......
......@@ -22,7 +22,7 @@
'moderator': 1,
'participant': 2,
'visitor': 3,
'none': 4,
'none': 2,
};
const { Strophe, Backbone, Promise, $iq, $build, $msg, $pres, b64_sha1, sizzle, _, moment } = converse.env;
......@@ -187,6 +187,8 @@
this.occupants.browserStorage = new Backbone.BrowserStorage.session(
b64_sha1(`converse.occupants-${_converse.bare_jid}${this.get('jid')}`)
);
this.occupants.chatroom = this;
this.registerHandlers();
this.on('change:chat_state', this.sendChatState, this);
},
......@@ -726,22 +728,6 @@
return this;
},
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.
*/
const 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 (pres) {
/* Given a presence stanza, update the occupant model
* based on its contents.
......@@ -753,7 +739,7 @@
if (data.type === 'error') {
return true;
}
const occupant = this.findOccupant(data);
const occupant = this.occupants.findOccupant(data);
if (data.type === 'unavailable') {
if (occupant) {
// Even before destroying, we set the new data, so
......@@ -788,7 +774,8 @@
'from': from,
'nick': Strophe.getResourceFromJid(from),
'type': pres.getAttribute("type"),
'states': []
'states': [],
'show': 'online'
};
_.each(pres.childNodes, function (child) {
switch (child.nodeName) {
......@@ -987,6 +974,11 @@
_converse.ChatRoomOccupant = Backbone.Model.extend({
defaults: {
'show': 'offline'
},
initialize (attributes) {
this.set(_.extend({
'id': _converse.connection.getUniqueId(),
......@@ -1014,13 +1006,48 @@
const role1 = occupant1.get('role') || 'none';
const role2 = occupant2.get('role') || 'none';
if (MUC_ROLE_WEIGHTS[role1] === MUC_ROLE_WEIGHTS[role2]) {
const nick1 = occupant1.get('nick').toLowerCase();
const nick2 = occupant2.get('nick').toLowerCase();
return nick1 < nick2 ? -1 : (nick1 > nick2? 1 : 0);
try {
const nick1 = occupant1.get('nick').toLowerCase();
const nick2 = occupant2.get('nick').toLowerCase();
return nick1 < nick2 ? -1 : (nick1 > nick2? 1 : 0);
} catch (e) {
debugger;
}
} else {
return MUC_ROLE_WEIGHTS[role1] < MUC_ROLE_WEIGHTS[role2] ? -1 : 1;
}
},
fetchMembers () {
this.chatroom.getJidsWithAffiliations(['member', 'owner', 'admin'])
.then((jids) => {
_.each(jids, (attrs) => {
const occupant = this.findOccupant({'jid': attrs.jid});
if (occupant) {
occupant.save(attrs);
} else {
this.create(attrs);
}
});
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
},
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.
*/
const jid = Strophe.getBareJidFromJid(data.jid);
if (jid !== null) {
return this.where({'jid': jid}).pop();
} else {
return this.where({'nick': data.nick}).pop();
}
},
});
......
<li class="{{{ o.role }}} occupant" id="{{{ o.id }}}"
<li class="occupant" id="{{{ o.id }}}"
{[ if (o.role === "moderator") { ]}
title="{{{ o.jid }}} {{{ o.desc_moderator }}} {{{ o.hint_occupant }}}"
{[ } ]}
{[ if (o.role === "occupant") { ]}
title="{{{ o.jid }}} {{{ o.desc_occupant }}} {{{ o.hint_occupant }}}"
{[ if (o.role === "participant") { ]}
title="{{{ o.jid }}} {{{ o.desc_participant }}} {{{ o.hint_occupant }}}"
{[ } ]}
{[ if (o.role === "visitor") { ]}
title="{{{ o.jid }}} {{{ o.desc_visitor }}} {{{ o.hint_occupant }}}"
{[ } ]}
{[ if (!_.includes(["visitor", "occupant", "moderator"], o.role)) { ]}
{[ if (!_.includes(["visitor", "participant", "moderator"], o.role)) { ]}
title="{{{ o.jid }}} {{{ o.hint_occupant }}}"
{[ } ]}><div class="occupant-status occupant-{{{o.show}}} circle" title="{{{o.hint_show}}}"></div>{{{o.nick}}}</li>
{[ } ]}>
<div class="row no-gutters">
<div class="col-auto">
<div class="occupant-status occupant-{{{o.show}}} circle" title="{{{o.hint_show}}}"></div>
</div>
<div class="col">
<span class="occupant-nick">
{{{o.nick || o.jid}}}
</span>
{[ if (o.affiliation === "owner") { ]}
<span class="badge badge-danger">{{{o.label_owner}}}</span>
{[ } ]}
{[ if (o.affiliation === "admin") { ]}
<span class="badge badge-info">{{{o.label_admin}}}</span>
{[ } ]}
{[ if (o.affiliation === "member") { ]}
<span class="badge badge-info">{{{o.label_member}}}</span>
{[ } ]}
{[ if (o.role === "moderator") { ]}
<span class="badge badge-info">{{{o.label_moderator}}}</span>
{[ } ]}
{[ if (o.role === "visitor") { ]}
<span class="badge badge-secondary">{{{o.label_visitor}}}</span>
{[ } ]}
</div>
</div>
</li>
......@@ -72,15 +72,32 @@
};
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="${Strophe.NS.MUC_ADMIN}"] item`, iq),
(item) => ({
'jid': item.getAttribute('jid'),
'affiliation': item.getAttribute('affiliation'),
})
(item) => {
const data = {
'affiliation': item.getAttribute('affiliation'),
}
const 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;
}
const nick = item.getAttribute('nick');
if (nick) {
data['nick'] = nick;
}
const role = item.getAttribute('role');
if (role) {
data['role'] = nick;
}
return data;
}
);
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment