Move various MUC methods onto the Backbone.Model
To more cleanly separate views and models and to make MUC in headless mode more viable. Refs #1032
This commit is contained in:
parent
b0c22d983c
commit
9528d81c00
16
CHANGES.md
16
CHANGES.md
@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
## UI changes
|
## UI changes
|
||||||
|
|
||||||
* The UI is now based on Bootstrap4 and Flexbox is used extensively.
|
- The UI is now based on Bootstrap4 and Flexbox is used extensively.
|
||||||
* #956 Conversation pane should show my own identity in pane header
|
- #956 Conversation pane should show my own identity in pane header
|
||||||
|
|
||||||
## New Features
|
## New Features
|
||||||
|
|
||||||
@ -13,15 +13,21 @@
|
|||||||
|
|
||||||
## Configuration changes
|
## Configuration changes
|
||||||
|
|
||||||
* Removed the `xhr_custom_status` and `xhr_custom_status_url` configuration
|
- Removed the `xhr_custom_status` and `xhr_custom_status_url` configuration
|
||||||
settings. If you relied on these settings, you can instead listen for the
|
settings. If you relied on these settings, you can instead listen for the
|
||||||
[statusMessageChanged](https://conversejs.org/docs/html/events.html#contactstatusmessagechanged)
|
[statusMessageChanged](https://conversejs.org/docs/html/events.html#contactstatusmessagechanged)
|
||||||
event and make the XMLHttpRequest yourself.
|
event and make the XMLHttpRequest yourself.
|
||||||
* Removed `xhr_user_search` in favor of only accepting `xhr_user_search_url` as configuration option.
|
- Removed `xhr_user_search` in favor of only accepting `xhr_user_search_url` as configuration option.
|
||||||
* The data returned from the `xhr_user_search_url` must now include the user's
|
- The data returned from the `xhr_user_search_url` must now include the user's
|
||||||
`jid` instead of just an `id`.
|
`jid` instead of just an `id`.
|
||||||
- New configuration setting [nickname](https://conversejs.org/docs/html/configurations.html#nickname)
|
- New configuration setting [nickname](https://conversejs.org/docs/html/configurations.html#nickname)
|
||||||
|
|
||||||
|
## Architectural changes
|
||||||
|
|
||||||
|
- Extracted the views from `converse-muc.js` into `converse-muc-views.js` and
|
||||||
|
where appropriate moved methods from the views into the models/collections.
|
||||||
|
This makes MUC possible in headless mode.
|
||||||
|
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
|
|
||||||
- Spoiler messages didn't include the message author's name.
|
- Spoiler messages didn't include the message author's name.
|
||||||
|
@ -7594,6 +7594,9 @@ body.reset {
|
|||||||
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji a:hover,
|
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji a:hover,
|
||||||
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji a:hover {
|
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji a:hover {
|
||||||
color: #8f2831; }
|
color: #8f2831; }
|
||||||
|
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley a.toggle-smiley,
|
||||||
|
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley a.toggle-smiley {
|
||||||
|
padding: 0; }
|
||||||
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar,
|
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar,
|
||||||
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar {
|
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar {
|
||||||
box-shadow: 0 -1px 1px 0 rgba(0, 0, 0, 0.4); }
|
box-shadow: 0 -1px 1px 0 rgba(0, 0, 0, 0.4); }
|
||||||
|
@ -7647,28 +7647,31 @@ body {
|
|||||||
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji a:hover,
|
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji a:hover,
|
||||||
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji a:hover {
|
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li .toolbar-menu ul li.insert-emoji a:hover {
|
||||||
color: #8f2831; }
|
color: #8f2831; }
|
||||||
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar,
|
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley,
|
||||||
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar {
|
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley {
|
||||||
box-shadow: 0 -1px 1px 0 rgba(0, 0, 0, 0.4); }
|
padding: 0 0 0 0.5em; }
|
||||||
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker,
|
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar,
|
||||||
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker {
|
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar {
|
||||||
padding-top: 0.5em; }
|
box-shadow: 0 -1px 1px 0 rgba(0, 0, 0, 0.4); }
|
||||||
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker ul,
|
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker,
|
||||||
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker ul {
|
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker {
|
||||||
display: flex;
|
padding-top: 0.5em; }
|
||||||
flex-direction: row;
|
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker ul,
|
||||||
justify-content: space-between; }
|
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker ul {
|
||||||
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker li,
|
display: flex;
|
||||||
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-skintone-picker li,
|
flex-direction: row;
|
||||||
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker li,
|
justify-content: space-between; }
|
||||||
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-skintone-picker li {
|
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker li,
|
||||||
padding: 0.2em;
|
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-skintone-picker li,
|
||||||
font-size: 26px; }
|
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker li,
|
||||||
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker li:hover,
|
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-skintone-picker li {
|
||||||
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-skintone-picker li:hover,
|
padding: 0.2em;
|
||||||
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker li:hover,
|
font-size: 26px; }
|
||||||
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-skintone-picker li:hover {
|
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker li:hover,
|
||||||
background-color: #DCF9F6; }
|
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-skintone-picker li:hover,
|
||||||
|
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-category-picker li:hover,
|
||||||
|
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-smiley .emoji-toolbar .emoji-skintone-picker li:hover {
|
||||||
|
background-color: #DCF9F6; }
|
||||||
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-otr ul,
|
#converse-embedded-chat .chatbox .sendXMPPMessage .chat-toolbar li.toggle-otr ul,
|
||||||
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-otr ul {
|
#conversejs .chatbox .sendXMPPMessage .chat-toolbar li.toggle-otr ul {
|
||||||
z-index: 99; }
|
z-index: 99; }
|
||||||
@ -7788,13 +7791,11 @@ body {
|
|||||||
line-height: 26px; }
|
line-height: 26px; }
|
||||||
#conversejs.fullscreen .chatbox .sendXMPPMessage ul {
|
#conversejs.fullscreen .chatbox .sendXMPPMessage ul {
|
||||||
width: 100%; }
|
width: 100%; }
|
||||||
#conversejs.fullscreen .chatbox .sendXMPPMessage .toggle-smiley {
|
#conversejs.fullscreen .chatbox .sendXMPPMessage .toggle-smiley ul.emoji-toolbar .emoji-category-picker {
|
||||||
padding-left: 0.5em; }
|
margin-right: 5em; }
|
||||||
#conversejs.fullscreen .chatbox .sendXMPPMessage .toggle-smiley ul.emoji-toolbar .emoji-category-picker {
|
#conversejs.fullscreen .chatbox .sendXMPPMessage .toggle-smiley ul.emoji-toolbar .emoji-category {
|
||||||
margin-right: 5em; }
|
padding-left: 10px;
|
||||||
#conversejs.fullscreen .chatbox .sendXMPPMessage .toggle-smiley ul.emoji-toolbar .emoji-category {
|
padding-right: 10px; }
|
||||||
padding-left: 10px;
|
|
||||||
padding-right: 10px; }
|
|
||||||
|
|
||||||
@media screen and (max-width: 767px) {
|
@media screen and (max-width: 767px) {
|
||||||
#conversejs.fullscreen .chatbox {
|
#conversejs.fullscreen .chatbox {
|
||||||
|
@ -34,8 +34,8 @@ For more info on how to use (or add promises), you can read the
|
|||||||
Below we will now list all events and also specify whether they are available
|
Below we will now list all events and also specify whether they are available
|
||||||
as promises.
|
as promises.
|
||||||
|
|
||||||
List of Events (and promises)
|
List of global events (and promises)
|
||||||
-----------------------------
|
------------------------------------
|
||||||
|
|
||||||
Hooking into events that Converse.js emits is a great way to extend or
|
Hooking into events that Converse.js emits is a great way to extend or
|
||||||
customize its functionality.
|
customize its functionality.
|
||||||
@ -478,3 +478,16 @@ windowStateChanged
|
|||||||
When window state has changed. Used to determine when a user left the page and when came back.
|
When window state has changed. Used to determine when a user left the page and when came back.
|
||||||
|
|
||||||
``_converse.on('windowStateChanged', function (data) { ... });``
|
``_converse.on('windowStateChanged', function (data) { ... });``
|
||||||
|
|
||||||
|
|
||||||
|
List of events on the ChatRoom Backbone.Model
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
configurationNeeded
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Triggered when a new room has been created which first needs to be configured
|
||||||
|
and when `auto_configure` is set to `false`.
|
||||||
|
|
||||||
|
Used by the core `ChatRoomView` view in order to know when to render the
|
||||||
|
configuration form for a new room.
|
||||||
|
@ -433,6 +433,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.toggle-smiley {
|
&.toggle-smiley {
|
||||||
|
a.toggle-smiley {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
.emoji-toolbar {
|
.emoji-toolbar {
|
||||||
box-shadow: 0 -1px 1px 0 rgba(0, 0, 0, 0.4);
|
box-shadow: 0 -1px 1px 0 rgba(0, 0, 0, 0.4);
|
||||||
|
|
||||||
|
@ -78,7 +78,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.toggle-smiley {
|
.toggle-smiley {
|
||||||
padding-left: 0.5em;
|
|
||||||
ul {
|
ul {
|
||||||
&.emoji-toolbar {
|
&.emoji-toolbar {
|
||||||
.emoji-category-picker {
|
.emoji-category-picker {
|
||||||
|
460
spec/chatroom.js
460
spec/chatroom.js
@ -127,7 +127,7 @@
|
|||||||
// Mock 'getRoomFeatures', otherwise the room won't be
|
// Mock 'getRoomFeatures', otherwise the room won't be
|
||||||
// displayed as it waits first for the features to be returned
|
// displayed as it waits first for the features to be returned
|
||||||
// (when it's a new room being created).
|
// (when it's a new room being created).
|
||||||
spyOn(_converse.ChatRoomView.prototype, 'getRoomFeatures').and.callFake(function () {
|
spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(function () {
|
||||||
var deferred = new $.Deferred();
|
var deferred = new $.Deferred();
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
return deferred.promise();
|
return deferred.promise();
|
||||||
@ -426,7 +426,7 @@
|
|||||||
* know about them because we receive their presences before we
|
* know about them because we receive their presences before we
|
||||||
* receive our own.
|
* receive our own.
|
||||||
*/
|
*/
|
||||||
presence = $pres({
|
var presence = $pres({
|
||||||
to: 'dummy@localhost/_converse.js-29092160',
|
to: 'dummy@localhost/_converse.js-29092160',
|
||||||
from: 'coven@chat.shakespeare.lit/oldguy'
|
from: 'coven@chat.shakespeare.lit/oldguy'
|
||||||
}).c('x', {xmlns: Strophe.NS.MUC_USER})
|
}).c('x', {xmlns: Strophe.NS.MUC_USER})
|
||||||
@ -446,7 +446,7 @@
|
|||||||
* </x>
|
* </x>
|
||||||
* </presence></body>
|
* </presence></body>
|
||||||
*/
|
*/
|
||||||
var presence = $pres({
|
presence = $pres({
|
||||||
to: 'dummy@localhost/_converse.js-29092160',
|
to: 'dummy@localhost/_converse.js-29092160',
|
||||||
from: 'coven@chat.shakespeare.lit/some1'
|
from: 'coven@chat.shakespeare.lit/some1'
|
||||||
}).c('x', {xmlns: Strophe.NS.MUC_USER})
|
}).c('x', {xmlns: Strophe.NS.MUC_USER})
|
||||||
@ -615,140 +615,159 @@
|
|||||||
null, ['rosterGroupsFetched'], {},
|
null, ['rosterGroupsFetched'], {},
|
||||||
function (done, _converse) {
|
function (done, _converse) {
|
||||||
|
|
||||||
test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1');
|
test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'dummy').then(function () {
|
||||||
var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
|
var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
|
||||||
var $chat_content = $(view.el).find('.chat-content');
|
var chat_content = view.el.querySelector('.chat-content');
|
||||||
|
var $chat_content = $(chat_content);
|
||||||
|
var time = chat_content.querySelector('time');
|
||||||
|
expect(time).not.toBe(null);
|
||||||
|
expect(time.getAttribute('class')).toEqual('message chat-info chat-date badge badge-info');
|
||||||
|
expect(time.getAttribute('data-isodate')).toEqual(moment().startOf('day').format());
|
||||||
|
expect(time.textContent).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
|
||||||
|
expect(chat_content.querySelectorAll('div.chat-info').length).toBe(1);
|
||||||
|
expect(chat_content.querySelector('div.chat-info').textContent).toBe(
|
||||||
|
"dummy has entered the room"
|
||||||
|
);
|
||||||
|
|
||||||
/* <presence to="dummy@localhost/_converse.js-29092160"
|
var baseTime = new Date();
|
||||||
* from="coven@chat.shakespeare.lit/some1">
|
jasmine.clock().install();
|
||||||
* <x xmlns="http://jabber.org/protocol/muc#user">
|
jasmine.clock().mockDate(baseTime);
|
||||||
* <item affiliation="owner" jid="dummy@localhost/_converse.js-29092160" role="moderator"/>
|
var ONE_DAY_LATER = 86400000;
|
||||||
* <status code="110"/>
|
jasmine.clock().tick(ONE_DAY_LATER);
|
||||||
* </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');
|
/* <presence to="dummy@localhost/_converse.js-29092160"
|
||||||
expect($time.length).toEqual(1);
|
* from="coven@chat.shakespeare.lit/some1">
|
||||||
expect($time.attr('class')).toEqual('message chat-info chat-date badge badge-info');
|
* <x xmlns="http://jabber.org/protocol/muc#user">
|
||||||
expect($time.data('isodate')).toEqual(moment().startOf('day').format());
|
* <item affiliation="owner" jid="dummy@localhost/_converse.js-29092160" role="moderator"/>
|
||||||
expect($time.text()).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
|
* <status code="110"/>
|
||||||
expect($chat_content.find('div.chat-info:first').html()).toBe("some1 has entered the room");
|
* </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': 'some1@localhost/_converse.js-290929789',
|
||||||
|
'role': 'moderator'
|
||||||
|
});
|
||||||
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
|
|
||||||
// XXX: Hack. We clear the chat contents instead of mocking the date
|
time = chat_content.querySelector('time[data-isodate="'+moment().startOf('day').format()+'"]');
|
||||||
$chat_content.html('');
|
expect(time).not.toBe(null);
|
||||||
|
expect(time.getAttribute('class')).toEqual('message chat-info chat-date badge badge-info');
|
||||||
|
expect(time.getAttribute('data-isodate')).toEqual(moment().startOf('day').format());
|
||||||
|
expect(time.textContent).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
|
||||||
|
expect(chat_content.querySelector('div.chat-info:last-child').textContent).toBe(
|
||||||
|
"some1 has entered the room"
|
||||||
|
);
|
||||||
|
|
||||||
// Test a user leaving a chat room
|
jasmine.clock().tick(ONE_DAY_LATER);
|
||||||
presence = $pres({
|
|
||||||
to: 'dummy@localhost/_converse.js-29092160',
|
// Test a user leaving a chat room
|
||||||
type: 'unavailable',
|
presence = $pres({
|
||||||
from: 'coven@chat.shakespeare.lit/some1'
|
to: 'dummy@localhost/_converse.js-29092160',
|
||||||
})
|
type: 'unavailable',
|
||||||
.c('status', 'Disconnected: Replaced by new connection').up()
|
from: 'coven@chat.shakespeare.lit/some1'
|
||||||
.c('x', {xmlns: Strophe.NS.MUC_USER})
|
})
|
||||||
|
.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.querySelector('time[data-isodate="'+moment().startOf('day').format()+'"]');
|
||||||
|
expect(time).not.toBe(null);
|
||||||
|
expect(time.getAttribute('class')).toEqual('message chat-info chat-date badge badge-info');
|
||||||
|
expect(time.getAttribute('data-isodate')).toEqual(moment().startOf('day').format());
|
||||||
|
expect(time.textContent).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
|
||||||
|
expect($(chat_content).find('div.chat-info').length).toBe(4);
|
||||||
|
expect($(chat_content).find('div.chat-info:last').html()).toBe(
|
||||||
|
'some1 has left the room. '+
|
||||||
|
'"Disconnected: Replaced by new connection"');
|
||||||
|
|
||||||
|
jasmine.clock().tick(ONE_DAY_LATER);
|
||||||
|
|
||||||
|
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="'+moment().format()+'" 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', {
|
.c('item', {
|
||||||
'affiliation': 'none',
|
'affiliation': 'none',
|
||||||
'jid': 'some1@localhost/_converse.js-290929789',
|
'jid': 'newguy@localhost/_converse.js-290929789',
|
||||||
'role': 'none'
|
'role': 'participant'
|
||||||
});
|
});
|
||||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
|
|
||||||
$time = $chat_content.find('time');
|
var $time = $chat_content.find('time');
|
||||||
expect($time.length).toEqual(1);
|
expect($time.length).toEqual(4);
|
||||||
expect($time.attr('class')).toEqual('message chat-info chat-date badge badge-info');
|
|
||||||
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
|
$time = $chat_content.find('time:eq(3)');
|
||||||
$chat_content.html('');
|
expect($time.attr('class')).toEqual('message chat-info chat-date badge badge-info');
|
||||||
|
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(5);
|
||||||
|
expect($chat_content.find('div.chat-info:last').html()).toBe("newguy has entered the room");
|
||||||
|
|
||||||
var stanza = Strophe.xmlHtmlNode(
|
jasmine.clock().tick(ONE_DAY_LATER);
|
||||||
'<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({
|
stanza = Strophe.xmlHtmlNode(
|
||||||
to: 'dummy@localhost/_converse.js-29092160',
|
'<message xmlns="jabber:client"' +
|
||||||
from: 'coven@chat.shakespeare.lit/newguy'
|
' to="dummy@localhost/_converse.js-290929789"' +
|
||||||
}).c('x', {xmlns: Strophe.NS.MUC_USER})
|
' type="groupchat"' +
|
||||||
.c('item', {
|
' from="coven@chat.shakespeare.lit/some1">'+
|
||||||
'affiliation': 'none',
|
' <body>hello world</body>'+
|
||||||
'jid': 'newguy@localhost/_converse.js-290929789',
|
' <delay xmlns="urn:xmpp:delay" stamp="'+moment().format()+'" from="some1@localhost"/>'+
|
||||||
'role': 'participant'
|
'</message>').firstChild;
|
||||||
});
|
_converse.connection._dataRecv(test_utils.createRequest(stanza));
|
||||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
|
||||||
|
|
||||||
$time = $chat_content.find('time');
|
jasmine.clock().tick(ONE_DAY_LATER);
|
||||||
expect($time.length).toEqual(2);
|
|
||||||
|
|
||||||
$time = $chat_content.find('time:eq(1)');
|
// Test a user leaving a chat room
|
||||||
expect($time.attr('class')).toEqual('message chat-info chat-date badge badge-info');
|
presence = $pres({
|
||||||
expect($time.data('isodate')).toEqual(moment().startOf('day').format());
|
to: 'dummy@localhost/_converse.js-29092160',
|
||||||
expect($time.text()).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
|
type: 'unavailable',
|
||||||
expect($chat_content.find('div.chat-info').length).toBe(1);
|
from: 'coven@chat.shakespeare.lit/newguy'
|
||||||
expect($chat_content.find('div.chat-info:first').html()).toBe("newguy has entered the room");
|
})
|
||||||
|
.c('status', 'Disconnected: Replaced by new connection').up()
|
||||||
|
.c('x', {xmlns: Strophe.NS.MUC_USER})
|
||||||
|
.c('item', {
|
||||||
|
'affiliation': 'none',
|
||||||
|
'jid': 'newguy@localhost/_converse.js-290929789',
|
||||||
|
'role': 'none'
|
||||||
|
});
|
||||||
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
|
|
||||||
// XXX: Hack. We clear the chat contents instead of mocking the date
|
$time = $chat_content.find('time');
|
||||||
$chat_content.html('');
|
expect($time.length).toEqual(6);
|
||||||
|
|
||||||
stanza = Strophe.xmlHtmlNode(
|
$time = $chat_content.find('time:eq(5)');
|
||||||
'<message xmlns="jabber:client"' +
|
expect($time.attr('class')).toEqual('message chat-info chat-date badge badge-info');
|
||||||
' to="dummy@localhost/_converse.js-290929789"' +
|
expect($time.data('isodate')).toEqual(moment().startOf('day').format());
|
||||||
' type="groupchat"' +
|
expect($time.text()).toEqual(moment().startOf('day').format("dddd MMM Do YYYY"));
|
||||||
' from="coven@chat.shakespeare.lit/some1">'+
|
expect($chat_content.find('div.chat-info').length).toBe(6);
|
||||||
' <body>hello world</body>'+
|
expect($chat_content.find('div.chat-info:last').html()).toBe(
|
||||||
' <delay xmlns="urn:xmpp:delay" stamp="2018-01-01T09:35:39Z" from="some1@localhost"/>'+
|
'newguy has left the room. '+
|
||||||
'</message>').firstChild;
|
'"Disconnected: Replaced by new connection"');
|
||||||
_converse.connection._dataRecv(test_utils.createRequest(stanza));
|
|
||||||
|
|
||||||
// Test a user leaving a chat room
|
jasmine.clock().uninstall();
|
||||||
presence = $pres({
|
done();
|
||||||
to: 'dummy@localhost/_converse.js-29092160',
|
return;
|
||||||
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('message chat-info chat-date badge badge-info');
|
|
||||||
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",
|
||||||
@ -818,7 +837,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').t(message).tree();
|
}).c('body').t(message).tree();
|
||||||
view.handleMUCMessage(msg);
|
view.model.onMessage(msg);
|
||||||
expect($(view.el).find('.chat-message').hasClass('mentioned')).toBeTruthy();
|
expect($(view.el).find('.chat-message').hasClass('mentioned')).toBeTruthy();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -848,7 +867,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').t(message).tree();
|
}).c('body').t(message).tree();
|
||||||
view.handleMUCMessage(msg);
|
view.model.onMessage(msg);
|
||||||
expect(_.includes($(view.el).find('.chat-msg-author').text(), '**Dyon van de Wege')).toBeTruthy();
|
expect(_.includes($(view.el).find('.chat-msg-author').text(), '**Dyon van de Wege')).toBeTruthy();
|
||||||
expect($(view.el).find('.chat-msg-content').text()).toBe(' is tired');
|
expect($(view.el).find('.chat-msg-content').text()).toBe(' is tired');
|
||||||
|
|
||||||
@ -859,7 +878,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').t(message).tree();
|
}).c('body').t(message).tree();
|
||||||
view.handleMUCMessage(msg);
|
view.model.onMessage(msg);
|
||||||
expect(_.includes($(view.el).find('.chat-msg-author:last').text(), '**Max Mustermann')).toBeTruthy();
|
expect(_.includes($(view.el).find('.chat-msg-author:last').text(), '**Max Mustermann')).toBeTruthy();
|
||||||
expect($(view.el).find('.chat-msg-content:last').text()).toBe(' is as well');
|
expect($(view.el).find('.chat-msg-content:last').text()).toBe(' is as well');
|
||||||
done();
|
done();
|
||||||
@ -1321,7 +1340,7 @@
|
|||||||
.c('status').attrs({code:'210'}).nodeTree;
|
.c('status').attrs({code:'210'}).nodeTree;
|
||||||
|
|
||||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
var info_text = $(view.el).find('.chat-content .chat-info').text();
|
var info_text = $(view.el).find('.chat-content .chat-info:first').text();
|
||||||
expect(info_text).toBe('Your nickname has been automatically set to thirdwitch');
|
expect(info_text).toBe('Your nickname has been automatically set to thirdwitch');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -1442,7 +1461,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').t(text);
|
}).c('body').t(text);
|
||||||
view.onChatRoomMessage(message.nodeTree);
|
view.model.onMessage(message.nodeTree);
|
||||||
var $chat_content = $(view.el).find('.chat-content');
|
var $chat_content = $(view.el).find('.chat-content');
|
||||||
expect($chat_content.find('.chat-message').length).toBe(1);
|
expect($chat_content.find('.chat-message').length).toBe(1);
|
||||||
expect($chat_content.find('.chat-msg-content').text()).toBe(text);
|
expect($chat_content.find('.chat-msg-content').text()).toBe(text);
|
||||||
@ -1480,7 +1499,7 @@
|
|||||||
type: 'groupchat',
|
type: 'groupchat',
|
||||||
id: view.model.messages.at(0).get('msgid')
|
id: view.model.messages.at(0).get('msgid')
|
||||||
}).c('body').t(text);
|
}).c('body').t(text);
|
||||||
view.onChatRoomMessage(message.nodeTree);
|
view.model.onMessage(message.nodeTree);
|
||||||
expect($chat_content.find('.chat-message').length).toBe(1);
|
expect($chat_content.find('.chat-message').length).toBe(1);
|
||||||
expect($chat_content.find('.chat-msg-content').last().text()).toBe(text);
|
expect($chat_content.find('.chat-msg-content').last().text()).toBe(text);
|
||||||
// We don't emit an event if it's our own message
|
// We don't emit an event if it's our own message
|
||||||
@ -1502,7 +1521,7 @@
|
|||||||
* scrollbar.
|
* scrollbar.
|
||||||
*/
|
*/
|
||||||
for (var i=0; i<20; i++) {
|
for (var i=0; i<20; i++) {
|
||||||
view.handleMUCMessage(
|
view.model.onMessage(
|
||||||
$msg({
|
$msg({
|
||||||
from: 'lounge@localhost/someone',
|
from: 'lounge@localhost/someone',
|
||||||
to: 'dummy@localhost.com',
|
to: 'dummy@localhost.com',
|
||||||
@ -1513,7 +1532,7 @@
|
|||||||
// Give enough time for `markScrolled` to have been called
|
// Give enough time for `markScrolled` to have been called
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
view.content.scrollTop = 0;
|
view.content.scrollTop = 0;
|
||||||
view.handleMUCMessage(
|
view.model.onMessage(
|
||||||
$msg({
|
$msg({
|
||||||
from: 'lounge@localhost/someone',
|
from: 'lounge@localhost/someone',
|
||||||
to: 'dummy@localhost.com',
|
to: 'dummy@localhost.com',
|
||||||
@ -1562,7 +1581,10 @@
|
|||||||
spyOn(window, 'alert');
|
spyOn(window, 'alert');
|
||||||
var subject = '<img src="x" onerror="alert(\'XSS\');"/>';
|
var subject = '<img src="x" onerror="alert(\'XSS\');"/>';
|
||||||
var view = _converse.chatboxviews.get('jdev@conference.jabber.org');
|
var view = _converse.chatboxviews.get('jdev@conference.jabber.org');
|
||||||
view.setChatRoomSubject('ralphm', subject);
|
view.model.set({'subject': {
|
||||||
|
'text': subject,
|
||||||
|
'author': 'ralphm'
|
||||||
|
}});
|
||||||
var chat_content = view.el.querySelector('.chat-content');
|
var chat_content = view.el.querySelector('.chat-content');
|
||||||
expect($(chat_content).find('.chat-event:last').text()).toBe('Topic set by ralphm');
|
expect($(chat_content).find('.chat-event:last').text()).toBe('Topic set by ralphm');
|
||||||
expect($(chat_content).find('.chat-topic:last').text()).toBe(subject);
|
expect($(chat_content).find('.chat-topic:last').text()).toBe(subject);
|
||||||
@ -1615,35 +1637,14 @@
|
|||||||
var view = _converse.chatboxviews.get('lounge@localhost');
|
var view = _converse.chatboxviews.get('lounge@localhost');
|
||||||
var $chat_content = $(view.el).find('.chat-content');
|
var $chat_content = $(view.el).find('.chat-content');
|
||||||
|
|
||||||
// The user has just entered the room and receives their own
|
|
||||||
// presence from the server.
|
|
||||||
// See example 24:
|
|
||||||
// http://xmpp.org/extensions/xep-0045.html#enter-pres
|
|
||||||
var presence = $pres({
|
|
||||||
to:'dummy@localhost/pda',
|
|
||||||
from:'lounge@localhost/oldnick',
|
|
||||||
id:'DC352437-C019-40EC-B590-AF29E879AF97'
|
|
||||||
}).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
|
|
||||||
.c('item').attrs({
|
|
||||||
affiliation: 'member',
|
|
||||||
jid: 'dummy@localhost/pda',
|
|
||||||
role: 'participant'
|
|
||||||
}).up()
|
|
||||||
.c('status').attrs({code:'110'}).up()
|
|
||||||
.c('status').attrs({code:'210'}).nodeTree;
|
|
||||||
|
|
||||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
|
||||||
var $occupants = $(view.el.querySelector('.occupant-list'));
|
var $occupants = $(view.el.querySelector('.occupant-list'));
|
||||||
expect($occupants.children().length).toBe(1);
|
expect($occupants.children().length).toBe(1);
|
||||||
expect($occupants.children().first(0).text()).toBe("oldnick");
|
expect($occupants.children().first(0).text()).toBe("oldnick");
|
||||||
|
|
||||||
expect($chat_content.find('div.chat-info').length).toBe(2);
|
expect($chat_content.find('div.chat-info').length).toBe(1);
|
||||||
expect($chat_content.find('div.chat-info:first').html()).toBe("oldnick has entered the room");
|
expect($chat_content.find('div.chat-info:first').html()).toBe("oldnick has entered the room");
|
||||||
expect($chat_content.find('div.chat-info:last').html()).toBe(
|
|
||||||
__(_converse.muc.new_nickname_messages["210"], "oldnick")
|
|
||||||
);
|
|
||||||
|
|
||||||
presence = $pres().attrs({
|
var presence = $pres().attrs({
|
||||||
from:'lounge@localhost/oldnick',
|
from:'lounge@localhost/oldnick',
|
||||||
id:'DC352437-C019-40EC-B590-AF29E879AF98',
|
id:'DC352437-C019-40EC-B590-AF29E879AF98',
|
||||||
to:'dummy@localhost/pda',
|
to:'dummy@localhost/pda',
|
||||||
@ -1660,13 +1661,13 @@
|
|||||||
.c('status').attrs({code:'110'}).nodeTree;
|
.c('status').attrs({code:'110'}).nodeTree;
|
||||||
|
|
||||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
expect($chat_content.find('div.chat-info').length).toBe(3);
|
expect($chat_content.find('div.chat-info').length).toBe(2);
|
||||||
expect($chat_content.find('div.chat-info').last().html()).toBe(
|
expect($chat_content.find('div.chat-info').last().html()).toBe(
|
||||||
__(_converse.muc.new_nickname_messages["303"], "newnick")
|
__(_converse.muc.new_nickname_messages["303"], "newnick")
|
||||||
);
|
);
|
||||||
|
|
||||||
$occupants = $(view.el.querySelector('.occupant-list'));
|
$occupants = $(view.el.querySelector('.occupant-list'));
|
||||||
expect($occupants.children().length).toBe(0);
|
expect($occupants.children().length).toBe(1);
|
||||||
|
|
||||||
presence = $pres().attrs({
|
presence = $pres().attrs({
|
||||||
from:'lounge@localhost/newnick',
|
from:'lounge@localhost/newnick',
|
||||||
@ -1682,12 +1683,10 @@
|
|||||||
.c('status').attrs({code:'110'}).nodeTree;
|
.c('status').attrs({code:'110'}).nodeTree;
|
||||||
|
|
||||||
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
expect($chat_content.find('div.chat-info').length).toBe(4);
|
expect($chat_content.find('div.chat-info').length).toBe(2);
|
||||||
expect($chat_content.find('div.chat-info').get(2).textContent).toBe(
|
expect($chat_content.find('div.chat-info').get(1).textContent).toBe(
|
||||||
__(_converse.muc.new_nickname_messages["303"], "newnick")
|
__(_converse.muc.new_nickname_messages["303"], "newnick")
|
||||||
);
|
);
|
||||||
expect($chat_content.find('div.chat-info').last().html()).toBe(
|
|
||||||
"newnick has entered the room");
|
|
||||||
$occupants = $(view.el.querySelector('.occupant-list'));
|
$occupants = $(view.el.querySelector('.occupant-list'));
|
||||||
expect($occupants.children().length).toBe(1);
|
expect($occupants.children().length).toBe(1);
|
||||||
expect($occupants.children().first(0).text()).toBe("newnick");
|
expect($occupants.children().first(0).text()).toBe("newnick");
|
||||||
@ -1907,9 +1906,9 @@
|
|||||||
.up()
|
.up()
|
||||||
.c('status').attrs({code:'110'}).up()
|
.c('status').attrs({code:'110'}).up()
|
||||||
.c('status').attrs({code:'307'}).nodeTree;
|
.c('status').attrs({code:'307'}).nodeTree;
|
||||||
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
|
|
||||||
var view = _converse.chatboxviews.get('lounge@localhost');
|
var view = _converse.chatboxviews.get('lounge@localhost');
|
||||||
view.onChatRoomPresence(presence);
|
|
||||||
expect($(view.el.querySelector('.chat-area')).is(':visible')).toBeFalsy();
|
expect($(view.el.querySelector('.chat-area')).is(':visible')).toBeFalsy();
|
||||||
expect($(view.el.querySelector('.occupants')).is(':visible')).toBeFalsy();
|
expect($(view.el.querySelector('.occupants')).is(':visible')).toBeFalsy();
|
||||||
var $chat_body = $(view.el.querySelector('.chatroom-body'));
|
var $chat_body = $(view.el.querySelector('.chatroom-body'));
|
||||||
@ -1992,16 +1991,12 @@
|
|||||||
var view = _converse.chatboxviews.get('lounge@localhost');
|
var view = _converse.chatboxviews.get('lounge@localhost');
|
||||||
spyOn(view, 'close').and.callThrough();
|
spyOn(view, 'close').and.callThrough();
|
||||||
spyOn(_converse, 'emit');
|
spyOn(_converse, 'emit');
|
||||||
spyOn(view, 'leave');
|
spyOn(view.model, 'leave');
|
||||||
view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
|
view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
|
||||||
view.el.querySelector('.close-chatbox-button').click();
|
view.el.querySelector('.close-chatbox-button').click();
|
||||||
expect(view.close).toHaveBeenCalled();
|
expect(view.close).toHaveBeenCalled();
|
||||||
expect(view.leave).toHaveBeenCalled();
|
expect(view.model.leave).toHaveBeenCalled();
|
||||||
// XXX: After refactoring, the chat box only gets closed
|
expect(_converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
|
||||||
// once we have confirmation from the server. To test this,
|
|
||||||
// we would have to mock the returned presence stanza.
|
|
||||||
// See the "leave" method on the ChatRoomView.
|
|
||||||
// expect(_converse.emit).toHaveBeenCalledWith('chatBoxClosed', jasmine.any(Object));
|
|
||||||
done();
|
done();
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
@ -2600,18 +2595,19 @@
|
|||||||
|
|
||||||
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
||||||
.then(function () {
|
.then(function () {
|
||||||
var presence = $pres().attrs({
|
|
||||||
from:'lounge@localhost/thirdwitch',
|
|
||||||
id:'n13mt3l',
|
|
||||||
to:'dummy@localhost/pda',
|
|
||||||
type:'error'})
|
|
||||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
|
||||||
.c('error').attrs({by:'lounge@localhost', type:'auth'})
|
|
||||||
.c('not-authorized').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
|
||||||
|
|
||||||
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
||||||
spyOn(view, 'renderPasswordForm').and.callThrough();
|
spyOn(view, 'renderPasswordForm').and.callThrough();
|
||||||
view.onChatRoomPresence(presence);
|
|
||||||
|
var presence = $pres().attrs({
|
||||||
|
from:'problematic@muc.localhost/dummy',
|
||||||
|
id:'n13mt3l',
|
||||||
|
to:'dummy@localhost/pda',
|
||||||
|
type:'error'})
|
||||||
|
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||||
|
.c('error').attrs({by:'lounge@localhost', type:'auth'})
|
||||||
|
.c('not-authorized').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'});
|
||||||
|
|
||||||
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
|
|
||||||
var $chat_body = $(view.el).find('.chatroom-body');
|
var $chat_body = $(view.el).find('.chatroom-body');
|
||||||
expect(view.renderPasswordForm).toHaveBeenCalled();
|
expect(view.renderPasswordForm).toHaveBeenCalled();
|
||||||
@ -2636,16 +2632,16 @@
|
|||||||
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
||||||
.then(function () {
|
.then(function () {
|
||||||
var presence = $pres().attrs({
|
var presence = $pres().attrs({
|
||||||
from:'lounge@localhost/thirdwitch',
|
from:'problematic@muc.localhost/dummy',
|
||||||
id:'n13mt3l',
|
id:'n13mt3l',
|
||||||
to:'dummy@localhost/pda',
|
to:'dummy@localhost/pda',
|
||||||
type:'error'})
|
type:'error'})
|
||||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||||
.c('error').attrs({by:'lounge@localhost', type:'auth'})
|
.c('error').attrs({by:'lounge@localhost', type:'auth'})
|
||||||
.c('registration-required').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
.c('registration-required').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||||
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
||||||
spyOn(view, 'showErrorMessage').and.callThrough();
|
spyOn(view, 'showErrorMessage').and.callThrough();
|
||||||
view.onChatRoomPresence(presence);
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
expect($(view.el).find('.chatroom-body p:last').text()).toBe('You are not on the member list of this room.');
|
expect($(view.el).find('.chatroom-body p:last').text()).toBe('You are not on the member list of this room.');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -2659,16 +2655,16 @@
|
|||||||
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
||||||
.then(function () {
|
.then(function () {
|
||||||
var presence = $pres().attrs({
|
var presence = $pres().attrs({
|
||||||
from:'lounge@localhost/thirdwitch',
|
from:'problematic@muc.localhost/dummy',
|
||||||
id:'n13mt3l',
|
id:'n13mt3l',
|
||||||
to:'dummy@localhost/pda',
|
to:'dummy@localhost/pda',
|
||||||
type:'error'})
|
type:'error'})
|
||||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||||
.c('error').attrs({by:'lounge@localhost', type:'auth'})
|
.c('error').attrs({by:'lounge@localhost', type:'auth'})
|
||||||
.c('forbidden').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
.c('forbidden').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||||
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
||||||
spyOn(view, 'showErrorMessage').and.callThrough();
|
spyOn(view, 'showErrorMessage').and.callThrough();
|
||||||
view.onChatRoomPresence(presence);
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
expect($(view.el).find('.chatroom-body p:last').text()).toBe('You have been banned from this room.');
|
expect($(view.el).find('.chatroom-body p:last').text()).toBe('You have been banned from this room.');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -2682,16 +2678,16 @@
|
|||||||
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
||||||
.then(function () {
|
.then(function () {
|
||||||
var presence = $pres().attrs({
|
var presence = $pres().attrs({
|
||||||
from:'lounge@localhost/thirdwitch',
|
from:'problematic@muc.localhost/dummy',
|
||||||
id:'n13mt3l',
|
id:'n13mt3l',
|
||||||
to:'dummy@localhost/pda',
|
to:'dummy@localhost/pda',
|
||||||
type:'error'})
|
type:'error'})
|
||||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||||
.c('error').attrs({by:'lounge@localhost', type:'cancel'})
|
.c('error').attrs({by:'lounge@localhost', type:'cancel'})
|
||||||
.c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
.c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||||
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
||||||
spyOn(view, 'showErrorMessage').and.callThrough();
|
spyOn(view, 'showErrorMessage').and.callThrough();
|
||||||
view.onChatRoomPresence(presence);
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
expect($(view.el).find('.chatroom-body form.chatroom-form label:first').text()).toBe('Please choose your nickname');
|
expect($(view.el).find('.chatroom-body form.chatroom-form label:first').text()).toBe('Please choose your nickname');
|
||||||
|
|
||||||
var $input = $(view.el).find('.chatroom-body form.chatroom-form input:first');
|
var $input = $(view.el).find('.chatroom-body form.chatroom-form input:first');
|
||||||
@ -2722,14 +2718,14 @@
|
|||||||
_converse.muc_nickname_from_jid = true;
|
_converse.muc_nickname_from_jid = true;
|
||||||
|
|
||||||
var attrs = {
|
var attrs = {
|
||||||
from:'lounge@localhost/dummy',
|
from:'problematic@muc.localhost/dummy',
|
||||||
id:'n13mt3l',
|
|
||||||
to:'dummy@localhost/pda',
|
to:'dummy@localhost/pda',
|
||||||
type:'error'
|
type:'error'
|
||||||
};
|
};
|
||||||
|
attrs.id = new Date().getTime();
|
||||||
var presence = $pres().attrs(attrs)
|
var presence = $pres().attrs(attrs)
|
||||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||||
.c('error').attrs({by:'lounge@localhost', type:'cancel'})
|
.c('error').attrs({by:'problematic@muc.localhost', type:'cancel'})
|
||||||
.c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
.c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||||
|
|
||||||
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
||||||
@ -2738,24 +2734,26 @@
|
|||||||
|
|
||||||
// Simulate repeatedly that there's already someone in the room
|
// Simulate repeatedly that there's already someone in the room
|
||||||
// with that nickname
|
// with that nickname
|
||||||
view.onChatRoomPresence(presence);
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
expect(view.join).toHaveBeenCalledWith('dummy-2');
|
expect(view.join).toHaveBeenCalledWith('dummy-2');
|
||||||
|
|
||||||
attrs.from = 'lounge@localhost/dummy-2';
|
attrs.from = 'problematic@muc.localhost/dummy-2';
|
||||||
|
attrs.id = new Date().getTime();
|
||||||
presence = $pres().attrs(attrs)
|
presence = $pres().attrs(attrs)
|
||||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||||
.c('error').attrs({by:'lounge@localhost', type:'cancel'})
|
.c('error').attrs({by:'problematic@muc.localhost', type:'cancel'})
|
||||||
.c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
.c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||||
view.onChatRoomPresence(presence);
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
|
|
||||||
expect(view.join).toHaveBeenCalledWith('dummy-3');
|
expect(view.join).toHaveBeenCalledWith('dummy-3');
|
||||||
|
|
||||||
attrs.from = 'lounge@localhost/dummy-3';
|
attrs.from = 'problematic@muc.localhost/dummy-3';
|
||||||
|
attrs.id = new Date().getTime();
|
||||||
presence = $pres().attrs(attrs)
|
presence = $pres().attrs(attrs)
|
||||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||||
.c('error').attrs({by:'lounge@localhost', type:'cancel'})
|
.c('error').attrs({by:'problematic@muc.localhost', type:'cancel'})
|
||||||
.c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
.c('conflict').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||||
view.onChatRoomPresence(presence);
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
expect(view.join).toHaveBeenCalledWith('dummy-4');
|
expect(view.join).toHaveBeenCalledWith('dummy-4');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -2769,16 +2767,16 @@
|
|||||||
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
||||||
.then(function () {
|
.then(function () {
|
||||||
var presence = $pres().attrs({
|
var presence = $pres().attrs({
|
||||||
from:'lounge@localhost/thirdwitch',
|
from:'problematic@muc.localhost/dummy',
|
||||||
id:'n13mt3l',
|
id:'n13mt3l',
|
||||||
to:'dummy@localhost/pda',
|
to:'dummy@localhost/pda',
|
||||||
type:'error'})
|
type:'error'})
|
||||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||||
.c('error').attrs({by:'lounge@localhost', type:'cancel'})
|
.c('error').attrs({by:'lounge@localhost', type:'cancel'})
|
||||||
.c('not-allowed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
.c('not-allowed').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||||
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
||||||
spyOn(view, 'showErrorMessage').and.callThrough();
|
spyOn(view, 'showErrorMessage').and.callThrough();
|
||||||
view.onChatRoomPresence(presence);
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
expect($(view.el).find('.chatroom-body p:last').text()).toBe('You are not allowed to create new rooms.');
|
expect($(view.el).find('.chatroom-body p:last').text()).toBe('You are not allowed to create new rooms.');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -2792,16 +2790,16 @@
|
|||||||
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
||||||
.then(function () {
|
.then(function () {
|
||||||
var presence = $pres().attrs({
|
var presence = $pres().attrs({
|
||||||
from:'lounge@localhost/thirdwitch',
|
from:'problematic@muc.localhost/dummy',
|
||||||
id:'n13mt3l',
|
id:'n13mt3l',
|
||||||
to:'dummy@localhost/pda',
|
to:'dummy@localhost/pda',
|
||||||
type:'error'})
|
type:'error'})
|
||||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||||
.c('error').attrs({by:'lounge@localhost', type:'cancel'})
|
.c('error').attrs({by:'lounge@localhost', type:'cancel'})
|
||||||
.c('not-acceptable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
.c('not-acceptable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||||
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
||||||
spyOn(view, 'showErrorMessage').and.callThrough();
|
spyOn(view, 'showErrorMessage').and.callThrough();
|
||||||
view.onChatRoomPresence(presence);
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
expect($(view.el).find('.chatroom-body p:last').text()).toBe("Your nickname doesn't conform to this room's policies.");
|
expect($(view.el).find('.chatroom-body p:last').text()).toBe("Your nickname doesn't conform to this room's policies.");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -2815,16 +2813,16 @@
|
|||||||
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
||||||
.then(function () {
|
.then(function () {
|
||||||
var presence = $pres().attrs({
|
var presence = $pres().attrs({
|
||||||
from:'lounge@localhost/thirdwitch',
|
from:'problematic@muc.localhost/dummy',
|
||||||
id:'n13mt3l',
|
id:'n13mt3l',
|
||||||
to:'dummy@localhost/pda',
|
to:'dummy@localhost/pda',
|
||||||
type:'error'})
|
type:'error'})
|
||||||
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
.c('x').attrs({xmlns:'http://jabber.org/protocol/muc'}).up()
|
||||||
.c('error').attrs({by:'lounge@localhost', type:'cancel'})
|
.c('error').attrs({by:'lounge@localhost', type:'cancel'})
|
||||||
.c('item-not-found').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
.c('item-not-found').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||||
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
||||||
spyOn(view, 'showErrorMessage').and.callThrough();
|
spyOn(view, 'showErrorMessage').and.callThrough();
|
||||||
view.onChatRoomPresence(presence);
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
expect($(view.el).find('.chatroom-body p:last').text()).toBe("This room does not (yet) exist.");
|
expect($(view.el).find('.chatroom-body p:last').text()).toBe("This room does not (yet) exist.");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -2838,7 +2836,7 @@
|
|||||||
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
test_utils.openChatRoomViaModal(_converse, 'problematic@muc.localhost', 'dummy')
|
||||||
.then(function () {
|
.then(function () {
|
||||||
var presence = $pres().attrs({
|
var presence = $pres().attrs({
|
||||||
from:'lounge@localhost/thirdwitch',
|
from:'problematic@muc.localhost/dummy',
|
||||||
id:'n13mt3l',
|
id:'n13mt3l',
|
||||||
to:'dummy@localhost/pda',
|
to:'dummy@localhost/pda',
|
||||||
type:'error'})
|
type:'error'})
|
||||||
@ -2847,7 +2845,7 @@
|
|||||||
.c('service-unavailable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
.c('service-unavailable').attrs({xmlns:'urn:ietf:params:xml:ns:xmpp-stanzas'}).nodeTree;
|
||||||
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
var view = _converse.chatboxviews.get('problematic@muc.localhost');
|
||||||
spyOn(view, 'showErrorMessage').and.callThrough();
|
spyOn(view, 'showErrorMessage').and.callThrough();
|
||||||
view.onChatRoomPresence(presence);
|
_converse.connection._dataRecv(test_utils.createRequest(presence));
|
||||||
expect($(view.el).find('.chatroom-body p:last').text()).toBe("This room has reached its maximum number of occupants.");
|
expect($(view.el).find('.chatroom-body p:last').text()).toBe("This room has reached its maximum number of occupants.");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -3091,7 +3089,7 @@
|
|||||||
test_utils.waitUntil(function () {
|
test_utils.waitUntil(function () {
|
||||||
return u.isVisible(modal.el);
|
return u.isVisible(modal.el);
|
||||||
}, 1000).then(function () {
|
}, 1000).then(function () {
|
||||||
spyOn(_converse.ChatRoomView.prototype, 'getRoomFeatures').and.callFake(function () {
|
spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(function () {
|
||||||
var deferred = new $.Deferred();
|
var deferred = new $.Deferred();
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
return deferred.promise();
|
return deferred.promise();
|
||||||
@ -3126,7 +3124,7 @@
|
|||||||
test_utils.waitUntil(function () {
|
test_utils.waitUntil(function () {
|
||||||
return u.isVisible(modal.el);
|
return u.isVisible(modal.el);
|
||||||
}, 1000).then(function () {
|
}, 1000).then(function () {
|
||||||
spyOn(_converse.ChatRoomView.prototype, 'getRoomFeatures').and.callFake(function () {
|
spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(function () {
|
||||||
var deferred = new $.Deferred();
|
var deferred = new $.Deferred();
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
return deferred.promise();
|
return deferred.promise();
|
||||||
@ -3206,7 +3204,7 @@
|
|||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').t(message).tree();
|
}).c('body').t(message).tree();
|
||||||
|
|
||||||
view.handleMUCMessage(msg);
|
view.model.onMessage(msg);
|
||||||
|
|
||||||
expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
|
expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
|
||||||
expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(1);
|
expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(1);
|
||||||
@ -3218,7 +3216,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').t(message).tree();
|
}).c('body').t(message).tree();
|
||||||
view.handleMUCMessage(msg);
|
view.model.onMessage(msg);
|
||||||
|
|
||||||
expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
|
expect(roomspanel.el.querySelectorAll('.available-room').length).toBe(1);
|
||||||
expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(1);
|
expect(roomspanel.el.querySelectorAll('.msgs-indicator').length).toBe(1);
|
||||||
@ -3308,7 +3306,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
|
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
|
||||||
view.handleMUCMessage(msg);
|
view.model.onMessage(msg);
|
||||||
|
|
||||||
// Check that the notification appears inside the chatbox in the DOM
|
// Check that the notification appears inside the chatbox in the DOM
|
||||||
var events = view.el.querySelectorAll('.chat-event');
|
var events = view.el.querySelectorAll('.chat-event');
|
||||||
@ -3334,7 +3332,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
|
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
|
||||||
view.handleMUCMessage(msg);
|
view.model.onMessage(msg);
|
||||||
|
|
||||||
events = view.el.querySelectorAll('.chat-event');
|
events = view.el.querySelectorAll('.chat-event');
|
||||||
expect(events.length).toBe(4);
|
expect(events.length).toBe(4);
|
||||||
@ -3356,7 +3354,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
|
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
|
||||||
view.handleMUCMessage(msg);
|
view.model.onMessage(msg);
|
||||||
events = view.el.querySelectorAll('.chat-event');
|
events = view.el.querySelectorAll('.chat-event');
|
||||||
expect(events.length).toBe(4);
|
expect(events.length).toBe(4);
|
||||||
expect(events[0].textContent).toEqual('some1 has entered the room');
|
expect(events[0].textContent).toEqual('some1 has entered the room');
|
||||||
@ -3378,7 +3376,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').t('hello world').tree();
|
}).c('body').t('hello world').tree();
|
||||||
view.handleMUCMessage(msg);
|
view.model.onMessage(msg);
|
||||||
|
|
||||||
var messages = view.el.querySelectorAll('.message');
|
var messages = view.el.querySelectorAll('.message');
|
||||||
expect(messages.length).toBe(8);
|
expect(messages.length).toBe(8);
|
||||||
@ -3483,7 +3481,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
|
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
|
||||||
view.handleMUCMessage(msg);
|
view.model.onMessage(msg);
|
||||||
|
|
||||||
// Check that the notification appears inside the chatbox in the DOM
|
// Check that the notification appears inside the chatbox in the DOM
|
||||||
var events = view.el.querySelectorAll('.chat-event');
|
var events = view.el.querySelectorAll('.chat-event');
|
||||||
@ -3503,7 +3501,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
|
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
|
||||||
view.handleMUCMessage(msg);
|
view.model.onMessage(msg);
|
||||||
|
|
||||||
events = view.el.querySelectorAll('.chat-event');
|
events = view.el.querySelectorAll('.chat-event');
|
||||||
expect(events.length).toBe(3);
|
expect(events.length).toBe(3);
|
||||||
@ -3522,7 +3520,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
|
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
|
||||||
view.handleMUCMessage(msg);
|
view.model.onMessage(msg);
|
||||||
events = view.el.querySelectorAll('.chat-event');
|
events = view.el.querySelectorAll('.chat-event');
|
||||||
expect(events.length).toBe(3);
|
expect(events.length).toBe(3);
|
||||||
expect(events[0].textContent).toEqual('some1 has entered the room');
|
expect(events[0].textContent).toEqual('some1 has entered the room');
|
||||||
@ -3541,7 +3539,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
|
}).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
|
||||||
view.handleMUCMessage(msg);
|
view.model.onMessage(msg);
|
||||||
events = view.el.querySelectorAll('.chat-event');
|
events = view.el.querySelectorAll('.chat-event');
|
||||||
expect(events.length).toBe(3);
|
expect(events.length).toBe(3);
|
||||||
expect(events[0].textContent).toEqual('some1 has entered the room');
|
expect(events[0].textContent).toEqual('some1 has entered the room');
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
</forwarded>
|
</forwarded>
|
||||||
</result>
|
</result>
|
||||||
</message>`).firstElementChild;
|
</message>`).firstElementChild;
|
||||||
chatroomview.onChatRoomMessage(stanza);
|
chatroomview.model.onMessage(stanza);
|
||||||
expect(chatroomview.content.querySelectorAll('.chat-message').length).toBe(1);
|
expect(chatroomview.content.querySelectorAll('.chat-message').length).toBe(1);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -158,7 +158,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').t(message).tree();
|
}).c('body').t(message).tree();
|
||||||
view.handleMUCMessage(msg);
|
view.model.onMessage(msg);
|
||||||
|
|
||||||
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).is(':visible')).toBeTruthy();
|
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).is(':visible')).toBeTruthy();
|
||||||
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe('1');
|
expect($(_converse.minimized_chats.toggleview.el.querySelector('.unread-message-count')).text()).toBe('1');
|
||||||
|
@ -173,7 +173,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').t(text);
|
}).c('body').t(text);
|
||||||
view.onChatRoomMessage(message.nodeTree);
|
view.model.onMessage(message.nodeTree);
|
||||||
expect(_converse.playSoundNotification).toHaveBeenCalled();
|
expect(_converse.playSoundNotification).toHaveBeenCalled();
|
||||||
|
|
||||||
text = "This message won't play a sound";
|
text = "This message won't play a sound";
|
||||||
@ -183,7 +183,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').t(text);
|
}).c('body').t(text);
|
||||||
view.onChatRoomMessage(message.nodeTree);
|
view.model.onMessage(message.nodeTree);
|
||||||
expect(_converse.playSoundNotification, 1);
|
expect(_converse.playSoundNotification, 1);
|
||||||
_converse.play_sounds = false;
|
_converse.play_sounds = false;
|
||||||
|
|
||||||
@ -194,7 +194,7 @@
|
|||||||
to: 'dummy@localhost',
|
to: 'dummy@localhost',
|
||||||
type: 'groupchat'
|
type: 'groupchat'
|
||||||
}).c('body').t(text);
|
}).c('body').t(text);
|
||||||
view.onChatRoomMessage(message.nodeTree);
|
view.model.onMessage(message.nodeTree);
|
||||||
expect(_converse.playSoundNotification, 1);
|
expect(_converse.playSoundNotification, 1);
|
||||||
_converse.play_sounds = false;
|
_converse.play_sounds = false;
|
||||||
done();
|
done();
|
||||||
|
@ -97,7 +97,7 @@
|
|||||||
view.model.set({'minimized': true});
|
view.model.set({'minimized': true});
|
||||||
var contact_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
|
var contact_jid = mock.cur_names[5].replace(/ /g,'.').toLowerCase() + '@localhost';
|
||||||
var nick = mock.chatroom_names[0];
|
var nick = mock.chatroom_names[0];
|
||||||
view.handleMUCMessage(
|
view.model.onMessage(
|
||||||
$msg({
|
$msg({
|
||||||
from: room_jid+'/'+nick,
|
from: room_jid+'/'+nick,
|
||||||
id: (new Date()).getTime(),
|
id: (new Date()).getTime(),
|
||||||
@ -112,7 +112,7 @@
|
|||||||
expect(_.includes(room_el.classList, 'unread-msgs'));
|
expect(_.includes(room_el.classList, 'unread-msgs'));
|
||||||
|
|
||||||
// If the user is mentioned, the counter also gets updated
|
// If the user is mentioned, the counter also gets updated
|
||||||
view.handleMUCMessage(
|
view.model.onMessage(
|
||||||
$msg({
|
$msg({
|
||||||
from: room_jid+'/'+nick,
|
from: room_jid+'/'+nick,
|
||||||
id: (new Date()).getTime(),
|
id: (new Date()).getTime(),
|
||||||
@ -123,7 +123,7 @@
|
|||||||
var indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
|
var indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");
|
||||||
expect(indicator_el.textContent).toBe('1');
|
expect(indicator_el.textContent).toBe('1');
|
||||||
|
|
||||||
view.handleMUCMessage(
|
view.model.onMessage(
|
||||||
$msg({
|
$msg({
|
||||||
from: room_jid+'/'+nick,
|
from: room_jid+'/'+nick,
|
||||||
id: (new Date()).getTime(),
|
id: (new Date()).getTime(),
|
||||||
|
@ -416,6 +416,7 @@
|
|||||||
'isodate': isodate,
|
'isodate': isodate,
|
||||||
'data': data
|
'data': data
|
||||||
}));
|
}));
|
||||||
|
this.insertDayIndicator(this.content.lastElementChild);
|
||||||
this.scrollDown();
|
this.scrollDown();
|
||||||
return isodate;
|
return isodate;
|
||||||
},
|
},
|
||||||
|
@ -269,13 +269,16 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
ChatRoomView: {
|
ChatRoom: {
|
||||||
|
|
||||||
initialize () {
|
onMessage (stanza) {
|
||||||
const { _converse } = this.__super__;
|
/* MAM (message archive management XEP-0313) messages are
|
||||||
this.__super__.initialize.apply(this, arguments);
|
* ignored, since they're handled separately.
|
||||||
this.model.on('change:mam_enabled', this.fetchArchivedMessagesIfNecessary, this);
|
*/
|
||||||
this.model.on('change:connection_status', this.fetchArchivedMessagesIfNecessary, this);
|
if (sizzle(`[xmlns="${Strophe.NS.MAM}"]`, stanza).length > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return this.__super__.onMessage.apply(this, arguments);
|
||||||
},
|
},
|
||||||
|
|
||||||
isDuplicate (message, original_stanza) {
|
isDuplicate (message, original_stanza) {
|
||||||
@ -285,8 +288,18 @@
|
|||||||
}
|
}
|
||||||
const archive_id = getMessageArchiveID(original_stanza);
|
const archive_id = getMessageArchiveID(original_stanza);
|
||||||
if (archive_id) {
|
if (archive_id) {
|
||||||
return this.model.messages.filter({'archive_id': archive_id}).length > 0;
|
return this.messages.filter({'archive_id': archive_id}).length > 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
ChatRoomView: {
|
||||||
|
|
||||||
|
initialize () {
|
||||||
|
const { _converse } = this.__super__;
|
||||||
|
this.__super__.initialize.apply(this, arguments);
|
||||||
|
this.model.on('change:mam_enabled', this.fetchArchivedMessagesIfNecessary, this);
|
||||||
|
this.model.on('change:connection_status', this.fetchArchivedMessagesIfNecessary, this);
|
||||||
},
|
},
|
||||||
|
|
||||||
renderChatArea () {
|
renderChatArea () {
|
||||||
@ -297,16 +310,6 @@
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleMUCMessage (stanza) {
|
|
||||||
/* MAM (message archive management XEP-0313) messages are
|
|
||||||
* ignored, since they're handled separately.
|
|
||||||
*/
|
|
||||||
if (sizzle(`[xmlns="${Strophe.NS.MAM}"]`, stanza).length > 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return this.__super__.handleMUCMessage.apply(this, arguments);
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchArchivedMessagesIfNecessary () {
|
fetchArchivedMessagesIfNecessary () {
|
||||||
if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED ||
|
if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED ||
|
||||||
!this.model.get('mam_enabled') ||
|
!this.model.get('mam_enabled') ||
|
||||||
@ -321,7 +324,7 @@
|
|||||||
fetchArchivedMessages (options) {
|
fetchArchivedMessages (options) {
|
||||||
/* Fetch archived chat messages for this Chat Room
|
/* Fetch archived chat messages for this Chat Room
|
||||||
*
|
*
|
||||||
* Then, upon receiving them, call onChatRoomMessage
|
* Then, upon receiving them, call onMessage
|
||||||
* so that they are displayed inside it.
|
* so that they are displayed inside it.
|
||||||
*/
|
*/
|
||||||
const that = this;
|
const that = this;
|
||||||
@ -337,7 +340,7 @@
|
|||||||
function (messages) {
|
function (messages) {
|
||||||
that.clearSpinner();
|
that.clearSpinner();
|
||||||
if (messages.length) {
|
if (messages.length) {
|
||||||
_.each(messages, that.onChatRoomMessage.bind(that));
|
_.each(messages, that.model.onMessage.bind(that));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function () {
|
function () {
|
||||||
@ -363,7 +366,6 @@
|
|||||||
message_archiving_timeout: 8000, // Time (in milliseconds) to wait before aborting MAM request
|
message_archiving_timeout: 8000, // Time (in milliseconds) to wait before aborting MAM request
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
_converse.onMAMError = function (iq) {
|
_converse.onMAMError = function (iq) {
|
||||||
if (iq.querySelectorAll('feature-not-implemented').length) {
|
if (iq.querySelectorAll('feature-not-implemented').length) {
|
||||||
_converse.log(
|
_converse.log(
|
||||||
|
@ -151,6 +151,99 @@
|
|||||||
|
|
||||||
_converse.api.promises.add(['roomsPanelRendered']);
|
_converse.api.promises.add(['roomsPanelRendered']);
|
||||||
|
|
||||||
|
// Configuration values for this plugin
|
||||||
|
// ====================================
|
||||||
|
// Refer to docs/source/configuration.rst for explanations of these
|
||||||
|
// configuration settings.
|
||||||
|
_converse.api.settings.update({
|
||||||
|
auto_list_rooms: false,
|
||||||
|
hide_muc_server: false, // TODO: no longer implemented...
|
||||||
|
muc_disable_moderator_commands: false,
|
||||||
|
visible_toolbar_buttons: {
|
||||||
|
'toggle_occupants': true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* http://xmpp.org/extensions/xep-0045.html
|
||||||
|
* ----------------------------------------
|
||||||
|
* 100 message Entering a room 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 room
|
||||||
|
* 102 message Configuration change Inform occupants that room now shows unavailable members
|
||||||
|
* 103 message Configuration change Inform occupants that room now does not show unavailable members
|
||||||
|
* 104 message Configuration change Inform occupants that a non-privacy-related room configuration change has occurred
|
||||||
|
* 110 presence Any room presence Inform user that presence refers to one of its own room occupants
|
||||||
|
* 170 message or initial presence Configuration change Inform occupants that room logging is now enabled
|
||||||
|
* 171 message Configuration change Inform occupants that room logging is now disabled
|
||||||
|
* 172 message Configuration change Inform occupants that the room is now non-anonymous
|
||||||
|
* 173 message Configuration change Inform occupants that the room is now semi-anonymous
|
||||||
|
* 174 message Configuration change Inform occupants that the room is now fully-anonymous
|
||||||
|
* 201 presence Entering a room Inform user that a new room has been created
|
||||||
|
* 210 presence Entering a room Inform user that the service has assigned or modified the occupant's roomnick
|
||||||
|
* 301 presence Removal from room Inform user that he or she has been banned from the room
|
||||||
|
* 303 presence Exiting a room Inform all occupants of new room nickname
|
||||||
|
* 307 presence Removal from room Inform user that he or she has been kicked from the room
|
||||||
|
* 321 presence Removal from room Inform user that he or she is being removed from the room because of an affiliation change
|
||||||
|
* 322 presence Removal from room Inform user that he or she is being removed from the room because the room has been changed to members-only and the user is not a member
|
||||||
|
* 332 presence Removal from room Inform user that he or she is being removed from the room because of a system shutdown
|
||||||
|
*/
|
||||||
|
_converse.muc = {
|
||||||
|
info_messages: {
|
||||||
|
100: __('This room is not anonymous'),
|
||||||
|
102: __('This room now shows unavailable members'),
|
||||||
|
103: __('This room does not show unavailable members'),
|
||||||
|
104: __('The room configuration has changed'),
|
||||||
|
170: __('Room logging is now enabled'),
|
||||||
|
171: __('Room logging is now disabled'),
|
||||||
|
172: __('This room is now no longer anonymous'),
|
||||||
|
173: __('This room is now semi-anonymous'),
|
||||||
|
174: __('This room is now fully-anonymous'),
|
||||||
|
201: __('A new room has been created')
|
||||||
|
},
|
||||||
|
|
||||||
|
disconnect_messages: {
|
||||||
|
301: __('You have been banned from this room'),
|
||||||
|
307: __('You have been kicked from this room'),
|
||||||
|
321: __("You have been removed from this room because of an affiliation change"),
|
||||||
|
322: __("You have been removed from this room because the room has changed to members-only and you're not a member"),
|
||||||
|
332: __("You have been removed from this room because the MUC (Multi-user chat) service 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')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
function insertRoomInfo (el, stanza) {
|
function insertRoomInfo (el, stanza) {
|
||||||
/* Insert room info (based on returned #disco IQ stanza)
|
/* Insert room info (based on returned #disco IQ stanza)
|
||||||
@ -422,13 +515,18 @@
|
|||||||
this.markScrolled = _.debounce(this._markScrolled, 100);
|
this.markScrolled = _.debounce(this._markScrolled, 100);
|
||||||
|
|
||||||
this.model.messages.on('add', this.onMessageAdded, this);
|
this.model.messages.on('add', this.onMessageAdded, this);
|
||||||
this.model.on('show', this.show, this);
|
|
||||||
this.model.on('destroy', this.hide, this);
|
|
||||||
this.model.on('change:connection_status', this.afterConnected, this);
|
|
||||||
this.model.on('change:affiliation', this.renderHeading, this);
|
this.model.on('change:affiliation', this.renderHeading, this);
|
||||||
this.model.on('change:chat_state', this.sendChatState, this);
|
this.model.on('change:chat_state', this.sendChatState, this);
|
||||||
|
this.model.on('change:connection_status', this.afterConnected, this);
|
||||||
this.model.on('change:description', this.renderHeading, this);
|
this.model.on('change:description', this.renderHeading, this);
|
||||||
this.model.on('change:name', this.renderHeading, this);
|
this.model.on('change:name', this.renderHeading, this);
|
||||||
|
this.model.on('change:subject', this.setChatRoomSubject, this);
|
||||||
|
this.model.on('configurationNeeded', this.getAndRenderConfigurationForm, this);
|
||||||
|
this.model.on('destroy', this.hide, this);
|
||||||
|
this.model.on('show', this.show, this);
|
||||||
|
|
||||||
|
this.model.occupants.on('add', this.showJoinNotification, this);
|
||||||
|
this.model.occupants.on('remove', this.showLeaveNotification, this);
|
||||||
|
|
||||||
this.createEmojiPicker();
|
this.createEmojiPicker();
|
||||||
this.createOccupantsView();
|
this.createOccupantsView();
|
||||||
@ -441,7 +539,7 @@
|
|||||||
this.fetchMessages();
|
this.fetchMessages();
|
||||||
_converse.emit('chatRoomOpened', this);
|
_converse.emit('chatRoomOpened', this);
|
||||||
}
|
}
|
||||||
this.getRoomFeatures().then(handler, handler);
|
this.model.getRoomFeatures().then(handler, handler);
|
||||||
} else {
|
} else {
|
||||||
this.fetchMessages();
|
this.fetchMessages();
|
||||||
_converse.emit('chatRoomOpened', this);
|
_converse.emit('chatRoomOpened', this);
|
||||||
@ -487,9 +585,8 @@
|
|||||||
createOccupantsView () {
|
createOccupantsView () {
|
||||||
/* Create the ChatRoomOccupantsView Backbone.NativeView
|
/* Create the ChatRoomOccupantsView Backbone.NativeView
|
||||||
*/
|
*/
|
||||||
const model = new _converse.ChatRoomOccupants();
|
this.model.occupants.chatroomview = this;
|
||||||
model.chatroomview = this;
|
this.occupantsview = new _converse.ChatRoomOccupantsView({'model': this.model.occupants});
|
||||||
this.occupantsview = new _converse.ChatRoomOccupantsView({'model': model});
|
|
||||||
this.occupantsview.model.on('change:role', this.informOfOccupantsRoleChange, this);
|
this.occupantsview.model.on('change:role', this.informOfOccupantsRoleChange, this);
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
@ -550,6 +647,7 @@
|
|||||||
|
|
||||||
afterConnected () {
|
afterConnected () {
|
||||||
if (this.model.get('connection_status') === converse.ROOMSTATUS.ENTERED) {
|
if (this.model.get('connection_status') === converse.ROOMSTATUS.ENTERED) {
|
||||||
|
this.hideSpinner();
|
||||||
this.setChatState(_converse.ACTIVE);
|
this.setChatState(_converse.ACTIVE);
|
||||||
this.scrollDown();
|
this.scrollDown();
|
||||||
this.focus();
|
this.focus();
|
||||||
@ -583,7 +681,12 @@
|
|||||||
/* Close this chat box, which implies leaving the room as
|
/* Close this chat box, which implies leaving the room as
|
||||||
* well.
|
* well.
|
||||||
*/
|
*/
|
||||||
this.leave();
|
this.hide();
|
||||||
|
if (Backbone.history.getFragment() === "converse/room?jid="+this.model.get('jid')) {
|
||||||
|
_converse.router.navigate('');
|
||||||
|
}
|
||||||
|
this.model.leave();
|
||||||
|
_converse.ChatBoxView.prototype.close.apply(this, arguments);
|
||||||
},
|
},
|
||||||
|
|
||||||
setOccupantsVisibility () {
|
setOccupantsVisibility () {
|
||||||
@ -802,7 +905,7 @@
|
|||||||
case 'nick':
|
case 'nick':
|
||||||
_converse.connection.send($pres({
|
_converse.connection.send($pres({
|
||||||
from: _converse.connection.jid,
|
from: _converse.connection.jid,
|
||||||
to: this.getRoomJIDAndNick(match[2]),
|
to: this.model.getRoomJIDAndNick(match[2]),
|
||||||
id: _converse.connection.getUniqueId()
|
id: _converse.connection.getUniqueId()
|
||||||
}).tree());
|
}).tree());
|
||||||
break;
|
break;
|
||||||
@ -848,76 +951,39 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleMUCMessage (stanza) {
|
|
||||||
/* Handler for all MUC messages sent to this chat room.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) stanza: The message stanza.
|
|
||||||
*/
|
|
||||||
const configuration_changed = stanza.querySelector("status[code='104']");
|
|
||||||
const logging_enabled = stanza.querySelector("status[code='170']");
|
|
||||||
const logging_disabled = stanza.querySelector("status[code='171']");
|
|
||||||
const room_no_longer_anon = stanza.querySelector("status[code='172']");
|
|
||||||
const room_now_semi_anon = stanza.querySelector("status[code='173']");
|
|
||||||
const room_now_fully_anon = stanza.querySelector("status[code='173']");
|
|
||||||
if (configuration_changed || logging_enabled || logging_disabled ||
|
|
||||||
room_no_longer_anon || room_now_semi_anon || room_now_fully_anon) {
|
|
||||||
this.getRoomFeatures();
|
|
||||||
}
|
|
||||||
_.flow(this.showStatusMessages.bind(this), this.onChatRoomMessage.bind(this))(stanza);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
getRoomJIDAndNick (nick) {
|
|
||||||
/* Utility method to construct the JID for the current user
|
|
||||||
* as occupant of the room.
|
|
||||||
*
|
|
||||||
* This is the room JID, with the user's nick added at the
|
|
||||||
* end.
|
|
||||||
*
|
|
||||||
* For example: room@conference.example.org/nickname
|
|
||||||
*/
|
|
||||||
if (nick) {
|
|
||||||
this.model.save({'nick': nick});
|
|
||||||
} else {
|
|
||||||
nick = this.model.get('nick');
|
|
||||||
}
|
|
||||||
const room = this.model.get('jid');
|
|
||||||
const jid = Strophe.getBareJidFromJid(room);
|
|
||||||
return jid + (nick !== null ? `/${nick}` : "");
|
|
||||||
},
|
|
||||||
|
|
||||||
registerHandlers () {
|
registerHandlers () {
|
||||||
/* Register presence and message handlers for this chat
|
/* Register presence and message handlers for this chat
|
||||||
* room
|
* room
|
||||||
*/
|
*/
|
||||||
const room_jid = this.model.get('jid');
|
// XXX: Ideally this can be refactored out so that we don't
|
||||||
this.removeHandlers();
|
// need to do stanza processing inside the views in this
|
||||||
this.presence_handler = _converse.connection.addHandler(
|
// module. See the comment in "onPresence" for more info.
|
||||||
this.onChatRoomPresence.bind(this),
|
this.model.addHandler('presence', 'ChatRoomView.onPresence', this.onPresence.bind(this));
|
||||||
Strophe.NS.MUC, 'presence', null, null, room_jid,
|
// XXX instead of having a method showStatusMessages, we could instead
|
||||||
{'ignoreNamespaceFragment': true, 'matchBareFromJid': true}
|
// 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));
|
||||||
this.message_handler = _converse.connection.addHandler(
|
|
||||||
this.handleMUCMessage.bind(this),
|
|
||||||
null, 'message', 'groupchat', null, room_jid,
|
|
||||||
{'matchBareFromJid': true}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
removeHandlers () {
|
onPresence (pres) {
|
||||||
/* Remove the presence and message handlers that were
|
/* Handles all MUC presence stanzas.
|
||||||
* registered for this chat room.
|
*
|
||||||
|
* Parameters:
|
||||||
|
* (XMLElement) pres: The stanza
|
||||||
*/
|
*/
|
||||||
if (this.message_handler) {
|
// XXX: Current thinking is that excessive stanza
|
||||||
_converse.connection.deleteHandler(this.message_handler);
|
// processing inside a view is a "code smell".
|
||||||
delete this.message_handler;
|
// 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);
|
||||||
}
|
}
|
||||||
if (this.presence_handler) {
|
|
||||||
_converse.connection.deleteHandler(this.presence_handler);
|
|
||||||
delete this.presence_handler;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
join (nick, password) {
|
join (nick, password) {
|
||||||
@ -928,66 +994,14 @@
|
|||||||
* (String) password: Optional password, if required by
|
* (String) password: Optional password, if required by
|
||||||
* the room.
|
* the room.
|
||||||
*/
|
*/
|
||||||
nick = nick ? nick : this.model.get('nick');
|
if (!nick && !this.model.get('nick')) {
|
||||||
if (!nick) {
|
|
||||||
this.checkForReservedNick();
|
this.checkForReservedNick();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
if (this.model.get('connection_status') === converse.ROOMSTATUS.ENTERED) {
|
this.model.join(nick, password);
|
||||||
// We have restored a chat room from session storage,
|
|
||||||
// so we don't send out a presence stanza again.
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stanza = $pres({
|
|
||||||
'from': _converse.connection.jid,
|
|
||||||
'to': this.getRoomJIDAndNick(nick)
|
|
||||||
}).c("x", {'xmlns': Strophe.NS.MUC})
|
|
||||||
.c("history", {'maxstanzas': _converse.muc_history_max_stanzas}).up();
|
|
||||||
if (password) {
|
|
||||||
stanza.cnode(Strophe.xmlElement("password", [], password));
|
|
||||||
}
|
|
||||||
this.model.save('connection_status', converse.ROOMSTATUS.CONNECTING);
|
|
||||||
_converse.connection.send(stanza);
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
sendUnavailablePresence (exit_msg) {
|
|
||||||
const presence = $pres({
|
|
||||||
type: "unavailable",
|
|
||||||
from: _converse.connection.jid,
|
|
||||||
to: this.getRoomJIDAndNick()
|
|
||||||
});
|
|
||||||
if (exit_msg !== null) {
|
|
||||||
presence.c("status", exit_msg);
|
|
||||||
}
|
|
||||||
_converse.connection.sendPresence(presence);
|
|
||||||
},
|
|
||||||
|
|
||||||
leave(exit_msg) {
|
|
||||||
/* Leave the chat room.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) exit_msg: Optional message to indicate your
|
|
||||||
* reason for leaving.
|
|
||||||
*/
|
|
||||||
this.hide();
|
|
||||||
if (Backbone.history.getFragment() === "converse/room?jid="+this.model.get('jid')) {
|
|
||||||
_converse.router.navigate('');
|
|
||||||
}
|
|
||||||
this.occupantsview.model.reset();
|
|
||||||
this.occupantsview.model.browserStorage._clear();
|
|
||||||
if (_converse.connection.connected) {
|
|
||||||
this.sendUnavailablePresence(exit_msg);
|
|
||||||
}
|
|
||||||
u.safeSave(
|
|
||||||
this.model,
|
|
||||||
{'connection_status': converse.ROOMSTATUS.DISCONNECTED}
|
|
||||||
);
|
|
||||||
this.removeHandlers();
|
|
||||||
_converse.ChatBoxView.prototype.close.apply(this, arguments);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderConfigurationForm (stanza) {
|
renderConfigurationForm (stanza) {
|
||||||
/* Renders a form given an IQ stanza containing the current
|
/* Renders a form given an IQ stanza containing the current
|
||||||
* room configuration.
|
* room configuration.
|
||||||
@ -1037,78 +1051,15 @@
|
|||||||
|
|
||||||
form_el.addEventListener('submit', (ev) => {
|
form_el.addEventListener('submit', (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this.saveConfiguration(ev.target).then(
|
this.model.saveConfiguration(ev.target).then(
|
||||||
this.getRoomFeatures.bind(this)
|
this.model.getRoomFeatures.bind(this.model)
|
||||||
);
|
);
|
||||||
|
this.closeForm();
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
saveConfiguration (form) {
|
|
||||||
/* Submit the room configuration form by sending an IQ
|
|
||||||
* stanza to the server.
|
|
||||||
*
|
|
||||||
* Returns a promise which resolves once the XMPP server
|
|
||||||
* has return a response IQ.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (HTMLElement) form: The configuration form DOM element.
|
|
||||||
*/
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const inputs = form ? sizzle(':input:not([type=button]):not([type=submit])', form) : [],
|
|
||||||
configArray = _.map(inputs, u.webForm2xForm);
|
|
||||||
this.model.sendConfiguration(configArray, resolve, reject);
|
|
||||||
this.closeForm();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
autoConfigureChatRoom () {
|
|
||||||
/* Automatically configure room based on the
|
|
||||||
* 'roomconfig' data on this view's model.
|
|
||||||
*
|
|
||||||
* Returns a promise which resolves once a response IQ has
|
|
||||||
* been received.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) stanza: IQ stanza from the server,
|
|
||||||
* containing the configuration.
|
|
||||||
*/
|
|
||||||
const that = this;
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.fetchRoomConfiguration().then(function (stanza) {
|
|
||||||
const configArray = [],
|
|
||||||
fields = stanza.querySelectorAll('field'),
|
|
||||||
config = that.model.get('roomconfig');
|
|
||||||
let count = fields.length;
|
|
||||||
|
|
||||||
_.each(fields, function (field) {
|
|
||||||
const fieldname = field.getAttribute('var').replace('muc#roomconfig_', ''),
|
|
||||||
type = field.getAttribute('type');
|
|
||||||
let value;
|
|
||||||
if (fieldname in config) {
|
|
||||||
switch (type) {
|
|
||||||
case 'boolean':
|
|
||||||
value = config[fieldname] ? 1 : 0;
|
|
||||||
break;
|
|
||||||
case 'list-multi':
|
|
||||||
// TODO: we don't yet handle "list-multi" types
|
|
||||||
value = field.innerHTML;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
value = config[fieldname];
|
|
||||||
}
|
|
||||||
field.innerHTML = $build('value').t(value);
|
|
||||||
}
|
|
||||||
configArray.push(field);
|
|
||||||
if (!--count) {
|
|
||||||
that.model.sendConfiguration(configArray, resolve, reject);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
closeForm () {
|
closeForm () {
|
||||||
/* Remove the configuration form without submitting and
|
/* Remove the configuration form without submitting and
|
||||||
* return to the chat view.
|
* return to the chat view.
|
||||||
@ -1117,47 +1068,6 @@
|
|||||||
this.renderAfterTransition();
|
this.renderAfterTransition();
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchRoomConfiguration (handler) {
|
|
||||||
/* Send an IQ stanza to fetch the room configuration data.
|
|
||||||
* Returns a promise which resolves once the response IQ
|
|
||||||
* has been received.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Function) handler: The handler for the response IQ
|
|
||||||
*/
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
_converse.connection.sendIQ(
|
|
||||||
$iq({
|
|
||||||
'to': this.model.get('jid'),
|
|
||||||
'type': "get"
|
|
||||||
}).c("query", {xmlns: Strophe.NS.MUC_OWNER}),
|
|
||||||
(iq) => {
|
|
||||||
if (handler) {
|
|
||||||
handler.apply(this, arguments);
|
|
||||||
}
|
|
||||||
resolve(iq);
|
|
||||||
},
|
|
||||||
reject // errback
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
getRoomFeatures () {
|
|
||||||
/* Fetch the room disco info, parse it and then
|
|
||||||
* save it on the Backbone.Model of this chat rooms.
|
|
||||||
*/
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
_converse.connection.disco.info(
|
|
||||||
this.model.get('jid'),
|
|
||||||
null,
|
|
||||||
_.flow(this.model.parseRoomFeatures.bind(this.model), resolve),
|
|
||||||
() => { reject(new Error("Could not parse the room features")) },
|
|
||||||
5000
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getAndRenderConfigurationForm (ev) {
|
getAndRenderConfigurationForm (ev) {
|
||||||
/* Start the process of configuring a chat room, either by
|
/* Start the process of configuring a chat room, either by
|
||||||
* rendering a configuration form, or by auto-configuring
|
* rendering a configuration form, or by auto-configuring
|
||||||
@ -1174,7 +1084,7 @@
|
|||||||
* the settings.
|
* the settings.
|
||||||
*/
|
*/
|
||||||
this.showSpinner();
|
this.showSpinner();
|
||||||
this.fetchRoomConfiguration()
|
this.model.fetchRoomConfiguration()
|
||||||
.then(this.renderConfigurationForm.bind(this))
|
.then(this.renderConfigurationForm.bind(this))
|
||||||
.catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
|
.catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
|
||||||
},
|
},
|
||||||
@ -1207,7 +1117,6 @@
|
|||||||
this.onNickNameFound.bind(this),
|
this.onNickNameFound.bind(this),
|
||||||
this.onNickNameNotFound.bind(this)
|
this.onNickNameNotFound.bind(this)
|
||||||
)
|
)
|
||||||
return this;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onNickNameFound (iq) {
|
onNickNameFound (iq) {
|
||||||
@ -1410,7 +1319,7 @@
|
|||||||
return notification;
|
return notification;
|
||||||
},
|
},
|
||||||
|
|
||||||
displayNotificationsforUser (notification) {
|
showNotificationsforUser (notification) {
|
||||||
/* Given the notification object generated by
|
/* Given the notification object generated by
|
||||||
* parseXUserElement, display any relevant messages and
|
* parseXUserElement, display any relevant messages and
|
||||||
* information to the user.
|
* information to the user.
|
||||||
@ -1444,13 +1353,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
displayJoinNotification (stanza) {
|
showJoinNotification (occupant) {
|
||||||
const nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
|
if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
|
||||||
const stat = stanza.querySelector('status');
|
return;
|
||||||
|
}
|
||||||
|
const nick = occupant.get('nick');
|
||||||
|
const stat = occupant.get('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 =
|
||||||
tpl_info({
|
tpl_info({
|
||||||
'data': `data-leavejoin="${nick}"`,
|
'data': `data-leavejoin="${nick}"`,
|
||||||
@ -1460,10 +1372,10 @@
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let message;
|
let message;
|
||||||
if (_.get(stat, 'textContent')) {
|
if (_.isNil(stat)) {
|
||||||
message = __('%1$s has entered the room. "%2$s"', nick, stat.textContent);
|
|
||||||
} else {
|
|
||||||
message = __('%1$s has entered the room', nick);
|
message = __('%1$s has entered the room', nick);
|
||||||
|
} else {
|
||||||
|
message = __('%1$s has entered the room. "%2$s"', nick, stat);
|
||||||
}
|
}
|
||||||
const data = {
|
const data = {
|
||||||
'data': `data-join="${nick}"`,
|
'data': `data-join="${nick}"`,
|
||||||
@ -1484,18 +1396,18 @@
|
|||||||
this.scrollDown();
|
this.scrollDown();
|
||||||
},
|
},
|
||||||
|
|
||||||
displayLeaveNotification (stanza) {
|
showLeaveNotification (occupant) {
|
||||||
const nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
|
const nick = occupant.get('nick');
|
||||||
const stat = stanza.querySelector('status');
|
const stat = occupant.get('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', {}).join === `"${nick}"`) {
|
_.get(last_el, 'dataset', {}).join === `"${nick}"`) {
|
||||||
|
|
||||||
let message;
|
let message;
|
||||||
if (_.get(stat, 'textContent')) {
|
if (_.isNil(stat)) {
|
||||||
message = __('%1$s has entered and left the room. "%2$s"', nick, stat.textContent);
|
|
||||||
} else {
|
|
||||||
message = __('%1$s has entered and left the room', nick);
|
message = __('%1$s has entered and left the room', nick);
|
||||||
|
} else {
|
||||||
|
message = __('%1$s has entered and left the room. "%2$s"', nick, stat);
|
||||||
}
|
}
|
||||||
last_el.outerHTML =
|
last_el.outerHTML =
|
||||||
tpl_info({
|
tpl_info({
|
||||||
@ -1506,10 +1418,10 @@
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let message;
|
let message;
|
||||||
if (_.get(stat, 'textContent')) {
|
if (_.isNil(stat)) {
|
||||||
message = __('%1$s has left the room. "%2$s"', nick, stat.textContent);
|
|
||||||
} else {
|
|
||||||
message = __('%1$s has left the room', nick);
|
message = __('%1$s has left the room', nick);
|
||||||
|
} else {
|
||||||
|
message = __('%1$s has left the room. "%2$s"', nick, stat);
|
||||||
}
|
}
|
||||||
const data = {
|
const data = {
|
||||||
'message': message,
|
'message': message,
|
||||||
@ -1530,20 +1442,6 @@
|
|||||||
this.scrollDown();
|
this.scrollDown();
|
||||||
},
|
},
|
||||||
|
|
||||||
displayJoinOrLeaveNotification (stanza) {
|
|
||||||
if (stanza.getAttribute('type') === 'unavailable') {
|
|
||||||
this.displayLeaveNotification(stanza);
|
|
||||||
} else {
|
|
||||||
const nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
|
|
||||||
if (!this.occupantsview.model.find({'nick': nick})) {
|
|
||||||
// Only show join message if we don't already have the
|
|
||||||
// occupant model. Doing so avoids showing duplicate
|
|
||||||
// join messages.
|
|
||||||
this.displayJoinNotification(stanza);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
showStatusMessages (stanza) {
|
showStatusMessages (stanza) {
|
||||||
/* Check for status codes and communicate their purpose to the user.
|
/* Check for status codes and communicate their purpose to the user.
|
||||||
* See: http://xmpp.org/registrar/mucstatus.html
|
* See: http://xmpp.org/registrar/mucstatus.html
|
||||||
@ -1556,16 +1454,7 @@
|
|||||||
const is_self = stanza.querySelectorAll("status[code='110']").length;
|
const is_self = stanza.querySelectorAll("status[code='110']").length;
|
||||||
const iteratee = _.partial(this.parseXUserElement.bind(this), _, stanza, is_self);
|
const iteratee = _.partial(this.parseXUserElement.bind(this), _, stanza, is_self);
|
||||||
const notifications = _.reject(_.map(elements, iteratee), _.isEmpty);
|
const notifications = _.reject(_.map(elements, iteratee), _.isEmpty);
|
||||||
if (_.isEmpty(notifications)) {
|
_.each(notifications, this.showNotificationsforUser.bind(this));
|
||||||
if (_converse.muc_show_join_leave &&
|
|
||||||
stanza.nodeName === 'presence' &&
|
|
||||||
this.model.get('connection_status') === converse.ROOMSTATUS.ENTERED) {
|
|
||||||
this.displayJoinOrLeaveNotification(stanza);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_.each(notifications, this.displayNotificationsforUser.bind(this));
|
|
||||||
}
|
|
||||||
return stanza;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
showErrorMessageFromPresence (presence) {
|
showErrorMessageFromPresence (presence) {
|
||||||
@ -1637,87 +1526,18 @@
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
onOwnChatRoomPresence (pres) {
|
setChatRoomSubject () {
|
||||||
/* Handles a received presence relating to the current
|
|
||||||
* user.
|
|
||||||
*
|
|
||||||
* For locked rooms (which are by definition "new"), the
|
|
||||||
* room will either be auto-configured or created instantly
|
|
||||||
* (with default config) or a configuration room will be
|
|
||||||
* rendered.
|
|
||||||
*
|
|
||||||
* If the room is not locked, then the room will be
|
|
||||||
* auto-configured only if applicable and if the current
|
|
||||||
* user is the room's owner.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) pres: The stanza
|
|
||||||
*/
|
|
||||||
this.model.saveAffiliationAndRole(pres);
|
|
||||||
|
|
||||||
const locked_room = pres.querySelector("status[code='201']");
|
|
||||||
if (locked_room) {
|
|
||||||
if (this.model.get('auto_configure')) {
|
|
||||||
this.autoConfigureChatRoom().then(this.getRoomFeatures.bind(this));
|
|
||||||
} else if (_converse.muc_instant_rooms) {
|
|
||||||
// Accept default configuration
|
|
||||||
this.saveConfiguration().then(this.getRoomFeatures.bind(this));
|
|
||||||
} else {
|
|
||||||
this.getAndRenderConfigurationForm();
|
|
||||||
return; // We haven't yet entered the room, so bail here.
|
|
||||||
}
|
|
||||||
} else if (!this.model.get('features_fetched')) {
|
|
||||||
// The features for this room weren't fetched.
|
|
||||||
// That must mean it's a new room without locking
|
|
||||||
// (in which case Prosody doesn't send a 201 status),
|
|
||||||
// otherwise the features would have been fetched in
|
|
||||||
// the "initialize" method already.
|
|
||||||
if (this.model.get('affiliation') === 'owner' && this.model.get('auto_configure')) {
|
|
||||||
this.autoConfigureChatRoom().then(this.getRoomFeatures.bind(this));
|
|
||||||
} else {
|
|
||||||
this.getRoomFeatures();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.model.save('connection_status', converse.ROOMSTATUS.ENTERED);
|
|
||||||
},
|
|
||||||
|
|
||||||
onChatRoomPresence (pres) {
|
|
||||||
/* Handles all MUC presence stanzas.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) pres: The stanza
|
|
||||||
*/
|
|
||||||
if (pres.getAttribute('type') === 'error') {
|
|
||||||
this.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
|
|
||||||
this.showErrorMessageFromPresence(pres);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const is_self = pres.querySelector("status[code='110']");
|
|
||||||
if (is_self && pres.getAttribute('type') !== 'unavailable') {
|
|
||||||
this.onOwnChatRoomPresence(pres);
|
|
||||||
}
|
|
||||||
this.hideSpinner().showStatusMessages(pres);
|
|
||||||
// This must be called after showStatusMessages so that
|
|
||||||
// "join" messages are correctly shown.
|
|
||||||
this.occupantsview.updateOccupantsOnPresence(pres);
|
|
||||||
if (this.model.get('role') !== 'none' &&
|
|
||||||
this.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING) {
|
|
||||||
this.model.save('connection_status', converse.ROOMSTATUS.CONNECTED);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
setChatRoomSubject (sender, subject) {
|
|
||||||
// For translators: the %1$s and %2$s parts will get
|
// For translators: the %1$s and %2$s parts will get
|
||||||
// replaced by the user and topic text respectively
|
// replaced by the user and topic text respectively
|
||||||
// Example: Topic set by JC Brand to: Hello World!
|
// Example: Topic set by JC Brand to: Hello World!
|
||||||
|
const subject = this.model.get('subject');
|
||||||
this.content.insertAdjacentHTML(
|
this.content.insertAdjacentHTML(
|
||||||
'beforeend',
|
'beforeend',
|
||||||
tpl_info({
|
tpl_info({
|
||||||
'data': '',
|
'data': '',
|
||||||
'isodate': moment().format(),
|
'isodate': moment().format(),
|
||||||
'extra_classes': 'chat-event',
|
'extra_classes': 'chat-event',
|
||||||
'message': __('Topic set by %1$s', sender)
|
'message': __('Topic set by %1$s', subject.author)
|
||||||
}));
|
}));
|
||||||
this.content.insertAdjacentHTML(
|
this.content.insertAdjacentHTML(
|
||||||
'beforeend',
|
'beforeend',
|
||||||
@ -1725,91 +1545,9 @@
|
|||||||
'data': '',
|
'data': '',
|
||||||
'isodate': moment().format(),
|
'isodate': moment().format(),
|
||||||
'extra_classes': 'chat-topic',
|
'extra_classes': 'chat-topic',
|
||||||
'message': subject
|
'message': subject.text
|
||||||
}));
|
}));
|
||||||
this.scrollDown();
|
this.scrollDown();
|
||||||
},
|
|
||||||
|
|
||||||
isDuplicateBasedOnTime (message) {
|
|
||||||
/* Checks whether a received messages is actually a
|
|
||||||
* duplicate based on whether it has a "ts" attribute
|
|
||||||
* with a unix timestamp.
|
|
||||||
*
|
|
||||||
* This is used for better integration with Slack's XMPP
|
|
||||||
* gateway, which doesn't use message IDs but instead the
|
|
||||||
* aforementioned "ts" attributes.
|
|
||||||
*/
|
|
||||||
const entity = _converse.disco_entities.get(_converse.domain);
|
|
||||||
if (entity.identities.where({'name': "Slack-XMPP"})) {
|
|
||||||
const ts = message.getAttribute('ts');
|
|
||||||
if (_.isNull(ts)) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return this.model.messages.where({
|
|
||||||
'sender': 'me',
|
|
||||||
'message': this.model.getMessageBody(message)
|
|
||||||
}).filter(
|
|
||||||
(msg) => Math.abs(moment(msg.get('time')).diff(moment.unix(ts))) < 5000
|
|
||||||
).length > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
isDuplicate (message, original_stanza) {
|
|
||||||
const msgid = message.getAttribute('id'),
|
|
||||||
jid = message.getAttribute('from'),
|
|
||||||
resource = Strophe.getResourceFromJid(jid),
|
|
||||||
sender = resource && Strophe.unescapeNode(resource) || '';
|
|
||||||
if (msgid) {
|
|
||||||
return this.model.messages.filter(
|
|
||||||
// Some bots (like HAL in the prosody chatroom)
|
|
||||||
// respond to commands with the same ID as the
|
|
||||||
// original message. So we also check the sender.
|
|
||||||
(msg) => msg.get('msgid') === msgid && msg.get('fullname') === sender
|
|
||||||
).length > 0;
|
|
||||||
}
|
|
||||||
return this.isDuplicateBasedOnTime(message);
|
|
||||||
},
|
|
||||||
|
|
||||||
onChatRoomMessage (message) {
|
|
||||||
/* Given a <message> stanza, create a message
|
|
||||||
* Backbone.Model if appropriate.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) msg: The received message stanza
|
|
||||||
*/
|
|
||||||
const original_stanza = message,
|
|
||||||
forwarded = message.querySelector('forwarded');
|
|
||||||
let delay;
|
|
||||||
if (!_.isNull(forwarded)) {
|
|
||||||
message = forwarded.querySelector('message');
|
|
||||||
delay = forwarded.querySelector('delay');
|
|
||||||
}
|
|
||||||
const jid = message.getAttribute('from'),
|
|
||||||
resource = Strophe.getResourceFromJid(jid),
|
|
||||||
sender = resource && Strophe.unescapeNode(resource) || '',
|
|
||||||
subject = _.propertyOf(message.querySelector('subject'))('textContent');
|
|
||||||
|
|
||||||
if (this.isDuplicate(message, original_stanza)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (subject) {
|
|
||||||
this.setChatRoomSubject(sender, subject);
|
|
||||||
}
|
|
||||||
if (sender === '') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
this.model.incrementUnreadMsgCounter(original_stanza);
|
|
||||||
this.model.createMessage(message, delay, original_stanza);
|
|
||||||
if (sender !== this.model.get('nick')) {
|
|
||||||
// We only emit an event if it's not our own message
|
|
||||||
_converse.emit(
|
|
||||||
'message',
|
|
||||||
{'stanza': original_stanza, 'chatbox': this.model}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2030,86 +1768,6 @@
|
|||||||
`height: calc(100% - ${el.offsetHeight}px - 5em);`;
|
`height: calc(100% - ${el.offsetHeight}px - 5em);`;
|
||||||
},
|
},
|
||||||
|
|
||||||
parsePresence (pres) {
|
|
||||||
const id = Strophe.getResourceFromJid(pres.getAttribute("from"));
|
|
||||||
const data = {
|
|
||||||
nick: id,
|
|
||||||
type: pres.getAttribute("type"),
|
|
||||||
states: []
|
|
||||||
};
|
|
||||||
_.each(pres.childNodes, function (child) {
|
|
||||||
switch (child.nodeName) {
|
|
||||||
case "status":
|
|
||||||
data.status = child.textContent || null;
|
|
||||||
break;
|
|
||||||
case "show":
|
|
||||||
data.show = child.textContent || 'online';
|
|
||||||
break;
|
|
||||||
case "x":
|
|
||||||
if (child.getAttribute("xmlns") === Strophe.NS.MUC_USER) {
|
|
||||||
_.each(child.childNodes, function (item) {
|
|
||||||
switch (item.nodeName) {
|
|
||||||
case "item":
|
|
||||||
data.affiliation = item.getAttribute("affiliation");
|
|
||||||
data.role = item.getAttribute("role");
|
|
||||||
data.jid = item.getAttribute("jid");
|
|
||||||
data.nick = item.getAttribute("nick") || data.nick;
|
|
||||||
break;
|
|
||||||
case "status":
|
|
||||||
if (item.getAttribute("code")) {
|
|
||||||
data.states.push(item.getAttribute("code"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
findOccupant (data) {
|
|
||||||
/* Try to find an existing occupant based on the passed in
|
|
||||||
* data object.
|
|
||||||
*
|
|
||||||
* If we have a JID, we use that as lookup variable,
|
|
||||||
* otherwise we use the nick. We don't always have both,
|
|
||||||
* but should have at least one or the other.
|
|
||||||
*/
|
|
||||||
const jid = Strophe.getBareJidFromJid(data.jid);
|
|
||||||
if (jid !== null) {
|
|
||||||
return this.model.where({'jid': jid}).pop();
|
|
||||||
} else {
|
|
||||||
return this.model.where({'nick': data.nick}).pop();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateOccupantsOnPresence (pres) {
|
|
||||||
/* Given a presence stanza, update the occupant models
|
|
||||||
* based on its contents.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) pres: The presence stanza
|
|
||||||
*/
|
|
||||||
const data = this.parsePresence(pres);
|
|
||||||
if (data.type === 'error') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const occupant = this.findOccupant(data);
|
|
||||||
if (data.type === 'unavailable') {
|
|
||||||
if (occupant) { occupant.destroy(); }
|
|
||||||
} else {
|
|
||||||
const jid = Strophe.getBareJidFromJid(data.jid);
|
|
||||||
const attributes = _.extend(data, {
|
|
||||||
'jid': jid ? jid : undefined,
|
|
||||||
'resource': data.jid ? Strophe.getResourceFromJid(data.jid) : undefined
|
|
||||||
});
|
|
||||||
if (occupant) {
|
|
||||||
occupant.save(attributes);
|
|
||||||
} else {
|
|
||||||
this.model.create(attributes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
promptForInvite (suggestion) {
|
promptForInvite (suggestion) {
|
||||||
const reason = prompt(
|
const reason = prompt(
|
||||||
|
@ -39,6 +39,8 @@
|
|||||||
Strophe.addNamespace('MUC_ROOMCONF', Strophe.NS.MUC + "#roomconfig");
|
Strophe.addNamespace('MUC_ROOMCONF', Strophe.NS.MUC + "#roomconfig");
|
||||||
Strophe.addNamespace('MUC_USER', Strophe.NS.MUC + "#user");
|
Strophe.addNamespace('MUC_USER', Strophe.NS.MUC + "#user");
|
||||||
|
|
||||||
|
converse.MUC_NICK_CHANGED_CODE = "303";
|
||||||
|
|
||||||
converse.CHATROOMS_TYPE = 'chatroom';
|
converse.CHATROOMS_TYPE = 'chatroom';
|
||||||
|
|
||||||
converse.ROOM_FEATURES = [
|
converse.ROOM_FEATURES = [
|
||||||
@ -107,90 +109,6 @@
|
|||||||
const { _converse } = this,
|
const { _converse } = this,
|
||||||
{ __ } = _converse;
|
{ __ } = _converse;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: Inside plugins, all calls to the translation machinery
|
|
||||||
// (e.g. u.__) should only be done in the initialize function.
|
|
||||||
// If called before, we won't know what language the user wants,
|
|
||||||
// and it'll fall back to English.
|
|
||||||
|
|
||||||
/* http://xmpp.org/extensions/xep-0045.html
|
|
||||||
* ----------------------------------------
|
|
||||||
* 100 message Entering a room 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 room
|
|
||||||
* 102 message Configuration change Inform occupants that room now shows unavailable members
|
|
||||||
* 103 message Configuration change Inform occupants that room now does not show unavailable members
|
|
||||||
* 104 message Configuration change Inform occupants that a non-privacy-related room configuration change has occurred
|
|
||||||
* 110 presence Any room presence Inform user that presence refers to one of its own room occupants
|
|
||||||
* 170 message or initial presence Configuration change Inform occupants that room logging is now enabled
|
|
||||||
* 171 message Configuration change Inform occupants that room logging is now disabled
|
|
||||||
* 172 message Configuration change Inform occupants that the room is now non-anonymous
|
|
||||||
* 173 message Configuration change Inform occupants that the room is now semi-anonymous
|
|
||||||
* 174 message Configuration change Inform occupants that the room is now fully-anonymous
|
|
||||||
* 201 presence Entering a room Inform user that a new room has been created
|
|
||||||
* 210 presence Entering a room Inform user that the service has assigned or modified the occupant's roomnick
|
|
||||||
* 301 presence Removal from room Inform user that he or she has been banned from the room
|
|
||||||
* 303 presence Exiting a room Inform all occupants of new room nickname
|
|
||||||
* 307 presence Removal from room Inform user that he or she has been kicked from the room
|
|
||||||
* 321 presence Removal from room Inform user that he or she is being removed from the room because of an affiliation change
|
|
||||||
* 322 presence Removal from room Inform user that he or she is being removed from the room because the room has been changed to members-only and the user is not a member
|
|
||||||
* 332 presence Removal from room Inform user that he or she is being removed from the room because of a system shutdown
|
|
||||||
*/
|
|
||||||
_converse.muc = {
|
|
||||||
info_messages: {
|
|
||||||
100: __('This room is not anonymous'),
|
|
||||||
102: __('This room now shows unavailable members'),
|
|
||||||
103: __('This room does not show unavailable members'),
|
|
||||||
104: __('The room configuration has changed'),
|
|
||||||
170: __('Room logging is now enabled'),
|
|
||||||
171: __('Room logging is now disabled'),
|
|
||||||
172: __('This room is now no longer anonymous'),
|
|
||||||
173: __('This room is now semi-anonymous'),
|
|
||||||
174: __('This room is now fully-anonymous'),
|
|
||||||
201: __('A new room has been created')
|
|
||||||
},
|
|
||||||
|
|
||||||
disconnect_messages: {
|
|
||||||
301: __('You have been banned from this room'),
|
|
||||||
307: __('You have been kicked from this room'),
|
|
||||||
321: __("You have been removed from this room because of an affiliation change"),
|
|
||||||
322: __("You have been removed from this room because the room has changed to members-only and you're not a member"),
|
|
||||||
332: __("You have been removed from this room because the MUC (Multi-user chat) service 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')
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Configuration values for this plugin
|
// Configuration values for this plugin
|
||||||
// ====================================
|
// ====================================
|
||||||
// Refer to docs/source/configuration.rst for explanations of these
|
// Refer to docs/source/configuration.rst for explanations of these
|
||||||
@ -200,17 +118,10 @@
|
|||||||
allow_muc_invitations: true,
|
allow_muc_invitations: true,
|
||||||
auto_join_on_invite: false,
|
auto_join_on_invite: false,
|
||||||
auto_join_rooms: [],
|
auto_join_rooms: [],
|
||||||
auto_list_rooms: false,
|
|
||||||
hide_muc_server: false,
|
|
||||||
muc_disable_moderator_commands: false,
|
|
||||||
muc_domain: undefined,
|
muc_domain: undefined,
|
||||||
muc_history_max_stanzas: undefined,
|
muc_history_max_stanzas: undefined,
|
||||||
muc_instant_rooms: true,
|
muc_instant_rooms: true,
|
||||||
muc_nickname_from_jid: false,
|
muc_nickname_from_jid: false
|
||||||
muc_show_join_leave: true,
|
|
||||||
visible_toolbar_buttons: {
|
|
||||||
'toggle_occupants': true
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
_converse.api.promises.add(['roomsAutoJoined']);
|
_converse.api.promises.add(['roomsAutoJoined']);
|
||||||
|
|
||||||
@ -275,6 +186,156 @@
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
this.constructor.__super__.initialize.apply(this, arguments);
|
||||||
|
this.occupants = new _converse.ChatRoomOccupants();
|
||||||
|
this.registerHandlers();
|
||||||
|
},
|
||||||
|
|
||||||
|
registerHandlers () {
|
||||||
|
/* Register presence and message handlers for this chat
|
||||||
|
* room
|
||||||
|
*/
|
||||||
|
const room_jid = this.get('jid');
|
||||||
|
this.removeHandlers();
|
||||||
|
this.presence_handler = _converse.connection.addHandler((stanza) => {
|
||||||
|
_.each(_.values(this.handlers.presence), (callback) => callback(stanza));
|
||||||
|
this.onPresence(stanza);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
Strophe.NS.MUC, 'presence', null, null, room_jid,
|
||||||
|
{'ignoreNamespaceFragment': true, 'matchBareFromJid': true}
|
||||||
|
);
|
||||||
|
this.message_handler = _converse.connection.addHandler((stanza) => {
|
||||||
|
_.each(_.values(this.handlers.message), (callback) => callback(stanza));
|
||||||
|
this.onMessage(stanza);
|
||||||
|
return true;
|
||||||
|
}, null, 'message', 'groupchat', null, room_jid,
|
||||||
|
{'matchBareFromJid': true}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeHandlers () {
|
||||||
|
/* Remove the presence and message handlers that were
|
||||||
|
* registered for this chat room.
|
||||||
|
*/
|
||||||
|
if (this.message_handler) {
|
||||||
|
_converse.connection.deleteHandler(this.message_handler);
|
||||||
|
delete this.message_handler;
|
||||||
|
}
|
||||||
|
if (this.presence_handler) {
|
||||||
|
_converse.connection.deleteHandler(this.presence_handler);
|
||||||
|
delete this.presence_handler;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
|
||||||
|
join (nick, password) {
|
||||||
|
/* Join the chat room.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* (String) nick: The user's nickname
|
||||||
|
* (String) password: Optional password, if required by
|
||||||
|
* the room.
|
||||||
|
*/
|
||||||
|
nick = nick ? nick : this.get('nick');
|
||||||
|
if (!nick) {
|
||||||
|
throw new TypeError('join: You need to provide a valid nickname');
|
||||||
|
}
|
||||||
|
if (this.get('connection_status') === converse.ROOMSTATUS.ENTERED) {
|
||||||
|
// We have restored a chat room from session storage,
|
||||||
|
// so we don't send out a presence stanza again.
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
const stanza = $pres({
|
||||||
|
'from': _converse.connection.jid,
|
||||||
|
'to': this.getRoomJIDAndNick(nick)
|
||||||
|
}).c("x", {'xmlns': Strophe.NS.MUC})
|
||||||
|
.c("history", {'maxstanzas': _converse.muc_history_max_stanzas}).up();
|
||||||
|
if (password) {
|
||||||
|
stanza.cnode(Strophe.xmlElement("password", [], password));
|
||||||
|
}
|
||||||
|
this.save('connection_status', converse.ROOMSTATUS.CONNECTING);
|
||||||
|
_converse.connection.send(stanza);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
leave (exit_msg) {
|
||||||
|
/* Leave the chat room.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* (String) exit_msg: Optional message to indicate your
|
||||||
|
* reason for leaving.
|
||||||
|
*/
|
||||||
|
this.occupants.reset();
|
||||||
|
this.occupants.browserStorage._clear();
|
||||||
|
if (_converse.connection.connected) {
|
||||||
|
this.sendUnavailablePresence(exit_msg);
|
||||||
|
}
|
||||||
|
u.safeSave(this, {'connection_status': converse.ROOMSTATUS.DISCONNECTED});
|
||||||
|
this.removeHandlers();
|
||||||
|
},
|
||||||
|
|
||||||
|
sendUnavailablePresence (exit_msg) {
|
||||||
|
const presence = $pres({
|
||||||
|
type: "unavailable",
|
||||||
|
from: _converse.connection.jid,
|
||||||
|
to: this.getRoomJIDAndNick()
|
||||||
|
});
|
||||||
|
if (exit_msg !== null) {
|
||||||
|
presence.c("status", exit_msg);
|
||||||
|
}
|
||||||
|
_converse.connection.sendPresence(presence);
|
||||||
|
},
|
||||||
|
|
||||||
|
getRoomFeatures () {
|
||||||
|
/* Fetch the room disco info, parse it and then save it.
|
||||||
|
*/
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
_converse.connection.disco.info(
|
||||||
|
this.get('jid'),
|
||||||
|
null,
|
||||||
|
_.flow(this.parseRoomFeatures.bind(this), resolve),
|
||||||
|
() => { reject(new Error("Could not parse the room features")) },
|
||||||
|
5000
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getRoomJIDAndNick (nick) {
|
||||||
|
/* Utility method to construct the JID for the current user
|
||||||
|
* as occupant of the room.
|
||||||
|
*
|
||||||
|
* This is the room JID, with the user's nick added at the
|
||||||
|
* end.
|
||||||
|
*
|
||||||
|
* For example: room@conference.example.org/nickname
|
||||||
|
*/
|
||||||
|
if (nick) {
|
||||||
|
this.save({'nick': nick});
|
||||||
|
} else {
|
||||||
|
nick = this.get('nick');
|
||||||
|
}
|
||||||
|
const room = this.get('jid');
|
||||||
|
const jid = Strophe.getBareJidFromJid(room);
|
||||||
|
return jid + (nick !== null ? `/${nick}` : "");
|
||||||
|
},
|
||||||
|
|
||||||
directInvite (recipient, reason) {
|
directInvite (recipient, reason) {
|
||||||
/* Send a direct invitation as per XEP-0249
|
/* Send a direct invitation as per XEP-0249
|
||||||
*
|
*
|
||||||
@ -314,30 +375,6 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
sendConfiguration (config, callback, errback) {
|
|
||||||
/* Send an IQ stanza with the room configuration.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (Array) config: The room configuration
|
|
||||||
* (Function) callback: Callback upon succesful IQ response
|
|
||||||
* The first parameter passed in is IQ containing the
|
|
||||||
* room configuration.
|
|
||||||
* The second is the response IQ from the server.
|
|
||||||
* (Function) errback: Callback upon error IQ response
|
|
||||||
* The first parameter passed in is IQ containing the
|
|
||||||
* room configuration.
|
|
||||||
* The second is the response IQ from the server.
|
|
||||||
*/
|
|
||||||
const iq = $iq({to: this.get('jid'), type: "set"})
|
|
||||||
.c("query", {xmlns: Strophe.NS.MUC_OWNER})
|
|
||||||
.c("x", {xmlns: Strophe.NS.XFORM, type: "submit"});
|
|
||||||
_.each(config || [], function (node) { iq.cnode(node).up(); });
|
|
||||||
callback = _.isUndefined(callback) ? _.noop : _.partial(callback, iq.nodeTree);
|
|
||||||
errback = _.isUndefined(errback) ? _.noop : _.partial(errback, iq.nodeTree);
|
|
||||||
return _converse.connection.sendIQ(iq, callback, errback);
|
|
||||||
},
|
|
||||||
|
|
||||||
parseRoomFeatures (iq) {
|
parseRoomFeatures (iq) {
|
||||||
/* Parses an IQ stanza containing the room's features.
|
/* Parses an IQ stanza containing the room's features.
|
||||||
*
|
*
|
||||||
@ -432,6 +469,106 @@
|
|||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
saveConfiguration (form) {
|
||||||
|
/* Submit the room configuration form by sending an IQ
|
||||||
|
* stanza to the server.
|
||||||
|
*
|
||||||
|
* Returns a promise which resolves once the XMPP server
|
||||||
|
* has return a response IQ.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* (HTMLElement) form: The configuration form DOM element.
|
||||||
|
* If no form is provided, the default configuration
|
||||||
|
* values will be used.
|
||||||
|
*/
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const inputs = form ? sizzle(':input:not([type=button]):not([type=submit])', form) : [],
|
||||||
|
configArray = _.map(inputs, u.webForm2xForm);
|
||||||
|
this.sendConfiguration(configArray, resolve, reject);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
autoConfigureChatRoom () {
|
||||||
|
/* Automatically configure room based on this model's
|
||||||
|
* 'roomconfig' data.
|
||||||
|
*
|
||||||
|
* Returns a promise which resolves once a response IQ has
|
||||||
|
* been received.
|
||||||
|
*/
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.fetchRoomConfiguration().then((stanza) => {
|
||||||
|
const configArray = [],
|
||||||
|
fields = stanza.querySelectorAll('field'),
|
||||||
|
config = this.get('roomconfig');
|
||||||
|
let count = fields.length;
|
||||||
|
|
||||||
|
_.each(fields, (field) => {
|
||||||
|
const fieldname = field.getAttribute('var').replace('muc#roomconfig_', ''),
|
||||||
|
type = field.getAttribute('type');
|
||||||
|
let value;
|
||||||
|
if (fieldname in config) {
|
||||||
|
switch (type) {
|
||||||
|
case 'boolean':
|
||||||
|
value = config[fieldname] ? 1 : 0;
|
||||||
|
break;
|
||||||
|
case 'list-multi':
|
||||||
|
// TODO: we don't yet handle "list-multi" types
|
||||||
|
value = field.innerHTML;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
value = config[fieldname];
|
||||||
|
}
|
||||||
|
field.innerHTML = $build('value').t(value);
|
||||||
|
}
|
||||||
|
configArray.push(field);
|
||||||
|
if (!--count) {
|
||||||
|
this.sendConfiguration(configArray, resolve, reject);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchRoomConfiguration () {
|
||||||
|
/* Send an IQ stanza to fetch the room configuration data.
|
||||||
|
* Returns a promise which resolves once the response IQ
|
||||||
|
* has been received.
|
||||||
|
*/
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
_converse.connection.sendIQ(
|
||||||
|
$iq({
|
||||||
|
'to': this.get('jid'),
|
||||||
|
'type': "get"
|
||||||
|
}).c("query", {xmlns: Strophe.NS.MUC_OWNER}),
|
||||||
|
resolve,
|
||||||
|
reject
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
sendConfiguration (config, callback, errback) {
|
||||||
|
/* Send an IQ stanza with the room configuration.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* (Array) config: The room configuration
|
||||||
|
* (Function) callback: Callback upon succesful IQ response
|
||||||
|
* The first parameter passed in is IQ containing the
|
||||||
|
* room configuration.
|
||||||
|
* The second is the response IQ from the server.
|
||||||
|
* (Function) errback: Callback upon error IQ response
|
||||||
|
* The first parameter passed in is IQ containing the
|
||||||
|
* room configuration.
|
||||||
|
* The second is the response IQ from the server.
|
||||||
|
*/
|
||||||
|
const iq = $iq({to: this.get('jid'), type: "set"})
|
||||||
|
.c("query", {xmlns: Strophe.NS.MUC_OWNER})
|
||||||
|
.c("x", {xmlns: Strophe.NS.XFORM, type: "submit"});
|
||||||
|
_.each(config || [], function (node) { iq.cnode(node).up(); });
|
||||||
|
callback = _.isUndefined(callback) ? _.noop : _.partial(callback, iq.nodeTree);
|
||||||
|
errback = _.isUndefined(errback) ? _.noop : _.partial(errback, iq.nodeTree);
|
||||||
|
return _converse.connection.sendIQ(iq, callback, errback);
|
||||||
|
},
|
||||||
|
|
||||||
saveAffiliationAndRole (pres) {
|
saveAffiliationAndRole (pres) {
|
||||||
/* Parse the presence stanza for the current user's
|
/* Parse the presence stanza for the current user's
|
||||||
* affiliation.
|
* affiliation.
|
||||||
@ -555,6 +692,256 @@
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
findOccupant (data) {
|
||||||
|
/* Try to find an existing occupant based on the passed in
|
||||||
|
* data object.
|
||||||
|
*
|
||||||
|
* If we have a JID, we use that as lookup variable,
|
||||||
|
* otherwise we use the nick. We don't always have both,
|
||||||
|
* but should have at least one or the other.
|
||||||
|
*/
|
||||||
|
const jid = Strophe.getBareJidFromJid(data.jid);
|
||||||
|
if (jid !== null) {
|
||||||
|
return this.occupants.where({'jid': jid}).pop();
|
||||||
|
} else {
|
||||||
|
return this.occupants.where({'nick': data.nick}).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateOccupantsOnPresence (pres) {
|
||||||
|
/* Given a presence stanza, update the occupant model
|
||||||
|
* based on its contents.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* (XMLElement) pres: The presence stanza
|
||||||
|
*/
|
||||||
|
const data = this.parsePresence(pres);
|
||||||
|
if (data.type === 'error') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const occupant = this.findOccupant(data);
|
||||||
|
if (data.type === 'unavailable') {
|
||||||
|
if (occupant) {
|
||||||
|
// Even before destroying, we set the new data, so
|
||||||
|
// that we can for example show the
|
||||||
|
// disconnection message.
|
||||||
|
occupant.set(data);
|
||||||
|
}
|
||||||
|
if (!_.includes(data.states, converse.MUC_NICK_CHANGED_CODE)) {
|
||||||
|
// We only destroy the occupant if this is not a
|
||||||
|
// nickname change operation.
|
||||||
|
if (occupant) {
|
||||||
|
occupant.destroy();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const jid = Strophe.getBareJidFromJid(data.jid);
|
||||||
|
const attributes = _.extend(data, {
|
||||||
|
'jid': jid ? jid : undefined,
|
||||||
|
'resource': data.jid ? Strophe.getResourceFromJid(data.jid) : undefined
|
||||||
|
});
|
||||||
|
if (occupant) {
|
||||||
|
occupant.save(attributes);
|
||||||
|
} else {
|
||||||
|
this.occupants.create(attributes);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
parsePresence (pres) {
|
||||||
|
const id = Strophe.getResourceFromJid(pres.getAttribute("from"));
|
||||||
|
const data = {
|
||||||
|
nick: id,
|
||||||
|
type: pres.getAttribute("type"),
|
||||||
|
states: []
|
||||||
|
};
|
||||||
|
_.each(pres.childNodes, function (child) {
|
||||||
|
switch (child.nodeName) {
|
||||||
|
case "status":
|
||||||
|
data.status = child.textContent || null;
|
||||||
|
break;
|
||||||
|
case "show":
|
||||||
|
data.show = child.textContent || 'online';
|
||||||
|
break;
|
||||||
|
case "x":
|
||||||
|
if (child.getAttribute("xmlns") === Strophe.NS.MUC_USER) {
|
||||||
|
_.each(child.childNodes, function (item) {
|
||||||
|
switch (item.nodeName) {
|
||||||
|
case "item":
|
||||||
|
data.affiliation = item.getAttribute("affiliation");
|
||||||
|
data.role = item.getAttribute("role");
|
||||||
|
data.jid = item.getAttribute("jid");
|
||||||
|
data.nick = item.getAttribute("nick") || data.nick;
|
||||||
|
break;
|
||||||
|
case "status":
|
||||||
|
if (item.getAttribute("code")) {
|
||||||
|
data.states.push(item.getAttribute("code"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
isDuplicateBasedOnTime (message) {
|
||||||
|
/* Checks whether a received messages is actually a
|
||||||
|
* duplicate based on whether it has a "ts" attribute
|
||||||
|
* with a unix timestamp.
|
||||||
|
*
|
||||||
|
* This is used for better integration with Slack's XMPP
|
||||||
|
* gateway, which doesn't use message IDs but instead the
|
||||||
|
* aforementioned "ts" attributes.
|
||||||
|
*/
|
||||||
|
const entity = _converse.disco_entities.get(_converse.domain);
|
||||||
|
if (entity.identities.where({'name': "Slack-XMPP"})) {
|
||||||
|
const ts = message.getAttribute('ts');
|
||||||
|
if (_.isNull(ts)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return this.messages.where({
|
||||||
|
'sender': 'me',
|
||||||
|
'message': this.getMessageBody(message)
|
||||||
|
}).filter(
|
||||||
|
(msg) => Math.abs(moment(msg.get('time')).diff(moment.unix(ts))) < 5000
|
||||||
|
).length > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
isDuplicate (message, original_stanza) {
|
||||||
|
const msgid = message.getAttribute('id'),
|
||||||
|
jid = message.getAttribute('from'),
|
||||||
|
resource = Strophe.getResourceFromJid(jid),
|
||||||
|
sender = resource && Strophe.unescapeNode(resource) || '';
|
||||||
|
if (msgid) {
|
||||||
|
return this.messages.filter(
|
||||||
|
// Some bots (like HAL in the prosody chatroom)
|
||||||
|
// respond to commands with the same ID as the
|
||||||
|
// original message. So we also check the sender.
|
||||||
|
(msg) => msg.get('msgid') === msgid && msg.get('fullname') === sender
|
||||||
|
).length > 0;
|
||||||
|
}
|
||||||
|
return this.isDuplicateBasedOnTime(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchFeaturesIfConfigurationChanged (stanza) {
|
||||||
|
const configuration_changed = stanza.querySelector("status[code='104']"),
|
||||||
|
logging_enabled = stanza.querySelector("status[code='170']"),
|
||||||
|
logging_disabled = stanza.querySelector("status[code='171']"),
|
||||||
|
room_no_longer_anon = stanza.querySelector("status[code='172']"),
|
||||||
|
room_now_semi_anon = stanza.querySelector("status[code='173']"),
|
||||||
|
room_now_fully_anon = stanza.querySelector("status[code='173']");
|
||||||
|
|
||||||
|
if (configuration_changed || logging_enabled || logging_disabled ||
|
||||||
|
room_no_longer_anon || room_now_semi_anon || room_now_fully_anon) {
|
||||||
|
this.getRoomFeatures();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onMessage (stanza) {
|
||||||
|
/* Handler for all MUC messages sent to this chat room.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* (XMLElement) stanza: The message stanza.
|
||||||
|
*/
|
||||||
|
this.fetchFeaturesIfConfigurationChanged(stanza);
|
||||||
|
|
||||||
|
const original_stanza = stanza,
|
||||||
|
forwarded = stanza.querySelector('forwarded');
|
||||||
|
let delay;
|
||||||
|
if (!_.isNull(forwarded)) {
|
||||||
|
stanza = forwarded.querySelector('message');
|
||||||
|
delay = forwarded.querySelector('delay');
|
||||||
|
}
|
||||||
|
const jid = stanza.getAttribute('from'),
|
||||||
|
resource = Strophe.getResourceFromJid(jid),
|
||||||
|
sender = resource && Strophe.unescapeNode(resource) || '',
|
||||||
|
subject = _.propertyOf(stanza.querySelector('subject'))('textContent');
|
||||||
|
|
||||||
|
if (this.isDuplicate(stanza, original_stanza)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (subject) {
|
||||||
|
u.safeSave(this, {'subject': {'author': sender, 'text': subject}});
|
||||||
|
}
|
||||||
|
if (sender === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.incrementUnreadMsgCounter(original_stanza);
|
||||||
|
this.createMessage(stanza, delay, original_stanza);
|
||||||
|
if (sender !== this.get('nick')) {
|
||||||
|
// We only emit an event if it's not our own message
|
||||||
|
_converse.emit('message', {'stanza': original_stanza, 'chatbox': this});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onPresence (pres) {
|
||||||
|
/* Handles all MUC presence stanzas.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* (XMLElement) pres: The stanza
|
||||||
|
*/
|
||||||
|
if (pres.getAttribute('type') === 'error') {
|
||||||
|
this.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const is_self = pres.querySelector("status[code='110']");
|
||||||
|
if (is_self && pres.getAttribute('type') !== 'unavailable') {
|
||||||
|
this.onOwnPresence(pres);
|
||||||
|
}
|
||||||
|
this.updateOccupantsOnPresence(pres);
|
||||||
|
if (this.get('role') !== 'none' && this.get('connection_status') === converse.ROOMSTATUS.CONNECTING) {
|
||||||
|
this.save('connection_status', converse.ROOMSTATUS.CONNECTED);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onOwnPresence (pres) {
|
||||||
|
/* Handles a received presence relating to the current
|
||||||
|
* user.
|
||||||
|
*
|
||||||
|
* For locked rooms (which are by definition "new"), the
|
||||||
|
* room will either be auto-configured or created instantly
|
||||||
|
* (with default config) or a configuration room will be
|
||||||
|
* rendered.
|
||||||
|
*
|
||||||
|
* If the room is not locked, then the room will be
|
||||||
|
* auto-configured only if applicable and if the current
|
||||||
|
* user is the room's owner.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* (XMLElement) pres: The stanza
|
||||||
|
*/
|
||||||
|
this.saveAffiliationAndRole(pres);
|
||||||
|
|
||||||
|
const locked_room = pres.querySelector("status[code='201']");
|
||||||
|
if (locked_room) {
|
||||||
|
if (this.get('auto_configure')) {
|
||||||
|
this.autoConfigureChatRoom().then(this.getRoomFeatures.bind(this));
|
||||||
|
} else if (_converse.muc_instant_rooms) {
|
||||||
|
// Accept default configuration
|
||||||
|
this.saveConfiguration().then(this.getRoomFeatures.bind(this));
|
||||||
|
} else {
|
||||||
|
this.trigger('configurationNeeded');
|
||||||
|
return; // We haven't yet entered the room, so bail here.
|
||||||
|
}
|
||||||
|
} else if (!this.get('features_fetched')) {
|
||||||
|
// The features for this room weren't fetched.
|
||||||
|
// That must mean it's a new room without locking
|
||||||
|
// (in which case Prosody doesn't send a 201 status),
|
||||||
|
// otherwise the features would have been fetched in
|
||||||
|
// the "initialize" method already.
|
||||||
|
if (this.get('affiliation') === 'owner' && this.get('auto_configure')) {
|
||||||
|
this.autoConfigureChatRoom().then(this.getRoomFeatures.bind(this));
|
||||||
|
} else {
|
||||||
|
this.getRoomFeatures();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.save('connection_status', converse.ROOMSTATUS.ENTERED);
|
||||||
|
},
|
||||||
|
|
||||||
isUserMentioned (message) {
|
isUserMentioned (message) {
|
||||||
/* Returns a boolean to indicate whether the current user
|
/* Returns a boolean to indicate whether the current user
|
||||||
* was mentioned in a message.
|
* was mentioned in a message.
|
||||||
@ -725,7 +1112,7 @@
|
|||||||
_converse.chatboxviews.each(function (view) {
|
_converse.chatboxviews.each(function (view) {
|
||||||
if (view.model.get('type') === converse.CHATROOMS_TYPE) {
|
if (view.model.get('type') === converse.CHATROOMS_TYPE) {
|
||||||
view.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
|
view.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
|
||||||
view.registerHandlers();
|
view.model.registerHandlers();
|
||||||
view.join();
|
view.join();
|
||||||
view.fetchMessages();
|
view.fetchMessages();
|
||||||
}
|
}
|
||||||
|
@ -213,7 +213,8 @@
|
|||||||
const name = ev.target.getAttribute('data-room-name');
|
const name = ev.target.getAttribute('data-room-name');
|
||||||
const jid = ev.target.getAttribute('data-room-jid');
|
const jid = ev.target.getAttribute('data-room-jid');
|
||||||
if (confirm(__("Are you sure you want to leave the room %1$s?", name))) {
|
if (confirm(__("Are you sure you want to leave the room %1$s?", name))) {
|
||||||
_converse.chatboxviews.get(jid).leave();
|
// TODO: replace with API call
|
||||||
|
_converse.chatboxviews.get(jid).close();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user