2016-02-16 08:46:47 +01:00
|
|
|
// Converse.js (A browser based XMPP chat client)
|
|
|
|
// http://conversejs.org
|
|
|
|
//
|
2017-02-13 15:37:17 +01:00
|
|
|
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
|
2016-02-16 08:46:47 +01:00
|
|
|
// Licensed under the Mozilla Public License (MPLv2)
|
|
|
|
//
|
2017-04-21 15:52:16 +02:00
|
|
|
/*global define */
|
2016-02-16 08:46:47 +01:00
|
|
|
|
2016-02-13 23:58:50 +01:00
|
|
|
/* This is a Converse.js plugin which add support for multi-user chat rooms, as
|
|
|
|
* specified in XEP-0045 Multi-user chat.
|
|
|
|
*/
|
2016-02-16 08:46:47 +01:00
|
|
|
(function (root, factory) {
|
2017-02-13 15:37:17 +01:00
|
|
|
define([
|
2017-07-10 21:14:48 +02:00
|
|
|
"jquery.noconflict",
|
2017-08-16 15:19:41 +02:00
|
|
|
"form-utils",
|
2017-02-14 15:08:39 +01:00
|
|
|
"converse-core",
|
2017-08-15 16:46:55 +02:00
|
|
|
"lodash.fp",
|
2016-09-23 10:54:55 +02:00
|
|
|
"tpl!chatarea",
|
|
|
|
"tpl!chatroom",
|
2017-04-21 18:10:51 +02:00
|
|
|
"tpl!chatroom_disconnect",
|
2017-02-19 11:02:36 +01:00
|
|
|
"tpl!chatroom_features",
|
2016-09-23 10:54:55 +02:00
|
|
|
"tpl!chatroom_form",
|
2017-02-19 11:02:36 +01:00
|
|
|
"tpl!chatroom_head",
|
2017-02-27 18:50:55 +01:00
|
|
|
"tpl!chatroom_invite",
|
2017-11-10 17:26:04 +01:00
|
|
|
"tpl!chatroom_join_form",
|
2016-09-23 10:54:55 +02:00
|
|
|
"tpl!chatroom_nickname_form",
|
|
|
|
"tpl!chatroom_password_form",
|
|
|
|
"tpl!chatroom_sidebar",
|
2016-10-27 13:30:58 +02:00
|
|
|
"tpl!chatroom_toolbar",
|
2016-09-23 10:54:55 +02:00
|
|
|
"tpl!chatrooms_tab",
|
|
|
|
"tpl!info",
|
|
|
|
"tpl!occupant",
|
|
|
|
"tpl!room_description",
|
|
|
|
"tpl!room_item",
|
|
|
|
"tpl!room_panel",
|
2017-06-14 18:09:22 +02:00
|
|
|
"tpl!spinner",
|
2017-02-14 11:19:01 +01:00
|
|
|
"awesomplete",
|
2017-07-21 12:41:16 +02:00
|
|
|
"converse-chatview",
|
2017-11-10 21:20:13 +01:00
|
|
|
"converse-disco",
|
2017-12-20 20:29:57 +01:00
|
|
|
"backbone.overview",
|
|
|
|
"backbone.orderedlistview",
|
2017-11-10 21:20:13 +01:00
|
|
|
"backbone.vdomview"
|
2016-02-28 20:24:06 +01:00
|
|
|
], factory);
|
2016-09-23 10:54:55 +02:00
|
|
|
}(this, function (
|
2017-07-10 21:14:48 +02:00
|
|
|
$,
|
2017-12-06 14:59:01 +01:00
|
|
|
u,
|
2016-12-20 11:42:20 +01:00
|
|
|
converse,
|
2017-08-15 16:46:55 +02:00
|
|
|
fp,
|
2016-09-23 10:54:55 +02:00
|
|
|
tpl_chatarea,
|
|
|
|
tpl_chatroom,
|
2017-04-21 18:10:51 +02:00
|
|
|
tpl_chatroom_disconnect,
|
2017-02-19 11:02:36 +01:00
|
|
|
tpl_chatroom_features,
|
2016-09-23 10:54:55 +02:00
|
|
|
tpl_chatroom_form,
|
2017-02-19 11:02:36 +01:00
|
|
|
tpl_chatroom_head,
|
2017-02-27 18:50:55 +01:00
|
|
|
tpl_chatroom_invite,
|
2017-11-10 17:26:04 +01:00
|
|
|
tpl_chatroom_join_form,
|
2016-09-23 10:54:55 +02:00
|
|
|
tpl_chatroom_nickname_form,
|
|
|
|
tpl_chatroom_password_form,
|
|
|
|
tpl_chatroom_sidebar,
|
2016-10-27 13:30:58 +02:00
|
|
|
tpl_chatroom_toolbar,
|
2016-09-23 10:54:55 +02:00
|
|
|
tpl_chatrooms_tab,
|
|
|
|
tpl_info,
|
|
|
|
tpl_occupant,
|
|
|
|
tpl_room_description,
|
|
|
|
tpl_room_item,
|
2017-02-14 11:19:01 +01:00
|
|
|
tpl_room_panel,
|
2017-06-14 18:09:22 +02:00
|
|
|
tpl_spinner,
|
2017-02-14 11:19:01 +01:00
|
|
|
Awesomplete
|
2016-09-23 10:54:55 +02:00
|
|
|
) {
|
2017-06-07 00:55:25 +02:00
|
|
|
|
2016-02-19 11:43:46 +01:00
|
|
|
"use strict";
|
2017-06-12 20:30:58 +02:00
|
|
|
const ROOMS_PANEL_ID = 'chatrooms';
|
|
|
|
const CHATROOMS_TYPE = 'chatroom';
|
2016-11-02 23:08:20 +01:00
|
|
|
|
2017-12-14 18:43:00 +01:00
|
|
|
const MUC_ROLE_WEIGHTS = {
|
|
|
|
'moderator': 1,
|
|
|
|
'participant': 2,
|
|
|
|
'visitor': 3,
|
|
|
|
'none': 4,
|
|
|
|
};
|
|
|
|
|
2017-08-16 15:19:41 +02:00
|
|
|
const { Strophe, Backbone, Promise, $iq, $build, $msg, $pres, b64_sha1, sizzle, _, moment } = converse.env;
|
2016-02-16 08:46:47 +01:00
|
|
|
|
|
|
|
// Add Strophe Namespaces
|
|
|
|
Strophe.addNamespace('MUC_ADMIN', Strophe.NS.MUC + "#admin");
|
|
|
|
Strophe.addNamespace('MUC_OWNER', Strophe.NS.MUC + "#owner");
|
|
|
|
Strophe.addNamespace('MUC_REGISTER', "jabber:iq:register");
|
|
|
|
Strophe.addNamespace('MUC_ROOMCONF', Strophe.NS.MUC + "#roomconfig");
|
|
|
|
Strophe.addNamespace('MUC_USER', Strophe.NS.MUC + "#user");
|
|
|
|
|
2017-06-12 20:30:58 +02:00
|
|
|
const ROOM_FEATURES = [
|
2017-02-28 22:34:16 +01:00
|
|
|
'passwordprotected', 'unsecured', 'hidden',
|
2017-11-05 18:47:30 +01:00
|
|
|
'publicroom', 'membersonly', 'open', 'persistent',
|
2017-02-28 22:34:16 +01:00
|
|
|
'temporary', 'nonanonymous', 'semianonymous',
|
|
|
|
'moderated', 'unmoderated', 'mam_enabled'
|
|
|
|
];
|
2017-06-12 20:30:58 +02:00
|
|
|
const ROOM_FEATURES_MAP = {
|
2017-03-19 19:23:02 +01:00
|
|
|
'passwordprotected': 'unsecured',
|
|
|
|
'unsecured': 'passwordprotected',
|
2017-11-05 18:47:30 +01:00
|
|
|
'hidden': 'publicroom',
|
|
|
|
'publicroom': 'hidden',
|
2017-03-19 19:23:02 +01:00
|
|
|
'membersonly': 'open',
|
|
|
|
'open': 'membersonly',
|
|
|
|
'persistent': 'temporary',
|
|
|
|
'temporary': 'persistent',
|
|
|
|
'nonanonymous': 'semianonymous',
|
|
|
|
'semianonymous': 'nonanonymous',
|
|
|
|
'moderated': 'unmoderated',
|
|
|
|
'unmoderated': 'moderated'
|
|
|
|
};
|
2017-07-21 15:05:22 +02:00
|
|
|
|
|
|
|
converse.ROOMSTATUS = {
|
2017-02-24 11:54:54 +01:00
|
|
|
CONNECTED: 0,
|
|
|
|
CONNECTING: 1,
|
2017-03-02 23:28:22 +01:00
|
|
|
NICKNAME_REQUIRED: 2,
|
2017-04-04 16:45:50 +02:00
|
|
|
PASSWORD_REQUIRED: 3,
|
|
|
|
DISCONNECTED: 4,
|
|
|
|
ENTERED: 5
|
2017-02-24 11:54:54 +01:00
|
|
|
};
|
|
|
|
|
2016-12-20 11:42:20 +01:00
|
|
|
converse.plugins.add('converse-muc', {
|
2016-06-10 00:05:55 +02:00
|
|
|
/* Optional dependencies are other plugins which might be
|
2017-03-08 12:15:19 +01:00
|
|
|
* overridden or relied upon, and therefore need to be loaded before
|
|
|
|
* this plugin. They are called "optional" because they might not be
|
|
|
|
* available, in which case any overrides applicable to them will be
|
|
|
|
* ignored.
|
2016-06-09 11:01:54 +02:00
|
|
|
*
|
2017-03-08 12:15:19 +01:00
|
|
|
* It's possible however to make optional dependencies non-optional.
|
|
|
|
* If the setting "strict_plugin_dependencies" is set to true,
|
2016-06-10 00:05:55 +02:00
|
|
|
* an error will be raised if the plugin is not found.
|
2016-06-09 11:01:54 +02:00
|
|
|
*
|
2016-06-10 00:05:55 +02:00
|
|
|
* NB: These plugins need to have already been loaded via require.js.
|
2016-06-09 11:01:54 +02:00
|
|
|
*/
|
|
|
|
optional_dependencies: ["converse-controlbox"],
|
2016-02-16 08:46:47 +01:00
|
|
|
|
|
|
|
overrides: {
|
|
|
|
// Overrides mentioned here will be picked up by converse.js's
|
|
|
|
// plugin architecture they will replace existing methods on the
|
|
|
|
// relevant objects or classes.
|
|
|
|
//
|
|
|
|
// New functions which don't exist yet can also be added.
|
2016-02-13 23:58:50 +01:00
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
_tearDown () {
|
|
|
|
const rooms = this.chatboxes.where({'type': CHATROOMS_TYPE});
|
2017-06-23 18:21:50 +02:00
|
|
|
_.each(rooms, function (room) {
|
2017-12-06 14:59:01 +01:00
|
|
|
u.safeSave(room, {'connection_status': converse.ROOMSTATUS.DISCONNECTED});
|
2017-06-23 18:21:50 +02:00
|
|
|
});
|
|
|
|
this.__super__._tearDown.call(this, arguments);
|
|
|
|
},
|
|
|
|
|
2017-05-23 18:16:47 +02:00
|
|
|
ChatBoxes: {
|
2017-07-10 17:46:22 +02:00
|
|
|
model (attrs, options) {
|
|
|
|
const { _converse } = this.__super__;
|
2017-06-07 00:55:25 +02:00
|
|
|
if (attrs.type == CHATROOMS_TYPE) {
|
2017-05-23 18:36:40 +02:00
|
|
|
return new _converse.ChatRoom(attrs, options);
|
2017-05-23 18:16:47 +02:00
|
|
|
} else {
|
|
|
|
return this.__super__.model.apply(this, arguments);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
2017-05-16 11:55:59 +02:00
|
|
|
|
2017-05-23 18:16:47 +02:00
|
|
|
ControlBoxView: {
|
2017-07-10 17:46:22 +02:00
|
|
|
renderRoomsPanel () {
|
|
|
|
const { _converse } = this.__super__;
|
2017-05-16 11:55:59 +02:00
|
|
|
this.roomspanel = new _converse.RoomsPanel({
|
2017-12-15 22:52:14 +01:00
|
|
|
'parent': this.el.querySelector('.controlbox-panes'),
|
2017-11-10 17:26:04 +01:00
|
|
|
'model': new (_converse.RoomsPanelModel.extend({
|
2017-07-10 17:46:22 +02:00
|
|
|
id: b64_sha1(`converse.roomspanel${_converse.bare_jid}`), // Required by sessionStorage
|
2017-05-16 11:55:59 +02:00
|
|
|
browserStorage: new Backbone.BrowserStorage[_converse.storage](
|
2017-07-10 17:46:22 +02:00
|
|
|
b64_sha1(`converse.roomspanel${_converse.bare_jid}`))
|
2017-05-16 11:55:59 +02:00
|
|
|
}))()
|
|
|
|
});
|
2017-06-05 14:50:29 +02:00
|
|
|
this.roomspanel.insertIntoDOM().model.fetch();
|
2017-05-16 11:55:59 +02:00
|
|
|
if (!this.roomspanel.model.get('nick')) {
|
|
|
|
this.roomspanel.model.save({
|
|
|
|
nick: Strophe.getNodeFromJid(_converse.bare_jid)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
_converse.emit('roomsPanelRendered');
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
renderContactsPanel () {
|
|
|
|
const { _converse } = this.__super__;
|
2016-08-31 12:06:17 +02:00
|
|
|
this.__super__.renderContactsPanel.apply(this, arguments);
|
2016-12-20 10:30:20 +01:00
|
|
|
if (_converse.allow_muc) {
|
2017-05-16 11:55:59 +02:00
|
|
|
this.renderRoomsPanel();
|
2016-02-13 23:58:50 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
2016-02-28 20:24:06 +01:00
|
|
|
ChatBoxViews: {
|
2017-07-10 17:46:22 +02:00
|
|
|
onChatBoxAdded (item) {
|
|
|
|
const { _converse } = this.__super__;
|
2017-06-12 20:30:58 +02:00
|
|
|
let view = this.get(item.get('id'));
|
2017-06-07 00:55:25 +02:00
|
|
|
if (!view && item.get('type') === CHATROOMS_TYPE) {
|
2016-12-20 10:30:20 +01:00
|
|
|
view = new _converse.ChatRoomView({'model': item});
|
2016-03-21 10:57:38 +01:00
|
|
|
return this.add(item.get('id'), view);
|
2016-02-28 20:24:06 +01:00
|
|
|
} else {
|
2016-08-31 12:06:17 +02:00
|
|
|
return this.__super__.onChatBoxAdded.apply(this, arguments);
|
2016-02-28 20:24:06 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
initialize () {
|
2016-02-16 08:46:47 +01:00
|
|
|
/* The initialize function gets called as soon as the plugin is
|
|
|
|
* loaded by converse.js's plugin machinery.
|
|
|
|
*/
|
2017-07-10 17:46:22 +02:00
|
|
|
const { _converse } = this,
|
2017-09-26 18:27:41 +02:00
|
|
|
{ __ } = _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;
|
|
|
|
}
|
|
|
|
|
2016-12-04 17:53:35 +01:00
|
|
|
// XXX: Inside plugins, all calls to the translation machinery
|
2017-12-06 14:59:01 +01:00
|
|
|
// (e.g. u.__) should only be done in the initialize function.
|
2016-12-04 17:53:35 +01:00
|
|
|
// If called before, we won't know what language the user wants,
|
2017-02-19 10:58:30 +01:00
|
|
|
// and it'll fall back to English.
|
2016-12-04 17:53:35 +01:00
|
|
|
|
|
|
|
/* 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
|
|
|
|
*/
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.muc = {
|
2016-12-04 17:53:35 +01:00
|
|
|
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"),
|
2017-09-22 10:45:03 +02:00
|
|
|
332: __("You have been removed from this room because the MUC (Multi-user chat) service is being shut down")
|
2016-12-04 17:53:35 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2017-02-13 09:23:42 +01:00
|
|
|
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")
|
2016-12-04 17:53:35 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
new_nickname_messages: {
|
2017-09-24 20:36:39 +02:00
|
|
|
210: ___('Your nickname has been automatically set to %1$s'),
|
|
|
|
303: ___('Your nickname has been changed to %1$s')
|
2016-12-04 17:53:35 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-02-16 08:46:47 +01:00
|
|
|
// Configuration values for this plugin
|
2016-08-11 18:03:02 +02:00
|
|
|
// ====================================
|
|
|
|
// Refer to docs/source/configuration.rst for explanations of these
|
|
|
|
// configuration settings.
|
2017-07-05 11:03:13 +02:00
|
|
|
_converse.api.settings.update({
|
2016-02-13 23:58:50 +01:00
|
|
|
allow_muc: true,
|
2016-08-11 18:03:02 +02:00
|
|
|
allow_muc_invitations: true,
|
|
|
|
auto_join_on_invite: false,
|
|
|
|
auto_join_rooms: [],
|
2016-03-17 13:24:26 +01:00
|
|
|
auto_list_rooms: false,
|
2016-02-19 10:36:01 +01:00
|
|
|
hide_muc_server: false,
|
2016-12-07 14:48:47 +01:00
|
|
|
muc_disable_moderator_commands: false,
|
2016-10-26 15:21:36 +02:00
|
|
|
muc_domain: undefined,
|
2016-08-11 18:03:02 +02:00
|
|
|
muc_history_max_stanzas: undefined,
|
|
|
|
muc_instant_rooms: true,
|
2016-10-27 13:30:58 +02:00
|
|
|
muc_nickname_from_jid: false,
|
2017-02-24 11:54:54 +01:00
|
|
|
muc_show_join_leave: true,
|
2016-10-27 13:30:58 +02:00
|
|
|
visible_toolbar_buttons: {
|
|
|
|
'toggle_occupants': true
|
|
|
|
},
|
2016-03-13 17:16:53 +01:00
|
|
|
});
|
2017-09-29 00:07:16 +02:00
|
|
|
_converse.api.promises.add(['roomsPanelRendered', 'roomsAutoJoined']);
|
|
|
|
|
2017-10-31 22:08:06 +01:00
|
|
|
|
2017-10-31 23:04:46 +01:00
|
|
|
function openRoom (jid) {
|
2017-12-06 14:59:01 +01:00
|
|
|
if (!u.isValidJID(jid)) {
|
2017-10-31 23:04:46 +01:00
|
|
|
return converse.log(
|
|
|
|
`Invalid JID "${jid}" provided in URL fragment`,
|
|
|
|
Strophe.LogLevel.WARN
|
|
|
|
);
|
|
|
|
}
|
2017-10-31 22:08:06 +01:00
|
|
|
const promises = [_converse.api.waitUntil('roomsAutoJoined')]
|
|
|
|
if (!_converse.allow_bookmarks) {
|
|
|
|
promises.push( _converse.api.waitUntil('bookmarksInitialized'));
|
2017-09-29 00:07:16 +02:00
|
|
|
}
|
2017-10-31 22:08:06 +01:00
|
|
|
Promise.all(promises).then(() => {
|
2017-10-31 23:04:46 +01:00
|
|
|
_converse.api.rooms.open(jid);
|
2017-10-31 22:08:06 +01:00
|
|
|
});
|
|
|
|
}
|
2017-10-31 23:04:46 +01:00
|
|
|
_converse.router.route('converse/room?jid=:jid', openRoom);
|
2017-09-29 00:07:16 +02:00
|
|
|
|
2017-05-07 20:16:13 +02:00
|
|
|
|
2017-10-31 22:07:40 +01:00
|
|
|
function openChatRoom (settings, bring_to_foreground) {
|
2017-06-15 16:22:49 +02:00
|
|
|
/* Opens a chat room, making sure that certain attributes
|
2016-12-04 10:43:39 +01:00
|
|
|
* are correct, for example that the "type" is set to
|
|
|
|
* "chatroom".
|
|
|
|
*/
|
2017-08-09 15:50:24 +02:00
|
|
|
if (_.isUndefined(settings.jid)) {
|
|
|
|
throw new Error("openChatRoom needs to be called with a JID");
|
|
|
|
}
|
|
|
|
settings.type = CHATROOMS_TYPE;
|
|
|
|
settings.id = settings.jid;
|
|
|
|
settings.box_id = b64_sha1(settings.jid)
|
|
|
|
return _converse.chatboxviews.showChat(settings, bring_to_foreground);
|
2017-10-31 22:07:40 +01:00
|
|
|
}
|
2016-02-16 08:46:47 +01:00
|
|
|
|
2017-05-23 18:36:40 +02:00
|
|
|
_converse.ChatRoom = _converse.ChatBox.extend({
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
defaults () {
|
2017-06-15 16:22:49 +02:00
|
|
|
return _.assign(
|
|
|
|
_.clone(_converse.ChatBox.prototype.defaults),
|
|
|
|
_.zipObject(ROOM_FEATURES, _.map(ROOM_FEATURES, _.stubFalse)),
|
|
|
|
{
|
|
|
|
// For group chats, we distinguish between generally unread
|
|
|
|
// messages and those ones that specifically mention the
|
|
|
|
// user.
|
|
|
|
//
|
|
|
|
// To keep things simple, we reuse `num_unread` from
|
|
|
|
// _converse.ChatBox to indicate unread messages which
|
|
|
|
// mention the user and `num_unread_general` to indicate
|
|
|
|
// generally unread messages (which *includes* mentions!).
|
|
|
|
'num_unread_general': 0,
|
|
|
|
|
|
|
|
'affiliation': null,
|
2017-07-21 15:05:22 +02:00
|
|
|
'connection_status': converse.ROOMSTATUS.DISCONNECTED,
|
2017-07-19 08:30:04 +02:00
|
|
|
'name': '',
|
2017-06-15 16:22:49 +02:00
|
|
|
'description': '',
|
|
|
|
'features_fetched': false,
|
|
|
|
'roomconfig': {},
|
|
|
|
'type': CHATROOMS_TYPE,
|
|
|
|
}
|
|
|
|
);
|
2017-06-07 17:47:06 +02:00
|
|
|
},
|
2017-05-24 08:40:09 +02:00
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
isUserMentioned (message) {
|
2017-05-23 18:36:40 +02:00
|
|
|
/* Returns a boolean to indicate whether the current user
|
|
|
|
* was mentioned in a message.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (String): The text message
|
|
|
|
*/
|
2017-07-10 17:46:22 +02:00
|
|
|
return (new RegExp(`\\b${this.get('nick')}\\b`)).test(message);
|
2017-05-23 18:36:40 +02:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
incrementUnreadMsgCounter (stanza) {
|
2017-05-23 18:36:40 +02:00
|
|
|
/* Given a newly received message, update the unread counter if
|
|
|
|
* necessary.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (XMLElement): The <messsage> stanza
|
|
|
|
*/
|
2017-06-12 20:30:58 +02:00
|
|
|
const body = stanza.querySelector('body');
|
2017-05-23 18:36:40 +02:00
|
|
|
if (_.isNull(body)) {
|
|
|
|
return; // The message has no text
|
|
|
|
}
|
2017-12-06 14:59:01 +01:00
|
|
|
if (u.isNewMessage(stanza) && this.newMessageWillBeHidden()) {
|
2017-05-24 08:40:09 +02:00
|
|
|
this.save({'num_unread_general': this.get('num_unread_general') + 1});
|
|
|
|
if (this.isUserMentioned(body.textContent)) {
|
|
|
|
this.save({'num_unread': this.get('num_unread') + 1});
|
|
|
|
_converse.incrementMsgCounter();
|
|
|
|
}
|
2017-05-23 18:36:40 +02:00
|
|
|
}
|
|
|
|
},
|
2017-05-24 08:40:09 +02:00
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
clearUnreadMsgCounter() {
|
2017-12-06 14:59:01 +01:00
|
|
|
u.safeSave(this, {
|
2017-06-19 11:08:57 +02:00
|
|
|
'num_unread': 0,
|
|
|
|
'num_unread_general': 0
|
|
|
|
});
|
2017-05-24 08:40:09 +02:00
|
|
|
}
|
2017-05-23 18:36:40 +02:00
|
|
|
});
|
|
|
|
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.ChatRoomView = _converse.ChatBoxView.extend({
|
2016-02-16 08:46:47 +01:00
|
|
|
/* Backbone View which renders a chat room, based upon the view
|
2016-03-19 23:16:00 +01:00
|
|
|
* for normal one-on-one chat boxes.
|
|
|
|
*/
|
2016-02-16 08:46:47 +01:00
|
|
|
length: 300,
|
|
|
|
tagName: 'div',
|
2016-11-22 17:42:58 +01:00
|
|
|
className: 'chatbox chatroom hidden',
|
2016-03-20 00:03:00 +01:00
|
|
|
is_chatroom: true,
|
2016-02-16 08:46:47 +01:00
|
|
|
events: {
|
|
|
|
'click .close-chatbox-button': 'close',
|
2017-04-13 13:18:52 +02:00
|
|
|
'click .configure-chatroom-button': 'getAndRenderConfigurationForm',
|
2017-07-15 11:55:07 +02:00
|
|
|
'click .toggle-smiley': 'toggleEmojiMenu',
|
|
|
|
'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
|
2016-02-16 08:46:47 +01:00
|
|
|
'click .toggle-clear': 'clearChatRoomMessages',
|
|
|
|
'click .toggle-call': 'toggleCall',
|
|
|
|
'click .toggle-occupants a': 'toggleOccupants',
|
2016-05-30 11:45:39 +02:00
|
|
|
'click .new-msgs-indicator': 'viewUnreadMessages',
|
2016-07-26 11:04:05 +02:00
|
|
|
'click .occupant': 'onOccupantClicked',
|
2017-03-30 12:40:17 +02:00
|
|
|
'keypress .chat-textarea': 'keyPressed',
|
|
|
|
'click .send-button': 'onSendButtonClicked'
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
initialize () {
|
2016-02-16 08:46:47 +01:00
|
|
|
this.model.messages.on('add', this.onMessageAdded, this);
|
2016-04-05 13:23:16 +02:00
|
|
|
this.model.on('show', this.show, this);
|
|
|
|
this.model.on('destroy', this.hide, this);
|
2017-02-17 22:17:19 +01:00
|
|
|
this.model.on('change:connection_status', this.afterConnected, this);
|
2016-12-02 18:41:05 +01:00
|
|
|
this.model.on('change:affiliation', this.renderHeading, this);
|
2017-02-18 18:15:16 +01:00
|
|
|
this.model.on('change:chat_state', this.sendChatState, this);
|
|
|
|
this.model.on('change:description', this.renderHeading, this);
|
2016-12-02 18:41:05 +01:00
|
|
|
this.model.on('change:name', this.renderHeading, this);
|
2016-04-05 13:23:16 +02:00
|
|
|
|
2017-07-16 14:12:17 +02:00
|
|
|
this.createEmojiPicker();
|
2016-12-04 13:32:14 +01:00
|
|
|
this.createOccupantsView();
|
2017-02-28 18:25:33 +01:00
|
|
|
this.render().insertIntoDOM();
|
2017-02-18 18:14:30 +01:00
|
|
|
this.registerHandlers();
|
2017-02-27 22:09:17 +01:00
|
|
|
|
2017-07-21 15:05:22 +02:00
|
|
|
if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
|
2017-07-12 09:55:43 +02:00
|
|
|
const handler = () => {
|
2017-06-12 20:30:58 +02:00
|
|
|
this.join();
|
|
|
|
this.fetchMessages();
|
|
|
|
_converse.emit('chatRoomOpened', this);
|
2017-07-12 09:55:43 +02:00
|
|
|
}
|
|
|
|
this.getRoomFeatures().then(handler, handler);
|
2017-02-18 18:14:30 +01:00
|
|
|
} else {
|
|
|
|
this.fetchMessages();
|
2017-05-07 20:16:13 +02:00
|
|
|
_converse.emit('chatRoomOpened', this);
|
2017-02-18 18:14:30 +01:00
|
|
|
}
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
render () {
|
2017-04-04 16:45:50 +02:00
|
|
|
this.el.setAttribute('id', this.model.get('box_id'));
|
|
|
|
this.el.innerHTML = tpl_chatroom();
|
2016-12-02 18:41:05 +01:00
|
|
|
this.renderHeading();
|
2016-02-16 08:46:47 +01:00
|
|
|
this.renderChatArea();
|
2017-07-21 15:05:22 +02:00
|
|
|
if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
|
2017-04-04 16:45:50 +02:00
|
|
|
this.showSpinner();
|
|
|
|
}
|
2017-12-06 14:59:01 +01:00
|
|
|
u.refreshWebkit();
|
2016-02-16 08:46:47 +01:00
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
renderHeading () {
|
2017-02-17 22:17:19 +01:00
|
|
|
/* Render the heading UI of the chat room. */
|
2016-12-02 18:41:05 +01:00
|
|
|
this.el.querySelector('.chat-head-chatroom').innerHTML = this.generateHeadingHTML();
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
renderChatArea () {
|
2016-12-04 10:43:39 +01:00
|
|
|
/* Render the UI container in which chat room messages will
|
|
|
|
* appear.
|
|
|
|
*/
|
2017-12-06 14:59:01 +01:00
|
|
|
if (_.isNull(this.el.querySelector('.chat-area'))) {
|
|
|
|
const container_el = this.el.querySelector('.chatroom-body');
|
|
|
|
container_el.innerHTML = tpl_chatarea({
|
|
|
|
'label_message': __('Message'),
|
|
|
|
'label_send': __('Send'),
|
|
|
|
'show_send_button': _converse.show_send_button,
|
|
|
|
'show_toolbar': _converse.show_toolbar,
|
|
|
|
'unread_msgs': __('You have unread messages')
|
|
|
|
});
|
|
|
|
container_el.insertAdjacentElement('beforeend', this.occupantsview.el);
|
2016-10-27 13:30:58 +02:00
|
|
|
this.renderToolbar(tpl_chatroom_toolbar);
|
2017-12-02 14:57:10 +01:00
|
|
|
this.content = this.el.querySelector('.chat-content');
|
|
|
|
this.$content = $(this.content);
|
2017-12-06 14:59:01 +01:00
|
|
|
this.toggleOccupants(null, true);
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
createOccupantsView () {
|
2017-02-18 11:03:26 +01:00
|
|
|
/* Create the ChatRoomOccupantsView Backbone.View
|
|
|
|
*/
|
2017-06-12 20:30:58 +02:00
|
|
|
const model = new _converse.ChatRoomOccupants();
|
2017-02-19 12:03:24 +01:00
|
|
|
model.chatroomview = this;
|
|
|
|
this.occupantsview = new _converse.ChatRoomOccupantsView({'model': model});
|
2017-12-07 07:05:37 +01:00
|
|
|
this.occupantsview.model.on('change:role', this.informOfOccupantsRoleChange, this);
|
2017-02-27 22:09:17 +01:00
|
|
|
return this;
|
2017-02-18 11:03:26 +01:00
|
|
|
},
|
|
|
|
|
2017-12-14 14:07:39 +01:00
|
|
|
informOfOccupantsRoleChange (occupant, changed) {
|
|
|
|
const previous_role = occupant._previousAttributes.role;
|
|
|
|
if (previous_role === 'moderator') {
|
|
|
|
this.showStatusNotification(
|
|
|
|
__("%1$s is no longer a moderator.", occupant.get('nick')),
|
|
|
|
false, true)
|
|
|
|
}
|
|
|
|
if (previous_role === 'visitor') {
|
|
|
|
this.showStatusNotification(
|
|
|
|
__("%1$s has been given a voice again.", occupant.get('nick')),
|
|
|
|
false, true)
|
|
|
|
}
|
|
|
|
|
2017-12-07 07:05:37 +01:00
|
|
|
if (occupant.get('role') === 'visitor') {
|
|
|
|
this.showStatusNotification(
|
2017-12-14 14:07:39 +01:00
|
|
|
__("%1$s has been muted.", occupant.get('nick')),
|
|
|
|
false, true)
|
|
|
|
}
|
|
|
|
if (occupant.get('role') === 'moderator') {
|
|
|
|
this.showStatusNotification(
|
|
|
|
__("%1$s is now a moderator.", occupant.get('nick')),
|
|
|
|
false, true)
|
2017-12-07 07:05:37 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
generateHeadingHTML () {
|
2017-02-18 11:03:26 +01:00
|
|
|
/* Returns the heading HTML to be rendered.
|
|
|
|
*/
|
2017-02-19 10:58:30 +01:00
|
|
|
return tpl_chatroom_head(
|
2017-02-18 11:03:26 +01:00
|
|
|
_.extend(this.model.toJSON(), {
|
2017-07-19 08:30:04 +02:00
|
|
|
Strophe: Strophe,
|
2017-02-18 11:03:26 +01:00
|
|
|
info_close: __('Close and leave this room'),
|
|
|
|
info_configure: __('Configure this room'),
|
2017-02-18 18:15:16 +01:00
|
|
|
description: this.model.get('description') || ''
|
2017-02-18 11:03:26 +01:00
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
afterShown () {
|
2017-02-17 22:17:19 +01:00
|
|
|
/* Override from converse-chatview, specifically to avoid
|
|
|
|
* the 'active' chat state from being sent out prematurely.
|
|
|
|
*
|
|
|
|
* This is instead done in `afterConnected` below.
|
|
|
|
*/
|
2017-06-14 20:14:45 +02:00
|
|
|
if (this.model.collection && this.model.collection.browserStorage) {
|
2017-02-17 22:17:19 +01:00
|
|
|
// Without a connection, we haven't yet initialized
|
|
|
|
// localstorage
|
|
|
|
this.model.save();
|
|
|
|
}
|
2017-03-01 08:42:26 +01:00
|
|
|
this.occupantsview.setOccupantsHeight();
|
2017-02-17 22:17:19 +01:00
|
|
|
},
|
|
|
|
|
2017-12-20 11:02:01 +01:00
|
|
|
show (focus) {
|
|
|
|
if (u.isVisible(this.el)) {
|
|
|
|
if (focus) { this.focus(); }
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Override from converse-chatview in order to not use
|
|
|
|
// "fadeIn", which causes flashing.
|
|
|
|
u.showElement(this.el);
|
|
|
|
this.afterShown();
|
|
|
|
if (focus) { this.focus(); }
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
afterConnected () {
|
2017-07-21 15:05:22 +02:00
|
|
|
if (this.model.get('connection_status') === converse.ROOMSTATUS.ENTERED) {
|
2017-02-17 22:17:19 +01:00
|
|
|
this.setChatState(_converse.ACTIVE);
|
2017-12-19 22:37:51 +01:00
|
|
|
this.renderEmojiPicker();
|
2017-02-17 22:17:19 +01:00
|
|
|
this.scrollDown();
|
|
|
|
this.focus();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
getExtraMessageClasses (attrs) {
|
2017-06-12 20:30:58 +02:00
|
|
|
let extra_classes = _converse.ChatBoxView.prototype
|
2017-02-15 21:30:32 +01:00
|
|
|
.getExtraMessageClasses.apply(this, arguments);
|
|
|
|
|
|
|
|
if (this.is_chatroom && attrs.sender === 'them' &&
|
2017-05-23 18:48:17 +02:00
|
|
|
this.model.isUserMentioned(attrs.message)) {
|
2017-02-15 21:30:32 +01:00
|
|
|
// Add special class to mark groupchat messages
|
|
|
|
// in which we are mentioned.
|
|
|
|
extra_classes += ' mentioned';
|
|
|
|
}
|
|
|
|
return extra_classes;
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
getToolbarOptions () {
|
2016-10-27 13:30:58 +02:00
|
|
|
return _.extend(
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.ChatBoxView.prototype.getToolbarOptions.apply(this, arguments),
|
2016-10-27 13:30:58 +02:00
|
|
|
{
|
|
|
|
label_hide_occupants: __('Hide the list of occupants'),
|
2016-12-20 10:30:20 +01:00
|
|
|
show_occupants_toggle: this.is_chatroom && _converse.visible_toolbar_buttons.toggle_occupants
|
2016-10-27 13:30:58 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
close (ev) {
|
2016-12-04 10:43:39 +01:00
|
|
|
/* Close this chat box, which implies leaving the room as
|
|
|
|
* well.
|
|
|
|
*/
|
2016-03-28 13:42:33 +02:00
|
|
|
this.leave();
|
|
|
|
},
|
|
|
|
|
2017-12-06 16:38:16 +01:00
|
|
|
setOccupantsVisibility () {
|
|
|
|
if (this.model.get('hidden_occupants')) {
|
|
|
|
const icon_el = this.el.querySelector('.icon-hide-users');
|
|
|
|
if (!_.isNull(icon_el)) {
|
|
|
|
icon_el.classList.remove('icon-hide-users');
|
|
|
|
icon_el.classList.add('icon-show-users');
|
|
|
|
}
|
|
|
|
this.el.querySelector('.chat-area').classList.add('full');
|
|
|
|
u.hideElement(this.el.querySelector('.occupants'));
|
2017-12-14 18:30:05 +01:00
|
|
|
} else {
|
|
|
|
const icon_el = this.el.querySelector('.icon-show-users');
|
|
|
|
if (!_.isNull(icon_el)) {
|
|
|
|
icon_el.classList.remove('icon-show-users');
|
|
|
|
icon_el.classList.add('icon-hide-users');
|
|
|
|
}
|
|
|
|
this.el.querySelector('.chat-area').classList.remove('full');
|
|
|
|
this.el.querySelector('.occupants').classList.remove('hidden');
|
2017-12-06 16:38:16 +01:00
|
|
|
}
|
|
|
|
this.occupantsview.setOccupantsHeight();
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
toggleOccupants (ev, preserve_state) {
|
2016-12-04 10:43:39 +01:00
|
|
|
/* Show or hide the right sidebar containing the chat
|
|
|
|
* occupants (and the invite widget).
|
|
|
|
*/
|
2016-02-16 08:46:47 +01:00
|
|
|
if (ev) {
|
|
|
|
ev.preventDefault();
|
|
|
|
ev.stopPropagation();
|
|
|
|
}
|
2017-12-06 16:38:16 +01:00
|
|
|
if (!preserve_state) {
|
2017-12-14 18:30:05 +01:00
|
|
|
this.model.set({'hidden_occupants': !this.model.get('hidden_occupants')});
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
2017-12-06 16:38:16 +01:00
|
|
|
this.setOccupantsVisibility();
|
2017-12-06 14:59:01 +01:00
|
|
|
this.scrollDown();
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
onOccupantClicked (ev) {
|
2016-07-26 11:04:05 +02:00
|
|
|
/* When an occupant is clicked, insert their nickname into
|
|
|
|
* the chat textarea input.
|
|
|
|
*/
|
|
|
|
this.insertIntoTextArea(ev.target.textContent);
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
requestMemberList (chatroom_jid, affiliation) {
|
2016-12-06 16:18:33 +01:00
|
|
|
/* Send an IQ stanza to the server, asking it for the
|
|
|
|
* member-list of this room.
|
|
|
|
*
|
|
|
|
* See: http://xmpp.org/extensions/xep-0045.html#modifymember
|
|
|
|
*
|
|
|
|
* Parameters:
|
2017-01-26 15:49:02 +01:00
|
|
|
* (String) chatroom_jid: The JID of the chatroom for
|
|
|
|
* which the member-list is being requested
|
2016-12-06 19:34:54 +01:00
|
|
|
* (String) affiliation: The specific member list to
|
|
|
|
* fetch. 'admin', 'owner' or 'member'.
|
2016-12-06 16:18:33 +01:00
|
|
|
*
|
|
|
|
* Returns:
|
|
|
|
* A promise which resolves once the list has been
|
|
|
|
* retrieved.
|
|
|
|
*/
|
2017-07-12 09:55:43 +02:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
affiliation = affiliation || 'member';
|
|
|
|
const iq = $iq({to: chatroom_jid, type: "get"})
|
|
|
|
.c("query", {xmlns: Strophe.NS.MUC_ADMIN})
|
|
|
|
.c("item", {'affiliation': affiliation});
|
|
|
|
_converse.connection.sendIQ(iq, resolve, reject);
|
|
|
|
});
|
2016-12-06 16:18:33 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
parseMemberListIQ (iq) {
|
2016-12-06 16:18:33 +01:00
|
|
|
/* Given an IQ stanza with a member list, create an array of member
|
|
|
|
* objects.
|
|
|
|
*/
|
|
|
|
return _.map(
|
2017-12-23 21:28:46 +01:00
|
|
|
sizzle(`query[xmlns="${Strophe.NS.MUC_ADMIN}"] item`, iq),
|
2017-07-10 17:46:22 +02:00
|
|
|
(item) => ({
|
|
|
|
'jid': item.getAttribute('jid'),
|
|
|
|
'affiliation': item.getAttribute('affiliation'),
|
|
|
|
})
|
2016-12-06 16:18:33 +01:00
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
computeAffiliationsDelta (exclude_existing, remove_absentees, new_list, old_list) {
|
2016-12-07 12:30:28 +01:00
|
|
|
/* Given two lists of objects with 'jid', 'affiliation' and
|
|
|
|
* 'reason' properties, return a new list containing
|
|
|
|
* those objects that are new, changed or removed
|
2016-12-06 16:18:33 +01:00
|
|
|
* (depending on the 'remove_absentees' boolean).
|
|
|
|
*
|
2016-12-06 20:39:16 +01:00
|
|
|
* The affiliations for new and changed members stay the
|
|
|
|
* same, for removed members, the affiliation is set to 'none'.
|
2016-12-06 16:18:33 +01:00
|
|
|
*
|
2016-12-07 12:30:28 +01:00
|
|
|
* The 'reason' property is not taken into account when
|
|
|
|
* comparing whether affiliations have been changed.
|
|
|
|
*
|
2016-12-06 16:18:33 +01:00
|
|
|
* Parameters:
|
2016-12-06 21:04:08 +01:00
|
|
|
* (Boolean) exclude_existing: Indicates whether JIDs from
|
2016-12-07 12:30:28 +01:00
|
|
|
* the new list which are also in the old list
|
2016-12-06 21:04:08 +01:00
|
|
|
* (regardless of affiliation) should be excluded
|
|
|
|
* from the delta. One reason to do this
|
|
|
|
* would be when you want to add a JID only if it
|
|
|
|
* doesn't have *any* existing affiliation at all.
|
2016-12-06 20:39:16 +01:00
|
|
|
* (Boolean) remove_absentees: Indicates whether JIDs
|
2016-12-07 12:30:28 +01:00
|
|
|
* from the old list which are not in the new list
|
2016-12-06 20:39:16 +01:00
|
|
|
* should be considered removed and therefore be
|
|
|
|
* included in the delta with affiliation set
|
|
|
|
* to 'none'.
|
2016-12-07 12:30:28 +01:00
|
|
|
* (Array) new_list: Array containing the new affiliations
|
|
|
|
* (Array) old_list: Array containing the old affiliations
|
2016-12-06 16:18:33 +01:00
|
|
|
*/
|
2017-06-12 20:30:58 +02:00
|
|
|
const new_jids = _.map(new_list, 'jid');
|
|
|
|
const old_jids = _.map(old_list, 'jid');
|
2016-12-07 12:30:28 +01:00
|
|
|
|
2016-12-06 20:39:16 +01:00
|
|
|
// Get the new affiliations
|
2017-07-10 17:46:22 +02:00
|
|
|
let delta = _.map(
|
|
|
|
_.difference(new_jids, old_jids),
|
|
|
|
(jid) => new_list[_.indexOf(new_jids, jid)]
|
|
|
|
);
|
2016-12-06 21:04:08 +01:00
|
|
|
if (!exclude_existing) {
|
|
|
|
// Get the changed affiliations
|
2016-12-07 14:02:03 +01:00
|
|
|
delta = delta.concat(_.filter(new_list, function (item) {
|
2017-06-12 20:30:58 +02:00
|
|
|
const idx = _.indexOf(old_jids, item.jid);
|
2016-12-07 12:30:28 +01:00
|
|
|
if (idx >= 0) {
|
2016-12-07 14:02:03 +01:00
|
|
|
return item.affiliation !== old_list[idx].affiliation;
|
2016-12-06 21:04:08 +01:00
|
|
|
}
|
2016-12-07 12:30:28 +01:00
|
|
|
return false;
|
|
|
|
}));
|
2016-12-06 21:04:08 +01:00
|
|
|
}
|
2016-12-06 16:18:33 +01:00
|
|
|
if (remove_absentees) {
|
2016-12-06 20:39:16 +01:00
|
|
|
// Get the removed affiliations
|
2017-07-10 17:46:22 +02:00
|
|
|
delta = delta.concat(
|
|
|
|
_.map(
|
|
|
|
_.difference(old_jids, new_jids),
|
|
|
|
(jid) => ({'jid': jid, 'affiliation': 'none'})
|
|
|
|
)
|
|
|
|
);
|
2016-12-06 16:18:33 +01:00
|
|
|
}
|
2016-12-06 20:39:16 +01:00
|
|
|
return delta;
|
2016-12-06 16:18:33 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
sendAffiliationIQ (chatroom_jid, affiliation, member) {
|
2017-01-26 15:49:02 +01:00
|
|
|
/* Send an IQ stanza specifying an affiliation change.
|
|
|
|
*
|
|
|
|
* Paremeters:
|
|
|
|
* (String) chatroom_jid: JID of the relevant room
|
|
|
|
* (String) affiliation: affiliation (could also be stored
|
|
|
|
* on the member object).
|
|
|
|
* (Object) member: Map containing the member's jid and
|
|
|
|
* optionally a reason and affiliation.
|
|
|
|
*/
|
2017-07-12 09:55:43 +02:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const iq = $iq({to: chatroom_jid, type: "set"})
|
|
|
|
.c("query", {xmlns: Strophe.NS.MUC_ADMIN})
|
|
|
|
.c("item", {
|
|
|
|
'affiliation': member.affiliation || affiliation,
|
|
|
|
'jid': member.jid
|
|
|
|
});
|
|
|
|
if (!_.isUndefined(member.reason)) {
|
|
|
|
iq.c("reason", member.reason);
|
|
|
|
}
|
|
|
|
_converse.connection.sendIQ(iq, resolve, reject);
|
|
|
|
});
|
2017-01-26 15:49:02 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
setAffiliation (affiliation, members) {
|
2016-12-09 20:11:16 +01:00
|
|
|
/* Send IQ stanzas to the server to set an affiliation for
|
|
|
|
* the provided JIDs.
|
2016-12-08 08:28:20 +01:00
|
|
|
*
|
|
|
|
* See: http://xmpp.org/extensions/xep-0045.html#modifymember
|
|
|
|
*
|
2016-12-09 20:11:16 +01:00
|
|
|
* XXX: Prosody doesn't accept multiple JIDs' affiliations
|
|
|
|
* being set in one IQ stanza, so as a workaround we send
|
|
|
|
* a separate stanza for each JID.
|
|
|
|
* Related ticket: https://prosody.im/issues/issue/795
|
|
|
|
*
|
2016-12-08 08:28:20 +01:00
|
|
|
* Parameters:
|
2017-01-26 15:49:02 +01:00
|
|
|
* (String) affiliation: The affiliation
|
2016-12-08 08:28:20 +01:00
|
|
|
* (Object) members: A map of jids, affiliations and
|
|
|
|
* optionally reasons. Only those entries with the
|
|
|
|
* same affiliation as being currently set will be
|
|
|
|
* considered.
|
|
|
|
*
|
|
|
|
* Returns:
|
|
|
|
* A promise which resolves and fails depending on the
|
|
|
|
* XMPP server response.
|
|
|
|
*/
|
2017-07-10 17:46:22 +02:00
|
|
|
members = _.filter(members, (member) =>
|
2016-12-09 20:11:16 +01:00
|
|
|
// We only want those members who have the right
|
|
|
|
// affiliation (or none, which implies the provided
|
|
|
|
// one).
|
2017-07-10 17:46:22 +02:00
|
|
|
_.isUndefined(member.affiliation) ||
|
|
|
|
member.affiliation === affiliation
|
|
|
|
);
|
2017-06-12 20:30:58 +02:00
|
|
|
const promises = _.map(
|
2017-01-26 15:49:02 +01:00
|
|
|
members,
|
|
|
|
_.partial(this.sendAffiliationIQ, this.model.get('jid'), affiliation)
|
|
|
|
);
|
2017-07-12 09:55:43 +02:00
|
|
|
return Promise.all(promises);
|
2016-12-08 08:28:20 +01:00
|
|
|
},
|
|
|
|
|
2017-07-12 09:55:43 +02:00
|
|
|
setAffiliations (members) {
|
2016-12-08 08:28:20 +01:00
|
|
|
/* Send IQ stanzas to the server to modify the
|
2016-12-06 19:34:54 +01:00
|
|
|
* affiliations in this room.
|
2016-12-05 11:50:01 +01:00
|
|
|
*
|
|
|
|
* See: http://xmpp.org/extensions/xep-0045.html#modifymember
|
|
|
|
*
|
|
|
|
* Parameters:
|
2016-12-08 08:28:20 +01:00
|
|
|
* (Object) members: A map of jids, affiliations and optionally reasons
|
2016-12-05 11:50:01 +01:00
|
|
|
* (Function) onSuccess: callback for a succesful response
|
|
|
|
* (Function) onError: callback for an error response
|
|
|
|
*/
|
2017-06-12 20:30:58 +02:00
|
|
|
const affiliations = _.uniq(_.map(members, 'affiliation'));
|
2017-07-12 09:55:43 +02:00
|
|
|
_.each(affiliations, _.partial(this.setAffiliation.bind(this), _, members));
|
2016-12-05 11:50:01 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
marshallAffiliationIQs () {
|
2016-12-07 11:34:02 +01:00
|
|
|
/* Marshall a list of IQ stanzas into a map of JIDs and
|
|
|
|
* affiliations.
|
2016-12-07 12:30:28 +01:00
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* Any amount of XMLElement objects, representing the IQ
|
|
|
|
* stanzas.
|
2016-12-07 11:34:02 +01:00
|
|
|
*/
|
2017-12-23 21:28:46 +01:00
|
|
|
return _.flatMap(arguments[0], this.parseMemberListIQ);
|
2016-12-07 11:34:02 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
getJidsWithAffiliations (affiliations) {
|
2016-12-06 21:04:08 +01:00
|
|
|
/* Returns a map of JIDs that have the affiliations
|
|
|
|
* as provided.
|
|
|
|
*/
|
2017-01-26 15:49:02 +01:00
|
|
|
if (_.isString(affiliations)) {
|
2016-12-06 21:04:08 +01:00
|
|
|
affiliations = [affiliations];
|
|
|
|
}
|
2017-07-12 09:55:43 +02:00
|
|
|
return new Promise((resolve, reject) => {
|
2017-10-31 20:39:44 +01:00
|
|
|
const promises = _.map(
|
|
|
|
affiliations,
|
|
|
|
_.partial(this.requestMemberList, this.model.get('jid'))
|
|
|
|
);
|
|
|
|
|
2017-07-12 09:55:43 +02:00
|
|
|
Promise.all(promises).then(
|
|
|
|
_.flow(this.marshallAffiliationIQs.bind(this), resolve),
|
|
|
|
_.flow(this.marshallAffiliationIQs.bind(this), resolve)
|
|
|
|
);
|
|
|
|
});
|
2016-12-06 21:04:08 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
updateMemberLists (members, affiliations, deltaFunc) {
|
2016-12-06 20:39:16 +01:00
|
|
|
/* Fetch the lists of users with the given affiliations.
|
|
|
|
* Then compute the delta between those users and
|
2016-12-06 16:18:33 +01:00
|
|
|
* the passed in members, and if it exists, send the delta
|
|
|
|
* to the XMPP server to update the member list.
|
|
|
|
*
|
|
|
|
* Parameters:
|
2016-12-06 20:39:16 +01:00
|
|
|
* (Object) members: Map of member jids and affiliations.
|
2016-12-06 19:34:54 +01:00
|
|
|
* (String|Array) affiliation: An array of affiliations or
|
|
|
|
* a string if only one affiliation.
|
2016-12-06 21:04:08 +01:00
|
|
|
* (Function) deltaFunc: The function to compute the delta
|
|
|
|
* between old and new member lists.
|
2016-12-06 16:18:33 +01:00
|
|
|
*
|
|
|
|
* Returns:
|
|
|
|
* A promise which is resolved once the list has been
|
|
|
|
* updated or once it's been established there's no need
|
|
|
|
* to update the list.
|
|
|
|
*/
|
2017-06-12 20:30:58 +02:00
|
|
|
this.getJidsWithAffiliations(affiliations).then((old_members) => {
|
2017-07-12 09:55:43 +02:00
|
|
|
this.setAffiliations(deltaFunc(members, old_members));
|
2016-12-06 19:34:54 +01:00
|
|
|
});
|
2016-12-06 16:18:33 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
directInvite (recipient, reason) {
|
2016-12-03 17:39:59 +01:00
|
|
|
/* Send a direct invitation as per XEP-0249
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (String) recipient - JID of the person being invited
|
|
|
|
* (String) reason - Optional reason for the invitation
|
|
|
|
*/
|
2016-12-05 11:50:01 +01:00
|
|
|
if (this.model.get('membersonly')) {
|
|
|
|
// When inviting to a members-only room, we first add
|
2016-12-06 21:04:08 +01:00
|
|
|
// the person to the member list by giving them an
|
|
|
|
// affiliation of 'member' (if they're not affiliated
|
|
|
|
// already), otherwise they won't be able to join.
|
2017-06-12 20:30:58 +02:00
|
|
|
const map = {}; map[recipient] = 'member';
|
|
|
|
const deltaFunc = _.partial(this.computeAffiliationsDelta, true, false);
|
2016-12-07 12:30:28 +01:00
|
|
|
this.updateMemberLists(
|
|
|
|
[{'jid': recipient, 'affiliation': 'member', 'reason': reason}],
|
|
|
|
['member', 'owner', 'admin'],
|
|
|
|
deltaFunc
|
|
|
|
);
|
2016-12-05 11:50:01 +01:00
|
|
|
}
|
2017-06-12 20:30:58 +02:00
|
|
|
const attrs = {
|
2016-12-03 17:39:59 +01:00
|
|
|
'xmlns': 'jabber:x:conference',
|
|
|
|
'jid': this.model.get('jid')
|
2016-02-16 08:46:47 +01:00
|
|
|
};
|
|
|
|
if (reason !== null) { attrs.reason = reason; }
|
|
|
|
if (this.model.get('password')) { attrs.password = this.model.get('password'); }
|
2017-06-12 20:30:58 +02:00
|
|
|
const invitation = $msg({
|
2016-12-20 10:30:20 +01:00
|
|
|
from: _converse.connection.jid,
|
2016-03-01 23:26:36 +01:00
|
|
|
to: recipient,
|
2016-12-20 10:30:20 +01:00
|
|
|
id: _converse.connection.getUniqueId()
|
2016-02-16 08:46:47 +01:00
|
|
|
}).c('x', attrs);
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.connection.send(invitation);
|
|
|
|
_converse.emit('roomInviteSent', {
|
2016-03-01 23:26:36 +01:00
|
|
|
'room': this,
|
|
|
|
'recipient': recipient,
|
|
|
|
'reason': reason
|
|
|
|
});
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
handleChatStateMessage (message) {
|
2016-08-12 14:52:33 +02:00
|
|
|
/* Override the method on the ChatBoxView base class to
|
|
|
|
* ignore <gone/> notifications in groupchats.
|
|
|
|
*
|
|
|
|
* As laid out in the business rules in XEP-0085
|
|
|
|
* http://xmpp.org/extensions/xep-0085.html#bizrules-groupchat
|
|
|
|
*/
|
2016-08-12 16:40:04 +02:00
|
|
|
if (message.get('fullname') === this.model.get('nick')) {
|
|
|
|
// Don't know about other servers, but OpenFire sends
|
|
|
|
// back to you your own chat state notifications.
|
|
|
|
// We ignore them here...
|
|
|
|
return;
|
|
|
|
}
|
2016-12-20 10:30:20 +01:00
|
|
|
if (message.get('chat_state') !== _converse.GONE) {
|
|
|
|
_converse.ChatBoxView.prototype.handleChatStateMessage.apply(this, arguments);
|
2016-08-12 14:52:33 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
sendChatState () {
|
2016-08-12 16:40:04 +02:00
|
|
|
/* Sends a message with the status of the user in this chat session
|
|
|
|
* as taken from the 'chat_state' attribute of the chat box.
|
|
|
|
* See XEP-0085 Chat State Notifications.
|
|
|
|
*/
|
2017-07-21 15:05:22 +02:00
|
|
|
if (this.model.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
|
2017-02-17 22:17:19 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-06-12 20:30:58 +02:00
|
|
|
const chat_state = this.model.get('chat_state');
|
2016-12-20 10:30:20 +01:00
|
|
|
if (chat_state === _converse.GONE) {
|
2016-08-12 16:40:04 +02:00
|
|
|
// <gone/> is not applicable within MUC context
|
|
|
|
return;
|
|
|
|
}
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.connection.send(
|
2016-08-12 16:40:04 +02:00
|
|
|
$msg({'to':this.model.get('jid'), 'type': 'groupchat'})
|
|
|
|
.c(chat_state, {'xmlns': Strophe.NS.CHATSTATES}).up()
|
|
|
|
.c('no-store', {'xmlns': Strophe.NS.HINTS}).up()
|
|
|
|
.c('no-permanent-store', {'xmlns': Strophe.NS.HINTS})
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
sendChatRoomMessage (text) {
|
2016-12-04 10:43:39 +01:00
|
|
|
/* Constuct a message stanza to be sent to this chat room,
|
|
|
|
* and send it to the server.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (String) text: The message text to be sent.
|
|
|
|
*/
|
2017-06-12 20:30:58 +02:00
|
|
|
const msgid = _converse.connection.getUniqueId();
|
|
|
|
const msg = $msg({
|
2016-02-16 08:46:47 +01:00
|
|
|
to: this.model.get('jid'),
|
2016-12-20 10:30:20 +01:00
|
|
|
from: _converse.connection.jid,
|
2016-02-16 08:46:47 +01:00
|
|
|
type: 'groupchat',
|
|
|
|
id: msgid
|
|
|
|
}).c("body").t(text).up()
|
2016-12-20 10:30:20 +01:00
|
|
|
.c("x", {xmlns: "jabber:x:event"}).c(_converse.COMPOSING);
|
|
|
|
_converse.connection.send(msg);
|
2016-02-16 08:46:47 +01:00
|
|
|
this.model.messages.create({
|
2016-05-25 11:00:25 +02:00
|
|
|
fullname: this.model.get('nick'),
|
2016-02-16 08:46:47 +01:00
|
|
|
sender: 'me',
|
|
|
|
time: moment().format(),
|
|
|
|
message: text,
|
2017-07-10 17:46:22 +02:00
|
|
|
msgid
|
2016-02-16 08:46:47 +01:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
modifyRole(room, nick, role, reason, onSuccess, onError) {
|
|
|
|
const item = $build("item", {nick, role});
|
2017-06-12 20:30:58 +02:00
|
|
|
const iq = $iq({to: room, type: "set"}).c("query", {xmlns: Strophe.NS.MUC_ADMIN}).cnode(item.node);
|
2016-02-16 08:46:47 +01:00
|
|
|
if (reason !== null) { iq.c("reason", reason); }
|
2017-12-06 22:10:54 +01:00
|
|
|
return _converse.connection.sendIQ(iq, onSuccess, onError);
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
validateRoleChangeCommand (command, args) {
|
2016-02-16 08:46:47 +01:00
|
|
|
/* Check that a command to change a chat room user's role or
|
2016-03-19 23:16:00 +01:00
|
|
|
* affiliation has anough arguments.
|
|
|
|
*/
|
2016-02-16 08:46:47 +01:00
|
|
|
// TODO check if first argument is valid
|
|
|
|
if (args.length < 1 || args.length > 2) {
|
|
|
|
this.showStatusNotification(
|
2017-08-23 11:57:17 +02:00
|
|
|
__('Error: the "%1$s" command takes two arguments, the user\'s nickname and optionally a reason.',
|
|
|
|
command),
|
2016-02-16 08:46:47 +01:00
|
|
|
true
|
|
|
|
);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
clearChatRoomMessages (ev) {
|
2016-12-04 10:43:39 +01:00
|
|
|
/* Remove all messages from the chat room UI.
|
|
|
|
*/
|
2017-01-26 15:49:02 +01:00
|
|
|
if (!_.isUndefined(ev)) { ev.stopPropagation(); }
|
2017-06-12 20:30:58 +02:00
|
|
|
const result = confirm(__("Are you sure you want to clear the messages from this room?"));
|
2016-03-16 10:03:00 +01:00
|
|
|
if (result === true) {
|
2017-12-23 21:28:46 +01:00
|
|
|
this.content.innerHTML = '';
|
2016-03-16 10:03:00 +01:00
|
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
onCommandError () {
|
2016-12-03 17:39:59 +01:00
|
|
|
this.showStatusNotification(__("Error: could not execute the command"), true);
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
onMessageSubmitted (text) {
|
2016-02-16 08:46:47 +01:00
|
|
|
/* Gets called when the user presses enter to send off a
|
2016-03-19 23:16:00 +01:00
|
|
|
* message in a chat room.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (String) text - The message text.
|
|
|
|
*/
|
2016-12-20 10:30:20 +01:00
|
|
|
if (_converse.muc_disable_moderator_commands) {
|
2016-12-07 14:48:47 +01:00
|
|
|
return this.sendChatRoomMessage(text);
|
|
|
|
}
|
2017-06-12 20:30:58 +02:00
|
|
|
const match = text.replace(/^\s*/, "").match(/^\/(.*?)(?: (.*))?$/) || [false, '', ''],
|
2017-02-01 18:36:20 +01:00
|
|
|
args = match[2] && match[2].splitOnce(' ') || [],
|
|
|
|
command = match[1].toLowerCase();
|
|
|
|
switch (command) {
|
2016-02-16 08:46:47 +01:00
|
|
|
case 'admin':
|
2017-02-01 18:36:20 +01:00
|
|
|
if (!this.validateRoleChangeCommand(command, args)) { break; }
|
2016-12-08 08:28:20 +01:00
|
|
|
this.setAffiliation('admin',
|
|
|
|
[{ 'jid': args[0],
|
|
|
|
'reason': args[1]
|
2017-07-12 09:55:43 +02:00
|
|
|
}]).then(null, this.onCommandError.bind(this));
|
2016-02-16 08:46:47 +01:00
|
|
|
break;
|
|
|
|
case 'ban':
|
2017-02-01 18:36:20 +01:00
|
|
|
if (!this.validateRoleChangeCommand(command, args)) { break; }
|
2016-12-08 08:28:20 +01:00
|
|
|
this.setAffiliation('outcast',
|
|
|
|
[{ 'jid': args[0],
|
|
|
|
'reason': args[1]
|
2017-07-12 09:55:43 +02:00
|
|
|
}]).then(null, this.onCommandError.bind(this));
|
2016-02-16 08:46:47 +01:00
|
|
|
break;
|
|
|
|
case 'clear':
|
|
|
|
this.clearChatRoomMessages();
|
|
|
|
break;
|
|
|
|
case 'deop':
|
2017-02-01 18:36:20 +01:00
|
|
|
if (!this.validateRoleChangeCommand(command, args)) { break; }
|
2016-02-16 08:46:47 +01:00
|
|
|
this.modifyRole(
|
2017-12-14 14:07:39 +01:00
|
|
|
this.model.get('jid'), args[0], 'participant', args[1],
|
2016-02-16 08:46:47 +01:00
|
|
|
undefined, this.onCommandError.bind(this));
|
|
|
|
break;
|
|
|
|
case 'help':
|
|
|
|
this.showHelpMessages([
|
2017-07-10 17:46:22 +02:00
|
|
|
`<strong>/admin</strong>: ${__("Change user's affiliation to admin")}`,
|
|
|
|
`<strong>/ban</strong>: ${__('Ban user from room')}`,
|
|
|
|
`<strong>/clear</strong>: ${__('Remove messages')}`,
|
2017-12-14 14:07:39 +01:00
|
|
|
`<strong>/deop</strong>: ${__('Change user role to participant')}`,
|
2017-07-10 17:46:22 +02:00
|
|
|
`<strong>/help</strong>: ${__('Show this menu')}`,
|
|
|
|
`<strong>/kick</strong>: ${__('Kick user from room')}`,
|
|
|
|
`<strong>/me</strong>: ${__('Write in 3rd person')}`,
|
|
|
|
`<strong>/member</strong>: ${__('Grant membership to a user')}`,
|
|
|
|
`<strong>/mute</strong>: ${__("Remove user's ability to post messages")}`,
|
|
|
|
`<strong>/nick</strong>: ${__('Change your nickname')}`,
|
|
|
|
`<strong>/op</strong>: ${__('Grant moderator role to user')}`,
|
|
|
|
`<strong>/owner</strong>: ${__('Grant ownership of this room')}`,
|
|
|
|
`<strong>/revoke</strong>: ${__("Revoke user's membership")}`,
|
|
|
|
`<strong>/subject</strong>: ${__('Set room subject')}`,
|
|
|
|
`<strong>/topic</strong>: ${__('Set room subject (alias for /subject)')}`,
|
|
|
|
`<strong>/voice</strong>: ${__('Allow muted user to post messages')}`
|
2016-02-16 08:46:47 +01:00
|
|
|
]);
|
|
|
|
break;
|
|
|
|
case 'kick':
|
2017-02-01 18:36:20 +01:00
|
|
|
if (!this.validateRoleChangeCommand(command, args)) { break; }
|
2016-02-16 08:46:47 +01:00
|
|
|
this.modifyRole(
|
|
|
|
this.model.get('jid'), args[0], 'none', args[1],
|
|
|
|
undefined, this.onCommandError.bind(this));
|
|
|
|
break;
|
|
|
|
case 'mute':
|
2017-02-01 18:36:20 +01:00
|
|
|
if (!this.validateRoleChangeCommand(command, args)) { break; }
|
2016-02-16 08:46:47 +01:00
|
|
|
this.modifyRole(
|
|
|
|
this.model.get('jid'), args[0], 'visitor', args[1],
|
|
|
|
undefined, this.onCommandError.bind(this));
|
|
|
|
break;
|
|
|
|
case 'member':
|
2017-02-01 18:36:20 +01:00
|
|
|
if (!this.validateRoleChangeCommand(command, args)) { break; }
|
2016-12-08 08:28:20 +01:00
|
|
|
this.setAffiliation('member',
|
|
|
|
[{ 'jid': args[0],
|
|
|
|
'reason': args[1]
|
2017-07-12 09:55:43 +02:00
|
|
|
}]).then(null, this.onCommandError.bind(this));
|
2016-02-16 08:46:47 +01:00
|
|
|
break;
|
|
|
|
case 'nick':
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.connection.send($pres({
|
|
|
|
from: _converse.connection.jid,
|
2016-02-16 08:46:47 +01:00
|
|
|
to: this.getRoomJIDAndNick(match[2]),
|
2016-12-20 10:30:20 +01:00
|
|
|
id: _converse.connection.getUniqueId()
|
2016-02-16 08:46:47 +01:00
|
|
|
}).tree());
|
|
|
|
break;
|
|
|
|
case 'owner':
|
2017-02-01 18:36:20 +01:00
|
|
|
if (!this.validateRoleChangeCommand(command, args)) { break; }
|
2016-12-08 08:28:20 +01:00
|
|
|
this.setAffiliation('owner',
|
|
|
|
[{ 'jid': args[0],
|
|
|
|
'reason': args[1]
|
2017-07-12 09:55:43 +02:00
|
|
|
}]).then(null, this.onCommandError.bind(this));
|
2016-02-16 08:46:47 +01:00
|
|
|
break;
|
|
|
|
case 'op':
|
2017-02-01 18:36:20 +01:00
|
|
|
if (!this.validateRoleChangeCommand(command, args)) { break; }
|
2016-02-16 08:46:47 +01:00
|
|
|
this.modifyRole(
|
|
|
|
this.model.get('jid'), args[0], 'moderator', args[1],
|
|
|
|
undefined, this.onCommandError.bind(this));
|
|
|
|
break;
|
|
|
|
case 'revoke':
|
2017-02-01 18:36:20 +01:00
|
|
|
if (!this.validateRoleChangeCommand(command, args)) { break; }
|
2016-12-08 08:28:20 +01:00
|
|
|
this.setAffiliation('none',
|
|
|
|
[{ 'jid': args[0],
|
|
|
|
'reason': args[1]
|
2017-07-12 09:55:43 +02:00
|
|
|
}]).then(null, this.onCommandError.bind(this));
|
2016-02-16 08:46:47 +01:00
|
|
|
break;
|
|
|
|
case 'topic':
|
2017-02-01 18:36:20 +01:00
|
|
|
case 'subject':
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.connection.send(
|
2016-02-16 08:46:47 +01:00
|
|
|
$msg({
|
|
|
|
to: this.model.get('jid'),
|
2016-12-20 10:30:20 +01:00
|
|
|
from: _converse.connection.jid,
|
2016-02-16 08:46:47 +01:00
|
|
|
type: "groupchat"
|
|
|
|
}).c("subject", {xmlns: "jabber:client"}).t(match[2]).tree()
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
case 'voice':
|
2017-02-01 18:36:20 +01:00
|
|
|
if (!this.validateRoleChangeCommand(command, args)) { break; }
|
2016-02-16 08:46:47 +01:00
|
|
|
this.modifyRole(
|
2017-12-14 14:07:39 +01:00
|
|
|
this.model.get('jid'), args[0], 'participant', args[1],
|
2016-02-16 08:46:47 +01:00
|
|
|
undefined, this.onCommandError.bind(this));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
this.sendChatRoomMessage(text);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
handleMUCMessage (stanza) {
|
2016-12-04 10:43:39 +01:00
|
|
|
/* Handler for all MUC messages sent to this chat room.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (XMLElement) stanza: The message stanza.
|
|
|
|
*/
|
2017-06-12 20:30:58 +02:00
|
|
|
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']");
|
2016-12-04 13:05:25 +01:00
|
|
|
if (configuration_changed || logging_enabled || logging_disabled ||
|
|
|
|
room_no_longer_anon || room_now_semi_anon || room_now_fully_anon) {
|
2016-12-05 07:03:44 +01:00
|
|
|
this.getRoomFeatures();
|
2016-12-04 13:05:25 +01:00
|
|
|
}
|
2017-01-26 15:49:02 +01:00
|
|
|
_.flow(this.showStatusMessages.bind(this), this.onChatRoomMessage.bind(this))(stanza);
|
2016-02-16 08:46:47 +01:00
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
getRoomJIDAndNick (nick) {
|
2016-12-04 10:43:39 +01:00
|
|
|
/* 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
|
|
|
|
*/
|
2016-07-20 01:59:52 +02:00
|
|
|
if (nick) {
|
2016-07-20 15:59:02 +02:00
|
|
|
this.model.save({'nick': nick});
|
2016-07-20 01:59:52 +02:00
|
|
|
} else {
|
|
|
|
nick = this.model.get('nick');
|
|
|
|
}
|
2017-06-12 20:30:58 +02:00
|
|
|
const room = this.model.get('jid');
|
|
|
|
const node = Strophe.getNodeFromJid(room);
|
|
|
|
const domain = Strophe.getDomainFromJid(room);
|
2017-07-10 17:46:22 +02:00
|
|
|
return node + "@" + domain + (nick !== null ? `/${nick}` : "");
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
registerHandlers () {
|
2016-12-04 10:43:39 +01:00
|
|
|
/* Register presence and message handlers for this chat
|
|
|
|
* room
|
|
|
|
*/
|
2017-06-12 20:30:58 +02:00
|
|
|
const room_jid = this.model.get('jid');
|
2016-10-13 18:22:37 +02:00
|
|
|
this.removeHandlers();
|
2016-12-20 10:30:20 +01:00
|
|
|
this.presence_handler = _converse.connection.addHandler(
|
2016-10-13 18:22:37 +02:00
|
|
|
this.onChatRoomPresence.bind(this),
|
|
|
|
Strophe.NS.MUC, 'presence', null, null, room_jid,
|
|
|
|
{'ignoreNamespaceFragment': true, 'matchBareFromJid': true}
|
|
|
|
);
|
2016-12-20 10:30:20 +01:00
|
|
|
this.message_handler = _converse.connection.addHandler(
|
2016-10-13 18:22:37 +02:00
|
|
|
this.handleMUCMessage.bind(this),
|
2017-07-16 12:51:20 +02:00
|
|
|
null, 'message', 'groupchat', null, room_jid,
|
2016-10-13 18:22:37 +02:00
|
|
|
{'matchBareFromJid': true}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
removeHandlers () {
|
2016-12-04 10:43:39 +01:00
|
|
|
/* Remove the presence and message handlers that were
|
|
|
|
* registered for this chat room.
|
|
|
|
*/
|
2016-10-13 18:22:37 +02:00
|
|
|
if (this.message_handler) {
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.connection.deleteHandler(this.message_handler);
|
2016-10-13 18:22:37 +02:00
|
|
|
delete this.message_handler;
|
|
|
|
}
|
|
|
|
if (this.presence_handler) {
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.connection.deleteHandler(this.presence_handler);
|
2016-10-13 18:22:37 +02:00
|
|
|
delete this.presence_handler;
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
join (nick, password) {
|
2016-12-04 10:43:39 +01:00
|
|
|
/* Join the chat room.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (String) nick: The user's nickname
|
|
|
|
* (String) password: Optional password, if required by
|
|
|
|
* the room.
|
|
|
|
*/
|
2016-12-05 07:03:44 +01:00
|
|
|
nick = nick ? nick : this.model.get('nick');
|
|
|
|
if (!nick) {
|
|
|
|
return this.checkForReservedNick();
|
|
|
|
}
|
2017-07-21 15:05:22 +02:00
|
|
|
if (this.model.get('connection_status') === converse.ROOMSTATUS.ENTERED) {
|
2016-12-04 14:39:09 +01:00
|
|
|
// We have restored a chat room from session storage,
|
|
|
|
// so we don't send out a presence stanza again.
|
2016-12-08 14:23:17 +01:00
|
|
|
return this;
|
2016-12-04 14:39:09 +01:00
|
|
|
}
|
2017-06-12 20:30:58 +02:00
|
|
|
const stanza = $pres({
|
2016-12-20 10:30:20 +01:00
|
|
|
'from': _converse.connection.jid,
|
2016-07-20 01:59:52 +02:00
|
|
|
'to': this.getRoomJIDAndNick(nick)
|
|
|
|
}).c("x", {'xmlns': Strophe.NS.MUC})
|
2016-12-20 10:30:20 +01:00
|
|
|
.c("history", {'maxstanzas': _converse.muc_history_max_stanzas}).up();
|
2016-02-16 08:46:47 +01:00
|
|
|
if (password) {
|
|
|
|
stanza.cnode(Strophe.xmlElement("password", [], password));
|
|
|
|
}
|
2017-07-21 15:05:22 +02:00
|
|
|
this.model.save('connection_status', converse.ROOMSTATUS.CONNECTING);
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.connection.send(stanza);
|
2016-12-05 07:03:44 +01:00
|
|
|
return this;
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
sendUnavailablePresence (exit_msg) {
|
|
|
|
const presence = $pres({
|
2017-06-23 18:21:50 +02:00
|
|
|
type: "unavailable",
|
|
|
|
from: _converse.connection.jid,
|
|
|
|
to: this.getRoomJIDAndNick()
|
|
|
|
});
|
|
|
|
if (exit_msg !== null) {
|
|
|
|
presence.c("status", exit_msg);
|
2017-04-19 14:47:13 +02:00
|
|
|
}
|
2017-06-23 18:21:50 +02:00
|
|
|
_converse.connection.sendPresence(presence);
|
2016-11-02 15:51:23 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
leave(exit_msg) {
|
2016-12-04 10:43:39 +01:00
|
|
|
/* Leave the chat room.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (String) exit_msg: Optional message to indicate your
|
|
|
|
* reason for leaving.
|
|
|
|
*/
|
2016-12-05 11:48:13 +01:00
|
|
|
this.hide();
|
2017-10-31 23:04:46 +01:00
|
|
|
if (Backbone.history.getFragment() === "converse/room?jid="+this.model.get('jid')) {
|
|
|
|
_converse.router.navigate('');
|
|
|
|
}
|
2016-12-04 13:32:14 +01:00
|
|
|
this.occupantsview.model.reset();
|
|
|
|
this.occupantsview.model.browserStorage._clear();
|
2017-06-23 18:21:50 +02:00
|
|
|
if (_converse.connection.connected) {
|
|
|
|
this.sendUnavailablePresence(exit_msg);
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
2017-12-06 14:59:01 +01:00
|
|
|
u.safeSave(
|
2017-06-23 20:16:16 +02:00
|
|
|
this.model,
|
2017-07-21 15:05:22 +02:00
|
|
|
{'connection_status': converse.ROOMSTATUS.DISCONNECTED}
|
2016-11-02 15:51:23 +01:00
|
|
|
);
|
2017-06-23 18:21:50 +02:00
|
|
|
this.removeHandlers();
|
|
|
|
_converse.ChatBoxView.prototype.close.apply(this, arguments);
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
renderConfigurationForm (stanza) {
|
2016-12-03 17:39:59 +01:00
|
|
|
/* Renders a form given an IQ stanza containing the current
|
|
|
|
* room configuration.
|
|
|
|
*
|
|
|
|
* Returns a promise which resolves once the user has
|
|
|
|
* either submitted the form, or canceled it.
|
|
|
|
*
|
|
|
|
* Parameters:
|
2017-06-23 20:16:16 +02:00
|
|
|
* (XMLElement) stanza: The IQ stanza containing the room
|
|
|
|
* config.
|
2016-12-03 17:39:59 +01:00
|
|
|
*/
|
2017-12-06 14:59:01 +01:00
|
|
|
const container_el = this.el.querySelector('.chatroom-body');
|
|
|
|
_.each(container_el.querySelectorAll('.chatroom-form-container'), u.removeElement);
|
|
|
|
_.each(container_el.children, u.hideElement);
|
|
|
|
container_el.insertAdjacentHTML('beforeend', tpl_chatroom_form());
|
2016-12-03 17:39:59 +01:00
|
|
|
|
2017-12-23 21:28:46 +01:00
|
|
|
const form_el = container_el.querySelector('form.chatroom-form'),
|
|
|
|
fieldset_el = form_el.querySelector('fieldset:first-child'),
|
|
|
|
fields = stanza.querySelectorAll('field'),
|
|
|
|
title = _.get(stanza.querySelector('title'), 'textContent'),
|
|
|
|
instructions = _.get(stanza.querySelector('instructions'), 'textContent');
|
|
|
|
|
|
|
|
u.removeElement(fieldset_el.querySelector('span.spinner'));
|
|
|
|
fieldset_el.insertAdjacentHTML('beforeend', `<legend>${title}</legend>`);
|
|
|
|
|
2016-02-16 08:46:47 +01:00
|
|
|
if (instructions && instructions !== title) {
|
2017-12-23 21:28:46 +01:00
|
|
|
fieldset_el.insertAdjacentHTML('beforeend', `<p class="instructions">${instructions}</p>`);
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
2017-12-23 21:28:46 +01:00
|
|
|
_.each(fields, function (field) {
|
|
|
|
fieldset_el.insertAdjacentHTML('beforeend', u.xForm2webForm(field, stanza));
|
2016-02-16 08:46:47 +01:00
|
|
|
});
|
2017-12-23 21:28:46 +01:00
|
|
|
|
|
|
|
// Render save/cancel buttons
|
|
|
|
const last_fieldset_el = document.createElement('fieldset');
|
|
|
|
last_fieldset_el.insertAdjacentHTML(
|
|
|
|
'beforeend',
|
|
|
|
`<input type="submit" class="pure-button button-primary" value="${__('Save')}"/>`);
|
|
|
|
last_fieldset_el.insertAdjacentHTML(
|
|
|
|
'beforeend',
|
|
|
|
`<input type="button" class="pure-button button-cancel" value="${__('Cancel')}"/>`);
|
|
|
|
form_el.insertAdjacentElement('beforeend', last_fieldset_el);
|
|
|
|
|
|
|
|
last_fieldset_el.querySelector('input[type=button]').addEventListener('click', (ev) => {
|
2016-12-03 17:39:59 +01:00
|
|
|
ev.preventDefault();
|
2017-12-06 14:59:01 +01:00
|
|
|
this.closeForm();
|
2016-12-03 17:39:59 +01:00
|
|
|
});
|
2017-12-06 22:10:54 +01:00
|
|
|
|
2017-12-23 21:28:46 +01:00
|
|
|
form_el.addEventListener('submit', (ev) => {
|
2017-12-06 22:10:54 +01:00
|
|
|
ev.preventDefault();
|
|
|
|
this.saveConfiguration(ev.target).then(
|
|
|
|
this.getRoomFeatures.bind(this)
|
|
|
|
);
|
|
|
|
},
|
|
|
|
false
|
|
|
|
);
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
sendConfiguration(config, onSuccess, onError) {
|
2016-12-03 17:39:59 +01:00
|
|
|
/* Send an IQ stanza with the room configuration.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (Array) config: The room configuration
|
|
|
|
* (Function) onSuccess: 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) onError: 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.
|
|
|
|
*/
|
2017-06-12 20:30:58 +02:00
|
|
|
const iq = $iq({to: this.model.get('jid'), type: "set"})
|
2016-02-16 08:46:47 +01:00
|
|
|
.c("query", {xmlns: Strophe.NS.MUC_OWNER})
|
|
|
|
.c("x", {xmlns: Strophe.NS.XFORM, type: "submit"});
|
2016-12-03 17:39:59 +01:00
|
|
|
_.each(config || [], function (node) { iq.cnode(node).up(); });
|
|
|
|
onSuccess = _.isUndefined(onSuccess) ? _.noop : _.partial(onSuccess, iq.nodeTree);
|
|
|
|
onError = _.isUndefined(onError) ? _.noop : _.partial(onError, iq.nodeTree);
|
2016-12-20 10:30:20 +01:00
|
|
|
return _converse.connection.sendIQ(iq, onSuccess, onError);
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
saveConfiguration (form) {
|
2016-12-03 17:39:59 +01:00
|
|
|
/* 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.
|
|
|
|
*/
|
2017-07-12 09:55:43 +02:00
|
|
|
return new Promise((resolve, reject) => {
|
2017-12-23 21:28:46 +01:00
|
|
|
const inputs = form ? sizzle(':input:not([type=button]):not([type=submit])', form) : [],
|
|
|
|
configArray = _.map(inputs, u.webForm2xForm);
|
2017-07-12 09:55:43 +02:00
|
|
|
this.sendConfiguration(configArray, resolve, reject);
|
2017-12-06 14:59:01 +01:00
|
|
|
this.closeForm();
|
2017-06-12 20:30:58 +02:00
|
|
|
});
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
autoConfigureChatRoom () {
|
2016-11-20 17:42:11 +01:00
|
|
|
/* Automatically configure room based on the
|
2017-02-18 18:15:16 +01:00
|
|
|
* 'roomconfig' data on this view's model.
|
2016-12-03 17:39:59 +01:00
|
|
|
*
|
|
|
|
* Returns a promise which resolves once a response IQ has
|
|
|
|
* been received.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (XMLElement) stanza: IQ stanza from the server,
|
|
|
|
* containing the configuration.
|
2016-11-20 17:42:11 +01:00
|
|
|
*/
|
2017-07-10 17:46:22 +02:00
|
|
|
const that = this;
|
2017-07-12 09:55:43 +02:00
|
|
|
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);
|
2017-04-13 13:18:52 +02:00
|
|
|
}
|
2017-07-12 09:55:43 +02:00
|
|
|
configArray.push(field);
|
|
|
|
if (!--count) {
|
|
|
|
that.sendConfiguration(configArray, resolve, reject);
|
|
|
|
}
|
|
|
|
});
|
2017-04-13 13:18:52 +02:00
|
|
|
});
|
2016-11-20 17:42:11 +01:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2017-12-06 14:59:01 +01:00
|
|
|
closeForm () {
|
2016-12-03 17:39:59 +01:00
|
|
|
/* Remove the configuration form without submitting and
|
|
|
|
* return to the chat view.
|
|
|
|
*/
|
2017-12-06 14:59:01 +01:00
|
|
|
u.removeElement(this.el.querySelector('.chatroom-form-container'));
|
|
|
|
this.renderAfterTransition();
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
fetchRoomConfiguration (handler) {
|
2016-12-03 17:39:59 +01:00
|
|
|
/* 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
|
|
|
|
*/
|
2017-07-12 09:55:43 +02:00
|
|
|
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
|
|
|
|
);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
parseRoomFeatures (iq) {
|
|
|
|
/* See http://xmpp.org/extensions/xep-0045.html#disco-roominfo
|
|
|
|
*
|
|
|
|
* <identity
|
|
|
|
* category='conference'
|
|
|
|
* name='A Dark Cave'
|
|
|
|
* type='text'/>
|
|
|
|
* <feature var='http://jabber.org/protocol/muc'/>
|
|
|
|
* <feature var='muc_passwordprotected'/>
|
|
|
|
* <feature var='muc_hidden'/>
|
|
|
|
* <feature var='muc_temporary'/>
|
|
|
|
* <feature var='muc_open'/>
|
|
|
|
* <feature var='muc_unmoderated'/>
|
|
|
|
* <feature var='muc_nonanonymous'/>
|
|
|
|
* <feature var='urn:xmpp:mam:0'/>
|
|
|
|
*/
|
|
|
|
const features = {
|
2017-07-17 17:10:25 +02:00
|
|
|
'features_fetched': true,
|
|
|
|
'name': iq.querySelector('identity').getAttribute('name')
|
|
|
|
}
|
2017-07-12 09:55:43 +02:00
|
|
|
_.each(iq.querySelectorAll('feature'), function (field) {
|
|
|
|
const fieldname = field.getAttribute('var');
|
|
|
|
if (!fieldname.startsWith('muc_')) {
|
|
|
|
if (fieldname === Strophe.NS.MAM) {
|
|
|
|
features.mam_enabled = true;
|
2016-12-03 17:39:59 +01:00
|
|
|
}
|
2017-07-12 09:55:43 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
features[fieldname.replace('muc_', '')] = true;
|
|
|
|
});
|
|
|
|
const desc_field = iq.querySelector('field[var="muc#roominfo_description"] value');
|
|
|
|
if (!_.isNull(desc_field)) {
|
|
|
|
features.description = desc_field.textContent;
|
|
|
|
}
|
|
|
|
this.model.save(features);
|
2016-12-03 17:39:59 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
getRoomFeatures () {
|
2016-12-04 13:05:25 +01:00
|
|
|
/* Fetch the room disco info, parse it and then
|
2016-12-03 17:39:59 +01:00
|
|
|
* save it on the Backbone.Model of this chat rooms.
|
|
|
|
*/
|
2017-07-12 09:55:43 +02:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
_converse.connection.disco.info(
|
|
|
|
this.model.get('jid'),
|
|
|
|
null,
|
|
|
|
_.flow(this.parseRoomFeatures.bind(this), resolve),
|
|
|
|
() => { reject(new Error("Could not parse the room features")) },
|
|
|
|
5000
|
|
|
|
);
|
|
|
|
});
|
2016-12-03 17:39:59 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
getAndRenderConfigurationForm (ev) {
|
2016-12-03 17:39:59 +01:00
|
|
|
/* Start the process of configuring a chat room, either by
|
|
|
|
* rendering a configuration form, or by auto-configuring
|
|
|
|
* based on the "roomconfig" data stored on the
|
|
|
|
* Backbone.Model.
|
|
|
|
*
|
|
|
|
* Stores the new configuration on the Backbone.Model once
|
|
|
|
* completed.
|
|
|
|
*
|
|
|
|
* Paremeters:
|
|
|
|
* (Event) ev: DOM event that might be passed in if this
|
|
|
|
* method is called due to a user action. In this
|
|
|
|
* case, auto-configure won't happen, regardless of
|
|
|
|
* the settings.
|
|
|
|
*/
|
2017-04-13 13:18:52 +02:00
|
|
|
this.showSpinner();
|
2017-11-05 18:47:30 +01:00
|
|
|
this.fetchRoomConfiguration()
|
|
|
|
.then(this.renderConfigurationForm.bind(this))
|
|
|
|
.catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
submitNickname (ev) {
|
2016-12-04 10:43:39 +01:00
|
|
|
/* Get the nickname value from the form and then join the
|
|
|
|
* chat room with it.
|
|
|
|
*/
|
2016-07-19 16:18:40 +02:00
|
|
|
ev.preventDefault();
|
2017-06-12 20:30:58 +02:00
|
|
|
const nick_el = ev.target.nick;
|
|
|
|
const nick = nick_el.value;
|
2016-07-19 16:18:40 +02:00
|
|
|
if (!nick) {
|
2017-05-24 17:18:40 +02:00
|
|
|
nick_el.classList.add('error');
|
2016-07-19 16:18:40 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
2017-05-24 17:18:40 +02:00
|
|
|
nick_el.classList.remove('error');
|
2016-07-19 16:18:40 +02:00
|
|
|
}
|
2017-12-06 14:59:01 +01:00
|
|
|
this.el.querySelector('.chatroom-form-container').outerHTML = tpl_spinner();
|
2016-07-20 01:59:52 +02:00
|
|
|
this.join(nick);
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
checkForReservedNick () {
|
2016-09-23 09:41:55 +02:00
|
|
|
/* User service-discovery to ask the XMPP server whether
|
2016-07-20 01:59:52 +02:00
|
|
|
* this user has a reserved nickname for this room.
|
|
|
|
* If so, we'll use that, otherwise we render the nickname
|
|
|
|
* form.
|
|
|
|
*/
|
|
|
|
this.showSpinner();
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.connection.sendIQ(
|
2016-07-20 01:59:52 +02:00
|
|
|
$iq({
|
|
|
|
'to': this.model.get('jid'),
|
2016-12-20 10:30:20 +01:00
|
|
|
'from': _converse.connection.jid,
|
2016-07-20 01:59:52 +02:00
|
|
|
'type': "get"
|
|
|
|
}).c("query", {
|
|
|
|
'xmlns': Strophe.NS.DISCO_INFO,
|
|
|
|
'node': 'x-roomuser-item'
|
|
|
|
}),
|
|
|
|
this.onNickNameFound.bind(this),
|
2016-08-11 14:00:52 +02:00
|
|
|
this.onNickNameNotFound.bind(this)
|
2016-07-20 01:59:52 +02:00
|
|
|
);
|
2016-12-05 07:03:44 +01:00
|
|
|
return this;
|
2016-07-20 01:59:52 +02:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
onNickNameFound (iq) {
|
2016-07-20 01:59:52 +02:00
|
|
|
/* We've received an IQ response from the server which
|
|
|
|
* might contain the user's reserved nickname.
|
2016-12-04 10:43:39 +01:00
|
|
|
* If no nickname is found we either render a form for
|
|
|
|
* them to specify one, or we try to join the room with the
|
|
|
|
* node of the user's JID.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (XMLElement) iq: The received IQ stanza
|
2016-07-20 01:59:52 +02:00
|
|
|
*/
|
2017-12-23 21:28:46 +01:00
|
|
|
const identity_el = iq.querySelector('query[node="x-roomuser-item"] identity'),
|
|
|
|
nick = identity_el ? identity_el.getAttribute('name') : null;
|
2016-07-20 01:59:52 +02:00
|
|
|
if (!nick) {
|
2016-08-11 14:00:52 +02:00
|
|
|
this.onNickNameNotFound();
|
2016-07-20 01:59:52 +02:00
|
|
|
} else {
|
|
|
|
this.join(nick);
|
|
|
|
}
|
2016-07-19 16:18:40 +02:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
onNickNameNotFound (message) {
|
2016-12-20 10:30:20 +01:00
|
|
|
if (_converse.muc_nickname_from_jid) {
|
2016-08-11 14:00:52 +02:00
|
|
|
// We try to enter the room with the node part of
|
|
|
|
// the user's JID.
|
2017-08-28 09:27:59 +02:00
|
|
|
this.join(this.getDefaultNickName());
|
2016-08-11 14:00:52 +02:00
|
|
|
} else {
|
|
|
|
this.renderNicknameForm(message);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
getDefaultNickName () {
|
2016-08-11 14:19:43 +02:00
|
|
|
/* The default nickname (used when muc_nickname_from_jid is true)
|
|
|
|
* is the node part of the user's JID.
|
|
|
|
* We put this in a separate method so that it can be
|
|
|
|
* overridden by plugins.
|
|
|
|
*/
|
2016-12-20 10:30:20 +01:00
|
|
|
return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid));
|
2016-08-11 14:19:43 +02:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
onNicknameClash (presence) {
|
2016-08-11 14:00:52 +02:00
|
|
|
/* When the nickname is already taken, we either render a
|
|
|
|
* form for the user to choose a new nickname, or we
|
|
|
|
* try to make the nickname unique by adding an integer to
|
|
|
|
* it. So john will become john-2, and then john-3 and so on.
|
|
|
|
*
|
|
|
|
* Which option is take depends on the value of
|
|
|
|
* muc_nickname_from_jid.
|
|
|
|
*/
|
2016-12-20 10:30:20 +01:00
|
|
|
if (_converse.muc_nickname_from_jid) {
|
2017-06-12 20:30:58 +02:00
|
|
|
const nick = presence.getAttribute('from').split('/')[1];
|
2016-08-11 14:19:43 +02:00
|
|
|
if (nick === this.getDefaultNickName()) {
|
2016-08-11 14:00:52 +02:00
|
|
|
this.join(nick + '-2');
|
|
|
|
} else {
|
2017-06-12 20:30:58 +02:00
|
|
|
const del= nick.lastIndexOf("-");
|
|
|
|
const num = nick.substring(del+1, nick.length);
|
2016-08-11 14:00:52 +02:00
|
|
|
this.join(nick.substring(0, del+1) + String(Number(num)+1));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.renderNicknameForm(
|
2017-03-02 23:06:40 +01:00
|
|
|
__("The nickname you chose is reserved or "+
|
|
|
|
"currently in use, please choose a different one.")
|
2016-08-11 14:00:52 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-12-06 14:59:01 +01:00
|
|
|
hideChatRoomContents () {
|
|
|
|
const container_el = this.el.querySelector('.chatroom-body');
|
|
|
|
if (!_.isNull(container_el)) {
|
|
|
|
_.each(container_el.children, (child) => { child.classList.add('hidden'); });
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
renderNicknameForm (message) {
|
2016-08-11 14:00:52 +02:00
|
|
|
/* Render a form which allows the user to choose their
|
|
|
|
* nickname.
|
|
|
|
*/
|
2017-12-06 14:59:01 +01:00
|
|
|
this.hideChatRoomContents();
|
|
|
|
_.each(this.el.querySelectorAll('span.centered.spinner'), u.removeElement);
|
2017-01-26 15:49:02 +01:00
|
|
|
if (!_.isString(message)) {
|
2016-07-20 10:16:08 +02:00
|
|
|
message = '';
|
|
|
|
}
|
2017-12-06 14:59:01 +01:00
|
|
|
const container_el = this.el.querySelector('.chatroom-body');
|
|
|
|
container_el.insertAdjacentHTML(
|
|
|
|
'beforeend',
|
2017-02-19 10:58:30 +01:00
|
|
|
tpl_chatroom_nickname_form({
|
2016-07-19 16:18:40 +02:00
|
|
|
heading: __('Please choose your nickname'),
|
|
|
|
label_nickname: __('Nickname'),
|
2016-07-20 10:16:08 +02:00
|
|
|
label_join: __('Enter room'),
|
|
|
|
validation_message: message
|
2016-07-19 16:18:40 +02:00
|
|
|
}));
|
2017-07-21 15:05:22 +02:00
|
|
|
this.model.save('connection_status', converse.ROOMSTATUS.NICKNAME_REQUIRED);
|
2017-12-06 14:59:01 +01:00
|
|
|
|
|
|
|
const form_el = this.el.querySelector('.chatroom-form');
|
2017-12-06 22:10:54 +01:00
|
|
|
form_el.addEventListener('submit', this.submitNickname.bind(this), false);
|
2016-07-19 16:18:40 +02:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
submitPassword (ev) {
|
2016-02-16 08:46:47 +01:00
|
|
|
ev.preventDefault();
|
2017-12-23 21:28:46 +01:00
|
|
|
const password = this.el.querySelector('.chatroom-form input[type=password]').value;
|
|
|
|
this.showSpinner();
|
2016-07-20 01:59:52 +02:00
|
|
|
this.join(this.model.get('nick'), password);
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
renderPasswordForm () {
|
2017-12-06 14:59:01 +01:00
|
|
|
const container_el = this.el.querySelector('.chatroom-body');
|
|
|
|
_.each(container_el.children, u.hideElement);
|
|
|
|
_.each(this.el.querySelectorAll('.spinner'), u.removeElement);
|
|
|
|
|
|
|
|
container_el.insertAdjacentHTML('beforeend',
|
2017-02-19 10:58:30 +01:00
|
|
|
tpl_chatroom_password_form({
|
2016-02-16 08:46:47 +01:00
|
|
|
heading: __('This chatroom requires a password'),
|
|
|
|
label_password: __('Password: '),
|
|
|
|
label_submit: __('Submit')
|
|
|
|
}));
|
2017-12-06 14:59:01 +01:00
|
|
|
|
2017-07-21 15:05:22 +02:00
|
|
|
this.model.save('connection_status', converse.ROOMSTATUS.PASSWORD_REQUIRED);
|
2017-12-06 22:10:54 +01:00
|
|
|
this.el.querySelector('.chatroom-form').addEventListener(
|
|
|
|
'submit', this.submitPassword.bind(this), false);
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
showDisconnectMessage (msg) {
|
2017-12-06 14:59:01 +01:00
|
|
|
u.hideElement(this.el.querySelector('.chat-area'));
|
|
|
|
u.hideElement(this.el.querySelector('.occupants'));
|
|
|
|
_.each(this.el.querySelectorAll('.spinner'), u.removeElement);
|
|
|
|
this.el.querySelector('.chatroom-body').insertAdjacentHTML(
|
|
|
|
'beforeend',
|
|
|
|
tpl_chatroom_disconnect({
|
|
|
|
'disconnect_message': msg
|
|
|
|
})
|
|
|
|
);
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
getMessageFromStatus (stat, stanza, is_self) {
|
2016-12-02 18:41:05 +01:00
|
|
|
/* Parameters:
|
|
|
|
* (XMLElement) stat: A <status> element.
|
|
|
|
* (Boolean) is_self: Whether the element refers to the
|
|
|
|
* current user.
|
|
|
|
* (XMLElement) stanza: The original stanza received.
|
|
|
|
*/
|
2017-06-12 20:30:58 +02:00
|
|
|
const code = stat.getAttribute('code');
|
2017-02-24 11:54:54 +01:00
|
|
|
if (code === '110') { return; }
|
|
|
|
if (code in _converse.muc.info_messages) {
|
2016-12-20 10:30:20 +01:00
|
|
|
return _converse.muc.info_messages[code];
|
2017-02-24 11:54:54 +01:00
|
|
|
}
|
2017-06-12 20:30:58 +02:00
|
|
|
let nick;
|
2017-02-24 11:54:54 +01:00
|
|
|
if (!is_self) {
|
|
|
|
if (code in _converse.muc.action_info_messages) {
|
|
|
|
nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
|
|
|
|
return __(_converse.muc.action_info_messages[code], nick);
|
|
|
|
}
|
|
|
|
} else if (code in _converse.muc.new_nickname_messages) {
|
|
|
|
if (is_self && code === "210") {
|
|
|
|
nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
|
|
|
|
} else if (is_self && code === "303") {
|
|
|
|
nick = stanza.querySelector('x item').getAttribute('nick');
|
2016-11-21 13:55:19 +01:00
|
|
|
}
|
2017-02-24 11:54:54 +01:00
|
|
|
return __(_converse.muc.new_nickname_messages[code], nick);
|
2016-11-21 13:55:19 +01:00
|
|
|
}
|
|
|
|
return;
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
saveAffiliationAndRole (pres) {
|
2016-12-03 17:39:59 +01:00
|
|
|
/* Parse the presence stanza for the current user's
|
|
|
|
* affiliation.
|
2016-12-02 18:41:05 +01:00
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (XMLElement) pres: A <presence> stanza.
|
2016-03-19 23:16:00 +01:00
|
|
|
*/
|
2017-07-10 17:46:22 +02:00
|
|
|
const item = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"] item`, pres).pop();
|
2017-06-12 20:30:58 +02:00
|
|
|
const is_self = pres.querySelector("status[code='110']");
|
2017-06-27 12:56:50 +02:00
|
|
|
if (is_self && !_.isNil(item)) {
|
2017-06-12 20:30:58 +02:00
|
|
|
const affiliation = item.getAttribute('affiliation');
|
|
|
|
const role = item.getAttribute('role');
|
2016-12-05 07:03:44 +01:00
|
|
|
if (affiliation) {
|
|
|
|
this.model.save({'affiliation': affiliation});
|
|
|
|
}
|
|
|
|
if (role) {
|
|
|
|
this.model.save({'role': role});
|
|
|
|
}
|
2016-11-21 13:55:19 +01:00
|
|
|
}
|
2016-12-02 18:41:05 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
parseXUserElement (x, stanza, is_self) {
|
2016-12-02 18:41:05 +01:00
|
|
|
/* Parse the passed-in <x xmlns='http://jabber.org/protocol/muc#user'>
|
|
|
|
* element and construct a map containing relevant
|
|
|
|
* information.
|
|
|
|
*/
|
|
|
|
// 1. Get notification messages based on the <status> elements.
|
2017-06-12 20:30:58 +02:00
|
|
|
const statuses = x.querySelectorAll('status');
|
|
|
|
const mapper = _.partial(this.getMessageFromStatus, _, stanza, is_self);
|
|
|
|
const notification = {};
|
|
|
|
const messages = _.reject(_.map(statuses, mapper), _.isUndefined);
|
2017-02-24 11:54:54 +01:00
|
|
|
if (messages.length) {
|
|
|
|
notification.messages = messages;
|
|
|
|
}
|
2016-12-02 18:41:05 +01:00
|
|
|
// 2. Get disconnection messages based on the <status> elements
|
2017-06-12 20:30:58 +02:00
|
|
|
const codes = _.invokeMap(statuses, Element.prototype.getAttribute, 'code');
|
|
|
|
const disconnection_codes = _.intersection(codes, _.keys(_converse.muc.disconnect_messages));
|
|
|
|
const disconnected = is_self && disconnection_codes.length > 0;
|
2016-11-21 13:55:19 +01:00
|
|
|
if (disconnected) {
|
|
|
|
notification.disconnected = true;
|
2016-12-20 10:30:20 +01:00
|
|
|
notification.disconnection_message = _converse.muc.disconnect_messages[disconnection_codes[0]];
|
2016-11-21 13:55:19 +01:00
|
|
|
}
|
2016-12-02 18:41:05 +01:00
|
|
|
// 3. Find the reason and actor from the <item> element
|
2017-06-12 20:30:58 +02:00
|
|
|
const item = x.querySelector('item');
|
2016-12-02 18:41:05 +01:00
|
|
|
// By using querySelector above, we assume here there is
|
|
|
|
// one <item> per <x xmlns='http://jabber.org/protocol/muc#user'>
|
|
|
|
// element. This appears to be a safe assumption, since
|
|
|
|
// each <x/> element pertains to a single user.
|
|
|
|
if (!_.isNull(item)) {
|
2017-06-12 20:30:58 +02:00
|
|
|
const reason = item.querySelector('reason');
|
2016-12-02 18:41:05 +01:00
|
|
|
if (reason) {
|
|
|
|
notification.reason = reason ? reason.textContent : undefined;
|
|
|
|
}
|
2017-06-12 20:30:58 +02:00
|
|
|
const actor = item.querySelector('actor');
|
2016-12-02 18:41:05 +01:00
|
|
|
if (actor) {
|
|
|
|
notification.actor = actor ? actor.getAttribute('nick') : undefined;
|
|
|
|
}
|
|
|
|
}
|
2016-11-21 13:55:19 +01:00
|
|
|
return notification;
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
displayNotificationsforUser (notification) {
|
2016-11-21 13:55:19 +01:00
|
|
|
/* Given the notification object generated by
|
|
|
|
* parseXUserElement, display any relevant messages and
|
|
|
|
* information to the user.
|
2016-03-19 23:16:00 +01:00
|
|
|
*/
|
2016-11-21 13:55:19 +01:00
|
|
|
if (notification.disconnected) {
|
|
|
|
this.showDisconnectMessage(notification.disconnection_message);
|
2016-11-21 14:10:36 +01:00
|
|
|
if (notification.actor) {
|
2017-08-23 11:57:17 +02:00
|
|
|
this.showDisconnectMessage(__('This action was done by %1$s.', notification.actor));
|
2016-11-21 14:10:36 +01:00
|
|
|
}
|
2016-11-21 13:55:19 +01:00
|
|
|
if (notification.reason) {
|
2017-08-23 11:57:17 +02:00
|
|
|
this.showDisconnectMessage(__('The reason given is: "%1$s".', notification.reason));
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
2017-07-21 15:05:22 +02:00
|
|
|
this.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
|
2016-02-16 08:46:47 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-06-12 20:30:58 +02:00
|
|
|
_.each(notification.messages, (message) => {
|
2017-12-15 17:24:30 +01:00
|
|
|
this.content.insertAdjacentHTML('beforeend', tpl_info({'message': message, 'data': ''}));
|
2016-11-21 13:55:19 +01:00
|
|
|
});
|
|
|
|
if (notification.reason) {
|
2017-11-23 12:21:59 +01:00
|
|
|
this.showStatusNotification(__('The reason given is: "%1$s".', notification.reason), true);
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
2017-12-14 14:45:14 +01:00
|
|
|
if (_.get(notification.messages, 'length')) {
|
2016-05-27 14:53:07 +02:00
|
|
|
this.scrollDown();
|
|
|
|
}
|
2016-11-21 13:55:19 +01:00
|
|
|
},
|
|
|
|
|
2017-12-15 17:24:30 +01:00
|
|
|
displayJoinNotification (stanza) {
|
2017-06-12 20:30:58 +02:00
|
|
|
const nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
|
|
|
|
const stat = stanza.querySelector('status');
|
2017-12-22 21:49:10 +01:00
|
|
|
const last_el = this.content.querySelector('.message:last-child');
|
2017-12-15 17:24:30 +01:00
|
|
|
if (_.includes(_.get(last_el, 'classList', []), 'chat-info') &&
|
|
|
|
_.get(last_el, 'dataset', {}).leave === `"${nick}"`) {
|
|
|
|
last_el.outerHTML =
|
|
|
|
tpl_info({
|
|
|
|
'message': __(nick+' has left and re-entered the room.'),
|
|
|
|
'data': `data-leavejoin="${nick}"`
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
let message = __(nick+' has entered the room.');
|
|
|
|
if (_.get(stat, 'textContent')) {
|
|
|
|
message = message + ' "' + stat.textContent + '"';
|
|
|
|
}
|
|
|
|
const data = {
|
|
|
|
'message': message,
|
|
|
|
'data': `data-join="${nick}"`
|
|
|
|
};
|
|
|
|
if (_.includes(_.get(last_el, 'classList', []), 'chat-info') &&
|
|
|
|
_.get(last_el, 'dataset', {}).joinleave === `"${nick}"`) {
|
|
|
|
|
|
|
|
last_el.outerHTML = tpl_info(data);
|
2017-02-24 11:54:54 +01:00
|
|
|
} else {
|
2017-12-15 17:24:30 +01:00
|
|
|
this.content.insertAdjacentHTML('beforeend', tpl_info(data));
|
2017-02-24 11:54:54 +01:00
|
|
|
}
|
|
|
|
}
|
2017-12-15 17:24:30 +01:00
|
|
|
this.scrollDown();
|
|
|
|
},
|
|
|
|
|
|
|
|
displayLeaveNotification (stanza) {
|
|
|
|
const nick = Strophe.getResourceFromJid(stanza.getAttribute('from'));
|
|
|
|
const stat = stanza.querySelector('status');
|
|
|
|
const last_el = this.content.querySelector(':last-child');
|
|
|
|
if (_.includes(_.get(last_el, 'classList', []), 'chat-info') &&
|
|
|
|
_.get(last_el, 'dataset', {}).join === `"${nick}"`) {
|
|
|
|
|
|
|
|
let message = __('%1$s has entered and left the room.', nick);
|
|
|
|
if (_.get(stat, 'textContent')) {
|
|
|
|
message = message + ' "' + stat.textContent + '"';
|
|
|
|
}
|
|
|
|
last_el.outerHTML =
|
|
|
|
tpl_info({
|
|
|
|
'message': message,
|
|
|
|
'data': `data-joinleave="${nick}"`
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
let message = __('%1$s has left the room.', nick);
|
|
|
|
if (_.get(stat, 'textContent')) {
|
|
|
|
message = message + ' "' + stat.textContent + '"';
|
|
|
|
}
|
|
|
|
const data = {
|
|
|
|
'message': message,
|
|
|
|
'data': `data-leave="${nick}"`
|
|
|
|
}
|
|
|
|
if (_.includes(_.get(last_el, 'classList', []), 'chat-info') &&
|
|
|
|
_.get(last_el, 'dataset', {}).leavejoin === `"${nick}"`) {
|
|
|
|
|
|
|
|
last_el.outerHTML = tpl_info(data);
|
2017-02-28 05:49:01 +01:00
|
|
|
} else {
|
2017-12-15 17:24:30 +01:00
|
|
|
this.content.insertAdjacentHTML('beforeend', tpl_info(data));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
2017-02-28 05:49:01 +01:00
|
|
|
}
|
|
|
|
}
|
2017-02-24 11:54:54 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
showStatusMessages (stanza) {
|
2016-11-21 13:55:19 +01:00
|
|
|
/* Check for status codes and communicate their purpose to the user.
|
|
|
|
* See: http://xmpp.org/registrar/mucstatus.html
|
2016-12-03 17:39:59 +01:00
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (XMLElement) stanza: The message or presence stanza
|
|
|
|
* containing the status codes.
|
2016-11-21 13:55:19 +01:00
|
|
|
*/
|
2017-07-10 17:46:22 +02:00
|
|
|
const elements = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"]`, stanza);
|
2017-06-12 20:30:58 +02:00
|
|
|
const is_self = stanza.querySelectorAll("status[code='110']").length;
|
|
|
|
const iteratee = _.partial(this.parseXUserElement.bind(this), _, stanza, is_self);
|
2017-12-15 17:24:30 +01:00
|
|
|
const notifications = _.reject(_.map(elements, iteratee), _.isEmpty);
|
|
|
|
if (_.isEmpty(notifications)) {
|
|
|
|
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));
|
|
|
|
}
|
2016-12-02 18:41:05 +01:00
|
|
|
return stanza;
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
showErrorMessage (presence) {
|
2017-02-24 11:31:05 +01:00
|
|
|
// We didn't enter the room, so we must remove it from the MUC add-on
|
2017-06-12 20:30:58 +02:00
|
|
|
const error = presence.querySelector('error');
|
2017-02-24 12:50:14 +01:00
|
|
|
if (error.getAttribute('type') === 'auth') {
|
|
|
|
if (!_.isNull(error.querySelector('not-authorized'))) {
|
2016-02-16 08:46:47 +01:00
|
|
|
this.renderPasswordForm();
|
2017-02-24 12:50:14 +01:00
|
|
|
} else if (!_.isNull(error.querySelector('registration-required'))) {
|
2017-04-21 18:10:51 +02:00
|
|
|
this.showDisconnectMessage(__('You are not on the member list of this room.'));
|
2017-02-24 12:50:14 +01:00
|
|
|
} else if (!_.isNull(error.querySelector('forbidden'))) {
|
2017-04-21 18:10:51 +02:00
|
|
|
this.showDisconnectMessage(__('You have been banned from this room.'));
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
2017-02-24 12:50:14 +01:00
|
|
|
} else if (error.getAttribute('type') === 'modify') {
|
|
|
|
if (!_.isNull(error.querySelector('jid-malformed'))) {
|
2017-04-21 18:10:51 +02:00
|
|
|
this.showDisconnectMessage(__('No nickname was specified.'));
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
2017-02-24 12:50:14 +01:00
|
|
|
} else if (error.getAttribute('type') === 'cancel') {
|
|
|
|
if (!_.isNull(error.querySelector('not-allowed'))) {
|
2017-04-21 18:10:51 +02:00
|
|
|
this.showDisconnectMessage(__('You are not allowed to create new rooms.'));
|
2017-02-24 12:50:14 +01:00
|
|
|
} else if (!_.isNull(error.querySelector('not-acceptable'))) {
|
2017-04-21 18:10:51 +02:00
|
|
|
this.showDisconnectMessage(__("Your nickname doesn't conform to this room's policies."));
|
2017-02-24 12:50:14 +01:00
|
|
|
} else if (!_.isNull(error.querySelector('conflict'))) {
|
2016-08-11 14:00:52 +02:00
|
|
|
this.onNicknameClash(presence);
|
2017-02-24 12:50:14 +01:00
|
|
|
} else if (!_.isNull(error.querySelector('item-not-found'))) {
|
2017-04-21 18:10:51 +02:00
|
|
|
this.showDisconnectMessage(__("This room does not (yet) exist."));
|
2017-02-24 12:50:14 +01:00
|
|
|
} else if (!_.isNull(error.querySelector('service-unavailable'))) {
|
2017-04-21 18:10:51 +02:00
|
|
|
this.showDisconnectMessage(__("This room has reached its maximum number of occupants."));
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
renderAfterTransition () {
|
2017-03-02 23:28:22 +01:00
|
|
|
/* Rerender the room after some kind of transition. For
|
|
|
|
* example after the spinner has been removed or after a
|
|
|
|
* form has been submitted and removed.
|
|
|
|
*/
|
2017-07-21 15:05:22 +02:00
|
|
|
if (this.model.get('connection_status') == converse.ROOMSTATUS.NICKNAME_REQUIRED) {
|
2017-03-02 23:28:22 +01:00
|
|
|
this.renderNicknameForm();
|
2017-07-21 15:05:22 +02:00
|
|
|
} else if (this.model.get('connection_status') == converse.ROOMSTATUS.PASSWORD_REQUIRED) {
|
2017-04-04 16:45:50 +02:00
|
|
|
this.renderPasswordForm();
|
2017-03-02 23:28:22 +01:00
|
|
|
} else {
|
2017-12-06 16:38:16 +01:00
|
|
|
this.el.querySelector('.chat-area').classList.remove('hidden');
|
|
|
|
this.setOccupantsVisibility();
|
2017-03-02 23:28:22 +01:00
|
|
|
this.scrollDown();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
showSpinner () {
|
2017-12-06 14:59:01 +01:00
|
|
|
u.removeElement(this.el.querySelector('.spinner'));
|
|
|
|
|
|
|
|
const container_el = this.el.querySelector('.chatroom-body');
|
|
|
|
const children = Array.prototype.slice.call(container_el.children, 0);
|
|
|
|
container_el.insertAdjacentHTML('afterbegin', tpl_spinner());
|
|
|
|
_.each(children, u.hideElement);
|
|
|
|
|
2017-04-04 16:45:50 +02:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
hideSpinner () {
|
2016-07-19 16:18:40 +02:00
|
|
|
/* Check if the spinner is being shown and if so, hide it.
|
|
|
|
* Also make sure then that the chat area and occupants
|
|
|
|
* list are both visible.
|
|
|
|
*/
|
2017-06-12 20:30:58 +02:00
|
|
|
const spinner = this.el.querySelector('.spinner');
|
2017-03-02 23:28:22 +01:00
|
|
|
if (!_.isNull(spinner)) {
|
2017-12-06 14:59:01 +01:00
|
|
|
u.removeElement(spinner);
|
2017-03-02 23:28:22 +01:00
|
|
|
this.renderAfterTransition();
|
2016-07-19 16:18:40 +02:00
|
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
onOwnChatRoomPresence (pres) {
|
2017-04-13 13:18:52 +02:00
|
|
|
/* Handles a received presence relating to the current
|
|
|
|
* user.
|
2016-12-03 17:39:59 +01:00
|
|
|
*
|
2017-04-13 13:18:52 +02:00
|
|
|
* 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
|
2016-12-03 17:39:59 +01:00
|
|
|
*/
|
2017-04-13 13:18:52 +02:00
|
|
|
this.saveAffiliationAndRole(pres);
|
|
|
|
|
2017-06-12 20:30:58 +02:00
|
|
|
const locked_room = pres.querySelector("status[code='201']");
|
2017-04-13 13:18:52 +02:00
|
|
|
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.
|
2017-05-24 08:40:09 +02:00
|
|
|
// That must mean it's a new room without locking
|
2017-04-13 13:18:52 +02:00
|
|
|
// (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();
|
|
|
|
}
|
|
|
|
}
|
2017-07-21 15:05:22 +02:00
|
|
|
this.model.save('connection_status', converse.ROOMSTATUS.ENTERED);
|
2016-12-03 17:39:59 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
onChatRoomPresence (pres) {
|
2016-12-03 17:39:59 +01:00
|
|
|
/* Handles all MUC presence stanzas.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (XMLElement) pres: The stanza
|
|
|
|
*/
|
2016-12-04 13:05:25 +01:00
|
|
|
if (pres.getAttribute('type') === 'error') {
|
2017-07-21 15:05:22 +02:00
|
|
|
this.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
|
2016-08-11 14:00:52 +02:00
|
|
|
this.showErrorMessage(pres);
|
2016-12-04 13:05:25 +01:00
|
|
|
return true;
|
2016-12-05 11:50:01 +01:00
|
|
|
}
|
2017-06-12 20:30:58 +02:00
|
|
|
const is_self = pres.querySelector("status[code='110']");
|
2017-04-13 13:18:52 +02:00
|
|
|
if (is_self && pres.getAttribute('type') !== 'unavailable') {
|
|
|
|
this.onOwnChatRoomPresence(pres);
|
2016-12-04 13:05:25 +01:00
|
|
|
}
|
2017-02-24 11:54:54 +01:00
|
|
|
this.hideSpinner().showStatusMessages(pres);
|
2017-02-28 05:49:01 +01:00
|
|
|
// This must be called after showStatusMessages so that
|
|
|
|
// "join" messages are correctly shown.
|
2016-12-04 13:05:25 +01:00
|
|
|
this.occupantsview.updateOccupantsOnPresence(pres);
|
2017-02-24 11:54:54 +01:00
|
|
|
if (this.model.get('role') !== 'none' &&
|
2017-07-21 15:05:22 +02:00
|
|
|
this.model.get('connection_status') === converse.ROOMSTATUS.CONNECTING) {
|
|
|
|
this.model.save('connection_status', converse.ROOMSTATUS.CONNECTED);
|
2016-12-05 07:03:44 +01:00
|
|
|
}
|
2016-10-13 18:22:37 +02:00
|
|
|
return true;
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
setChatRoomSubject (sender, subject) {
|
2017-02-19 10:58:30 +01:00
|
|
|
// For translators: the %1$s and %2$s parts will get
|
|
|
|
// replaced by the user and topic text respectively
|
2016-05-27 14:53:07 +02:00
|
|
|
// Example: Topic set by JC Brand to: Hello World!
|
2017-12-15 17:24:30 +01:00
|
|
|
this.content.insertAdjacentHTML(
|
|
|
|
'beforeend',
|
|
|
|
tpl_info({
|
|
|
|
'message': __('Topic set by %1$s to: %2$s', sender, subject),
|
|
|
|
'data': ''
|
|
|
|
}));
|
2016-05-27 14:53:07 +02:00
|
|
|
this.scrollDown();
|
|
|
|
},
|
|
|
|
|
2017-07-18 12:35:22 +02:00
|
|
|
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.
|
|
|
|
*/
|
2017-07-21 17:58:46 +02:00
|
|
|
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;
|
|
|
|
}
|
2017-07-18 12:35:22 +02:00
|
|
|
}
|
2017-07-21 17:58:46 +02:00
|
|
|
return false;
|
2017-07-18 12:35:22 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
isDuplicate (message) {
|
|
|
|
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);
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
onChatRoomMessage (message) {
|
2016-12-04 13:05:25 +01:00
|
|
|
/* Given a <message> stanza, create a message
|
|
|
|
* Backbone.Model if appropriate.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (XMLElement) msg: The received message stanza
|
|
|
|
*/
|
2017-07-10 17:46:22 +02:00
|
|
|
const original_stanza = message,
|
|
|
|
forwarded = message.querySelector('forwarded');
|
|
|
|
let delay;
|
2016-11-24 02:07:32 +01:00
|
|
|
if (!_.isNull(forwarded)) {
|
|
|
|
message = forwarded.querySelector('message');
|
|
|
|
delay = forwarded.querySelector('delay');
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
2017-07-10 17:46:22 +02:00
|
|
|
const jid = message.getAttribute('from'),
|
2016-02-16 08:46:47 +01:00
|
|
|
resource = Strophe.getResourceFromJid(jid),
|
|
|
|
sender = resource && Strophe.unescapeNode(resource) || '',
|
2017-07-18 12:35:22 +02:00
|
|
|
subject = _.propertyOf(message.querySelector('subject'))('textContent');
|
|
|
|
|
|
|
|
if (this.isDuplicate(message)) {
|
2016-05-25 10:31:35 +02:00
|
|
|
return true;
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
|
|
|
if (subject) {
|
2016-05-27 14:53:07 +02:00
|
|
|
this.setChatRoomSubject(sender, subject);
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
|
|
|
if (sender === '') {
|
|
|
|
return true;
|
|
|
|
}
|
2017-05-23 18:16:47 +02:00
|
|
|
this.model.incrementUnreadMsgCounter(original_stanza);
|
2016-11-24 02:07:32 +01:00
|
|
|
this.model.createMessage(message, delay, original_stanza);
|
2016-02-16 08:46:47 +01:00
|
|
|
if (sender !== this.model.get('nick')) {
|
|
|
|
// We only emit an event if it's not our own message
|
2017-04-20 21:25:58 +02:00
|
|
|
_converse.emit(
|
|
|
|
'message',
|
|
|
|
{'stanza': original_stanza, 'chatbox': this.model}
|
|
|
|
);
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.ChatRoomOccupant = Backbone.Model.extend({
|
2017-07-10 17:46:22 +02:00
|
|
|
initialize (attributes) {
|
2016-07-19 14:45:25 +02:00
|
|
|
this.set(_.extend({
|
2016-12-20 10:30:20 +01:00
|
|
|
'id': _converse.connection.getUniqueId(),
|
2016-07-19 14:45:25 +02:00
|
|
|
}, attributes));
|
2016-07-18 16:11:04 +02:00
|
|
|
}
|
|
|
|
});
|
2016-07-19 14:45:25 +02:00
|
|
|
|
2017-12-14 18:43:00 +01:00
|
|
|
_converse.ChatRoomOccupantView = Backbone.VDOMView.extend({
|
2016-02-19 10:36:01 +01:00
|
|
|
tagName: 'li',
|
2017-07-10 17:46:22 +02:00
|
|
|
initialize () {
|
2016-02-19 10:36:01 +01:00
|
|
|
this.model.on('change', this.render, this);
|
|
|
|
this.model.on('destroy', this.destroy, this);
|
|
|
|
},
|
2016-07-26 11:04:05 +02:00
|
|
|
|
2017-12-14 18:43:00 +01:00
|
|
|
toHTML () {
|
2017-06-12 20:30:58 +02:00
|
|
|
const show = this.model.get('show') || 'online';
|
2017-12-14 18:43:00 +01:00
|
|
|
return tpl_occupant(
|
2016-07-26 08:59:34 +02:00
|
|
|
_.extend(
|
2017-02-19 01:57:06 +01:00
|
|
|
{ 'jid': '',
|
2017-02-28 06:46:21 +01:00
|
|
|
'show': show,
|
|
|
|
'hint_show': _converse.PRETTY_CHAT_STATUS[show],
|
2017-08-23 11:57:17 +02:00
|
|
|
'hint_occupant': __('Click to mention %1$s in your message.', this.model.get('nick')),
|
2017-02-19 01:57:06 +01:00
|
|
|
'desc_moderator': __('This user is a moderator.'),
|
|
|
|
'desc_occupant': __('This user can send messages in this room.'),
|
|
|
|
'desc_visitor': __('This user can NOT send messages in this room.')
|
2017-12-14 18:43:00 +01:00
|
|
|
}, this.model.toJSON())
|
2016-02-19 10:36:01 +01:00
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
destroy () {
|
2017-12-23 21:28:46 +01:00
|
|
|
this.el.parentElement.removeChild(this.el);
|
2016-02-19 10:36:01 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.ChatRoomOccupants = Backbone.Collection.extend({
|
2017-12-14 18:43:00 +01:00
|
|
|
model: _converse.ChatRoomOccupant,
|
|
|
|
|
|
|
|
comparator (occupant1, occupant2) {
|
|
|
|
const role1 = occupant1.get('role') || 'none';
|
|
|
|
const role2 = occupant2.get('role') || 'none';
|
|
|
|
if (MUC_ROLE_WEIGHTS[role1] === MUC_ROLE_WEIGHTS[role2]) {
|
|
|
|
const nick1 = occupant1.get('nick').toLowerCase();
|
|
|
|
const nick2 = occupant2.get('nick').toLowerCase();
|
|
|
|
return nick1 < nick2 ? -1 : (nick1 > nick2? 1 : 0);
|
|
|
|
} else {
|
|
|
|
return MUC_ROLE_WEIGHTS[role1] < MUC_ROLE_WEIGHTS[role2] ? -1 : 1;
|
|
|
|
}
|
|
|
|
},
|
2016-02-19 10:36:01 +01:00
|
|
|
});
|
|
|
|
|
2017-12-20 20:29:57 +01:00
|
|
|
_converse.ChatRoomOccupantsView = Backbone.OrderedListView.extend({
|
2016-02-19 10:36:01 +01:00
|
|
|
tagName: 'div',
|
|
|
|
className: 'occupants',
|
2017-12-20 20:29:57 +01:00
|
|
|
listItems: 'model',
|
|
|
|
sortEvent: 'change:role',
|
|
|
|
listSelector: '.occupant-list',
|
|
|
|
|
|
|
|
ItemView: _converse.ChatRoomOccupantView,
|
2016-02-19 10:36:01 +01:00
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
initialize () {
|
2017-12-20 20:29:57 +01:00
|
|
|
Backbone.OrderedListView.prototype.initialize.apply(this, arguments);
|
2017-12-14 18:43:00 +01:00
|
|
|
|
2017-02-19 12:03:24 +01:00
|
|
|
this.chatroomview = this.model.chatroomview;
|
2017-02-27 18:50:55 +01:00
|
|
|
this.chatroomview.model.on('change:open', this.renderInviteWidget, this);
|
|
|
|
this.chatroomview.model.on('change:affiliation', this.renderInviteWidget, this);
|
2017-03-19 19:23:02 +01:00
|
|
|
this.chatroomview.model.on('change:hidden', this.onFeatureChanged, this);
|
|
|
|
this.chatroomview.model.on('change:mam_enabled', this.onFeatureChanged, this);
|
|
|
|
this.chatroomview.model.on('change:membersonly', this.onFeatureChanged, this);
|
|
|
|
this.chatroomview.model.on('change:moderated', this.onFeatureChanged, this);
|
|
|
|
this.chatroomview.model.on('change:nonanonymous', this.onFeatureChanged, this);
|
|
|
|
this.chatroomview.model.on('change:open', this.onFeatureChanged, this);
|
|
|
|
this.chatroomview.model.on('change:passwordprotected', this.onFeatureChanged, this);
|
|
|
|
this.chatroomview.model.on('change:persistent', this.onFeatureChanged, this);
|
2017-11-05 18:47:30 +01:00
|
|
|
this.chatroomview.model.on('change:publicroom', this.onFeatureChanged, this);
|
2017-03-19 19:23:02 +01:00
|
|
|
this.chatroomview.model.on('change:semianonymous', this.onFeatureChanged, this);
|
|
|
|
this.chatroomview.model.on('change:temporary', this.onFeatureChanged, this);
|
|
|
|
this.chatroomview.model.on('change:unmoderated', this.onFeatureChanged, this);
|
|
|
|
this.chatroomview.model.on('change:unsecured', this.onFeatureChanged, this);
|
2017-12-14 18:43:00 +01:00
|
|
|
|
|
|
|
const id = b64_sha1(`converse.occupants${_converse.bare_jid}${this.chatroomview.model.get('jid')}`);
|
|
|
|
this.model.browserStorage = new Backbone.BrowserStorage.session(id);
|
|
|
|
this.render();
|
|
|
|
this.model.fetch({
|
|
|
|
'add': true,
|
|
|
|
'silent': true,
|
2017-12-20 20:29:57 +01:00
|
|
|
'success': this.sortAndPositionAllItems.bind(this)
|
2017-12-14 18:43:00 +01:00
|
|
|
});
|
2016-02-19 10:36:01 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
render () {
|
2017-04-04 16:45:50 +02:00
|
|
|
this.el.innerHTML = tpl_chatroom_sidebar(
|
|
|
|
_.extend(this.chatroomview.model.toJSON(), {
|
|
|
|
'allow_muc_invitations': _converse.allow_muc_invitations,
|
|
|
|
'label_occupants': __('Occupants')
|
|
|
|
})
|
2017-02-19 11:02:36 +01:00
|
|
|
);
|
|
|
|
if (_converse.allow_muc_invitations) {
|
2017-03-19 19:23:02 +01:00
|
|
|
_converse.api.waitUntil('rosterContactsFetched').then(
|
|
|
|
this.renderInviteWidget.bind(this)
|
|
|
|
);
|
2017-02-19 11:02:36 +01:00
|
|
|
}
|
|
|
|
return this.renderRoomFeatures();
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
renderInviteWidget () {
|
2017-12-15 22:52:14 +01:00
|
|
|
const form = this.el.querySelector('form.room-invite');
|
2017-02-27 18:50:55 +01:00
|
|
|
if (this.shouldInviteWidgetBeShown()) {
|
|
|
|
if (_.isNull(form)) {
|
2017-06-12 20:30:58 +02:00
|
|
|
const heading = this.el.querySelector('.occupants-heading');
|
2017-12-15 22:52:14 +01:00
|
|
|
heading.insertAdjacentHTML(
|
|
|
|
'afterend',
|
|
|
|
tpl_chatroom_invite({
|
|
|
|
'error_message': null,
|
|
|
|
'label_invitation': __('Invite'),
|
|
|
|
})
|
|
|
|
);
|
2017-02-27 18:50:55 +01:00
|
|
|
this.initInviteWidget();
|
|
|
|
}
|
2017-12-15 22:52:14 +01:00
|
|
|
} else if (!_.isNull(form)) {
|
|
|
|
form.remove();
|
2017-02-27 18:50:55 +01:00
|
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
renderRoomFeatures () {
|
2017-06-12 20:30:58 +02:00
|
|
|
const picks = _.pick(this.chatroomview.model.attributes, ROOM_FEATURES),
|
2017-07-10 17:46:22 +02:00
|
|
|
iteratee = (a, v) => a || v,
|
2017-03-01 08:42:26 +01:00
|
|
|
el = this.el.querySelector('.chatroom-features');
|
|
|
|
|
|
|
|
el.innerHTML = tpl_chatroom_features(
|
2017-02-19 11:02:36 +01:00
|
|
|
_.extend(this.chatroomview.model.toJSON(), {
|
2017-02-28 22:34:16 +01:00
|
|
|
'has_features': _.reduce(_.values(picks), iteratee),
|
2017-02-27 18:59:33 +01:00
|
|
|
'label_features': __('Features'),
|
2017-02-18 20:41:44 +01:00
|
|
|
'label_hidden': __('Hidden'),
|
|
|
|
'label_mam_enabled': __('Message archiving'),
|
|
|
|
'label_membersonly': __('Members only'),
|
|
|
|
'label_moderated': __('Moderated'),
|
|
|
|
'label_nonanonymous': __('Non-anonymous'),
|
|
|
|
'label_open': __('Open'),
|
|
|
|
'label_passwordprotected': __('Password protected'),
|
|
|
|
'label_persistent': __('Persistent'),
|
|
|
|
'label_public': __('Public'),
|
|
|
|
'label_semianonymous': __('Semi-anonymous'),
|
|
|
|
'label_temporary': __('Temporary'),
|
|
|
|
'label_unmoderated': __('Unmoderated'),
|
2017-12-20 17:56:18 +01:00
|
|
|
'label_unsecured': __('No password'),
|
2017-04-04 09:11:55 +02:00
|
|
|
'tt_hidden': __('This room is not publicly searchable'),
|
2017-02-18 20:41:44 +01:00
|
|
|
'tt_mam_enabled': __('Messages are archived on the server'),
|
|
|
|
'tt_membersonly': __('This room is restricted to members only'),
|
|
|
|
'tt_moderated': __('This room is being moderated'),
|
2017-07-19 09:17:46 +02:00
|
|
|
'tt_nonanonymous': __('All other room occupants can see your XMPP username'),
|
2017-02-18 20:41:44 +01:00
|
|
|
'tt_open': __('Anyone can join this room'),
|
|
|
|
'tt_passwordprotected': __('This room requires a password before entry'),
|
2017-04-04 09:11:55 +02:00
|
|
|
'tt_persistent': __('This room persists even if it\'s unoccupied'),
|
|
|
|
'tt_public': __('This room is publicly searchable'),
|
2017-07-19 09:17:46 +02:00
|
|
|
'tt_semianonymous': __('Only moderators can see your XMPP username'),
|
2017-02-18 20:41:44 +01:00
|
|
|
'tt_temporary': __('This room will disappear once the last person leaves'),
|
|
|
|
'tt_unmoderated': __('This room is not being moderated'),
|
|
|
|
'tt_unsecured': __('This room does not require a password upon entry')
|
2017-03-01 08:42:26 +01:00
|
|
|
}));
|
|
|
|
this.setOccupantsHeight();
|
2016-06-10 16:13:52 +02:00
|
|
|
return this;
|
2016-02-19 10:36:01 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
onFeatureChanged (model) {
|
2017-03-19 19:23:02 +01:00
|
|
|
/* When a feature has been changed, it's logical opposite
|
|
|
|
* must be set to the opposite value.
|
|
|
|
*
|
|
|
|
* So for example, if "temporary" was set to "false", then
|
|
|
|
* "persistent" will be set to "true" in this method.
|
|
|
|
*
|
|
|
|
* Additionally a debounced render method is called to make
|
|
|
|
* sure the features widget gets updated.
|
|
|
|
*/
|
|
|
|
if (_.isUndefined(this.debouncedRenderRoomFeatures)) {
|
|
|
|
this.debouncedRenderRoomFeatures = _.debounce(
|
|
|
|
this.renderRoomFeatures, 100, {'leading': false}
|
|
|
|
);
|
|
|
|
}
|
2017-06-12 20:30:58 +02:00
|
|
|
const changed_features = {};
|
2017-03-19 19:23:02 +01:00
|
|
|
_.each(_.keys(model.changed), function (k) {
|
|
|
|
if (!_.isNil(ROOM_FEATURES_MAP[k])) {
|
|
|
|
changed_features[ROOM_FEATURES_MAP[k]] = !model.changed[k];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.chatroomview.model.save(changed_features, {'silent': true});
|
|
|
|
this.debouncedRenderRoomFeatures();
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
setOccupantsHeight () {
|
2017-06-12 20:30:58 +02:00
|
|
|
const el = this.el.querySelector('.chatroom-features');
|
2017-03-01 08:42:26 +01:00
|
|
|
this.el.querySelector('.occupant-list').style.cssText =
|
2017-07-10 17:46:22 +02:00
|
|
|
`height: calc(100% - ${el.offsetHeight}px - 5em);`;
|
2017-03-01 08:42:26 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
parsePresence (pres) {
|
2017-06-12 20:30:58 +02:00
|
|
|
const id = Strophe.getResourceFromJid(pres.getAttribute("from"));
|
|
|
|
const data = {
|
2016-02-19 10:36:01 +01:00
|
|
|
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":
|
2017-02-28 06:27:30 +01:00
|
|
|
data.show = child.textContent || 'online';
|
2016-02-19 10:36:01 +01:00
|
|
|
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;
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
findOccupant (data) {
|
2016-07-19 15:04:02 +02:00
|
|
|
/* 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.
|
|
|
|
*/
|
2017-06-12 20:30:58 +02:00
|
|
|
const jid = Strophe.getBareJidFromJid(data.jid);
|
2016-07-18 16:11:04 +02:00
|
|
|
if (jid !== null) {
|
2016-07-19 15:04:02 +02:00
|
|
|
return this.model.where({'jid': jid}).pop();
|
2016-07-18 16:11:04 +02:00
|
|
|
} else {
|
2016-07-19 15:04:02 +02:00
|
|
|
return this.model.where({'nick': data.nick}).pop();
|
2016-07-18 16:11:04 +02:00
|
|
|
}
|
2016-07-19 15:04:02 +02:00
|
|
|
},
|
2016-07-18 16:11:04 +02:00
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
updateOccupantsOnPresence (pres) {
|
2016-12-04 10:43:39 +01:00
|
|
|
/* Given a presence stanza, update the occupant models
|
|
|
|
* based on its contents.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (XMLElement) pres: The presence stanza
|
|
|
|
*/
|
2017-06-12 20:30:58 +02:00
|
|
|
const data = this.parsePresence(pres);
|
2016-07-19 15:04:02 +02:00
|
|
|
if (data.type === 'error') {
|
|
|
|
return true;
|
|
|
|
}
|
2017-06-12 20:30:58 +02:00
|
|
|
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);
|
|
|
|
}
|
2016-02-19 10:36:01 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
promptForInvite (suggestion) {
|
2017-06-12 20:30:58 +02:00
|
|
|
const reason = prompt(
|
2017-08-23 11:57:17 +02:00
|
|
|
__('You are about to invite %1$s to the chat room "%2$s". ', suggestion.text.label, this.model.get('id')) +
|
2017-02-14 14:33:31 +01:00
|
|
|
__("You may optionally include a message, explaining the reason for the invitation.")
|
|
|
|
);
|
|
|
|
if (reason !== null) {
|
|
|
|
this.chatroomview.directInvite(suggestion.text.value, reason);
|
|
|
|
}
|
2017-07-21 19:53:34 +02:00
|
|
|
const form = suggestion.target.form,
|
|
|
|
error = form.querySelector('.pure-form-message.error');
|
|
|
|
if (!_.isNull(error)) {
|
|
|
|
error.parentNode.removeChild(error);
|
|
|
|
}
|
2017-02-14 14:33:31 +01:00
|
|
|
suggestion.target.value = '';
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
inviteFormSubmitted (evt) {
|
2017-02-14 14:33:31 +01:00
|
|
|
evt.preventDefault();
|
2017-07-21 19:53:34 +02:00
|
|
|
const el = evt.target.querySelector('input.invited-contact'),
|
|
|
|
jid = el.value;
|
|
|
|
if (!jid || _.filter(jid.split('@')).length < 2) {
|
|
|
|
evt.target.outerHTML = tpl_chatroom_invite({
|
|
|
|
'error_message': __('Please enter a valid XMPP username'),
|
|
|
|
'label_invitation': __('Invite'),
|
|
|
|
});
|
|
|
|
this.initInviteWidget();
|
|
|
|
return;
|
|
|
|
}
|
2017-02-14 14:33:31 +01:00
|
|
|
this.promptForInvite({
|
|
|
|
'target': el,
|
|
|
|
'text': {
|
2017-07-21 19:53:34 +02:00
|
|
|
'label': jid,
|
|
|
|
'value': jid
|
2017-02-14 14:33:31 +01:00
|
|
|
}});
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
shouldInviteWidgetBeShown () {
|
2017-02-27 18:50:55 +01:00
|
|
|
return _converse.allow_muc_invitations &&
|
|
|
|
(this.chatroomview.model.get('open') ||
|
|
|
|
this.chatroomview.model.get('affiliation') === "owner"
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
initInviteWidget () {
|
2017-06-12 20:30:58 +02:00
|
|
|
const form = this.el.querySelector('form.room-invite');
|
2017-02-27 18:50:55 +01:00
|
|
|
if (_.isNull(form)) {
|
|
|
|
return;
|
|
|
|
}
|
2017-12-06 22:10:54 +01:00
|
|
|
form.addEventListener('submit', this.inviteFormSubmitted.bind(this), false);
|
2017-06-12 20:30:58 +02:00
|
|
|
const el = this.el.querySelector('input.invited-contact');
|
|
|
|
const list = _converse.roster.map(function (item) {
|
|
|
|
const label = item.get('fullname') || item.get('jid');
|
2017-02-14 11:19:01 +01:00
|
|
|
return {'label': label, 'value':item.get('jid')};
|
|
|
|
});
|
2017-06-12 20:30:58 +02:00
|
|
|
const awesomplete = new Awesomplete(el, {
|
2017-02-14 11:19:01 +01:00
|
|
|
'minChars': 1,
|
|
|
|
'list': list
|
2016-02-19 10:36:01 +01:00
|
|
|
});
|
2017-07-21 19:53:34 +02:00
|
|
|
el.addEventListener('awesomplete-selectcomplete',
|
|
|
|
this.promptForInvite.bind(this));
|
2016-02-19 10:36:01 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-11-10 17:26:04 +01:00
|
|
|
|
2017-11-10 21:20:13 +01:00
|
|
|
_converse.MUCJoinForm = Backbone.VDOMView.extend({
|
2017-11-10 17:26:04 +01:00
|
|
|
initialize () {
|
|
|
|
this.model.on('change:muc_domain', this.render, this);
|
|
|
|
},
|
|
|
|
|
2017-12-13 22:39:41 +01:00
|
|
|
toHTML () {
|
2017-11-10 21:20:13 +01:00
|
|
|
return tpl_chatroom_join_form(_.assign(this.model.toJSON(), {
|
2017-11-10 17:26:04 +01:00
|
|
|
'server_input_type': _converse.hide_muc_server && 'hidden' || 'text',
|
|
|
|
'server_label_global_attr': _converse.hide_muc_server && ' hidden' || '',
|
|
|
|
'label_room_name': __('Room name'),
|
|
|
|
'label_nickname': __('Nickname'),
|
|
|
|
'label_server': __('Server'),
|
|
|
|
'label_join': __('Join Room'),
|
|
|
|
'label_show_rooms': __('Show rooms')
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
_converse.RoomsPanelModel = Backbone.Model.extend({
|
|
|
|
defaults: {
|
|
|
|
'muc_domain': '',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.RoomsPanel = Backbone.View.extend({
|
2016-02-16 08:46:47 +01:00
|
|
|
/* Backbone View which renders the "Rooms" tab and accompanying
|
2016-03-19 23:16:00 +01:00
|
|
|
* panel in the control box.
|
|
|
|
*
|
|
|
|
* In this panel, chat rooms can be listed, joined and new rooms
|
|
|
|
* can be created.
|
|
|
|
*/
|
2016-02-16 08:46:47 +01:00
|
|
|
tagName: 'div',
|
|
|
|
className: 'controlbox-pane',
|
|
|
|
id: 'chatrooms',
|
|
|
|
events: {
|
2017-06-13 17:20:59 +02:00
|
|
|
'submit form.add-chatroom': 'openChatRoom',
|
2016-02-16 08:46:47 +01:00
|
|
|
'click input#show-rooms': 'showRooms',
|
2017-06-13 17:20:59 +02:00
|
|
|
'click a.open-room': 'openChatRoom',
|
2016-10-18 11:04:36 +02:00
|
|
|
'click a.room-info': 'toggleRoomInfo',
|
2016-02-16 08:46:47 +01:00
|
|
|
'change input[name=server]': 'setDomain',
|
|
|
|
'change input[name=nick]': 'setNick'
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
initialize (cfg) {
|
2017-11-10 17:26:04 +01:00
|
|
|
this.join_form = new _converse.MUCJoinForm({'model': this.model});
|
2017-12-15 22:52:14 +01:00
|
|
|
this.parent_el = cfg.parent;
|
2017-06-05 14:50:29 +02:00
|
|
|
this.tab_el = document.createElement('li');
|
2016-02-16 08:46:47 +01:00
|
|
|
this.model.on('change:muc_domain', this.onDomainChange, this);
|
|
|
|
this.model.on('change:nick', this.onNickChange, this);
|
2017-06-09 23:43:58 +02:00
|
|
|
_converse.chatboxes.on('change:num_unread', this.renderTab, this);
|
2017-06-15 12:32:33 +02:00
|
|
|
_converse.chatboxes.on('add', _.debounce(this.renderTab, 100), this);
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
render () {
|
2017-11-10 17:26:04 +01:00
|
|
|
this.el.innerHTML = tpl_room_panel();
|
2017-11-10 21:20:13 +01:00
|
|
|
this.join_form.setElement(this.el.querySelector('.add-chatroom'));
|
2017-11-10 17:26:04 +01:00
|
|
|
this.join_form.render();
|
|
|
|
|
2017-06-09 23:43:58 +02:00
|
|
|
this.renderTab();
|
2017-06-12 20:30:58 +02:00
|
|
|
const controlbox = _converse.chatboxes.get('controlbox');
|
2017-06-09 23:43:58 +02:00
|
|
|
if (controlbox.get('active-panel') !== ROOMS_PANEL_ID) {
|
|
|
|
this.el.classList.add('hidden');
|
2017-11-10 17:26:04 +01:00
|
|
|
} else {
|
|
|
|
this.el.classList.remove('hidden');
|
2017-06-09 23:43:58 +02:00
|
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
renderTab () {
|
2017-06-12 20:30:58 +02:00
|
|
|
const controlbox = _converse.chatboxes.get('controlbox');
|
|
|
|
const chatrooms = fp.filter(
|
2017-12-06 14:59:01 +01:00
|
|
|
_.partial(u.isOfType, CHATROOMS_TYPE),
|
2017-06-09 23:43:58 +02:00
|
|
|
_converse.chatboxes.models
|
|
|
|
);
|
2017-06-05 14:50:29 +02:00
|
|
|
this.tab_el.innerHTML = tpl_chatrooms_tab({
|
2016-11-02 23:08:20 +01:00
|
|
|
'label_rooms': __('Rooms'),
|
2017-06-09 23:43:58 +02:00
|
|
|
'is_current': controlbox.get('active-panel') === ROOMS_PANEL_ID,
|
2017-12-06 14:59:01 +01:00
|
|
|
'num_unread': fp.sum(fp.map(fp.curry(u.getAttribute)('num_unread'), chatrooms))
|
2017-06-05 14:50:29 +02:00
|
|
|
});
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
insertIntoDOM () {
|
2017-06-05 14:50:29 +02:00
|
|
|
this.parent_el.appendChild(this.render().el);
|
|
|
|
this.tabs = this.parent_el.parentNode.querySelector('#controlbox-tabs');
|
|
|
|
this.tabs.appendChild(this.tab_el);
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
onDomainChange (model) {
|
2016-12-20 10:30:20 +01:00
|
|
|
if (_converse.auto_list_rooms) {
|
2016-02-16 08:46:47 +01:00
|
|
|
this.updateRoomsList();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
onNickChange (model) {
|
2017-12-06 22:10:54 +01:00
|
|
|
const nick = this.el.querySelector('input.new-chatroom-nick');
|
|
|
|
if (!_.isNull(nick)) {
|
|
|
|
nick.value = model.get('nick');
|
|
|
|
}
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-12-15 22:52:14 +01:00
|
|
|
removeSpinner () {
|
|
|
|
_.each(this.el.querySelectorAll('span.spinner'),
|
|
|
|
(el) => el.parentNode.removeChild(el)
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
informNoRoomsFound () {
|
2017-12-23 21:28:46 +01:00
|
|
|
const chatrooms_el = this.el.querySelector('#available-chatrooms');
|
2016-02-16 08:46:47 +01:00
|
|
|
// For translators: %1$s is a variable and will be replaced with the XMPP server name
|
2017-12-23 21:28:46 +01:00
|
|
|
chatrooms_el.innerHTML = `<dt>${__('No rooms on %1$s', this.model.get('muc_domain'))}</dt>`;
|
2017-12-15 22:52:14 +01:00
|
|
|
const input_el = this.el.querySelector('input#show-rooms');
|
|
|
|
input_el.classList.remove('hidden')
|
|
|
|
this.removeSpinner();
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
onRoomsFound (iq) {
|
2016-02-16 08:46:47 +01:00
|
|
|
/* Handle the IQ stanza returned from the server, containing
|
2016-03-19 23:16:00 +01:00
|
|
|
* all its public rooms.
|
|
|
|
*/
|
2017-12-15 22:52:14 +01:00
|
|
|
const available_chatrooms = this.el.querySelector('#available-chatrooms');
|
|
|
|
this.rooms = iq.querySelectorAll('query item');
|
2016-02-16 08:46:47 +01:00
|
|
|
if (this.rooms.length) {
|
|
|
|
// For translators: %1$s is a variable and will be
|
|
|
|
// replaced with the XMPP server name
|
2017-12-18 13:19:56 +01:00
|
|
|
available_chatrooms.innerHTML =
|
|
|
|
`<dt>${__('Rooms on %1$s',this.model.get('muc_domain'))}</dt>`;
|
2017-12-15 22:52:14 +01:00
|
|
|
const div = document.createElement('div');
|
2017-07-10 17:46:22 +02:00
|
|
|
const fragment = document.createDocumentFragment();
|
|
|
|
for (let i=0; i<this.rooms.length; i++) {
|
|
|
|
const name = Strophe.unescapeNode(
|
2017-12-18 13:19:56 +01:00
|
|
|
this.rooms[i].getAttribute('name') ||
|
|
|
|
this.rooms[i].getAttribute('jid')
|
2017-07-10 17:46:22 +02:00
|
|
|
);
|
2017-12-15 22:52:14 +01:00
|
|
|
div.innerHTML = tpl_room_item({
|
|
|
|
'name': name,
|
|
|
|
'jid': this.rooms[i].getAttribute('jid'),
|
|
|
|
'open_title': __('Click to open this room'),
|
|
|
|
'info_title': __('Show more information on this room')
|
|
|
|
});
|
|
|
|
fragment.appendChild(div.firstChild);
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
2017-12-15 22:52:14 +01:00
|
|
|
available_chatrooms.appendChild(fragment);
|
|
|
|
const input_el = this.el.querySelector('input#show-rooms');
|
|
|
|
input_el.classList.remove('hidden')
|
|
|
|
this.removeSpinner();
|
2016-02-16 08:46:47 +01:00
|
|
|
} else {
|
|
|
|
this.informNoRoomsFound();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
updateRoomsList () {
|
2016-02-16 08:46:47 +01:00
|
|
|
/* Send and IQ stanza to the server asking for all rooms
|
2016-03-19 23:16:00 +01:00
|
|
|
*/
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.connection.sendIQ(
|
2016-02-16 08:46:47 +01:00
|
|
|
$iq({
|
|
|
|
to: this.model.get('muc_domain'),
|
2016-12-20 10:30:20 +01:00
|
|
|
from: _converse.connection.jid,
|
2016-02-16 08:46:47 +01:00
|
|
|
type: "get"
|
|
|
|
}).c("query", {xmlns: Strophe.NS.DISCO_ITEMS}),
|
|
|
|
this.onRoomsFound.bind(this),
|
2017-12-23 21:28:46 +01:00
|
|
|
this.informNoRoomsFound.bind(this),
|
|
|
|
5000
|
2016-02-16 08:46:47 +01:00
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
showRooms () {
|
2017-12-23 21:28:46 +01:00
|
|
|
const chatrooms_el = this.el.querySelector('#available-chatrooms');
|
|
|
|
const server_el = this.el.querySelector('input.new-chatroom-server');
|
|
|
|
const server = server_el.value;
|
2016-02-16 08:46:47 +01:00
|
|
|
if (!server) {
|
2017-12-23 21:28:46 +01:00
|
|
|
server_el.classList.add('error');
|
2016-02-16 08:46:47 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-12-23 21:28:46 +01:00
|
|
|
this.el.querySelector('input.new-chatroom-name').classList.remove('error');
|
|
|
|
server_el.classList.remove('error');
|
|
|
|
chatrooms_el.innerHTML = '';
|
2017-12-15 22:52:14 +01:00
|
|
|
|
|
|
|
const input_el = this.el.querySelector('input#show-rooms');
|
|
|
|
input_el.classList.add('hidden')
|
|
|
|
input_el.insertAdjacentHTML('afterend', tpl_spinner());
|
|
|
|
|
2016-02-16 08:46:47 +01:00
|
|
|
this.model.save({muc_domain: server});
|
|
|
|
this.updateRoomsList();
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
insertRoomInfo (el, stanza) {
|
2016-10-18 11:04:36 +02:00
|
|
|
/* Insert room info (based on returned #disco IQ stanza)
|
2016-12-04 13:05:25 +01:00
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (HTMLElement) el: The HTML DOM element that should
|
|
|
|
* contain the info.
|
|
|
|
* (XMLElement) stanza: The IQ stanza containing the room
|
|
|
|
* info.
|
2016-10-18 11:04:36 +02:00
|
|
|
*/
|
|
|
|
// All MUC features found here: http://xmpp.org/registrar/disco-features.html
|
2017-07-19 08:30:04 +02:00
|
|
|
el.querySelector('span.spinner').outerHTML =
|
2017-02-19 10:58:30 +01:00
|
|
|
tpl_room_description({
|
2017-07-19 08:30:04 +02:00
|
|
|
'jid': stanza.getAttribute('from'),
|
2017-12-23 21:28:46 +01:00
|
|
|
'desc': _.get(_.head(sizzle('field[var="muc#roominfo_description"] value', stanza)), 'textContent'),
|
|
|
|
'occ': _.get(_.head(sizzle('field[var="muc#roominfo_occupants"] value', stanza)), 'textContent'),
|
|
|
|
'hidden': sizzle('feature[var="muc_hidden"]', stanza).length,
|
|
|
|
'membersonly': sizzle('feature[var="muc_membersonly"]', stanza).length,
|
|
|
|
'moderated': sizzle('feature[var="muc_moderated"]', stanza).length,
|
|
|
|
'nonanonymous': sizzle('feature[var="muc_nonanonymous"]', stanza).length,
|
|
|
|
'open': sizzle('feature[var="muc_open"]', stanza).length,
|
|
|
|
'passwordprotected': sizzle('feature[var="muc_passwordprotected"]', stanza).length,
|
|
|
|
'persistent': sizzle('feature[var="muc_persistent"]', stanza).length,
|
|
|
|
'publicroom': sizzle('feature[var="muc_publicroom"]', stanza).length,
|
|
|
|
'semianonymous': sizzle('feature[var="muc_semianonymous"]', stanza).length,
|
|
|
|
'temporary': sizzle('feature[var="muc_temporary"]', stanza).length,
|
|
|
|
'unmoderated': sizzle('feature[var="muc_unmoderated"]', stanza).length,
|
2016-10-18 11:04:36 +02:00
|
|
|
'label_desc': __('Description:'),
|
2017-07-19 08:30:04 +02:00
|
|
|
'label_jid': __('Room Address (JID):'),
|
2016-10-18 11:04:36 +02:00
|
|
|
'label_occ': __('Occupants:'),
|
|
|
|
'label_features': __('Features:'),
|
|
|
|
'label_requires_auth': __('Requires authentication'),
|
|
|
|
'label_hidden': __('Hidden'),
|
|
|
|
'label_requires_invite': __('Requires an invitation'),
|
|
|
|
'label_moderated': __('Moderated'),
|
|
|
|
'label_non_anon': __('Non-anonymous'),
|
|
|
|
'label_open_room': __('Open room'),
|
|
|
|
'label_permanent_room': __('Permanent room'),
|
|
|
|
'label_public': __('Public'),
|
|
|
|
'label_semi_anon': __('Semi-anonymous'),
|
|
|
|
'label_temp_room': __('Temporary room'),
|
|
|
|
'label_unmoderated': __('Unmoderated')
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
toggleRoomInfo (ev) {
|
2016-10-18 11:04:36 +02:00
|
|
|
/* Show/hide extra information about a room in the listing.
|
|
|
|
*/
|
2017-12-23 21:28:46 +01:00
|
|
|
const parent_el = ev.target.parentElement,
|
|
|
|
div_el = parent_el.querySelector('div.room-info');
|
|
|
|
if (div_el) {
|
|
|
|
u.slideIn(div_el).then(u.removeElement)
|
2016-02-16 08:46:47 +01:00
|
|
|
} else {
|
2017-12-23 21:28:46 +01:00
|
|
|
parent_el.insertAdjacentHTML('beforeend', tpl_spinner());
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.connection.disco.info(
|
2017-12-23 21:28:46 +01:00
|
|
|
ev.target.getAttribute('data-room-jid'),
|
|
|
|
null,
|
|
|
|
_.partial(this.insertRoomInfo, parent_el)
|
2016-10-18 11:04:36 +02:00
|
|
|
);
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
parseRoomDataFromEvent (ev) {
|
2017-12-23 21:28:46 +01:00
|
|
|
let name, jid;
|
2016-02-16 08:46:47 +01:00
|
|
|
if (ev.type === 'click') {
|
2017-12-23 21:28:46 +01:00
|
|
|
name = ev.target.textContent;
|
|
|
|
jid = ev.target.getAttribute('data-room-jid');
|
2016-02-16 08:46:47 +01:00
|
|
|
} else {
|
2017-12-23 21:28:46 +01:00
|
|
|
const name_el = this.el.querySelector('input.new-chatroom-name');
|
|
|
|
const server_el= this.el.querySelector('input.new-chatroom-server');
|
|
|
|
const server = server_el.value;
|
|
|
|
name = name_el.value.trim();
|
|
|
|
name_el.value = ''; // Clear the input
|
2016-02-16 08:46:47 +01:00
|
|
|
if (name && server) {
|
|
|
|
jid = Strophe.escapeNode(name.toLowerCase()) + '@' + server.toLowerCase();
|
2017-12-23 21:28:46 +01:00
|
|
|
name_el.classList.remove('error');
|
|
|
|
server_el.classList.remove('error');
|
2016-02-16 08:46:47 +01:00
|
|
|
this.model.save({muc_domain: server});
|
|
|
|
} else {
|
2017-12-23 21:28:46 +01:00
|
|
|
if (!name) { name_el.classList.add('error'); }
|
|
|
|
if (!server) { server_el.classList.add('error'); }
|
2016-02-16 08:46:47 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2017-06-15 16:22:49 +02:00
|
|
|
return {
|
2016-02-16 08:46:47 +01:00
|
|
|
'jid': jid,
|
|
|
|
'name': name || Strophe.unescapeNode(Strophe.getNodeFromJid(jid)),
|
2017-06-15 16:22:49 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
openChatRoom (ev) {
|
2017-06-15 16:22:49 +02:00
|
|
|
ev.preventDefault();
|
2017-08-08 16:27:07 +02:00
|
|
|
const data = this.parseRoomDataFromEvent(ev);
|
|
|
|
if (!_.isUndefined(data)) {
|
2017-10-31 22:07:40 +01:00
|
|
|
openChatRoom(data);
|
2017-08-08 16:27:07 +02:00
|
|
|
}
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
setDomain (ev) {
|
2016-02-16 08:46:47 +01:00
|
|
|
this.model.save({muc_domain: ev.target.value});
|
|
|
|
},
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
setNick (ev) {
|
2016-02-16 08:46:47 +01:00
|
|
|
this.model.save({nick: ev.target.value});
|
|
|
|
}
|
|
|
|
});
|
2016-12-04 10:43:39 +01:00
|
|
|
/************************ End of ChatRoomView **********************/
|
|
|
|
|
2016-02-16 08:46:47 +01:00
|
|
|
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.onDirectMUCInvitation = function (message) {
|
2016-12-04 10:43:39 +01:00
|
|
|
/* A direct MUC invitation to join a room has been received
|
|
|
|
* See XEP-0249: Direct MUC invitations.
|
|
|
|
*
|
|
|
|
* Parameters:
|
|
|
|
* (XMLElement) message: The message stanza containing the
|
|
|
|
* invitation.
|
|
|
|
*/
|
2017-12-23 21:28:46 +01:00
|
|
|
const x_el = message.querySelector('x[xmlns="jabber:x:conference"]'),
|
|
|
|
from = Strophe.getBareJidFromJid(message.getAttribute('from')),
|
|
|
|
room_jid = x_el.getAttribute('jid'),
|
|
|
|
reason = x_el.getAttribute('reason');
|
|
|
|
|
2017-07-10 17:46:22 +02:00
|
|
|
let contact = _converse.roster.get(from),
|
2016-03-22 08:49:25 +01:00
|
|
|
result;
|
|
|
|
|
2016-12-20 10:30:20 +01:00
|
|
|
if (_converse.auto_join_on_invite) {
|
2016-03-22 08:49:25 +01:00
|
|
|
result = true;
|
|
|
|
} else {
|
|
|
|
// Invite request might come from someone not your roster list
|
|
|
|
contact = contact? contact.get('fullname'): Strophe.getNodeFromJid(from);
|
|
|
|
if (!reason) {
|
|
|
|
result = confirm(
|
2017-08-23 11:57:17 +02:00
|
|
|
__("%1$s has invited you to join a chat room: %2$s", contact, room_jid)
|
2016-03-22 08:49:25 +01:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
result = confirm(
|
2017-08-23 11:57:17 +02:00
|
|
|
__('%1$s has invited you to join a chat room: %2$s, and left the following reason: "%3$s"',
|
2016-03-22 08:49:25 +01:00
|
|
|
contact, room_jid, reason)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (result === true) {
|
2017-10-31 22:07:40 +01:00
|
|
|
const chatroom = openChatRoom({
|
2016-03-22 08:49:25 +01:00
|
|
|
'jid': room_jid,
|
2017-12-23 21:28:46 +01:00
|
|
|
'password': x_el.getAttribute('password')
|
2016-03-22 08:49:25 +01:00
|
|
|
});
|
2017-07-21 15:05:22 +02:00
|
|
|
if (chatroom.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) {
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.chatboxviews.get(room_jid).join();
|
2016-03-22 08:49:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2016-04-05 13:23:16 +02:00
|
|
|
|
2016-12-20 10:30:20 +01:00
|
|
|
if (_converse.allow_muc_invitations) {
|
2017-06-12 20:30:58 +02:00
|
|
|
const registerDirectInvitationHandler = function () {
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.connection.addHandler(
|
2016-12-04 10:43:39 +01:00
|
|
|
function (message) {
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.onDirectMUCInvitation(message);
|
2016-12-04 10:43:39 +01:00
|
|
|
return true;
|
|
|
|
}, 'jabber:x:conference', 'message');
|
|
|
|
};
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.on('connected', registerDirectInvitationHandler);
|
|
|
|
_converse.on('reconnected', registerDirectInvitationHandler);
|
2016-12-04 10:43:39 +01:00
|
|
|
}
|
|
|
|
|
2017-06-12 20:30:58 +02:00
|
|
|
function autoJoinRooms () {
|
2016-12-04 10:43:39 +01:00
|
|
|
/* Automatically join chat rooms, based on the
|
|
|
|
* "auto_join_rooms" configuration setting, which is an array
|
|
|
|
* of strings (room JIDs) or objects (with room JID and other
|
|
|
|
* settings).
|
|
|
|
*/
|
2016-12-20 10:30:20 +01:00
|
|
|
_.each(_converse.auto_join_rooms, function (room) {
|
2017-01-26 15:49:02 +01:00
|
|
|
if (_.isString(room)) {
|
2017-02-02 19:29:38 +01:00
|
|
|
_converse.api.rooms.open(room);
|
2017-01-26 15:49:02 +01:00
|
|
|
} else if (_.isObject(room)) {
|
2017-02-02 19:29:38 +01:00
|
|
|
_converse.api.rooms.open(room.jid, room.nick);
|
2016-03-22 09:15:52 +01:00
|
|
|
} else {
|
2017-07-05 11:33:55 +02:00
|
|
|
_converse.log(
|
|
|
|
'Invalid room criteria specified for "auto_join_rooms"',
|
|
|
|
Strophe.LogLevel.ERROR);
|
2016-03-22 09:15:52 +01:00
|
|
|
}
|
|
|
|
});
|
2017-09-29 00:07:16 +02:00
|
|
|
_converse.emit('roomsAutoJoined');
|
2017-06-12 20:30:58 +02:00
|
|
|
}
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.on('chatBoxesFetched', autoJoinRooms);
|
2016-04-05 13:23:16 +02:00
|
|
|
|
2017-02-14 15:58:29 +01:00
|
|
|
_converse.getChatRoom = function (jid, attrs, fetcher) {
|
2016-06-16 17:20:11 +02:00
|
|
|
jid = jid.toLowerCase();
|
2017-12-15 21:51:07 +01:00
|
|
|
return _converse.getViewForChatBox(
|
|
|
|
fetcher(_.extend({
|
|
|
|
'id': jid,
|
|
|
|
'jid': jid,
|
|
|
|
'name': Strophe.unescapeNode(Strophe.getNodeFromJid(jid)),
|
|
|
|
'type': CHATROOMS_TYPE,
|
|
|
|
'box_id': b64_sha1(jid)
|
|
|
|
}, attrs),
|
|
|
|
attrs.bring_to_foreground
|
|
|
|
));
|
2016-06-16 17:20:11 +02:00
|
|
|
};
|
|
|
|
|
2016-03-22 08:49:25 +01:00
|
|
|
/* We extend the default converse.js API to add methods specific to MUC
|
|
|
|
* chat rooms.
|
|
|
|
*/
|
2016-12-20 10:30:20 +01:00
|
|
|
_.extend(_converse.api, {
|
2016-02-16 08:46:47 +01:00
|
|
|
'rooms': {
|
2017-07-10 17:46:22 +02:00
|
|
|
'close' (jids) {
|
2017-01-26 15:49:02 +01:00
|
|
|
if (_.isUndefined(jids)) {
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.chatboxviews.each(function (view) {
|
2016-06-03 10:22:31 +02:00
|
|
|
if (view.is_chatroom && view.model) {
|
|
|
|
view.close();
|
|
|
|
}
|
|
|
|
});
|
2017-01-26 15:49:02 +01:00
|
|
|
} else if (_.isString(jids)) {
|
2017-06-12 20:30:58 +02:00
|
|
|
const view = _converse.chatboxviews.get(jids);
|
2016-06-03 10:22:31 +02:00
|
|
|
if (view) { view.close(); }
|
|
|
|
} else {
|
2017-01-26 15:49:02 +01:00
|
|
|
_.each(jids, function (jid) {
|
2017-06-12 20:30:58 +02:00
|
|
|
const view = _converse.chatboxviews.get(jid);
|
2016-06-03 10:22:31 +02:00
|
|
|
if (view) { view.close(); }
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2017-07-10 17:46:22 +02:00
|
|
|
'open' (jids, attrs) {
|
2017-01-26 15:49:02 +01:00
|
|
|
if (_.isString(attrs)) {
|
2016-08-19 17:16:36 +02:00
|
|
|
attrs = {'nick': attrs};
|
2017-01-26 15:49:02 +01:00
|
|
|
} else if (_.isUndefined(attrs)) {
|
2016-08-19 17:16:36 +02:00
|
|
|
attrs = {};
|
|
|
|
}
|
2016-11-30 11:00:12 +01:00
|
|
|
if (_.isUndefined(attrs.maximize)) {
|
|
|
|
attrs.maximize = false;
|
|
|
|
}
|
2016-12-20 10:30:20 +01:00
|
|
|
if (!attrs.nick && _converse.muc_nickname_from_jid) {
|
|
|
|
attrs.nick = Strophe.getNodeFromJid(_converse.bare_jid);
|
2016-10-13 18:22:37 +02:00
|
|
|
}
|
2017-01-26 15:49:02 +01:00
|
|
|
if (_.isUndefined(jids)) {
|
2016-02-16 08:46:47 +01:00
|
|
|
throw new TypeError('rooms.open: You need to provide at least one JID');
|
2017-01-26 15:49:02 +01:00
|
|
|
} else if (_.isString(jids)) {
|
2017-10-31 22:07:40 +01:00
|
|
|
return _converse.getChatRoom(jids, attrs, openChatRoom);
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
2017-10-31 22:07:40 +01:00
|
|
|
return _.map(jids, _.partial(_converse.getChatRoom, _, attrs, openChatRoom));
|
2016-02-16 08:46:47 +01:00
|
|
|
},
|
2017-07-10 17:46:22 +02:00
|
|
|
'get' (jids, attrs, create) {
|
2017-01-26 15:49:02 +01:00
|
|
|
if (_.isString(attrs)) {
|
2016-08-19 17:16:36 +02:00
|
|
|
attrs = {'nick': attrs};
|
2017-01-26 15:49:02 +01:00
|
|
|
} else if (_.isUndefined(attrs)) {
|
2016-08-19 17:16:36 +02:00
|
|
|
attrs = {};
|
|
|
|
}
|
2017-01-26 15:49:02 +01:00
|
|
|
if (_.isUndefined(jids)) {
|
2017-06-12 20:30:58 +02:00
|
|
|
const result = [];
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.chatboxes.each(function (chatbox) {
|
2017-06-07 00:55:25 +02:00
|
|
|
if (chatbox.get('type') === CHATROOMS_TYPE) {
|
2017-02-14 15:58:29 +01:00
|
|
|
result.push(_converse.getViewForChatBox(chatbox));
|
2016-06-24 10:54:39 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return result;
|
|
|
|
}
|
2017-06-12 20:30:58 +02:00
|
|
|
const fetcher = _.partial(_converse.chatboxviews.getChatBox.bind(_converse.chatboxviews), _, create);
|
2016-08-19 17:16:36 +02:00
|
|
|
if (!attrs.nick) {
|
2016-12-20 10:30:20 +01:00
|
|
|
attrs.nick = Strophe.getNodeFromJid(_converse.bare_jid);
|
2016-06-16 17:20:11 +02:00
|
|
|
}
|
2017-01-26 15:49:02 +01:00
|
|
|
if (_.isString(jids)) {
|
2017-02-14 15:58:29 +01:00
|
|
|
return _converse.getChatRoom(jids, attrs, fetcher);
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
2017-02-14 15:58:29 +01:00
|
|
|
return _.map(jids, _.partial(_converse.getChatRoom, _, attrs, fetcher));
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2016-12-04 14:39:09 +01:00
|
|
|
|
2017-07-21 12:41:16 +02:00
|
|
|
/* Event handlers */
|
|
|
|
_converse.on('addClientFeatures', () => {
|
|
|
|
if (_converse.allow_muc) {
|
|
|
|
_converse.connection.disco.addFeature(Strophe.NS.MUC);
|
|
|
|
}
|
|
|
|
if (_converse.allow_muc_invitations) {
|
|
|
|
_converse.connection.disco.addFeature('jabber:x:conference'); // Invites
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
_converse.on('reconnected', function reconnectToChatRooms () {
|
2016-12-04 14:39:09 +01:00
|
|
|
/* Upon a reconnection event from converse, join again
|
|
|
|
* all the open chat rooms.
|
|
|
|
*/
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.chatboxviews.each(function (view) {
|
2017-06-07 00:55:25 +02:00
|
|
|
if (view.model.get('type') === CHATROOMS_TYPE) {
|
2017-07-21 15:05:22 +02:00
|
|
|
view.model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
|
2017-06-23 20:19:38 +02:00
|
|
|
view.registerHandlers();
|
2016-12-05 07:03:44 +01:00
|
|
|
view.join();
|
2017-06-23 20:19:38 +02:00
|
|
|
view.fetchMessages();
|
2016-12-04 14:39:09 +01:00
|
|
|
}
|
|
|
|
});
|
2017-07-21 12:41:16 +02:00
|
|
|
});
|
2016-12-04 14:39:09 +01:00
|
|
|
|
2017-11-10 15:53:59 +01:00
|
|
|
|
|
|
|
function setMUCDomainFromDisco (controlboxview) {
|
|
|
|
/* Check whether service discovery for the user's domain
|
|
|
|
* returned MUC information and use that to automatically
|
|
|
|
* set the MUC domain for the "Rooms" panel of the controlbox.
|
|
|
|
*/
|
|
|
|
function featureAdded (feature) {
|
|
|
|
if ((feature.get('var') === Strophe.NS.MUC)) {
|
|
|
|
setMUCDomain(feature.get('from'), controlboxview);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_converse.api.waitUntil('discoInitialized').then(() => {
|
|
|
|
_converse.api.listen.on('serviceDiscovered', featureAdded);
|
|
|
|
// Features could have been added before the controlbox was
|
|
|
|
// initialized. We're only interested in MUC
|
|
|
|
_converse.disco_entities.each((entity) => {
|
|
|
|
const feature = entity.features.findWhere({'var': Strophe.NS.MUC });
|
|
|
|
if (feature) {
|
|
|
|
featureAdded(feature)
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
|
|
|
|
}
|
|
|
|
|
|
|
|
function setMUCDomain (domain, controlboxview) {
|
|
|
|
_converse.muc_domain = domain;
|
|
|
|
controlboxview.roomspanel.model.save({'muc_domain': domain});
|
|
|
|
}
|
|
|
|
|
|
|
|
function fetchAndSetMUCDomain (controlboxview) {
|
|
|
|
if (controlboxview.model.get('connected')) {
|
|
|
|
if (!controlboxview.roomspanel.model.get('muc_domain')) {
|
|
|
|
if (_.isUndefined(_converse.muc_domain)) {
|
|
|
|
setMUCDomainFromDisco(controlboxview);
|
|
|
|
} else {
|
|
|
|
setMUCDomain(_converse.muc_domain, controlboxview);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_converse.on('controlboxInitialized', function (view) {
|
|
|
|
if (!_converse.allow_muc) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
fetchAndSetMUCDomain(view);
|
|
|
|
view.model.on('change:connected', _.partial(fetchAndSetMUCDomain, view));
|
|
|
|
});
|
|
|
|
|
2017-06-12 20:30:58 +02:00
|
|
|
function disconnectChatRooms () {
|
2016-12-04 14:39:09 +01:00
|
|
|
/* When disconnecting, or reconnecting, mark all chat rooms as
|
|
|
|
* disconnected, so that they will be properly entered again
|
|
|
|
* when fetched from session storage.
|
|
|
|
*/
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.chatboxes.each(function (model) {
|
2017-06-07 00:55:25 +02:00
|
|
|
if (model.get('type') === CHATROOMS_TYPE) {
|
2017-07-21 15:05:22 +02:00
|
|
|
model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
|
2016-12-04 14:39:09 +01:00
|
|
|
}
|
|
|
|
});
|
2017-06-12 20:30:58 +02:00
|
|
|
}
|
2016-12-20 10:30:20 +01:00
|
|
|
_converse.on('reconnecting', disconnectChatRooms);
|
|
|
|
_converse.on('disconnecting', disconnectChatRooms);
|
2016-02-16 08:46:47 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}));
|