Commit bb9e394e authored by JC Brand's avatar JC Brand

muc: handle join/leave notifications similarly to CSNs

Remove the `muc_show_join_leave_status` config setting.
We don't show the optional statuses at all now.
parent c20dbbbb
......@@ -25,6 +25,7 @@ Soon we'll deprecate the latter, so prepare now.
- Replace Backbone with [Skeletor](https://github.com/skeletorjs/skeletor)
- Start using [lit-html](https://lit-html.polymer-project.org/) instead of lodash for templating.
- [muc_fetch_members](https://conversejs.org/docs/html/configuration.html#muc-fetch-members) now also accepts an array of affiliations to fetch.
- Remove the configuration setting `muc_show_join_leave_status`. The optional status message is no longer shown at all.
## 6.0.0 (2020-01-09)
......
......@@ -1223,17 +1223,6 @@ muc_show_join_leave
Determines whether Converse will show info messages inside a chatroom
whenever a user joins or leaves it.
muc_show_join_leave_status
--------------------------
* Default; ``true``
Determines whether Converse shows the optionally included status message when a
user joins or leaves the MUC. This setting only has an effect if
``muc_show_join_leave`` is set to ``true``.
See https://xmpp.org/extensions/xep-0045.html#changepres
muc_show_logs_before_join
-------------------------
......
This diff is collapsed.
......@@ -21,8 +21,7 @@
const muc_jid = 'lounge@montague.lit';
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.api.chatviews.get(muc_jid);
await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length);
const presence = u.toStanza(`
let presence = u.toStanza(`
<presence xmlns="jabber:client" to="${_converse.jid}" from="${muc_jid}/romeo">
<x xmlns="http://jabber.org/protocol/muc#user">
<status code="201"/>
......@@ -32,6 +31,18 @@
</presence>
`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 1);
presence = u.toStanza(`
<presence xmlns="jabber:client" to="${_converse.jid}" from="${muc_jid}/romeo1">
<x xmlns="http://jabber.org/protocol/muc#user">
<status code="210"/>
<item role="moderator" affiliation="owner" jid="${_converse.jid}"/>
<status code="110"/>
</x>
</presence>
`);
_converse.connection._dataRecv(test_utils.createRequest(presence));
await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2);
const messages = view.el.querySelectorAll('.chat-info');
......@@ -48,9 +59,6 @@
const muc_jid = 'lounge@montague.lit';
await test_utils.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.api.chatviews.get(muc_jid);
await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length);
expect(view.el.querySelectorAll('.chat-info').length).toBe(1);
const presence = u.toStanza(`
<presence xmlns="jabber:client" to="${_converse.jid}" from="${muc_jid}/romeo">
<x xmlns="http://jabber.org/protocol/muc#user">
......@@ -68,11 +76,11 @@
spyOn(view.model, 'createInfoMessages').and.callThrough();
_converse.connection._dataRecv(test_utils.createRequest(presence));
await u.waitUntil(() => view.model.createInfoMessages.calls.count());
await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2);
await u.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 1);
_converse.connection._dataRecv(test_utils.createRequest(presence));
await u.waitUntil(() => view.model.createInfoMessages.calls.count() === 2);
expect(view.el.querySelectorAll('.chat-info').length).toBe(2);
expect(view.el.querySelectorAll('.chat-info').length).toBe(1);
done();
}));
});
......
......@@ -236,7 +236,8 @@
'author': 'ralphm'
}});
expect(sizzle('.chat-event:last').pop().textContent.trim()).toBe('Topic set by ralphm');
expect(view.el.querySelector('.chat-head__desc').textContent.trim()).toBe(subject);
const desc = await u.waitUntil(() => view.el.querySelector('.chat-head__desc'));
expect(desc.textContent.trim()).toBe(subject);
done();
}));
});
......
......@@ -203,7 +203,7 @@ converse.plugins.add('converse-chatview', {
this.removeAll();
});
this.listenTo(this.model.csn, 'change', this.renderChatStateNotification);
this.listenTo(this.model.notifications, 'change', this.renderChatStateNotification);
this.listenTo(this.model, 'change:status', this.onStatusMessageChanged);
this.listenTo(this.model, 'destroy', this.remove);
......@@ -249,7 +249,7 @@ converse.plugins.add('converse-chatview', {
);
render(result, this.el);
this.content = this.el.querySelector('.chat-content');
this.csn = this.el.querySelector('.chat-content__notifications');
this.notifications = this.el.querySelector('.chat-content__notifications');
this.msgs_container = this.el.querySelector('.chat-content__messages');
this.renderChatStateNotification();
this.renderMessageForm();
......@@ -258,14 +258,14 @@ converse.plugins.add('converse-chatview', {
},
renderChatStateNotification () {
if (this.model.csn.get('chat_state') === _converse.COMPOSING) {
this.csn.innerText = __('%1$s is typing', this.model.getDisplayName());
} else if (this.model.csn.get('chat_state') === _converse.PAUSED) {
this.csn.innerText = __('%1$s has stopped typing', this.model.getDisplayName());
} else if (this.model.csn.get('chat_state') === _converse.GONE) {
this.csn.innerText = __('%1$s has gone away', this.model.getDisplayName());
if (this.model.notifications.get('chat_state') === _converse.COMPOSING) {
this.notifications.innerText = __('%1$s is typing', this.model.getDisplayName());
} else if (this.model.notifications.get('chat_state') === _converse.PAUSED) {
this.notifications.innerText = __('%1$s has stopped typing', this.model.getDisplayName());
} else if (this.model.notifications.get('chat_state') === _converse.GONE) {
this.notifications.innerText = __('%1$s has gone away', this.model.getDisplayName());
} else {
this.csn.innerText = '';
this.notifications.innerText = '';
}
},
......
This diff is collapsed.
......@@ -334,7 +334,7 @@ converse.plugins.add('converse-chat', {
}
this.set({'box_id': `box-${btoa(jid)}`});
this.initMessages();
this.initCSN();
this.initNotifications();
if (this.get('type') === _converse.PRIVATE_CHAT_TYPE) {
this.presence = _converse.presences.findWhere({'jid': jid}) || _converse.presences.create({'jid': jid});
......@@ -367,8 +367,8 @@ converse.plugins.add('converse-chat', {
});
},
initCSN () {
this.csn = new Model();
initNotifications () {
this.notifications = new Model();
},
afterMessagesFetched () {
......@@ -425,11 +425,11 @@ converse.plugins.add('converse-chat', {
this.setEditable(attrs, attrs.time, stanza);
if (attrs['chat_state'] && attrs.sender === 'them') {
this.csn.set('chat_state', attrs.chat_state);
this.notifications.set('chat_state', attrs.chat_state);
}
if (u.shouldCreateMessage(attrs)) {
const msg = this.handleCorrection(attrs) || await this.createMessage(attrs);
this.csn.set({'chat_state': null});
this.notifications.set({'chat_state': null});
this.incrementUnreadMsgCounter(msg);
}
}
......
......@@ -16,6 +16,8 @@ import muc_utils from "./utils/muc";
import stanza_utils from "./utils/stanza";
import u from "./utils/form";
converse.MUC_TRAFFIC_STATES = ['entered', 'exited'];
const MUC_ROLE_WEIGHTS = {
'moderator': 1,
'participant': 2,
......@@ -121,6 +123,7 @@ converse.plugins.add('converse-muc', {
'muc_history_max_stanzas': undefined,
'muc_instant_rooms': true,
'muc_nickname_from_jid': false,
'muc_show_join_leave': true,
'muc_show_logs_before_join': false
});
api.promises.add(['roomsAutoJoined']);
......@@ -368,7 +371,7 @@ converse.plugins.add('converse-muc', {
this.debouncedRejoin = debounce(this.rejoin, 250);
this.set('box_id', `box-${btoa(this.get('jid'))}`);
this.initMessages();
this.initCSN();
this.initNotifications();
this.initOccupants();
this.initDiscoModels(); // sendChatState depends on this.features
this.registerHandlers();
......@@ -377,6 +380,10 @@ converse.plugins.add('converse-muc', {
await this.restoreSession();
this.session.on('change:connection_status', this.onConnectionStatusChanged, this);
this.listenTo(this.occupants, 'add', this.onOccupantAdded);
this.listenTo(this.occupants, 'remove', this.onOccupantRemoved);
this.listenTo(this.occupants, 'change:show', this.onOccupantShowChanged);
const restored = await this.restoreFromCache()
if (!restored) {
this.join();
......@@ -461,6 +468,33 @@ converse.plugins.add('converse-muc', {
}
},
onOccupantAdded (occupant) {
if (api.settings.get('muc_show_join_leave') &&
this.session.get('connection_status') === converse.ROOMSTATUS.ENTERED &&
occupant.get('show') === 'online') {
this.updateNotifications(occupant.get('nick'), 'entered');
}
},
onOccupantRemoved (occupant) {
if (api.settings.get('muc_show_join_leave') &&
this.session.get('connection_status') === converse.ROOMSTATUS.ENTERED &&
occupant.get('show') === 'online') {
this.updateNotifications(occupant.get('nick'), 'exited');
}
},
onOccupantShowChanged (occupant) {
if (occupant.get('states').includes('303') || !api.settings.get('muc_show_join_leave')) {
return;
}
if (occupant.get('show') === 'offline') {
this.updateNotifications(occupant.get('nick'), 'exited');
} else if (occupant.get('show') === 'online') {
this.updateNotifications(occupant.get('nick'), 'entered');
}
},
/**
* Clear stale cache and re-join a MUC we've been in before.
* @private
......@@ -1885,34 +1919,42 @@ converse.plugins.add('converse-muc', {
}
},
removeCSNFor (actor, state) {
const actors_per_state = this.csn.toJSON();
removeNotification (actor, state) {
const actors_per_state = this.notifications.toJSON();
const existing_actors = Array.from(actors_per_state[state]) || [];
if (existing_actors.includes(actor)) {
const idx = existing_actors.indexOf(actor);
existing_actors.splice(idx, 1);
this.csn.set(state, Array.from(existing_actors));
this.notifications.set(state, Array.from(existing_actors));
}
},
updateCSN (attrs) {
const actor = attrs.nick;
const state = attrs.chat_state;
const actors_per_state = this.csn.toJSON();
/**
* Update the notifications model by adding the passed in nickname
* to the array of nicknames that all match a particular state.
* The state can be a XEP-0085 Chat State or a XEP-0045 join/leave
* state.
* @param {String} actor - The nickname of the actor that causes the notification
* @param {String} state - The state representing the type of notificcation
*/
updateNotifications (actor, state) {
const actors_per_state = this.notifications.toJSON();
const existing_actors = actors_per_state[state] || [];
if (existing_actors.includes(actor)) {
return;
}
const new_actors_per_state = converse.CHAT_STATES.reduce((out, s) => {
const reducer = (out, s) => {
if (s === state) {
out[s] = [...existing_actors, actor];
} else {
out[s] = (actors_per_state[s] || []).filter(a => a !== actor);
}
return out;
}, {});
this.csn.set(new_actors_per_state);
window.setTimeout(() => this.removeCSNFor(actor, state), 10000);
};
const actors_per_chat_state = converse.CHAT_STATES.reduce(reducer, {});
const actors_per_traffic_state = converse.MUC_TRAFFIC_STATES.reduce(reducer, {});
this.notifications.set(Object.assign(actors_per_chat_state, actors_per_traffic_state));
window.setTimeout(() => this.removeNotification(actor, state), 10000);
},
/**
......@@ -1963,7 +2005,7 @@ converse.plugins.add('converse-muc', {
this.setEditable(attrs, attrs.time);
if (attrs['chat_state']) {
this.updateCSN(attrs);
this.updateNotifications(attrs.nick, attrs.chat_state);
}
if (u.shouldCreateGroupchatMessage(attrs)) {
const msg = this.handleCorrection(attrs) || await this.createMessage(attrs);
......
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