Commit 7b9c97df authored by JC Brand's avatar JC Brand

Also squash leave/join messages

And fix an HTML rendering bug for info messages and nicks that contain spaces
parent 71cc98d6
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -558,7 +558,7 @@ ...@@ -558,7 +558,7 @@
expect($chat_content.find('div.chat-info').length).toBe(4); expect($chat_content.find('div.chat-info').length).toBe(4);
var $msg_el = $chat_content.find('div.chat-info:last'); var $msg_el = $chat_content.find('div.chat-info:last');
expect($msg_el.html()).toBe("newguy has left and re-entered the groupchat"); expect($msg_el.html()).toBe("newguy has left and re-entered the groupchat");
expect($msg_el.data('leavejoin')).toBe('"newguy"'); expect($msg_el.data('leavejoin')).toBe('newguy');
presence = $pres({ presence = $pres({
to: 'dummy@localhost/_converse.js-29092160', to: 'dummy@localhost/_converse.js-29092160',
...@@ -575,7 +575,7 @@ ...@@ -575,7 +575,7 @@
expect($chat_content.find('div.chat-info').length).toBe(4); expect($chat_content.find('div.chat-info').length).toBe(4);
const msg_el = sizzle('div.chat-info', chat_content).pop(); const msg_el = sizzle('div.chat-info', chat_content).pop();
expect(msg_el.textContent).toBe('newguy has left the groupchat'); expect(msg_el.textContent).toBe('newguy has left the groupchat');
expect(msg_el.getAttribute('data-leave')).toBe('"newguy"'); expect(msg_el.getAttribute('data-leave')).toBe('newguy');
presence = $pres({ presence = $pres({
to: 'dummy@localhost/_converse.js-29092160', to: 'dummy@localhost/_converse.js-29092160',
...@@ -687,6 +687,160 @@ ...@@ -687,6 +687,160 @@
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)) }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
})); }));
it("combines subsequent join/leave messages when users enter or exit a groupchat",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) {
test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'dummy')
.then(() => {
const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
const chat_content = view.el.querySelector('.chat-content');
expect(sizzle('div.chat-info', chat_content).length).toBe(1);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("dummy has entered the groupchat");
let presence = Strophe.xmlHtmlNode(
`<presence xmlns="jabber:client" to="dummy@localhost/resource" from="coven@chat.shakespeare.lit/fabio">
<c xmlns="http://jabber.org/protocol/caps" node="http://conversations.im" ver="INI3xjRUioclBTP/aACfWi5m9UY=" hash="sha-1"/>
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" jid="fabio@montefuscolo.com.br/Conversations.ZvLu" role="participant"/>
</x>
</presence>`).firstElementChild;
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(2);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("fabio has entered the groupchat");
presence = Strophe.xmlHtmlNode(
`<presence xmlns="jabber:client" to="dummy@localhost/resource" from="coven@chat.shakespeare.lit/Dele Olajide">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" jid="deleo@traderlynk.4ng.net/converse.js-39320524" role="participant"/>
</x>
</presence>`).firstElementChild;
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(3);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat");
presence = Strophe.xmlHtmlNode(
`<presence xmlns="jabber:client" to="dummy@localhost/resource" from="coven@chat.shakespeare.lit/jcbrand">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="owner" jid="jc@opkode.com/converse.js-30645022" role="moderator"/>
<status code="110"/>
</x>
</presence>`).firstElementChild;
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(4);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("jcbrand has entered the groupchat");
presence = Strophe.xmlHtmlNode(
`<presence xmlns="jabber:client" to="dummy@localhost/resource" type="unavailable" from="coven@chat.shakespeare.lit/Dele Olajide">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" jid="deleo@traderlynk.4ng.net/converse.js-39320524" role="none"/>
</x>
</presence>`).firstElementChild;
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(4);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("Dele Olajide has entered and left the groupchat");
presence = Strophe.xmlHtmlNode(
`<presence xmlns="jabber:client" to="dummy@localhost/resource" from="coven@chat.shakespeare.lit/Dele Olajide">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" jid="deleo@traderlynk.4ng.net/converse.js-74567907" role="participant"/>
</x>
</presence>`).firstElementChild;
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(4);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat");
presence = Strophe.xmlHtmlNode(
`<presence xmlns="jabber:client" to="dummy@localhost/resource" from="coven@chat.shakespeare.lit/fuvuv" xml:lang="en">
<c xmlns="http://jabber.org/protocol/caps" node="http://jabber.pix-art.de" ver="5tOurnuFnp2h50hKafeUyeN4Yl8=" hash="sha-1"/>
<x xmlns="vcard-temp:x:update"/>
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" jid="fuvuv@blabber.im/Pix-Art Messenger.8zoB" role="participant"/>
</x>
</presence>`).firstElementChild;
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(5);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("fuvuv has entered the groupchat");
presence = Strophe.xmlHtmlNode(
`<presence xmlns="jabber:client" to="dummy@localhost/resource" type="unavailable" from="coven@chat.shakespeare.lit/fuvuv">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" jid="fuvuv@blabber.im/Pix-Art Messenger.8zoB" role="none"/>
</x>
</presence>`).firstElementChild;
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(5);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("fuvuv has entered and left the groupchat");
presence = Strophe.xmlHtmlNode(
`<presence xmlns="jabber:client" to="dummy@localhost/resource" type="unavailable" from="coven@chat.shakespeare.lit/fabio">
<status>Disconnected: Replaced by new connection</status>
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" jid="fabio@montefuscolo.com.br/Conversations.ZvLu" role="none"/>
</x>
</presence>`).firstElementChild;
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(5);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
`fabio has entered and left the groupchat. "Disconnected: Replaced by new connection"`);
presence = Strophe.xmlHtmlNode(
`<presence xmlns="jabber:client" to="dummy@localhost/resource" from="coven@chat.shakespeare.lit/fabio">
<c xmlns="http://jabber.org/protocol/caps" node="http://conversations.im" ver="INI3xjRUioclBTP/aACfWi5m9UY=" hash="sha-1"/>
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" jid="fabio@montefuscolo.com.br/Conversations.ZvLu" role="participant"/>
</x>
</presence>`).firstElementChild;
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(5);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
`fabio has entered the groupchat`);
// XXX: hack so that we can test leave/enter of occupants
// who were already in the room when we joined.
chat_content.innerHTML = '';
presence = Strophe.xmlHtmlNode(
`<presence xmlns="jabber:client" to="dummy@localhost/resource" type="unavailable" from="coven@chat.shakespeare.lit/fabio">
<status>Disconnected: closed</status>
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" jid="fabio@montefuscolo.com.br/Conversations.ZvLu" role="none"/>
</x>
</presence>`).firstElementChild;
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(1);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
`fabio has left the groupchat. "Disconnected: closed"`);
presence = Strophe.xmlHtmlNode(
`<presence xmlns="jabber:client" to="dummy@localhost/resource" type="unavailable" from="coven@chat.shakespeare.lit/Dele Olajide">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" jid="deleo@traderlynk.4ng.net/converse.js-74567907" role="none"/>
</x>
</presence>`).firstElementChild;
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(2);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
`Dele Olajide has left the groupchat`);
presence = Strophe.xmlHtmlNode(
`<presence xmlns="jabber:client" to="dummy@localhost/resource" from="coven@chat.shakespeare.lit/fabio">
<c xmlns="http://jabber.org/protocol/caps" node="http://conversations.im" ver="INI3xjRUioclBTP/aACfWi5m9UY=" hash="sha-1"/>
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" jid="fabio@montefuscolo.com.br/Conversations.ZvLu" role="participant"/>
</x>
</presence>`).firstElementChild;
_converse.connection._dataRecv(test_utils.createRequest(presence));
expect(sizzle('div.chat-info', chat_content).length).toBe(2);
expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(
`fabio has left and re-entered the groupchat`);
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}));
it("shows a new day indicator if a join/leave message is received on a new day", it("shows a new day indicator if a join/leave message is received on a new day",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
......
...@@ -499,7 +499,7 @@ ...@@ -499,7 +499,7 @@
return this; return this;
}, },
showChatEvent (message, data='') { showChatEvent (message) {
const isodate = moment().format(); const isodate = moment().format();
this.content.insertAdjacentHTML( this.content.insertAdjacentHTML(
'beforeend', 'beforeend',
...@@ -507,7 +507,6 @@ ...@@ -507,7 +507,6 @@
'extra_classes': 'chat-event', 'extra_classes': 'chat-event',
'message': message, 'message': message,
'isodate': isodate, 'isodate': isodate,
'data': data
})); }));
this.insertDayIndicator(this.content.lastElementChild); this.insertDayIndicator(this.content.lastElementChild);
this.scrollDown(); this.scrollDown();
......
...@@ -163,8 +163,7 @@ ...@@ -163,8 +163,7 @@
msg = u.stringToElement( msg = u.stringToElement(
tpl_info(_.extend(this.model.toJSON(), { tpl_info(_.extend(this.model.toJSON(), {
'extra_classes': 'chat-error', 'extra_classes': 'chat-error',
'isodate': moment_time.format(), 'isodate': moment_time.format()
'data': ''
}))); })));
return this.replaceElement(msg); return this.replaceElement(msg);
}, },
......
...@@ -1462,7 +1462,6 @@ ...@@ -1462,7 +1462,6 @@
this.content.insertAdjacentHTML( this.content.insertAdjacentHTML(
'beforeend', 'beforeend',
tpl_info({ tpl_info({
'data': '',
'isodate': moment().format(), 'isodate': moment().format(),
'extra_classes': 'chat-event', 'extra_classes': 'chat-event',
'message': message 'message': message
...@@ -1505,19 +1504,22 @@ ...@@ -1505,19 +1504,22 @@
} }
const nick = occupant.get('nick'), const nick = occupant.get('nick'),
stat = occupant.get('status'), stat = occupant.get('status'),
last_el = this.content.lastElementChild; last_leave_el = this.getImmediateNotification(this.content.lastElementChild, nick, 'leave');
if (_.includes(_.get(last_el, 'classList', []), 'chat-info') && if (_.includes(_.get(last_leave_el, 'classList', []), 'chat-info') &&
_.get(last_el, 'dataset', {}).leave === `"${nick}"`) { _.get(last_leave_el, 'dataset', {}).leave === nick) {
last_el.outerHTML = let el = this.content.lastElementChild;
el.insertAdjacentElement('afterend', last_leave_el);
last_leave_el.outerHTML =
tpl_info({ tpl_info({
'data': `data-leavejoin="${nick}"`, 'data_name': 'leavejoin',
'data_value': nick,
'isodate': moment().format(), 'isodate': moment().format(),
'extra_classes': 'chat-event', 'extra_classes': 'chat-event',
'message': __('%1$s has left and re-entered the groupchat', nick) 'message': __('%1$s has left and re-entered the groupchat', nick)
}); });
const el = this.content.lastElementChild; el = this.content.lastElementChild;
setTimeout(() => u.addClass('fade-out', el), 5000); setTimeout(() => u.addClass('fade-out', el), 5000);
setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5250); setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5250);
} else { } else {
...@@ -1528,15 +1530,16 @@ ...@@ -1528,15 +1530,16 @@
message = __('%1$s has entered the groupchat. "%2$s"', nick, stat); message = __('%1$s has entered the groupchat. "%2$s"', nick, stat);
} }
const data = { const data = {
'data': `data-join="${nick}"`, 'data_name': 'join',
'data_value': nick,
'isodate': moment().format(), 'isodate': moment().format(),
'extra_classes': 'chat-event', 'extra_classes': 'chat-event',
'message': message 'message': message
}; };
if (_.includes(_.get(last_el, 'classList', []), 'chat-info') && if (_.includes(_.get(last_leave_el, 'classList', []), 'chat-info') &&
_.get(last_el, 'dataset', {}).joinleave === `"${nick}"`) { _.get(last_leave_el, 'dataset', {}).joinleave === nick) {
last_el.outerHTML = tpl_info(data); last_leave_el.outerHTML = tpl_info(data);
} else { } else {
const el = u.stringToElement(tpl_info(data)); const el = u.stringToElement(tpl_info(data));
this.content.insertAdjacentElement('beforeend', el); this.content.insertAdjacentElement('beforeend', el);
...@@ -1546,15 +1549,24 @@ ...@@ -1546,15 +1549,24 @@
this.scrollDown(); this.scrollDown();
}, },
getImmediateJoinNotification (el, nick) { getImmediateNotification (el, nick, type='join') {
while (!_.isNil(el)) { while (!_.isNil(el)) {
const data = _.get(el, 'dataset', {}); const data = _.get(el, 'dataset', {});
if (!_.includes(_.get(el, 'classList', []), 'chat-info')) { if (!_.includes(_.get(el, 'classList', []), 'chat-info')) {
return; return;
} }
if (moment(el.getAttribute('data-isodate')).isSame(new Date(), "day") && if (!moment(el.getAttribute('data-isodate')).isSame(new Date(), "day")) {
(data.join === `"${nick}"` || data.leavejoin === `"${nick}"`)) { el = el.previousElementSibling;
return el; continue;
}
if (type === 'join') {
if (data.join === nick || data.leavejoin === nick) {
return el;
}
} else {
if (data.leave === nick || data.joinleave === nick) {
return el;
}
} }
el = el.previousElementSibling; el = el.previousElementSibling;
} }
...@@ -1566,12 +1578,12 @@ ...@@ -1566,12 +1578,12 @@
} }
const nick = occupant.get('nick'), const nick = occupant.get('nick'),
stat = occupant.get('status'), stat = occupant.get('status'),
last_join_el = this.getImmediateJoinNotification(this.content.lastElementChild, nick), last_join_el = this.getImmediateNotification(this.content.lastElementChild, nick, 'join'),
data = _.get(last_join_el, 'dataset', {}); data = _.get(last_join_el, 'dataset', {});
if (last_join_el) { if (last_join_el) {
let message; let message;
if (data.join === `"${nick}"`) { if (data.join === nick) {
if (_.isNil(stat)) { if (_.isNil(stat)) {
message = __('%1$s has entered and left the groupchat', nick); message = __('%1$s has entered and left the groupchat', nick);
} else { } else {
...@@ -1581,7 +1593,8 @@ ...@@ -1581,7 +1593,8 @@
el.insertAdjacentElement('afterend', last_join_el); el.insertAdjacentElement('afterend', last_join_el);
last_join_el.outerHTML = last_join_el.outerHTML =
tpl_info({ tpl_info({
'data': `data-joinleave="${nick}"`, 'data_name': 'joinleave',
'data_value': nick,
'isodate': moment().format(), 'isodate': moment().format(),
'extra_classes': 'chat-event', 'extra_classes': 'chat-event',
'message': message 'message': message
...@@ -1589,7 +1602,7 @@ ...@@ -1589,7 +1602,7 @@
el = this.content.lastElementChild; el = this.content.lastElementChild;
setTimeout(() => u.addClass('fade-out', el), 5000); setTimeout(() => u.addClass('fade-out', el), 5000);
setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5250); setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5250);
} else if (data.leavejoin === `"${nick}"`) { } else if (data.leavejoin === nick) {
if (_.isNil(stat)) { if (_.isNil(stat)) {
message = __('%1$s has left the groupchat', nick); message = __('%1$s has left the groupchat', nick);
} else { } else {
...@@ -1597,7 +1610,8 @@ ...@@ -1597,7 +1610,8 @@
} }
last_join_el.outerHTML = last_join_el.outerHTML =
tpl_info({ tpl_info({
'data': `data-leave="${nick}"`, 'data_name': 'leave',
'data_value': nick,
'isodate': moment().format(), 'isodate': moment().format(),
'extra_classes': 'chat-event', 'extra_classes': 'chat-event',
'message': message 'message': message
...@@ -1614,7 +1628,8 @@ ...@@ -1614,7 +1628,8 @@
'message': message, 'message': message,
'isodate': moment().format(), 'isodate': moment().format(),
'extra_classes': 'chat-event', 'extra_classes': 'chat-event',
'data': `data-leave="${nick}"` 'data_name': 'leave',
'data_value': nick
} }
const el = u.stringToElement(tpl_info(data)); const el = u.stringToElement(tpl_info(data));
this.content.insertAdjacentElement('beforeend', el); this.content.insertAdjacentElement('beforeend', el);
...@@ -1727,7 +1742,6 @@ ...@@ -1727,7 +1742,6 @@
this.content.insertAdjacentHTML( this.content.insertAdjacentHTML(
'beforeend', 'beforeend',
tpl_info({ tpl_info({
'data': '',
'isodate': date, 'isodate': date,
'extra_classes': 'chat-event', 'extra_classes': 'chat-event',
'message': message 'message': message
...@@ -1737,7 +1751,6 @@ ...@@ -1737,7 +1751,6 @@
this.content.insertAdjacentHTML( this.content.insertAdjacentHTML(
'beforeend', 'beforeend',
tpl_info({ tpl_info({
'data': '',
'isodate': date, 'isodate': date,
'extra_classes': 'chat-topic', 'extra_classes': 'chat-topic',
'message': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), {'whiteList': {}})), 'message': u.addHyperlinks(xss.filterXSS(_.get(this.model.get('subject'), 'text'), {'whiteList': {}})),
......
{[ if (o.render_message) { ]} {[ if (o.render_message) { ]}
<!-- XXX: Should only ever be rendered if the message text has been sanitized already --> <!-- XXX: Should only ever be rendered if the message text has been sanitized already -->
<div class="message chat-info {{{o.extra_classes}}}" <div class="message chat-info {{{o.extra_classes}}}"
data-isodate="{{{o.isodate}}}" data-isodate="{{{o.isodate}}}" {[ if (o.data_name) { ]} data-{{{o.data_name}}}="{{{o.data_value}}}"{[ } ]}>{{o.message}}</div>
{{{o.data}}}>{{o.message}}</div>
{[ } else { ]} {[ } else { ]}
<div class="message chat-info {{{o.extra_classes}}}" <div class="message chat-info {{{o.extra_classes}}}"
data-isodate="{{{o.isodate}}}" data-isodate="{{{o.isodate}}}" {[ if (o.data_name) { ]} data-{{{o.data_name}}}="{{{o.data_value}}}"{[ } ]}>{{{o.message}}}</div>
{{{o.data}}}>{{{o.message}}}</div>
{[ } ]} {[ } ]}
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