converse-muc: Create info
and error
messages on the model
instead of on the view.
This commit is contained in:
parent
970ba96ce1
commit
bbe2a62295
@ -2344,7 +2344,8 @@
|
||||
.c('status').attrs({code:'210'}).nodeTree;
|
||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||
view.model.sendMessage('hello world');
|
||||
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
|
||||
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 3);
|
||||
|
||||
expect(view.model.messages.last().get('affiliation')).toBe('owner');
|
||||
expect(view.model.messages.last().get('role')).toBe('moderator');
|
||||
expect(view.el.querySelectorAll('.chat-msg').length).toBe(3);
|
||||
|
186
spec/muc.js
186
spec/muc.js
@ -359,7 +359,7 @@
|
||||
*/
|
||||
const presence = $pres({
|
||||
to:'romeo@montague.lit/orchard',
|
||||
from:'lounge@montague.lit/thirdwitch',
|
||||
from:'lounge@montague.lit/nicky',
|
||||
id:'5025e055-036c-4bc5-a227-706e7e352053'
|
||||
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
|
||||
.c('item').attrs({
|
||||
@ -371,9 +371,12 @@
|
||||
.c('status').attrs({code:'201'}).nodeTree;
|
||||
|
||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||
const info_text = view.el.querySelector('.chat-content .chat-info').textContent;
|
||||
expect(info_text).toBe('A new groupchat has been created');
|
||||
|
||||
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
|
||||
|
||||
const info_texts = Array.from(view.el.querySelectorAll('.chat-content .chat-info')).map(e => e.textContent);
|
||||
expect(info_texts[0]).toBe('A new groupchat has been created');
|
||||
expect(info_texts[1]).toBe('nicky has entered the groupchat');
|
||||
|
||||
// An instant room is created by saving the default configuratoin.
|
||||
//
|
||||
@ -448,7 +451,7 @@
|
||||
done()
|
||||
}));
|
||||
|
||||
it("shows a notification if its not anonymous",
|
||||
it("shows a notification if it's not anonymous",
|
||||
mock.initConverse(
|
||||
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
|
||||
async function (done, _converse) {
|
||||
@ -465,27 +468,7 @@
|
||||
* </x>
|
||||
* </presence></body>
|
||||
*/
|
||||
let presence = $pres({
|
||||
to: 'romeo@montague.lit/orchard',
|
||||
from: 'coven@chat.shakespeare.lit/some1'
|
||||
}).c('x', {xmlns: Strophe.NS.MUC_USER})
|
||||
.c('item', {
|
||||
'affiliation': 'owner',
|
||||
'jid': 'romeo@montague.lit/_converse.js-29092160',
|
||||
'role': 'moderator'
|
||||
}).up()
|
||||
.c('status', {code: '110'}).up()
|
||||
.c('status', {code: '100'});
|
||||
|
||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||
expect(chat_content.querySelectorAll('.chat-info').length).toBe(2);
|
||||
expect(sizzle('div.chat-info:first', chat_content).pop().textContent)
|
||||
.toBe("This groupchat is not anonymous");
|
||||
expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
|
||||
.toBe("some1 has entered the groupchat");
|
||||
|
||||
// Check that we don't show the notification twice
|
||||
presence = $pres({
|
||||
const presence = $pres({
|
||||
to: 'romeo@montague.lit/orchard',
|
||||
from: 'coven@chat.shakespeare.lit/some1'
|
||||
}).c('x', {xmlns: Strophe.NS.MUC_USER})
|
||||
@ -497,7 +480,8 @@
|
||||
.c('status', {code: '110'}).up()
|
||||
.c('status', {code: '100'});
|
||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||
expect(chat_content.querySelectorAll('.chat-info').length).toBe(2);
|
||||
|
||||
await test_utils.waitUntil(() => chat_content.querySelectorAll('.chat-info').length === 2);
|
||||
expect(sizzle('div.chat-info:first', chat_content).pop().textContent)
|
||||
.toBe("This groupchat is not anonymous");
|
||||
expect(sizzle('div.chat-info:last', chat_content).pop().textContent)
|
||||
@ -1814,6 +1798,7 @@
|
||||
.c('status').attrs({code:'210'}).nodeTree;
|
||||
|
||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2);
|
||||
const info_text = sizzle('.chat-content .chat-info:first', view.el).pop().textContent;
|
||||
expect(info_text).toBe('Your nickname has been automatically set to thirdwitch');
|
||||
done();
|
||||
@ -2096,7 +2081,7 @@
|
||||
done();
|
||||
}));
|
||||
|
||||
it("informs users if their nicknames has been changed.",
|
||||
it("informs users if their nicknames have been changed.",
|
||||
mock.initConverse(
|
||||
null, ['rosterGroupsFetched'], {},
|
||||
async function (done, _converse) {
|
||||
@ -2167,11 +2152,12 @@
|
||||
.c('status').attrs({code:'110'}).nodeTree;
|
||||
|
||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2);
|
||||
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2);
|
||||
|
||||
expect(sizzle('div.chat-info:last').pop().textContent).toBe(
|
||||
__(_converse.muc.new_nickname_messages["303"], "newnick")
|
||||
);
|
||||
expect(view.model.get('connection_status')).toBe(converse.ROOMSTATUS.DISCONNECTED);
|
||||
expect(view.model.get('connection_status')).toBe(converse.ROOMSTATUS.ENTERED);
|
||||
|
||||
occupants = view.el.querySelector('.occupant-list');
|
||||
expect(occupants.childNodes.length).toBe(1);
|
||||
@ -2509,6 +2495,7 @@
|
||||
.c('status', {code: '104'}).up()
|
||||
.c('status', {code: '172'});
|
||||
_converse.connection._dataRecv(test_utils.createRequest(message));
|
||||
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 3);
|
||||
const chat_body = view.el.querySelector('.chatroom-body');
|
||||
expect(sizzle('.message:last', chat_body).pop().textContent)
|
||||
.toBe('This groupchat is now no longer anonymous');
|
||||
@ -2551,6 +2538,7 @@
|
||||
.up()
|
||||
.c('status').attrs({code:'110'}).up()
|
||||
.c('status').attrs({code:'307'}).nodeTree;
|
||||
|
||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||
|
||||
const view = _converse.chatboxviews.get('lounge@montague.lit');
|
||||
@ -3232,6 +3220,8 @@
|
||||
}).up()
|
||||
.c('status', {'code': '307'});
|
||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||
|
||||
await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 4);
|
||||
expect(view.el.querySelectorAll('.chat-info')[3].textContent).toBe("annoying guy has been kicked out");
|
||||
expect(view.el.querySelectorAll('.chat-info').length).toBe(4);
|
||||
done();
|
||||
@ -3628,6 +3618,33 @@
|
||||
|
||||
const groupchat_jid = 'members-only@muc.montague.lit'
|
||||
await test_utils.openChatRoomViaModal(_converse, groupchat_jid, 'romeo');
|
||||
const view = _converse.chatboxviews.get(groupchat_jid);
|
||||
const iq = await test_utils.waitUntil(() => _.filter(
|
||||
_converse.connection.IQ_stanzas,
|
||||
iq => iq.querySelector(
|
||||
`iq[to="${groupchat_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
|
||||
)).pop());
|
||||
|
||||
// State that the chat is members-only via the features IQ
|
||||
const features_stanza = $iq({
|
||||
'from': groupchat_jid,
|
||||
'id': iq.getAttribute('id'),
|
||||
'to': 'romeo@montague.lit/desktop',
|
||||
'type': 'result'
|
||||
})
|
||||
.c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
|
||||
.c('identity', {
|
||||
'category': 'conference',
|
||||
'name': 'A Dark Cave',
|
||||
'type': 'text'
|
||||
}).up()
|
||||
.c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
|
||||
.c('feature', {'var': 'muc_hidden'}).up()
|
||||
.c('feature', {'var': 'muc_temporary'}).up()
|
||||
.c('feature', {'var': 'muc_membersonly'}).up();
|
||||
_converse.connection._dataRecv(test_utils.createRequest(features_stanza));
|
||||
await test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING);
|
||||
|
||||
const presence = $pres().attrs({
|
||||
from: `${groupchat_jid}/romeo`,
|
||||
id: u.getUniqueId(),
|
||||
@ -3637,8 +3654,6 @@
|
||||
.c('error').attrs({by:'lounge@montague.lit', type:'auth'})
|
||||
.c('registration-required').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||
|
||||
const view = _converse.chatboxviews.get(groupchat_jid);
|
||||
spyOn(view, 'showErrorMessage').and.callThrough();
|
||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||
expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent)
|
||||
.toBe('You are not on the member list of this groupchat.');
|
||||
@ -3652,6 +3667,29 @@
|
||||
|
||||
const groupchat_jid = 'off-limits@muc.montague.lit'
|
||||
await test_utils.openChatRoomViaModal(_converse, groupchat_jid, 'romeo');
|
||||
|
||||
const iq = await test_utils.waitUntil(() => _.filter(
|
||||
_converse.connection.IQ_stanzas,
|
||||
iq => iq.querySelector(
|
||||
`iq[to="${groupchat_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
|
||||
)).pop());
|
||||
|
||||
const features_stanza = $iq({
|
||||
'from': groupchat_jid,
|
||||
'id': iq.getAttribute('id'),
|
||||
'to': 'romeo@montague.lit/desktop',
|
||||
'type': 'result'
|
||||
})
|
||||
.c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
|
||||
.c('identity', {'category': 'conference', 'name': 'A Dark Cave', 'type': 'text'}).up()
|
||||
.c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
|
||||
.c('feature', {'var': 'muc_hidden'}).up()
|
||||
.c('feature', {'var': 'muc_temporary'}).up()
|
||||
_converse.connection._dataRecv(test_utils.createRequest(features_stanza));
|
||||
|
||||
const view = _converse.chatboxviews.get(groupchat_jid);
|
||||
await test_utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING);
|
||||
|
||||
const presence = $pres().attrs({
|
||||
from: `${groupchat_jid}/romeo`,
|
||||
id: u.getUniqueId(),
|
||||
@ -3661,7 +3699,6 @@
|
||||
.c('error').attrs({by:'lounge@montague.lit', type:'auth'})
|
||||
.c('forbidden').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||
|
||||
const view = _converse.chatboxviews.get(groupchat_jid);
|
||||
spyOn(view, 'showErrorMessage').and.callThrough();
|
||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||
expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent)
|
||||
@ -3697,6 +3734,7 @@
|
||||
done();
|
||||
}));
|
||||
|
||||
|
||||
it("will automatically choose a new nickname if a nickname conflict happens and muc_nickname_from_jid=true",
|
||||
mock.initConverse(
|
||||
null, ['rosterGroupsFetched'], {},
|
||||
@ -3765,7 +3803,26 @@
|
||||
|
||||
const groupchat_jid = 'impermissable@muc.montague.lit'
|
||||
await test_utils.openChatRoomViaModal(_converse, groupchat_jid, 'romeo')
|
||||
var presence = $pres().attrs({
|
||||
|
||||
// We pretend this is a new room, so no disco info is returned.
|
||||
const iq = await test_utils.waitUntil(() => _.filter(
|
||||
_converse.connection.IQ_stanzas,
|
||||
iq => iq.querySelector(
|
||||
`iq[to="${groupchat_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
|
||||
)).pop());
|
||||
const features_stanza = $iq({
|
||||
'from': 'room@conference.example.org',
|
||||
'id': iq.getAttribute('id'),
|
||||
'to': 'romeo@montague.lit/desktop',
|
||||
'type': 'error'
|
||||
}).c('error', {'type': 'cancel'})
|
||||
.c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
|
||||
_converse.connection._dataRecv(test_utils.createRequest(features_stanza));
|
||||
|
||||
const view = _converse.chatboxviews.get(groupchat_jid);
|
||||
await test_utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
|
||||
|
||||
const presence = $pres().attrs({
|
||||
from: `${groupchat_jid}/romeo`,
|
||||
id: u.getUniqueId(),
|
||||
to:'romeo@montague.lit/pda',
|
||||
@ -3773,7 +3830,6 @@
|
||||
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||
.c('error').attrs({by:'lounge@montague.lit', type:'cancel'})
|
||||
.c('not-allowed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||
const view = _converse.chatboxviews.get(groupchat_jid);
|
||||
spyOn(view, 'showErrorMessage').and.callThrough();
|
||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||
expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent)
|
||||
@ -3788,6 +3844,25 @@
|
||||
|
||||
const groupchat_jid = 'conformist@muc.montague.lit'
|
||||
await test_utils.openChatRoomViaModal(_converse, groupchat_jid, 'romeo');
|
||||
|
||||
const iq = await test_utils.waitUntil(() => _.filter(
|
||||
_converse.connection.IQ_stanzas,
|
||||
iq => iq.querySelector(
|
||||
`iq[to="${groupchat_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
|
||||
)).pop());
|
||||
const features_stanza = $iq({
|
||||
'from': groupchat_jid,
|
||||
'id': iq.getAttribute('id'),
|
||||
'to': 'romeo@montague.lit/desktop',
|
||||
'type': 'result'
|
||||
}).c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
|
||||
.c('identity', {'category': 'conference', 'name': 'A Dark Cave', 'type': 'text'}).up()
|
||||
.c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
|
||||
_converse.connection._dataRecv(test_utils.createRequest(features_stanza));
|
||||
|
||||
const view = _converse.chatboxviews.get(groupchat_jid);
|
||||
await test_utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
|
||||
|
||||
const presence = $pres().attrs({
|
||||
from: `${groupchat_jid}/romeo`,
|
||||
id: u.getUniqueId(),
|
||||
@ -3797,7 +3872,6 @@
|
||||
.c('error').attrs({by:'lounge@montague.lit', type:'cancel'})
|
||||
.c('not-acceptable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||
|
||||
const view = _converse.chatboxviews.get(groupchat_jid);
|
||||
spyOn(view, 'showErrorMessage').and.callThrough();
|
||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||
expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent)
|
||||
@ -3812,6 +3886,25 @@
|
||||
|
||||
const groupchat_jid = 'nonexistent@muc.montague.lit'
|
||||
await test_utils.openChatRoomViaModal(_converse, groupchat_jid, 'romeo');
|
||||
|
||||
const iq = await test_utils.waitUntil(() => _.filter(
|
||||
_converse.connection.IQ_stanzas,
|
||||
iq => iq.querySelector(
|
||||
`iq[to="${groupchat_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
|
||||
)).pop());
|
||||
const features_stanza = $iq({
|
||||
'from': groupchat_jid,
|
||||
'id': iq.getAttribute('id'),
|
||||
'to': 'romeo@montague.lit/desktop',
|
||||
'type': 'result'
|
||||
}).c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
|
||||
.c('identity', {'category': 'conference', 'name': 'A Dark Cave', 'type': 'text'}).up()
|
||||
.c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
|
||||
_converse.connection._dataRecv(test_utils.createRequest(features_stanza));
|
||||
|
||||
const view = _converse.chatboxviews.get(groupchat_jid);
|
||||
await test_utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
|
||||
|
||||
const presence = $pres().attrs({
|
||||
from: `${groupchat_jid}/romeo`,
|
||||
id: u.getUniqueId(),
|
||||
@ -3821,7 +3914,6 @@
|
||||
.c('error').attrs({by:'lounge@montague.lit', type:'cancel'})
|
||||
.c('item-not-found').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||
|
||||
const view = _converse.chatboxviews.get(groupchat_jid);
|
||||
spyOn(view, 'showErrorMessage').and.callThrough();
|
||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||
expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent)
|
||||
@ -3836,6 +3928,25 @@
|
||||
|
||||
const groupchat_jid = 'maxed-out@muc.montague.lit'
|
||||
await test_utils.openChatRoomViaModal(_converse, groupchat_jid, 'romeo')
|
||||
|
||||
const iq = await test_utils.waitUntil(() => _.filter(
|
||||
_converse.connection.IQ_stanzas,
|
||||
iq => iq.querySelector(
|
||||
`iq[to="${groupchat_jid}"] query[xmlns="http://jabber.org/protocol/disco#info"]`
|
||||
)).pop());
|
||||
const features_stanza = $iq({
|
||||
'from': groupchat_jid,
|
||||
'id': iq.getAttribute('id'),
|
||||
'to': 'romeo@montague.lit/desktop',
|
||||
'type': 'result'
|
||||
}).c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
|
||||
.c('identity', {'category': 'conference', 'name': 'A Dark Cave', 'type': 'text'}).up()
|
||||
.c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
|
||||
_converse.connection._dataRecv(test_utils.createRequest(features_stanza));
|
||||
|
||||
const view = _converse.chatboxviews.get(groupchat_jid);
|
||||
await test_utils.waitUntil(() => (view.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING));
|
||||
|
||||
const presence = $pres().attrs({
|
||||
from: `${groupchat_jid}/romeo`,
|
||||
id: u.getUniqueId(),
|
||||
@ -3845,7 +3956,6 @@
|
||||
.c('error').attrs({by:'lounge@montague.lit', type:'cancel'})
|
||||
.c('service-unavailable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||
|
||||
const view = _converse.chatboxviews.get(groupchat_jid);
|
||||
spyOn(view, 'showErrorMessage').and.callThrough();
|
||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||
expect(view.el.querySelector('.chatroom-body .disconnect-container .disconnect-msg:last-child').textContent)
|
||||
@ -4878,3 +4988,5 @@
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
|
@ -112,6 +112,8 @@ converse.plugins.add('converse-message-view', {
|
||||
this.renderFileUploadProgresBar();
|
||||
} else if (this.model.get('type') === 'error') {
|
||||
this.renderErrorMessage();
|
||||
} else if (this.model.get('type') === 'info') {
|
||||
this.renderInfoMessage();
|
||||
} else {
|
||||
await this.renderChatMessage();
|
||||
}
|
||||
@ -212,6 +214,16 @@ converse.plugins.add('converse-message-view', {
|
||||
}
|
||||
},
|
||||
|
||||
renderInfoMessage () {
|
||||
const msg = u.stringToElement(
|
||||
tpl_info(Object.assign(this.model.toJSON(), {
|
||||
'extra_classes': 'chat-info',
|
||||
'isodate': dayjs(this.model.get('time')).toISOString()
|
||||
}))
|
||||
);
|
||||
return this.replaceElement(msg);
|
||||
},
|
||||
|
||||
renderErrorMessage () {
|
||||
const msg = u.stringToElement(
|
||||
tpl_info(Object.assign(this.model.toJSON(), {
|
||||
|
@ -197,6 +197,12 @@ converse.plugins.add('converse-minimize', {
|
||||
|
||||
|
||||
const minimizableChatBoxView = {
|
||||
|
||||
/**
|
||||
* Maximizes a minimized chat box.
|
||||
* Will trigger {@link _converse#chatBoxMaximized}
|
||||
* @returns {_converse.ChatBoxView|_converse.ChatRoomView}
|
||||
*/
|
||||
maximize () {
|
||||
// Restores a minimized chat box
|
||||
const { _converse } = this.__super__;
|
||||
@ -216,6 +222,11 @@ converse.plugins.add('converse-minimize', {
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Minimizes a chat box.
|
||||
* Will trigger {@link _converse#chatBoxMinimized}
|
||||
* @returns {_converse.ChatBoxView|_converse.ChatRoomView}
|
||||
*/
|
||||
minimize (ev) {
|
||||
const { _converse } = this.__super__;
|
||||
if (ev && ev.preventDefault) { ev.preventDefault(); }
|
||||
@ -234,6 +245,7 @@ converse.plugins.add('converse-minimize', {
|
||||
* @example _converse.api.listen.on('chatBoxMinimized', view => { ... });
|
||||
*/
|
||||
_converse.api.trigger('chatBoxMinimized', this);
|
||||
return this;
|
||||
},
|
||||
|
||||
onMinimizedChanged (item) {
|
||||
|
@ -139,87 +139,6 @@ converse.plugins.add('converse-muc-views', {
|
||||
Object.assign(_converse.ControlBoxView.prototype, { renderRoomsPanel });
|
||||
}
|
||||
|
||||
|
||||
function ___ (str) {
|
||||
/* This is part of a hack to get gettext to scan strings to be
|
||||
* translated. Strings we cannot send to the function above because
|
||||
* they require variable interpolation and we don't yet have the
|
||||
* variables at scan time.
|
||||
*
|
||||
* See actionInfoMessages further below.
|
||||
*/
|
||||
return str;
|
||||
}
|
||||
|
||||
/* https://xmpp.org/extensions/xep-0045.html
|
||||
* ----------------------------------------
|
||||
* 100 message Entering a groupchat Inform user that any occupant is allowed to see the user's full JID
|
||||
* 101 message (out of band) Affiliation change Inform user that his or her affiliation changed while not in the groupchat
|
||||
* 102 message Configuration change Inform occupants that groupchat now shows unavailable members
|
||||
* 103 message Configuration change Inform occupants that groupchat now does not show unavailable members
|
||||
* 104 message Configuration change Inform occupants that a non-privacy-related groupchat configuration change has occurred
|
||||
* 110 presence Any groupchat presence Inform user that presence refers to one of its own groupchat occupants
|
||||
* 170 message or initial presence Configuration change Inform occupants that groupchat logging is now enabled
|
||||
* 171 message Configuration change Inform occupants that groupchat logging is now disabled
|
||||
* 172 message Configuration change Inform occupants that the groupchat is now non-anonymous
|
||||
* 173 message Configuration change Inform occupants that the groupchat is now semi-anonymous
|
||||
* 174 message Configuration change Inform occupants that the groupchat is now fully-anonymous
|
||||
* 201 presence Entering a groupchat Inform user that a new groupchat has been created
|
||||
* 210 presence Entering a groupchat Inform user that the service has assigned or modified the occupant's roomnick
|
||||
* 301 presence Removal from groupchat Inform user that he or she has been banned from the groupchat
|
||||
* 303 presence Exiting a groupchat Inform all occupants of new groupchat nickname
|
||||
* 307 presence Removal from groupchat Inform user that he or she has been kicked from the groupchat
|
||||
* 321 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because of an affiliation change
|
||||
* 322 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because the groupchat has been changed to members-only and the user is not a member
|
||||
* 332 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because of a system shutdown
|
||||
*/
|
||||
_converse.muc = {
|
||||
info_messages: {
|
||||
100: __('This groupchat is not anonymous'),
|
||||
102: __('This groupchat now shows unavailable members'),
|
||||
103: __('This groupchat does not show unavailable members'),
|
||||
104: __('The groupchat configuration has changed'),
|
||||
170: __('groupchat logging is now enabled'),
|
||||
171: __('groupchat logging is now disabled'),
|
||||
172: __('This groupchat is now no longer anonymous'),
|
||||
173: __('This groupchat is now semi-anonymous'),
|
||||
174: __('This groupchat is now fully-anonymous'),
|
||||
201: __('A new groupchat has been created')
|
||||
},
|
||||
|
||||
disconnect_messages: {
|
||||
301: __('You have been banned from this groupchat'),
|
||||
307: __('You have been kicked from this groupchat'),
|
||||
321: __("You have been removed from this groupchat because of an affiliation change"),
|
||||
322: __("You have been removed from this groupchat because the groupchat has changed to members-only and you're not a member"),
|
||||
332: __("You have been removed from this groupchat because the service hosting it is being shut down")
|
||||
},
|
||||
|
||||
action_info_messages: {
|
||||
/* XXX: Note the triple underscore function and not double
|
||||
* underscore.
|
||||
*
|
||||
* This is a hack. We can't pass the strings to __ because we
|
||||
* don't yet know what the variable to interpolate is.
|
||||
*
|
||||
* Triple underscore will just return the string again, but we
|
||||
* can then at least tell gettext to scan for it so that these
|
||||
* strings are picked up by the translation machinery.
|
||||
*/
|
||||
301: ___("%1$s has been banned"),
|
||||
303: ___("%1$s's nickname has changed"),
|
||||
307: ___("%1$s has been kicked out"),
|
||||
321: ___("%1$s has been removed because of an affiliation change"),
|
||||
322: ___("%1$s has been removed for not being a member")
|
||||
},
|
||||
|
||||
new_nickname_messages: {
|
||||
210: ___('Your nickname has been automatically set to %1$s'),
|
||||
303: ___('Your nickname has been changed to %1$s')
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* Insert groupchat info (based on returned #disco IQ stanza)
|
||||
* @function insertRoomInfo
|
||||
* @param { HTMLElement } el - The HTML DOM element that contains the info.
|
||||
@ -581,7 +500,6 @@ converse.plugins.add('converse-muc-views', {
|
||||
this.render();
|
||||
this.updateAfterMessagesFetched();
|
||||
this.createOccupantsView();
|
||||
this.registerHandlers();
|
||||
this.onConnectionStatusChanged();
|
||||
/**
|
||||
* Triggered once a groupchat has been opened
|
||||
@ -779,6 +697,8 @@ converse.plugins.add('converse-muc-views', {
|
||||
const conn_status = this.model.get('connection_status');
|
||||
if (conn_status === converse.ROOMSTATUS.NICKNAME_REQUIRED) {
|
||||
this.renderNicknameForm();
|
||||
} else if (conn_status === converse.ROOMSTATUS.PASSWORD_REQUIRED) {
|
||||
this.renderPasswordForm();
|
||||
} else if (conn_status === converse.ROOMSTATUS.CONNECTING) {
|
||||
this.showSpinner();
|
||||
} else if (conn_status === converse.ROOMSTATUS.ENTERED) {
|
||||
@ -786,6 +706,10 @@ converse.plugins.add('converse-muc-views', {
|
||||
this.setChatState(_converse.ACTIVE);
|
||||
this.scrollDown();
|
||||
this.focus();
|
||||
} else if (conn_status === converse.ROOMSTATUS.DISCONNECTED) {
|
||||
this.showDisconnectMessage();
|
||||
} else if (conn_status === converse.ROOMSTATUS.DESTROYED) {
|
||||
this.showDestroyedMessage();
|
||||
}
|
||||
},
|
||||
|
||||
@ -1106,7 +1030,8 @@ converse.plugins.add('converse-muc-views', {
|
||||
if (!this.verifyRoles(['visitor', 'participant', 'moderator'])) {
|
||||
break;
|
||||
} else if (args.length === 0) {
|
||||
this.showErrorMessage(__('You need to provide a nickname'))
|
||||
// e.g. Your nickname is "coolguy69"
|
||||
this.showErrorMessage(__('Your nickname is "%1$s"', this.model.get('nick')))
|
||||
} else {
|
||||
const jid = Strophe.getBareJidFromJid(this.model.get('jid'));
|
||||
_converse.api.send($pres({
|
||||
@ -1152,42 +1077,6 @@ converse.plugins.add('converse-muc-views', {
|
||||
return true;
|
||||
},
|
||||
|
||||
registerHandlers () {
|
||||
/* Register presence and message handlers for this chat
|
||||
* groupchat
|
||||
*/
|
||||
// XXX: Ideally this can be refactored out so that we don't
|
||||
// need to do stanza processing inside the views in this
|
||||
// module. See the comment in "onPresence" for more info.
|
||||
this.model.addHandler('presence', 'ChatRoomView.onPresence', this.onPresence.bind(this));
|
||||
// XXX instead of having a method showStatusMessages, we could instead
|
||||
// create message models in converse-muc.js and then give them views in this module.
|
||||
this.model.addHandler('message', 'ChatRoomView.showStatusMessages', this.showStatusMessages.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles all MUC presence stanzas.
|
||||
* @private
|
||||
* @method _converse.ChatRoomView#onPresence
|
||||
* @param { XMLElement } pres - The stanza
|
||||
*/
|
||||
onPresence (pres) {
|
||||
// XXX: Current thinking is that excessive stanza
|
||||
// processing inside a view is a "code smell".
|
||||
// Instead stanza processing should happen inside the
|
||||
// models/collections.
|
||||
if (pres.getAttribute('type') === 'error') {
|
||||
this.showErrorMessageFromPresence(pres);
|
||||
} else {
|
||||
// Instead of doing it this way, we could perhaps rather
|
||||
// create StatusMessage objects inside the messages
|
||||
// Collection and then simply render those. Then stanza
|
||||
// processing is done on the model and rendering in the
|
||||
// view(s).
|
||||
this.showStatusMessages(pres);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders a form given an IQ stanza containing the current
|
||||
* groupchat configuration.
|
||||
@ -1246,32 +1135,6 @@ converse.plugins.add('converse-muc-views', {
|
||||
}
|
||||
},
|
||||
|
||||
onNicknameClash (presence) {
|
||||
/* When the nickname is already taken, we either render a
|
||||
* form for the user to choose a new nickname, or we
|
||||
* try to make the nickname unique by adding an integer to
|
||||
* it. So john will become john-2, and then john-3 and so on.
|
||||
*
|
||||
* Which option is take depends on the value of
|
||||
* muc_nickname_from_jid.
|
||||
*/
|
||||
if (_converse.muc_nickname_from_jid) {
|
||||
const nick = presence.getAttribute('from').split('/')[1];
|
||||
if (nick === _converse.getDefaultMUCNickname()) {
|
||||
this.model.join(nick + '-2');
|
||||
} else {
|
||||
const del= nick.lastIndexOf("-");
|
||||
const num = nick.substring(del+1, nick.length);
|
||||
this.model.join(nick.substring(0, del+1) + String(Number(num)+1));
|
||||
}
|
||||
} else {
|
||||
this.renderNicknameForm(
|
||||
__("The nickname you chose is reserved or "+
|
||||
"currently in use, please choose a different one.")
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
hideChatRoomContents () {
|
||||
const container_el = this.el.querySelector('.chatroom-body');
|
||||
if (!_.isNull(container_el)) {
|
||||
@ -1279,9 +1142,11 @@ converse.plugins.add('converse-muc-views', {
|
||||
}
|
||||
},
|
||||
|
||||
renderNicknameForm (message='') {
|
||||
renderNicknameForm () {
|
||||
/* Render a form which allows the user to choose theirnickname.
|
||||
*/
|
||||
const message = this.model.get('nickname_validation_message');
|
||||
this.model.save('nickname_validation_message', undefined);
|
||||
this.hideChatRoomContents();
|
||||
if (!this.nickname_form) {
|
||||
this.nickname_form = new _converse.MUCNicknameForm({
|
||||
@ -1297,8 +1162,11 @@ converse.plugins.add('converse-muc-views', {
|
||||
u.safeSave(this.model, {'connection_status': converse.ROOMSTATUS.NICKNAME_REQUIRED});
|
||||
},
|
||||
|
||||
renderPasswordForm (message='') {
|
||||
renderPasswordForm () {
|
||||
this.hideChatRoomContents();
|
||||
const message = this.model.get('password_validation_message');
|
||||
this.model.save('password_validation_message', undefined);
|
||||
|
||||
if (!this.password_form) {
|
||||
this.password_form = new _converse.MUCPasswordForm({
|
||||
'model': new Backbone.Model(),
|
||||
@ -1308,33 +1176,32 @@ converse.plugins.add('converse-muc-views', {
|
||||
const container_el = this.el.querySelector('.chatroom-body');
|
||||
container_el.insertAdjacentElement('beforeend', this.password_form.el);
|
||||
} else {
|
||||
this.model.set('validation_message', message);
|
||||
this.password_form.model.set('validation_message', message);
|
||||
}
|
||||
u.showElement(this.password_form.el);
|
||||
this.model.save('connection_status', converse.ROOMSTATUS.PASSWORD_REQUIRED);
|
||||
},
|
||||
|
||||
showDestroyedMessage (error) {
|
||||
showDestroyedMessage () {
|
||||
u.hideElement(this.el.querySelector('.chat-area'));
|
||||
u.hideElement(this.el.querySelector('.occupants'));
|
||||
sizzle('.spinner', this.el).forEach(u.removeElement);
|
||||
|
||||
const message = this.model.get('destroyed_message');
|
||||
const reason = this.model.get('destroyed_reason');
|
||||
const moved_jid = this.model.get('moved_jid');
|
||||
this.model.save({
|
||||
'destroyed_message': undefined,
|
||||
'destroyed_reason': undefined,
|
||||
'moved_jid': undefined
|
||||
});
|
||||
const container = this.el.querySelector('.disconnect-container');
|
||||
const moved_jid = _.get(
|
||||
sizzle('gone[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', error).pop(),
|
||||
'textContent'
|
||||
).replace(/^xmpp:/, '').replace(/\?join$/, '');
|
||||
const reason = _.get(
|
||||
sizzle('text[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', error).pop(),
|
||||
'textContent'
|
||||
);
|
||||
container.innerHTML = tpl_chatroom_destroyed({
|
||||
'_': _,
|
||||
'__':__,
|
||||
'jid': moved_jid,
|
||||
'reason': reason ? `"${reason}"` : null
|
||||
});
|
||||
|
||||
const switch_el = container.querySelector('a.switch-chat');
|
||||
if (switch_el) {
|
||||
switch_el.addEventListener('click', ev => {
|
||||
@ -1348,51 +1215,37 @@ converse.plugins.add('converse-muc-views', {
|
||||
u.showElement(container);
|
||||
},
|
||||
|
||||
showDisconnectMessages (msgs) {
|
||||
if (_.isString(msgs)) {
|
||||
msgs = [msgs];
|
||||
showDisconnectMessage () {
|
||||
const message = this.model.get('disconnection_message');
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
u.hideElement(this.el.querySelector('.chat-area'));
|
||||
u.hideElement(this.el.querySelector('.occupants'));
|
||||
sizzle('.spinner', this.el).forEach(u.removeElement);
|
||||
|
||||
const messages = [message];
|
||||
const actor = this.model.get('disconnection_actor');
|
||||
if (actor) {
|
||||
messages.push(__('This action was done by %1$s.', actor));
|
||||
}
|
||||
const reason = this.model.get('disconnection_reason');
|
||||
if (reason) {
|
||||
messages.push(__('The reason given is: "%1$s".', reason));
|
||||
}
|
||||
this.model.save({
|
||||
'disconnection_message': undefined,
|
||||
'disconnection_reason': undefined,
|
||||
'disconnection_actor': undefined
|
||||
});
|
||||
const container = this.el.querySelector('.disconnect-container');
|
||||
container.innerHTML = tpl_chatroom_disconnect({
|
||||
'_': _,
|
||||
'disconnect_messages': msgs
|
||||
'disconnect_messages': messages
|
||||
})
|
||||
u.showElement(container);
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @method _converse.ChatRoomView#getMessageFromStatus
|
||||
* @param { XMLElement } stat: A <status> element
|
||||
* @param { Boolean } is_self: Whether the element refers to the current user
|
||||
* @param { XMLElement } stanza: The original stanza received
|
||||
*/
|
||||
getMessageFromStatus (stat, stanza, is_self) {
|
||||
const code = stat.getAttribute('code');
|
||||
if (code === '110' || (code === '100' && !is_self)) { return; }
|
||||
if (code in _converse.muc.info_messages) {
|
||||
return _converse.muc.info_messages[code];
|
||||
}
|
||||
let nick;
|
||||
if (!is_self) {
|
||||
if (code in _converse.muc.action_info_messages) {
|
||||
nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
|
||||
return __(_converse.muc.action_info_messages[code], nick);
|
||||
}
|
||||
} else if (code in _converse.muc.new_nickname_messages) {
|
||||
if (is_self && code === "210") {
|
||||
nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
|
||||
} else if (is_self && code === "303") {
|
||||
nick = stanza.querySelector('x item').getAttribute('nick');
|
||||
}
|
||||
return __(_converse.muc.new_nickname_messages[code], nick);
|
||||
}
|
||||
return;
|
||||
},
|
||||
|
||||
getNotificationWithMessage (message) {
|
||||
let el = this.content.lastElementChild;
|
||||
while (!_.isNil(el)) {
|
||||
@ -1407,49 +1260,6 @@ converse.plugins.add('converse-muc-views', {
|
||||
}
|
||||
},
|
||||
|
||||
parseXUserElement (x, stanza, is_self) {
|
||||
/* Parse the passed-in <x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
* element and construct a map containing relevant
|
||||
* information.
|
||||
*/
|
||||
// 1. Get notification messages based on the <status> elements.
|
||||
const statuses = x.querySelectorAll('status');
|
||||
const mapper = _.partial(this.getMessageFromStatus, _, stanza, is_self);
|
||||
const notification = {};
|
||||
const messages = _.reject(
|
||||
_.reject(_.map(statuses, mapper), _.isUndefined),
|
||||
message => this.getNotificationWithMessage(message)
|
||||
);
|
||||
if (messages.length) {
|
||||
notification.messages = messages;
|
||||
}
|
||||
// 2. Get disconnection messages based on the <status> elements
|
||||
const codes = _.invokeMap(statuses, Element.prototype.getAttribute, 'code');
|
||||
const disconnection_codes = _.intersection(codes, Object.keys(_converse.muc.disconnect_messages));
|
||||
const disconnected = is_self && disconnection_codes.length > 0;
|
||||
if (disconnected) {
|
||||
notification.disconnected = true;
|
||||
notification.disconnection_message = _converse.muc.disconnect_messages[disconnection_codes[0]];
|
||||
}
|
||||
// 3. Find the reason and actor from the <item> element
|
||||
const item = x.querySelector('item');
|
||||
// By using querySelector above, we assume here there is
|
||||
// one <item> per <x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
// element. This appears to be a safe assumption, since
|
||||
// each <x/> element pertains to a single user.
|
||||
if (!_.isNull(item)) {
|
||||
const reason = item.querySelector('reason');
|
||||
if (reason) {
|
||||
notification.reason = reason ? reason.textContent : undefined;
|
||||
}
|
||||
const actor = item.querySelector('actor');
|
||||
if (actor) {
|
||||
notification.actor = actor ? actor.getAttribute('nick') : undefined;
|
||||
}
|
||||
}
|
||||
return notification;
|
||||
},
|
||||
|
||||
insertNotification (message) {
|
||||
this.content.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
@ -1461,33 +1271,6 @@ converse.plugins.add('converse-muc-views', {
|
||||
);
|
||||
},
|
||||
|
||||
showNotificationsforUser (notification) {
|
||||
/* Given the notification object generated by
|
||||
* parseXUserElement, display any relevant messages and
|
||||
* information to the user.
|
||||
*/
|
||||
if (notification.disconnected) {
|
||||
const messages = [];
|
||||
messages.push(notification.disconnection_message);
|
||||
if (notification.actor) {
|
||||
messages.push(__('This action was done by %1$s.', notification.actor));
|
||||
}
|
||||
if (notification.reason) {
|
||||
messages.push(__('The reason given is: "%1$s".', notification.reason));
|
||||
}
|
||||
this.showDisconnectMessages(messages);
|
||||
this.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
|
||||
return;
|
||||
}
|
||||
if (_.get(notification.messages, 'length')) {
|
||||
notification.messages.forEach(message => this.insertNotification(message));
|
||||
this.scrollDown();
|
||||
}
|
||||
if (notification.reason) {
|
||||
this.showChatEvent(__('The reason given is: "%1$s".', notification.reason));
|
||||
}
|
||||
},
|
||||
|
||||
onOccupantAdded (occupant) {
|
||||
if (occupant.get('show') === 'online') {
|
||||
this.showJoinNotification(occupant);
|
||||
@ -1644,56 +1427,6 @@ converse.plugins.add('converse-muc-views', {
|
||||
this.scrollDown();
|
||||
},
|
||||
|
||||
/**
|
||||
* Check for status codes and communicate their purpose to the user.
|
||||
* See: https://xmpp.org/registrar/mucstatus.html
|
||||
* @private
|
||||
* @method _converse.ChatRoomView#showStatusMessages
|
||||
* @param { XMLElement } stanza - The message or presence stanza containing the status codes
|
||||
*/
|
||||
showStatusMessages (stanza) {
|
||||
const elements = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"]`, stanza);
|
||||
const is_self = stanza.querySelectorAll("status[code='110']").length;
|
||||
const iteratee = _.partial(this.parseXUserElement.bind(this), _, stanza, is_self);
|
||||
const notifications = _.reject(_.map(elements, iteratee), _.isEmpty);
|
||||
notifications.forEach(n => this.showNotificationsforUser(n));
|
||||
},
|
||||
|
||||
showErrorMessageFromPresence (presence) {
|
||||
// We didn't enter the groupchat, so we must remove it from the MUC add-on
|
||||
const error = presence.querySelector('error');
|
||||
if (error.getAttribute('type') === 'auth') {
|
||||
if (!_.isNull(error.querySelector('not-authorized'))) {
|
||||
this.renderPasswordForm(__("Password incorrect"));
|
||||
} else if (!_.isNull(error.querySelector('registration-required'))) {
|
||||
this.showDisconnectMessages(__('You are not on the member list of this groupchat.'));
|
||||
} else if (!_.isNull(error.querySelector('forbidden'))) {
|
||||
this.showDisconnectMessages(__('You have been banned from this groupchat.'));
|
||||
}
|
||||
} else if (error.getAttribute('type') === 'cancel') {
|
||||
if (!_.isNull(error.querySelector('not-allowed'))) {
|
||||
this.showDisconnectMessages(__('You are not allowed to create new groupchats.'));
|
||||
} else if (!_.isNull(error.querySelector('not-acceptable'))) {
|
||||
this.showDisconnectMessages(__("Your nickname doesn't conform to this groupchat's policies."));
|
||||
} else if (sizzle('gone[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', error).length) {
|
||||
this.showDestroyedMessage(error);
|
||||
} else if (!_.isNull(error.querySelector('conflict'))) {
|
||||
this.onNicknameClash(presence);
|
||||
} else if (!_.isNull(error.querySelector('item-not-found'))) {
|
||||
this.showDisconnectMessages(__("This groupchat does not (yet) exist."));
|
||||
} else if (!_.isNull(error.querySelector('service-unavailable'))) {
|
||||
this.showDisconnectMessages(__("This groupchat has reached its maximum number of participants."));
|
||||
} else if (!_.isNull(error.querySelector('remote-server-not-found'))) {
|
||||
const messages = [__("Remote server not found")];
|
||||
const reason = _.get(error.querySelector('text'), 'textContent');
|
||||
if (reason) {
|
||||
messages.push(__('The explanation given is: "%1$s".', reason));
|
||||
}
|
||||
this.showDisconnectMessages(messages);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
renderAfterTransition () {
|
||||
/* Rerender the groupchat after some kind of transition. For
|
||||
* example after the spinner has been removed or after a
|
||||
|
@ -61,7 +61,8 @@ converse.ROOMSTATUS = {
|
||||
NICKNAME_REQUIRED: 2,
|
||||
PASSWORD_REQUIRED: 3,
|
||||
DISCONNECTED: 4,
|
||||
ENTERED: 5
|
||||
ENTERED: 5,
|
||||
DESTROYED: 6
|
||||
};
|
||||
|
||||
|
||||
@ -124,6 +125,76 @@ converse.plugins.add('converse-muc', {
|
||||
}
|
||||
|
||||
|
||||
function ___ (str) {
|
||||
/* This is part of a hack to get gettext to scan strings to be
|
||||
* translated. Strings we cannot send to the function above because
|
||||
* they require variable interpolation and we don't yet have the
|
||||
* variables at scan time.
|
||||
*/
|
||||
return str;
|
||||
}
|
||||
|
||||
/* https://xmpp.org/extensions/xep-0045.html
|
||||
* ----------------------------------------
|
||||
* 100 message Entering a groupchat Inform user that any occupant is allowed to see the user's full JID
|
||||
* 101 message (out of band) Affiliation change Inform user that his or her affiliation changed while not in the groupchat
|
||||
* 102 message Configuration change Inform occupants that groupchat now shows unavailable members
|
||||
* 103 message Configuration change Inform occupants that groupchat now does not show unavailable members
|
||||
* 104 message Configuration change Inform occupants that a non-privacy-related groupchat configuration change has occurred
|
||||
* 110 presence Any groupchat presence Inform user that presence refers to one of its own groupchat occupants
|
||||
* 170 message or initial presence Configuration change Inform occupants that groupchat logging is now enabled
|
||||
* 171 message Configuration change Inform occupants that groupchat logging is now disabled
|
||||
* 172 message Configuration change Inform occupants that the groupchat is now non-anonymous
|
||||
* 173 message Configuration change Inform occupants that the groupchat is now semi-anonymous
|
||||
* 174 message Configuration change Inform occupants that the groupchat is now fully-anonymous
|
||||
* 201 presence Entering a groupchat Inform user that a new groupchat has been created
|
||||
* 210 presence Entering a groupchat Inform user that the service has assigned or modified the occupant's roomnick
|
||||
* 301 presence Removal from groupchat Inform user that he or she has been banned from the groupchat
|
||||
* 303 presence Exiting a groupchat Inform all occupants of new groupchat nickname
|
||||
* 307 presence Removal from groupchat Inform user that he or she has been kicked from the groupchat
|
||||
* 321 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because of an affiliation change
|
||||
* 322 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because the groupchat has been changed to members-only and the user is not a member
|
||||
* 332 presence Removal from groupchat Inform user that he or she is being removed from the groupchat because of a system shutdown
|
||||
*/
|
||||
_converse.muc = {
|
||||
info_messages: {
|
||||
100: __('This groupchat is not anonymous'),
|
||||
102: __('This groupchat now shows unavailable members'),
|
||||
103: __('This groupchat does not show unavailable members'),
|
||||
104: __('The groupchat configuration has changed'),
|
||||
170: __('groupchat logging is now enabled'),
|
||||
171: __('groupchat logging is now disabled'),
|
||||
172: __('This groupchat is now no longer anonymous'),
|
||||
173: __('This groupchat is now semi-anonymous'),
|
||||
174: __('This groupchat is now fully-anonymous'),
|
||||
201: __('A new groupchat has been created')
|
||||
},
|
||||
|
||||
new_nickname_messages: {
|
||||
// XXX: Note the triple underscore function and not double underscore.
|
||||
210: ___('Your nickname has been automatically set to %1$s'),
|
||||
303: ___('Your nickname has been changed to %1$s')
|
||||
},
|
||||
|
||||
disconnect_messages: {
|
||||
301: __('You have been banned from this groupchat'),
|
||||
307: __('You have been kicked from this groupchat'),
|
||||
321: __("You have been removed from this groupchat because of an affiliation change"),
|
||||
322: __("You have been removed from this groupchat because the groupchat has changed to members-only and you're not a member"),
|
||||
332: __("You have been removed from this groupchat because the service hosting it is being shut down")
|
||||
},
|
||||
|
||||
action_info_messages: {
|
||||
// XXX: Note the triple underscore function and not double underscore.
|
||||
301: ___("%1$s has been banned"),
|
||||
303: ___("%1$s's nickname has changed"),
|
||||
307: ___("%1$s has been kicked out"),
|
||||
321: ___("%1$s has been removed because of an affiliation change"),
|
||||
322: ___("%1$s has been removed for not being a member")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function openRoom (jid) {
|
||||
if (!u.isValidMUCJID(jid)) {
|
||||
return _converse.log(
|
||||
@ -300,7 +371,6 @@ converse.plugins.add('converse-muc', {
|
||||
const room_jid = this.get('jid');
|
||||
this.removeHandlers();
|
||||
this.presence_handler = _converse.connection.addHandler(stanza => {
|
||||
Object.values(this.handlers.presence).forEach(callback => callback(stanza));
|
||||
this.onPresence(stanza);
|
||||
return true;
|
||||
},
|
||||
@ -308,7 +378,6 @@ converse.plugins.add('converse-muc', {
|
||||
{'ignoreNamespaceFragment': true, 'matchBareFromJid': true}
|
||||
);
|
||||
this.message_handler = _converse.connection.addHandler(stanza => {
|
||||
Object.values(this.handlers.message).forEach(callback => callback(stanza));
|
||||
this.onMessage(stanza);
|
||||
return true;
|
||||
}, null, 'message', 'groupchat', null, room_jid,
|
||||
@ -331,21 +400,6 @@ converse.plugins.add('converse-muc', {
|
||||
return this;
|
||||
},
|
||||
|
||||
addHandler (type, name, callback) {
|
||||
/* Allows 'presence' and 'message' handlers to be
|
||||
* registered. These will be executed once presence or
|
||||
* message stanzas are received, and *before* this model's
|
||||
* own handlers are executed.
|
||||
*/
|
||||
if (_.isNil(this.handlers)) {
|
||||
this.handlers = {};
|
||||
}
|
||||
if (_.isNil(this.handlers[type])) {
|
||||
this.handlers[type] = {};
|
||||
}
|
||||
this.handlers[type][name] = callback;
|
||||
},
|
||||
|
||||
getDisplayName () {
|
||||
const name = this.get('name');
|
||||
if (name) {
|
||||
@ -1013,9 +1067,9 @@ converse.plugins.add('converse-muc', {
|
||||
}).c('query', {'xmlns': Strophe.NS.MUC_REGISTER})
|
||||
);
|
||||
} catch (e) {
|
||||
if (sizzle('not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
|
||||
if (sizzle(`not-allowed[xmlns="${Strophe.NS.STANZAS}"]`, e).length) {
|
||||
err_msg = __("You're not allowed to register yourself in this groupchat.");
|
||||
} else if (sizzle('registration-required[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
|
||||
} else if (sizzle(`registration-required[xmlns="${Strophe.NS.STANZAS}"]`, e).length) {
|
||||
err_msg = __("You're not allowed to register in this groupchat because it's members-only.");
|
||||
}
|
||||
_converse.log(e, Strophe.LogLevel.ERROR);
|
||||
@ -1036,9 +1090,9 @@ converse.plugins.add('converse-muc', {
|
||||
.c('field', {'var': 'muc#register_roomnick'}).c('value').t(nick)
|
||||
);
|
||||
} catch (e) {
|
||||
if (sizzle('service-unavailable[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
|
||||
if (sizzle(`service-unavailable[xmlns="${Strophe.NS.STANZAS}"]`, e).length) {
|
||||
err_msg = __("Can't register your nickname in this groupchat, it doesn't support registration.");
|
||||
} else if (sizzle('bad-request[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
|
||||
} else if (sizzle(`bad-request[xmlns="${Strophe.NS.STANZAS}"]`, e).length) {
|
||||
err_msg = __("Can't register your nickname in this groupchat, invalid data form supplied.");
|
||||
}
|
||||
_converse.log(err_msg);
|
||||
@ -1320,6 +1374,7 @@ converse.plugins.add('converse-muc', {
|
||||
* @param { XMLElement } stanza - The message stanza.
|
||||
*/
|
||||
async onMessage (stanza) {
|
||||
this.createInfoMessages(stanza);
|
||||
this.fetchFeaturesIfConfigurationChanged(stanza);
|
||||
const original_stanza = stanza;
|
||||
const forwarded = sizzle(`forwarded[xmlns="${Strophe.NS.FORWARD}"]`, stanza).pop();
|
||||
@ -1360,21 +1415,168 @@ converse.plugins.add('converse-muc', {
|
||||
}
|
||||
},
|
||||
|
||||
handleDisconnection (stanza) {
|
||||
const is_self = !_.isNull(stanza.querySelector("status[code='110']"));
|
||||
const x = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"]`, stanza).pop();
|
||||
if (!x) {
|
||||
return;
|
||||
}
|
||||
const codes = sizzle('status', x).map(s => s.getAttribute('code'));
|
||||
const disconnection_codes = _.intersection(codes, Object.keys(_converse.muc.disconnect_messages));
|
||||
const disconnected = is_self && disconnection_codes.length > 0;
|
||||
if (!disconnected) {
|
||||
return;
|
||||
}
|
||||
// By using querySelector we assume here there is
|
||||
// one <item> per <x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
// element. This appears to be a safe assumption, since
|
||||
// each <x/> element pertains to a single user.
|
||||
const item = x.querySelector('item');
|
||||
const reason = item ? _.get(item.querySelector('reason'), 'textContent') : undefined;
|
||||
const actor = item ? _.invoke(item.querySelector('actor'), 'getAttribute', 'nick') : undefined;
|
||||
const message = _converse.muc.disconnect_messages[disconnection_codes[0]];
|
||||
this.setDisconnectionMessage(message, reason, actor);
|
||||
},
|
||||
|
||||
onErrorPresence (pres) {
|
||||
// TODO: currently showErrorMessageFromPresence handles
|
||||
// 'error" presences in converse-muc-views.
|
||||
// Instead, they should be handled here and the presence
|
||||
// handler removed from there.
|
||||
if (sizzle(`error not-authorized[xmlns="${Strophe.NS.STANZAS}"]`, pres).length) {
|
||||
this.save('connection_status', converse.ROOMSTATUS.PASSWORD_REQUIRED);
|
||||
} else if (sizzle(`error[type="modify"]`, pres).length) {
|
||||
this.handleModifyError(pres);
|
||||
|
||||
/**
|
||||
* Create info messages based on a received presence stanza
|
||||
* @private
|
||||
* @method _converse.ChatRoom#createInfoMessages
|
||||
* @param { XMLElement } stanza: The presence stanza received
|
||||
*/
|
||||
createInfoMessages (stanza) {
|
||||
const is_self = !_.isNull(stanza.querySelector("status[code='110']"));
|
||||
const x = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"]`, stanza).pop();
|
||||
if (!x) {
|
||||
return;
|
||||
}
|
||||
const codes = sizzle('status', x).map(s => s.getAttribute('code'));
|
||||
|
||||
codes.forEach(code => {
|
||||
let message;
|
||||
if (code === '110' || (code === '100' && !is_self)) {
|
||||
return;
|
||||
} else if (code in _converse.muc.info_messages) {
|
||||
message = _converse.muc.info_messages[code];
|
||||
|
||||
} else if (!is_self && (code in _converse.muc.action_info_messages)) {
|
||||
const nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
|
||||
message = __(_converse.muc.action_info_messages[code], nick);
|
||||
const item = x.querySelector('item');
|
||||
const reason = item ? _.get(item.querySelector('reason'), 'textContent') : undefined;
|
||||
const actor = item ? _.invoke(item.querySelector('actor'), 'getAttribute', 'nick') : undefined;
|
||||
if (actor) {
|
||||
message += '\n' + __('This action was done by %1$s.', actor);
|
||||
}
|
||||
if (reason) {
|
||||
message += '\n' + __('The reason given is: "%1$s".', reason);
|
||||
}
|
||||
} else if (is_self && (code in _converse.muc.new_nickname_messages)) {
|
||||
let nick;
|
||||
if (is_self && code === "210") {
|
||||
nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
|
||||
} else if (is_self && code === "303") {
|
||||
nick = stanza.querySelector('x item').getAttribute('nick');
|
||||
}
|
||||
this.save('nick', nick);
|
||||
message = __(_converse.muc.new_nickname_messages[code], nick);
|
||||
}
|
||||
|
||||
if (message) {
|
||||
this.messages.create({'type': 'info', message});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
setDisconnectionMessage (message, reason, actor) {
|
||||
this.save({
|
||||
'connection_status': converse.ROOMSTATUS.DISCONNECTED,
|
||||
'disconnection_message': message,
|
||||
'disconnection_reason': reason,
|
||||
'disconnection_actor': actor
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
onNicknameClash (presence) {
|
||||
if (_converse.muc_nickname_from_jid) {
|
||||
const nick = presence.getAttribute('from').split('/')[1];
|
||||
if (nick === _converse.getDefaultMUCNickname()) {
|
||||
this.join(nick + '-2');
|
||||
} else {
|
||||
const del= nick.lastIndexOf("-");
|
||||
const num = nick.substring(del+1, nick.length);
|
||||
this.join(nick.substring(0, del+1) + String(Number(num)+1));
|
||||
}
|
||||
} else {
|
||||
this.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
|
||||
this.save({
|
||||
'nickname_validation_message': __(
|
||||
"The nickname you chose is reserved or "+
|
||||
"currently in use, please choose a different one."),
|
||||
'connection_status': converse.ROOMSTATUS.NICKNAME_REQUIRED
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Parses a <presence> stanza with type "error" and sets the proper
|
||||
* `connection_status` value for this {@link _converse.ChatRoom} as
|
||||
* well as any additional output that can be shown to the user.
|
||||
* @private
|
||||
* @param { XMLElement } stanza - The presence stanza
|
||||
*/
|
||||
onErrorPresence (stanza) {
|
||||
if (sizzle(`error not-authorized[xmlns="${Strophe.NS.STANZAS}"]`, stanza).length) {
|
||||
this.save({
|
||||
'password_validation_message': __("Password incorrect"),
|
||||
'connection_status': converse.ROOMSTATUS.PASSWORD_REQUIRED
|
||||
});
|
||||
}
|
||||
const error = stanza.querySelector('error');
|
||||
const error_type = error.getAttribute('type');
|
||||
|
||||
if (error_type === 'modify') {
|
||||
this.handleModifyError(stanza);
|
||||
} else if (error_type === 'auth') {
|
||||
if (error.querySelector('registration-required')) {
|
||||
this.setDisconnectionMessage(__('You are not on the member list of this groupchat.'));
|
||||
} else if (error.querySelector('forbidden')) {
|
||||
this.setDisconnectionMessage(__('You have been banned from this groupchat.'));
|
||||
}
|
||||
} else if (error_type === 'cancel') {
|
||||
if (error.querySelector('not-allowed')) {
|
||||
this.setDisconnectionMessage(__('You are not allowed to create new groupchats.'));
|
||||
} else if (error.querySelector('not-acceptable')) {
|
||||
this.setDisconnectionMessage(__("Your nickname doesn't conform to this groupchat's policies."));
|
||||
} else if (sizzle(`gone[xmlns="${Strophe.NS.STANZAS}"]`, error).length) {
|
||||
const moved_jid = _.get(sizzle(`gone[xmlns="${Strophe.NS.STANZAS}"]`, error).pop(), 'textContent')
|
||||
.replace(/^xmpp:/, '')
|
||||
.replace(/\?join$/, '');
|
||||
const reason = _.get(sizzle(`text[xmlns="${Strophe.NS.STANZAS}"]`, error).pop(), 'textContent');
|
||||
this.save({
|
||||
'connection_status': converse.ROOMSTATUS.DESTROYED,
|
||||
'destroyed_reason': reason,
|
||||
'moved_jid': moved_jid
|
||||
});
|
||||
} else if (error.querySelector('conflict')) {
|
||||
this.onNicknameClash(stanza);
|
||||
} else if (error.querySelector('item-not-found')) {
|
||||
this.setDisconnectionMessage(__("This groupchat does not (yet) exist."));
|
||||
} else if (error.querySelector('service-unavailable')) {
|
||||
this.setDisconnectionMessage(__("This groupchat has reached its maximum number of participants."));
|
||||
} else if (error.querySelector('remote-server-not-found')) {
|
||||
const message = __("Remote server not found");
|
||||
const text = _.get(error.querySelector('text'), 'textContent');
|
||||
const reason = text ? __('The explanation given is: "%1$s".', text) : undefined;
|
||||
this.setDisconnectionMessage(message, reason);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Handles all MUC presence stanzas.
|
||||
* @private
|
||||
@ -1388,6 +1590,7 @@ converse.plugins.add('converse-muc', {
|
||||
if (stanza.querySelector("status[code='110']")) {
|
||||
this.onOwnPresence(stanza);
|
||||
}
|
||||
this.createInfoMessages(stanza);
|
||||
this.updateOccupantsOnPresence(stanza);
|
||||
|
||||
if (this.get('role') !== 'none' && this.get('connection_status') === converse.ROOMSTATUS.CONNECTING) {
|
||||
@ -1414,7 +1617,7 @@ converse.plugins.add('converse-muc', {
|
||||
this.saveAffiliationAndRole(stanza);
|
||||
|
||||
if (stanza.getAttribute('type') === 'unavailable') {
|
||||
this.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
|
||||
this.handleDisconnection(stanza);
|
||||
} else {
|
||||
const locked_room = stanza.querySelector("status[code='201']");
|
||||
if (locked_room) {
|
||||
|
@ -114,8 +114,10 @@
|
||||
return _converse.chatboxviews.get(jid);
|
||||
};
|
||||
|
||||
utils.openChatRoom = function (_converse, room, server) {
|
||||
return _converse.api.rooms.open(`${room}@${server}`);
|
||||
utils.openChatRoom = async function (_converse, room, server) {
|
||||
const model = await _converse.api.rooms.open(`${room}@${server}`);
|
||||
await model.messages.fetched;
|
||||
return model;
|
||||
};
|
||||
|
||||
utils.getRoomFeatures = async function (_converse, room, server, features=[]) {
|
||||
|
Loading…
Reference in New Issue
Block a user