Commit 3a0dac3a authored by Weblate's avatar Weblate

Merge remote-tracking branch 'origin/master'

parents d0e116a0 da3670d9
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
- Show status messages in an MUC room when a user's role changes. - Show status messages in an MUC room when a user's role changes.
- In MUC chat rooms, collapse multiple, consecutive join/leave messages. - In MUC chat rooms, collapse multiple, consecutive join/leave messages.
- Performance improvements for rendering private chats, rooms and the contacts roster. - Performance improvements for rendering private chats, rooms and the contacts roster.
- MUC Leave/Join messages now also show a new day indicator if applicable.
### API changes ### API changes
- New API method `_converse.disco.supports` to check whether a certain - New API method `_converse.disco.supports` to check whether a certain
......
...@@ -879,6 +879,60 @@ ...@@ -879,6 +879,60 @@
}).then(done); }).then(done);
})); }));
it("is ignored if it's intended for a different resource and filter_by_resource is set to true",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
test_utils.createContacts(_converse, 'current');
test_utils.openControlBox();
test_utils.openContactsPanel(_converse);
test_utils.waitUntil(function () {
return _converse.rosterview.$el.find('.roster-group').length;
}, 300)
.then(function () {
// Send a message from a different resource
var message, sender_jid, msg;
spyOn(_converse, 'log');
spyOn(_converse.chatboxes, 'getChatBox').and.callThrough();
_converse.filter_by_resource = true;
sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
msg = $msg({
from: sender_jid,
to: _converse.bare_jid+"/some-other-resource",
type: 'chat',
id: (new Date()).getTime()
}).c('body').t("This message will not be shown").up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
expect(_converse.log).toHaveBeenCalledWith(
"onMessage: Ignoring incoming message intended for a different resource: dummy@localhost/some-other-resource",
Strophe.LogLevel.INFO);
expect(_converse.chatboxes.getChatBox).not.toHaveBeenCalled();
_converse.filter_by_resource = false;
message = "This message sent to a different resource will be shown";
msg = $msg({
from: sender_jid,
to: _converse.bare_jid+"/some-other-resource",
type: 'chat',
id: '134234623462346'
}).c('body').t(message).up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
expect(_converse.chatboxes.getChatBox).toHaveBeenCalled();
var chatboxview = _converse.chatboxviews.get(sender_jid);
var $chat_content = chatboxview.$el.find('.chat-content:last');
var msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
expect(msg_txt).toEqual(message);
done();
});
}));
});
it("can be received out of order, and will still be displayed in the right order", it("can be received out of order, and will still be displayed in the right order",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
...@@ -1017,60 +1071,6 @@ ...@@ -1017,60 +1071,6 @@
}); });
})); }));
it("is ignored if it's intended for a different resource and filter_by_resource is set to true",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
test_utils.createContacts(_converse, 'current');
test_utils.openControlBox();
test_utils.openContactsPanel(_converse);
test_utils.waitUntil(function () {
return _converse.rosterview.$el.find('.roster-group').length;
}, 300)
.then(function () {
// Send a message from a different resource
var message, sender_jid, msg;
spyOn(_converse, 'log');
spyOn(_converse.chatboxes, 'getChatBox').and.callThrough();
_converse.filter_by_resource = true;
sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
msg = $msg({
from: sender_jid,
to: _converse.bare_jid+"/some-other-resource",
type: 'chat',
id: (new Date()).getTime()
}).c('body').t("This message will not be shown").up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
expect(_converse.log).toHaveBeenCalledWith(
"onMessage: Ignoring incoming message intended for a different resource: dummy@localhost/some-other-resource",
Strophe.LogLevel.INFO);
expect(_converse.chatboxes.getChatBox).not.toHaveBeenCalled();
_converse.filter_by_resource = false;
message = "This message sent to a different resource will be shown";
msg = $msg({
from: sender_jid,
to: _converse.bare_jid+"/some-other-resource",
type: 'chat',
id: '134234623462346'
}).c('body').t(message).up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree();
_converse.chatboxes.onMessage(msg);
expect(_converse.chatboxes.getChatBox).toHaveBeenCalled();
var chatboxview = _converse.chatboxviews.get(sender_jid);
var $chat_content = chatboxview.$el.find('.chat-content:last');
var msg_txt = $chat_content.find('.chat-message').find('.chat-msg-content').text();
expect(msg_txt).toEqual(message);
done();
});
}));
});
it("is ignored if it's a malformed headline message", it("is ignored if it's a malformed headline message",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
var $msg = converse.env.$msg; var $msg = converse.env.$msg;
var Strophe = converse.env.Strophe; var Strophe = converse.env.Strophe;
var Promise = converse.env.Promise; var Promise = converse.env.Promise;
var moment = converse.env.moment;
return describe("ChatRooms", function () { return describe("ChatRooms", function () {
describe("The \"rooms\" API", function () { describe("The \"rooms\" API", function () {
...@@ -591,6 +592,147 @@ ...@@ -591,6 +592,147 @@
done(); done();
})); }));
it("shows a new day indicator if a join/leave message is received on a new day",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1');
var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
var $chat_content = view.$el.find('.chat-content');
/* <presence to="dummy@localhost/_converse.js-29092160"
* from="coven@chat.shakespeare.lit/some1">
* <x xmlns="http://jabber.org/protocol/muc#user">
* <item affiliation="owner" jid="dummy@localhost/_converse.js-29092160" role="moderator"/>
* <status code="110"/>
* </x>
* </presence></body>
*/
var presence = $pres({
to: 'dummy@localhost/_converse.js-29092160',
from: 'coven@chat.shakespeare.lit/some1'
}).c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'owner',
'jid': 'dummy@localhost/_converse.js-29092160',
'role': 'moderator'
}).up()
.c('status', {code: '110'});
_converse.connection._dataRecv(test_utils.createRequest(presence));
var $time = $chat_content.find('time');
expect($time.length).toEqual(1);
expect($time.attr('class')).toEqual('chat-info chat-date');
expect($time.data('isodate')).toEqual(moment().startOf('day').format());
expect($time.text()).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
expect($chat_content.find('div.chat-info:first').html()).toBe("some1 has entered the room.");
// XXX: Hack. We clear the chat contents instead of mocking the date
$chat_content.html('');
// Test a user leaving a chat room
presence = $pres({
to: 'dummy@localhost/_converse.js-29092160',
type: 'unavailable',
from: 'coven@chat.shakespeare.lit/some1'
})
.c('status', 'Disconnected: Replaced by new connection').up()
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'some1@localhost/_converse.js-290929789',
'role': 'none'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
$time = $chat_content.find('time');
expect($time.length).toEqual(1);
expect($time.attr('class')).toEqual('chat-info chat-date');
expect($time.data('isodate')).toEqual(moment().startOf('day').format());
expect($time.text()).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
expect($chat_content.find('div.chat-info').length).toBe(1);
expect($chat_content.find('div.chat-info:last').html()).toBe(
'some1 has left the room. '+
'"Disconnected: Replaced by new connection"');
// XXX: Hack. We clear the chat contents instead of mocking the date
$chat_content.html('');
var stanza = Strophe.xmlHtmlNode(
'<message xmlns="jabber:client"' +
' to="dummy@localhost/_converse.js-290929789"' +
' type="groupchat"' +
' from="coven@chat.shakespeare.lit/some1">'+
' <body>hello world</body>'+
' <delay xmlns="urn:xmpp:delay" stamp="2018-01-01T09:35:39Z" from="some1@localhost"/>'+
'</message>').firstChild;
_converse.connection._dataRecv(test_utils.createRequest(stanza));
presence = $pres({
to: 'dummy@localhost/_converse.js-29092160',
from: 'coven@chat.shakespeare.lit/newguy'
}).c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'newguy@localhost/_converse.js-290929789',
'role': 'participant'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
$time = $chat_content.find('time');
expect($time.length).toEqual(2);
$time = $chat_content.find('time:eq(1)');
expect($time.attr('class')).toEqual('chat-info chat-date');
expect($time.data('isodate')).toEqual(moment().startOf('day').format());
expect($time.text()).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
expect($chat_content.find('div.chat-info').length).toBe(1);
expect($chat_content.find('div.chat-info:first').html()).toBe("newguy has entered the room.");
// XXX: Hack. We clear the chat contents instead of mocking the date
$chat_content.html('');
stanza = Strophe.xmlHtmlNode(
'<message xmlns="jabber:client"' +
' to="dummy@localhost/_converse.js-290929789"' +
' type="groupchat"' +
' from="coven@chat.shakespeare.lit/some1">'+
' <body>hello world</body>'+
' <delay xmlns="urn:xmpp:delay" stamp="2018-01-01T09:35:39Z" from="some1@localhost"/>'+
'</message>').firstChild;
_converse.connection._dataRecv(test_utils.createRequest(stanza));
// Test a user leaving a chat room
presence = $pres({
to: 'dummy@localhost/_converse.js-29092160',
type: 'unavailable',
from: 'coven@chat.shakespeare.lit/some1'
})
.c('status', 'Disconnected: Replaced by new connection').up()
.c('x', {xmlns: Strophe.NS.MUC_USER})
.c('item', {
'affiliation': 'none',
'jid': 'some1@localhost/_converse.js-290929789',
'role': 'none'
});
_converse.connection._dataRecv(test_utils.createRequest(presence));
$time = $chat_content.find('time');
expect($time.length).toEqual(2);
$time = $chat_content.find('time:eq(1)');
expect($time.attr('class')).toEqual('chat-info chat-date');
expect($time.data('isodate')).toEqual(moment().startOf('day').format());
expect($time.text()).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
expect($chat_content.find('div.chat-info').length).toBe(1);
expect($chat_content.find('div.chat-info:last').html()).toBe(
'some1 has left the room. '+
'"Disconnected: Replaced by new connection"');
done();
return;
}));
it("shows its description in the chat heading", it("shows its description in the chat heading",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
...@@ -1863,7 +2005,7 @@ ...@@ -1863,7 +2005,7 @@
textarea.textContent = '/help This is the room subject'; textarea.textContent = '/help This is the room subject';
$(textarea).trigger($.Event('keypress', {keyCode: 13})); $(textarea).trigger($.Event('keypress', {keyCode: 13}));
expect(view.onMessageSubmitted).toHaveBeenCalled(); expect(view.onMessageSubmitted).toHaveBeenCalled();
const info_messages = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); const info_messages = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info:not(.chat-date)'), 0);
expect(info_messages.length).toBe(17); expect(info_messages.length).toBe(17);
expect(info_messages.pop().textContent).toBe('/voice: Allow muted user to post messages'); expect(info_messages.pop().textContent).toBe('/voice: Allow muted user to post messages');
expect(info_messages.pop().textContent).toBe('/topic: Set room subject (alias for /subject)'); expect(info_messages.pop().textContent).toBe('/topic: Set room subject (alias for /subject)');
...@@ -2105,7 +2247,7 @@ ...@@ -2105,7 +2247,7 @@
.c('status', {'code': '307'}); .c('status', {'code': '307'});
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
expect( expect(
view.el.querySelectorAll('.chat-info')[2].textContent).toBe( view.el.querySelectorAll('.chat-info')[3].textContent).toBe(
"annoyingGuy has been kicked out"); "annoyingGuy has been kicked out");
done(); done();
}); });
......
...@@ -382,37 +382,41 @@ ...@@ -382,37 +382,41 @@
); );
}, },
insertDayIndicator (date, insert_method) { insertDayIndicator (next_msg_el) {
/* Inserts an indicator /* Inserts an indicator into the chat area, showing the
* into the chat area, showing the day as given by the * day as given by the passed in date.
* passed in date.
* *
* Parameters: * The indicator is only inserted if necessary.
* (String) date - An ISO8601 date string.
* (Function) insert_method - The method to be used to
* insert the indicator
*/
const day_date = moment(date).startOf('day');
insert_method(tpl_new_day({
isodate: day_date.format(),
datestring: day_date.format("dddd MMM Do YYYY")
}));
},
insertMessage (attrs, insert_method) {
/* Helper method which appends a message (or prepends if the
* 2nd parameter is set to true) to the end of the chat box's
* content area.
* *
* Parameters: * Parameters:
* (Object) attrs: An object containing the message attributes. * (HTMLElement) next_msg_el - The message element before
* which the day indicator element must be inserted.
* This element must have a "data-isodate" attribute
* which specifies its creation date.
*/ */
_.flow((el) => { const prev_msg_el = this.getPreviousMessageElement(next_msg_el),
insert_method(el); prev_msg_date = _.isNull(prev_msg_el) ? null : prev_msg_el.getAttribute('data-isodate'),
return el; next_msg_date = next_msg_el.getAttribute('data-isodate');
if (_.isNull(prev_msg_date) || moment(next_msg_date).isAfter(prev_msg_date, 'day')) {
const day_date = moment(next_msg_date).startOf('day');
next_msg_el.insertAdjacentHTML('beforeBegin',
tpl_new_day({
'isodate': day_date.format(),
'datestring': day_date.format("dddd MMM Do YYYY")
})
);
}
}, },
this.scrollDown.bind(this)
)(this.renderMessage(attrs)); getPreviousMessageElement (el) {
let prev_msg_el = el.previousSibling;
while (!_.isNull(prev_msg_el) &&
!u.hasClass(prev_msg_el, 'message') &&
!u.hasClass(prev_msg_el, 'chat-info')) {
prev_msg_el = prev_msg_el.previousSibling
}
return prev_msg_el;
}, },
getLastMessageElement () { getLastMessageElement () {
...@@ -469,11 +473,6 @@ ...@@ -469,11 +473,6 @@
} }
}, },
getDayIndicatorElement (date) {
return this.content.querySelector(
`.chat-date[data-isodate="${date.startOf('day').format()}"]`);
},
showMessage (attrs) { showMessage (attrs) {
/* Inserts a chat message into the content area of the chat box. /* Inserts a chat message into the content area of the chat box.
* Will also insert a new day indicator if the message is on a * Will also insert a new day indicator if the message is on a
...@@ -488,37 +487,17 @@ ...@@ -488,37 +487,17 @@
*/ */
const current_msg_date = moment(attrs.time) || moment, const current_msg_date = moment(attrs.time) || moment,
prepend_html = _.bind(this.content.insertAdjacentHTML, this.content, 'afterbegin'), prepend_html = _.bind(this.content.insertAdjacentHTML, this.content, 'afterbegin'),
previous_msg_date = this.getLastMessageDate(current_msg_date); previous_msg_date = this.getLastMessageDate(current_msg_date),
message_el = this.renderMessage(attrs);
if (_.isNull(previous_msg_date)) { if (_.isNull(previous_msg_date)) {
this.insertMessage(attrs, _.bind(this.content.insertAdjacentElement, this.content, 'afterbegin')); this.content.insertAdjacentElement('afterbegin', message_el);
this.insertDayIndicator(current_msg_date, prepend_html);
} else { } else {
const previous_msg_el = sizzle(`[data-isodate="${previous_msg_date}"]:last`, this.content).pop(); const previous_msg_el = sizzle(`[data-isodate="${previous_msg_date}"]:last`, this.content).pop();
const day_el = this.getDayIndicatorElement(current_msg_date) previous_msg_el.insertAdjacentElement('afterend', message_el);
if (current_msg_date.isAfter(previous_msg_date, 'day')) {
if (_.isNull(day_el)) {
this.insertMessage(
attrs,
_.bind(previous_msg_el.insertAdjacentElement, previous_msg_el, 'afterend')
);
this.insertDayIndicator(
current_msg_date,
_.bind(this.content.insertAdjacentHTML, previous_msg_el, 'afterend')
);
} else {
this.insertMessage(
attrs,
_.bind(previous_msg_el.insertAdjacentElement, day_el, 'afterend')
);
}
} else {
this.insertMessage(
attrs,
_.bind(previous_msg_el.insertAdjacentElement, previous_msg_el, 'afterend')
);
}
} }
this.insertDayIndicator(message_el);
this.scrollDown();
}, },
getExtraMessageTemplateAttributes () { getExtraMessageTemplateAttributes () {
......
...@@ -1837,6 +1837,7 @@ ...@@ -1837,6 +1837,7 @@
const nick = Strophe.getResourceFromJid(stanza.getAttribute('from')); const nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
const stat = stanza.querySelector('status'); const stat = stanza.querySelector('status');
const last_el = this.content.lastElementChild; const last_el = this.content.lastElementChild;
if (_.includes(_.get(last_el, 'classList', []), 'chat-info') && if (_.includes(_.get(last_el, 'classList', []), 'chat-info') &&
_.get(last_el, 'dataset', {}).leave === `"${nick}"`) { _.get(last_el, 'dataset', {}).leave === `"${nick}"`) {
last_el.outerHTML = last_el.outerHTML =
...@@ -1860,7 +1861,9 @@ ...@@ -1860,7 +1861,9 @@
last_el.outerHTML = tpl_info(data); last_el.outerHTML = tpl_info(data);
} else { } else {
this.content.insertAdjacentHTML('beforeend', tpl_info(data)); const el = u.stringToElement(tpl_info(data));
this.content.insertAdjacentElement('beforeend', el);
this.insertDayIndicator(el);
} }
} }
this.scrollDown(); this.scrollDown();
...@@ -1890,6 +1893,7 @@ ...@@ -1890,6 +1893,7 @@
} }
const data = { const data = {
'message': message, 'message': message,
'isodate': moment().format(),
'data': `data-leave="${nick}"` 'data': `data-leave="${nick}"`
} }
if (_.includes(_.get(last_el, 'classList', []), 'chat-info') && if (_.includes(_.get(last_el, 'classList', []), 'chat-info') &&
...@@ -1897,7 +1901,9 @@ ...@@ -1897,7 +1901,9 @@
last_el.outerHTML = tpl_info(data); last_el.outerHTML = tpl_info(data);
} else { } else {
this.content.insertAdjacentHTML('beforeend', tpl_info(data)); const el = u.stringToElement(tpl_info(data));
this.content.insertAdjacentElement('beforeend', el);
this.insertDayIndicator(el);
} }
} }
this.scrollDown(); this.scrollDown();
......
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