Commit 21217666 authored by JC Brand's avatar JC Brand

More RAI improvements

- Add test for incoming RAI message
- Only enable RAI if the user is affilated in MUC being left
- Handle error presence indicating a resouce-constraint
- Don't unregister stanza handlers in `leave`, since we still want to
  listen to RAI-related stanzas. Instead unregister upon the `destroy`
  event.
parent fe365a65
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
- #1083: Add support for XEP-0393 Message Styling - #1083: Add support for XEP-0393 Message Styling
- #2275: Allow punctuation to immediately precede a mention - #2275: Allow punctuation to immediately precede a mention
- Add support for XEP-0437 Room Activity Indicators see [muc-subscribe-to-rai](https://conversejs.org/docs/html/configuration.html#muc-subscribe-to-rai)
- Bugfix: Connection protocol not updated based on XEP-0156 connection methods - Bugfix: Connection protocol not updated based on XEP-0156 connection methods
- Bugfix: `null` inserted by emoji picker and can't switch between skintones - Bugfix: `null` inserted by emoji picker and can't switch between skintones
- New hook: [getMessageActionButtons](https://conversejs.org/docs/html/api/-_converse.html#event:getMessageActionButtons) - New hook: [getMessageActionButtons](https://conversejs.org/docs/html/api/-_converse.html#event:getMessageActionButtons)
......
...@@ -1444,7 +1444,7 @@ muc_show_logs_before_join ...@@ -1444,7 +1444,7 @@ muc_show_logs_before_join
If set to ``true``, when opening a MUC for the first time (or if you don't have If set to ``true``, when opening a MUC for the first time (or if you don't have
a nickname configured for it), you'll see the message history (if the a nickname configured for it), you'll see the message history (if the
server supports [XEP-0313 Message Archive Management](https://xmpp.org/extensions/xep-0313.html)) server supports `XEP-0313 Message Archive Management <https://xmpp.org/extensions/xep-0313.html>`_)
and the nickname form at the bottom. and the nickname form at the bottom.
muc_subscribe_to_rai muc_subscribe_to_rai
...@@ -1452,11 +1452,12 @@ muc_subscribe_to_rai ...@@ -1452,11 +1452,12 @@ muc_subscribe_to_rai
* Default: ``false`` * Default: ``false``
This option enables support for XEP-0437 Room Activity Indicators. This option enables support for `XEP-0437 Room Activity Indicators <https://xmpp.org/extensions/xep-0313.html>`_.
When a MUC is no longer visible (the ``hidden`` flag becomes ``true``), then When a MUC is no longer visible (specifically, when the ``hidden`` flag becomes ``true``),
Converse will make sure that its subscribed to activity indicators on the MUC then Converse will exit the MUC and subscribe to activity indicators on the MUC host.
host.
When the MUC becomes visible again (``hidden`` gets set to ``false``), the MUC will be rejoined.
.. _`nickname`: .. _`nickname`:
......
...@@ -2367,10 +2367,11 @@ describe("Groupchats", function () { ...@@ -2367,10 +2367,11 @@ describe("Groupchats", function () {
['rosterGroupsFetched', 'chatBoxesFetched'], {}, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) { async function (done, _converse) {
const nick = "some1";
const IQ_stanzas = _converse.connection.IQ_stanzas; const IQ_stanzas = _converse.connection.IQ_stanzas;
const muc_jid = 'coven@chat.shakespeare.lit'; const muc_jid = 'coven@chat.shakespeare.lit';
await _converse.api.rooms.open(muc_jid, {'nick': 'some1'}); await _converse.api.rooms.open(muc_jid, { nick });
const stanza = await u.waitUntil(() => _.filter( const stanza = await u.waitUntil(() => _.filter(
IQ_stanzas, IQ_stanzas,
iq => iq.querySelector( iq => iq.querySelector(
...@@ -2423,7 +2424,9 @@ describe("Groupchats", function () { ...@@ -2423,7 +2424,9 @@ describe("Groupchats", function () {
.c('feature', {'var': 'muc_nonanonymous'}); .c('feature', {'var': 'muc_nonanonymous'});
_converse.connection._dataRecv(mock.createRequest(features_stanza)); _converse.connection._dataRecv(mock.createRequest(features_stanza));
let view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); let view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
const sent_stanzas = _converse.connection.sent_stanzas;
await u.waitUntil(() => sent_stanzas.filter(s => s.matches(`presence[to="${muc_jid}/${nick}"]`)).pop());
view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
expect(view.model.features.get('fetched')).toBeTruthy(); expect(view.model.features.get('fetched')).toBeTruthy();
expect(view.model.features.get('passwordprotected')).toBe(true); expect(view.model.features.get('passwordprotected')).toBe(true);
...@@ -4412,8 +4415,8 @@ describe("Groupchats", function () { ...@@ -4412,8 +4415,8 @@ describe("Groupchats", function () {
spyOn(_converse.ChatRoomOccupants.prototype, 'fetchMembers').and.callThrough(); spyOn(_converse.ChatRoomOccupants.prototype, 'fetchMembers').and.callThrough();
const sent_IQs = _converse.connection.IQ_stanzas; const sent_IQs = _converse.connection.IQ_stanzas;
const muc_jid = 'coven@chat.shakespeare.lit'; const muc_jid = 'coven@chat.shakespeare.lit';
const nick = 'romeo';
const room_creation_promise = _converse.api.rooms.open(muc_jid, {'nick': 'romeo'}); const room_creation_promise = _converse.api.rooms.open(muc_jid, {nick});
// Check that the groupchat queried for the features. // Check that the groupchat queried for the features.
let stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`)).pop()); let stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq[to="${muc_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`)).pop());
...@@ -4441,11 +4444,11 @@ describe("Groupchats", function () { ...@@ -4441,11 +4444,11 @@ describe("Groupchats", function () {
.c('feature', {'var': 'muc_temporary'}).up() .c('feature', {'var': 'muc_temporary'}).up()
.c('feature', {'var': 'muc_membersonly'}).up(); .c('feature', {'var': 'muc_membersonly'}).up();
_converse.connection._dataRecv(mock.createRequest(features_stanza)); _converse.connection._dataRecv(mock.createRequest(features_stanza));
await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING)); const sent_stanzas = _converse.connection.sent_stanzas;
await u.waitUntil(() => sent_stanzas.filter(s => s.matches(`presence[to="${muc_jid}/${nick}"]`)).pop());
expect(view.model.features.get('membersonly')).toBeTruthy(); expect(view.model.features.get('membersonly')).toBeTruthy();
await room_creation_promise; await room_creation_promise;
await mock.createContacts(_converse, 'current'); await mock.createContacts(_converse, 'current');
let sent_stanza, sent_id; let sent_stanza, sent_id;
......
...@@ -5,11 +5,14 @@ const u = converse.env.utils; ...@@ -5,11 +5,14 @@ const u = converse.env.utils;
// See: https://xmpp.org/rfcs/rfc3921.html // See: https://xmpp.org/rfcs/rfc3921.html
fdescribe("XEP-0437 Room Activity Indicators", function () { describe("XEP-0437 Room Activity Indicators", function () {
it("will be activated for a MUC that becomes hidden", it("will be activated for a MUC that becomes hidden",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {'muc_subscribe_to_rai': true, 'view_mode': 'fullscreen'}, ['rosterGroupsFetched'], {
'allow_bookmarks': false, // Hack to get the rooms list to render
'muc_subscribe_to_rai': true,
'view_mode': 'fullscreen'},
async function (done, _converse) { async function (done, _converse) {
expect(_converse.session.get('rai_enabled_domains')).toBe(undefined); expect(_converse.session.get('rai_enabled_domains')).toBe(undefined);
...@@ -79,18 +82,86 @@ fdescribe("XEP-0437 Room Activity Indicators", function () { ...@@ -79,18 +82,86 @@ fdescribe("XEP-0437 Room Activity Indicators", function () {
expect(Strophe.serialize(sent_stanzas[1])).toBe( expect(Strophe.serialize(sent_stanzas[1])).toBe(
`<presence to="${muc_jid}/romeo" type="unavailable" xmlns="jabber:client">`+ `<presence to="${muc_jid}/romeo" type="unavailable" xmlns="jabber:client">`+
`<priority>0</priority>`+ `<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="PxXfr6uz8ClMWIga0OB/MhKNH/M=" xmlns="http://jabber.org/protocol/caps"/>`+ `<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>` `</presence>`
); );
expect(Strophe.serialize(sent_stanzas[2])).toBe( expect(Strophe.serialize(sent_stanzas[2])).toBe(
`<presence to="montague.lit" xmlns="jabber:client">`+ `<presence to="montague.lit" xmlns="jabber:client">`+
`<priority>0</priority>`+ `<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="PxXfr6uz8ClMWIga0OB/MhKNH/M=" xmlns="http://jabber.org/protocol/caps"/>`+ `<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`<rai xmlns="urn:xmpp:rai:0"/>`+ `<rai xmlns="urn:xmpp:rai:0"/>`+
`</presence>` `</presence>`
); );
view.model.save({'hidden': false}); await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED);
expect(view.model.get('has_activity')).toBe(false);
const lview = _converse.rooms_list_view
const room_el = await u.waitUntil(() => lview.el.querySelector(".available-chatroom"));
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeFalsy();
const activity_stanza = u.toStanza(`
<message from="${Strophe.getDomainFromJid(muc_jid)}">
<rai xmlns="urn:xmpp:rai:0">
<activity>${muc_jid}</activity>
</rai>
</message>
`);
_converse.connection._dataRecv(mock.createRequest(activity_stanza));
await u.waitUntil(() => view.model.get('has_activity'));
expect(Array.from(room_el.classList).includes('unread-msgs')).toBeTruthy();
done();
}));
it("may not be activated due to server resource constraints",
mock.initConverse(
['rosterGroupsFetched'], {
'allow_bookmarks': false, // Hack to get the rooms list to render
'muc_subscribe_to_rai': true,
'view_mode': 'fullscreen'},
async function (done, _converse) {
expect(_converse.session.get('rai_enabled_domains')).toBe(undefined);
const muc_jid = 'lounge@montague.lit';
const muc_domain = Strophe.getDomainFromJid(muc_jid);
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
const view = _converse.api.chatviews.get(muc_jid);
expect(view.model.get('hidden')).toBe(false);
const sent_stanzas = [];
spyOn(_converse.connection, 'send').and.callFake(s => sent_stanzas.push(s?.nodeTree ?? s));
view.model.save({'hidden': true});
await u.waitUntil(() => sent_stanzas.filter(s => s.nodeName === 'presence').length === 2);
expect(Strophe.serialize(sent_stanzas[0])).toBe(
`<presence to="${muc_jid}/romeo" type="unavailable" xmlns="jabber:client">`+
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`</presence>`
);
expect(Strophe.serialize(sent_stanzas[1])).toBe(
`<presence to="montague.lit" xmlns="jabber:client">`+
`<priority>0</priority>`+
`<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
`<rai xmlns="urn:xmpp:rai:0"/>`+
`</presence>`
);
expect(view.model.session.get('connection_status')).toBe(converse.ROOMSTATUS.DISCONNECTED);
expect(_converse.session.get('rai_enabled_domains')).toBe(` ${muc_domain}`);
// If an error presence with "resource-constraint" is returned, we rejoin
const activity_stanza = u.toStanza(`
<presence type="error" from="${Strophe.getDomainFromJid(muc_jid)}">
<error type="wait"><resource-constraint xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error>
</presence>
`);
_converse.connection._dataRecv(mock.createRequest(activity_stanza));
await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.CONNECTING);
expect(_converse.session.get('rai_enabled_domains')).toBe(' ');
done(); done();
})); }));
......
...@@ -66,6 +66,7 @@ const ChatRoomMixin = { ...@@ -66,6 +66,7 @@ const ChatRoomMixin = {
this.on('change:chat_state', this.sendChatState, this); this.on('change:chat_state', this.sendChatState, this);
this.on('change:hidden', this.onHiddenChange, this); this.on('change:hidden', this.onHiddenChange, this);
this.on('destroy', this.removeHandlers, this);
await this.restoreSession(); await this.restoreSession();
this.session.on('change:connection_status', this.onConnectionStatusChanged, this); this.session.on('change:connection_status', this.onConnectionStatusChanged, this);
...@@ -109,20 +110,6 @@ const ChatRoomMixin = { ...@@ -109,20 +110,6 @@ const ChatRoomMixin = {
} }
}, },
/**
* Handles incoming message stanzas from the service that hosts this MUC
* @private
* @method _converse.ChatRoom#onPresence
* @param { XMLElement } stanza
*/
handleMessageFromMUCService (stanza) {
const rai = stanza.querySelector(`rai[xmlns="${Strophe.NS.RAI}"]`);
const active_mucs = Array.from(rai?.querySelectorAll('activity') || []).map(m => m.getAttribute('xmlns'));
if (active_mucs.includes(this.get('jid'))) {
this.save({ 'has_activity': true });
}
},
/** /**
* Join the MUC * Join the MUC
* @private * @private
...@@ -138,6 +125,8 @@ const ChatRoomMixin = { ...@@ -138,6 +125,8 @@ const ChatRoomMixin = {
// so we don't send out a presence stanza again. // so we don't send out a presence stanza again.
return this; return this;
} }
// Set this early, so we don't rejoin in onHiddenChange
this.session.save('connection_status', converse.ROOMSTATUS.CONNECTING);
await this.refreshDiscoInfo(); await this.refreshDiscoInfo();
nick = await this.getAndPersistNickname(nick); nick = await this.getAndPersistNickname(nick);
if (!nick) { if (!nick) {
...@@ -161,7 +150,6 @@ const ChatRoomMixin = { ...@@ -161,7 +150,6 @@ const ChatRoomMixin = {
if (password) { if (password) {
stanza.cnode(Strophe.xmlElement('password', [], password)); stanza.cnode(Strophe.xmlElement('password', [], password));
} }
this.session.save('connection_status', converse.ROOMSTATUS.CONNECTING);
api.send(stanza); api.send(stanza);
return this; return this;
}, },
...@@ -200,13 +188,8 @@ const ChatRoomMixin = { ...@@ -200,13 +188,8 @@ const ChatRoomMixin = {
} }
}, },
/** async enableRAI () {
* Handler that gets called when the 'hidden' flag is toggled. if (api.settings.get('muc_subscribe_to_rai') && this.getOwnAffiliation() !== 'none') {
* @private
* @method _converse.ChatRoomView#onHiddenChange
*/
async onHiddenChange () {
if (this.get('hidden') && api.settings.get('muc_subscribe_to_rai')) {
this.sendMarkerForLastMessage('received', true); this.sendMarkerForLastMessage('received', true);
if (this.session.get('connection_status') !== converse.ROOMSTATUS.DISCONNECTED) { if (this.session.get('connection_status') !== converse.ROOMSTATUS.DISCONNECTED) {
await this.leave(); await this.leave();
...@@ -217,8 +200,19 @@ const ChatRoomMixin = { ...@@ -217,8 +200,19 @@ const ChatRoomMixin = {
api.user.presence.send(null, muc_domain, null, $build('rai', { 'xmlns': Strophe.NS.RAI })); api.user.presence.send(null, muc_domain, null, $build('rai', { 'xmlns': Strophe.NS.RAI }));
_converse.session.save({ 'rai_enabled_domains': `${rai_enabled} ${muc_domain}` }); _converse.session.save({ 'rai_enabled_domains': `${rai_enabled} ${muc_domain}` });
} }
}
},
/**
* Handler that gets called when the 'hidden' flag is toggled.
* @private
* @method _converse.ChatRoomView#onHiddenChange
*/
onHiddenChange () {
if (this.get('hidden')) {
this.enableRAI();
} else if (this.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) { } else if (this.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) {
this.onReconnection(); this.rejoin();
} }
}, },
...@@ -259,6 +253,7 @@ const ChatRoomMixin = { ...@@ -259,6 +253,7 @@ const ChatRoomMixin = {
* @method _converse.ChatRoom#rejoin * @method _converse.ChatRoom#rejoin
*/ */
rejoin () { rejoin () {
this.registerHandlers();
this.clearCache(); this.clearCache();
return this.join(); return this.join();
}, },
...@@ -285,7 +280,6 @@ const ChatRoomMixin = { ...@@ -285,7 +280,6 @@ const ChatRoomMixin = {
}, },
async onReconnection () { async onReconnection () {
this.registerHandlers();
await this.rejoin(); await this.rejoin();
this.announceReconnection(); this.announceReconnection();
}, },
...@@ -409,6 +403,24 @@ const ChatRoomMixin = { ...@@ -409,6 +403,24 @@ const ChatRoomMixin = {
} }
}, },
/**
* Handles incoming message stanzas from the service that hosts this MUC
* @private
* @method _converse.ChatRoom#handleMessageFromMUCHost
* @param { XMLElement } stanza
*/
handleMessageFromMUCHost (stanza) {
const rai = sizzle(`rai[xmlns="${Strophe.NS.RAI}"]`, stanza).pop();
const active_mucs = Array.from(rai?.querySelectorAll('activity') || []).map(m => m.textContent);
if (active_mucs.includes(this.get('jid'))) {
this.save({
'has_activity': true,
'num_unread_general': 0 // Either/or between activity and unreads
});
}
},
/** /**
* Parses an incoming message stanza and queues it for processing. * Parses an incoming message stanza and queues it for processing.
* @private * @private
...@@ -452,6 +464,7 @@ const ChatRoomMixin = { ...@@ -452,6 +464,7 @@ const ChatRoomMixin = {
*/ */
registerHandlers () { registerHandlers () {
const muc_jid = this.get('jid'); const muc_jid = this.get('jid');
const muc_domain = Strophe.getDomainFromJid(muc_jid);
this.removeHandlers(); this.removeHandlers();
this.presence_handler = _converse.connection.addHandler( this.presence_handler = _converse.connection.addHandler(
stanza => this.onPresence(stanza) || true, stanza => this.onPresence(stanza) || true,
...@@ -463,11 +476,10 @@ const ChatRoomMixin = { ...@@ -463,11 +476,10 @@ const ChatRoomMixin = {
{ 'ignoreNamespaceFragment': true, 'matchBareFromJid': true } { 'ignoreNamespaceFragment': true, 'matchBareFromJid': true }
); );
const muc_domain = Strophe.getDomainFromJid(muc_jid);
this.domain_presence_handler = _converse.connection.addHandler( this.domain_presence_handler = _converse.connection.addHandler(
stanza => this.handleMessageFromMUCService(stanza) || true, stanza => this.onPresenceFromMUCHost(stanza) || true,
null, null,
'message', 'presence',
null, null,
null, null,
muc_domain muc_domain
...@@ -483,6 +495,15 @@ const ChatRoomMixin = { ...@@ -483,6 +495,15 @@ const ChatRoomMixin = {
{ 'matchBareFromJid': true } { 'matchBareFromJid': true }
); );
this.domain_message_handler = _converse.connection.addHandler(
stanza => this.handleMessageFromMUCHost(stanza) || true,
null,
'message',
null,
null,
muc_domain
);
this.affiliation_message_handler = _converse.connection.addHandler( this.affiliation_message_handler = _converse.connection.addHandler(
stanza => this.handleAffiliationChangedMessage(stanza) || true, stanza => this.handleAffiliationChangedMessage(stanza) || true,
Strophe.NS.MUC_USER, Strophe.NS.MUC_USER,
...@@ -500,10 +521,18 @@ const ChatRoomMixin = { ...@@ -500,10 +521,18 @@ const ChatRoomMixin = {
_converse.connection && _converse.connection.deleteHandler(this.message_handler); _converse.connection && _converse.connection.deleteHandler(this.message_handler);
delete this.message_handler; delete this.message_handler;
} }
if (this.domain_message_handler) {
_converse.connection && _converse.connection.deleteHandler(this.domain_message_handler);
delete this.domain_message_handler;
}
if (this.presence_handler) { if (this.presence_handler) {
_converse.connection && _converse.connection.deleteHandler(this.presence_handler); _converse.connection && _converse.connection.deleteHandler(this.presence_handler);
delete this.presence_handler; delete this.presence_handler;
} }
if (this.domain_presence_handler) {
_converse.connection && _converse.connection.deleteHandler(this.domain_presence_handler);
delete this.domain_presence_handler;
}
if (this.affiliation_message_handler) { if (this.affiliation_message_handler) {
_converse.connection && _converse.connection.deleteHandler(this.affiliation_message_handler); _converse.connection && _converse.connection.deleteHandler(this.affiliation_message_handler);
delete this.affiliation_message_handler; delete this.affiliation_message_handler;
...@@ -717,7 +746,6 @@ const ChatRoomMixin = { ...@@ -717,7 +746,6 @@ const ChatRoomMixin = {
api.user.presence.send('unavailable', this.getRoomJIDAndNick(), exit_msg); api.user.presence.send('unavailable', this.getRoomJIDAndNick(), exit_msg);
} }
u.safeSave(this.session, { 'connection_status': converse.ROOMSTATUS.DISCONNECTED }); u.safeSave(this.session, { 'connection_status': converse.ROOMSTATUS.DISCONNECTED });
this.removeHandlers();
}, },
async close () { async close () {
...@@ -1131,7 +1159,7 @@ const ChatRoomMixin = { ...@@ -1131,7 +1159,7 @@ const ChatRoomMixin = {
* @returns { ('none'|'outcast'|'member'|'admin'|'owner') } * @returns { ('none'|'outcast'|'member'|'admin'|'owner') }
*/ */
getOwnAffiliation () { getOwnAffiliation () {
return this.getOwnOccupant()?.attributes?.affiliation; return this.getOwnOccupant()?.attributes?.affiliation || 'none';
}, },
/** /**
...@@ -2142,6 +2170,32 @@ const ChatRoomMixin = { ...@@ -2142,6 +2170,32 @@ const ChatRoomMixin = {
} }
}, },
/**
* Listens for incoming presence stanzas from the service that hosts this MUC
* @private
* @method _converse.ChatRoom#onPresenceFromMUCHost
* @param { XMLElement } stanza - The presence stanza
*/
onPresenceFromMUCHost (stanza) {
if (stanza.getAttribute('type') === 'error') {
const error = stanza.querySelector('error');
if (error?.getAttribute('type') === 'wait' && error?.querySelector('resource-constraint')) {
// If we get a <resource-constraint> error, we assume it's in context of XEP-0437 RAI.
// We remove this MUC's host from the list of enabled domains and rejoin the MUC.
const rai_enabled = _converse.session.get('rai_enabled_domains') || '';
const muc_domain = Strophe.getDomainFromJid(this.get('jid'));
if (rai_enabled.includes(muc_domain)) {
const regex = new RegExp(muc_domain, 'g');
_converse.session.save({ 'rai_enabled_domains': rai_enabled.replace(regex, '') });
if (this.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) {
this.rejoin();
}
}
}
}
},
/** /**
* Handles incoming presence stanzas coming from the MUC * Handles incoming presence stanzas coming from the MUC
* @private * @private
...@@ -2284,7 +2338,7 @@ const ChatRoomMixin = { ...@@ -2284,7 +2338,7 @@ const ChatRoomMixin = {
}, },
clearUnreadMsgCounter () { clearUnreadMsgCounter () {
if (this.get('num_unread_general') > 0 || this.get('num_unread') > 0) { if (this.get('num_unread_general') > 0 || this.get('num_unread') > 0 || this.get('has_activity')) {
this.sendMarkerForMessage(this.messages.last()); this.sendMarkerForMessage(this.messages.last());
} }
u.safeSave(this, { u.safeSave(this, {
......
...@@ -1030,11 +1030,8 @@ const ChatBoxView = View.extend({ ...@@ -1030,11 +1030,8 @@ const ChatBoxView = View.extend({
onWindowStateChanged (state) { onWindowStateChanged (state) {
if (state === 'visible') { if (state === 'visible') {
if (!this.model.isHidden()) { if (!this.model.isHidden() && this.model.get('num_unread', 0)) {
// this.model.setChatState(_converse.ACTIVE); this.model.clearUnreadMsgCounter();
if (this.model.get('num_unread', 0)) {
this.model.clearUnreadMsgCounter();
}
} }
} else if (state === 'hidden') { } else if (state === 'hidden') {
this.model.setChatState(_converse.INACTIVE, { 'silent': true }); this.model.setChatState(_converse.INACTIVE, { 'silent': true });
......
...@@ -11,7 +11,7 @@ import tpl_chatroom_head from 'templates/chatroom_head.js'; ...@@ -11,7 +11,7 @@ import tpl_chatroom_head from 'templates/chatroom_head.js';
import tpl_muc_bottom_panel from 'templates/muc_bottom_panel.js'; import tpl_muc_bottom_panel from 'templates/muc_bottom_panel.js';
import tpl_muc_destroyed from 'templates/muc_destroyed.js'; import tpl_muc_destroyed from 'templates/muc_destroyed.js';
import tpl_muc_disconnect from 'templates/muc_disconnect.js'; import tpl_muc_disconnect from 'templates/muc_disconnect.js';
import { $build, $pres, Strophe } from 'strophe.js/src/strophe'; import { $pres, Strophe } from 'strophe.js/src/strophe';
import tpl_muc_nickname_form from 'templates/muc_nickname_form.js'; import tpl_muc_nickname_form from 'templates/muc_nickname_form.js';
import tpl_spinner from 'templates/spinner.js'; import tpl_spinner from 'templates/spinner.js';
import { Model } from '@converse/skeletor/src/model.js'; import { Model } from '@converse/skeletor/src/model.js';
...@@ -694,9 +694,7 @@ const ChatRoomViewMixin = { ...@@ -694,9 +694,7 @@ const ChatRoomViewMixin = {
// Override from converse-chatview, specifically to avoid // Override from converse-chatview, specifically to avoid
// the 'active' chat state from being sent out prematurely. // the 'active' chat state from being sent out prematurely.
// This is instead done in `onConnectionStatusChanged` below. // This is instead done in `onConnectionStatusChanged` below.
if (u.isPersistableModel(this.model)) { this.model.clearUnreadMsgCounter();
this.model.clearUnreadMsgCounter();
}
this.scrollDown(); this.scrollDown();
}, },
......
...@@ -23,31 +23,36 @@ const bookmark = (o) => { ...@@ -23,31 +23,36 @@ const bookmark = (o) => {
} }
const unread_indicator = (o) => html`<span class="list-item-badge badge badge--muc msgs-indicator">${ o.room.get('num_unread') }</span>`;
const activity_indicator = () => html`<span class="list-item-badge badge badge--muc msgs-indicator"></span>`;
const room_item = (o) => { const room_item = (o) => {
const i18n_leave_room = __('Leave this groupchat'); const i18n_leave_room = __('Leave this groupchat');
const unread_indicator = (o) => html`<span class="list-item-badge badge badge--muc msgs-indicator">${ o.room.get('num_unread') }</span>`; const has_unread_msgs = o.room.get('num_unread_general') || o.room.get('has_activity');
return html` return html`
<div class="list-item controlbox-padded available-chatroom d-flex flex-row ${ o.currently_open(o.room) ? 'open' : '' } ${ o.room.get('num_unread_general') ? 'unread-msgs' : '' }" <div class="list-item controlbox-padded available-chatroom d-flex flex-row ${ o.currently_open(o.room) ? 'open' : '' } ${ has_unread_msgs ? 'unread-msgs' : '' }"
data-room-jid="${o.room.get('jid')}"> data-room-jid="${o.room.get('jid')}">
${ o.room.get('num_unread') ? unread_indicator(o) : '' } ${ o.room.get('num_unread') ? unread_indicator(o) : (o.room.get('has_activity') ? activity_indicator(o) : '') }
<a class="list-item-link open-room available-room w-100" <a class="list-item-link open-room available-room w-100"
data-room-jid="${o.room.get('jid')}" data-room-jid="${o.room.get('jid')}"
title="${__('Click to open this groupchat')}" title="${__('Click to open this groupchat')}"
@click=${o.openRoom}>${o.room.getDisplayName()}</a> @click=${o.openRoom}>${o.room.getDisplayName()}</a>
${ o.allow_bookmarks ? bookmark(o) : '' } ${ o.allow_bookmarks ? bookmark(o) : '' }
<a class="list-item-action room-info fa fa-info-circle" <a class="list-item-action room-info fa fa-info-circle"
data-room-jid="${o.room.get('jid')}" data-room-jid="${o.room.get('jid')}"
title="${__('Show more information on this groupchat')}" title="${__('Show more information on this groupchat')}"
@click=${o.showRoomDetailsModal}></a> @click=${o.showRoomDetailsModal}></a>
<a class="list-item-action fa fa-sign-out-alt close-room" <a class="list-item-action fa fa-sign-out-alt close-room"
data-room-jid="${o.room.get('jid')}" data-room-jid="${o.room.get('jid')}"
data-room-name="${o.room.getDisplayName()}" data-room-name="${o.room.getDisplayName()}"
title="${i18n_leave_room}" title="${i18n_leave_room}"
@click=${o.closeRoom}></a> @click=${o.closeRoom}></a>
</div>`; </div>`;
} }
......
...@@ -30,7 +30,7 @@ const RoomsListView = View.extend({ ...@@ -30,7 +30,7 @@ const RoomsListView = View.extend({
}, },
renderIfRelevantChange (model) { renderIfRelevantChange (model) {
const attrs = ['bookmarked', 'hidden', 'name', 'num_unread', 'num_unread_general']; const attrs = ['bookmarked', 'hidden', 'name', 'num_unread', 'num_unread_general', 'has_activity'];
const changed = model.changed || {}; const changed = model.changed || {};
if (u.isChatRoom(model) && Object.keys(changed).filter(m => attrs.includes(m)).length) { if (u.isChatRoom(model) && Object.keys(changed).filter(m => attrs.includes(m)).length) {
this.render(); this.render();
......
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